GW-Basic Sous-programmes de langage assembleur (code machine)
Cette annexe s'adresse
principalement aux utilisateurs expérimentés dans la programmation en langage
assembleur.
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
arguments.
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 qui reste résident et stockez 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 qui est 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écalage dans le segment courant du sous-programme appelé.
les arguments
sont les variables ou les constantes, séparées par des virgules, qui doivent
être passées à la routine.
Pour chaque paramètre dans les arguments, le
décalage 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)
GW-BASIC et le décalage (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 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 qui
contient 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).
Les sept règles suivantes doivent être respectées lors du codage
d'un sous-programme :
1. 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.
2. 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écalages 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.
3. 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.
4. Les valeurs sont renvoyées à GW-BASIC en incluant dans la liste d'arguments le nom de la variable qui reçoit le résultat.
5. Si l'argument est une
chaîne, le paramètre offset pointe sur trois octets appelés le descripteur de
chaîne. L'octet 0 du descripteur de chaîne contient la longueur de la chaîne (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 dans l'espace de chaîne.
Noter
:La routine appelée ne doit modifier le contenu d'aucun des trois octets du
descripteur de chaîne.
6. Les chaînes peuvent être modifiées par les
routines utilisateur, mais leur longueur ne doit pas être modifiée. GW-BASIC ne
peut pas manipuler correctement les chaînes si leurs longueurs sont modifiées
par des routines externes.
7. Si l'argument est un littéral de chaîne dans le programme, le descripteur de chaîne 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 dans le programme.
Par exemple, la ligne suivante force la copie du littéral de chaîne dans l'espace de chaîne alloué en dehors de l'espace mémoire du programme :
20 A$="BASIC"+""
La chaîne peut alors être
modifiée sans affecter le programme.
Exemples:
100 DEF SEG=&H2000
110
ACC=&H7FA
120 CALL ACC(A,B$,C)
La ligne 100 définit le segment
sur 2000 hex. La valeur de la variable ACC est ajoutée à l'adresse en tant que
mot bas après que la valeur DEF SEG est décalée à 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 hex.
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 le stockage d'un résultat de retour dans la variable C.
Noter: 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 :
MOVSW
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 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 | Stocke la variable A dans 'C' |
RET 6 | Restaure la pile ; Retour |
Appels de fonction USR
Bien que l'instruction CALL est
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.
Syntaxe:
USR[n](argument)
n est un nombre compris entre 0
et 9 qui spécifie 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.
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écalage d'appel de fonction USR. Ce décalage 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), qui spécifie le type d'argument donné. La
valeur NTF peut être l'une des suivantes :
La valeur NTF spécifie
2 un entier sur deux octets
(format complément à deux)
3 une chaîne
4 un nombre à virgule flottante
simple précision
8 un nombre à virgule flottante 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.
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 (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. L'octet 0 du descripteur de chaîne contient la longueur de la chaîne (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 dans le segment de données GW-BASIC.
Si l'argument est un
littéral de chaîne dans le programme, le descripteur de chaîne 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,
simple précision ou double précision) que l'argument qui lui a é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 stockée dans le FAC.
Programmes qui
appellent des programmes en langage d'assembleur
Cette section contient deux exemples de programmes GW-BASIC
qui charge une routine en langage assembleur pour additionner deux nombres,
retourne la somme en mémoire
reste résident en mémoire.
Le segment de code et le
décalage vers la première routine sont stockés dans le vecteur d'interruption à
0:100H.
L'exemple 1 appelle une sous-routine en langage assembleur :
10 DEF SEG=0
100 CS=PEEK(&H102)+PEEK(&H103)*256
200 OFFSET=PEEK(&H100)+PEEK(&H101)*256
250 DEF SEG
300 C1%=2:C2%=3:C3%=0
400 TWOSUM=OFFSET
500 DEF SEG=CS
600 CALL TWOSUM(C1%,C2%,C3%)
700 PRINT C3%
800 END
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 ;get address of parameter b 0109 8B 04 mov ax,[si] ;get value of b 010B 8B 76 0A mov si,[bp]+10 ;get address of parameter a 010E 03 04 add ax,[si] ;add value of a to value of b 0110 8B 7E 06 mov di,[bp]+6 ;get address of parameter c 0113 89 05 mov di,ax ;store sum in parameter c 0115 5D pop bp 0116 ca 00 06 ret 6 0119 usrprg endp ;Program to put procedure in ;memory and remain resident. The ;offset and segment are stored ;in location 100-103H. 0119 start1: 0119 B8 00 00 mov ax,0 011C 8E D8 mov ds,ax ;data segment to 0000H 011E BB 01 00 mov bx,0100H ;pointer to int vector 100H 0121 83 7F 02 0 cmp word ptr [bx],0 0125 75 16 jne quit ;program already run, exit 0127 83 3F 00 cmp word ptr2 [bx],0 012A 75 11 jne quit ;program already run exit 012C B8 01 03 R mov ax,offset usrprg 012F 89 07 mov [bx],ax ;program offset 0131 8C c8 mov ax,cs 0133 89 47 02 mov [bx+2],ax ;data segment 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