Пролог в x64 Windows – больше параметров

Вопросы и ответы

Я компилирую следующий код в Visual Studio в 64-битном формате:

int func1(int a, int b,int c, int d) {
    int res = 0;
    return res;
}

int main() {
    int p = 0;
    p = func1(1,2,3,4);
    return 0;
}

и открываю его в x64dbg:

sub rsp,38
mov dword ptr ss:[rsp+20],0
mov r9d,4
mov r8d,3
mov edx,2
mov ecx,1
call <prolog_epilog_64bit.int __cdecl func1(int, int, int, int)>
mov dword ptr ss:[rsp+20],eax
xor eax,eax
add rsp,38
ret 

Я понимаю, почему 38:

return add
---------------
8 байт
---------------
4 * 8 = 32=20h для регистра (быстрый вызов)
---------------
4 байта для int
---------------
12 (Выравнивание стека в x64)

8+20+4+c=38

Теперь у меня есть этот код:

int func2(int a, int b,int c, int d,int e) {
    int res = 0;
    return res;
}

int main() {
    int p = 0;
    p = func2(1,2,3,4,5);
    return 0;
}

и открываю его в x64dbg:

sub rsp,48
mov dword ptr ss:[rsp+30],0
mov dword ptr ss:[rsp+20],5
mov r9d,4
mov r8d,3
mov edx,2
mov ecx,1
call <prolog_epilog_64bit.int __cdecl func2(int, int, int, int, int)>
mov dword ptr ss:[rsp+30],eax
xor eax,eax
add rsp,48
ret 
return add
---------------
8 байт
---------------
4 * 8 = 32=20h для регистра (быстрый вызов)
---------------
8 байт для параметров стека
----------------------
4 байта для локальной переменной int
---------------
4 байта (Выравнивание стека в x64)

8 + 20 + 8 + 4 + 4=48

Почему 48?

Должны ли быть пробелы между локальными переменными и параметрами, передаваемыми по стеку? Если мы увеличим количество параметров функции MAIN (2 параметра), это не изменится. (48)

Я ожидал увидеть здесь значение 38, но этого не произошло.

Почему RSP+30 и RSP+20?

Ответ

В вашем вопросе вы поднимаете несколько интересных аспектов касаемо стека в x64 и организации вызовов функций. Давайте разберем код и ответим на ваши вопросы по шагам.

1. Почему 48?

Когда вы вызываете func2(int a, int b, int c, int d, int e), у вас есть следующие параметры:

  • A, B, C, D передаются через регистры: rcx, rdx, r8, r9 (всего 4 параметра).
  • Пятый параметр (E) передается через стек, так как в x64 существует ограничение на количество параметров, которые можно передать через регистры – максимум это 4.

Для того чтобы подготовить стек для вызова функции, происходит следующее:

  1. Выравнивание стека: Вызовы функций в x64 требуют, чтобы стек был выровнен на границу 16 байт.
  2. Хранение локальных переменных и переданных параметров: Стек для func2 выглядит следующим образом:
    • 8 байт для сохранения адреса возврата.
    • 32 байта (4 параметра по 8 байт) переданных параметров в регистрах.
    • 8 байт для пятого параметра, который передан через стек.
    • 4 байта для локальной переменной.
    • 4 байт для выравнивания стека (всегда дополнительно надо учитывать, чтобы поддерживать выравнивание на границу 16 байт).

Таким образом, замедляя шаги, мы получаем:

  • 8 (адрес возврата) + 32 (4 параметра) + 8 (пятый параметр через стек) + 4 (локальная переменная) + 4 (выравнивание стека) = 48 байт.

2. Почему RSP+20 и RSP+30?

RSP+20 и RSP+30 используются для хранения параметров и локальных переменных на стеке.

  • RSP+20 – это адрес, по которому хранится пятый параметр (E). Таким образом, он сохраняется на стеке.
  • RSP+30 – это адрес локальной переменной (res), которую вы объявили в функции.

Поэтому, просмотрев эти значения можно понять, что они указывают на разные места в стеке, в которых хранятся передаваемые параметры и локальные переменные.

3. Добавление параметров в main

Если вы добавите параметры в main (например, два параметра), это изменение не повлияет на структуру стека для вызова func2(), так как параметры для func2 по-прежнему решаются независимо от параметров main. Стек для функции main просто станет больше, если мы примем их в расчет, но параметры для других функций, таких как func2, останутся неизменными.

Итог

Стек в x64 архитектуре организован так, чтобы обеспечивать эффективность передачи параметров и хранение локальных переменных с учетом строгих требований к выравниванию. Установка правильного выравнивания и организация передачи через регистры/стек — это основа работы функций.

Как вы видите, в случае функции func2, она занимает 48 байт на стеке для корректного выполнения с учетом всех параметров, локальных переменных и правил выравнивания.

Оцените материал
Добавить комментарий

Капча загружается...