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


Обработка исключений в 64-разрядных системах

Общие сведения о структурированной обработке исключений и обработке исключений C++, а также о поведении в архитектуре x64. Общие сведения об обработке исключений см. в разделе Обработка исключений в Visual C++.

Данные раскрутки для обработки исключений и поддержки отладчика

Для обработки исключений и поддержки отладки требуется несколько структур данных.

структура RUNTIME_FUNCTION

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

Размер Значение
ULONG Начальный адрес функции
ULONG Конечный адрес функции
ULONG Адрес данных раскрутки

Структура RUNTIME_FUNCTION должна быть согласована в памяти в формате DWORD. Все адреса зависят от образа, то есть они являются 32-разрядными смещениями от начального адреса образа, содержащего запись таблицы функций. Эти записи сортируются и помещаются в раздел. pdata образа PE32+. Для динамически создаваемых функций [JIT-компиляторов] среда выполнения для поддержки этих функций должна либо использовать RtlInstallFunctionTableCallback, либо RtlAddFunctionTable, чтобы предоставить эти сведения операционной системе. В противном случае это приведет к ненадежной обработке исключений и отладке процессов.

Структура UNWIND_INFO

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

Размер Значение
UBYTE: 3 Версия
UBYTE: 5 Флаги
UBYTE Размер пролога
UBYTE Число кодов раскрутки
UBYTE: 4 Регистр кадра
UBYTE: 4 Смещение регистра кадра (масштабированное)
USHORT * n Массив кодов раскрутки
переменная Может иметь форму (1) или (2) ниже

(1) Обработчик исключений

Размер Значение
ULONG Адрес обработчика исключений
переменная Данные обработчика для конкретного языка (необязательно)

(2) Связанные данные раскрутки

Размер Значение
ULONG Начальный адрес функции
ULONG Конечный адрес функции
ULONG Адрес данных раскрутки

Структура UNWIND_INFO должна быть согласована в памяти в формате DWORD. Вот что означает каждое поле:

  • Версия

    Номер версии данных раскрутки, в настоящее время 1.

  • Flags

    В настоящее время определены три флага:

    Флаг Description
    UNW_FLAG_EHANDLER Функция имеет обработчик исключений, который должен вызываться при поиске функций, нуждающихся в проверке исключений.
    UNW_FLAG_UHANDLER Функция имеет обработчик завершения, который должен вызываться при раскрутке исключения.
    UNW_FLAG_CHAININFO Эта структура данных раскрутки не является первичной для процедуры. Вместо этого запись связанных данных раскрутки является содержимым предыдущей записи RUNTIME_FUNCTION. Дополнительные сведения см. в разделе Структуры связанных данных раскрутки. Если этот флаг установлен, то флаги UNW_FLAG_EHANDLER и UNW_FLAG_UHANDLER должны быть сняты. Кроме того, поля регистра кадра и распределения фиксированного стека должны иметь те же значения, что и в основных данных раскрутки.
  • Размер пролога

    Длина пролога функции в байтах.

  • Число кодов раскрутки

    Число слотов в массиве кодов раскрутки. Некоторым кодам раскрутки, например UWOP_SAVE_NONVOL, требуется более одного слота в массиве.

  • Регистр кадра

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

  • Смещение регистра кадра (масштабированное)

    В случае ненулевого значения поля регистра кадра это поле масштабируется со смещением относительно RSP, примененного к регистру FP во время установки. Фактический регистр FP имеет значение RSP + 16 * это число, что позволяет смещения от 0 до 240. Это смещение позволяет указывать регистр FP в середине выделения локального стека для динамических кадров стека, а это позволяет повысить плотность кода с помощью более коротких инструкций. (То есть дополнительные инструкции могут использовать форму 8-битного смещения со знаком.)

  • Массив кодов раскрутки

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

  • Адрес обработчика исключений

    Относительный указатель на исключение или обработчик завершения, зависящий от языка функции, если флаг UNW_FLAG_CHAININFO снят и установлен один из флагов UNW_FLAG_EHANDLER или UNW_FLAG_UHANDLER.

  • Данные обработчика для конкретного языка

    Данные обработчика исключений для конкретного языка функции. Формат этих данных не указан и полностью определяется конкретным используемым обработчиком исключений.

  • Связанные данные раскрутки

    Если установлен флаг UNW_FLAG_CHAININFO, то структура UNWIND_INFO заканчивается тремя элементами UWORD. Эти элементы UWORD представляют сведения о RUNTIME_FUNCTION для функции связанных данных раскрутки.

