Programmare con l assembler dell Il linguaggio assembler. Il linguaggio assembler

Documenti analoghi
Programmare con l assembler dell 8088

Il set istruzioni di MIPS Modalità di indirizzamento. Proff. A. Borghese, F. Pedersini

Programmazione Assembly per 8088: Esercizi svolti

Architettura di una CPU

Istruzioni macchina. Dove sono gli operandi? Ciclo della CPU. Elementi di un istruzione macchina. Rappresentazione delle istruzioni

Linguaggio Assembly e linguaggio macchina

Famiglia dei processori INTEL

Linguaggio Assembly e linguaggio macchina

Architettura hardware

Linguaggio Macchina. Linguaggio Macchina. Linguaggio Macchina. Linguaggio Macchina ADD A,B ISTRUZIONE SUCCESSIVA

Architettura di un calcolatore e linguaggio macchina. Primo modulo Tecniche della programmazione

Il linguaggio assembly

Istruzioni assembler Istruzione N Registri

Linguaggio macchina. 3 tipi di istruzioni macchina. Istruzioni per trasferimento dati. Istruzioni logico/aritmetiche

Struttura CPU. Struttura e Funzione del Processore. Capitolo 12. Compiti CPU:

Componenti e connessioni. Capitolo 3

ARCHITETTURA DI UN SISTEMA DI ELABORAZIONE

Modi di indirizzamento

FONDAMENTI DI INFORMATICA Lezione n. 11

Accesso a memoria. Accesso a memoria. Accesso a memoria. Modalità di indirizzamento. Lezione 5 e 6. Architettura degli Elaboratori A.

Il linguaggio Assembly

Calcolatori Elettronici A a.a. 2008/2009

Modi di indirizzamento del processore MC68000 (parte prima)

Richiami sull architettura del processore MIPS a 32 bit

Corso di Architettura (Prof. Scarano) 09/04/2002

Informatica Teorica. Macchine a registri

Nel microprocessore 8086 abbiamo una gran quantità di registri

Somma di numeri binari

Elaborazione dell informazione

Calcolatori Elettronici Lezione A4 Programmazione a Moduli

Esercitazione n. 3. Dott. Salvatore Pontarelli

LINGUAGGIO MACCHINA e ASSEMBLER. Una CPU MINIMA Il linguaggio macchina di MINIMA Il linguaggio Assembler per MINIMA

Le etichette nei programmi. Istruzioni di branch: beq. Istruzioni di branch: bne. Istruzioni di jump: j

Mini-dispensa sui puntatori in C

Istruzioni di modifica della sequenza di elaborazione

Esercizi per il recupero del debito formativo:

Lecture 2: Prime Istruzioni

Architettura dei calcolatori e sistemi operativi. Sottoprogrammi e MIPS. Espressioni algebriche. Capitolo 2 P&H

Università degli Studi di Cassino

Cenni ad Assembly Intel

Il Ciclo Fetch-Decode-Execute. C Nyssen/Aberdeen College 2003

Calcolatori Elettronici Parte VIII: linguaggi assemblativi

Architettura dei computer

Corso di Calcolatori Elettronici MIPS: Istruzioni di confronto Istruzioni di controllo Formato delle istruzioni in L.M.

Università degli Studi di Roma La Sapienza

Input/output da file I/O ANSI e I/O UNIX FLUSSI E FILE FLUSSI FLUSSI di TESTO FLUSSI BINARI FILE

Rappresentazione dell Informazione

Il linguaggio del calcolatore: linguaggio macchina e linguaggio assembly

DAGLI ALGORITMI AI LINGUAGGI. Linguaggi di Programmazione

Il processore. Istituzionii di Informatica -- Rossano Gaeta

Capitolo 5 Elementi architetturali di base

ARCHITETTURA DEI MICROPROCESSORI INTEL 8086/8088

Algoritmo. La programmazione. Algoritmo. Programmare. Procedimento di risoluzione di un problema

La CPU e la Memoria. Sistemi e Tecnologie Informatiche 1. Struttura del computer. Sistemi e Tecnologie Informatiche 2

Rappresentazione dell informazione

Lezione 13: Il linguaggio assembly di LC-3

PASSI DI SVILUPPO DI UN PROGRAMMA: ESEMPIO

Analogico vs. Digitale. LEZIONE II La codifica binaria. Analogico vs digitale. Analogico. Digitale

Dal sorgente all eseguibile I programmi Assembly. Prof. Alberto Borghese Dipartimento di Scienze dell Informazione

Linguaggi e moduli. Dott. Franco Liberati

Funzioni, Stack e Visibilità delle Variabili in C

Lezione 4. Sommario. L artimetica binaria: I numeri relativi e frazionari. I numeri relativi I numeri frazionari

Laboratorio di Architettura degli Elaboratori

Corso di Informatica

Il Modello di von Neumann (2) Prevede 3 entità logiche:

Lezione4: MIPS e Istruzioni (1 Parte)

Fondamenti di Informatica - 1. Prof. B.Buttarazzi A.A. 2011/2012

Assembler. In verde sono evidenziati i comandi del debug. Attiva la scrittura di istruzioni assembler nella locazione 0100.

La codifica digitale

Primi passi col linguaggio C

Lezione 20. Assembly MIPS: Il set istruzioni, strutture di controllo in Assembly

LA CODIFICA DELL INFORMAZIONE. Introduzione ai sistemi informatici D. Sciuto, G. Buonanno, L. Mari, McGraw-Hill Cap.2

ARCHITETTURA DEI MICROPROCESSORI INTEL 8086/8088

