Что означает "Теневой стэк" или "Теневое хранилище" (Stack’s shadow store) в "соглашении вызовов для x64" в Windows API?

Я читал соглашение Microsoft-ское о том как вызывать функции из Windows API когда пишешь программу на ассемблере 64-битную, но тут заметил что говорят про какой то «Теневой стэк» или «Теневое пространство». Не понимаю что это, зачем, и как это сделать.
Ну то есть я догадываюсь что это возможно просто зарезервированное в место в стэке, но если так, то зачем и сколько мéста резервировать - не понятно. И ещё, в таком случае, не понятно нужно ли резервировать каждый раз перед вызовом функции или только один раз в программе.
В этой документации Microsoft не нашел точно что это. Где-то говорят про 4 ячейки стэка, где-то про 128-битное выравнивание регистра-указателя «rsp», но откуда появляется этот «Теневой стэк» не понятно


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

Автор решения: Developer

Короче я разобрался. После передачи параметров в процедуру, нужно выделить в стэке 4 ячейки, и ещё сколько-то ячеек чтобы указатель стэка "rsp" был выровненным по адресу так, чтобы адрес был кратен 16 (так как есть некоторые SSE-инструкции которые работают только с данными у которых размер 16 байт и некоторые из них без выравнивания не работают). Причём этот адрес должен быть кратным после прыжка к адресу функции. То есть чтобы он был выровнен при начале выполнения процедуры. Но для этого нужно чтобы он, перед использованием команды "call", был на одну ячейку далёк от выравнивания, так как, во время перехода к процедуре командой "call", указатель сдвигается на одну ячейку вперёд и таким образом после выполнения этой команды будет уже выровнен, так как сдвинут на одну ячейку вперёд (которой недоставало до выравнивания). То есть нужно выделить свободное место в стэке так, чтобы было и место для 4 ячеек стэка и указатель стэка "rsp" указывал на адрес почти кратный 16 (почти - потому что без одной ячейки). И не забывайте что стэк при старте программы выравнен изначально, поэтому выравнен стэк или нет смотрите сами по своему коду, но смотреть не придётся если вы всегда перед вызовом процедуры выравниваете стэк по адресу без-одной-ячейки-кратному 16, ведь каждая процедура будет знать что в её руках всегда выровненный стэк (и вы сами при просмотре кода будете это знать и не придётся считать выравнен указатель или нет).
Вот как это выглядит в коде:

mov     rcx, offset nameOfDLL   ; Просто кладём параметр
sub     rsp, 40                 ; Почти выравниваем стэк, без достающей до выравнивания ячейки, попутно выделяя пространство для 4 ячеек (то есть попутно выделяя 32 байта для этих ячеек)
call    LoadLibraryA            ; Вызываем процедуру. Этот вызов передвинет указатель стэка "rsp" на одну ячейку вверх (после сохранения адреса возврата, по которому вернётся вызванная процедура), которой и не доставало до выравнивания. То есть в процедуру указатель "rsp" придёт выровненным
add     rsp, 40                 ; Восстанавливаем стэк обратно. Ведь, в соглашении вызовов в x64, стэк очищается вызвавшим

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

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

Вообще-то "Теневой стек" - это немного другое.. Система резервирует программам пул памяти для стека определённого размера (на системах х32 это 256 КБ), однако выделяет эту память не всю сразу, а частями по 8 КБ (2 страницы). Выше этих 8 КБ имеется сторожевая страница "PageGuard", и если программа исчерпает стек до сторожевой, то выделяются следующие 8 КБ и т.д. Так вот свободная стековая память и называется "Теневой" (на рис.ниже это белая часть "свободно"). Затенённый стек имеется не только на х64, но и на х32.

Раньше были вирусы, которые прятались в теневой памяти стека и ждали, когда система активизирует их страницу. Но с приходом х64 в атрибутах страниц появился бит(NX) и неисполняемый стек, поэтому эти вирусы отжили свой век. Я когда-то проводил опыты со стеком, ознакомиться с которыми можно здесь:

введите сюда описание изображения

→ Ссылка