Vous êtes sur la page 1sur 9

Assembleur 80386

Le langage Assembleur (ASM) est un langage de bas niveau permettant l’écriture de


programmes rapides et d’interfaces vers des périphériques (drivers). Il est utilisé pour écrire
les drivers, les portions optimisées de programme,…
Un programme en ASM peut fonctionner en deux modes :
1. Le mode réel : le programme peut adresser n’importe quel endroit en mémoire. C’est
le cas des PC originels 8086 créés en 1981.
2. Le mode protégé : ce mode supporte les fonctionnalités que les systèmes d'exploitation
modernes offrent, comme la mémoire virtuelle et la protection mémoire (Windows et
Linux).
Dans ce cours, nous avons choisi le mode protégé.
Les TP se feront dans l’environnement Linux (fédora). Les programmes en ASM ont une
extension .S et sont compilés avec la commande gcc –o sortie prog.S. et puis exécuté an
tapant sortie à l’invite de commande.
Comme toute programmation, la programmation en ASM nécessite une méthodologie :
• Définir une méthode de résolution en décrivant l'algorithme de résolution du
problème ;
• Définir les fonctions et les procédures
• Choisir les paramètres en entrée et en sortie de chaque fonction ou procédure
• Choisir les variables globales, et les variables locales
• Écrire les corps des fonctions et procédures
Dans ce cours, on supposera que ces étapes de résolution d'un problème sont réalisées, puis on
ne s'intéressera qu'à la traduction du l'algorithme final en un programme ASM.

Explication des instructions:exemples

Les bases du langage Assembleur

Structure générale d’un programme ASM


Un programme ASM a la structure suivante :

.intel_syntax noprefix # Directive indiquant le type du processeur


.data #Indication du début de la zone de déclaration des variables
// Liste des variables

// définitions des procédures.

.global main #Indication du programme principal


main: #Etiquette du début du programme principal
//Liste des instructions du programme principal

push 0
call exit # Sortie du programme principal (arrêt)

// Définitions éventuelles des procédures

Les variables
En ASM, les variables sont déclarées dans la zone indiquée par la directives .data. La
syntaxe de déclaration est :
variable .type : valeur initiale
Le nom d’une variable commence toujours par une lettre.
Les différents types utilisés sont :
 byte : entiers sur un octet (8 bits)
 word : entiers sur un mot (16 bits)
 int : entiers sur un double-mot (32 bits)
 ascii : chaînes de caractère
 asciz : chaînes de caractère terminées par l’octet 0
 space <rep>,<val> : répète <rep> fois la valeur <val> codée sur 1 octet
 fill <rep>,<taille>,<val> : répète <rep> fois la valeur <val> codée sur <taille> octets

Pour utiliser la variable n, il faut écrire tout simplement n. Mais pour utiliser l’adresse de
n, on peut écrire offset n ou [n].

Les commentaires
Un commentaire commence par le caractère ‘#’ suivi du texte.

Les étiquettes
Les étiquettes sont les noms que l’on donne à une instruction permettant de faire un saut à
cette instruction à partir de n’importe quel endroit du programme. Dans un programme, une
étiquette est utilisée pour nommer une seule instruction. La syntaxe de déclaration est :
nom_étiquette :

Les registres
Ce sont des zones internes au processeur permettant de stocker de manière temporaire les
données. Le programmeur peut utiliser les registres EAX, EBX, ECX, EDX, EDI, ESI, ESP et
EBP dans un programme.
Les registres EAX, EBX, ECX et EDX peuvent être utilisés par le programmeur. Il peut y
affecter n’importe quelle donnée. La taille de ces registres est de 4 octets. La partie basse de
ces registres peut être utilisé pour les données de 16 bits. Pour le faire, il faut simplement
supprimer la lettre E. Le registre de 16 bits ainsi obtenu peut être aussi divisé en deux parties
et chaque partie utilisée pour les données de 8 bits. Par AX=AH+AL.
Les registres EDI et ESI peuvent aussi être utilisés pour stocker n’importe quelle donnée,
mais le programmeur n’a pas le droit de les diviser.
Les registres ESP et EBP sont utilisés pour manipuler la pile. ESP désigne toujours le sommet
de la pile. EBP est utilisé dans des sous programmes pour parcourir la pile.

La pile
C’est la partie de la mémoire de l’ordinateur, permettant de :
 sauvegarder temporairement des informations,
 passer des paramètres aux sous-programmes,
 créer des variables locales,
 sauvegarder le contexte lors de l’appel aux sous-programmes,
La pile est manipulée avec les instructions PUSH (empiler) et POP (dépiler). On la manipule
aussi avec les registres ESP et EBP.

Les instructions
 La plupart des instructions assembleur (aussi appelées mnémoniques) ont 0, 1 ou 2