Debug di un programma

Sistemi di numerazione

Calcolatori Elettronici Lezione A2 Architettura i8086

Università degli studi di Bologna Anno Accademico 2000/2001 Corso di Architettura degli elaboratori

Informatica ALGORITMI E LINGUAGGI DI PROGRAMMAZIONE. Francesco Tura. F. Tura

GESTIONE DELLA MEMORIA CENTRALE 6.1 D. - UNICAL

Programmi per la macchina di Mano addizione di due numeri

ARCHITETTURA DEL MICROPROCESSORE INTEL 8086 (iapx86/10)

Architettura degli Elaboratori

Calcolatori Elettronici Parte X: l'assemblatore as88

1. Si effettui la divisione di 7/5 utilizzando un efficiente algoritmo e illustrando la corrispondente architettura hardware.

Cos è un algoritmo. Si dice algoritmo la descrizione di un metodo di soluzione di un problema che sia

La codifica. dell informazione

Codifica binaria. Rappresentazioni medianti basi diverse

Breve guida AL LINGUAGGIO ASSEMBLY (emulatore EMU8086)

Organizzazione di un SO monolitico

La codifica. dell informazione

Laboratorio di Architettura lezione 5. Massimo Marchiori W3C/MIT/UNIVE

Architettura di un calcolatore

Lez. 5 La Programmazione. Prof. Salvatore CUOMO

Programmazione in Java (I modulo)

Lezione 3. I numeri relativi

PROBLEMI ALGORITMI E PROGRAMMAZIONE

perror: individuare l errore quando una system call restituisce -1

L Assembler Istruzioni Aritmetiche. M. Rebaudengo - M. Sonza Reorda. Politecnico di Torino Dip. di Automatica e Informatica

Elementi di Architettura

Riassunto. Riassunto. Ciclo fetch&execute. Concetto di programma memorizzato. Istruzioni aritmetiche add, sub, mult, div

Transcript:

Programmare con l assembler dell 8088 1 Il linguaggio assembler Al livello di astrazione più basso, un programma scritto usando il linguaggio macchina (o ISA) di un certo processore è formato da una sequenza di istruzioni descritte ciascuna mediante una sequenza di bit. Per ogni istruzione, i bit di cui è composta indicano il tipo di operazione da compiere (somma, complemento, salto ) e gli operandi su cui eseguire l operazione (registri della macchina o indirizzi delle celle di memoria) 2 Il linguaggio assembler Poiché scrivere programmi usando i numeri binari è estremamente scomodo, tutte le CPU permettono di usare una rappresentazione simbolica del linguaggio macchina, il linguaggio assembler Con l assembler è possibile usare nomi simbolici per le istruzioni (ADD, SUB ) e per gli operandi (registri e indirizzi delle celle di memoria) Un programma assemblatore trasformerà poi le istruzioni assembler in corrispondenti istruzioni macchina 3 1

Il linguaggio assembler In un qualsiasi assembler, le istruzioni macchina sono indicate da nomi mnemonici, ovviamente più facili da ricordare che non una sequenza di bit. Nomi simbolici (o symbolic names) possono essere usati per indicare valori costanti Labels (etichette) possono essere usate per fare riferimento ad una specifica istruzione (ad esempio la destinazione di un salto) e più in generale ad un indirizzo di memoria. 4 Il linguaggio assembler Di solito è anche possibile usare delle pseudoistruzioni, che permettono di dare comandi all assemblatore per guidare il processo di traduzione Un particolare tipo di pseudoistruzioni sono le macro, che possono essere usate per specificare in modo abbreviato una sequenza di istruzioni assembler: ogni volta che l assemblatore incontra in un programma una particolare macro, la sostituisce con la sequenza di istruzioni macchina associate a quella macro. 5 Il linguaggio assembler Per poter programmare in assembler è necessaria una conoscenza approfondita non solo delle istruzioni di cui è formato l assembler di un particolare processore: è necessario conoscere in dettaglio l architettura del processore stesso È la situazione opposta ai linguaggi ad alto livello, che (entro certi limiti) permettono di ignorare l architettura interna del processore su cui verranno fatti girare i programmi scritti in quei linguaggi 6 2

Il linguaggio assembler Imparare a programmare in assembler sui processori usati nei moderni computer è molto complicato, perché questi processori sono estremamente complessi e sofisticati. Inoltre, quando si scrive un programma in assembler, lo si compila (si dice più comunemente: lo si assembla ) per generare un programma binario, lo si esegue sulla CPU per cui è stato scritto, e il programma si comporta in maniera sbagliata perché contiene degli errori, è quasi sempre impossibile sapere cosa è andato storto 7 Il linguaggio assembler Per questa ragione, è molto più comodo poter lanciare e testare il programma non sul vero e proprio processore, ma su un simulatore, che permetta di: Eseguire il programma una istruzione una alla volta Fermare l esecuzione del programma ad un punto specificato, e poi riprenderla da quel punto Visualizzare lo stato interno del processore, ossia i valori contenuti nei vari registri e nella memoria primaria (in alcuni casi) Modificare il contenuto di una qualsiasi registro o cella di RAM mentre l esecuzione del programma è sospesa in un certo punto. 8 Il linguaggio assembler Naturalmente, i programmi eseguiti su un simulatore girano molto più lentamente che se fossero eseguiti sul processore simulato, ma l uso del simulatore rende estremamente più semplice l apprendimento del linguaggio assembler e il debugging dei programmi. Il simulatore è più comunemente chiamato interpreter o tracer, proprio perché interpreta i programmi e permette di tracciarne l esecuzione. Nota: tutte le figure di questi lucidi sono tratte dalla sezione C del Tanenbaum 9 3

