c++clinuxdebuggingbacktrace

How ro resolve cpp symbols from backtrace_symbols() in the offset during runtime for addr2line


To catch fatal errors like Segmentation Fault during runtime I write a custom SignalHandler that will print a stack trace to console and into a log file.

To achieve this I use (as hundreds before me) the backtrace() and backtrace_symbols() functions in combination with addr2line.

A call to backtrace_symbols() produces following output:

Obtained 8 stack frames.
./Mainboard_Software(+0xb1af5) [0x56184991baf5]
./Mainboard_Software(+0xb1a79) [0x56184991ba79]
/lib/x86_64-linux-gnu/libpthread.so.0(+0x12dd0) [0x7fe72948bdd0]
./Mainboard_Software(causeSIGFPE+0x16) [0x561849918a10]
./Mainboard_Software(_Z13MainboardInit7QString+0xf3) [0x56184990e0df]
./Mainboard_Software(main+0x386) [0x5618499182a3]
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xeb) [0x7fe727fd909b]
./Mainboard_Software(_start+0x2a) [0x5618498ff0aa]

I need to pass the offset to addr2line to get my module name and line number.

$ addr2line -C -a -s -f -p -e ./D098_Mainboard_Software 0xb1a79
0x00000000000b1a79: HandleBacktraceSignals at SignalModule.c:492

However, in some modules (especially cpp ones) I get the offset as a combination off sybols and hex, like _Z13MainboardInit7QString+0xf3

I can resolve the symbol to hex with a call to nm:

$ nm Mainboard_Software | grep _Z13MainboardInit7QString
00000000000a3fec T _Z13MainboardInit7QString

Now I can add these two hex numbers, pass them to addr2line and get my module name and line number, even demangled if I want to:

$ addr2line -C -a -s -f -p -e ./D098_Mainboard_Software 0xa40df
0x00000000000a40df: MainboardInit(QString) at MainboardInit.cpp:219

But I want to do the last two steps during runtime. Is there a way to resolve these symbols (e.g. _Z13MainboardInit7QString+0xf3) during runtime so that I can pass them directly to addr2line? My program consists of both .c and.cpp modules.


