2016-06-21 14 views
5

Ho i seguenti dubbi:Perché abbiamo bisogno di allocare lo stack quando abbiamo una zona rossa?

Come sappiamo System V x86-64 ABI ci fornisce un'area di dimensioni fisse (128 byte) nel frame dello stack, detta anche zona rossa. Quindi, come risultato, non è necessario utilizzare, ad esempio, sub rsp, 12. Basta fare mov [rsp-12], X e basta.

Ma non riesco a capirlo. Perchè importa? È necessario sub rsp, 12 senza redzone? Dopo tutto, la dimensione dello stack è limitata all'inizio, quindi perché sub rsp, 12 è importante? So che ci permette di seguire la cima della pila ma ignoriamola in quel momento.

So che alcune istruzioni usano il valore rsp (come ret) ma non mi interessa in quel momento.

Il nocciolo del problema è: Non abbiamo Red Zone e che abbiamo fatto:

function: 
    mov [rsp-16], rcx 
    mov [rsp-32], rcx 
    mov [rsp-128], rcx 
    mov [rsp-1024], rcx 
    ret 

E 'la differenza con?

function: 
    sub rsp, 1024 
    mov [rsp-16], rcx 
    mov [rsp-32], rcx 
    mov [rsp-128], rcx 
    mov [rsp-1024], rcx 
    add rsp, 1024 
    ret 
+0

Il secondo snippet di codice che hai mostrato qui è sbagliato. Se decrementi il ​​puntatore dello stack, devi * ripristinarlo prima di tornare dalla funzione. Quindi, dovresti aggiungere 'add rsp, 1024' prima di' ret'. –

+0

Quale ABI è quello? Presumo quello Linux, ma ce ne sono altri, ad es. quello per Windows 64, Mac OS X 64 bit, ecc. –

+1

@rudy Per quanto ne so, ci sono solo due ABI x86-64: il System V AMD64 ABI (usato da Linux, Solaris, OS X e altri POSIX -compliant operating systems) e l'implementazione di Microsoft utilizzata su Windows. La domanda sembra riguardare la prima. –

risposta

9

La "zona rossa" non è strettamente necessaria. Secondo i tuoi termini, potrebbe essere considerato "inutile". Tutto ciò che si può fare usando la zona rossa, si potrebbe anche fare il modo tradizionale in cui lo si faceva mirando all'ABI IA-32.

Ecco ciò che il AMD64 ABI dice circa la "zona rossa":

L'area di 128 byte al di là della posizione puntata da %rsp è considerata riservata e non può essere modificato da un segnale o interrompere gestori. Pertanto, le funzioni possono utilizzare quest'area per i dati temporanei che non sono necessari per le chiamate di funzione. In particolare, le funzioni foglia possono usare quest'area per l'intero frame dello stack, piuttosto che regolare il puntatore dello stack nel prologo e nell'epilogo. Questa zona è conosciuta come la zona rossa.

Il vero scopo della zona rossa è come un ottimizzazione. La sua esistenza consente al codice di presumere che i 128 byte inferiori a rsp non vengano violati in modo asincrono dai segnali o dai gestori di interrupt, il che rende possibile utilizzarlo come spazio di lavoro. Ciò rende superfluo creare in modo esplicito lo spazio libero nello stack spostando il puntatore dello stack in rsp. Questa è un'ottimizzazione perché ora è possibile eliminare le istruzioni per decrementare e ripristinare rsp, risparmiando tempo e spazio.

Quindi sì, mentre si potrebbe fare questo con AMD64 (e avrebbe bisogno di farlo con IA-32):

function: 
    push rbp      ; standard "prologue" to save the 
    mov rbp, rsp     ; original value of rsp 

    sub rsp, 32     ; reserve scratch area on stack 
    mov QWORD PTR [rsp], rcx ; copy rcx into our scratch area 
    mov QWORD PTR [rsp+8], rdx ; copy rdx into our scratch area 

    ; ...do something that clobbers rcx and rdx... 

    mov rcx, [rsp]    ; retrieve original value of rcx from our scratch area 
    mov rdx, [rsp+8]    ; retrieve original value of rdx from our scratch area 
    add rsp, 32     ; give back the stack space we used as scratch area 

    pop rbp      ; standard "epilogue" to restore rsp 
    ret 

