Android NDK: obteniendo el backtrace


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 bloqueo. El problema es que no hay <execinfo.h> para el NDK.

¿Hay alguna otra forma de obtener ese rastro?

Author: givi, 2011-11-14

7 answers

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

Por lo que sé, esta función no está incluida en la biblioteca Bionic C utilizada por Android.

Usted podría intentar tirar de la fuente para Glibc backtrace en su proyecto, y luego reconstruir las cosas interesantes con la mesa de desenrollado, pero suena como un trabajo duro para mí.

Si tiene información de depuración, usted podría intentar lanzar GDB con un script que se adjunta a su proceso, e imprime una traza inversa de esa manera, pero no tengo idea si GDB funciona en Android (aunque Android es básicamente Linux, por lo que mucho id bien, los detalles de instalación pueden ser problemáticos?) Usted puede llegar más lejos por el dumping de núcleo de alguna manera (¿Bionic apoyo que?) y analizarlo después de los hechos.

 16
Author: ams,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-11-28 11:36:28

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

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

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

Se puede usar para rastrear hacia atrás en LogCat como

#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());
}
 31
Author: Eugene Shapovalov,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-09-18 15:25:58

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

  • funciona con el NDK r10e (no necesita el árbol de fuentes completo de Android AOSP)
  • NO requiere ninguna biblioteca adicional de terceros (no libunwind, libbacktrace, corkscrew, CallStack)
  • NO depende de que se instale ninguna biblioteca compartida en el dispositivo (por ejemplo, sacacorchos, que se eliminó en Android 5)
  • NO le 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:

  • <unwind.h> encabezado que está en la cadena de herramientas NDK / dirs (NO libunwind)
  • dladdr()
  • __cxxabiv1::__cxa_demangle() de <cxxabi.h> (véase la nota de STLPort más abajo)

Hasta ahora, he probado esto solo con un brazo Dispositivo Android 5.1 y lo llamé solo desde mi programa principal (no desde un controlador de señal). Estaba usando el ndk-build predeterminado que elige gcc para la plataforma arm.

Por favor, comente si es capaz de hacer que esto funcione

  • en otros sistemas operativos Android
  • de un controlador SIGSEGV en un accidente (mi objetivo era simplemente imprimir un seguimiento de pila en un fallo de aserción)
  • usando conjuntos de herramientas clang en lugar de gcc

Tenga en cuenta que el NDK r10e tiene <unwind.h> código para muchos arquitecturas en conjuntos de herramientas gcc y clang para que el soporte se vea amplio.

El soporte de desmangling del nombre del símbolo de C++ depende de una función __cxxabiv1::__cxa_demangle() que proviene del STL de C++ que se incluye con el NDK. Esto debería funcionar como está si está haciendo su compilación de Android con la GNU STL (APP_STL := gnustl_static o gnustl_shared en Application.mk; vea esta página para más información). Si actualmente no usa STL, simplemente agregue APP_STL := gnustl_static o gnustl_shared a Application.mk. Si está utilizando STLPort, usted tiene que disfrutar de un tipo especial de diversión (más abajo).

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

Mucha gente ha notado que el script ndk-build elimina símbolos de tu NDK .so mientras lo copias al directorio libs/ de tu proyecto. Eso es cierto (usando nm en las dos copias de la .so da resultados muy diferentes) SIN EMBARGO esta capa particular de stripping sorprendentemente no impide que el código de abajo funcione. De alguna manera, incluso después de la extracción todavía hay símbolos (siempre y cuando se acordó de no compilar con -fvisibility=hidden). Aparecen con nm -D.

Otras publicaciones sobre este tema han discutido otras opciones de compilador como -funwind-tables. No encontré que necesitara establecer tal opción. Las opciones predeterminadas de ndk-build funcionaron.

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

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

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

¿Qué pasa si está utilizando STLPort STL en lugar de GNU STL?

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

  • El primer problema es que STLPort carece de la llamada __cxxabiv1::__cxa_demangle() desde <cxxabi.h>. Necesitará descargar dos archivos fuente cp-demangle.c y cp-demangle.h desde este repositorio y colocarlos en un subdirectorio demangle/ debajo de su fuente, luego haga esto en lugar de #include <cxxabi.h>:

    #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, no dos, sino TRES tipos diferentes e incompatibles de <unwind.h> en el NDK. Y lo adivinaste, el <unwind.h> en STLPort (en realidad está en la biblioteca gabi++ que viene a lo largo de un paseo cuando se elige STLPort) es incompatible. El hecho de que STLPort / gabi++ includes esté antes que la toolchain includes (consulte las opciones -I de su salida ndk-build) significa que STLPort le impide usar lo real <unwind.h>. No pude encontrar ninguna solución mejor que entrar y hackear 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 alguna solución más elegante, sin embargo sospecho que cambiar el orden de las opciones del compilador -I probablemente creará otros problemas, ya que los STL generalmente quieren anular los archivos de inclusión de la cadena de herramientas.

Disfrute!

 19
Author: Louis Semprini,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-02-23 18:50:09

Aquí hay un método loco de una línea para obtener un seguimiento de pila fantásticamente detallado que incluye C / C++ (nativo) y Java: abuse JNI

env->FindClass(NULL);

Siempre y cuando su aplicación se compila de depuración, o de lo contrario utiliza CheckJNI de Android, esta llamada errónea activará el comprobador JNI incorporado de Android que producirá un magnífico seguimiento de pila en la consola (desde la fuente de registro "arte"). Este seguimiento de pila se realiza dentro de Android libart.so utilizando todas las últimas tecnologías y campanas y silbatos que no están fácilmente disponibles para los usuarios humildes de NDK como nosotros.

Puede habilitar CheckJNI incluso para aplicaciones que no están depuradas compiladas. Vea esta FAQ de Google para más detalles.

No sé si este truco funciona desde un controlador de SIGSEGV (de SIGSEGV puede obtener un rastro de pila de la pila 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 pila esté disponible en su código (por ejemplo, para que pueda enviarlo a través de la red o el registro it), ver mi otra respuesta en esta misma pregunta.

 6
Author: Louis Semprini,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-02-23 19:12:53

Puedes usar el CallStack:

#include <utils/CallStack.h>

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

Los resultados tendrán que ser deshilachados por c++filt o algo similar:

D/CallStack( 2277): #08  0x0x40b09ac8: <_ZN7android15TimedEventQueue11threadEntryEv>+0x0x40b09961
D/CallStack( 2277): #09  0x0x40b09b0c: <_ZN7android15TimedEventQueue13ThreadWrapperEPv>+0x0x40b09af9

You@work > fil c++filt _ZN7android15TimedEventQueue11threadEntryEv _ZN7android15TimedEventQueue13ThreadWrapperEPv

    android::TimedEventQueue::threadEntry()
    android::TimedEventQueue::ThreadWrapper(void*)
 5
Author: Vladimir Kunschikov,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-02-11 16:13:14

Si solo desea unos pocos (por ejemplo, 2 - 5) marcos de llamada más altos y si su GCC es lo suficientemente reciente, puede considerar el uso de algunos dirección de retorno o builtins de dirección de marco.

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

 1
Author: Basile Starynkevitch,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-11-13 22:48:10

Aquí es cómo capturar backtrace en ARM de 32 bits, utilizando libunwind, que se incluye con NDKs Android modernos (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 backtrace implementados, incluido el método mostrado anteriormente.

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

 0
Author: Alexei Khlebnikov,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-05-12 17:36:38