【问题标题】:How to get fullstacktrace using _Unwind_Backtrace on SIGSEGV如何在 SIGSEGV 上使用 _Unwind_Backtrace 获取 fullstacktrace
【发布时间】:2011-09-09 09:21:44
【问题描述】:

我通过代码处理 SIGSEGV:

int C()
{
  int *i = NULL;
  *i = 10; // Crash there
}

int B()
{
  return C();
}

int A()
{
   return B();
}

int main(void)
{
  struct sigaction handler;
  memset(&handler,0,sizeof(handler));
  handler.sa_sigaction = handler_func;
  handler.sa_flags = SA_SIGINFO;
  sigaction(SIGSEGV,&handler,NULL);
  return(C());
}

处理程序代码在哪里:

static int handler_func(int signal, siginfo_t info, void* rserved)
{
  const void* stack[MAX_DEPTH];
  StackCrowlState state;
  state.addr = stack;
  state.count = MAX_DEPTH;

  _Unwind_Reason_Code code = _Unwind_Backtrace(trace_func,&state);
  printf("Stack trace count: %d, code: %d\n",MAX_DEPTH - state.count, code);

  kill(getpid(),SIGKILL);
}

static _Unwind_Reason_Code trace_func(void* context, void* arg)
{
  StackCrowlState *state = (StackCrowlState *)arg;
  if(state->count>0)
  {
     void *ip = (void *)_Unwind_GetIP(context);
     if(ip)
     {
       state->addr[0] = ip;
       state->count--;
       state->addr++;
     }
  }
  return(_URC_NO_REASON);
}

但是 trace_func 只调用一次,并且只在 _Unwind_Backtrace 调用时显示。是否可以使用 _Unwind_Backtrace 获取导致 SIGSEGV 信号的代码的堆栈跟踪?

thnx

