Esercitazione 1 - 20 ottobre 2015 (90 min)
Questionario
Prima di iniziare l'esercitazione, si prega di riempire il
questionario anonimo sul primo anno del corso di laurea in Ingegneria Informatica e Automatica.
Configurazione
Aula 15 (canale provenienza AO)
In quest'aula lavoreremo con l'installazione nativa di Linux Debian.
Per questa prima esercitazione, è necessario installare le librerie gcc a 32 bit. Seguire i seguenti passi:
- aprire un terminale (icona sul dock in basso)
- digitare su e poi la password comunicata in aula
- digitare apt-get update
- digitare apt-get install gcc-multilib e dare conferma
- digitare exit, oppure Ctrl+d
Creare una
directory di lavoro sc1516 e posizionarsi nella directory creata come segue:
$ mkdir /home/studente15/Scrivania/sc1516
$ cd /home/studente15/Scrivania/sc1516
Aula 16 (canale provenienza PZ)
In quest'aula lavoreremo con Linux LXLE nella VM Oracle Virtualbox BIAR.
Per questa prima esercitazione, è necessario installare le librerie gcc a 32 bit. Seguire i seguenti passi:
- aprire un terminale (icona sul dock in basso)
- digitare sudo apt-get install gcc-multilib, inserire la password amministratore biar e dare conferma
Creare una
directory di lavoro sc1516 e posizionarsi nella directory creata come segue:
$ mkdir /home/biar/Desktop/sc1516
$ cd /home/biar/Desktop/sc1516
Primi passi
Creare un file dal nome
hello.c:
$ touch hello.c
$ geany hello.c &
Il primo comando crea un file vuoto chiamato
hello.c. Il secondo apre l'editor di testo
geany. Si noti l'& alla fine della riga, che serve per lanciare il programma "in background". Se omettiamo l'& e
geany non è già aperto, il terminale si blocca finché non viene chiuso
geany. Osserviamo che usando la
freccia su si riprendono
comandi precedentemente inseriti nel terminale. Inoltre, con il tasto
tabulazione (TAB) si ha
autocompletamento dei nomi di file e directory nel terminale.
Esercizio 1 (compilazione separata file)
L'obiettivo dell'esercizio è sperimentare l'uso delle opzioni di
gcc per la compilazione a stadi separati. Si immetta il seguente programma C di esempio nel file
hello.c:
#include <stdio.h>
int main
() {
printf("Hello World\n");
return 0;
}
Si effettuino i seguenti passi (dopo ogni passo si esamini il contenuto della directory corrente con il comando
ls):
- Preprocessamento: gcc -E hello.c > hello.i. Si esamini il file creato hello.i controllandone il tipo con file hello.i e aprendolo con geany hello.i. Si noti come contenga tutte le dichiarazioni importate dall'header stdio.h e il contenuto del file hello.c in coda.
- Traduzione da C ad Assembly IA32: gcc -m32 -S hello.i. Si esamini il file creato hello.s controllandone il tipo con file hello.s e aprendolo con geany hello.s.
- Generazione file oggetto: gcc -m32 -c hello.s. Si esamini il file creato hello.s controllandone il tipo con file hello.o.
- Linking del programma e generazione file eseguibile: gcc -m32 hello.o -o hello. Si esamini il file creato hello controllandone il tipo con file hello.
- Esecuzione del programma: ./hello
- Disassemblamento del file oggetto: objdump -d hello.o > hellodump.txt e ispezionare il file creato con geany hellodump.txt. Si noti come objdump riporta sia la versione macchina del codice (byte istruzioni in esadecimale) che l'assembly.
- Disassemblamento del file eseguibile: objdump -d hello > hellodumpexe.txt e ispezionare il file creato con geany hellodumpexe.txt. Si scorra il file finché non si incontra la funzione main. Si noti come il file eseguibile contiene molto altro codice oltre al main. In particolare, si osservi la funzione _start, da cui effettivamente parte il processo e si noti come l'ultima istruzione della funzione _start è hlt, che segnala al sistema operativo la terminazione del processo.
Si noti l'opzione
-m32 che serve per generare codice IA32 in un sistema operativo a 64 bit.
Esercizio 2 (espressioni)
Si traduca il seguente programma C in un modulo assembly IA32
es2.s:
int es2() {
return 5*6-2*7-1;
}
Consultare il
capitolo 4 della dispensa per una descrizione delle istruzioni IA32. Si ricordi che il valore di ritorno di una funzione è nel registro A e che è possibile usare liberamente come se fossero variabili solo i registri A, C e D. Si tenga presente inoltre che l'istruzione prodotto
imul deve avere come destinazione necessariamente un registro.
- Suggerimento: si usi il seguente approccio. Si traduca dapprima il programma C originale in un programma C equivalente in cui tutte le operazioni aritmetiche hanno la forma D = D op S. Si traduca poi questo programma C equivalente in IA32. Si suggerisce di inserire per comodità i programmi C da tradurre direttamente nel modulo .s in forma di commento come segue (usando la sintassi: # commento):
# Programma C originale:
# int es2() {
# return 5*6-2*7-1;
# }
# Programma C equivalente:
# int es2() {
# int a = 5; // usiamo nomi delle variabili corrispondenti ai registri che useremo in IA32
# a = a * 6;
# int c = 2;
# c = c * 7;
# a = a - c;
# a = a - 1;
# return a;
# }
.globl es2
es2:
...
ret
Si consideri il seguente programma di prova:
#include <stdio.h>
int es2
();
// prototipo della funzione definita in es2.s
int main
() {
int x = es2
();
printf("x=%d\n", x
);
// deve stampare x=15
return 0;
}
Si compili il programma con:
$ gcc -m32 es2-main.c es2.s -o es2
notare che la riga di comando contiene tutti i moduli necessari al funzionamento del programma (sia
es2-main.c che
es2.s). Si esegua poi il programma con:
$ ./es2
Esercizio 3 (espressioni)
Si traduca il seguente programma C in un modulo assembly IA32
es3.s:
int es3() {
return 3*(5+6)-2*7;
}
Si scriva un main di prova e si testi il funzionamento del programma come fatto nell'esercizio 1.
Esercizio 4 (accesso ai parametri)
Si traduca il seguente programma C in un modulo assembly IA32
es4.s:
int es4(int x) {
return x+10;
}
- Suggerimento: per accedere ai parametri IA32, si suggerisce di copiarne il contenuto in registri.
Si scriva un main di prova e si testi il funzionamento del programma.
Esercizio 5 (accesso ai parametri)
Si traduca il seguente programma C in un modulo assembly IA32
es5.s:
int es5(int x, int y, int z) {
return 2*x-3*y+z;
}
Si scriva un main di prova e si testi il funzionamento del programma.
Esercizio 6 (accesso ai parametri)
Si traduca il seguente programma C in un modulo assembly IA32
es6.s:
short es6(short x, short y) {
return x*x + y*y;
}
Si scriva un main di prova e si testi il funzionamento del programma.
Esercizio 7 (uso dei puntatori)
Si traduca il seguente programma C in un modulo assembly IA32
es7.s:
int es7(int* x) {
return *x + 1;
}
Si consideri il seguente main di prova e si testi il funzionamento del programma:
#include <stdio.h>
int es7
(int* x
);
int main
() {
int a =
10;
int b = es7
(&a
);
printf("a=%d, b=%d\n", a, b
);
// deve stampare: a=10, b=11
return 0;
}
Esercizio 8 (uso dei puntatori)
Si traduca il seguente programma C in un modulo assembly IA32
es8.s:
void init(int* p) {
p[0] = 30;
p[1] = 20;
p[2] = 10;
}
Si consideri il seguente main di prova e si testi il funzionamento del programma:
#include <stdio.h>
void init
(int* v
);
int main
() {
int v
[3] =
{-1,
-2,
-3};
init
(v
);
printf("v[0]=%d, v[1]=%d, v[2]=%d\n", v
[0], v
[1], v
[2]);
// deve stampare: v[0]=30, v[1]=20, v[2]=10
return 0;
}
Soluzioni
Le soluzioni dell'esercitazione sono
qui (file zip, 5.3 KB).