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 certaine fonction est appelée? Ce que j'ai à l'esprit est quelque chose comme ceci:
void foo()
{
print_stack_trace();
// foo's body
return
}
Où print_stack_trace
fonctionne de la même manière caller
qu'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...
}
où register_stack_trace_function
met une sorte de point d'arrêt interne qui provoquera l'impression d'une trace de pile à chaque foo
appel.
Est-ce que quelque chose comme ça existe dans une bibliothèque C standard?
Je travaille sous 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 nombres pseudo-aléatoires 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.
s/easier/either/
Comment diable est-ce arrivé?s/either/easier
. Ce que j'aurais besoin de faire avec gdb, c'est d'écrire un script qui interrompt cette fonction et imprime la trace de la pile, puis continue. Maintenant que j'y pense, il est peut-être temps pour moi d'en apprendre davantage sur les scripts gdb.Réponses:
Pour une solution Linux uniquement, vous pouvez utiliser backtrace (3) qui renvoie simplement un tableau de
void *
(en fait, chacun de ces points 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) :
la source
glibc
, malheureusement, lesbacktrace_symbols
fonctions ne fournissent pas le nom de la fonction, le nom du fichier source et le numéro de ligne.-rdynamic
, vérifiez également que votre système de construction n'ajoute pas d'-fvisibility=hidden
option! (car il supprimera complètement l'effet de-rdynamic
)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 en fait imprimer les numéros de ligne.
Il vient en fait des appels à
addr2line
cependant , ce qui est laid et pourrait être lent si votre prennent 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 très probablement
boost_stacktrace.cpp
Malheureusement, cela semble être un ajout plus récent, et le package
libboost-stacktrace-dev
n'est pas présent dans Ubuntu 16.04, seulement 18.04:Nous devons ajouter
-ldl
à la fin sinon la compilation échoue.Production:
La sortie et est expliquée plus en détail dans la section "Glibc Backtrace" ci-dessous, qui est analogue.
Notez comment
my_func_1(int)
etmy_func_1(float)
, qui sont mutilés en raison d'une surcharge de fonction , ont été bien démêlés pour nous.Notez que les premiers
int
appels sont coupés d'une ligne (28 au lieu de 27 et le second de deux lignes (27 au lieu de 29). Il a été suggéré dans les commentaires que cela est dû au fait que l'adresse d'instruction suivante est considérée, ce qui fait que 27 devient 28 et 29 saute de la boucle et devient 27.On observe alors qu'avec
-O3
, la sortie est complètement mutilée: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
:Production:
Donc, comme prévu, nous voyons que cette méthode est extrêmement lente pour les appels externes
addr2line
, et ne sera faisable que si un nombre limité d'appels est effectué.Chaque impression de trace arrière semble prendre des centaines de millisecondes, alors soyez averti que si une trace arrière 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
principal c
Compiler:
-rdynamic
est l'option clé requise.Courir:
Les sorties:
Nous voyons donc immédiatement qu'une optimisation en ligne s'est produite, et certaines fonctions ont été perdues de la trace.
Si nous essayons d'obtenir les adresses:
on obtient:
qui est complètement éteint.
Si nous faisons la même chose avec à la
-O0
place,./main.out
donne la trace complète correcte:puis:
donne:
donc les lignes sont décalées d'un seul, TODO pourquoi? Mais cela pourrait encore être utilisable.
Conclusion: les backtraces ne peuvent apparaître parfaitement qu'avec
-O0
. Avec les optimisations, la trace d'origine est fondamentalement modifiée dans le code compilé.Je n'ai pas pu trouver un moyen simple de démêler automatiquement les symboles C ++ avec ceci cependant, voici quelques hacks:
Testé sur Ubuntu 16.04, GCC 6.4.0, libc 2.23.
glibc
backtrace_symbols_fd
Cet assistant est un peu plus pratique que
backtrace_symbols
, et produit une sortie fondamentalement identique:Testé sur Ubuntu 16.04, GCC 6.4.0, libc 2.23.
glibc
backtrace
avec piratage de démêlage C ++ 1:-export-dynamic
+dladdr
Adapté de: https://gist.github.com/fmela/591333/c64f4eb86037bb237862a8283df70cdfc25f01d3
C'est un "hack" car il faut changer l'ELF avec
-export-dynamic
.glibc_ldl.cpp
Compilez et exécutez:
production:
Testé sur Ubuntu 18.04.
glibc
backtrace
avec C ++ demangling hack 2: analyse de la sortie de la trace de retourMontré à: https://panthema.net/2008/0901-stacktrace-demangled/
Ceci est un hack car il nécessite une analyse.
TODO le compiler et le montrer ici.
libunwind
TODO est-ce que cela a un avantage sur le backtrace glibc? Une sortie très similaire, nécessite également de modifier la commande build, mais ne fait pas partie de la glibc donc nécessite une installation de package supplémentaire.
Code adapté de: https://eli.thegreenplace.net/2015/programmatic-access-to-the-call-stack-in-c/
principal c
Compilez et exécutez:
Soit il
#define _XOPEN_SOURCE 700
faut être au top, soit il faut utiliser-std=gnu99
:Courir:
Production:
et:
donne:
Avec
-O0
:et:
donne:
Testé sur Ubuntu 16.04, GCC 6.4.0, libunwind 1.1.
libunwind avec démêlage de nom C ++
Code adapté de: https://eli.thegreenplace.net/2015/programmatic-access-to-the-call-stack-in-c/
dérouler.cpp
Compilez et exécutez:
Production:
et puis nous pouvons trouver les lignes de
my_func_2
etmy_func_1(int)
avec:qui donne:
TODO: pourquoi les lignes sont-elles une par une?
Testé sur Ubuntu 18.04, GCC 7.4.0, libunwind 1.2.1.
Automatisation GDB
Nous pouvons également faire cela avec GDB sans recompiler en utilisant: Comment faire une action spécifique quand un certain point d'arrêt est atteint dans GDB?
Bien que si vous envisagez d'imprimer beaucoup la trace arrière, ce sera probablement moins rapide que les autres options, mais peut-être que nous pouvons atteindre des vitesses natives avec
compile code
, mais je suis paresseux de le tester maintenant: Comment appeler l'assembly dans gdb?main.cpp
main.gdb
Compilez et exécutez:
Production:
TODO Je voulais faire cela avec juste à
-ex
partir de la ligne de commande pour ne pas avoir à créermain.gdb
mais je ne pouvais pas fairecommands
fonctionner le.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 évaluer un peu plus, mais veuillez voter pour cette réponse.
TODO: J'ai essayé de minimiser le code de cette réponse, qui fonctionnait, à une seule fonction, mais il s'agit de segfaulting, faites-moi savoir si quelqu'un peut trouver pourquoi.
dwfl.cpp
Compilez et exécutez:
Production:
Analyse comparative:
Production:
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 19.10 amd64, libdw-dev 0.176-1.1.
Voir également
la source
Il n'y a pas de moyen normalisé de le faire. Pour Windows, la fonctionnalité est fournie dans la bibliothèque DbgHelp
la source
Vous pouvez utiliser une fonction macro au lieu d'une instruction return dans la fonction spécifique.
Par exemple, au lieu d'utiliser return,
Vous pouvez utiliser une fonction macro.
Chaque fois qu'une erreur se produit dans une fonction, vous verrez une pile d'appels de style Java comme indiqué ci-dessous.
Le code source complet est disponible ici.
c-callstack à https://github.com/Nanolat
la source
Une autre réponse à un vieux fil.
Lorsque j'ai besoin de faire cela, j'utilise généralement
system()
etpstack
Donc quelque chose comme ça:
Cette sortie
Cela devrait fonctionner sous Linux, FreeBSD et Solaris. Je ne pense pas que macOS ait pstack ou un simple équivalent, mais ce fil semble avoir une alternative .
Si vous utilisez
C
, vous devrez utiliserC
des fonctions de chaîne.J'ai utilisé 7 pour le nombre maximum de chiffres dans le PID, basé sur ce post .
la source
Spécifique à Linux, TLDR:
backtrace
inglibc
produit des traces de pile précises uniquement lorsqu'il-lunwind
est lié (fonctionnalité non documentée spécifique à la plateforme).#include <elfutils/libdwfl.h>
(cette bibliothèque n'est documentée que dans son fichier d'en-tête).backtrace_symbols
etbacktrace_symbolsd_fd
sont les moins informatifs.Sur Linux moderne, vous pouvez obtenir les adresses stacktrace en utilisant function
backtrace
. Le moyen non documenté debacktrace
produire des adresses plus précises sur les plates-formes populaires consiste à établir un lien avec-lunwind
(libunwind-dev
sur Ubuntu 18.04) (voir l'exemple de sortie ci-dessous).backtrace
utilise la fonction_Unwind_Backtrace
et par défaut celle-ci provientlibgcc_s.so.1
et cette implémentation est la plus portable. Quand-lunwind
est lié, il fournit une version plus précise de_Unwind_Backtrace
mais cette bibliothèque est moins portable (voir les architectures prises en charge danslibunwind/src
).Malheureusement, le compagnon
backtrace_symbolsd
et lesbacktrace_symbols_fd
fonctions n'ont pas été en mesure de résoudre les adresses stacktrace en noms de fonction avec le nom du fichier source et le numéro de ligne depuis probablement une décennie maintenant (voir l'exemple de sortie ci-dessous).Cependant, il existe une autre méthode pour résoudre les adresses en symboles et elle produit les traces les plus utiles avec le nom de la fonction , le fichier source et le numéro de ligne . La méthode consiste à
#include <elfutils/libdwfl.h>
établir un lien avec-ldw
(libdw-dev
sur Ubuntu 18.04).Exemple de travail C ++ (
test.cc
):Compilé sur Ubuntu 18.04.4 LTS avec gcc-8.3:
Les sorties:
Quand no
-lunwind
est lié, cela produit un stacktrace moins précis:À titre de comparaison, la
backtrace_symbols_fd
sortie pour le même stacktrace est la moins informative:Dans une version de production (ainsi que la version de langue C) , vous pouvez vous faire ce code supplémentaire robuste en remplaçant
boost::core::demangle
,std::string
etstd::cout
avec leurs appels sous - jacents.Vous pouvez également remplacer
__cxa_throw
pour capturer le stacktrace lorsqu'une exception est levée et l'imprimer lorsque l'exception est interceptée. Au moment où il entre dans lecatch
bloc, la pile a été déroulée, il est donc trop tard pour appelerbacktrace
, et c'est pourquoi la pile doit être capturée surthrow
laquelle est implémentée par fonction__cxa_throw
. Notez que dans un programme multi-thread__cxa_throw
peut être appelé simultanément par plusieurs threads, de sorte que s'il capture le stacktrace dans un tableau global qui doit êtrethread_local
.la source
-lunwind
problème a été découvert lors de la création de cet article, je l'utilisais auparavantlibunwind
directement pour obtenir le stacktrace et j'allais le publier, mais lebacktrace
fait pour moi quand il-lunwind
est lié.gcc
n'expose pas l'API, n'est-ce pas?Vous pouvez implémenter vous-même la fonctionnalité:
Utilisez une pile globale (chaîne) et au début de chaque fonction, poussez le nom de la fonction et d'autres valeurs (par exemple, des paramètres) sur cette pile; à la sortie de la fonction, le pop à nouveau.
Écrivez une fonction qui imprimera le contenu de la pile lorsqu'elle sera appelée et utilisez-la dans la fonction où vous voulez voir la pile d'appels.
Cela peut sembler beaucoup de travail mais c'est assez utile.
la source
call_registror MY_SUPERSECRETNAME(__FUNCTION__);
qui pousse l'argument dans son constructeur et apparaît dans son destructeur FUNCTION représente toujours le nom de la fonction courante.Bien sûr, la question suivante est: cela suffira-t-il?
Le principal inconvénient de stack-traces est que pourquoi 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 vous suggère 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 s'arrêtera, mais vous aurez un rapport complet au lieu d'une simple trace de pile.Si vous souhaitez un moyen moins intrusif, 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.
la source
Vous pouvez utiliser Poppy pour cela. Il est normalement utilisé pour rassembler la trace de la pile lors d'un crash, mais il peut également le générer pour un programme en cours d'exécution.
Maintenant, voici la bonne partie: il peut générer les valeurs de paramètres réelles pour chaque fonction de la pile, et même des variables locales, des compteurs de boucle, etc.
la source
Je sais que ce fil est vieux, mais je pense qu'il peut être utile pour d'autres personnes. Si vous utilisez gcc, vous pouvez utiliser ses fonctions d'instrument (option -finstrument-functions) pour enregistrer n'importe quel appel de fonction (entrée et sortie). Jetez un œil à ceci pour plus d'informations: http://hacktalks.blogspot.fr/2013/08/gcc-instrument-functions.html
Vous pouvez ainsi, par exemple, pousser et insérer tous les appels dans une pile, et lorsque vous voulez l'imprimer, vous n'avez qu'à regarder ce que vous avez dans votre pile.
Je l'ai testé, il fonctionne parfaitement et est très pratique
MISE À JOUR: vous pouvez également trouver des informations sur l'option de compilation -finstrument-functions dans la doc GCC concernant les options d'Instrumentation: https://gcc.gnu.org/onlinedocs/gcc/Instrumentation-Options.html
la source
Vous pouvez utiliser les bibliothèques Boost pour imprimer la pile d'appels actuelle.
Homme ici: https://www.boost.org/doc/libs/1_65_1/doc/html/stacktrace.html
la source
cannot locate SymEnumSymbolsExW at C:\Windows\SYSTEM32\dbgeng.dll
sur Win10.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.la source
Non, il n'y en a pas, bien que des solutions dépendant de la plate-forme puissent exister.
la source