<author><url name="Konstantin Boldyshev" url="mailto:konst@linuxassembly.org"> <!-- Compléter: Linux Gazette n°53 - Traducteur:Le Roy David --> <!-- T CUT CUT CUT CUT CUT CUT CUT CUT CUT CUT CUT CUT CUT CUT CUT CUT CUT --> <sect>Introduction à la programmation en Assembleur sous Unix <p>Par Konstantin Boldyshev <tt><url name="konst@linuxassembly.org" url="mailto:konst@linuxassembly.org"></tt> <!-- La traduction --> Ce document doit être un tutorial, montrant comment écrire un simple programme assembleur sur beaucoup de systèmes d'exploitation Unix sur plateforme IA32 (i386). Les programmes inclus sont peut être utilisables sur d'autres architectures. Ce document explique la structure d'un programme, la convention des appels systèmes, et le processus de construction. Il accompagne le HOWTO Assembleur Linux, qui peut aussi vous interesser, bien qu'il soit plus spécifique à Linux. version 0.3, du 9 avril 2000. <sect1>Introduction <sect2>Aspect Légal. <p>Copyrigth © 1999-2000 Konstantin Boldyshev. Vous avez la permission de copier, distribuer et/ou modifier ce document sous les termes de la licence de documentation libre GNU version 1.1 ou suivantes publié par la Free Software Fondation. <sect2>Obtenir ce document <p>La dernière version de ce document est disponible sur <url url="http://linuxassembly.org/intro.html" name="http://linuxassembly.org/intro.html">. Si vous êtes en train de lire une copie vieille de plusieurs mois, aller voir sur le site pour une version plus récente. <sect2>Les outils dont vous avez besoin <p>Vous aurez besoin de quelques outils pour jouer avec les programmes inclus dans ce tutorial. En premier vous aurez besoin d'un assembleur (compilateur). La règle veut que les distributions modernes possèdent <tt>gas</tt>, mais il y a des fois ou c'est <tt>nasm</tt> (Netwide Assembleur) qui est présent. Vous pouvez télécharger nasm à partir d'<url url="www.cryogen.com/Nasm" name="ici">, il est donné avec ses sources. Compilez-le, ou essayer de trouver des binaires pour votre système~; veuillez noter que beaucoup de distributions (ou moins les distributions Linux) possèdent déjà <tt>nasm</tt>. En second, vous aurez besoin d'un linker, <tt>ld</tt>, puisque <tt>nasm</tt> ne produit que du code object. Toutes les distributions doivent posséder <tt>ld</tt>. Si vous voulez aller plus loin, il vous faudra aussi installer les fichiers <em>includes</em> de votre système, et aussi les sources du noyau. Maintenant que nous sommes prêts à travailler, bienvenue. <sect1>Hello, World~! <p>Nous allons maintenant écrire notre programme, un classique "Hello, World" (hello.asm). Vous pouvez descendre les sources et binaires <url url="hello.tgz" name="ici">. Mais avant laisser moi vous expliquer les bases. <sect2>Appel système <p>A moins que le programme ne fasse que mettre en pratique des algorithmes mathématiques, il lui faudra récuperer des informations (entrée) et en produire (sortie). Vous aurez donc besoin des services fournis par le système d'exploitation. En fait, programmer en assembleur ne change que très peu d'un système d'exploitation à l'autre, pour autant que l'on ne touche pas aux services du système. Il existe 2 façons d'effectuer un appel système sous UNIX: avec un <em/wrapper/ de la librarie C (libc), ou directement. Les choses qui changent selon les différents noyaux UNIX sont les ensembles d'appels systèmes et les conventions d'appels systèmes (néanmoins comme ils suivent les normes POSIX, il existe beaucoup de ressemblance entre eux). Note pour les (anciens) programmeurs DOS qui se demanderaient ce qu'est un appel système. Si vous avez déjà écrit un programme en assembleur DOS (et beaucoup de programmeur en assembleur IA32 l'on fait), vous vous rappelez des services DOS <tt>int 0x21, int 0x25, int 0x26</tt>, etc.... Ce sont des appels systèmes. Néanmoins leur implémentation réelle est totalement différente, et cela ne signifie pas qu'un appel système est effectué obligatoirement par une interruption. De même, les programmeurs DOS mélangent assez souvent les services du système d'exploitation avec ceux du BIOS comme <tt>int 0x10</tt> ou <tt>int 0x16</tt>, et sont assez surpris lorsqu'ils n'arrivent pas à faire cela sous UNIX. <sect2>Architecture du programme <p>Les UNIX modernes sont 32bits, tournent en mode protégé, possèdent un modèle de mémoire plat (flat), et utilisent le format ELF pour les binaires. Les programmes sont divisés en segment (section): .text pour votre code (lecture seule), .data pour vos données (lecture./écriture), .bss pour les données non initialisées (lecture/écriture). En fait, il en existe quelques autres, avec des segments définis par l'utilisateur, mais il n'y a que peu de chance pour que vous les utilisiez. ils sortent donc de notre champ d'interêt. Les programmes doivent avoir un segment .text. Bon, maintenant nous allons nous plonger dans les détails des spécifications des systèmes d'exploitation. <sect2>Linux <p>Les appels systèmes Linux sont effectué par <tt>int 0x80</tt> (Il existe en fait un patch pour le noyau qui permet des appels systèmes par l'instruction syscall (sysenter) sur les nouveaux processeurs, mais la chose est encore expérimentale). Linux diffère de la syntaxte classique UNIX, et possède un "appel rapide" (fastcall) pour les appels systèmes (cela ressemble au DOS). Le numéro des fonctions systèmes est passé par <tt>eax</tt> et les arguments sont passé par les registres plutôt que par la pile. En conséquence, il peut y avoir au maximun 5 arguments dans <tt>ebx, ecx, edx, esi, edi</tt>. Si il y a plus de 5 arguments on les passe à travers une structure comme premier argument. Le résultat est retourné dans eax, la pile n'est absolument pas utilisée. Les numéros des appels systèmes sont dans <tt>sys/syscall.h</tt>, qui inclue <tt>asm/unistd.h</tt>. Il existe aussi un peu de documentation dans la deuxième section des man pages (pour trouver des informations sur l'appel système <tt>write</tt>, tapez <tt>man 2 write</tt>). Il existe beaucoup de tentatives d'obtenir une documentation à jour des appels systèmes Linux, allez voir les adresses dans la section <ref id="reference" name="Références">. Voici donc à quoi ressemble notre programme Linux: <code> section .text global _start ; Doit être déclaré pour le linker (ld) msg db 'Hello, world!',Oxa ; Notre cher texte len equ $ - msg ; La taille de notre texte _start: ; Nous informons le programme où est notre point d'entrée mov edx,len ; Taille du message mov ecx,msg ; Message à écrire mov ebx,1 ; Descripteur de fichier (stdout) mov eax,4 ; Numéro d'appel système (sys_write) int 0x80 ; Appel Noyau mov eax,1 ; Numéro d'appel système (sys_exit) int 0x80 ; Appel Noyau </code> Comme vous allez le voir après, la convention d'appel système Linux est des plus brèves. Réferences du noyau Linux: <itemize> <item>arch/i386/kernel/entry.S <item>include/asm-i386/unistd.h <item>include/linux/sys.h </itemize> <sect2>FreeBSD <p>FreeBSD possède la convention classique, où le numéro de l'appel système est dans eax, et les paramètres dans la pile (le premier argument pousse le dernier). Les appels systèmes sont obtenus à travers l'appel à la fonction contenant <tt>int 0x80</tt> et <tt>ret</tt>, et non pas seulement <tt>0x80</tt> (l'adresse de retour DOIT être sur la pile avant d'effectuer <tt>int 0x80</tt>). L'appelant doit nettoyer la pile après l'appel. Le résultat est retourné comme d'habitude dans eax. On peut aussi utiliser <tt>call 7:0</tt> à la place. Le résultat final est le même, sans compter l'accroissement de la taille du programme, en effet vous aurez besoin de rajouter un <tt>push eax</tt> avant, et ces deux instructions prennent un peu plus de place. Les numéro des appels systèmes sont dans sys/syscall.h, la documentation est dans la deuxième section des pages de manuels. Bon, je pense que les sources éclaireront un peu plus ceci: Note: Le code inclu doit pouvoir fonctionner sur d'autres *BSD aussi, je pense. <code> section .text global _start ; Doit être déclaré pour le linkeur (ld) msg db "Hello, world!",0xa ; Notre cher texte len equ $ - msg ; La taille de notre texte _syscall: int 0x80 ; Appel Système ret _start: ; Donne le point d'entré au linker push dword len ; Taille du message push dword msg ; Message à écrire push dword 1 ; Descripteur de fichier (stdout) mov eax,0x4 ; Numéro d'appel système (sys_write) call _syscall ; Appel Noyau ; En fait il existe une autre ; manière d'effectuer un appel système ; push eax ; call 7:0 add esp,12 ; Nettoye la pile (3 arguments * 4) push dword 0 ; Code de sortie mov eax,0x1 ; Numéro d'appel système (sys_exit) call _syscall ; Appel Noyau ; Nous n'avons pas besoin de retourner de sys_exit, ; Il n'y a pas besoin de nettoyer la pile. </code> Réferences du noyau BSD: <itemize> <item>386/i386/exception.s <item>i386/i386/trap.c <item>sys/syscall.h </itemize> <sect2>BeOS <p>Le noyau BeOS utilise la convention d'appel classique UNIX. La différence avec l'exemple FreeBSD est que l'on appelle <tt>int 0x25</tt>. Pour les informations sur comment trouver les numéros d'appels systèmes et d'autres détails intéressant, examiner <url name="asmutils" url="http://linuxassembly.org/asmutils">, et spécialement le fichier os_beos.inc. Note: pour que <tt>nasm</tt> compile correctement sur BeOS vous devez insérez <tt>#include "nasm.h"</tt> dans <tt>float.h</tt>, and <tt>#include < stdio.h></tt> dans <tt>nasm.h</tt>. <code> section .text global _start ; Doit être déclaré pour le linker (ld) msg db "Hello, world!",0xa ; Notre cher texte len equ $ - msg ; La taille de notre chaine _syscall: ; Appel Système int 0x25 ret _start: ; Donne le point d'entrée au linkeur push dword len ; Taille du Message push dword msg ; Message à écrire push dword 1 ; Descripteur de fichier(stdout) mov eax,0x3 ; Numéro d'appel système (sys_write) call _syscall ; Appel Noyau add esp,12 ; Nettoye la pile (3 * 4) push dword 0 ; Code de sortie mov eax,0x3f ; Numéro d'appel système (sys_exit) call _syscall ; Appel Noyau ; Pas besoin de nettoyer la pile </code> <sect2>Construire un binaire <p>La construction d'un binaire est un processus en deux temps: compilation et liaison. Pour faire de notre fichier hello.asm un binaire, nous devons faire les choses suivantes: <code> $ nasm -f elf hello.asm # Ceci génère le fichier objet hello.o $ld -s -o hello hello.c # Ceci génère l'éxécutable hello </code> Et voila~! Simple, non. Maintenant vous pouvez lancez votre programme hello en tapant <tt>./hello</tt>, cela devrait fonctionner. Regarder la taille du binaire -- surpris ?? <sect1>Références <label id="reference"> <p> J'espère que vous avez apprécié ce voyage. Si vous êtes intéréssé par la programmation en assembleur sous Unix, je vous recommande fortement d'aller sur le site <url url="http://linuxassembly.org" name="Linux Assembly"> pour plus d'informations, et de télécharger le package <url url="http://linuxassembly.org/asmutils.html" name="asmutils">, il contient beaucoup de code exemple. Pour une vue globale de la programmation Linux/UNIX, référez-vous au <url url="http://linuxassembly.org/howto.html" name="HOWTO Assembleur Linux">. Merci de votre interêt~! <p>Copyright 2000, Paru dans le numéro 53 de la Linux Gazette de Mai 2000. <p>Traduction française de David Le Roy <url url="mailto:llewellyn@free.fr" name="llewellyn@free.fr"> </sect> </article>