Linux (およびその他のいくつかの OS、特にWindows ではなく、独自の異なる ABI を持つ) で使用される x86-64 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 ビット変位の範囲内でしたが、このケースではそうではないことに注意してください。)