web-dev-qa-db-fra.com

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

Existe-t-il un moyen de vider la pile d'appels dans un processus en cours d'exécution en C ou C++ chaque fois qu'une fonction donnée est appelée? Ce que j'ai en tête est quelque chose comme ceci:

void foo()
{
   print_stack_trace();

   // foo's body

   return
}

print_stack_trace fonctionne de manière similaire à caller en Perl.

Ou quelque chose comme ça:

int main (void)
{
    // will print out debug info every time foo() is called
    register_stack_trace_function(foo); 

    // etc...
}

register_stack_trace_function place une sorte de point d’arrêt interne qui entraînera l’impression d’une trace de pile chaque fois que foo sera appelé.

Est-ce que quelque chose comme ça existe dans une bibliothèque C standard?

Je travaille sur Linux, en utilisant GCC.


Contexte

J'ai un test qui se comporte différemment en fonction de certains commutateurs de ligne de commande qui ne devraient pas affecter ce comportement. Mon code a un générateur de nombre pseudo-aléatoire qui, je suppose, est appelé différemment en fonction de ces commutateurs. Je veux pouvoir exécuter le test avec chaque ensemble de commutateurs et voir si le générateur de nombres aléatoires est appelé différemment pour chacun.

95
Nathan Fellman

Pour une solution exclusivement Linux, vous pouvez utiliser backtrace (3) qui retourne simplement un tableau de void * (en fait, chacun de ces points pointe sur l'adresse de retour du cadre de pile correspondant). Pour traduire cela en quelque chose d'usage, il y a backtrace_symbols (3) .

Faites attention au section de notes dans backtrace (3) :

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

72
Idan K

Boost stacktrace

Documenté à: 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 à addr2line cependant, ce qui est moche et peut être lent si vous prenez trop de traces.

  • démêle par défaut

  • Boost est uniquement en-tête, donc pas besoin de modifier votre système de construction le plus probablement

main.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) {
    my_func_2();
}

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

int main() {
    my_func_1(1);   /* line 19 */
    my_func_1(2.0); /* line 20 */
}

Malheureusement, cela semble être un ajout plus récent, et le paquet 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 main.out -std=c++11 \
  -Wall -Wextra -pedantic-errors main.cpp -ldl

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

Ensuite:

./main.out

donne:

 0# my_func_2() at /root/lkmc/main.cpp:7
 1# my_func_1(int) at /root/lkmc/main.cpp:16
 2# main at /root/lkmc/main.cpp:20
 3# __libc_start_main in /lib/x86_64-linux-gnu/libc.so.6
 4# _start in ./main.out

 0# my_func_2() at /root/lkmc/main.cpp:7
 1# my_func_1(double) at /root/lkmc/main.cpp:12
 2# main at /root/lkmc/main.cpp:21
 3# __libc_start_main in /lib/x86_64-linux-gnu/libc.so.6
 4# _start in ./main.out

Et avec -O3:

 0# my_func_2() at /usr/include/boost/stacktrace/stacktrace.hpp:217
 1# my_func_1(double) at /root/lkmc/main.cpp:11
 2# __libc_start_main in /lib/x86_64-linux-gnu/libc.so.6
 3# _start in ./main.out

 0# my_func_2() at /usr/include/boost/stacktrace/stacktrace.hpp:217
 1# main at /root/lkmc/main.cpp:21
 2# __libc_start_main in /lib/x86_64-linux-gnu/libc.so.6
 3# _start in ./main.out

Gardez à l’esprit que les optimisations ont en général mutilé irrémédiablement les traces de fond. L’optimisation des appels en attente en est un exemple notable: Qu'est-ce que l'optimisation des appels en queue?

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

Testé sur Ubuntu 18.04, GCC 7.3.0, boost 1.65.1.

trace de la glibc

Documenté à: https://www.gnu.org/software/libc/manual/html_node/Backtraces.html

principal 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 la clé requise.

Courir:

./main.out

Les 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 en ligne s’est produite et que certaines fonctions ont été perdues.

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 au lieu, ./main.out donne la trace complète correcte:

./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 coupées par un seul, TODO pourquoi? Mais cela pourrait encore être utilisable.

Conclusion: les traces de fond ne peuvent éventuellement apparaître que parfaitement avec -O0. Avec les optimisations, la trace d'origine est fondamentalement modifiée dans le code compilé.

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 fondamentalement 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.

libunwind

TODO cela at-il un avantage par rapport à la trace arrière glibc? Une sortie très similaire, nécessite également de modifier la commande de construction, mais moins largement disponible.

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

principal 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 top, ou il faut utiliser -std=gnu99:

Courir:

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

démêlage C++

Peut être fait avec abi::__cxa_demangle pour glibc backtrace et libunwind, voir un exemple à l’adresse suivante: https://eli.thegreenplace.net/2015/programmatic-access-to-the-call-stack-in-c /

