GNU/Linux >> Tutoriels Linux >  >> Linux

imprimer la pile d'appels en C ou C++

Pour une solution Linux uniquement, vous pouvez utiliser backtrace(3) qui renvoie simplement un tableau de void * (en fait, chacun d'eux pointe vers l'adresse de retour du cadre de pile correspondant). Pour les traduire en quelque chose d'utile, il y a backtrace_symbols(3).

Faites attention à la section des notes dans backtrace(3):

Les noms de symboles peuvent être indisponibles sans l'utilisation d'options spéciales de l'éditeur de liens. Pour les systèmes utilisant l'éditeur de liens GNU, il est nécessaire d'utiliser l'option-rdynamic linker. Notez que les noms des fonctions "statiques" ne sont pas exposés et ne seront pas disponibles dans le backtrace.


Existe-t-il un moyen de vider la pile des appels dans un processus en cours d'exécution en C ou C++ chaque fois qu'une certaine fonction est appelée ?

Vous pouvez utiliser une fonction macro au lieu de l'instruction return dans la fonction spécifique.

Par exemple, au lieu d'utiliser retour,

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

    ... do something ...

    return 0
}

Vous pouvez utiliser une fonction macro.

#include "c-callstack.h"

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

    ... do something ...

    NL_RETURN(0);
}

Chaque fois qu'une erreur se produit dans une fonction, vous verrez une pile d'appels de style Java comme indiqué ci-dessous.

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)

Le code source complet est disponible ici.

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


Améliorer la trace de pile

Documenté sur :https://www.boost.org/doc/libs/1_66_0/doc/html/stacktrace/getting_started.html#stacktrace.getting_started.how_to_print_current_call_stack

C'est l'option la plus pratique que j'ai vue jusqu'à présent, car elle :

  • peut réellement imprimer les numéros de ligne.

    Il fait juste des appels au addr2line cependant, ce qui ajoute une dépendance externe laide et ralentira considérablement votre code si vous faites beaucoup de traces

  • démêle par défaut

  • Boost n'est qu'en-tête, donc pas besoin de modifier votre système de construction très probablement

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
    }
}

Malheureusement, il semble que ce soit un ajout plus récent, et le package libboost-stacktrace-dev n'est pas présent dans Ubuntu 16.04, seulement 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

Nous devons ajouter -ldl à la fin sinon la compilation échoue.

Sortie :

 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

La sortie est expliquée plus en détail dans la section "glibc backtrace" ci-dessous, qui est analogue.

Notez comment my_func_1(int) et my_func_1(float) , qui sont mutilés en raison d'une surcharge de fonctions, ont été bien démembrés pour nous.

Notez que le premier int appels est désactivé d'une ligne (28 au lieu de 27 et le second est désactivé de deux lignes (27 au lieu de 29). Il a été suggéré dans les commentaires que c'est parce que l'adresse d'instruction suivante est prise en compte, ce qui fait que 27 devient 28 , et 29 sortent de la boucle et deviennent 27.

On observe alors qu'avec -O3 , la sortie est complètement mutilée :

 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

Les backtraces sont en général irrémédiablement mutilées par les optimisations. L'optimisation des appels de queue en est un exemple notable :qu'est-ce que l'optimisation des appels de queue ?

Benchmark exécuté sur -O3 :

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

Sortie :

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

Donc comme prévu, on voit que cette méthode est extrêmement lente susceptible de faire des appels externes au addr2line , et ne sera possible que si un nombre limité d'appels sont passés.

Chaque impression de backtrace semble prendre des centaines de millisecondes, alors soyez averti que si un backtrace se produit très souvent, les performances du programme en souffriront considérablement.

Testé sur Ubuntu 19.10, GCC 9.2.1, boost 1.67.0.

glibc backtrace

Documenté sur :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;
}

Compiler :

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

-rdynamic est l'option clé requise.

Exécuter :

./main.out

Sorties :

./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]

Nous voyons donc immédiatement qu'une optimisation de l'intégration s'est produite et que certaines fonctions ont été perdues dans la trace.

Si nous essayons d'obtenir les adresses :

addr2line -e main.out 0x4008f9 0x4008fe

on obtient :

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

qui est complètement éteint.

Si nous faisons la même chose avec -O0 à la place, ./main.out donne la bonne trace complète :

./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]

puis :

addr2line -e main.out 0x400a74 0x400a79

donne :

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

donc les lignes sont décalées d'une seule, TODO pourquoi ? Mais cela pourrait toujours être utilisable.

Conclusion :les backtraces ne peuvent s'afficher parfaitement qu'avec -O0 . Avec les optimisations, la trace d'origine est fondamentalement modifiée dans le code compilé.

Je n'ai pas trouvé de moyen simple de démêler automatiquement les symboles C++ avec ceci, cependant, voici quelques hacks :

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

Testé sur Ubuntu 16.04, GCC 6.4.0, libc 2.23.

glibc backtrace_symbols_fd

Cette aide est un peu plus pratique que backtrace_symbols , et produit une sortie pratiquement identique :