Solution

  • Took me a while but with Linux, one can use the dlfcn.h GNU library. Just be sure to define _GNU_SOURCE above all header file includes. Beware this include will make your program POSIX nonconform.

    For the linker flags add -ldl for both architectures and -g3 for x86 and -g3, -funwind-tables,-mapcs-frame for ARM.

    #define _GNU_SOURCE
    
    #include <execinfo.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <string.h>
    
    #include <dlfcn.h>
    #include <gnu/lib-names.h>
    
    #define STACK_FRAMES_BUFFERSIZE (int)128
    
    static void * STACK_FRAMES_BUFFER[128];
    static void * OFFSET_FRAMES_BUFFER[128];
    static char   EXECUTION_FILENAME[32] = "Mainboard_Software";
    
    
    /*-----------------------------------------------------------------------------------*/
    /*
     * Function will attempt to backtrace the signal cause by collecting the last called addresses.
     * The addresses will then be translated into readable stings by addr2line
     */
    
    static void PrintBacktrace(void)
    {
       const char errorString[] = "Offset cannot be resolved: No offset present?\n\0?";
       char       printArray[100] = {0};
       size_t     bufferEntries;
       char **    stackFrameStrings;
       size_t     frameIterator;
    
       //backtrace the last calls
       bufferEntries = backtrace(STACK_FRAMES_BUFFER, STACK_FRAMES_BUFFERSIZE);
       stackFrameStrings = backtrace_symbols(STACK_FRAMES_BUFFER, (int)bufferEntries);
    
       //print the number of obtained frames
      sprintf(printArray,"\nObtained %zd stack frames.\n\r", bufferEntries);
      (void)write(STDERR_FILENO, printArray, strlen(printArray));
    
       //iterate over addresses and print the stings
       for (frameIterator = 0; frameIterator < bufferEntries; frameIterator++)
       {
    #if __x86_64__
          //calculate the offset on x86_64 and print the file and line number with addr2line
          OFFSET_FRAMES_BUFFER[frameIterator] = CalculateOffset(stackFrameStrings[frameIterator]);
          if(OFFSET_FRAMES_BUFFER[frameIterator] == NULL)
          {
             (void)write(STDERR_FILENO, errorString, strlen(errorString));
          }
          else
          {
             Addr2LinePrint(OFFSET_FRAMES_BUFFER[frameIterator]);
          }
    #endif
    #if __arm__
          //the address itself can be used on ARM for a call to addr2line
          Addr2LinePrint(STACK_FRAMES_BUFFER[frameIterator]);
    #endif
       }
       free (stackFrameStrings);
     }
    
    /*-----------------------------------------------------------------------------------*/
    /*
     * Use add2line on the obtained addresses to get a readable sting
     */
    static void Addr2LinePrint(void const * const addr)
    {
      char addr2lineCmd[512] = {0};
    
      //have addr2line map the address to the relent line in the code
      (void)sprintf(addr2lineCmd,"addr2line -C -i -f -p -s -a -e ./%s %p ", EXECUTION_FILENAME, addr);
    
      //This will print a nicely formatted string specifying the function and source line of the address
      (void)system(addr2lineCmd);
    }
    /*-----------------------------------------------------------------------------------*/
    /*
     * Pass a string which was returned by a call to backtrace_symbols() to get the total offset
     * which might be decoded as (symbol + offset). This function will return the calculated offset
     * as void pointer, this pointer can be passed to addr2line in a following call.
     */
    void *  CalculateOffset(char * stackFrameString)
    {
       void *     objectFile;
       void *     address;
       void *     offset = NULL;
       char       symbolString[75] = {'\0'};
       char       offsetString[25] = {'\0'};
       char *      dlErrorSting;
       int        checkSscanf = EOF;
       int        checkDladdr = 0;
       Dl_info    symbolInformation;
    
       //parse the string obtained by backtrace_symbols() to get the symbol and offset
       parseStrings(stackFrameString, symbolString, offsetString);
    
       //convert the offset from a string to a pointer
       checkSscanf = sscanf(offsetString, "%p",&offset);
    
       //check if a symbol string was created,yes, convert symbol string to offset
       if(symbolString[0] != '\0')
       {
          //open the object (if NULL the executable itself)
          objectFile = dlopen(NULL, RTLD_LAZY);
          //check for error
          if(!objectFile)
          {
             dlErrorSting = dlerror();
             (void)write(STDERR_FILENO, dlErrorSting, strlen(dlErrorSting));
          }
          //convert sting to a address
          address = dlsym(objectFile, symbolString);
          //check for error
          if(address == NULL)
          {
             dlErrorSting = dlerror();
             (void)write(STDERR_FILENO, dlErrorSting, strlen(dlErrorSting));
          }
          //extract the symbolic information pointed by address
          checkDladdr = dladdr(address, &symbolInformation);
    
          if(checkDladdr != 0)
          {
             //calculate total offset of the symbol
             offset = (symbolInformation.dli_saddr - symbolInformation.dli_fbase) + offset;
             //close the object
             dlclose(objectFile);
          }
          else
          {
             dlErrorSting = dlerror();
             (void)write(STDERR_FILENO, dlErrorSting, strlen(dlErrorSting));
          }
       }
    
       return checkSscanf != EOF ? offset : NULL;
    }
    /*-----------------------------------------------------------------------------------*/
    /*
     * Parse a string which was returned from backtrace_symbols() to get the symbol name
     * and the offset. 
     */
    
    void parseStrings(char * stackFrameString, char * symbolString, char * offsetString)
    {
       char *        symbolStart = NULL;
       char *        offsetStart = NULL;
       char *        offsetEnd = NULL;
       unsigned char stringIterator = 0;
    
       //iterate over the string and search for special characters
       for(char * iteratorPointer = stackFrameString; *iteratorPointer; iteratorPointer++)
       {
          //The '(' char indicates the beginning of the symbol
          if(*iteratorPointer == '(')
          {
             symbolStart = iteratorPointer;
          }
          //The '+' char indicates the beginning of the offset
          else if(*iteratorPointer == '+')
          {
             offsetStart = iteratorPointer;
          }
          //The ')' char indicates the end of the offset
          else if(*iteratorPointer == ')')
          {
             offsetEnd = iteratorPointer;
          }
    
       }
       //Copy the symbol string into an array pointed by symbolString
       for(char * symbolPointer = symbolStart+1; symbolPointer != offsetStart; symbolPointer++)
       {
          symbolString[stringIterator] = *symbolPointer;
          ++stringIterator;
       }
       //Reset string iterator for the new array which will be filled
       stringIterator = 0;
       //Copy the offset string into an array pointed by offsetString
       for(char * offsetPointer = offsetStart+1; offsetPointer != offsetEnd; offsetPointer++)
       {
          offsetString[stringIterator] = *offsetPointer;
          ++stringIterator;
       }
    }
    
    

    Calls to this function will produce output like this on console:

    Obtained 11 stack frames.
    0x00000000000b1ba5: PrintBacktrace at SignalModule.c:524
    0x00000000000b1aeb: HandleBacktraceSignals at SignalModule.c:494
    0x0000000000012dd0: ?? ??:0
    0x00000000000aea85: baz at testFunctions.c:75
    0x00000000000aea6b: bar at testFunctions.c:70
    0x00000000000aea5f: foo at testFunctions.c:65
    0x00000000000aea53: causeSIGSEGV at testFunctions.c:53
    0x00000000000a412f: MainboardInit(QString) at MainboardInit.cpp:218
    0x00000000000ae2f3: main at Main.cpp:142 (discriminator 2)
    0x000000000002409b: ?? ??:0
    0x00000000000950fa: _start at ??:?