web-dev-qa-db-fra.com

Android NDK: obtenir la trace

Je développe l'application native qui fonctionne avec Android via le NDK . Je dois appeler la fonction backtrace() en cas de panne. Le problème est qu'il n'y a pas de <execinfo.h> pour le NDK. 

Y a-t-il un autre moyen de récupérer cette trace?

40
givi

backtrace() est une extension Glibc non standard, et même alors quelque peu fragile sur ARM (vous devez avoir tout construit avec -funwind-tables, je pense, et ensuite avoir une Glibc quelque peu nouvelle?)

Pour autant que je sache, cette fonction n'est pas incluse dans la bibliothèque Bionic C utilisée par Android.

Vous pouvez essayer d'extraire le code source de Glibc dans votre projet, puis de reconstruire les éléments intéressants avec la table de déroulement, mais cela me semble être un travail difficile.

Si vous avez des informations de débogage, vous pouvez essayer de lancer GDB avec un script qui s’attache à votre processus et d’imprimer une trace de cette façon, mais je n’ai aucune idée si GDB fonctionne sous Android (bien qu’Android soit essentiellement Linux, les détails de l'installation peuvent être problématiques?) Vous pouvez aller plus loin en vidant core de quelque manière que ce soit (Bionic le supporte-t-il?) et en l'analysant après coup.

17
ams

Android n'a pas backtrace(), mais unwind.h est là pour servir. La symbolisation est possible via dladdr().

Le code suivant est ma simple implémentation de backtrace (sans démêlage):

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

Il peut être utilisé pour revenir en arrière dans LogCat comme

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

Voici une méthode folle à une ligne pour obtenir une trace de pile incroyablement détaillée incluant à la fois C/C++ (natif) et Java: abuse JNI

env->FindClass(NULL);

Tant que votre application est compilée pour le débogage, ou utilise autrement CheckJNI d'Android, cet appel erroné déclenchera le vérificateur JNI intégré d'Android qui produira une superbe trace de pile sur la console (à partir de la source de journal "art"). Cette trace de pile est effectuée à l'intérieur du libart.so d'Android à l'aide des toutes dernières technologies, ainsi que de nombreuses fonctionnalités qui ne sont pas facilement disponibles pour les utilisateurs de NDK comme nous.

Vous pouvez activer CheckJNI même pour les applications non déboguées compilées. Voir this google FAQ pour plus de détails.

Je ne sais pas si cette astuce fonctionne avec un gestionnaire SIGSEGV (de SIGSEGV, vous obtiendrez peut-être une trace de pile d'une pile incorrecte, ou peut-être que l'art ne sera pas déclenché du tout), mais cela vaut la peine d'essayer.

Si vous avez besoin d’une solution qui rend la trace de pile disponible dans votre code (par exemple, pour pouvoir l’envoyer sur le réseau ou la journaliser), consultez mon autre réponse à la même question.

6
Louis Semprini

Vous pouvez utiliser le CallStack:

#include <utils/CallStack.h>

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

Les résultats devront être réduits de c++filt ou quelque chose de similaire: 

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

vous travaillez> $ c ++ filt _ZN7Android15TimedEventQueue11threadEntryEv _ZN7Android15TimedEventQueue13ThreadWrapperEPv 

    Android::TimedEventQueue::threadEntry()
    Android::TimedEventQueue::ThreadWrapper(void*)
5
Vladimir Kunschikov

Si vous ne voulez que quelques trames d’appel (par exemple, 2 à 5) et si votre GCC est suffisamment récent, vous pouvez envisager d’utiliser une adresse intégrée ou une adresse de trame.

(Mais je ne connais pas grand chose sur Android, alors je peux me tromper)

1

Voici comment capturer la trace de trace sur ARM 32 bits, à l'aide de libunwind, fournie avec les NDK Android modernes (tels que le 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);
}

Voici un exemple d’application de test de traçage avec 3 méthodes de traçage implémentées, y compris la méthode présentée ci-dessus.

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

0
Alexei Khlebnikov