GNU/Linux >> Linux Esercitazione >  >> Linux

stampa stack di chiamate in C o C++

Per una soluzione solo Linux puoi usare backtrace(3) che restituisce semplicemente un array di void * (infatti ognuno di questi punta all'indirizzo di ritorno dallo stack frame corrispondente). Per tradurli in qualcosa di utile, c'è backtrace_symbols(3).

Presta attenzione alla sezione delle note in backtrace(3):

I nomi dei simboli potrebbero non essere disponibili senza l'uso di speciali opzioni di linker. Per i sistemi che usano il linker GNU, è necessario usare l'opzione-rdynamic linker. Nota che i nomi delle funzioni "statiche" non sono esposti e non saranno disponibili nel backtrace.


Esiste un modo per scaricare lo stack di chiamate in un processo in esecuzione in C o C++ ogni volta che viene chiamata una determinata funzione?

È possibile utilizzare una funzione macro anziché l'istruzione return nella funzione specifica.

Ad esempio, invece di utilizzare return,

int foo(...)
{
    if (error happened)
        return -1;

    ... do something ...

    return 0
}

Puoi utilizzare una funzione macro.

#include "c-callstack.h"

int foo(...)
{
    if (error happened)
        NL_RETURN(-1);

    ... do something ...

    NL_RETURN(0);
}

Ogni volta che si verifica un errore in una funzione, vedrai lo stack di chiamate in stile Java come mostrato di seguito.

Error(code:-1) at : so_topless_ranking_server (sample.c:23)
Error(code:-1) at : nanolat_database (sample.c:31)
Error(code:-1) at : nanolat_message_queue (sample.c:39)
Error(code:-1) at : main (sample.c:47)

Il codice sorgente completo è disponibile qui.

c-callstack su https://github.com/Nanolat


Potenzia stacktrace

Documentato su:https://www.boost.org/doc/libs/1_66_0/doc/html/stacktrace/getting_started.html#stacktrace.getting_started.how_to_print_current_call_stack

Questa è l'opzione più conveniente che ho visto finora, perché:

  • può effettivamente stampare i numeri di riga.

    Effettua solo chiamate a addr2line tuttavia, il che aggiunge una brutta dipendenza esterna e rallenterà considerevolmente il tuo codice se stai facendo molte tracce

  • demangles per impostazione predefinita

  • Boost è solo l'intestazione, quindi molto probabilmente non è necessario modificare il sistema di compilazione

boost_stacktrace.cpp

#include <iostream>

#define BOOST_STACKTRACE_USE_ADDR2LINE
#include <boost/stacktrace.hpp>

void my_func_2(void) {
    std::cout << boost::stacktrace::stacktrace() << std::endl;
}

void my_func_1(double f) {
    (void)f;
    my_func_2();
}

void my_func_1(int i) {
    (void)i;
    my_func_2();
}

int main(int argc, char **argv) {
    long long unsigned int n;
    if (argc > 1) {
        n = strtoul(argv[1], NULL, 0);
    } else {
        n = 1;
    }
    for (long long unsigned int i = 0; i < n; ++i) {
        my_func_1(1);   // line 28
        my_func_1(2.0); // line 29
    }
}

Sfortunatamente, sembra essere un'aggiunta più recente e il pacchetto libboost-stacktrace-dev non è presente in Ubuntu 16.04, solo 18.04:

sudo apt-get install libboost-stacktrace-dev
g++ -fno-pie -ggdb3 -O0 -no-pie -o boost_stacktrace.out -std=c++11 \
  -Wall -Wextra -pedantic-errors boost_stacktrace.cpp -ldl
./boost_stacktrace.out

Dobbiamo aggiungere -ldl alla fine altrimenti la compilazione fallisce.

Uscita:

 0# boost::stacktrace::basic_stacktrace<std::allocator<boost::stacktrace::frame> >::basic_stacktrace() at /usr/include/boost/stacktrace/stacktrace.hpp:129
 1# my_func_1(int) at /home/ciro/test/boost_stacktrace.cpp:18
 2# main at /home/ciro/test/boost_stacktrace.cpp:29 (discriminator 2)
 3# __libc_start_main in /lib/x86_64-linux-gnu/libc.so.6
 4# _start in ./boost_stacktrace.out

 0# boost::stacktrace::basic_stacktrace<std::allocator<boost::stacktrace::frame> >::basic_stacktrace() at /usr/include/boost/stacktrace/stacktrace.hpp:129
 1# my_func_1(double) at /home/ciro/test/boost_stacktrace.cpp:13
 2# main at /home/ciro/test/boost_stacktrace.cpp:27 (discriminator 2)
 3# __libc_start_main in /lib/x86_64-linux-gnu/libc.so.6
 4# _start in ./boost_stacktrace.out

L'output e viene ulteriormente spiegato nella sezione "glibc backtrace" di seguito, che è analoga.

Nota come my_func_1(int) e my_func_1(float) , che sono alterati a causa del sovraccarico di funzioni, sono stati ben smantellati per noi.

Nota che il primo int le chiamate sono disattivate di una riga (28 invece di 27 e la seconda è disattivata di due righe (27 anziché 29). , e 29 salta fuori dal giro e diventa 27.

Lo osserviamo quindi con -O3 , l'output è completamente mutilato:

 0# boost::stacktrace::basic_stacktrace<std::allocator<boost::stacktrace::frame> >::size() const at /usr/include/boost/stacktrace/stacktrace.hpp:215
 1# my_func_1(double) at /home/ciro/test/boost_stacktrace.cpp:12
 2# __libc_start_main in /lib/x86_64-linux-gnu/libc.so.6
 3# _start in ./boost_stacktrace.out

 0# boost::stacktrace::basic_stacktrace<std::allocator<boost::stacktrace::frame> >::size() const at /usr/include/boost/stacktrace/stacktrace.hpp:215
 1# main at /home/ciro/test/boost_stacktrace.cpp:31
 2# __libc_start_main in /lib/x86_64-linux-gnu/libc.so.6
 3# _start in ./boost_stacktrace.out

I backtrace sono in generale irrimediabilmente mutilati dalle ottimizzazioni. L'ottimizzazione delle chiamate di coda ne è un esempio notevole:che cos'è l'ottimizzazione delle chiamate di coda?

Benchmark eseguito su -O3 :

time  ./boost_stacktrace.out 1000 >/dev/null

Uscita:

real    0m43.573s
user    0m30.799s
sys     0m13.665s

Quindi, come previsto, vediamo che questo metodo è estremamente lento probabilmente per le chiamate esterne a addr2line , e sarà fattibile solo se viene effettuato un numero limitato di chiamate.

Ogni stampa di backtrace sembra richiedere centinaia di millisecondi, quindi tieni presente che se un backtrace si verifica molto spesso, le prestazioni del programma ne risentiranno in modo significativo.

Testato su Ubuntu 19.10, GCC 9.2.1, boost 1.67.0.

glibc backtrace

Documentato su:https://www.gnu.org/software/libc/manual/html_node/Backtraces.html

main.c

#include <stdio.h>
#include <stdlib.h>

/* Paste this on the file you want to debug. */
#include <stdio.h>
#include <execinfo.h>
void print_trace(void) {
    char **strings;
    size_t i, size;
    enum Constexpr { MAX_SIZE = 1024 };
    void *array[MAX_SIZE];
    size = backtrace(array, MAX_SIZE);
    strings = backtrace_symbols(array, size);
    for (i = 0; i < size; i++)
        printf("%s\n", strings[i]);
    puts("");
    free(strings);
}

void my_func_3(void) {
    print_trace();
}

void my_func_2(void) {
    my_func_3();
}

void my_func_1(void) {
    my_func_3();
}

int main(void) {
    my_func_1(); /* line 33 */
    my_func_2(); /* line 34 */
    return 0;
}

Compila:

gcc -fno-pie -ggdb3 -O3 -no-pie -o main.out -rdynamic -std=c99 \
  -Wall -Wextra -pedantic-errors main.c

-rdynamic è l'opzione chiave richiesta.

Esegui:

./main.out

Uscite:

./main.out(print_trace+0x2d) [0x400a3d]
./main.out(main+0x9) [0x4008f9]
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf0) [0x7f35a5aad830]
./main.out(_start+0x29) [0x400939]

./main.out(print_trace+0x2d) [0x400a3d]
./main.out(main+0xe) [0x4008fe]
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf0) [0x7f35a5aad830]
./main.out(_start+0x29) [0x400939]

