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_static
或gnustl_shared
到Application.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.c
和cp-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
tosources/cxx-stl/gabi++/include/unwind.h.NOT
-
sources/cxx-stl/gabi++/include/unwind-arm.h
tosources/cxx-stl/gabi++/include/unwind-arm.h.NOT
-
sources/cxx-stl/gabi++/include/unwind-itanium.h
tosources/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,所以我可能是错的)
- 将数据从一个活动传递到另一个活动,而不是在第二个活动中显示
- android.view.InflateException:二进制XML文件行#12:错误膨胀类<unknown>
- 如何检查定位服务是否启用?
- 如何在默认主题(Theme.Holo.Light)中将ActionBar中心的标题alignment
- 在日志中看到消息:“app:theme is deprecated”?
- 如何在android中以编程方式查找当前正在运行的应用程序?
- 应用程序(22.0.0)和testing应用程序(21.0.3)的已解决版本不同
- 如何在android textview中通过unicode设置emoji
- Android以编程方式设置自定义视图的高度和宽度