Programmation embarquée sur Raspberry Pi sans sonde JTAG – Partie 2/2

2.1.2 GDB

Il est conseillé d’écrire ces commandes, préalables à toute les prochaines sessions de debug, dans un fichier que GDB pourra alors lire et exécuter via l’option de lancement -x <fichier>, ou la commande source <fichier>. Le fichier run.gdb, dont le TTY doit être ajusté selon le cas, est à ce titre fourni à la racine du dépôt.

Ou encore :

Nous pouvons alors le laisser s’exécuter et observer la sortie standard sur la console GDB :

Ou encore l’exécuter au pas-à-pas :

Les fonctionnalités de GDB se classent en deux grandes catégories : le debug de code et le debug de données. Entre autres, il est possible de visualiser la pile d’appel, de lire/écrire les données, les registres et la mémoire. Mais aussi d’insérer des breakpoints, d’intercepter des appels de fonctions à l’aide de breakpoints conditionnels…

Il est également possible d’envoyer des commandes spécifiques au serveur GDB via la commande monitor <commande>, lui permettant d’implémenter ses propres commandes. Par exemple, ici, la capture d’exceptions :

Cet exemple de programme constitue donc un point de départ idéal pour une exploration plus approfondie des possibilités de GDB et/ou du développement embarqué sur RPi. Le lecteur peut laisser libre cours à son imagination pour exploiter d’autres fonctions de la bibliothèque C (ex. : scanf(), fopen()…) ou en utilisant d’autres exemples de programmes C standards à embarquer sur la RPi en se reposant sur la bibliothèque C fournie (ex. : la runtime C++).

Un programme se reposant sur l’extension File I/O ne peut pas s’exécuter sans client ou serveur GDB puisque ce sont eux qui échangent et exécutent les appels systèmes (figure 9). Cette fonctionnalité doit donc être considérée comme un moyen de développement supplémentaire dont les utilisateurs peuvent bénéficier ou non selon leur bon vouloir. À bon entendeur.

2.2 Raytracer

Cet exemple est une nouvelle illustration des concepts vus jusqu’à présent et appliqués cette fois-ci à un programme exploitant de manière plus avancée la RPi. Il utilise en effet son unité flottante ainsi que son GPU. Nous finirons avec sa « mise en production » sur la carte microSD de la RPi pour qu’il démarre de manière autonome, sans aucune intervention de GDB.

Figure 10 : Image générée par le raytracer en bare-metal sur la sortie HDMI de la RPi.

2.2.1 Compilation

Ce raytracer affiche sur la sortie HDMI les images calculées par l’algorithme de raytracing (figure 10), contrôlé par le GPU, lui-même commandé par le processeur ARM à travers des requêtes échangées par mailbox [10]. Le calcul se fait pixel par pixel et les résultats sont écrits dans le framebuffer alloué par le GPU. Le fichier VC.c contient le code source des fonctions d’affichage graphiques : une fonction pour demander au GPU de préparer un framebuffer, et une seconde fonction pour récupérer son adresse. Le framebuffer consiste alors en une matrice de pixels à écrire au format 32 bits ARGB.

Pour le compiler :

Cet exemple introduit aussi le niveau d’optimisation g avec l’option -Og qui permet d’optimiser le programme tout en conservant une qualité de debug du programme acceptable. Elle évite donc les optimisations les plus agressives qui entraînent trop de perte de traçabilité entre le code objet et son code source. L’expérience de debug est quoi qu’il en soit dégradée (par exemple, impossibilité de lire certaines variables, exécution au pas-à-pas du code réordonnancé, etc.) par rapport à un programme compilé sans aucune optimisation.

2.2.2 GDB

Nous allons utiliser GDB afin d’observer le fonctionnement de l’algorithme de raytracing en employant les moyens de modification dynamique des données du programme.

Démarrer d’abord la session de debug :

