Qual è lo scopo del registro del puntatore del frame EBP?


95

Sono un principiante nel linguaggio assembly e ho notato che il codice x86 emesso dai compilatori di solito mantiene il puntatore del frame anche in modalità di rilascio / ottimizzato quando potrebbe usare il EBPregistro per qualcos'altro.

Capisco perché il puntatore al frame potrebbe semplificare il debug del codice e potrebbe essere necessario se alloca()viene chiamato all'interno di una funzione. Tuttavia, x86 ha pochissimi registri e usarne due per contenere la posizione dello stack frame quando uno sarebbe sufficiente non ha senso per me. Perché omettere il frame pointer è considerata una cattiva idea anche nelle build ottimizzate / di rilascio?


19
Se pensi che x86 abbia pochissimi registri dovresti controllare 6502 :)
Sedat Kapanoglu


1
Anche C99 VLA può trarne vantaggio.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功


1
Il puntatore del fotogramma non rende ridondante il puntatore dello stack? . TL; DR: 1. allineamento dello stack non banale 2. allocazione dello stack ( alloca) 3. facilità di implementazione del runtime: gestione delle eccezioni, sandbox, GC
Alexander Malakhov

Risposte:


102

Il puntatore al frame è un puntatore di riferimento che consente a un debugger di sapere dove si trova una variabile locale o un argomento con un singolo offset costante. Sebbene il valore di ESP cambi nel corso dell'esecuzione, EBP rimane lo stesso, consentendo di raggiungere la stessa variabile con lo stesso offset (come il primo parametro sarà sempre EBP + 8 mentre gli offset ESP possono cambiare in modo significativo poiché si spinge / schioccare cose)

Perché i compilatori non buttano via il frame pointer? Perché con il puntatore al frame, il debugger può capire dove le variabili e gli argomenti locali stanno usando la tabella dei simboli poiché è garantito che siano a un offset costante rispetto a EBP. Altrimenti non c'è un modo semplice per capire dove si trova una variabile locale in qualsiasi punto del codice.

Come menzionato da Greg, aiuta anche a svolgere lo stack per un debugger poiché EBP fornisce un elenco collegato inverso di stack frame, consentendo quindi al debugger di calcolare la dimensione dello stack frame (variabili locali + argomenti) della funzione.

La maggior parte dei compilatori fornisce un'opzione per omettere i puntatori ai frame sebbene renda il debug molto difficile. Questa opzione non dovrebbe mai essere utilizzata globalmente, nemmeno nel codice di rilascio. Non sai quando dovrai eseguire il debug del crash di un utente.


10
Il compilatore probabilmente sa cosa fa a ESP. Gli altri punti sono validi, però, +1
erikkallen

8
I debugger moderni possono eseguire lo stack backtrace anche nel codice compilato con -fomit-frame-pointer. Questa impostazione è l'impostazione predefinita nel recente gcc.
Peter Cordes

2
@SedatKapanoglu: una sezione dati registra le informazioni necessarie: yosefk.com/blog/…
Peter Cordes

3
@SedatKapanoglu: la .eh_frame_hdrsezione viene utilizzata anche per le eccezioni di runtime. Lo troverai (con objdump -h) nella maggior parte dei binari su un sistema Linux, è circa 16k per /bin/bash, contro 572B per GNU /bin/true, 108k per ffmpeg. C'è un'opzione gcc per disabilitare la generazione, ma è una sezione di dati "normale", non una sezione di debug che striprimuove per impostazione predefinita. Altrimenti non potresti tornare indietro attraverso una funzione di libreria che non aveva simboli di debug. Quella sezione può essere più grande delle push/mov/popistruzioni che sostituisce, ma ha un costo di runtime vicino allo zero (es. Cache uop).
Peter Cordes

3
Riguardo a "come il primo parametro sarà sempre su EBP-4": Non è il primo parametro su EBP + 8 (su x86)?
Aydin K.

31

Aggiungo solo i miei due centesimi a risposte già buone.

Fa parte di una buona architettura del linguaggio avere una catena di stack frame. Il BP punta al frame corrente, dove sono memorizzate le variabili locali della subroutine. (Le persone locali hanno offset negativi e gli argomenti sono offset positivi.)

L'idea che impedisca l'utilizzo di un registro perfettamente valido nell'ottimizzazione solleva la domanda: quando e dove l'ottimizzazione è effettivamente utile?