Структура UNWIND_CODE

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

Размер Значение
UBYTE Смещение в прологе
UBYTE: 4 Код операции раскрутки
UBYTE: 4 Сведения об операции

Массив сортируется по убыванию смещения в прологе.

Смещение в прологе

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

Код операции раскрутки

Примечание. Для некоторых кодов операций требуется смещение без знака в значение в локальном кадре стека. Это смещение от начала, то есть наименьшего адреса фиксированного выделения стека. Если поле регистра кадра в UNWIND_INFO равно нулю, это смещение отсчитывается с RSP. Если поле регистра кадра не равно нулю, это смещение будет иметь значение, где при установленном регистре FP было обнаружено RSP. Он равен регистру FP минус смещение регистра FP (16 * смещение регистра масштабируемого кадра в UNWIND_INFO). Если используется регистр FP, любой код очистки, принимающий смещение, должен использоваться только после того, как регистр FP установлен в прологе.

Для всех кодов операций, за исключением UWOP_SAVE_XMM128 и UWOP_SAVE_XMM128_FAR, смещение всегда кратно 8, поскольку все интересующие значения стека хранятся в 8-байтовых границах (сам стек всегда имеет размер 16 байтов). Для кодов операций, которые принимают короткое смещение (меньше 512 КБ), итоговый USHORT в узлах для этого кода содержит смещение, поделенное на 8. Для кодов операций, которые принимают длинное смещение (512K <= смещение 4 ГБ), последние два узла USHORT для этого кода содержат смещение < (в малоконечном формате).

Для кодов операций UWOP_SAVE_XMM128 и UWOP_SAVE_XMM128_FAR смещение всегда кратно 16, так как все 128-разрядные операции XMM должны выполняться в согласованной 16-байтовой памяти. Таким образом, для UWOP_SAVE_XMM128 используется коэффициент масштабирования, равный 16, разрешая смещение менее 1 МБ.

