2015-04-10 13 views
26

Sto scrivendo questo per Android (solo ARM), ma credo che il principio sia lo stesso anche per Linux generico.Come srotolare lo stack per ottenere il backtrace per il puntatore dello stack specificato (SP)?

Sto cercando di acquisire la traccia dello stack dall'interno del gestore di segnale, in modo che possa effettuare il log quando la mia app si arresta in modo anomalo. Questo è quello che ho trovato usando <unwind.h>.
inizializzazione:

struct sigaction signalhandlerDescriptor; 
memset(&signalhandlerDescriptor, 0, sizeof(signalhandlerDescriptor)); 
signalhandlerDescriptor.sa_flags = SA_SIGINFO; 
signalhandlerDescriptor._u._sa_sigaction = signalHandler; 
sigaction(SIGSEGV, &signalhandlerDescriptor, 0); 

Il codice stesso:

struct BacktraceState 
{ 
    void** current; 
    void** end; 
    void* pc; 
}; 

inline _Unwind_Reason_Code unwindCallback(struct _Unwind_Context* context, void* arg) 
{ 
    BacktraceState* state = static_cast<BacktraceState*>(arg); 
    state->pc = (void*)_Unwind_GetIP(context); 
    if (state->pc) 
    { 
     if (state->current == state->end) 
      return _URC_END_OF_STACK; 
     else 
      *state->current++ = reinterpret_cast<void*>(state->pc); 
    } 
    return _URC_NO_REASON; 
} 

inline size_t captureBacktrace(void** addrs, size_t max, unsigned long pc) 
{ 
    BacktraceState state = {addrs, addrs + max, (void*)pc}; 
    _Unwind_Backtrace(unwindCallback, &state); 
    personality_routine(); 

    return state.current - addrs; 
} 

inline void dumpBacktrace(std::ostream& os, void** addrs, size_t count) 
{ 
    for (size_t idx = 0; idx < count; ++idx) { 
     const void* addr = addrs[idx]; 
     const char* symbol = ""; 

     Dl_info info; 
     if (dladdr(addr, &info) && info.dli_sname) { 
      symbol = info.dli_sname; 
     } 

     int status = -3; 
     char * demangledName = abi::__cxa_demangle(symbol, 0, 0, &status); 
     os << "#" << idx << ": " << addr << " " << (status == 0 ? demangledName : symbol) << "\n"; 
     free(demangledName); 
    } 
} 

void signalHandler(int sig, siginfo_t *siginfo, void *uctx) 
{ 
    ucontext * context = (ucontext*)uctx; 
    unsigned long PC = context->uc_mcontext.arm_pc; 
    unsigned long SP = context->uc_mcontext.arm_sp; 

    Logger() << __PRETTY_FUNCTION__ << "Fatal signal:" << sig; 
    const size_t maxNumAddresses = 50; 
    void* addresses[maxNumAddresses]; 
    std::ostringstream oss; 

    const size_t actualNumAddresses = captureBacktrace(addresses, maxNumAddresses, PC); 
    dumpBacktrace(oss, addresses, actualNumAddresses); 
    Logger() << oss.str(); 
    exit(EXIT_FAILURE); 
} 

Problema: se ottengo il registro PC chiamando _Unwind_GetIP(context) in unwindCallback, ottengo la traccia completa per il gestore del segnale pila. Che è uno stack separato, e ovviamente non è quello che voglio. Così ho provato a fornire il PC prelevato dal gestore di segnale ucontext e ho ottenuto un risultato strano: ottengo una voce di stack, è la voce corretta - la funzione che ha causato il segnale in primo luogo. Ma è registrato due volte (anche l'indirizzo è lo stesso, quindi non è un nome simbolico per cercare bug). Ovviamente, non è abbastanza buono - ho bisogno dell'intero stack. E mi chiedo se questo risultato è puramente accidentale (vale a dire che non dovrebbe funzionare in generale.

Ora, ho letto ho bisogno di fornire anche lo stack pointer, che mi pare di poter ottenere da ucontext, stesso PC. Ma io non so cosa fare con esso. Devo rilassarsi manualmente invece di usare _Unwind_Backtrace? Se sì, puoi darmi codice di esempio? Ho cercato la parte migliore di un giorno, e ancora non riuscivo a trovare qualcosa che potrebbe copiare e incollare nel mio progetto.

Per quello che vale, ecco la fonte libunwind che contiene _Unwind_Backtrace definizione. Pensavo di poter capire qualcosa se vedo la sua fonte, ma è molto più complicato di quanto mi aspettassi.

+0

FWIW, il modo in cui la Dalvik VM raccoglie tracce di stack nativi da altri thread inizia qui: https://android.googlesource.com /platform/dalvik/+/kitkat-release/vm/interp/Stack.cpp#1389. L'implementazione sta inviando segnali ad altri thread per indurli a fare la raccolta. – fadden

+0

@fadden: sfortunatamente utilizza 'corkscrew/backtrace.h', che non è disponibile nell'NDK e ho sentito che la libreria è stata rimossa da Android 5. –

+0

Su una nota correlata - Assicurati che' -fno- omit-frame-pointer' è passato nel 'CFLAGS' al compilatore per permettere di tracciare il grafo delle chiamate. I puntatori di frame saranno disattivati ​​automaticamente dai flag di ottimizzazione (ad esempio. -O2'). – TheCodeArtist

risposta

1

In primo luogo, è necessario leggere la sezione sulle funzioni "segnale asincrono sicuri":

http://man7.org/linux/man-pages/man7/signal.7.html

Questo è l'intero set di funzioni che sono sicuri di chiamare un gestore di segnale. La cosa peggiore che puoi fare è chiamare tutto ciò che chiama malloc()/free() sotto il cofano - o farlo da solo.

In secondo luogo, farlo funzionare prima di un gestore di segnale.

In terzo luogo, questi sono probabilmente a proposito:

How to get C++ backtrace on Android

Android NDK: getting the backtrace

+0

** 1. ** Grazie. Sono consapevole che chiamare 'malloc()' non è sicuro (oltre a fare molte altre cose), ma non c'è modo di evitarlo. Inoltre, l'app si è già bloccata, la cosa peggiore che può succedere è che non otterrò la traccia dello stack registrata. Che cosa sta succedendo adesso. ** 2. ** Funziona al di fuori del gestore del segnale, è stata la prima cosa che ho testato (con PC acquisito da '_Unwind_GetIP', ovviamente). ** 3. ** Il primo collegamento sembra irrilevante, il secondo contiene una risposta utile su cui è costruito il mio codice. –

+3

** 1. ** No, la cosa peggiore è che l'app si bloccherà e non uscirà mai a meno che qualcosa non lo uccida esplicitamente. E ci sono infiniti modi per evitare di chiamare malloc(), anche indirettamente. ** 2. ** È necessario farlo funzionare utilizzando un punto di partenza di ucontext. ** 3. ** Se è davvero necessario il backtrace dello stack, basta eseguire 'system (" pstack PID ");' per emettere la traccia dello stack corrente. Mentre 'system()' non è async-signal-safe, di solito è costruito su 'fork()/exec()', che sono. Oppure puoi lanciare il tuo 'fork()/exec()'. –

+0

** 2. ** Capisco, ma COME? .. ** 3. ** Ci proverò, ma sospetto seriamente che richieda root, il che è inaccettabile. –

Problemi correlati