The x86-64 ABI used by Linux (i niektórych innych systemów operacyjnych, chociaż szczególnie nie Windows, który ma swój własny inny ABI) definiuje „czerwonej strefy” 128 bajtów poniżej wskaźnika stosu, który nie jest gwarantowana być dotykane przez sygnału lub programy obsługi przerw. (Patrz rys. 3.3 i §3.2.2.)
Funkcja liścia (czyli taka, która nie wywołuje niczego innego) może w związku z tym korzystać z tego obszaru, aby cokolwiek chce - nie robi nic takiego jak call
, który dane na wskaźniku stosu; i dowolny sygnał lub przerywnik będzie podążać za ABI i upuść wskaźnik stosu o co najmniej dodatkowe 128 bajtów przed przechowywaniem czegokolwiek.
(kodowanie Krótsze instrukcje są dostępne dla podpisanych 8-bitowych przemieszczeń, więc punkt czerwonej strefie jest to, że zwiększa ilość lokalnych danych, że funkcja liści można uzyskać dostęp za pomocą tych krótszych instrukcji.)
To co tu się dzieje.
Ale ... ten kod nie korzysta z tych krótszych kodowań (używa przesunięć od rbp
zamiast rsp
). Dlaczego nie? To również całkowicie niepotrzebnie oszczędza edi
i rsi
- pytasz, dlaczego oszczędzasz edi
zamiast rdi
, ale dlaczego w ogóle to oszczędzasz?
Odpowiedź jest taka, że kompilator generuje naprawdę zgnijący kod, ponieważ nie włączono optymalizacji. Jeśli włączysz żadnej optymalizacji, cała funkcja jest prawdopodobne, aby zwinąć w dół do:
mov eax, 0
ret
bo tak naprawdę wszystko to musi zrobić: buffer[]
jest lokalny, więc zmiany wprowadzone do niego nigdy nie będzie widoczny na cokolwiek innego, więc można go zoptymalizować; poza tym, cała funkcja musi zrobić, to powrócić 0.
Oto lepszy przykład.Funkcja ta jest kompletna bzdura, ale używa podobnej tablicy, podczas gdy robi wystarczająco dużo, aby upewnić się, że rzeczy nie wszystkie zostaną zoptymalizowane z dala:
$ 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];
}
kompilowane z niektórych optymalizacji można zobaczyć podobnym zastosowaniu czerwonej strefie , ale tym razem to naprawdę nie wykorzystać przesunięcia od 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
teraz dostosować go bardzo nieznacznie, wkładając telefon do innej funkcji, tak że jest foo()
nie jest już funkcją bramy:
$ 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];
}
Teraz czerwona linia nie może być używany, więc można zobaczyć coś więcej jak ty pierwotnie zakładano:
$ 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
(Zauważ, że tmp[200]
był w zakresie podpisanego 8 -bitowe przesunięcie w pierwszym przypadku, ale nie występuje w tym przypadku).
12-bajtowe oddzielenie wynika z wyrównania. 'rsi' ma 8 bajtów, więc dopełnienie jest potrzebne, aby zachować wyrównanie do 8 bajtów. Ale nie mogę mówić za niedostateczne przydzielenie stosu. – Mysticial
To prawdopodobnie zapisuje EDI i RSI tylko dlatego, że nie jest wymagane, aby zapisać je przez dzwoniącego? Ale nadal sposób, w jaki są zapisywane, wydaje się dziwny. – csstudent2233
co się stanie, gdy skompilujesz go za pomocą 'gcc -s' (w celu uzyskania wyjścia zespołu) - ponieważ jeśli nie masz włączonego debugowania w kompilacji, twoje wyniki gdb mogą być nieparzyste – KevinDTimm