Код операции раскрутки — одно из следующих значений:

  • UWOP_PUSH_NONVOL (0) 1 узел

    Отправка неизменяемого регистра целых чисел с уменьшением значения RSP на 8. Сведения об операции — это номер регистра. Из-за ограничений, накладываемых на эпилоги, коды раскрутки UWOP_PUSH_NONVOL должны находиться в прологе первыми и, соответственно, в конце массива кодов раскрутки. Это относительное упорядочение применяется ко всем остальным кодам раскрутки, кроме UWOP_PUSH_MACHFRAME.

  • UWOP_ALLOC_LARGE (1) 2 или 3 узла

    Выделение большого размера области в стеке. Существует две формы. Если сведения об операции равны 0, то размер выделения, деленный на 8, записывается в следующий слот, что позволяет выделить до 512 КБ-8. Если сведения об операции равны 1, немасштабируемый размер выделения записывается в два следующих слота в формате с прямым порядком байтов, что позволяет выделить до 4 ГБ-8.

  • UWOP_ALLOC_SMALL (2) 1 узел

    Выделение небольшого размера области в стеке. Размер выделения — это поле сведений об операции * 8 + 8, что позволяет выделить от 8 до 128 байт.

    Код очистки для выделения стека всегда должен использовать кратчайшую возможную кодировку:

    Размер выделения Код очистки
    8–128 байтов UWOP_ALLOC_SMALL
    136–512 КБ 8 байтов UWOP_ALLOC_LARGE, сведения об операции = 0
    512 КБ– 4ГБ 8 байтов UWOP_ALLOC_LARGE, сведения об операции = 1
  • UWOP_SET_FPREG (3) 1 узел

    Установите регистр указателя кадра, задав для регистра некоторое смещение текущего RSP. Смещение равно полю смещения регистра кадров (масштабируемый) в UNWIND_INFO * 16, что позволяет смещениям от 0 до 240. Использование смещения позволяет установить указатель кадра, указывающий на середину фиксированного выделения стека, что способствует повышению плотности кода благодаря повышению уровня доступа к использованию коротких форм инструкций. Поле сведений об операции зарезервировано и не должно использоваться.

  • UWOP_SAVE_NONVOL (4) 2 узла

    Сохраните неизменяемый регистр целых чисел в стеке, используя MOV вместо PUSH. Этот код в основном используется для упаковки со сжатием, где неизменяемый регистр сохраняется в стеке в расположении, которое было выделено ранее. Сведения об операции — это номер регистра. Смещение стека, масштабированное на 8, записывается в следующий слот кода операции раскрутки, как описано в примечании выше.

  • UWOP_SAVE_NONVOL_FAR (5) 3 узла

    Сохраните неизменяемый регистр целых чисел в стеке с длинным смещением, используя MOV вместо PUSH. Этот код в основном используется для упаковки со сжатием, где неизменяемый регистр сохраняется в стеке в расположении, которое было выделено ранее. Сведения об операции — это номер регистра. Немасштабированное смещение стека записывается в следующие два слота кода операции раскрутки, как описано в примечании выше.

  • UWOP_SAVE_XMM128 (8) 2 узла

    Сохраните все 128 бит неизменяемого регистра XMM в стеке. Сведения об операции — это номер регистра. Смещение стека, масштабированное на 16, записывается в следующий слот.

  • UWOP_SAVE_XMM128_FAR (9) 3 узла

    Сохраните все 128 бит неизменяемого регистра XMM в стеке с длинным смещением. Сведения об операции — это номер регистра. Немасштабированное смещение стека записывается в следующие два слота.

  • UWOP_PUSH_MACHFRAME (10) 1 узел

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

    Расположение Значение
    RSP+32 SS
    RSP+24 Старый RSP
    RSP+16 EFLAGS
    RSP+8 CS
    RSP RIP

    Если сведения об операции равны 1, то один из этих кадров был помещен:

    Расположение Значение
    RSP+40 SS
    RSP+32 Старый RSP
    RSP+24 EFLAGS
    RSP+16 CS
    RSP+8 RIP
    RSP Код ошибки

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

    1. Извлечение обратного адреса RIP с вершины стека в Temp

    2. Отправка СС

    3. Отправка старого RSP

    4. Отправка EFLAGS

    5. Отправка CS

    6. Отправка Temp

    7. Отправка кода ошибки (если сведения об операции = 1)

    Смоделированная операция UWOP_PUSH_MACHFRAME уменьшает значение RSP на 40 (сведения об операции = 0) или 48 (сведения об операции = 1).

Сведения об операции

Значение битов сведений об операции зависит от кода операции. Чтобы закодировать регистр общего назначения (целое число), используется следующее сопоставление:

бит Регистр
0 RAX
1 RCX
2 RDX
3 RBX
4 RSP
5 RBP
6 RSI
7 RDI
8–15 R8–R15

Структуры связанных данных раскрутки

Если установлен флаг UNW_FLAG_CHAININFO, то структура данных раскрутки является вторичной, а поле общего адреса обработчика исключений/связанных данных содержит основные данные раскрутки. Этот пример кода получает основные данные раскрутки, предполагая, что unwindInfo является структурой, для которой установлен флаг UNW_FLAG_CHAININFO.

