Stack e chiamata di funzioni in ARM
In questa pagina 7
In questa pagina 5
Chiamata e ritorno
BL funzione(branch with link): salva inLR(r14) l'indirizzo dell'istruzione successiva e salta.BX LR(oMOV pc, lr): torna al chiamante.
Problema: se funzione chiama a sua volta un'altra funzione, BL sovrascrive LR. Bisogna salvarlo da qualche parte: sullo stack.
Convenzione di chiamata (AAPCS)
Chi chiama e chi è chiamato devono essere d'accordo su dove stanno argomenti, risultato e quali registri possono essere modificati.
| Registri | Regola |
|---|---|
r0–r3 |
primi 4 argomenti; r0 (e r1 per valori a 64 bit) contiene il risultato. La funzione chiamata può modificarli liberamente (caller-saved: se il chiamante ne ha bisogno dopo la chiamata, li salva lui) |
r4–r11 |
callee-saved: se la funzione li usa, deve salvarli all'inizio e ripristinarli prima di tornare |
r12 |
temporaneo |
sp |
deve avere lo stesso valore all'ingresso e all'uscita; allineato a 8 byte alle chiamate |
lr |
indirizzo di ritorno |
Dal quinto argomento in poi si passano sullo stack.
Lo stack
Area di memoria gestita come pila. In ARM è discendente pieno (full descending): cresce verso indirizzi minori e sp punta all'ultimo elemento inserito.
PUSH {r4, lr}=STMDB sp!, {r4, lr}: , poi salvar4in elrin .POP {r4, pc}=LDMIA sp!, {r4, pc}: ricaricar4e mette inpcil vecchiolr(quindi ritorna), .
Il record di attivazione (stack frame) di una funzione contiene: registri salvati, indirizzo di ritorno, variabili locali che non stanno nei registri, argomenti oltre il quarto per le funzioni che chiama.
Funzione foglia
Non chiama altre funzioni: se usa solo r0–r3 e r12 non salva niente.
int somma3(int a, int b, int c) { return a + b + c; }somma3: ADD r0, r0, r1
ADD r0, r0, r2
BX lrChiamata: MOV r0, #1 / MOV r1, #2 / MOV r2, #3 / BL somma3 → r0 = 6.
Funzione ricorsiva: fattoriale
int fatt(int n) { if (n <= 1) return 1; return n * fatt(n - 1); }fatt: CMP r0, #1
MOVLE r0, #1 @ caso base: n <= 1, ritorna 1
BXLE lr
PUSH {r4, lr} @ salva r4 (lo usiamo) e lr (BL lo sovrascrive)
MOV r4, r0 @ r4 = n, deve sopravvivere alla chiamata
SUB r0, r0, #1
BL fatt @ r0 = fatt(n - 1)
MUL r0, r4, r0 @ r0 = n * fatt(n - 1)
POP {r4, pc} @ ripristina r4 e ritornaEvoluzione per fatt(3) chiamata da main con sp = 0x8000:
| Momento | sp |
Contenuto aggiunto allo stack |
|---|---|---|
ingresso in fatt(3), dopo PUSH |
0x7FF8 | r4 di main, lr verso main |
fatt(2), dopo PUSH |
0x7FF0 | r4 = 3, lr verso fatt(3) |
fatt(1) |
0x7FF0 | nessuno: caso base, ritorna 1 |
ritorno in fatt(2) |
0x7FF8 dopo POP | nessuno; r0 = 2 · 1 = 2 |
ritorno in fatt(3) |
0x8000 dopo POP | nessuno; r0 = 3 · 2 = 6 |
Lo stack torna al valore iniziale: ogni PUSH ha il suo POP. Ogni livello di ricorsione occupa 8 byte di stack; una ricorsione troppo profonda esaurisce lo stack (stack overflow).
Variabili locali sullo stack
f: PUSH {r4, lr}
SUB sp, sp, #16 @ spazio per un array locale di 4 interi
STR r0, [sp, #0] @ locale[0] = argomento
...
ADD sp, sp, #16 @ libera lo spazio
POP {r4, pc}Errori tipici
- Non salvare
lrin una funzione che chiama altre funzioni: il ritorno finisce nel posto sbagliato (spesso un ciclo infinito). - Contare su
r0–r3dopo unaBL: possono essere stati modificati. - PUSH e POP con liste di registri diverse: lo stack resta sbilanciato.
Versione ripasso
Chiamata
BL f salva in LR l'indirizzo successivo e salta; BX LR torna. Se f chiama un'altra funzione, BL sovrascrive LR: va salvato sullo stack.
Convenzione (AAPCS)
r0-r3: primi 4 argomenti,r0(er1) risultato; caller-saved (li salva il chiamante se gli servono).r4-r11: callee-saved (la funzione li salva e li ripristina).r12temporaneo;spuguale in ingresso e in uscita, allineato a 8 byte;lrritorno. Dal quinto argomento: sullo stack.
Stack
Discendente pieno: cresce verso indirizzi minori, sp punta all'ultimo elemento.
PUSH {r4, lr}STMDB sp!, {r4, lr}: ,r4in ,lrin .POP {r4, pc}LDMIA sp!, {r4, pc}: ripristina e ritorna, .- Record di attivazione: registri salvati, ritorno, locali, argomenti oltre il quarto.
- Foglia:
somma3:ADD r0,r0,r1;ADD r0,r0,r2;BX lr; con 1, 2, 3r0 = 6.
Fattoriale ricorsivo
fatt: CMP r0,#1; MOVLE r0,#1; BXLE lr (caso base); PUSH {r4, lr}; MOV r4,r0; SUB r0,r0,#1; BL fatt; MUL r0,r4,r0; POP {r4, pc}.
fatt(3) con sp = 0x8000: ingresso 0x7FF8 (r4 di main, lr verso main); fatt(2) 0x7FF0 (r4 = 3, lr verso fatt(3)); fatt(1) caso base, nessun PUSH; ritorno in fatt(2): 0x7FF8, r0 = 2; in fatt(3): 0x8000, r0 = 6. Ogni livello occupa 8 byte (rischio di stack overflow).
Locali
SUB sp,sp,#16 riserva 4 interi (STR r0,[sp,#0]...), ADD sp,sp,#16 li libera prima di POP.
Errori tipici: non salvare lr in una funzione non foglia; contare su r0-r3 dopo una BL; PUSH e POP con liste diverse.