Plan du site  
français  English
pixel
pixel

Articles - Étudiants SUPINFO

Chapitre 09 - L'assembleur x86

PLAN DU CHAPITRE

Un CPU exécute des instructions qui sont la résultante des programmes écrits avec des langages de programmation. Ces instructions contenues dans la mémoire composent le langage d’assemblage (assembleur). Pour qu’un programme fonctionne, le CPU lit une part une les instructions. Donc pour les atteindre, les instructions sont rangées dans la mémoire via des adresses. Au chargement d’un programme, le CPU dispose de l’adresse d’origine et contient dans son Compteur Ordinal l’adresse de la future instruction à exécuter.

Les instructions disposent de toutes les données pour que combinées entre elles et exécutées sur le CPU l’algorithme original traduit en langage de programmation fonctionne. Les données des instructions se retrouveront soit dans les registres du CPU soit dans la mémoire centrale.

Il existe 2 familles d’architecture de CPU, les CISC (Complex Instruction Set Computer) et les RISC (Reduced Instruction Set Computer). Leurs différences résident dans la construction des instructions. Un ensemble d’instructions est appelé jeux d’instructions ou ISA (Instruction Set Architecture).

Le CISC dispose de jeux d’instructions pouvant effectuer plusieurs opérations, comme les opérations arithmétiques, les opérations logiques, le chargement et la récupération de données en mémoire. Ils sont présents dans les x86, les pentiums, d’Intel, les 68xx de Motorola.

Le RISC dispose d’instructions relativement petites, unitaires, codées sur les mêmes tailles de mot avec une exécution cadencée sur les mêmes cycles d’horloge. Les instructions RISC sont possibles grâce à ses nombreux registres, caches (et aux mémoires) ne limitant pas la taille des demandes. Les puces RISC sont conçues pour exécuter ces instructions très rapidement.

Pour rappel, un registre est un circuit ou composant de mémorisation interne au CPU et permet de stocker adresse et donnée utiles au bon fonctionnement d’un programme en cours d’exécution. Dans un CPU, les registres sont toujours en nombre limités et pour un meilleur contrôle chaque registre dispose d’un nom.

Ce chapitre va vous permettre d’appréhender la programmation en assembleur 8086. Il vous propose d’apprendre l’organisation interne d’un processeur 80x86 16bits (base de toute une lignée de micro-processeur INTEL), de comprendre l’utilité des différents registres de son CPU et de découvrir les bases du langage assembleur 8086 au travers de l’émulateur EMU 8086.

Toutes les parties de ce chapitre sont guidées par notre mode de programmation en 8086 car nous nous plaçons dans le cas où une machine actuelle est trop difficile d’approche sans connaitre certains fondamentaux. Même si cela parait un peu vieillissant, il est de bonne augure de concevoir les principes de base de façon simples. Cette étude permettra ensuite d’aborder dans d’autres cours des parties plus actuelles voire futuristes de nos architectures. Il ne faut pas se méprendre sur les architectures actuelles, elles sont toutes fondées sur des premiers principes, elles évoluent, elles se transforment mais les modèles eux restent toujours un fondement pour aller utiliser, appréhender et comprendre les nouveautés. Nous allons donc décrire étapes par étapes les principes de la programmation en assembleur 8086 tout en modélisant et étudiant la machine du même type dont elle dépend. L’émulateur 8086 sera notre outil de simulation lorsque nous en aurons besoin. L’émulateur sera bien sûr étudié pour mieux l’utiliser.

L’assembleur est un langage de programmation proche du langage d’un ordinateur n’utilisant pas directement une notation exclusivement faite avec des 0 et des 1. Il dispose de mnémoniques (de plus haut niveau) pour construire des programmes avec structures de données et structures de traitements. L’assembleur dépend fortement du type de processeur. Donc il n’y a pas de langage d’assembleur unique et chaque constructeur a son assembleur. Par conséquent, il est obligatoire de maitriser le fonctionnement d'un processeur pour pouvoir aborder cette partie.

L'assembleur contrôle directement le CPU, il a une maîtrise du système et peut proposer des programmes rapides. Bien que les langages de plus niveau comme le C, C++, C#, Python, … permettent de faire des programmes facilement, ils n'optimisent pas le code d'exécution. L’impact est d’avoir des programmes plus volumineux pour faire la même demande.

Ce chapitre comporte principalement 9 parties sur la programmation assembleur :

  1. les instructions de l’assembleur.

  2. les variables. 3. le fonctionnement du système vidéo.

  3. les instructions de programmation par ancres.

  4. les procédures.

  5. les ininterruptions.

  6. la construction de programmes.

  7. l’outil assembleur 8086.

  8. les adressages mémoire.

PRESENTATION DU MICROPROCESSEUR 80X86

Le microprocesseur 80x86 se présente sous la forme d'un boîtier DIP (Dual Inline Package). En électronique un DIP est un boitier contenant des circuits intégrés qui dispose de sorties pour se connecter à un environnement externe (de nouveau un circuit intégré). Il est à noter que les DIP sont lié à un PCB (Printed Circuit Board) pour faciliter la manipulation tout en limitant les détériorations. Ainsi le boitier peut s’insérer facilement sans risque. Par conséquent la PCB (ou carte électronique) est une plaque de soutènement de composants permettant les liens entre les composants. Ce processeur a un bus d'adresses et un bus de données multiplexés (certaines pattes transmettent à certains moments un bit d'adresse et à d'autres moments un bit de donnée). Il est organisé autour d’un bus interne de données de 16 bits, il comporte:

  • 16 broches pour transporter les données (AD0 ... AD15).

  • 20 broches pour véhiculer les adresses (A0 ... A19). Il peut adresser 220 (= 1048576) positions mémoire différentes contenant chacune 1 octet (8 bits) : la mémoire a au plus une capacité de 1 Mo.

  • de registres, 16 bits pour stocker des données.

  • de registres, 16 bits pour stocker des adresses (codées sur 20 bits).

  • de registres temporaires directement pour UAL.

  • un bloc pour former des adresses, 20 bits à partir de morceaux d’adresse codés sur 16 bits.

Figure 1.1. Représentations externe d’un 80x86

Représentations externe d’un 80x86

Un registre est un composant de mémoire interne au microprocesseur dont sa capacité est calibrée sur une taille des instructions. Ainsi nous pouvons avoir des tailles de registres de 8 bits, 16 bits, 32 bits, 64 bits, … selon le modèle et la structure du micro-processeur. Le rôle du registre est de stocker des adresses ou des résultats intermédiaires des calculs en cours. Les commandes de l'assembleur manipulent les registres. Pour les contrôler et les utiliser chaque registre dispose d’un nom.

Comme tout CPU le 8086 dispose d'un certain nombre de type de registres:

  • les registres généraux destinés au traitement des valeurs. Ils permettent d’effectuer des additions, des multiplications, des calculs logiques, nommés A (Accumulateur), B (Base), C (Compteur), D (Donnée).

  • les registres d'adressages (registres de segments, pointeurs et index) utilisés pour pointer, lire ou écrire un endroit en mémoire, nommés D (Donnée), S (Stack - Pile), C (Code), E (Extra). L’IP (Instruction Pointer) sera aussi nommé CO (Compteur Ordinal).

Figure 1.2. Représentations interne d’un 80x86

Représentations interne d’un 80x86

  • les registres des états (Flags - Indicateurs) pour indiquer l'état du CPU. Ils donnent une indication sur le résultat de la dernière instruction exécutée, par exemple ZF (Zero Flag) qui indique zéro, CF (Carry Flag) qui indique une retenue, NF (Negative Flag) ou SF (Sign Flag) qui indique si le résultat est négatif ou positif, OF (Overflow Flag) qui indique un dépassement de capacité de registre, PF (Parity Flag) qui indique si les 8 bits de poids fort sont constitué d’un nombre pair de 1 ….

Figure 1.3. Registres des états du 80x86

Registres des états du 80x86

Les flags du registre d’état de la figure ci-dessus contiendront alors les valeurs mis à 0 ou 1 par le processeur (à la suite de l'exécution d'une instruction) ou par le programmeur pour modifier le mode de fonctionnement du processeur.

La gestion des registres est faite avec des instructions processeurs. Ces instructions sont formées avec le code binaire des programmes à exécuter. Il est possible de programmer le processeur avec des langages de plus bas niveau et pour rendre plus facile la manipulation, la lecture et l’écriture de telles instructions binaires, le programmeur utilise des symboles (mots clés, ou mnémoniques) à la place de des codes binaires et fait appel à l’assembleur. Les symboles en assembleur sont souvent la compression d'un mot ou d'une expression réalisant une action. Par exemple MOV (MOVe), MUL (MULtiply), … . Les mnémoniques sont suivies de registres, de valeurs, … .

L’architecture du 80x86 est little-endian (le Motorola 68000 et le PowerPC sont par exemple big-endian). Par exemple pour l’entier F1AB16 en mémoire, il y a 2 possibilités :

  • le mode big-endian : D'abord l'octet F116, puis l'octet AB16.

  • le mode little-endian : D'abord l'octet AB16, puis l'octet F116.

Si nous manipulons des nombres codés sur 16 bits (1 mot mémoire), nous utilisons 4 registres généraux :

  • AX, l‘Accumulateur, utilisé pour stocker les résultats de certains calculs arithmétiques et logiques.

  • BX, la Base, utilisé comme registre de base pour des données dans le segment pointé par le registre de segment ES. Un segment est un ensemble d’octets consécutifs dont un octet désigne le couple (numéro du segment, déplacement dans le segment).

  • CX, le Compteur, utilisé pour les boucles.

  • DX, le registre de Données, contient l'adresse des ports d'entrée/sortie (pour les instructions IN et OUT) et sert également d'extension à AX pour manipuler les données sur 32 bits.

Si nous manipulons des nombres codés sur 8 bits (1 octet), nous utilisons les 4 registres généraux en mode 8 bits pour en produire 8 registres AH, AL, BH, BL, CH, CL, DH et DL avec comme convention, H est mis pour "High" et L pour "Low" :

  • AH contient l'octet de poids fort du registre AX.

  • BH contient l'octet de poids fort du registre BX.

  • … .

  • AL contient l'octet de poids faible du registre AX.

  • BL contient l'octet de poids faible du registre BX.

  • … .

Figure 1.4. Registres généraux en mode 16bits et 8 bits

Registres généraux en mode 16bits et 8 bits