Il linguaggio assembler Il tracer t88. (comando: t88 HlloWrld) (a): il programma; (b): una schermata del tracer 10 Il processore 8088 L 8088 è uno degli antenati del moderni processori Intel: introdotto nel 1979, era una macchina a 16 bit, clock a 5 8 MHz, spazio di indirizzamento da 1 MB, era usato nei primi PC della IBM. Molte delle istruzioni dell 8088 si ritrovano nell ISA dei processori più moderni (ma a 64 o 32 bit anziché a 16 bit) e imparare a programmare l 8088 è una buona introduzione per la programmazione assembler dei processori Intel moderni La semplicità delle istruzioni dell 8088 le rende simili a quelle di molte architetture RISC più moderne. 11 funzionamento dell 8088 L 8088 ha un datapath molto semplice, non pipelined (ricordate che nella famiglia 80x86 la pipeline è comparsa solo col 486) e senza cache. Ogni singola istruzione viene eseguita nei seguenti passi: 1. prelievo dell istruzione puntata dal PC dalla RAM 2. incremento del PC 3. decodifica dell istruzione 4. prelievo degli operandi dalla memoria o dai registri 5. esecuzione dell istruzione 6. immagazzinamento del risultato in memoria o in un registro 7. goto 1 (Le prossime figure: Tanenbaum, Fig. C-2.) 12 4

registri generali dell 8088 L 8088 ha 4 registri generali a 16 bit, ciascuno suddiviso in 2 registri da 8 bit per le operazioni che lavorano su dati di tipo byte e caratteri. Sebbene i registri possano essere usati come registri generali, essi possono anche avere un uso specifico : AX: accumulator. Comunemente usato per contenere il risultato di alcune istruzioni (ad esempio la MUL. E una forma di indirizzamento implicito). MUL (addr) //AX = AX dato il cui indirizzo è addr BX: base register. Se usato tra parentesi specifica la word in RAM il cui indirizzo è contenuto in BX. MOV AX, (BX) //AX = dato il cui indirizzo è in BX 13 registri generali dell 8088 CX: counter register. Usato per contenere il contatore per gestire i loop. Viene automaticamente decrementato dalla istruzione LOOP, fino a che CX = 0. MOV CX, 10 lb:...... LOOP lb // decrementa CX, salta se CX > 0 DX: data register. Usato insieme ad AX nelle operazioni a 32 bit. 14 registri generali dell 8088 DX: data register. Usato insieme ad AX nelle operazioni a 32 bit. In questo caso, DX conterrà i 16 bit più significativi, e AX i restanti 16 bit meno significativi. DX ha quindi la funzione specifica di permettere di elaborare dati di tipo long Ogni registro a 16 bit è diviso in due parti che possono essere manipolate separatamente. Ad esempio: 1. ADDB AH, AL // AH = AH + AL 2. MOV AX, 258 // AH e AL ora contengono i valori 1 e 2 Se eseguiamo la 1 dopo la 2 AX conterrà il valore 770 (0302 16 ) 15 5

Lo stack segment dell 8088 Nell 8088, ad un programma è sempre associato una porzione di RAM da 64KB per contenere lo stack del programma 0 Quando il programma chiama una procedura (ad esempio A nella figura), nello stack vengono memorizzati gli argomenti della procedura, eventuali variabili locali, e l indirizzo di ritorno quando la procedura termina. La porzione di stack che contiene le informazioni di A è detta stack frame Se dentro A viene chiamata la procedura B, sulla cima dello stack viene allocato uno stack frame per B. Quando una procedura termina, il suo stack frame viene rimosso Stack frame della procedura B Stack frame della procedura A 65536 16 Lo stack segment dell 8088 Lo stack parte dall indirizzo più alto del segmento da 64 KB, e cresce verso il basso, ossia occupando man mano i byte di indirizzo più basso (questa è una scelta comune a molti processori). Quando si parla della cima, o top dello stack, si fa quindi in realtà riferimento al dato nello stack memorizzato all indirizzo più basso La chiamata di una procedura (istruzione CALL) fa quindi sempre espandere lo stack, in quanto viene allocato un nuovo stack frame Il ritorno da una procedura riduce invece la dimensione dello stack, perché viene deallocato lo stack frame della procedura terminata. 17 registri puntatori dell 8088 I registri puntatori contengono un indirizzo di RAM: SP: stack pointer. Punta sempre alla cima dello stack in RAM. Viene quindi decrementato dalla chiamata di una procedura e viene incrementato quando la procedura termina (perché il suo stack frame viene rimosso). Lo stack può essere anche usato per contenere risultati parziali del programma in esecuzione, mediante l uso delle istruzioni push e pop: PUSH AX // SP = SP-2; (SP) = AX POP AX // AX = (SP); SP = SP+2 18 Notate che la PUSH decrementa SP, mentre la POP incrementa SP. 6

