vendredi, mai 14, 2010

Debugging Industriel ?

Finalement, j'ai opté pour une approche toute neuve (pour moi) pour ce problème de malloc. C'est toujours via gdb que j'attaque le problème, mais je laisse tomber la sur-couche graphique (ddd) au profit d'un petit script perl. Celui-ci va placer les breakpoints à l'entrée et à la sortie des fonctions "malloc" et "free" (ainsi qu'à des emplacements stratégiques dans les opérateurs new et delete du C++) et enregistrer consciencieusement la pile d'appels, le contenu des registres du processeur ARM9 et le contenu du tableau __malloc_av_ dont j'ai parlé précédemment, et qui représente la "partie visible" de l'état maintenu par malloc.

Ce côté "totalement automatique" permet d'obtenir une trace exhaustive (64Mo) de ce qui s'est passé au niveau gestion de la mémoire pendant l'exécution du programme, que je peux ensuite traiter avec d'autres scripts. Par exemple pour vérifier qu'il y a bien eu un appel a free avant chaque delete operator, et surtout qu'il y a un malloc()=X pour chaque free(X), et l'emplacement dans la trace de la dernière opération sur l'adresse X dans le cas contraire.

Avec un peu d'ajustement, je devrais même être capable de trouver les malloc() pour lesquels il n'y a aucun free .. et donc potentiellement d'identifier les fuites de mémoire dans mon code. Bien sûr, il existe d'autres programmes pour faire ça, et ce de manière plus automatique (valgrind, si ma mémoire est bonne). Il y a aussi des implémentations qui intègrent ce genre de tests (cf. la variable d'environnement MALLOC_CHECK_ sous Linux) ... je profite du côté "hobby" de ce projet pour découvrir et tester des hypothèses, me construire de petits outils et tout ça ...

Et j'ai finalement mis le doigt sur le problème ... une bête initialisation manquante dans la classe GobState. C++ n'aide vraiment pas le programmeur, de ce point de vue-là. Je devrais m'habituer à compiler avec -Weffc++, s'il ne contenait pas une quantité ridicule de warnings qui me sont inutiles.

2 commentaires:

PypeBros a dit…

# tracemem.pl
$mallocAV = "0x0205b970";

# nm gedsdemo -->> 0203078d T malloc
$malloc = "0x0203078c"; # actually at ....8c

# unfortunately, there is no invocation of _Znwj (the C++ allocator)
# that we can backtrace >_<. Idea is thus to set a breakpoint at the
# exit to catch the location from r1 when it is about to jump back to
# the caller.
# just do 'n' at that position, and you'll be clear to invoke bt again.

$end_operator_new = "0x20298ba"; # _Znwj
# 20298ba: 4708 bx r1
$end_operator_delete = "0x202acbc"; # _ZdlPv
$end_malloc = "0x203079e";


$free = "0x2030774";

# also set a breakpoint at

$|=1;

print <<___
target remote localhost:$ARGV[0]
break *$malloc
break *$end_malloc
break *$end_operator_delete
break *$end_operator_new
break *$free
c
___
;

while (1) {
print <<___
bt
info r
x /32x $mallocAV
ni
bt
c
___
;

}

PypeBros a dit…

#checkmem.pl

# nm gedsdemo -->> 0203078d T malloc
$malloc = "0x0203078c"; # actually at ....8c

# unfortunately, there is no invocation of _Znwj (the C++ allocator)
# that we can backtrace >_<. Idea is thus to set a breakpoint at the
# exit to catch the location from r1 when it is about to jump back to
# the caller.
# just do 'n' at that position, and you'll be clear to invoke bt again.

$end_operator_new = "0x20298ba"; # _Znwj
# 20298ba: 4708 bx r1
$end_operator_delete = "0x202acbc"; # _ZdlPv
$end_malloc = "0x203079e";


$free = "0x2030774";

# also set a breakpoint at

$|=1;
while (<>) {
$lineno++;
if (/Breakpoint (\d), 0x([0-9a-f]+) in (.*)/) {
die "unexpected breakpoint $1 after $last" if
$1 == 2 && $last!=1 or
$1==4 && $last!=2 or
$1==3 && $last!=5;
$last = $1;
$count++;
while(<>) {
$lineno++;
$addr = $1 if /r0[^a-z0-9A-Z]*0x([0-9a-f]+)/;
last if /Continuing/;
}
if ($last == 2) {
$alloc{$addr}=$lineno;
}
if ($last == 5) {
print STDERR "@ $lineno, who allocated $addr ($alloc{$addr}) ?" if !exists $alloc{$addr} || $alloc{$addr}<0;
$alloc{$addr}=-$lineno;
}
}
}

print STDERR "$count breakpoints checked.\n";