Cette série d’articles aura pour but de vous donner une bonne base en assembleur pour l’architecture x86. Elle s’adresse principalement à un lectorat ayant déjà programmé en C ou C++. En effet, bien que l’assembleur soit principalement utilisé afin d’accéder plus directement au matériel, je crois qu’il est extrêmement intéressant de voir comment il est possible de l’interfacer avec le C et le C++. Ceci permet de bien comprendre les rouages du C et du C++ afin de savoir ce que le processeur fait vraiment lors de l’exécution d’un programme.
Cette série d’environ dix articles vous permettra de vous familiariser avec un langage de bas niveau. Bien que l’environnement choisi est Linux, le principe du langage assembleur reste sensiblement le même peu importe le système d’exploitation et l’architecture. Une fois son fonctionnement bien saisi, il n’est pas très difficile de passer d’une architecture CISC vers une architecture RISC par exemple. La base est souvent la même dans la mesure où on utilise toujours des registres et de la mémoire. À titre d’expérience personnelle, je suis moi-même passé de l’assembleur x86 vers un processeur RISC pour un système embarqué.
Les différents chapitres sont fortement inspirés d’un cours que j’ai suivi à l’École Polytechnique de Montréal. Le cours s’intitulait Programmation de systèmes sur microprocesseur et se donnait pour la dernière fois lors du trimestre d’hiver 2006. Bien que ce cours en soit fortement inspiré, j’ai fait des modifications dans plusieurs chapitres et j’ai fait mes propres exemples tout en ajoutant ce que j’ai appris lors des séances de laboratoire. Voici donc ce que je vous propose pour commencer, il se peut qu’il y ait des changement en cours de route.
1- Introduction
Il s’agit ici d’une brève introduction servant à présenter le langage assembleur, ses avantages ainsi que ses inconvénients.
2- L’environnement de programmation
Ce chapitre sert en fait à vous présenter différents outils qui sont à votre disposition sur les systèmes Unix. Il sera ici question de l’assembleur GNU as, de l’éditeur de liens GNU ld, des compilateurs GNU gcc et GNU g++, du dévermineur GNU gdb et de son interface graphique Insight. Bien qu’il existe d’autres outils tout aussi bien, je crois que ce sont les plus courrament utilisés sous Linux.
3- Représentation des nombres
Ce chapitre n’est pas néssaire pour les personnes qui sont déjà familières avec les nombres binaires et hexadécimaux. Il sera donc question des différentes bases que l’on a pour représenter les nombres et comment il est possible de passer d’une base vers une autre. Cette partie est essentielle pour bien comprendre le langage assembleur et c’est encore plus vrai pour des systèmes embarqués.
4- Architecture x86
Nous verrons une bonne partie de l’architecture x86 sans entrer trop dans le détail puisque l’architecture d’un processeur est excessivement complexe. Il pourrait s’agir d’un sujet pour un futur article. Nous verrons donc les registres généraux, l’organisation de la mémoire, le registre indicateur, la représentation des données, les modes d’adressage, la pile et les interruptions (brève introduction). Encore une fois, avant d’entrer dans l’apprentissage du langage assembleur, il est important de connaître l’architecture sur laquelle on travaille puisque l’assembleur communique directement avec le matériel. Il n’est pas nécessaire d’avoir une base en électronique/logique même si cela s’avère un atout.
5- Le langage assembleur
Nous entrons finalement dans le vif du sujet avec une présentation des instructions les plus souvent utilisées avec le processeur. Nous verrons les instructions de transfert, les sauts conditionnels et inconditionnels, comment représenter les structures de contrôle (if, else...), les additions/soustractions, multiplications/divisions, les opérations sur les bits, sur les chaînes de caractères, les instructions logiques, etc...
6- Chargement d’un programme et son exécution
Cette section cible plus particulièrement le système d’exploitation Linux (et les UNIX en général) puisqu’il sera question de l’entête ELF. Nous verrons aussi l’état des registres lors du chargement d’un programme ainsi que la disposition des segments.
7- Interface avec le C/C++
Il s’agit selon moi d’une des sections les plus intéressantes. Nous verrons de quelle manière il est possible d’interfacer du langage assembleur avec les langages C et C++. Ceci nous permettra de comprendre comment sont passés les paramètres des fonctions, les valeurs de retour et peut-être même de bien comprendre ce qu’est un pointeur. Nous verrons donc plus en détail la pile.
8- Le coprocesseur mathématique
Après un peu de pratique avec le processeur et le fonctionnement d’une pile, nous verrons comment se servir du coprocesseur mathématique pour effectuer des opérations sur des nombres à virgule flottante par exemple. Nous verrons aussi comment l’interfacer avec les langages C et C++.
9- Les directives et les macros
Il sera ici question des données et des constantes, de l’alignement en mémoire, la compilation conditionnelle ainsi que les macros.
10- Assembleur en-ligne
Il s’agit d’une partie de code assembleur que l’on peut mettre dans du code C ou C++ directement avec l’instruction asm. Nous verrons aussi l’assembleur en-ligne étendu qui permet de mieux interagir avec les variables locales d’une fonction par exemple.
Voilà qui devra suffire pour vous permettre de bien maîtriser les bases du langage assembleur. Pour ceux et celles qui voudront aller plus en profondeur, il existe d’excellents livres sur le sujet que je mettrai en lien à la fin de mes articles.