Примечание
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
Windows на устройствах ARM64 использует один механизм структурированной обработки исключений для асинхронных аппаратных и синхронных программных исключений. Обработчики исключения для конкретных языков созданы на базе структурированной обработки исключений Windows с помощью вспомогательных функций языка. В этом документе описывается обработка исключений в Windows в ARM64. Он иллюстрирует вспомогательные средства языка, используемые кодом, созданным сборщиком Microsoft ARM и компилятором MSVC.
Цели и причины для использования
Соглашения о данных очистки исключений и эти сведения предназначены для реализации следующих задач.
Предоставьте достаточно ясное описание, позволяющее производить раскрутку без проверки кода во всех случаях.
Для анализа кода требуется загрузка его в память. Он предотвращает размотку в некоторых ситуациях, когда это полезно (трассировка, семплирование, отладка).
Анализ кода является сложным процессом, поэтому компилятор должен генерировать только те инструкции, которые может декодировать развёртчик.
Если распаковка не может быть полностью описана с помощью кодов распаковки, то в некоторых случаях необходимо вернуться к декодированию инструкций. Декодирование инструкций повышает общую сложность и в идеале следует избегать.
Поддержка очистки в середине пролога и середине эпилога.
- Очистка в Windows используется не только для обработки исключений. Крайне важно обеспечить правильную очистку кода, даже если он находится в середине пролога или эпилога.
Использование минимального объема пространства.
Для предотвращения значительного увеличения двоичного размера коды распаковки не должны агрегироваться.
Так как коды очистки, скорее всего, будут заблокированы в памяти, небольшое пространство обеспечивает минимальные издержки для каждого загруженного двоичного файла.
Предположения
Эти предположения вносятся в описание обработки исключений:
Прологи и эпилоги стремятся зеркально отображать друг друга. Используя эту общую характеристику, можно значительно сократить объем метаданных, необходимых для описания размотки. В теле функции не имеет значения, отменены ли операции пролога или операции эпилога выполнены по порядку. Оба случая должны давать одинаковые результаты.
Как правило, функции в целом имеют относительно небольшой размер. На этом основано несколько оптимизаций для более эффективной упаковки данных.
В эпилогах нет условного кода.
Выделенный регистр указателя кадра: если
sp
сохраняется в другом регистре (x29
) в прологе, этот регистр остается неизменным на протяжении всей функции. Это означает, что исходный файлsp
может быть восстановлен в любое время.Если
sp
не сохраняется в другом регистре, все манипуляции указателя стека выполняются исключительно в прологу и эпилогу.Структура кадра стека организована так, как описано в следующем разделе.
Схема кадра стека ARM64
Для функций, связанных с кадрами, fp
lr
пара может сохраняться в любой позиции в локальной переменной в зависимости от соображений оптимизации. Цель состоит в том, чтобы максимально увеличить количество локалов, которые можно достичь одной инструкцией на основе указателя кадра (x29
) или указателя стека (sp
). Однако для alloca
функций он должен быть связан, и x29
должен указывать на нижнюю часть стека. Чтобы обеспечить лучшее покрытие режима адресации пар регистров, области сохранения в энергозависимых регистрах располагаются в верхней части локального стека. Ниже приведены примеры, иллюстрирующие несколько наиболее эффективных последовательностей пролога. Для ясности и улучшения локальности кэша, резервные регистры сохраняются во всех канонических прологах в порядке возрастания.
#framesz
ниже представлен размер всего стека (за исключением alloca
области).
#localsz
и #outsz
обозначают размер локальной области (включая область сохранения для пары <x29, lr>
) и размер исходящего параметра соответственно.
Со сцеплением, #localsz <= 512
stp x19,x20,[sp,#-96]! // pre-indexed, save in 1st FP/INT pair stp d8,d9,[sp,#16] // save in FP regs (optional) stp x0,x1,[sp,#32] // home params (optional) stp x2,x3,[sp,#48] stp x4,x5,[sp,#64] stp x6,x7,[sp,#82] stp x29,lr,[sp,#-localsz]! // save <x29,lr> at bottom of local area mov x29,sp // x29 points to bottom of local sub sp,sp,#outsz // (optional for #outsz != 0)
Связанный, #localsz > 512
stp x19,x20,[sp,#-96]! // pre-indexed, save in 1st FP/INT pair stp d8,d9,[sp,#16] // save in FP regs (optional) stp x0,x1,[sp,#32] // home params (optional) stp x2,x3,[sp,#48] stp x4,x5,[sp,#64] stp x6,x7,[sp,#82] sub sp,sp,#(localsz+outsz) // allocate remaining frame stp x29,lr,[sp,#outsz] // save <x29,lr> at bottom of local area add x29,sp,#outsz // setup x29 points to bottom of local area
Независимые, листовые функции (
lr
несохраненные)stp x19,x20,[sp,#-80]! // pre-indexed, save in 1st FP/INT reg-pair stp x21,x22,[sp,#16] str x23,[sp,#32] stp d8,d9,[sp,#40] // save FP regs (optional) stp d10,d11,[sp,#56] sub sp,sp,#(framesz-80) // allocate the remaining local area
Доступ ко всем локальным ресурсам зависит от
sp
.<x29,lr>
указывает на предыдущий кадр. Для размера <кадра = 512 область сохранения регистров можно оптимизировать, если ее переместить в нижнюю часть стека. Недостатком является то, что он не согласуется с другими макетами выше. Сохраненные регистры занимают часть диапазона для парных регистров и режима адресации смещения с пред- и пост-индексированием.Незаклинированные, неконечные функции (сохраняется
lr
в сохраненной области Int)stp x19,x20,[sp,#-80]! // pre-indexed, save in 1st FP/INT reg-pair stp x21,x22,[sp,#16] // ... stp x23,lr,[sp,#32] // save last Int reg and lr stp d8,d9,[sp,#48] // save FP reg-pair (optional) stp d10,d11,[sp,#64] // ... sub sp,sp,#(framesz-80) // allocate the remaining local area
Или с четным числом сохраненных регистров типа Int,
stp x19,x20,[sp,#-80]! // pre-indexed, save in 1st FP/INT reg-pair stp x21,x22,[sp,#16] // ... str lr,[sp,#32] // save lr stp d8,d9,[sp,#40] // save FP reg-pair (optional) stp d10,d11,[sp,#56] // ... sub sp,sp,#(framesz-80) // allocate the remaining local area
Сохранено только
x19
;sub sp,sp,#16 // reg save area allocation* stp x19,lr,[sp] // save x19, lr sub sp,sp,#(framesz-16) // allocate the remaining local area
* Выделение области сохранения регистров не включено в
stp
, так как предварительно индексированный рег-листstp
не может быть представлен с помощью кодов раскрутки.Доступ ко всем локальным ресурсам зависит от
sp
.<x29>
указывает на предыдущий кадр.Связанное, #framesz <= 512, #outsz = 0
stp x29,lr,[sp,#-framesz]! // pre-indexed, save <x29,lr> mov x29,sp // x29 points to bottom of stack stp x19,x20,[sp,#(framesz-32)] // save INT pair stp d8,d9,[sp,#(framesz-16)] // save FP pair
По сравнению с первым примером prolog выше этот пример имеет преимущество: все инструкции по сохранению регистра готовы выполняться только после одной инструкции выделения стека. Это означает, что не существует антизависимостей от
sp
, препятствующих параллелизму на уровне инструкций.Связанный, размер > кадра 512 (опционально для функций без
alloca
)stp x29,lr,[sp,#-80]! // pre-indexed, save <x29,lr> stp x19,x20,[sp,#16] // save in INT regs stp x21,x22,[sp,#32] // ... stp d8,d9,[sp,#48] // save in FP regs stp d10,d11,[sp,#64] mov x29,sp // x29 points to top of local area sub sp,sp,#(framesz-80) // allocate the remaining local area
Для оптимизации
x29
можно разместить в любой позиции в локальной области, чтобы обеспечить лучшее покрытие для режима адресации reg-pair и до-/послеиндексированного смещения. Локальные указатели кадров ниже можно получить на основеsp
.Цепной, размер кадра > 4K, с или без alloca()
stp x29,lr,[sp,#-80]! // pre-indexed, save <x29,lr> stp x19,x20,[sp,#16] // save in INT regs stp x21,x22,[sp,#32] // ... stp d8,d9,[sp,#48] // save in FP regs stp d10,d11,[sp,#64] mov x29,sp // x29 points to top of local area mov x15,#(framesz/16) bl __chkstk sub sp,sp,x15,lsl#4 // allocate remaining frame // end of prolog ... sub sp,sp,#alloca // more alloca() in body ... // beginning of epilog mov sp,x29 // sp points to top of local area ldp d10,d11,[sp,#64] ... ldp x29,lr,[sp],#80 // post-indexed, reload <x29,lr>
Сведения об обработке исключений в ARM64
.pdata
Записи
Записи .pdata
представляют собой упорядоченный массив элементов фиксированной длины, описывающих каждую функцию управления стека в двоичном файле PE. Фраза "манипуляции со стеком" имеет важное значение: листовые функции, которые не требуют локального хранилища и не нужно сохранять и восстанавливать неизменяемые регистры, не требуют .pdata
записи. Для экономии пространства эти записи должны быть опущены явным образом. Развертывание из одной из этих функций может получить адрес возврата непосредственно из lr
, чтобы перейти к вызывающей программе.
Каждая .pdata
запись для ARM64 составляет 8 байтов. Общий формат каждой записи помещает 32-битный RVA начала функции в первую позицию, за которым следует второе слово, которое содержит либо указатель на блок переменной длины .xdata
, либо упакованное слово, описывающее каноническую последовательность сворачивания функции.
Поля следующие.
Function Start RVA (Относительный виртуальный адрес начала функции) представляет собой 32-битовый относительный виртуальный адрес начала функции.
Флаг — это 2-разрядное поле, указывающее, как интерпретировать оставшиеся 30 битов второго
.pdata
слова. Если Flag равно 0, то оставшиеся биты формируют Exception Information RVA (при этом два младших бита неявно равны 0). Если Flag (Флаг) имеет ненулевое значение, то оставшиеся биты формируют структуру Packed Unwind Data (Упакованные данные очистки).Exception Information RVA (Относительный виртуальный адрес сведений об исключении) представляет собой адрес структуры сведений об исключении переменной длины, хранимой в разделе
.xdata
. Эти данные должны быть выровнены по 4-байтовой границе.Упакованные данные очистки представляют собой сжатое описание операций, необходимых для выполнения очистки из функции в канонической форме. В этом случае запись
.xdata
не требуется.
.xdata
Записи
Когда упакованного формата развёртки недостаточно для описания развёртки функции, необходимо создать запись .xdata
переменной длины. Адрес этой записи хранится во втором слове записи .pdata
. Формат .xdata
представляет собой упакованный набор слов переменной длины:
Эти данные состоят из четырех разделов.
Односоставный или двусоставный заголовок, описывающий общий размер структуры и предоставляющий ключевые данные о её функциях. Второе слово присутствует только в том случае, если для обоих полей Epilog Count (Число эпилогов) и Code Words (Слова кодов) задано значение 0. Заголовок содержит следующие битовые поля:
a. Function Length (Длина функции) представляет собой 18-битовое поле. Оно указывает общую длину функции в байтах, поделенную на 4. Если функция превышает 1 мегабайт, для описания функции необходимо использовать несколько
.pdata
и.xdata
записей. Дополнительные сведения см. в разделе Большие функции.б. Vers (Версия) представляет собой 2-битовое поле. Она описывает версию оставшейся части
.xdata
. Сейчас определена только версия 0, поэтому значения 1–3 запрещены.с. X представляет собой 1-битовое поле. Оно указывает наличие (1) или отсутствие (0) данных исключения.
д. E представляет собой 1-битовое поле. Он указывает, что информация, описывающая один эпилог, упакована в заголовок (1), а не требует добавления дополнительных слов позже (0).
д) Epilog Count (Число эпилогов) представляет собой 5-битовое поле, имеющее два значения в зависимости от состояния бита E:
Если E равен 0, то он указывает общее число областей эпилога, описанных в разделе 2. Если в функции присутствует более 31 области, для поля Code Words (Слова кодов) следует установить значение 0, чтобы указать на потребность в слове расширения.
Если E равен 1, то это поле указывает индекс первого кода отката, описывающего единственный эпилог.
е) Code Words (Кодовые слова) представляет собой 5-битовое поле, которое указывает количество 32-разрядных слов, необходимых для размещения всех кодов развёртки в разделе 3. Если требуется более 31 слова (т. е. 124 кодов разворачивания), это поле должно быть равно 0 для указания на то, что требуется слово расширения.
ж. Extended Epilog Count (Расширенное число эпилогов) и Extended Code Words (Расширенные слова кодов) представляют собой 16- и 8-битовые поля, соответственно. Они предоставляют дополнительное пространство для кодирования необычайно большого количества эпилогов или необычайно большого количества управляющих кодов развёртывания. Слово расширения, содержащее это поле, присутствует только в том случае, если для полей Epilog Count (Число эпилогов) и Code Words (Слова кодов) в первом слове заголовка задано значение 0.
Если количество эпилогов не равно нулю, после заголовка и необязательного расширенного заголовка идет список сведений об областях эпилога, упакованных в одно слово. Они хранятся в порядке увеличения начального смещения. Каждый диапазон содержит следующие биты:
a. Epilog Start Offset (Смещение начала эпилога) представляет собой 18-битовое поле, которое описывает смещение эпилога в байтах, поделенное на 4, относительно начала функции.
б. Res представляет собой 4-битовое поле, зарезервированное для будущего расширения. Оно должно иметь значение 0.
с. Epilog Start Index (Индекс начала эпилога) представляет собой 10-битовое поле (на 2 бита больше, чем поле Extended Code Words (Расширенные слова кодов)). Оно указывает индекс байта первого кода раскрутки, описывающего этот эпилог.
После списка областей эпилога идет массив байтов с кодами развёртки, которые подробно описаны далее. Этот массив дополняется до ближайшей границы полного слова в конце. В этот массив записываются коды разворачивания. Они начинаются с ближайшего к телу функции и переходят к краям функции. Байты для каждого кода отмены хранятся в старшем порядке байтов, поэтому наиболее значимый байт считывается первым, определяя операцию и длину остального кода.
Наконец, если поле X в заголовке равно 1, после байтов кодов очистки располагаются сведения об обработчике исключений. Он состоит из одного относительного виртуального адреса обработчика исключений, содержащего адрес самого обработчика исключений. После него сразу идет объем данных переменной длины, требуемых этому обработчику исключений.
Запись .xdata
спроектирована таким образом, чтобы можно было получить первые 8 байтов и использовать их для вычисления полного размера записи, за исключением длины данных исключений переменного размера. Следующий фрагмент кода вычисляет размер записи:
ULONG ComputeXdataSize(PULONG Xdata)
{
ULONG Size;
ULONG EpilogScopes;
ULONG UnwindWords;
if ((Xdata[0] >> 22) != 0) {
Size = 4;
EpilogScopes = (Xdata[0] >> 22) & 0x1f;
UnwindWords = (Xdata[0] >> 27) & 0x1f;
} else {
Size = 8;
EpilogScopes = Xdata[1] & 0xffff;
UnwindWords = (Xdata[1] >> 16) & 0xff;
}
if (!(Xdata[0] & (1 << 21))) {
Size += 4 * EpilogScopes;
}
Size += 4 * UnwindWords;
if (Xdata[0] & (1 << 20)) {
Size += 4; // Exception handler RVA
}
return Size;
}
Хотя пролог и каждый эпилог имеют собственный индекс в кодах раскрутки, эта таблица используется ими совместно. Не исключено (и это не так уж редко), что они все могут использовать одни и те же коды. (Пример см. в примере 2 в разделе Примеры.) Разработчикам компиляторов следует оптимизировать в этом случае. Это связано с тем, что самый большой индекс, который может быть указан, - 255, что ограничивает общее количество кодов распаковки для определенной функции.
Коды распутывания
Массив кодов отмены — это набор последовательностей, которые точно описывают, как отменить эффекты пролога. Они хранятся в том же порядке, что и операции, которые необходимо отменить. Коды разворачивания представляют собой небольшой набор инструкций, закодированных в виде строки байтов. После завершения выполнения обратный адрес вызывающей функции находится в регистре lr
. А для всех неизменяемых регистров восстанавливаются значения, актуальные на момент вызова функции.
Если бы существовала уверенность в том, что исключения возникают только в теле функции и никогда не возникают в прологе или эпилоге, потребовалась бы всего одна последовательность. Однако модель очистки Windows требует, чтобы код мог выполнять очистку из частично выполненного пролога или эпилога. Чтобы выполнить это требование, коды разворачивания были тщательно спроектированы, чтобы однозначно сопоставлять 1:1 с каждым соответствующим кодом операции в прологе и эпилоге. Эта особенность имеет несколько разных применений.
Сосчитав число кодов раскрутки, можно вычислить длину пролога и эпилога.
Считая число инструкций после начала области эпилога, можно пропустить эквивалентное число кодов очистки. Мы можем выполнить оставшуюся часть последовательности, чтобы завершить частично выполненное эпилогом восстановление.
Считая число инструкций до окончания пролога, можно пропустить эквивалентное число кодов распутывания. Можно выполнить оставшуюся часть последовательности, чтобы отменить только те части пролога, которые завершили выполнение.
Коды развёртки кодируются в соответствии с таблицей ниже. Все unwind-коды представляют собой одинарный или двойной байт, за исключением того, который выделяет огромный стек (alloc_l
). В общей сложности существует 22 кода раскрутки. Каждый код очистки соответствует ровно одной инструкции в прологе или эпилоге, что делает возможной очистку частично выполненных прологов и эпилогов.
Код разворачивания | Биты и интерпретация |
---|---|
alloc_s |
000xxxxx: выделение небольшого стека размером < 512 (2^5 * 16). |
save_r19r20_x |
001zzzzz: сохранить <x19,x20> пару в [sp-#Z*8]! , предварительно индексированное смещение >= -248 |
save_fplr |
01zzzzzz: сохранить пару <x29,lr> в [sp+#Z*8] , смещение <= 504. |
save_fplr_x |
10zzzzzz: сохранение <x29,lr> пары в [sp-(#Z+1)*8]! , прединдексированное смещение >= -512 |
alloc_m |
11000xxx'xxxxxxxx: выделить большой стек размером < 32K (2^11 * 16, КБ). |
save_regp |
110010xx'xxzzzz: save x(19+#X) pair at [sp+#Z*8] , offset <= 504 |
save_regp_x |
110011xx'xxzzzzzzzz: сохранение пары x(19+#X) в [sp-(#Z+1)*8]! , предварительно индексированного смещения >= -512 |
save_reg |
110100xx'xxzzzzzz: сохранить рег x(19+#X) на [sp+#Z*8] , смещение <= 504 |
save_reg_x |
1101010x'xxxzzzzz: сохранить регистр x(19+#X) по адресу [sp-(#Z+1)*8]! , с предындексированным смещением >= -256 |
save_lrpair |
1101011x'xxzzzzzz: сохранение пары <x(19+2*#X),lr> в [sp+#Z*8] , смещение <= 504 |
save_fregp |
1101100x'xxzzzz: save pair d(8+#X) at [sp+#Z*8] , offset <= 504 |
save_fregp_x |
1101101x'xxzzzzzzzz: сохранить пару d(8+#X) в [sp-(#Z+1)*8]! , предварительно индексированный сдвиг >= -512 |
save_freg |
1101110x'xxzzzzzz: сохранить регистр d(8+#X) по адресу [sp+#Z*8] , смещение <= 504 |
save_freg_x |
11011110'xxxzzzzz: сохранить регистр d(8+#X) по адресу [sp-(#Z+1)*8]! , предварительно индексированное смещение >= -256 |
alloc_z |
11011111'zzzzzzzz: выделить стек размером z * SVE-VL |
alloc_l |
11100000'xxxxxxxx'xxxxxxxx'xxxxxxxx: выделение большого стека размером < 256M (2^24 * 16) |
set_fp |
11100001: настройка x29 с помощью mov x29,sp |
add_fp |
11100010'xxxxxxxx: настройка с помощью x29 add x29,sp,#x*8 |
nop |
11100011: операция раскрутки не требуется. |
end |
11100100: конец кода очистки. Подразумевает ret в эпилоге. |
end_c |
11100101: конец кода завершения действий в текущей цепочке областей видимости. |
save_next |
11100110: сохранить следующую пару регистров. |
save_any_xreg |
11100111'0pxrrrrr'00ooooo: save register(s)
|
save_any_dreg |
11100111'0pxrrrrr'01oooooo: сохранить регистр(ы)
|
save_any_qreg |
11100111'0pxrrrrr'10oooooo: сохранить регистр(ы)
|
save_zreg |
11100111'0oo0rrrr'11oooooo: сохранить регистр Z(#r+8) в [sp + #o * VL] , (Z8 через Z23 ) |
save_preg |
11100111'0oo1rrr'11oooooo: сохраните регистр P(#r) по адресу [sp + #o * (VL / 8)] , (отP4 до P15 ; значения r и [0, 3] зарезервированы) |
11100111'1yyyyyyy': зарезервировано | |
11101xxx: зарезервировано для пользовательских стэков, создаваемых только для ассемблерных подпрограмм. | |
11101000: настраиваемый стек для MSFT_OP_TRAP_FRAME |
|
11101001: настраиваемый стек для MSFT_OP_MACHINE_FRAME |
|
11101010: настраиваемый стек для MSFT_OP_CONTEXT |
|
11101011: настраиваемый стек для MSFT_OP_EC_CONTEXT |
|
11101100: настраиваемый стек для MSFT_OP_CLEAR_UNWOUND_TO_CALL |
|
11101101: зарезервировано | |
11101110: зарезервировано | |
11101111: зарезервировано | |
11110xx: зарезервировано | |
11111000'гггггггг: зарезервировано | |
11111001'yyyyyyyy'yyyyyyyy: зарезервировано | |
11111010'yyyyyyyy'yyyyyyyy'yyyyyyyy: зарезервировано | |
11111011'yyyyyyyy'yyyyyyyy'yyyyyyyy'yyyyyyyy: зарезервировано | |
pac_sign_lr |
11111100: подпишите обратный адрес в lr при помощи pacibsp |
11111101: зарезервировано | |
11111110: зарезервировано | |
11111111: зарезервировано |
В инструкциях с большими значениями, охватывающими несколько байт, старшие биты сохраняются первыми. Данная конструкция позволяет определить общий размер кода развёртки в байтах, обращая внимание только на первый байт кода. Поскольку каждый код очистки однозначно сопоставлен с инструкцией в прологе или эпилоге, можно вычислить размер пролога или эпилога. Пройдите от начала до конца последовательности и используйте таблицу подстановки или аналогичное устройство, чтобы определить длину соответствующего opcode.
Использование адресации со смещением после индексирования не допускается в прологе. Все диапазоны смещения (#Z) соответствуют кодировке stp
/str
адресации, за исключением save_r19r20_x
, где 248 достаточно для всех зон сохранения (10 регистров Int + 8 регистров FP + 8 входных регистров).
save_next
должно идти после сохранения для пары регистров: save_regp
, save_regp_x
, save_fregp
, save_fregp_x
, save_r19r20_x
или другого save_next
. Его также можно использовать в сочетании с save_any_xreg
, save_any_dreg
или save_any_qreg
, но только если p = 1
. Он сохраняет следующую пару регистров в порядке увеличения чисел в следующем пространстве стека.
save_next
не следует использовать за пределами последнего регистра того же типа.
Поскольку размеры стандартных инструкций возврата и перехода одинаковы, в сценариях хвостового вызова не требуется отдельный end
код разворачивания.
end_c
предназначен для управления несмежными фрагментами функций в целях оптимизации. Код end_c
, указывающий на конец кодов размотки в текущей области, должен быть дополнен другой серией кодов размотки, заканчивающейся настоящим end
. Коды развертывания между end_c
и end
представляют собой операции пролога в родительском регионе (так называемый "фантомный" пролог). Дополнительные сведения и примеры описаны в разделе ниже.
Упакованные данные распаковки
Для функций, прологи и эпилоги которых соответствуют описанной ниже канонической форме, можно использовать упакованные данные очистки. Это полностью устраняет необходимость в .xdata
записи и значительно снижает затраты на предоставление данных распаковки. Канонические прологи и эпилоги предназначены для удовлетворения распространенных требований простой функции: одна из которых не требует обработчика исключений, и которая выполняет операции установки и удаления в стандартном порядке.
Формат записи с упакованными данными для распаковки .pdata
выглядит следующим образом:
Поля следующие.
- Function Start RVA (Относительный виртуальный адрес начала функции) представляет собой 32-битовый относительный виртуальный адрес начала функции.
-
Флаг — это 2-разрядное поле, как описано выше, со следующими значениями:
- 00 = упакованные данные раскручивания не используются; оставшиеся биты указывают на
.xdata
запись - 01 = упакованные данные распаковки, используемые с одним прологом и одним эпилогом в начале и в конце секции.
- 10 = упакованные данные развёртки, используемые для кода без пролога и эпилога. Полезно для описания отдельных сегментов функций
- 11 = зарезервировано.
- 00 = упакованные данные раскручивания не используются; оставшиеся биты указывают на
-
Function Length (Длина функции) представляет собой 11-битовое поле, которое предоставляет длину всей функции в байтах, поделенную на 4. Если функция превышает 8 кб, вместо нее необходимо использовать полную
.xdata
запись. -
Frame Size (Размер кадра) представляет собой 9-битовое поле, которое указывает число выделенных для этой функции бит в стеке, поделенное на 16. Функции, которые выделяют более чем (8k-16) байт стека, должны использовать полноценную запись
.xdata
. Он включает в себя локальную переменную, исходящую область параметров, область сохраненного абонентом int и FP, а также область параметров дома. Он исключает динамическую область выделения. -
CR — это 2-разрядный флаг, указывающий, включает ли функция дополнительные инструкции по настройке цепочки кадров и обратной связи:
- 00 = несвязанная функция,
<x29,lr>
пара не сохраняется в стеке - 01 = функция без сцепления,
<lr>
сохранен в стеке - 10 = цепочечная функция с подписанным адресом возврата
pacibsp
- 11 = связанная функция, инструкция использования пары операции хранения или загрузки используется в прологе/эпилоге
<x29,lr>
- 00 = несвязанная функция,
- H — это 1-битовый флаг, указывающий, помещает ли функция целочисленные параметры регистров (x0–x7), сохраняя их в самом начале функции. (0 = не регистрирует дома, 1 = регистры домов).
- RegI представляет собой 4-битовое поле, указывающее число неизменяемых регистров типа INT (x19–x28), сохраненных в каноническом расположении стека.
- RegF представляет собой 3-битовое поле, указывающее число неизменяемых регистров типа FP (d8–d15), сохраненных в каноническом расположении стека. (RegF=0: регистрация FP не сохраняется; RegF 0: сохраняются регистры RegF>+1 FP). Упакованные данные очистки нельзя использовать для функций, которые сохраняют только один регистр типа FP.
Канонические прологи, которые попадают в категории 1, 2 (без области исходящих параметров), 3 и 4 в разделе выше, могут быть представлены в упакованном формате очистки. Эпилоги для канонических функций имеют аналогичную форму, за исключением того, что H не оказывает никакого влияния, инструкция set_fp
опускается, и порядок выполнения шагов и инструкции в каждом шаге в эпилоге является обратным. Алгоритм упакованного .xdata
выполняет следующие действия, подробности указаны в следующей таблице.
Шаг 0. Предварительное вычисление размера каждой области.
Шаг 1: Подпишите адрес возврата.
Шаг 2. Сохранение регистров, сохраненных в вызываемом объекте Int.
Шаг 3. Этот шаг предназначен для типа 4 в ранних разделах.
lr
сохраняется в конце области Int.
Шаг 4. Сохраните регистры, сохраняемые вызываемой функцией (callee-saved registers) FP.
Шаг 5. Сохранение входных аргументов в основной области параметров.
Шаг 6. Выделение оставшегося стека, включая локальную область, <x29,lr>
пару и исходящую область параметров. 6a соответствует каноническому типу 1. 6b и 6c предназначены для канонического типа 2. 6d и 6e предназначены для типа 3 и типа 4.
Номер шага | Значения флагов | Число инструкций | Код операции | Код разворачивания |
---|---|---|---|---|
0 | #intsz = RegI * 8; if (CR==01) #intsz += 8; // lr #fpsz = RegF * 8; if(RegF) #fpsz += 8; #savsz=((#intsz+#fpsz+8*8*H)+0xf)&~0xf) #locsz = #famsz - #savsz |
|||
1 | CR == 10 | 1 | pacibsp |
pac_sign_lr |
2 | 0 <RegI<= 10 |
RegI / 2 + RegI % 2 |
stp x19,x20,[sp,#savsz]! stp x21,x22,[sp,#16] ... |
save_regp_x save_regp ... |
3 | CR == 01* | 1 | str lr,[sp,#(intsz-8)] * |
save_reg |
4 | 0 <RegF<= 7 | (RegF + 1) / 2 + (RegF + 1) % 2) |
stp d8,d9,[sp,#intsz] **stp d10,d11,[sp,#(intsz+16)] ... str d(8+RegF),[sp,#(intsz+fpsz-8)] |
save_fregp ... save_freg |
5 | H == 1 | 4 | stp x0,x1,[sp,#(intsz+fpsz)] stp x2,x3,[sp,#(intsz+fpsz+16)] stp x4,x5,[sp,#(intsz+fpsz+32)] stp x6,x7,[sp,#(intsz+fpsz+48)] |
nop nop nop nop |
6a | (CR == 10 || CR == 11) &>#locsz
<= 512 |
2 | stp x29,lr,[sp,#-locsz]! mov x29,sp *** |
save_fplr_x set_fp |
6b | (CR == 10 || CR == 11) &> 512 < #locsz <= 4080 |
3 | sub sp,sp,#locsz stp x29,lr,[sp,0] add x29,sp,0 |
alloc_m save_fplr set_fp |
6c | (CR == 10 || CR == 11) &>#locsz
> 4080 |
4 | sub sp,sp,4080 sub sp,sp,#(locsz-4080) stp x29,lr,[sp,0] add x29,sp,0 |
alloc_m alloc_s /alloc_m save_fplr set_fp |
6d | (CR == 00 || CR == 01) &>#locsz
<= 4080 |
1 | sub sp,sp,#locsz |
alloc_s /alloc_m |
6e | (CR == 00 || CR == 01) &>#locsz
> 4080 |
2 | sub sp,sp,4080 sub sp,sp,#(locsz-4080) |
alloc_m alloc_s /alloc_m |
* Если CR == 01 и RegI является нечетным числом, шаг 3 и последний save_reg
в шаге 2 объединяются в один save_regp
.
** Если regI == CR == 0 и RegF != 0, первый stp
для с плавающей запятой выполняет предопределение.
В эпилоге отсутствует инструкция, соответствующая mov x29,sp
. Упакованные данные развёртки нельзя использовать, если для функции требуется восстановление sp
из x29
.
Развёртывание частичных прологов и эпилогов
В наиболее распространённых ситуациях размотки исключение или вызов происходит в теле функции, вдали от пролога и всех эпилогов. В таких ситуациях размотка является простой: размотка просто выполняет команды из массива размотки. Он начинается с индекса 0 и продолжается до обнаружения опкода end
.
В случае, когда исключение или прерывание происходит во время выполнения пролога или эпилога, правильное развертывание становится еще более сложной задачей. В таких ситуациях стековый фрейм создается только частично. Проблема заключается в том, чтобы определить, что именно было сделано, чтобы правильно отменить это.
Например, возьмем следующую последовательность пролога и эпилога.
0000: stp x29,lr,[sp,#-256]! // save_fplr_x 256 (pre-indexed store)
0004: stp d8,d9,[sp,#224] // save_fregp 0, 224
0008: stp x19,x20,[sp,#240] // save_regp 0, 240
000c: mov x29,sp // set_fp
...
0100: mov sp,x29 // set_fp
0104: ldp x19,x20,[sp,#240] // save_regp 0, 240
0108: ldp d8,d9,[sp,224] // save_fregp 0, 224
010c: ldp x29,lr,[sp],#256 // save_fplr_x 256 (post-indexed load)
0110: ret lr // end
Рядом с каждым кодом операции находится соответствующий код распаковки, описывающий эту операцию. Как видно, последовательность кодов очистки для пролога является зеркальным отображением кодов очистки для эпилога (без учета последней инструкции эпилога). Это распространенная ситуация: поэтому мы всегда предполагаем, что коды раскрутки для пролога хранятся в обратном порядке по сравнению с порядком выполнения пролога.
Итак, как для пролога, так и для эпилога, у нас есть общий набор кодов раскрутки.
set_fp
, , save_regp 0,240
save_fregp,0,224
, save_fplr_x_256
end
Ситуация с эпилогом гораздо проще, так как действия выполняются в обычном порядке. Начиная с смещения 0 в эпилоге (который начинается с смещения 0x100 в функции), мы ожидаем, что полная последовательность отката выполнится, так как никакие операции по очистке еще не произведены. Если мы окажемся в одной инструкции в (по смещению 2 в эпилоге), мы можем успешно развернуться, пропуская первый код раскрутки. Эту ситуацию можно обобщить и предположить соответствие 1:1 между операционными кодами и кодами сворачивания. Затем, чтобы начать очистку с инструкции n в эпилоге, следует пропустить первые n кодов очистки и начать выполнение оттуда.
Аналогичная логика работает для пролога, но в обратном порядке. Если мы начинаем откат с нулевого смещения в прологе, не нужно выполнять никаких действий. Если мы выполним отмену сдвига начиная с 2, то есть начиная с одной инструкции, то хотим начать выполнение последовательности отмены начиная с одного кода отмены до конца. (Помните, что коды хранятся в обратном порядке.) И здесь мы также можем обобщить: если мы начинаем очистку из инструкции n в прологе, мы должны начать выполнять n коды очистки с конца списка кодов.
Коды пролога и эпилога не всегда совпадают точно, поэтому массив развёртки может содержать несколько последовательностей кодов. Чтобы определить, с какого места начинать обработку кодов, используйте следующую логику.
При развёртке внутри функции начинайте выполнять коды разворачивания с индекса 0 и продолжайте, пока не дойдете до
end
опкода.Если разгрузка осуществляется из эпилога, используйте в качестве отправной точки начальный индекс, характерный для эпилога, указанный в области эпилога. Вычислите, сколько байт насчитывает PC от начала эпилога. Затем продвигайтесь вперед по кодам раскрутки, пропуская коды, пока все уже выполненные инструкции не будут учтены. Затем выполните действия, начиная с этой точки.
Если разворачивание осуществляется из пролога, используйте индекс 0 в качестве отправной точки. Вычислите длину кода пролога из последовательности, а затем рассчитайте, на сколько байт PC отстоит от конца пролога. Затем продолжайте по кодам раскрутки, пропуская их до тех пор, пока не будут учтены все еще не выполненные инструкции. Затем выполните действия, начиная с этой точки.
Эти правила означают, что коды распаковки для пролога должны всегда быть первыми в массиве. И именно эти коды используются для очистки в общем случае, когда очистка осуществляется из тела. Сразу после последовательности кода для пролога должны идти специальные последовательности для эпилога.
Фрагменты функции
Для целей оптимизации кода и других причин может потребоваться разделить функцию на отдельные фрагменты (также называемые регионами). При разбиении каждый результирующий фрагмент функции требует отдельной записи .pdata
(и, возможно, .xdata
).
Если у разделенного вторичного фрагмента есть собственный пролог, выполнять подстройку стека в этом прологе не следует. Все пространство стека, необходимое вторичному региону, должно быть предварительно выделено его родительским регионом (или главным регионом). Это предварительное размещение позволяет строго манипулировать указателем стека в исходном прологе функции.
Типичным случаем фрагментов функции является "разделение кода", где компилятор может переместить регион кода из его основной функции. Существует три необычных случая, которые могут привести к разделению кода.
Пример
(регион 1: начало)
stp x29,lr,[sp,#-256]! // save_fplr_x 256 (pre-indexed store) stp x19,x20,[sp,#240] // save_regp 0, 240 mov x29,sp // set_fp ...
(регион 1: конец)
(регион 3: начало)
...
(регион 3: конец)
(регион 2: начало)
... mov sp,x29 // set_fp ldp x19,x20,[sp,#240] // save_regp 0, 240 ldp x29,lr,[sp],#256 // save_fplr_x 256 (post-indexed load) ret lr // end
(регион 2: завершение)
Только пролог (регион 1: все эпилоги находятся в отдельных регионах):
Необходимо описать только пролог. Этот пролог не может быть представлен в компактном
.pdata
формате. В полном.xdata
случае его можно представить, задав значение Epilog Count = 0. См. регион 1 в примере выше.Коды раскрутки:
set_fp
,save_regp 0,240
,save_fplr_x_256
,end
.Только эпилоги (регион 2: пролог находится в хост регионе)
Предполагается, что к моменту, когда поток выполнения переходит в этот регион, весь код пролога был выполнен. Частичная очистка может выполняться в эпилогах так же, как в обычной функции. Этот тип региона не может быть представлен компактным
.pdata
. В полной.xdata
записи его можно закодировать с помощью "фантомного" пролога, обрамленного парой кодаend_c
иend
отмены. Начальныйend_c
указывает, что размер пролога равен нулю. Начальный индекс эпилога для одного эпилога указывает наset_fp
.Код развёртки для региона 2:
end_c
,set_fp
,save_regp 0,240
,save_fplr_x_256
,end
.Нет прологов или эпилогов (регион 3: прологи все эпилоги находятся в других фрагментах):
Компактный
.pdata
формат можно применить с помощью параметра Flag = 10. С полной.xdata
записью счетчик эпилога = 1. Код раскрутки совпадает с кодом для региона 2 выше, но индекс начала эпилога также указывает наend_c
. В этом регионе кода никогда не будет выполняться частичная очистка.
Еще одним более сложным случаем фрагментов функций является "shrink wrapping". Компилятор может отложить сохранение некоторых регистров, сохраняемых вызываемым, до тех пор, пока не выйдет за пределы входного пролога функции.
(регион 1: начало)
stp x29,lr,[sp,#-256]! // save_fplr_x 256 (pre-indexed store) stp x19,x20,[sp,#240] // save_regp 0, 240 mov x29,sp // set_fp ...
(регион 2: начало)
stp x21,x22,[sp,#224] // save_regp 2, 224 ... ldp x21,x22,[sp,#224] // save_regp 2, 224
(регион 2: завершение)
... mov sp,x29 // set_fp ldp x19,x20,[sp,#240] // save_regp 0, 240 ldp x29,lr,[sp],#256 // save_fplr_x 256 (post-indexed load) ret lr // end
(регион 1: конец)
В прологе региона 1 пространство стека выделено заранее. Вы можете видеть, что в регионе 2 будет использоваться тот же код раскрутки стека, даже если он будет перемещён из своей исходной функции.
Регион 1: set_fp
, save_regp 0,240
, save_fplr_x_256
end
. Начальный индекс эпилога указывает на set_fp
, как обычно.
Регион 2: save_regp 2, 224
, end_c
, set_fp
, save_regp 0,240
, save_fplr_x_256
, end
. Индекс начала Epilog указывает на первый код развёртки save_regp 2, 224
.
Большие функции
Фрагменты можно использовать для описания функций, превышающих ограничение 1M, введенное битовыми полями в заголовке .xdata
. Чтобы описать такую необычно большую функцию, её необходимо разбить на фрагменты меньше 1М. Для каждого фрагмента требуется подстройка, чтобы он не разбивал эпилог на множество частей.
Только первый фрагмент функции будет содержать пролог, все остальные фрагменты помечаются как не содержащие пролог. В зависимости от имеющегося числа эпилогов каждый фрагмент может содержать от нуля до нескольких эпилогов. Помните, что область каждого эпилога во фрагменте указывает начальное смещение относительно начала данного фрагмента, а не начала функции.
Если фрагмент не имеет пролога и эпилога, он по-прежнему требует собственной записи .pdata
(и, возможно, .xdata
), чтобы описать, как произвести выход из тела функции.
Примеры
Пример 1. Обрамленная, компактная форма
|Foo| PROC
|$LN19|
str x19,[sp,#-0x10]! // save_reg_x
sub sp,sp,#0x810 // alloc_m
stp fp,lr,[sp] // save_fplr
mov fp,sp // set_fp
// end of prolog
...
|$pdata$Foo|
DCD imagerel |$LN19|
DCD 0x416101ed
;Flags[SingleProEpi] functionLength[492] RegF[0] RegI[1] H[0] frameChainReturn[Chained] frameSize[2080]
Пример 2. Цепочка кадров с полной формой, с зеркальным прологом и эпилогом
|Bar| PROC
|$LN19|
stp x19,x20,[sp,#-0x10]! // save_regp_x
stp fp,lr,[sp,#-0x90]! // save_fplr_x
mov fp,sp // set_fp
// end of prolog
...
// begin of epilog, a mirror sequence of Prolog
mov sp,fp
ldp fp,lr,[sp],#0x90
ldp x19,x20,[sp],#0x10
ret lr
|$pdata$Bar|
DCD imagerel |$LN19|
DCD imagerel |$unwind$cse2|
|$unwind$Bar|
DCD 0x1040003d
DCD 0x1000038
DCD 0xe42291e1
DCD 0xe42291e1
;Code Words[2], Epilog Count[1], E[0], X[0], Function Length[6660]
;Epilog Start Index[0], Epilog Start Offset[56]
;set_fp
;save_fplr_x
;save_r19r20_x
;end
Индекс начала Эпилога [0] указывает на ту же последовательность кода развертки Пролога.
Пример 3. Вариадинская несоединяемая функция
|Delegate| PROC
|$LN4|
sub sp,sp,#0x50
stp x19,lr,[sp]
stp x0,x1,[sp,#0x10] // save incoming register to home area
stp x2,x3,[sp,#0x20] // ...
stp x4,x5,[sp,#0x30]
stp x6,x7,[sp,#0x40] // end of prolog
...
ldp x19,lr,[sp] // beginning of epilog
add sp,sp,#0x50
ret lr
AREA |.pdata|, PDATA
|$pdata$Delegate|
DCD imagerel |$LN4|
DCD imagerel |$unwind$Delegate|
AREA |.xdata|, DATA
|$unwind$Delegate|
DCD 0x18400012
DCD 0x200000f
DCD 0xe3e3e3e3
DCD 0xe40500d6
DCD 0xe40500d6
;Code Words[3], Epilog Count[1], E[0], X[0], Function Length[18]
;Epilog Start Index[4], Epilog Start Offset[15]
;nop // nop for saving in home area
;nop // ditto
;nop // ditto
;nop // ditto
;save_lrpair
;alloc_s
;end
Индекс начала эпилога [4] указывает на середину кода развертывания пролога (частичное повторное использование массива развертывания).
См. также
Общие сведения о соглашениях ABI ARM64
Обработка исключений в ARM