arguments (opérandes).
 Ces opérandes sont soit un label (pour les instructions de saut uniquement), soit un
registre, soit un accès à la mémoire (suivant les différents modes d’adressage).
 En général pour une même instruction, il ne peut y avoir plus d’un accès à la mémoire.
 En général les opérandes doivent être de même taille (1, 2 ou 4 octets).

Les appels système


Ce sont les instructions qui permettent d’exécuter les opérations du système d’exploitation
comme les instructions de lecture, d’écriture, …

Un premier programme ASM : faire la somme a=b+c ;

.intel_syntax noprefix
.data
a : .int
b : .int 10
c : .int 5
.global main
main:
mov eax, b
add eax, c
mov a, eax

push 0
call exit

Les instructions composées


Instructions de condition (if, switch. . . )

Forme 1 (pas de sinon) : ‘si a<b alors inst finsi’ se traduit :


Cmp a,b
Jge finsi
inst
finsi :

Forme 2 (avec sinon) : ‘si a<b alors inst1 sinon inst2 finsi’ se traduit par
Cmp a,b
Jge sinon
Inst1
Jmp finsi
Sinon :
inst2
finsi

Forme 3 : connecteur ET


si (x <= z et y <= z)
inst 1
sinon
inst 2
finsi

cmp x,z;
jg sinon
cmp y,z
jg sinon
inst 1
jmp finsi
sinon:
inst 2
finsi:

Forme 4 : connecteur OU


si (x <= z ou y <= z)
inst 1
sinon
inst 2
finsi

cmp x,z;
jle alors
cmp y,z;
jg sinon
alors:
inst 1
jmp finsi;
sinon :
inst 2
finsi:

Forme 5 : les cas


cas (lettre) {
case ’a’: code=2; break;
case ’b’: code=5; break;
case ’c’: code=3; break;
default: code=0;}

cmp lettre,’a’;
je cas_a;
cmp lettre,’b’;
je cas_b;
cmp lettre,’c’;
je cas_c;
jmp default;
cas_a:
mov code,2;
jmp fin_cas;
cas_b:
mov code,5;
jmp fin_cas;
cas_c:
mov code,3;
jmp fin_cas;
default:
mov code,0;
fin_cas:
Instructions de boucle (for, while, . . . )

Forme 1 : boucle pour


Pour i allant de 1 à valeur_finale faire
Inst
Finpour

Mov ecx,1
Pour : cmp ecx, valeur_finale
jg finpour
inst
inc ecx
jmp pour
Finpour :

Forme 2 : boucle tant que


Tant que (i<n) faire
Inst
Fintantque

mov ecx, i
tantque:
cmp ecx,i
jge fintantque
inst
jmp tantque
fintantque:

Forme 3 : répéter … jusqu’à condition


Répéter
Inst
Jusqu’à (a>b)

Repeter :
Inst
Cmp a,b
Jle repeter
Jusqua :

Les procédures
Le code d’une procédure commence par une étiquette et s’achève par l’instruction ret. Ce
code peut être écrit avant ou après le programme principal main. L’appel de la procédure se
fait par l’instruction call procedure. L’instruction call empile la valeur du registre EIP
(compteur ordinal)

Passage des paramètres


Le passage de paramètre peut se faire de deux manières :
1. Utilisation des registres : les paramètres sont placés dans les registres avant
l’instruction call.
2. Utilisation de la pile : les paramètres sont empilés avant l’appel de la procédure. Après
la procédure, il faut les dépiler. Chaque empilement augmente le sommet de la pile
(ESP) de la taille du paramètre. Le dépilement doit tenir compte de ces tailles avec
l’instruction ADD ESP, nombre.

Variables locales
Contrairement au langage du haut niveau, pour créer les variables dans une procédure, on
utilise :
1. Les registres 
2. La pile : pour créer une variable, il faut simplement augmenter le registre ESP de la
taille de la variable. Pour une variable de type int, il faut faire ADD ESP, 4. Avec la
pile, il faut toujours supprimer ces variables à la fin de la procédure en diminuant ESP
de la taille (en octets) des variables créées. L’accès aux variables créées se fait avec le
registre EBP.
Le corps d’une procédure aura toujours la forme :
Procedure:
Push ebp
Mov ebp, esp

Corps de la procedure

Pop ebp
Ret.

Les macros
Les macros ne sont pas réellement des sous-programmes : ils permettent de simplifier
l’écriture d’un programme et disparaissent après la compilation. Le contenu d’une macro est
recopié à l’endroit ou on l’appelle. La macro peut être placée n’importe où dans le code, mais
doit précéder l’endroit où on l’appelle. Le code de la macro disparaît après la compilation
Une macro possède un nom, et accepte des arguments. Forme générale d’une macro :
.macro nom_macro arg1,arg2,...,argn
.........
Instructions
.........
.endm

