Поделиться через


Пролог и эпилог

Для каждой функции, в которой выделяется память в стеке, вызываются другие функции, сохраняются защищенные регистры или выполняется обработка исключений, необходимо использовать пролог. На адрес пролога накладываются ограничения, описываемые в данных завершения, которые связаны с соответствующей записью таблицы функций (см. раздел Обработка исключений (x64)). При необходимости в прологе сохраняются регистры аргумента (по внутренним адресам), помещаются в стек защищенные регистры, выделяется фиксированная часть стека для локальных и временных переменных, а также создается указатель кадра. В связанных данных завершения описывается действие пролога, а также предоставляются сведения, используемые для отмены результатов выполнения кода пролога.

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

При сохранении защищенных регистров рекомендуется перемещать их в стек до выделения фиксированной части стека. Если выделение фиксированной части стека выполняется до сохранения защищенных регистров, для обращения к области сохраненного регистра в большинстве случаев требуется 32-разрядное смещение. Производительность функций помещения и перемещения регистров примерно одинакова и будет оставаться такой в ближайшем будущем, независимо от предполагаемой зависимости между функциями помещения. Защищенные регистры могут сохраняться в любом порядке. Однако в качестве первой операции с защищенным регистром в прологе необходимо выполнять сохранение регистра.

Ниже приведен типичный код пролога:

mov       [RSP + 8], RCX
push   R15
push   R14
push   R13
sub      RSP, fixed-allocation-size
lea      R13, 128[RSP]
...

В этом прологе аргумент регистра RCX сохраняется по внутреннему адресу, сохраняются защищенные регистры R13-R15, выделяется кадр фиксированной части кадра стека, а также создается указатель кадра, который указывает на выделенную фиксированную область размером 128 байт. Благодаря использованию смещения обеспечивается обращение к большему числу адресов выделенной фиксированной области с помощью однобайтовых смещений.

Если размер фиксированной области памяти превышает размер одной страницы памяти или равен ему, перед изменением RSP следует вызвать вспомогательную функцию. Вызываемая функция __chkstk обеспечивает проверку подлежащей выделению области стека на предмет допустимости расширения стека. В этом случае приведенный выше пример пролога будет выглядеть следующим образом:

mov       [RSP + 8], RCX
push   R15
push   R14
push   R13
mov      RAX,  fixed-allocation-size
call   __chkstk
sub      RSP, RAX
lea      R13, 128[RSP]
...

Вспомогательная функция __chkstk изменяет только регистры R10 и R11. Другие регистры и коды условий не изменяются. В частности, при ее выполнении регистр RAX возвращается без изменений. Все защищенные регистры и регистры передачи аргументов также не изменяются.

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

В коде эпилога необходимо придерживаться строгого набора правил, применяемых к коду завершения, что позволяет обеспечить безопасное завершение без вызова исключений и прерываний. Это позволяет уменьшить объем используемых данных завершения, поскольку не используются дополнительные данные для описания каждого эпилога. Вместо этого выполнение эпилога определяется в коде завершения посредством прямого просмотра потока кода для идентификации эпилога.

Если в функции не используется указатель кадра, в эпилоге сначала отменяется выделение фиксированной части стека, затем извлекаются сохраненные значения защищенных регистров, после чего управление возвращается вызывающей функции. Например:

add      RSP, fixed-allocation-size
pop      R13
pop      R14
pop      R15
ret

Если в функции используется указатель кадра, перед выполнением эпилога необходимо выполнить усечение стека до размера фиксированной выделяемой области. С технической точки зрения эта операция не входит в состав эпилога. Ниже приведен пример эпилога, который может использоваться для отмены ранее выполненного пролога:

lea      RSP, -128[R13]
; epilogue proper starts here
add      RSP, fixed-allocation-size
pop      R13
pop      R14
pop      R15
ret

На практике, если используется указатель кадра, не имеет смысла выполнять изменение регистра RSP в два этапа, поэтому вместо приведенного выше можно использовать следующий эпилог:

lea      RSP, fixed-allocation-size – 128[R13]
pop      R13
pop      R14
pop      R15
ret

Выше приведены единственно допустимые формы эпилога. Эпилог должен включать выражение add RSP,constant или lea RSP,constant[FPReg], за которым следуют последовательность из нескольких (или ни одной) команд извлечения 8-байтовых регистров (pop), а также команды return или jmp. В эпилоге допускается использование не всех операторов jmp. К допустимым относятся только операторы jmp со ссылками на память ModRM, в которых значение поля mod ModRM равно 00. Использование в эпилоге операторов jmp, для которых значение поля mod ModRM равно 01 или 10, не допускается. Дополнительные сведения о допустимых ссылках ModRM см. в таблице A-15 в разделе, посвященном инструкциям общего и системного назначения, руководства программиста архитектуры процессора AMD x86-64 (том 3). Использование другого кода в эпилоге не допускается. В частности, в эпилоге не допускается планирование каких-либо задач, в том числе загрузки возвращаемого значения.

Обратите внимание, что если указатель кадра не используется, в эпилоге необходимо использовать выражение add RSP,constant для отмены выделения фиксированной части стека. Использование вместо него выражения lea RSP,constant[RSP] не допускается. Это ограничение позволяет уменьшить число шаблонов, распознаваемых при поиске эпилогов.

Если эти правила соблюдаются, в коде завершения определяется выполняемый в данный момент эпилог и имитируется выполнение оставшейся части эпилога, что позволяет воссоздать контекст вызывающей функции.

См. также

Ссылки

Программные соглашения x64