Assembleur
Langage de programmation du plus bas niveau lisible par un humain. Un assembleur est spécifique à chaque architecture et son langage expose directement le jeu d'instructions et les registres.
Historique
En 1947, Kathleen Booth développe un langage symbolique nommé « Contracted Notation ». Plutôt que d'utiliser des codes numériques pour effectuer toutes les opérations, un ensemble de mnémoniques est associé à ces codes. Ce langage est le précurseur des langages d'assemblage.
Ingénieur électricien, Nathaniel Rochester commence à travailler chez IBM en 1948 et co-conçoit l'IBM 701, le premier ordinateur scientifique commercial de l'entreprise. Au début des années 1950, il écrit pour cet ordinateur le premier assembleur symbolique largement diffusé.
Bien qu'il soit beaucoup plus facile d'écrire un programme en langage assembleur qu'en langage machine, le travail reste fastidieux et réservé à ceux et celles qui ont de bonnes connaissances de la machine et de ses rouages internes.
Source
Le code source d'un programme en langage assembleur est contenu dans des fichiers texte ayant, habituellement, .asm ou .s comme extension.
Syntaxe
Fait inhabituel : sur l'architecture x86, deux syntaxes coexistent pour le langage assembleur, auxquelles s'ajoutent de légères variation d'un assembleur à l'autre.
La première syntaxe, conçue dans les laboratoires d'AT&T, en porte le nom. La seconde tire le sien d'Intel, dont les manuels du 8086 l'ont établie.
Plus concise et généralement jugée plus intuitive, c'est cette dernière qui sera utilisée dans ce résumé, avec l'assembleur MASM.
Squelette
Avec une architecture x86-64 et le système d'exploitation Windows, la fonction ExitProcess permet de terminer le programme et la valeur de retour est stockée dans le registre ecx :
ExitProcess proto
.data
; Déclaration et initialisation de variables.
.code
main proc
sub rsp, 40 ; Espace sur la pile exigé par l'ABI x64 de Windows et alignement.
; Code source du programme.
xor ecx, ecx ; Code de retour (0) du programme.
call ExitProcess ; Fin du programme.
main endp
end
Compilation
Le programme ml64.exe est un assembleur 64 bits pour le système d'exploitation Windows :
ml64 /c main.asm
L'argument /c (compile) permet de traduire le langage assembleur en langage machine.
Liaison
Le programme link.exe est un lieur pour le système d'exploitation Windows :
link main.obj kernel32.lib /subsystem:console /entry:main
La fonction ExitProcess provient de la bibliothèque du noyau de Windows et sera liée au programme. L'argument /subsystem permet de spécifier qu'il s'agit d'un programme s'exécutant en console et l'argument /entry permet de spécifier la fonction principale du programme.
Commentaire
Le caractère point-virgule permet de mettre en commentaire tout ce qui suit :
; Commentaire
Bloc
Il n'y a pas de bloc de code délimité en langage assembleur, mais il est possible de déclarer des étiquettes agissant comme des adresses symboliques dans le code :
etiquette:
; Instructions
Variables
Les variables initialisées sont déclarées dans le segment de données :
.data
Leur déclaration débute par leur identifiant, suivi de leur taille, et se termine par une valeur initiale.
Taille
Les termes utilisés pour identifier la taille sont :
| Terme | Taille | MASM |
|---|---|---|
| byte | 1 octet | BYTE |
| word | 2 octets | WORD |
| double word | 4 octets | DWORD |
| quad word | 8 octets | QWORD |
MASM
octet BYTE 42
Instructions
Les mnémoniques permettent de représenter les codes d'opération du jeu d'instructions.
Affectation
Le jeu d'instructions x86-64 permet quelques combinaisons d'affectation :
mov rax, rbx ; Registre <- Registre
mov rbx, 42 ; Registre <- Constante
mov rcx, [variable] ; Registre <- Mémoire
mov [variable], rdx ; Mémoire <- Registre
mov [variable], 42 ; Mémoire <- Constante
Logiques
Le jeu d'instructions x86-64 permet plusieurs combinaisons d'opérations logiques, qui s'effectuent bit à bit :
Non
Le résultat est stocké dans le registre ou la variable :
not rax
not [variable]
ET, OU et OU exclusif
Le résultat est stocké dans la destination :
and rax, rbx ; Registre <- Registre ET Registre
and rbx, 42 ; Registre <- Registre ET Constante
and rcx, [variable] ; Registre <- Registre ET Mémoire
and [variable], rdx ; Mémoire <- Mémoire ET Registre
and [variable], 42 ; Mémoire <- Mémoire ET Constante
La syntaxe et les combinaisons sont les mêmes pour les instructions or et xor.
Arithmétiques
Le jeu d'instructions x86-64 permet plusieurs combinaisons d'opérations arithmétiques :
Addition
Le résultat est stocké dans la destination :
add rax, rbx ; Registre <- Registre + Registre
add rbx, 42 ; Registre <- Registre + Constante
add rcx, [variable] ; Registre <- Registre + Mémoire
add [variable], rdx ; Mémoire <- Mémoire + Registre
add [variable], 42 ; Mémoire <- Mémoire + Constante
La syntaxe et les combinaisons sont les mêmes pour l'instruction sub.
Incrémentation et décrémentation
Il est possible d'incrémenter ou décrémenter :
inc rax ; Incrémentation registre
dec [variable] ; Decrémentation mémoire
Division
L'instruction div ne prend que le diviseur comme opérande. Le dividende est implicite en fonction de la taille de ce diviseur.
div bx ; Registre
div [variable] ; Mémoire
Le résultat est stocké selon la taille du diviseur :
| Diviseur | Dividende | Quotient | Reste |
|---|---|---|---|
| 1 octet | ax | al | ah |
| 2 octets | dx:ax | ax | dx |
| 4 octets | edx:eax | eax | edx |
| 8 octets | rdx:rax | rax | rdx |
Comparaisons
Le jeu d'instructions x86-64 permet de comparer des valeurs :
cmp rax, rbx ; Entre registres
cmp rbx, 43 ; Entre registre et constante
cmp rcx, [variable] ; Entre registre et mémoire
cmp [variable], 42 ; Entre mémoire et constante
Sauts conditionnels
Il y a plusieurs sauts conditionnels possibles à suite d'une comparaison :
| Saut signé | Saut non signé | Description |
|---|---|---|
je | je | Si égal |
jne | jne | Si différent |
jg | ja | Si le premier opérande est plus grand |
jge | jae | Si le premier opérande est plus grand ou égal |
jl | jb | Si le premier opérande est plus petit |
jle | jbe | Si le premier opérande est plus petit ou égal |
Saut inconditionnel
L'instruction jmp permet de modifier le flot d'exécution séquentiel :
jmp etiquette
Structure conditionnelle
Il n'y a pas de if ni de else dans le jeu d'instructions x86-64; il faut plutôt utiliser des sauts conditionnels et inconditionnels :
cmp [proposition], 0
je Sinon
; Instructions si vrai
jmp FinSi
Sinon:
; Instructions si faux
FinSi:
Pile
Le jeu d'instructions x86-64 permet quelques façons de manipuler la pile.
Empiler
Pour empiler une donnée sur la pile :
push rax ; Registre
push 42 ; Constante
push [variable] ; Mémoire
Le registre sp est décrémenté lors de chaque empilement :