/* 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("");
}

Testé sur Ubuntu 16.04, GCC 6.4.0, libc 2.23.

glibc backtrace avec le hack de démêlage C++ 1 :-export-dynamic + dladdr

Adapté de :https://gist.github.com/fmela/591333/c64f4eb86037bb237862a8283df70cdfc25f01d3

Ceci est un "hack" car il nécessite de changer l'ELF avec -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);
}

Compiler et exécuter :

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 

sortie :

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]

Testé sur Ubuntu 18.04.

glibc backtrace avec C++ demangling hack 2 :analyser la sortie de backtrace

Affiché sur :https://panthema.net/2008/0901-stacktrace-demangled/

C'est un hack car il nécessite une analyse.

TODO le faire compiler et le montrer ici.

libunwind

TODO cela a-t-il un avantage sur la trace glibc ? Une sortie très similaire nécessite également la modification de la commande build, mais ne fait pas partie de la glibc et nécessite donc une installation de paquet supplémentaire.

Code adapté de :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;
}

Compiler et exécuter :

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

Soit #define _XOPEN_SOURCE 700 doit être au-dessus, ou nous devons utiliser -std=gnu99 :

  • Le type `stack_t` n'est-il plus défini sur Linux ?
  • Glibc - erreur dans ucontext.h, mais uniquement avec -std=c11

Exécuter :

./main.out

Sortie :

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

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

et :

addr2line -e main.out 0x4007db 0x4007e2

donne :

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

Avec -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)

et :

addr2line -e main.out 0x4009f3 0x4009f8

donne :

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

Testé sur Ubuntu 16.04, GCC 6.4.0, libunwind 1.1.

libunwind avec suppression des noms C++

Code adapté de :https://eli.thegreenplace.net/2015/programmatic-access-to-the-call-stack-in-c/

dérouler.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);
}

Compiler et exécuter :

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

Sortie :

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)

et ensuite nous pouvons trouver les lignes de my_func_2 et my_func_1(int) avec :

addr2line -e unwind.out 0x400c80 0x400cb7

ce qui donne :

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

À FAIRE :pourquoi les lignes sont-elles décalées d'une unité ?

Testé sur Ubuntu 18.04, GCC 7.4.0, libunwind 1.2.1.

Automatisation GDB

Nous pouvons également le faire avec GDB sans recompiler en utilisant :Comment effectuer une action spécifique lorsqu'un certain point d'arrêt est atteint dans GDB ?

Bien que si vous imprimez beaucoup le backtrace, ce sera probablement moins rapide que les autres options, mais nous pouvons peut-être atteindre des vitesses natives avec compile code , mais j'ai la flemme de le tester maintenant :comment appeler l'assembly dans 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);
}

main.gdb

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

Compiler et exécuter :

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

Sortie :

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]

TODO Je voulais faire ça avec juste -ex depuis la ligne de commande pour ne pas avoir à créer main.gdb mais je n'ai pas pu obtenir le commands pour y travailler.

Testé dans Ubuntu 19.04, GDB 8.2.

noyau Linux

Comment imprimer la trace actuelle de la pile de threads dans le noyau Linux ?

libdwfl

Cela a été mentionné à l'origine sur :https://stackoverflow.com/a/60713161/895245 et c'est peut-être la meilleure méthode, mais je dois comparer un peu plus, mais veuillez voter pour cette réponse.

À FAIRE :J'ai essayé de réduire le code de cette réponse, qui fonctionnait, à une seule fonction, mais il y a erreur de segmentation, faites-moi savoir si quelqu'un peut trouver pourquoi.

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
    }
}

Compiler et exécuter :

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

Nous avons également besoin de libunwind car cela rend les résultats plus corrects. Si vous vous en passez, ça tourne, mais vous verrez que certaines lignes sont un peu fausses.

Sortie :

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

Analyse comparative :

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

Sortie :

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

Nous voyons donc que cette méthode est 10 fois plus rapide que le stacktrace de Boost, et pourrait donc être applicable à plus de cas d'utilisation.

Testé dans Ubuntu 22.04 amd64, libdw-dev 0.186, libunwind 1.3.2.

libbacktrace

https://github.com/ianlancetaylor/libbacktrace

Considérant l'auteur de la bibliothèque harcore, cela vaut la peine d'essayer cela, peut-être que c'est The One. A FAIRE vérifier.

Une bibliothèque C qui peut être liée à un programme C/C++ pour produire des backtraces symboliques

Depuis octobre 2020, libbacktrace prend en charge les exécutables ELF, PE/COFF, Mach-O et XCOFF avec des informations de débogage DWARF. En d'autres termes, il prend en charge GNU/Linux, *BSD, macOS, Windows et AIX. La bibliothèque est écrite pour faciliter l'ajout de la prise en charge d'autres formats de fichiers objets et de débogage.

La bibliothèque s'appuie sur l'API de déroulement C++ définie sur https://itanium-cxx-abi.github.io/cxx-abi/abi-eh.html Cette API est fournie par GCC et clang.

Voir aussi

  • Comment récupérer une trace de pile en C ?
  • Comment faire en sorte que backtrace()/backtrace_symbols() imprime les noms des fonctions ?
  • Existe-t-il un moyen portable/conforme aux normes d'obtenir les noms de fichiers et les numéros de ligne dans une trace de pile ?
  • Meilleur moyen d'invoquer gdb depuis le programme interne pour imprimer son stacktrace ?
  • trace automatique de la pile en cas d'échec :
    • sur exception C++ :afficher la trace de pile C++ sur exception
    • générique :comment générer automatiquement un stacktrace lorsque mon programme plante

Linux
  1. Le programme de thread C++ simple ne peut pas être compilé ?

  2. Appeler une fonction C à partir du code C++

  3. Imprimer la valeur du pointeur de pile

  4. Imprimer errno comme mnémonique ?

  5. 2 imprimantes 1 file d'attente

Appel système Intel x86 vs x64

Comment imprimer l'heure actuelle (avec millisecondes) en utilisant C++ / C++11

Comment obtenir ps pour imprimer le groupe?

Comment imprimer un message sur stderr dans Go ?

Comment passer des paramètres à l'appel système Linux ?

Pouvez-vous faire en sorte que n'importe quel programme sous Linux imprime une trace de pile en cas d'erreur de segmentation ?