...e finalmente il primo programma...

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 più piccolo dettaglio :
(Nota : il programma è stato scritto per Turbo Assembler 4.0 ma è facilmente modificabile per altri compilatori)

TUT5.EXE TUT5.ASM ;TUT5.ASM (Hello Word) - by b0nuS 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 inizio: ;etichetta che segna l'inizio del programma (entry point) 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 inizio ; 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 tut5.asm

( verranno visualizzate alcune informazioni sulla compilazione )

C:\tlink tut5.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:

- TINY : 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 più comune, un segmento per il codice e uno per i
	  dati e lo stack tutti non oltre i 64Kb.
- MEDIUM : il codice usa più 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 più segmenti; sia codice
          che dati superano i 64Kb.
- FLAT : supporta la modalità a 32bit dei processori 386+; in questa
	 modalità viene abolito il metodo di indirizzamento SEGMENTO:OFFSET,
	 l'indirizzo è dato da un unico numero a 32bit.
.STACK 100h
------------
Dice al compilatore quanto spazio deve riservare per lo stack. Se viene omesso il compilatore usa per default 400h (1Kb). Se viene omessa l'indicazione numerica l'assemblatore tasm assume 400h (se viene omessa proprio la direttiva .stack tasm non crea alcun stack!)

.DATA
-------
Inizializza il segmento dati. Dopo questa direttiva si dichiarano le variabili da usare nel programma.

Messaggio DB "Salve Mondo",13,10,'$'
------------------------------------
Questa istruzione assegna alla variabile Messaggio il valore Salve Mondo, cioè la stringa "Salve Mondo" più altri 3 caratteri.
DB definisce dei Byte in memoria e in questo caso il numero dei byte è 14 : 13 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 Data Segment.
Mov è (penso) l'istruzione più 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!
Come ho letto da un vecchio libro di Norton, la ragione è semplicemente perchè questa istruzione non è stata implementata per rendere più economico il processore!

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 chiamata 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. Chiamata 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 è la vostra Bibbia: directory /pub/msdos/programming/doc/interXX.zip!!)
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 routine di interrupt???
Gli indirizzi di 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 più 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 :


TUT5-1.COM TUT5-1.ASM ;TUT5-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 4 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 più 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!


Assembly Page di Antonio
<< Indice >>