Les temps de traitement longs du raytracer et sa boucle infinie de rendu vidéo sont de parfaits candidats à la fonctionnalité d’interruption asynchrone de l’exécution de la cible, c’est-à-dire tandis qu’elle exécute le raytracer, lorsque le client reçoit le signal <Ctrl> + <c> (SIGINT). Cette fonctionnalité repose sur une interruption matérielle qui nécessite donc de démasquer les interruptions externes du processeur. Pour ce faire, nous utilisons les fonctionnalités de modification des registres ainsi que de scripting GDB, dont la syntaxe des expressions est identique à celle du langage C. Le registre en question est en plus l’un des registres superviseurs, absents du mode GDB « natif », mais ici communiqué par Alpha :

Nous sommes désormais en mesure d’interrompre à tout moment l’exécution du raytracer sur la RPi :

Les interruptions externes sont masquées par le point d’entrée _start tel qu’implémenté par la bibliothèque C newlib. Le démasquage doit donc intervenir une fois cette fonction terminée, lorsque nous atteignons la fonction main().

Nous reprenons alors la main avec GDB où le programme a été interrompu. Voici donc un exemple modifiant successivement la couleur d’une sphère :

Le raytracer est statistiquement le plus souvent interrompu durant l’exécution de la fonction sqrt() (racine carré), car longue à exécuter et appelée très fréquemment.

2.2.3 Embarquement

Nous souhaitons finalement embarquer le programme et qu’il s’exécute automatiquement au démarrage de la RPi sans avoir besoin d’utiliser GDB pour le charger. Nous allons donc l’interfacer avec le bootloader de la RPi qui charge puis exécute le contenu de la carte microSD, selon les directives contenues dans le fichier config.txt. En l’absence de ce fichier, le comportement par défaut copie le fichier kernel.img dans la RAM à l’adresse 0x8000 et lance l’exécution à cette adresse.

Il est avant tout nécessaire de prendre en compte les dépendances avec l’utilisation de GDB (ex. : File I/O) ou du serveur Alpha (ex. : mapping mémoire). Si nécessaire, il convient alors de les implémenter. Ainsi, nous avons fait le choix arbitraire de profiter du comportement par défaut du bootloader pour placer à l’adresse 0x8000 un second point d’entrée contenant le code d’initialisation, puis invoquant le point d’entrée de la runtime C _start. D’autres solutions sont parfaitement envisageables et le code source de cette implémentation est fourni à titre d’exemple dans sdk/CPU_start.S.

Pour générer kernel.img et le copier sur la carte microSD :

Insérez à nouveau la carte microSD dans la RPi. Celle-ci démarre directement avec le raytracer sans plus aucune intervention de GDB.

Enfin, le fichier kernel.img, forme purement binaire de l’exécutable raytracer.elf, ne contient plus aucune information de debug. Il n’est donc plus possible de le débugger depuis le code source en tant que tel, mais uniquement sous forme binaire désassemblée par GDB (layout asm). Il est toutefois possible d’obtenir les informations de debug dans un fichier séparé. Le fichier kernel.dbg généré à ses côtés par la commande de compilation précédente contient toutes les informations de debug de kernel.img. Pour finir, voici comment les utiliser :

Dans cet exemple, nous faisons manuellement ce que la commande load effectuait pour nous depuis le fichier ELF raytracer.elf. Les deux méthodes sont d’ailleurs strictement équivalentes dans le cas présent qui ne comporte aucune autre distinction que les formats des fichiers raytracer.elf et kernel.img : tous deux contiennent le même programme binaire.

Ce dernier exemple est un moyen de distribuer le programme tout en conservant les informations de debug. Il constitue le point de départ d’une possible stratégie de support et maintenance d’une distribution binaire d’un programme embarqué.

Conclusion