registri puntatori dell 8088 BP: base pointer: può contenere un indirizzo qualsiasi dello stack, ma di solito viene usato per puntare all inizio dello stack frame sulla cima dello stack, ossia lo stack frame della procedura in esecuzione (questo stack frame è quindi delimitato da BP e SP). In questo caso, attraverso BP è possibile risalire alle variabili locali della procedura stessa (vedremo meglio più avanti) SI e DI vengono usati insieme a BP per indirizzare dati nello stack, oppure insieme a BX per indirizzare dati nel segmento dati del programma in esecuzione. Ad esempio: LODS (senza argomenti) usa il contenuto di SI per indirizzare il DATA SEGMENT e mette la word indirizzata in AX. SI viene automaticamente incrementato di 2 per puntare alla word successiva. 19 registri speciali dell 8088 IP: instruction pointer. Non è altro che il Program Counter dell 8088 (ma ovviamente Intel deve cambiare i nomi standard...) Flag Register. E la Program Status Word del processore, ossia un registro di bit (alcuni inutilizzati): ecco il significato di alcuni: Z: il risultato dell ultima operazione eseguita è zero (Zero bit) S: il risultato dell ultima operazione eseguita è negativo (Negative bit) O: il risultato dell ultima operazione ha generato overflow (Overflow bit) C: il risultato dell ultima operazione ha generato un riporto (Carry bit) I: abilita/disabilita gli interrupt 20 I registri dei segmenti dell 8088 Un eseguibile 8088 è composto da 3 segmenti di RAM grandi al massimo 64KB: il codice, i dati e lo stack. L indirizzo di partenza di ciascun segmento è contenuto nel corrispondente registro. E indirizzabile un ulteriore segmento extra attraverso il registro ES. In realtà, di solito i segmento dei dati e dello stack coincidono. Però, lo stack viene allocato nella parte alta del segmento, e cresce verso il basso, mentre le strutture dati sono allocate a partire dalla prima locazione del segmento. Notate che in assembler le variabili di un programma possono essere accedute solo attraverso il loro indirizzo (che però può essere 21 associato ad un nome, una label, per comodità) 7

Organizzazione della memoria il PC punta sempre una locazione interna al code segment: se PC = 0 viene puntata la prima locazione del code segment, non l indirizzo di RAM 0. l 8088 è in grado di gestire 1MB di RAM. Come è possibile con registri da 16 bit? L indirizzo di partenza di ogni segmento è sempre un multiplo di 64 KB, ed è un indirizzo su 20 bit costruito aggiungendo 4 bit a zero al fondo del corrispondente segment register (ossia quattro bit meno significativi vengono aggiungi all indirizzo su 16 bit). Quindi, un indirizzo assoluto è costruito moltiplicando il contenuto del corrispondente segment register per 16 e aggiungendo l indirizzo specificato all interno di quel segmento. 22 Organizzazione della memoria Ad esempio, se per un programma DS = 7 e BX = 12 (in decimale), l istruzione MOV AX, (BX) mette in AX il dato di 16 bit situato all indirizzo in RAM: 7 16 + 12 = 124. In altre parole, l indirizzo binario su 20 bit implicato da DS = 7 è 00000000000001110000. A questo va aggiunto BX = 12 = 1100 ottenendo 00000000000001111100 (124 decimale) Per ogni riferimento in memoria uno dei segment register è usato nel modo visto per generare l effettivo indirizzo in RAM: la trasformazione di un indirizzo da 16 bit a 20 bit viene ovviamente fatta tutta a livello hardware. 23 Indirizzamento della memoria Nell assembler di un qualsiasi processore deve essere specificato in quali modi le istruzioni possono fare riferimento ai dati in RAM, ossia, come possono indirizzare la memoria. Nelle macchine RISC poi le diverse modalità possono essere usate solo nelle istruzioni LOAD e STORE, mentre nelle macchine CISC anche le altre istruzioni possono usare le diverse modalità per fare riferimento ai dati in RAM (eventualmente con alcune restrizioni, ad esempio: nelle istruzioni con almeno due operandi, uno dei due deve necessariamente essere un registro) 24 8