Les 2 registres d'index, notés SI (Source Index) et DI (Destination Index), sont utilisés pour indexer les éléments d'un tableau. Dans les instructions de mouvements de chaînes d'octets, ils sont utilisés simultanément :

  • SI indexe les caractères de la chaîne émettrice.

  • DI indexe les caractères de la chaîne réceptrice.

Une instruction dans le CPU est composée de plusieurs segments. Un segment qui désigne le code du programme, un segment qui désigne les données, un segment qui désigne l’organisation des appels, ….. Une instruction contient donc tous ses segments, mais sous forme d’adresse pointant vers la mémoire. Pour accéder à sa mémoire centrale, le 80x86 dispose de registres de segment suivants:

Figure 1.5. Pointeur de zone pour un programme en assembleur 8086

Pointeur de zone pour un programme en assembleur 8086

  • CS (Code Segment) pointe sur la base du segment qui contient le code (les instructions que doit exécuter le processeur).

  • DS (Data Segment) pointe sur la base du segment contenant les données (variables, tableaux ...).

  • SS (Stack Segment) pointe sur la base du segment qui contient la pile gérée par les registres SP et BP.

  • ES (Extra Segment) pointe sur la base d'un segment supplémentaire qui est généralement utilisé pour compléter le segment de données.

Ces segments sont situés selon la place disponible dans la mémoire. Parfois, ces segments peuvent se chevaucher partiellement. Les adresses des segments sont données et gérées par le système d’exploitation lors du mécanisme de chargement du programme à exécuter. Comme les adresses sont ensuite contrôlées de façon automatique, normalement il n’y a pas de risque de confusions d’accès (des systèmes de gestion d’erreurs peuvent se mettre en place comme les blue Sreen). Par contre, les déplacements sont liés aux instructions contenues dans le programme en se base sur une adresse d’origine.

La pile est une zone de mémoire qui permet de conserver de manière temporaire des données (par exemple, l’état des registres lors d’un appel de procédure). Nous utilisons 2 registres pointeurs :

  • SP (Stack Pointer) pointe sur le sommet de la pile et se met à jour automatiquement par les instructions d'empilement et de dépilement.

  • BP (Base Pointer) pointe la base de la région de la pile contenant les données accessibles (variables locales, paramètres,...) à l'intérieur d'une procédure. Il doit être mis à jour par le programmeur.

La pile n'est pas gérée avec des registres, elle utilise la mémoire (RAM). Dans cette pile, les données sont naturellement stables et seuls les pointeurs SP et BP sont utilisés pour atteindre les données. Cette pile est de type LIFO et elle est manipulée par :

  • PUSH pour empiler une valeur de 16 bits (le pointeur SP est décrémenté automatiquement de 2).

  • POP pour dépiler une valeur de 16 bits (SP est incrémenté automatiquement de 2).

Le bus d’adresse de la mémoire réelle est de 20 bits. Or le 80x86 n’a pas de registre de 20 bits mais que de 16 bits. Un registre de 16 bits référence 64Ko de mémoire, soit 216 = 65 536. Pour répondre à cette problématique le 80x86 en mode réel combine un des neuf registres généraux avec l'un des quatre registres de segments pour former une adresse de 20 bits.

Le registre IP (Instruction pointer) est appelé pointeur d'instruction ou compteur ordinal. La valeur contenue dans ce registre aiguille instruction par instruction les déplacements le micro-processeur. Il propose toujours la prochaine adresse de l’instruction à exécuter par le micro-processeur. Le registre IP est constamment modifié après chaque fin instruction pour qu'il puisse pointer sur l'instruction suivante. Ce registre permet de pointer une case mémoire dans le segment de code afin que le 80x86 puisse charger la prochaine instruction à exécuter.

À chaque type d'accès en mémoire, il faut faire correspondre un registre de segment et parfois un registre général, par exemple :

  • CS et IP sont combinés pour accéder à la prochaine instruction à exécuter sous la forme CS:IP.

  • SS et SP sont combinés en SS:SP pour toutes les opérations concernant la pile.

II est possible d'utiliser un registre de segment autre que celui utilisé par défaut en le spécifiant explicitement. Mais les valeurs de CS, DS et SS sont produites par le système d’exploitation dès le lancement du programme. Ces valeurs de segments étant implicites, il sera possible de les utiliser juste en spécifiant les offset de déplacements.

Pour adresser 1 méga-octet, il faut 4 bits, en plus des 16 bits d'un registre général. Cette combinaison est obtenue en décalant le registre de segment de 4 bits et en ajoutant le résultat au contenu du registre général pour obtenir l'adresse sur 20 bits.

Le résultat est appelé EA (Effective Address). L'EA est placée sur le bus d'adresses afin de pouvoir accéder à l'emplacement mémoire correspondant. L’EA est constituée de deux parties, le registre de segment définissant une zone mémoire de 64Ko, et le registre général spécifiant un déplacement à partir de l'origine de ce segment de 64Ko (c'est-à-dire une adresse sur 16 bits à l'intérieur de ce segment).

L’EA est exprimée en donnant le registre segment et le registre général séparés par deux points par exemple: DS:SI. Le registre DS est le segment de 64Ko et SI contient le déplacement dans ce segment. Si nous avons par exemple 1A8B:0010, l'adresse du segment est 1A8B et le déplacement dans le segment est 0010 en hexadécimal. Pour obtenir l’EA il faut faire : 1A8B * 1016 + 10 = 1A8C0.

LES INSTRUCTIONS DE BASE EN ASSEMBLEUR 8086

Le codage des nombres

Les nombres sont codés sur 1 ou 2 octets selon la taille de la valeur est l’utilisation des registres (une extension est possible pour 4 octet, mais les opérations seront spécifiques). Il y a 4 types de codages des nombres aussi bien en base 2, 8, 10, 16, …. :

  1. entiers positifs (1110b, 26o, 10, FFh).

  2. entiers entier négatifs (-1110b, -26o, -10, -FFh).

  3. décimaux compactés (codage binaire).

  4. décimaux non compactés (nombre à virgule).

L’algorithme de codage des négatifs est le complément à 2. Les normes de stockage des décimaux sont standardisées par la IEEE745. Une valeur numérique commence toujours par un chiffre, c’est ainsi que si l’émulateur refuse d’exécuter une valeur hexadécimale, c’est qu’il ne comprend pas qu’il s’agit d’un chiffre. Par conséquent, il faudra ajouter en tête de la valeur 0x ou simplement 0 pour indiquer une constante.

Les instructions

Un microprocesseur exécute un jeu d'instructions relatif au programme créé par le concepteur. Une instruction réalise une action simple sur le microprocesseur, comme "récupérer une nombre en mémoire", "additionner deux nombres et placer le résultat en mémoire". L'assembleur aura pour rôle de convertir le fichier source contenant les instructions; en mnémoniques (indication sous forme de lettre de l’opération à effectuer), en actions sur les registres et mémoire. Le fichier exécutable produit contient les codes binaires de chacune des instructions, compréhensible uniquement par le microprocesseur associé. L'assembleur est donc qu’une traduction d’un fichier source éditer en langage de haut niveau vers un langage binaire (de bas niveau).

Le codage d’une instruction en langage de programmation est constitué d’un code-opérateur (CodeOp) ou mnémonique (codé sur 1 ou 2 octets) suivi d’opérandes (valeur immédiate, registre, adresse). Par exemple, pour additionner 6010 avec 1510, le code en assembleur sera le suivant:

      
MOV  AX, 60
ADD  AX, 15

Ces instructions se traduisent en :

1. placer la valeur immédiate 60 dans le registre AX (MOV Ax, 60).

2. additionner le contenu de AX avec 15 et replacer le résultat dans AX (ADD AX, 15).

Ces instructions sont codées en hexadécimal, car il y a une correspondance numérique entre les mnémoniques, registres et valeurs, nous obtenons :

B83C0016

050F0016

Et l’algorithme de codage avec l’hexadécimal produit en binaire :

1011100000111100000000002

0000010100001111000000002

En lisant les valeurs binaires, les seules valables pour le microprocesseur, l’assembleur en utilisant les noms des registres, les mnémoniques,… devient bien un langage de programmation, et même de haut niveau dû à sa lisibilité par rapport au binaire.

Pour des raisons de souplesse, d’explication et de manipulation une instruction sera constituée de l’opérateur (ADD, MOV,….) suivi des adressages (données participant à l’opération). C’est ainsi nous aurons besoin d’avoir pour les adressages de nommer et manipuler:

  • registre (direct par A, B, C, D, S).

  • registre ou variable (direct par A, B, C, D, S ou définit par DB ou DW).

  • variable (définit par DB ou DW).

  • constante (définit pas EQU).

  • label (nom donné comme ancre de programmation).

Opérations logiques

Le 8086 permet d’effectuer des opérations binaires classiques grâce au mnémoniques AND, OR, NOT et XOR. Ces opérateurs peuvent être utilisés avec :

  • AND/OR/XOR register ou variable, registre

  • AND/OR/XOR register, register ou variable

  • AND/OR/XOR register ou variable, constante

  • AND/OR/XOR register ou variable, nombre

  • NOT registre ou variable

Example 1.1. Des opérations avec opérateurs logiques binaires classiques

       
MOV AL, 01100001b 
AND AL, 11011111b --> Nous obtenons 01000001b
OR AL, 01110110b  --> Nous obtenons 01110111b
NOT AL            --> Nous obtenons 10001000b 
XOR AL, 01010101b --> Nous obtenons 11011101b
XOR BX, BX        --> Nous obtenons 00000000b


Le 8086 permet d’effectuer des opérations de décalage sur des nombres signés ou non signés. Les bits qui sont expulsés sont stockés dans le bit CF :

  • SHL (SHift Left) et SAL (Shift Arithmetic Left).

  • SHR (SHift Right) et SAR (Shift Arithmetic Right).

La différence entre les 2 types de décalage est que dans la seconde version le bit de signe n’est pas concerné par ce décalage. Cette opération est plus rapide à exécuter que l’opération de multiplication ou de division par 2. Le nombre de bits de décalage est indiqué dans la seconde opérande.

Example 1.2. Des opérations avec opérateurs logiques binaires de décalage

        
MOV AL, 11110000b 
SHL AL, 1 --> Nous obtenons 11100000b
SAL AL, 1 --> Nous obtenons 11000000b 
SAR AL, 1 --> Nous obtenons 10100000b 
SHR AL, 1 --> Nous obtenons 01010000b