Quindi vediamo immediatamente che si è verificata un'ottimizzazione incorporata e alcune funzioni sono state perse dalla traccia.

Se cerchiamo di ottenere gli indirizzi:

addr2line -e main.out 0x4008f9 0x4008fe

otteniamo:

/home/ciro/main.c:21
/home/ciro/main.c:36

che è completamente spento.

Se facciamo lo stesso con -O0 invece, ./main.out fornisce la traccia completa corretta:

./main.out(print_trace+0x2e) [0x4009a4]
./main.out(my_func_3+0x9) [0x400a50]
./main.out(my_func_1+0x9) [0x400a68]
./main.out(main+0x9) [0x400a74]
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf0) [0x7f4711677830]
./main.out(_start+0x29) [0x4008a9]

./main.out(print_trace+0x2e) [0x4009a4]
./main.out(my_func_3+0x9) [0x400a50]
./main.out(my_func_2+0x9) [0x400a5c]
./main.out(main+0xe) [0x400a79]
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf0) [0x7f4711677830]
./main.out(_start+0x29) [0x4008a9]

e poi:

addr2line -e main.out 0x400a74 0x400a79

dà:

/home/cirsan01/test/main.c:34
/home/cirsan01/test/main.c:35

quindi le linee sono fuori di una sola, TODO perché? Ma potrebbe essere ancora utilizzabile.