Indirizzamento della memoria Nel caso dell 8088, un indirizzo in RAM specifica sempre un byte ben preciso, ma se si stanno usando argomenti a 16 bit (il caso più frequente) o a 32 bit (per i long), l indirizzo specifica implicitamente anche il byte successivo o i tre byte successivi. Ad esempio: ADD CX (20) Somma a CX il contenuto della word da 2 byte memorizzata agli indirizzi 20 e 21 della RAM, e mette il risultato in CX (poiché l 8088 adotta l ordinamento little endian, il byte meno significativo di ogni word è memorizzato all indirizzo più basso) 25 Indirizzamento della memoria Gli assembler di tutti i processori adottano modalità per specificare gli operandi più o meno simili fra loro, incluse le modalità dell 8088. Ecco qui di seguito la specifica completa di tutte le modalità di indirizzamento dell 8088, che deve ovviamente specificare anche gli operandi che non sono variabili in RAM: i registri e i valori immediati (prossime 2 figure: Tanenbaum, Fig. C-3) 26 Indirizzamento della memoria Notate che quando parliamo di indirizzamento di un dato in RAM, ci riferiamo evidentemente alle informazioni contenute nel segmento dati (il simbolo # indica un valore immediato: un numero o una label) 27 9

Indirizzamento della memoria Le principali modalità di indirizzamento della RAM sono: Indirizzamento diretto, in cui l istruzione contiene l indirizzo dell operando: ADD CX, (20) // CX = CX + [20] Indirizzamento a registro indiretto, in cui l indirizzo dell operando è contenuto in un registro (BX, SI o DI) ADD CX, (BX) // CX = CX + [BX] 28 Indirizzamento della memoria Indirizzamento con spiazzamento, in cui il valore contenuto nel registro viene sommato ad una costante numerica per formare l indirizzo ADD CX, 10(BX) // CX = CX + [10+BX] Vi sono modalità di indirizzamento della RAM attraverso i registri più sofisticate, ad esempio: l indirizzamento a registri con indice: PUSH (BX) (DI) che inserisce sulla cima dello stack il contenuto della cella di memoria il cui indirizzo è dato dalla somma dei valori in BX e DI Tutte le modalità di indirizzamento che abbiamo visto (ma ve ne sono altre) permettono di specificare quelli che si chiamano: 29 indirizzi effettivi Istruzioni dell 8088 E impossibile scrivere programmi in assembler senza conoscere a fondo e nei dettagli le istruzioni dell ISA del processore su cui stiamo lavorando. Per comodità le istruzioni vengono suddivise in un insieme di classi più o meno omogenee, perché hanno caratteristiche simili nella sintassi e nell effetto che hanno sullo stato del processore. Nei prossimi lucidi vediamo alcune delle istruzioni più significative dell 8088. In generale, per usare l assembler di una qualsiasi CPU è necessario fare riferimento al manuale dell assembler di quella CPU che descrive in dettaglio tutte le istruzioni riconosciute e il loro effetto sul processore (prossime 3 figure: Tanenbaum, Fig. C-4) Potete trovare una descrizione completa degli instruction set x86 in rete 30 10

Istruzioni dell 8088 Trasferimento, copia e operazioni aritmetiche MOV: è l istruzione di gran lunga più usata, e permette di: Copiare quanto indicato da un indirizzo effettivo (e) in un registro (r) o viceversa (r! e, e! r) Copiare un dato immediato (#) in un indirizzo effettivo (e! #) 31 Istruzioni dell 8088 Trasferimento, copia e operazioni aritmetiche La lettera (B) indica l istruzione equivalente su operandi di tipo byte. Ad esempio: MOVB AH, (20) Notate la colonna status flag : il gruppo di istruzioni da MOV a XLAT non modifica i flag del flag register ADD e SUB: sono ovviamente le operazioni di somma e sottrazione. Possono avere effetto sugli status flag: O (overflow) viene settato a 1 se il risultato dell istruzione non può essere espresso con i 16 bit a disposizione. Viene resettato a 0 altrimenti Gli altri flag vengono settati/resettati concordemente con quanto indicato nel lucido 20 ADC e SBB usano il Carry bit (il bit/flag di riporto) per poter lavorare con interi a 32 bit 32 Istruzioni dell 8088 Trasferimento, copia e operazioni aritmetiche MUL, DIV: notate come queste istruzioni abbiano un solo operando esplicito: MUL (BX) // DX:AX = AX [BX] L Overflow ed il Carry bit vengono settati a 1 quando il risultato non può essere rappresentato in una word, i bit S e Z rimangono indefiniti DIV (BX) // AX = quoziente di AX / [BX] // DX = resto di AX / [BX] Tutti gli status bit rimangono indefiniti, ma un interrupt viene inviato in caso di divisione per zero. IMUL, IDIV permettono di lavorare con valori signed 33 11

Istruzioni dell 8088 Operazioni logiche, di shift e sui bit CBW e CWD operano sulla copia DX:AX Le RCL, RCR coinvolgono anche il Carry bit, e quindi di fatto danno luogo ad operazioni di shift circolare a 17 bit. SHR sposta tutti i bit a destra di uno, aggiungendo uno zero sulla sinistra. 34 Istruzioni dell 8088 Operazioni di test e manipolazione dei flag Queste istruzioni vengono usate principalmente per produrre la condizione da usare nelle istruzioni di salto condizionato. TEST: viene fatto l AND bit a bit tra i due operandi. Se sono tutti uguali viene settato a 1 il flag Z (Zero bit) CMP: viene fatta la differenza tra i due operandi, e i vari flag vengono settati corrispondentemente. 35 Istruzioni dell 8088 Cicli e operazioni su stringhe LOOP: decrementa CX e salta alla label se CX > 0. Il salto deve essere nell intervallo [ 128, +127] byte, perché è specificato su 8 bit con segno Notate che non si può dire quante istruzioni si possono saltare al massimo perché le istruzioni x86 non hanno lunghezza fissa. Le altre istruzioni LOOP testano anche il flag Zero REP CMPS eseguono operazioni ripetute su intere stringhe in RAM. Questo è un tipico esempio di istruzioni CISC Errore: togliete back errore: è CX > 0 36 12

Istruzioni dell 8088 Istruzioni di salto e salto condizionato JMP xxx: dove xxx può essere una label o un effective address, e il salto deve rimanere nel range [ 32768, +32767] byte I salti condizionati (spesso usati subito dopo una TEST o una CMP), ammettono uno spiazzamento massimo di 128 byte Per saltare più lontano si usa la tecnica jump over jump. Ad esempio, il branch: diventa: JE far_label JNE skip JMP far_label skip: 37 Istruzioni dell 8088 Chiamate di Subroutine (procedure) All interno di un programma assembler è possibile definire delle subroutine, che possono essere chiamate con l istruzione CALL Le subroutine possono avere degli argomenti, che il codice della subroutine si aspetta di trovare sullo stack. Prima di chiamare la subroutine quindi, il programma chiamante deve mettere gli argomenti sullo stack (in ordine inverso, se si fa riferimento alla sintassi del C) usando una PUSH per ciascun argomento. Quando viene eseguita la CALL, per prima cosa il valore corrente del PC viene automaticamente caricato sullo stack, in modo da salvare l indirizzo di ritorno dalla subroutine (return address): ossia da quale istruzione prosegue l esecuzione dopo la terminazione della subroutine 38 Chiamate di Subroutine Istruzioni dell 8088 Dopo il salvataggio dell indirizzo di ritorno sullo stack, nel PC viene scritto l indirizzo della prima istruzione della subroutine, che corrisponde all argomento (label o indirizzo effettivo) dell istruzione CALL L ultima istruzione della subroutine deve essere RET, che semplicemente preleva l indirizzo di ritorno sulla cima dello stack e lo scrive nel PC RET può avere un argomento numerico: il numero di byte occupati dagli argomenti passati alla subroutine. Viene automaticamente aggiunto a SP per pulire lo stack alla fine della subroutine. 39 13

Istruzioni dell 8088 Codice delle Subroutine Per semplificare il ritorno da una subroutine, le prime istruzioni della subroutine sono di solito le seguenti (vedremo poi perché): PUSH BP // salva BP sullo stack. MOV BP, SP // ora BP punta alla cima dello stack, dove // è memorizzato OLD BP. BP = SP Return address si trova ora al indirizzo BP+2, e Argument 1 e Argument 2 si trovano a BP+4 e BP+6 Ecco esempi di come possono essere acceduti gli argomenti all interno della subroutine: MUL 4(BP) ADD AX, 6(BP) 40 Codice delle Subroutine Istruzioni dell 8088 Il codice della subroutine può usare lo stack nel modo appena visto, per passare argomenti e chiamare un altra subroutine. Ma lo stack si può anche usare come spazio per allocare variabili locali alla procedura, che sono riferite attraverso BP. BP e SP delimitano lo stack frame corrente: SP cambia col variare della dimensione dello stack frame corrente, mentre BP punta alla base dello stack frame, e quindi rimane costante finché quel frame è attivo) Così, ad esempio: ADD -2(BP), AX usa i primi due byte dello stack frame per salvare un risultato parziale 41 Istruzioni dell 8088 Ritorno dalla Subroutine Il codice della subroutine può allocare un numero imprecisato di variabili locali sullo stack, e soprattutto un numero che può variare da una chiamata all altra. Senza dover calcolare ogni volta la quantità di stack usato dalla subroutine, quando questa termina il suo stack frame può essere rimosso, e si può riattivare lo stack frame della procedura chiamante così: MOV SP, BP POP BP // BP " OLD BP RET Notate che questo non è altro che il complemento di quanto fatto all inizio 42 14

Istruzioni dell 8088 Precauzioni nella chiamata di una Subroutine La subroutine può ovviamente utilizzare i registri generali della macchina. Se il codice chiamante ha messo dei valori significativi in questi registri, prima di chiamare la subroutine dovrebbe salvarne il contenuto, ad esempio sullo stack. Con quattro registri generali in tutto, è molto probabile che ciò si renda necessario Ecco un esempio di come, avere molti registri a disposizione, riduce la probabilità di dover ricorrere all uso di spazio temporaneo in memoria principale Se si scrive codice in un linguaggio ad alto livello, tutti questi dettagli devono essere gestiti in modo automatico dal compilatore, una cosa assolutamente non banale 43 Subroutine di Sistema Istruzioni dell 8088 Un qualsiasi processore possiede delle istruzioni per comunicare con l esterno, usando opportune porte di comunicazione. Ad esempio, nell 8088 l istruzione: OUT portnumber, AX Scrive il contenuto di AX sulla porta di uscita specificata da portnumber Queste istruzioni sono ovviamente necessarie per operare sui file, e in generale per implementare le operazioni di input/output del processore, ma per permettere ad un interprete di funzionare su piattaforme e sistemi operativi diversi, vengono sostituite da un insieme opportuno di subroutine di sistema 44 Istruzioni dell 8088 Subroutine di Sistema Essendo delle subroutine, sono chiamate come abbiamo visto: 1) gli argomenti vengono inseriti sullo stack in ordine inverso 2) viene inserita sullo stack la label/nome della subroutine 3) viene chiamata la system trap SYS 45 15