Nous nous sommes donc totalement affranchis du coût et de la complexité que peut représenter une sonde JTAG. Maîtriser un tel équipement est une tâche complexe, souvent attribuée aux professionnels les plus aguerris. L’utilisation de GDB, couplée à une implémentation du serveur entièrement logicielle, facilite la démocratisation et la simplification du développement embarqué au quotidien. L’effort nécessaire pour mettre en œuvre le serveur relève strictement des mêmes compétences que celles du développement embarqué. GDB est en plus le débugger bénéficiant de la plus large communauté en ligne, et tout étudiant en informatique sait par exemple l’utiliser après avoir débuggé son premier programme C ou C++. Depuis les choix d’architecture en phases amont de R&D jusqu’à la vérification et la validation d’un logiciel embarqué complet en phase de production, chacun peut trouver son compte parmi les fonctionnalités de GDB. Les sondes restent néanmoins nécessaires dans certains cas bien précis, comme le debug des firmwares, le debug spécialisé de certaines unités processeur (ex. : lire/écrire des caches internes) ou encore le debug temps réel de bus rapides (ex. : debug PCIe).

Les opportunités sont désormais nombreuses sur ce terrain de jeu RPi. Comprendre le mode superviseur ARM, développer du logiciel bare-metal haute performance, ou encore découvrir les interfaces SPI ou USB, sont tout autant de sujets bas niveaux et universels à explorer.

Christophe Plé
[CEO de Farjump et Expert en développement logiciel embarqué]

Julio Guerra
[CTO de Farjump]

Références

[1] Distribution ARM officielle de la toolchain GNU sur https://developer.arm.com/open-source/gnu-toolchain/gnu-rm
[2] Exemple de convertisseur seul sur http://amzn.eu/3H5FIx9
[3] Exemple de câbles jumper sur http://amzn.eu/jiuRBbi
[4] Exemple de câble USB pour le convertisseur [1] sur http://amzn.eu/ihFNmFf
[5] Exemple de convertisseur format « câble » sur http://amzn.eu/flbbyHF
[6] Documentation des tracepoints sur https://sourceware.org/gdb/onlinedocs/gdb/Tracepoints.html
[7] Documentation de l’extension File I/O de GDB sur https://sourceware.org/gdb/onlinedocs/gdb/File_002dI_002fO-Remote-Protocol-Extension.html
[8] Documentation de l’interface texte sur https://sourceware.org/gdb/onlinedocs/gdb/TUI.html
[9] Liste des appels système supportés sur https://sourceware.org/gdb/onlinedocs/gdb/List-of-Supported-Calls.html
[10] Documentation du framebuffer du SoC BCM2835 sur http://elinux.org/RPi_Framebuffer

 

Programmation embarquée sur Raspberry Pi sans sonde JTAG -Partie 1/2

Le standard JTAG, au succès indéniable, est aujourd’hui ancré dans la majorité des processeurs et proposé comme moyen privilégié de programmation embarquée et de debug. Toutefois, l’utilisation d’une sonde JTAG n’est en rien triviale. L’utilisation du débuggeur GNU s’impose alors, car il est possible de l’employer en bare-metal, c’est-à-dire sans aucun système d’exploitation embarqué pour gérer le processeur et sa carte. À titre d’illustration, nous nous intéressons dans cet article à la programmation bare-metal d’une Raspberry Pi à l’aide de GDB uniquement.

En situation de développement embarqué, la Raspberry Pi (RPi) est appelée « système cible » (target), tandis que le poste de travail est appelé « système hôte » (host). L’hôte doit donc programmer le système cible à distance (remote) via un moyen de communication dédié. À noter également que nous sommes dans une situation de développement croisé : la RPi, bare-metal et architecture ARM, ne correspond pas au système hôte, couramment muni d’une architecture Intel avec un système d’exploitation (OS). Il convient donc d’utiliser une chaîne d’outils croisés pour produire et manipuler un programme cible depuis un système hôte.

Lire la suite

À nouveau disponible en kiosque : notre hors-série spécial programmation réseau !

