Domanda Risolto Stack e Procedure

Stato
Discussione chiusa ad ulteriori risposte.

mattstack

Utente Bronze
1 Aprile 2021
62
19
12
41
Sono nella confusione più totale, non ci sto capendo davvero niente, come funzionano le procedure a basso livello?
Per quale motivo per indirizzare dati nello stack ho bisogno del registro BP e non posso usare lo SP, perché un istruzione del genere genera un errore:
Codice:
mov     ax, WORD PTR [sp+4]
Cos'è che mi impedisce di usare registri diversi da quelli di base per indirizzare dati in memoria?
Niente mi impedisce di incrementare lo SP di "n" unità, eseguire "n" pop per avere accesso ai parametri, e poi decrementare lo stack pointer così da puntare all'indirizzo di ritorno.
Non capisco l'utilità del BP.
Scusate se chiedo molto, ma sono davvero molto confuso, più leggo a riguardo più mi confondo, qualcuno mi potrebbe spiegare bene tutto ciò?
 
e non posso usare lo SP, perché un istruzione del genere genera un errore

8086 mette a disposizione alcune modalità di indirizzamento che riguardano la memoria (sono 24) e SP non è presente in queste modalità.
Estrapolo l'immagine da un mio articolo:

cdn-9-png.44343


Quando avviene una CALL viene modificato il valore in SP e viene salvato l'indirizzo di ritorno sullo stack (che è l'indirizzo dell'istruzione successiva alla CALL).
Il prologo della funzione:

Codice:
       push     bp
       mov      bp, sp

ti consente di utilizzare BP e leggere i parametri o accedere alle variabili locali alla funzione.
La convenienza dell'utilizzo di BP è proprio questa: agli offset positivi trovi gli argomenti della funzione, agli offset negativi trovi le variabili locali alla funzione (se ce ne sono).
Quando viene chiamata la funzione viene creato quello che si chiama "stack frame". Il vantaggio è che hai delle posizioni costanti sul dove si trovano i tuoi dati. Per dire, all'offset BP-8 avrai sempre il medesimo valore; se usi direttamente lo stack pointer no, in quanto ogni PUSH/POP altera questi riferimenti.

In 8086 non è appunto possibile indirizzare direttamente della memoria usando SP, ma in x86 e x86-64 è possibile. L'unico vantaggio è quello di evitare prologo ed epilogo; un aspetto che rende più complesso il codice è che l'offset sottratto al registro cambia in base alle PUSH/POP che sono state fatte.

Un esempio penso ti possa chiarire maggiormente il funzionamento (è una versione a 32bit, con MASM):
Codice:
call              main
print            str$(eax),13,10
inkey
invoke            ExitProcess,0

main          proc
   
    mov           edx, 456
    mov     dword ptr [esp-8], 123
    push          edx

    mov           eax, [esp-8]
    pop           edx
    
    ret        
main          endp

Ignora str$, invoke, inkey... sono MACRO per agevolare le operazioni.

L'output se lo esegui è questo:
Codice:
0
Press any key to continue ...

Come noti, in [esp-8], non c'è il valore 123. Se usi ESP (medesimo di SP, ma a 32bit) devi tenere traccia tu dei push/pop che vengono fatti e modificare il valore che vai a sottrarre al registro ESP.
In questo caso, trattandosi di x86, a esp bisogna sottrarre 4 e non 8: questo perchè la PUSH del registro EDX ha già sottratto 4 a ESP.

Se utilizzi EBP, fai semplicemente questo:
Codice:
main          proc
    
    push          ebp
    mov           ebp, esp
    sub           esp, 4
    
    mov           edx, 456
    mov     dword ptr [ebp-4], 123
    push          edx

    mov           eax, [ebp-4]
    pop           edx

    mov           esp, ebp
    pop           ebp

    ret        4
main          endp

Come noti si va sempre a sottrarre 4 al registro EBP poichè la posizione rimane costante (in quanto PUSH/POP modificano solo ESP).
 
  • Mi piace
Reazioni: mattstack e 0xbro
Stato
Discussione chiusa ad ulteriori risposte.