Le 8086 permet de réaliser des opérations de rotation sur des nombres signés ou non signés. Les bits expulsés sont replacés dans les trous formés par ce décalage :

  • ROL (Rotate Left) et RCL (Rotate Throw Carry Left).

  • ROR (Rotate Right) et RCR (Rotate Throw Carry Right).

La différence entre les 2 types de décalage est que dans la seconde version le bit CF est utilisé en plus des bits à modifier (lors d’un mouvement, le trou est rempli avec une copie du bit CF et l’autre bit, qui est expulsé, est placé dans CF). Le nombre de bits de décalage est indiqué dans la seconde opérande.

Example 1.3. Des opérations avec opérateurs logiques binaires de rotation

        
MOV AL, 11110000b 
ROL AL, 1 --> Nous obtenons 11100001b ; nous supposons CF=0 
RCL AL, 1 --> Nous obtenons 11000010b et CF = 1
RCR AL, 1 --> Nous obtenons 11100001b et CF = 0
ROR AL, 1 --> Nous obtenons 11110000b


Opérations arithmétiques

Comme pour tous calculateurs et microprocesseurs les unités de traitements arithmétiques font les opérations +, -, * et /. Pour les utiliser et le décrire, nous passons toujours par des symboles. Par contre pour effectuer des calculs dits non de bases, il faudra faire des programmes et enchainer les calculs, décalages et rotations.

Les mnémoniques ADD (ADDition) et SUB (SUBtraction) permettent d’effectuer des additions et des soustractions. Ils sont construit par :

  • ADD registre, registre ou variable

  • ADD registre ou variable, registre

  • ADD registre ou variable, constante

  • SUB registre, registre ou variable

  • SUB registre ou variable, registre

  • SUB registre ou variable, constante

Example 1.4. Addition et soustraction en assembleur 8086

     
ADD AX, BX ; effectue l’opération AX <-- AX + BX
SUB AX, BX ; effectue l’opération AX <-- AX – BX
          


Si une addition ou une soustraction sont codées sur 16 bits, alors leurs résultats seront codés sur au plus 16 + 1 bits soit 16 bits de résultat + 1 bit de retenue (le bit CF). Les opérations ADD et SUB sont effectuées sans tenir compte de l’état initial du bit CF du registre d’état mais l’état de ce bit peut être modifié à l’issue de l’opération.

Le 8086 propose 2 instructions INC et DEC pour incrémenter ou de décrémenter :

  • INC registre ou variable

  • DEC registre ou variable

Par exemple :

  • INC AX incrémente la valeur stockée dans AX. INC AX donne le même résultat que ADD AX, 1 mais le premier mnémonique correspond à un codeop de 1 octet alors que le codeop correspondant au second mnémonique en occupe 3.

  • DEC BX décrémente la valeur stockée dans BX. DEC BX donne le même résultat que SUB BX, 1 mais le premier mnémonique correspond à un codeop de 1 octet alors que le codeop correspondant au second mnémonique en occupe 3.

Pour effectuer des additions sur 32 bits avec des registres limités à 16 bits, nous devons scinder les nombres en 2 parties :

  • 1 registre pour stocker les 16 bits de poids fort.

  • 1 registre pour stocker les 16 bits de poids faible. Nous effectuons d’abord l’addition entre les 16 bits de poids faible puis l’addition entre les 16 bits de poids fort en utilisant le bit CF pour propager la retenue entre les 2 blocs de 16 bits.

Figure 1.6. Propagation d’une retenue pour un mot de 32 bits en ASM

Propagation d’une retenue pour un mot de 32 bits en ASM

Pour demander au processeur d’effectuer des additions qui tiennent compte du bit CF, nous devons utiliser un nouveau mnémonique : ADC (ADdition with Carry).

Il n’existe pas de mnémonique SUBC mais un mnémonique appelé SBB (SuBtract with Borrow) qui effectue le même type d’opération mais en sens inverse.

Comme les opérations ADC et SBB modifient l’état du bit CF, il sert à effectuer des opérations sur 48 bits, 64 bits … en stockant les résultats intermédiaires en mémoire, l’opération se fera par bloc de 16 bits.

Le mnémonique MUL (MULtiply) permet les multiplications, mais son mode de fonctionnement est très différent par rapport à celui de l'addition et la soustraction.

  • MUL registre ou variable.

La multiplication MUL avec deux données de 8 bits donne comme résultat une valeur codée sur 16 bits, donc de façon directe les valeurs données ne peuvent pas dépasser les bits de poids faibles. Si les valeurs dépassent les 8 bits, il faut alors utiliser plusieurs registres. Pour multiplier un nombre N par un nombre M, nous devons d'abord copier le nombre N dans le registre AL (ou AX) puis invoquer l’instruction MUL M :

  • si l’opérande est un octet, nous calculons AL * opérande et le résultat est stocké dans AX.

  • si l’opérande est un mot, nous calculons AX * opérande et le résultat est stocké dans les registres DX et AX (DX contenant la partie de poids fort).

Le 8086 propose un autre mnémonique pour effectuer les multiplications entre 2 nombres signés. Le mnémonique IMUL est utilisé de la même manière que le mnémonique MUL à la différence que les opérandes sont considérés comme des nombres signés (codés en complément à 2) et que le résultat est un nombre signé.

Le mnémonique de la division euclidienne est DIV et son mode de fonctionnement est voisin de celui de MUL.

Pour diviser un nombre N par un nombre M, nous devons d'abord copier le nombre N dans le registre AX (ou AX et DX) puis invoquer l’instruction DIV M :

  • si l’opérande est un octet, nous calculons AX / opérande : le quotient est stocké dans AL et le reste dans AH.

  • dans le cas d’un mot, nous calculons (AX DX) / opérande : le quotient est stocké dans AX et le reste dans DX.

Le 8086 propose un autre mnémonique pour effectuer les divisions euclidiennes entre 2 nombres signés. Le mnémonique IDIV est utilisé de la même manière que le mnémonique DIV. Les différences sont que les opérandes sont considérées comme des nombres signés (codés en complément à 2) et que le résultat est un nombre signé.

Utilisations des contrôles

Le 8086 offre 3 instructions pour modifier directement l’état du bit CF :

  • STC (SeT Carry flag) permet de mettre CF à 1.

  • CLC (CLear Carry flag) permet de mettre CF à 0.

  • CMC (CoMplement Carry flag) permet d’inverser l’état du bit CF.

Le 8086 offre 4 instructions pour modifier directement l’état des bits DF (direction) et IF (interruptions externes) :

  • STD (SeT Direction flag) permet de mettre DF à 1.

  • CLD (CLear Direction flag) permet de mettre DF à 0.

  • STI (SeT Interrupt flag) permet de mettre IF à 1.

  • CLI (CLear Interrupt flag) permet de mettre IF à 0.

Les mnémoniques STI et CLI sont utiles pour implanter au niveau assembleur des procédures qui ne peuvent pas être interrompues par des événements externes.

Les mnémoniques STD et CLD sont utiles pour aiguiller le sens de manipulation des chaines d’octets. Si le flag DF est à 0, le traitement est de gauche à droite, si DF est à 1 le traitement va de droite à gauche.

LES VARIABLES EN ASSEMBLEUR 8086

Comme pour tous les langages, avant d’utiliser une variable, il faut initialement la déclarer. Les variables sont considérées comme des emplacements mémoire qui peuvent être manipulés par l’intermédiaire de leur adresse ou de leur nom.

Le nom d’une variable peut être une combinaison de chiffres, de lettres et du caractère « _ », mais il doit respecter les contraintes suivantes :

  1. ne jamais commencer par un chiffre.

  2. ne pas correspondre à un mot clé du langage Assembleur.

Lorsque l’association entre le nom et la variable est créée, ce nom peut être utilisé pour désigner l’emplacement mémoire contenant l’information. L’assembleur traduit le nom en une adresse lors du processus d’assemblage.

Simples variables et numériques

L'association entre le nom et l'adresse s'effectue en utilisant les instructions DB (Define Byte – 1 octet pour 8086) ou DW (Define Word – 2 octets pour 8086) selon que la variable contient des octets ou des mots.

La valeur associée à la variable peut être un nombre (hexadécimal, décimal ou binaire) ou le symbole ? (si la variable n’est pas initialisée), par exemple :

    
var1 DB 50h
var2 DB ?
var3 DW 1110h 

Il peut être intéressant de connaître l’adresse d’une variable pour utiliser cette dernière comme argument des interruptions logicielles. Celle-ci se décompose en 2 parties :

  1. l’adresse du segment, obtenue par l’opérateur SEG.

  2. le déplacement à l’intérieur du segment, accessible grâce à l’opérateur OFFSET ou l’instruction LEA (Load Effective Address). L’instruction LEA CX, var1 sera traduite en MOV CX, 00100h (avec par exemple 00100h étant la valeur du déplacement pour la variable var1).

Lors du processus d’assemblage, des transformations sont opérées au niveau du code :

  1. SEG nom remplacé par le nom du registre associé au segment contenant la donnée.

  2. OFFSET nom remplacé par la valeur du déplacement.

     
var1 DB 50h

MOV AX, SEG    var1
MOV BX, OFFSET var1

L’exemple ci-dessus sera interprété par, un placement de pointer suivi d’un déplacement (valeur dépendant du nombre d’instruction contenu dans le fichier) :

     
MOV AX, CS
MOV BX, 00100h

Une constante numérique est définie grâce à la directive EQU qui indique à l’assembleur que le nom de cette constante doit être remplacée par sa valeur au moment de la création de l’exécutable.

     
var1 EQU 50h

MOV AX,  var1

L’exemple ci-dessus, définit une constante nommée var1 dont sa valeur est 50h et sera interprété par :

     
MOV AX, 00050h

Il faut noter la différence entre une constante initialisée par EQU et une variable initialisée par DB ou DW. EQU construit une constante dont la valeur est immédiate (et normalement invariante) alors que les autres déclarations construisent des variables dont les valeurs se situent aux adresses de stockage des valeurs. C’est donc pour cela que DB et DS pourront ne pas avoir de valeurs initialement données et pourront récupérer des valeurs au cours de l’exécution du programme.

Tableaux

Un tableau peut être vu comme une liste de variables qui sont associées à un même nom. La création d’un tel tableau s’effectue alors de la manière suivante :

     
tab DB 01h, 02h, 03h

