Tuesday, September 15, 2009

Debugging sur DS

En règle générale, j'aime encore bien le débugging "à la dure" à grand coup de désassembleur et autres guru meditation, mais de temps en temps, ça ne suffit plus. Je ne suis pas contre le débugging "tea-time" avec une bonne tisane, un fluo et quelques listings, ceci dit ...

I usually enjoy the "guru meditation" kind of debugging, analyzing machine code and registers dump, with a good cup of tea, listings and a "swing" fluo pen. Yet, this time it wasn't enough, so i reused the kind of techniques i've mastered toying with my network processor, a few years ago. Since there are tons of tutos on using GDB in English, I'll focus on trying to give you the feeling of what's going on.
Mais cette fois-ci, c'est le module player NTXM qui tourne sur l'ARM7 qui posait problème, et sur l'ARM7, pas de moyen facile de sortir des messages d'erreur. J'avais bien bricolé un petit mécanisme de "rapport d'erreur" pour libntxm, qui restait désespérément muet. Bref, c'est un peu par chance que j'ai découvert que la version 0.7.3 de desmume (sous Linux, en tout cas) intègre un double "stub GDB". Du coup, moi:

desmume-cli gedsdemo.nds --arm7gdb=6667 --arm9gdb=6669
ln -s `which arm-eabi-gdb` gdb
ddd runme/arm7/runme.arm7.elf &
ddd gedsdemo.elf &
Ca mérite un mot d'explication. GDB est le débugguer intégré au compilateur gcc et g++ dans les systèmes Linux, les distributions cygwin, et le devkitpro. Dans la plupart des cas, on appellera directement le débuggueur avec le programme à corriger sur le système-cible, mais pour le développement embarqué, on sait rarement faire tourner le débuggueur complet sur le système-cible. Dans ce cas, on fait tourner une version minimale du debuggueur (le stub) avec le programme, sur le système-cible (ici, l'émulateur DS) et le front-end peut tourner sur un autre système (le PC linux, en l'occurence). Desmume intègre deux 'stubs', un pour chaque processeur de la DS, et on donne au démarrage le numéro de port à utiliser pour chacun d'eux.

desmume-cli gedsdemo.nds --arm7gdb=6667 --arm9gdb=6669
Gdb is the natural companion debugger in the GNU gcc/g++ compiler suite. It is a command-line debugger with a fairly crude (compared e.g. to Borland's debugger), but very complete and powerful interface. It is naturally available in the devkitpro toolchain as arm-eabi-gdb binary where it can disassemble and understand ARM7 and ARM9 machine code as long as they are in .ELF binaries. To get a fancy environment that lets me browse source and data structure, i use DDD (data display debugger) that acts as a front-end for a gdb session that it will launch and completely manage for you. And as crème de la crème, DDD also provides you a direct interface through the GDB session by means of a console at the bottom of its window, so if some magic button is missing for you to use the gdb tricks you know, just type it and you'll get it.
Ensuite, on démarre le débuggueur en lui donnant le fichier ELF à manipuler. Personnellement, j'utilise ddd (Data Display Debugger), qui permet l'affichage des données en graphe et le suivi de l'exécution dans les sources, en plus de toutes les fonctionnalités de gdb (breakpoints, inspection de la pile et des registres, etc.)
L'astuce est d'expliquer à ddd d'utiliser la version de gdb pour processeur ARM du devkitpro plutôt que la version pour intel propre à ma machine. Il suffit pour ça de créer un lien symbolique du nom de "gdb" pointant vers arm-eabi-gdb dans un des répertoires couverts par la variable $PATH. Dans mon cas, le répertoire courant fait l'affaire.

Reste à expliquer à chacun des gdb qu'il doit s'attacher à un stub existant plutôt que d'essayer de faire tourner lui-même le programme (par la commande run, qu'on aurait utilisé dans une session de débugging classique). Pour ça, j'utilise

target remote localhost:6667
dans la fenêtre qui fait tourner le code ARM7 et

target remote localhost:6669
dans la fenêtre qui fait tourner le code ARM9. Histoire de pouvoir suivre l'exécution des programmes, j'ai donné un "break main" dans chaque fenêtre, histoire d'interrompre le programme lors du début de son exécution en C.

Now, the system you want to debug is different from the system that runs the debugger. You may not be used to it, but remember that it is an über-common situation for people doing cross-compiling, kernel development and embedded systems. Debugging your NDS program with DDD and Desmume emulator just happens to be a specific instance of this problem. The debugged system will have to provide a (lightweight) stub for gdb, that is, a piece of code that blindly grab memory content, dumps registers, enforce breakpoints and stuff. On your debugging system, gdb has the guts to attach to this stub through the "target remote " command. Once attached, it all happens as if your debugged system had an extra process running gdb through a window of your desktop. Enjoy conditional breakpoints, data structure browsing and all ^_^.

Le reste dépend évidemment du problème qu'on cherche à résoudre. Ici, c'était une histoire de communication entre ARM7 et ARM9 (du moins, c'est ce que je croyais). Un breakpoint côté ARM9 sur l'envoi de commandes m'apprend que cette partie-là s'exécute bien sans problème, par contre mon breakpoint sur "calcNextPos" dans le module player n'est jamais appelé. Je passe en revue l'initialisation de l'ARM7 pas à pas et je me rends compte qu'il bloque au niveau de l'initialisation du Wifi ... logique, le code ARM9 pour la démo n'utilisait pas le Wifi. Faute d'un "ppp-nowifi.arm7", j'ajoute un "new Wifi(...); Wifi_Disable()" dans le code de ma démo pour pouvoir faire ma release "dans les temps" (avant le 9/9/2009 :)

PS: pour ceux que ça intéresse, je précise que QEMU (émulateur PC) intègre également un stub gdb, et que pour les autres programmes (p.ex. mes outils sur network processor), il suffit d'utiliser gdbserver qui supporte aussi bien les connexions TCP que le debugging sur cable null-modem.

Ca marche tellement bien que pour les tests que je veux faire sur mon LevelEditor, je vais carrément construire une "version light" (ledsdemo) qui tourne sous desmume et développer les nouveaux widgets, etc. dans cet environnement-là. Je le sens bien.

To conclude, 0xtob's library wasn't to blame if i got no sound: i made a slight mistake in my ARM7 initialisation code that prevented the module player from ever executing. Oh, and btw, yes, I am aware that a GDB stub running on bare DS hardware and accepting connections through WiFi is available. It just haven't been needed so far.

Just one last trick that is handy: ddd comes with tons of options to select the "inferior debugger", but i just prefer symlinking arm-eabi-gdb so that it is found under the name "gdb" when the $PATH is scanned, looking for the command to be run. Aliases won't work here.

1 comment:

PypeBros said...

ddd n'est malheureusement pas trop doué pour mettre des breakpoints sur des noms de fonction C++. Il vaudra généralement mieux passer par un "arm-eabi-objdump -x gedsdemo.elf | grep doslopes" pour retrouver le nom exact (en l'occurence _ZN10GameObject8doslopesEi) à utiliser pour les commandes "break"...