Ответ 1
x86-64 ABI, используемая Linux (и некоторые другие ОС, хотя, в частности, не Windows, у которой есть свой собственный ABI), определяет "красную зону" 128 байт ниже указателя стека, который гарантированно не будет затронут сигналами или обработчиками прерываний. (См. Рис. 3.3 и п. 3.2.2.)
Функция листа (то есть одна, которая не вызывает ничего другого), поэтому может использовать эту область для чего угодно - она не делает ничего подобного call
, который помещает данные в указатель стека; и любой обработчик сигнала или прерывания будет следовать за ABI и удалять указатель стека хотя бы на дополнительные 128 байтов, прежде чем хранить что-либо.
(Короткие кодировки команд доступны для подписанных 8-битных перемещений, поэтому точкой красной зоны является то, что она увеличивает количество локальных данных, доступ к которым может использовать функция листа, используя эти более короткие инструкции.)
Что здесь происходит.
Но... этот код не использует эти более короткие кодировки (он использует смещения от rbp
, а не rsp
). Почему нет? Он также сохраняет ненужные значения edi
и rsi
- вы спрашиваете, почему он сохраняет edi
вместо rdi
, но почему он вообще сохраняет его?
Ответ заключается в том, что компилятор генерирует действительно мутный код, потому что оптимизация не включена. Если вы включите какую-либо оптимизацию, вся ваша функция, скорее всего, упадет до:
mov eax, 0
ret
потому что это действительно все, что ему нужно сделать: buffer[]
является локальным, поэтому внесенные в него изменения никогда не будут видны никому другому, поэтому их можно оптимизировать; кроме того, все, что нужно сделать, это вернуть 0.
Итак, вот лучший пример. Эта функция является полной бессмыслицей, но использует подобный массив, делая достаточно, чтобы гарантировать, что все не все оптимизировано:
$ 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];
}
Скомпилированный с некоторой оптимизацией, вы можете увидеть подобное использование красной зоны,
кроме этого времени он действительно использует смещения от 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
Теперь немного измените его, вставив вызов другой функции,
так что foo()
уже не является листовой функцией:
$ 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];
}
Теперь красную зону нельзя использовать, поэтому вы видите что-то большее, как вы первоначально ожидалось:
$ 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
(Обратите внимание, что tmp[200]
находился в диапазоне подписанного 8-битного смещения в первом случае, но не находится в этом.)