当Android应用程序崩溃时,我能否获取C++堆栈跟踪?

12

在我的C++代码中,大多数错误会导致应用程序直接退出,没有任何LogCat输出,也没有设备上的消息。空指针和不正确的JNI使用经常导致这种结果,毋庸置疑,这使得调试非常困难。

目前,我可以通过ndk-gdb中的“bt”命令获取堆栈跟踪,但如果崩溃发生在启动后的前2秒内,则无法获取,因为ndk-gdb会在进程启动并附加到它之后才能开始工作。此外,ndk-gdb不可靠,经常会说找不到任何符号,或者抱怨非致命的“SIGILL”错误,例如。

是否有一种方法可以捕获错误并在应用程序崩溃时打印堆栈跟踪或其他信息?例如,如果发生了SIGSEGV,则我想知道应用程序试图访问的地址。


1
请检查此答案。此为 Android 特定的解决方案。 https://dev59.com/bGsz5IYBdhLWcg3wFUG_#28858941 - Behrouz.M
4个回答

4

trace.txt 文件提供了什么信息?我不记得它的位置是在 /data/anr/trace.txt 还是在 /data/data/{pkg}/trace.txt


1

您需要通过捕获SIGSEGV来执行代码,以便在出现segv时执行。这是posix代码,因此类似的代码应该也适用于Android:

void abortHandler( int signum, siginfo_t* si, void* unused )
{
   const char* name = NULL;
   switch( signum )
   {
   case SIGABRT: name = "SIGABRT";  break;
   case SIGSEGV: name = "SIGSEGV";  break;
   case SIGBUS:  name = "SIGBUS";   break;
   case SIGILL:  name = "SIGILL";   break;
   case SIGFPE:  name = "SIGFPE";   break;
   case SIGPIPE: name = "SIGPIPE";  break;
   }

   if ( name )
      printf( stderr, "Caught signal %d (%s)\n", signum, name );
   else 
      printf( stderr, "Caught signal %d\n", signum );

   printStackTrace( stderr );

   exit( signum );
}

void handleCrashes()
{
   struct sigaction sa;
   sa.sa_flags = SA_SIGINFO;
   sa.sa_sigaction = abortHandler;
   sigemptyset( &sa.sa_mask );

   sigaction( SIGABRT, &sa, NULL );
   sigaction( SIGSEGV, &sa, NULL );
   sigaction( SIGBUS,  &sa, NULL );
   sigaction( SIGILL,  &sa, NULL );
   sigaction( SIGFPE,  &sa, NULL );
   sigaction( SIGPIPE, &sa, NULL );
}

下一步是调用该函数来注册信号处理程序。您可以在main函数中的第一件事情就这样做,但是那样的话,直到main函数才能获得堆栈跟踪。如果您想在此之前获得堆栈跟踪,可以从全局对象的构造函数中调用此函数。但是不能保证它将是第一个被调用的构造函数。有方法可以确保它早期被调用。例如,在调试版本中重载operator new,以便在第一次分配时首先初始化堆栈跟踪,然后调用真正的operator new。这将为您提供从第一次分配开始的堆栈跟踪。
要打印堆栈跟踪:
void printStackTrace( unsigned int max_frames = 63 )
{
   void* addrlist[max_frames+1];

   // retrieve current stack addresses
   u32 addrlen = backtrace( addrlist, sizeof( addrlist ) / sizeof( void* ));

   if ( addrlen == 0 ) 
   {
      printf( stderr, "  <empty, possibly corrupt>\n" );
      return;
   }

   char** symbollist = backtrace_symbols( addrlist, addrlen );

   for ( u32 i = 3; i < addrlen; i++ )
      printf( stderr, "%s\n", symbollist[i] ):
}

您需要做更多的工作来解开符号以使它们可读。尝试使用abi::__cxa_demangle。当然,要使用-g进行构建,并使用-rdynamic进行链接。


谢谢。我已经修复了我正在寻找的错误,但下次我一定会尝试这个方法。我不确定我是否理解-rdynamic,虽然我查阅了文档:“在支持它的目标上向ELF链接器传递标志-export-dynamic。这指示链接器将所有符号添加到动态符号表中,而不仅仅是使用的符号。对于某些使用dlopen或允许从程序内部获取回溯的情况,需要此选项。”(同时,-g“在目标的首选格式中打开调试信息”。) - Qwertie
9
该死!回溯(backtrace)应该在execinfo.h文件中,但Android上没有这个文件!(execinfo.h:没有该文件或目录) - Qwertie
3
在Android上没有"backtrace"。 - Violet Giraffe
这段代码仅适用于glibc / Linux。 在Bionic / Android下,您可以使用附带的libunwind实现。 - Raúl Salinas-Monteagudo

1

按照以下说明进行操作:https://developer.android.com/ndk/guides/ndk-stack

也就是说:

  1. 进入您的libnative-lib.so(或其他名称)文件所在的文件夹(对于我来说,是/app/build/intermediates/cxx/RelWithDebInfo/5ww1v5k5/obj/arm64-v8a);确保选择方便的文件夹(即与您的测试设备处理器架构相对应的文件夹)
  2. 将logcat的内容复制/粘贴到一个txt文件中(例如foo.txt);确保此日志包含您的崩溃 :) 将类似于以下内容:

--------- 崩溃开始 2021-12-22 11:01:37.533 7268-11335/? A/libc: Fatal signal 11 (SIGSEGV),code 1 (SEGV_MAPERR),fault addr 0x1 in tid 11335 (Thread-87),pid 7268 等等...(之后有很多行)

  1. 将foo.txt文件放在与libnative-lib.so文件夹相同的文件夹中。
  2. 确保你在/my_user_name/.bash_profile文件中设置了NDK路径,例如:

export ANDROID_NDK=/Users/my_user_name/Library/Android/sdk/ndk/22.1.7171670

  1. 回到包含架构文件夹的文件夹中(在我的情况下是..../5ww1v5k5/obj),在终端中输入:

    $ANDROID_NDK/ndk-stack -sym arm64-v8a -dump arm64-v8a/foo.txt

终端将生成一个可读的堆栈跟踪。


-2

是的,在那里不存在 'execinfo.h',但是有 CallStack:

#include <utils/CallStack.h>
..
CallStack cs;
cs.dump();

希望它能在这样的信号处理程序中有所帮助。

我一定漏掉了什么:致命错误:utils/CallStack.h:没有那个文件或目录 #include <utils/CallStack.h> --- 或许还需要在Android.mk中添加其他内容? - Fellow Traveler
3
NDK文件夹中没有CallStack.h文件! - Violet Giraffe
https://android.googlesource.com/platform/system/core.git/+/master/include/utils/CallStack.hhttps://android.googlesource.com/platform/frameworks/native/+/jb-dev/include/utils/CallStack.h - Vladimir Kunschikov
我能看到处理程序被调用了,但是CallStack没有在日志中打印任何内容。这怎么可能呢? - fsquirrel

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接