Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Роббинс Д. - Отладка приложений для Microsoft .NET и Microsoft Windows - 2004

.pdf
Скачиваний:
350
Добавлен:
13.08.2013
Размер:
3.3 Mб
Скачать

ГЛАВА 13 Обработчики ошибок

493

 

 

 

 

 

 

 

 

 

 

 

 

g_stFrame.Params[ 1 ]

,

 

 

g_stFrame.Params[ 2 ]

,

 

 

g_stFrame.Params[ 3 ]

) ;

 

 

}

 

 

 

// Выводить имя модуля?

 

 

 

if ( GSTSO_MODULE == ( dwOpts & GSTSO_MODULE ) )

 

 

 

{

 

 

 

iCurr += wsprintf ( g_szBuff + iCurr , _T ( " " ) ) ;

 

 

ASSERT ( iCurr < ( BUFF_SIZE MAX_PATH ) ) ;

 

 

 

iCurr += BSUGetModuleBaseName ( GetCurrentProcess ( ) ,

 

(HINSTANCE)dwModBase

,

 

g_szBuff + iCurr

,

 

BUFF_SIZE iCurr

) ;

 

}

 

 

 

ASSERT ( iCurr < ( BUFF_SIZE MAX_PATH ) ) ;

 

 

 

DWORD64 dwDisp ;

 

 

 

// Выводить имя символа?

 

 

 

if ( GSTSO_SYMBOL == ( dwOpts & GSTSO_SYMBOL ) )

 

 

 

{

 

 

 

// Начало поиска адреса исключения.

 

 

 

PIMAGEHLP_SYMBOL64 pSym = (PIMAGEHLP_SYMBOL64)&g_stSymbol ;

 

ZeroMemory ( pSym , SYM_BUFF_SIZE ) ;

 

 

 

pSym >SizeOfStruct = sizeof ( IMAGEHLP_SYMBOL64 ) ;

 

 

pSym >MaxNameLength = SYM_BUFF_SIZE

 

 

 

sizeof ( IMAGEHLP_SYMBOL64 ) ;

 

 

pSym >Address = g_stFrame.AddrPC.Offset ;

 

 

 

if ( TRUE ==

 

 

 

SymGetSymFromAddr64 ( GetCurrentProcess ( )

,

 

g_stFrame.AddrPC.Offset

,

 

&dwDisp

 

,

 

pSym

 

) )

 

{

 

 

 

if ( dwOpts & ~GSTSO_SYMBOL )

 

 

 

{

 

 

 

iCurr += wsprintf ( g_szBuff + iCurr , _T ( "," ));

}

//Копируемая в буфер информация о символах

//не должна превышать объем свободного места.

//Имена символов имеют формат ANSI

int iLen = ( lstrlenA ( pSym >Name ) * sizeof ( TCHAR)); if ( iLen > ( BUFF_SIZE iCurr

( MAX_SYM_SIZE + 50 ) ) )

{

#ifdef UNICODE

см. след. стр.

494 ЧАСТЬ IV Мощные средства и методы отладки неуправляемого кода

// Получение места в стеке для преобразования строки. TCHAR * pWideName = (TCHAR*)_alloca ( iLen + 1 ) ;

BSUAnsi2Wide ( pSym >Name , pWideName , iLen + 1 ) ;

lstrcpyn ( g_szBuff + iCurr

,

 

pWideName

,

 

BUFF_SIZE iCurr 1

) ;

 

#else

 

 

lstrcpyn ( g_szBuff + iCurr

,

 

pSym >Name

,

 

BUFF_SIZE iCurr 1

) ;

 

#endif // UNICODE

 

 

// Выход

 

 

szRet = g_szBuff ;

 

 

__leave ;

 

 

}

 

 

else

 

 

{

 

 

if ( dwDisp > 0 )

 

 

{

 

 

iCurr += wsprintf ( g_szBuff + iCurr

,

k_NAMEDISPFMT

,

pSym >Name

,

dwDisp

 

) ;

}

 

 

else

 

 

{

 

 

iCurr += wsprintf ( g_szBuff + iCurr ,

 

k_NAMEFMT

,

 

pSym >Name

) ;

}

 

 

}

 

 

}

 

 

else

 

 

{

 

 

//Если символ не был найден, информация об исходном файле

//и номере строки также не будет получена, поэтому выходим. szRet = g_szBuff ;

__leave ;

}

}

