Sous-programmes de langage d'assemblage (code machine)
Cette page est écrite principalement pour les utilisateurs expérimentés dans la programmation en langage assembleur. Le GW-BASIC vous permet d'interfacer avec les sous-routines du langage d'assemblage en utilisant la fonction USR et l'instruction CALL.
La fonction USR permet aux sous-programmes du langage assembleur d'être appelés de la même manière que les fonctions intrinsèques GW-BASIC. Cependant, l'instruction CALL est recommandée pour interfacer des programmes en langage machine avec GW-BASIC. L'instruction CALL est compatible avec plus de langages que l'appel de fonction USR, produit un code source plus lisible et peut transmettre plusieurs paramètres.
Allocation de mémoire
L'espace mémoire doit être réservé pour un sous-programme en langage assembleur (ou code machine) avant de pouvoir le charger. Il existe trois méthodes recommandées pour réserver de l'espace pour les routines en langage assembleur :
- Spécifiez un tableau et utilisez VARPTR pour localiser le début du tableau avant chaque accès.
- Utilisez le commutateur /m dans la ligne de commande. Obtenez le segment de données (DS) de GW-BASIC et ajoutez la taille de DS pour référencer l'espace réservé au-dessus du segment de données.
- Exécutez un fichier .COM restant résident et entreposez un pointeur vers celui-ci dans un emplacement de vecteur d'interruption inutilisé.
- Il existe trois méthodes recommandées pour charger des routines en langage assembleur :
- BLOAD le fichier. Utilisez DEBUG pour charger un fichier .EXE étant en mémoire haute, exécutez GW-BASIC et BSAVE le fichier .EXE.
- Exécutez un fichier .COM contenant les routines. Enregistrez le pointeur vers ces routines dans des emplacements de vecteur d'interruption inutilisés, afin que votre application dans GW-BASIC puisse obtenir le pointeur et utiliser la ou les routines.
- Placez la routine dans la zone spécifiée.
Si, lorsqu'un sous-programme en langage assembleur est appelé, plus d'espace de pile est nécessaire, l'espace de pile GW-BASIC peut être enregistré et une nouvelle pile configurée pour être utilisée par le sous-programme en langage assembleur. L'espace de pile GW-BASIC doit être restauré, cependant, avant de quitter le sous-programme.
Instruction CALL
CALL variablename[(arguments)] |
variablename contient le déplacement dans le segment courant du sous-programme appelé.
arguments sont les variables ou constantes, séparées par des virgules, devant être passées à la routine.
Pour chaque paramètre dans les arguments, le déplacement de 2 octets de l'emplacement du paramètre dans le segment de données (DS) est poussé sur la pile.
Le segment de code d'adresse de retour (CS) de GW-BASIC et le déplacement (IP) sont poussés sur la pile.
Un long appel à l'adresse de segment donnée dans la dernière instruction DEF SEG et le décalage donné dans variablename transfèrent le contrôle à la routine de l'utilisateur.
Le segment de pile (SS), le segment de données (DS), le segment supplémentaire (ES) et le pointeur de pile (SP) doivent être conservés.
La figure suivante montre l'état de la pile au moment de l'instruction CALL :
La routine de l'utilisateur a maintenant le contrôle. Les paramètres peuvent être référencés en déplaçant le pointeur de pile (SP) vers le pointeur de base (BP) et en ajoutant un décalage positif à BP.
Lors de l'entrée, les registres de segment DS, ES et SS pointent tous vers l'adresse du segment contenant le code interpréteur GW-BASIC. Le registre de segment de code CS contient la dernière valeur fournie par DEF SEG. Si aucun DEF SEG n'a été spécifié, il pointe alors vers la même adresse que DS, ES et SS (le DEF SEG par défaut).
La figure suivante montre l'état de la pile lors de l'exécution du sous-programme appelé :
Les sept règles suivantes doivent être respectées lors du codage d'un sous-programme :
- La routine appelée peut détruire le contenu des registres AX, BX, CX, DX, SI, DI et BP. Ils ne nécessitent pas de restauration lors du retour à GW-BASIC. Cependant, tous les registres de segment et le pointeur de pile doivent être restaurés. Les bonnes pratiques de programmation exigent que les interruptions activées ou désactivées soient restaurées à l'état observé lors de l'entrée.
- Le programme appelé doit connaître le nombre et la longueur des paramètres passés. Les références aux paramètres sont des déplacements positifs ajoutés à BP, en supposant que la routine appelée a déplacé le pointeur de pile actuel dans BP ; c'est-à-dire MOV BP,SP. Lorsque 3 paramètres sont passés, l'emplacement de PO est à BP + 10, P1 est à BP + 8 et P2 est à BP + 6.
- La routine appelée doit faire un RETURN n (n est deux fois le nombre de paramètres dans la liste d'arguments) pour ajuster la pile au début de la séquence d'appel. En outre, les programmes doivent être définis par une instruction PROC FAR.
- Les valeurs sont renvoyées à GW-BASIC en incluant dans la liste d'arguments le nom de la variable recevant le résultat.
- Si l'argument est une chaîne de caractères, le paramètre offset pointe sur trois octets appelés le descripteur de chaîne de caractères. L'octet 0 du descripteur de chaîne de caractères contient la longueur de la chaîne de caractères (0 à 255). Les octets 1 et 2, respectivement, sont les huit bits inférieurs et supérieurs de l'adresse de début de chaîne de caractères dans l'espace de chaîne de caractères.
- 20 A$="BASIC"+""
- La chaîne de caractères peut alors être modifiée sans affecter le programme.
La routine appelée ne doit modifier le contenu d'aucun des trois octets du descripteur de chaîne de caractères.
Les chaînes de caractères peuvent être modifiées par les routines utilisateur, mais leur longueur ne doit pas être modifiée. Le GW-BASIC ne peut pas manipuler correctement les chaînes de caractères si leurs longueurs sont modifiées par des routines externes.
Si l'argument est un littéral de chaîne de caractères dans le programme, le descripteur de chaîne de caractères pointe vers le texte du programme. Veillez à ne pas modifier ou détruire votre programme de cette façon. Pour éviter des résultats imprévisibles, ajoutez +"" au littéral de chaîne de caractères dans le programme. Par exemple, la ligne suivante force la copie du littéral de chaîne dans l'espace de chaîne de caractères alloué en dehors de l'espace mémoire du programme :
Voici un exemple :
- 100 DEF SEG=&H2000
- 110 ACC=&H7FA
- 120 CALL ACC(A,B$,C)
- .
- .
- .
La ligne 100 définit le segment sur 2000 hexadécimal. La valeur de la variable ACC est ajoutée à l'adresse en tant que mot bas après que la valeur DEF SEG s'est déplacer à gauche de quatre bits (c'est une fonction du microprocesseur, pas de GW-BASIC). Ici, ACC est défini sur &H7FA, de sorte que l'appel à ACC exécute le sous-programme à l'emplacement 2000:7FA hexadécimal.
Lors de l'entrée, seuls 16 octets (huit mots) restent disponibles dans l'espace de pile alloué. Si le programme appelé nécessite un espace de pile supplémentaire, le programme utilisateur doit réinitialiser le pointeur de pile sur un nouvel espace alloué. Assurez-vous de restaurer le pointeur de pile ajusté au début de la séquence d'appel lors du retour à GW-BASIC.
La séquence suivante en langage assembleur illustre l'accès aux paramètres passés et l'entreposage d'un résultat de retour dans la variable C.
Note
- Le programme appelé doit connaître le type de variable pour les paramètres numériques passés. Dans ces exemples, l'instruction suivante ne copie que deux octets :
Ceci est adéquat si les variables A et C sont des nombres entiers. Il faudrait copier quatre octets s'ils étaient en simple précision, ou copier huit octets s'ils étaient en double précision.
- MOV BP,SP ; Obtient la position actuelle de la pile dans BP
- MOV BX,8[BP] ; Obtient l'adresse de la description B$
- MOV CL,[BX] ; Obtient la longueur de B$ en CL
- MOV DX,1[BX] ; Obtient l'adresse du descripteur de chaîne de caractères B$ dans DX
- MOV SI,10[BP] ; Obtient l'adresse de A en SI
- MOV DI,6[BP] ; Obtient le pointeur vers C dans DI
- MOVSW ; Entrepose la variable A dans 'C'
- RET 6 ; Restaure la pile ; Retour
Appels de fonction USR
Bien que l'instruction CALL soit la méthode recommandée pour appeler les sous-routines du langage d'assemblage, l'appel de fonction USR est toujours disponible pour la compatibilité avec les programmes écrits précédemment. Voici sa syntaxe :
USR[n](argument) |
n est un nombre compris entre 0 et 9 spécifiant la routine USR appelée (voir instruction DEF USR). Si n est omis, USR0 est supposé.
argument est une expression numérique ou de chaîne de caractères.
Dans GW-BASIC, une instruction DEF SEG doit être exécutée avant un appel de fonction USR pour garantir que le segment de code pointe vers la sous-routine appelée. L'adresse de segment donnée dans l'instruction DEF SEG détermine le segment de départ du sous-programme.
Pour chaque appel de fonction USR, une instruction DEF USR correspondante doit avoir été exécutée pour définir le déplacement d'appel de fonction USR. Ce déplacement et l'adresse DEF SEG actuellement active déterminent l'adresse de début du sous-programme.
Lorsque l'appel de la fonction USR est effectué, le registre AL contient l'indicateur de type de numéro (NTF), spécifiant le type d'argument donné. La valeur NTF peut être l'une des suivantes :
Valeur NTF | Spécification |
---|---|
2 | un entier sur deux octets (format complément à deux) |
3 | une chaîne de caractères |
4 | un nombre à virgule flottante de simple précision |
8 | un nombre à virgule flottante de double précision |
Si l'argument d'un appel de fonction USR est un nombre (AL<>73), la valeur de l'argument est placée dans l'accumulateur à virgule flottante (FAC). Le FAC a une longueur de 8 octets et se trouve dans le segment de données GW-BASIC. Le registre BX pointera sur le cinquième octet du FAC. La Figure suivante montre la représentation de tous les types de numéros GW-BASIC dans le FAC :
Si l'argument est un nombre à virgule flottante simple précision :
- BX+3 est l'exposant, moins 128. Le point binaire est à gauche du bit le plus significatif de la mantisse.
- BX+2 contient les sept bits les plus élevés de la mantisse avec le premier 1 supprimé (sous-entendu). Le bit 7 est le signe du nombre (0=positif, 1=négatif).
- BX+1 contient les 8 bits médians de la mantisse.
- BX+0 contient les 8 bits les plus bas de la mantisse.
Si l'argument est un entier :
- BX+1 contient les huit bits supérieurs de l'argument.
- BX+0 contient les huit bits inférieurs de l'argument.
Si l'argument est un nombre à virgule flottante double précision :
- BX+0 à BX+3 sont les mêmes que pour la virgule flottante simple précision.
- BX-1 à BX-4 contiennent quatre octets supplémentaires de mantisse. BX-4 contient les huit bits les plus bas de la mantisse.
Si l'argument est une chaîne de caractères (indiquée par la valeur 3 stockée dans le registre AL), la paire de registres (DX) pointe sur trois octets appelés le descripteur de chaîne de caractères. L'octet 0 du descripteur de chaîne de caractères contient la longueur de la chaîne de caractères (0 à 255). Les octets 1 et 2, respectivement, sont les huit bits inférieurs et supérieurs de l'adresse de début de chaîne de caractères dans le segment de données GW-BASIC.
Si l'argument est un littéral de chaîne de caractères dans le programme, le descripteur de chaîne de caractères pointe vers le texte du programme. Veillez à ne pas modifier ou détruire des programmes de cette manière (voir l'instruction CALL précédente).
Généralement, la valeur renvoyée par un appel de fonction USR est du même type (entier, chaîne de caractères, simple précision ou double précision) que l'argument lui ayant été transmis. Les registres qui doivent être conservés sont les mêmes que dans l'instruction CALL.
Un retour lointain est nécessaire pour quitter le sous-programme USR. La valeur retournée doit être entreposée dans le FAC.
Programmes appellant des programmes en langage d'assemblage
Cette section contient deux exemples de programmes GW-BASIC qui :
- charger une routine en langage assembleur pour additionner deux nombres
- remettre la somme en mémoire
- rester résident dans la mémoire
Le segment de code et le déplacement vers le premier sous-programme sont entreposés dans le vecteur d'interruption à 0:100H.
L'exemple 1 appelle une sous-routine en langage assembleur :
La sous-routine en langage assembleur appelée dans le programme ci-dessus doit être assemblée, liée et convertie en un fichier .COM. Le programme, lorsqu'il est exécuté avant l'exécution du programme GW-BASIC, restera en mémoire jusqu'à ce que l'alimentation du système soit coupée ou que le système soit redémarré.
- 0100 org 100H
- 0100 double segment
- assume cs:double
- 0100 EB 17 90 start: jmp start1
- 0103 usrprg proc far
- 0103 55 push bp
- 0104 8B EC mov bp,sp
- 0106 8B 76 08 mov si,[bp]+8 ;obtenir l'adresse du paramètre b
- 0109 8B 04 mov ax,[si] ;obtenir la valeur de b
- 010B 8B 76 0A mov si,[bp]+10 ;obtenir l'adresse du paramètre a
- 010E 03 04 add ax,[si] ;ajouter la valeur de a à la valeur de b
- 0110 8B 7E 06 mov di,[bp]+6 ;obtenir l'adresse du paramètre c
- 0113 89 05 mov di,ax ;entrepose la somme dans le paramètre c
- 0115 5D pop bp
- 0116 ca 00 06 ret 6
- 0119 usrprg endp
- ;Programme pour mettre la procédure en mémoire et rester résident. Le déplacement et le segment sont entreposés à l'emplacement 100-103H.
- 0119 start1:
- 0119 B8 00 00 mov ax,0
- 011C 8E D8 mov ds,ax ;segment de données à 0000H
- 011E BB 01 00 mov bx,0100H ;pointeur vers le vecteur int 100H
- 0121 83 7F 02 0 cmp word ptr [bx],0
- 0125 75 16 jne quit ;programme déjà exécuté, quitter
- 0127 83 3F 00 cmp word ptr2 [bx],0
- 012A 75 11 jne quit ;programme déjà exécuté quitter
- 012C B8 01 03 R mov ax,offset usrprg
- 012F 89 07 mov [bx],ax ;déplacement de programme
- 0131 8C c8 mov ax,cs
- 0133 89 47 02 mov [bx+2],ax ;segments de données
- 0136 0E push cs
- 0137 1F pop ds
- 0138 BA 0141 R mov dx,offset veryend
- 013B CD 27 int 27h
- 013D quit:
- 013D CD 20 int 20h
- 013F veryend:
- 013F double ends
- end start
L'exemple 2 place la sous-routine du langage assembleur dans la zone spécifiée :
- 10 I=0:JC=0
- 100 DIM A%(23)
- 150 MEM%=VARPTR(A%(1))
- 200 FOR I=1 TO 23
- 300 READ JC
- 400 POKE MEM%,JC
- 450 MEM%=MEM%+1
- 500 NEXT
- 600 C1%=2:C2%=3:C3%=0
- 700 TWOSUM=VARPTR(A%(1))
- 800 CALL TWOSUM(C1%,C2%,C3%)
- 900 PRINT C3%
- 950 END
- 1000 DATA &H55,&H8b,&Hec &H8b,&H76,&H08,&H8b,&H04,&H8b,&H76
- 1100 DATA &H0a,&H03,&H04,&H8b,&H7e,&H06,&H89,&H05,&H5d
- 1200 DATA &Hca,&H06,&H00