ARM C/C++ Software Stack Back Trace

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.

About minghuasweblog

a long time coder
This entry was posted in All, C/C++, Hardware and tagged , , , , , , . Bookmark the permalink.

5 Responses to ARM C/C++ Software Stack Back Trace

  1. Allen says:

    I am trying to do something similar for arm7 which does not seem to have “rp”.
    Any suggestions is appreciated.

  2. kamath says:

    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);

  3. kamath says:

    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.

  4. JD says:

    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.

  5. Pingback: ARM: link register and frame pointer

Leave a comment