2013-03-27 10 views
6

Qualche tempo fa stavo sperimentando con il gruppo di scrittura routine e il collegamento con i programmi in C e ho trovato che ho appena posso saltare di serie C-call prologo all'epilogoperché usare ebp in funzione prologo/epilogo?

push ebp 
    mov ebp, esp 
    (sub esp, 4 
    ... 
    mov esp, ebp) 
    pop ebp 

basta saltare tutto e indirizzo semplicemente esp , come

mov eax, [esp+4]   ;; take argument 
    mov [esp-4], eax   ;; use some local variable storage 

Sembra funzionare abbastanza bene. Perché questo ebp è usato - è forse indirizzarsi attraverso ebp più veloce o cosa?

+0

Questa tecnica ha un nome: _frame puntatore optimization_. – legends2k

+0

possibile duplicato di [Qual è lo scopo del registro del puntatore del frame EBP?] (Http://stackoverflow.com/questions/579262/what-is-the-purpose-of-the-ebp-frame-pointer-register) –

risposta

9

Non c'è alcun obbligo di utilizzare uno stack frame, ma ci sono sicuramente alcuni vantaggi:

In primo luogo , se ogni funzione ha utilizzato questo stesso processo, possiamo usare questa conoscenza per determinare facilmente una sequenza di chiamate (lo stack di chiamate) invertendo il processo. Sappiamo che dopo un'istruzione call, ESP punti per l'indirizzo di ritorno e che la prima cosa che la funzione chiamata farà è push l'attuale EBP e quindi copiare ESP in EBP. Quindi, in qualsiasi momento possiamo guardare i dati indicati da EBP che sarà il precedente EBP e che EBP+4 sarà l'indirizzo di ritorno dell'ultima chiamata di funzione. Possiamo quindi stampare il (32bit assumendo) stack di chiamate utilizzando qualcosa di simile (scusate il arrugginita C++):

void LogStack(DWORD ebp) 
{ 
    DWORD prevEBP = *((DWORD*)ebp); 
    DWORD retAddr = *((DWORD*)(ebp+4)); 

    if (retAddr == 0) return; 

    HMODULE module; 
    GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, (const char*)retAddr, &module); 
    char* fileName = new char[256]; 
    fileName[255] = 0; 
    GetModuleFileNameA(module, fileName, 255); 
    printf("0x%08x: %s\n", retAddr, fileName); 
    delete [] fileName; 
    if (prevEBP != 0) LogStack(prevEBP); 
} 

Questo sarà quindi stampare l'intera sequenza di chiamate (o meglio, i loro indirizzi di ritorno) fino a quel momento.

Inoltre, dal momento che EBP non cambia a meno che non si aggiorna in modo esplicito che (a differenza di ESP, che cambia quando si push/pop), di solito è più facile fare riferimento i dati nello stack rispetto al EBP, piuttosto che rispetto al ESP, dal momento che con quest'ultimo, è necessario essere a conoscenza di tutte le istruzioni push/pop che potrebbero essere state chiamate tra l'inizio della funzione e il riferimento.

Come altri hanno detto, si dovrebbe evitare di utilizzare pila indirizzi sottoESP come qualsiasi call s fate ad altre funzioni sono suscettibili di sovrascrivere i dati a questi indirizzi.Si dovrebbe invece riservare spazio sullo stack per l'utilizzo da parte vostra funzione dal solito:

sub esp, [number of bytes to reserve] 

Dopo questo, la regione dello stack tra il primo e ESPESP - [number of bytes reserved] è sicuro da usare. Prima di uscire la funzione è necessario liberare lo spazio di stack riservato utilizzando una corrispondenza:

add esp, [number of bytes reserved] 
+0

Haha, :) questo è buono (voglio dire in particolare l'esempio LogStack) Mi dispiace di poter avere tutte e tre le risposte perché sono tutte molto buone :) Per quanto riguarda la cosa, personalmente mi piace essere un maniaco dell'optimizzazione (voglio per scrivere le routine asm più veloci possibili) quindi la cosa giusta è se forse questo esp-4 overvrittens non si verificano, o lo fanno davvero? (qualche testo su di esso forse? qualcuno?) Se è così posso saltare tutto il prologo come ho scritto ma invece di usare esp-4 usare un buffer statico per i locali - dovrebbe essere la velocità massima: U ye? – user2214913

+2

@ user2214913: stai entrando nell'ottimizzazione prematura. Qualsiasi lavoro significativo che farà il tuo codice, in un programma non banale, sminuirebbe i cicli da te eliminati in quel modo. E le persone che hanno bisogno di eseguire il debug del tuo codice non lo apprezzeranno. – DCoder

+3

@ user2214913 Come dice DCoder, probabilmente non state producendo codice più veloce scrivendo in assembly, e ogni guadagno di velocità derivante dall'abbandonare i frame di stack e non riservare lo stack space prima di usarlo sarà sminuito dalle inefficienze che state introducendo. Il fatto che tu stia facendo questa semplice domanda suggerisce che il tuo codice probabilmente non è scritto per evitare rallentamenti da errori di cache, o sfruttare l'ordinamento di istruzioni specifico della CPU per ottenere l'esecuzione parallela. Un compilatore decente sarà a conoscenza di queste cose e in genere produrrà un codice molto più veloce dell'assemblaggio manuale. – Iridium

4

Funziona, tuttavia, una volta ricevuto un interrupt, il processore sposterà tutti i suoi registri e flag nello stack, sovrascrivendo il valore. Lo stack è lì per un motivo, usarlo ...

+1

Abbastanza sicuro che non è il modo in cui gli interrupt funzionano. – Iridium

+2

@Iridium, spiegare? – stdcall

+0

Hmm, sei sicuro? Ho usato questo e non ho riscontrato un errore è solo una questione di fortuna? Se è così capisco che posso usarlo ma non dovrei usare solo i valori di [esp-X]? o in altro modo posso sub esp 20 gallina aggiungere 20 ad esso? – user2214913

9

L'uso di EBP è di grande aiuto durante il debug del codice, in quanto consente ai debugger di attraversare i frame dello stack in una catena di chiamate.

It [crea] una lista concatenata che collegava il puntatore per ciascuno dei chiamanti a una funzione. Da EBP per una routine, è possibile recuperare l'intero stack di chiamate per una funzione.

Vedi http://en.wikibooks.org/wiki/X86_Disassembly/Functions_and_Stack_Frames
E in particolare la pagina di link ad esso che copre la tua domanda: http://blogs.msdn.com/b/larryosterman/archive/2007/03/12/fpo.aspx

+0

molto tnx, questa pagina su fpu è davvero la risposta a questo - ma la cosa che mellowcandla ha detto che è molto interessante anche – user2214913

+0

Anche se potrei aggiungere che non capisco come funziona questa lista collegata, e che sembra che senza ebp questa informazione può essere recuperato anche - se la funzione può tornare indietro (e tornare indietro e tornare indietro) allora puoi tracciare anche questo percorso - senza ebp – user2214913

+0

@ user2214913: la funzione può tornare al suo chiamante, perché l'indirizzo di ritorno è memorizzato nello stack accanto ai parametri spinti. Tuttavia, un debugger non può distinguere l'indirizzo di ritorno da nessun altro valore sullo stack, quindi non può eseguire la scansione dello stack per trovare successivamente il prossimo indirizzo di ritorno. Il debugger non può determinare in modo affidabile la quantità di spazio dello stack utilizzata in locale e ignorarlo. Seriamente, leggi le pagine collegate. – DCoder