Информация V3 о размотке стека при обработке исключений

Scope

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

Для кода с поддержкой APX требуется Unwind V3. Код, отличный от APX, должен по-прежнему использовать обычные (предварительная версия 3) сведения о очистке.

Предупреждение

Unwind Information V3 — это предварительная спецификация. Существует по-прежнему риск критических изменений или упущений. Код, созданный в соответствии с этим руководством, следует считать кодом предварительной версии.

Терминология

Срок Определение
Фрагмент Непрерывная область машинного кода, описанная одной RUNTIME_FUNCTION / UNWIND_INFO_V3 парой. Функция может состоять из основного фрагмента и нуля или нескольких подфрагментов , связанных друг с другом. Дополнительные сведения см. в разделе Структуры связанной информации о размотке.
WOD Дескриптор операции развёртывания стека — это упакованное представление переменной длины одной операции развёртывания стека (push, alloc, save и т. д.).
Список WOD Массив байтов в составе полезной нагрузки, в котором хранятся все WOD для пролога и эпилога(ов) фрагмента.
Смещение IP-адресов Беззнаковое байтовое смещение команды относительно начала пролога или эпилога.
Полезная нагрузка Область переменной длины, расположенная сразу после 4-байтового заголовка UNWIND_INFO_V3, размер которой задаётся в PayloadWords 16-разрядных словах. Содержит большое расширение пролога, смещения IP пролога, дескрипторы эпилога (с соответствующими смещениями IP) и пул WOD.
RVA Относительный виртуальный адрес — это смещение от базового адреса образа во время загрузки.

Общая структура объектного файла

Отмена очистки версии 3 повторно использует существующие соглашения PE/COFF .pdata и .xdata разделов без изменений:

.pdata   - sorted array of IMAGE_AMD64_RUNTIME_FUNCTION_ENTRY (12 bytes each)
.xdata   - UNWIND_INFO_V3 structures referenced by .pdata entries

Каждая RUNTIME_FUNCTION запись:

Смещение Size Поле
0 4 BeginAddress — RVA запуска фрагмента
4 4 EndAddress — RVA первого байта после конца фрагмента
8 4 UnwindInfoAddress — RVA UNWIND_INFO_V3 в .xdata

Никаких изменений из версии 1 или версии 2.

UNWIND_INFO_V3 Заголовка

Все поля с несколькими байтами являются маленькими. Бит 0 — это LSB каждого байта.

Байт Биты Поле Ширина
0 [2:0] Version (= 3) 3 бита
0 [7:3] Flags 5 битов
1 [7:0] SizeOfProlog 8 бит
2 [7:0] PayloadWords 8 бит
3 [4:0] NumberOfOps 5 битов
3 [7:5] NumberOfEpilogs 3 бита

Семантика поля

Поле Description
Version Этот параметр должен содержать значение 3.
Flags Те же определения флагов, что и V1 или V2: UNW_FLAG_EHANDLER (0x01), (0x02), UNW_FLAG_UHANDLERUNW_FLAG_CHAININFO (0x04). Новое в V3: UNW_FLAG_LARGE (0x08). Бит 4 зарезервирован (ноль).
SizeOfProlog Смещение в байтах до начала первой инструкции, не входящей в пролог. Если UNW_FLAG_LARGE задано, это 8-разрядное поле является низким байтом 16-разрядного размера пролога. Дополнительные сведения о 16-разрядной форме см. UNW_FLAG_LARGE и UNWIND_INFO_LARGE_V3.
PayloadWords Количество 16-разрядных слов полезных данных, следующих за этим заголовком. Полезные данные содержат смещения IP пролога, все дескрипторы эпилога (включая их смещения IP) и пул WOD. Не включает RVA обработчика исключений или цепочку RUNTIME_FUNCTION , которая может следовать. Алгоритм для определения местоположения данных обработчика/цепочки такой же, как в V1 или V2: header + 4 + PayloadWords * 2, с выравниванием по DWORD.
NumberOfOps Количество WOD в прологе (0–31). Ноль означает отсутствие пролога. Если для функции требуется более 31 операций пролога, используйте подфрагмент.
NumberOfEpilogs Количество EPILOG_INFO_V3 дескрипторов, которые следуют за смещениями IP пролога (0–7). Ноль означает отсутствие эпилогов в этом фрагменте. Если требуется более 7, используйте подфрагмент.

UNW_FLAG_LARGE и UNWIND_INFO_LARGE_V3.

