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.

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.

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 --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
  1. Nice. See also http://www.pixelbeat.org/libs/trace.h
    addr2line is mentioned there. You may also want to integrate c++filt?

    Also see the more recently available, libsegfault: http://blog.andrew.net.au/2007/08/15

  2. Followed here from reddit.

    I was pretty skeptical of this, but I’ll be damned if it doesn’t work. I checked it w/ libunwind v0.99 on Ubuntu 10.04 w/ gcc 4.4.3. The full example source is on pastebin, and the test results are at the bottom of the display.

    http://pastebin.com/0JW1Kh9R

    It’s not exactly python’s traceback module, but that’s pretty freaking awesome. Cool idea. Some possible ideas for improvement…

    1: Can we do the same thing, but store the debug symbol map in a separate file for addr2line to use? Being able to reduce the memory bloat from the debug symbols would be nice (tho I don’t think just having the symbols there hurts speed)

    2: I’d like to try and hack in addr2line’s functionality natively so we can get rid of the external calls, so you could generate a speedy stacktrace at runtime and resume running after the error.

    But still, cool tip!

  3. addr2line’s functionality can be replaced with help of lib bfd, which has support for working with debug symbols and debug info. addr2line itself uses it. I might try to write at some point later a handy function which will get the file:line from address, using the lib bfd (if there isn’t one already)

  4. GR8 insight
    I also found this link:
    http://www.questionscompiled.com/answer.jsp?qid=4&technology=cpp

    on stack winding and unwinding in a c++ program with help of gdb showing stack frames which are created.

  5. Stack unwinding (stack trace) with GCC | Developer I/O - pingback on February 15, 2013 at 4:53 pm
  6. Brilliant post, highly appreciated thank you!

    Just fyi, I was getting linking errors like that: (compiled with -lunwind)
    main.cc:(.text+0x155): undefined reference to `_Ux86_64_init_local’

    Putting #define UNW_LOCAL_ONLY before the libunwind.h include fixed it.

  7. Thanks for the info, Apoc.

Leave a Comment