GNU/Linux >> Linux Esercitazione >  >> Linux

Cercando di capire il complicato allineamento dello stack di gcc nella parte superiore di main che copia l'indirizzo di ritorno

Ci ho provato:

;# As you have already noticed, the compiler wants to align the stack
;# pointer on a 16 byte boundary before it pushes anything. That's
;# because certain instructions' memory access needs to be aligned
;# that way.
;# So in order to first save the original offset of esp (+4), it
;# executes the first instruction:
lea    ecx,[esp+0x4]

;# Now alignment can happen. Without the previous insn the next one
;# would have made the original esp unrecoverable:
and    esp,0xfffffff0

;# Next it pushes the return addresss and creates a stack frame. I
;# assume it now wants to make the stack look like a normal
;# subroutine call:
push   DWORD PTR [ecx-0x4]
push   ebp
mov    ebp,esp

;# Remember that ecx is still the only value that can restore the
;# original esp. Since ecx may be garbled by any subroutine calls,
;# it has to save it somewhere:
push   ecx

Questa operazione viene eseguita per mantenere lo stack allineato a un limite di 16 byte. Alcune istruzioni richiedono che determinati tipi di dati siano allineati su un limite massimo di 16 byte. Per soddisfare questo requisito, GCC si assicura che lo stack sia inizialmente allineato a 16 byte e alloca lo spazio dello stack in multipli di 16 byte. Questo può essere controllato usando l'opzione -mpreferred-stack-boundary=num . Se utilizzi -mpreferred-stack-boundary=2 (per un allineamento 2=4 byte), questo codice di allineamento non verrà generato perché lo stack è sempre allineato almeno a 4 byte. Tuttavia potresti avere problemi se il tuo programma utilizza tipi di dati che richiedono un allineamento più forte.

Secondo il manuale di gcc:

Su Pentium e PentiumPro, i valori double e long double dovrebbero essere allineati a un limite di 8 byte (vedere -malign-double) o subire significative penalizzazioni delle prestazioni in fase di esecuzione. Su Pentium III, il tipo di dati SSE (Streaming SIMD Extension) __m128 potrebbe non funzionare correttamente se non è allineato a 16 byte.

Per garantire il corretto allineamento di questi valori nello stack, il limite dello stack deve essere allineato come quello richiesto da qualsiasi valore memorizzato nello stack. Inoltre, ogni funzione deve essere generata in modo tale da mantenere lo stack allineato. Pertanto, chiamare una funzione compilata con un limite di stack preferito più alto da una funzione compilata con un limite di stack preferito inferiore molto probabilmente disallineerà lo stack. Si raccomanda che le librerie che utilizzano i callback utilizzino sempre l'impostazione predefinita.

Questo allineamento aggiuntivo consuma spazio aggiuntivo nello stack e generalmente aumenta la dimensione del codice. Il codice sensibile all'utilizzo dello spazio dello stack, come i sistemi embedded e i kernel del sistema operativo, potrebbe voler ridurre l'allineamento preferito a -mpreferred-stack-boundary=2.

Il lea carica il puntatore dello stack originale (da prima della chiamata a main ) in ecx , poiché il puntatore dello stack sta per essere modificato. Viene utilizzato per due scopi:

  1. per accedere agli argomenti del main funzione, poiché sono relativi al puntatore dello stack originale
  2. per ripristinare il puntatore dello stack al suo valore originale al ritorno da main

lea    ecx,[esp+0x4] ; I assume this is for getting the adress of the first argument of     the main...why ?
and    esp,0xfffffff0 ; ??? is the compiler trying to align the stack pointer on 16 bytes ???
push   DWORD PTR [ecx-0x4] ; I understand the assembler is pushing the return adress....why ?
push   ebp                
mov    ebp,esp
push   ecx  ;why is ecx pushed too ??

Anche se ogni istruzione funzionasse perfettamente senza penalità di velocità nonostante gli operandi arbitrariamente allineati, l'allineamento aumenterebbe comunque le prestazioni. Immagina un ciclo che fa riferimento a una quantità di 16 byte che si sovrappone solo a due righe della cache. Ora, per caricare quel piccolo wchar nella cache, devono essere rimosse due intere righe di cache, e cosa succede se ne hai bisogno nello stesso ciclo? La cache è così tremendamente più veloce della RAM che le prestazioni della cache sono sempre fondamentali.

Inoltre, di solito c'è una penalità di velocità per spostare gli operandi disallineati nei registri. Dato che lo stack viene riallineato, dobbiamo naturalmente salvare il vecchio allineamento per attraversare i frame dello stack per i parametri e tornare.

ecx è un registro temporaneo quindi deve essere salvato. Inoltre, a seconda del livello di ottimizzazione, alcune delle operazioni di collegamento dei frame che non sembrano strettamente necessarie per eseguire il programma potrebbero essere importanti per impostare una catena di frame pronta per la traccia.


Linux
  1. Il kernel Linux:le 5 migliori innovazioni

  2. Trova la geolocalizzazione di un indirizzo IP dalla riga di comando

  3. Trova il computer su una rete LAN?

  4. Cos'è l'utente debian-+?

  5. Cercando di capire il modo corretto per creare un percorso statico in CentOS, si prega di aiutare

VA Linux:la società Linux che un tempo governava il NASDAQ

Come trovare l'indirizzo IP di una macchina virtuale KVM

Come personalizzare il comando top di Linux

Un modo semplice per comprendere il comando IOStat

Come trovare l'indirizzo IP condiviso principale del tuo server in cPanel

I 20 giochi Steam più votati per Linux a cui non puoi resistere