ASSERT ( iCurr < ( BUFF_SIZE MAX_PATH ) ) ;

// Выводить информацию об исходном файле и номере строки? if ( GSTSO_SRCLINE == ( dwOpts & GSTSO_SRCLINE ) )

{

ГЛАВА 13 Обработчики ошибок

495

 

 

 

 

 

 

 

 

 

ZeroMemory ( &g_stLine , sizeof ( IMAGEHLP_LINE64 ) ) ;

 

 

g_stLine.SizeOfStruct = sizeof ( IMAGEHLP_LINE64 ) ;

 

 

DWORD dwLineDisp ;

 

 

if ( TRUE == SymGetLineFromAddr64 ( GetCurrentProcess ( )

,

 

g_stFrame.AddrPC.Offset,

 

&dwLineDisp

,

 

&g_stLine

))

 

{

 

 

if ( dwOpts & ~GSTSO_SRCLINE )

 

 

{

 

 

iCurr += wsprintf ( g_szBuff + iCurr , _T ( "," ));

 

}

 

 

//Копируемая информация об исходном файле и номере

//строки не должна превышать объем свободного места. int iLen = lstrlenA ( g_stLine.FileName ) ;

if ( iLen > ( BUFF_SIZE iCurr

( MAX_PATH + 50

) ) )

{

#ifdef UNICODE

// Получение места в стеке для преобразования строки. TCHAR * pWideName = (TCHAR*)_alloca ( iLen + 1 ) ;

BSUAnsi2Wide ( g_stLine.FileName

,

 

pWideName

 

,

 

iLen + 1

 

) ;

 

lstrcpyn ( g_szBuff + iCurr

 

,

 

pWideName

,

 

 

BUFF_SIZE iCurr 1

) ;

 

#else

 

 

 

lstrcpyn ( g_szBuff + iCurr

 

,

 

g_stLine.FileName

 

,

 

BUFF_SIZE iCurr 1

) ;

 

#endif

 

 

 

// Выход

 

 

 

szRet = g_szBuff ;

 

 

 

__leave ;

 

 

 

}

 

 

 

else

 

 

 

{

 

 

 

if ( dwLineDisp > 0 )

 

 

 

{

 

 

 

iCurr += wsprintf( g_szBuff + iCurr

,

k_FILELINEDISPFMT

,

g_stLine.FileName

,

g_stLine.LineNumber

,

dwLineDisp

 

) ;

см. след. стр.

496 ЧАСТЬ IV Мощные средства и методы отладки неуправляемого кода

}

else

{

iCurr += wsprintf ( g_szBuff + iCurr

,

k_FILELINEFMT

,

g_stLine.FileName

,

g_stLine.LineNumber

) ;

}

}

}

}

szRet = g_szBuff ;

}

__except ( EXCEPTION_EXECUTE_HANDLER )

{

ASSERT ( !"Crashed in InternalGetStackTraceString" ) ; szRet = NULL ;

}

return ( szRet ) ;

}

LPCTSTR __stdcall GetRegisterString ( EXCEPTION_POINTERS * pExPtrs )

