TROISIEME PARTIE

LE LANGAGE ASSEMBLEUR



    II. SAUTS INCONDITIONNELS, PROCEDURES ET MACROS
    1. Les sauts inconditionnels

Ce paragraphe répond à la question : « Mais comment fait-on un GOTO en assembleur ? ».

L’instruction équivalente est JMP qui signifie « jump ».

La syntaxe est

JMP MonLabel

La référence à l’étiquette MonLabel sera remplacée lors de la compilation par la distance (signée) en octets qui sépare l’instruction qui suit immédiatement le JMP de l’instruction adressée par MonLabel. En pratique, le JMP s’utilise exactement comme un GOTO en BASIC.

Cette instruction permet de faire un saut inconditionnel : le programme fera ce saut quoiqu’il arrive.

Exemple :

Le mot “short” ajouté après “jmp” indique au compilateur que le label “COUCOU” se trouve à une distance (signée) qui peut être stockée sur un seul octet. Le compilateur ne le sait pas encore lorsqu’il essaie de compiler l’instruction de saut car le label se trouve en deçà de cette instruction. C’est pourquoi il prévoit deux octets pour écrire le saut, au cas où l’adresse d’arrivée soit éloignée de plus de 128 octets. Lorsqu’il effectue une deuxième « passe », s’il s’aperçoit qu’un seul octet aurait suffi il remplit alors l’octet inutile avec l’instruction NOP (« No Operation ») qui ne fait rien du tout. Le programme marchera parfaitement (encore heureux !), mais cela gaspille un octet. Le programmeur a la possibilité d’aider le compilateur en ajoutant le mot “short”, ainsi un seul octet est prévu pour la distance de saut. Si le label est placé plus haut que l'instruction de saut, il est inutile de l'écrire.

Remarque : Ce mot peut également être utilisé avec les instructions de saut conditionnel que nous étudierons plus loin.

    2. Les procédures
    a) appels de procédures

Ceux qui ont déjà programmé en BASIC connaissent les instructions GOSUB et RETURN. Leurs équivalents en assembleur sont CALL et RET.

  • L’instruction CALL sert à appeler une procédure :

Syntaxe 

CALL MonLabel

Action : empile l’offset de l’instruction suivante puis fait un simple saut à l’adresse représentée par MonLabel.

  • RET (ou RETN) permet de retourner à l’instruction qui suit immédiatement le CALL :

Syntaxe :

RET

Action : dépile l’adresse de retour et la met dans IP. Le programme continue donc à l’adresse qui suit le CALL.

Remarque : Rappelez-vous qu’il est possible de terminer un programme COM avec l’instruction RET. Puisque le DOS empile un zéro de deux octets au chargement du programme, IP prendra la valeur 0000 lorsque le processeur exécutera cette instruction : il pointera donc vers le début du PSP. Or le PSP commence toujours par les deux octets suivants : CDh 20h (ce qui s’écrit INT 20h en assembleur). L’interruption 20h, qui permet de terminer un programme COM, sera donc appelée et le contrôle sera rendu au DOS.

Voici un exemple d’utilisation des procédures aussi simple que possible : ce programme COM appelle 12 fois une procédure qui écrit un message à l’écran et rend la main au DOS. Il n’est d’aucune utilité et n’est pas optimisé du tout.

Remarque : Les codes ASCII 10 et 13 représentent respectivement la fin de ligne et le retour chariot. Grâce à eux, on revient à la ligne chaque fois qu’on a écrit le message.

Ce programme fonctionne parfaitement. Toutefois, les conventions d’écriture veulent que les procédures soient écrites comme suit :

Le programme compilé est toujours le même, mais le code est plus lisible puisqu'on distingue les procédures des simples labels. Le mot “near” signifie que l’adresse de cette procédure est réduite à un offset. Il n’y a pas d’adresse de segment. La procédure ne pourra donc être appelée que de l’intérieur même du segment.

Le mot qui a le sens contraire de “near” est “far”. Les « call far » sont assez peu utilisés.

    b) le passage des paramètres

Il existe deux moyens de passer des paramètres à une procédure : les registres et la pile.

  • Le passage par registres :

Il consiste à transmettre les paramètres dans des registres convenus à l’avance, par exemple AX et BX. Les deux gros avantages de cette méthode sont sa simplicité et la vitesse d’exécution. L’inconvénient majeur est le nombre limité de registres, d’autant plus qu’au moment de l’appel, certains d’entre eux ne seront peut-être pas disponibles.

  • Le passage par la pile :

C’est la méthode qu’utilisent les langages de haut niveau tels que le C ou le PASCAL. Avant l’appel, on empile les paramètres un à un. La procédure appelée se chargera de les lire.

