Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to unwind the stack to get backtrace for the specified stack pointer (SP)?

I'm writing this for Android (ARM only), but I believe the principle is the same for generic Linux as well.

I'm trying to capture the stack trace from within the signal handler, so that I can log it when my app crashes. This is what I've come up with using <unwind.h>.
Initialization:

struct sigaction signalhandlerDescriptor; memset(&signalhandlerDescriptor, 0, sizeof(signalhandlerDescriptor)); signalhandlerDescriptor.sa_flags = SA_SIGINFO; signalhandlerDescriptor._u._sa_sigaction = signalHandler; sigaction(SIGSEGV, &signalhandlerDescriptor, 0); 

The code itself:

struct BacktraceState {     void** current;     void** end;     void* pc; };  inline _Unwind_Reason_Code unwindCallback(struct _Unwind_Context* context, void* arg) {     BacktraceState* state = static_cast<BacktraceState*>(arg);     state->pc = (void*)_Unwind_GetIP(context);     if (state->pc)     {         if (state->current == state->end)             return _URC_END_OF_STACK;         else             *state->current++ = reinterpret_cast<void*>(state->pc);     }     return _URC_NO_REASON; }  inline size_t captureBacktrace(void** addrs, size_t max, unsigned long pc) {     BacktraceState state = {addrs, addrs + max, (void*)pc};     _Unwind_Backtrace(unwindCallback, &state);     personality_routine();      return state.current - addrs; }  inline void dumpBacktrace(std::ostream& os, void** addrs, size_t count) {     for (size_t idx = 0; idx < count; ++idx) {         const void* addr = addrs[idx];         const char* symbol = "";          Dl_info info;         if (dladdr(addr, &info) && info.dli_sname) {             symbol = info.dli_sname;         }          int status = -3;         char * demangledName = abi::__cxa_demangle(symbol, 0, 0, &status);         os << "#" << idx << ": " << addr << "  " << (status == 0 ? demangledName : symbol) << "\n";         free(demangledName);     } }  void signalHandler(int sig, siginfo_t *siginfo, void *uctx) {     ucontext * context = (ucontext*)uctx;     unsigned long PC = context->uc_mcontext.arm_pc;     unsigned long SP = context->uc_mcontext.arm_sp;      Logger() << __PRETTY_FUNCTION__ << "Fatal signal:" << sig;     const size_t maxNumAddresses = 50;     void* addresses[maxNumAddresses];     std::ostringstream oss;      const size_t actualNumAddresses = captureBacktrace(addresses, maxNumAddresses, PC);     dumpBacktrace(oss, addresses, actualNumAddresses);     Logger() << oss.str();     exit(EXIT_FAILURE); } 