PRUNTIME_FUNCTION primaryUwindInfo = (PRUNTIME_FUNCTION)&(unwindInfo->UnwindCode[( unwindInfo->CountOfCodes + 1 ) & ~1]);

Связанные данные полезны в двух ситуациях. Во-первых, их можно использовать для несмежных сегментов кода. Используя связанные данные, можно уменьшить размер необходимых данных раскрутки, поскольку не нужно дублировать массив кодов раскрутки из основных данных раскрутки.

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

Элемент UNWIND_INFO с флагом UNW_FLAG_CHAININFO может содержать запись RUNTIME_FUNCTION, элемент UNWIND_INFO которой также имеет флаг UNW_FLAG_CHAININFO, иногда называемый множественной упаковкой со сжатием. Наконец, указатели связанных данных раскрутки поступают в элемент UNWIND_INFO, у которого снят флаг UNW_FLAG_CHAININFO. Этот элемент является первичным элементом UNWIND_INFO, который указывает на фактическую точку входа процедуры.

Процедура раскрутки

Массив кодов раскрутки сортируется по убыванию. При возникновении исключения полный контекст сохраняется операционной системой в записи контекста. Затем вызывается логика диспетчеризации исключений, которая многократно выполняет следующие действия для поиска обработчика исключений:

  1. Используется текущий протокол RIP, хранящийся в записи контекста, для поиска записи таблицы RUNTIME_FUNCTION, описывающей текущую функцию (или ее часть для связанных записей UNWIND_INFO).

  2. Если запись в таблице функций не найдена, то она находится в конечной функции, а RSP напрямую обращается к указателю возврата. Указатель возврата в [RSP] хранится в обновленном контексте, а моделирование RSP увеличивается на 8, и шаг 1 повторяется.

  3. Если обнаружена запись в таблице функций, RIP может находиться в трех регионах: 1) в заключительном фрагменте, 2) в прологе или 3) в коде, который может быть охвачен обработчиком исключений.

    • В первом случае, если RIP находится в эпилоге, то управление выходит за пределы функции; для этой функции может отсутствовать обработчик исключений, а влияние эпилога должно продолжать вычислять контекст вызывающей функции. Чтобы определить, находится ли RIP в эпилоге, проверяется поток кода из RIP. Если этот поток кода можно сопоставить с завершающей частью допустимого эпилога, то он находится в эпилоге, а оставшаяся часть эпилога имитируется; запись контекста обновляется по мере обработки каждой инструкции. После этой обработки шаг 1 повторяется.

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

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

  4. Если обработчик конкретного языка возвращает обработанное состояние, выполнение продолжается с использованием исходной записи контекста.

  5. Если обработчик не зависит от языка или обработчик возвращает состояние "продолжить поиск", запись контекста следует раскрутить до состояния вызывающего объекта. Это выполняется путем обработки всех элементов массива кодов раскрутки, отменяя результат каждого из них. Затем шаг 1 повторяется.

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

Наименьший набор данных раскрутки — 8 байтов. Это будет представлять функцию, которая выделила не более 128 байтов стека и, возможно, сохранила один неизменяемый регистр. Это также размер структуры связанных данных раскрутки для пролога нулевой длины без кодов раскрутки.

Обработчик конкретного языка

Относительный адрес обработчика конкретного языка содержится в UNWIND_INFO каждый раз, когда установлены флаги UNW_FLAG_EHANDLER или UNW_FLAG_UHANDLER. Как описано в предыдущем разделе, обработчик конкретного языка вызывается как часть поиска обработчика исключений или как часть раскрутки. Он имеет следующий прототип:

typedef EXCEPTION_DISPOSITION (*PEXCEPTION_ROUTINE) (
    IN PEXCEPTION_RECORD ExceptionRecord,
    IN ULONG64 EstablisherFrame,
    IN OUT PCONTEXT ContextRecord,
    IN OUT PDISPATCHER_CONTEXT DispatcherContext
);

