Android NDK: obtener la traza inversa

Estoy desarrollando la aplicación nativa que funciona con Android a través del NDK. Necesito llamar a la función backtrace() cuando hay un locking. El problema es que no hay para el NDK.

¿Hay alguna otra forma de obtener esa traza?

backtrace() es una extensión Glibc no estándar, e incluso algo inestable en ARM (creo que tienes que haber construido todo con -funwind-tables , creo, y luego tienes un Glibc algo nuevo?)

Hasta donde yo sé, esta función no está incluida en la biblioteca Bionic C utilizada por Android.

Podrías intentar introducir la fuente de la traza inversa de Glibc en tu proyecto, y luego reconstruir las cosas interesantes con la tabla de desenrollado, pero a mí me parece un trabajo duro.

Si tiene información de depuración, podría intentar iniciar GDB con un script que se una a su proceso, e imprima una traza inversa de esa manera, pero no tengo idea si GDB funciona en Android (aunque Android es básicamente Linux, así que mucho id bien, los detalles de la instalación pueden ser problemáticos?) Usted puede ir más allá al deshacerse del núcleo de algún modo (¿lo respalda Bionic?) y analizarlo después de los hechos.

Android no tiene backtrace() , pero se unwind.h Está aquí para servir. La simbolización es posible a través de dladdr() .

El siguiente código es mi implementación simple de backtrace (sin demanda):

 #include  #include  #include  #include  namespace { struct BacktraceState { void** current; void** end; }; static _Unwind_Reason_Code unwindCallback(struct _Unwind_Context* context, void* arg) { BacktraceState* state = static_cast(arg); uintptr_t pc = _Unwind_GetIP(context); if (pc) { if (state->current == state->end) { return _URC_END_OF_STACK; } else { *state->current++ = reinterpret_cast(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"; } } 

Se puede utilizar para retroceder en LogCat como

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

Aquí hay un código que funciona y completo que implementa dump_stack () comenzando con la respuesta de Eugene Shapovalov y realiza búsquedas de símbolos y el nombre de C ++ en el dispositivo. Esta solución:

  • funciona con el NDK r10e (no necesita el árbol fuente completo de Android AOSP)
  • NO requiere bibliotecas de terceros adicionales (sin libunwind, libbacktrace, sacacorchos, CallStack)
  • NO depende de ninguna biblioteca compartida que se esté instalando en el dispositivo (p. ej., sacacorchos, que se eliminó en Android 5)
  • NO lo obliga a asignar direcciones a símbolos en su máquina de desarrollo; todos los nombres de símbolos se revelan en el dispositivo Android en su código

Utiliza estas instalaciones, que están integradas en el NDK:

  • encabezado que está en el NDK toolchain / dirs (NO libunwind)
  • dladdr()
  • __cxxabiv1::__cxa_demangle() desde (ver la nota de STLport a continuación)

Hasta ahora, probé esto solo con un dispositivo Android 5.1 basado en el arm y lo llamé solo desde mi progtwig principal (no desde un controlador de señal). Estaba usando el ndk-build predeterminado que elige gcc para la plataforma del arm.

Comente si puede hacer que esto funcione

  • en otros sistemas operativos Android
  • de un controlador SIGSEGV en crash (mi objective era simplemente imprimir un seguimiento de stack en caso de fallo de aserción)
  • utilizar conjuntos de herramientas de clang en lugar de gcc

Tenga en cuenta que el r10e NDK tiene el código para muchas architectures en los conjuntos de herramientas gcc y clang, por lo que el soporte parece amplio.

El soporte de demanda de nombre de símbolo de C ++ depende de una función __cxxabiv1::__cxa_demangle() que proviene del C ++ STL que se incluye con el NDK. Esto debería funcionar como está si estás haciendo tu comstackción de Android con el GNU STL ( APP_STL := gnustl_static o gnustl_shared en Application.mk , mira esta página para más información). Si actualmente no está utilizando ningún STL, simplemente agregue APP_STL := gnustl_static o gnustl_shared a Application.mk . Si está utilizando STLport, debe disfrutar de un tipo especial de diversión (más abajo).

IMPORTANTE: para que este código funcione, no debe usar la -fvisibility=hidden gcc compiler (al menos en sus comstackciones de depuración). Esa opción se usa comúnmente para ocultar símbolos de miradas indiscretas en comstackciones de lanzamiento.

