2010-07-17 31 views
3

GNU libc's backtrace et In-circuit emulators/debuggers ne sont pas toujours disponibles lorsque le code portage vers une nouvelle plate-forme, en particulier lorsque la cible est un compilateur micro C tel que pour le Z80. (Généralement, un bogue de programme «se bloque» quelque part ou plante le gadget.)Une façon simple, uniforme et portable, y compris le suivi et tracé rétrograde dans un programme C

Existe-t-il une alternative à la méthode classique «wolf fencing» pour insérer manuellement printf? Quelque chose de simple et de portable (n'utilisant pas d'extensions C) qu'un codeur peut faire en développant un programme qui inclut le traçage et le backtracing dans un programme C?

BTW: Voici quelques autres questions sur stackoverflow qui sont liées, mais ceux-ci utilisent tous les deux GNU GLIBC's backtrace et backtrace est souvent compilateur/implémentation spécifique:

Répondre

2

Il y a une mise en œuvre à RosettaCode.org qui utilise la même idée de base que la suggestion de @ jsl4tv.

exemple, étant donné le code classique suivant C avec construit dans "accrocher":

#include <stdio.h> 
#include <stdlib.h> 

void inner(int k) 
{ 
    for(;;){} /* hang */ 
} 

void middle(int x, int y) 
{ 
    inner(x*y); 
} 

void outer(int a, int b, int c) 
{ 
    middle(a+b, b+c); 
} 

int main() 
{ 
    outer(2,3,5); 
    return(EXIT_SUCCESS); 
} 

#define STACK_TRACE_ON et #include "stack_trace.h" de RosettaCode.org puis insérer BEGIN (f)/se termine là où nécessaire:

#include <stdio.h> 
#include <stdlib.h> 

#define STACK_TRACE_ON /* compile in these "stack_trace" routines */ 
#include "stack_trace.h" 

void inner(int k) 
BEGIN(inner) 
    print_indent(); printf("*** Now dump the stack ***\n"); 
    print_stack_trace(); 
    for(;;){} /* hang */ 
END 

void middle(int x, int y) 
BEGIN(middle) 
    inner(x*y); 
END 

void outer(int a, int b, int c) 
BEGIN(outer) 
    middle(a+b, b+c); 
END 

int main() 
BEGIN(main) 
    stack_trace.on = TRUE; /* turn on runtime tracing */ 
    outer(2,3,5); 
    stack_trace.on = FALSE; 
    RETURN(EXIT_SUCCESS); 
END 

Produit:

stack_trace_test.c:19: BEGIN outer[0x80487b4], stack(depth:1, size:60) 
stack_trace_test.c:14: BEGIN middle[0x8048749], stack(depth:2, size:108) 
stack_trace_test.c:8:  BEGIN inner[0x80486d8], stack(depth:3, size:156) 
stack_trace_test.c:8:  *** Now dump the stack *** 
stack_trace_test.c:8: inner[0x80486d8]  --- stack(depth:4, size:156) --- 
stack_trace_test.c:14: middle[0x8048749]  --- stack(depth:3, size:108) --- 
stack_trace_test.c:19: outer[0x80487b4]  --- stack(depth:2, size:60) --- 
stack_trace_test.c:24: main[0x804882a] --- stack(depth:1, size:0) --- 
stack_trace_test.c:8:  --- (depth 4) --- 