Istruzioni dell 8088 Subroutine di Sistema Eventuali valori di ritorno vengono messi in AX, o in DX:AX Le subroutine di sistema garantiscono che tutti i registri (a parte AX e DX) non vengono modificati dalla subroutine stessa. Quando la subroutine di sistema termina, lascia sullo stack gli argomenti con cui è stata chiamata, che possono essere cancellati (basta modificare opportunamente SP) o riutilizzati per un altra chiamata. 46 Programmare in Assembler Come abbiamo già osservato, sviluppare programmi in assembler per un processore, è un buon modo per conoscere a fondo e vedere all opera l architettura di quel processore. Un programma scritto in assembler sarà assemblato da un assemblatore, e potrà poi essere eseguito su un processore che adotti quel linguaggio macchina. Ovviamente, il programma può contenere errori, che però sono difficili da trovare semplicemente eseguendo il programma. Per questa ragione, lo sviluppo di programmi assembler è di solito portato avanti attraverso un simulatore della macchina che permette di simulare appunto l esecuzione dei programmi sul processore per cui sono stati sviluppati. Il simulatore della macchina interpreta le istruzioni di un programma e permette di tracciarne l esecuzione: viene quindi di solito chiamato interpreter o tracer 47 Programmare in Assembler Ma un simulatore svolge di solito anche funzioni di debugger: permette in fatti di eseguire il programma passo passo, una sola istruzione per volta, tracciando il contenuto dei registri della macchina che simula mano a mano che le istruzioni li usano e li modificano. Naturalmente, il simulatore permette di fare molte altre cose, ad esempio di eseguire n istruzioni, di inserire dei breakpoint e di eseguire il programma fino al raggiungimento di uno specifico breakpoint, di visualizzare il contenuto di un segmento di memoria, e così via. Nel caso di processori embedded, è comodo usare il simulatore di un processore per sviluppare applicazioni che dovranno girare su quel processore, lavorando però in un ambiente più user-friendly, (ossia usando il simulatore in ambiente Windows o Unix) 48 16

