2011-12-01 21 views
11

Curiosità gcc davvero bizzarra. Controllare questo fuori:Perché GCC sottrarre il valore errato al puntatore dello stack quando si assegna un array grande senza chiamate di funzione successive?

main() { int a[100]; a[0]=1; } 

produce questo complesso:

0: 55      push %rbp 
    1: 48 89 e5    mov %rsp,%rbp 
    4: 48 81 ec 18 01 00 00 sub $0x118,%rsp 
    b: c7 85 70 fe ff ff 01 movl $0x1,-0x190(%rbp) 
    12: 00 00 00 
    15: c9      leaveq 
    16: c3      retq 

La parte superiore della pila è chiaramente 400, siccome è un array di 100 * 4. Quindi quando scrive alla prima voce, lo fa rbp - 400 (riga 'b'). Buona. Ma perché sottrae 280 dal puntatore stack (riga '4')? Non punta al centro dell'array?

Se aggiungiamo una chiamata di funzione dopo, gcc fa la cosa giusta:

b() {} 
main() { int a[100]; a[0]=1; b(); } 

produce questa assemblea:

0000000000000000 <b>: 
    0: 55      push %rbp 
    1: 48 89 e5    mov %rsp,%rbp 
    4: c9      leaveq 
    5: c3      retq 

0000000000000006 <main>: 
    6: 55      push %rbp 
    7: 48 89 e5    mov %rsp,%rbp 
    a: 48 81 ec 90 01 00 00 sub $0x190,%rsp 
    11: c7 85 70 fe ff ff 01 movl $0x1,-0x190(%rbp) 
    18: 00 00 00 
    1b: b8 00 00 00 00   mov $0x0,%eax 
    20: e8 00 00 00 00   callq 25 <main+0x1f> 
    25: c9      leaveq 
    26: c3      retq 

Qui, si sottrae correttamente 400 (linea 'A').

Perché la modifica quando si aggiunge una chiamata di funzione? Gcc è solo pigro e non lo fa correttamente perché non importa? Cosa sta succedendo? Evidentemente ciò accade solo durante la compilazione per x86_64, ma non per x86 normale. Questo ha qualcosa di strano con "redzone" di x86_64? Cosa sta succedendo precisamente?

+0

Perché sei preoccupato per il codice che non ha alcun effetto, come è evidente dal codice !? Inoltre, il tuo (secondo) esempio dovrebbe utilizzare alcune convenzioni di chiamata in cui è coinvolto lo stack, perché non vi è alcun passaggio di parametri coinvolto (nello stack) nell'esempio. Nota a margine: odio il montaggio AT & T :) – 0xC0000022L

+3

Forse per curiosità? O non è questo un motivo valido nel tuo libro? A proposito, ho trovato le risposte illuminanti – hirschhornsalz

risposta

13

L'ipotesi è corretta. È una "zona rossa". La zona rossa è lo spazio da rsp-128 a rsp, che può essere utilizzato da una funzione per le variabili locali e per l'archiviazione temporanea. Questo spazio non è toccato dai gestori di interrupt ed eccezioni. Ovviamente, la zona rossa viene distrutta dalle chiamate di funzione, quindi se viene chiamata una funzione qualsiasi, nessuna variabile locale può essere nella zona rossa.

La zona rossa può essere utilizzata solo in Linux a 64 bit, BSD e Mac. Non è disponibile nel codice del kernel.

Può essere utilizzato per ottimizzare lo spazio, poiché con la zona rossa è possibile fare riferimento a un massimo di 512 byte di variabili locali con istruzioni brevi, basate solo su rsp e ebp. Senza la zona rossa sono disponibili solo 384 byte. Tutte le variabili locali al di fuori di questo limite sono accessibili con codice più lungo o con registri aggiuntivi.

Per l'esempio, l'utilizzo della zona rossa non è necessario, ma gcc preferisce utilizzarlo per tutte le funzioni "foglia". È semplicemente più semplice implementare il compilatore in questo modo.

+0

Informazioni sulla zona rossa non disponibile nel codice del kernel: in realtà, il kernel Linux non la onora. –

5

L'x86-64 ABI richiede una "zona rossa" di 128 byte oltre il puntatore dello stack che può essere utilizzato senza modificare %rsp. Nel primo esempio, main() è una funzione foglia, quindi il compilatore sta ottimizzando l'uso dello spazio di stack, ovvero non ci sono chiamate di funzione, quindi questa regione non verrà sovrascritta.