Vous l’avez manqué ? Sachez que notre guide dédié à la programmation réseau en Python est actuellement de retour en kiosque ! Vous commencerez tout d’abord par y découvrir les modules essentiels à la programmation réseau, puis serez accompagné dans la création de plusieurs projets (client XMPP, bot IRC, robot Slack). Pour finir, vous apprendrez à créer un script communiquant par SMS et analyserez un serveur de fichiers. Ce guide est toujours disponible sur notre boutique et bien entendu sur notre plateforme de lecture en ligne ConnectLire la suite

Étendre un serveur MySQL/MariaDB avec des fonctions compilées – Partie 1/2

MySQL et MariaDB offrent la possibilité de développer des fonctions compilées, à utiliser comme toute autre fonction native dans vos requêtes SQL. Étudions la puissance et les dangers d’ajouter du code au serveur.

Les User Defined Functions sont un moyen d’étendre les possibilités du serveur MySQL en ajoutant des fonctions écrites en C ou C++, qui peuvent être utilisées comme d’autres fonctions SQL dans des requêtes (c’estàdire, qui se comportent comme les fonctions natives telles que REPLACE, SUBSTR ou SUM).

Lire la suite

L’édito de GNU/Linux Magazine n°208 !

Ce mois-ci, je voulais revenir avec vous sur la manière dont nous nous exprimons, nous, informaticiens pour communiquer, non plus avec des machines, mais entre nous. Si des règles existent pour les ordinateurs, de manière à ce que l’information leur parvienne sans possibilité de compréhension approximative (et donc sans interprétation possible), il en va de même du français et, comme le disait Boileau en son temps : « Ce qui se conçoit bien s’énonce clairement. Et les mots pour le dire arrivent aisément. ». Tout comme un disque n’est pas un cercle, un langage de programmation n’est pas un langage de description ! Encore récemment un étudiant me parlait du langage html comme d’un langage de programmation or, par définition, un langage de programmation contient des structures conditionnelles et des structures de boucles… qui sont absentes du html comme du LaTeX, etc. Il ne s’agit donc pas d’un langage de programmation, mais bien d’un langage de description, permettant de spécifier comment structurer un document, la nuance est importante ! Lire la suite

Détectez l’apparition d’objets « abandonnés » dans un flux vidéo !

Alerte au colis suspect ? Apprenez à détecter l’apparition d’objets « abandonnés » dans un flux vidéo avec le nouveau GNU/Linux Magazine ! Vous découvrirez comment utiliser une caméra en Python, étudier l’influence de différents paramètres de traitements d’image, encadrer l’objet « abandonné » détecté et enfin, indiquer depuis combien de temps cet objet est immobile. En dehors de cet article phare, ce numéro vous proposera de protéger vos partages NFS avec Kerberos, stocker des données de manière persistante dans des conteneurs Docker ou encore monter votre premier cluster virtualisé. GNU/Linux Magazine n°208 est disponible chez votre marchand de journaux, sur notre plateforme de lecture en ligne ainsi que sur notre boutique en ligneLire la suite

Transfert de style : et si Van Gogh peignait Tux ? – Partie 2/2

3.3 Fast neural network

Il existe une implémentation plus récente du style transfer permettant un calcul bien plus rapide. Celle-ci est basée sur un article d’octobre 2016 [9]. L’idée est de remplacer l’erreur (loss) « par pixel » par l’erreur « perceptuelle ». En d’autres termes, le système ne cherche plus à faire coller chaque pixel à l’image originale, mais calcule la perte à un niveau plus élevé, ce qui permet de mesurer plus finement la similitude « perçue » entre deux images. De plus, l’implémentation utilise une modification dans l’architecture du réseau qui permet également un gain de performance. Celle-ci se base sur un précalcul du modèle, qui peut alors être utilisé à faible coût pour être appliqué sur de nouvelles images.

Lire la suite