Почему в этом прологе функции нет инструкции "sub rsp" и почему параметры функции хранятся с отрицательным смещением rbp?
Вот что я понял, прочитав некоторые документы по сегментации памяти: когда вызывается функция, есть несколько инструкций (называемых прологом функции), которые сохраняют указатель кадра в стеке, копируют значение указателя стека в базовый указатель и сохраняют некоторые память для локальных переменных.
Вот тривиальный код, который я пытаюсь отладить с помощью GDB:
void test_function(int a, int b, int c, int d) {
int flag;
char buffer[10];
flag = 31337;
buffer[0] = 'A';
}
int main() {
test_function(1, 2, 3, 4);
}
Цель отладки этого кода состояла в том, чтобы понять, что происходит в стеке при вызове функции: поэтому мне пришлось исследовать память на разных этапах выполнения программы (перед вызовом функции и во время ее выполнения). Хотя мне удалось увидеть такие вещи, как адрес возврата и сохраненный указатель кадра, изучив базовый указатель, я действительно не могу понять, что я собираюсь написать после дизассемблированного кода.
Дизассемблирование:
(gdb) disassemble main
Dump of assembler code for function main:
0x0000000000400509 <+0>: push rbp
0x000000000040050a <+1>: mov rbp,rsp
0x000000000040050d <+4>: mov ecx,0x4
0x0000000000400512 <+9>: mov edx,0x3
0x0000000000400517 <+14>: mov esi,0x2
0x000000000040051c <+19>: mov edi,0x1
0x0000000000400521 <+24>: call 0x4004ec <test_function>
0x0000000000400526 <+29>: pop rbp
0x0000000000400527 <+30>: ret
End of assembler dump.
(gdb) disassemble test_function
Dump of assembler code for function test_function:
0x00000000004004ec <+0>: push rbp
0x00000000004004ed <+1>: mov rbp,rsp
0x00000000004004f0 <+4>: mov DWORD PTR [rbp-0x14],edi
0x00000000004004f3 <+7>: mov DWORD PTR [rbp-0x18],esi
0x00000000004004f6 <+10>: mov DWORD PTR [rbp-0x1c],edx
0x00000000004004f9 <+13>: mov DWORD PTR [rbp-0x20],ecx
0x00000000004004fc <+16>: mov DWORD PTR [rbp-0x4],0x7a69
0x0000000000400503 <+23>: mov BYTE PTR [rbp-0x10],0x41
0x0000000000400507 <+27>: pop rbp
0x0000000000400508 <+28>: ret
End of assembler dump.
Я понимаю, что "сохранение указателя кадра в стеке" выполняется с помощью " push rbp", "копирование значения указателя стека в базовый указатель" выполняется с помощью "mov rbp, rsp", но меня смущает то, что отсутствие "sub rsp $n_bytes" для "сохранения некоторой памяти для локальных переменных". Я видел это на многих выставках (даже в некоторых темах здесь на stackru).
Я также читал, что аргументы должны иметь положительное смещение от базового указателя (после того, как он заполнен значением указателя стека), так как если они расположены в функции вызывающей стороны и стек увеличивается к младшим адресам, то имеет смысл, что когда базовый указатель обновляется значением указателя стека, компилятор возвращается в стек, добавляя некоторые положительные числа. Но мой код, похоже, хранит их с отрицательным смещением, точно так же, как локальные переменные. Я также не могу понять, почему они помещаются в эти регистры (в основную)... не должны ли они быть сохранены непосредственно в смещенном rsp " "?
Возможно, эти различия связаны с тем, что я использую 64-битную систему, но мои исследования не привели меня к чему-то, что могло бы объяснить, с чем я столкнулся.
2 ответа
Системный V ABI для x86-64 определяет red zone
128 байтов ниже %rsp
, Эти 128 байтов принадлежат функции до тех пор, пока она не вызывает другую функцию (это leaf function
). Обработчики прерываний должны учитывать красную зону, так как они являются по сути непроизвольными вызовами функций.
Все локальные переменные вашего test_function
, которая является функцией листа, вписывается в красную зону, таким образом, нет регулировки %rsp
нужно. (Кроме того, функция не имеет видимых побочных эффектов и будет оптимизирована при любой разумной настройке оптимизации).
Но мой код, кажется, хранит их с отрицательным смещением, как и локальные переменные
Первые аргументы x86_64 передаются в регистрах, а не в стеке. Так когда rbp
установлен в rsp
они не находятся в стеке и не могут иметь положительного смещения.
Их подталкивают только к:
сохранить состояние регистра для второго вызова функции.
В этом случае это не требуется, так как это листовая функция.
упростить распределение регистров
Но оптимизированный распределитель мог бы работать лучше без потери памяти.
Ситуация была бы другой, если бы вы имели:
- Функция x86_64 с множеством аргументов. Те, которые не помещаются в регистры, попадают в стек.
- IA-32, где каждый аргумент помещается в стек.
отсутствие "sub rsp $n_bytes" для "сохранения некоторой памяти для локальных переменных".
Пропажа sub rsp
В красной зоне листовой функции часть вопроса уже задавалась по адресу: Почему пролог функции GCC x86-64 выделяет меньше стека, чем локальные переменные?