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.)
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
Probabilmente salva EDI e RSI semplicemente perché non è necessario salvarli dal chiamante? Ma il modo in cui vengono salvati sembra ancora strano. – csstudent2233
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