Il est alors possible d’accéder à une case précise du tableau en utilisant un opérateur d’indexation : nous écrivons le nom du tableau suivi du numéro de la case placé entre crochet (les cases étant numérotées de 0 à N-1 pour un tableau comportant N éléments) :

     
MOV AX, tab[1]

Si un tableau est construit en répétant une ou plusieurs valeurs, il peut être utile d’utiliser l’opérateur DUP, par exemple :

      
tab DB 5 DUP (1,2)

L’opérateur DUP nécessite 2 informations :

  • le nombre de répétition (un entier placé après DB).

  • la liste des variables à répéter, que nous plaçons entre parenthèses.

Chaines de caractères

Une chaîne de caractères peut être vue comme un tableau contenant une suite de code ASCII séparés par une virgule (correspondant chacun à une lettre). Dans tous les cas, et pour faciliter les parcours et les recherches, les chaines de caractères seront terminées par le symbole $ (pour marquer la fin de la chaîne de caractères).

     
var4 DB 72, 69, 76, 76, 111, 36

Cette écriture est fastidieuse car il faut connaître le code ASCII de chaque caractère. Nous pouvons opter pour une séquence dissociée de caractères notés chacun entre symboles ' ' et séparés par une virgule.

      
var5 DB 'H' , 'E' , 'L' , 'L' , 'O' , '$' 

Ou pour une séquence concaténée de caractères avec un symbole ' en début et en fin.

      
var6 DB 'HELLO$' 

LE SYSTEME VIDEO DU 8086

Le moniteur

La présentation reste adapter à notre mode de programmation assembleur. Nous allons rester dans des modes simples, qui permettent de mieux appréhender l’apprentissage voulu ici. Pour décrire le balayage et la formation d’une image couleur, le moniteur reçoit de la part du contrôleur d'écran des signaux analogiques correspondant à l'image à afficher :

  • certains fils transportent les 3 informations à destination des canons électrons.

  • d'autres transportent des signaux de synchronisation permettant de piloter le dispositif électronique de balayage du moniteur.

En première définition, le moniteur couleur fonctionne de la manière suivante :

  1. le moniteur reçoit un premier signal de synchronisation qui ordonne au moniteur de baisser très fortement l'intensité des canons (de manière à faire disparaître le spot) puis à modifier l'alimentation des bobines de déviation de manière à placer le spot dans le coin supérieur gauche de l'écran (côté spectateur).

  2. le moniteur reçoit un deuxième signal de synchronisation qui lui indique de prendre en compte les signaux de couleur à destination des canons (il s’agit d’un spot).

  3. les fils transportant les signaux de couleur et le signal de balayage vertical sont envoyés de manière synchrone vers le moniteur. Le spot se déplace de la gauche vers la droite et les luminophores de la ligne sont bombardés chacun leur tour avec une intensité dépendant de la couleur à produire.

  4. le moniteur reçoit un signal lui indiquant d'éteindre le spot et de le ramener très rapidement vers la gauche de l'écran, un cran plus bas pour former la ligne suivante.

Les étapes 3 et 4 sont répétées jusqu'à former l'image complète. Lorsque l'image est totalement formée, nous retournons à la première étape.

Le contrôleur d’écran

Le contrôleur d'écran est souvent désigné sous le terme de carte vidéo ou encore de CRTC (Cathode Ray Tube Controler). Il existe de très nombreuses cartes vidéo capables de gérer les dessins en 3D, les textures ... Cependant, comme nous nous intéressons au processeur 80x86, nous allons plutôt décrire une carte plus ancienne, la carte CGA (Color Graphic Adaptator), dont le fonctionnement sera plus aisé à comprendre. Construite autour d'un circuit appelée le CRTC 6845 de Motorola. Cette carte peut fonctionner selon différents modes texte:

  • 40 x 25 caractères en monochrome.

  • 40 x 25 caractères en 16 couleurs.

  • 80 x 25 caractères en monochrome.

  • 80 x 25 caractères en 16 couleurs. Cette carte peut fonctionner selon différents modes graphiques:

  • 320 x 200 pixels en monochrome.

  • 320 x 200 pixels en 4 couleurs.

  • 640 x 200 pixels en 2 couleurs.

La carte CGA dispose d'une palette de 16 couleurs d’où sont tirées 2 sous-palettes utilisées pour le mode 320x200 en 4 couleurs:

  • sous-palette n°1 : "couleur de fond choisie", Turquoise, Violet, Blanc.

  • sous-palette n°2 : "couleur de fond choisie", Vert, Rouge, Jaune.

Table 1.1. Palette de 16 couleurs pour CGA

Décimal Hexadécimale Binaire Nom de la couleur
0 00h 0000 Noir
1 01h 0001 Bleu
2 02h 0010 Vert
3 03h 0011 Bleu de cobalt
4 04h 0100 Rouge
5 05h 0101 Violet
6 06h 0110 Marron
7 07h 0111 Gris clair
8 08h 1000 Gris sombre
9 09h 1001 Bleu clair
10 0Ah 1010 Vert clair
11 0Bh 1011 Bleu de cobalt clair
12 0Ch 1100 Rouge clair
13 0Dh 1101 Violet clair
14 0Eh 1110 Jaune
15 0Fh 1111 Blanc

La carte CGA dispose de registres internes du CRTC 6845 suffisants pour les anciennes cartes graphiques telles que la carte MDA (Monochrome Display Adaptator) qui équipait l'IBM PC de 1981. La carte CGA ayant des possibilités supplémentaires, d'autres registres sont additionnés, comme par exemple :

  • un registre de sélection de mode.

  • un registre de sélection de la couleur.

Le registre de sélection de mode est sur 1 octet. Il va permettre de distinguer plusieurs modes tels que :

Table 1.2. Registre de sélection de mode

Contrôleur de mode Numéro du bit dans l’octet
Affichage de caractères bit 0 02 = 40 * 25
12 = 80 * 25
Mode vidéo bit 1 02 = mode texte
12 = mode graphique
Signal de couleur bit 2 02 = sortir
12 = inhiber
Production d'un signal bit 3 02 = non
12 = oui
Mode graphique bit 4 12 = 640 * 200
Effet d'affichage bit 5 02 = couleur de fond clair
12 = clignotant

Le registre de sélection de couleur est sur 1 octet. Il va permettre de distinguer plusieurs couleurs tels que :

Table 1.3. Registre de sélection de couleur

Contrôleur de couleur Numéro du bit dans l’octet
Couleur de fond bit 3 mode texte 40 * 25
mode graphique 320 * 25
Couleur de fond intense bit 4 mode texte
Palette de couleur bit 5 mode graphique 320 * 200

La mémoire vidéo

Pour le système d'affichage, il existe la mémoire vidéo. Elle est accédée à la fois par le processeur (le plus souvent en écriture) et par le Motorola 6845 qui constitue le cœur de la carte vidéo CGA.

Cette zone de mémoire a une capacité de 16 Ko et commence à l'adresse B800:0000. Les 16 Ko correspondent à 16 * 1024 octets, soit 16384 emplacements qui sont organisés de différentes manières selon le mode vidéo choisi. Dans cette présentation, nous allons nous limitons au mode texte, plus facile à appréhender. En mode texte, un caractère est codé par 2 octets :

  • le premier octet correspond simplement au code ASCII du caractère à afficher.

  • le second octet permet de coder les attributs de ce caractère (la couleur de fond, la couleur de forme, et son clignotement).

Le mode texte en 80*25 nécessite 80*25*2 octets soit 4000 octets. Comme la mémoire vidéo a une capacité de 16 Ko, nous pouvons mettre en place 4 pages :

  • la première page commence généralement à l'adresse B800:0000.

  • la deuxième à l'adresse B800:1000.

  • la troisième en B800:2000.

  • la quatrième en B800:3000.

L'intérêt de ce système de pagination est de pouvoir construire une page (que le processeur accède en écriture) pendant qu'une autre page est affichée (le Motorola 6845 accède en lecture à une autre zone mémoire). Cela permet une plus grande fluidité au niveau de l'affichage. Lorsque la page est prête, il suffit de modifier les registres 0Ch 0Dh du Motorola 6845 pour qu'il affiche la bonne page. Pour déterminer la valeur du déplacement (offset) à l'intérieur d'une page, nous pouvons appliquer la formule : offset (colonne, ligne) = ligne * 160 + colonne * 2.

Après cette longue description, nous allons enfin afficher des caractères à l'écran. Pour commencer, nous allons d'abord afficher le message «Bonjour,monde!!!» en écrivant les lettres dans les bonnes cases de la mémoire vidéo grâce à l'instruction MOV.

Figure 1.7. Programme et émulation d’affichage en ASM

Programme et émulation d’affichage en ASM

Figure 1.8. : Programme et exécution de lettres de couleur en ASM

: Programme et exécution de lettres de couleur en ASM

Figure 1.9. Programme et exécution de lettres avec fond en couleur en ASM

Programme et exécution de lettres avec fond en couleur en ASM

Figure 1.10. Programme et exécution de lettres en couleur et fond en couleur en ASM

Programme et exécution de lettres en couleur et fond en couleur en ASM

Les différents affichages ont été faits avec une vision interne de l’architecture. Nous verrons dans la suite qu’il est possible de faire intervenir des fonctions prédéfinies (notion d’interruptions) pour faire des affichages. Pour cela il faut introduire les notions de navigation dans le code et ensuite nous construirons les interruptions.

LES INSTRUCTIONS DE SAUTS EN ASSEMBLEUR 8086

Les étiquettes

