Initiation à l'assembleur (2)

    Ecriture de programme sous MASM/TASM


    Dans cet article nous allons voir comment écrire des programmes assembleur sous MASM (Microsoft Assembler) et TASM (Borland Turbo Assembler). La structure d'un fichier est très ressemblante sous ces deux compilateurs.

    Tout d'abord le format de fichier à utiliser est le format .ASM. Le compilateur va créer un fichier objet (.OBJ) que l'on devra "lier" avec un "linker" (LINK ou TLINK) pour avoir enfin le fichier .EXE ou .COM. Nous ne verrons dans cet article que l'écriture des fichiers .COM qui s'avère beaucoup plus simple que les .EXE.

    Rappel sur les fichiers .COM: un fichier .COM est un fichier binaire, c'est à dire que le système va lire le fichier octet par octet et éxecuter chaque octet en langage machine, il n'y a donc pas d'en tête, à la différence des .EXE.

    Dans un programme assembleur, on distingue trois "zones" de mémoire: le code, les données et la pile. Un fichier COM doit stocker ces trois zones dans une taille maximale de 64 ko (cette limite est dûe à l'architecture spéciale des processeur Intel). Chacune des "zones" est en fait un segment, on distingue normalement un segment de code pointé par CS, un segment de données pointé par DS et un segment de pile pointé par SS; chaque segment faisant 64 ko. La limite physique d'un fichier COM étant de 64 ko, les trois segments seront confondus. Donc le seul registre de segment que l'on peut manipuler sans problème est ES. Pour les autres, on doit être sûr de la manipulation.

    Un programme COM, lors de son execution est chargé en mémoire conventionnelle par le DOS. Au début de son seul segment, on trouve le PSP (un bloc de mémoire qui contient des informations indispensables au DOS) qui a une taille de 256 octets. La première instruction assembleur du programme sera donc à l'offset 256 ou 100h en héxadécimal. Pour que le compilateur puisse compiler un fichier COM, il faut specifier que le programme commence donc en CS:0100h par la directive ORG (voir plus bas).

    Pour quitter un programme COM, il y a plusieurs façons, les anciennes méthodes sont l'appel à la fonction 00 de l'interruption 21h ou l'interruption 20h ou l'instruction RET. Dans des versions "plus récentes" (version 2.0 !!) du DOS, il est possible d'appeler al fonction 4Ch de l'interruption 21h qui permet de renvoyer au programme parent un code d'erreur (que l'on peut ensuite tester avec les ERRORLEVEL des fichiers BAT).

    Structure d'un programme sous MASM / TASM:

    Pour écrire un programme, il faut d'abord spécifier le jeu d'instructions utilisé (cette option n'est disponible que sous TASM) par .286, .386 ou .486 (ne pas oublier le "."); le jeu utilisé par défaut est le 8086.

    Il faut ensuite déclarer un segment de code (qui servira aussi de segment de donnée dans le cas d'un COM) de la façon suivante:

    Nom_segment SEGMENT [Options_segment]

    par exemple: "code segment" suffit à déclarer un segment. Les options de segment ne sont pas importantes pour le moment. votre programme. Cette déclaration délimite le début d'un bloc; pour terminer ce bloc on devra utiliser la direcctive suivante:

    Nom_segment ENDS

    On trouvera ensuite la directive ORG 100H pour spécifier que le programme commence à l'offset 100H du segment à cause du PSP (voir plus haut).

    On devra ensuite délimiter une sorte de bloc qui note le début du programme et la fin du fichier. Le début du programme devra être une étiquette. Tout à la fin du programme, on devra utiliser "END " suivi du nom de la première étiquette.

    .386              ;seulement disponible sous TASM
    code    segment
            org 100h
    start:
            ...        ;programme
    code    ends
            end start
    

    On remarque ici que des commentaires peuvent être ajoutés après chaque instruction par le préfixe ";".

    Compilation:

    La syntaxe de MASM est la suivante: MASM Nom_fichier; Nom_Fichier ne doit pas contenir le suffixe ".ASM". Si tout se passe bien, vous devrez avoir une erreur !! ou plutôt un avertissement (warning). Cet avertissement indique "no stack segment", ce qui veut dire que le programme ne possède pas de segment de pile. Cette faute est normale, car MASM pense que vous programmez un fichier .EXE, et qu'il faut donc un segment de pile. OUBLIEZ cette faute pour le moment.

    Vous pourrez constatez ensuite (s'il n'y a pas de fautes graves) un fichier .OBJ. Vous devez lier ce fichier .OBJ pour donner un fichier EXE. avec le programme LINK dont la syntaxe est: LINK Nom_fichier; Ici aussi, Nom_fichier ne contient pas le suffixe ".OBJ". Voilà vous avez un fichier EXE qui normalement tourne correctement. Seulement vous avez programmé comme s'il s'agissait d'un fichier COM.

    Vous devez donc convertir ce fichier EXE en COM avec le programme EXE2BIN livré dans d'anciennes versions du DOS. La première fois, le DOS vous avertira que la version du DOS n'est pas la bonne, vous devrez donc trifouiller cette version avec SETVER. Syntaxe de EXE2BIN: EXE2BIN Nom_fichier.EXE Nom_Fichier.COM

    La compilation avec TASM est beaucoup plus simple: TASM Nom_fichier toujours sans suffixe ; puis TLINK /t Nom_fichier sans suffixe. Rien de plus simple !

    Les procédures:

    Pour déclarer une procédure, vous devez spécifier un bloc commencé par Nom_procédure PROC NEAR | FAR et terminé par Nom_procédure ENDP. Dans un programme COM, seul les procédures NEAR sont autorisées (sauf cas exceptionnels). La programmation d'une procédure ne diffère pas.

    ...
    
    SetScreen proc near
            ; entrée: mode dans AX
            ; sortie: aucune
    
            push ax
            push bx
            mov bx,ax
            
    
            mov ah,0       ; fonction 0 interruption 10h: charger un mode d'écran.
            mov al,bl
            int 10h
    
            pop bx
            pop ax
    
            ret            ; ne pas oublier pour le retour
    SetScren endp
    
    ...
    

    Cette fonction active un mode d'écran, on transmet ce mode dans AX avant l'appel de la procédure. Dans le programme, on écrira par exemple:

    ...
    
            mov ax,13h      
            call SetScreen
    ...
    

    Les constantes:

    En assembleur, vous pouvez déclarer des variables constantes grâce à l'instruction EQU. Nom_variable EQU Valeur Ces valeurs dépendent du compilateur et sont donc calculés lors de la compilation et non lors de l'éxecution. On peut donc avoir des calculs comme par exemple: nombre equ 3+5+69

    Les constantes peuvent être testées à l'intérieur du programme pour permettre une compilation conditionnelle. (Attention, c'est une compilation et non une éxécution conditionnelle). Les tests se font par les pseudo-commandes IF - ENDIF. Une condition peut être égale (EQ), >= (GE), >(GT), <= (LE), <(LT), ou différente (NE).

    cpu     equ 0 ; 0 pour 386 - 1 pour les autres
    ;.... dans le segment de code ....
    
            IF cpu EQ 0
                    mov eax,ebx
            ELSE
                    mov ax,bx
            ENDIF
    ;..... exemple sans aucun effet .....
    

    Dans cet exemple, si on décide de compiler le programme pour processeurs inférieurs au 386, il suffit de mettre la constante cpu à une valeur différente de 0. Cela permet d'écrire du code facilement récompilage pour des changements de configuration ou de plateformes.

    TASM a des constanes par défauts,voici les plus importantes

    $               position courante
    @Cpu            Type de CPU
    @curseg         Segment sourant
    @code           Nom du segment de code
    @data           Nom du segment de données
    @CodeSize       Modèle de mémoire du segment de code (0=near, 1=far)
    @DataSize       Modèle de mémoire du segment de données (0=near, 1=far, 2=huge)
    @@Interface     Type de MODEL (C, TPASCAL, BASIC ...)
    ??date          Chaîne indiquant la date actuelle
    ??time          Chaîne indiquant l'heure actuelle
    ??version       Chaîne indiquant la version de TASM
    ??Filename      Chaîne indiquant le fichier compilé
    

    Les structures:

    De même qu'en C ou en Pascal, il est possible en assembleur de créer de nouveaux types de variables composés de plusieurs autres types standards. Ceci est possible grâce à l'instruction STRUC. STRUC est aussi un bloc considéré comme un segment (donc terminé par ENDS).

    Employee STRUC
            nom db 25 dup (?)
            age db ?
            fonction db 75 dup (?)
    ENDS
    ...
    ; on déclare la nouvelle variable de cette façon:
    
            francoise Employee ?
            pascal Employee ?
    
    ; il est aussi possible d'initialiser les différentes valeurs de cette façon:
            francoise Employee <'françoise dugenou',25,'secrétaire'>
    ...
    ; on accède aux éléments de cette façon:
    
            mov francoise.age,35
    ...
    

    On retrouve comme en C (struct) et en Pascal (record) le "." pour accèder aux élements des variables.

    Les macros:

    Il est possible en assembleur d'utiliser des macros; pour permettre de programmer plus rapidement. Si vous devez par exemple repetez quinze fois l'instruction out dx,al vous pouvez utiliser la commande REPT. C'est aussi un bloc considéré comme une macro (terminé par ENDM).

    REPT 15
            out dx,al
    ENDM
    

    Le compilateur va en fait faire comme si vous aviez taper 15 fois out dx,al.

    Si vous avez des blocs d'instructions qui se répètent assez fréquemment, vous pouvez utilisez la commande MACRO (bloc terminé par ENDM).

    MACRO pushtout
            push ax
            push bx
            push cx
            push dx
    ENDM
    MACRO poptout
            pop dx
            pop cx
            pop bx
            pop ax
    ENDM
    ...
    ; l'appel à la macro se fait de la façon suivante:
            pushtout
            mov ax, ....
            poptout
    

    A chaque appel de la macro, le compilateur insère en fait la macro dans votre programme. Ce n'est pas la même chose qu'une procédure. Vous ne gagnez pas de place dans votre programme.

    Une macro peut être déclarée à tout moment du moment que sa déclaration précède son appel.

    Attention: toute utilisation de symbols dans une macro (nom d'étiquette, variables, constantes) doit être fait en déclarant le symbol en local. Car au cas où vous comptez appeler plusieurs fois une macro (ce qui arrive souvent, sinon la macro perd son interêt), le compilateur inclura les étiquettes ou les noms de variables ou de constantes plusieurs fois, d'où des erreurs qui peuvent être longues à décéler.

    Exemple:

    ; si vous avez la macro suivante:
    macro1 macro
            xor ax,ax
            jmp ok
    ok:
            xor bx,bx
    endm
    
    ; lors de son appel:
    ...
            macro1
            macro1
    ...
    
    ; le compilateur va interpréter de la sorte:
    ...
    
    ; 1er appel
            xor ax,ax
            jmp ok          ; oui mais lequel ?
    ok:
            xor bx,bx
    
    ; 2ème appel
    
            xor ax,ax
            jmp ok          ; saut vers quel ok ? --> erreur
    ok:
            xor bx,bx
    

    Pour remédier à ce problème, il faut utiliser la commande LOCAL qui déclare une variable locale:

    macro1 macro
            local ok                   ; de cette manière ok peut être utilisé plusieurs fois
    
            xor ax,ax
            jmp ok
    ok:
            xor bx,bx
    endm

    Voilà pour ce mois-ci, l'initiation à l'assembleur continue le mois prochain ...


    Je pensais pouvoir écrire un article sur les modules assembleur importables en langages de plus hauts niveaux, mais j'ai eu quelques problèmes ... ce sera peut-être pour le mois prochain.