【问题讨论】:

    标签: c gcc signals stack-trace stack-unwinding


    【解决方案1】:

    如果你特别想使用_Unwind_Context(),你可以这样做(代码是32位ARM特定的):


    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.
            // They sometimes happen when using _Unwind_Backtrace()
            // with the compiler optimizations,
            // when the Link Register is overwritten by the inner
            // stack frames.
            if (ip == 0)
                return true;
    
            // Ignore duplicate addresses.
            // They sometimes happen when using _Unwind_Backtrace()
            // with the compiler optimizations,
            // because we both add the second address from the Link Register
            // in ProcessRegisters() and receive the same address
            // in UnwindBacktraceCallback().
            if (address_count > 0 && ip == addresses[address_count - 1])
                return true;
    
            // Finally add the address to the storage.
            addresses[address_count++] = ip;
            return true;
        }
    };
    
    void ProcessRegisters(
            _Unwind_Context* unwind_context, BacktraceState* state) {
        assert(state);
        assert(unwind_context);
    
        const ucontext_t* signal_ucontext = state->signal_ucontext;
        assert(signal_ucontext);
    
        const sigcontext* signal_mcontext = &(signal_ucontext->uc_mcontext);
        assert(signal_mcontext);
    
        _Unwind_SetGR(unwind_context, REG_R0,  signal_mcontext->arm_r0);
        _Unwind_SetGR(unwind_context, REG_R1,  signal_mcontext->arm_r1);
        _Unwind_SetGR(unwind_context, REG_R2,  signal_mcontext->arm_r2);
        _Unwind_SetGR(unwind_context, REG_R3,  signal_mcontext->arm_r3);
        _Unwind_SetGR(unwind_context, REG_R4,  signal_mcontext->arm_r4);
        _Unwind_SetGR(unwind_context, REG_R5,  signal_mcontext->arm_r5);
        _Unwind_SetGR(unwind_context, REG_R6,  signal_mcontext->arm_r6);
        _Unwind_SetGR(unwind_context, REG_R7,  signal_mcontext->arm_r7);
        _Unwind_SetGR(unwind_context, REG_R8,  signal_mcontext->arm_r8);
        _Unwind_SetGR(unwind_context, REG_R9,  signal_mcontext->arm_r9);
        _Unwind_SetGR(unwind_context, REG_R10, signal_mcontext->arm_r10);
        _Unwind_SetGR(unwind_context, REG_R11, signal_mcontext->arm_fp);
        _Unwind_SetGR(unwind_context, REG_R12, signal_mcontext->arm_ip);
        _Unwind_SetGR(unwind_context, REG_R13, signal_mcontext->arm_sp);
        _Unwind_SetGR(unwind_context, REG_R14, signal_mcontext->arm_lr);
        _Unwind_SetGR(unwind_context, REG_R15, signal_mcontext->arm_pc);
    
        // Program Counter register aka Instruction Pointer will contain
        // the address of the instruction where the crash happened.
        // UnwindBacktraceCallback() will not supply us with it.
        state->AddAddress(signal_mcontext->arm_pc);
    
        // UnwindBacktraceCallback() does not always supply us with
        // the return address of the frame where the crash happened.
        // Sometimes Link Register will contain this address
        // (noticed when compiling with Clang without optimization),
        // but LR may also contain address of some previously visitied frame
        // (noticed when compiling with GCC without optimization),
        // or LR may contain null address
        // (noticed when compiling with Clang with optimization).
        // These heuristics are unreliable.
    #if __clang__
        state->AddAddress(signal_mcontext->arm_lr);
    #endif
    }
    
    _Unwind_Reason_Code UnwindBacktraceCallback(
            struct _Unwind_Context* unwind_context, void* state_voidp) {
        assert(unwind_context);
        assert(state_voidp);
    
        BacktraceState* state = (BacktraceState*)state_voidp;
        assert(state);
    
        // On the first UnwindBacktraceCallback() call,
        // set registers to _Unwind_Context and BacktraceState.
        if (state->address_count == 0) {
            ProcessRegisters(unwind_context, state);
            return _URC_NO_REASON;
        }
    
        uintptr_t ip = _Unwind_GetIP(unwind_context);
        bool ok = state->AddAddress(ip);
        if (!ok)
            return _URC_END_OF_STACK;
    
        return _URC_NO_REASON;
    }
    
    void CaptureBacktrace(BacktraceState* state) {
        assert(state);
        _Unwind_Backtrace(UnwindBacktraceCallback, state);
    }
    
    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);
        CaptureBacktrace(&backtrace_state);
        // Do something with the backtrace - print, save to file, etc.
    }
    

    但我建议您不要使用 _Unwind_Context(),而是使用预编译的 libunwind 用于 32 位 ARM,与现代 Android NDK(sources/cxx-stl/llvm-libc++/libs/armeabi-v7a/libunwind.a)和所有 LLVM 捆绑在一起。您将不得不使用 libc++ (LLVM STL)。怎么做,在这个我的回答中演示,你必须在这里结合例子。

    https://stackoverflow.com/a/50027799/1016580

    如果你使用 libstdc++ (GNU STL),你可以使用 Dar Hoo 的解决方案:

    https://stackoverflow.com/a/48593413/1016580

    【讨论】:

    • 抱歉,为什么要投反对票?我的建议是不正确还是没用?我正在尝试链接,以便通过将多个线程指向相同的答案来对具有相同问题的多个线程进行重复数据删除。
    • 欢迎提供解决方案的链接,但请确保您的答案在没有它的情况下有用:add context around the link 这样您的其他用户就会知道它是什么以及为什么会出现,然后引用最相关的内容您链接到的页面的一部分,以防目标页面不可用。 Answers that are little more than a link may be deleted.
    • 我已经提供了答案的基本部分。希望现在更好。
    • 为什么更喜欢 libunwind unw_set_reg_Unwind_SetGR 他们不同吗?谢谢!
    • 根据我的经验,libunwind 在 32 位 ARM CPU 上比 _Unwind_Context() 工作得更好,即在回溯中捕获更多地址。显然它的工作方式略有不同。但是,在 64 位 CPU 上,无论是在 64 位模式下还是在 32 位模式下,带有跳帧的 _Unwind_Context() 可能会更好地工作。我在 2018 年 4 月写了答案,当时 32 位 CPU 在 Android 手机中很流行,但现在到 2021 年底,Android 手机大多包含 64 位 CPU。您可以使用我的小型测试应用程序尝试 3 种回溯方法:github.com/alexeikh/android-ndk-backtrace-test,看看哪种方法最适合您。
    【解决方案2】:

    您想从信号触发函数回溯,但您从信号处理函数回溯。这是两个不同的堆栈。 (注意,sigaction 中的 SA_ONSTACK 标志与您的问题无关。)

    要找到触发函数的栈指针,使用handler的第三个参数,即void *rserved。这个问题可以参考答案:Getting the saved instruction pointer address from a signal handler

    【讨论】:

    • 您知道一旦从信号处理程序的第三个参数中检索到触发函数的堆栈指针,该怎么处理它吗?你还能使用 _Unwind_Backtrace 吗?我有这个确切的问题,阅读您的答案后我真的不明白该怎么做。
    • 又过了一年多,我也不知道在这种情况下该怎么办......如何让 _Unwind_Backtrace 真正使用旧的堆栈指针?
    • 又过了一年,同样的问题:我知道如何获得SP,但我不知道如何处理它!
    【解决方案3】:

    最好使用 backtrace 和 backtrace_symbols_fd 从信号处理程序获取堆栈跟踪。

    【讨论】:

    • 在使用 gcc 的平台上除外,因此有 _Unwind_Backtrace,但没有 GNU libc,因此没有 backtracebacktrace_symbols_fd
    • 一些 linux 发行版使用 musl libc。
    【解决方案4】:

    您可以改用__gnu_Unwind_Backtrace。 ARM32 示例:

    typedef struct
    {
        uintptr_t r[16];
    } core_regs;
    
    typedef struct
    {
        uintptr_t demand_save_flags;
        core_regs   core;
    } phase2_vrs;
    
    extern "C" _Unwind_Reason_Code __gnu_Unwind_Backtrace(_Unwind_Trace_Fn trace, void * trace_argument, phase2_vrs * entry_vrs);
    
    int AndroidGetBackTraceWithContext(VOID **stack, UINT32 size, ucontext_t *ctx)
    {
        ANDROID_UNWIND_STATE state;
        state.count = size;
        state.stack = stack;
    
        // First call stack is current pc
        state.stack[0] = (VOID *)ctx->uc_mcontext.arm_pc;
        state.stack++;
        state.count--;
    
        phase2_vrs pre_signal_state;
        pre_signal_state.demand_save_flags = 0;
        pre_signal_state.core = *reinterpret_cast<const core_regs*>(&(ctx->uc_mcontext.arm_r0));
    
        // Return value is of no use and might be wrong on some systems
        __gnu_Unwind_Backtrace(DmpAndroidUnwindCallback, &state, &pre_signal_state);
    
        return size - state.count;
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-09-04
      • 2020-05-08
      • 2020-04-20
      • 2017-08-16
      • 1970-01-01
      • 2011-06-02
      • 1970-01-01
      • 2010-11-08
      相关资源
      最近更新 更多