Problem: if I get the PC register by calling _Unwind_GetIP(context) in unwindCallback, I get the complete trace for the signal handler stack. Which is a separate stack, and that's obviously not what I want. So I tried supplying the PC taken from the ucontext in signal handler, and got a weird result: I get one stack entry, it is the correct entry - the function which caused the signal in the first place. But it's logged twice (even the address is the same, so it's not a symbolic name look up bug). Obviously, that's not good enough - I need the whole stack. And I wonder if this result is merely accidental (i. e. it shouldn't work in general.

Now, I read I need to also supply the stack pointer, which I apparently can get from ucontext, same as PC. But I don't know what to do with it. Do I have to unwind manually instead of using _Unwind_Backtrace? If so, can you give me sample code? I've been searching for the better part of a day, and still couldn't find anything I could copy and paste into my project.

For what it's worth, here's the libunwind source which contains _Unwind_Backtrace definition. Thought I could figure something out if I see its source, but it's way more complicated than I expected.

like image 784
Violet Giraffe Avatar asked Apr 10 '15 10:04

Violet Giraffe


1 Answers

In order to to get stacktrace of code which caused SIGSEGV instead of stacktrace of the signal handler, you have to get ARM registers from ucontext_t and use them for unwinding.

But it is hard to do with _Unwind_Backtrace(). Thus, if you use libc++ (LLVM STL) and compile for 32-bit ARM, better try precompiled libunwind, bundled with modern Android NDKs (at sources/cxx-stl/llvm-libc++/libs/armeabi-v7a/libunwind.a). Here is a sample code.


// This method can only be used on 32-bit ARM with libc++ (LLVM STL). // Android NDK r16b contains "libunwind.a" for armeabi-v7a ABI. // This library is even silently linked in by the ndk-build, // so we don't have to add it manually in "Android.mk". // We can use this library, but we need matching headers, // namely "libunwind.h" and "__libunwind_config.h". // For NDK r16b, the headers can be fetched here: // https://android.googlesource.com/platform/external/libunwind_llvm/+/ndk-r16/include/ #if _LIBCPP_VERSION && __has_include("libunwind.h") #include "libunwind.h" #endif  struct BacktraceState {     const ucontext_t*   signal_ucontext;     size_t              address_count = 0;     static const size_t address_count_max = 30;     uintptr_t           addresses[address_count_max] = {};      BacktraceState(const ucontext_t* ucontext) : signal_ucontext(ucontext) {}      bool AddAddress(uintptr_t ip) {         // No more space in the storage. Fail.         if (address_count >= address_count_max)             return false;          // Reset the Thumb bit, if it is set.         const uintptr_t thumb_bit = 1;         ip &= ~thumb_bit;          // Ignore null addresses.         if (ip == 0)             return true;          // Finally add the address to the storage.         addresses[address_count++] = ip;         return true;     } };  void CaptureBacktraceUsingLibUnwind(BacktraceState* state) {     assert(state);      // Initialize unw_context and unw_cursor.     unw_context_t unw_context = {};     unw_getcontext(&unw_context);     unw_cursor_t  unw_cursor = {};     unw_init_local(&unw_cursor, &unw_context);      // Get more contexts.     const ucontext_t* signal_ucontext = state->signal_ucontext;     assert(signal_ucontext);     const sigcontext* signal_mcontext = &(signal_ucontext->uc_mcontext);     assert(signal_mcontext);      // Set registers.     unw_set_reg(&unw_cursor, UNW_ARM_R0, signal_mcontext->arm_r0);     unw_set_reg(&unw_cursor, UNW_ARM_R1, signal_mcontext->arm_r1);     unw_set_reg(&unw_cursor, UNW_ARM_R2, signal_mcontext->arm_r2);     unw_set_reg(&unw_cursor, UNW_ARM_R3, signal_mcontext->arm_r3);     unw_set_reg(&unw_cursor, UNW_ARM_R4, signal_mcontext->arm_r4);     unw_set_reg(&unw_cursor, UNW_ARM_R5, signal_mcontext->arm_r5);     unw_set_reg(&unw_cursor, UNW_ARM_R6, signal_mcontext->arm_r6);     unw_set_reg(&unw_cursor, UNW_ARM_R7, signal_mcontext->arm_r7);     unw_set_reg(&unw_cursor, UNW_ARM_R8, signal_mcontext->arm_r8);     unw_set_reg(&unw_cursor, UNW_ARM_R9, signal_mcontext->arm_r9);     unw_set_reg(&unw_cursor, UNW_ARM_R10, signal_mcontext->arm_r10);     unw_set_reg(&unw_cursor, UNW_ARM_R11, signal_mcontext->arm_fp);     unw_set_reg(&unw_cursor, UNW_ARM_R12, signal_mcontext->arm_ip);     unw_set_reg(&unw_cursor, UNW_ARM_R13, signal_mcontext->arm_sp);     unw_set_reg(&unw_cursor, UNW_ARM_R14, signal_mcontext->arm_lr);     unw_set_reg(&unw_cursor, UNW_ARM_R15, signal_mcontext->arm_pc);      unw_set_reg(&unw_cursor, UNW_REG_IP, signal_mcontext->arm_pc);     unw_set_reg(&unw_cursor, UNW_REG_SP, signal_mcontext->arm_sp);      // unw_step() does not return the first IP.     state->AddAddress(signal_mcontext->arm_pc);      // Unwind frames one by one, going up the frame stack.     while (unw_step(&unw_cursor) > 0) {         unw_word_t ip = 0;         unw_get_reg(&unw_cursor, UNW_REG_IP, &ip);          bool ok = state->AddAddress(ip);         if (!ok)             break;     } }  void SigActionHandler(int sig, siginfo_t* info, void* ucontext) {     const ucontext_t* signal_ucontext = (const ucontext_t*)ucontext;     assert(signal_ucontext);      BacktraceState backtrace_state(signal_ucontext);     CaptureBacktraceUsingLibUnwind(&backtrace_state);     // Do something with the backtrace - print, save to file, etc. } 

Here is a sample backtrace testing app with 3 implemented backtracing methods, including the method shown above.

https://github.com/alexeikh/android-ndk-backtrace-test

like image 115
Alexei Khlebnikov Avatar answered Sep 23 '22 14:09

Alexei Khlebnikov