Les étiquettes (nommées aussi labels) permettent de spécifier au programme à quel endroit continuer une exécution. Il s’agit d’une ancre de programmation, comme un point d’entrée pour commencer des instructions. Pour accéder à une étiquette, il est possible d’utiliser un mnémonique de contrôle du flot d'instructions : JMP (pour jump). Nous parlons ainsi de programmation par Go To non conditionnelle. Le branchement inconditionnel consiste simplement à sauter d'une position dans le code à une autre position pour continuer l'exécution. Ce type de branchement est opposé aux branchements conditionnels qui réalisent des sauts en fonction des tests effectués sur les bits du registre d'état. Le saut inconditionnel est JMP suivi d'un nom de l'étiquette (ou d'un nombre représentant l'adresse de destination codée sur 4 octets mais ce n'est pas conseillé). Pour que les instructions s’exécutent, l’étiquette atteinte est convertie en une adresse au moment de l'assemblage des instructions de saut.

Pour ajouter une étiquette dans le programme assembleur, il suffit de déclarer un nom (qui ne commence pas par un chiffre) suivi des 2 points ( : ).

Par exemple :

            
etiquette1: MOV AX, BX 
            JMP etiquette1

La partie du programme ci-dessus provoque une boucle entre l’instruction JMP etiquette1 et l’ancre etiquette1. Lors de l’exécution de l’instruction le JMP décodée instruit le saut vers l’étiquette1. Pour cet exemple comme le programme reste une séquence vers JMP, il y a une boucle de créée.

Les conditionnelles

A l'instar de l'instruction JMP, les instructions de branchement conditionnel s'utilisent avec un label. Elles doivent être placées juste après l'instruction (CMP, DEC, INC ...) qui modifient l'état du registre d'état sinon les autres mnémoniques placées entre cette instruction et le branchement conditionnel pourraient altérer les bits du registre d'état. L'instruction CMP (pour compare) affecte les drapeaux du registre d'état (instructions de branchement conditionnel afin d'implanter des boucles, des tests ...). La comparaison de 2 nombres s'effectue en faisant une pseudo-soustraction qui affecte les drapeaux dans le registre d’état :

  • OF : Overflow Flag

  • SF : Sign Flag

  • ZF : Zero Flag

  • AF : Auxiliary Carry Flag

  • PF : Parity Flag

  • CF : Carry Flag

Compte tenu de la ressemblance avec l'instruction SUB, les types des opérandes traités sont :

  • CMP registre, mémoire

  • CMP mémoire, registre

  • CMP registre, registre

  • CMP mémoire, immédiat

  • CMP registre, immédiat

Les mnémoniques de sauts conditionnels sont :

Table 1.4. Instructions de branchement en assembleur

Instruction Description Condition Instruction opposée Condition
JA Jump if Above CF = 0 et ZF = 0 JNA CF = 1 ou ZF = 1
JAE Jump if Above or equal CF = 0 JNAE CF = 1
JB Jump if Below CF = 1 JNB CF = 0
JBE Jump if Below or Equal CF = 1 ou ZF = 1 JNBE CF = 0 et ZF = 0
JC Jump if Carry CF = 1 JNC CF = 0
JE Jump if Equal ZF = 1 JNE ZF = 0
JG Jump if Greater ZF = 0 et SF = OF JNG ZF = 1 ou SF != OF
JGE Jump if Greater or Equal SF = OF JNGE SF != OF
JL Jump if Less SF != OF JNL SF = OF
JLE Jump if Less or Equal SF != OF ou ZF = 1 JNLE SF = OF et ZF = 0
JO Jump if Overflow OF = 1 JNO OF = 0
JP Jump if Parity PF = 1 JNP PF = 0
JPE Jump if Parity Even PF = 1 JNPE PF = 0
JPO Jump if Parity Odd PF = 0 JNPO PF = 1
JS Jump if Sign SF = 0 JNS SF = 1
JZ Jump if Zero ZF = 1 JNZ ZF = 0

L'amplitude des sauts conditionnels est limitée à 127 octets vers l'avant et 128 octets vers l'arrière. Pour contourner ce problème, nous pouvons coupler ce saut conditionnel avec un saut inconditionnel, par exemple :

            
            CMP AX, BX
            JZ grandSaut
            JMP plusLoin

grandSaut:  JMP plusLoin2
            ….

plusLoin:   ….

plusLoin2:  ….

Il est à noter, lors de l’émulation des branchements par saut, l’émulateur modifiera votre programme si il y a un saut plus simple en termes de mise à jour des flags conforme à la demande du programme écrit.

Les boucles

La première manière d'implanter des boucles en utilisant les mnémoniques de sauts conditionnels (JNZ, JMP ...) : cela se rapproche du while et du do ... while.

La seconde méthode (qui se rapproche plutôt du for) consiste à utiliser le registre CX comme un compteur par l’intermédiaire d’une des mnémoniques suivantes :

Table 1.5. Instructions de boucle en assembleur

Instruction Description
LOOP Décrémente CX et va à l’étiquette si CX != 0
LOOPE Décrémente CX et va à l’étiquette si CX != 0 et ZF = 1
LOOPNE Décrémente CX et va à l’étiquette si CX != 0 et ZF = 0
LOOPNZ Décrémente CX et va à l’étiquette si CX != 0 et ZF = 0
LOOPZ Décrémente CX et va à l’étiquette si CX != 0 et ZF = 1
JCXZ va à l’étiquette si CX = 0

La construction d'une boucle s’effectue en respectant quelques contraintes :

  • charger la valeur de la boucle dans le registre CX.

  • faire précéder la première instruction de la boucle par une étiquette.

  • terminer la boucle par une instruction de type LOOP qui pointe sur l'étiquette.

L’exemple de code suivant propose une itération avec un loop dont les tours de décrémentation sont gérés par le registre CX :

              
        MOV CX, 5 

boucle: INC AX 
        ...
        LOOP boucle ; Si CX != 0, nous rebouclons
        ...

LES PROCEDURES EN ASSEMBLEUR 8086

Il peut être utile de placer dans des procédures distinctes des sections de code qui sont appelées plusieurs fois de manière à :

  • minimiser les recopies de code.

  • diminuer la taille du code.

  • augmenter la lisibilité du code.

La construction

A l’instar de sauts, la mise en place d’une procédure s’effectue en respectant quelques contraintes :

  • nous devons d’abord écrire son nom (une étiquette mais sans les 2 points) suivi de la directive PROC pour signaler à l’assembleur qu'il s'agit d'une procédure.

  • nous plaçons ensuite les instructions assembleur correspondant à l'implantation de la procédure (la dernière étant l’instruction RET).

  • nous terminons le code de la procédure par une ligne comportant le nom de la procédure suivi de la directive ENDP.

L'écriture d'une procédure peut se résumer par le squelette de code ci-dessous :

            
Name PROC

….

RET

Name ENDP

Les appels

Comme le montre le programme de la figure ci-dessous, lorsqu’une procédure est implantée, elle peut être appelée par l'instruction CALL suivie du nom de la procédure. Le mnémonique sauvegarde l’IP dans la pile, indiquant ainsi l'adresse de retour. La procédure est déroulée jusqu’à l’instruction RET qui restaure l’IP depuis la pile et qui entraîne l’exécution de l’instruction qui suit CALL.

Figure 1.11. Exemple d'un appel de fonction en assembleur

Exemple d'un appel de fonction en assembleur

Lorsque nous quittons une procédure appelante pour entrer dans une procédure appelée, nous devons sauvegarder le contexte d’exécution de la procédure appelante afin de pouvoir reprendre son exécution lors du retour de la procédure appelée. Ce contexte d’exécution de la procédure appelante est défini par le contenu de l’ensemble des registres qu’elle utilise :

  • les registres AX, BX …

  • le registre d’état.

Si nous ne conservons pas l’état de ces registres et si ces derniers sont modifiés par la procédure appelée, le fonctionnement de la procédure appelante est corrompu. Le moyen le plus couramment utilisé pour conserver ces données est la pile (le segment SS) que nous manipulons grâce aux instructions suivantes :

  • PUSH, PUSHA et PUSHF.

  • POP, POPA et POPF.

L’instruction PUSH permet de conserver une donnée à l’adresse SS:[SP] et de décrémenter SP de 2 (octets). Nous pouvons réaliser 3 types de PUSH :

  • avec un nombre : PUSH 10h

  • avec le contenu d’un registre : PUSH AX

  • avec le contenu d’une case mémoire : PUSH [BX].

L’instruction PUSHA permet d’empiler le contenu des registres AX, CX, DX, BX, SP, BP, SI et DI. Nous avons donc une équivalence entre l’instruction PUSHA et l’ensemble des instructions pris dans l’ordre PUSH AX, PUSH CX, PUSH DX, ….

L’instruction PUSHF permet de conserver le contenu du registre d’état à l’adresse SS:[SP]. Comme ce registre a une taille de 16 bits SP est décrémentée de 2 (octets) à l’issue de cette opération.

L’instruction POP permet de récupérer une donnée à l’adresse SS:[SP] et d’incrémenter SP de 2 (octets). Nous pouvons réaliser 2 types de POP :

  • pour stocker l’information récupérée dans un registre : POP AX

  • pour stocker l’information récupérée dans une case mémoire : POP[BX]

L’instruction POPA effectue le traitement inverse de PUSHA. POPA récupère les informations stockée dans la pile pour les placer dans les registres DI, SI, BP, SP, BX, DX, CX, et AX. Comme précédemment, nous avons une équivalence entre l’instruction POPA et l’ensemble des instructions pris dans l’ordre (ordre inversé de PUSHA). Il est à noter que POP SP est ignoré pour ne pas perturber le fonctionnement de la pile.

L’instruction POPF effectue le traitement inverse de PUSHF. POPF permet de copier le contenu stocké à l’adresse SS:[SP] vers le registre d’état. Comme ce registre a une taille de 16 bits SP est incrémentée de 2 (octets) à l’issue de cette opération.

LES INSTRUCTIONS D’INTERRUPTIONS EN ASSEMBLEUR 8086

Lors d’une utilisation normale d’un ordinateur, il y a en permanence l’exécution d’instructions. Toutes ces instructions ne dépendent pas d’un même programme. En effet, il y a plusieurs programmes qui se partagent le CPU, les entrées/sorties et donc pour exécuter les instructions, il y a un procédé cyclique d’interruption / sauvegarde / exécution / restauration. Il existe un ensemble d’interruptions :

Figure 1.12. Hiérarchie des interruptions

Hiérarchie des interruptions

Le principe de fonctionnement des interruptions est :

  • de stopper le programme principal.

  • de lire la table des vecteurs d’interruption pour connaître l’adresse de la procédure chargée de traiter l’interruption.

  • de sauvegarder le contexte d’exécution.

  • d’exécuter la procédure.

  • de recharger le contexte d’exécution afin de reprendre l’exécution du programme principal.

Le principe des interruptions matérielles

Le principe d’interruption matérielle est :

Figure 1.13. Principe d'interruption matérielle

Principe d'interruption matérielle

Les interruptions matérielles permettent au processeur de réagir aux actions des périphériques externes comme le clavier, la souris …

La prise en compte d’une interruption