Muchas personas han notado que el script ndk-build elimina los símbolos de su NDK .so mientras los copia al directorio libs / de su proyecto. Eso es cierto (usar nm en las dos copias de .so da resultados muy diferentes) SIN EMBARGO esta capa particular de stripping sorprendentemente no evita que el siguiente código funcione. De alguna manera, incluso después de eliminar, todavía hay símbolos (siempre que recuerde no comstackr con -fvisibility=hidden ). Aparecen con nm -D .

Otras publicaciones sobre este tema han discutido otras opciones del comstackdor como -funwind-tables . No encontré que necesitaba establecer ninguna de esas opciones. Las opciones predeterminadas de ndk-build funcionaron.

Para usar este código, reemplace _my_log() con su función favorita de registro o cadena.

Los usuarios de STLport ven notas especiales a continuación.

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

¿Qué sucede si usa STLport STL en lugar de GNU STL?

Apesta ser tú (y yo). Hay dos problemas:

  • El primer problema es que STLport carece de la __cxxabiv1::__cxa_demangle() desde . Deberá descargar dos archivos fuente cp-demangle.c y cp-demangle.h de este repository y colocarlos en un subdirectorio demangle/ en su fuente, luego haga esto en lugar de #include :

     #define IN_LIBGCC2 1 // means we want to define __cxxabiv1::__cxa_demangle namespace __cxxabiv1 { extern "C" { #include "demangle/cp-demangle.c" } } 
  • El segundo problema es más desagradable. Resulta que no hay uno, ni dos, sino TRES tipos diferentes e incompatibles de en el NDK. Y lo has adivinado, el en STLport (en realidad está en la biblioteca de gabi ++ que viene de paseo cuando eliges STLport) es incompatible. El hecho de que el STLport / gabi ++ incluya venir antes que la cadena de herramientas incluye (ver las opciones -I de su salida ndk-build) significa que STLport le impide usar el real . No pude encontrar ninguna solución mejor que ingresar y piratear los nombres de archivo dentro de mi NDK instalado:

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

Estoy seguro de que hay una solución más elegante, sin embargo, sospecho que cambiar el orden de las opciones de comstackdor -I probablemente creará otros problemas, ya que los STL en general quieren anular los archivos de inclusión de toolchain.

¡Disfrutar!

Este es un método loco de una línea para obtener una traza de stack fantásticamente detallada que incluye tanto C / C ++ (nativo) como Java: abuso de JNI

 env->FindClass(NULL); 

Siempre que su aplicación esté depurada comstackda o use el CheckJNI de Android, esta llamada errónea activará el comprobador JNI incorporado de Android que producirá un magnífico seguimiento de stack en la consola (desde la fuente de registro “artística”). Este seguimiento de stack se realiza dentro de libart.so de Android utilizando todas las últimas tecnologías y campanas y silbatos que no están fácilmente disponibles para los usuarios de NDK humildes como nosotros.

Puede habilitar CheckJNI incluso para aplicaciones que no están comstackdas. Consulte estas Preguntas frecuentes de Google para obtener más información.

No sé si este truco funciona desde un controlador SIGSEGV (desde SIGSEGV puede obtener un rastro de stack de la stack incorrecta, o tal vez el arte no se activará en absoluto), pero vale la pena intentarlo.

Si necesita una solución que haga que el seguimiento de la stack esté disponible en su código (por ejemplo, para poder enviarlo a través de la red o registrarlo), consulte mi otra respuesta en esta misma pregunta.

Puedes usar CallStack:

 #include  void log_backtrace() { CallStack cs; cs.update(2); cs.dump(); } 

Los resultados necesitarán eliminación de datos por c++filt o algo similar:

 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*) 

Si solo quiere unos pocos (por ejemplo, de 2 a 5) marcos de llamadas superiores y si su GCC es lo suficientemente reciente, podría considerar el uso de direcciones internas de direcciones de remitentes o direcciones de remite.

(Pero no sé mucho sobre Android, así que podría estar equivocado)

Aquí se muestra cómo capturar traza inversa en ARM de 32 bits, utilizando libunwind, que se incluye con los NDK modernos de Android (como NDK r16b).


 // 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); } 

Aquí hay una aplicación de prueba de backtrace de muestra con 3 métodos de backtracing implementados, incluido el método que se muestra arriba.

https://github.com/alexeikh/android-ndk-backtrace-test