Assembly 8088
Short Description
Manuale di Assembly 8088...
Description
© Copyleft 2010 Fabian Priftaj
Appunti di programmazione in assembly 8088
Appunti di programmazione in linguaggio
Assembly 8088
“la presente dispensa rappresenta un riassunto della parte di programmazione in linguaggio assemblativo, tratta dal libro1 di testo usato a lezione”
1 Andrew S. Tanenbaum, Architettura dei calcolatori, Un approccio strutturale - 5^ed.
Pagina 1
© Copyleft 2010 Fabian Priftaj
Appunti di programmazione in assembly 8088
Indice generale PARTE 1, IL LINGUAGGIO ASSEMBLY 1.1 – Introduzione PARTE 2, IL PROCESSORE 8088 2.1 – Introduzione 2.2 – Ciclo di Neumann 2.3 – Registri dell'architettura 8088 2.3.1 – Registri ad uso generale 2.3.2 – Registri puntatore 2.3.3 – Registri indice 2.3.4 – Registro di flag 2.3.5 – Registro IP (Instruction Pointer) 2.3.6 – Registri di segmento PARTE 3, INDIRIZZAMENTO DELLA MEMORIA 3.1 – Introduzione all'indirizzamento 3.2 – Indirizzamento diretto 3.3 – Indirizzamento indiretto 3.4 – Indirizzamento con spiazzamento 3.5 – Indirizzamento a registro con indice 3.6 – Indirizzamento immediato 3.7 – Indirizzamento implicito PARTE 4, ISTRUZIONI DELL'8088 4.1 – Trasferimento, copia e aritmetica 4.2 – Cicli e operazioni iterative su stringhe 4.3 – Istruzioni di salto e di chiamata 4.3.1 – Salti condizionali 4.4 – Chiamate di subroutine 4.5 – Chiamate di sistema e subroutine di sistema PARTE 5, ASSEMBLATORE as88 5.1 – Introduzione all'assemblatore PARTE 6, ESEMPIO DI PROGRAMMAZIONE ASSEMBLY 6.1 – Esempio di “Hello, World!”
Pagina 2
© Copyleft 2010 Fabian Priftaj
Appunti di programmazione in assembly 8088
PARTE 1, LINGUAGGIO ASSEMBLATIVO 1.1 – Introduzione L'assemblatore è un programma che riceve in ingresso un file contenente un programma in linguaggio assembly, e genera un programma che contiene codice binario, pronto ad essere eseguito. Gli assemblatori usano nomi simbolici per variabili e costanti, ed etichette per le istruzioni e gli indirizzi di memoria.
PARTE 2, IL PROCESSORE 8088 2.1 – Introduzione Il processore è composto da un insieme di registri, utili per memorizzare le informazioni importanti per il suo funzionamento. Tra i vari registri, quello più importante è il registro PC (Program Counter), il quale contiene al suo interno l'indirizzo della prossima istruzione da eseguire. A volte questo registro viene detto anche IP (Instruction Pointer). 2.2 – Ciclo di Neumann L'attività dei processori consiste nell'esecuzione di istruzioni in rapida successione. L'esecuzione di ogni singola istruzione viene scomposta in più fasi: Fetch dell'istruzione dalla memoria tramite il registro PC Incremento l'indirizzo del registro PC Decodifica dell'istruzione prelevata Fetch dei dati necessari dalla memoria o dai registri Esecuzione dell'istruzione Memorizzazione dell'istruzione in memoria o nei registri Ritorno al punto iniziale 2.3 – Registri dell'architettura 8088 L'architettura 8088 ha a disposizione 14 registri, che costituiscono lo spazio di lavoro a disposizione del programmatore. I registri all'interno di questa architettura si dividono a seconda l'uso a cui sono destinati: Registri ad uso generale, Registri di segmento, Registri puntatore, Registri indici, Codici di condizioni, Registro Instruction Pointer (IP) . 2.3.1 – Registri ad uso generale I registri ad uso generale possono essere visti come registri di 16bit, oppure come delle coppie di registri formate da 8bit. Quando andiamo ad accedere al registri AX in realtà quello che stiamo facendo è accedere a due registri, ovvero AH:AL, dove AL rappresenta i primi 8bit del dato (partendo da sinistra) e AH rappresenta gli altri 8bit (quelli più significativi). I registri che fanno parte in questo gruppo sono: AX → registro accumulatore, viene usato spesso per contenere il risultato
Pagina 3
© Copyleft 2010 Fabian Priftaj
Appunti di programmazione in assembly 8088
delle operazioni. BX → registro di base, può essere usato al posto di AX, ma ha anche alcune funzioni che invece il registro AX non possiede. Ad esempio il registro BX può contenere un puntatore alla memoria, mentre AX no. Vediamo un esempio: MOV AX, BX “copia in AX il contenuto di BX” MOV AX,(BX) “copia in AX la parola di memoria che si trova all'indirizzo specificato da BX” CX → registro contatore, viene usato spesso nei cicli e viene decrementato automaticamente dall'istruzione LOOP e i cicli terminano quando CX ha valore zero (“0”). DX → registro dati, è usato spesso insieme al registro AX per contenere istruzioni lunghe 32 bit (DX:AX). 2.3.2 – Registri puntatore Tra questi registri troviamo il puntatore allo Stack detto SP (Stack Pointer). Un'altro registro importante è il BP (Base Pointer) il quale punta alla base dello Stack. Dunque il BP punta alla base del record di attivazione corrente (al suo indirizzo più grande) e SP punta alla cima del record di attivazione (al suo indirizzo più piccolo), perciò questi due registri delimitano il record di attivazione. 2.3.3 – Registri indice Appartengono a questo gruppo i due registri SI (indice sorgente), e DI (indice destinazione). Questi registri sono spesso usati in combinazione con BP per fare riferimento ai dati nello Stack, o con BX per localizzare i dati in memoria. 2.3.4 – Registro di flag E' un insieme di registri da 1bit ciascuno. Alcuni di questi bit rappresentano il risultato di espressioni aritmetiche, in questo modo: Z – risultato zero S – risultato negativo V – risultato di overflow C – risultato con riporto A – risultato con riporto ausiliario (oltre i 3bit) P – parità del risultato I – attivazione degli interrupt T – attiva la modalità di tracing D – controlla la direzione delle operazioni su stringhe 2.3.5 – Registro IP (Instruction Pointer) Il registro IP punta all'indirizzo dell'istruzione successiva a quella attualmente in esecuzione. Questo registro non viene direttamente usato dalle istruzioni.
Pagina 4
© Copyleft 2010 Fabian Priftaj
Appunti di programmazione in assembly 8088
2.3.6 – Registri di segmento L'architettura 8088 usa il concetto di segmento per distinguere le diverse porzioni di memoria. Esistono 4 segmenti: CS segmento di codice, Contiene le istruzioni del programma. Il registro PC è interpretato come un registro appartenente a questo segmento. DS segmento dati, Contiene i dati inizializzati e non del programma. SS segmento di stack, Contiene le variabili locali e i risultati intermedi. Gli indirizzi SP e BP fanno riferimento a questo segmento. Il segmento di Stack è costituito da parole di 2byte, per cui il puntatore allo stack (SP) contiene sempre un numero pari. Esso è riempito in senso decrescente a partire dagli indirizzi più grandi. Il comando PUSH decrementa di 2 il puntatore allo stack (SP), e memorizza l'operando in cima allo stack. Il comando POP recupera il valore in cima allo stack e incrementa di 2 il puntatore allo stack (SP). ES segmento extra, è un segmento di riserva che può essere allocato in memoria ovunque sia necessario. L'indirizzo iniziale di un segmento si ottiene estendendo i 16bit del registro concatenandogli 4 zeri sulla destra (nelle posizioni meno significative). Questo vuol dire che gli indirizzi dei segmenti indicano sempre indirizzi multipli di 16. In pratica un indirizzo fisico si calcola moltiplicando il registro di segmento per 16 e sommandogli l'offset. L'indirizzo fisico della successiva istruzione da eseguire si ottiene facendo scorrere il contenuto del registro CS di 4 posizioni a sinistra e sommando il valore del PC.
PARTE 3, INDIRIZZAMENTO DELLA MEMORIA 3.1 – Introduzione all'indirizzamento L'8088 mette a disposizione alcune modalità di indirizzamento per far riferimento ai dati in memoria. Molte istruzioni contengono 2 operandi chiamati in genere destinazione e sorgente. Ad esempio le istruzioni: MOV AX, BX ADD CX, 270
“esegue una copia del contenuto di BX in AX” “somma al contenuto di CX il valore 270”
L'architettura 8088 richiede in un istruzione, che almeno uno degli operandi sia un registro. Esistono alcune istruzioni a un operando (come gli incrementi, decrementi, scorrimenti, negazioni, etc.,). In questi casi non ci sono vincoli sui registri. L'8088 supporta 4 tipi di dati fondamentali:
Pagina 5
© Copyleft 2010 Fabian Priftaj
Appunti di programmazione in assembly 8088
byte, word (2 byte), long (4 byte) e decimali binari (BCD2, non supportati dal assemblatore in allegato al libro). Un indirizzo di memoria fa riferimento sempre ad 1 byte, ma in caso di riferimento ad una parola ci si riferisce implicitamente anche ai byte che seguono quello indicato. Ad esempio la parola all'indirizzo 20 comprende le locazioni di memoria 20 e 21. L'8088 assume un ordinamento di tipo littleendian, cioè la parte meno significativa di una parola è memorizzata all'indirizzo minore. All'interno dello Stack le parole devono essere allocate in corrispondenza degli indirizzi pari. La coppia di registri DX:AX è l'unica messa a disposizione dal processore per i dati long (AX contiene la parte meno significativa). 3.2 – Indirizzamento diretto Questa modalità di indirizzamento è quella più semplice, mediante la quale l'istruzione contiene l'indirizzo dei dati nell'operando stesso. Ad esempio l'istruzione: ADD CX, (20) specifica che bisogna sommare al contenuto del registro CX, il contenuto della parola di memoria agli indirizzi 20 e 21. 3.3 – Indirizzamento indiretto In questo caso l'indirizzo dell'operando viene memorizzato in uno dei registri BX, SI, DI. In tutti e tre i casi si tratta di un operando che fa riferimento al segmento dati. 3.4 – Indirizzamento con spiazzamento Questo tipo di indirizzamento è particolarmente utile per il trattamento degli array. Ad esempio se il registro SI = 5, è possibile caricare nel registro AL il quinto carattere della stringa FORMAT, con la seguente istruzione: MOVB AL, FORMAT(SI) L'intera stringa può essere scandita incrementando o decrementando il registro ad ogni passo. 3.5 – Indirizzamento a registro con indice L'indirizzamento a registro con indice è molto simile all'indirizzamento con spiazzamento. In questo caso si memorizza la base dell'array (cioè il suo indirizzo minore) nel registro BX e si usa il registro SI o DI per scandirlo. Ad esempio: PUSH (BX)(DI) L'istruzione effettua la somma degli indirizzi tra il registro BX e DI, e successivamente si posiziona al indirizzo dato dalla somma dei due registri, preleva il contenuto e lo posiziona in cima allo Stack. 3.6 – Indirizzamento immediato In questo caso l'operando dell'istruzione è una costante. Ad esempio: CMP AX, 50 2 Binary Coded Decimal
Pagina 6
© Copyleft 2010 Fabian Priftaj
Appunti di programmazione in assembly 8088
Confronta il registro AX con il valore 50 e memorizza l'esito del confronto nel registro dei flag. 3.7 – Indirizzamento implicito L'operando o gli operandi dell'istruzione sono specificati implicitamente dall'istruzione stessa: PUSH AX Pone in cima allo stack il contenuto di AX, e allo stesso tempo decrementa il valore di SP.
PARTE 4, ISTRUZIONI DELL'8088 4.1 – Trasferimento, copia e aritmetica MOV dest, src L'istruzione più utilizzata è MOV, che specifica una sorgente ed una destinazione. Se la sorgente è un registro, allora la destinazione deve essere un indirizzo effettivo. E' possibile anche avere per sorgente un indirizzo effettivo e per destinazione un registro. E' possibile fare riferimento anche alla parola chiave MOVB, per i trasferimenti di byte. Le istruzioni di trasferimento non hanno alcun effetto sul registro dei flag. E' necessario notare che le istruzioni di trasferimento non trasferiscono dati, ma copiano solamente. XCHG dest, src Quest'istruzione serve a scambiare il contenuto di un registro con il contenuto di un'indirizzo effettivo e viceversa. Esiste anche per questa parola chiave la sua versione per il trasferimento dei byte, XCHGB. LEA dest, src LEA sta per “Load Effective Address”, ovvero “Caricamento di un indirizzo effettivo”. Serve a calcolare il valore numerico dell'indirizzo effettivo e a memorizzarlo in un registro. (dest=registro, src=indirizzo effettivo). PUSH opr L'istruzione PUSH serve ad inserire operandi in cima allo Stack. L'operando in questione può essere una costante oppure un indirizzo. In realtà ogni volta che si inserisce nello Stack con quest'istruzione, viene “implicitamente” decrementato il valore del puntatore allo Stack (SP “Stack Pointer”) di 2 e viene inserito il valore alla nuova locazione in memoria puntata da SP. POP addr Rimuove l'operando dallo Stack e lo salva nell'indirizzo effettivo (addr). Anche in questo caso viene modificato il valore del registro SP, il quale viene incrementato di 2.
Pagina 7
© Copyleft 2010 Fabian Priftaj
Appunti di programmazione in assembly 8088
PUSHF opr | POPF addr Effettuano la PUSH e la POP del registro dei flag. XLAT Serve a caricare nel registro AL il byte che si trova all'indirizzo AL + BX. E' utile per fare ricerche rapide nelle tabelle di dimensioni pari a 256byte. IN e OUT Sono definite dall'8088 (ma non sono implementate nell'interprete fornito con il libro di Tanenbaum). Si tratta di istruzioni di trasferimento da/verso dispositivi di I/O. Altre istruzioni del linguaggio Assembly: Operatori aritmetici Tutte le istruzioni in questo gruppo modificano lo stato dei registri di flag, in base all'istruzione eseguita. ADD a,b “Somma: Aggiunge 'b' in 'a'” ADC a,b “Somma con riporto” SUB a,b “Sottrazione” SBB a,b “Sottrazione con prestito” MUL a,b “Moltiplicazione senza segno” DIV a,b “Divisione senza segno” IMUL a,b “Moltiplicazione con segno” IDIV a,b “Divisione con segno” Altri operatori aritmetici INC dest “Incremento della destinazione” DEC dest “Decremento della destinazione” NEG dest “Complemento binario” CMP a,b “Confronto tra gli operandi a e b” CLC “Azzeramento del flag di riporto” Operatori logici NOT dest “Complemento logico” AND a,b “a AND b” OR a,b “a OR b” XOR a,b “a XOR b” Operazioni di salto ICC et “Salto condizionato (et: etichetta)” 4.2 – Cicli e operazioni iterative su stringhe LOOP etichetta Decrementa il registro CX e se il risultato è positivo salta all'etichetta specificata. LOOPZ etichetta Confronta il registro CX con il registro di flag Z per decidere se interrompere l'esecuzione del ciclo. Se DEC(CX) ≥ 0 e Z=1 il ciclo si interrompe.
Pagina 8
© Copyleft 2010 Fabian Priftaj
Appunti di programmazione in assembly 8088
LOOPNZ etichetta Confronta il registro CX con il registro di flag Z per decidere se interrompere l'esecuzione del ciclo. Se DEC(CX)≥0 e Z=0 il ciclo si interrompe. Esistono anche meccanismi iterativi per il trattamento delle stringhe, disponibili dalle istruzioni REP, REPZ, REPNZ. Queste istruzioni prevedono che il registro SI punti al segmento dati, mentre DI al segmento extra localizzato dall'indirizzo ES. 4.3 – Istruzioni di salto e di chiamata L'operazione di salto più semplice è svolta dall'istruzione JMP che può contenere come destinazione un indirizzo oppure un etichetta. Esistono due tipi di salti: NEAR (vicino) e FAR (lungo). Si ha un salto corto quando la destinazione si trova nel segmento di codice corrente, che non può modificare durante l'esecuzione del programma. In caso di salto lungo invece il contenuto del registro CS (Code Segment) viene modificato. 4.3.1 – Salti condizionati L'8088 ha 15 istruzioni di salto condizionato: JNA (Jump if Not Above), JBE (Jump if Below or Equal)
Al di sotto o uguale
JNB (Jump if Not Below), JAE (Jump if Above or Equal), JNC (Jump if Not Carry)
Non al di sotto
CF=0
Zero, Uguale
ZF=1
Maggiore di..
SF=OF, ZF=0
JE (Jump if Equal), JZ (Jump if Zero) JNLE (Jump if Not Less than), JG (Jump if Greater)
Maggiore o uguale
JNL (Jump if Not Less than), JGE (Jump if Greater or Equal)
CF=1, ZF=0
SF=OF
Overflow
OF=1
Segno negativo
SF=1
CX=0
CX=0
JB (Jump if Below), JNAE (Jump if Not Above or Equal), JC (Jump if Carry)
Al di sotto
CF=1
JNBE (Jump if Not Below or Equal), JA (Jump if Above)
Al di sopra
CF=0 & ZF=0
JO (Jump if Overflow) JS JCXZ (Jump if CX Equal Zero)
JNE (Jump if Not Equal), JNZ (Jump if Not Zero) JL (Jump if Less than), JNGE (Jump if Not Greater or Equal)
Diverso da zero
ZF=0
Minore di..
SF≠OF
Minore o uguale
JLE (Jump if Less than), JNG (Jump if Not Greater than) JNO (Jump if Not Overflow) JNS
Pagina 9
SF≠OF, ZF=1
No overflow
OF=0
Non negativo
SF=0
© Copyleft 2010 Fabian Priftaj
Appunti di programmazione in assembly 8088
4.4 – Chiamate di subroutine L'8088 dispone di un'istruzione per la chiamata di procedura, detta anche subroutine. Analogamente alle istruzioni di salto, anche per le subroutine si fa distinzione tra chiamate vicine e chiamate a distanza (NEAR e FAR CALL). L'interprete di Tanenbaum implementa solo le NEAR CALL. La destinazione può essere un etichetta oppure un indirizzo. I parametri della subroutine devono essere inseriti nello Stack in ordine inverso. Dopo il PUSH degli argomenti si esegue l'istruzione CALL, che comincia con l'inserire sullo Stack il valore corrente del registro PC, per salvare l'indirizzo di ritorno (in modo da riprendere l'esecuzione del programma al termine della subroutine). L'istruzione RET recupera il vecchio valore del registro PC dallo Stack e lo scrive all'interno del PC, così l'esecuzione riprende dall'istruzione successiva alla CALL. Gli argomenti devono essere accessibili alla subroutine per cui, in genere, l'inizio di una nuova subroutine inizia con il PUSH del BP (Base Pointer), e successivamente si copia il valore corrente di SP in BP. Così facendo l'indirizzo base punta al suo valore precedente, e l'indirizzo di ritorno si trova alla posizione BP+2, mentre il 1° e 2° argomento si trovano rispettivamente agli indirizzi BP+4 e BP+6. Al ritorno da una subroutine è necessario copiare il puntatore base nel puntatore allo Stack, effettuare la POP del vecchio BP ed infine eseguire l'istruzione RET. 4.5 – Chiamate di sistema e subroutine di sistema Queste routine possono essere chiamate con la sequenza di chiamata standard: PUSH degli argomenti sullo Stack in ordine inverso PUSH del numero di chiamata che identifica la subroutine si esegue l'istruzione SYS senza parametri I risultati sono inseriti all'interno del registro AX, oppure nel registro DX:AX nel caso il risultato sia un long. Suggerimento: per comodità è possibile definire all'inizio del programma alcune costanti per i nomi delle chiamate di sistema, in modo da poterle chiamare per nome invece che per numero. OPEN *fname, 0/1/2 Questo comando serve per l'apertura di un file. Prende come primo argomento l'indirizzo della stringa contenente il nome del file. Il secondo argomento stabilisce la modalità di accesso al file: (0=lettura, 1=scrittura, 2=lettura/scrittura). Se il file è aperto in scrittura ma non esiste, la funzione provvede a crearlo. CREAT *fname, 0/1/2 La chiamata CREAT crea un file vuoto con i permessi specificati dal secondo argomento. La funzione OPEN e CREAT restituiscono entrambe un intero (come risultato dell'operazione) all'interno del registro AX; questo numero viene detto descrittore di file e può essere usato per la lettura, scrittura o chiusura del file. In caso il descrittore di file è negativo, indica un errore nella
Pagina 10
© Copyleft 2010 Fabian Priftaj
Appunti di programmazione in assembly 8088
chiamata di routine. READ fd, buffer, nbytes WRITE fd, buffer, nbytes Le chiamate READ e WRITE hanno 3 argomenti: il descrittore di file, un buffer per i dati e il numero di byte da trasferire. Siccome lo Stack usa la politica LIFO allora è necessario inserire gli argomenti in ordine inverso: PUSH del numero di byte da leggere/scrivere PUSH dell'indirizzo del buffer PUSH del descrittore di file PUSH del numero di chiamata che identifica la routine (READ o WRITE) CLOSE fd Questa chiamata serve a chiudere un file. Richiede solo il descrittore di file, e restituisce il valore 0 all'intero del registro AX per indicare che l'operazione ha avuto successo. EXIT status Inserisce sullo Stack lo stato di uscita del processo e non restituisce alcun valore. LSEEK fd, offset, 0/1/2 Modifica il puntatore di lettura/scrittura di un file già aperto. Il primo argomento è il descrittore di file, il secondo argomento è un dato long perciò va scomposto nelle due parole che lo costituiscono e va inserito nello Stack per prima la metà più significativa (anche nel caso in cui l'offset fosse contenuto tutto nella metà meno significativa). Il terzo argomento indica se il puntatore di lettura/scrittura va calcolato: (0=all'inizio del file, 1=alla posizione corrente, 2=alla fine del file). Il valore restituito è la nuova posizione del puntatore relativa all'inizio del file, memorizzato come long nei registri DX:AX. GETCHAR La funzione legge un carattere dall'input standard, lo salva nel registro AL e azzera AH. In caso di fallimento il registro AX vale 1. PUTCHAR char Scrive un byte sullo standard output e, in caso di successo restituisce lo stesso byte scritto, mentre restituisce 1 in caso di fallimento. PRINTF *format, arg Stampa informazioni formattate. Il primo argomento è l'indirizzo della stringa. Per formattare l'output sono presenti dei caratteri speciali: “%d” → notazione decimale “%x” → notazione esadecimale “%o” → notazione ottale “%s” → notazione di stringa Ad esempio la stringa “%d” indica che l'argomento successivo è un intero, presente sullo Stack, e che dovrà essere convertito in notazione decimale prima della stampa. SPRINTF buffer, *format, arg In questo caso la funzione riceve in input un buffer, che contiene la stringa che dovrà stampata sullo standard output. Gli altri argomenti sono gli stessi
Pagina 11
© Copyleft 2010 Fabian Priftaj
Appunti di programmazione in assembly 8088
della PRINTF. SSCANF buffer, *format, arg Questa funzione è il contrario della funzione PRINTF. Legge gli argomenti contenuti nel buffer seguendo il formato specificato all'indirizzo format secondo gli argomenti arg.
PARTE 5, ASSEMBLATORE as88 5.1 – Introduzione all'assemblatore Per la memorizzazione del codice e dei dati all'interno di un file .s, l'assemblatore usa 3 sezioni diverse: .TEXT (per le istruzioni del processore) .DATA (per la dichiarazione di variabili inizializzate) .BSS (per la dichiarazione di variabili non inizializzate) Ci sono due tipi di etichette, quelle globali (costituite da caratteri alfanumerici seguite dal simbolo “:”) e quelle locali (costituite da un unico carattere seguito dal simbolo “:”). Tutti gli identificatori del linguaggio sono definiti dai loro primi 8 caratteri: ad esempio BLOCKSIZE e BLOCKSIZZ sono lo stesso simbolo BLOCKSIZ. I valore numerici possono essere ottali, decimali (iniziano con 0), esadecimali (iniziano con 0x). Gli operatori logici sono & ^ ~ per le operazioni bitabit di AND, OR, NOT. Le espressioni possono contenere parentesi quadre, ma non tonde, per evitare di fare confusione con le modalità di indirizzamento. Vediamo alcune delle parole chiavi messe a disposizione dal assemblatore: .SECT .TEXT → rappresenta la sezione delle istruzioni .SECT .DATA → indica la parte dei dati inizializzati .SECT .BSS → parte dei dati non inizializzati .BYTE → definisce una sequenza di byte .WORD → definisce una sequenza di word .LONG → definisce una sequenza di long .ASCII “str”→ salva “str” senza inserire il carattere di terminazione .ASCIIZ “str”→ salva “str” inserendo il carattere di terminazione .SPACE n → incremento il contatore di locazioni di n posizioni .ALIGN n → allinea il contatore di loc. alla loc. che dista n byte .EXTERN → identifica un nome esterno La definizione di stringhe consente l'uso di caratteri di escape, tra cui: “\n” → Invio a capo “\t” → Tabulazione “\\” → Backslash “\b” → Backspace “\f” → Avanzamento modulo “\r” → Ritorno a capo “\”” → Virgolette
Pagina 12
© Copyleft 2010 Fabian Priftaj
Appunti di programmazione in assembly 8088
L'assemblatore as88 è modellato sugli assemblatori standard di UNIX, e come tale differenzia dagli altri assemblatori per l'8088: MASM (di Microsoft) e TASM (di Borland), entrambi progettati per sistemi DOS. MASM e TASM supportano entrambi i 3 modelli di memoria resi disponibili dall'architettura 8088: tiny , tutto il codice e i dati devono essere contenuti in 64Kb. small, il segmento di codice e dati possono raggiungere ciascuno i 64Kb. large, ammette più segmenti per il codice e per i dati. L'assemblatore as88 usa un modello di memoria che somiglia al modello small, anche se quando è usato senza il tracer, può gestire i registri di segmento senza restrizioni.
PARTE 6, ESEMPIO DI PROGRAMMAZIONE ASSEMBLY 6.1 – Esempio di “Hello, World!” Analizziamo un semplice programma in linguaggio Assembly, in grado di stampare sull'output video il testo “Hello World”
_EXIT = 1 _WRITE = 4 _STDOUT = 1 .SECT .TEXT start: MOV CX, dehw PUSH CX PUSH hw PUSH _STDOUT PUSH _WRITE SYS ADD SP, 8 SUB CX, AX PUSH CX PUSH _EXIT SYS .SECT .DATA hw: .ASCII "Hello World\n" de: .BYTE 0 .SECT .BSS
Pagina 13
! 1 ! 2 ! 3 ! 4 ! 5 ! 6 ! 7 ! 8 ! 9 ! 10 ! 11 ! 12 ! 13 ! 14 ! 15 ! 16 ! 17 ! 18 ! 19 ! 20 ! 21
© Copyleft 2010 Fabian Priftaj
Appunti di programmazione in assembly 8088
DETTAGLI L'istruzione .SECT .TEXT nella riga 4 specifica che le righe successive sono delle istruzioni al processore. Analogamente la riga 17 indica che le righe sottostanti fanno parte della sezione delle variabili inizializzate. La riga 19 inizializza una stringa di dati da 12byte, compresi lo spazio e il carattere di fine riga (\n). Le righe 5, 18 e 20 contengono delle etichette, riconoscibili dalla presenza del simbolo “:”. Queste etichette rappresentano valori numerici, per cui sono simili a delle costanti; tuttavia il valore numerico non può essere stabilito a priori, ed è quindi compito dell'assemblatore calcolarlo. L'etichetta start vale 0, ma il valore di un eventuale etichetta all'interno della sezione .TEXT dipenderebbe dal numero di byte che la precedono. Nella riga 6 si ha la differenza di due etichette. Questa riga è equivalente all'istruzione “MOV CX, 12”, con la differenza che in questo caso il calcolo della lunghezza della stringa è lasciato al programmatore, mentre nell'esempio è compito dell'assemblatore farlo. Le righe da 7 a 11 mostrano l'istruzione equivalente alla funzione in C: write(1, hw, 12), dove in questo caso il primo parametro indica il descrittore di file per lo standard output (1), il secondo parametro è l'indirizzo della stringa (hw), ed il terzo parametro rappresenta la lunghezza della stringa (12). La riga 10 impila sullo Stack il numero della chiamata write e la riga 11 effettua la chiamata. La riga 12 ripristina il puntatore allo Stack (SP) alla posizione precedente alla chiamata. Se l'operazione di scrittura ha avuto successo il numero di byte scritti è contenuto nel registro AX. La riga 13 sottrae il risultato della chiamata della riga 11 al valore del registro CX per stabilire se la chiamata ha avuto successo. Di conseguenza, lo stato di uscita del programma sarà 0 solo in caso di successo. Le righe 14 e 15 preparano l'esecuzione per la chiamata di sistema exit, impilando sullo Stack lo stato d'uscita e il codice della funzione EXIT.
Pagina 14
View more...
Comments