Android NDK:获得回溯

时间:2011-11-13 22:18:20

标签: android c++ c android-ndk

我正在开发通过NDK与Android配合使用的本机应用程序。 发生崩溃时我需要调用backtrace()函数。问题是NDK没有<execinfo.h>

还有其他方法可以获得回溯吗?

7 个答案:

答案 0 :(得分:36)

Android没有backtrace(),但unwind.h就在这里。可以通过dladdr()进行符号化。

以下代码是我对回溯的简单实现(没有解码):

#include <iostream>
#include <iomanip>

#include <unwind.h>
#include <dlfcn.h>

namespace {

struct BacktraceState
{
    void** current;
    void** end;
};

static _Unwind_Reason_Code unwindCallback(struct _Unwind_Context* context, void* arg)
{
    BacktraceState* state = static_cast<BacktraceState*>(arg);
    uintptr_t pc = _Unwind_GetIP(context);
    if (pc) {
        if (state->current == state->end) {
            return _URC_END_OF_STACK;
        } else {
            *state->current++ = reinterpret_cast<void*>(pc);
        }
    }
    return _URC_NO_REASON;
}

}

size_t captureBacktrace(void** buffer, size_t max)
{
    BacktraceState state = {buffer, buffer + max};
    _Unwind_Backtrace(unwindCallback, &state);

    return state.current - buffer;
}

void dumpBacktrace(std::ostream& os, void** buffer, size_t count)
{
    for (size_t idx = 0; idx < count; ++idx) {
        const void* addr = buffer[idx];
        const char* symbol = "";

        Dl_info info;
        if (dladdr(addr, &info) && info.dli_sname) {
            symbol = info.dli_sname;
        }

        os << "  #" << std::setw(2) << idx << ": " << addr << "  " << symbol << "\n";
    }
}

它可以用于回溯到LogCat中,如

#include <sstream>
#include <android/log.h>

void backtraceToLogcat()
{
    const size_t max = 30;
    void* buffer[max];
    std::ostringstream oss;

    dumpBacktrace(oss, buffer, captureBacktrace(buffer, max));

    __android_log_print(ANDROID_LOG_INFO, "app_name", "%s", oss.str().c_str());
}

答案 1 :(得分:20)

这是一些工作和完整的代码,通过从Eugene Shapovalov的回答开始实现dump_stack(),并在设备上进行符号查找和C ++名称解码。这个解决方案:

  • 适用于NDK r10e(您不需要完整的Android AOSP源代码树)
  • 不需要任何额外的第三方库(没有libunwind,libbacktrace,corkscrew,CallStack)
  • 不依赖于设备上安装的任何共享库(例如,在Android 5中被砍掉的开瓶器)
  • 不会强制您将地址映射到开发计算机上的符号;所有符号名称都在代码中的Android设备上显示

它使用内置于NDK中的这些工具:

    NDK工具链/ dirs(不是libunwind)中的
  • <unwind.h>标题
  • dladdr()
  • 来自__cxxabiv1::__cxa_demangle()
  • <cxxabi.h>(请参阅下面的STLport说明)

到目前为止,我只使用基于arm的Android 5.1设备测试了这个,我只能从我的主程序(而不是信号处理程序)调用它。我使用默认的ndk-build,为arm平台选择gcc。

如果能够完成这项工作,请发表评论

  • 在其他Android操作系统上
  • 来自崩溃的SIGSEGV处理程序(我的目标只是在断言失败时打印堆栈跟踪)
  • 使用clang工具集代替gcc

请注意,r10e NDK在gcc和clang工具集中的许多体系结构都有<unwind.h>代码,因此支持看起来很广泛。

