Debugging & troubleshooting tutorial: backtrace + addr2line


Planning to write a series of posts about debugging & trouble shooting tricks. And I’d like to make backtrace as a start. As a typical programmer(AKA nerd), I’d like to jump to the topic directly before run into blah-blah.

Everyone knows, bug is free, so it may be at everywhere, to check out your free gift(s) sending to your clients, you may consider backtrace.

It’s a great way for trouble shooting. It’ll be terribly useful when your program run into core dump.

And it’s really simple, all you need to do is:
1. Register SIGSEGV and use signal(SIGSEGV, &your_function); to catch the signal.
2. Put the code to the head of your main function.

Here I copied a code snippet for you:

#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <execinfo.h>
#include <signal.h>

void dump(int signo)
{
 void *buffer[30] = {0};
 size_t size;
 char **strings = NULL;
 size_t i = 0;

 size = backtrace(buffer, 30);
 fprintf(stdout, "Obtained %zd stack frames.nm\n", size);
 strings = backtrace_symbols(buffer, size);
 if (strings == NULL)
 {
  perror("backtrace_symbols.");
  exit(EXIT_FAILURE);
 }
 
 for (i = 0; i < size; i++)
 {
  fprintf(stdout, "%s\n", strings[i]);
 }
 free(strings);
 strings = NULL;
 exit(0);
}

void func_c()
{
  //This is the "free gift" you send to your clients
 *((volatile char *)0x0) = 0x9999;
}

void func_b()
{
 func_c();
}

void func_a()
{
 func_b();
}

int main(int argc, const char *argv[])
{
 if (signal(SIGSEGV, dump) == SIG_ERR)
  perror("can't catch SIGSEGV");
 func_a();
 return 0;
}

Save it with filename like backtrace1.c, and compile it with command:
gcc backtrace1.c -o test

gcc -g -rdynamic backtrace1.c -o test

Run the newly compiled program

./test

When your free gift shows up, and caused a segmentation fault(core dump), you’ll see something like this:

=================== call trace ======================
Obtained 7 stack frames.nm
./test(dump+0x45) [0x80487a9]
[0x736400]
./test(func_b+0x8) [0x804886c]
./test(func_a+0x8) [0x8048876]
./test(main+0x33) [0x80488ab]
/lib/libc.so.6(__libc_start_main+0xe6) [0xbbccc6]
./test() [0x80486d1]

Dont panic. Dont you like the lovely address numbers? no? Okay,  a few more steps you’ll get the truth:

1. Use objdump to disassemble the executable file

objdump -d test > test.s

2. Check the assemble codes for the displayed addresss: 0x804886c

08048864 <func_b>:
 8048864:       55                      push   %ebp
 8048865:       89 e5                   mov    %esp,%ebp
 8048867:       e8 eb ff ff ff          call   8048857 <func_c>
 804886c:       5d                      pop    %ebp
 804886d:       c3                      ret

As you can see, the previous step was “call 8048857 <func_c>“, we can make the conclusion that it was <func_c> where went wrong, although address 804886c did not locate direct to the error code.

3.  A more effecial tool than disassembling the objects: addr2line

You can use addr2line to convert it code line.

addr2line 0x804886C -e test -f -C

Truth revealed.

Leave a comment

Your email address will not be published. Required fields are marked *