Article: Структурная обработка BSoD'а Author: Great ICQ #: 893-894 Date: 03.03.2007 Lang: C/C++ kernel & user mode Трудно отлаживать драйвера ядра в Windows отчасти потому, что большинство ошибок ведет к краху системы, и даже отладчик ядра с этим мало что может сделать. Если мы попробуем, например, скопировать одну строку в другую, не выделив места под нее, нас не спасет даже обработка исключений - в любом случае будет синий экран смерти UNEXPECTED_KERNEL_MODE_TRAP из-за возникшей ловушки General Protection Fault. В одной из предыдущих статей я расписал, как можно заставить систему игнорировать ошибки. Соглашусь, что этот метод весьма "кривой", потому что не позволяет даже обработать ее как-либо. Мы можем только наблюдать окошко с ошибкой. В этот раз я предложу более удобный метод. Попробуем сделать обработку BSoD такой же, как и, например, обработку исключений. То есть можно поставить свой обработчик на BSoD, который на некотором участке небезопасного кода будет отслеживать возникновение ошибки. Если возникла ошибка, которую он обрабатывает, то он производит соответствующие действия, а если нет - передает управление на другие обработчики. Если никакие обработчики не обработали ошибку, показывается синий экран смерти. Метод в точности копирует структурную обработку исключений (Structured Exception Handling, SEH). Точно так же выбирается участок кода, внутри которого все ошибки будут ловиться нашими обработчиками. Со стороны программиста-пользователя, это будет выглядеть так: PBUGCHECK_FILTER SetUnhandledBugCheckFilter(PBUGCHECK_FILTER NewFilter, BOOLEAN bSet) Эта функция добавляет на вершину стека обработчиков или удаляет из стека указанный обработчик (действие задается флагом bSet). При добавлении она возвращает обработчик, который был на вершине стека до этого. NewFilter указывает на новый обработчик - stdcall-функцию, которая принимает параметр BUGCHECK_INFO* и возвращает ULONG. Структура BUGCHECK_INFO содержит параметры синего экрана и описывается следующим образом: Code: typedef struct _BUGCHECK_INFO { ULONG BugCheckCode; ULONG BugCheckParameters[4]; } BUGCHECK_INFO, *PBUGCHECK_INFO; Возвращаемое значение фильтра может быть либо BUGCHECK_CONTINUE_SEARCH (0) для продолжения поиска подходящего фильтра, либо BUGCHECK_EXECUTE_HANDLER (1) для прекращения поиска. Если фильтр возвращает BUGCHECK_EXECUTE_HANDLER, генерируется специальное исключение с кодом STATUS_BUGCHECK (0xC0FF0001), которое может быть обработано обычным образом через SEH. Таким образом, возникновение фатальной ошибки сводится к генерации исключения. Рассмотрим пример. Напишем два фильтра: Code: ULONG NTAPI UnhandledBugcheckFilter2(PBUGCHECK_INFO BugcheckInfo) { DPRINT("In UnhandledBugcheckFilter2()"); return BUGCHECK_CONTINUE_SEARCH; } ULONG NTAPI UnhandledBugcheckFilter(PBUGCHECK_INFO BugcheckInfo) { DPRINT("In UnhandledBugcheckFilter()"); if( BugcheckInfo->BugCheckCode == MANUALLY_INITIATED_CRASH ) return BUGCHECK_EXECUTE_HANDLER; return BUGCHECK_CONTINUE_SEARCH; } UnhandledBugcheckFilter2 всегда возвращает BUGCHECK_CONTINUE_SEARCH, инициируя продолждение поиска фильтра, а UnhandledBugcheckFilter возвращает BUGCHECK_EXECUTE_HANDLER, когда багчек-код равен MANUALLY_INITIATED_CRASH. Таким образом, если мы выполним такой код: Code: SetUnhandledBugCheckFilter( UnhandledBugcheckFilter, TRUE ); SetUnhandledBugCheckFilter( UnhandledBugcheckFilter2, TRUE ); произойдет следующее - при возникновении ошибки с кодом MANUALLY_INITIATED_CRASH сгенерируется исключение STATUS_BUGCHECK, при всех остальных ошибках вызовется синий экран как обычно. Попробуем перехватить это исключение: Code: __try { DPRINT("Trying..."); KeBugCheckEx( MANUALLY_INITIATED_CRASH, 0, 0, 0, 0 ); DPRINT("Unsuccessful =("); } __except(EXCEPTION_EXECUTE_HANDLER) { // We should catch STATUS_BUGCHECK DPRINT("Catch: %08x", GetExceptionCode()); } Таким образом, при вызове KeBugCheckEx( MANUALLY_INITIATED_CRASH, 0, 0, 0, 0 ) на самом деле не будет никакого бсода, сработают установленные фильтры, сгенерируется исключение STATUS_BUGCHECK и сработает обработчик __except. Как видно из отладочного вывода DbgPrint, все успешно сработало. Фильтр UnhandledBugcheckFilter2 пропустил бсод, а UnhandledBugcheckFilter заблокировал его. Таким образом, получается удобный механизм для отладки и выполнения заведомо небезопасных частей кода, которые могут повлечь генерацию BSoD'а. Реализация. С технической точки зрения, тут все просто. Ставим сплайсингом хук на KeBugCheckEx (Windows 2000) или KeBugCheck2 (Windows XP/2k3), а в обработчике перебираем стек фильтров. Примерная реализация выглядит так: Code: typedef struct _BUGCHECK_INFO { ULONG BugCheckCode; ULONG BugCheckParameters[4]; } BUGCHECK_INFO, *PBUGCHECK_INFO; #define BUGCHECK_CONTINUE_SEARCH 0 #define BUGCHECK_EXECUTE_HANDLER 1 #define MAX_BUGCHECK_FILTERS 1024 #define STATUS_BUGCHECK 0xC0FF0001L typedef ULONG (NTAPI *PBUGCHECK_FILTER)(PBUGCHECK_INFO); PBUGCHECK_FILTER BugCheckFilters[MAX_BUGCHECK_FILTERS] = {0}; ULONG CurrentBugCheckFilter = 0; // // Main bug check processing routine // void KiProcessBugCheck( IN DWORD iBugCheckCode, IN DWORD iBugCheckParameter1, IN DWORD iBugCheckParameter2, IN DWORD iBugCheckParameter3, IN DWORD iBugCheckParameter4, IN PVOID iTrapFrame ) { // Log error DPRINT("[*] KiProcessBugCheck call - FATAL SYSTEM ERROR. BugCheck code %08x", iBugCheckCode); if(CurrentBugCheckFilter > 0) { __try { // Заполняем структуру BUGCHECK_INFO BugCheckInfo = { iBugCheckCode, { iBugCheckParameter1, iBugCheckParameter2, iBugCheckParameter3, iBugCheckParameter4 } }; // Перебираем обработчики for(int i=CurrentBugCheckFilter-1;i>=0;i--) { if(!BugCheckFilters[i]) continue; ULONG status = BugCheckFilters[i]( &BugCheckInfo ); // вызов if( status == BUGCHECK_CONTINUE_SEARCH ) continue; if( status == BUGCHECK_EXECUTE_HANDLER ) ExRaiseStatus( STATUS_BUGCHECK ); // выбрасываем исключение STATUS_BUGCHECK } } __except( (GetExceptionCode()==STATUS_BUGCHECK)?EXCEPTION_CONTINUE_SEARCH:EXCEPTION_EXECUTE_HANDLER ) { DPRINT("Exception while enumerating handlers: %08x", GetExceptionCode()); } } // Если мы все-таки сюда добрались, значит все фильтры пропустили багчек. Уроним систему NTSTATUS UnsetHooks(); if( UnsetHooks() == STATUS_SUCCESS ) KeBugCheckEx( iBugCheckCode, iBugCheckParameter1, iBugCheckParameter2, iBugCheckParameter3, iBugCheckParameter4 ); // Что-то пошло не так и уронить систему не вышло. Просто завесим DPRINT("Cannot unset hooks, hang system"); KfRaiseIrql( HIGH_LEVEL ); for(;;) { } } Полный код тестового драйвера можно обнаружить тут: http://gr8.cih.ms/bsodhandling.cpp (Большая часть кода взята из моей статьи "Жизнь после смерти" и должна быть уже знакома ) Вот, собственно, и все. Пока)
стоит отметить, что код, который ставит фильтр и (возможно) генерит бсод, должен выполняться только при IRQL = PASSIVE_LEVEL. Начало хендлера надо переписать так: Code: void KiProcessBugCheck( ... ) { // Log error ... if(CurrentBugCheckFilter > 0 [b][i]&& KeGetCurrentIrql() == PASSIVE_LEVEL[/i][/b]) {