Android NDK:获取回溯
Android NDK: getting the backtrace
我正在开发通过NDK与Android协同工作的本机应用程序。当发生崩溃时,我需要调用backtrace()
函数。问题是NDK没有<execinfo.h>
。
有没有其他方法可以找回痕迹?
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());
}
以下是一些工作且完整的代码,通过从Eugene Shapovalov的答案开始实现dump_stack((,并在设备上执行符号查找和C++名称去映射。此解决方案:
- 适用于NDK r10e(您不需要完整的Android AOSP源代码树(
- 不需要任何额外的第三方库(无libunfold、libbacktrace、corkscrew、CallStack(
- 不依赖于设备上安装的任何共享库(例如,在Android 5中被砍掉的开瓶器(
- 不会强迫您将地址映射到开发机器上的符号;所有符号名称都显示在Android设备上的代码中
它使用NDK:中内置的这些设施
- NDK工具链/dirs中的
<unwind.h>
标头(NOT libunvell( dladdr()
<cxxabi.h>
中的__cxxabiv1::__cxa_demangle()
(参见下面的STL端口注释(
到目前为止,我只在一个基于arm的Android 5.1设备上测试了这一点,我只从我的主程序中调用了它(而不是从信号处理器中调用(。我使用的是默认的ndk构建,它为arm平台选择gcc。
请评论,如果你能使这项工作
- 在其他Android操作系统上
- 来自崩溃时的SIGSEGV处理程序(我的目标只是在断言失败时打印堆栈跟踪(
- 使用clang工具集而不是gcc
请注意,r10e NDK在gcc和clang工具集中有许多体系结构的<unwind.h>
代码,因此支持范围很广。
C++符号名称解映射支持依赖于一个__cxxabiv1::__cxa_demangle()
函数,该函数来自NDK中包含的C++STL。如果您使用GNUSTL(Application.mk
中的APP_STL := gnustl_static
或gnustl_shared
;有关更多信息,请参阅本页(进行Android构建,这应该可以正常工作。如果您当前根本不使用STL,只需将APP_STL := gnustl_static
或gnustl_shared
添加到Application.mk
即可。如果你正在使用STLport,你必须享受一种特殊的乐趣(详见下文(。
重要信息:若要使此代码正常工作,您不得使用-fvisibility=hidden
gcc编译器选项(至少在调试构建中(。该选项通常用于在发布版本中隐藏符号以防窥探。
许多人注意到,ndk构建脚本从ndk .so
中剥离符号,同时将其复制到项目的libs/目录中。这是真的(在.so
的两个副本上使用nm
会产生非常不同的结果(然而,令人惊讶的是,这种特殊的剥离层并没有阻止下面的代码工作。不知怎的,即使在剥离后仍然有符号(只要你记得不要用-fvisibility=hidden
编译(。他们带着nm -D
出现。
关于这个主题的其他文章讨论了其他编译器选项,如-funwind-tables
。我没有发现我需要设置任何这样的选项。默认的ndk构建选项有效。
要使用此代码,请将_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缺少来自
<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" } }
第二个问题更令人讨厌。事实证明,在NDK中,
<unwind.h>
不是一种,也不是两种,而是三种不同的、不兼容的类型。你猜对了,STLport中的<unwind.h>
(实际上它在你选择STLport时附带的gabi++库中(是不兼容的。STLport/gabi++includes位于工具链includes之前(请参阅ndk构建输出的-I
选项(,这意味着STLport阻止您使用真正的<unwind.h>
。我找不到比进去破解我安装的NDK:中的文件名更好的解决方案了sources/cxx-stl/gabi++/include/unwind.h
至sources/cxx-stl/gabi++/include/unwind.h.NOT
sources/cxx-stl/gabi++/include/unwind-arm.h
至sources/cxx-stl/gabi++/include/unwind-arm.h.NOT
sources/cxx-stl/gabi++/include/unwind-itanium.h
至sources/cxx-stl/gabi++/include/unwind-itanium.h.NOT
我相信还有一些更优雅的解决方案,但我怀疑切换-I
编译器选项的顺序可能会产生其他问题,因为STL通常希望覆盖工具链包含文件。
享受吧!
backtrace()
是一个非标准的Glibc扩展,即使在ARM上也有点不稳定(我认为你需要用-funwind-tables
构建所有东西,然后有一个新的Glibc?(
据我所知,Android使用的仿生C库中没有包含此功能。
你可以试着把Glibc回溯的来源拉到你的项目中,然后用展开表重建有趣的东西,但这对我来说听起来很难。
如果你有调试信息,你可以尝试用一个附加到你的进程的脚本启动GDB,并以这种方式打印回溯,但我不知道GDB是否适用于Android(尽管Android基本上是Linux,所以id很好,安装细节可能有问题?(。
这里有一个疯狂的单行方法,可以获得非常详细的堆栈跟踪,其中包括C/C++(本机(和Java:滥用JNI
env->FindClass(NULL);
只要你的应用程序是编译调试的,或者以其他方式使用Android的CheckJNI,这个错误的调用就会触发Android的内置JNI检查器,它会在控制台上产生一个华丽的堆栈跟踪(来自"art"日志源(。这个堆栈跟踪是在安卓的libart.so
中完成的,使用了所有最新的技术和铃声,而像我们这样的低NDK用户不容易获得这些技术和铃声
您甚至可以为未编译调试的应用程序启用CheckJNI。有关详细信息,请参阅此谷歌常见问题解答。
我不知道这个技巧在SIGSEGV处理程序中是否有效(从SIGSEDV中,你可能会得到错误堆栈的堆栈跟踪,或者根本不会触发art(,但值得一试。
如果您需要一个在代码中提供堆栈跟踪的解决方案(例如,这样您就可以通过网络发送或记录它(,请参阅我在同一问题中的另一个答案。
您可以使用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++过滤_ZN7android15TimedEventQueue11threadEntryEv_ZN7aandroid15Timed EventQueue13ThreadWrapperPv
android::TimedEventQueue::threadEntry()
android::TimedEventQueue::ThreadWrapper(void*)
以下是如何使用与现代Android NDK(如NDK r16b(捆绑在一起的libunfold在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
如果你只想要几个(例如2-5(最顶层的调用帧,并且你的GCC足够新,你可以考虑使用一些返回地址或帧地址内置。
(但我对Android不太了解,所以我可能错了(
Bionic execinfo.h
头从API 33级(Android 14(开始公开,允许您像在常规Linux上一样在运行时收集回溯。backtrace
的手册页上提供了一个示例:https://man7.org/linux/man-pages/man3/backtrace.3.html
对于旧版本的Android,您可以在以下位置重用代码:https://cs.android.com/android/platform/superproject/+/master:bionic/libc/bionic/execinfo.cpp.
- 当回溯以零开始时,如何调试崩溃
- C++为构建时间获取QDateTime的可靠方法
- lambda参数转换为constexpr技巧,然后获取带链接的数组
- 如何使用 < 和 > 命令获取 c++ 中的输入和输出?
- 使用指针从C++中的数组中获取最大值
- 如何获取std::result_of函数的返回类型
- 如何在openssl-ecc中获取十六进制格式的私钥
- 使用Unreal C++获取VR耳机的世界位置/方向
- 获取日期异步信号安全吗?如果在信号处理程序中使用,它会导致死锁吗
- 从C字符串中获取奇怪的字符串长度
- 为什么我的for循环不能正确获取argv
- 从python中调用C++函数并获取返回值
- 在远程嵌入式设备上使用核心文件的 GDB - 如何获取有关回溯的更多信息?
- 如何展开堆栈以获取指定堆栈指针(SP)的回溯
- 如何同时获取 what() 和回溯跟踪未捕获的异常
- 如何从谷歌测试中的异常中获取回溯信息
- 在展开堆栈之前获取回溯
- c++获取不同线程的回溯
- Android NDK:获取回溯
- 获取静态二进制文件的AOSP回溯