Android NDK:获得回溯

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

有没有其他的方式来得到回溯?

backtrace()是一个非标准的Glibc扩展,甚至在ARM上有些不稳定(你需要用-funwind-tables创build所有的东西,我想,然后有一个新的Glibc?)

据我所知,这个函数不包含在Android使用的Bionic C库中。

您可以尝试将Glibc回溯源拉到您的项目中,然后用展开表重build有趣的事情,但对我来说听起来像是辛苦的工作。

如果你有debugging信息,你可以尝试启动一个附加到你的进程的脚本的GDB,并以这种方式打印回溯,但我不知道GDB是否可以在Android上工作(尽pipeAndroid基本上是Linux,所以很好,安装的细节可能会有问题?)你可能会进一步倾销核心以某种方式(仿生支持吗?)和事后分析。

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

下面的代码是我简单的backtrace实现(没有demangling):

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

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

  • 与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设备testing了这个,我只从我的主程序(而不是从信号处理程序)调用它。 我使用默认的ndk-build为arm平台select了gcc。

请评论,如果你能做这个工作

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

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

C ++符号名称demangling支持取决于来自NDK中包含的C ++ STL的__cxxabiv1::__cxa_demangle()函数。 如果你正在用GNU STL( APP_STL := gnustl_static或者Application.mk gnustl_shared ;请参阅这个页面以获得更多信息)进行你的Android构build,这应该是按原样工作的。 如果您现在正在使用STL,只需将APP_STL := gnustl_staticgnustl_sharedApplication.mk 。 如果你正在使用STLport,你必须享受一种特殊的乐趣(下面更多)。

重要提示:为了使这个代码正常工作,您不能使用-fvisibility=hidden gcc编译器选项(至less在您的debugging版本中)。 该选项通常用于在发布版本中隐藏符号。

许多人已经注意到,ndk-build脚本会将NDK .so符号复制到项目的libs /目录中。 这是真的(在.so的两个副本上使用nm给出了非常不同的结果)然而,这个特定的剥离层令人惊讶地不能防止下面的代码工作。 不知何故,即使剥离后仍然有符号(只要你记住不用-fvisibility=hidden编译)。 他们用nm -D显示。

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

要使用此代码,请将_my_log()replace为您最喜欢的日志logging或string函数。

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缺less来自<cxxabi.h>__cxxabiv1::__cxa_demangle()调用。 你需要从这个版本库下载两个源文件cp-demangle.ccp-demangle.h ,并把它们放在你的源代码下的demangle/子目录下,然后代替#include <cxxabi.h>

     #define IN_LIBGCC2 1 // means we want to define __cxxabiv1::__cxa_demangle namespace __cxxabiv1 { extern "C" { #include "demangle/cp-demangle.c" } } 
  • 第二个问题更加讨厌。 事实certificateNDK中不是一个,也不是两个,而是三个不同的,不兼容的<unwind.h>types。 你猜对了,STLport里的<unwind.h> (实际上是在gabi ++库里,当你selectSTLport的时候出现)是不兼容的。 STLport / gabi ++包含在工具链包含之前(请参阅您的ndk-build输出的-I选项)意味着STLport阻止您使用真正的<unwind.h> 。 我找不到任何更好的解决scheme,而不是进入我的安装的NDK里面的文件名。

    • sources/cxx-stl/gabi++/include/unwind.h to sources/cxx-stl/gabi++/include/unwind.h.NOT
    • sources/cxx-stl/gabi++/include/unwind-arm.h to sources/cxx-stl/gabi++/include/unwind-arm.h.NOT
    • sources/cxx-stl/gabi++/include/unwind-itanium.h to sources/cxx-stl/gabi++/include/unwind-itanium.h.NOT

我敢肯定有一些更优雅的解决scheme,但是我怀疑切换-I编译器选项的顺序可能会产生其他问题,因为STL通常要覆盖工具链包含文件。

请享用!

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

 env->FindClass(NULL); 

只要你的应用程序编译debugging,或者使用Android的CheckJNI,这个错误的调用将触发Android的内置JNI检查器,这将在控制台(从“艺术”日志源)产生一个华丽的堆栈跟踪。 这个堆栈跟踪是在Android的libart.so完成的,使用所有最新的技术和花里胡哨的工具,这些对于像我们这样的低级NDK用户来说并不容易。

即使对于未编译debugging的应用程序,也可以启用CheckJNI。 查看这个谷歌常见问题的细节。

我不知道这个技巧是否可以从SIGSEGV处理程序(从SIGSEGV获得堆栈跟踪错误的堆栈,或者艺术将不会被触发),但值得一试。

如果你需要一个解决scheme,使你的代码中可用的堆栈跟踪(例如,所以你可以通过networking发送或logging),看到我在这个问题的其他答案。

您可以使用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 

you @ work> $ c ++ filt _ZN7android15TimedEventQueue11threadEntryEv _ZN7android15TimedEventQueue13ThreadWrapperEPv

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

如果你只是想要一些(例如2-5)最上面的调用帧,并且如果你的GCC足够近,你可能会考虑使用一些返回地址或帧地址内置。

(但我不太了解Android,所以我可能是错的)