After working with LLVM for several years now, one problem that remains unsolved is how to generate a stack backtrace for exceptions.
My basic approach is simple: I have two different exception personality functions, a “lightweight” one that just does the bare minimum needed to handle exception, and a “capturing” one that (ideally) records the call frame information as it unwinds the stack. My motivation for doing it this way is that it would be too expensive to always capture call frame information on every exception, so instead my compiler only uses the heavier personality function when the exception backtrace information is actually going to be used.
Within the personality function, there’s a call to _Unwind_Backtrace(), which walks through the list of call frames and calls a callback for each one. Within the callback, I can get the value of the return address for each call frame using _Unwind_GetIP(). So far so good.
The problem is converting those addresses into meaningful symbols. For some reason that I don’t understand, dladdr() doesn’t seem to work on LLVM-generated functions, even though I know those functions have full DWARF debugging information. If I insert a printf into my backtrace code, and print out the addresses of each return address I see something like this:
The hex values are ones where dladdr() failed to provide a function name. As you can see, the only functions it was able to deal with are the libc startup function, and _Unwind_Raise_Exception itself. Yet I know these functions have symbolic names, since I can step through them in gdb, set breakpoints, and so on.
I’ve tried a number of other approaches: Calling dlopen(NULL) and then using dlsym() to try and locate __data_start so that I can then attempt to manually parse the DWARF debug frames to translate the return addresses into function names. Unfortunately, I can’t seem to locate __data_start at all. I’ve also tried calling the libc backtrace() function, but it produced similarly useless results.
The really icky part about all of this is that even if I do come up with a solution for these problems, I will then have to re-solve the same problems for each different platform that LLVM supports. I kinda wish that there was some LLVM intrinsic or library function that would hide all these details from me