Soluzioni Esercizi sull'ISA IA32
Esercizio 1
Si traduca in C il seguente programma IA32:
e1: ; parametro x in 8(%ebp) e y in 12(%ebp) - interi 4 byte
pushl %ebp ; prologo
movl %esp, %ebp
movl 8(%ebp), %eax ; z = x
imull %eax, %eax ; z = z * z
addl 12(%ebp), %eax ; z = z + y
popl %ebp ; epilogo
ret ; return z
Si risponda inoltre alle seguenti domande:
- Cosa calcola la funzione e1?
- Cosa sarebbe cambiato se invece di eax avessimo usato ecx?
Soluzione:
T e1(T x, T y) {
return x*x+y;
}
dove
T è indifferentemente
int,
unsigned,
long o
unsigned long (un intero con o senza segno a 32 bit).
Risposte domande:
- Calcola la funzione x2+y
- La funzione sarebbe stata scorretta, poiché il valore calcolato non sarebbe stato restituito dalla funzione (si usa eax per restituire valori al chiamante)
Esercizio 2
Si traduca in C il seguente programma IA32:
e2:
pushl %ebp ; prologo
movl %esp, %ebp
movw 8(%ebp), %ax ; primo parametro (intero a 2 byte) in ax
movw 12(%ebp), %cx ; secondo parametro (intero a 2 byte) in cx
cmpw %ax, %cx ; calcola cx-ax
cmovgew %cx, %ax ; sovrascrive ax con cx (2 byte) se cx-ax >= 0 (ge, confronto con segno)
movswl %ax, %eax ; copia ax in eax (con segno)
popl %ebp ; epilogo
ret ; restituisce eax
Si risponda inoltre alle seguenti domande:
- Cosa calcola la funzione e2?
- Come verrebbe compilato il programma se gcc fosse invocato con l'opzione -fomit-frame-pointer (che non genera prologo ed epilogo che gestisce lo stack frame usando ebp)?
- Come avremmo dovuto modificare il programma se invece di cx avessimo usato bx?
Soluzione:
short e2(short x, short y) {
return x > y ? x : y;
}
Si noti che sarebbe stato errato usare
unsigned short invece di
short poiché il testo usa
cmovgew e
movswl. Se avessimo trovato
cmovaew e
movzwl avremmo concluso che il tipo è
unsigned short.
Risposte domande:
- La funzione calcola il massimo dei suoi due parametri
- Il codice IA32 sarebbe stato:
e2:
movw 4(%esp), %ax ; usiamo esp invece di ebp
movw 8(%esp), %cx ; usiamo esp invece di ebp
cmpw %ax, %cx
cmovgew %cx, %ax
movswl %ax, %eax
ret
- poiché il registro B è callee-save, avremmo dovuto salvarlo nel prologo e ripristinarlo nell'epilogo:
e2:
pushl %ebp
pushl %ebx
movl %esp, %ebp
movw 12(%ebp), %ax
movw 16(%ebp), %bx
cmpw %ax, %bx
cmovgew %bx, %ax
movswl %ax, %eax
popl %ebx
popl %ebp
ret
Esercizio 3
Si traduca in C il seguente programma IA32:
e3:
pushl %edi ; prologo: salva registri callee-save edi ed esi
pushl %esi
xorl %eax, %eax ; assegna eax = 0, indice di scorrimento array
movl 20(%esp), %ecx ; terzo parametro in ecx
movl 16(%esp), %edx ; secondo parametro in edx
movl 12(%esp), %esi ; primo parametro in esi
jmp L4
L2:
xorl %eax, %eax ; assegna eax = 0, valore restituito
jmp L6 ; salta alla fine della funzione
L1:
movl (%edx,%eax,4), %edi ; assegna edi = edx[eax] => edx è puntatore a intero a 4 byte
cmpl %edi, (%esi,%eax,4) ; calcola esi[eax]-edx[eax] => esi è puntatore a intero a 4 byte
jne L2 ; salta a L2 se esi[eax]-edx[eax] != 0, cioè esi[eax] != edx[eax]
incl %eax ; eax++
L4:
cmpl %ecx, %eax ; calcola eax-ecx
jl L1 ; salta a L1 se eax-ecx < 0 (l=less, con segno) - rimane in ciclo
movl $1, %eax ; assegna eax = 1, valore restituito
L6:
popl %esi ; prologo: ripristina registri callee-save edi ed esi
popl %edi
ret
Si risponda inoltre alle seguenti domande:
- Cosa calcola la funzione e3?
- Cosa succederebbe se si eliminassero dal codice le operazioni di push e pop?
Soluzione:
Usando come nomi di variabili i corrispondenti nomi dei registri IA32 usati nel codice assembly, otteniamo:
int e3(T* esi, T* edx, int ecx) {
int eax;
for (eax=0; eax<ecx; eax++)
if (esi[eax]!=edx[eax]) return 0;
return 1;
}
Usando dei nomi di variabili più familiari:
int e3(T* a, T* b, int n) {
int i;
for (i=0; i<n; i++)
if (a[i]!=b[i]) return 0;
return 1;
}
dove
T è indifferentemente
int,
unsigned,
long o
unsigned long (un intero con o senza segno a 32 bit).
Risposte domande:
- La funzione verifica se due array della stessa dimensione sono uguali
- Se eliminassimo le push e pop otterremmo un programma che non rispetta le convenzioni sulla gestione dei registri callee-save: la funzione e3 sarebbe infatti errata nel caso in cui un suo chiamante usasse il contenuto dei registri edi ed esi dopo la chiamata assumendo che non sono stati alterati da e3.
Esercizio 4
Si traduca in C il seguente programma IA32:
e4:
pushl %ebp ; prologo
movl %esp, %ebp
pushl %esi ; salva registro callee-save
movl 8(%ebp), %eax ; primo parametro in eax
movw (%eax), %cx ; cx = *eax (legge 2 byte dall'indirizzo eax)
movl 12(%ebp), %edx ; secondo parametro in edx
movw (%edx), %si ; si = *edx (legge 2 byte dall'indirizzo edx)
movw %si, (%eax) ; *eax = si (scrive all'indirizzo eax i 2 byte precedentemente letti dall'indirizzo edx)
movw %cx, (%edx) ; *edx = cx (scrive all'indirizzo edx i 2 byte precedentemente letti dall'indirizzo eax)
popl %esi ; epilogo - ripristina registro callee-save
popl %ebp
ret
Si risponda inoltre alla seguente domanda:
- Cosa calcola la funzione e4?
Soluzione:
void e4(T* eax, T* edx) {
T cx = *eax;
T si = *edx;
*eax = si;
*edx = cx;
}
dove
T è indifferentemente
short oppure
unsigned short. Usando dei nomi di variabili più familiari e una sola variabile locale:
void e4(T* x, T* y) {
T tmp = *x;
*x = *y;
*y = tmp;
}
Si noti che, poiché alla fine il registro
eax contiene il primo parametro, avrebbe anche potuto essere:
T* e4(T* x, T* y) {
T tmp = *x;
*x = *y;
*y = tmp;
return x;
}
o anche:
int e4(T* x, T* y) {
T tmp = *x;
*x = *y;
*y = tmp;
return (int)x;
}
Risposta domanda:
- La funzione scambia il contenuto di due oggetti di 2 byte in memoria
Esercizio 5
Si traduca in C il seguente programma IA32:
e5:
movl 4(%esp), %ecx ; primo parametro in eax
movl 8(%esp), %edx ; secondo parametro in eax<