Aller au contenu principal

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 :

TermeTailleMASM
byte1 octetBYTE
word2 octetsWORD
double word4 octetsDWORD
quad word8 octetsQWORD

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 :

DiviseurDividendeQuotientReste
1 octetaxalah
2 octetsdx:axaxdx
4 octetsedx:eaxeaxedx
8 octetsrdx:raxraxrdx

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 jeSi égal
jnejneSi différent
jgjaSi le premier opérande est plus grand
jgejaeSi le premier opérande est plus grand ou égal
jljbSi le premier opérande est plus petit
jlejbeSi 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 :

push

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 :

pop

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