Breve panoramica sulla famiglia 80x86 La famiglia 80x06 è nata nel 1981 con il processore 8086 ed è proseguita con la linea del Pentium. Ciò che accomuna tutti questi processori è che sono tutti compatibili all'indietro, ma ciascuna nuova generazione ha aggiunto nel tempo nuove caratteristiche e maggior potenza di calcolo rispetto ai suoi predecessori. Oggi i computer dotati di processori 8088, 8086, 286, 386 o 486 sono storia poiché si tratta di computer lenti e superati. Anche i computer dotati di processore Pentium della ultima generazione sono ormai scomparsi per lasciare il posto agli attuali multi-core a 32 o 64 bit sui quali gira un software sempre più esigente in termini di risorse e velocità. Il processore a 16 bit rimane perciò una palestra dove si possono affrontare senza troppe complicazioni tecnologiche i concetti basilari della programmazione Assembly. Rappresentazione dei numeri in notazione binaria Prima di cominciare a capire come programmare in Assembly, è opportuno cercare di comprendere come i numeri sono rappresentati all'interno della CPU. I numero sono rappresentati in notazione binaria, ovvero in base 2. Ci sono diversi termini che sono utilizzati per descrivere numeri di differenti dimensioni, e sono descritti qui di seguito. 1 BIT: 0 Un bit è il tipo di dato più semplice che esiste. Può assumere valore zero oppure uno. 1 NIBBLE: 0000 4 BITS Un nibble è quattro bit, o mezzo byte. Notate che può assumere il valore massimo di 15 (1111 = 15). Esso è la base della notazione esadecimale (base 16), che viene utilizzata per rappresentare i numeri nei programmi Assembly. I numeri esadecimali vanno da 1 a F e sono seguiti da un h per indicare che il numero è espresso in questa notazione. Ad esempio: Fh = 15 decimale. I numeri esadecimali che cominciano con una lettera sono preceduti da uno 0 (zero). 1 BYTE 00000000 2 NIBBLES 8 BITS Un byte è 8 bit o 2 nibble. Un byte ha un valore massimo di FFh (255 decimale). Poiché un byte è 2 byte, la rappresentazione esadecimale è formata da due cifre consecutive seguite da h. Ad esempio: 3Dh. Un byte è anche la dimensione dei registri a 8-bit, che vedremo in seguito. 1 WORD 0000000000000000 2 BYTES 4 NIBBLES 16 BITS Una word è costituita da due byte affiancati. Una word può assumere un valore massimo di FFFFh (65536). Poiché una word è composta da quattro nibble, è rappresentata da quattro cifre esadecimali. Una word è anche la dimensione dei registri a 16-bit. Registri I registri sono locazioni di memoria all'interno della CPU dove un numero può essere memorizzato e manipolato. Vi sono tre dimensioni di registro: 8-bit, 16-bit, e (dal 386 in poi) 32-bit. Vi sono quattro differenti tipi di registri: registri general-purpose, registri di segmento, registri indice e registri di stack. Prima descriveremo i registri principali. I registri di stack e segmento saranno affrontati in seguito. Registri General-Purpose Registri General-Purpose Sono registri a 16-bit. Vi sono quattro registri general-purpose: AX, BX, CX, DX. Sono suddivisi in registri a 8-bit. AX è suddiviso in AH, che contiene il byte alto e AL che contiene il byte basso. Con il processore 386 e superiori vi sono anche registri a 32-bit. Questi hanno gli stessi nomi dei registri a 16-bit ma con una E come prefisso (es.: EAX). Si possono utilizzare AL, AH, AX ed EAX separatamente e considerare come registri separati per alcune oprazioni. Se AX contenesse 24689 decimal: AH AL 01100000 01110001 AH varrebbe 96 e AL varrebbe 113. Se aggiungessimo 1 ad AL, questo varrebbe 114 e AH rimarrebbe invariato. SI, DI, SP e BP possono essere utilizzati come registri general-purpose, ma hanno usi più specifici. Questi non sono suddivisi in due metà. Registri Indice Questi sono a volte chiamati registri puntatore e sono registri a 16-bit. Sono utilizzati principalmente con le istruzioni che operano con le stringhe di caratteri. Vi sono tre registri indice: SI (source index), DI (destination index) e IP (instruction pointer). Con il processore 386 e superiori vi sono anche registri indice a 32-bit: EDI e ESI. Nota: Come registro indice per le stringhe si può usare anche il registro BX. IP è un registro indice ma non può essere manipolato direttamente poiché contiene l'indirizzo dell'istruzione successiva durante l'esecuzione. Registri di Stack BP e SP sono registri di stack e sono utilizzati quando si ha a che fare con lo stack. Li vedremo in seguito. Segmenti e Offset I primi progettisti del processore 8008 decisero che nessuno avrebbe mai avuto bisogno di utilizzare più di un megabyte di memoria, così costruirono il chip in modo che non potesse indirizzare memoria oltre il megabyte. Il problema è che per indirizzare un intero megabyte sono necessari almeno 20 bit. I registri hanno solo 16 bit, ma i progettisti non ritennero di utilizzare due registri perché credevano che lo spazio di indirizzamento che si otteneva con 32 bit fosse esagerato per chiunque. Pertanto idearono quello che sembrava una soluzione intelligente per risolvere il problema: segmento e offset. Ecco come viene ottenuto l'indirizzamento con due registri, ma senza utilizzare 32 bit. OFFSET = SEGMENT * 16 SEGMENT = OFFSET / 16 (the lower 4 bits are lost) Un registro contiene il segmento e un altro registro contiene l'offset. Unendo insieme i due registri si ottiene un indirizzo a 20-bit. SEGMENT 0010010000010000---- OFFSET ----0100100000100010 20-bit Address 00101000100100100010 DS contiene il segmento e SI contiene l'offset. Poiché entrambi hanno dimensione 16-bit gli indirizzi si accavallano. Nello schema è mostrato come la coppia di indirizzi DS:SI è utilizzata per ottenere l'indirizzo di 20 bit. La notazione standard per una coppia Segmento/Offset è SEGMENT:OFFSET. I registri di segmento sono CS, DS, ES, SS. Con il 386 e processori successivi esistono anche FS e GS. I registri di offset sono BX, DI, SI, BP, SP, IP. Con il processore 386 che opera in modalità protetta, QUALSIASI registro generale può essere utilizzato come registro di offset. (Eccetto IP, che non può essere manipolato direttamente). Lo Stack Poiché esistono soltanto sei registri che possono essere utilizzati per la maggior parte delle operazioni, è naturale chiedersi come superare questa apparente limitazione. E' semplice. Esiste una speciale area di memoria, denominata stack, in cui possono essere salvati e recuperati i valori desiderati. Quest'area di memoria è organizzata come una pila di piatti. L'ultimo che viene messo è il primo ad essere prelevato. Questo tipo di organizzazione è denominata Last On First Off (LOFO) oppure Last In First Out (LIFO). Come lo stack è organizzato Via via che nuovi dati sono posti nello stack, questo cresce verso il basso. Come si vede dalla figura, lo stack ha inizio ad un indirizzo alto e cresce verso il basso. E' opportuno accertarsi di non mettere troppi dati nello stack, se non si vuole rischiare l'errore di stack overflow. Introduzione alle istruzioni Assembly In Assembly sono disponibili moltissime istruzioni, ma sono soltanto una ventina quelle che bisogna assolutamente conoscere e che vengono utilizzate più spesso. La maggior parte delle istruzioni sono identificate da un codice mnemonico di tre caratteri e sono seguite da uno o più operandi separati da virgole. Per esempio per mettere un dato in un registro, si può utilizzare l'istruzione MOV. mov mov mov mov ax,10 bx,20 cx,30 dx,40 ; ; ; ; put put put put 10 20 30 40 into into into into ax bx cx dx Notate che in Assembly tutto quello che viene scritto dopo il ; (punto e virgola) viene ignorato. Questo è utile per poter commentare il codice. Push e Pop: Due istruzioni per utilizzare lo Stack Abbiamo già parlato dello stack, ma non di come fare ad inserire e prelevare i dati. Ci sono due semplici istruzioni che è necessario conoscere: PUSH e POP. Ecco la sintassi per utilizzarli: PUSH: Inserisce un dato in cima allo stack Sintassi: push dato POP: Preleva un dato dallo stack e lo memorizza in uno specifico registro o variabile. Sintassi: pop registro (o variabile) Questo esempio di codice mostra come utilizzare le istruzioni PUSH e POP. push cx push ax pop cx pop ax ; ; ; ; put put put put cx on ax on value value the stack the stack from stack into cx from stack into ax Notate che i valori di CX e AX vengono scambiati. C'è un istruzione specifica per scambiare i valori contenuti in due registri: XCHG, che può essere usato per rimpiazzare il precedente frammento di codice con la sola istruzione "xchg ax,cx". Tipi di operando Ci sono tre tipi di operando in Assembly: immediati, registro, e memoria. Un operando immediato è un valore numerico conosciuto al momento della comilazione ed è sempre costante. Un operando di tipo registro è un valore contenuto in un registro come ad esempio AX o SI. Un operando in memoria è un valore che viene mantenuto e acceduto in memoria. Alcune Istruzioni da conoscere Questo è un elenco di alcune importanti istruzioni che è necessario conoscere prima di poter comprendere e scrivere programmi in Assembly. MOV: copia un valore da una locazione ad un'altra. Sintassi: MOV destinazione, sorgente Ad esempio: mov ax,10 mov bx,cx mov dx,Number ; moves an immediate value into ax ; moves value from cx into bx ; moves the value of Number into dx INT: richiama una funzione DOS o del BIOS. Sintassi: INT interrupt number Ad esempio: int 21h int 10h ; Calls DOS service ; Calls the Video BIOS interrupt La maggior parte degli interrupt hanno più di una funzione. Questo significa che è necessario fornire un numero specificatore alla funzione che si desidera richiamare. In genere questo numero viene specificato in AH. Ad esempio per stampare un messaggio sullo schermo bisogna fare così: mov ah,9 int 21h ; subroutine number 9 ; call the interrupt Ma prima bisogna specificare cosa stampare. Questa funzione richiede che: la coppia DS:DX contenga il puntatore a dove si trova il contenuto della stringa. La stringa deve essere terminata con il simbolo di dollaro ($). Questo sarebbe semplice da ottenere se il registro DS potesse essere manipolato direttamente; bisogna invece utilizzare il registro AX nel modo seguente. L'esempio mostra la tecnica da utilizzare: mov mov mov mov int dx,OFFSET Message ax,SEG Message ds,ax ah,9 21h ; ; ; ; ; DX contains offset of message AX contains segment of message DS:DX points to message function 9 - display string call dos service Le direttive OFFSET e SEG servono per ottenere il segmento e l'offset dove si trova il messaggio, e non il suo contenuto. Ora che sappiamo come scrivere il codice per visualizzare il messaggio, non resta che dichiarare il messaggio. Nel segmento dati dichiariamo il messaggio nel modo seguente: Message DB "Hello World!$" Notate che la stringa è terminata con il simbolo di dollaro. Cosa significa 'DB'? DB è un codice mnemonico che sta per "declare byte" e il messaggio è un array di byte (un carattere ASCII occupa un byte). I dati possono essere dichiarati di varie dimensioni: byte (DB), word (DW) e double (DD). Ecco alcuni esempi di dichiarazione di dati: Number1 db ? Number2 dw ? Il simbolo di punto di domanda alla fine significa che il dato non è inizializzato, cioè non ha un valore iniziale. Se si vuole specificare un valore di inizializzazione, è sufficiente scrivere: Number1 db 0 Number2 dw 1 In questo caso Number1 assume valore 0 e Number2 è uguale a 1, quando il programma è mandato in esecuzione. Se si dichiara una variabile come word, non si può copiare il valore di questa variabile in un registro a 8-bit, e allo stesso modo non si può dichiarare una variabile come byte e copiarne il contenuto in un registro a 16-bit. Per esempio: mov al,Number1 mov ax,Number1 ; ok ; error mov bx,Number2 mov bl,Number2 ; ok ; error Tutto quello che è necessario ricordarsi è che si può soltanto copiare byte in registri a 8-bit e word in registri a 16-bit. Il primo programma Assembly Ora che abbiamo illustrato le istruzioni fondamentali e come dichiarare i dati, vediamo un programma Assembly completo, che può essere compilato. Ci si riferisce qui al compilatore masm tasm. Listing 1: 1STPROG.ASM ; This is a simple program which displays "Hello World!" ; on the screen. .model small .stack .data Message db "Hello World!$" ; message to be display .code mov dx,OFFSET Message ; offset of Message is in DX mov ax,SEG Message ; segment of Message is in AX mov ds,ax ; DS:DX points to string mov ah,9 ; function 9 - display string int 21h ; call dos service mov ax,4c00h ; return to dos DOS int 21h END start ;end here Qualche semplice esempio Il modo in cui abbiamo specificato l'indirizzo del messaggio da stampare era un po' complicato. Servivano ben tre linee di codice, non del tutto semplici da ricordare. mov dx,OFFSET MyMessage mov ax,SEG MyMessage mov ds,ax Possiamo rimpiazzare tutto questo con una singola linea. Questo rende il codice più semplice da leggere e da ricordare. mov dx,OFFSET MyMessage Perché funzioni, bisogna anche aggiungere queste istruzioni all'inizio del programma. mov ax,@data mov ds,ax Nota: per l'assemblatore tasm, la prima delle due linee è leggermente diversa: mov ax,data Questa tecnica si basa sul fatto che tutti i dati in un segmento hanno il medesimo SEG value. Impostando questo valore in DS, evita di doverlo ricaricare ogni volta che desideriamo indirizzare un altro dato nello stesso segmento. Input da tastiera Utilizzeremo l'interrupt 16h, funzione 00h per leggere la tastiera. Questa chiamata legge un dato dal buffer della tastiera. Se nessun dato è presente, aspetta fino a quando non è disponibile. Restituisce lo SCAN code in AH e la traduzione ASCII in AL. xor ah,ah int 16h ; function 00h - get character ; interrupt 16h Tutto quello di cui dobbiamo preoccuparci per ora è il valore ASCII che si trova in AL. Nota: XOR effettua un OR booleano esclusivo. E' comunemente utilizzato per azzerare un registro o una variabile. Stampare un carattere Il problema è che abbiamo il tasto premuto in AH. Come facciamo a visualizzarlo? Non possiamo usare la funzione 9h perché avremmo dovuto aver già definito una stringa terminandola con il simbolo di dollaro. Questo è invece ciò che dobbiamo fare. ; after calling function 00h of interrupt 16h mov dl,al mov ah,02h int 21h ; move al (ascii code) into dl ; function 02h of interrupt 21h ; call interrupt 21h Se desiderate salvare il valore di AH, è necessario fare PUSH di AX prima della chiamata e recuperarlo in seguito con POP. Controllo del flusso In Assembly c'è un insieme di comandi per il controllo del flusso, come in molti altri linguaggi. Prima di tutto l'istruzione fondamentale: jmp label Tutto quello che fa è di saltare alla locazione specificata e continuare l'esecuzione del codice da quel punto. Per esempio: jmp ALabel . . ALabel: Cosa dobbiamo fare se vogliamo valutare delle condizioni? Abbiamo letto un valore dalla tastiera ma ora vogliamo fare qualche operazione. Ad esempio stampiamo qualcosa se questo valore è uguale ad una certa costante. Come facciamo? E' semplice. Utilizziamo comandi di salto condizionato. cmp ax,3 je correct ; is AX = 3? ; yes Ecco un elenco di questi comandi: JA Salta se il primo operando è maggiore del secondo operando JAE Come sopra, ma salta anche se gli operandi hanno lo stesso valore JB Salta se il primo operando è minore del secondo operando JBE Come sopra, ma salta anche se gli operandi hanno lo stesso valore JNA Salta se il primo operando non è maggiore del secondo operando (JBE) JNAE Salta se il primo operando non è maggiore del o uguale al secondo operando (JNB) JNB Salta se il primo operando non è minore del secondo operando (JAE) JNBE Salta se il primo operando non è minore del o uguale al secondo operando (JA) JZ Salta se i due operandi sono uguali JE Come JZ JNZ Salta se i due operandi NON sono uguali JNE Come JNZ JC Salta se il flag di carry è impostato CMP: compara un registro o una variabile con un valore Sintassi: CMP registro o variabile, valore jxx destination Un esempio d'uso è il seguente: cmp al,'Y' je ItsYES ; compare the value in al with Y ; if it is equal then jump to ItsYES Ogni istruzione richiede una certa quantità di spazio nel segmento codice. Si ottiene un avviso dall'assemblatore se si cerca di saltare a locazioni lontane più di 127 byte in entrambe le direzioni. Si può risolvere il problema, modificando una sequenza come questa: cmp ax,10 je done ; is AX 10? ; yes, lets finish in qualcosa come questa: cmp ax,10 jne notdone jmp done notdone: ; is AX 10? ; no it is not ; we are now done Questo risolve il problema, ma in genere è più opportuno organizzare il codice usando chiamate a procedure. Ora vedremo un programma che dimostra input, output e controllo del flusso. Listing 2: PROGFLOW.ASM ; a program to demonstrate program flow and input/output .model tiny .code org 100h start: mov dx,OFFSET Message mov ah,9 int 21h ; display a message on the screen ; using function 09h ; of interrupt 21h mov dx,OFFSET Prompt mov ah,9 int 21h ; display a message on the screen ; using function 09h ; of interrupt 21h jmp First_Time Prompt_Again: mov dx,OFFSET Another mov ah,9 int 21h ; display a message on the screen ; using function 09h ; of interrupt 21h First_Time: mov mov int xor dx,OFFSET Again ah,9 21h ah,ah ; ; ; ; display a message on the screen using function 09h of interrupt 21h function 00h of int mov mov mov int 16h bl,al dl,al ah,02h 21h ; ; ; ; ; interrupt 16h gets a character save to bl move al to dl function 02h - display character call DOS service cmp bl,'Y' je Prompt_Again cmp bl,'y' je Prompt_Again ; ; ; ; is if is if al=Y? yes then display it again al=y? yes then display it again theEnd: mov dx,OFFSET GoodBye mov ah,9 int 21h ; print goodbye message ; using function 9 ; of interrupt 21h mov ah,4Ch int 21h ; terminate program .DATA CR equ 13 LF equ 10 Message DB Prompt DB Again DB Another DB GoodBye DB end start ; enter ; line-feed "A Simple Input/Output Program$" CR,LF,"Here is your first prompt.$" CR,LF,"Do you want to be prompted again? $" CR,LF,"Here is another prompt!$" CR,LF,"Goodbye then." Altre istruzioni che si devono conoscere Quello che segue è un elenco di alcuni istruzioni Assembly fondamentali che è molto importante conoscere e che sono utilizzate molto spesso. ADD: Aggiunge il contenuto del secondo operando al valore del primo operando Sintassi: ADD operando1,operando2 Questo esempio aggiunge il valore di operando2 a quello di operando1. Il risultato è memorizzato nell'operando1. Un tipo di dato immediato non può essere utilizzato come operando1, ma può essere utilizzato come operando2. SUB: Sottrae il contenuto del secondo operando dal valore del primo operando Sintassi: SUB operand1,operand2 Questo esempio sottrae il valore di operando2 da quello di operando1. Il risultato è memorizzato nell'operando1. Un tipo di dato immediato non può essere utilizzato come operando1, ma può essere utilizzato come operando2. MUL: Moltiplica due interi senza segno (sempre positivi) IMUL: Moltiplica due interi con segno (positivi o negativi) Sintassi: MUL registro o variabile IMUL registro o variabile Questo esempio moltiplica AL o AX per il valore del registro o della variabile specificati. Se si specifica un operando della dimensione di un byte, AL viene moltiplicato per l'operando e il risultato è posto nel registro AX. Se invece si specifica un operando della dimensione di una word, AX viene moltiplicato per l'operando e il risultato è posto in DX:AX. Con i processori 386, 486 e Pentium può essere utilizzato anche il registro EAX e il risultato viene posto in EDX:EAX. DIV: Divide due interi senza segno (sempre positivi) IDIV: Divide due interi con segno (positivi o negativi) Sintassi: DIV registro o variabile IDIV registro o variabile L'istruzione di divisione funziona in modo simile alla moltiplicazione, dividendo il valore contenuto in AX per il registro o la variabile specificati. Il risultato è memorizzato in due diverse locazioni. AL contiene il quoziente della divisione mentre il resto è posto in AH. Se l'operando è un registro a 16-bit, il numero alla locazione DX:AX viene diviso per l'operando e il quoziente e il resto sono posti rispettivamente in AX e DX. Introduzione alle procedure In Assembly una procedura è l'equivalente di una funzione in C o in Pascal. Una procedura fornisce un modo semplice per incapsulare un calcolo complesso in modo che possa essere utilizzato senza preoccuparsi di come funziona internamente. Con procedure attentamente strutturate, è possibile realizzare programmi di grosse dimensioni, nascondendo i dettagli implementativi e ottenendo codice più leggibile. Ecco come si definisce una procedura: PROC AProcedure . . ; some code to do something . ret ; if this is not here then your computer will crash ENDP AProcedure Invocare una procedura è altrettanto semplice. Tutto quello che si deve fare è quanto segue: call AProcedure Il programma che segue è un esempio di come usare una procedura. E' simile al primo programma visto, tutto quello che fa è stampare la frase "Hello World!" sullo schermo. Listing 3: SIMPPROC.ASM ; This is a simple program to demonstrate procedures. It should ; print Hello World! on the screen when ran. .model tiny .code org 100h Start: call Display_Hi mov ax,4C00h int 21h ; Call the procedure ; return to DOS Display_Hi PROC mov dx,OFFSET HI mov ah,9 int 21h ret Display_Hi ENDP HI DB "Hello World!" ; define a message end Start Procedure che passano parametri Le procedure non sarebbero così utili se non si potessero passare parametri per modificarli o utilizzarli all'interno della procedura. Vi sono tre diversi modi di passare un parametro: in un registro, in memoria e sullo stack. Di seguito ci sono tre esempi che svolgono tutti lo stesso compito. Stampano il carattere ASCII 254 in un punto specifico dello schermo. Le dimensioni (in byte) dei tre programmi una volta compilati sono rispettivamente: 38 per il parametro passato con registro, 69 per il parametro passato in memoria e 52 per il parametro passato sullo stack. Passare i parametri nei registri Il vantaggio di passare i parametri nei registri è la semplicità. Basta copiare i parametri nei registri prima di invocare la procedura. Listing 4: PROC1.ASM ; this a procedure to print a block on the screen using ; registers to pass parameters (cursor position of where to ; print it and colour). .model tiny .code org 100h Start: mov mov mov mov dh,4 dl,5 al,254 bl,4 ; ; ; ; row to print character on column to print character on ascii value of block to display colour to display character call PrintChar ; print our character mov ax,4C00h ; terminate program int 21h PrintChar PROC NEAR push bx push cx ; save registers to be destroyed xor bh,bh mov ah,2 int 10h ; clear bh - video page 0 ; function 2 - move cursor ; row and col are already in dx pop bx ; restore bx xor mov mov int ; ; ; ; bh,bh ah,9 cx,1 10h pop cx display page - 0 function 09h write char & attrib display it once call bios service ; restore registers ret ; return to where it was called PrintChar ENDP end Start Passare i parametri in memoria Il vantaggio di questo metodo è l'estrema semplicità, ma ha lo svantaggio di rendere il programma più ingombrante e lento. Per passare i parametri attraverso la memoria, è sufficiente copiare i parametri in variabili situate in memoria. Si può usare una variabile nello stesso modo in cui si usa un registro, ma bisogna tenere conto che le istruzioni che utilizzano i registri sono molto più veloci. Listing 5: PROC2.ASM ; this a procedure to print a block on the screen using memory ; to pass parameters (cursor position of where to print it and ; colour). .model tiny .code org 100h Start: mov mov mov mov Row,4 Col,5 Char,254 Colour,4 ; ; ; ; row to print character column to print character on ascii value of block to display colour to display character call PrintChar ; print our character mov ax,4C00h ; terminate program int 21h PrintChar PROC NEAR push ax cx bx ; save registers to be destroyed xor mov mov mov int bh,bh ah,2 dh,Row dl,Col 10h mov mov xor mov mov int al,Char bl,Colour bh,bh ah,9 cx,1 10h pop bx cx ax ; clear bh - video page 0 ; function 2 - move cursor ; call Bios service ; ; ; ; display page - 0 function 09h write char & attrib display it once call bios service ; restore registers ret ; return to where it was called PrintChar ENDP ; variables to store data Row db Col db Colour db Char db ? ? ? ? end Start Passare i parametri sullo stack Questo è il modo più potente e flessibile di passare i parametri. Il problema è che risulta un poco più complicato. Listing 6: PROC3.ASM ; this a procedure to print a block on the screen using the ; stack to pass parameters (cursor position of where to print ; it and colour). .model tiny .code org 100h Start: mov dh,4 ; mov dl,5 ; mov al,254 ; mov bl,4 ; push dx ax bx row to print string on column to print string on ascii value of block to display colour to display character ; put parameters onto the stack call PrintString ; print our string pop bx ax dx mov ax,4C00h int 21h ;restore registers ;terminate program PrintString PROC NEAR push bp mov bp,sp push cx ; save bp ; put sp into bp ; save registers to be destroyed xor mov mov int bh,bh ; clear bh - video page 0 ah,2 ; function 2 - move cursor dx,[bp+8] ; restore dx 10h ; call bios service mov mov xor mov mov int ax,[bp+6] bx,[bp+4] bh,bh ; ah,9 ; cx,1 ; 10h ; pop cx pop bp ; character ; attribute display page - 0 function 09h write char & attrib display it once call bios service ; restore registers ret ; return to where it was called PrintString ENDP end Start Stack di una procedura con due parametri Per prelevare un parametro dallo stack, tutto quello che serve fare è conoscere l'indirizzo dove si trova. L'ultimo parametro si trova all'indirizzo BP+2 e il precedente all'indirizzo BP+4 Cosa sono i "Memory Models"? In molti esempi abbiamo utilizzato la direttiva .MODEL per specificare il modello di memoria da utilizzare. Cosa significa? Sintassi: .MODEL MemoryModel Dove MemoryModel può essere SMALL, COMPACT, MEDIUM, LARGE, HUGE, TINY o FLAT. Tiny Significa che esiste un unico segmento per codice e dati. Questo tipo di programma può essere un eseguibile .COM. Small Significa che per default tutto il codice si trova in un unico segmento e tutti i dati dichiarati si trovano in un altro solo segmento. Questo implica che tutte le procedure e le variabili sono indirizzate come NEAR utilizzando solo gli offset. Compact Significa che per default tutto il codice si trova in un unico segmento ma i dati possono essere dichiarati in più segmenti diversi. Questo implica che i dati sono indirizzati utilizzando sia il segmento sia l'offset. Gli elementi di codice (procedure) sono NEAR e le variabili sono FAR. Medium E' l'opposto di compact. I dati sono NEAR e le procedure sono FAR. Large Questo significa che sia le procedure sia le variabili sono FAR. Bisogna indirizzarle entrambe utilizzando sia il segmento, sia l'offset. Flat Questo modello di memoria si usa quando il processore opera in modalità protetta e la memoria non è segmentata. E' utilizzabile solo con sistemi operativi a 32-bit, come Windows 9x e Windows NT. Macro (in Turbo Assembler) Nota: Tutti gli esempi sono in Turbo Assembler. Le macro sono molto utili per raggruppare sequenze di istruzioni usate frequentemente, per le quali non è opportuno realizzare una procedura. Le macro sono sostituite al momento della compilazione con il codice che contengono. Questa è la sintassi per definire una macro: Name_of_macro macro ; ; a sequence of instructions ; endm Questi due esempi illustrano le macro per salvare e ripristinare di gruppi di registri: SaveRegs macro push push push push ax bx cx dx endm RestoreRegs macro pop pop pop pop dx cx bx ax endm Notate che i registri sono prelevati in ordine inverso a quello con cui sono stati salvati nello stack. Per usare una macro in un programma è sufficiente utilizzare il nome della macro come una qualsiasi istruzione. SaveRegs ; some other instructions RestoreRegs Questo esempio mostra come si può usare una macro per risparmiare righe di codice. Questa macro stampa semplicemente un messaggio sullo schermo. OutMsg macro SomeText local PrintMe,SkipData jmp SkipData PrintMe db SomeText,'$' SkipData: push ax dx cs mov dx,OFFSET cs:PrintMe mov ah,9 int 21h pop cs dx ax endm L'unico problema con le macro è che usandole troppo spesso, la dimensione del codice del programma tende ad aumentare e si rischia di incontrare errori di duplicazione delle label e di nomi delle variabili. Il modo corretto di risolvere questo problema è usare la direttiva LOCAL che consente di dichiarare nomi che valgono solo all'inerno delle macro. Sintassi: LOCAL name Dove name è il nome di una variabile locale o di una label. Macro con parametri Un'altra utile proprietà delle macro è che possono avere parametri. Il numero di parametri è limitato solo dalla lunghezza della linea di codice. Sintassi: Name_of_Macro macro par1,par2,par3 ; ; commands go here ; endm Questo esempio somma il primo e il secondo parametro e pone il risultato nel terzo: AddMacro macro num1,num2,result push ax ; save ax from being destroyed mov ax,num1 ; put num1 into ax add ax,num2 ; add num2 to it mov result,ax ; move answer into result pop ax ; restore ax endm Funzioni che operano sui file I file possono essere aperti, letti e scritti. Il DOS ha diversi modi per fare queste operazioni, che risparmiano la necessità di dover scrivere ogni volta routine ad hoc. Quella che segue è una lista di utili funzioni dell'interrupt 21h che operano con i file. Nota: I bit sono numerati da destra verso sinistra. Function 3Dh: open file Apre un file esistente in lettura, scrittura o in aggiunta, con il nome e drive specificati. INPUT: AH = 3Dh AL = bits 0-2 Access mode 000 = read only 001 = write only 010 = read/write bits 4-6 Sharing mode (DOS 3+) 000 = compatibility mode 001 = deny all 010 = deny write 011 = deny read 100 = deny none DS:DX = segment:offset of ASCIIZ pathname OUTPUT: CF = 0 function is succesful AX = handle CF = 1 error has occured AX = error code 01h missing file sharing software 02h file not found 03h path not found or file does not exist 04h no handle available 05h access denied 0Ch access mode not permitted Cosa significa ASCIIZ? Una stringa ASCIIZ è una stringa di caratteri ASCII terminata con uno zero anziché con il simbolo di dollaro. Importante: Ricordatevi di salvare l'handle del file, poiché è necessario per le operazioni successive. Come salvare gli handle dei file E' importante salvare l'handle del file perché serve per fare qualsiasi operazione con il file. Come si fa? Ci sono due metodi utilizzabili: copiare l'handle in un registro e rinunciare a utilizzarlo finché il file è in uso, oppure copiarlo in una locazione di memoria. Gli svantaggi con il primo metodo è che bisogna ricordarsi di non utilizzare il registro utilizzato per contenere l'handle, e che viene sprecato un registro per qualcosa di più utile. Perciò utilizzeremo il secondo metodo. Ecco come fare: FileHandle DW 0 . . . mov FileHandle,ax ; use this for saving the file handle ; save the file handle Function 3Eh: close file Chiude un file che è stato aperto. INPUT: AX = 3Eh BX = file handle OUTPUT: CF = 0 function is successful AX = destroyed CF = 1 function not successful AX = error code - 06h file not opened or unauthorised handle. Importante: Non chiamate questa funzione con un handle di valore zero, poiché in questo caso verrebbe chiuso lo standard input (la tastiera) e non sareste più in grado di immettere dati dalla tastiera. Function 3Fh: read file/device Legge byte da un file o da un device in un buffer di memoria. INPUT: AH = 3Fh BX = handle CX = number of bytes to be read DS:DX = segment:offset of a buffer OUTPUT: CF = 0 function is successful AX = number of bytes read CF = 1 an error has occurred 05h access denied 06h illegal handle or file not opened Se CF = 0 e AX = 0, significa che il puntatore si trova già alla fine del file e non si possono leggere altri dati. Se CF = 0 e AX è minore di CX, significa che soltanto una parte dei byte desiderati è stata letta perché è stata raggiunta la fine del file o si è verificato un errore. Questa funzione può essere anche utilizzata per leggere l'input dalla tastiera. Usando zero come handle, la lettura ha termine dopo il primo carriage return incontrato o dopo che un numero prefissato di byte è stato letto. Questo è un modo semplice ed efficace per permettere all'utente di inserire soltanto un certo numero di caratteri. Listing 7: READFILE.ASM ; a program to demonstrate creating a file and then reading from ; it .model small .stack .code mov ax,@data mov ds,ax mov mov mov int dx,OFFSET FileName ; put address of filename in dx al,2 ; access mode - read and write ah,3Dh ; function 3Dh -open a file 21h ; call DOS service mov Handle,ax jc ErrorOpening mov mov mov mov int ; base address of data segment ; put this in ds ; save file handle for later ; jump if carry flag set - error! dx,offset Buffer ; address of buffer in dx bx,Handle ; handle in bx cx,100 ; amount of bytes to be read ah,3Fh ; function 3Fh - read from file 21h ; call dos service jc ErrorReading ; jump if carry flag set - error! mov bx,Handle mov ah,3Eh int 21h ; put file handle in bx ; function 3Eh - close a file ; call DOS service mov mov xor mov cx,100 ; length of string si,OFFSET Buffer ; DS:SI - address of string bh,bh ; video page - 0 ah,0Eh ; function 0Eh - write character NextChar: lodsb int 10h loop NextChar mov ax,4C00h int 21h ; AL = next character in string ; call BIOS service ; terminate program ErrorOpening: mov mov int mov int dx,offset OpenError ; display an error ah,09h ; using function 09h 21h ; call DOS service ax,4C01h ; end program with an errorlevel =1 21h ErrorReading: mov dx,offset ReadError ; display an error mov ah,09h ; using function 09h int 21h ; call DOS service mov ax,4C02h ; end program with an errorlevel =2 int 21h .data Handle DW ? ; to store file handle FileName DB "C:\test.txt",0 ; file to be opened OpenError DB "An error has occured(opening)!$" ReadError DB "An error has occured(reading)!$" Buffer DB 100 dup (?) ; buffer to store data END Function 3Ch: Create file Crea un file vuoto sul drive e con il nome specificati. INPUT: AH = 3Ch CX = file attribute bit 0 = 1 read-only file bit 1 = 1 hidden file bit 2 = 1 system file bit 3 = 1 volume (ignored) bit 4 = 1 reserved (0) - directory bit 5 = 1 archive bit bits 6-15 reserved (0) DS:DX = segment:offset of ASCIIZ pathname OUTPUT: CF = 0 function is successful AX = handle CF = 1 an error has occurred 03h path not found 04h no available handle 05h access denied Importante: Se esiste già un file con lo stesso nome, il file esistente viene perso. Perciò è opportuno assicurarsi che non esista già un file con lo stesso nome. Ciò può essere fatto con la funzione che segue. Function 4Eh: find first matching file Cerca il primo file con il nome specificato. INPUT: AH = 4Eh CX = file attribute mask (bits can be combined) bit 0 = 1 read only bit 1 = 1 hidden bit 2 = 1 system bit 3 = 1 volume label bit 4 = 1 directory bit 5 = 1 archive bit 6-15 reserved DS:DX = segment:offset of ASCIIZ pathname OUTPUT: CF = 0 function is successful [DTA] Disk Transfer Area = FindFirst data block The DTA block: Offset Size in bytes Meaning 0 21 22 24 26 30 21 1 2 2 4 13 Reserved File attributes Time last modified Date last modified Size of file (in bytes) File name (ASCIIZ) Un esempio di come verificare l'esistenza di un file: File DB "C:\file.txt",0 mov dx,OFFSET File mov cx,3Fh mov ah,4Eh int 21h ; ; ; ; ; name of file that we want address of filename file mask 3Fh - any file function 4Eh - find first file call DOS service jc NoFile ; print message saying file exists NoFile: ; continue with creating file Questo è un esempio per creare un file e scriverci dei dati. Listing 8: CREATE.ASM ; This example program creates a file and then writes to it. .model small .stack .code mov mov mov mov int ax,@data ; base address of data segment ds,ax ; put it in ds dx,offset StartMessage ah,09h 21h mov xor mov int dx,offset FileName ; put offset of filename in dx cx,cx ; clear cx - make ordinary file ah,3Ch ; function 3Ch - create a file 21h ; call DOS service jc CreateError mov dx,offset FileName ; jump if there is an error ; put offset of filename in dx mov al,2 mov ah,3Dh int 21h ; access mode -read and write ; function 3Dh - open the file ; call dos service jc OpenError mov Handle,ax ; jump if there is an error ; save value of handle mov mov mov mov int ; ; ; ; ; dx,offset WriteMe bx,Handle cx,38 ah,40h 21h address of information to write file handle for file 38 bytes to be written function 40h - write to file call dos service jc WriteError cmp ax,cx jne WriteError ; jump if there is an error ; was all the data written? ; no it wasn't - error! mov bx,Handle mov ah,3Eh int 21h ; put file handle in bx ; function 3Eh - close a file ; call dos service mov dx,offset EndMessage mov ah,09h int 21h ReturnToDOS: mov ax,4C00h int 21h ; terminate program WriteError: mov dx,offset WriteMessage jmp EndError OpenError: mov dx,offset OpenMessage jmp EndError CreateError: mov dx,offset CreateMessage EndError: mov ah,09h int 21h mov ax,4C01h int 21h .data CR equ 13 LF equ 10 StartMessage DB "This program creates a file called NEW.TXT" DB ,"on the C drive.$" EndMessage DB CR,LF,"File create OK, look at file to" DB ,"be sure.$" WriteMessage DB "An error has occurred (WRITING)$" OpenMessage DB "An error has occurred (OPENING)$" CreateMessage DB "An error has occurred (CREATING)$" WriteMe DB "HELLO, THIS IS A TEST, HAS IT WORKED?",0 FileName DB "C:\new.txt",0 ; name of file to open Handle DW ? ; to store file handle END Questo è un esempio di come cancellare un file dopo aver verificato che esiste: Listing 9: DELFILE.ASM ; a demonstration of how to delete a file. The file new.txt on ; c: is deleted (this file is created by create.exe). We also ; check if the file exits before trying to delete it .model small .stack .data CR equ 13 LF equ 10 File Deleted NoFile ErrDel db db db db "C:\new.txt",0 "Deleted file c:\new.txt$" "c:\new.txt doesn't exits - exiting$" "Can't delete file - probably write protected$" .code mov ax,@data mov ds,ax mov dx,OFFSET File mov cx,3Fh mov ah,4Eh int 21h jc FileDontExist ; ; ; ; address of filename to look for file mask 3Fh - any file function 4Eh - find first file call dos service mov dx,OFFSET File mov ah,41h int 21h jc ErrorDeleting ; ; ; ; DS:DX points to file to be killed function 41h - delete file call DOS service jump if there was an error mov dx,OFFSET Deleted jmp Endit ; display message ErrorDeleting: mov dx,OFFSET ErrDel jmp Endit FileDontExist: mov dx,OFFSET NoFile EndIt: mov ah,9 int 21h mov ax,4C00h int 21h end ; terminate program and exit to DOS ; call DOS service Usare le funzioni FindFirst e FindNext Listing 10: DIRC.ASM ; this program demonstrates how to look for files. It prints ; out the names of all the files in the c:\drive and names of ; the sub-directories .model small .stack .data FileName db "c:\*.*",0 ;file name DTA db 128 dup(?) ;buffer to store the DTA ErrorMsg db "An Error has occurred - exiting.$" .code mov ax,@data mov ds,a mov es,ax ; set up ds to be equal to the ; data segment ; also es mov dx,OFFSET DTA mov ah,1AH int 21h mov mov mov int ; DS:DX points to DTA ; function 1Ah - set DTA ; call DOS service cx,3Fh ; attribute mask - all files dx,OFFSET FileName ; DS:DX points ASCIZ filename ah,4Eh ; function 4Eh - find first 21h ; call DOS service jc error ; jump if carry flag is set LoopCycle: mov dx,OFFSET FileName ; DS:DX points to file name mov ah,4Fh ; function 4fh - find next int 21h ; call DOS service jc exit mov mov xor mov ; exit if carry flag is set cx,13 si,OFFSET DTA+30 bh,bh ah,0Eh ; ; ; ; length of filename DS:SI points to filename in DTA video page - 0 function 0Eh - write character NextChar: lodsb int 10h ; AL = next character in string ; call BIOS service loop NextChar mov mov xor rep di,OFFSET DTA+30 cx,13 al,al stosb jmp LoopCycle ; ; ; ; ES:DI points to DTA length of filename fill with zeros erase DTA ; continue searching error: mov dx,OFFSET ErrorMsg mov ah,9 int 21h exit: mov ax,4C00h int 21h end ; exit to DOS ; display error message Istruzioni per le stringhe In Assembly vi sono diverse utili istruzioni per operare con le stringhe. Segue l'elenco delle istruzioni e la sintassi per utilizzarle: MOV* Move String: copia un byte, una word o una double word dall'indirizzo DS:SI all'indirizzo ES:DI Sintassi: movsb movsw movsd ; move byte ; move word ; move double word CMPS* Compare string: confronta un byte, una word o una double word all'indirizzo DS:SI con un byte, una word o una double word all'indirizzo ES:DI Sintassi: cmpsb cmpsw cmpsd ; compare byte ; compare word ; compare double word Nota: Questa istruzione è normalmente utilizzata con il prefisso REP. SCAS* Search string: cerca il valore in AL, AX o EAX nella stringa all'indirizzo ES:DI Sintassi: scasb scasw scasd ; search for AL ; search for AX ; search for EAX Nota: Questa istruzione è normalmente utilizzata con il prefisso REPZ o REPNZ. REP Prefisso utilizzabile davanti ad un'istruzione per le stringhe per ripetere l'istruzione tante volte quanto specificato nel registro CX. Sintassi: rep StringInstruction STOS* Copia un byte, una word o una double word da AL, AX o EAX all'indirizzo ES:DI Sintassi: stosb stosw stosd ; move AL into ES:DI ; move AX into ES:DI ; move EAX into ES:DI LODS* Copia un byte, una word o una double word dall'indirizzo ES:DI in AL, AX o EAX Sintassi: lodsb lodsw lodsd ; move ES:DI into AL ; move ES:DI into AX ; move ES:DI into EAX L'esempio successivo mostra come usare queste istruzioni. Listing 11: STRINGS.ASM .model small .stack .code mov mov mov mov mov int ax,@data ; ax points to of data segment ds,ax ; put it into ds es,ax ; put it in es too ah,9 ; function 9 - display string dx,OFFSET Message1 ; ds:dx points to message 21h ; call dos function cld mov si,OFFSET String1 ; clear direction flag ; make ds:si point to String1 mov di,OFFSET String2 ; make es:di point to String2 mov cx,18 ; length of strings rep movsb ; copy string1 into string2 mov ah,9 ; function 9 - display string mov dx,OFFSET Message2 ; ds:dx points to message int 21h ; call dos function mov dx,OFFSET String1 ; display String1 int 21h ; call DOS service mov dx,OFFSET Message3 ; ds:dx points to message int 21h ; call dos function mov dx,OFFSET String2 ; display String2 int 21h ; call DOS service mov si,OFFSET Diff1 ; make ds:si point to Diff1 mov di,OFFSET Diff2 ; make es:di point to Diff2 mov cx,39 ; length of strings repz cmpsb ; compare strings jnz Not_Equal ; jump if they are not the same mov ah,9 ; function 9 - display string mov dx,OFFSET Message4 ; ds:dx points to message int 21h ; call dos function jmp Next_Operation Not_Equal: mov ah,9 ; function 9 - display string mov dx,OFFSET Message5 ; ds:dx points to message int 21h ; call dos function Next_Operation: mov di,OFFSET SearchString ; make es:di point to string mov cx,36 ; length of string mov al,'H' ; character to search for repne scasb ; find first match jnz Not_Found mov mov int jmp ah,9 ; function 9 - display string dx,OFFSET Message6 ; ds:dx points to message 21h ; call dos function Lodsb_Example Not_Found: mov ah,9 ; function 9 - display string mov dx,OFFSET Message7 ; ds:dx points to message int 21h ; call dos function Lodsb_Example: mov ah,9 ; function 9 - display string mov dx,OFFSET NewLine ; ds:dx points to message int 21h ; call dos function mov mov xor mov cx,17 ; length of string si,OFFSET Message ; DS:SI - address of string bh,bh ; video page - 0 ah,0Eh ; function 0Eh - write character NextChar: lodsb ; AL = next character in string int 10h loop NextChar ; call BIOS service mov ax,4C00h int 21h ; return to DOS .data CR equ 13 LF equ 10 NewLine db CR,LF,"$" String1 db "This is a string!$" String2 db 18 dup(0) Diff1 db "This string is nearly the same as Diff2$" Diff2 db "This string is nearly the same as Diff1$" Equal1 db "The strings are equal$" Equal2 db "The strings are not equal$" Message db "This is a message" SearchString db "1293ijdkfjiu938uHello983fjkfjsi98934$" Message1 Message2 Message3 Message4 Message5 Message6 Message7 db db db db db db db "Using String instructions example program.$" CR,LF,"String1 is now: $" CR,LF,"String2 is now: $" CR,LF,"Strings are equal!$" CR,LF,"Strings are not equal!$" CR,LF,"Character was found.$" CR,LF,"Character was not found.$" end Scoprire la versione del DOS In molti programmi è necessario scoprire la versione del DOS. Questo perché si sta utilizzando una funzione DOS che è disponibile soltanto da una certa versione in poi. Per conoscere la versione è sufficiente fare come segue: mov ah,30h ; function 30h - get MS-DOS version int 21h ; call DOS function Questa funzione restituisce il major number di versione in AL e il minor number in AH. Per esempio se la versione fosse 4.01, AL varrebbe 4 e AH varrebbe 01. Il problema è che dal DOS 5 in poi, il comando SETVER può modificare la versione che deve essere restituita. Il modo per aggirare il problema è il seguente. mov ah,33h ; function 33h - actual DOS version mov al,06h ; subfunction 06h int 21h ; call interrupt 21h Questo frammento di codice restituisce la versione corrente del DOS, indipendentemente dal fatto che sia stato utilizzato SETVER per modificare il numero di versione. La major version viene posta in BL e la minor version in BH. Ovviamente funziona solo con le versioni del DOS dalla 5 in poi. Push e Pop multipli Si possono inserire e prelevare dallo stack più di un registro con una sola linea di codice, sia con TASM, sia con MASM. Questo rende il codice più semplice da comprendere. push ax bx cx dx pop dx cx bx ax ; save registers ; restore registers Quando TASM e A86 compilano queste linee, le traducono in istruzioni di PUSH e POP separate. Questa notazione server soltanto per maggior concisione e comprensibilità. Nota: Perché le istruzioni mostrate sopra siano compilate correttamente da A86, bisogna separare i registri con una virgola (,). Le istruzioni PUSHA/PUSHAD e POPA/POPAD PUSHA è un'utile istruzione che inserisce nello stack tutti i registri general-purpose. In pratica PUSHA corrisponde alle seguenti istruzioni: temp push push push push push push push push = SP ax cx dx bx temp bp si di Il vantaggio principale è di essere un comando molto conciso. Inoltre viene eseguito molto più velocemente delle corrispettive istruzioni. POPA esegue la funzione inversa, prelevando tutti questi registri dallo stack. PUSHAD e POPAD svolgono le stesse funzioni di PUSHA e POPA, ma utilizzando i registri a 32-bit: ESP, EAX, ECX, EDX, EBX, EBP, ESI e EDI. Usare lo Shift per moltiplicazione e divisione Le istruzioni MUL e DIV sono molto lente e dovrebbero essere utilizzate solo quando la velocità non è una richiesta. Per effettuare moltiplicazioni e divisioni più velocemente si possono utilizzare gli operatori di shift a destra o a sinistra. Ogni operazione di shift moltiplica o divide per una potenza di 2. Funziona allo stesso modo degli operatori << e >> del linguaggio C. Ci sono quattro differenti modi di utilizzare l'istruzione di shift. SHL Moltiplica per due un operando senza segno SHR Divide per due un operando senza segno SAR Divide per due un operando con segno SAL Moltiplica per due un operando con segno La sintassi è identica per tutti e quattro le modalità: SHL operando1,operando2 Nota: Con il processore 8086 operando2 può assumere solo il valore 1. Con il processore 286/386 il valore di operando2 deve essere minore o uguale di 31. Cicli Usare l'istruzione Loop è preferibile rispetto ad utilizzare un JMP con label. Si imposta il numero di ripetizioni da effettuare nel registro CX ed ogni volta che il programma incontra l'istruzione Loop, CX viene automaticamente decrementato e viene fatto un salto alla label specificata come argomento. Il salto deve essere limitato a 127 byte in avanti o 128 byte all'indietro. Sintassi: mov cx,100 ; 100 times to loop Label: . Loop Label: ; decrement CX and loop to Label Questo produce esattamente lo stesso risultato del codice seguente: mov cx,100 ; 100 times to loop Label: dec cx ; CX = CX-1 jnz Label ; continue until done Quale ritenete più comprensibile? Utilizzare DEC/JNZ risulta essere più veloce dal 486 in poi ed è utile quando non è possibile utilizzare il registro CX. L'istruzione JNZ salta se il flag zero non è impostato. Azzerando il registro CX, il flag zero viene impostato. Come usare un debugger E' tempo di utilizzare un debugger per capire cosa un programma sta effettivamente facendo. A questo scopo si può usare il Turbo Debugger. Ma prima abbiamo bisogno di un programma su cui lavorare. Listing 12: DEBUG.ASM ; example program to demonstrate how to use a debugger .model tiny .code org 100h start: push ax push bx push cx ; save value of ax ; save value of bx ; save value of cx mov ax,10 mov bx,20 mov cx,3 ; first parameter is 10 ; second parameter is 20 ; third parameter is 3 Call ChangeNumbers pop cx pop bx pop ax ; restore cx ; restore bx ; restore dx mov ax,4C00h int 21h ; exit to dos ChangeNumbers PROC add ax,bx ; adds number in bx to ax mul cx ; multiply ax by cx mov dx,ax ; return answer in dx ret ChangeNumbers ENDP end start Ora compiliamolo come eseguibile .COM e poi digitiamo: td name of file Il Turbo Debugger viene eseguito, e vengono mostrate le istruzioni che compongono il nostro programma. Per esempio le prime linee del programma vengono mostrate come: cs:0000 50 cs:0001 53 cs:0002 51 push ax push bx push cx Alcune combinazioni di tasti per il Turbo Debugger: F5 F7 F9 ALT F4 Size Window Next Instruction Run Step Backwards I numeri che vengono copiato nei registri sono differenti da quelli del codice sorgente perché sono rappresentati in forma esadecimale (base 16) poiché questa notazione è semplice da convertire in forma binaria, ed è anche più concisa e comprensibile. Sulla destra dello schermo c'è una finestra che mostra i valori contenuti dei registri. In questo momento tutti i registri principali sono vuoti. Premendo F7 per eseguire la prima linea del programma. Poiché la prima linea inserisce il valore del registro AX nello stack, si vede che lo stack pointer (SP) viene modificato. Continuate a premere F7 fino ad arrivare alla linea mov ax,000A, e oltrepassatela. Ora, guardando nella finestra che mostra il contenuto dei registri, si vede che AX contiene A. Procedendo passo passo, BX assume il valore 14 e CX assume il valore 3. Poi AX assume il valore 1E che è A+14, e così via istruzione dopo istruzione. Infine CX, BX e AX sono impostati al loro valore iniziale di zero. Altre operazioni di output in modalità testo Vi sono altri modi per ottenere l'output in modalità testo. Questo primo programma è un esempio di come muovere il cursore per visualizzare una stringa di testo in una posizione a piacere. Listing 12: TEXT1.ASM .model tiny .code org 100h start: mov mov mov xor int dh,12 dl,32 ah,02h bh,bh 10h ; ; ; ; ; cursor col cursor row move cursor to the right place video page 0 call bios service mov dx,OFFSET Text mov ah,9 int 21h ; DS:DX points to message ; function 9 - display string ; all dos service mov ax,4C00h int 21h ; exit to dos Text DB "This is some text$" end start L'esempio successivo mostra come scrivere sullo schermo utilizzando la funzione 40h dell'interrupt 21h. Listing 13: TEXT2.ASM .model small .stack .code mov ax,@data mov ds,ax ; set up ds as the segment for data mov mov mov mov int ; ; ; ; ; ah,40h bx,1 cx,17 dx,OFFSET Text 21h mov ax,4C00h int 21h function 40h - write file handle = 1 (screen) length of string DS:DX points to string call DOS service ; terminate program .data Text DB "This is some text" end Il programma che segue mostra come impostare e richiamare la funzione 13h dell'interrupt 10h "write string". Questa funzione ha la caratteristica di poter scrivere una stringa in un posto a piacere sullo schermo con uno specifico colore. Tuttavia è complessa da impostare. Listing 14: TEXT3.ASM .model small .stack .code mov ax,@data mov es,ax ; set up ds as the segment for data ; put this in es mov mov mov xor mov ; ; ; ; ; bp,OFFSET Text ah,13h al,01h bh,bh bl,5 ES:BP points to message function 13 - write string attrib in bl,move cursor video page 0 attribute - magenta mov mov mov int cx,17 dh,5 dl,5 10h ; ; ; ; mov ax,4C00h int 21h length of string row to put string column to put string call BIOS service ; return to DOS .data Text DB "This is some text" end Il programma successivo mostra come scrivere sullo schermo usando rep stosw per scrivere nella memoria video. Listing 15: TEXT4.ASM .model small .stack .code mov mov xor mov mov mov cld rep ax,0B800h es,ax di,di ah,4 al,"G" cx,4000 stosw mov ax,4C00h int 21h ; ; ; ; ; ; ; ; segment of video buffer put this into es clear di, ES:DI points to video memory attribute - red character to put there amount of times to put it there direction - forwards output character at ES:[DI] ; return to DOS end Il programma successivo mostra come scrivere una stringa nella memoria video. Listing 15: DIRECTWR.ASM ; write a string direct to video memory .model small .stack .code mov ax,@data mov ds,ax mov mov mov mov mov xor ax,0B800h es,ax ah,3 cx,17 si,OFFSET Text di,di ; ; ; ; ; segment of video buffer put this into es attribute - cyan length of string to print DX:SI points to string lodsb mov es:[di],al inc di mov es:[di],ah inc di ; ; ; ; put next character into al output character to video memory move along to next column output attribute to video memory loop Wr_Char ; loop until done Wr_Char: mov ax,4C00h int 21h ; return to DOS .data Text DB "This is some text" end Modalità 13h La modalità 13h è disponibile soltanto sulle schede VGA, MCGA e superiori. Verificare che la modalità 13h sia consentita E' opportuno comunicare all'utente se il suo computer non è in grado di supportare la modalità 13h, invece di lasciare che il programma vada in crash. Ecco come fare. Listing 16: CHECK13.ASM .model small .stack .data NoSupport db "Mode 13h is not supported on this computer." db ,"You need either a MCGA or VGA video" db ,"card/monitor.$" Supported db "Mode 13h is supported on this computer.$" .code mov ax,@data mov ds,ax ; set up DS to point to data segment ; use ax call Check_Mode_13h ; check if mode 13h is possible jc Error ; if cf=1 there is an error mov ah,9 ; function 9 - display string mov dx,OFFSET Supported ; DS:DX points to message int 21h ; call DOS service jmp To_DOS ; exit to DOS Error: mov ah,9 ; function 9 - display string mov dx,OFFSET NoSupport ; DS:DX points to message int 21h ; call DOS service To_DOS: mov ax,4C00h int 21h ; exit to DOS Check_Mode_13h PROC ; Returns: CF = 1 Mode 13h not possible mov ax,1A00h int 10h cmp al,1Ah je Mode_13h_OK stc ; ; ; ; ; Request video info for VGA Get Display Combination Code Is VGA or MCGA present? mode 13h is supported mode 13h isn't supported CF=1 Mode_13h_OK: ret Check_Mode_13h ENDP end E' sufficiente includere questo codice per verificare se la modalità 13h è supportata, per essere certi di poterla utilizzare. Impostare la modalità video Impostare la modalità è molto semplice. Ecco come fare. mov ax,13h int 10h ; set mode 13h ; call BIOS service Una volta che ci troviamo in modalità 13h e abbiamo completato le operazioni, è necessario reimpostare la modalità precedente. Questo viene fatto in due fasi. Prima dobbiamo salvare la modalità video e poi re-impostarla al valore originario. VideoMode db ? . . mov ah,0Fh ; function 0Fh - get current mode int 10h ; Bios video service call mov VideoMode,al ; save current mode ; program code here mov al,VideoMode ; set previous video mode xor ah,ah ; clear ah - set mode int 10h ; call bios service mov ax,4C00h int 21h ; exit to dos ; call dos function Ora che sappiamo come entrare in modalità 13h, facciamo qualche operazione. Per prima cosa scriviamo alcuni pixel sullo schermo. Funzione 0Ch: Write Graphics Pixel Questa funzione imposta un punto colorato sullo schermo alle coordinate grafiche specificate. INPUT: AH = 0Ch AL = Colore del punto CX = Colonna dello schermo (coordinata x) DX = Riga dello schermo (coordinata y) OUTPUT: Nulla eccetto il pixel sullo schermo. Questo programma dimostra come disegnare dei punti. Disegna quattro pixel rossi nel mezzo dello schermo. Listing 17: PIXINT.ASM ; example of plotting pixels in mode 13 using bios services ; INT 10h .model tiny .code org 100h start: mov ax,13 int 10h ; mode = 13h ; call bios service mov mov mov mov int ah,0Ch al,4 cx,160 dx,100 10h ; ; ; ; ; function 0Ch color 4 - red x position = 160 y position = 100 call BIOS service inc int inc int dec int dx 10h cx 10h dx 10h ; ; ; ; ; ; plot call plot call plot call pixel downwards BIOS service pixel to right BIOS service pixel up BIOS service xor ax,ax int 16h ; function 00h - get a key ; call BIOS service mov ax,3 int 10h ; mode = 3 ; call BIOS service mov ax,4C00h int 21h ; exit to DOS end start Alcune ottimizzazioni Questo metodo non è troppo veloce e potremmo renderlo molto più veloce. Come? Scrivendo direttamente nella memoria video. Ciò si ottiene abbastanza facilmente. Il segmento di memoria VGA si trova all'indirizzo 0A000h. Per conoscere l'indirizzo di ciascun pixel, utilizzate questa semplice formula per ottenere l'offset. Offset = X + ( Y * 320 ) Tutto quello che dobbiamo fare è scrivere un valore a questa locazione, ottenendo così un pixel. Il numero rappresenta il colore desiderato. Ci sono due istruzioni che possiamo usare per scrivere un pixel sullo schermo. La prima possibilità è quella di utilizzare stosb per impostare il valore nel registro AL all'indirizzo ES:DI; la seconda possibilità è quella di sfruttare una nuova forma dell'istruzione MOV, come segue: mov es:[di], color Quale sarebbe meglio utilizzare? La scelta deve essere guidata dalla velocità di esecuzione, ovvero dal numero di cicli necessari per svolgere l'operazione. Minore è il numero di cicli, più veloce è l'operazione. Instruction Pentium 486 386 286 86 STOSB MOV AL to SEG:OFF 3 1 5 1 4 4 3 3 11 10 Se si utilizza l'istruzione MOV, bisogna ricordarsi di incrementare DI (operazione che STOSB effettua automaticamente). Esempi Qui di seguito sono riportati gli esempi illustrati durante le lezioni. Indirizzamento immediato // INDIRIZZAMENTO IMMEDIATO // L'operando sorgente è un valore numerico. asm { // Il registro AX viene impostato a zero XOR EAX,EAX // Il registro AX viene caricato impostando separatamente // la parte alta (AH) e la parte bassa (AL) con due operandi // a 8 bit MOV AH,00 MOV AL,04 // Il regsitro AX viene caricato con una singola istruzione // utilizzando un operando a 16 bit // (ogni numero esadecimale che inizia con una lettera deve // essere preceduto dallo 0) MOV AX,0FFFFH // La dimensione in bit dell'operando sorgente viene adattata // a quello dell'operando destinazione MOV AX,302 // Lo stesso vale per operandi a 8 bit MOV AL,-40 } Indirizzamento a registro // INDIRIZZAMENTO A REGISTRO // L'operando sorgente è contenuto in uno dei registri. asm { // Il contenuto di AX viene trasferito nel registro DX XOR EAX,EAX MOV AX,01234H MOV DX,AX // Si possono usare registri sorgente e destinazione a 8 bit XOR EAX,EAX MOV AL,056H MOV DL,AL } Indirizzamento diretto // INDIRIZZAMENTO DIRETTO // L'operando sorgente viene prelevato all'indirizzo di memoria // specificato. In genere l'operando è una label. DWORD dwDato = 0x12345678; // dwDato DD 012345678H WORD wDato = 0x1234; // wDato DW 01234H BYTE bDato = 0x12; // bDato DB 012H asm { // EAX viene caricato con il contenuto della locazione // di memoria dwDato (32 bit) XOR EAX,EAX MOV EAX,dwDato // AX viene caricato con il contenuto della locazione // di memoria wDato (16 bit) XOR EAX,EAX MOV AX,wDato // AL viene caricato con il contenuto della locazione // di memoria bDato (8 bit) XOR EAX,EAX MOV AL,bDato } Indirizzamento indiretto con registro // INDIRIZZAMENTO INDIRETTO CON REGISTRO // L'operando sorgente viene prelevato all'indirizzo di memoria // contenuto in un registro. DWORD dwDato = 0x12345678; // dwDato DD 012345678H WORD wDato = 0x1234; // wDato DW 01234H BYTE bDato = 0x12; // bDato DB 012H asm { // EAX viene caricato con il contenuto della locazione // di memoria dwDato (32 bit) XOR EAX,EAX LEA EBX,dwDato MOV EAX,[EBX] // AX viene caricato con il contenuto della locazione // di memoria wDato (16 bit) XOR EAX,EAX LEA EBX,wDato MOV AX,[EBX] // AL viene caricato con il contenuto della locazione // di memoria bDato (8 bit) XOR EAX,EAX LEA BX,bDato MOV AL,[EBX] } Indirizzamento relativo con registro base // INDIRIZZAMENTO RELATIVO CON REGISTRO BASE // L'indirizzo effettivo dell'operando sorgente viene determinato // sommando uno spiazzamento al contenuto di un registro base. char msg[] = "Ibis redibis non morieris"; // msg DB 'Ibis redibis non morieris','$' asm { // Imposta AL con il secondo carattere del messaggio XOR EAX,EAX LEA EBX,msg MOV AL,[EBX]+1 // Imposta AL con il terzo carattere del messaggio XOR EAX,EAX LEA EBX,msg MOV AL,2[EBX] // Imposta AL con il quarto carattere del messaggio XOR EAX,EAX LEA EBX,msg MOV AL,[EBX+3] } Indirizzamento diretto con registro indice // INDIRIZZAMENTO DIRETTO CON REGISTRO INDICE // L'indirizzo dell'operando viene calcolato sommando un indirizzo // di memoria al contenuto di un registro indice. char msg[] = "Ibis redibis non morieris"; // msg DB 'Ibis redibis non morieris','$' asm { // Imposta AL con il quarto carattere del messaggio XOR EAX,EAX MOV EBX,3 MOV AL,msg[EBX] } Indirizzamento con registri base e indice // INDIRIZZAMENTO CON REGISTRI BASE E INDICE // L'indirizzo dell'operando viene calcolato sommando il contenuto // del registro base con il contenuto del registro indice. char msg[] = "Ibis redibis non morieris"; // msg DB 'Ibis redibis non morieris','$' asm { // Imposta AL con il quarto carattere del messaggio XOR EAX,EAX LEA EBX,msg MOV ECX,3 MOV AL,[EBX][ECX] } Somma esadecimale con indirizzamento immediato // SOMMA ESADECIMALE CON INDIRIZZAMENTO IMMEDIATO // Somma di tre numeri utilizzando l'indirizzamento immediato. asm { // Azzera EAX SUB EAX,EAX // Carica AL con il valore 23H MOV AL,23H // Somma 0AH ad AL ADD AL,0AH // Somma 10H ad AL ADD AL,10H // Il risultato è in AL } Sottrazione esadecimale con indirizzamento diretto // // // // SOTTRAZIONE ESADECIMALE CON INDIRIZZAMENTO DIRETTO I numeri da sottrarre vengono referenziati tramite il loro indirizzo e vengono sottratti utilizzando la tecnica di indirizzamento diretto. WORD WORD WORD WORD NUM1 = 0x1234; NUM2 = 0x24; NUM3 = 0xABC; RISULTATO; // // // // NUM1 DW 1234H NUM2 DW 24H NUM3 DW 0ABCH RISULTATO DW ? asm { // Azzera EAX SUB EAX,EAX // Carica NUM1 nel registro AX MOV AX,NUM1 // Sottrae NUM2 a NUM1 SUB AX,NUM2 // Sottrae NUM3 al risultato precedente SUB AX,NUM3 // Memorizza il risultato in RISULTATO MOV RISULTATO,AX } Somma in precisione multipla con indirizzamento diretto // SOMMA IN PRECISIONE MULTIPLA CON INDIRIZZAMENTO DIRETTO WORD WORD WORD WORD WORD WORD NUM1 = 0x1234; NUM2 = 0x5678; NUM3 = 0x9ABC; NUM4 = 0xDEF0; RIS_LSW; RIS_MSW; // // // // // // NUM1 DW NUM2 DW NUM3 DW NUM3 DW RIS_LSW RIS_MSW asm { // Azzera EAX e EBX 1234H 5678H 9ABCH 0DEF0H DW ? DW ? SUB EAX,EAX SUB EBX,EBX // Carica il primo numero nel registro AX MOV AX,NUM1 // Somma il secondo numero al primo ADD AX,NUM2 // Se il Carry è a 1, viene sommato a BX ADC BX,00H // Somma il terzo numero al registro AX ADD AX,NUM3 // Se il Carry è a 1, viene sommato a BX ADC BX,00H // Somma il quarto numero al registro AX ADD AX,NUM4 // Se il Carry è a 1, viene sommato a BX ADC BX,00H // La somma AX viene copiata in RIS_LSW MOV RIS_LSW,AX // La somma BX viene copiata in RIS_MSW MOV RIS_MSW,BX } Somma in precisione multipla con indirizzamento con registro indice // // // // // SOMMA IN PRECISIONE MULTIPLA CON INDIRIZZAMENTO CON REGISTRO INDICE - Una tabella in memoria contiene gli addendi. - Un ciclo di programma scandisce la tabella. - L'indirizzamento con registro indice permette di accedere alle righe della tabella. WORD TABELLA[] = {0x1234, 0x9ABC, 0x1111, 0x3333, WORD RIS_LSW; WORD RIS_MSW; 0x5678, 0xDEF0, 0x2222, 0x4444}; asm { // Azzera EAX, EBX, ECX, EDX SUB EAX,EAX SUB EBX,EBX SUB ECX,ECX SUB EDX,EDX // Indirizzo di TABELLA in EBX LEA EBX,TABELLA // Indice a 0 MOV ESI,00H // TABELLA DW 1234H, 5678H // DW 9ABCH, 0DEF0H // DW 1111H, 2222H // DW 3333H, 4444H // RIS_LSW DW ? // RIS_MSW DW ? // Primo numero in AX MOV AX,TABELLA[ESI] // Numero di righe della tabella MOV CX,08H // Incrementa l'indice di una WORD (2 BYTE) ANCORA: ADD ESI,02H // Somma l'i-esimo elemento della tabella ADD AX,TABELLA[ESI] // Se il Carry = 1, viene sommato a DX ADC DX,00H // se CX non è 0, continua LOOP ANCORA // Tasferisce AX in RIS_LSW MOV RIS_LSW,AX // Tasferisce DX in RIS_MSW MOV RIS_MSW,DX } Somma di numeri decimali con indirizzamento indiretto con registro // SOMMA DI NUMERI DECIMALI CON INDIRIZZAMENTO INDIRETTO CON REGISTRO BYTE ADDENDI[] = {1,2,3,4,5,6,7,8,9,10}; // ADDENDI DB 1,2,3,4,5,6,7,8,9,10 BYTE RISULTATO; // RISULTATO DB ? asm { // Azzera EAX, EBX, ECX, EDX SUB EAX,EAX SUB EBX,EBX SUB ECX,ECX SUB EDX,EDX // Indirizzo di ADDENDI in EBX LEA EBX,ADDENDI // Imposta il contatore MOV CX,10 // Primo numero in AX MOV AL,ADDENDI // Incrementa l'indice di 1 BYTE AGAIN: ADD EBX,01H // Somma l'i-esimo addendo ADD AL,[EBX] // Conversione decimale della somma DAA // se CX non è 0, continua LOOP AGAIN // Copia il risultato in RISULATATO MOV RISULTATO,AL } Moltiplicazione per somme ripetute // MOLTIPLICAZIONE PER SOMME RIPETUTE BYTE MOLTIPLICANDO = 0x2A; // MOLTIPLICANDO DB 2AH BYTE MOLTIPLICATORE = 0x78; // MOLTIPLICATORE DB 78H WORD PRODOTTO; // PRODOTTO DB ? asm { // Azzera EAX, EBX, ECX, EDX SUB EAX,EAX SUB EBX,EBX SUB ECX,ECX SUB EDX,EDX // Moltiplicatore in CL MOV CL,MOLTIPLICATORE SUB CL,01H // Moltiplicando in AL MOV AL,MOLTIPLICANDO // Somma il moltiplicando al risultato parziale CICLO1: ADD AL,MOLTIPLICANDO ADC AH,00H // Se CL non è 0, continua LOOP CICLO1 // Risultato finale in PRODOTTO MOV PRODOTTO,AX } Moltiplicazione, elevamento al quadrato e al cubo con l'istruzione di moltiplicazione // MOLTIPLICAZIONE, ELEVAMENTO AL QUADRATO E AL CUBO CON // L'ISTRUZIONE DI MOLTIPLICAZIONE WORD NUM1 = 0x1234; // NUM1 DW 1234H WORD NUM2 = 0x00CD; // NUM2 DW 00CDH DWORD RIS1; // RIS1 DD ? DWORD RIS2; // RIS2 DD ? DWORD RIS3; // RIS3 DD ? asm { // Azzera EAX, EBX, ECX, EDX SUB EAX,EAX SUB EBX,EBX SUB ECX,ECX SUB EDX,EDX // MOLTIPLICAZIONE // Azzera il registro di overflow SUB DX,DX // NUM1 in AX MOV AX,NUM1 // Moltiplicazione di NUM1 con NUM2 MUL NUM2 // AX in LSB di RIS1 MOV WORD PTR RIS1,AX // DX in MSB di RIS1 MOV WORD PTR RIS1+2,DX // ELEVAMENTO AL QUADRATO // Azzera il registro di overflow SUB DX,DX // NUM1 in AX MOV AX,NUM1 // NUM1 moltiplicato per se stesso MUL NUM1 // AX in LSB di RIS2 MOV WORD PTR RIS2,AX // DX in MSB di RIS2 MOV WORD PTR RIS2+2,DX // ELEVAMENTO AL CUBO // Azzera il registro di overflow SUB DX,DX // NUM2 in AX MOV AX,NUM2 // AX = AX * NUM2 MUL NUM2 // AX = AX * NUM2 MUL NUM2 // AX in LSB di RIS3 MOV WORD PTR RIS3,AX // DX in MSB di RIS3 MOV WORD PTR RIS3+2,DX } Divisione con variabili di tipo doubleword // DIVISIONE CON VARIABILI DI TIPO DOUBLEWORD DWORD WORD WORD WORD DIVIDENDO = 0x2A8B7654; // DIVIDENDO DD DIVISORE = 0X5ABC; // DIVISORE DW QUOZIENTE; // QUOZIENTE DW RESTO; // RESTO DW 02A8B7654H 5ABCH ? ? asm { // Azzera EAX, EDX SUB EAX,EAX SUB EDX,EDX // LSB di DIVIDENDO in AX MOV AX,WORD PTR DIVIDENDO // MSB di DIVIDENDO in DX MOV DX,WORD PTR DIVIDENDO+2 // (DX AX) diviso per DIVISORE DIV DIVISORE // AX in QUOZIENTE MOV QUOZIENTE,AX // DX in RESTO MOV RESTO,DX } Estrazione di radice quadrata // ESTRAZIONE DI RADICE QUADRATA WORD NUMERO = 0x1CE4; // NUMERO DW 1CE4H WORD RADICE; // RADICE DW ? asm { // Azzera EAX, ECX, EDX SUB EAX,EAX SUB ECX,ECX SUB EDX,EDX // Numero da sottrarre MOV CX,01H // Sottrazione dell'i-esimo numero dispari CICLO2: SUB NUMERO,CX // Esce dal ciclo se NUMERO < 0 JL FINE // Incrementa il valore della radice quadrata INC DX // Calcola il successivo numero dispari ADD CX,02H // Ripete il ciclo JMP CICLO2 FINE: } // Il risultato è in RADICE MOV RADICE,DX Tabella di lookup per logaritmi // // // // TABELLA DI LOOKUP PER LOGARITMI Il programma determina il logaritmo di un numero, dopo aver memorizzato in forma tabellare i valori della funzione logaritmo per un predefinito insieme di numeri. WORD TABELLA[] = {0,3010,4771,6021,6990,7782,8451,9031,9542,10000}; // TABELLA DW 0,3010,4771,6021,6990,7782,8451,9031,9542,10000 BYTE NUMERO = 7; // NUMERO DB 7 WORD LOGARITMO; // LOGARITMO DW ? asm { // Azzera EAX, EBX, EDX SUB EAX,EAX SUB EBX,EBX SUB EDX,EDX // Indice per la ricerca MOV AL,NUMERO // Corregge l'indice (non esiste il logaritmo di 0) SUB AL,1 // Moltiplica l'indice per 2 (la tabella è fatta di WORD) ROL AL,1 // Indirizzo di TABELLA in EBX LEA EBX,TABELLA // Converte da BYTE a WORD CBW // Somma indirizzo e indice ADD BX,AX // Estrae il dato in tabella MOV DX,[EBX] // Risultato in LOGARITMO MOV LOGARITMO,DX } Conversione di codici mediante tabelle di lookup // CONVERSIONE DI CODICI MEDIANTE TABELLE DI LOOKUP // Il programma esegue la conversione di un numero esadecimale // nella codifica Gray equivalente. BYTE GRAY[] = {0x2,0x3,0x7,0x5,0x4,0xC,0xD,0xF,0xE,0xA}; // GRAY DB 0010B, 0110B, 0111B, 0101B, 0100B // DB 1100B, 1101B, 1111B, 1110B, 1010B BYTE VALORE = 8; // VALORE DB 8H BYTE RISPOSTA; // RISPOSTA DB ? asm { // Azzera EAX, EBX SUB EAX,EAX SUB EBX,EBX // Indirizzo della tabella di codifica in EBX LEA EBX,GRAY // Numero da convertire (indice tabella) in AL MOV AL,VALORE // Codifica in AL XLAT GRAY // Risultato in RISPOSTA MOV RISPOSTA,AL } Conversione da numeri ASCII a numeri esadecimali // // // // CONVERSIONE DA NUMERI ASCII A NUMERI ESADECIMALI Il programma converte un numero espresso in formato ASCII nel corrispondente numero in formato esadecimale. L'input si suppone compreso tra 30H e 46H. // ASCII // 30H // 31H // 32H // 33H // 34H // 35H // 36H // 37H // 38H // 39H // 41H // 42H // 43H // 44H // 45H // 46H HEX 0H 1H 2H 3H 4H 5H 6H 7H 8H 9H AH BH CH DH EH FH BYTE ASCII = 0x42; // ASCII DB 42H BYTE HEX; // HEX DB ? asm { // Azzera EAX SUB EAX,EAX // Numero ASCII da convertire in AL MOV AL,ASCII // Conversione SUB AL,30H // Il numero è compreso tra 0 e 9? CMP AL,9 // Salta se il numero è maggiore di 9 JG LETTERA // Vai alla fine JMP FINEPRG // Conversione per numeri maggiori di 9 LETTERA:SUB AL,07H // Risultato in HEX FINEPRG:MOV HEX,AL }