【问题标题】:How to unwind the stack to get backtrace for the specified stack pointer (SP)?如何展开堆栈以获取指定堆栈指针(SP)的回溯?
【发布时间】:2015-06-16 01:11:18
【问题描述】:

我正在为 Android(仅限 ARM)编写此代码,但我相信通用 Linux 的原理也是相同的。

我正在尝试从信号处理程序中捕获堆栈跟踪,以便在我的应用程序崩溃时记录它。这就是我使用<unwind.h> 想出的。
初始化:

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

代码本身:

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);
}

问题:如果我通过在unwindCallback 中调用_Unwind_GetIP(context) 获得PC 寄存器,我将获得信号处理程序堆栈的完整跟踪。这是一个单独的堆栈,这显然不是我想要的。因此,我尝试提供从信号处理程序中的ucontext 获取的 PC,并得到了一个奇怪的结果:我得到一个堆栈条目,它是正确的条目 - 首先导致信号的函数。但是它被记录了两次(即使地址相同,所以它不是符号名称查找错误)。显然,这还不够好——我需要整个堆栈。而且我想知道这个结果是否仅仅是偶然的(即它通常不应该起作用。

现在,我读到我还需要提供堆栈指针,我显然可以从ucontext 获得,与 PC 相同。但我不知道该怎么办。我必须手动放松而不是使用_Unwind_Backtrace吗?如果是这样,你能给我示例代码吗?我一直在寻找一天中的大部分时间,但仍然找不到可以复制并粘贴到我的项目中的任何内容。

对于它的价值,这是包含 _Unwind_Backtrace 定义的 libunwind 源。如果我看到它的来源,我想我可以弄清楚一些事情,但它比我预期的要复杂。

【问题讨论】:

  • FWIW,Dalvik VM 从其他线程收集本机堆栈跟踪的方式从这里开始:android.googlesource.com/platform/dalvik/+/kitkat-release/vm/…。该实现正在向其他线程发送信号以使它们进行收集。
  • @fadden:不幸的是,它使用了corkscrew/backtrace.h,这在 NDK 中不可用,而且我听说该库已从 Android 5 中删除。
  • 在相关说明中 - 确保在 CFLAGS 中将 -fno-omit-frame-pointer 传递给编译器以允许跟踪调用图。优化标志(例如-O2)会静默禁用帧指针。
  • @TheCodeArtist:是的,它通过了。正如我已经提到的,即使使用 -O3,我也无法获取调用 dumpBacktrace 的线程的堆栈跟踪。
  • 这是一个几乎只有标题的库,它能够在出现分段错误的情况下很好地打印出堆栈。 github.com/vmarkovtsev/DeathHandler

标签: c++ linux android-ndk arm backtrace


【解决方案1】:

首先,您需要阅读“异步信号安全”功能部分:

http://man7.org/linux/man-pages/man7/signal.7.html

这是在信号处理程序中可以安全调用的整个函数集。关于你能做的最糟糕的事情就是在后台调用任何调用 malloc()/free() 的东西 - 或者自己做。

其次,首先让它在信号处理程序之外工作。

第三,这些可能是恰当的:

How to get C++ backtrace on Android

Android NDK: getting the backtrace

【讨论】:

  • 1. 谢谢。我知道调用malloc() 是不安全的(以及做许多其他事情),但没有办法避免它。此外,该应用程序已经崩溃,最糟糕的事情是我不会记录堆栈跟踪。这就是现在正在发生的事情。 2. 它确实在信号处理程序之外工作,这是我测试的第一件事(显然,PC 是由_Unwind_GetIP 收购的)。 3. 第一个链接似乎无关紧要,第二个链接包含一个有用的答案,我的代码就是基于该答案构建的。
  • 1. 不,最糟糕的是应用程序会死锁并且永远不会退出,除非有什么明确的杀死它。并且有无数种方法可以避免调用 malloc(),甚至是间接调用。 2. 您需要使用 ucontext 起点使其工作。 3. 如果您确实需要堆栈回溯,只需运行system( "pstack PID" ); 以发出当前堆栈跟踪。虽然system() 本身不是异步信号安全的,但它通常建立在fork()/exec() 之上,它们是。或者你可以自己滚动fork()/exec()
  • 2. 我明白了,但是如何?.. 3. 我会尝试,但我严重怀疑它需要 root,即不可接受。
  • 2. 您可能必须使用 google-breakpad 来获取 ucontext:code.google.com/p/google-breakpad 3. 我没有正在运行的 Android立即安装,但我不认为您需要 root 来 pstack 您自己的进程。
  • 2. 我不止一次地看过 Breakpad,它看起来非常复杂,而且工作起来很痛苦(也有点矫枉过正)。 3. 我的问题中的代码中确实有ucontext。我可以从那里获得 SP 以及许多其他寄存器值。但我不知道如何使用它。我最好的主意是在 AOSP 源代码中找到_Unwind_Backtrace 的源代码(它自己的任务),将其代码提取到我的项目中(不是最好的主意,因为它可能无法跨不同的 Android 平台版本移植)并用 SP 代替我从ucontext 得到的那个。
【解决方案2】:

为了获取导致 SIGSEGV 的代码的堆栈跟踪而不是信号处理程序的堆栈跟踪,您必须从 ucontext_t 获取 ARM 寄存器并将它们用于展开。

但是_Unwind_Backtrace() 很难做到。因此,如果您使用 libc++ (LLVM STL) 并为 32 位 ARM 编译,最好尝试预编译的 libunwind,与现代 Android NDK 捆绑在一起(sources/cxx-stl/llvm-libc++/libs/armeabi-v7a/libunwind.a)。这是一个示例代码。


// 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.
}

这是一个示例回溯测试应用程序,其中包含 3 种实现的回溯方法,包括上面显示的方法。

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

【讨论】:

    【解决方案3】:

    作为通过在 arm-linux-eabihf 上工作的信号处理程序(例如从其中抛出异常)展开的一部分,我还从信号处理程序中获得了工作回溯。

    我很确定这是 glibc 特定的,因此不适用于 Android,但也许可以对其进行调整或对灵感有用:https://github.com/mvduin/arm-signal-unwind

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2015-11-16
      • 2013-01-20
      • 1970-01-01
      • 2014-10-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多