L'ottimizzazione è utile solo in cicli ristretti che 1) non chiamano funzioni, 2) dove il contatore del programma trascorre una frazione significativa del suo tempo e 3) nel codice che il compilatore effettivamente vedrà mai (cioè funzioni non di libreria). Questa è solitamente una frazione molto piccola del codice complessivo, specialmente nei sistemi di grandi dimensioni.

Altro codice può essere distorto e compresso per sbarazzarsi dei cicli, e semplicemente non avrà importanza, perché il contatore del programma non è praticamente mai lì.

So che non l'hai chiesto, ma nella mia esperienza, il 99% dei problemi di prestazioni non ha nulla a che fare con l'ottimizzazione del compilatore. Hanno tutto a che fare con l'over-design.


Grazie @Mike, ho trovato molto utile la tua risposta.
sixtyfootersdude

2
L'eliminazione del puntatore al frame consente di risparmiare anche un paio di istruzioni per ogni chiamata di funzione, che è una piccola ottimizzazione di per sé. A proposito, il tuo uso di "implora la domanda" non è corretto; vuoi dire "solleva la domanda".
augurar

@augurar: risolto. Grazie. Sono un po 'un
burbero

3
@augurar Il linguaggio si evolve: "implora la domanda" ora significa solo "solleva la domanda". Essere un pignolo prescrittivista per un utilizzo obsoleto non aggiunge nulla.
user3364825

9

Dipende dal compilatore, certamente. Ho visto codice ottimizzato emesso da compilatori x86 che utilizza liberamente il registro EBP come registro per scopi generali. (Non ricordo con quale compilatore l'ho notato, però.)

I compilatori possono anche scegliere di mantenere il registro EBP per facilitare lo svolgimento dello stack durante la gestione delle eccezioni, ma ancora una volta questo dipende dalla precisa implementazione del compilatore.


L'impostazione predefinita della maggior parte dei compilatori è -fomit-frame-pointerquando l'ottimizzazione è abilitata. (quando l'ABI lo consente). GCC, clang, ICC e MSVC lo fanno tutti, IIRC, anche quando prendono di mira Windows a 32 bit. Sì, la mia risposta su Perché è meglio usare ebp rispetto al registro esp per individuare i parametri sullo stack? mostra che anche Windows a 32 bit può omettere il puntatore al frame. Linux x86 a 32 bit sicuramente può farlo e lo fa. E naturalmente gli ABI a 64 bit hanno consentito l'omissione del frame-pointer dall'inizio.
Peter Cordes

4

Tuttavia, x86 ha pochissimi registri

Questo è vero solo nel senso che gli opcode possono indirizzare solo 8 registri. Il processore stesso avrà in realtà molti più registri di quello e utilizzerà la ridenominazione dei registri, il pipelining, l'esecuzione speculativa e altre parole d'ordine del processore per aggirare quel limite. Wikipedia ha un buon paragrafo introduttivo su cosa può fare un processore x86 per superare il limite di registrazione: http://en.wikipedia.org/wiki/X86#Current_implementations .


1
La domanda originale riguarda il codice generato, che è strettamente limitato ai registri referenziabili dagli opcode.
Darron

1
Sì, ma questo è il motivo per cui al giorno d'oggi omettere il puntatore del frame nelle build ottimizzate non è così importante.
Michael,

1
Tuttavia, rinominare i registri non è esattamente la stessa cosa che avere a disposizione un numero maggiore di registri. Ci sono ancora molte situazioni in cui la ridenominazione dei registri non aiuta, ma i registri più "regolari" sì.
jalf

1

L'uso di stack frame è diventato incredibilmente economico in qualsiasi hardware, anche remotamente moderno. Se hai stack frame economici, salvare un paio di registri non è così importante. Sono sicuro che gli stack frame veloci rispetto a più registri sono stati un compromesso ingegneristico e gli stack frame veloci hanno vinto.

Quanto risparmi andando puro registro? Ne vale la pena?


Più registri sono limitati dalla codifica delle istruzioni. x86-64 utilizza bit nel byte del prefisso REX per estendere la parte delle istruzioni che specifica il registro da 3 a 4 bit per i registri src e dest. Se ci fosse spazio, x86-64 probabilmente sarebbe andato a 32 registri architetturali, sebbene il salvataggio / ripristino di così tanti cambi di contesto inizi a sommarsi. 15 è un enorme passo avanti rispetto a 7, ma 31 è un miglioramento molto minore nella maggior parte dei casi. (senza contare il puntatore dello stack come generico.) Rendere push / pop veloce è ottimo per qualcosa di più del semplice stack frame. Tuttavia, non è un compromesso con il numero di registri.
Peter Cordes
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.