backtrace / without debugger
You may not always have gdb(1)
at hand. Here are a couple of other
options at your disposal.
#1 Use addr2line to get the crash location
$ cat badmem.c
void function_c() { int *i = (int*)0xdeadbeef; *i = 123; } // <-- line 1
void function_b() { function_c(); }
void function_a() { function_b(); }
int main() { function_a(); return 0; }
$ gcc -g badmem.c -o badmem
$ ./badmem
Segmentation fault
No core dump? You can still get some info.
$ tail -n1 /var/log/syslog
... badmem[1171]: segfault at deadbeef ip 00000000004004da sp 00007fff8825dcd0 error 6 in badmem[400000+1000]
$ echo 00000000004004da | addr2line -Cfe ./badmem
function_c
/home/walter/srcelf/bt/badmem.c:1
#2 Do platform specific stack wizardry
We extend the badmem.c example from above and add non-portable backtrace facilities. (Tested on an x86_64.)
$ cat poormanbt.c
#include <ucontext.h>
#include <unistd.h>
#include <signal.h>
#include <stdio.h>
#define REG_RSP 15
#define REG_RIP 16
void function_c() { int *i = (int*)0xdeadbeef; *i = 123; } // <-- line 7
void function_b(int a, int b, int c) { function_c(); } // <-- line 8
void function_a() { function_b(1, 2, 3); } // <-- line 9
void poormanbt(int signum, siginfo_t *info, void *data) {
struct ucontext *uc = (struct ucontext*)data;
unsigned long *l = (unsigned long*)uc->uc_mcontext.gregs[REG_RSP];
printf("%lx (ip) %lx (sp)\n", uc->uc_mcontext.gregs[REG_RIP], (unsigned long)l);
for (; *l; l = (unsigned long*)*l) { printf("%lx (ip) %lx (sp)\n", l[1], l[0]); }
fflush(stdout);
_exit(1);
}
int main() {
// prepare bt
struct sigaction sa = {0,};
sa.sa_sigaction = poormanbt;
sigfillset(&sa.sa_mask);
sa.sa_flags = SA_SIGINFO;
sigaction(SIGSEGV, &sa, (void*)0);
// fire badmem
function_a();
return 0; // <-- line 28
}
We have it output instruction pointer addresses from the stack at
SIGSEGV
. This example uses the SA_SIGINFO
flag that enables signal
stack info to be passed into the signal handler.
$ gcc poormanbt.c -g -o poormanbt
$ ./poormanbt
4006ba (ip) 7fff61feb150 (sp)
4006dd (ip) 7fff61feb170 (sp)
4006f7 (ip) 7fff61feb180 (sp)
40080e (ip) 7fff61feb240 (sp)
$ ./poormanbt | addr2line -Cfe ./poormanbt
function_c
/home/walter/srcelf/bt/poormanbt.c:7
function_b
/home/walter/srcelf/bt/poormanbt.c:8
function_a
/home/walter/srcelf/bt/poormanbt.c:9
main
/home/walter/srcelf/bt/poormanbt.c:28
But you really don’t want to do the above. Enter execinfo
.
#3 Use the special purpose execinfo backtrace
This is far more portable.
$ cat libbt.c
#include <execinfo.h>
#include <unistd.h>
#include <signal.h>
#include <stdio.h>
void function_c() { int *i = (int*)0xdeadbeef; *i = 123; } // line 5
void function_b(int a, int b, int c) { function_c(); } // line 6
void function_a() { function_b(1, 2, 3); } // line 7
void libbt(int signum, siginfo_t *info, void *data) {
int i, j;
void *buffer[16];
i = backtrace((void**)&buffer, 16); // <-- line 12
for (j = 0; j < i; ++j) { printf("%p (ip)\n", buffer[j]); }
fflush(stdout);
_exit(1);
}
int main() {
// prepare bt
struct sigaction sa = {0,};
sa.sa_sigaction = libbt;
sigfillset(&sa.sa_mask);
sigaction(SIGSEGV, &sa, (void*)0);
// fire badmem
function_a();
return 0; // line 25
}
We changed poormanbt.c
around a bit and are using backtrace(3)
instead of the ucontext_t
. This function seems to know what it’s
doing.
$ gcc libbt.c -g -o libbt
$ ./libbt
0x40077c (ip)
0x7f538e77baf0 (ip)
0x40070a (ip)
0x40072d (ip)
0x400747 (ip)
0x40083d (ip)
0x7f538e766c4d (ip)
0x400639 (ip)
$ ./libbt | addr2line -Cfe ./libbt
libbt
/home/walter/srcelf/bt/libbt.c:12
??
??:0
function_c
/home/walter/srcelf/bt/libbt.c:5
function_b
/home/walter/srcelf/bt/libbt.c:6
function_a
/home/walter/srcelf/bt/libbt.c:7
main
/home/walter/srcelf/bt/libbt.c:26
??
??:0
_start
??:0
Useful? Not so often. But it’s good to know these methods exist.