Conclusione:i backtrace possono essere mostrati perfettamente solo con -O0 . Con le ottimizzazioni, il backtrace originale viene sostanzialmente modificato nel codice compilato.

Non sono riuscito a trovare un modo semplice per smantellare automaticamente i simboli C++ con questo, tuttavia, ecco alcuni hack:

  • https://panthema.net/2008/0901-stacktrace-demangled/
  • https://gist.github.com/fmela/591333/c64f4eb86037bb237862a8283df70cdfc25f01d3

Testato su Ubuntu 16.04, GCC 6.4.0, libc 2.23.

glibc backtrace_symbols_fd

Questo helper è un po' più comodo di backtrace_symbols , e produce un output sostanzialmente identico:

/* Paste this on the file you want to debug. */
#include <execinfo.h>
#include <stdio.h>
#include <unistd.h>
void print_trace(void) {
    size_t i, size;
    enum Constexpr { MAX_SIZE = 1024 };
    void *array[MAX_SIZE];
    size = backtrace(array, MAX_SIZE);
    backtrace_symbols_fd(array, size, STDOUT_FILENO);
    puts("");
}

Testato su Ubuntu 16.04, GCC 6.4.0, libc 2.23.

glibc backtrace con il demangling in C++ hack 1:-export-dynamic + dladdr

Adattato da:https://gist.github.com/fmela/591333/c64f4eb86037bb237862a8283df70cdfc25f01d3

Questo è un "hack" perché richiede di cambiare l'ELF con -export-dynamic .

glibc_ldl.cpp

#include <dlfcn.h>     // for dladdr
#include <cxxabi.h>    // for __cxa_demangle

#include <cstdio>
#include <string>
#include <sstream>
#include <iostream>