ExceptionRecord предоставляет указатель на запись исключения, имеющую стандартное определение Win64.

EstablisherFrame — это адрес основания фиксированного выделения стека для этой функции.

ContextRecord указывает на контекст исключения на момент возникновения исключения (в случае обработчика исключений) или текущего контекста раскрутки (в случае обработчика завершения).

DispatcherContext указывает на контекст диспетчеризации для этой функции. Он имеет следующее определение:

typedef struct _DISPATCHER_CONTEXT {
    ULONG64 ControlPc;
    ULONG64 ImageBase;
    PRUNTIME_FUNCTION FunctionEntry;
    ULONG64 EstablisherFrame;
    ULONG64 TargetIp;
    PCONTEXT ContextRecord;
    PEXCEPTION_ROUTINE LanguageHandler;
    PVOID HandlerData;
} DISPATCHER_CONTEXT, *PDISPATCHER_CONTEXT;

ControlPc — это значение RIP в этой функции. Это значение является либо адресом исключения, либо адресом, по которому элемент управления покинул функцию установки. RIP позволяет определить, находится ли элемент управления в какой-либо защищенной конструкции внутри этой функции (например, блок __try для __try/__except или __try/__finally).

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

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

EstablisherFrame — это адрес основания фиксированного выделения стека для этой функции.

TargetIp предоставляет дополнительный адрес инструкции, указывающий адрес продолжения раскрутки. Этот адрес пропускается, если не указан EstablisherFrame.

ContextRecord указывает на контекст исключения (для использования системным диспетчером исключений/кодом раскрутки).

LanguageHandler указывает на вызываемую подпрограмму обработчика конкретного языка.

HandlerData указывает на данные обработчика конкретного языка для этой функции.

Вспомогательные процедуры раскрутки для MASM

Для написания правильных подпрограмм сборки существует набор псевдоопераций, которые можно использовать параллельно с фактическими инструкциями сборки для создания соответствующих .pdata и .xdata. Также существует набор макросов, которые позволяют упростить использование псевдоопераций для наиболее распространенных целей.

Необработанные псевдооперации

Псевдооперация Description
PROC FRAME [:ehandler] Приводит к тому, что компилятор MASM создает запись таблицы функций в .pdata и данные раскрутки в .xdata для структурированной обработки исключений функции. Если имеется ehandler, эта процедура указывается в .xdata в качестве обработчика конкретного языка.

При использовании атрибута FRAME за ним должна следовать директива .ENDPROLOG. Если функция является конечной (как определено в типах функций), атрибут FRAME не нужен, как и оставшаяся часть этих псевдоопераций.
.PUSHREG register Создает запись кода раскрутки UWOP_PUSH_NONVOL для указанного номера регистра, используя текущее смещение в прологе.

Используйте его только с непостоянными целочисленными регистрами. Для принудительной отправки изменяемых регистров используйте .ALLOCSTACK 8
.SETFRAME register, offset Заполняет поле регистра кадра и смещение в данных раскрутки с помощью указанного регистра и смещения. Смещение должно быть кратным 16 и меньше или равно 240. Эта директива также создает запись кода раскрутки UWOP_SET_FPREG для указанного регистра, используя текущее смещение пролога.
.ALLOCSTACK size Создает UWOP_ALLOC_SMALL или UWOP_ALLOC_LARGE с указанным размером для текущего смещения в прологе.

Операнд size должен быть кратен 8.
.SAVEREG register, offset Создает запись кода раскрутки UWOP_SAVE_NONVOL или UWOP_SAVE_NONVOL_FAR для указанного регистра и смещения с использованием текущего смещения пролога. Компилятор MASM выбирает наиболее эффективную кодировку.