Pour cela on utilise le registre BP de la manière suivante : on transfère la valeur de SP dans BP à l’aide de l’instruction “MOV BP, SP”. L’adresse de retour (qui sera dépilée quand le processeur rencontrera l'instruction “RET”) se trouve à l’adresse SS:[BP], le dernier paramètre est adressé par SS:[BP + 2], l’avant-dernier par SS:[BP + 4], etc… On peut ainsi lire n’importe quel paramètre sans le dépiler.

Attention cependant ! A la fin de la procédure, il faudra tout de même rendre à SP la valeur qu’il avait avant l’empilage des paramètres. Pour cela, on fait suivre l’instruction RET du nombre de paramètres, multiplié par 2 (car chaque paramètre comporte deux octets).

Exemple :

Soyez toujours très vigilant avec les appels de procédures : pensez que l’adresse de retour sera dépilée lorsque le programme rencontrera l’instruction RET. Il serait donc suicidaire de laisser une donnée empilée avant d’appeler cette instruction.

    3. Les macros

Etant donné que certaines instructions se répètent constamment dans un programme, l’écriture de macrofonctions (ou macros) est un moyen pratique de rendre votre code source plus lisible.

Il est possible de choisir pour certaines suites d’instructions un nom qui les représente. Lorsque le compilateur (en fait, le préprocesseur) rencontrera ce nom dans votre code source, il le remplacera par les lignes de code qu’il désigne. Ces lignes forment une « macro ».

Les macros, à la différence des procédures, n’ont aucune signification pour la machine. Seul le compilateur comprend leur signification. Elles ne sont qu’un artifice mis à la disposition du programmeur pour clarifier son programme. Lorsque le compilateur rencontre le nom d’une macro dans votre code, il le remplace par le code de la macro. Tout se passe exactement comme si vous aviez tapé vous-même ce code à la place du nom de la macro.

Ainsi, si vous appelez quinze fois une macro dans votre programme, le compilateur écrira quinze fois le code de cette macro. C’est toute la différence avec les procédures qui ne sont écrites qu’une seule fois mais peuvent être appelées aussi souvent qu’on veut à l’aide d’un CALL.

Voici comment écrire une macro : l’exemple suivant sert à écrire un message à l’écran.

Le code précédent peut être écrit n’importe où dans votre programme, à condition qu’il se trouve avant tout appel de cette macro. Afin d’éviter les ennuis, il est fortement conseillé de réunir vos macros au début du code, avant toute autre ligne. De toute façon, il ne sera pas compilé à l’endroit où vous l’avez écrit mais aux endroits où se trouvent les appels de macros.

Le mot “text?” est un « paramètre ». Le point d’interrogation n’est pas requis ; nous l’avons mis pour indiquer qu’il s’agit d’un paramètre et non d’une variable. Une macro peut utiliser plusieurs paramètres séparés par des virgules.

Pour appeler cette macro, il vous suffit d’écrire la ligne

ecrit_texte “Coucou ! Ceci est un essai !”

Le compilateur se chargera alors de la remplacer par les instructions comprises entre la première et la dernière ligne de cet exemple, en prenant le soin de remplacer le mot “text?” par le message fourni en paramètre.

En assembleur, chaque label doit avoir un nom unique. Or, si une macro est appelée plusieurs fois, les mêmes noms seront utilisés. Il faut donc la plupart du temps inclure la directive LOCAL qui forcera le compilateur à changer le nom des labels à chaque appel de la macro. Attention : cette directive doit suivre immédiatement la déclaration de la macro.

Supposons à présent que l’on veuille écrire à l’écran le message « Je suis bien content » et revenir à la ligne à l’aide de notre macro ecrit_texte.

La syntaxe suivante :

ecrit_texte “Coucou ! Ceci est un essai !”, 10, 13

est incorrecte, car le compilateur croirait que l’on a écrit trois paramètres ! Il faut alors entourer notre unique paramètre par les signes ‘<’ et ‘>’  :

ecrit_texte <”Coucou ! Ceci est un essai !”, 10, 13>

    4. La directive EQU

La directive EQU a un rôle voisin de celui des macros. Elle permet de remplacer un simple mot par d’autres plus complexes. Son intérêt est qu’elle peut être invoquée en plein milieu d’une ligne.

Quelques exemples :

Longueur EQU (findebut)

Message EQU “Bonjour messieurs ! Comment allez-vous ?”, ‘$

Version EQU 2

Quitter EQU ret

Quitter2 EQU int 20h

Mettre_dans_AH EQU mov ah,

Interruption_21h EQU int 21h

Un programme qui contient de telles directives peut se terminer ainsi :

Mettre_dans_AH 4Ch

Interruption_21h

Ou ainsi (si c’est un COM) :

Quitter2

    5. L’inclusion de fichiers

Il est possible d’accéder à des procédures, des macros ou des définitions EQU qui se trouvent dans d’autres fichiers. Cela permet de se constituer des librairies de macros ou de procédures que l’on peut réutiliser d’un programme à l’autre.

Pour inclure le fichier TOTO.LIB, écrivez au début de votre code source :

La condition “if1” indique au compilateur que l’inclusion ne doit s’effectuer que lors de la première passe.
Le fichier TOTO.LIB est un fichier texte tout à fait banal qui contient des lignes de code en assembleur.



Sommaire

Suite