Come saprete scrivere programmi stand alone in assembly non è una cosa molto
comoda visto anche che oggi esistono dei compilatori che riescono ad
ottimizzare il codice in modo sorprendente, quindi invece di scrivere il
programma interamente in assembly si preferisce spesso utilizzare un
linguaggio ad alto livello che chiama alcune routine critiche scritte in
assembly.
Per fare questo col Pascal il vostro programma deve contenere la direttiva $L
e la dichiarazione delle procedure e funzioni EXTERNAL, a sua volta il
programma assembly deve contenere le direttive PUBLIC e EXTRN.
La direttiva {$L FILEASM.OBJ} indica al programma Pascal di cercare il file
FILEASM.OBJ che come avrete già capito deve essere compilato.
Ogni procedura o funzione Assembly che volete che venga vista dal programma
Pascal deve essere dichiarata PUBLIC e deve avere un corrispondente sempre nel
programma Pascal.
La sintassi di una procedura EXTERNAL in Pascal è simile ad una dichiarazione
di una procedura FORWARD e deve stare al livello più esterno del programma o
della unit (non può cioè essere dichiarata all'interno di altre procedure):
procedure AsmProc(a:integer;b:real); external;Questa dichiarazione corrisponderà al seguente frammento di codice Assembly:
CODE SEGMENT BYTE PUBLIC AsmProc PROC NEAR PUBLIC AsmProc ... ... ... AsmProc ENDP CODE ENDSSolo le label dichiarate con la direttiva PUBLIC nel programma Assembly sono visibili dal Pascal ed esse devono essere dichiarate nel CODE SEGMENT in quanto il Turbo Pascal non permette definizioni PUBLIC nel DATA SEGMENT.
Un modulo Assembly può accedere ad ogni funzione , procedura, variabile o
costante solo se dichiarata nel livello più esterno del programma Pascal
(quello delle variabili globali).
Supponiamo di aver dichiarato le seguenti variabili in Pascal:
var a:byte; b:word; d:integer; c:shortint; e:real; l:pointer;tutte queste variabili sono accessibili dal programma Assembly usando la direttiva EXTRN in questo modo:
EXTRN A:BYTE EXTRN B:WORD EXTRN C:BYTE EXTRN D:WORD EXTRN E:FWORD EXTRN L:DWORDper quanto riguarda le procedure e le funzioni facciamo riferimento al seguente esempio:
{Codice Turbo Pascal} unit Esempio1; interface procedure Proc1; procedure Proc2; implementation var A:word; procedure AsmProc; near; external; {$L ASMPROC.OBJ} procedure Proc2; begin writeln('Siamo in Proc2'); end; procedure Proc3; near; begin writeln('Siamo in Proc3'); end; procedure Proc4; far; begin writeln('Siamo in Proc4'); end; procedure Proc1; begin writeln('Siamo in Proc1'); A:=10; writeln('Prima di AsmProc A vale ',A); AsmProc; writeln('Dopo AsmProc A vale ',A); end; end. ;Codice Assembly DATA SEGMENT WORD PUBLIC ASSUME DS:DATA EXTRN A:WORD DATA ENDS CODE SEGMENT BYTE PUBLIC ASSUME CS:CODE EXTRN Proc2:FAR EXTRN Proc3:NEAR EXTRN Proc4:FAR AsmProc PROC NEAR PUBLIC AsmProc CALL FAR PTR Proc2 CALL Proc3 CALL FAR PTR Proc4 mov cx,ds:A ;Queste 3 instruzioni sottraggono 2 da A sub cx,2 mov ds:A,cx ret AsmProc ENDP CODE ENDS ENDIl programma principale sar...:
program Prova; uses Esempio1; begin proc1; end.Provate a compilare il tutto e ad eseguire il programma, non spenderò ulterior tempo basta vedere l'output del programma per capire cosa succede.
Vediamo ora un altro aspetto molto importante : il passaggio dei parametri. Il Turbo Pascal passa i parametri alle funzioni ed alle procedure salvandoli nello stack nell'ordine in cui li incontra nella dichiarazione, vedremo di seguito come questi vengono salvati:
- PARAMETRI PASSATI PER VALORE -
I parametri passati per valore sono quelli che non possono essere modificati
dalla funzione o procedura che li usa, questi vengono trattati in modo diverso
a seconda del tipo:
- Tipi scalari (Boolean, Char, Shortint,Byte, Integer, Word, Longint,Subrange type ed Enumerated types); dipende dalla dimesione della variabile. Se la variabile occupa 1 byte viene salvata nello stack come se fosse di 2 byte ma la parte più significativa è ignorata. Se la variabile occupa 2 byte viene pushata nello stack cosi com'è. Se la variabile occupa 4 byte occuper... due posizioni nello stack: prima vengono salvati i 2byte più significativi poi gli altri 2 - Real Sono salvati nello stack e occupano 3 posizioni (6byte) prima viene salvata la parte più significativa. - Pointer Vengono salvati direttamente nello stack come puntatori far (la prima word contiene il segmento la seconda l'offset).Il programma assembly può usare le istruzioni LDS o LES per recuperare il valore del puntatore. - String Le stringhe a causa della loro lunghezza non vengono salvate nello stack, in esso si salva il puntatore alla stringa. E' quindi dovere della procedura chiamata non modificare la stringa referenziata dal puntatore, essa deve copiarsi la stringa e lavorare sulla copia. - Record e Array Se la loro dimensione è di 1, 2 o 4 byte vengono salvati nello stack come i tipi scalari, se invece la loro dimensione è 3, 5 o maggiore nello stack si salva il puntatore come nel caso di stringhe. - Set Vengono trattati come le stringhe, si salva un puntatore che punta ad una rappresentazione a 32 bit del set; il primo bit (LSB) corrisponde al primo elemento del set.- PARAMETRI PASSATI PER INDIRIZZO -
Il Turbo Pascal si aspetta che tutti i parametri nello stack siano rimossi
prima di uscire dal sottoprogramma.
Ci sono due modi per sistemare lo stack. Potete utilizzare l'istruzione RETn
dove n è il numero di byte dei parametri che sono stati salvati nello stack,
oppure si può salvare l'indirizzo di ritorno ed estrarre i parametri uno a uno.
Vediamo ora come si accede ai parametri passati dal Turbo Pascal al programma
Assembly.
Quando la routine riceve il controllo in cima allo stack c'è l'indirizzo di
ritorno (2 o 4 byte dipende se la routine è near o far) e sotto i parametri
che gli sono stati passati.
Ci sono 3 tecniche per accedere a questi parametri :
- Usare il registro BP per accedere allo stack - Usare un altro registro (base o indice) per prelevare i parametri - Estrarre l'indirizzo di ritorno e poi i parametriLe prime due sono più complicate e le vediamo dopo, la terza prevede di salvare l'indirizzo di ritorno in un posto sicuro e poi di mettere i parametri in registri; quest'ultima tecnica funziona bene se la routine in questione non richiede spazio per le variabili locali.
CODE SEGMENT ASSUME cs:CODE MiaProc PROC FAR ;procedure MiaProc(i,j:integer); external; PUBLIC MiaProc j EQU WORD PTR [bp+6] ;j è sopra il BP salvato e l'ndirizzo ;di ritorno i EQU WORD PTR [bp+8] ;i è appena sopra j push bp ;salva il valore di BP mov bp,sp ;fa puntare BP allo stack mov ax,i ;in questo modo accedo alle varibili ... ...Quando si usa il BP per accedere ai parametri il Turbo Assembler prevede un metodo alternativo per calcolare gli offset delle variabili utilizzando la direttiva ARG.
CODE SEGMENT ASSUME cs:CODE MiaProc PROC FAR ;procedure MiaProc(i,j:integer);external; PUBLIC MiaProc ARG j:WORD, i:WORD = RetBytes push bp mov bp,sp mov ax,i ... ...L'istruzione ARG j:WORD, i:WORD = RetBytes uguaglia automaticamente i a [WORD PTR BP + 6] e j a [WORD PTR BP + 8].
function MiaFunc(i,j:char):string; external;La direttiva ARG deve essere qualcosa del tipo:
ARG j:BYTE:2, i:BYTE:2 =RetBytes RETURNS result:DWORDIl :2 dopo gli argomenti è necessario per dire al Turbo Assembler che ogni carattere viene salvato come un array di 2 byte (a noi interessa solo il byte meno significativo); la parola RETURNS specifica la variabile di uscita che nel caso di stringhe come abbiamo visto è il puntatore ad essa.
Un'altra semplificazione messa a disposizione dal Turbo Assembler è la direttiva .MODEL che permette di specificare oltre il modello di memoria da usare il linguaggio da supportare. Sempre sull'esempio di prima:
.MODEL large, PASCAL .CODE MiaProc PROC FAR i:WORD, j:WORD PUBLIC MiaProc mov ax,i ... ...Notate che non si devono specificare i parametri in ordine inverso e che alcune altre cose non servono (push bp, mov bp,sp) inoltre viene settato il ritorno al chiamante con POP BP e RETn.
Il secondo modo per accedere ai parametri (si era detto) era quello di usare
un altro Base Register (BX) o un Index Register (SI o DI).
Ricordate pero che il segmento di default per questi registri è CS non SS
quindi si deve fare attenzione :
CODE SEGMENT ASSUME cs:CODE MiaProc PROC FAR PUBLIC MiaProc j EQU WORD PTR ss:[bx+4] i EQU WORD PTR ss:[bx+6] mov bx,sp mov ax,i ... ...Qui sopra è mostrato come si può utilizzare il registro BX. Siccome non si deve salvare BP questo modo è più efficiente.
Ora che abbaiamo visto come il Turbo Pascal passa i parametri ad una routine
Assembly , vediamo come sempre il Turbo Pascal salva i parametri di uscita ad
una funzione.
Come per i parametri d'ingresso dipende dal tipo:
- Tipi scalari Se la dimensione del dato è 1 byte lo mette in AL Se la dimensione è 2 byte lo mette in AX Se la dimensione è 4 byte lo mette in DX:AX (con la parte più significativa in DX) - Real I 6 byte del numero reale vengono salvati in 3 registri DX BX AX con la parte più significativa in DX e quella meno significativa in AX. - String Le stringhe vengono salvate in un'area temporanea allocata dal Pascal prima della chiamata; un puntatore di tipo FAR a quest'area viene salvato nello stack prima del primo parametro.(Nota: questo puntatore non fa parte della lista dei parametri). - Pointer I puntatori sono salvati in DX:AX (segment:offset)L'ultimo argomento che vorrei trattare riguarda l'allocazione di memoria (sia statica che volatile) per le nostre routine Assembly.
DATA SEGMENT PUBLIC MiaInt DW ? MioChar DB ? ... ... DATA ENDSTutto questo con 2 restrizioni : primo queste variabili sono private (non possono essere visibili dal programma Pascal), secondo non possono essere pre-inizializzate (quindi MiaInt DW 45 non causa un errore ma MiaInt non verr. inizializzata a 45 quando eseguirò il programma).
CODE SEGMENT ASSUME cs:CODE MiaProc PROC FAR PUBLIC MiaProc LOCAL a:WORD, b:WORD = LocalSpace ;a va in [bp-2] e b in [bp-4] i EQU WORD PTR [bp+6] ;i sta sopra BP e l'indirizzo ;di ritorno push bp ;salvo BP mov bp,sp sub sp,LocalSpace ;crea lo spazio per le 2 word mov ax,45 mov a,ax ;a:=45 xor ax,ax ;a:=0 mov b,ax ;b:=0 ... ... ;fate quello che volete fare!! ... mov sp,bp ;ripristina l'originale SP pop bp ;recupera BP ret 2 ;estrae i 2 byte di i MiaProc ENDP CODE ENDS ENDL'istruzione LOCAL a:WORD, b:WORD = LocalSpace riserva [BP-2] per a e [BP-4] per b e assegna a LocalSpace il valore 4 che altro non è che la dimensione dell'area per le variabili locali della procedura.
Bene Takeshi spero di essere stato sufficientemente chiaro e di averti dato qualche utile tips per Interfacciare i tuoi programmi(ni) in grafica 3D con efficienti routine in assembly in modo da far ruotare quel maledetto cubo a velocità supersoniche anche su un 286... :-)))
Assembly Page di Antonio |
|