Win x64, выравнивание стека на 16 байт. Выравнивание чего?

Везде пишут, что перед вызовом WinApi функций стек должен быть выровнен на границу 16 байт, ESP должен быть кратен 16. Компилирую простую программу на fasm:

format PE64 GUI 5.0
entry start
 
include 'include\win64a.inc'
 
section '.data' data readable writeable
 
Caption db 'Msg', 0
Message db 'Hello World!', 0
 
section '.text' code readable executable
start:
    sub rsp, 8*5
    mov rcx, 0
    mov rdx, Message
    mov r8, Caption
    mov r9, 0  
    call [MessageBox]
   
    xor rcx, rcx
    call [ExitProcess]
 
section '.idata' import data readable writeable
 
    library kernel32, 'kernel32.dll',\
        user32, 'user32.dll'
   
    include 'include\api\kernel32.inc'
    include 'include\api\user32.inc'

Смотрю под отладчиком: введите сюда описание изображения

Как видно на входе RSP уже не кратен 16 - 000000000006FF58h. И в вызове MessageBox RSP будет равен 000000000006FF28h (sub rsp, 8*5 и адрес возврата). То есть он не выравнен на 16 байт. И всё работает.

Если я попытаюсь выравнять перед вызовом, например так:

start:
    and rsp, 0fffffffffffffff0h
   
    sub rsp, 8*5
    mov rcx, 0
    mov rdx, Message
    mov r8, Caption
    mov r9, 0  
    call [MessageBox]
 
    xor rcx, rcx
    call [ExitProcess]

То получаю EXCEPTION_ACCESS_VIOLATION: введите сюда описание изображения

Я подумал это ошибка доступа к памяти, но если я заменю в коде

start:
    sub rsp, 8*5
    mov rcx, 0
    mov rdx, Message
    mov r8, Caption
    mov r9, 0  
    call [MessageBox]
   
    xor rcx, rcx
    call [ExitProcess]
sub rsp, 8*5

на

sub rsp, 8*3

То в таком варианте даже ошибки нет.

Так относительно чего должен быть RSP кратен 16? Относительно того RSP, который имеется на входе в программу? О каком выравнивании идёт речь?


Ответы (2 шт):

Автор решения: Илья К

Регистр стека указывает на адрес возврата и адрес возврата не в счет, адрес возврата и должен быт кратным 8 (восьми), выровненным по параграфу должна быть память после (под) адресом возврата, она должна быть кратна 16, по той причине что многие функции API используют "быстрые" команды SIMD которые требуют выравнивания данных по параграфам.

Сделай так чтобы параметры которые ты передаешь в API были выровнены на 16 байт.

→ Ссылка
Автор решения: user7860670

Все правильно пишут, перед выполнением call вершина стека должна быть выравнена по 16 байтам, т.е. значение RSP должно иметь вид XXXXXXXXXXXXXXX0h. Инструкция call сохраняет на стек адрес возврата, смещая его на 8 вниз. Соответственно на входе процедуры вы наблюдаете значение RSP вида XXXXXXXXXXXXXXX8h.

Далее, выравнивание стека у вас производится верно только в первом примере: sub rsp, 8*5. По сравнению с предыдущим вызовом указатель вершины стека сместится на значение 8 + 8 * 5, т.е. снова будет выровнен по 16 байтам, и все работает как нодо.

Во втором же примере перед выравниваем еще зачем-то делается and rsp, 0fffffffffffffff0h, что в результате приводит к смещению указателя вершины стека на значение 8 - 8 + 8 * 5, т.е. он окажется не выровнен и приведёт к ошибке где-то дальше, когда встречается векторная инструкция, сделанная с расчётом на выравнивание.

→ Ссылка