Если в заголовке UNW_FLAG_LARGE установлен Flags, первый байт полезной нагрузки представляет собой 1-байтовое расширение UNWIND_INFO_LARGE_V3:

Байт Биты Поле Ширина
0 [7:0] SizeOfPrologHighByte 8 бит

SizeOfPrologHighByte в сочетании с UNWIND_INFO_V3.SizeOfProlog образуют 16-разрядный размер пролога: SizeOfProlog16 = (SizeOfPrologHighByte << 8) | SizeOfProlog

Кроме того, если UNW_FLAG_LARGE задано:

  • Записи смещения IP в прологе являются 16-разрядными беззнаковыми (по 2 байта каждая), а не 8-разрядными.

Байт UNWIND_INFO_LARGE_V3 является частью полезной нагрузки и входит в состав PayloadWords. Формула смещения обработчика остается неизменной.

Этот флаг необходим только для прологов, превышающих 255 байт.

Поиск обработчика исключений или сведений о цепочке

Идентичен версии 1 или версии 2:

handler_offset = ALIGN_UP(sizeof(UNWIND_INFO_V3) + PayloadWords * 2, 4)

Если UNW_FLAG_EHANDLER или UNW_FLAG_UHANDLER задано, 4-байтовый обработчик RVA имеет значение handler_offset, за которым следует данные обработчика, зависящие от языка.

Если UNW_FLAG_CHAININFO задано, 12-байт RUNTIME_FUNCTION находится в handler_offset.

Структура полезной нагрузки

