GCC damangling stack traces

来源:互联网 发布:源码授权域名绑定ip 编辑:程序博客网 时间:2024/06/06 00:08

GCC damangling stack traces

Tons of useful info:
this URL is no useful http://www.acsu.buffalo.edu/~charngda/backtrace.html
BTW:I’m afraid to lose these pages,so i’ve copied these pages at here

CALL STACK TRACE GENERATION

Call stack trace (“backtrace”) is very useful in debugging. Here are several ways to retrieve the backtrace in a user program.
(The Contents are mostly from here1 and here2 )

I copied here1 as follows

here1:Pre-mortem Backtracing

here1:Pre-mortem Backtracing

A backtrace is often the first step in debugging a problem. Generating a backtrace is generally thought of as a function of the debugger, on a core file after a process has died. However it is sometimes quite useful to generate a live backtrace while a process runs. For example, crashing the process in the field may not be acceptable if a problem is survivable. Logging a backtrace and other information can provide enough to locate the root cause, without having to trigger any customer downtime.

在debugging一个问题的时候backtrace通常是第一步。当一个核心文件的进程死了后,构造一个backtrace通常是调试者认为的比较好的办法,然而,有时候在程序执行的过程中产生一个动态的backtrace是一个更有效的办法。例如,在一个field,crashing进程,如果一个问题是非致命的那可能也是not acceptable。logging 一个backtrace 以及其他信息能提供足够多的信息来定位主要的问题,并且不需要触动任何customer downtime

gcc backtrace support

The simplest way to get a crude backtrace is the __builtin_return_address() macro supplied by gcc. You provide the frame number you want to retrieve, and get the return address for that stack frame:

stack

用gcc宏给出的方法是最简单的方法之一,通过给出想要修正的frame number,然后返回栈帧的地址

void do_backtrace2(){    void *pc0 = __builtin_return_address(0);    void *pc1 = __builtin_return_address(1);    void *pc2 = __builtin_return_address(2);    void *pc3 = __builtin_return_address(3);    printf("Frame 0: PC=%p\n", pc0);    printf("Frame 1: PC=%p\n", pc1);    printf("Frame 2: PC=%p\n", pc2);    printf("Frame 3: PC=%p\n", pc3);}

this code will produce the following output:

Frame 0: PC=0x80483caFrame 1: PC=0x80483e1Frame 2: PC=0x62079dFrame 3: PC=0x80482b9

__builtin_return_address() has significant limitations. It constructs code at compile time to walk back through the stack frames. That means you cannot use a variable in a loop, you can only use integer constants like the 0,1,2,3 shown above. Also on some architectures, including my beloved MIPS , only __builtin_return_address(0) works. MIPS has no frame pointer, making it difficult to walk back up the stack. Frame 0 can use the return address register directly.

总结来说上面那种方法有诸多限制,只能通过frame number来查看

>

glibc’s backtrace()

>
glibc contains a simple backtrace function, which is somewhat more powerful than __builtin_return_address(). The backtrace() call populates an array with the program counter of each calling function, while a separate backtrace_symbols() call can look up the symbolic names for each address:
>
glibc有一个简单的backtrace函数,它比第一种更强大,backtrace()函数调用在程序中的一个数组,通过backtrace_symbols()分开,然后看哥哥地址的标识名字。

