PROGRAMMAZIONE IN ASSEMBLY

il primo programma.

 



Questi file sono protetti dal diritto d'autore e sono liberamente
riproducibili a condizione di non farne uso comemrciale o economico o per
fine di lucro senza la preventiva autorizzazione dell'autore. In ogni caso
deve sempre essere indicato il nome dell'autore e il suo indirizzo. Ogni
altra modalita' di utilizzo deve considerarsi contraria alla volonta'
dell'Autore.

Finalmente dopo tante chiacchiere (in)utili siamo arrivati ad analizzare il
primo programma che come ogni tutorial che si rispetti è il classico "Salve
Mondo" (in italiano mi suona male, in inglese : "Hello World"!).
Riporto qui di seguito il listato che poi commenterò in ogni suo piu' piccolo
dettaglio :
(Nota : il programma è stato scritto per Turbo Assembler ma è facilmente
modificabile per altri compilatori)


;TUT4.ASM (Hello Word) - by b0nu$ 1997
.MODEL small ; indica al compilatore il modello di memoria da usare
.STACK 100h ; dimensiona lo Stack

.DATA ; inizio del segmento dati
Messaggio DB "Salve Mondo",13,10,'$' ;dichiarazione del messaggio

.CODE ; inizio del segmento di codice
mov ax,SEG Messaggio ; ax = indirizzo del Segmento Dati
mov ds,ax ; ds = ax
mov dx,OFFSET Messaggio ; ds = offset del Segmento Dati
mov ah,09h ; ah = 09h
int 21h ; chiamata all'interrupt DOS
mov ah,4Ch ; ah = 4Ch
int 21h ; chiamata all'interrupt DOS
END ; fine del programma

Una volta scritto il codice con un semplice editor di testo il programma deve
essere compilato e poi linkato, per far questo scrivete dal prompt del DOS
C:\tasm tut4.asm

( verranno visualizzate alcune informazioni sulla compilazione )

C:\tlink tut4.obj

(ora il file eseguibile è pronto : tut4.exe)
Per eseguire il programma scrivete semplicemente tut4.

Vediamolo ora nel dettaglio istruzione per istruzione :

.MODEL small
-------------
Definisce il tipo dei segmenti di memoria da utilizzare nel programma.Le
principali scelte possibili sono:
- TYNY : tutto il codice e i dati sono in un unico segmento (stanno in 64Kb).
Questo modello è il modello utilizzato per i file con estensione COM.
- SMALL : è il modello piu' comune, un segmento per il codice e uno per i dati
e lo stack tutti non oltre i 64Kb.
- MEDIUM : il codice usa piu' segmenti, pu• quindi superare la barriera dei
64Kb. I dati e lo stack come nel modello small.
- COMPACT : è come lo small ma per accedere ai dati uso puntatori di tipo FAR
quest'ultimi possono infatti superare i 64Kb.
- LARGE : è come il compact ma con il codice in piu' segmenti; sia codice che
dati superano i 64Kb.

.SACK 100h
------------
Dice al compilatore quanto spazio deve riservare per lo stack. Se viene omesso
il compilatore usa per default 200h (1Kb)

.DATA
-------
Inizializza il segmento dati. Questa direttiva serve per dichiarare le variabili
da usare nel programma.

Messaggio DB "Salve Mondo",13,10,'$'
------------------------------------
Questa istruzione assegna alla variabile Messaggio la stringa "Salve Mondo".
DB definisce dei Byte in memoria e in questo caso il numero dei byte è 14 : 11
per la stringa Salve mondo uno per il carattere 13 (CR), uno per il 10 (LF) e
uno per il terminatore '$', che deve essere sempre presente alla fine di una
stringa.
In questo caso la variabile è quindi inizializzata se avessimo voluto solo
riservare dello spazio (ad esempio 10 Byte) per riempirlo durante l'esecuzione
del programma avremmo dovuto scrivere:

Nome_Variabile DB 10 DUP(?)

Quindi abbiamo visto che la direttiva DB definisce byte , ne esistono altre:

DW - Define Word
DD - Define Double word
DQ - Define Quadword
DF - Define 48-bit (puntatore FAR 6 byte)
DT - Define TenByte

.CODE
------
Indica l'inizio del segmento di codice del programma.

mov ax,SEG Messaggio
--------------------
Questa prima istruzione (vera e propria) sposta in AX il valore del puntatore al
Code Segment.
Mov è l'istruzione piu' usata nei programmi, è quella che permette di
spostare dati dalla memoria alla cpu e viceversa.
Vediamo quali sono i modi di indirizzamento (così si chiamano) possibili:

1.Indirizzamento immediato
mov ax,1643
1643 è il dato numerico da mettere in AX.

2.Indirizzamento assoluto
mov ax,[7563]
7563 è l'indirizzo del dato da mettere in AX.

3.Indirizzamento con registro
mov ax,[si]
metto in ax il dato puntato da si (che si trova all'indirizzo si).Si può anche
scrivere:
mov ax,[si+45] ; avete capito cosa fa vero?

4.Indirizzamento indiretto
mov ax,[[1450]]
equivale alle seguenti istruzioni:
mov si,[1540]
mov ax,[si]
in pratica in 1450 c'è l'indirizzo del dato da mettere in AX.

Note :
- L'indirizzamento immediato non può essere usato per cambiare il valore dei
segmenti(vedi prossima istruzione).
- Non posso spostare dati da memoria a memoria devo sempre passare tramite un
registro

mov ds,ax
-----------
Questa istruzione dovrebbe essere ormai chiara il contenuto di ax viene spostato
nel data segment. Quest'ultimo quindi punta all'inizio del segmento dati.
Qualcuno potrebbe pensare di condensare le due istruzioni fin'ora viste in una
sola in questo modo:

mov ds,SEG Messaggio ; ERRATO !!!!

beh...è sbagliato! Non si può fare uno spostamento memoria-memoria.

mov dx,OFFSET Messaggio
------------------------
Ora copiamo l'offset della variabile Messaggio in dx. OFFSET come SEG è una
parola riservata del linguaggio.

mov ah,09h
------------
Copia il valore 09h (esadecimale) in ah. Nota che 09h viene copiato nella parte
alta di ax senza modificare il valore di al.

int 21h
--------
Ora si dovrebbe aprire una lunga (lunghissima) parentesi sugli Interrupt.
Gli Interrupt sono delle funzioni messe a disposizione dal sistema operativo o
dal BIOS per permettere alcune operazioni. Si possono paragonare alle funzioni
di libreria che si usano nei linguaggi ad alto livello certo queste hanno
un'interfaccia meno comoda.
consideriamo questo interrupt in particolare : INT 21h :
Questa è una funzione del Sistema Operativo (DOS) (tutti gli int 21h sono del
DOS) , ma cosa fa ???
Per sapere cosa fa si deve guardare al valore di AH al momento della chimata che
nel nostro caso è 09h; bene se si va a guardare sul Reference Manual si vede che
questa funzione (int 21h funz. 09h) stampa una stringa sullo standard output
(Monitor) e si vede che la stringa che stampa è quella puntata da DS:DX che nel
nostro caso è proprio il messaggio "Salve Mondo".
Riassumendo la chiamata ad interrupt prevede le seguenti operazioni:
1. Settaggio dei parametri per la chiamata nei GIUSTI registri
2. Settaggio del valore di AH che identifica la funzione da eseguire
3. Chimata all'INT XXh (XX = 21 per le chiamate al DOS)
4. (Non sempre)Lettura dei parametri dai registri
Il quarto punto non sempre deve essere eseguito ad esempio nel caso di stampa di
una stringa non è necessario ma se consideriamo la funzione che legge una
stringa dalla Console una volta letta dovremo pur farci qualcosa !!!!
Per avere la lista degli interrupt potete scaricare da internet la famosa lista
di Ralph Brown dal sito ftp : x2ftp.oulu.fi (nota : questa lista S la vostra
Bibbia !!)
Spero abbiate capito cosa sono gli interrupt e allora vediamo cosa succede
quando ne viene generato uno (consideriamo proprio il nostro int 21h,09h).
Al momento della chiamata il flusso lineare del programma si interrompe, viene
salvato lo stato della CPU (registri, flag, ecc...) e l'IP salta all'indirizzo
della routine di interrupt, vi chiederete: dove sono, le rountine di
interrupt???
Tutte queste routine sono memorizzate nel primo K di RAM la parte di memoria
che va dall'indirizzo 000h all'indirizzo 3FFh; ogni funzione ha a disposizione
4byte , quindi nel nostro es. 21h * 4 = 84h che è l'indirizzo della funzione
DOS.
Vedremo piu' avanti come è possibile modificare queste funzioni.
Una volta eseguito il codice dell'interrupt il controllo torna a programma,
viene ripristinato lo stato della CPU e si continua l'esecuzione.
Per ora fermo qui la discussione sugli interrupt; ne vedremo altri nei prossimi
esempi e imparerete ad usarli.


mov ah,4Ch
------------
OK !! Lo so che sapete già cosa fa e se guardate anche alla prossima istruzione
capite anche perchè lo fa , OK ??
Beh forse non sapete che funzione è la 4Ch, ve lo dirò io !
Questa funzione ritorna il controllo del computer al DOS. E' importante per far
terminare il programma.

int 21h
---------
Fa terminare il programma è un'altra funzione del DOS. Se togliamo questa
istruzione il computer si "impalla" e non ci resta che fare un bel reset.
PROVATE !!!


Analizziamo ora un'altra versione dello stesso programma :

;TUT4-1.ASM - by b0nuS 1997
SEG_A SEGMENT
ASSUME CS:SEG_A, DS:SEG_A
ORG 100H

Salve PROC FAR
INIZIO: JMP START ;salta a START

Messaggio DB "Salve Mondo",13,10,'$' ;dichiarazione del messaggio

START:
mov dx,OFFSET Messaggio ; ds = offset del Segmento Dati
mov ah,09h ; ah = 09h
int 21h ; chiamata all'interrupt DOS

RETN
Salve ENDP

SEG_A ENDS
END INIZIO

Qual'è la differenza con l'esempio precedente ?
Vediamole insieme...
Prima di tutto manca sia il DATA SEGMENT che lo STACK SEGMENT e tutto il
programma sta in un unico segmento (SEG_A).
Inoltre questo programma va linkato con l'opzione /t che invece di creare un
eseguibile con estensione EXE crea un file .COM
Oltre a queste grosse differenze ci sono alcune direttive diverse:

ASSUME CS:SEG_A DS:SEG_A
Indica al programma di riferire sia il CS che il DS al SEG_A (unico segmento per
i dati e per il codice)

ORG 100h
Indica l'indirizzo di partenza del programma. NOTA: tutti i programmi con
estensione COM DEVONO cominciare all'indirizzo 100h !!!

In questo esempio il programma non è altro che una procedura che viene chiamata
subito all'inizio del programma con l'istruzione JUMP START che non fa altro che
saltare all'etichetta START.
Alla fine della procedura c'è l'istruzione RETN che ritorna al chiamante.
C'è inoltre da notare che manca l'interrupt per la terminazione del programma
che nel caso di programmi .COM non serve !
IL resto del programma è uguale alla versione precedente.

Nei nostri futuri esempi useremo spesso questo tipo di programmi (anche se sono
limitati a 64Kb ) in quanto per le nostre applicazioni sono sufficienti.

Vorrei aprire infine una parentesi sui programmi assembly.
Una volta scritto il codice sorgente (.ASM) questo viene compilato in codice
oggetto(.OBJ) ed infine linkato per creare il file eseguibile (.EXE o .COM).
Se provate a vedere il contenuto di quest'ultimo file binario vedrete solo una
serie di caratteri ASCII indecifrabile, ebbene ogni carattere ha un preciso
significato esiste infatti una corrispondeza biunivoca tra il codice sorgente
e il codice eseguibile al punto che il programma finale può essere deassemblato
per tornare al file sorgente; a dire il vero questa operazione non è poi così
facile è però possibile.
Se infatti provate ad usare un debugger come il Turbo Debugger accanto ad ogni
istruzione assembly c'è il suo relativo OPCODE.
Quindi ogni istruzione in linguaggio assembly viene tradotta in modo univoco
in un OPCODE esadecimale, ad esempio l'istruzione int 21h diventa CD 21 (in
esadecimale) e occupa due byte, uno per l'istruzione uno per l'operando.
Come dicevamo nel tutorial 3 la CPU legge questi OPCODE nella fase di prefetch
e poi li decodifica in codice macchina.
Spero sappiate che un carattere ASCII occupa un byte (0-255, 0h-FFh).

Ora che abbiamo analizzato a fondo il programma forse preferite ritornare a
programmare in PASCAL che per scrivere una stringa sul monitor usa una semplice
istruzione. Come vedete anche nelle operazioni piu' semplici le cose da fare
sono molte ma è proprio questo il bello : il controllo assoluto sulla
macchina!!!
Beh non vi scoraggiate con un po' di pratica potrete diventare dei veri GURU
della programmazione!


Per richieste, consigli, suggerimenti, aiuti, contributi, ecc..., contattatemi

b0nu$,e-mail :bonus@numerica.it

hAck & c ya :-)

TORNA INDIETRO