Search code examples
c++linuxaddr2line

How to detect if a function pointer comes from a shared library or the main executable?


I'm using backtrace and backtrace_symbols to get a stacktrace when a custom exception is thrown in my program (for now, on Linux... Ubuntu specifically)

trace_size = backtrace(stack_traces, MAX_STACK_FRAMES);
messages = backtrace_symbols(stack_traces, trace_size);

From the stacktrace, I'm able to loop over each frame and extract an address that the program was at for this frame.

I get two types of addresses depending on where a function was defined:

  1. A library address:
/path/to/library.so(mangled_function_name+0x75) [0x7f02bcdfa849]
                                                 ^^^^^^^^^^^^^^
  1. executable address:
/path/to/executable(mangled_function_name?+0x90) [0x403336]
                                                  ^^^^^^^^

[0x7f02bcdfa849] and [0x403336] are where my concerns lie...

I want to pass these addresses to addr2line.

However, for addresses belonging to shared libraries, this won't work. I learned that I first need to calculate the offset of the parsed address from the base address that the library was loaded at.

addr2line -e /path/to/library.so 0x7f02bcdfa849 # won't work
# or
addr2line -e /path/to/executable 0x403336 # works just fine

I know I can use ::dladdr to find the base address and find the offset from that.

But, how can I tell, at runtime, whether I need to do this transformation? As in, how can I tell if an address belongs to a shared library at runtime?

I've tried simply checking if the .so shows up in the frame of the stacktrace but that feels... wrong.


Solution

  • ::dladdr1 has the information I needed.

    Given I have addr_ptr which is either the address in a shared library or in the shared exe, I can get the base address to subtract from addr_ptr by calling into ::dladdr1 and using the returned ::link_map data structure.

    The ::link_map has a field called l_addr which is 0 for the exe and non-zero for linked libraries.

    So given an addr_ptr that has the parsed address:

    #include <dlfcn.h> // for ::dladdr1
    #include <link.h> // for ::link_map
    ...
    
        const void* final_ptr = nullptr;
        ::Dl_info dli_;
        if (::link_map *lm = nullptr;
            ::dladdr1(addr_ptr, &dli_, reinterpret_cast<void **>(&lm), RTLD_DL_LINKMAP)) {
            if (lm != nullptr) {
                auto lib_delta_int = reinterpret_cast<std::uintptr_t>(addr_ptr) -
                                     reinterpret_cast<std::uintptr_t>(lm->l_addr);
    
                final_ptr = reinterpret_cast<const void *>(lib_delta_int);
            }
        }
    

    final_ptr has the correct value now to pass to addr2line