Une version bien polie [open source] de cette méthode BEGIN ~ END serait parfaite. (Esp s'il a une clause "FINALEMENT" pour le traitement des exceptions).

Conseils/URL appréciés.

+0

Bien! Cela va dans ma liste de signets. – jsl4tv

+0

@ jsl4tv - J'ai utilisé une implémentation il y a des années, ça fonctionnait même sur K & R C, mais ce n'était pas open-source ... par conséquent "le chien a mangé le code source!" – NevilleDNZ

+0

La seule chose qui me manque dans le code rosetta, une chose importante qui manque, c'est le code pour instrumentaliser/décorer votre source automagiquement. Bien sûr, ma prochaine demande de fonctionnalité serait la plus difficile "... et les paramètres aussi". Je * pense * que vous pourriez obtenir cela d'une manière portable en faisant toutes les fonctions varargs et en utilisant l'accès poli/standard aux args. Grand article à faire! – jsl4tv

0

Sur Symbian, il y avait des scripts pour parcourir les registres et empiler des choses qui ressemblaient à des adresses de code.

Ce n'est pas portable, mais cela ne dépend pas non plus de la décoration du code. C'était un compromis nécessaire sur une plateforme où le nombre d'octets comptait ... et ce n'était pas aussi limité que le Z80! Mais assez limité pour compiler sans frame-pointeurs et autres.

Pour calculer un backtrace à partir d'une pile sans pointeur de trame, vous devez travailler sur la pile et non sur celle-ci.

+0

Donné un choix de code C de décoration ou en utilisant une autre langue: le code de décoration semble plus portable. Surtout si la décoration du code est bien rangée et peut être insérée avec un script. – NevilleDNZ

4

Voici le noyau du noyau de ma réponse: écris du code.

Le noyau de ma réponse est: Si votre compilateur attribue la population locale sur la pile toujours, alors ...

Ajouter blobs à la pile à chaque entrée de fonction qui enregistre le nom de la fonction, jeter dans la magie des nombres pour peut-être attraper des cassures de pile.

typedef struct stack_debug_blob_ { 
    int magic1; 
    const char * function_name; 
    int magic2; 
    struct stack_debug_blob_ * called_by; 
    int magic3; 
} stack_debug_blob; 

stack_debug_blob * top_of_stack_debug_blobs = 0; 

Créez une macro ENTRÉE (f) en prenant le nom de la fonction. La macro devrait être sur la première ligne de code dans chaque fonction après l'ouverture {. Il ajoute une structure avec un pointeur sur le nom de la fonction (const) char *, un pointeur vers la structure précédente sur la pile, et peut-être quelques nombres magiques pour vérifier la santé mentale. Faire le haut du pointeur de pile de blob à cette nouvelle structure. Pour que les choses restent aussi portables que possible, tout ce que peut faire ENTER est de déclarer et d'initialiser les variables. D'où le evil_hack pour faire un peu plus de calculs que juste initialiser une variable.

Créez une fonction pour parcourir la liste des pointeurs de vérification des blobs et des nombres magiques. Il devrait signaler une erreur (peut-être imprimer sur stderr, peut-être bloquer le cpu avec while (1) {/ * nada * /}, peut-être entrer dans le débogueur ... dépend de votre matériel) s'il trouve des choses foirées.

Créez une macro EXIT() qui vérifie votre pile de blobs, puis désolidarise le plus haut de la liste chaînée. Il doit être placé aux points de sortie de toutes vos fonctions.

#define EXIT() do {           \ 
    check_debug_blobs();          \ 
    top_of_stack_debug_blobs = new_stack_debug_blob.called_by; \ 
    new_stack_debug_blob.magic1 -= 1; /* paranoia */   \ 
} while (0) 

sera probablement aussi besoin de remplacer par des appels de macro RETURN de tous retour, la macro RETURN est comme EXIT, mais il a un retour avant la} while (0).

Créez une fonction pour parcourir la liste des blobs en imprimant les noms des fonctions, appelez-la quelque chose comme stacktrace ou backtrace.

Écrivez un programme pour instrumenter votre code C avec des appels à ENTER (f) et EXIT() et RETURN (x).

Gauche quelques détails pour vous permettre de vous amuser avec elle ...

Voir aussi Any porting available of backtrace for uclibc?

+2

magic1, magic2 et magic3 ont l'air utiles. – NevilleDNZ

+0

vous pouvez renommer 'ENTER' comme' ENTER_' et '#define ENTER() ENTER _ (__ FUNCTION __)'. –