GNU/Linux Magazine France

Subscribe to flux GNU/Linux Magazine France
Développement et administration sur systèmes open source
Mis à jour : il y a 2 heures 56 min

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

lun, 11/27/2017 - 09:00

J’ai installé un Windows…

Oui, vous avez bien lu et je n’en suis pas fier : j’ai installé un Windows !

Pourquoi, me demanderez-vous. Eh bien cédant à la pression de mes enfants souhaitant jouer aux jeux de dernière génération tels que StarCraft ou Age of Empires 2, je me suis dit que c’était sans doute la solution la plus simple et que j’allais repasser leur ordinateur en double boot. Il est vrai que ces jeux fonctionnent très bien sous Wine, mais pas StarCraft remastered et il y a des problèmes de réseau avec Age of Empires 2 (de plus la version remastered ne devrait pas tarder à sortir et ne fonctionnera certainement pas elle non plus). Je me suis donc dit que l’on n’était guère regardant sur le système d’exploitation d’une console de jeu et pourquoi ne pas considérer la partition Windows comme telle ? J’ai donc pris mon courage à deux mains et j’ai installé Windows, certes pas l’un des plus récents, un Windows 7 qui était fourni avec les machines.

Quel moment agréable ce fut de se retrouver face à ce système si simple à installer, à devoir rechercher sur le Web les différents pilotes qui n’étaient jamais disponibles dans la bonne version, à voir ces ordinateurs qui tournaient pourtant parfaitement sous Debian se mettre à ramer atrocement, à devoir rebooter le système à de multiples reprises, un vrai bonheur ! Ce sont finalement des choses que l’on oublie vite, des traumatismes que l’on enfouit au fin fond de notre subconscient et qui ressurgissent tout à coup. Même pour charger une simple page web ça rame ! Il faut le voir pour le croire ! Et puis une installation qui s’interrompt parce que le système a décidé de se mettre à jour et veut rebooter… mais c’est qui le patron ? Oh !

Après quelques heures, l’installation est fonctionnelle, les jeux sont installés et je vais pouvoir passer à l’installation de la partition GNU/Linux. Par acquis de conscience, je teste Minecraft. Ah, tiens, dans la version Windows on peut voir par transparence la présence des donjons. C’est bien pratique ! Je me dis donc que pour des enfants qui n’ont jamais connu que Linux, il peut être intéressant qu’ils soient confrontés au côté obscur de la force, les laisser quelques jours pour qu’ils se fassent eux-mêmes une opinion. Finalement, après cinq minutes d’utilisation, j’ai eu droit à un « Papa, tu peux me remettre le système qui fonctionne ? ». Je suis bon pour réinstaller une Debian. Ce sera certainement moins long de configurer proprement Wine que d’utiliser ce…, ce… , ce « système » !

Il fallait le faire, et je l’ai fait, j’ai installé Windows pensant naïvement que cela solutionnerait les problèmes de jeux et en fait pas du tout. Bien sûr, sur une machine récente, dopée à la RAM et au CPU, nul doute que tout se serait déroulé correctement (et encore… est-ce vraiment certain ?). Bref, nous avons tous choisi GNU/Linux et si vous avez oublié pourquoi, refaites donc un tour du côté de Windows, vous verrez que nous n’avons pas tort… Et j’ai gardé le meilleur pour la fin : simplement en passant de Jessie à Stretch et en installant Wine staging tout fonctionne… mieux que sous Windows !

Je vous souhaite une bonne fin d’année et je vous retrouverai avec plaisir pour le prochain numéro !

Tristan Colombo

-> Découvrir/acheter ce numéro : https://boutique.ed-diamond.com/en-kiosque/1282-gnulinux-magazine-210.html

-> S’abonner : https://boutique.ed-diamond.com/abonnements/3-gnu-linux-magazine

Mettez en place votre premier Honeypot !

ven, 11/24/2017 - 09:42

Pour bien finir l’année, GNU/Linux Magazine vous propose d’organiser une petite chasse aux pirates en mettant en place votre premier Honeypot ! Vous comprendrez ainsi le fonctionnement de ces pièges, apprendrez à détecter des attaques en bruteforce, à analyser vos logs pour mieux comprendre ces attaques, et enfin, à améliorer votre piège. Ce numéro vous attend en kiosque dès aujourd’hui, mais aussi sur notre plateforme de lecture en ligne Connect et sur notre boutique.

Au sommaire

Actus & Humeur
p. 06 Honeypot qui mal y pense

IA, Robotique & Science
p. 18 Au cœur de la VM Pharo

Système & Réseau
p. 24 LinuxKit ou le meilleur des containers et des UniKernels
p. 32 Déployer un environnement de déploiement avec debian-installer, Ansible et Git
p. 44 Sécurisez vos données par LVM et RAID

IoT & Embarqué
p. 52 Un environnement exécutif visant la compatibilité POSIX : NuttX pour contrôler un analyseur de réseau à base de STM32

Hack & Bidouille
p. 68 Intégrez vos scripts au bureau et gérez les URI

Libs & Modules
p. 72 Introduction à gstreamer 1.0

Sécurité & Vulnérabilité
p. 86 Utilisez et étendez PhotoRec pour récupérer vos données perdues

Sécurisez votre infrastructure Linux !

ven, 11/03/2017 - 09:30

La sécurité est au coeur du nouveau hors-série de GNU/Linux Magazine ! Vous découvrirez en effet dans ce numéro spécial quels moyens mettre en oeuvre pour sécuriser votre infrastructure Linux. Au menu des différents chapitres proposés : la protection de votre réseau, le renforcement de la protection de votre système, le filtrage de vos accès web ainsi que les tests de sécurité et réactions en cas d’incident. Ce guide vous attend dès à présent en kiosque, sur la boutique et sur notre plateforme de lecture en ligne Connect.

Au sommaire

Débutez : Protégez votre réseau avec un firewall nftables ou ModSecurity

p. 08 Filtrez votre réseau avec nftables

p. 22 Protégez-vous avec ModSecurity, le pare-feu web open source

Système : Durcissez la protection de votre système avec OpenSCAP et AuditD

p. 36 Sécuriser un serveur CentOS dès l’installation : une histoire de serpent et de SCAL^HP

p. 54 Journalisez les actions de vos utilisateurs avec AuditD

Infrastructure serveur : Filtrez les accès web avec Dansguardian et vos mails avec MailScanner

p. 74 Créez votre passerelle anti-SPAM multicouche avec Postfix, Postgrey, et MailScanner

p. 94 Filtrez et limitez l’accès au Web avec Dansguardian

Audit / Pentest : Effectuez des tests de sécurité et apprenez à réagir en cas d’incident

p. 112 Petit cours de R.I.E.N.

p. 110 Utilisez des scanners de réseau et de vulnérabilités

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

lun, 10/30/2017 - 09:00

Python, on aime ou on n’aime pas, mais le langage ne laisse pas indifférent. Il y a les fans, ceux qui ne jurent que par ce langage et qui vont jusqu’à l’extrémisme en oubliant tous les autres langages qui ne sont pas pour autant à jeter à la poubelle et il y a les opposants farouches, eux aussi sombrant souvent dans l’extrémisme et dénigrant un « pseudo-langage » qui n’est même pas compilé et qui envahit de plus en plus les magazines et les articles sur le net.

