Printing stack trace pragmatically from C/C++ code is an efficient way to speed up troubleshooting and debugging. Since testing, debugging, and sustaining cost more than half of software total cost, printing backtrace is very important to producing quality software quickly. The problem is, in some versions of kernel/toolchain combination, it works. Only when you upgrade to a later version, you find out it breaks. To make things worse, you cannot downgrade or upgrade again to another version.
The usual way of doing the backtrace is by calling backtrace()
to get a caller address list from the stack. Then using backtrace_symbols()
get a list of corresponding symbols at those caller addresses. At the final step, demangle the name by abi::__cxa_demangle()
.
If your compiler/library do not give you a full list of the caller address, you can implement it by yourself. It involves a few steps.
Step 1: Compile your code with frame pointers
Assume you use an arm eabi gnu compiler. Add two options to your build process.
-fno-omit-frame-pointer -rdynamic
The first option makes sure a consistent frame layout can be obtained. From that your program can walk up the stack and get all the caller addresses. The 2nd option is to make sure the symbols will be built into the binary so you’ll be able to get symbol names by address.
Since arm eabi is actually the APCS
ABI, the option -mapcs-frame
can also be used.
Step 2: Write an Assembly Function to Grab FP Register
Once the options are used, write an assembly file like this:
.file "getfp.s" .text .align 2 .global get_fp .type get_fp, %function get_fp: mov rp, fp mov pc, lr
Step 3: Write a C Function to Walk the Frames
Looking at the assembly code the gcc options will affect, the following function will generate the list of callers from the highly structural stack frames.
extern int get_fp(void); void print_symbol(void *addr) { void *addresslist[2] = {addr, 0}; char **symbollist = backtrace_symbols(addrlist, 1); // symbol name string is at symbollist[0]. print it. } void print_backtrace(void) { int topfp = get_fp(); for (int i=0; i < 10; i++) { int fp = *(((int*)topfp) -3); int sp = *(((int*)topfp) -2); int lr = *(((int*)topfp) -1); int pc = *(((int*)topfp) -0); if ( i==0 ) print_symbol( (void*) pc); // top frame if (fp != 0) print_symbol( (void*) lr); // middle frame else print_symbol( (void*)pc); // bottom frame, lr invalid topfp = fp; } }
Step 4: From your program , call print_backtrace()
More info
Take a look at a sample program by Timo Bingmann on github for how to demangle the symbol.
I am trying to do something similar for arm7 which does not seem to have “rp”.
Any suggestions is appreciated.
Allen, you can use the below code instead.
int topFramePtrVal = 0;
asm(“mov %0, fp”: “=r” (topFramePtrVal)); //inline assembly, avoids the assembly file
int *p_FramePtr = (int *)(topFramePtrVal);
Also, missing is the check for null pointer in the end.
topfp = fp;
after this, you need the below code
if ( (int *)topfp == null) break;
this would be needed in multithreaded programs.
Warning: GCC use R11/FP register in ARM mode only. In thumb more GCC use R7 in frame pointer role. Therefore unwind in mixed ARM/Thumb mode is more complicated.
Pingback: ARM: link register and frame pointer