C Debugging Techniques

Table of Contents

1 backtrace(3)

#include <stdio.h>
#include <execinfo.h>

#include <stdlib.h>

void handler(char *caller) {
  void *array[10];
  size_t size;
  printf("Stack trace start for %s\n", caller);
  size = backtrace(array, 10);
  backtrace_symbols_fd(array, size, 2);
  printf("Stack Trace End\n");
}

void car() {
  handler("char()");
  printf("continue");
}

void baz() {car();}
void bar() { baz();}
void foo() {bar();}

int main(int argc, char **argv) {
  foo();
}

compile:

gcc -g -rdynamic a.c

Output:

Stack trace start for char()
./b.out(handler+0x33)[0x400969]
./b.out(car+0xe)[0x4009a2]
./b.out(baz+0xe)[0x4009c1]
./b.out(bar+0xe)[0x4009d1]
./b.out(foo+0xe)[0x4009e1]
./b.out(main+0x19)[0x4009fc]
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf5)[0x7ffff7a52b45]
./b.out[0x400869]
Stack Trace End
continue

2 -finstrument-funcitons

See gcc options.

Generate instrumentation calls for entry and exit to functions. Just after function entry and just before function exit, the following profiling functions are called with the address of the current function and its call site. (On some platforms, _builtinreturnaddress does not work beyond the current function, so the call site information may not be available to the profiling functions otherwise.) void _cygprofilefuncenter (void *thisfn, void *callsite); void _cygprofilefuncexit (void *thisfn, void *callsite);

trace.c:

#include <stdio.h>
#include <time.h>

static FILE *fp_trace;

void __attribute__ ((constructor))
trace_begin (void)
{
  fp_trace = fopen("trace.out", "w");
}

void __attribute__ ((destructor))
trace_end (void)
{
  if (fp_trace != NULL) {
    fclose(fp_trace);
  }
}

void __cyg_profile_func_enter (void *func, void *caller) {
  if (fp_trace != NULL) {
    fprintf(fp_trace, "e %p %p %lu\n", func, caller, time(NULL));
  }
}

void __cyg_profile_func_exit(void *func, void *caller) {
  if (fp_trace != NULL) {
    fprintf(fp_trace, "x %p %p %lu\n", func, caller, time(NULL));
  }
}

a.c:

#include <stdio.h>
#include <string.h>
void foo() {
  printf("foo\n");
}
int main() {
  foo();
}

Compile:

gcc -finstrument-functions -g -c -o a.o a.c
gcc -c -o trace.o trace.c
gcc a.o trace.o -o a.out
./a.out

The trace.out will be generated:

e 0x4006c8 0x7ffff7a52b45 1470343711
e 0x400696 0x4006e7 1470343711
x 0x400696 0x4006e7 1470343711
x 0x4006c8 0x7ffff7a52b45 1470343711

For those addrees:

nm a.out | grep 4006c8
addr2line -f -e a.out 0x4006c8 | head -1 # function name
addr2line -s -e a.out 0x4006c8 # filename:linum

3 Callgrind

valgrind --tool=callgrind ./a.out

Open another terminal and callgrindcontrol -b to see the trace. The program must be running.