Comment peut itérer manuellement les images de la stack en C?

Tout en gérant les signaux dans les applications, je peux voir correctement la trace dans le débogueur. Mais l’appel système à la trace ne montre pas correctement les images de stack. Existe-t-il une différence dans la manière dont gdb stocke les images de stack et comment l’appel système à trace les vide?

Vous ne pouvez pas parcourir de manière portable les trames de stack dans C99 ou C11 .

D’abord parce qu’il n’y a aucune garantie de stack d’appels dans la norme C. (on pourrait imaginer un compilateur C effectuant une parsing complète du programme et évitant la stack si cela ne sert à rien, par exemple si la récursivité ne peut pas se produire; je ne connais pas un tel compilateur C). Voir par exemple cette question de la FAQ C pour les implémentations étranges du C.

Ensuite, étant donné que le compilateur peut parfois procéder à certaines optimisations , par exemple, certains appels en ligne (même pour des fonctions non marquées en inline , en particulier lorsque vous demandez des optimisations de temps de liaison avec l’ -flto transmise à gcc ), ou émettre parfois des appels en -flto (et GCC peut faire les deux). Vous pouvez désactiver les optimisations, mais vous perdez alors beaucoup de performances. Un compilateur optimiseur placerait certaines variables uniquement dans des registres et réutiliserait certains emplacements de stack pour plusieurs variables.

Enfin, sur certaines architectures (par exemple 32 bits x86), un code (en particulier un code de bibliothèque, par exemple à l’intérieur de libc ) peut être compilé avec -fomit-frame-pointer sans -fomit-frame-pointer il est impossible d’obtenir des informations sur les frameworks.

Vous pouvez utiliser la libbacktrace de Ian Taylor dans GCC ; vous pouvez aussi utiliser la fonction backtrace (3) de GNU glibc ; vous pouvez même utiliser les adresses intégrées de retour disponibles lors de la compilation avec GCC . Mais tous ces outils pourraient ne pas fonctionner sur du code optimisé.

Concrètement, si vous avez vraiment besoin d’un peu de backtrace, implémentez-le vous-même (je le fais dans mon système MELT , dont le code C ++ est généré, en regroupant les locaux dans une struct locale), ou évitez d’optimiser trop, par exemple en compilant avec gcc -g -O1 uniquement.

Notez que backtrace n’est pas un appel système (répertorié dans syscalls (2) ), mais une fonction de bibliothèque spécifique à la glibc .

Lisez aussi très attentivement signal (7) et sigreturn (2) . Très peu de fonctions (async-signal-safe-safe) peuvent être appelées de manière fiable (directement ou indirectement) par les gestionnaires de signaux, et backtrace (ou printf ) n’en fait pas partie. En pratique, un gestionnaire de signal portable devrait souvent simplement définir un indicateur volatile sigatomic_t que vous devriez tester ailleurs – ou appeler siglongjmp (3) .

Le débogueur utilise un ensemble supplémentaire de données supplémentaires placées dans le fichier binary par le compilateur par gcc lorsque vous utilisez l’option -g . Ces données ne sont pas utilisées par l’appel de trace et seules les informations de base de l’éditeur de liens sont utilisées. Cela signifie par exemple que toutes les données statiques ne sont pas visibles par la trace, mais par gdb. Cela entraîne également diverses optimisations qui détruisent la trace que gdb utilise grâce à des connaissances explicites.

Rappelez-vous que gdb est spécifique à un langage et à un compilateur spécifiques, alors que le retour en arrière est beaucoup plus portable.

Voir la page de manuel de backtrace http://linux.die.net/man/3/backtrace :

L’omission des pointeurs de trame (comme l’indique l’un quelconque des niveaux d’optimisation non nuls de gcc (1)) peut entraîner la violation de ces hypothèses.

Si l’appel de trace voulait utiliser ces informations, il devrait vous obliger à toujours comstackr avec les symboles de débogage, ce qui entraînerait une surcharge beaucoup plus importante et de nombreux autres problèmes.

Le problème peut être que, au moment où la trace est exécutée, votre stack a peut-être été corrompue.

Il suffit de prendre une adresse de variable locale dans la fonction. Calculez la taille de la stack. ajoutez-le à partir de l’adresse de variable locale et imprimez-le entre l’adresse de variable d’addition et d’emplacement;

vous avez votre stack imprimée 🙂