Разбирался с exception handling под х64... Есть некий stub, на асме. Он дергает функцию. Надо что бы стаб ловил exception'ы которые происходят в функции и "ниже". Попытка 1. Для стаба не генерится инфа о unwind, но потом в рантайме добавляется через RtlAddFunctionTable. Стаб: Код (Text): ExecuteGuard PROC push rbp call rcx pop rbp ret ExecuteGuard ENDP Exception handler никогда не вызывается, как бы я не добавлял нужные мне структурки. RtlAddFunctionTable ошибок не возвращает. На только что добавленную область RtlLookupFunctionEntry выдает NULL - структурки нету. Параметры перепроверялись н-цать раз. Из мыслей - если для процесса с его imagebase уже есть табличка, оно не добавит еще один элемент. Т.е. работает RtlAddFunctionTable только и только для случаев, когда действительно динамический код и он не принадлежит процессу/dll. Убился день. Попытка 2. Сгенерил для стаба unwind info через макросы. Код (Text): ExecuteGuard PROC FRAME : ExceptionGuardHandler push rbp .pushreg rbp .endprolog xor eax,eax div eax pop rbp ret ExecuteGuard ENDP Ура, exception handler вызывается! Меняю код на: Код (Text): ExecuteGuard PROC FRAME : ExceptionGuardHandler push rbp .pushreg rbp .endprolog call ecx pop rbp ret ExecuteGuard ENDP Вызывается функция которая передана в первом параметре, оная кидает exception (тоже division by zero). Мой exception handler не вызывается. Как ни крутил параметры unwind - не вызывается и все тут. Взял windbg, словил exception, проверил exception handler list - мой хендлер первый в списке. Начал дебагать ntdll.dll от KiUserExceptionDispatch. И вот что нашел... RtlVirtualUnwind, который "отматывает" стек делает это таким образом: 1. По rip ищет структурку которая отвечает за функцию 2. Ищет "эпилог" функции. А именно выход. А именно 0xC2/0xC3. Прямо хардкодом. Паралельно пропускает (и эмулирует) опкоды работы со стеком. Так вот, мне влом было вникать, но оказалось, что если rip при выходе из функции (в моем случае это на pop rbp) указывает на эпилог (тоже pop rbp), то exception handler вызван не будет. Исправилось таким образом: Код (Text): ExecuteGuard PROC FRAME : ExceptionGuardHandler push rbp .pushreg rbp .endprolog call ecx nop pop rbp ret ExecuteGuard ENDP Второй день был потерян. Выводы: х64 exception handling, может быть, и хорошая идея. Но реализация - ни в какие ворота. Начиная с того, что документации почти ноль - постоянные отсылы к производителям процов, к calling convention, etc. Calling convention в свою очередь ничего про exception'ны не говорит. И заканчивая тем, что нигде никаких ошибок не происходит, что реально усложняет дебаг и просто напросто сбивает с толку. Да и поиск конца функции по опкодам это еще та мега-технология.. P.S. Если у кого получилось заставить работать RtlAddFunctionTable для функции внутри модуля, но без структурки в .pdata - скажите как...
dermatolog От раскрутки по стеку решили отказаться, во-первых, по соображениям надёжности и безопасности: слишком легко его запортить, в т.ч. и сознательно. Во-вторых, новая модель практически полностью устраняет накладные расходы на __try/__except в коде ф-ции (теперь exception frame не строится при каждом входе в ф-цию). Правда, стоимость раскрутки(unwind) значительно повышается, но это не имеет значения при правильном использовании исключений.
green Со всем согласен, только есть одно НО - помимо автоматической генерации кода МС-ными компиляторами есть еще необходимость в "ручной" установке своих обработчиков. Простого и удобного механизма сейчас просто нет - приходится все делать на уровне Execption Table (причем если это делается не средствами компилятора, то это полный пипец )).
Идея, действительно хорошая. Они целиком и полностью избавились от runtime расходов на установку handler'ов. В результате, если раньше для игр (например), делательно было выключать exception handling, т.к. чуть быстрее работало, то теперь не имеет значения. Они часть макро ассмеблера. В каждом PE-32+ есть новая секция, звется .pdata. В ней лежат структурки которые были сгенерены компилятором и которые "покрывают" код. Собственно директива компилятора FRAME: Код (Text): MyFunc PROC FRAME MyFunc ENDP и генерит нужные структурки в этой самой секции, при линковке. А вот эти макросы нужны что бы система (а именно RtlVirtualUnwind) смогла "отмотать" стек, в случае exception. В целом: + Нет накладных расходов на установку обработчиков во время исполнения + Более "четкая" система unwind'а стека при exception, чем сильно упростили процесс stack walking'a. - Мало внятной документации - Баги реализации (или фичи, только никто об этом не знает)? - Вообще весь код должен быть "покрыт" такими структурками. Если exception происходит в коде, который не "покрыт", приложение тихо мирно упадет (в случае если не стоит UnhandledExceptionFilter). Скажем для кода на плюсах, размер структурки около 2-3% размера файла.
Joes Если честно, то я не понял эту фразу. В UNWIND_INFO описан размер пролога (зеркальная копия эпилога). На кой еще что-то парсить до RET? И сразу вопрос - а если у меня концом функции является JMP - то всё, приехали?
В смысле с сообщением Обработчики в цепочке не вызовутся, только UnhandledExceptionHandler или VectoredExceptionHandler Пример: 1. Хацкерская DLL у которой некая функция ее API "непокрыта" структуркой. 2. Дергаем функцию, там exception, приложение валится и ничего не поможет кроме UnhandledExceptionHandler. P.S. Это в случае если функция не "leaf". Leaf функции не могут модифицировать стек и не могут вызывать другие функции. В смысле - у них первое значение в стеке - адрес возврата.
Я тоже думал. Не придумал Возможно связано с тем, что в эпилоге не всегда делается так: Код (Text): lea rsp, [rbp] или аналогично, а стек восстанавливать надо. Кстати, вот ссылка. http://msdn2.microsoft.com/en-us/library/tawsa7cb.aspx И чего они пишут: В моем случае два варианта: 1. Я не сделал "правильный" пролог (не было add RSP, xx), но почему оно проигнорировало exception handler? 2. Таки баг с адресом выхода (дебаг, вроде бы, подтвердил) Забыл добавить сразу: вышестоящий SEH handler вызывался. Т.е. такой вот код: Код (Text): void test() { __try { ExecuteGuard(someTest); } __except(EXCEPTION_EXECUTE_HANDLER) { printf("Caught exception.\n"); } } Печатал "Caught exception.". Мой exception handler не вызывался вообще. Пока не добавил nop. Если без REX префикса 0x48 - считает локальным прыжком. Т.е. не выходом из функции и скипает. Если с префиксом - выходом.
Joes В "Calling convention" написано, что вызывающая функция обязана зарезервировать в стеке 4*8 байтов. Поэтому "sub rsp, 20" ИМХО является минимальным набором для пролога. Опять же раскрутить пролог в обратном направлении никаких проблем не вызывает - если мы сделали SUB RSP, то в эпилоге ВСЕГДА делаем ADD: Код (Text): 00000000004011C0 53 push rbx 00000000004011C1 4883EC20 sub rsp, 20 ... 000000000040124A 4883C420 add rsp, 20 000000000040124E 5B pop rbx 000000000040124F C3 ret Не - это точно муть какая-то ) Каво там скипать если в RUNTIME_FUNCTION жестко заданы границы функции.
Дык, пофиг: выделял, не выделял - побоку. Если нет nop, то даже в твоем примере exception handler не вызовется. В смысле: Код (Text): ExecuteGuard PROC FRAME : ExceptionGuardHandler push rbp .pushreg rbp sub rsp, 20 .allocstack 20 .endprolog call ecx add rsp, 20 pop rbp ret ExecuteGuard ENDP Не работает А если несколько точек выхода? Код (Text): push rbx sub rsp, 20 ; Тут закончился пролог и идет код функции уже push rcx call someFunc jmp quit add rsp, 20 pop rbx ret quit: pop rcx add rsp, 20 pop rbx ret Разве в данном случае jmp является эпилогом? Если его не обработать, оно не выкинет rcx из стека и в результате оно все поломается.. Вобщем, еще та технология-ниппель.
Joes Код (Text): push rcx Такого не будет 100%!!! UNWIND_INFO однозначно определяет глубину стека для ВСЕЙ функции, т.к. при возникновении ошибки в someFunc ты потом хрен раскрутишь стек до функции, которая вызвала твой код. Поэтому все манипуляции со стеком делаются ТОЛЬКО в прологе и эпилоге.
Увы, может быть и будет. Тебе ж надо дергать функции, а они кушают параметры, а не все параметры передаются через регистры Да и в MSDN написано что может.
Это ж где она документирована? Я не нашел, но может плохо искал :-\ Кроме того, почему ж оно не должно вызваться, если я точно знаю что мне надо обработать exception... Причем я проверил, у меня стоит и EHANDLER и UHANDLER (exception и unwind соответственно).
мсдн поковырял: Unwind Helpers for MASM MASM Macros Ну и намутили же они "вспомогательных" средств...
Joes Видимо, причина в том, что x64 EH заточен под ЯВУ, в которых пролог/эпилог на уровне языка не контроллируется.