C ++符号名称demangling支持依赖于来自NDK附带的C ++ STL的__cxxabiv1::__cxa_demangle()函数。如果您使用GNU STL(APP_STL := gnustl_staticgnustl_shared Application.mk进行Android构建,则应该按原样运行;有关详细信息,请参阅this page。如果您根本不使用STL,只需将APP_STL := gnustl_staticgnustl_shared添加到Application.mk即可。如果您正在使用STLport,您必须享受一种特殊的乐趣(更多信息如下)。

重要:要使此代码生效,您不能使用-fvisibility=hidden gcc编译器选项(至少在您的调试版本中)。该选项通常用于在发布版本中隐藏符号以防止窥探。

许多人都注意到ndk-build脚本会将NDK .so中的符号删除,同时将其复制到项目的libs /目录中。这是真的(在nm的两个副本上使用.so给出了截然不同的结果)然而,这个特定的剥离层令人惊讶地不会阻止下面的代码工作。不知怎的,即使在剥离之后仍然存在符号(只要你记得不用-fvisibility=hidden编译)。他们出现nm -D

本主题的其他帖子讨论了其他编译​​器选项,如-funwind-tables。我没有发现我需要设置任何这样的选项。默认的ndk-build选项有效。

要使用此代码,请将_my_log()替换为您喜欢的日志记录或字符串函数。

STLport用户会看到以下特殊说明。

#include <unwind.h>
#include <dlfcn.h>
#include <cxxabi.h>

struct android_backtrace_state
{
    void **current;
    void **end;
};

_Unwind_Reason_Code android_unwind_callback(struct _Unwind_Context* context, 
                                            void* arg)
{
    android_backtrace_state* state = (android_backtrace_state *)arg;
    uintptr_t pc = _Unwind_GetIP(context);
    if (pc) 
    {
        if (state->current == state->end) 
        {
            return _URC_END_OF_STACK;
        } 
        else 
        {
            *state->current++ = reinterpret_cast<void*>(pc);
        }
    }
    return _URC_NO_REASON;
}

void dump_stack(void)
{
    _my_log("android stack dump");

    const int max = 100;
    void* buffer[max];

    android_backtrace_state state;
    state.current = buffer;
    state.end = buffer + max;

    _Unwind_Backtrace(android_unwind_callback, &state);

    int count = (int)(state.current - buffer);

    for (int idx = 0; idx < count; idx++) 
    {
        const void* addr = buffer[idx];
        const char* symbol = "";

        Dl_info info;
        if (dladdr(addr, &info) && info.dli_sname) 
        {
            symbol = info.dli_sname;
        }
        int status = 0; 
        char *demangled = __cxxabiv1::__cxa_demangle(symbol, 0, 0, &status); 

        _my_log("%03d: 0x%p %s",
                idx,
                addr,
                (NULL != demangled && 0 == status) ?
                demangled : symbol);

        if (NULL != demangled)
            free(demangled);        
    }

    _my_log("android stack dump done");
}

如果您使用STLport STL而不是GNU STL怎么办?

很难成为你(和我)。有两个问题:

  • 第一个问题是STLport缺少来自__cxxabiv1::__cxa_demangle()的{​​{1}}来电。您需要从this repository下载两个源文件<cxxabi.h>cp-demangle.c,并将它们放在源代码下的cp-demangle.h子目录中,然后执行此操作而不是demangle/

    #include <cxxabi.h>
  • 第二个问题更令人讨厌。事实证明NDK中不是一个,而是两个,而是三个不同的,不兼容的#define IN_LIBGCC2 1 // means we want to define __cxxabiv1::__cxa_demangle namespace __cxxabiv1 { extern "C" { #include "demangle/cp-demangle.c" } } 类型。而且你猜对了,STLport中的<unwind.h>(实际上它是你选择STLport时出现的gabi ++库)是不兼容的。 STLport / gabi ++包含在工具链包含之前的事实(参见你的ndk-build输出&#39; s <unwind.h>选项)意味着STLport阻止你使用真正的-I。我找不到更好的解决方案,而不是进入并破解我安装的NDK中的文件名:

    • <unwind.h>sources/cxx-stl/gabi++/include/unwind.h
    • sources/cxx-stl/gabi++/include/unwind.h.NOTsources/cxx-stl/gabi++/include/unwind-arm.h
    • sources/cxx-stl/gabi++/include/unwind-arm.h.NOTsources/cxx-stl/gabi++/include/unwind-itanium.h

我确定有一些更优雅的解决方案,但我怀疑切换sources/cxx-stl/gabi++/include/unwind-itanium.h.NOT编译器选项的顺序可能会产生其他问题,因为STL通常希望覆盖工具链包含文件。

享受!

答案 2 :(得分:18)

backtrace()是一个非标准的Glibc扩展,甚至在ARM上有些不稳定(你需要用-funwind-tables构建所有内容,我想,然后有一个新的Glibc?)< / p>

据我所知,此功能未包含在Android使用的Bionic C库中。

您可以尝试将Glibc回溯的源代码拉入您的项目,然后使用展开表重建有趣的东西,但这对我来说听起来很辛苦。

如果您有调试信息,您可以尝试使用附加到您的进程的脚本启动GDB,并以这种方式打印回溯,但我不知道GDB是否适用于Android(尽管Android基本上是Linux,所以那么多很好,安装细节可能有问题吗?)你可以通过某种方式倾销核心(Bionic支持那个吗?)并在事后分析它进一步。

答案 3 :(得分:6)

这是一个疯狂的单行方法,用于获取包含C / C ++(本机)和Java的非常详细的堆栈跟踪:滥用JNI

Console.Write(value);

只要你的应用程序是编译调试,或者使用Android的CheckJNI,这个错误的调用将触发Android的内置JNI检查器,它将在控制台上产生一个华丽的堆栈跟踪(来自“art”日志源)。这个堆栈跟踪是在Android的$('#show').on("click", function() { var files = $("#your-files")[0].files[0]; var temp = ""; temp += "<br>Filename: " + files.name; temp += "<br>Type: " + files.type; temp += "<br>Size: " + files.size + " bytes"; $('#details').html(temp); });内完成的,使用了所有最新技术以及那些像我们这样的低端NDK用户不易获得的铃声和口哨声。

即使是未编译调试的应用,也可以启用CheckJNI。有关详细信息,请参阅this google FAQ

我不知道这个技巧是否适用于SIGSEGV处理程序(来自SIGSEGV,您可能会得到错误堆栈的堆栈跟踪,或者艺术根本不会被触发)但是值得一试。

如果您需要一个能够在您的代码中使用堆栈跟踪的解决方案(例如,您可以通过网络发送或记录它),请在同一问题中查看我的其他答案。

答案 4 :(得分:5)

您可以使用CallStack:

#include <utils/CallStack.h>

void log_backtrace()
{
    CallStack cs;
    cs.update(2);
    cs.dump();
}

结果需要c++filt或类似的东西去除:

D/CallStack( 2277): #08  0x0x40b09ac8: <_ZN7android15TimedEventQueue11threadEntryEv>+0x0x40b09961
D/CallStack( 2277): #09  0x0x40b09b0c: <_ZN7android15TimedEventQueue13ThreadWrapperEPv>+0x0x40b09af9

你@ work&gt; $ c ++ filt _ZN7android15TimedEventQueue11threadEntryEv _ZN7android15TimedEventQueue13ThreadWrapperEPv

    android::TimedEventQueue::threadEntry()
    android::TimedEventQueue::ThreadWrapper(void*)

答案 5 :(得分:1)

如果您只想要一些(例如2 - 5)个最顶层的呼叫帧,并且如果您的GCC足够近,您可以考虑使用一些return address or frame address builtins.

(但我对Android知之甚少,所以我错了)

答案 6 :(得分:0)

以下是使用与现代Android NDK(例如NDK r16b)捆绑在一起的libunwind在32位ARM上捕获回溯的方法。

// 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/
#include "libunwind.h"

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;

        // 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,
    // the address of the instruction which caused the crash.
    // Thus let's add this address manually.
    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);

    exit(0);
}

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

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