Salta al contenuto
Note per Studenti Stack e chiamata di funzioni in ARM

Stack e chiamata di funzioni in ARM

In questa pagina 7
In questa pagina 5

Chiamata e ritorno

  • BL funzione (branch with link): salva in LR (r14) l'indirizzo dell'istruzione successiva e salta.
  • BX LR (o MOV 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}: sp←sp−8sp \leftarrow sp - 8, poi salva r4 in [sp][sp] e lr in [sp+4][sp+4].
  • POP {r4, pc} = LDMIA sp!, {r4, pc}: ricarica r4 e mette in pc il vecchio lr (quindi ritorna), sp←sp+8sp \leftarrow sp + 8.

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.

c
int somma3(int a, int b, int c) { return a + b + c; }
armasm
somma3: ADD r0, r0, r1
        ADD r0, r0, r2
        BX  lr

Chiamata: MOV r0, #1 / MOV r1, #2 / MOV r2, #3 / BL somma3 → r0 = 6.

Funzione ricorsiva: fattoriale

c
int fatt(int n) { if (n <= 1) return 1; return n * fatt(n - 1); }
armasm
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 ritorna

Evoluzione 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

armasm
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 lr in una funzione che chiama altre funzioni: il ritorno finisce nel posto sbagliato (spesso un ciclo infinito).
  • Contare su r0–r3 dopo una BL: 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 (e r1) risultato; caller-saved (li salva il chiamante se gli servono).
  • r4-r11: callee-saved (la funzione li salva e li ripristina).
  • r12 temporaneo; sp uguale in ingresso e in uscita, allineato a 8 byte; lr ritorno. Dal quinto argomento: sullo stack.

Stack

Discendente pieno: cresce verso indirizzi minori, sp punta all'ultimo elemento.

  • PUSH {r4, lr} == STMDB sp!, {r4, lr}: sp←sp−8sp \leftarrow sp - 8, r4 in [sp][sp], lr in [sp+4][sp+4].
  • POP {r4, pc} == LDMIA sp!, {r4, pc}: ripristina e ritorna, sp←sp+8sp \leftarrow sp + 8.
  • 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, 3 →\to r0 = 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.

Esercizi su questo argomento

Teoria collegata