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


;TUT4.ASM - 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:                                 ; 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  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 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 no 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.
- FLAT : supporta la modalità a 32bit dei processori 386+; in questa modalità
	 viene abolita 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)

.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.
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 piu' usata nei programmi, è quella che permette di 
spostare dati dalla memoria alla cpu e viceversa.
Vediamo quali sono i modi di indirizzamento ( cosi 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 indiretto
      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?                                                    


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.
Non avremmo potuto fare :
	mov  ds,SEG Messaggio	; ERRATO !!!!

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. 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 rountine 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 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!

** [email protected] **