Сразу после 4-байтового заголовка полезная нагрузка располагается в следующем порядке (всё упаковано, семантика #pragma pack(1)):

  1. UNWIND_INFO_LARGE_V3 - 1 байт, присутствует только в том случае, если UNW_FLAG_LARGE задано.
  2. Смещения IP-адресов пролога — NumberOfOps байты (или 16-разрядные слова if UNW_FLAG_LARGE).
  3. Для каждого эпилога (повторяется NumberOfEpilogs раз):
    • EPILOG_INFO_V3 Дескриптор.
    • EPILOG_INFO_EX_V3 или EPILOG_INFO_LARGE_EX_V3 расширенный дескриптор (присутствует только в том случае NumberOfOps > 0).
    • Массив смещения IP — NumberOfOps байты (или 16-разрядные слова if EPILOG_INFO_LARGE).
  4. Пул WOD — оставшиеся байты.

Общий размер = PayloadWords × 2 байта (может включать заполнение для заполнения слов).

Смещения IP-адресов Prolog

Массив записей NumberOfOps. Каждая запись является несписанным байтом, предоставляющим смещение IP с начала фрагмента инструкции, выполняющей соответствующую операцию очистки. Если UNW_FLAG_LARGE установлено, каждая запись вместо этого представляет собой 16-разрядное слово без знака.

Порядок: Первая запись соответствует операции, наиболее близкой к телу функции (инструкция пролога последняя с эффектом развёртывания стека). Последняя запись соответствует операции, ближайшей к точке входа функции ( первая инструкция prolog). Это соответствует соглашению V1 или V2 о перечислении кодов отмены в обратном порядке выполнения. WOD пролога всегда начинаются с нулевого байтового смещения в пуле WOD, и к ним применяется тот же порядок.

Дескрипторы эпилога

Ноль или более EPILOG_INFO_V3 структур следуют смещениям IP-адресов пролога. Каждый может иметь необязательный EPILOG_INFO_EX_V3 (или EPILOG_INFO_LARGE_EX_V3, если установлен флаг EPILOG_INFO_LARGE), а также собственный массив смещений IP переменного размера, размером NumberOfOps:

Когда NumberOfOps > 0 (полный дескриптор)

Стандартная форма (EPILOG_INFO_LARGE не задана):

EPILOG_INFO_V3:

Байт Биты Поле Ширина
0 [2:0] Flags 3 бита
0 [7:3] NumberOfOps 5 битов
1–2 [15:0] EpilogOffset 16-разрядная подпись

EPILOG_INFO_EX_V3:

Байт Биты Поле Ширина
0–1 [15:0] FirstOp 16-разрядная без знака
2 [7:0] IpOffsetOfLastInstruction 8 бит без знака
3 .. - Массив смещения IP NumberOfOps байты, без знака

Крупный формат (EPILOG_INFO_LARGE набор):

EPILOG_INFO_V3:

Байт Биты Поле Ширина
0 [2:0] Flags 3 бита
0 [7:3] NumberOfOps 5 битов
1–2 [15:0] EpilogOffset 16-разрядная подпись

EPILOG_INFO_LARGE_EX_V3:

Байт Биты Поле Ширина
0–1 [15:0] FirstOp 16-разрядная без знака
2–3 [15:0] IpOffsetOfLastInstruction 16 бит без знака
4 .. - Массив смещения IP NumberOfOps × 2 байта, беззнаковое 16-разрядное

Когда NumberOfOps == 0 (унаследованный дескриптор)

EPILOG_INFO_V3:

Байт Биты Поле Ширина
0 [2:0] Flags 3 бита
0 [7:3] NumberOfOps (= 0) 5 битов
1–2 [15:0] EpilogOffset 16-разрядная подпись

Запись EPILOG_INFO_V3 с NumberOfOps == 0 наследует действующие значения для полей NumberOfOps, FirstOp, IpOffsetOfLastInstruction и массива смещений IP от первой предшествующей записи EPILOG_INFO_V3 с NumberOfOps != 0. Компилятор должен гарантировать, что Flags биты 0 и 1 в записи эпилога с NumberOfOps == 0 имеют те же значения, что и в первой предшествующей EPILOG_INFO_V3 с NumberOfOps != 0.

Семантика поля эпилога

Поле Description
Flags Bit 0: EPILOG_INFO_PARENT_FRAGMENT_TRANSFER — устанавливается, если этот эпилог передаёт управление обратно в родительский фрагмент (например, через JMP), а не возвращает управление вызывающей стороне. Бит 1: EPILOG_INFO_LARGE — при установке расширенный дескриптор использует EPILOG_INFO_LARGE_EX_V3 (16-разрядный IpOffsetOfLastInstruction), и каждая запись смещения IP имеет длину 16 бит, что позволяет учитывать эпилоги размером более 255 байт. Бит 2: зарезервирован (ноль).
NumberOfOps Количество WOD для этого эпилога (0–31). Ноль — это специальное значение, которое означает "наследование от предыдущего дескриптора эпилога".
EpilogOffset 16-разрядное смещение со знаком до первой инструкции этого эпилога. Для первого дескриптора эпилога: положительные значения представляют собой байтовые смещения от начала фрагмента, а отрицательные значения — байтовые смещения от конца фрагмента, то есть от первого байта сразу после его конца. Для последующих дескрипторов эпилога: разность с начала предыдущего эпилога. Все эпилоги должны использовать один и тот же знак - либо все отсортированные по возрастанию от начала, либо все отсортированные по убыванию от хвоста.
FirstOp Индекс байтов в пул WOD, где находится первый WOD для этого эпилога. Это смещение в байтах, а не индекс WOD. Эпилоги могут совместно использовать WOD с прологом или друг с другом, указывая на один и тот же регион пула.
IpOffsetOfLastInstruction Беззнаковое смещение в байтах от начала эпилога до последней инструкции в эпилоге, обычно RET или JMP. 8-разрядный в EPILOG_INFO_EX_V3; 16-разрядный в EPILOG_INFO_LARGE_EX_V3. Механизм раскрутки стека использует это значение, чтобы определить, где заканчивается эпилог и где возобновляется выполнение тела функции.

Смещения IP Epilog

Сразу после каждого полного дескриптора эпилога (когда NumberOfOps > 0) массив из записей NumberOfOps содержит смещение IP для каждой инструкции эпилога, для которой имеется соответствующий WOD. Каждая запись представляет собой неподписанный байт или 16-разрядное слово без знака при EPILOG_INFO_LARGE установке. Упорядочение: первая запись = операция, близкая к телу (первая инструкция эпилога с эффектом очистки), последняя запись = операция, близкая к инструкции передачи элементов управления.

Пул WOD

Оставшиеся байты полезной нагрузки образуют пул WOD. WOD-ы пролога неявно начинаются с байтового смещения 0 в этом пуле. Список WOD каждого эпилога начинается с байтового смещения, которое задаётся его полем FirstOp. WOD упаковываются без выравнивания и заполнения между ними.

Количество WOD, используемых для пролога, составляет UNWIND_INFO_V3.NumberOfOps. Число, используемое для каждого эпилога, является соответствующим EPILOG_INFO_V3.NumberOfOps (или унаследованным).

Справочник по кодировке WOD

WOD имеют переменную длину (1–5 байт) и упаковываются с помощью #pragma pack(1). Код опкода закодирован в битах низкого порядка первого байта. Для декодирования требуется многоуровневая проверка битов.

Таблица отправки opcode

Чтобы декодировать WOD, прочитайте первый байт и проверьте его низкие биты в следующем порядке:

Тест Значение кода операции Тип WOD Size
byte[0] == 0x00 0 WOD_SET_FPREG 2 байта
byte[0] == 0x01 1 WOD_ALLOC_HUGE 5 байт
byte[0] == 0x02 2 WOD_ALLOC_LARGE 3 байта
byte[0] == 0x03 3 WOD_PUSH_CANONICAL_FRAME 2 байта
(byte[0] & 0x07) == 0x04 4 WOD_PUSH 1 байт
(byte[0] & 0x07) == 0x05 5 WOD_SAVE_NONVOL_FAR 5 байт
(byte[0] & 0x07) == 0x06 6 WOD_SAVE_NONVOL 3 байта
(byte[0] & 0x07) == 0x07 7 WOD_PUSH_CONSECUTIVE_2 1 байт
(byte[0] & 0x0F) == 0x08 8 WOD_ALLOC_SMALL 1 байт
(byte[0] & 0x0F) == 0x09 9 WOD_SAVE_XMM128_FAR 5 байт
(byte[0] & 0x0F) == 0x0A 10 WOD_SAVE_XMM128 3 байта
(byte[0] & 0x3F) == 0x20 32 WOD_PUSH2 2 байта

** Алгоритм декодирования WOD (псевдокод):**

uint8_t b0 = pool[offset];
uint8_t op3 = b0 & 0x07;

switch (op3)
{
case 4: return WOD_PUSH;           // 3-bit opcode = 100b
case 5: return WOD_SAVE_NONVOL_FAR;// 3-bit opcode = 101b
case 6: return WOD_SAVE_NONVOL;    // 3-bit opcode = 110b
case 7: return WOD_PUSH_CONSECUTIVE_2; // 3-bit opcode = 111b
default: break; // bits[2:0] are 0b000, 0b001, 0b010, or 0b011
}

uint8_t op4 = b0 & 0x0F;
switch (op4)
{
case 0x08: return WOD_ALLOC_SMALL;
case 0x09: return WOD_SAVE_XMM128_FAR;
case 0x0A: return WOD_SAVE_XMM128;
default: break;
}

uint8_t op6 = b0 & 0x3F;
if (op6 == 0x20) return WOD_PUSH2;

// 8-bit opcode (full byte match)
switch (b0)
{
case 0x00: return WOD_SET_FPREG;
case 0x01: return WOD_ALLOC_HUGE;
case 0x02: return WOD_ALLOC_LARGE;
case 0x03: return WOD_PUSH_CANONICAL_FRAME;
default:   return INVALID;
}

Макеты WOD (Bit-Level)

Все битовые позиции нумерованы LSB-first в каждом байте. Поля целочисленного числа с несколькими байтами являются маленькими.

WOD_PUSH - 1 байт

Byte 0: [2:0] = 100b (opcode 4)
        [7:3] = Register (5 bits, AMD64 integer register number)

Эффект:PUSH <reg> настраивает RSP на 8 и сохраняет регистр.

WOD_PUSH2 - 2 байта

Byte 0: [5:0] = 100000b (opcode 32)
        [7:6] = Register1[1:0] (low 2 bits)
Byte 1: [2:0] = Register1[4:2] (high 3 bits)
        [7:3] = Register2 (5 bits)

Эффект:PUSH2 <reg1>, <reg2> отправляет два регистра с одной инструкцией (APX). Корректирует RSP на 16.

WOD_PUSH_CONSECUTIVE_2 - 1 байт

Byte 0: [2:0] = 111b (opcode 7)
        [7:3] = Register (5 bits)

Эффект: Толкает Register и Register+1 последовательно. Изменяет RSP в общей сложности на 16. Register должно быть ограничено диапазоном [0, 30], так как значение 31 выведет Register+1 за пределы допустимого диапазона.

WOD_ALLOC_SMALL - 1 байт

Byte 0: [3:0] = 1000b (opcode 8)
        [7:4] = Size (4 bits)

Фактическое выделение:(Size + 1) * 8 Байт. Диапазон: 8–128 байт в шагах от 8.

WOD_ALLOC_LARGE - 3 байта

Byte 0: [7:0] = 0x02 (opcode 2)
Bytes 1–2: Size (16-bit unsigned, little-endian)

Фактическое выделение:Size * 8 Байт. Диапазон: до 524 280 байт.

WOD_ALLOC_HUGE - 5 байт

Byte 0: [7:0] = 0x01 (opcode 1)
Bytes 1–4: Size (32-bit unsigned, little-endian)

Фактическое выделение: Необработанное число байтов (без масштабирования). Диапазон: до 4 ГиБ.

WOD_SET_FPREG - 2 байта

Byte 0: [7:0] = 0x00 (opcode 0)
Byte 1: [3:0] = Register (4 bits, 0–15)
        [7:4] = Offset (4 bits)

Эффект: Устанавливает указатель кадра. <reg> = RSP + Offset * 16.

WOD_SAVE_NONVOL - 3 байта

Byte 0: [2:0] = 110b (opcode 6)
        [7:3] = Register (5 bits)
Bytes 1–2: Displacement (16-bit unsigned, little-endian)

Эффект:MOV [RSP + Displacement * 8], <reg> сохраняет неволатильный целочисленный регистр в стек.

WOD_SAVE_NONVOL_FAR - 5 байт

Byte 0: [2:0] = 101b (opcode 5)
        [7:3] = Register (5 bits)
Bytes 1–4: Displacement (32-bit unsigned, little-endian)

Эффект: То же, что и WOD_SAVE_NONVOL, но с 32-разрядным немасштабируемым байтовым смещением. Используется, если смещение не соответствует 16 битам, масштабируемым на 8.

WOD_SAVE_XMM128 - 3 байта

Byte 0: [3:0] = 1010b (opcode 10)
        [7:4] = Register (4 bits, XMM0–XMM15)
Bytes 1–2: Displacement (16-bit unsigned, little-endian)

Эффект:MOVAPS [RSP + Displacement * 16], <xmm> сохраняет 128-разрядную регистрацию XMM.

WOD_SAVE_XMM128_FAR - 5 байт

Byte 0: [3:0] = 1001b (opcode 9)
        [7:4] = Register (4 bits, XMM0–XMM15)
Bytes 1–4: Displacement (32-bit unsigned, little-endian)

Эффект: То же, что и WOD_SAVE_XMM128, но с 32-разрядным немасштабируемым байтовым смещением.

WOD_PUSH_CANONICAL_FRAME - 2 байта

Byte 0: [7:0] = 0x03 (opcode 3)
Byte 1: [7:0] = Type (8 bits)

Эффект: Указывает, что оборудование или ОС поместило канонический фрейм в стек. Значения типа различают:

  • Кадр компьютера без кода ошибки
  • Рама машины с кодом ошибки
  • Кадр компьютера с push-стеком теневого стека
  • Контекстная запись

(Точные значения типов определяются ОС; обратитесь к заголовкам пакета SDK Windows.)

Сводка констант opcode

#define WOD_OP_SET_FPREG              0   // 8-bit opcode, 2 bytes
#define WOD_OP_ALLOC_HUGE             1   // 8-bit opcode, 5 bytes
#define WOD_OP_ALLOC_LARGE            2   // 8-bit opcode, 3 bytes
#define WOD_OP_PUSH_CANONICAL_FRAME   3   // 8-bit opcode, 2 bytes
#define WOD_OP_PUSH                   4   // 3-bit opcode, 1 byte
#define WOD_OP_SAVE_NONVOL_FAR        5   // 3-bit opcode, 5 bytes
#define WOD_OP_SAVE_NONVOL            6   // 3-bit opcode, 3 bytes
#define WOD_OP_PUSH_CONSECUTIVE_2     7   // 3-bit opcode, 1 byte
#define WOD_OP_ALLOC_SMALL            8   // 4-bit opcode, 1 byte
#define WOD_OP_SAVE_XMM128_FAR        9   // 4-bit opcode, 5 bytes
#define WOD_OP_SAVE_XMM128           10   // 4-bit opcode, 3 bytes
#define WOD_OP_PUSH2                 32   // 6-bit opcode, 2 bytes

Регистрация кодировки

Целые регистры используют стандартную нумерацию AMD64 (5 бит, 0–31):

Ценность Регистрация
0 RAX
1 RCX
2 RDX
3 RBX
4 RSP
5 RBP
6 РСИ (индекс относительной силы)
7 RDI
8–15 R8 — R15
16 - 31 R16 — R31 (APX)

Поля регистра XMM имеют длину 4 бита (0–15) и напрямую соответствуют XMM0–XMM15.