Programmare in Assembler Sebbene vi sia una corrispondenza 1 a 1 tra istruzioni assembler e istruzioni macchina, l assembler permette di programmare usando nomi simbolici per le istruzioni e i registri: l assemblatore genererà poi il codice macchina vero e proprio, formato da sequenze di bit. L assembler permette inoltre di usare etichette (in inglese label), per indicare specifiche posizioni in memoria: la posizione di una variabile in RAM o una istruzione a cui deve saltare un branch Durante la generazione del codice macchina l assemblatore mappa le label del programma ai valori binari che rappresentano: indirizzi della RAM Un assembler permette anche di definire costanti: ossia associazioni tra valori numerici e nomi simbolici. 49 Una nota sugli assemblatori Il compilatore di un programma scritto in assembler viene comunemente detto assemblatore L output di un assemblatore è un file oggetto Spesso, un programma usa subroutines contenute in file oggetto separati. Il linker mette insieme tutti i file oggetto e produce un file eseguibile binario, che può essere caricato in memoria ed eseguito. Mentre l assemblatore produce il codice oggetto, scrive in una Symbol table i nomi di tutte le costanti e le label che incontra, insieme con il loro valore numerico. Però, mentre le costanti definite all inizio del programma in fase di compilazione possono essere immediatamente associate al loro valore numerico nella symbol table, per le label non è così ovvio. 50 Una nota sugli assemblatori Infatti una label può fare riferimento ad una locazione che ha un indirizzo maggiore di quello del punto in cui compare la label. Ad esempio come in questo caso: JB mylabel MUL (BX) mylabel: ADD AX, BX Quando il compilatore incontra la prima occorrenza di mylabel, non sa ancora a quale indirizzo fa riferimento. Per gestire queste situazioni, l assemblatore deve scandire due volte il codice del programma 51 17

Una nota sugli assemblatori Nella prima passata, ogni riga di codice viene analizzata e un location counter viene incrementato del numero di byte necessari per contenere quella riga. Ad esempio, nel caso: mylabel: ADD AX, BX JB mylabel //occupa 2 byte MUL (BX) //occupa 3 byte Quando l assemblatore incontra JB mylabel inserisce una nuova entry mylabel nella symbol table. Quando nel programma incontra mylabel: associa alla entry mylabel della symbol table il valore corrente del location counter Nella seconda passata il valore numerico di ogni label è noto, e può iniziare la generazione del codice binario (ad esempio, JB mylabel può essere trasformato in JB 5, e poi 52 in una sequenza di cifre binarie) l ACK-Based Assembler as88 Per un dato processore, possono essere disponibili diversi assemblatori/linker. Ovviamente tutti producono codice binario in grado di girare sul processore per cui sono stati concepiti. Ogni assemblatore/linker è poi normalmente accompagnato da un interprete/tracer/debugger per poter operare con una certa comodità sul codice sviluppato. Diversi kit di sviluppo possono quindi essere disponibili in letteratura e sul mercato. Quelli professionali possono avere costi molto alti, dato che sono usati per lo sviluppo di software che verrà poi venduto. 53 l ACK-Based Assembler as88 Pseudoistruzioni: ogni assemblatore prevede un insieme specifico di pseudoistruzioni: direttive che influenzano il processo di generazione del codice ma che non vengono tradotte in codice binario. Nell Amsterdam Compiler Kit (ACK) as88, le pseudoistruzioni più importanti sono quelle che permettono di definire le sezioni del codice (TEXT) e dei dati (DATA) del programma, che andranno poi memorizzate nei corrispondenti segmenti di memoria All interno della sezione dati è anche possibile definire una sottosezione di dati (variabili) non inizializzati detta BSS (Block Started by Symbol) 54 18

l ACK-Based Assembler as88 Vi sono altre pseudoistruzioni riconosciute dall ACK. Ad esempio.word per definire un vettore di dati, o.ascii per definire una stringa. Vedremo l uso di alcune di queste pseudoistruzioni negli esempi. 55 l ACK-Based Assembler as88 Labels: Nell ACK qualsiasi istruzione o dato può essere preceduto da una label. Ne esistono di due tipi: Global labels: stringhe alfanumeriche seguite dai due punti (ad esempio: mylabel5: ) Devono essere uniche in tutto il programma, e non devono essere uguali a nomi di istruzioni, pseudoistruzioni, eccetera. Local labels: una cifra da 0 a 9 seguita dai due punti. (es: 5: ) Sono limitate alla sola sezione TEXT, e possono occorrere più volte. Una istruzione come: JE 2f significa: Jump if Equal forward alla prima label 2 che incontri JE 4b significa: Jump if Equal back alla prima label 4 che incontri 56 l ACK-Based tracer t88 Il tracer-debugger che useremo assume di interfacciarsi con un terminale standard 24 x 80 linee. Il suo layout, formato da 7 finestre, è il seguente (Tanenbaum, Fig. C-10) 1 2 3 4 5 6 7 57 19

