如何展开堆栈以获取指定堆栈指针(SP)的回溯? [英] How to unwind the stack to get backtrace for the specified stack pointer (SP)?

查看:69
本文介绍了如何展开堆栈以获取指定堆栈指针(SP)的回溯?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

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

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

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

代码本身:

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) << "
";
        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,并得到了一个奇怪的结果:我得到一个堆栈条目,它是正确的条目 - 首先导致信号的函数.但是它被记录了两次(即使地址相同,所以它不是符号名称查找错误).显然,这还不够好——我需要整个堆栈.而且我想知道这个结果是否仅仅是偶然的(即它通常不应该起作用.

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.

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

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.

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

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.

推荐答案

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

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.

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

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


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


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

这篇关于如何展开堆栈以获取指定堆栈指针(SP)的回溯?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