// This function produces a stack backtrace with demangled function & method names.
std::string backtrace(int skip = 1)
{
    void *callstack[128];
    const int nMaxFrames = sizeof(callstack) / sizeof(callstack[0]);
    char buf[1024];
    int nFrames = backtrace(callstack, nMaxFrames);
    char **symbols = backtrace_symbols(callstack, nFrames);

    std::ostringstream trace_buf;
    for (int i = skip; i < nFrames; i++) {
        Dl_info info;
        if (dladdr(callstack[i], &info)) {
            char *demangled = NULL;
            int status;
            demangled = abi::__cxa_demangle(info.dli_sname, NULL, 0, &status);
            std::snprintf(
                buf,
                sizeof(buf),
                "%-3d %*p %s + %zd\n",
                i,
                (int)(2 + sizeof(void*) * 2),
                callstack[i],
                status == 0 ? demangled : info.dli_sname,
                (char *)callstack[i] - (char *)info.dli_saddr
            );
            free(demangled);
        } else {
            std::snprintf(buf, sizeof(buf), "%-3d %*p\n",
                i, (int)(2 + sizeof(void*) * 2), callstack[i]);
        }
        trace_buf << buf;
        std::snprintf(buf, sizeof(buf), "%s\n", symbols[i]);
        trace_buf << buf;
    }
    free(symbols);
    if (nFrames == nMaxFrames)
        trace_buf << "[truncated]\n";
    return trace_buf.str();
}

void my_func_2(void) {
    std::cout << backtrace() << std::endl;
}

void my_func_1(double f) {
    (void)f;
    my_func_2();
}

void my_func_1(int i) {
    (void)i;
    my_func_2();
}

int main() {
    my_func_1(1);
    my_func_1(2.0);
}

Compila ed esegui:

g++ -fno-pie -ggdb3 -O0 -no-pie -o glibc_ldl.out -std=c++11 -Wall -Wextra \
  -pedantic-errors -fpic glibc_ldl.cpp -export-dynamic -ldl
./glibc_ldl.out 

uscita:

1             0x40130a my_func_2() + 41
./glibc_ldl.out(_Z9my_func_2v+0x29) [0x40130a]
2             0x40139e my_func_1(int) + 16
./glibc_ldl.out(_Z9my_func_1i+0x10) [0x40139e]
3             0x4013b3 main + 18
./glibc_ldl.out(main+0x12) [0x4013b3]
4       0x7f7594552b97 __libc_start_main + 231
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xe7) [0x7f7594552b97]
5             0x400f3a _start + 42
./glibc_ldl.out(_start+0x2a) [0x400f3a]

1             0x40130a my_func_2() + 41
./glibc_ldl.out(_Z9my_func_2v+0x29) [0x40130a]
2             0x40138b my_func_1(double) + 18
./glibc_ldl.out(_Z9my_func_1d+0x12) [0x40138b]
3             0x4013c8 main + 39
./glibc_ldl.out(main+0x27) [0x4013c8]
4       0x7f7594552b97 __libc_start_main + 231
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xe7) [0x7f7594552b97]
5             0x400f3a _start + 42
./glibc_ldl.out(_start+0x2a) [0x400f3a]

Testato su Ubuntu 18.04.

glibc backtrace con C++ demangling hack 2:analizza l'output del backtrace

Mostrato su:https://panthema.net/2008/0901-stacktrace-demangled/

Questo è un hack perché richiede l'analisi.

TODO fallo compilare e mostralo qui.

libunwind

TODO ha qualche vantaggio rispetto al backtrace di glibc? Output molto simile, richiede anche la modifica del comando build, ma non fa parte di glibc quindi richiede l'installazione di un pacchetto aggiuntivo.

Codice adattato da:https://eli.thegreenplace.net/2015/programmatic-access-to-the-call-stack-in-c/

main.c

/* This must be on top. */
#define _XOPEN_SOURCE 700

#include <stdio.h>
#include <stdlib.h>

/* Paste this on the file you want to debug. */
#define UNW_LOCAL_ONLY
#include <libunwind.h>
#include <stdio.h>
void print_trace() {
    char sym[256];
    unw_context_t context;
    unw_cursor_t cursor;
    unw_getcontext(&context);
    unw_init_local(&cursor, &context);
    while (unw_step(&cursor) > 0) {
        unw_word_t offset, pc;
        unw_get_reg(&cursor, UNW_REG_IP, &pc);
        if (pc == 0) {
            break;
        }
        printf("0x%lx:", pc);
        if (unw_get_proc_name(&cursor, sym, sizeof(sym), &offset) == 0) {
            printf(" (%s+0x%lx)\n", sym, offset);
        } else {
            printf(" -- error: unable to obtain symbol name for this frame\n");
        }
    }
    puts("");
}