Операнд offset должен быть положительным числом, кратным 8. Операнд offset указывается относительно базы кадра процедуры, которая обычно находится в RSP, или, если используется указатель кадра, немасштабированный указатель кадра.
.SAVEXMM128 register, offset Создает запись кода раскрутки UWOP_SAVE_XMM128 или UWOP_SAVE_XMM128_FAR для указанного регистра XMM и смещения с использованием текущего смещения пролога. Компилятор MASM выбирает наиболее эффективную кодировку.

Операнд offset должен быть положительным числом, кратным 16. Операнд offset указывается относительно базы кадра процедуры, которая обычно находится в RSP, или, если используется указатель кадра, немасштабированный указатель кадра.
. PUSHFRAME [код] Создает запись кода раскрутки UWOP_PUSH_MACHFRAME. Если указан необязательный операнд code, для записи кода раскрутки задается модификатор 1. В противном случае модификатор равен 0.
.ENDPROLOG Сообщает об окончании объявлений пролога. Должен находиться в первых 255 байтах функции.

Ниже приведен пример пролога функции с правильным использованием большинства кодов операций:

sample PROC FRAME
    db      048h; emit a REX prefix, to enable hot-patching
    push rbp
    .pushreg rbp
    sub rsp, 040h
    .allocstack 040h
    lea rbp, [rsp+020h]
    .setframe rbp, 020h
    movdqa [rbp], xmm7
    .savexmm128 xmm7, 020h ;the offset is from the base of the frame
                           ;not the scaled offset of the frame
    mov [rbp+018h], rsi
    .savereg rsi, 038h
    mov [rsp+010h], rdi
    .savereg rdi, 010h ; you can still use RSP as the base of the frame
                       ; or any other register you choose
    .endprolog

; you can modify the stack pointer outside of the prologue (similar to alloca)
; because we have a frame pointer.
; if we didn't have a frame pointer, this would be illegal
; if we didn't make this modification,
; there would be no need for a frame pointer

    sub rsp, 060h

; we can unwind from the next AV because of the frame pointer

    mov rax, 0
    mov rax, [rax] ; AV!

; restore the registers that weren't saved with a push
; this isn't part of the official epilog, as described in section 2.5

    movdqa xmm7, [rbp]
    mov rsi, [rbp+018h]
    mov rdi, [rbp-010h]

; Here's the official epilog

    lea rsp, [rbp+020h] ; deallocate both fixed and dynamic portions of the frame
    pop rbp
    ret
sample ENDP

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

Макросы MASM

Чтобы упростить использование необработанных псевдоопераций, существует набор макросов, определенный в ksamd64.inc, который можно использовать для создания типовых прологов и эпилогов процедур.

Макрос Description
alloc_stack(n) Выделяет кадр стека размером n байт (с помощью sub rsp, n) и выдает соответствующие данные раскрутки (.allocstack n)
save_reg reg, loc Сохраняет неизменяемый регистр reg в стеке по смещению RSP loc и выдает соответствующие данные раскрутки. (.savereg reg, loc)
push_reg reg Помещает неизменяемый регистр reg в стек и выдает соответствующие данные раскрутки. (.pushreg reg)
rex_push_reg reg Сохраняет неизменяемый регистр в стеке с помощью 2-байтовой отправки и выдает соответствующие данные раскрутки. Используйте этот макрос, если инструкция Push является первой инструкцией в функции, чтобы обеспечить возможность исправления этой функции.
save_xmm128 reg, loc Сохраняет неизменяемый регистр XMM reg в стеке по смещению RSP loc и выдает соответствующие данные раскрутки (.savexmm128 reg, loc)
set_frame reg, offset Задает регистр кадра reg равным RSP + offset (с использованием mov или lea) и выдает соответствующие данные раскрутки (.set_frame reg, offset)
push_eflags Помещает eflags в инструкцию pushfq и выдает соответствующие данные раскрутки (.alloc_stack 8)

Ниже приведен пример пролога функции с правильным использованием макросов:

sampleFrame struct
    Fill     dq ?; fill to 8 mod 16
    SavedRdi dq ?; Saved Register RDI
    SavedRsi dq ?; Saved Register RSI
sampleFrame ends

sample2 PROC FRAME
    alloc_stack(sizeof sampleFrame)
    save_reg rdi, sampleFrame.SavedRdi
    save_reg rsi, sampleFrame.SavedRsi
    .end_prolog

; function body

    mov rsi, sampleFrame.SavedRsi[rsp]
    mov rdi, sampleFrame.SavedRdi[rsp]

; Here's the official epilog

    add rsp, (sizeof sampleFrame)
    ret
sample2 ENDP

Описание данных раскрутки в языке C

Ниже приведено описание данных раскрутки в языке C:

typedef enum _UNWIND_OP_CODES {
    UWOP_PUSH_NONVOL = 0, /* info == register number */
    UWOP_ALLOC_LARGE,     /* no info, alloc size in next 2 slots */
    UWOP_ALLOC_SMALL,     /* info == size of allocation / 8 - 1 */
    UWOP_SET_FPREG,       /* no info, FP = RSP + UNWIND_INFO.FPRegOffset*16 */
    UWOP_SAVE_NONVOL,     /* info == register number, offset in next slot */
    UWOP_SAVE_NONVOL_FAR, /* info == register number, offset in next 2 slots */
    UWOP_SAVE_XMM128 = 8, /* info == XMM reg number, offset in next slot */
    UWOP_SAVE_XMM128_FAR, /* info == XMM reg number, offset in next 2 slots */
    UWOP_PUSH_MACHFRAME   /* info == 0: no error-code, 1: error-code */
} UNWIND_CODE_OPS;

typedef unsigned char UBYTE;

typedef union _UNWIND_CODE {
    struct {
        UBYTE CodeOffset;
        UBYTE UnwindOp : 4;
        UBYTE OpInfo   : 4;
    };
    USHORT FrameOffset;
} UNWIND_CODE, *PUNWIND_CODE;

#define UNW_FLAG_EHANDLER  0x01
#define UNW_FLAG_UHANDLER  0x02
#define UNW_FLAG_CHAININFO 0x04

typedef struct _UNWIND_INFO {
    UBYTE Version       : 3;
    UBYTE Flags         : 5;
    UBYTE SizeOfProlog;
    UBYTE CountOfCodes;
    UBYTE FrameRegister : 4;
    UBYTE FrameOffset   : 4;
    UNWIND_CODE UnwindCode[1];
/*  UNWIND_CODE MoreUnwindCode[((CountOfCodes + 1) & ~1) - 1];
*   union {
*       OPTIONAL ULONG ExceptionHandler;
*       OPTIONAL ULONG FunctionEntry;
*   };
*   OPTIONAL ULONG ExceptionData[]; */
} UNWIND_INFO, *PUNWIND_INFO;

typedef struct _RUNTIME_FUNCTION {
    ULONG BeginAddress;
    ULONG EndAddress;
    ULONG UnwindData;
} RUNTIME_FUNCTION, *PRUNTIME_FUNCTION;

#define GetUnwindCodeEntry(info, index) \
    ((info)->UnwindCode[index])

#define GetLanguageSpecificDataPtr(info) \
    ((PVOID)&GetUnwindCodeEntry((info),((info)->CountOfCodes + 1) & ~1))

#define GetExceptionHandler(base, info) \
    ((PEXCEPTION_HANDLER)((base) + *(PULONG)GetLanguageSpecificDataPtr(info)))

#define GetChainedFunctionEntry(base, info) \
    ((PRUNTIME_FUNCTION)((base) + *(PULONG)GetLanguageSpecificDataPtr(info)))

#define GetExceptionDataPtr(info) \
    ((PVOID)((PULONG)GetLanguageSpecificData(info) + 1))

См. также

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