Non necessità di farlo nei casi in cui si serve solo un'area scratch da 128 byte (o più piccola), perché allora possiamo usare la zona rossa come area di scratch.

Inoltre, dal momento che non abbiamo più per diminuire lo stack pointer, possiamo usare rsp come il puntatore base (invece di rbp), rendendo inutile per salvare e ripristinare rbp (nel prologo ed epilogo), e anche liberando su rbp da utilizzare come un altro registro generale!

(Tecnicamente, accendendo frame-pointer omissione (, abilitato di default con -O1 poiché l'ABI consente) sarebbe anche rendere possibile per il compilatore elidere prologo e sezioni epilogo, con gli stessi vantaggi. Tuttavia, in assenza di una zona rossa, la necessità di regolare il puntatore dello stack per riservare lo spazio non cambierà.)

Nota, tuttavia, l'ABI garantisce solo che le cose asincrone come i segnali e i gestori di interrupt non modificano la zona rossa. Le chiamate ad altre funzioni possono sovraccaricare i valori nella zona rossa, quindi non è particolarmente utile se non nelle funzioni foglia (che sono quelle funzioni che non chiamano altre funzioni, come se fossero nella "foglia" di un albero di chiamata di funzioni) .


Un ultimo punto: il Windows x64 ABIdeviates slightly from the AMD64 ABI used on other operating systems. In particolare, non ha alcun concetto di "zona rossa". L'area oltre rsp è considerata volatile e può essere sovrascritta in qualsiasi momento. Al contrario, richiede che il chiamante assegni uno stack home address space, che è quindi disponibile per l'utilizzo del callee nel caso in cui debba rovesciare uno dei parametri passati dal registro.

+0

Ok, ora è chiaro. Quindi, capisco che il gestore di segnale/interrupt del nostro processo prenda (in effetti il ​​sistema operativo lo fornisce) 'rsp' e lo usa. E, in effetti, può fare il problema quando abbiamo qualcosa sotto 'rsp'. Ovviamente è una situazione senza redzone. Si? – Gilgamesz

+1

Un gestore di segnale o interrupt * non può * utilizzare la zona rossa. Questo è garantito dall'ABI. –

3

Hai gli offset nel modo sbagliato nel tuo esempio, motivo per cui non ha senso. Il codice non dovrebbe accedere alla regione sotto il puntatore dello stack: non è definito. La zona rossa è lì per proteggere i primi 128 byte sotto il puntatore dello stack. Tuo secondo esempio dovrebbe contenere:

function: 
    sub rsp, 1024 
    mov [rsp+16], rcx 
    mov [rsp+32], rcx 
    mov [rsp+128], rcx 
    mov [rsp+1016], rcx 
    add rsp, 1024 
    ret 

Se la quantità di spazio graffio che una funzione ha bisogno è di 128 byte, allora può utilizzare indirizzi sotto il puntatore di stack senza dover regolare la pila: questo è l'ottimizzazione . Confrontare:

function:  // Not using red-zone. 
    sub rsp, 128 
    mov [rsp+120], rcx 
    add rsp, 128 
    ret 

Dello stesso codice usando l'rosso-zona:

function:  // Using the red-zone, no adjustment of stack 
    mov [rsp-8], rcx 
    ret 

La confusione riguardo gli offset dalla stack pointer è normalmente causato perché compilatori generano offset negativi dal telaio (RBP), offset non positivi dallo stack (RSP).

+1

'-fomit-frame-pointer' è il valore predefinito per gcc targeting Linux, a' -O1' e oltre, ed è stato per anni. Solitamente si vedono solo gli offset da 'rbp' per i locali nell'output' -O0', che non è molto divertente da guardare. Fatto divertente: la dimensione della zona rossa è stata scelta perché '-128' è lo spostamento massimo di un byte da' rsp'. –

+0

Non sapevo che fosse diventato un default. Sono più vecchio di quanto sembri :) – Amoss

+0

@Peter, perché non 255 byte, dato che il byte senza segno può assumere il valore massimo di 255? –

Problemi correlati