Dépiler
Pour dépiler une donnée du sommet de la pile :
pop rax ; Registre
pop [variable] ; Mémoire
Le registre sp est incrémenté lors de chaque dépilement :

Les données ne sont pas retirées de la mémoire.
Fonction
L'assembleur permet de définir et d'appeler des fonctions.
Définition
Une fonction est définie à partir d'une étiquette, et son exécution se termine par l'instruction ret :
fonction:
; Instructions
ret
Appel
L'appel d'une fonction se fait avec l'instruction call suivie de l'étiquette identifiant la fonction :
call fonction
L'instruction call effectue deux opérations :
- Empilement de la valeur du registre IP.
- Affectation de l'adresse de la fonction au registre IP.
Retour
L'instruction ret effectue l'opération inverse :
- Dépilement dans le registre IP.
Paramètres
La pile ne sert pas uniquement pour les appels et retours de fonctions, elle permet aussi de stocker les paramètres.
somme:
mov rax, [rsp + 8] ; Premier opérande.
add rax, [rsp + 16] ; Deuxième opérande.
ret 16 ; Dépilement des deux paramètres suite au retour.
main proc
sub rsp, 28h
push 40 ; Deuxième opérande.
push 2 ; Premier opérande.
call somme
xor ecx, ecx
call ExitProcess
main endp