Dans la première partie, nous avons abordé deux aspects fondamentaux de Qemu, la recompilation dynamique du code émulé et la gestion du temps. Cependant, nous sommes restés sur le banc des spectateurs. Bien que Qemu 1.0 supporte 27 machines ARM différentes, aucune n’est basée sur les SoC Freescale™ de la famille i.MX. Dans ce second volet, nous passerons à la pratique en créant « from scratch » le support de l’émulation d’une nouvelle machine construite autour d’un i.MX27 : la carte Armadeus APF27.
1. Introduction
D’autres sujets périphériques seront aussi abordés. En effet, la connaissance interne du noyau Linux, ou au moins certaines parties, est nécessaire à la création de modules d’émulation matérielle. Nous ferons quelques détours dans le processus de boot de Linux sur une architecture ARM, ainsi qu’une petite visite de ses horloges internes.
1.1. D’autres outils bien utiles
En première partie, un certain nombre d’outils ont été présentés. Ils étaient suffisants dans un contexte d’exploration du code source et seront encore très utiles dans un cadre de développement. Cependant, il faut étendre notre boîte à outils si l’on veut mener à bien notre nouvelle tâche.
Dans cette seconde partie, nous développerons des modules d’émulation de matériel qui devront se comporter, d’un point de vue du noyau Linux, comme leurs pendants physiques. Comme nous le verrons, cette tâche peut s’avérer assez difficile, car contrairement au développement d’applications classiques, le système n’est pas encore opérationnel lorsque notre code s’exécute. C’est particulièrement le cas de l’émulation de la mémoire qui est l’un des composants à être initialisé en premier par le noyau Linux avant même qu’une console ne soit disponible. Lors de la phase de mise au point et de correction de bugs, qui surviendront inéluctablement, on se retrouve alors " dans le noir " total : le code ne fonctionne pas, mais il est impossible de savoir où, ni pourquoi. Heureusement, Linux et ses options de mise au point vont venir à notre secours. Ces options sont activées par des paramètres passés au noyau et éventuellement en activant certaines options de compilation du noyau. Dans ce cas, une recompilation du noyau sera alors nécessaire pour en bénéficier. Passons-les en revue :
- loglevel=8 permet d’obtenir l’affichage de tous les messages émis par le noyau, jusqu’aux messages de type KERN_DEBUG.
- bootmem_debug permet d’obtenir des messages de débogage du sous-système bootmem. Celui-ci est responsable de l’allocation et de la configuration de la mémoire physique.
- mminit_loglevel=4 offre la possibilité d’obtenir tous les messages supplémentaires concernant l’initialisation et la vérification de la mémoire. Il nécessite l’activation de l’option de compilation du noyau CONFIG_DEBUG_MEMORY_INIT ainsi qu’une recompilation. Les messages produits sont de type KERN_DEBUG, il faut donc que le paramètre loglevel=8 soit lui aussi passé au noyau.
- earlyprintk provoque l’affichage des messages du noyau sur une console " précoce ". Par défaut, aucun message du noyau n’est affiché avant que la console système soit opérationnelle. Cette console est dans notre cas un port série et c’est uniquement lorsque ce port série fonctionne que la console système remplit ses offices. Tous les messages précédents sont alors affichés. Si le noyau plante avant que la console système fonctionne, aucun message ne sera visible, y compris ceux qui sont produits avec les paramètres ci-dessus. On est alors " dans le noir ". Un mécanisme est prévu dans le noyau pour palier cette fâcheuse situation. L’option de compilation CONFIG_EARLY_PRINTK permet la création d’une console " précoce " (early console) et le paramètre du noyau earlyprintk permet de l’activer. Cette console précoce affichera les messages du noyau avant même que la console système fonctionne.
Qemu dispose d’une option réservée au passage de paramètres au noyau Linux : -append. Pour bénéficier des options de débogage de Linux ci-dessus, il faudra donc ajouter à la ligne de commandes de Qemu : -append "console=ttymxc0 loglevel=8 bootmem_debug mminit_loglevel=4 earlyprintk".
Il arrive malheureusement que cela ne suffise pas à déterminer l’origine d’un bug vicieux. Cette fois-ci, c’est Qemu qui va voler à notre secours grâce à l’une de ses fonctionnalités très intéressante : son agent GDB. L’exécution et le débogage du noyau Linux sous le contrôle de Qemu et GDB ont été largement couverts par l’article de Pierre Ficheux [1]Pierre Ficheux, " Mise au point à distance avec GDB et QEMU ", OpenSilicium n°1 (janvier 2011) ainsi qu’à la page 281 de son ouvrage [2]Pierre Ficheux, Linux embarqué, 3e édition, Éditions Eyrolles (2010). Pour les lecteurs ne disposant pas de cette littérature vivement conseillée, je vais en présenter les grandes lignes.
GDB est conçu pour fonctionner dans certains cas en mode client/serveur. Cette utilisation est particulièrement avantageuse dans un contexte embarqué, lorsque la plateforme cible ne dispose pas assez de ressources CPU et mémoire pour exécuter l’intégralité de GDB, qui est assez gourmand ; ou bien qu’elle ne comporte pas une interface utilisateur pratique (pas d’interface réseau et un port série unique).
Dans ce mode de fonctionnement, le client est constitué de la partie lourde de GDB, c'est-à -dire le programme GDB lui-même, ainsi que le fichier ELF binaire à mettre au point. Ce dernier doit comporter les symboles de débogage générés par GCC (options -O0 -g). Cette partie cliente communique, via le protocole GDB, avec un serveur GDB, autrement appelé agent GDB. Suivant la situation, cet agent prendra différentes formes :
- Le programme gdbserver compilé pour l’architecture cible. C’est un petit programme d’une centaine de Ko pour sa version 7.1 ARM strippée. Il communique avec le client GDB et exécute sous son contrôle le programme à mettre au point qui n’a pas besoin des symboles de débogage. Il reste ainsi de taille minimale.
- KGDB, le débogueur officiel de Linux, est aussi un agent GDB, mais il ne rentre pas dans le cadre cet article.
- Qemu qui comporte un agent GDB activable avec l’option -s. Dans ce cas, le code invité exécuté par Qemu pourra l’être sous le contrôle d’un GDB client, qui sera dans notre cas le programme GDB pour l’architecture ARM compilé pour la plateforme x86. Ce dernier est fourni par le BSP Armadeus basé sur Buildroot que nous utiliserons dans la partie mise en pratique.
Avant d’aller plus loin, évitons deux confusions possibles :
- Bien que le noyau Linux soit débogable avec cette dernière solution, elle n’a rien à voir avec KGDB. D’ailleurs, le noyau Linux n’a pas besoin d’avoir l’option de compilation CONFIG_KGDB activée. C’est possible, car comme nous l’avons vu le mois dernier, Qemu contrôle chaque instruction du processeur virtuel, qu’elle soit dans le noyau ou pas.
- Ce n’est pas Qemu qui est débogué et exécuté sous le contrôle de GDB. C’est le code invité exécuté par le processeur virtuel de Qemu qui l’est. Pour déboguer Qemu lui-même, il faut le compiler avec les options de débogage et l’exécuter sous le contrôle d’un GDB hôte, c’est-à -dire, dans notre cas, un GDB x86.
- Donc, pour mettre en œuvre cette solution, nous aurons besoin :
- D’un noyau Linux pour l’architecture invitée, compilé avec l’option CONFIG_DEBUG_INFO ;
- D’un client GDB pour architecture ARM, compilé pour x86. Ce dernier est fourni par la chaîne de compilation croisée construite par Buildroot dans le BSP Armadeus.
Voici comment lancer Qemu en activant l’agent GDB :
|
|
$ qemu-system-arm -M apf27 -kernel apf27-linux.bin -append "console=ttymxc0 loglevel=8 bootmem_debug mminit_loglevel=4 earlyprintk" -initrd apf27-rootfs.cpio -nographic -s -S |
Les options qui nous intéressent pour l’instant sont -s et -S. Les autres seront commentées lors des travaux pratiques. -s indique à Qemu qu’il doit activer l’agent GDB. -S demande à Qemu de ne pas démarrer le processeur virtuel. Il démarrera lorsque le client GDB se connectera.
Maintenant, lançons le client GDB :
arm-linux-gdb est client GDB pour architecture ARM compilé pour x86. Il est situé dans le répertoire $ARMADEUS_ROOT/buildroot/output/build/staging_dir/usr/bin/, $ARMADEUS_ROOT étant le répertoire racine du BSP Armadeus. vmlinux est le fichier binaire contenant le noyau Linux avec ses symboles de débogage. Il correspond au fichier apf27-linux.bin de la commande précédente, à la différence que apf27-linux.bin est un fichier U-Boot uImage, alors que vmlinux est un fichier ELF, seul format accepté par GDB.