I wanted to be able to show some useful debugging information when my C program hits an unrecoverable error (or an error it's not worth trying to recover from in this case, like out of memory). There's no way to do it with what's in standard C, but there is a function for it in glibc called backtrace(3).
Here's a super simple function I wrote to do it, something I call after writing an error message to stderr:
void abort_with_backtrace (void) {
void *buffer[10];
int size = backtrace(buffer, 10);
backtrace_symbols_fd(buffer, size, STDERR_FILENO);
abort();
}
That gives me up to ten levels of call stack, something like this when compiled with debugging symbols:
./foo[0x41a346] ./foo(foo_chkstruct+0xc2)[0x41a426] ./foo[0x4121f4] ./foo[0x4122ea] ./foo(foo_munge_ast+0x18)[0x413153] ./foo(main+0x139)[0x4052d7] /lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf0)[0x4e5a830] ./foo(_start+0x29)[0x404be9]
Aside from compiling with the -g
option to get debugging symbols, you also have
to link with the -rdynamic
option for some reason.
Even then, as you can see above, it's not able to give you the names or location of functions
for all the stack frames. In particular it only shows a hex address for static
functions.
You can usually recover that information after the fact using the addr2line
program
from binutils, but it's a bit awkward:
$ addr2line -e foo -p -f 0x41a346 abort_with_backtrace at /home/geoff/work/foo/csrc/util.c:35
Hmmm, this whole article may just indicate that I shouldn't be trying to write a compiler in C.