void my_func_3(void) {
    print_trace();
}

void my_func_2(void) {
    my_func_3();
}

void my_func_1(void) {
    my_func_3();
}

int main(void) {
    my_func_1(); /* line 46 */
    my_func_2(); /* line 47 */
    return 0;
}

Compila ed esegui:

sudo apt-get install libunwind-dev
gcc -fno-pie -ggdb3 -O3 -no-pie -o main.out -std=c99 \
  -Wall -Wextra -pedantic-errors main.c -lunwind

O #define _XOPEN_SOURCE 700 deve essere in cima, oppure dobbiamo usare -std=gnu99 :

  • Il tipo `stack_t` non è più definito su Linux?
  • Glibc - errore in ucontext.h, ma solo con -std=c11

Esegui:

./main.out

Uscita:

0x4007db: (main+0xb)
0x7f4ff50aa830: (__libc_start_main+0xf0)
0x400819: (_start+0x29)

0x4007e2: (main+0x12)
0x7f4ff50aa830: (__libc_start_main+0xf0)
0x400819: (_start+0x29)

e:

addr2line -e main.out 0x4007db 0x4007e2

dà:

/home/ciro/main.c:34
/home/ciro/main.c:49

Con -O0 :

0x4009cf: (my_func_3+0xe)
0x4009e7: (my_func_1+0x9)
0x4009f3: (main+0x9)
0x7f7b84ad7830: (__libc_start_main+0xf0)
0x4007d9: (_start+0x29)

0x4009cf: (my_func_3+0xe)
0x4009db: (my_func_2+0x9)
0x4009f8: (main+0xe)
0x7f7b84ad7830: (__libc_start_main+0xf0)
0x4007d9: (_start+0x29)

e:

addr2line -e main.out 0x4009f3 0x4009f8

dà:

/home/ciro/main.c:47
/home/ciro/main.c:48

Testato su Ubuntu 16.04, GCC 6.4.0, libunwind 1.1.

libunwind con demangling del nome C++

Codice adattato da:https://eli.thegreenplace.net/2015/programmatic-access-to-the-call-stack-in-c/

unwind.cpp

#define UNW_LOCAL_ONLY
#include <cxxabi.h>
#include <libunwind.h>
#include <cstdio>
#include <cstdlib>
#include <iostream>

void backtrace() {
  unw_cursor_t cursor;
  unw_context_t context;

  // Initialize cursor to current frame for local unwinding.
  unw_getcontext(&context);
  unw_init_local(&cursor, &context);

  // Unwind frames one by one, going up the frame stack.
  while (unw_step(&cursor) > 0) {
    unw_word_t offset, pc;
    unw_get_reg(&cursor, UNW_REG_IP, &pc);
    if (pc == 0) {
      break;
    }
    std::printf("0x%lx:", pc);

    char sym[256];
    if (unw_get_proc_name(&cursor, sym, sizeof(sym), &offset) == 0) {
      char* nameptr = sym;
      int status;
      char* demangled = abi::__cxa_demangle(sym, nullptr, nullptr, &status);
      if (status == 0) {
        nameptr = demangled;
      }
      std::printf(" (%s+0x%lx)\n", nameptr, offset);
      std::free(demangled);
    } else {
      std::printf(" -- error: unable to obtain symbol name for this frame\n");
    }
  }
}

void my_func_2(void) {
    backtrace();
    std::cout << std::endl; // line 43
}

void my_func_1(double f) {
    (void)f;
    my_func_2();
}

void my_func_1(int i) {
    (void)i;
    my_func_2();
}  // line 54

int main() {
    my_func_1(1);
    my_func_1(2.0);
}

Compila ed esegui:

sudo apt-get install libunwind-dev
g++ -fno-pie -ggdb3 -O0 -no-pie -o unwind.out -std=c++11 \
  -Wall -Wextra -pedantic-errors unwind.cpp -lunwind -pthread
./unwind.out

Uscita:

0x400c80: (my_func_2()+0x9)
0x400cb7: (my_func_1(int)+0x10)
0x400ccc: (main+0x12)
0x7f4c68926b97: (__libc_start_main+0xe7)
0x400a3a: (_start+0x2a)

0x400c80: (my_func_2()+0x9)
0x400ca4: (my_func_1(double)+0x12)
0x400ce1: (main+0x27)
0x7f4c68926b97: (__libc_start_main+0xe7)
0x400a3a: (_start+0x2a)

e poi possiamo trovare le righe di my_func_2 e my_func_1(int) con:

addr2line -e unwind.out 0x400c80 0x400cb7

che dà:

/home/ciro/test/unwind.cpp:43
/home/ciro/test/unwind.cpp:54

DA FARE:perché le battute sono fuori di uno?

Testato su Ubuntu 18.04, GCC 7.4.0, libunwind 1.2.1.

Automazione GDB

Possiamo anche farlo con GDB senza ricompilare utilizzando:Come eseguire un'azione specifica quando viene raggiunto un determinato punto di interruzione in GDB?

Sebbene se stamperai molto il backtrace, questo sarà probabilmente meno veloce delle altre opzioni, ma forse possiamo raggiungere velocità native con compile code , ma sono pigro per provarlo ora:How to call assembly in gdb?

main.cpp

void my_func_2(void) {}

void my_func_1(double f) {
    my_func_2();
}

void my_func_1(int i) {
    my_func_2();
}

int main() {
    my_func_1(1);
    my_func_1(2.0);
}

principale.gdb

start
break my_func_2
commands
  silent
  backtrace
  printf "\n"
  continue
end
continue

Compila ed esegui:

g++ -ggdb3 -o main.out main.cpp
gdb -nh -batch -x main.gdb main.out

Uscita:

Temporary breakpoint 1 at 0x1158: file main.cpp, line 12.

Temporary breakpoint 1, main () at main.cpp:12
12          my_func_1(1);
Breakpoint 2 at 0x555555555129: file main.cpp, line 1.
#0  my_func_2 () at main.cpp:1
#1  0x0000555555555151 in my_func_1 (i=1) at main.cpp:8
#2  0x0000555555555162 in main () at main.cpp:12

#0  my_func_2 () at main.cpp:1
#1  0x000055555555513e in my_func_1 (f=2) at main.cpp:4
#2  0x000055555555516f in main () at main.cpp:13

[Inferior 1 (process 14193) exited normally]

DA FARE Volevo farlo solo con -ex dalla riga di comando per non dover creare main.gdb ma non sono riuscito a ottenere il commands lavorare lì.

Testato in Ubuntu 19.04, GDB 8.2.

kernel Linux

Come stampare la traccia dello stack del thread corrente all'interno del kernel Linux?

libdwfl

Questo è stato originariamente menzionato su:https://stackoverflow.com/a/60713161/895245 e potrebbe essere il metodo migliore, ma devo fare un benchmark un po' di più, ma per favore vota quella risposta.

TODO:ho provato a minimizzare il codice in quella risposta, che funzionava, a una singola funzione, ma è segfaulting, fammi sapere se qualcuno riesce a trovare il motivo.

dwfl.cpp

#include <cassert>
#include <iostream>
#include <memory>
#include <sstream>
#include <string>

#include <cxxabi.h> // __cxa_demangle
#include <elfutils/libdwfl.h> // Dwfl*
#include <execinfo.h> // backtrace
#include <unistd.h> // getpid

// https://stackoverflow.com/questions/281818/unmangling-the-result-of-stdtype-infoname
std::string demangle(const char* name) {
    int status = -4;
    std::unique_ptr<char, void(*)(void*)> res {
        abi::__cxa_demangle(name, NULL, NULL, &status),
        std::free
    };
    return (status==0) ? res.get() : name ;
}