{

//

Проверка параметра.

 

ASSERT ( FALSE == IsBadReadPtr ( pExPtrs

,

 

sizeof ( EXCEPTION_POINTERS ) ) ) ;

if

( TRUE == IsBadReadPtr ( pExPtrs

,

sizeof ( EXCEPTION_POINTERS ) ) )

{

TRACE0 ( "GetRegisterString invalid pExPtrs!\n" ) ; return ( NULL ) ;

}

#ifdef _X86_

//Этот вызов помещает в стек 48 байт, что может

//представлять проблему, если стек близок к переполнению. wsprintf(g_szBuff ,

_T ("EAX=%08X

EBX=%08X

ECX=%08X

EDX=%08X

ESI=%08X\n")\

_T

("EDI=%08X

EBP=%08X

ESP=%08X

EIP=%08X

FLG=%08X\n")\

_T

("CS=%04X

DS=%04X

SS=%04X

ES=%04X

")\

_T ("FS=%04X GS=%04X" ) ,

 

pExPtrs >ContextRecord >Eax

,

pExPtrs >ContextRecord >Ebx

,

pExPtrs >ContextRecord >Ecx

,

pExPtrs >ContextRecord >Edx

,

pExPtrs >ContextRecord >Esi

,

pExPtrs >ContextRecord >Edi

,

pExPtrs >ContextRecord >Ebp

,

pExPtrs >ContextRecord >Esp

,

 

 

 

ГЛАВА 13 Обработчики ошибок

497

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

pExPtrs >ContextRecord >Eip

 

,

 

 

pExPtrs >ContextRecord >EFlags

,

 

 

pExPtrs >ContextRecord >SegCs

 

,

 

 

pExPtrs >ContextRecord >SegDs

 

,

 

 

pExPtrs >ContextRecord >SegSs

 

,

 

 

pExPtrs >ContextRecord >SegEs

 

,

 

 

pExPtrs >ContextRecord >SegFs

 

,

 

 

pExPtrs >ContextRecord >SegGs

 

) ;

 

 

#elif _AMD64_

 

 

 

 

 

 

 

wsprintf ( g_szBuff

,

 

 

 

 

 

 

_T ("RAX=%016X

RBX=%016X

RCX=%016X

RDX=%016X

RSI=%016X\n")\

 

_T ("RDI=%016X

RBP=%016X

RSP=%016X

RIP=%016X

FLG=%016X\n")\

 

_T (" R8=%016X

R9=%016X

R10=%016X

R11=%016X

R12=%016X\n")\

 

_T ("R13=%016X

R14=%016X

R15=%016X" ) ,

 

 

 

pExPtrs >ContextRecord >Rax

 

,

 

 

 

 

pExPtrs >ContextRecord >Rbx

 

,

 

 

 

 

pExPtrs >ContextRecord >Rcx

 

,

 

 

 

 

pExPtrs >ContextRecord >Rdx

 

,

 

 

 

 

pExPtrs >ContextRecord >Rsi

 

,

 

 

 

 

pExPtrs >ContextRecord >Rdi

 

,

 

 

 

 

pExPtrs >ContextRecord >Rbp

 

,

 

 

 

 

pExPtrs >ContextRecord >Rsp

 

,

 

 

 

 

pExPtrs >ContextRecord >Rip

 

,

 

 

 

 

pExPtrs >ContextRecord >EFlags

,

 

 

 

 

pExPtrs >ContextRecord >R8

 

,

 

 

 

 

pExPtrs >ContextRecord >R9

 

,

 

 

 

 

pExPtrs >ContextRecord >R10

 

,

 

 

 

 

pExPtrs >ContextRecord >R11

 

,

 

 

 

 

pExPtrs >ContextRecord >R12

 

,

 

 

 

 

pExPtrs >ContextRecord >R13

 

,

 

 

 

 

pExPtrs >ContextRecord >R14

 

,

 

 

 

 

pExPtrs >ContextRecord >R15

 

) ;

 

 

 

 

#elif _IA64_

 

 

 

 

 

 

 

#pragma message ( "IA64 NOT DEFINED!!" )

 

 

 

 

#pragma FORCE COMPILATION ABORT!

 

 

 

 

 

#else

 

 

 

 

 

 

 

#pragma message ( "CPU NOT DEFINED!!" )

 

 

 

 

#pragma FORCE COMPILATION ABORT!

 

 

 

 

 

#endif

 

 

 

 

 

 

 

return ( g_szBuff ) ;

 

 

 

 

 

 

}

 

 

 

 

 

 

 

LPCTSTR ConvertSimpleException ( DWORD dwExcept )

{

switch ( dwExcept )

{

case EXCEPTION_ACCESS_VIOLATION :

return ( _T ( "EXCEPTION_ACCESS_VIOLATION" ) ) ;

см. след. стр.

498

ЧАСТЬ IV Мощные средства и методы отладки неуправляемого кода

 

 

 

 

 

 

 

break ;

 

 

case EXCEPTION_DATATYPE_MISALIGNMENT

:

 

return ( _T ( "EXCEPTION_DATATYPE_MISALIGNMENT" ) ) ;

 

break ;

 

 

case EXCEPTION_BREAKPOINT

:

 

return ( _T ( "EXCEPTION_BREAKPOINT" ) ) ;

 

break ;

 

 

case EXCEPTION_SINGLE_STEP

:

 

return ( _T ( "EXCEPTION_SINGLE_STEP" ) ) ;

 

break ;

 

 

case EXCEPTION_ARRAY_BOUNDS_EXCEEDED

:

 

return ( _T ( "EXCEPTION_ARRAY_BOUNDS_EXCEEDED" ) ) ;

 

break ;

 

 

case EXCEPTION_FLT_DENORMAL_OPERAND

:

 

return ( _T ( "EXCEPTION_FLT_DENORMAL_OPERAND" ) ) ;

 

break ;

 

 

case EXCEPTION_FLT_DIVIDE_BY_ZERO

:

 

return ( _T ( "EXCEPTION_FLT_DIVIDE_BY_ZERO" ) ) ;

 

break ;

 

 

case EXCEPTION_FLT_INEXACT_RESULT

:

 

return ( _T ( "EXCEPTION_FLT_INEXACT_RESULT" ) ) ;

 

break ;

 

 

case EXCEPTION_FLT_INVALID_OPERATION

:

 

return ( _T ( "EXCEPTION_FLT_INVALID_OPERATION" ) ) ;

 

break ;

 

 

case EXCEPTION_FLT_OVERFLOW

:

 

return ( _T ( "EXCEPTION_FLT_OVERFLOW" ) ) ;

 

break ;

 

 

case EXCEPTION_FLT_STACK_CHECK

:

 

return ( _T ( "EXCEPTION_FLT_STACK_CHECK" ) ) ;

 

break ;

 

 

case EXCEPTION_FLT_UNDERFLOW

:

 

return ( _T ( "EXCEPTION_FLT_UNDERFLOW" ) ) ;

 

break ;

 

 

case EXCEPTION_INT_DIVIDE_BY_ZERO

:

 

return ( _T ( "EXCEPTION_INT_DIVIDE_BY_ZERO" ) ) ;

 

break ;

 

 

 

 

ГЛАВА 13 Обработчики ошибок

499

 

 

 

 

 

 

 

 

 

case EXCEPTION_INT_OVERFLOW

:

 

return ( _T ( "EXCEPTION_INT_OVERFLOW" ) ) ;

 

break ;

 

 

case EXCEPTION_PRIV_INSTRUCTION

:

 

return ( _T ( "EXCEPTION_PRIV_INSTRUCTION" ) ) ;

 

break ;

 

 

case EXCEPTION_IN_PAGE_ERROR

:

 

return ( _T ( "EXCEPTION_IN_PAGE_ERROR" ) ) ;

 

break ;

 

 

case EXCEPTION_ILLEGAL_INSTRUCTION

:

 

return ( _T ( "EXCEPTION_ILLEGAL_INSTRUCTION" ) ) ;

 

break ;

 

 

case EXCEPTION_NONCONTINUABLE_EXCEPTION :

 

return ( _T ( "EXCEPTION_NONCONTINUABLE_EXCEPTION" ) ) ;

 

break ;

 

 

case EXCEPTION_STACK_OVERFLOW

:

 

return ( _T ( "EXCEPTION_STACK_OVERFLOW" ) ) ;

 

break ;

 

 

case EXCEPTION_INVALID_DISPOSITION

:

 

return ( _T ( "EXCEPTION_INVALID_DISPOSITION" ) ) ;

 

break ;

 

 

case EXCEPTION_GUARD_PAGE

:

 

return ( _T ( "EXCEPTION_GUARD_PAGE" ) ) ;

 

break ;

 

 

case EXCEPTION_INVALID_HANDLE

:

 

return ( _T ( "EXCEPTION_INVALID_HANDLE" ) ) ;

 

break ;

 

 

case 0xE06D7363

:

 

return ( _T ( "Microsoft C++ Exception" ) ) ;

 

break ;

 

 

default :

 

 

return ( NULL ) ;

 

 

break ;

 

 

}

}

// Инициализация символьной машины в случае надобности. void InitSymEng ( void )

{

if ( FALSE == g_bSymEngInit )

см. след. стр.

500 ЧАСТЬ IV Мощные средства и методы отладки неуправляемого кода

{

//Получение параметров символьной машины. DWORD dwOpts = SymGetOptions ( ) ;

//Включение загрузки информации о номерах строк.

SymSetOptions ( dwOpts

|

SYMOPT_LOAD_LINES

) ;

// Установка флага fInvadeProcess.

 

BOOL bRet = SymInitialize ( GetCurrentProcess ( ) ,

 

NULL

,

TRUE

) ;

ASSERT ( TRUE == bRet ) ;

 

g_bSymEngInit = bRet ;

 

}

}

// Очистка символьной машины в случае надобности void CleanupSymEng ( void )

{

if ( TRUE == g_bSymEngInit )

{

VERIFY ( SymCleanup ( GetCurrentProcess ( ) ) ) ;

g_bSymEngInit = FALSE ;

}

}

Для установки собственной функции фильтра исключений просто вызовите SetCrashHandlerFilter, которая сохраняет указатель на вашу функцию фильтр ис ключений в статической переменной и вызывает SetUnhandledExceptionFilter для установки действительного фильтра исключений — CrashHandlerExceptionFilter. Если вы не укажете модулей, ограничивающих фильтрацию исключений, CrashHandler ExceptionFilter будет всегда вызывать ваш обработчик исключений независимо от того, в каком модуле произошла ошибка. Это было сделано намеренно, чтобы вы могли устанавливать собственный заключительный обработчик исключений един ственным вызовом API. Лучше всего вызывать SetCrashHandlerFilter пораньше и обязательно вызывать ее еще раз с параметром NULL прямо перед выгрузкой фильтра, чтобы мой обработчик мог удалить вашу функцию фильтр. Диаграмма обработ чика ошибок показана на рис. 13 1.

Добавление модуля, ограничивающего обработку ошибок, выполняет, функция AddCrashHandlerLimitModule. Для этого нужно только передать в нее HMODULE нужно го модуля. Если вы хотите ограничить обработку ошибок несколькими модуля ми, просто вызовите AddCrashHandlerLimitModule для каждого из них. Массив опи сателей модулей создается в куче основного процесса.

Изучая листинг 13 4, вы увидите, что я не вызываю функций стандартной биб лиотеки C. Поскольку функции обработчика ошибок вызываются только в экст раординарных ситуациях, я не могу быть уверен в стабильной работе библиотечных функций. Для освобождения выделенной памяти я применяю автоматический

ГЛАВА 13 Обработчики ошибок

501

 

 

статический класс, деструктор которого вызывается при выгрузке BUGSLAYER UTIL.DLL. Я также предоставляю две функции, обеспечивающие получение размера массива ограничивающих модулей в элементах и копирование массива: GetLimit ModuleCount и GetLimitModulesArray. Реализацию функции RemoveCrashHandlerLimitModule

(удаление модуля, ограничивающего обработку ошибок) я оставил вам.

Обработчик ошибок

Необработанное исключение

 

 

CrashHandler API (BugslayerUtil.dll)

 

 

SetCrashHandlerFilter(

 

g_pfnOrigFilt =

 

 

ExceptCallBack);

 

SetUnhandledExceptionFilter(

 

 

 

 

CrashHandlerExceptionFilter);

 

 

 

 

CrashHandlerExceptionFilter

 

 

Обработчик

 

 

 

Первоначальный

исключений

Да

Список модулей пуст?

Нет

фильтр исключений

 

 

Исключение в определен-

 

 

ExcepCallBack

 

ном модуле?

 

g_pfnOrigFilt

 

 

 

 

 

Список модулей

 

 

Рис. 13 1. Диаграмма обработчика ошибок

Некоторый интерес представляет то, как я инициализирую сервер символов DBGHELP.DLL. Код обработчика ошибок может быть вызван в любое время, поэтому мне был нужен способ загрузки всех модулей процесса в момент ошибки. Это выполняется автоматически функцией SymInitialize, которая получает третий па раметр, fInvadeProcess, имеющий значение TRUE.

Еще один интересный момент — как я работаю с символами ANSI в мире Uni code. Так как код CrashHandler, представленный мной в первом издании, оказался очень популярным и применяется в бесчисленном множестве программ, я дол жен был учесть потребности программистов, желающих работать с новым CRASH HANDLER.CPP в существующих проектах. Я не хотел использовать для преобразо вания символов свою крупную оболочку SYMBOLENGINE.LIB для символьной ма шины, потому что это препятствовало бы непосредственной модернизации кода. В конце концов я решил выполнять большинство преобразований при помощи функции wsprintf с форматом %S, который при компиляции Unicode указывает, что параметр является строкой ANSI.

В тех фрагментах, где я должен был выполнять преобразование сам, я решил выделять память в стеке при помощи функции _alloca, а не в куче стандартной библиотеки C или ОС, потому что кучи могут быть повреждены и стать причи ной ошибки. Если ошибка будет вызвана переполнением стека, любой выполняе

502 ЧАСТЬ IV Мощные средства и методы отладки неуправляемого кода

мый мной код, вероятно, приведет к повторной ошибке задолго до того, как про грамма достигнет одного из вызовов _alloca.

Преобразование структур EXCEPTION_POINTERS

Наверняка вы уже написали свои обработчики исключений и ошибок, поэтому пора поговорить о структурах EXCEPTION_POINTERS, указатели на которые передаются в оба обработчика. В этих структурах хранится вся интересная информация об ошибке, поэтому я хотел разработать набор функций, которые вы могли бы вы зывать для преобразования сведений в удобную для понимания форму. Благода ря этим функциям вы можете сосредоточиться на отображении пользователям информации в том виде, который уместен для конкретного приложения. Все эти функции можно найти в листинге 13 4.

Я пытался сделать эти функции как можно проще. Все, что вам нужно сде лать, — передать в них указатель на структуру EXCEPTION_POINTERS. Каждая функция возвращает указатель на глобальную текстовую строку, поэтому я могу не выде лять память в куче и всегда уверен в наличии буферов достаточного объема. Кое кого из вас, наверное, смущает то, что я работаю с глобальными переменными и часто использую буферы, но мне кажется, что это самый безопасный код, кото рый я мог написать.

Функция GetRegisterString просто возвращает указатель на отформатирован ную строку, содержащую значения регистров. Функция GetFaultReason чуть инте реснее: она возвращает полное описание проблемы. Возвращаемая строка содер жит информацию о процессе, причине исключения, модуле, вызвавшем исклю чение, адресе исключения и — если доступны символы — информацию о функ ции, исходном файле и номере строки ошибки.

CrashHandlerTest.exe caused an EXCEPTION_ACCESS_VIOLATION in module

CrashHandlerTest.exe at 001B:004011D1, Baz()+0088 byte(s),

d:\dev\booktwo\disk\bugslayerutil\tests\crashhandler\crashhandler.cpp,

line 0061+0003 byte(s)

Наибольший интерес представляют функции GetFirstStackTraceString и GetNext StackTraceString. Как показывают имена, эти функции позволяют вам анализиро вать стек. Как и в случае API функций FindFirstFile и FindNextFile, для анализа всего стека вы можете вызвать GetFirstStackTraceString и затем продолжить вызывать GetNextStackTraceString, пока она не вернет FALSE. Второй параметр этих функций является указателем на структуру EXCEPTION_POINTERS, передаваемым вашей функ ции обработчика ошибок. Код обработчика ошибок поступает правильно: он кэ ширует значение, переданное в GetFirstStackTraceString, так что структура EXCEP TION_POINTERS в вашем обработчике ошибок остается нетронутой на тот случай, если вы позднее захотите передать указатель на нее в функции записи минидампов. GetNextStackTraceString на самом деле не использует переданную ей структуру EXCEPTION_POINTERS, но я не хотел нарушать совместимость CRASHHANDLER.CPP с программами, которые уже работают с ним.

Первый параметр функций GetFirstStackTraceString и GetNextStackTraceString

это параметр флагов, позволяющий контролировать объем информации, которую

Соседние файлы в предмете Программирование на C++