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

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

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

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

Информация о раскрутке функции состоит из нескольких структур данных, описанных ниже.

Сведения об информации Unwind с поддержкой Intel APX (усовершенствованные расширения производительности) см. в предварительной спецификации Unwind V3.

структура 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.

  • Флаги

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

    Флаг Описание
    UNW_FLAG_EHANDLER Функция имеет обработчик исключений, который вызывает операционная система, чтобы проверить состояние исключения и потенциально обработать его. Языковые функции, такие как предложение C __try , регистрируют такой обработчик.
    UNW_FLAG_UHANDLER Функция имеет обработчик завершения, который вызывается системой операций при очистке стека. Этот обработчик может освободить ресурсы, выделенные функцией в безопасном коде исключения. Языковые функции, такие как деструкторы локальных объектов C++ и предложения C __finally , регистрируют такой обработчик завершения.
    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. См. раздел «Код операций развёртывания» со значениями отдельных элементов. Чтобы обеспечить правильное выравнивание данных, этот массив всегда содержит четное количество записей, а окончательная запись может быть неиспользуемой. В этом случае массив будет на единицу больше, чем указано в поле кодов раскрутки.

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

    Указатель относительно образа на зависящий от языка обработчик исключений или обработчик завершения функции, если флаг 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. Если поле «Регистр кадра» не равно нулю, это смещение отсчитывается от того места, где находился RSP в момент, когда был установлен регистр FP. Он равен значению регистра FP за вычетом смещения регистра FP (16 * масштабируемое смещение регистра кадра в UNWIND_INFO). Если используется регистр FP, любой код развёртки, принимающий смещение, должен использоваться только после того, как регистр FP установлен в прологе.

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

Для опкодов 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 должны находиться в прологе первыми и, соответственно, в конце массива кодов раскрутки. Это относительное упорядочение применяется ко всем остальным unwind-кодам, кроме 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. Смещение равно значению поля смещения Frame Register (масштабируемого) в 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 Код ошибки

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

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

    2. Нажмите SS

    3. Отправить старое RSP

    4. Нажмите EFLAGS

    5. Нажмите CS

    6. Отправка Temp

    7. Push-код ошибки (если информация об операции = 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. Группа, которая сохраняет энергонезависимые регистры с помощью PUSH или изменяет регистр RSP с использованием дополнительного фиксированного выделения в стеке, не поддерживается.

Элемент UNWIND_INFO, у которого установлен параметр UNW_FLAG_CHAININFO, может содержать запись RUNTIME_FUNCTION, у элемента UNWIND_INFO которой также установлен параметр UNW_FLAG_CHAININFO; это явление иногда называется множественной shrink-wrap-обёрткой. В конце концов, прицеленные указатели сведений об очистке прибывают к элементу UNWIND_INFO , который UNW_FLAG_CHAININFO очищен. Этот элемент является основным UNWIND_INFO элементом, который указывает на фактическую точку входа процедуры.

Процедура разматывания

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

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

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

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

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

      • Случай b) Если RIP находится в прологе, управление ещё не передано в функцию. Обработчик исключений не может быть связан с этим исключением для этой функции. Эффекты пролога должны быть отменены для вычисления контекста вызывающей функции. RIP находится в прологе, если расстояние от начала функции до RIP меньше или равно размеру пролога, закодированному в информации о раскрутке стека. Механизм размотки стека последовательно просматривает массив кодов размотки вперёд до первой записи, смещение которой меньше или равно смещению RIP относительно начала функции, а затем отменяет действие всех оставшихся элементов в массиве кодов размотки. Затем шаг 1 повторяется.
    • Случай c) Если RIP не находится в прологе или эпилоге, а функция имеет обработчик исключений (UNW_FLAG_EHANDLER установлен), вызывается специфичный для языка обработчик. Обработчик сканирует свои данные и вызывает функции фильтра соответствующим образом. Обработчик конкретного языка может вернуть исключение, которое было обработано, или продолжить поиск. Он также может инициировать размотку непосредственно.

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

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

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

Наименьший набор данных раскрутки — 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-разрядные смещения, используемые в записи функции, и сведения о очистке должны быть добавлены в ImageBase, получить окончательный адрес.

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

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

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

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

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

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

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

Чтобы написать правильные подпрограммы сборки, используйте набор псевдо-операций вместе с фактическими инструкциями по сборке. Эти псевдооперации создают соответствующие .pdata и .xdata. Кроме того, используйте набор макросов, упрощающих применение этих псевдоопераций в наиболее распространённых случаях.

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

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

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

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

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

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

offset должен быть положительным числом и кратным 16. смещение отсчитывается относительно базы кадра процедуры, которая обычно находится в 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. Эти макросы помогают создавать типичные прологи процедур и эпилоги.

Макрос Описание
alloc_stack(n) Выделяет кадр стека размером n байт (с помощью sub rsp, n) и генерирует соответствующую информацию для раскрутки стека (.allocstack n)
save_reg reg, loc Сохраняет неволатильный регистр reg в стеке по смещению RSPloc и формирует соответствующую информацию для раскрутки стека (.savereg reg, loc)
push_reg reg Помещает энергонезависимый регистр reg в стек и генерирует соответствующую информацию о раскрутке стека (.pushreg reg)
rex_push_reg reg Сохраняет ненулевой регистр в стеке с помощью 2-байтовой отправки и выдает соответствующие сведения о очистке (.pushreg reg). Используйте этот макрос, если инструкция 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-разрядных систем