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


Пролог и эпилог для 64-разрядных систем

Каждая функция, которая выделяет пространство стека, вызывает другие функции, сохраняет энергонезависимые регистры или использует обработку исключений, должна содержать пролог с ограничениями адресов, описанными в данных очистки, связанных с соответствующей записью таблицы функций. Дополнительные сведения см. в разделе Обработка исключений 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-байтовых извлечений регистра, а также return или jmp. (В эпилоге допускается только подмножество инструкций jmp . Подмножество — это исключительно класс операторов с ссылками jmp на память ModRM, где значение поля мода ModRM равно 00. Использование инструкций jmp в эпилоге с значением поля мода ModRM 01 или 10 запрещено. Дополнительные сведения о допустимых ссылках на ModRM см. в таблице A-15 в руководстве программиста архитектуры AMD x86-64. Никакого другого кода не может появиться. В частности, в эпилоге ничего нельзя запланировать, включая загрузку возвращаемого значения.

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

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

См. также

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