“`

include

define BACKTRACE_SIZ 64

void do_backtrace()
{
void *array[BACKTRACE_SIZ];
size_t size, i;
char **strings;
>
size = backtrace(array, BACKTRACE_SIZ);
strings = backtrace_symbols(array, size);
>
for (i = 0; i < size; i++) {
printf(“%p : %s\n”, array[i], strings[i]);
}
>
free(strings); // malloced by backtrace_symbols
}

The output shows the backtrace with the address of each function call site:

gcc -g -o backtrace ./backtrace.c

./backtrace

0x8048422 : ./backtrace(backtrace_symbols+0xd6) [0x8048422]
0x80484be : ./backtrace(backtrace_symbols+0x172) [0x80484be]
0x80484d5 : ./backtrace(backtrace_symbols+0x189) [0x80484d5]
0x071479d : /lib/tls/libc.so.6(__libc_start_main+0xed) [0x71479d]
0x804837d : ./backtrace(backtrace_symbols+0x31) [0x804837d]

To get useful symbolic names, the `-rdynamic` option must be passed to the linker:

gcc -g -rdynamic -o backtrace ./backtrace.c

./backtrace

0x804874a : ./backtrace(do_backtrace+0x1a) [0x804874a]
0x80487e6 : ./backtrace(foo1+0xb) [0x80487e6]
0x80487fd : ./backtrace(main+0x15) [0x80487fd]
0x012679d : /lib/tls/libc.so.6(__libc_start_main+0xed) [0x12679d]
0x80486a5 : ./backtrace(backtrace_symbols+0x31) [0x80486a5]

``
There is also a
backtrace_symbols_fd()` function, which nicely prints the output to a file descriptor without having to malloc a table of strings. If thats all you’re trying to do, it is a simpler API.

>
As an aside: notice how the address of __libc_start_main varies in the examples above, 0x62079d versus 0x71479d versus 0x12679d? That is address space randomization in action. libc is mapped at a randomized base address every time a binary is started. The offset of __libc_start_main within the page is a constant 0x79d, but the upper bits of the address will vary from one run to the next. This helps prevent buffer overflow and other code injection attacks, by randomizing the address of library routines.

>

libunwind

libunwind is a library for extracting call chain information. It supports many different CPU architectures. Here is an example of using libunwind to accomplish a similar result as glibc’s backtrace() function:

“`

include

include

here2:Stack unwinding (stack trace) with GCC

here2:Stack unwinding (stack trace) with GCC

I always liked the nice stack trace you get in some languages like java, c#, etc, with a nice clean trace of where the issue happent. Can we have this in C/C++ with gcc? Of course we can.

Let’s use the following code in which we try to display our stack trace (assumes you are building it with -g to enable debug symbols):

#include void dummy_function2(){    // here will call our back_trace function to    // display the back_trace}void dummy_function1(){    dummy_function2 ();}int main(int argc, char **argv){    dummy_function1 ();}

On some platforms, gcc has a built-in function called __builtin_return_address. The info file says something like:

__builtin_return_address (LEVEL)’This function returns the return address of the current function,or of one of its callers. The LEVEL argument is number of framesto scan up the call stack. A value of `0′ yields the returnaddress of the current function, a value of `1′ yields the returnaddress of the caller of the current function, and so forth.

The availability and useability of this buil-in depends on platform, compiler,etc.
Guarded with this knowledge , we can build our first (and crude) backtrace:

void show_backtrace(){    // get current address    void* p = __builtin_return_address(0);    printf("0x%x\n", p);    // get callee address    p = __builtin_return_address(1);    printf("0x%x\n", p);    // we cannot get more addresses as we don't have any    // information about how many leves of calls we have}

running this will display something like:

$ ./a.out0x4007be0x4007ce

As we expected, it display the last two functions called before calling our function to display the backtrace. We could get more in-detail backtrace if we know how many levels to ask, but unfortunatelly we don’t know from where this function will be called. In any case it will be called at least from main() which is called from libc initialization function, so getting back two levels of stack trace should be safe. It display the two return addresses, altough we don’t have enough information yet for displaying a nice file:line no pair, but hey,this is more than nothing. And anyway, we’ll fix that later.

Investigating a bit more in detail, seems like the standard library offers a function backtrace() and two other helper functions to allow backtrace. One backtrace using this function could look similar to:

“`

include

