您的位置:首页 > 其它

GCC damangling stack traces

2015-10-30 19:06 393 查看

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:



用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=0x80483ca
Frame 1: PC=0x80483e1
Frame 2: PC=0x62079d
Frame 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 frames
to scan up the call stack. A value of `0′ yields the return
address of the current function, a value of `1′ yields the return
address 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.out
0x4007be
0x4007ce


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 parameter
sprintf (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 name
fgets (buf, 256, f);

// get file and line
fgets (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.out
objdump --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. */
void
print_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. */
void
dummy_function (void)
{
print_trace ();
}

int
main (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.
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  gcc stacktrace backtrace