Могу ли я получить трассировку стека С++ при сбое приложения Android?

Большинство ошибок, возникающих в моем коде на С++, заставляют приложение просто выйти, без каких-либо выходных данных LogCat, и на устройстве нет сообщений. Нулевые указатели и некорректное использование JNI часто дают этот результат, и, разумеется, он очень сильно отлаживает работу.

В настоящее время я могу получить трассировку стека с командой "bt" в ndk-gdb, но не в случае сбоя в течение первых 2 секунд запуска, потому что ndk-gdb запускает процесс и присоединяется к нему после его запуска, Кроме того, ndk-gdb ненадежен, часто говорит, что он не может найти никаких символов или жаловаться на нефатальные ошибки "SIGILL", например.

Есть ли способ уловить ошибку и распечатать трассировку стека или другую информацию при сбое приложения? Например, если был SIGSEGV, я хотел бы узнать, к какому адресу приложение пыталось получить доступ.

Ответ 1

Файл

trace.txt дает что-то? Я не помню, было ли его местоположение: /data/anr/trace.txt или /data/data/{pkg}/trace.txt

Ответ 2

Вам нужно начать с захвата 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 );
}

Следующее - вызвать эту функцию для регистрации обработчиков сигналов. Вы можете сделать это как первое в главном, но тогда вы не получите трассировки стека до основного. Если вы хотите их раньше, вы можете вызвать эту функцию из конструктора глобального объекта. Но нет никакой гарантии, что это будет первый вызываемый конструктор. Есть способы убедиться, что он вызван раньше. Например, оператор перегрузки 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.

Ответ 3

Да, "execinfo.h" там не существует, но CallStack делает:

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

Надеюсь, что это поможет в таком обработчике сигналов.