>
void show_backtrace()
{
void *array[10];
size_t size;
char **strings;
int i;
>
size = backtrace (array, 10);
strings = backtrace_symbols ((void const )array, size);
>
for (i = 0; i < size; i++)
{
printf (“%s\n”, strings[i]);
}
>
free (strings);
}

running the code we get a listing like the following:    $ ./a.out    ./a.out() [0x4006bd]    ./a.out() [0x40071e]    ./a.out() [0x40072e]    ./a.out() [0x400749]    /lib/libc.so.6(__libc_start_main+0xfd) [0x7f542a471c4d]    ./a.out() [0x4005e9]It is more than we get from the previous attempt, as we get now the entire stack trace, and we don’t have to be carefull about how many levels we unwind.  现在这个比前一个我们能得到更多信息了,我们能得到整个栈的trace,且我们不需要担心我们unwind多少层Still, is far from a nice file:line style of stack trace provided by the other languages. Time to get serious about. A bit more search on internet reveals a nice library called unwind. (can be found here). From the homepage we get the following:_The primary goal of this project is to define a portable and efficient C programming interface (API) to determine the call-chain of a program. […]_Sounds good. Let’s have an another approach of stack trace using this time the unwind library:

include

>
void show_backtrace (void)
{
char name[256];
unw_cursor_t cursor; unw_context_t uc;
unw_word_t ip, sp, offp;
>
unw_getcontext (&uc);
unw_init_local (&cursor, &uc);
>
while (unw_step(&cursor) > 0)
{
char file[256];
int line = 0;
>
name[0] = ‘\0’;
unw_get_proc_name (&cursor, name, 256, &offp);
unw_get_reg (&cursor, UNW_REG_IP, &ip);
unw_get_reg (&cursor, UNW_REG_SP, &sp);
>
printf (“%s ip = %lx, sp = %lx\n”, name, (long) ip, (long) sp);
}
}

Running the code with this version of stack trace, we get an output which resembles to following:

$ ./a.out
dummy_function2 ip = 400b05, sp = 7fff56e21eb0
dummy_function1 ip = 400b15, sp = 7fff56e21ec0
main ip = 400b30, sp = 7fff56e21ed0
__libc_start_main ip = 7fd13578bc4d, sp = 7fff56e21ef0
_start ip = 400869, sp = 7fff56e21fb0

Now this looks quite useable, we have the function names, we have the instruction pointer address.At this moment, half of the problem is solved, we have the list of functions through wich went the call, we have the addresses from where the call was made. All we need now is a way to convert this into a nice file:line type of strack trace. And from here things get dirty.到这个时候,问题解决了一半了,我们列出了函数的调用的地址,并且有从哪调用的地址。我们现在需要的是一个方法来转换这个地址到一个nice file:。。。。One way would be to use the readelf helper application, to retrieve the debug informations from the executable file and transform it in something which can be used easily:有一个方法:用readelf帮助函数,用它来帮助修复从执行文件和转换debuf信息

$ readelf –debug-dump=decodedline a.out

Decoded dump of debug contents of section .debug_line:

CU: /usr/lib/gcc/x86_64-linux-gnu/4.4.3/include/unwind_test.c:
File name Line number Starting address
unwind_test.c 79 0x400924

unwind_test.c 85 0x40092c
unwind_test.c 86 0x400940
unwind_test.c 88 0x400955

This starts to look interesting. Before the stack dump (maybe at application startup), we can execute the above command with popen and load the list of file:line:address values, then when while displaying the stack trace, we can lookup int this list to display a nice stack trace with function in file:line format. Still, there is lot of info to be loaded, specially if our application is quite large.There is an easier approach. There is a little known utility called addr2line, who has exactly the functionality we are looking for: if you give him and executable file and a address, it will try to transform it into function:file:line trio.

addr2line translates addresses into file names and line numbers.
Given an address in an executable or an offset in a section of a relocatable
object, it uses the debugging information to figure out which file name
and line number are associated with it.

