вторник, 9 декабря 2008 г.

История соглашений вызова, часть 5: amd64

Это перевод The history of calling conventions, part 5: amd64. Автор: Реймонд Чен.
Четвёртая часть.
Последней архитектурой, которую мы рассмотрим будет архитектура AMD64 (также известная как x86-64).

AMD64 берёт традиционную архитектуру x86 и увеличивает регистры до 64-х бит, именуя их rax, rbx и т.д. И также добавляет восемь дополнительных регистров, называя их просто как R8-R15.

- Первые четыре параметры в функцию передаются в rcx, rdx, r8 и r9. Все другие параметры передаются в стеке. Более того, для параметров в регистре резервируется место в стеке, на случай если вызываемая функция захочет сбросить регистры в стек; это также важно для функций с переменным числом аргументов.
- Параметры меньше 64-бит не дополняются нулями; верхние биты содержат мусор, поэтому не забывайте обнулять их явно, если вы собираетесь их использовать. Параметры размером больше 64-х бит передаются по ссылке.
- Возвращаемое значение помещается в rax. Если возвращаемое значение больше 64-х бит, то в функцию будет передан неявный секретный параметр, который содержит адрес, по которому нужно записать результат.
- Все регистры обязаны сохраняться во всемя вызова, кроме регистров rax, rcx, rdx, r8, r9, r10 и r11, которые свободны.
- Вызываемый не чистит стек. Это работа вызывающего.
- Стек должен быть всё время выровненным на границу 16-ти байт. Поскольку инструкция "call" записывает в стек 8-ми байтовый адрес возврата, то это значит, что каждая не листовая функция должна подправлять стек на значение вида 16n + 8 для восстановления выравнивания на 16 байт.

Вот пример:
void SomeFunction(int a, int b, int c, int d, int e);

void CallThatFunction()
{
SomeFunction(1, 2, 3, 4, 5);
SomeFunction(6, 7, 8, 9, 10);
}
После входа в CallThatFunction стек выглядит примерно так:
xxxxxxx0  .. данные на стеке .. 
xxxxxxx8 адрес возврата <- RSP
Из-за наличия в нём адреса возврата стек оказывается не выровненным. Функция CallThatFunction настраивает свой фрейм примерно так:
    sub    rsp, 0x28
Заметим, что размер локального стекового фрейма равен 16n + 8, так что в результате у нас получается выравненный стек:
xxxxxxx0  .. данные на стеке ..
xxxxxxx8 адрес возврата
xxxxxxx0 (arg5)
xxxxxxx8 (место для arg4)
xxxxxxx0 (место для arg3)
xxxxxxx8 (место для arg2)
xxxxxxx0 (место для arg1) <- RSP
Теперь мы готовим первый вызов:
        mov     dword ptr [rsp+0x20], 5     ; параметр 5
mov r9d, 4 ; параметр 4
mov r8d, 3 ; параметр 3
mov edx, 2 ; параметр 2
mov ecx, 1 ; параметр 1
call SomeFunction ; Вперёд, Спиди-Гонщик!
Когда функция SomeFunction возвращает управление, стек ещё не очищен и поэтому выглядит так же, как и выше. Тогда для второго вызова нам просто нужно занести новые значения в уже подготовленное место:
        mov     dword ptr [rsp+0x20], 10    ; параметр 5
mov r9d, 9 ; параметр 4
mov r8d, 8 ; параметр 3
mov edx, 7 ; параметр 2
mov ecx, 6 ; параметр 1
call SomeFunction ; Вперёд, Спиди-Гонщик!
Теперь CallThatFunction завершена и она должна очистить стек и вернуть управление:
        add     rsp, 0x28
ret
Заметьте, что вы практичеси не встречаете инструкций "push" в коде amd64, поскольку парадигмой вызывающего является резервирование пространства и повторное использование его.

2 комментария:

  1. "Теперь CallThatFunction завершена и она должна очистить стек и вернуть управление:"
    в оригинале - "CallThatFunction is now finished and can clean its stack and return."
    правильный перевод - Теперь CallThatFunction завершена и она может очистить стек и вернуть управление.
    Ибо: "Вызываемый не чистит стек. Это работа вызывающего."

    ОтветитьУдалить
  2. Мне кажется, вы неправильно поняли. CallThatFunction должна чистить стек не за собой, а за SomeFunction. Она и должна и может это сделать :)

    ОтветитьУдалить

Можно использовать некоторые HTML-теги, например:

<b>Жирный</b>
<i>Курсив</i>
<a href="http://www.example.com/">Ссылка</a>

Вам необязательно регистрироваться для комментирования - для этого просто выберите из списка "Анонимный" (для анонимного комментария) или "Имя/URL" (для указания вашего имени и ссылки на сайт). Все прочие варианты потребуют от вас входа в вашу учётку (поддерживается OpenID).

Пожалуйста, по возможности используйте "Имя/URL" вместо "Анонимный". URL можно просто не указывать.

Ваше сообщение может быть помечено как спам спам-фильтром - не волнуйтесь, оно появится после проверки администратором.