Вот пример разбора ошибки в своей собственной программе на Си, с использованием хидера, который я предлагал http://depositfiles.com/files/mommloixs может так легче кому будет понять Код (Text): //***************вывод отладочной информации об ошибке*** LONG DebugInformator(PEXCEPTION_POINTERS p_excep) { //Здесь мы можем работать с фреймами о которых говорил Clerk PEXCEPTION_RECORD p_excep_record = p_excep->ExceptionRecord; char sz_msg[512]; sprintf(sz_msg,"Error in Address:0x%08X\n",p_excep_record->ExceptionAddress); //.....Здесь еще много чего из фреймов полезного извлечь можно, // а потом вывести ч/з чего - нибудь, например MessageBox (кстати он может не //работать при некоторых ошибках) или DebugPrint или лог в файл LPVOID lpMsgBuf; FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, NULL,RtlNtStatusToDosError(p_excep_record->ExceptionCode), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR) &lpMsgBuf,0,NULL); strcat(sz_msg,NTSTATUS_TO_DESCRIPTION(p_excep_record->ExceptionCode)); strcat(sz_msg,"\n"); strcat(sz_msg,(LPCSTR)lpMsgBuf); LocalFree(lpMsgBuf); MessageBoxA(GetActiveWindow(),sz_msg,"ERROR",MB_ICONSTOP | MB_SYSTEMMODAL); //здесь можно принять решение что делать дальше: return EXCEPTION_CONTINUE_EXECUTION; //или return EXCEPTION_EXECUTE_HANDLER; //или return EXCEPTION_CONTINUE_SEARCH; } /*================================== В результате получим, в зависимости от ошибки сообщение типа: Error in Address: 0xFFA...... RPC_NT_SEND_INCOMPLETE В буфере запроса остались данные для отправки. */=================================== //*************в вашем коде*********************** __try { //Здесь помещается критический код (а лучше весь). return STATUS_SUCCESS; //например, возврат если все ОК, если возникает исключение // то обработка в DebugInformator } __except (DebugInformator(_exception_info()))
Если ты о sz_msg -то она выделяется динамически из стека, и освобождать ее не надо (выделять из стека не есть гуд, поэтому этот код только демонстрационный,естественно нуждающийся в доработке). Если о фреймах с отладочной информацией - их заполняет ядро, мы лишь получаем указатели на них. Освобождать их не нужно.
Есть ли смысл полностью отказаться от SEH'а в пользу перехвата KiUserExceptionDispatcher в целях повышения быстродействия кода?
DillerInc Только если ваш программный продукт использует seh настолько часто, что проседает производительность, например если реализована и часто используется самотрассировка.
DillerInc Да, но это должно быть очень специфическое приложение. Например возврат из иключения обычно выполняют возвращая из сех значение, после которого последует перезагрузка контекста в процессор посредством сервиса NtContinue. Это требует значительное время, поэтому оптимизировать можно вручную возвращая управление, например я в большинстве случаев использую это: Код (Text): SEH_PROLOG macro Local Delta1, Delta2 assume fs:nothing push ebp Call Delta1 Delta1: add dword ptr [esp],(ExceptionExit - Delta1) Call Delta2 Delta2: add dword ptr [esp],(ExceptionHandler - Delta2) push dword ptr fs:[TEB.Tib.ExceptionList] mov dword ptr fs:[TEB.Tib.ExceptionList],esp endm SEH_EPILOG macro ; clc ExceptionExit: pop dword ptr fs:[TEB.Tib.ExceptionList] lea esp,[esp + 4*3] endm ExceptionHandler proc C mov esp,dword ptr [esp + 8] ;(esp) -> ExceptionList xor eax,eax mov ebp,dword ptr [esp + 4*3] ; stc jmp dword ptr [esp + 4*2] ExceptionHandler endp Можно восстановить вручную также состояние процессора. Касательно VEH - изза входа в критическую секцию два треда не могут обрабатывать исключение в нём. Своя реализация сех будет более быстрой, но менее гибкой.
TSS, да,это можно назвать самотрассировкой - передача управления в процедуру протектора и трассировка этой процедуры. Clerk, спасибо за код. В процедуре ExceptionHandler "xor eax, eax" - это EXCEPTION_CONTINUE_EXECUTION?
DillerInc это не может быть EXCEPTION_CONTINUE_EXECUTION, потому что EXCEPTION_CONTINUE_EXECUTION (== -1) относится к чуть более высокоуровневой системе обработки исключений. точнее, это значение может вернуть фильтр исключений, вызываемый из предоставляемой окружением поддерживающих try\except компиляторов функции _except_handler3\_except_handler4. а вот уже _except_handler3\4 после этого вернёт системе ExceptionContinueExecution (== 0). ...ну это так, на всякий случай. чтобы не было путаницы между механизмами, предоставляемыми компиляторами и системой =) в xp sp2. в sp3 должны были (?) уже починить это недоразумение. в висте, например, VEH\VCH вызывается вне критической секции (и вне SRWLock).
catwalk_mission Это не недоразуменее, это аналогично и в XP, в 7 и висте. Иначе быть не может - сам список защищён критической секцией(RtlpCalloutEntryLock), иначе один тред могбы устанавливать хэндлер, другой использовать текущий, что привелобы к рассинхронизации и краху.
je_ Мерить ничего не нужно, задержка очевидна: - NtContinue(FastCall) - смена CPL. - KiServiceExit(FastExit) - вторая смена CPL. - KeContextToKframes(). Весь код занимает тысячи инструкций, это очень долго. Впрочем измерю, интересна задержка.
je_ 1. Отношение. Код (Text): .code CPU1_MASK equ 01B WAIT_SECONDS equ 4 UsTickCountLow equ 7FFE0000H UsTickCountMultiplier equ 7FFE0004H GET_TICK_COUNT macro mov eax,ds:[UsTickCountLow] mul dword ptr ds:[UsTickCountMultiplier] shrd eax,edx,18h endm WAIT_NEXT_TICK macro mov edx,ds:[UsTickCountLow] @@: mov eax,ds:[UsTickCountLow] cmp eax,edx je @b endm BREAKERR macro .if Eax int 3 .endif endm CalculateContinueCalls proc Local Context:CONTEXT Local CallCount:ULONG mov Context.ContextFlags,CONTEXT_ALL or CONTEXT_EXTENDED_REGISTERS invoke ZwGetContextThread, NtCurrentThread, addr Context BREAKERR mov Context.regEsp,Esp mov Context.regEip,offset Continue mov CallCount,eax WAIT_NEXT_TICK GET_TICK_COUNT add eax,1000*WAIT_SECONDS ; Sec. mov Context.regEsi,eax NextCall: invoke ZwContinue, addr Context, FALSE Continue: inc CallCount GET_TICK_COUNT cmp eax,esi jb NextCall mov ebx,CallCount ret CalculateContinueCalls endp CalculateIdleCalls proc Local CallCount:ULONG WAIT_NEXT_TICK GET_TICK_COUNT mov CallCount,0 lea esi,[eax + 1000*WAIT_SECONDS] ; Sec. @@: inc CallCount GET_TICK_COUNT cmp eax,esi jb @b mov eax,CallCount ret CalculateIdleCalls endp .data Buffer ULONG 100 DUP (?) .code Entry proc Local Affinity:ULONG Local Loops:ULONG cld lea edi,Buffer mov Affinity,CPU1_MASK mov Loops,10 invoke ZwSetInformationProcess, NtCurrentProcess, ProcessAffinityMask, addr Affinity, 4 BREAKERR invoke ZwSetInformationThread, NtCurrentThread, ThreadAffinityMask, addr Affinity, 4 BREAKERR @@: invoke CalculateContinueCalls invoke CalculateIdleCalls xor edx,edx div ebx dec Loops stosd jnz @b int 3 ret Entry endp Результат на P4-3014/XPSP3: Код (Text): 00403000 0000020B 00403004 00000211 00403008 000001EF 0040300C 000001D4 00403010 000001C2 00403014 000001AF 00403018 00000170 0040301C 0000021C 00403020 0000021D 00403024 0000021D Дабы результаты были стабильными используется большое время измерения(изза планирования). С NtContinue цикл выполняется примерно в ~400 раз медленее, чем без него. 2. Величина задержки. Код (Text): @@: invoke CalculateContinueCalls mov eax,ebx dec Loops stosd jnz @b int 3 Результат: Код (Text): 00403000 00269684 00403004 00266973 00403008 0026BF06 0040300C 0026ADBF 00403010 0026C841 00403014 0026CBA9 00403018 0026B79C 0040301C 0026CF90 00403020 0026D2AB 00403024 0026D24F Примерно ~640.000 итераций в секунду. Холостыми циклами можно пренебречь(1/400), получаем время затрачиваемое на вызов сервиса ~1,56 микросекунд. Это много(Idle циклов в секунду ~ 350.000.000 при высоком приоритете).
Диспетчер исключений, захват модификацией смещения в инструкции Call RtlDispatchException(код в #29): Код (Text): CPU1_MASK equ 01B WAIT_SECONDS equ 4 UsTickCountLow equ 7FFE0000H UsTickCountMultiplier equ 7FFE0004H GET_TICK_COUNT macro mov eax,ds:[UsTickCountLow] mul dword ptr ds:[UsTickCountMultiplier] shrd eax,edx,18h endm WAIT_NEXT_TICK macro mov edx,ds:[UsTickCountLow] @@: cmp ds:[UsTickCountLow],edx je @b endm BREAKERR macro .if Eax int 3 .endif endm .data KiDispatchReference PVOID offset RtlDispatchException ExceptionCounter ULONG ? .code include Dump.inc RtlDispatchException proc C ExceptionRecord:PEXCEPTION_RECORD, ContextRecord:PCONTEXT mov eax,ContextRecord mov esp,CONTEXT.regEsp[eax] inc ExceptionCounter GET_TICK_COUNT cmp eax,esi jnb @f ; ret ; Для возврата посредством NtContinue cli ; Для возврата вручную. @@: int 3 RtlDispatchException endp Entry proc Local Affinity:ULONG Local Loops:ULONG mov Affinity,CPU1_MASK invoke ZwSetInformationProcess, NtCurrentProcess, ProcessAffinityMask, addr Affinity, 4 BREAKERR invoke ZwSetInformationThread, NtCurrentThread, ThreadAffinityMask, addr Affinity, 4 BREAKERR push offset KiDispatchReference Call PiEntry BREAKERR WAIT_NEXT_TICK GET_TICK_COUNT lea esi,[eax + 1000*WAIT_SECONDS] ; Sec. cli Entry endp Результат - весьма стабильно вызывается, ~360.000//200.000 раз в секунду для исключения при исполнении привилегированной инструкции(второй результат при использовании NtContinue). Тоесть если вручную передавать управление то в общем это быстрее в 1.8 раза. Время от генерации исключения до возврата на юзермодный диспетчер для предыдущего типа исключений примерно 2.75 микросекунды. Задержка завивит от типа исключения, теоретически самой долгой будет при страничных нарушениях. Если у процесса имеется отладочный порт(например под олей запущен), то очень медленно вызывается диспетчер но стабильно, всего ~440 раз в секунду, тоесть в 800 раз медленнее происходит обработка исключений, можно использовать кстати для определения наличия отладочного порта(подобное вероятно можно использовать для обнаружения ядерного дебуггера из юзермода..).
у меня такой резултЪ полный SEH (с INT/IRETD) на 0800h-0900h тиков длиннее. .не стоит мороки полный SEH квалифицированно восстонавливает всё. прямой выход из SEH следует считать "узко-профильным".
а если ставить 010003 флаг в context, (только Reg/EIP) то снизим различие к 0400 тикам. (INT/IRETD = не-SYSENTER/SYSEXIT)