Chapitre 3 (Programmation en Assembleur)
Chapitre 3 (Programmation en Assembleur)
Chapitre 3 (Programmation en Assembleur)
I ) Introduction :
Lorsque l'on doit lire ou écrire un programme en langage machine, il est difficile d'utiliser la
notation hexadécimale. On écrit les programmes à l'aide des instructions en mnémonique
comme MOV, ADD, etc. Les concepteurs de processeurs, comme Intel, fournissent
toujours une documentation avec les codes des instructions de leurs processeurs, et les
symboles correspondantes.
L'assembleur est un utilitaire qui n'est pas interactif, (contrairement à l'utilitaire comme debug
: voir plus loin dans le cours). Le programme que l'on désire traduire en langage machine (on
dit assembler) doit être placé dans un fichier texte (avec l'extension .ASM sous DOS).
Le fichier .OBJ n'est pas directement exécutable. En effet, il arrive fréquemment que l'on
construise un programme exécutable à partir de plusieurs fichiers sources. Il faut (relier)
les fichiers objets à l'aide d'un utilitaire nommé éditeur de lien (même si l'on a qu'un seul).
L'éditeur de liens fabrique un fichier exécutable, avec l'extension .EXE.
Donc en conclusion pour assembler un programme on doit passer par les phases suivantes :
- Editer les liens pour avoir un programme exécutable. Les trois phases sont schématisées
par la figure suivante :
Remarque 1 :
On ne peut passer du code source vers le code objet que si le programme source ne
présente aucune erreur.
La saisie se fait par des logiciels qui s'appellent éditeurs de texte, donc on peut utiliser
n'importe quel éditeur de textes (tel que EDLINE sous MSDOS de Microsoft) sauf les
éditeurs sous Windows car ces éditeurs ajoutent dans le fichier des informations
(la taille des caractères, la police utilisée, la couleur etc...) que l'assembleur ne peut pas
comprendre .Pour utiliser les éditeurs sous Windows il est conseiller d'enregistrer les
fichiers sous forme RTF.
Editeur de lien :
Plusieurs logiciels permettent le passage entre les trois phases présentée dans la figure
précédente on peut citer : MASM (Microsoft Assembler : avec LINK comme éditeur de
lien), TASM (Turbo assembler : avec TLINK comme éditeur de lien) et NASM etc ...
Remarque 2 :
On peut générer à partir d'un fichier objet d'autres formes de fichier pour des systèmes autres
que l'ordinateur (compatible IBM). Les formes les plus connues sont INTEL HEX, ASCII
HEX etc ...
Remarque 3 :
L'assembleur est utilisé pour être plus prés de la machine, pour savoir exactement les
instructions générées (pour contrôler ou optimiser une opération) On retrouve
l'assembleur dans :
- la programmation des systèmes de base des machines (le pilotage du clavier, de l'écran,
etc...),
Les données sont déclarées par des directives, mots clefs spéciaux que comprend
l'assembleur (donc ils sont destinés à l'assembleur. Les instructions (sont destinées au
microprocesseur)
Remarque :
- Le champ Label (étiquette) est destiné pour marquer une ligne qui sera la cible d'une
instruction de saut ou de branchement. Une label peut être formée par 31 caractère
alphanumérique ({A.. Z} {a.. z} {0.. 9} {.?@_$}) au maximum .Les noms des registres ainsi
que la représentation mnémonique des instructions et les directives (voir plus loin) ne peuvent
être utilisées comme Label. Le champ Label doit se terminer par ' : ' et ne peut
commencer par un chiffre. De même il n'y a pas de distinction entre minuscules et
majuscules.
Exemple :
Une directive est utilisée par exemple pour créer de l'espace mémoire pour des variables, pour
définir des constantes, etc...
Exemples :
VAL EQU 50 ; assigne la valeur 50 au nom VAL
ET1 EQU VAL* 5 + 1 ; assigne une expression calculer a VAL
Ces directives sont utilisées pour déclarer les variables : L'assembleur attribue à chaque
variable une adresse. Dans le programme, on repère les variables grâce à leurs noms. Les
noms des variables sont composés d'une suite de 31 caractères au maximum, commençant
obligatoirement par une lettre. Le nom peut comporter des majuscules, des minuscules, des
chiffres, plus les caractères @, et _. Lors de la déclaration d'une variable, on peut lui affecter
une valeur initiale.
a° ) DB (Define byte): définit une variable de 8 bits : c a d elle réserve un espace mémoire
d'un octet : donc les valeurs qu'on peut stocker pour cette directive sont comprises entre 0 et
255 ( pour les nombres non signés ) et de -128 jusqu'à 127 pour les nombres signés .
Exemple :
Vil DB 12H ; Définit une variable (un octet) de valeur Initiale 12.
Tab DB 18H, 15H, 13H ; définit un tableau de 3 cases
;(3 octet) Qui démarre à partir de l'adresse TAB.
Mess DB 'ISET' ; définit aussi un tableau mais les valeurs de chaque case
;n'est autre que le code ascii de chaque lettre.
Name DB ? ; définit une variable 8 bits de valeur initiale quelconque .
b° ) DW ( define word) : définit une variable de 16 bits : c a d elle réserve un espace mémoire
d'un mot : donc les valeurs qu'on peut stocker pour cette directive sont comprises entre 0 et
65535 ( pour les nombres non signés ) et de -32768 jusqu'à 32767 pour les nombres signés .
Exemple :
ff DD 15500000H
Lorsque l'on veut déclarer un tableau de n cases, toutes initialisées à la même valeur, on
utilise la directive dup:
Dans certains cas, l'adressage indirect est ambigu. Par exemple, si l'on
écrit :
MOV [BX], 0 ; range 0 a l'adresse spécifiée par BX
consécutifs. Afin de lever l'ambiguïté, on doit utiliser une directive spécifiant la taille de la
donnée à transférer :
La directive SEGMENT contrôle la relation entre la génération du code objet et la gestion des
segments logiques ainsi générés.
- Associer les symboles représentant des adresses à un segment en considérant leur valeur
comme un déplacement par rapport au début du segment.
- spécifier des directives pour l'editeur de lien (nom du segment, champs d'opérande de
l'instruction SEGMENT déterminant le traitement du segment par l'éditeur de liens); ces
informations sont passées telles quelles.
Nom ENDS
(donc, SEGMENT soit crée un nouveau segment, soit ouvrir un segment en vue d'y ajouter du
code supplémentaire).
Remarque :
Il ne faut pas oublier l'instruction ENDS avant une telle opération, elle permet de
(temporairement) clore l'ancien segment.
COMMON :
Tous les segments avec l'étiquette (classe) seront placés à la même adresse de base (dans un
bloc contigu) ; des zones du type ( COMMON ) avec différents noms (( classe)) seront placés
l'un derrière l'autre.
PUBLIC :
Tous les segments avec ce qualificatif seront regroupés dans un seul segment résultant,
l'un derrière l'autre.
STACK :
Un seul segment avec ce qualificatif est accepté, il est destiné à la gestion de la pile.
MEMORY :
Le premier segment portant ce qualificatif sera placé à une position de mémoire en dessus de
tout autre segment; s'il y a d'avantage de segments de ce genre, ils seront traités comme les
segments du type ( COMMON ).
AT :
Les étiquettes définies dans un tel segment sont définies comme étant relatives à la
valeur ((adr) ) 16) * 16.
Il est possible de contrôler la manière dont l'éditeur de liens détermine l'adresse ou sera placé
un segment: on choisit l'alignement du segment (c.a.d de son premier byte).
Les données sont déclarées par des directives, Les directives qui déclarent des données
sont regroupées dans les segments de données, qui sont délimités par les directives
SEGMENT et ENDS.
Les instructions sont placées dans un autre segment, le segment de code. La ligne :
Code SEGMENT
(iset) ou (microprocesseur). Ce sera le segment de notre programme. Cette ligne ne sera pas
compilée : elle ne sert qu'à indiquer au compilateur le début d'un segment.
La ligne :
PROG
Comme nous l'avons vu, les directives SEGMENT et ENDS permettent de définir les segments
de codes et de données. La directive ASSUME permet d'indiquer à l'assembleur quel est le
segment de données et celui de codes
(etc...), afin qu'il génère des adresses correctes. Enfin, le programme doit commencer, avant
toute référence au segment de données, par initialiser le registre segment DS (même chose
pour : ES et SS), de la façon suivante :
Remarque:
On n'est pas tenu de rendre aux registres la valeur qu'ils avaient au début de notre programme.
En effet, avant de charger un programme, le
DOS sauvegarde le contenu de tous les registres puis met le contenu des registres généraux
(ainsi que SI, DI et BP) à zéro. Il les restaurera quand il prend la main.
V ) Structure d'un programme en mémoire :
Lorsque l'utilisateur exécute un programme, celui-ci est d'abord chargé en mémoire par
le système. Le DOS distingue deux modèles de programmes exécutables : les fichiers
COM et les fichiers EXE.
La différence fondamentale est que les programmes COM ne peuvent pas utiliser plus d'un
segment dans la mémoire. Leur taille est ainsi limitée à
64 Ko. Les programmes EXE ne sont quant à eux limités que par la mémoire disponible dans
l'ordinateur.
Lorsqu'il charge un fichier COM, le DOS lui alloue toute la mémoire disponible. Si celle-ci
est insuffisante, il le signale à l'utilisateur par un message et annule toute la procédure
d'exécution. Dans le cas contraire, il crée le PSP du programme au début du bloc de mémoire
réservé, et copie le programme à charger à la suite.
Le PSP (« Program Segment Prefix ») est une zone de 256 (100H) octets qui contient des
informations diverses au sujet du programme. C'est dans le PSP que se trouve la ligne de
commande tapée par l'utilisateur. Par exemple, le PSP d'un programme appelé
MONPROG, exécuté avec la commande
Un programme COM ne peut comporter qu'un seul segment, bien que le DOS lui réserve la
totalité de la mémoire disponible. Ceci a deux conséquences. La première est que les
adresses de segment sont inutiles dans le programme : les offsets seuls permettent d'adresser
n'importe quel
octet du segment. La seconde est que le PSP fait partie de ce segment, ce qui limite à 64 Ko-
256 octets la taille maximale d'un fichier COM. Cela implique également que le programme
lui-même débute à l'offset 100h et non à l'offset
0h.
Bien qu'il soit possible de n'utiliser qu'un seul segment à tout faire, la plupart des programmes
EXE ont un segment réservé au code , un ou deux autres aux données, et un dernier à la pile.
Le PSP a lui aussi son propre segment. Le programme commence donc à l'offset 0h du
segment de code et non à l'offset 100h.
Afin que le programme puisse être chargé et exécuté correctement, il faut que le système
sache où commence et où s'arrête chacun de ces segments. A cet effet, les compilateurs créent
un en-tête (ou « header ») au début de chaque fichier EXE. Ce header ne sera pas copié en
mémoire. Son rôle est simplement d'indiquer au DOS (lors du chargement) la position
relative de chaque segment dans le fichier.
Intéressons-nous à présent aux valeurs que le DOS donne à ces registres lors du
chargement en mémoire d'un fichier exécutable ! Elles diffèrent selon que le fichier est un
programme COM ou EXE. Pour écrire un programme en assembleur, il est nécessaire de
connaître ce tableau par coeur :
Dans un fichier EXE, le header indique au DOS les adresses initiales de chaque segment
par rapport au début du programme
(puisque le compilateur n'a aucun moyen de connaître l'adresse à laquelle le programme sera
chargé). Lors du chargement, le DOS ajoutera à ces valeurs l'adresse d'implantation pour
obtenir ainsi les véritables adresses de segment.
Dans le cas d'un fichier COM, tout est plus simple. Le programme ne comporte qu'un seul
segment, donc il suffit tout bêtement au DOS de charger CS, DS, ES et SS avec l'adresse
d'implantation.
EXE ?
Première raison : pour que le programmeur puisse accéder au PSP ! Deuxième raison :
parce qu'un programme EXE peut comporter un nombre quelconque de segments de
données. C'est donc au programmeur d'initialiser ces registres, s'il veut accéder à ses
données.
c°/ Structure d'un fichier .com
Code segment
Assume cs : code, ds : code, es : code, ss : code
Org 100h ; le programme debut à partir de 100h
Debut :
.....
..... ; mettre les instructions
.....
.....
Code ends
End debut
Voir paragraphe IV /
Le mot clef PROC commence la définition d'une procédure, near indiquant qu'il s'agit d'une
procédure située dans le même segment d'instructions que le programme appelant.
- le champ opérande qui contient la donnée, ou la référence à une donnée en mémoire (son
adresse).
Ce mode d'adressage concerne tout transfert ou toute opération, entre deux registres de même
taille.
Exemple :
Mov AX, BX ; cela signifie que l'opérande stocker dans le registre BX sera transféré vers le
registre AX. Quand on utilise l'adressage registre, le microprocesseur effectue toutes les
opérations d'une façon interne. Donc dans ce mode il n'y a pas d'échange avec la mémoire, ce
qui augmente la vitesse de traitement de l'opérande.
Dans ce mode d'adressage l'opérande apparaît dans l'instruction elle- même, exemple :
MOV AX,500H ; cela signifie que la valeur 500H sera stockée immédiatement
dans le registre AX
Remarque :
Exemple dans notre cas MOV AX,-500H donne AX =1111101100000000B MOV BL,-
20H donne BL = 11100000B
MOV AX,adr
La valeur adr est une constante (un déplacement) qui doit être ajouté au contenu du registre
DS pour former l'adresse physique de 20 bits.
Remarque :
En général le déplacement est ajouté par défaut avec le registre segment DS pour
former l'adresse physique de 20 bits, mais il faut signaler
qu'on peut utiliser ce mode d'adressage avec d'autres registres segment tel
Dans ce mode d'adressage l'adresse de l'opérande est stockée dans un registre qu'il faut bien
évidemment le charger au préalable par la bonne adresse. L'adresse de l'opérande sera stockée
dans un registre de base (BX ou BP) ou un indexe (SI ou DI).
Exemple :
Remarque:
Dans ce mode d'adressage Le déplacement est déterminé par soi, le contenu de BX, soit le
contenu de BP, auquel est éventuellement ajouté un décalage sur 8 ou 16 bits signé. DS et SS
sont pris par défaut.
Exemple :
MOV AX,[BX]+2
Cela signifie que dans le registre AX on va mettre le contenu de la case mémoire pointe par
BX+2
Remarque :