Fondamenti di Informatica: esercitazione di laboratorio Programmazione in linguaggio macchina e linguaggio assembler
Pier Luca Montessoro
1. Moltiplicazione
Scrivere in linguaggio assembler e tradurre manualmente in linguaggio macchina un programma che scriva nel registro R0 il prodotto, calcolato mediante l’algoritmo delle somme successive, dei due numeri interi contenuti agli indirizzi 0000 e 0002 della memoria.
2. Chiamata di funzione
Una chiamata di funzione viene eseguita dall’istruzione CALL che salva nello stack l’indirizzo a cui dovrà tornare il Program Counter al termine dell’esecuzione della funzione stessa (cioè nel momento in cui viene eseguita l’istruzione RET che preleva dallo stack il valore salvato).
Si modifichi il programma dell’esercizio scrivendo la funzione MUL che esegue la moltiplicazione tra i due argomenti passati nei registri R1 ed R2 e restituisce il risultato nel registro R0.
Si scriva poi un programma main che inizializzi R1 ed R2 e chiami la funzione MUL.
Si utilizzi il programma assembler.exe per tradurre il sorgente assembly in linguaggio macchina.
Si analizzi il comportamento del programma e il contenuto dello stack della CPU mediante l’esecuzione di un’istruzione alla volta con il simulatore SimCPU.
3. Salvataggio delle variabili locali nello stack e ricorsione
Quando viene chiamata una subroutine, le variabili locali attive, siano esse in memoria o nei registri, devono essere salvate. Le architetture RISC spesso dispongono di più gruppi di registri in cui mantenere “congelate” le variabili locali, ma in alternativa si può utilizzare lo stack di sistema salvando, oltre al program counter, anche le variabili locali. In questo caso si devono effettuare delle istruzioni di PUSH delle variabili in uso (per noi, i registri) subito prima di eseguire l'istruzione CALL (in questo modo la subroutine chiamata ha la libertà di utilizzare un qualsiasi registro) ed effettuare, in ordine inverso, le istruzioni di POP per ripristinare i valori delle variabili (registri) immediatamente dopo l'istruzione CALL (cioè non appena la subroutine chiamata restituisce il controllo al programma chiamante). Questo consente di isolare l’utilizzo dei registri da parte di una funzione dalle altre.
Utilizzando tale tenica, si modifichi il programma dell’esercizio precedente salvando il contenuto dei registri R1 ed R2 nello stack e ripristinandolo dopo la chiamata della funzione MUL.
Si verifichi l’effettivo ripristino dei valori nei registri mediante il sumulatore SimCPU.
4. Ricorsione
Il salvataggio delle variabili locali nello stack è fondamentale per consentire la ricorsione. Si scriva in linguaggio assembler una funzione ricorsiva per il calcolo del fattoriale (nota: si dovrà utilizzare la funzione MUL scritta in precedenza). Si utilizzi R0 per il valore restituito dalla funzione, e R1 per l’argomento passato nella chiamata.
Si esegua il programma istruzione per istruzione meiante SimCPU e si analizzi il contenuto dello stack.
Attenzione: lavorando su 16 bit la condizione di overflow si verifica molto presto! Si inserisca un controllo della condizione di overflow che interrompe l’esecuzione del programma e segnali la condizione di overflow scrivendo in tutti i registri il valore -1 (FFFF in esadecimale).
5. Per casa...
Si scrivano, in opportuni file .ixx, le seguenti funzioni di libreria elementari (alcune di esse richiederanno, a loro volta, di chiamare altre funzioni) e si utilizzi il programma include.exe per comporre il programma sorgente da passare all’assemblatore:
- strlen: scrivere in linguaggio macchina una funzione che scriva nel registro R0 la lunghezza di una stringa C (cioè terminata da ‘\0’) memorizzata a partire dall’indirizzo contenuto, al momento della chiamata della funzione, in R1
- gets: scrivere in linguaggio macchina una funzione che scriva in memoria, a partire dall’indirizzo contenuto nel registro R1 al momento della chiamata della funzione, una stringa letta da tastiera (fino al '\n' compreso)
- atoi: scrivere in linguaggio macchina una funzione che scriva nel registro R0 il valore intero positivo rappresentato dalla stringa ASCII presente in memoria a partire dall’indirizzo contenuto nel registro R1 al momento della chiamata della funzione
- printf ("%d", n): scrivere in linguaggio macchina una funzione che stampi una stringa ASCII che rappresenti in decimale il numero naturale contenuto nel registro R1 al momento della chiamata della funzione
- divisione: scrivere in linguaggio macchina una funzione che restituisca nel registro R0 il risultato della divisione intera, calcolato mediante l’algoritmo delle differenze successive, dei due numeri interi contenuti, alla chiamata della funzione, in R1 e in R2. Inoltre, la funzione dovrà lasciare in R1 il resto della divisione intera.