This will complete nicelly our stack unwinding. First, we need a way from getting the trio from addr2line. With the parameters as specifed bellow, the helper program will output first the name of function, or ?? if cannot resolve it and on the second line will display the file and line information in filename:lineno format (or ??:0 if cannot find the info). For this example we skip over the function name as we already have it from libunwind:

int getFileAndLine (unw_word_t addr, char *file, size_t flen, int *line)
{
static char buf[256];
char *p;

// prepare command to be executed// our program need to be passed after the -e parametersprintf (buf, "/usr/bin/addr2line -C -e ./a.out -f -i %lx", addr);FILE* f = popen (buf, "r");if (f == NULL){    perror (buf);    return 0;}// get function namefgets (buf, 256, f);// get file and linefgets (buf, 256, f);if (buf[0] != '?'){    int l;    char *p = buf;    // file name is until ':'    while (*p != ':')    {        p++;    }    *p++ = 0;    // after file name follows line number    strcpy (file , buf);    sscanf (p,"%d", line);}else{    strcpy (file,"unkown");    *line = 0;}pclose(f);

}

Now we need to modify our libunwind based stack winding to call this function and display the stack trace:

void show_backtrace (void)
{
char name[256];
unw_cursor_t cursor; unw_context_t uc;
unw_word_t ip, sp, offp;

unw_getcontext(&uc);unw_init_local(&cursor, &uc);while (unw_step(&cursor) > 0){    char file[256];    int line = 0;    name[0] = '\0';    unw_get_proc_name(&cursor, name, 256, &offp);    unw_get_reg(&cursor, UNW_REG_IP, &ip);    unw_get_reg(&cursor, UNW_REG_SP, &sp);    //printf ("%s ip = %lx, sp = %lx\n", name, (long) ip, (long) sp);    getFileAndLine((long)ip, file, 256, &line);    printf("%s in file %s line %d\n", name, file, line);}

}

Building again the application and running it, we’ll get the long awaited result:

$ ./a.out
dummy_function2 in file /[some path]/unwind_test.c line 141
dummy_function1 in file /[some path]/unwind_test.c line 146
main in file /[some path]/unwind_test.c line 151
__libc_start_main in file unkown line 0
_start in file unkown line 0
“`

Easiest approach: __builtin_return_address

GCC has a built-in function to retrieve call stack trace’s addresses. For example

void do_backtrace(){    printf("Frame 0: PC=%p\n", __builtin_return_address(0));    printf("Frame 1: PC=%p\n", __builtin_return_address(1));    printf("Frame 2: PC=%p\n", __builtin_return_address(2));    printf("Frame 3: PC=%p\n", __builtin_return_address(3));}

__builtin_return_address(0) is always current function’s address. On the other hand, __builtin_return_address(1), __builtin_return_address(2), … may not be available on all platforms.

What to do with these addresses ?

ddresses can be mapped to the binary executable or dynamic link libraries. This is always doable even if the binary executable has been stripped off the symbols.

To see the mapping during runtime, parse the following plain-text file on the /proc file system:

/proc/self/maps

A utility called pmap can do the same.

If the address belongs to a DLL, it is possible to obtain the function name since DLLs are usually not stripped.

Addresses can be mapped to function names. Even if a binary executable is compiled without -g option, it still contains function names. To see the function names in the binary executable, do

nm -C -n a.out

To see the function names programmatically in the binary executable during run-time, read later paragraphs.

Addresses can be mapped to line numbers in source files. This extra information (in DWARF format) is added to the binary executable if it is compiled with -g option. To see line numbers in source files, do

objdump -WL a.outobjdump --dwarf=decodedline a.out

or even better:

addr2line -ifC a.out 0x123456

where 0x123456 is the address of interest. To see line numbers in source files programmatically during run-time, read later paragraphs.
0x1234567随便自己写,在运行时候,可以看到原文件的行号

Approach 2: backtrace

backtrace and backtrace_symbols are functions in Glibc. To use backtrace_symbols, one must compile the program with -rdynamic option.

One does not need to compile with -g option (but -rdynamic option cannot be used together with -static option) since backtrace_symbols cannot retrieve line number information. Actually, one can even strip off the symbols, and the backtrace_symbols will still work. This is because when -rdynamic is used, all global symbols are also stored in .dynsym section in the ELF-formatted executable binary, and this section cannot be stripped away. (To see the content of .dynsym section, use readelf -s a.out command, or readelf -p .dynstr a.out command.)

`backtrace_symbols` obtains symbol information from .dynsym section.

(The main purpose of .dynsym section is for dynamic link libraries to expose their symbols so the runtime linker ld.so can find them.)
.dynsym选项是用来动态链接库函数来暴露他们的标识,然后再运行时候,ld.so能找到他们。

Here is the sample program:

#include <execinfo.h>void do_backtrace(){    #define BACKTRACE_SIZ 100    void    *array[BACKTRACE_SIZ];    size_t  size, i;    char    **strings;    size = backtrace(array, BACKTRACE_SIZ);    strings = backtrace_symbols(array, size);    for (i = 0; i < size; ++i) {        printf("%p : %s\n", array[i], strings[i]);    }    free(strings);}

local sample program:

#include <execinfo.h>#include <stdio.h>#include <stdlib.h>/* Obtain a backtrace and print it to stdout. */voidprint_trace (void){  void *array[10];  size_t size;  char **strings;  size_t i;  size = backtrace (array, 10);  strings = backtrace_symbols (array, size);  printf ("Obtained %zd stack frames.\n", size);  for (i = 0; i < size; i++)     printf ("%s\n", strings[i]);  free (strings);}/* A dummy function to make the backtrace more interesting. */voiddummy_function (void){  print_trace ();}intmain (void){  dummy_function ();  return 0;}

For C++ programs, to get demangled names, use abi::__cxa_demangle (include the header cxxabi.h)

Approach 3: Improved backtrace

The backtrace_symbols in Glibc uses dladdr to obtain function names, but it cannot retrieve line numbers. Jeff Muizelaar has an improved version here which can do line numbers.

If the user program is compiled without any special command-line options, then one can obtain function names (of course, provided the binary executable is not stripped.) Better yet, -rdynamic compiler option is not needed.

If the user program is compiled with -g option, one can obtain both line numbers and function names.

Note that to compile Jeff Muizelaar’s backtrace_symbols implementation, make sure the following two macros are defined and appears as the first two lines of a user program (they must precede before all #include …):

#define __USE_GNU#define _GNU_SOURCE  

and one needs Binary File Descriptor (BFD) library, which is now part of GNU binutils when linking Jeff’s code to the user program.

Approach 4: libunwind

libunwinddoes pretty much what the original backtrace/backtrace_symbols do. Its main purpose, however, is to unwind the stack programmatically (even more powerful than setjmp/longjmp pair) through unw_step and unw_resume calls. One can also peek and modify the saved register values on stack via unw_get_reg, unw_get_freg, unw_set_reg, and unw_set_freg calls.

If one just wants to retrieve the backtrace, use the following code:

#include <libunwind.h>void do_backtrace(){    unw_cursor_t    cursor;    unw_context_t   context;    unw_getcontext(&context);    unw_init_local(&cursor, &context);    while (unw_step(&cursor) > 0) {        unw_word_t  offset, pc;        char        fname[64];        unw_get_reg(&cursor, UNW_REG_IP, &pc);        fname[0] = '\0';        (void) unw_get_proc_name(&cursor, fname, sizeof(fname), &offset);        printf ("%p : (%s+0x%x) [%p]\n", pc, fname, offset, pc);    }}

and linked the user program with -lunwind -lunwind-x86_64.

There is no need to compile the user program with -g option.

0 0
原创粉丝点击