Què passa quan faig foo()?
Bloc 1 · Desenvolupament de Programari en Espai d’Usuari
Pràctica opcional i no avaluable de consolidació de les sessions tèoriques L’espai de memòria d’un procés i Crides de funció: CPU, registres i stack on s’han estudiat els conceptes espai d’adreces, stack frame, calling convention i registres.
gcc amb suport per a -g, -O0, -O2 i -mno-red-zone; gdb. Qualsevol entorn Linux x86-64 (les màquines dels Pralabs són suficients).
Part 1 · Predir abans d’executar
Aquest és el programa de partida. No l’executis encara.
#include <stdio.h>
#include <stdlib.h>
int global = 10;
int suma(int a, int b)
{
int local = 20;
return a + b + local;
}
int main(void)
{
int x = 5;
int *p = malloc(sizeof(int));
*p = 30;
int r = suma(x, *p);
printf("%d\n", r);
free(p);
return 0;
}On viu cadascun?
| Element | Regió |
|---|---|
suma() |
? |
global |
? |
x |
? |
p |
? |
*p |
? |
local |
? |
instruccions de main() |
? |
Recupera la idea de la Sessió 1: un punter i allò que apunta poden viure en regions diferents. On encaixen aquí p i *p?
La crida a suma()
Quan main() crida suma(x, *p):
- Quins arguments s’han de passar, i en quin ordre?
- Segons l’ABI x86-64 estudiada, en quins registres esperes trobar-los?
- Quin registre contindrà el resultat?
- Què esperes que passi amb
RSPquan entrem asuma()? - Què esperes que passi amb la return address?
- Què esperes que passi amb
RSPdesprés delret?
Escriu totes les respostes abans de passar a la Part 2.
Part 2 · Comprovar-ho amb GDB
Compila una versió preparada per inspeccionar-la:
gcc -g -O0 -fno-omit-frame-pointer -mno-red-zone program.c -o programEl -mno-red-zone ajuda a fer visible el pròleg de suma() en aquesta pràctica. No cal entendre què fa aquesta opció per fer la pràctica, la utilitzem per obtenir una execució més propera al model de stack frame estudiat a classe.
Comandes mínimes de GDB (no en necessites més):
break suma
run
info registers
x/16gx $rsp
disassemble suma
A. Entrada a suma()
- Quin és el valor de
RDI? - Quin és el valor de
RSI? - Quin valor té
RSP? - Quin valor té
RBP? - On és la return address?
B. Assembly
Copia només les instruccions rellevants de suma() i anota’n l’efecte:
instrucció efecte
----------- ----------------
push ... ...
mov ... ...
...
ret ...
No cal que entenguis cada instrucció assembly al detall. L’objectiu és relacionar:
C
↓
assembly
↓
registres
↓
stack
Una instrucció assembly no és un pas aïllat: cada una mou o llegeix algun registre o posició de la pila que ja coneixes pel nom.
Part 3 · Explicar la diferència
Respon amb les teves paraules, sense tornar a mirar GDB:
- Per què dues variables automàtiques qualssevol (per exemple,
xamain()ilocalasuma()) no tenen per què acabar totes dues físicament al stack de la mateixa manera? - Per què
pi*ppoden viure en regions diferents?
Quina diferència hi ha entre el codi de suma() i l’activació de suma()?
Part 4 · Trenca el model
Pren aquesta versió, més senzilla, de la funció:
__attribute__((noinline))
int suma(int a, int b)
{
return a + b;
}El compilador pot eliminar la funció del codi optimitzat. Hem afegit aquesta directiva perquè puguis comparar la funció a -O0 i -O2.
Compila-la de dues maneres:
gcc -g -O0 -fno-omit-frame-pointer program2.c -o program2_O0
gcc -g -O2 program2.c -o program2_O2Fixa’t que a la versió -O2 no forcem -fno-omit-frame-pointer. Volem veure què decideix el compilador pel seu compte.
Desassembla suma() a totes dues (gdb -batch -ex 'disassemble suma' programX) i respon:
- Continua existint
RBPcom abans? - Continua existint el stack frame clàssic (
push rbp; mov rsp,rbp; ...)? - El codi assembly és igual?
- Una variable de C ha de tenir necessàriament una adreça de memòria durant tota la seva vida? Justifica-ho a partir del que observes amb
-O2. - Quina diferència hi ha entre el model que hem estudiat i una execució optimitzada?
Una funció sempre crea exactament aquest stack frame és fals. El frame depèn de l’arquitectura, l’ABI, el compilador i les opcions d’optimització. El frame pointer es pot ometre, i una variable pot no tenir mai una adreça de memòria pròpia.
Pregunta de síntesi
Sense tornar a mirar GDB:
Explica, en 8–10 línies, què passa quan main() executa suma(x, *p), des d’abans del call fins que main() recupera el control.
Has d’integrar, en un sol relat continu: l’espai d’adreces, els arguments i l’ABI, call, la return address, RSP, RBP, la variable local, RAX i ret. Si ho pots explicar sencer sense saltar-te cap peça, has entès les dues sessions.
Tancament de la pràctica
Annota en un document de text o Markdown les teves respostes a totes les preguntes i reflexions. Si vols, pots afegir captures de pantalla de GDB per il·lustrar-les.
- Predicció · què creus que passarà (Part 1).
- Experiment · què observes amb GDB (Part 2).
- Diferència · què no ha passat com esperaves, si escau.
- Explicació · per què (Part 3).
- Model final · completa aquest diagrama amb registres, return address, stack frame, resultat i
call/ret. No et donem els valors: has de ser tu qui els situï.
main()
│
│ suma(5, 30)
│
│
▼
suma(a,b)
┌───────────────┐
│ │
│ │
│ │
└───────────────┘
│
│
│
▼
main()
Inclou també la taula d’estat següent, omplerta amb els valors reals que has observat (no cal completar cap cel·la que consideris no rellevant).
| Moment | RIP | RSP | RBP | RDI | RSI | RAX |
|---|---|---|---|---|---|---|
abans de call |
||||||
entrada a suma |
||||||
| després del pròleg | ||||||
| després del càlcul | ||||||
abans de ret |
||||||
retorn a main |