Noyau Linux

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

Voir aussi

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

Vous pouvez utiliser une fonction macro à la place d'une instruction return dans une fonction spécifique.

Par exemple, au lieu d’utiliser return,

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 survient 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 à https://github.com/Nanolat

6

Il n'y a pas de manière standardisée de le faire. Pour Windows, la fonctionnalité est fournie dans la bibliothèque DbgHelp

5
Paul Michalik

Une autre réponse à un vieux fil.

Lorsque j’ai besoin de faire cela, j’utilise généralement system() et pstack

Donc, quelque chose comme ça:

#include <sys/types.h>
#include <unistd.h>
#include <string>
#include <sstream>
#include <cstdlib>

void f()
{
    pid_t myPid = getpid();
    std::string pstackCommand = "pstack ";
    std::stringstream ss;
    ss << myPid;
    pstackCommand += ss.str();
    system(pstackCommand.c_str());
}

void g()
{
   f();
}


void h()
{
   g();
}

int main()
{
   h();
}

Cette sortie

#0  0x00002aaaab62d61e in waitpid () from /lib64/libc.so.6
#1  0x00002aaaab5bf609 in do_system () from /lib64/libc.so.6
#2  0x0000000000400c3c in f() ()
#3  0x0000000000400cc5 in g() ()
#4  0x0000000000400cd1 in h() ()
#5  0x0000000000400cdd in main ()

Cela devrait fonctionner sous Linux, FreeBSD et Solaris. Je ne pense pas que macOS ait pstack ou un équivalent simple, mais ceci le fil semble avoir une alternative .

4
Paul Floyd

Vous pouvez utiliser Coquelicot pour cela. Il est normalement utilisé pour rassembler la trace de la pile lors d’un crash, mais il peut également le sortir pour un programme en cours d’exécution.

Maintenant, voici la bonne partie: il peut sortir les valeurs de paramètre réelles pour chaque fonction de la pile, et même les variables locales, les compteurs de boucle, etc.

2
Orlin Georgiev

Vous pouvez implémenter la fonctionnalité vous-même:

Utiliser une pile globale (chaîne) et au début de chaque fonction Insérez le nom de la fonction et les autres valeurs (paramètres, par exemple) sur cette pile; à la sortie de la fonction, relancez le.

Ecrivez une fonction qui imprimera le contenu de la pile lorsqu’elle sera appelée et l’utiliserez dans la fonction où vous voulez voir la pile d’appel.

Cela peut sembler beaucoup de travail mais est très utile.

2
slashmais

Vous pouvez utiliser les bibliothèques Boost pour imprimer la pile d’appel actuelle.

#include <boost/stacktrace.hpp>

// ... somewhere inside the `bar(int)` function that is called recursively:
std::cout << boost::stacktrace::stacktrace();

Homme ici: https://www.boost.org/doc/libs/1_65_1/doc/html/stacktrace.html

2
Barkles

Bien sûr, la question suivante est la suivante: cela suffira-t-il?

Le principal inconvénient des traces de pile est que, si vous avez la fonction précise appelée, vous n'avez rien d'autre, comme la valeur de ses arguments, ce qui est très utile pour le débogage.

Si vous avez accès à gcc et à gdb, je suggérerais d'utiliser assert pour vérifier une condition spécifique et de produire un vidage de la mémoire si elle n'est pas remplie. Bien sûr, cela signifie que le processus va s’arrêter, mais vous aurez un rapport complet à la place d’une simple trace de pile.

Si vous souhaitez une méthode moins intrusive, vous pouvez toujours utiliser la journalisation. Il existe des installations d’exploitation forestière très efficaces, comme Pantheios par exemple. Ce qui une fois de plus pourrait vous donner une image beaucoup plus précise de ce qui se passe.

2
Matthieu M.

Je sais que ce fil est vieux, mais je pense qu'il peut être utile à d'autres personnes. Si vous utilisez gcc, vous pouvez utiliser les fonctions de l’instrument (option -finstrument-functions) pour enregistrer tout appel de fonction (entrée et sortie). Jetez un oeil à cela pour plus d'informations: http://hacktalks.blogspot.fr/2013/08/gcc-instrument-functions.html

Vous pouvez ainsi, par exemple, insérer et insérer tous les appels dans une pile et, lorsque vous souhaitez l’imprimer, il vous suffit de regarder ce que vous avez dans votre pile.

Je l'ai testé, il fonctionne parfaitement et est très pratique

UPDATE: vous pouvez également trouver des informations sur l'option de compilation -finstrument-functions dans le document GCC concernant les options d'instrumentation: https://gcc.gnu.org/onlinedocs/gcc/Instrumentation-Options.html

1
François

Vous pouvez utiliser le profileur GNU. Il montre également le graphe d’appel! La commande est gprof et vous devez compiler votre code avec une option.

0
Saurabh Shah