Cerca nel sito:
ricerca
avanzata

Frasi Celebri...

Talvolta si prende come cattiva abitudine l'essere infelici.

George Eliot 

Sondaggio:

Se oggi si tornasse a votare, voterei per...

Forza Italia
Alleanza Nazionale
Rifondazione Comunista
Democratici di Sinistra
Lega Nord
Verdi
Radicali
Altro

visualizza risultati


 

...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)

;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 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 più 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 più 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 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 4 byte, 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 :

;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 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 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!

 

successivo
–«  INDICE  »–

 

 

 

 
Powered by paper&pencil (carta&matita ) - Copyright © 2001-2022 Cataldo Sasso