Почему в этом прологе функции нет инструкции "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 выделяет меньше стека, чем локальные переменные?

Другие вопросы по тегам