2012-11-02 8 views
10

Si consideri il seguente programma semplice:Perché il prologo della funzione GCC x86-64 alloca meno stack rispetto alle variabili locali?

int main(int argc, char **argv) 
{ 
     char buffer[256]; 

     buffer[0] = 0x41; 
     buffer[128] = 0x41; 
     buffer[255] = 0x41; 

     return 0; 
} 

compilato con GCC 4.7.0 su una macchina x86-64. Smontaggio del main() con GDB dà:

0x00000000004004cc <+0>:  push rbp 
0x00000000004004cd <+1>:  mov rbp,rsp 
0x00000000004004d0 <+4>:  sub rsp,0x98 
0x00000000004004d7 <+11>: mov DWORD PTR [rbp-0x104],edi 
0x00000000004004dd <+17>: mov QWORD PTR [rbp-0x110],rsi 
0x00000000004004e4 <+24>: mov BYTE PTR [rbp-0x100],0x41 
0x00000000004004eb <+31>: mov BYTE PTR [rbp-0x80],0x41 
0x00000000004004ef <+35>: mov BYTE PTR [rbp-0x1],0x41 
0x00000000004004f3 <+39>: mov eax,0x0 
0x00000000004004f8 <+44>: leave 
0x00000000004004f9 <+45>: ret  

Perché è sub RSP solo 0x98 = 152d quando il buffer è 256 byte? Quando metto i dati nel buffer [0] sembra semplicemente utilizzare i dati al di fuori del frame dello stack allocato e usare rbp come riferimento, quindi qual è anche il punto del sub rsp, 0x98?

Un'altra domanda, che cosa fanno queste linee?

0x00000000004004d7 <+11>: mov DWORD PTR [rbp-0x104],edi 
0x00000000004004dd <+17>: mov QWORD PTR [rbp-0x110],rsi 

Perché l'EDI e non l'RDI devono essere salvati? Vedo che si sposta al di fuori della gamma massima del buffer allocato nel codice C tuttavia. È interessante anche il motivo per cui il delta tra le due variabili è così grande. Dal momento che EDI è solo 4 byte, perché ha bisogno di una separazione di 12 byte per le due variabili?

+2

La separazione di 12 byte è dovuta all'allineamento. 'rsi' è 8 byte, quindi il padding è necessario per mantenerlo allineato a 8 byte. Ma non posso parlare per la sotto-assegnazione dello stack. – Mysticial

+0

Probabilmente salva EDI e RSI semplicemente perché non è necessario salvarli dal chiamante? Ma il modo in cui vengono salvati sembra ancora strano. – csstudent2233

+0

cosa succede quando lo compili con 'gcc -s' (per ottenere l'output dell'assembly) - perché se non hai il debug attivato nella compilation in primo luogo, i tuoi risultati gdb possono essere dispari – KevinDTimm

risposta

15

I x86-64 ABI used by Linux (e alcuni altri sistemi operativi, sebbene notevolmente non Windows, che ha il suo diverso ABI) definisce una "zona rossa" di 128 byte di sotto del puntatore dello stack, che è garantito non essere toccato dal segnale o interrompe i gestori. (Vedi figura 3.3 e §3.2.2.)

Una funzione foglia (cioè una che non chiama altro) può quindi utilizzare quest'area per quello che vuole - non sta facendo nulla di simile a uno call che posizionerebbe dati al puntatore dello stack; e qualsiasi segnale o gestore di interrupt seguirà l'ABI e lascerà cadere il puntatore dello stack di almeno altri 128 byte prima di memorizzare qualcosa.

(codifiche di istruzioni più brevi sono disponibili per firmati spostamenti a 8 bit, in modo che il punto della zona rossa è che aumenta la quantità di dati locali che una funzione foglia può accedere utilizzando queste istruzioni più brevi.)

Ecco cosa sta succedendo qui.

Ma ... questo codice non sta facendo uso di quelle codifiche più brevi (utilizza offset da rbp anziché da rsp). Perchè no? Sta inoltre risparmiando completamente edi e rsi - chiedi perché sta salvando edi invece di rdi, ma perché lo sta salvando?

La risposta è che il compilatore sta generando codice veramente scadente, perché non sono attivate ottimizzazioni. Se si attiva alcuna ottimizzazione, la vostra intera funzione è probabile collasso verso il basso per:

mov eax, 0 
ret 

perché questo è davvero tutto ciò che deve fare: buffer[] è locale, in modo che le modifiche apportate ad esso non sarà mai visibile a qualsiasi altra cosa, quindi può essere ottimizzato; oltre a questo, tutte le funzioni ha bisogno di fare è tornare 0.


Quindi, ecco un esempio migliore.Questa funzione è assolutamente privi di senso, ma si avvale di una matrice simile, pur facendo abbastanza per assicurare che le cose non tutte vengono ottimizzate via:

$ cat test.c 
int foo(char *bar) 
{ 
    char tmp[256]; 
    int i; 

    for (i = 0; bar[i] != 0; i++) 
     tmp[i] = bar[i] + i; 

    return tmp[1] + tmp[200]; 
} 

Compilato con qualche ottimizzazione, si può vedere l'uso simile della zona rossa , ma questa volta lo fa davvero usare gli offset da rsp:

$ gcc -m64 -O1 -c test.c 
$ objdump -Mintel -d test.o 

test.o:  file format elf64-x86-64 


Disassembly of section .text: 

0000000000000000 <foo>: 
    0: 53      push rbx 
    1: 48 81 ec 88 00 00 00 sub rsp,0x88 
    8: 0f b6 17    movzx edx,BYTE PTR [rdi] 
    b: 84 d2     test dl,dl 
    d: 74 26     je  35 <foo+0x35> 
    f: 4c 8d 44 24 88   lea r8,[rsp-0x78] 
    14: 48 8d 4f 01    lea rcx,[rdi+0x1] 
    18: 4c 89 c0    mov rax,r8 
    1b: 89 c3     mov ebx,eax 
    1d: 44 28 c3    sub bl,r8b 
    20: 89 de     mov esi,ebx 
    22: 01 f2     add edx,esi 
    24: 88 10     mov BYTE PTR [rax],dl 
    26: 0f b6 11    movzx edx,BYTE PTR [rcx] 
    29: 48 83 c0 01    add rax,0x1 
    2d: 48 83 c1 01    add rcx,0x1 
    31: 84 d2     test dl,dl 
    33: 75 e6     jne 1b <foo+0x1b> 
    35: 0f be 54 24 50   movsx edx,BYTE PTR [rsp+0x50] 
    3a: 0f be 44 24 89   movsx eax,BYTE PTR [rsp-0x77] 
    3f: 8d 04 02    lea eax,[rdx+rax*1] 
    42: 48 81 c4 88 00 00 00 add rsp,0x88 
    49: 5b      pop rbx 
    4a: c3      ret  

Ora cerchiamo di modificarlo leggermente, con l'inserimento di una chiamata a un'altra funzione, in modo che foo() è non è più una funzione di foglia:

$ cat test.c 
extern void dummy(void); /* ADDED */ 

int foo(char *bar) 
{ 
    char tmp[256]; 
    int i; 

    for (i = 0; bar[i] != 0; i++) 
     tmp[i] = bar[i] + i; 

    dummy(); /* ADDED */ 

    return tmp[1] + tmp[200]; 
} 

Ora la zona rossa non può essere utilizzato, in modo da vedere qualcosa di più come te originariamente previsto:

$ gcc -m64 -O1 -c test.c 
$ objdump -Mintel -d test.o 

test.o:  file format elf64-x86-64 


Disassembly of section .text: 

0000000000000000 <foo>: 
    0: 53      push rbx 
    1: 48 81 ec 00 01 00 00 sub rsp,0x100 
    8: 0f b6 17    movzx edx,BYTE PTR [rdi] 
    b: 84 d2     test dl,dl 
    d: 74 24     je  33 <foo+0x33> 
    f: 49 89 e0    mov r8,rsp 
    12: 48 8d 4f 01    lea rcx,[rdi+0x1] 
    16: 48 89 e0    mov rax,rsp 
    19: 89 c3     mov ebx,eax 
    1b: 44 28 c3    sub bl,r8b 
    1e: 89 de     mov esi,ebx 
    20: 01 f2     add edx,esi 
    22: 88 10     mov BYTE PTR [rax],dl 
    24: 0f b6 11    movzx edx,BYTE PTR [rcx] 
    27: 48 83 c0 01    add rax,0x1 
    2b: 48 83 c1 01    add rcx,0x1 
    2f: 84 d2     test dl,dl 
    31: 75 e6     jne 19 <foo+0x19> 
    33: e8 00 00 00 00   call 38 <foo+0x38> 
    38: 0f be 94 24 c8 00 00 movsx edx,BYTE PTR [rsp+0xc8] 
    3f: 00 
    40: 0f be 44 24 01   movsx eax,BYTE PTR [rsp+0x1] 
    45: 8d 04 02    lea eax,[rdx+rax*1] 
    48: 48 81 c4 00 01 00 00 add rsp,0x100 
    4f: 5b      pop rbx 
    50: c3      ret  

(Si noti che tmp[200] era nella gamma di un segno a 8 -bit spostamento nel primo caso, ma non è in questo.)

+0

Ottima risposta illustrativa. In particolare l'osservazione che con l'ottimizzazione, dovrebbe semplicemente impostare 'eax = 0' e ritornare. –

+0

Forse memorizza edi invece di rsi perché è un int, non lungo. Né è necessario tuttavia come dici tu. –