std::string debug_info(Dwfl* dwfl, void* ip) {
    std::string function;
    int line = -1;
    char const* file;
    uintptr_t ip2 = reinterpret_cast<uintptr_t>(ip);
    Dwfl_Module* module = dwfl_addrmodule(dwfl, ip2);
    char const* name = dwfl_module_addrname(module, ip2);
    function = name ? demangle(name) : "<unknown>";
    if (Dwfl_Line* dwfl_line = dwfl_module_getsrc(module, ip2)) {
        Dwarf_Addr addr;
        file = dwfl_lineinfo(dwfl_line, &addr, &line, nullptr, nullptr, nullptr);
    }
    std::stringstream ss;
    ss << ip << ' ' << function;
    if (file)
        ss << " at " << file << ':' << line;
    ss << std::endl;
    return ss.str();
}

std::string stacktrace() {
    // Initialize Dwfl.
    Dwfl* dwfl = nullptr;
    {
        Dwfl_Callbacks callbacks = {};
        char* debuginfo_path = nullptr;
        callbacks.find_elf = dwfl_linux_proc_find_elf;
        callbacks.find_debuginfo = dwfl_standard_find_debuginfo;
        callbacks.debuginfo_path = &debuginfo_path;
        dwfl = dwfl_begin(&callbacks);
        assert(dwfl);
        int r;
        r = dwfl_linux_proc_report(dwfl, getpid());
        assert(!r);
        r = dwfl_report_end(dwfl, nullptr, nullptr);
        assert(!r);
        static_cast<void>(r);
    }

    // Loop over stack frames.
    std::stringstream ss;
    {
        void* stack[512];
        int stack_size = ::backtrace(stack, sizeof stack / sizeof *stack);
        for (int i = 0; i < stack_size; ++i) {
            ss << i << ": ";

            // Works.
            ss << debug_info(dwfl, stack[i]);

#if 0
            // TODO intended to do the same as above, but segfaults,
            // so possibly UB In above function that does not blow up by chance?
            void *ip = stack[i];
            std::string function;
            int line = -1;
            char const* file;
            uintptr_t ip2 = reinterpret_cast<uintptr_t>(ip);
            Dwfl_Module* module = dwfl_addrmodule(dwfl, ip2);
            char const* name = dwfl_module_addrname(module, ip2);
            function = name ? demangle(name) : "<unknown>";
            // TODO if I comment out this line it does not blow up anymore.
            if (Dwfl_Line* dwfl_line = dwfl_module_getsrc(module, ip2)) {
              Dwarf_Addr addr;
              file = dwfl_lineinfo(dwfl_line, &addr, &line, nullptr, nullptr, nullptr);
            }
            ss << ip << ' ' << function;
            if (file)
                ss << " at " << file << ':' << line;
            ss << std::endl;
#endif
        }
    }
    dwfl_end(dwfl);
    return ss.str();
}

void my_func_2() {
    std::cout << stacktrace() << std::endl;
    std::cout.flush();
}

void my_func_1(double f) {
    (void)f;
    my_func_2();
}

void my_func_1(int i) {
    (void)i;
    my_func_2();
}

int main(int argc, char **argv) {
    long long unsigned int n;
    if (argc > 1) {
        n = strtoul(argv[1], NULL, 0);
    } else {
        n = 1;
    }
    for (long long unsigned int i = 0; i < n; ++i) {
        my_func_1(1);   // line 122
        my_func_1(2.0); // line 123
    }
}

Compila ed esegui:

sudo apt install libdw-dev libunwind-dev
g++ -fno-pie -ggdb3 -O0 -no-pie -o dwfl.out -std=c++11 -Wall -Wextra -pedantic-errors dwfl.cpp -ldw -lunwind
./dwfl.out

Abbiamo anche bisogno di libunwind in quanto ciò rende i risultati più corretti. Se ne fai a meno, funziona, ma vedrai che alcune righe sono un po' sbagliate.

Uscita:

0: 0x402b72 stacktrace[abi:cxx11]() at /home/ciro/test/dwfl.cpp:65
1: 0x402cda my_func_2() at /home/ciro/test/dwfl.cpp:100
2: 0x402d76 my_func_1(int) at /home/ciro/test/dwfl.cpp:111
3: 0x402dd1 main at /home/ciro/test/dwfl.cpp:122
4: 0x7ff227ea0d8f __libc_start_call_main at ../sysdeps/nptl/libc_start_call_main.h:58
5: 0x7ff227ea0e3f [email protected]@GLIBC_2.34 at ../csu/libc-start.c:392
6: 0x402534 _start at ../csu/libc-start.c:-1

0: 0x402b72 stacktrace[abi:cxx11]() at /home/ciro/test/dwfl.cpp:65
1: 0x402cda my_func_2() at /home/ciro/test/dwfl.cpp:100
2: 0x402d5f my_func_1(double) at /home/ciro/test/dwfl.cpp:106
3: 0x402de2 main at /home/ciro/test/dwfl.cpp:123
4: 0x7ff227ea0d8f __libc_start_call_main at ../sysdeps/nptl/libc_start_call_main.h:58
5: 0x7ff227ea0e3f [email protected]@GLIBC_2.34 at ../csu/libc-start.c:392
6: 0x402534 _start at ../csu/libc-start.c:-1

Esecuzione del benchmark:

g++ -fno-pie -ggdb3 -O3 -no-pie -o dwfl.out -std=c++11 -Wall -Wextra -pedantic-errors dwfl.cpp -ldw
time ./dwfl.out 1000 >/dev/null

Uscita:

real    0m3.751s
user    0m2.822s
sys     0m0.928s

Quindi vediamo che questo metodo è 10 volte più veloce dello stacktrace di Boost e potrebbe quindi essere applicabile a più casi d'uso.

Testato su Ubuntu 22.04 amd64, libdw-dev 0.186, libunwind 1.3.2.

libbacktrace

https://github.com/ianlancetaylor/libbacktrace

Considerando l'autore della libreria harcore, vale la pena provarlo, forse è The One. TODO check it out.

Una libreria C che può essere collegata a un programma C/C++ per produrre backtrace simbolici

A partire da ottobre 2020, libbacktrace supporta gli eseguibili ELF, PE/COFF, Mach-O e XCOFF con informazioni di debug DWARF. In altre parole, supporta GNU/Linux, *BSD, macOS, Windows e AIX. La libreria è scritta per semplificare l'aggiunta del supporto per altri file oggetto e formati di debug.

La libreria si basa sull'API C++ unwind definita su https://itanium-cxx-abi.github.io/cxx-abi/abi-eh.html Questa API è fornita da GCC e clang.

Vedi anche

  • Come si può prendere una traccia dello stack in C?
  • Come fare in modo che backtrace()/backtrace_symbols() stampi i nomi delle funzioni?
  • Esiste un modo portatile/conforme agli standard per ottenere nomi di file e numeri di riga in una traccia dello stack?
  • Il modo migliore per invocare gdb dall'interno del programma per stampare il suo stacktrace?
  • traccia automatica dello stack in caso di errore:
    • sull'eccezione C++:C++ visualizza la traccia dello stack sull'eccezione
    • generico:come generare automaticamente uno stacktrace quando il mio programma va in crash

Linux
  1. Non è possibile compilare un semplice programma di thread C++?

  2. Come chiamare la funzione C in C++, la funzione C++ in C (mix di C e C++)

  3. Come eseguire il mmap dello stack per la chiamata di sistema clone() su Linux?

  4. Come stampare l'ora corrente (con millisecondi) usando C++ / C++11

  5. Chiama una funzione C dal codice C++

Come stampare i membri di un oggetto c++ usando GDB da un indirizzo se il tipo di classe dell'oggetto è come A::B

Formattazione struct timespec

Chiamata di sistema Intel x86 vs x64

Stampa il valore del puntatore dello stack

Come ottenere ps per stampare il gruppo?

2 stampanti 1 coda