Le processeur exécute un programme stocké dans le segment de code de la mémoire. Il doit être capable de réagir à des événements externes en interrompant le programme en cours pour exécuter une procédure de traitement de l’événement. Ce mécanisme nécessite l’adjonction d’un composant appelée contrôleur d’interruption sur lequel sont branchés les périphériques susceptibles de demander au processeur d’effectuer des traitements en réaction à leur fonctionnement (le clavier, la souris, le disque dur …).

Si nous rentrons dans les détails, le contrôleur d’interruption est composé de 2 circuits 8259A mis en cascade. Dont chaque fil, appelé IRQ (Interrupt ReQuest) est raccordé à un périphérique (ou un ensemble de périphériques).

Figure 1.14. Circuit des IRQ

Circuit des IRQ

Si nous prenons l’exemple du clavier, celui-ci est raccordé à l’IRQ 1. Lorsque le clavier sollicite l’attention du processeur, celui-ci envoie un signal sur cette patte. Le numéro de l’interruption est alors mis sur le bus de données pour être transmis vers le processeur. Lorsque le bus de données est stable, le contrôleur d’interruption envoie un signal IRQ Request vers le processeur pour lui demander de tenir compte du clavier. Le processeur indique par un INT ACKNOWLEDGE qu’il a pris en compte le signal d’interruption : le contrôleur d’interruption désactive alors ce signal pour pouvoir en reprendre un en compte ultérieurement.

Figure 1.15. IRQ clavier

IRQ clavier

La sauvegarde des contextes

Avant le traitement de l’IRQ, le processeur exécutait un programme, il existe un contexte d’exécution correspondant à l’état des différents registres au moment de cette interruption (en particulier le compteur ordinal qui pointe la prochaine instruction à exécuter). Le processeur lit d’abord une zone mémoire appelée la table des vecteurs d’interruption, afin de connaître l’adresse de la procédure de traitement en fonction du numéro de l’interruption. Le processeur exécute alors la première instruction de la procédure. Les premières instructions consistent généralement à sauvegarder le contexte dans la pile avant d’exécuter la procédure de traitement de l’interruption proprement dite. Lorsque le traitement proprement dit est terminé, nous rechargeons le contexte d’exécution depuis la pile (le compteur ordinal pointe sur l’instruction du programme principal dont l’exécution pourra reprendre) et nous exécutons l’instruction IRET (Interrupt RETurn). Ce procédé de sauvegarde est illustré par les schémas de la figure ci-dessous.

Figure 1.16. Sauvegarde d'un contexte

Sauvegarde d'un contexte

Certaines des interruptions matérielles (par exemple, le clavier) peuvent être masquées (le processeur peut ne pas en tenir compte) alors que d’autres (défaut de circuit RAM) ne peuvent pas l’être.

Ce mécanisme est utilisé lorsque le processeur doit effectuer un traitement critique. Cela concerne par exemple la commutation entre 2 processus dans un système d’exploitation multitâches (des informations critiques doivent être sauvegardées et le processeur ne doit en aucun cas être dérangé pendant cette phase).

Le principe des interruptions logicielles

Le principe d’interruption matérielle est :

Figure 1.17. Principe d'interruption logicielle

Principe d'interruption logicielle

L’écran

Ce principe appliqué au traitement de signaux électroniques externes a été étendu au traitement des signaux logiciels internes. Le programmeur a en effet la possibilité d’invoquer l’exécution de procédures toute faites (nous parlons d’interruption par abus de langage) en utilisant l’instruction INT suivi du numéro de l’interruption à exécuter, par exemple :

        
INT 10H

Le processeur interrompt alors le programme principal, sauvegarde le contexte d’exécution courante, et se branche sur la procédure de traitement selon le principe évoqué précédemment. Les interruptions logicielles sont en réalité des procédures toutes faites, mises à la disposition des programmeurs pour faciliter le contrôle des organes de l’ordinateur (disque dur, clavier …).

Nous distinguons :

  • les interruptions du BIOS (Basic Input Output System) qui sont implantées par le constructeur de la carte mère (les 32 premières interruptions).

  • les interruptions système (DOS, Windows, Linux …) qui sont chargées en mémoire lors du chargement du système d’exploitation (les 32 suivantes,). Nous allons en étudier 2 d’entre elles qui gèrent le clavier et l’écran.

L’écran peut être contrôlé par l’interruption n°10h (BIOS) ou l’interruption n°21h (DOS).

L’interruption 21h cache un ensemble de fonctions. Le numéro de la fonction utilisée et les paramètres de la fonction doivent être placés dans certains registres avant l’instruction INT 21h.

Si nous souhaitons afficher un caractère à l’écran, nous devons utiliser la fonction 06h. Pour cela, nous devons placer la valeur 06h dans le registre AH et le code ASCII du caractère à afficher dans le registre DL avant d’appeler l’interruption 21h, par exemple :

            
MOV AH, 6H
MOV DL, ‘A’
INT 21h

La routine de traitement lit d’abord le contenu de AH pour déterminer qu’il s’agisse de la fonction 06h puis elle lit le registre DL pour déterminer le caractère qui doit être affiché à la position courante du curseur sur l’écran.

Pour l’affichage des caractères, nous utilisons la fonction 09h. Pour cela, il faut effectuer les affectations ci-dessous avant d’appeler l’interruption 21h :

  • placer la valeur 09h dans le registre AH.

  • l’offset de cette même chaîne de caractères dans DX.

La chaîne de caractères est alors affichée à la position courante du curseur sur l’écran.

            
ORG 100h

MOV AH, 09h
MOV DX, OFFSET message

INT 21h
RET

message DB ‘Bonjour Monde $’

L’affichage d’une chaine de caractères peut de faire aussi avec une interruption. Pour cela, nous utilisons la fonction 40h. Il faut effectuer les affectations ci-dessous avant d’appeler l’interruption 21h :

  • placer la valeur 40h dans le registre AH.

  • placer la valeur 01h ou 02h dans le registre BX.

  • placer le nombre de caractères de la chaine dans CX.

  • l’offset de cette même chaîne de caractères dans DX.

La chaîne de caractères est alors affichée à la position courante du curseur sur l’écran.

            
ORG 100H

MOV AH, 40h
MOV BX, 02h
MOV CX, NBcaracteres
MOV DX, offset message
INT 21H
RET

message DB "Bonjour Monde!!"
Nbcaracteres DW 15

Il est toujours possible de se positionner ou nous voulons dans l’écran d’affichage, et pour cela il faut positionner le curseur. Nous utilisons la fonction 02h en effectuent les affectations ci-dessous avant d’appeler l’interruption 10h :

  • placer la valeur 02h dans le registre AH.

  • placer la valeur de ligne dans DH.

  • placer la valeur de la colonne dans DL.

La chaîne de caractères pourra s’afficher à la position définie par DH et DL.

           
ORG    100H

MOV    AH,02h
MOV    DH,05h
MOV    DL,20h
INT    10H
RET

Le clavier

Le clavier peut être contrôlé par l’interruption de la fonction 16h du BIOS ou par l’interruption de la fonction 21h du DOS. L’interruption 16h est également composée de plusieurs fonctions mais 2 nous intéressent plus particulièrement :

  • la fonction 00h qui permet de récupérer le code de la touche qui a été frappée au clavier.

  • la fonction 01h qui permet de vérifier si une touche a été frappée au clavier.

Pour lire la touche qui a été frappée sur le clavier, il faut utiliser la fonction 0h.

Pour cela, nous écrivons la valeur 0h dans AH puis nous exécutons l’instruction INT 16h.

Lors de son exécution, cette routine retire du buffer du clavier, le code de la touche qui a été tapé, pour le mettre à notre disposition :

  • le registre AL contient le code ASCII de la touche.

  • le registre AH contient le code clavier de la touche.

Pour tester si une touche a été frappée sur le clavier, il faut utiliser la fonction 1h.

Comme précédemment, nous écrivons la valeur 1h dans AH puis nous exécutons l’instruction INT 16h.

Si un caractère est présent dans le buffer, cette routine met le drapeau ZF à 0 (sinon il est à 1) puis elle copie depuis le buffer du clavier, le code de la touche qui a été tapé, pour le mettre à notre disposition :

  • le registre AL contient le code ASCII de la touche.

  • le registre AH contient le code clavier de la touche.

Si un programme contient une boucle qui teste de façon incessante si une touche a été frappée sur le clavier :

  • si une touche est effectivement frappée, nous exécutons la procédure correspondante

  • si ce n’est pas le cas, nous retournons au début de la boucle et nous recommençons le test.

Ce type de programme fait de l’attente active. Par conséquent, il teste de façon répétée si un événement est survenu pour déclencher une action au lieu d’être activée par l’événement lui-même. Il est généralement conseillé d’éviter ce type de programmation car elle conduit à gaspiller du temps CPU et par voie de conséquence à dégrader les performances d’un programme Assembleur.

Il est préférable d’adopter une technique de programmation événementielle où le programme réagit à des événements externes.

Pour réaliser ce type de programmation en assembleur, il est nécessaire de dérouter les interruptions.

Ce déroutement s’effectue simplement en modifiant l’adresse contenue dans la table des vecteurs d’interruption.

Si nous prenons l’exemple du clavier, celui-ci est d’abord géré par l’interruption 9h qui est appelée par le contrôleur d’interruption :

  • le traitement par défaut consiste à copier les codes concernant la touche qui a été frappée dans un buffer.

  • il est possible d’implanter notre propre procédure de traitement puis de modifier l’adresse de la procédure associée à l’interruption 9h de manière à ce que notre procédure soit appelée lorsqu’une touche est saisie.

Cette technique était utilisée dans les anciens jeux vidéo pour gagner en fluidité dans le contrôle du jeu.

Il est possible de faire du masquage d’interruption grâce à l’instruction CLI (CLear Interrupt) qui a pour effet de mettre à 0 le bit IF du registre d’état.

L’instruction STI (SeT Interruption) permet de rendre le processeur sensible aux interruptions masquables.

LES PROGRAMMES EN ASSEMBLEUR 8086

Pour construire un fichier en ASM, il faut adopter la rigueur de programmation habituelle, car il subira ensuite une transformation pour devenir un programme exécutable (Pour un programme assembleur, cette transformation est nommée assemblage). Une fois l’assemblage fait sans erreur, nous avons un fichier dit objet (écrit exclusivement en binaire sur des octets consécutifs en mémoire, découpé en segment de code). Si pour fonctionner, le programme a besoin d’autres instructions provenant d’un autre programme, il faudra lier tous les codes objets entre eux avec un éditeur de lien.

