[Date Prev][Date Next] [Thread Prev][Thread Next] [Date Index] [Thread Index]

Bug#735090: libc6: makecontext leads to partially wrong backtrace on i386



Package: libc6
Severity: normal

Dear Maintainer,

On i386, using backtrace(3) after makecontext(3) gives partially wrong
result.  Valgrind complains about "Invalid read" and that "Address is on
thread 1's stack".

In fact, backtrace() goes too far when walking up the stack.  The
attached code illustrates this.  Use it on an i386 system, or build it
with gcc's option "-m32" on amd64.

The code uses backtrace() and backtrace_symbols_fd() to print the stack
trace after a call to makecontext().  The content of the stack is
arranged so as to make the wrongness of the output clear.  In this
example, the stack trace is potentially infinite (only limited by the
parameter "size" for backtrace()).

A sample run gives the following output:
,----
| $ ./mk
| Try without fix...
| >>> dump_backtrace:
| ./mk(dump_backtrace+0x19)[0x80489e5]
| /lib/i386-linux-gnu/i686/cmov/libc.so.6(makecontext+0x4b)[0xf75e7a6b]
| [0xffd3014c]
| [0xffd2fbc8]
| [0xffd2fbc8]
| [0xffd2fbc8]
| [0xffd2fbc8]
| [0xffd2fbc8]
| [0xffd2fbc8]
| [0xffd2fbc8]
| [0xffd2fbc8]
| [0xffd2fbc8]
| [0xffd2fbc8]
| [0xffd2fbc8]
| [0xffd2fbc8]
| [0xffd2fbc8]
| <<<
| Done.
`----

The problem can also be seen with gdb.  Set a breakpoint on the
backtrace function, and ask gdb to display a backtrace on breakpoint.
Look at the last message (corrupt stack?):
,----
| $ gdb --quiet ./mk
| Reading symbols from /[...]/mk...done.
| (gdb) br backtrace
| Breakpoint 1 at 0x8048830
| (gdb) run
| Starting program: /[...]/mk 
| Try without fix...
| 
| Breakpoint 1, *__GI___backtrace (array=0x804df3c, size=16) at
| ../sysdeps/i386/backtrace.c:116
| 116     ../sysdeps/i386/backtrace.c: Aucun fichier ou dossier de ce
| type.
| (gdb) backtrace 
| #0  *__GI___backtrace (array=0x804df3c, size=16) at
| ../sysdeps/i386/backtrace.c:116
| #1  0x080489e5 in dump_backtrace () at mk.c:24
| #2  0xf7e99a6b in makecontext () at
| ../sysdeps/unix/sysv/linux/i386/makecontext.S:88
| #3  0xffffd91c in ?? ()
| #4  0xffffd398 in ?? ()
| Backtrace stopped: previous frame identical to this frame (corrupt
| stack?)
`----

In both cases, the backtrace is expected to stop at the call to
makecontext().

Setting the EBP register to NULL in the context given to makecontext
seems to improve the situation (line 49 in the example), as can be seen
by running the same example with a dummy parameter on the command line.

Maybe this should be done by makecontext itself?

Regards,

Arnaud Giersch

/* Build with: gcc -g -rdynamic -m32 -o mk mk.c
 * Run with or without argument on the command line
 */

#define _GNU_SOURCE

#include <execinfo.h>
#include <stdio.h>
#include <stdlib.h>
#include <ucontext.h>

#define DEBUG 0

#define BT_SIZE 16
#define STACK_SIZE 16384
#define PAD_SIZE 1024
#define MAX_REC 16

#define ERROR(s) do { perror(s); exit(EXIT_FAILURE); } while (0)

void dump_backtrace(void)
{
    void *bt[BT_SIZE];
    int n = backtrace(bt, BT_SIZE);
    printf(">>> dump_backtrace:\n");
    fflush(stdout);
    backtrace_symbols_fd(bt, n, fileno(stdout));
    printf("<<<\n");
}

greg_t setup_context(ucontext_t *ctx, ucontext_t *link,
                     void *stack, size_t size,
                     int fix, int depth)
{
    unsigned long res = (unsigned long)-1 / 3; /* 0x555...555 */
    if (depth > 0) {
        res = setup_context(ctx, link, stack, size, fix, depth - 1);
    } else {
        if (getcontext(ctx) == -1)
            ERROR("getcontext");
        ctx->uc_stack.ss_sp = stack;
        ctx->uc_stack.ss_size = size;
        ctx->uc_link = link;
        makecontext(ctx, dump_backtrace, 0);
#if defined(REG_EBP)
        res = ctx->uc_mcontext.gregs[REG_EBP];
        if (fix) {
            printf("Fixing EBP (%lx).\n", (long)res);
            ctx->uc_mcontext.gregs[REG_EBP] = 0;
        }
#else
        if (fix)
            printf("Warning: no fix available.\n");
#endif
    }
    if (DEBUG)
        fprintf(stderr, "### %p: %lx\n", &res, (long)res);
    return res;
}

void fill_stack(greg_t x)
{
    greg_t padding[PAD_SIZE];
    size_t i;
    if (DEBUG)
        fprintf(stderr, "### %p..%p: %lx\n",
                padding, padding + PAD_SIZE, (long)x);
    for (i = 0; i < PAD_SIZE; i++)
        padding[i] = x;
}

char stack1[STACK_SIZE];

int main(int argc, char *argv[])
{
    ucontext_t ctx0, ctx1;
    int fix = argc > 1 || argv[1];
    greg_t x;

    printf("Try %s fix...\n", fix ? "with" : "without");
    x = setup_context(&ctx1, &ctx0, stack1, STACK_SIZE, fix, MAX_REC);
    fill_stack(x);
    if (swapcontext(&ctx0, &ctx1) == -1)
        ERROR("swapcontext(1)");

    printf("Done.\n");
    return EXIT_SUCCESS;
}
-- System Information:
Debian Release: 7.3
  APT prefers proposed-updates
  APT policy: (500, 'proposed-updates'), (500, 'stable'), (50, 'unstable'), (40, 'experimental')
Architecture: amd64 (x86_64)
Foreign Architectures: i386

Kernel: Linux 3.2.0-4-amd64 (SMP w/8 CPU cores)
Locale: LANG=fr_FR.UTF-8, LC_CTYPE=fr_FR.UTF-8 (charmap=UTF-8)
Shell: /bin/sh linked to /bin/dash

Reply to: