Come si può prendere una traccia di stack in C?

So che non esiste una funzione C standard per farlo. Mi stavo chiedendo quali sono le tecniche per questo su Windows e * nix? (Windows XP è il mio sistema operativo più importante per farlo ora).

Lo abbiamo usato per i nostri progetti:

https://www.codeproject.com/kb/threads/stackwalker.aspx

Il codice è un po ‘confuso, ma funziona bene. Solo per Windows.

C’è backtrace () e backtrace_symbols ():

Dalla pagina man:

  #include  #include  ... void* callstack[128]; int i, frames = backtrace(callstack, 128); char** strs = backtrace_symbols(callstack, frames); for (i = 0; i < frames; ++i) { printf("%s\n", strs[i]); } free(strs); ... 

Un modo per usarlo in un modo più conveniente / OOP è quello di salvare il risultato di backtrace_symbols () in un costruttore di classi di eccezioni. Quindi, ogni volta che lanci quel tipo di eccezione hai la traccia dello stack. Quindi, fornire solo una funzione per la stampa. Per esempio:

class MyException : public std::exception { char ** strs; MyException( const std::string & message ) { int i, frames = backtrace(callstack, 128); strs = backtrace_symbols(callstack, frames); } void printStackTrace() { for (i = 0; i 

...

 provare {
    lanciare MyException ("Oops!");
 } catch (MyException e) {
     e.printStackTrace ();
 }

Ta da!

Nota: abilitare i flag di ottimizzazione può rendere imprecisa la traccia dello stack risultante. Idealmente, si userebbe questa capacità con i flag di debug e le flag di ottimizzazione distriggerste.

Per Windows controlla l’API StackWalk64 () (anche su Windows a 32 bit). Per UNIX è necessario utilizzare il modo nativo del sistema operativo per farlo, o eseguire il fallback su glibc’s backtrace (), se disponibile.

Si noti tuttavia che prendere uno Stacktrace nel codice nativo è raramente una buona idea, non perché non sia ansible, ma perché si sta tentando di raggiungere la cosa sbagliata.

Il più delle volte le persone cercano di ottenere uno stacktrace in, per esempio, una circostanza eccezionale, come quando viene rilevata un’eccezione, un asserimento fallisce o – il peggiore e il più sbagliato di tutti – quando si ottiene un’eccezione “fatale” o un segnale come un violazione della segmentazione.

Considerando l’ultimo problema, la maggior parte delle API richiede l’allocazione esplicita della memoria o l’esecuzione internamente. Farlo nello stato fragile in cui potrebbe trovarsi il tuo programma potrebbe rendere le cose ancora peggiori. Ad esempio, il rapporto di arresto anomalo (o coredump) non rifletterà la vera causa del problema, ma il tentativo fallito di gestirlo).

Presumo che tu stia cercando di ottenere quella cosa di gestione degli errori fatali, come la maggior parte delle persone sembra provarlo quando si tratta di ottenere uno stacktrace. Se è così, mi affiderei al debugger (durante lo sviluppo) e lascerei il processo in produzione (o mini-dump su Windows). Insieme a una corretta gestione dei simboli, non dovresti avere problemi a trovare le istruzioni che causano l’autopsia.

Per Windows, CaptureStackBackTrace() è anche un’opzione, che richiede meno codice di preparazione sul lato utente rispetto a StackWalk64() . (Inoltre, per uno scenario simile, CaptureStackBackTrace() funzionato meglio (in modo più affidabile) rispetto a StackWalk64() .

Dovresti usare la libreria unwind .

 unw_cursor_t cursor; unw_context_t uc; unw_word_t ip, sp; unw_getcontext(&uc); unw_init_local(&cursor, &uc); unsigned long a[100]; int ctr = 0; while (unw_step(&cursor) > 0) { unw_get_reg(&cursor, UNW_REG_IP, &ip); unw_get_reg(&cursor, UNW_REG_SP, &sp); if (ctr >= 10) break; a[ctr++] = ip; } 

Il tuo approccio funzionerebbe anche a meno che tu non effettui una chiamata da una libreria condivisa.

È ansible utilizzare il comando addr2line su Linux per ottenere la funzione / numero di riga sorgente del PC corrispondente.

Non esiste un modo indipendente dalla piattaforma per farlo.

La cosa più vicina che puoi fare è eseguire il codice senza ottimizzazioni. In questo modo puoi collegarti al processo (usando il visual c ++ debugger o GDB) e ottenere una traccia dello stack utilizzabile.

Solaris ha il comando pstack , che è stato anche copiato in Linux.

Posso indicarti il ​​mio articolo. Sono solo poche righe di codice.

Debug Post Mortem

Anche se attualmente ho problemi con l’ implementazione x64 di questo .

Negli ultimi anni ho utilizzato la libbacktrace di Ian Lance Taylor. È molto più pulito delle funzioni nella libreria GNU C che richiedono l’esportazione di tutti i simboli. Fornisce più utilità per la generazione di backtrace di libunwind. E, ultimo ma non meno importante, non è sconfitto da ASLR come approcci che richiedono strumenti esterni come addr2line .

Inizialmente, Libbacktrace faceva parte della distribuzione GCC, ma ora è reso disponibile dall’autore come libreria standalone con una licenza BSD:

https://github.com/ianlancetaylor/libbacktrace

Al momento della scrittura, non userei altro se non ho bisogno di generare backtrace su una piattaforma che non è supportata da libbacktrace.

Puoi farlo camminando indietro nello stack. In realtà, però, è spesso più facile aggiungere un identificatore a uno stack di chiamate all’inizio di ogni funzione e farlo scoppiare alla fine, quindi basta camminare per stampare il contenuto. È un po ‘un PITA, ma funziona bene e ti farà risparmiare tempo alla fine.