.macro add3 val1,val2,val3


mov eax,\val1
add eax,\val2
add eax,\val3
.endm
……………………………………..
add3 10,15,30
add3 eax,notes[10],45

Les instructions systèmes


Moyen de communiquer avec le système d’exploitation. L’instruction int sert à déclencher
une interruption logicielle. Dans le cas logiciel, les interruptions sont des sous-programmes
définis par le système d’exploitation.
Sous Linux, l’interruption système possède le numéro 0x80 : ce sera l’argument de
l’instruction int.
Le numéro (0 à 255) de la fonction que l’on souhaite exécuter est placé dans le registre eax.
Les paramètres éventuels dans d’autres registres : ebx, ecx, . . .
La fonction Exit
Cette fonction permet de terminer proprement l’exécution d’un programme.
Paramètre :
eax = 1
ebx = code de sortie
Retour : aucun
main:
.........
mov eax,1 # fonction EXIT
int 0x80 # appel interruption
La fonction Read()
Cette fonction lit un certain nombre de caractères dans un fichier et les place dans un buffer
en mémoire.
Paramètres :
eax = 3
ebx = descripteur de fichier
ecx = adresse du buffer
edx = nombre maximum de caractères à lire
Retour :
eax = nombre de caractères lus

Exemple : lire une chaîne de caractères au clavier (stdin) d’au plus 100 caractères.
.data
buffer: .space 100,0
nbcar: .int 0
main:
mov eax,3 # fonction READ
mov ebx,0 # 0=stdin
mov ecx,offset buffer
mov edx,100 #nb max de car.
int 0x80
mov nbcar,eax
La fonction write()
Cette fonction écrit un certain nombre de caractères depuis un buffer en mémoire vers un
fichier.
Paramètres :
eax = 4
ebx = descripteur de fichier
ecx = adresse du buffer
edx = nombre de caractères à écrire
Retour :
eax = nombre de caractères écrits

Exemple : écrire une chaîne de caractères à l’écran (stdout).


.data
chaine: .ascii "exemple"
main:
mov eax,4 # fonction WRITE
mov ebx,1 # 1=stdout
mov ecx,offset chaine
mov edx,7 # taille chaine
int 0x80
La fonction Open()
Cette fonction ouvre un fichier, le crée/écrase/. . . suivant certains paramètres.
Paramètres :
eax = 5
ebx = adresse du nom du fichier (en .asciz)
ecx = bits d’accès fichiers
edx = permissions du fichier
Retour :
eax = descripteur du fichier
Permissions du fichier (en octal) :
0400 : lecture par le propriétaire
0200 : écriture par le propriétaire
0100 : exécution par le propriétaire
040 : lecture par le groupe
020 : écriture par le groupe
010 : exécution par le groupe
04 : lecture par les autres
02 : écriture par les autres
01 : exécution par les autres
Quelques bits d’accès fichier (en octal) :
00 : lecture seule
01 : écriture seule
02 : lecture - écriture
0100 : création
01000 : écrase
02000 : rajoute à la fin

La fonction Close()
Cette fonction ferme l’accès à un fichier.
Paramètres :
eax = 6
ebx = descripteur de fichier
Retour : aucun
En cas d’erreur sur l’ouverture, la création, etc. d’un fichier : en sortie de l’appel à
l’interruption, le registre eax contient non pas le résultat attendu (nombre d’octets lus, etc.)
mais un code d’erreur négatif.

###
### print_int : affiche un entier
###
### arguments sur la pile :
### 1) l'entier à afficher
###
print_int:
push ebp # sauvegarder ebp
mov ebp,esp # sommet de pile dans ebp
pusha # sauvegarder tous les registres
push [ebp+8] # empiler l'argument 1 de print_int
push offset descripteur_int # empiler l'adresse de la chaîne descripteur
call printf # appel de la fonction C printf
add esp,8 # dépiler les deux arguments de printf
popa # récupérer les registres
pop ebp # recupérer ebp
ret # retour du sous-programme

###
### print_string : affiche une chaîne de caractères
###
### arguments sur la pile :
### 1) l'adresse de la chaîne à afficher
###

print_string:
push ebp # sauvegarder ebp
mov ebp,esp # sommet de pile dans ebp
pusha # sauvegarder tous les registres
push [ebp+8] # empiler l'argument 1 de print_string
call printf # appel de la fonction C printf
add esp,4 # dépiler l'argument de printf
popa # récupérer les registres
pop ebp # recupérer ebp
ret # retour du sous-programme

###
### print_string : affiche une chaîne de caractères
###
### arguments sur la pile :
###

print_endl:
push ebp
pusha # sauvegarder tous les registres
push '\n'
call putchar
add esp,4
popa # récupérer les registres
pop ebp
ret

Vous aimerez peut-être aussi