l ACK-Based tracer t88 Processor with registers (1): mostra i registri generali in decimale e gli altri registri (inclusi i byte register, come AL) in esadecimale. Al di sotto dell IP (Program Counter) viene indicata l istruzione correntemente puntata dall IP rispetto all ultima label globale Sopra l IP compaiono alcuni flag della PSW, e al di sotto il loro valore: O (overflow): indicato da una v D (direction): > o < S (sign): p o n Z (zero): indicato da una z C (carry): indicato da una c - indica che il flag è a 0 58 l ACK-Based tracer t88 Stack (2): visualizza il contenuto dello stack e la locazione correntemente puntata da SP Un numero a fianco di un indirizzo indica che quello è l indirizzo di ritorno di una subroutine. Il valore del numero indica di quale livello di subroutine si tratta rispetto al programma principale Program text (3): visualizza la porzione di codice correntemente in esecuzione, insieme con la posizione precisa del program counter 2 3 59 l ACK-Based tracer t88 Subroutine call stack (4): le righe di codice relative alle chiamate di subroutine più recenti (nell esempio, la chiamata di subroutine più recente è avvenuta alla riga di codice inpstart + 7 ) Interpreters Command (6): ultimi comandi dell interprete dati, e cursore dei comandi Error/Input/Output (5): E: eventuali errori del tracer I: eventuali valori dati in input righe restanti: output Global variables (7): mostra una porzione del data segment: 4 6 5 7 60 20

Comandi dell ACK tracer t88 Ogni comando va seguito da return, e dando il solo return viene eseguita solo una istruzione # (dove # è un numero > 0) esegue # istruzioni di seguito #g esegue le istruzioni fino alla linea di codice # Label+#b mette un breakpoint alla riga label+# (con c al posto di b rimuove il breakpoint) r esegue fino al prossimo breakpoint o fino alla fine, se non ci sono breakpoint 61 Comandi dell ACK tracer t88 - esegue fino all uscita dalla subroutine correntemente chiamata. + esegue fino alla chiamata di una subroutine = esegue fino che non si incontra lo stesso livello di subroutine corrente. Comodo per eseguire una chiamata di subroutine se ci si trova alla sua istruzione CALL 62 Primo esempio: Helloworld.s Questo programma è praticamente fatto solo di istruzioni per produrre l output Definizione delle pseudoistruzioni necessarie Inizio della sezione di codice (.TEXT) Calcola la lunghezza di Hello world\n come differenza di due label Esecuzione della system call _WRITE, che ha tre argomenti: file descriptor, indirizzo della stringa da stampare, lunghezza della stringa. Pulisce lo stack ( PUSH X = 2) Termina restituendo l esito della _WRITE (la _WRITE scrive in AX il numero di byte scritti) Inizio della sezione.data: le label hw e de servono per delimitare la stringa in stampa 63 21

Primo esempio: Helloworld.s comando t88 HlloWrld ecco la situazione quando il PC sta per eseguire l istruzione numero 12, per pulire lo stack prima della terminazione 64 Secondo esempio: vecprod.s Questo programma moltiplica gli elementi di due vettori a coppie, e produce in output la somma dei singoli prodotti. Il programma inizia preparandosi a chiamare la subroutine vecmul(count, vec1, vec2) count contiene il numero di elementi di uno due vettori. Riuscite a vedere come viene calcolato? Nella sezione DATA, la pseudoistruzione.align sposta il location counter alla prima locazione pari disponibile, in modo che vec1 possa essere allocato a partire da una locazione di indirizzo pari..space viene usato per riservare uno spazio di due byte per la variabile inprod, che sarà usata dal programma pricipale 65 Secondo esempio: vecprod.s Il codice della subroutine vecmul inizia, come abbiamo visto in precedenza, predisponendo i registri per trovare in BP+2 l indirizzo di ritorno, e in BP+4, BP+6 e BP+8 gli argomenti della chiamata,che vengono caricati nei registri: CX = count; SI = vec1; DI = vec2 vecmul ha bisogno di una variabile locale inizializzata a zero (chiamiamola varloc ) AX = vec1[i] vec2[i] varloc = varloc + AX Spostati alla word successiva di vec2 CX = CX 1; Jump if CX > 0 AX = varloc 66 22

Secondo esempio: vecprod.s Al ritorno da vecmul il risultato viene salvato nella variabile locale inprod, e poi vengono preparati gli argomenti per la stampa del risultato. In particolare, la pseudoistruzione _PRINTF usa la sintassi della printf del C: _PRINTF( Inner product is: %d\n, %d), dove: pfmt = Inner product is: %d\n AX = %d Infine, lo stack viene ripulito E il programma termina con una EXIT (0) 67 Secondo esempio: vecprod.s In questa figura vediamo la configurazione del tracer quando il programma deve eseguire per la prima volta la LODS. Notate sullo stack gli argomenti della vecmul e l indirizzo di ritorno, il vecchio valore di BP e la varloc. 68 Un semplice esercizio: Power.s Scrivete un programma assembler per l elevazione a potenza di due numeri interi in cui la base viene moltiplicata per se stessa un numero di volte pari all esponente. Base ed esponente sono due numeri interi dichiarati come WORD nel DATA SEGMENT (in altre parole, sono già in memoria e non devono essere richiesti in input, un po come abbiamo visto per i due vettori di vecprod) Il programma deve stampare in output il risultato dell elevazione a potenza (usate lo schema visto in vecprod) 69 23

L assembler dell 8088 Trovate una descrizione dettagliata delle cose viste in questi lucidi nell appendice C del Tanenbaum. trovate l Amsterdam Compiler Kit sul CD che accompagna il testo o agli indirizzi: www.cs.vu.nl/ack www.prenhall.com/tanenbaum Il kit contiene i due programmi fondamentali: as88 // l assemblatore (i file devono avere // l estensione. s. Ad esempio: as88 power.s ) t88 // il tracer (ad esempio, t88 power ) 70 24