Python n’est certes pas compilé, mais il permet de développer rapidement, sans se préoccuper de la gestion de la mémoire. D’un autre côté, suivant les développements, son aspect de langage semi-interprété est pénalisant, car entraînant un gaspillage de mémoire et une lenteur des programmes. Alors, puisque nous ne pourrons départager les « pros » des « antis », essayons de comprendre avec les outils qui sont à notre disposition pourquoi on parle tant de Python. Utilisons par exemple les tendances de questions sur StackOverflow :

On peut constater que Python vient de dépasser Java et n’est plus devancé que par JavaScript. Ceci s’explique par le nombre de sites web contenant du JavaScript, mais montre que malgré les développements d’applications Android en Java, les questions sont plus nombreuses sur Python. Les mauvaises langues pourront bien sûr arguer du fait que s’il y a beaucoup de questions, c’est que le langage n’est pas clair… passons et retenons seulement la croissance imbattable sur les deux dernières années.

Pour confirmer cela, penchons-nous sur d’autres données. Sur Google Trends, les recherches comme « python » ou « java » sont parasitées par d’autres significations que des langages de programmation, mais le projet PYPL (PopularitY of Programming Language sur http://pypl.github.io/PYPL.html) l’utilise néanmoins en se basant sur les recherches de tutoriels. Je vous invite à consulter le site et à utiliser les langages qui vous intéressent, pour moi ce seront les mêmes que les précédents.

Cette fois Python ne dépasse pas Java, mais sa croissance de 9,5 % sur les dix dernières années est là encore remarquable et l’étude des données fournies par GitHub (http://githut.info/) fournira là encore la même tendance.

Ces informations montrent donc qu’il y a un engouement de plus en plus fort pour ce langage et qu’on le veuille ou non, il faudra le connaître un minimum pour comprendre, pouvoir interagir avec les futurs développements. Donc oui, Python est de plus en plus utilisé, et vous découvrirez même dans ce numéro comment vous en servir pour envoyer/recevoir des SMS, interagir avec Google Calendar et mettre en place une authentification deux facteurs, mais dans GNU/Linux Magazine nous n’en oublions pas pour autant les autres langages et vous pourrez ainsi trouver par exemple dans ce numéro également du C et du Go (oui, le bon dernier de toutes les tendances si l’on ne tient pas compte des langages « exotiques »).

Surtout, n’oubliez pas que ce n’est pas parce qu’un langage est populaire qu’il est le meilleur. Le meilleur langage est celui le plus adapté à la tâche que vous devez réaliser ! Sur ce, bonne lecture !

Tristan Colombo

-> Découvrir/acheter ce numéro : https://boutique.ed-diamond.com/en-kiosque/1277-gnulinux-magazine-209.html

-> S’abonner : https://boutique.ed-diamond.com/abonnements/3-gnu-linux-magazine

Maîtrisez la gestion avancée de SMS sans vous ruiner !

ven, 10/27/2017 - 09:30

Avec l’article phare de ce nouveau numéro de GNU/Linux Magazine, vous apprendrez à envoyer des SMS sans vous ruiner à l’aide de votre PC et de votre Raspberry Pi. Pour ce faire, vous découvrirez comment configurer votre modem USB 3G, utiliser Gammu pour l’envoi de SMS, interfacer votre système avec Google Calendar, gérer la réception de SMS et enfin, mettre en place une authentification 2 facteurs. Pour vous occuper durant ce mois de novembre, vous apprendrez aussi avec ce nouveau numéro à réaliser une API REST en Go, à analyser statiquement et dynamiquement du code ou encore à faire vos premiers pas avec l’environnement de développement OpenSTM32 sur Steval-3DP001. Retrouvez GNU/Linux Magazine chez votre marchand de journaux, sur notre boutique ainsi que sur notre plateforme de lecture en ligne Connect !

Au sommaire

Actus & humeur

p.06 Petite leçon fictive de social engineering

IA, Robotique et Science

p.12 Le code Phase-Out : l’autre code binaire tronqué

Système & Réseau

p.34 Un système de fichiers haute disponibilité avec GlusterFS !

p.32 Mise en place d’une IP virtuelle avec Corosync et Pacemaker

p.36 InfluxDB, Grafana et Glances, le monitoring qui brille

IoT & Embarqué

p.46 Présentation et utilisation de la carte STEVAL-3DP001 pour le pilotage des imprimantes 3D

Hack & Bidouille

p.58 Vous donnez des rendez-vous et on vous oublie ? Envoyez automatiquement des SMS de rappel !

Libs & Modules

p.74 Réaliser une API REST avec Go

Mobile & Web

p.82 Démystifier l’injection de dépendances en PHP

Sécurité & Vulnérabilité

p.92 La trilogie du reverse engineering

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

jeu, 10/19/2017 - 09:00
2.1.2 GDB

glmf-rpi/ $ arm-none-eabi-gdb hello.elf GNU gdb (GNU Tools for ARM Embedded Processors) 7.12.0.20161204-git ... For help, type "help". Type "apropos word" to search for commands related to "word". (gdb) # 1. Configuration GDB dépendante de Alpha (gdb) source sdk/alpha.gdb (gdb) (gdb) # 2. Connexion a la RPi (gdb) # Nécessite les droits de lecture/écriture sur le TTY du lien série, (gdb) # ici le nœud `/dev/ttyUSB0` (gdb) set serial baud 115200 (gdb) target remote /dev/ttyUSB0 (gdb) (gdb) # 3. Téléchargement sur la RPi du programme passé en argument à GDB (gdb) load (gdb) # `load` place aussi le pointeur sur instruction sur son point (gdb) # d’entrée `_start` (gdb) (gdb) # 4. Lancer alors l’exécution jusqu’à atteindre la fonction main (gdb) tbreak main (gdb) continue

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.

glmf-rpi/ $ arm-none-eabi-gdb -x run.gdb hello.elf ... Temporary breakpoint 1, main () at src/hello-world/HelloWorld.c:5 5 printf("Hello %s!\n", "RPi"); (gdb) # Nous avons atteint la fonction main() (gdb) list 1 #include <stdio.h> 2 3 int main(void) 4 { 5 printf("Hello RPi!\n"); 6 } (gdb) # Prêt pour une nouvelle session de debug

Ou encore :

glmf-rpi/ $ arm-none-eabi-gdb hello.elf (gdb) source -v run.gdb ... Temporary breakpoint 1, main () at src/hello-world/HelloWorld.c:5 5 printf("Hello %s!\n", "RPi"); (gdb) # Nous avons atteint la fonction main() (gdb) list 1 #include <stdio.h> 2 3 int main(void) 4 { 5 printf("Hello RPi!\n"); 6 } (gdb) # Prêt pour une nouvelle session de debug

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

(gdb) continue Continuing. Hello RPi! Program received signal SIGTRAP, Trace/breakpoint trap. _exit (rc=0) at SYSFILEIO/MAKEFILE/../SOURCE/SYSFILEIO_EXIT.c:11 11 SYSFILEIO/MAKEFILE/../SOURCE/SYSFILEIO_EXIT.c: No such file or directory. (gdb)

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

(gdb) source run.gdb ... Temporary breakpoint 1, main () at src/hello-world/HelloWorld.c:5 5 printf("Hello %s!\n", "RPi"); (gdb) break printf Breakpoint 2 at 0x854c (gdb) continue Continuing. Breakpoint 2, 0x0000854c in printf () (gdb) bt #0 0x0000854c in printf () #1 0x00008334 in main () at src/hello-world/HelloWorld.c:5 (gdb) finish Run till exit from #0 0x0000854c in printf () Hello RPi! 0x00008334 in main () at src/hello-world/HelloWorld.c:5 5 printf("Hello %s!\n", "RPi"); (gdb) next 6 } (gdb) delete 2 (gdb) call printf("appel dynamique!\n") appel dynamique! $1 = 17

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 :

(gdb) monitor help ... (gdb) monitor gdb/catch RST : no : Reset Exception UND : no : Undefined Instruction Exception SWI : no : Software Interrupt Exception PABRT : no : Prefetch Abort Exception DABRT : no : Data Abort Exception IRQ : no : IRQ (interrupt) Exception FIQR : no : FIQ (fast interrupt) Exception

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 :

glmf-rpi/ $ make raytracer.elf arm-none-eabi-gcc -specs=sdk/Alpha.specs -mfloat-abi=hard -mfpu=vfp -march=armv6zk -mtune=arm1176jzf-s -g3 -ggdb -Wl,-Tsdk/link.ld -Lsdk -Wl,-umalloc -Wl,-Map,raytracer.map -o raytracer.elf -Og src/raytracer/main.c src/raytracer/Raytracing.c src/raytracer/VC.c src/raytracer/VC_aligned_buffer.S -lm

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 :

glmf-rpi/ $ arm-none-eabi-gdb -x run.gdb raytracer.elf ... Temporary breakpoint 1, main () at src/raytracer/main.c:221 221 { (gdb)

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 :

(gdb) # Démasquer les interruptions externes pour rendre possible (gdb) # l’interruption du programme via ctrl-c. (gdb) print /x $cpsr &= ~(1 << 7) $1 = 0x6000015f

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

(gdb) continue Continuing. ^C Program received signal SIGSTOP, Stopped (signal). 0x0000921c in __ieee754_sqrt ()

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 :

(gdb) set A_SPHERE[0].S_PROPERTY.S_A.F_GREEN = 0.9 (gdb) continue Continuing. ^C Program received signal SIGSTOP, Stopped (signal). 0x00008aa0 in RAYT_TRACE (…) at src/raytracer/Raytracing.c:306 306 S_RGB.F_BLUE = (P_PROPERTY->S_A.F_BLUE * P_RAYT_WORLD->S_AMBIANT_LIGHT.F_BLUE); (gdb) bt #0 0x00008aa0 in RAYT_TRACE (…) at src/raytracer/Raytracing.c:306 #1 0x00008e24 in RAYT_RENDER (…) at src/raytracer/Raytracing.c:421 #2 0x000083d0 in main () at src/raytracer/main.c:239 (gdb) set A_SPHERE[0].S_PROPERTY.S_A.F_GREEN = 0.3 (gdb) continue Continuing. ^C Program received signal SIGSTOP, Stopped (signal). 0x00009080 in sqrt () (gdb) set A_SPHERE[0].S_PROPERTY.S_A.F_GREEN = 0.6 (gdb) # Execution background (gdb) # Permet de garder la main sur le client tandis que le serveur la rend au programme. (gdb) continue & Continuing. (gdb) # Commande GDB équivalente au signal ctrl-c (gdb) interrupt Program received signal SIGSTOP, Stopped (signal). 0x00009240 in __ieee754_sqrt () (gdb) # Les variables telles que I_RESOLUTION_FACTOR sont lues une seule fois (gdb) # lors de l’initialisation du programme. Il est donc nécessaire de reprendre (gdb) # depuis le début l’exécution du programme pour pouvoir la modifier. (gdb) print I_RESOLUTION_FACTOR $2 = 2 (gdb) # Alpha reset le processeur lorsqu’il reçoit la commande kill, aussi induite (gdb) # par d’autres commandes GDB. C’est le cas notamment de la commande de (gdb) # connexion contenue dans le script run.gdb. Il suffit donc de le relancer (gdb) # pour recommencer une session. (gdb) source run.gdb ... Temporary breakpoint 1, main () at src/raytracer/main.c:221 221 { (gdb) # Nous pouvons ici modifier la résolution de l’image avant sa lecture (gdb) # par la fonction d’initialisation du GPU. (gdb) set I_RESOLUTION_FACTOR = 4 (gdb) continue (gdb) # Observer la nouvelle résolution.

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 :

glmf-rpi/ $ make kernel.img arm-none-eabi-objcopy -O binary raytracer.elf kernel.img arm-none-eabi-objcopy --only-keep-debug raytracer.elf kernel.dbg glmf-rpi/ $ # Insérer la carte microSD dans le poste de travail glmf-rpi/ $ sudo mount -t vfat -o rw,umask=0000 /dev/sdc /mnt glmf-rpi/ $ mv /mnt/config.txt /mnt/config.txt.gdb glmf-rpi/ $ cp -v kernel.img /mnt 'kernel.img' -> '/mnt/hdd/kernel.img' glmf-rpi/ $ sudo umount /mnt

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 :

glmf-rpi/ $ # Une fois le fichier config.txt restauré… glmf-rpi/ $ arm-none-eabi-gdb (gdb) source sdk/alpha.gdb (gdb) set serial baud 115200 (gdb) target remote /dev/ttyUSB0 Remote debugging using /dev/ttyUSB0 warning: No executable has been specified and target does not support determining executable automatically. Try using the "file" command. 0x07f10570 in ?? () (gdb) # 1. Lecture des informations de debug (gdb) file kernel.dbg program is being debugged already. Are you sure you want to change the file? (y or n) y Reading symbols from kernel.dbg...done. (gdb) # 2. Téléchargement binaire de kernel.img à l’adresse 0x8000 (gdb) restore kernel.img binary 0x8000 Restoring binary file kernel.img into memory (0x8000 to 0x1b130) (gdb) # 3. Placer le registre pointeur sur instruction à l’adresse (gdb) # de démarrage avec Alpha (gdb) set $pc = &_start (gdb) break main Breakpoint 1 at 0x8320: file src/raytracer/main.c, line 221. (gdb) continue Continuing. Breakpoint 1, main () at src/raytracer/main.c:221 221 { (gdb) # Prêt pour débugger kernel.img

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

mar, 10/17/2017 - 09:00

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.

Pour ce faire, nous déploierons d’abord sur le poste de travail la chaîne d’outils GNU croisés, arm-none-eabi, incluant le compilateur GCC et le débuggeur GDB, puis embarquerons le serveur GDB freemium Alpha directement sur la RPi, et l’utiliserons comme moyen de développement afin de développer et d’embarquer, avec la seule aide de GDB, une série d’exemples de programmes C standards allant du simple Hello World sur la sortie standard, au plus avancé raytracer utilisant le processeur graphique (GPU) pour produire des images sur la sortie HDMI de la RPi. Nous finirons enfin par embarquer le raytracer directement sur la RPi, pour qu’il soit démarré par son bootloader, comme si nous passions en production une fois le développement terminé.

Figure 1 : Vue générale du développement embarqué à l’aide de GDB.

1. Installation

Figure 2 : Matériel complet nécessaire : une RPi, un poste de travail, une carte microSD pour la RPi, un lecteur de carte microSD pour le poste de travail, un câble HDMI pour la RPi, un câble micro-USB pour alimenter la RPi, un convertisseur USB/UART-TTL3.3V, trois câbles jumper femelle pour le branchement convertisseur/RPi, un câble mini-USB pour le branchement convertisseur/poste de travail.

1.1 Poste de travail

Un poste de travail POSIX (GNU/Linux, Cygwin, OS X…) est nécessaire. Il exécutera la compilation croisée ARM pour RPi, ainsi que le client GDB. Les droits de lecture et écriture sont également nécessaires sur l’interface de communication employée et décrite plus bas.

Un dépôt git est mis à disposition : il contient les éléments nécessaires pour la suite (et pour échanger sur https://github.com/farjump/raspberry-pi en cas de problème). Les commandes ci-dessous permettent de le cloner :

$ git clone https://github.com/farjump/raspberry-pi.git glmf-rpi $ cd glmf-rpi glmf-rpi/ $ git checkout v1.0.0 glmf-rpi/ $ ls LICENSE Makefile boot/ run.gdb scripts/ sdk/ src/

Optionnellement, les plus modernes d’entre nous apprécieront certainement la commande make shell qui produit directement le poste de travail attendu dans un container docker basé sur Debian 9, puis y lance un terminal dans lequel peut s’exécuter de manière garantie la suite de l’article.

1.1.1 Chaîne de compilation croisée ARM

ARM distribue la chaîne de compilation croisée pour les principaux systèmes d’exploitation au format 64 bits uniquement [1]. Le script scripts/install-toolchain.sh, fourni dans le dépôt, télécharge et décompresse la version Linux 64 bits de la chaîne :

glmf-rpi/ $ sudo ./scripts/install-toolchain.sh --prefix /opt/glmf-arm-none-eabi [+] Installing ARM cross-compiler into `/opt/glmf-arm-none-eabi` [+] The toolchain has been successfully installed glmf-rpi/ $ PATH=/opt/glmf-arm-none-eabi/bin:$PATH glmf-rpi/ $ export PATH glmf-rpi/ $ arm-none-eabi-gcc -v && arm-none-eabi-gdb -v && echo mini test ok ... mini test ok

Compte tenu du succès de l’architecture ARM, les distributions les plus courantes (Debian, Archlinux, Fedora, etc.) la mettent aussi directement à disposition dans leurs gestionnaires de paquets, toujours en partie nommée avec le triplet arm-none-eabi. Pour OS X et Windows, il convient d’adapter les étapes précédentes à la distribution officielle ARM [1] en la déployant dans votre environnement.

1.2 Raspberry Pi 1.2.1 Carte microSD

La RPi démarre le programme qu’elle trouve sur sa carte microSD suivant les directives contenues dans le fichier de configuration config.txt. Le serveur GDB Alpha doit donc être copié sur la carte microSD pour être démarré à l’allumage de la RPi.

L’étape décrite ici simplifie au maximum la préparation de la carte microSD pour la RPi et son bootloader. Elle suppose une carte microSD vierge compatible avec la RPi. Un script d’aide à son installation est fourni, mais l’étape de formatage en FAT32, attendu par le firmware et le bootloader de la RPi, est laissée explicite afin d’insister sur le caractère irréversible de cette opération destructrice pour la carte microSD. Enfin, le script scripts/install-rpi-boot.sh installe le firmware, le bootloader et Alpha sur la carte microSD :

glmf-rpi/ $ # 1. Insérer la carte microSD dans le lecteur de carte du poste de travail glmf-rpi/ $ # 2. Trouver son point de montage glmf-rpi/ $ dmesg -H | tail [Feb14 13:37] sd 3:0:0:0: [sdc] 30318592 512-byte logical blocks: (15.5 GB/14.5 GiB) [ +0.026020] sdc: glmf-rpi/ $ # La carte microSD est ici apparue sur le nœud /dev/sdc glmf-rpi/ $ # 3. Formater la carte microSD en FAT32 glmf-rpi/ $ # Attention, cette étape supprime entièrement le contenu de la carte microSD glmf-rpi/ $ sudo mkfs.vfat -F 32 -n RPI -I /dev/sdc glmf-rpi/ $ # 4. Utiliser le script fourni, installant les fichiers sur la carte formatée glmf-rpi/ $ ./scripts/install-rpi-boot.sh /dev/sdc [+] Downloading the Raspberry Pi's firmware version 1.20161215 ######################################################################## 100.0% ######################################################################## 100.0% [+] Temporarily mounting `/dev/sdc` into `/tmp/rpi-sdcard-mountpoint` [+] Installing the RPi firmware and the Alpha debugger 'boot/bootcode.bin' -> '/tmp/rpi-sdcard-mountpoint/bootcode.bin' 'boot/start.elf' -> '/tmp/rpi-sdcard-mountpoint/start.elf' 'boot/Alpha.bin' -> '/tmp/rpi-sdcard-mountpoint/Alpha.bin' 'boot/config.txt' -> '/tmp/rpi-sdcard-mountpoint/config.txt' [+] Checking the integrity /tmp/rpi-sdcard-mountpoint/bootcode.bin: OK /tmp/rpi-sdcard-mountpoint/start.elf: OK /tmp/rpi-sdcard-mountpoint/Alpha.bin: OK [+] Un-mounting `/tmp/rpi-sdcard-mountpoint` [+] Your SD card is ready! [+] You can now insert it into the RPi and use Alpha through the RPI's Mini-UART

La carte microSD est alors prête à l’emploi et la RPi y trouvera tout le nécessaire pour démarrer le serveur GDB. À son prochain démarrage, le firmware, ayant pour tâche d’initialiser le processeur et sa carte, lira les directives contenues dans le fichier config.txt indiquant que le programme Alpha.bin doit être copié à l’adresse d’exécution 0x7F0_8000, point d’entrée du serveur.

1.2.2 Interface de communication GDB

Une fois le serveur GDB en cours d’exécution, celui-ci attend la connexion d’un client GDB et ses commandes, en écoutant une interface de communication. Le serveur GDB Alpha utilise l’interface série Mini-UART, car commune à toutes les versions de la RPi. Le port de cette interface n’étant pas disponible tel quel sur un poste de travail standard, il est nécessaire d’interposer un convertisseur entre les deux.

Du coté poste de travail, les ports séries sont les ports USB (et RS232 pour les plus anciens). Du côté RPi, l’interface Mini-UART, présente sur le connecteur GPIO (figure 3), se décompose en trois broches TTL 3.3 Volts. Il est donc nécessaire d’utiliser un convertisseur UART TTL 3.3V vers USB. Attention à bien utiliser un modèle 3.3V.

Figure 3 : Les connecteurs TTL 3.3V de l’interface Mini-UART sont toujours les mêmes pour toutes les RPi.

Figure 4 : Schéma de câblage entre la RPi 3 et un convertisseur : connexion de la masse et croisement entre les fils d’émission et réception.

Figure 5 : Exemple de câblage d’une RPi 3 avec un convertisseur série UART TTL-3.3V vers USB.

 

Figure 6 : Exemple de câblage d’une RPi 1 A+ avec un convertisseur série UART TTL-3.3V vers USB.


Une fois relié au poste de travail par un câble USB, le convertisseur est géré par l’OS et son driver de périphériques série, dont les points de montage possibles sont /dev/ttyUSBx ou bien /dev/ttyACMx pour Linux, COMx pour Windows, ou encore /dev/cu.usbserial-xxxxxx pour OS X. Pour la suite de cet article, /dev/ttyUSB0 désignera notre port série de communication GDB.

Le coût total du montage avoisine les 9€ et nécessite l’achat d’un convertisseur [2], de trois câbles jumper femelle [3] ainsi que d’un câble USB [4]. Des montages complets, couramment nommés « câbles TTL », plus compacts et incluant les câbles, sont aussi disponibles, mais à des prix beaucoup plus aléatoires [5].

2. Éditer, compiler & débugger

Nous sommes désormais prêts à utiliser GDB comme moyen de développement embarqué. Mais contrairement à une utilisation classique de GDB, dite « native », où le programme à exécuter et débugger est au préalable préparé par le système d’exploitation, il est nécessaire ici d’effectuer explicitement ces mêmes étapes préliminaires : téléchargement du programme sur la cible une fois la connexion distante établie. Le mode client/serveur de GDB offre en effet au serveur la possibilité de supporter la commande load qui prend alors directement en charge le format d’exécutable ELF et en télécharge les sections de code et de données sur la cible, puis place finalement le pointeur sur instruction sur son point d’entrée. Les sections de debug sont quant à elles lues et chargées par le client GDB et lui permettent d’apporter les fonctionnalités de debug de code et de données depuis le code source (source-level debugging).

Une session de debug d’un logiciel embarqué commence donc souvent par :

$ arm-none-eabi-gdb <fichier ELF avec informations de debug> (gdb) # 1. Configuration de l’interface de communication et connexion à la cible (gdb) target remote <interface> (gdb) # 2. Téléchargement du programme sur la cible (gdb) load (gdb) # Prêt!

Il est ensuite possible de profiter des fonctionnalités élémentaires de debug de code et de données. Les fonctionnalités plus avancées dépendent quant à elles du serveur. Par exemple, la version freemium d’Alpha, le serveur GDB utilisé, n’implémente pas les tracepoints [6], mais implémente l’extension File I/O [7] (dont l’utilité sera révélée par la suite). GDB signalera au final son incapacité à exécuter une commande si le serveur n’en est pas capable.

GDB est une solution d’instrumentation dynamique, par opposition à l’instrumentation statique qui se fait à la compilation du code source en le modifiant. GDB n’altère donc pas le programme et utilise les ressources de debug du processeur afin d’arrêter et d’observer l’exécution du programme.

De plus, Alpha met en place un environnement d’exécution permettant un développement bare-metal plus simple et dont le programme embarqué via GDB peut alors profiter. Alpha initialise en effet le processeur plus vastement que le bootloader de la RPi en activant, par exemple, l’unité flottante ou encore en programmant un espace mémoire (figure 7). Ceci doit donc être pris en considération lorsque GDB n’est plus utilisé pour charger le programme, qui doit alors lui-même effectuer les initialisations dont il a besoin. Il en est de même pour toutes les fonctionnalités nécessaires et en dehors du périmètre de cet environnement d’exécution.

Figure 7 : Mapping mémoire initialisé par Alpha dont le programme hérite.


Enfin, il ne sera pas nécessaire de redémarrer manuellement la RPi entre chaque session de debug puisqu’Alpha effectue un reset du processeur lorsque GDB lui transmet la commande kill. Cette commande est aussi induite par toutes celles impliquant, en debug natif, l’arrêt du processus, et notamment celle de connexion distante target remote <interface>, ainsi que la commande quit lorsque GDB est quitté. Relancer l’un des scripts GDB qui suit ou bien quitter GDB (correctement) implique donc un reset de la RPi, permettant ainsi de repartir depuis l’état zéro du processeur et d’éviter les effets de bord indésirables d’une session à une autre. À noter que le reset est observable grâce aux LED situées sur la RPi.

Toutes les sessions de debug présentées par la suite utilisent exclusivement l’interface ligne de commandes de GDB (CLI). Il est toutefois conseillé de profiter de son interface texte, TUI [8], et ce à tout moment, grâce à la commande tui enable (figure 8).

Figure 8 : Interface utilisateur texte de GDB.

2.1 Hello World 2.1.1 Compilation

Soit le programme :

#include <stdio.h> void main(void) { printf("Hello RPi!\n"); }

Malgré les apparences, ce programme n’est en rien anodin. Il fait en effet appel à des fonctions de la bibliothèque C standard alors qu’elles nécessitent normalement un système d’exploitation, absent dans le cas présent. Il y a d’une part l’initialisation de l’environnement d’exécution (runtime) C préalable à l’appel de la fonction main() (initialisation de la pile et des données), et d’autre part l’écriture sur la sortie standard par printf().

Pour ce faire, les fonctions et appels systèmes nécessaires sont implémentés dans la mesure du possible dans le contexte bare-metal. GDB bénéficie d’une fonctionnalité peu connue que le serveur GDB peut optionnellement implémenter pour transmettre et déléguer au client les appels système, alors exécutés sur le poste de travail sur lequel le client GDB s’exécute. Ainsi, printf()utilise l’appel système write() qui est communiqué et délégué au client tel quel, pour écrire donc finalement sur la sortie standard du client GDB, sur le poste de travail.

Figure 9 : Un appel système effectué depuis la bibliothèque C sur la RPi est délégué au client GDB sur le système hôte.


Cette astuce n’est possible qu’avec un nombre restreint d’appels systèmes [9] dont les capacités sont parfois limitées par GDB (ex. : ouvrir un fichier spécial), tandis que d’autres pourront être implémentés très simplement sans aucune aide d’un système d’exploitation ou de GDB (ex. : brk() pour malloc()).

Cette fonctionnalité de GDB s’appelle File I/O [7] et est implémentée par Alpha. Nous l’interfaçons avec la bibliothèque C newlib en remplaçant ses appels systèmes par des appels à Alpha (figure 9). Cette bibliothèque C est la seule, avec la bibliothèque C GNU glibc, à s’intégrer officiellement dans la chaîne de compilation GCC, ce qui en rend l’utilisation aussi aisée qu’en compilation native : compiler un programme est aussi simple et direct que arm-eabi-none-gcc <cppflags> <cflags> <ldflags> -o main.elf main.c <libs>. Le résultat final est la possibilité d’embarquer n’importe quel programme C standard.

Pour compiler le programme :

glmf-rpi/ $ make hello.elf arm-none-eabi-gcc -specs=sdk/Alpha.specs -mfloat-abi=hard -mfpu=vfp -march=armv6zk -mtune=arm1176jzf-s -g3 -ggdb -Wl,-Tsdk/link.ld -Lsdk -Wl,-Map,hello.map -o hello.elf src/hello-world/HelloWorld.c

Les options de compilation gcc présentes dans le Makefile sont précisément adaptées au processeur ARM11 de la première RPi : -mfloat-abi=hard -mfpu=vfp -march=armv6zk -mtune=arm1176jzf-s.

L’option –gN permet d’activer la génération des informations de debug du programme, où N est le niveau d’informations souhaité allant de 0 (désactivation) à 3 (maximum). L’option -ggdb permet quant à elle d’activer les extensions spécifiques à GDB.

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

Julio Guerra
[CTO de Farjump]

La seconde partie de cet article sera publiée prochainement sur le blog, restez connectés

Retrouvez cet article (et bien d’autres) dans GNU/Linux Magazine n°203, disponible sur la boutique et sur la plateforme de lecture en ligne Connect !

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

ven, 10/13/2017 - 09:30

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 Connect

Au sommaire

DÉMARREZ… la programmation réseau en Python avec les modules essentiels

p. 08 Utilisez TCP et UDP en Python

p. 22 Développez une application graphique utilisant le réseau

p. 34 Utilisez des API REST en Python

p. 46 Scapy, le couteau suisse Python pour le réseau

CRÉEZ… vos robots et clients en Python pour interagir avec des services web tels que GitHub, Google Drive, etc.

p. 58 Créez un système de migration des rapports de bug de GitHub à votre GitLab

p. 68 Créez un driver FUSE pour Google Drive

p. 76 Créez un bot IRC

p. 86 Créez un robot Slack

p. 98 Créez un client XMPP

PROGRESSEZ… en créant un script communiquant par SMS et en analysant un serveur de fichiers

p. 108 Envoyez des SMS avec un Raspberry Pi et Python

p. 116 Un exemple concret de serveur HTTP servant des fichiers

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

jeu, 10/05/2017 - 09:00
2.3 Installation

Une fois programmée, puis compilée, la fonction UDF se trouve sous la forme d’un binaire partagé (.so) qu’il est nécessaire de déposer à un endroit du système de fichiers où le serveur MySQL saura le trouver.

Ce chemin peut être différent selon les distributions, et surtout il peut être modifié par l’administrateur, dans la configuration de lancement du service.

La variable MySQL globale @@plugin_dir a pour rôle d’indiquer le chemin où le serveur obtient les binaires des UDF :

mysql> select @@plugin_dir; +------------------------+ | @@plugin_dir | +------------------------+ | /usr/lib/mysql/plugin/ | +------------------------+

C’est à cet emplacement que vous déposerez votre binaire. Le fichier doit appartenir au compte sous lequel tourne le service, lui être accessible en lecture et en exécution. En outre pour des raisons évidentes de sécurité vous devrez veiller à ce qu’il ne soit pas modifiable par d’autres utilisateurs.

$ sudo cp glmf-udf.so /usr/lib/mysql/plugin/ $ cd /usr/lib/mysql/plugin/ $ sudo chown mysql glmf-udf.so $ sudo chmod 0500 glmf-udf.so

2.4 Déclaration

Une fois que le serveur MySQL est en mesure de trouver et d’exécuter votre binaire, il faut lui indiquer la ou les fonctions UDF qu’il fournit.

mysql> CREATE AGGREGATE FUNCTION xxx RETURNS INTEGER SONAME "glmf-udf.so";

Le mot AGGREGATE n’est à indiquer que s’il s’agit d’une fonction d’agrégat. L’argument fourni après le mot SONAME est le nom du shared object relatif au chemin des plugins du serveur.

3. Pièges courants 3.1 Calculs et arrondis

À la lumière de la démonstration [5] faite dans GNU/Linux Magazine n°194 sur les difficultés liées au calcul à virgule flottante ou avec des très grands ou très petits nombres, la prudence s’impose dans les formules arithmétiques des fonctions UDF. Bien que les types double et long long laissent une bonne latitude concernant la précision ou la taille des nombres, la manipulation de valeurs potentiellement hétérogènes peut causer des inexactitudes difficiles à détecter.

Par exemple :

mysql> select 1e16 + 1 - 1e16; +-----------------+ | 1e16 + 1 - 1e16 | +-----------------+ | 0 | +-----------------+

3.2 Mémoire

Une fois chargée en mémoire lors de la première utilisation d’une fonction UDF, la bibliothèque reste montée. Les fuites de mémoire occasionnées par du code UDF accompagnent toute la durée d’exécution du service MySQL, jusqu’à son redémarrage.

3.3 Encodage des chaînes de caractères

Les arguments textuels sont passés aux UDF sous forme de char *. L’inconvénient direct est que le contenu de ces chaînes varie selon l’encodage (charset) en vigueur lors de l’appel à la fonction UDF. Il n’existe pas de moyen simple d’identifier automatiquement (dès l’appel) l’encodage des paramètres. Des hypothèses doivent être faites (sous forme de convention d’appel à votre fonction, ou par un argument supplémentaire qui contiendrait le charset si c’est important pour votre traitement).

3.4 Mise au point

Il n’est pas rare de devoir effectuer plusieurs essais lors de la création d’une fonction UDF, or la mise au point n’est pas aisée, car le code s’exécute dans le contexte du service MySQL lui-même. Aussi, il est conseillé d’effectuer les tests dans un serveur de développement, voire un conteneur Docker [6] :

$ docker run -d --name mysqlsrv -h mysqlsrv -e MYSQL_ROOT_PASSWORD=glmf mysql:5.7 $ docker exec -it mysqlsrv bash root@mysqlsrv:/#

3.5 Sécurité

Le code présent dans une bibliothèque UDF peut exécuter tout ce qui est autorisé au compte du service MySQL, et peut être déclenché par un simple utilisateur de la base de données sur simple appel de la fonction. Il est donc important de veiller à ce que les droits du compte du service soient convenablement configurés. En outre, il convient de ne pas faire confiance d’emblée à un binaire UDF, qui pourrait effectuer des actions sensibles ou malveillantes (ou mal codées !) provoquant des dégâts même sous le compte limité du service MySQL (qui possède tout de même tous les fichiers de données de la base…).

Avant de la déployer, il est préférable de toujours compiler le source d’une fonction UDF, à partir d’un dépôt de confiance.

Conclusion

La criticité de l’aspect sécuritaire des UDF oblige généralement les hébergeurs de serveurs de bases de données dans le nuage à les proscrire, à juste titre.

À l’heure où les directions informatiques tendent à s’orienter vers l’infrastructure locative, de façon à s’affranchir des tâches de maintenance et de supervision des serveurs de bases de données, il est à prévoir que les UDF, qui nécessitent un accès administratif à l’hôte, disparaîtront progressivement des architectures applicatives. Par ailleurs, l’augmentation des puissances de calcul permettent le plus souvent d’envisager une solution programmatique au niveau de la couche de persistance des applications, en lieu et place d’une fonction compilée dans le SGBD, même pour ce qui est des agrégats.

Néanmoins, la connaissance de ces possibilités d’extension de MySQL/MariaDB et leurs points faibles, demeure recommandée pour savoir sécuriser un système ou faire les bons choix de conception d’un applicatif.

Notez qu’il existe un projet d’inventaire et de contributions open source de fonctions UDF utiles et populaires [7]. Vos besoins particuliers sont peut-être déjà couverts, et vous pourrez souhaiter contribuer à cette plateforme avec vos prochaines créations.

Références

[1] http://www.mathematik.uni-ulm.de/help/mysql-3.22.25/manual.html
[2] http://dev.mysql.com/doc/refman/5.7/en/adding-native-function.html
[3] http://dev.mysql.com/doc/refman/5.7/en/adding-udf.html
[4] https://github.com/mysql/mysql-server/blob/5.7/sql/udf_example.cc
[5] LANGRONET F., « Peut-on vraiment calculer avec un ordinateur », GNU/Linux Magazine n°194, pp 22-31
[6] https://hub.docker.com/_/mysql/
[7] http://www.mysqludf.org/

Retrouvez cet article (et bien d’autres) dans GNU/Linux Magazine n°203, disponible sur la boutique et sur la plateforme de lecture en ligne Connect !

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

mar, 10/03/2017 - 09:00

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).

Elles sont entièrement compatibles entre MySQL et MariaDB, y compris au niveau binaire, car ces deux systèmes s’appuient sur la même interface pour l’écriture des UDF. Par souci de clarté, dans la suite de cet article les informations sont mentionnées pour MySQL, mais elles sont équivalentes pour MariaDB.

1. Qu’est-ce qu’une User Defined Function ? 1.1 Les fonctions en SQL

Tout d’abord, une fonction stockée est, à l’instar d’une procédure stockée, une fonction écrite comme une série d’instructions SQL, et enregistrée au niveau de la base de données. Elle porte un nom, et accepte un nombre arbitraire d’arguments. En plus des arguments d’appel, la fonction peut travailler avec d’autres données de la base, telles que le résultat d’une requête SELECT.

Le code SQL d’une fonction stockée est lisible en clair, et modifiable (par les personnes autorisées). Un export de la base peut contenir le code des fonctions et procédures stockées, si l’option –routines est précisée à l’outil de dump :

$ mysqldump --routines [autres options] nom_de_la_base

1.2 Cas des fonctions UDF

Une fonction User-Defined, quant à elle, est du code compilé sous la forme d’un fichier binaire partagé (shared object), et installé puis déclaré au niveau du serveur de bases de données. La fonction peut accepter par valeur un nombre prédéfini d’arguments, mais elle ne peut pas travailler sur d’autres données de la base.

Le code, puisque compilé, n’est pas lisible. Il n’est pas non plus exporté dans un dump de la base.

Une fonction UDF est généralement considérée plus véloce qu’une fonction SQL. Mais surtout, il n’est pas toujours possible, ou optimal, d’obtenir un résultat souhaité à l’aide des seules fonctions SQL. Par exemple, pour transformer une chaîne de caractères en mettant une majuscule à chaque mot, il peut s’avérer très complexe, voire impossible, de n’utiliser que les fonctions natives de MySQL (telles que LOCATE et SUBSTR). Et, même avec une boucle WHILE écrite en SQL, la complexité d’exécution et l’efficacité de l’algorithme sont considérablement moins contrôlables qu’avec du code C. La fonction compilée peut également utiliser des ressources (système, fichiers, structures de données, etc.) auxquelles les fonctions SQL natives n’ont pas accès (voir plus loin la section Sécurité).

En outre, UDF est actuellement (v5.7) la seule voie pour créer une fonction d’agrégat (à utiliser en conjonction avec la clause GROUP BY), fournissant un résultat à partir de plusieurs enregistrements (ayant un comportement similaire à SUM, AVG, etc.). Par exemple, MySQL ne fournit pas nativement de fonction pour calculer la valeur médiane d’une population de nombres : certains projets pourraient avoir besoin de cet outil.

Dans leur forme actuelle, les UDF ont été introduites dans MySQL à la version 3.23 (janvier 2001). Auparavant, qui voulait enrichir les possibilités de MySQL avec des fonctions écrites en langage C, devait recompiler [1] le serveur avec son code personnalisé (cette option demeure possible pour ajouter des fonctions natives [2]).

Les fonctions UDF peuvent servir à des fins très diverses, de l’implémentation de formules mathématiques ou statistiques, jusqu’à l’émission de requêtes HTTP, en passant par l’interprétation de balises XML ou l’extraction d’éléments HTML par exemple.

2. Mise en œuvre

Supposons que l’on souhaite réaliser une bibliothèque qui contiendra une fonction UDF que nous nommerons XXX (les noms de fonctions SQL sont indifférents à la casse). Le code source de chaque UDF se compose d’un ensemble de fonctions C que le moteur exécute selon un certain cycle. Notez qu’il est possible d’écrire plusieurs fonctions UDF dans un même shared object.

Il faut écrire le code en respectant certaines règles, puis le compiler, installer le binaire sur l’hôte qui exécute le service de bases de données, et déclarer la bibliothèque au serveur MySQL.

2.1 Prérequis

Pour pouvoir compiler le code d’une fonction UDF, le système doit disposer des paquets suivants (si basé sur Debian) :

$ sudo apt-get install gcc libmysqlclient-dev

2.2 Écriture d’une UDF

Une fonction UDF peut retourner un des trois types SQL suivants : INTEGER, REAL, STRING, qui correspondent respectivement aux types C : long long, double et char *. Les arguments qu’elle accepte sont de ces mêmes types, et par valeur (c’est-à-dire qu’une fonction ne peut pas modifier la valeur d’un champ de la base ou d’une variable MySQL).

Le code source d’une fonction UDF doit respecter certaines conventions et signatures [3]. Un exemple complet et instructif est fourni [4] avec le code source du serveur MySQL.

2.2.1 Fonctions simples

Une UDF simple (sans agrégat) se compose de trois fonctions C : xxx_init(), xxx(), et xxx_deinit(). Elles sont invoquées selon le cycle suivant :

Dans toute la suite, les xxx sont à comprendre comme un préfixe aux différentes fonctions C. Vous devrez le remplacer par le vrai nom de votre fonction UDF, tel qu’utilisé dans vos requêtes SQL.

  1. Le moteur identifie qu’une requête utilise la fonction XXX( ), et charge la bibliothèque correspondante si ce n’est pas déjà fait.
  2. Le moteur invoque le point d’entrée d’initialisation de la fonction : par convention, il s’agit obligatoirement du symbole xxx_init(), où le nom de la fonction UDF est en minuscules (ici xxx). La présence de cette fonction dans votre bibliothèque est optionnelle ; le moteur ne l’invoque que si elle s’y trouve, sans lever d’erreur dans le cas contraire. L’appel à _init a lieu une fois avant l’exécution de la requête (mais de nouveau pour chaque requête utilisant votre fonction, et autant de fois que la fonction y apparaît). Elle vous offre la possibilité d’allouer les ressources nécessaires (mémoire, etc.), de mettre à zéro vos compteurs, et autres opérations préliminaires. C’est également dans cette fonction que vous pouvez indiquer si la valeur NULL est une valeur de retour possible pour votre UDF.
  3. Une fois pour chaque ligne, le serveur appelle votre fonction xxx(), qui est la fonction principale (et qui porte obligatoirement le même nom que la fonction SQL). C’est elle qui retourne le résultat, ou qui indique si une erreur s’est produite (par exemple, en cas de division par zéro).
  4. Une fois la requête terminée, et toutes les lignes traitées, votre fonction xxx_deinit() (optionnelle) est appelée. C’est ici que vous libérez toutes les ressources réservées par _init ou dans votre fonction principale.

Si le code est en C++, penser à exporter les points d’entrée avec la directive extern “C” afin que les noms soient conservés.

2.2.2 Agrégats

Pour une fonction d’agrégat, deux points d’entrée supplémentaires doivent être fournis : xxx_add() et xxx_clear(). Le cycle devient le suivant :

  1. Identification et chargement.
  2. Appel à xxx_init().
  3. Le moteur groupe et trie les lignes selon les prescriptions de la clause GROUP BY.
  4. Au début de chaque nouveau groupe de lignes, appel de la fonction xxx_clear(). Vous y réinitialisez tous les compteurs et ressources communs à un même groupe de lignes.
  5. Pour chaque ligne du groupe, invocation de xxx_add(). Dans cette fonction, vous incrémentez vos compteurs et autres valeurs cumulatives dans vos propres structures de données (voir plus loin).
  6. À la fin de chaque groupe (une fois que _add a été appelé sur toutes les lignes du groupe), le moteur exécute votre fonction principale xxx() sur l’ensemble des données cumulées au cours de chaque appel _add dans votre structure personnelle.
  7. Reprise de l’étape 3, tant qu’il reste des groupes à traiter.
  8. Appel final à xxx_deinit().

Le nom _add() pourrait semer la confusion, mais il s’agit bien sûr d’ajouter une valeur à son groupe, afin d’y appliquer le calcul de votre choix. Il ne s’agit pas d’effectuer obligatoirement une simple addition.

2.2.3 Arguments et structure de travail

Chacune de ces fonctions reçoit en paramètre un pointeur vers une structure C UDF_INIT, toujours la même entre tous les appels au sein d’un même cycle.

Par exemple :

void xxx_add(UDF_INIT *initid, UDF_ARGS *args, char *is_null, char *error);

Un des membres de cette struct est char *ptr, réservé aux besoins du développeur : c’est par lui que vous pouvez maintenir vos compteurs et autres variables partagées via votre propre structure de données (particulièrement utile pour le cas des fonctions d’agrégat), en ayant pris soin d’allouer la mémoire nécessaire dans _init, et de la libérer dans _deinit.

Les arguments SQL passés à la fonction (les vraies valeurs, pour chaque ligne) se récupèrent grâce à la structure UDF_ARGS. Cette struct contient (entre autres) :

  • le membre unsigned int arg_count qui indique le nombre d’arguments ;
  • le membre enum Item_result *arg_type qui est un tableau indiquant le type SQL de chaque argument ;
  • le membre char **args qui fournit un tableau des valeurs effectives des arguments.

L’argument char *error vous permet d’indiquer (en lui donnant la valeur 1) si les paramètres d’appel à votre fonction provoquent une erreur. Dans ce cas, la valeur de retour de votre UDF pour cette ligne sera NULL.

2.2.4 Compilation

Une fois votre code rédigé (par exemple, dans un fichier glmf-udf.c), la compilation se fait par la commande :

$ gcc -fPIC -shared -I/usr/include/mysql -o glmf-udf.so glmf-udf.c

Ceci produit le fichier glmf-udf.so. L’argument -I (i majuscule) précise le chemin des en-têtes MySQL, qui peut se trouver à un autre emplacement sur votre distribution. C’est le rôle du paquet libmysqlclient-dev installé en prérequis.

Tout votre code doit être thread-safe : plusieurs requêtes peuvent s’exécuter en même temps, et une même requête peut faire appel plusieurs fois à votre fonction en différents endroits. Il faut donc notamment proscrire les variables globales et statiques.

Gabriel Zerbib

La seconde partie de cet article sera publiée prochainement sur le blog, restez connectés

Retrouvez cet article (et bien d’autres) dans GNU/Linux Magazine n°203, disponible sur la boutique et sur la plateforme de lecture en ligne Connect !

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

lun, 10/02/2017 - 09:00

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 !

Un autre exemple, plus mathématique, déjà abordé en 2016 (GLMF n°191) : nombre de personnes ne font toujours pas la distinction entre les préfixes décimaux et binaires pour quantifier la mémoire (préfixes introduits dans la norme IEC 60027-2 de 1999, soit depuis bientôt 20 ans !). Lorsque l’on parle d’un « kilo » octet on pense à 1024 octets, mais d’après le Système International d’unités, il s’agit de 1000 octets. D’où les préfixes binaires qui sont des puissances de 2 et permettent de quantifier par exemple 1024 octets et 210 octets. Pour différencier les deux notations, on ajoute un « i » (kibi-octet, etc.) et les Kio, Mio, Gio ou Tio remplaceront les ko, Mo, Go ou To. Entre 1 Go (109 octets) et 1 Gio (230 octets), il y a tout de même 7% d’erreurs ! Pourtant, nous voyons encore tous les jours des ko, Mo, etc. traîner dans des articles ou des documents. Nous sommes confrontés ici à une problématique de résistance au changement. En tant qu’informaticiens, nous savons combien il est important (et difficile) de la combattre… à nous de montrer l’exemple !

Pour finir, voici un autre exemple où cette fois-ci nombre d’informaticiens s’insurgent d’un mésusage de la langue française… qui en fait n’en est pas un. Je suis le premier à remplacer le terme « librairie » dans les articles par « bibliothèque ». À force d’être confronté au problème, je me suis demandé si, finalement, on ne pouvait pas accepter ce terme, si ce n’était pas moi qui faisais fausse route. Si l’on se questionne, dans une librairie comme dans une bibliothèque, quoi de plus normal que de trouver des livres ? Quelle est donc la différence entre ces deux mots ? Leur définition ne nous sera pas d’une grande utilité puisque l’on apprend qu’une bibliothèque est un « lieu où est rangée une collection de livres » (http://www.cnrtl.fr/definition/biblioth%C3%A8que) et qu’une librairie est définie, d’un point de vue littéraire, comme étant… une bibliothèque (http://www.cnrtl.fr/definition/librairie). Ça, c’est pour le sens premier (en général, une librairie a un sens marchand de nos jours, mais en bon français la « librairie du roi » désigne sa bibliothèque). En informatique, une bibliothèque est une « collection de programmes et de sous-programmes standards soigneusement testés, au moyen de laquelle on peut résoudre plusieurs types de problèmes ou de parties de problèmes ». Mais dans ce domaine, lorsque quelqu’un parle de librairie, on va tout de suite penser qu’il s’agit d’un anglicisme, d’une traduction fausse du terme anglais library ; nombre de personnes hurleront à l’hérésie. Pourtant, d’un point de vue purement formel, si A → B et que A = C, alors C → B… ce qui revient à dire que si « bibliothèque » → « collection de programmes » et que « bibliothèque » = « librairie », alors « librairie » → « collection de programmes ». Je pense donc qu’un peu de tolérance serait de mise dans ce domaine et je suis le premier à faire mon mea-culpa pour l’ensemble des auteurs que j’ai injustement corrigés.

On pourrait trouver encore bien des pièges à détailler, mais l’on peut voir que dans un domaine déjà semé de bugs et de problèmes de compatibilité, il n’est pas toujours évident d’exprimer correctement et précisément sa pensée. À nous donc de faire un effort sur notre langage pour être sûrs de bien nous comprendre !

Sur ce, je vous souhaite une bonne lecture et je vous retrouverai avec plaisir le mois prochain.

Tristan Colombo