Les instructions (codage du code-opérateur comprenant le mnémonique suivi du ou des valeurs immédiates, registres, adresses, …) sont écrites les unes en dessous des autres permettant à l'assembleur de différencier les commandes à faire. Nous distinguons 2 types de format, les fichiers pour faire des programmes COM et des fichiers pour faire des programmes EXE.

Soit le programme en format COM suivant :

      
segmentprogramme segment use16

assume CS: segmentprogramme, DS: segmentprogramme, SS: segmentprogramme 

ORG    100H

programme:
         MOV    AH,02h
         MOV    DH,05h
         MOV    DL,20h

         INT    10H
 
         MOV    AH, 40h
         MOV    BX, 02h
         MOV    CX, NBcaracteres
         MOV    DX, offset message

         INT    21H

RET

message DB "Bonjour Monde!!"
NBcaracteres DW 15

segmentprogramme ends
end programme

Un fichier COM ne comporte qu’un segment nous le voyons sur la figure ci-dessus par l’instruction : segmentprogramme segment use16.

Cette instruction Indique au compilateur le début de l’adresse du segment nommé segmentprogramme. use16 indique que les adresses de segment et de l’offset sont codées sur 16 bits et non sur 8 bits. Le segment est pointé initialement par CS, DS et SS. Il faut rappeler dans le programme ces pointages et l’instruction assume permet de faire un espace de nom sur DS. L’espace de nom permet d’éviter d’écrire systématiquement les variables avec l’expression ds:. Ensuite pour construire le code objet, le compilateur cherche quel registre l’utilisateur utilise, il veut un registre de segment. Il récupère le segment pointé par assume. Les instructions segmentprogramme ends et end programme permettent de donner les indications nécessaire de fin pour construire l’ensemble des codes objets.

Pour un fichier de type EXE, il faut distinguer les partie des code objets produits. Il se base sur le comportement dynamique d’un programme et le construit comme un processus et sépare les données du programme et de la pile des appels.

           
segmentprogramme segment use16

assume CS: segmentprogramme, DS: donnees, SS: pile  

programme:
          MOV AH,02h
          MOV DH,05h
          MOV DL,20h
          
          INT 10H ; Le décalage est fait
          
          MOV AX, donnees
          MOV DS, AX ; DS n’accepte que de l’adressage par registre
          MOV AH, 09h  
          MOV DX, offset message
          
          INT 21H
          
          MOV AH,4Ch  ; Une obligation pour les fichiers EXE
          
          INT 21H   

RET
segmentprogramme ends

donnees segment use16
message DB 'Bonjour Monde!! $' ; C’est une chaine 
Donnees ends

pile segment stack ;Déclaration de la pile stack mot réservé.
taille db 256 DUP (?) ;Réservation de place 256 caractères.
pile ends
end programme

Un fichier EXE comporte une distinction entre le programme, ses données et la pile des appels.

Figure 1.18. Pointeur de zone pour un programme en assembleur 8086

Pointeur de zone pour un programme en assembleur 8086

Les segments de pile, de données, de code, … sont dans leur zone mémoire. Les registres CS DS SS et ES sont les pointeurs d’adresse des segments d'instructions. ASSUME indique à l'assembleur où les segments sont situés.

L’OUTIL EMU8086

Lorsque nous démarrons l’outil Emu 8086, l'application nous propose une interface.

Figure 1.19. Interface initiale de l’outil Emu 8086

Interface initiale de l’outil Emu 8086

Si nous appuyons sur le bouton "Code Examples", nous avons accès à une liste d'exemple. Le programme Hello World étant trop complexe à ce stade de l’apprentissage (il faut d'abord décrire le fonctionnement de la carte vidéo pour comprendre cet exemple), nous allons nous orienter vers un programme de calcul avec des "ADD/ SUB". Pour utiliser les opérations, nous devons au préalable charger dans les registres des valeurs, et nous le ferons avec l’instruction MOV et les registres de bases AX et BX.

Figure 1.20. Etapes pour obtenir une fenêtre d’édition

Etapes pour obtenir une fenêtre d’édition

Pour une création d’édition d’un programme, nous le faisons en 3 étapes, New, Empty workspace et valider.

Chaque instruction assembleur (mnémonique) possède une correspondance en langage machine, appelée code opérateur (CodeOp), qui peut être codé sur un ou plusieurs octets. Cette correspondance est spécifiée par le constructeur - Intel - dans son databook ce qui permet au programmeur de développer leur assembleur et/ou leur émulateur.

Les instructions opération registre, valeur sont dites à adressage immédiat car nous indiquons "en dur" la valeur qui doit être chargée dans le registre :

     
MOV AX,15h ; CodeOp = B815
 

Les instructions opération registre, registre sont dites de registres car les données, sur lesquelles les opérations sont effectuées, sont contenues dans les registres :

           
MOV BX, AX ; CodeOp = 8BD8

Dans ces 2 cas, il n'y a pas de construction d'adresses sur 20 bits puisque nous ne faisons pas intervenir la mémoire, ces instructions sont rapides à exécuter.

Figure 1.21. Programme dans la fenêtre d’édition

Programme dans la fenêtre d’édition

Pour générer et commencer à exécuter le programme de la figure ci-dessus, il faut cliquer sur EMULATE. L'assembleur attribue aux instructions traduite en langage machine des adresses relatives. Le processus d’émulation respecte les conditions architecturales car les adresses sont attribuées de façon séquentielle à partir du début du programme. C’est la raison pour laquelle une origine doit être définie; fait par l’instruction ORG 100h.

Un programme, pour être exécuté est chargé en mémoire par le système d’exploitation. Le DOS distingue 2 modèles de programmes :

  1. les fichiers exécutables COM (utilisant un seul segment dans la mémoire de taille 64 Ko).

  2. les fichiers exécutables EXE (limités que par la mémoire disponible dans l’ordinateur).

Le DOS charge le fichier COM et lui alloue toute la mémoire disponible. Mais si la mémoire est insuffisante, il annule le chargement en l’indiquant à l’utilisateur.

Dès que le chargement se fait, le DOS crée un PSP (Program Segment Prefix) du programme lui-même, au début du bloc de mémoire, représentant une taille 100h d’où l’obligation de placer org 100h dans les programmes pour ne pas corrompre le PSP.

Un PSP contient des informations comme le nom et le type d’extension du programme, des paramètres d’entrées, …

Figure 1.22. Fenêtres de contrôle lors d’une exécution d’un programme

Fenêtres de contrôle lors d’une exécution d’un programme

Pour désigner une instruction contenue dans une case de la mémoire de 1 Mo, nous avons besoin d'une adresse codée sur 20 bits (cela dépasse les capacités des registres internes du 80x86).

Pour contourner ce problème, l'adresse est codée avec 2 registres :

  • le registre CS (Code Segment) contient l'adresse de la base du segment de code.

  • le registre IP (Instruction Pointer) contient une adresse relative à cette base (nous parlons de déplacement ou d’offset).

  • l'adresse absolue (ou adresse effective) est obtenue en utilisant le couple d’un segment (numéro du segment, déplacement) soit : CS * 1610 + IP.

Pour accéder à un octet particulier dans un segment, il suffit de compter le décalage de cet octet par rapport au début du segment. L'adresse d'un octet (en hexadécimale) se note AAAA:BBBB où AAAA est l'adresse de segment et BBBB est l’offset. Par exemple, le 17ième octet de la RAM (le numéro 16) est situé à l'adresse 0000:0010. L’octet 0000:0100 est l'octet numéro 256.

Si l'adresse de l'octet est 12F3:0230, alors son adresse effective est : 12F316 * 1016 + 023016 = 1316016. Un octet n'a pas une adresse unique. Par exemple, l'octet numéro 8810 peut être adressé par :

  1. 0000:0058

  2. 0001:0048

  3. 0002:0038

  4. 0003:0028

  5. 0004:0018

  6. 0005:0008

Figure 1.23. Correspondance Case mémoire dans un programme ASM

Correspondance Case mémoire dans un programme ASM

Appuyons sur le bouton "Single Step" afin de lancer une exécution pas-à-pas du programme. Chaque clik effectué représente une progression dans les instructions.

La première instruction (MOV AX, 5) est chargée, décodée et exécutée par le processeur :

  • le registre AX contient la valeur 00 05h.

  • le registre IP contient la valeur 0103h (IP passe de 0100h à 0103h car MOV AX, 5 est codé sur 3 octets).

La prochaine instruction pointée par IP (MOV BX, 10) est surlignée dans les différents écrans.

Figure 1.24. Exécution d’une instruction MOV Ax, 5 dans un programme ASM

Exécution d’une instruction MOV Ax, 5 dans un programme ASM

Appuyons sur le bouton "Single Step" pour exécuter la deuxième instruction (MOV BX, 10) :

  • le registre BX contient la valeur 00 10h.

  • le registre IP contient la valeur 0106h (IP passe de 103h à 0106h car MOV BX, 10 est codé sur 3 octets).

Figure 1.25. Exécution d’une instruction MOV Bx, 10 dans un programme ASM

Exécution d’une instruction MOV Bx, 10 dans un programme ASM

Nous exécutons la troisième instruction (ADD AX, BX) :

  • la fenêtre ALU affiche le contenu des 2 registres temporaires d'entrées et le résultat de l'opération.

  • le registre AX contient la valeur 000Fh (0005h + 000Ah = 000Fh).

  • le registre IP contient la valeur 0108h (IP passe de 0106h à 0108h car ADD AX, BX est codé sur 2 octets).

Figure 1.26. Exécution d’une instruction ADD Ax, Bx dans un programme ASM

Exécution d’une instruction ADD Ax, Bx dans un programme ASM

Nous exécutons la quatrième instruction (SUB AX, 1) :

  • la fenêtre ALU affiche le contenu des 2 registres temporaires d'entrées et le résultat de l'opération.

  • le registre AX contient la valeur 000Fh (000Fh - 0001h = 000Eh).

  • le registre IP contient la valeur 0010Bh (IP passe de 0108h à 0010Bh car SUB AX, 1 est codé sur 3 octets).

Figure 1.27. Exécution d’une instruction SUB Ax, 1 dans un programme ASM

Exécution d’une instruction SUB Ax, 1 dans un programme ASM

