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:
/path/to/library.so(mangled_function_name+0x75) [0x7f02bcdfa849]
^^^^^^^^^^^^^^
/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.
::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