web-dev-qa-db-fra.com

Comment peut-on récupérer une trace de pile en C?

Je sais qu'il n'y a pas de fonction C standard pour ce faire. Je me demandais quelles sont les techniques pour cela sur Windows et * nix? (Windows XP est mon système d'exploitation le plus important pour le faire en ce moment.)

78
Kevin

Nous l'avons utilisé pour nos projets:

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

Le code est un peu désordonné à mon humble avis, mais il fonctionne bien. Windows uniquement.

22
Mark
78
sanxiyn

Il y a backtrace () et backtrace_symbols ():

Depuis la page de manuel:

     #include <execinfo.h>
     #include <stdio.h>
     ...
     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);
     ...

Une façon d'utiliser cela d'une manière plus pratique/OOP consiste à enregistrer le résultat de backtrace_symbols () dans un constructeur de classe d'exception. Ainsi, chaque fois que vous lancez ce type d'exception, vous avez la trace de la pile. Ensuite, fournissez simplement une fonction pour l'imprimer. Par exemple:


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 

...



try {
   throw MyException("Oops!");
} catch ( MyException e ) {
    e.printStackTrace();
}

Ta da!

Remarque: l'activation des indicateurs d'optimisation peut rendre la trace de pile résultante inexacte. Idéalement, on utiliserait cette capacité avec les indicateurs de débogage activés et les indicateurs d'optimisation désactivés.

28
Tom

Pour Windows, vérifiez l'API StackWalk64 () (également sur Windows 32 bits). Pour UNIX, vous devez utiliser la méthode native du système d'exploitation pour le faire, ou recourir à backtrace () de glibc, si disponible.

Notez cependant que prendre un Stacktrace en code natif est rarement une bonne idée - non pas parce que ce n'est pas possible, mais parce que vous essayez généralement de réaliser la mauvaise chose.

La plupart du temps, les gens essaient d'obtenir une trace de pile dans, par exemple, une circonstance exceptionnelle, comme lorsqu'une exception est interceptée, une assertion échoue ou - pire et la plus mauvaise de toutes - lorsque vous obtenez une "exception" fatale ou un signal comme un violation de segmentation.

Compte tenu du dernier problème, la plupart des API vous obligeront à allouer explicitement de la mémoire ou peuvent le faire en interne. Le faire dans l'état fragile dans lequel votre programme peut être actuellement, peut aggraver les choses. Par exemple, le rapport de plantage (ou coredump) ne reflètera pas la cause réelle du problème, mais votre tentative a échoué de le gérer).

Je suppose que vous essayez de réaliser ce problème de gestion des erreurs fatales, car la plupart des gens semblent l'essayer pour obtenir une trace de pile. Si c'est le cas, je compterais sur le débogueur (pendant le développement) et laisser le processus coredump en production (ou mini-dump sur windows). Avec une bonne gestion des symboles, vous ne devriez pas avoir de mal à comprendre les instructions post mortem.

20
Christian.K

Pour Windows, CaptureStackBackTrace() est également une option, qui nécessite moins de code de préparation de la part de l'utilisateur que StackWalk64(). (De plus, pour un scénario similaire que j'ai eu, CaptureStackBackTrace() a fini par fonctionner mieux (de manière plus fiable) que StackWalk64().)

4
Quasidart

Vous devriez utiliser la bibliothèque de déroulement .

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

Votre approche fonctionnerait également très bien, sauf si vous appelez à partir d'une bibliothèque partagée.

Vous pouvez utiliser le addr2line commande sous Linux pour obtenir la fonction source/numéro de ligne du PC correspondant.

4
user262802

Il n'y a aucun moyen indépendant de le faire.

La chose la plus proche que vous pouvez faire est d'exécuter le code sans optimisation. De cette façon, vous pouvez vous attacher au processus (en utilisant le débogueur Visual C++ ou GDB) et obtenir une trace de pile utilisable.

4
Nils Pipenbrinck

Puis-je vous signaler mon article. Ce ne sont que quelques lignes de code.

Débogage post mortem

Bien que j'ai actuellement des problèmes avec le implémentation x64 de ceci .

2
RED SOFT ADAIR

Solaris possède la commande pstack , qui a également été copiée sous Linux.

2
wnoise

Depuis quelques années, j'utilise la libbacktrace d'Ian Lance Taylor. Il est beaucoup plus propre que les fonctions de la bibliothèque GNU C qui nécessitent d'exporter tous les symboles. Il fournit plus d'utilité pour la génération de backtraces que libunwind. Et enfin et surtout, il n'est pas vaincu par ASLR comme le sont les approches nécessitant des outils externes tels que addr2line.

Libbacktrace faisait initialement partie de la distribution GCC, mais il est maintenant mis à disposition par l'auteur en tant que bibliothèque autonome sous licence BSD:

https://github.com/ianlancetaylor/libbacktrace

Au moment d'écrire ces lignes, je n'utiliserais rien d'autre, sauf si j'ai besoin de générer des backtraces sur une plateforme qui n'est pas prise en charge par libbacktrace.

0
Erwan Legrand