Как на самом деле передаются аргументы в System V AMD64

12 марта 2025 · 6 минут

Соглашение о вызовах System V AMD64 используют Linux, FreeBSD и macOS. В общих чертах его знают все: «первые шесть аргументов в регистрах». Но когда смотришь на дизассемблер, всплывают детали, которых в этой фразе нет.

Целочисленные аргументы

Первые шесть целых и указателей идут в регистрах по порядку, остальные кладутся на стек справа налево:

АргументРегистр
1rdi
2rsi
3rdx
4rcx
5r8
6r9

Числа с плавающей точкой независимо от этого занимают xmm0–xmm7. Поэтому у f(int a, double b, int c) аргумент c окажется в rsi, а не в rdx: счётчики у двух классов раздельные.

Загадочный al перед printf

Перед вызовом функции с переменным числом аргументов компилятор пишет в al количество задействованных векторных регистров:

lea     rdi, [rip + .Lfmt]
mov     esi, 42
xor     eax, eax        ; ни одного double — al = 0
call    printf

Пролог printf смотрит на al и решает, нужно ли сохранять xmm0–xmm7 в область регистров для va_list. Забудьте обнулить eax в ассемблерной вставке — и получите редкий, плохо воспроизводимый мусор.

Выравнивание стека

В момент инструкции call указатель стека должен быть кратен 16. Сам call кладёт 8 байт адреса возврата, поэтому внутри функции rsp смещён на 8 — отсюда классический sub rsp, 8 или лишний push в прологе. Нарушение чаще всего проявляется падением на movaps где-нибудь глубоко в libc.

Red zone

Листовая функция может использовать 128 байт ниже rsp, не двигая его: ABI гарантирует, что обработчики сигналов эту область не тронут. Именно поэтому ядро собирают с -mno-red-zone — прерывания там приходят на тот же стек.

asmabix86-64