Nous exécutons la cinquième instruction (RET). Cette instruction est équivalente à effectuer POP IP :

  • nous dépilons la valeur stockée dans la pile à la position SS:SP (adresse : SS * 1610 + SP) pour la charger dans le registre IP. SS:SP (= 0B56h:FFFEh) et le mot (2 octets consécutifs) stocké à cette adresse valent 0000h.

  • puis IP = 0000h, la prochaine instruction exécutée est à l'adresse CS:IP = 0B56h:0000h.

  • nous modifions la valeur de SP car nous avons dépilé un élément. SP = FFFEh + 0002h = 10000h soit 0000h car SP est un registre 16 bits.

Figure 1.28. Préparation d''une exécution d’une instruction RET dans un programme ASM

Préparation d''une exécution d’une instruction RET dans un programme ASM

Nous appuyons sur single step et nous exécutons une instruction non saisie dans notre programme (INT 020h) qui a été automatiquement ajoutée par le système d'exploitation (de même que la valeur qui était empilée et la valeur des registres SS et SP).

Figure 1.29. Exécution d’une instruction INT 20h dans un programme ASM

Exécution d’une instruction INT 20h dans un programme ASM

INT 020h donne l'ordre au processeur d'exécuter l'interruption (routine ou procédure) numéro 20h (l’ordre de INT 020h est de quitter le système d’exploitation). Le processeur modifie le contenu de ses registres car il saute dans une autre zone mémoire puis il exécute cette instruction qui consiste simplement à terminer le programme et à rendre la main au système d'exploitation (ici une émulation du DOS).

Figure 1.30. Exécution d’une instruction de fin pour un programme ASM

Exécution d’une instruction de fin pour un programme ASM

L’ADRESSAGE

Nous avons vu 2 modes d'adressages. Ces 2 modes ne font pas intervenir la mémoire centrale : Le mode immédiat où un opérande est un nombre codé sur 8 ou 16 bits directement dans le codeop :

MOV AX, 15 (CodeOp = B815)

Le mode par registre qui fait intervenir 2 registres :

MOV AX, BX (CodeOp = 8BD8)

Les instructions d'un programme sont pointées par le registre IP qui contient une adresse relative au segment de code (Code Segment ou CS) avec :

  • l'adresse de base est stockée dans CS.

  • l'adresse absolue de l'instruction est CS:IP = CS * 1610 + IP.

L’adressage des données dans DS peut s’effectuer de différentes manières (en utilisant différentes combinaisons) et prenons DEP est un nombre qui correspond à un décalage en mémoire, exprimé en octets :

DS : DEP, DS : BX, DS : SI, DS : DI, DS : BX+DEP, DS : SI+DEP, DS : DI+DEP, DS : BX+SI, DS : BX+DI, DS : BX+SI+DEP, DS : BX+DI+DEP

Nous pouvons adresser des données dans la pile (Stack Segment ou SS) ou dans le segment supplémentaire (Extra Segment ou ES). Nous avons les combinaisons supplémentaires suivantes et prenons DEP est un nombre qui correspond à un décalage en mémoire, exprimé en octets :

ES : DEP, ES : BX, ES : SI, ES : DI, ES : BX+DEP, ES : SI+DEP, ES : DI+DEP, ES : BX+SI, ES : BX+DI, ES : BX+SI+DEP, ES : BX+DI+DEP

D’un point de vue programmation, ces adressages des données peuvent être utilisés à l’aide de l’instruction MOV.

D’un point de vue syntaxique, nous distinguons ce type d’adressage de ceux évoqués précédemment grâce à l’utilisation des crochets [ ] qui entoure la seconde partie de l’adresse, par exemples :

  • MOV DS:[BX], 10 signifie que nous stockons 10 dans la case dont l’adresse est DS:BX (soit DS * 1610 + BX). le segment concerné est DS, il n’est pas nécessaire de l’indiquer car il est sous-entendu et donc MOV [BX], 4 est équivalent à MOV DS:[BX], 4

  • MOV AX, DS:[BX+DI+0x10h] signifie qu'il faut copier le contenu de la case mémoire d’adresse DS:BX+DI+0x10h dans le registre AX.

  • MOV ES:[BX+SI], DX permet de stocker le contenu du registre DX dans la case mémoire d’adresse ES:[BX+SI].

  • MOV CX, SS:[BP+0x02h] permet de copier, dans le registre CX, le contenu dans la case mémoire d’adresse SS:[BP+0x02h].

Nous avons utilisé les instructions ADD, SUB et ADC en respectant l’une des 2 syntaxes (qui correspondent respectivement au mode immédiat et au mode par registre) :

  • ADD/SUB/ADC registre, immédiat

  • ADD/SUB/ADC registre, registre

Ces instructions peuvent s’appliquer en mode d’adressage en mémoire :

  • ADD/SUB/ADC registre, mémoire

  • ADD/SUB/ADC mémoire, registre

  • ADD/SUB/ADC mémoire, immédiat

Les instructions MUL, IMUL, DIV et IDIV ne supportent pas le mode immédiat. Ces instructions s’utilisent uniquement en mode par registre et en mode en mémoire. Nous avons les syntaxes suivantes :

  • MUL/IMUL/DIV/IDIV registre

  • MUL/IMUL/DIV/IDIV mémoire

Pour spécifier si le pointeur manipule un mot ou un octet (utile pour opération de division et de multiplication). Nous devons ajoutons une information supplémentaire devant le pointeur :

  • si le pointeur manipule un mot, nous le faisons précéder de WORD PTR ou w. :

    • MOV WORD PTR ES:[BX+DI+0x10h], AX

  • dans le cas contraire, nous le faisons précéder de BYTE PTR ou b. :

    • DIV b.ES:[BX+DI+0x10h]

Il est important de noter qu’il n’existe pas d’instruction avec un MOV pour transférer le contenu d’un registre de segment vers un autre registre de segment. Donc, il est indispensable de passer par un registre de données pour transférer les registres de segment.

Adressage direct : l'opérande est une case mémoire (registre DS par défaut) :

  • MOV AL,[15h]

    • = MOV AL,DS:[15h] ; (CodeOP A01500)

    • = MOV AL,DS:15h ; (CodeOP 3E A01500)

  • MOV AX,ES:[15h] = MOV AX,ES:15h ; (CodeOp 26 A11500)

Adressage basé : l'opérande est une case mémoire dont l'adresse est donnée par BX (avec DS par défaut) ou BP (avec SS par défaut) :

  • MOV AH,[BX]

    • = MOV AH,DS[BX] ; (CodeOp 8A27)

    • = MOV AH,DS:[BX] ; (CodeOp 3E8A27)

  • MOV AL,[BP]

    • = MOV AL,SS[BP] ; (CodeOp 8A4600)

    • = MOV AL,SS:[BP] ; (CodeOp 368A4600)

Adressage indexé : l'opérande est une case mémoire dont l'adresse est donnée par SI ou DI (avec DS par défaut, sauf mnémonique spécifique) :

  • MOV AH,[SI]

    • = MOV AH,DS[SI] ; (CodeOp 8A24)

    • = MOV AH,DS:[SI] ; (CodeOp 3E8A24)

Adressage basé et indexé :

  • MOV AH,[BX+DI]

    • = MOV AH,DS[BX+DI] ; (CodeOp 8A21)

    • = MOV AH,DS:[BX+DI] ; (CodeOp 3E8A21)

  • MOV [BP+SI],AH

    • = MOV SS[BP+SI],AH ; (CodeOp 8822)

    • = MOV SS:[BP+SI],AH ; (CodeOp 368822)

Adressage basé avec déplacement :

  • MOV AH,[BX+15h]

    • = MOV AH,DS[BX+15h] ; (CodeOp 8A6715)

    • = MOV AH,DS:[BX+15h] ; (CodeOp 3E8A6715)

Adressage indexé avec déplacement :

  • MOV AH,[DI+15h]

    • = MOV AH,DS[DI+15h] ; (CodeOp 8A6515)

    • = MOV AH,DS:[DI+15h] ; (CodeOp 3E8A6515)

Adressage basé et indexé avec déplacement :

  • MOV AH,[BX+SI+15h]

    • = MOV AH,DS[BX+SI+15h] ; (CodeOp 8A6015)

    • = MOV AH,DS:[BX+SI+15h] ; (CodeOp 3E8A6015)

Le support de cours 1CPA Essentiel proposé dans la formation SUPINFO est maintenant terminé. Il a proposé une vision Hardware Hardware (UAL, registres, mémoires et bus basés sur une architecture pédagogique) et Hardware Software (Assembleur).

A propos de SUPINFO | Contacts & adresses | Enseigner à SUPINFO | Presse | Conditions d'utilisation & Copyright | Respect de la vie privée | Investir
Logo de la société Cisco, partenaire pédagogique de SUPINFO, la Grande École de l'informatique, du numérique et du management Logo de la société IBM, partenaire pédagogique de SUPINFO, la Grande École de l'informatique, du numérique et du management Logo de la société Sun-Oracle, partenaire pédagogique de SUPINFO, la Grande École de l'informatique, du numérique et du management Logo de la société Apple, partenaire pédagogique de SUPINFO, la Grande École de l'informatique, du numérique et du management Logo de la société Sybase, partenaire pédagogique de SUPINFO, la Grande École de l'informatique, du numérique et du management Logo de la société Novell, partenaire pédagogique de SUPINFO, la Grande École de l'informatique, du numérique et du management Logo de la société Intel, partenaire pédagogique de SUPINFO, la Grande École de l'informatique, du numérique et du management Logo de la société Accenture, partenaire pédagogique de SUPINFO, la Grande École de l'informatique, du numérique et du management Logo de la société SAP, partenaire pédagogique de SUPINFO, la Grande École de l'informatique, du numérique et du management Logo de la société Prometric, partenaire pédagogique de SUPINFO, la Grande École de l'informatique, du numérique et du management Logo du IT Academy Program par Microsoft, partenaire pédagogique de SUPINFO, la Grande École de l'informatique, du numérique et du management

SUPINFO International University
Ecole d'Informatique - IT School
École Supérieure d'Informatique de Paris, leader en France
La Grande Ecole de l'informatique, du numérique et du management
Fondée en 1965, reconnue par l'État. Titre Bac+5 certifié au niveau I.
SUPINFO International University is globally operated by EDUCINVEST Belgium - Avenue Louise, 534 - 1050 Brussels