Я в процессе написания компилятора исключительно как опыт обучения. В настоящее время я изучаю кадры стека, компилируя простой код на языке C++, а затем изучая выходное asm, созданное gcc 4.9.2 для Windows x86.gcc x86 Выравнивание стека Windows
мой простой C++ код
#include <iostream>
using namespace std;
int globalVar;
void testStackStuff(void);
void testPassingOneInt32(int v);
void forceStackFrameCreation(int v);
int main()
{
globalVar = 0;
testStackStuff();
std::cout << globalVar << std::endl;
}
void testStackStuff(void)
{
testPassingOneInt32(666);
}
void testPassingOneInt32(int v)
{
globalVar = globalVar + v;
forceStackFrameCreation(v);
}
void forceStackFrameCreation(int v)
{
globalVar = globalVar + v;
}
Хорошо, когда скомпилирован с -mpreferred-стек-граница = 4 я ожидал увидеть стек выровненный до 16 байт (технически это выровненных до 16 байтов но с дополнительными 16 байтами неиспользуемого пространства стека). Пролог для основного, изготовленного gcc:
22 .loc 1 12 0
23 .cfi_startproc
24 0000 8D4C2404 lea ecx, [esp+4]
25 .cfi_def_cfa 1, 0
26 0004 83E4F0 and esp, -16
27 0007 FF71FC push DWORD PTR [ecx-4]
28 000a 55 push ebp
29 .cfi_escape 0x10,0x5,0x2,0x75,0
30 000b 89E5 mov ebp, esp
31 000d 51 push ecx
32 .cfi_escape 0xf,0x3,0x75,0x7c,0x6
33 000e 83EC14 sub esp, 20
34 .loc 1 12 0
35 0011 E8000000 call ___main
35 00
36 .loc 1 13 0
37 0016 C7050000 mov DWORD PTR _globalVar, 0
38 .loc 1 15 0
39 0020 E8330000 call __Z14testStackStuffv
линия 26 раундов esp до ближайшей границы 16 байт.
линии 27, 28 и 31 толкать в общей сложности 12 байтов в стек, а затем
линии 33 вычитает еще 20 байт из особ, что в общей сложности 32 байт!
Почему?
строка 39 затем вызывает testStackStuff.
ПРИМЕЧАНИЕ - этот вызов вызывает адрес возврата (4 байта).
Теперь давайте посмотрим на пролог для testStackStuff, имея в виду, что стек теперь находится на 4 байта ближе к следующей границе 16 байтов.
67 0058 55 push ebp
68 .cfi_def_cfa_offset 8
69 .cfi_offset 5, -8
70 0059 89E5 mov ebp, esp
71 .cfi_def_cfa_register 5
72 005b 83EC18 sub esp, 24
73 .loc 1 22 0
74 005e C704249A mov DWORD PTR [esp], 666
линия 67 подталкивает еще 4 байта (теперь 8 байт к границе).
строка 72 вычитает еще 24 байта (всего 32 байта).
В этот момент стопка теперь выравнивается правильно на границе 16 байтов. Но почему кратное 2?
Если я сменил флаги компилятора на -mpreferred-stack-border = 5, я бы ожидал, что стек выровнён до 32 байт, но снова gcc, кажется, создает фреймы стека, выровненные в 64 байта, в два раза превышающую сумму, которую я ожидал.
Вводных для основных
23 .cfi_startproc
24 0000 8D4C2404 lea ecx, [esp+4]
25 .cfi_def_cfa 1, 0
26 0004 83E4E0 and esp, -32
27 0007 FF71FC push DWORD PTR [ecx-4]
28 000a 55 push ebp
29 .cfi_escape 0x10,0x5,0x2,0x75,0
30 000b 89E5 mov ebp, esp
31 000d 51 push ecx
32 .cfi_escape 0xf,0x3,0x75,0x7c,0x6
33 000e 83EC34 sub esp, 52
34 .loc 1 12 0
35 0011 E8000000 call ___main
35 00
36 .loc 1 13 0
37 0016 C7050000 mov DWORD PTR _globalVar, 0
37 00000000
37 0000
38 .loc 1 15 0
39 0020 E8330000 call __Z14testStackStuffv
линия 26 раундов особ вплоть до границы байта ближайших 32
линия 27, 28 и 31 толчка в общей сложности 12 байт в стек, а затем
строка 33 вычитает еще 52 байта из esp, давая в общей сложности 64 байта!
и пролог для testStackStuff является
66 .cfi_startproc
67 0058 55 push ebp
68 .cfi_def_cfa_offset 8
69 .cfi_offset 5, -8
70 0059 89E5 mov ebp, esp
71 .cfi_def_cfa_register 5
72 005b 83EC38 sub esp, 56
73 .loc 1 22 0
(4 байта на стек с) называют __Z14testStackStuffv
(4 байта на стек с) нажимной EBP
(56 байт на стек с) sub esp, 56
всего 64 байта.
Кто-нибудь знает, почему gcc создает это дополнительное пространство для стека или я пропустил что-то очевидное?
Спасибо за любую помощь, которую вы можете предложить.
*, но gcc, похоже, создает кадры стека, выровненные с 64 байтами *. Нет, он использовал 'и esp, -32'. Размер фрейма стека выглядит как 64 байта, но его выравнивание - только 32B. –
related: http://stackoverflow.com/questions/38781118/why-is-gcc-generating-an-extra-return-address объясняет 'push DWORD PTR [ecx-4]' часть. –