Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Создание эффективных приложений для Windows Джеффри Рихтер 2004 (Книга).pdf
Скачиваний:
385
Добавлен:
15.06.2014
Размер:
8.44 Mб
Скачать

Собственные программные исключения генерируют в приложениях по целому ряду причин. Например, чтобы посылать информационные сообщения в системный журнал событий. Как только какая-нибудь функция в Вашей программе столкнется с той или иной проблемой, Вы можете вызвать RaiseException; при этом обработчик исключений следует разместить выше по дереву вызовов, тогда — в зависимости от типа исключения — он будет либо заносить его в журнал событий, либо сообщать о нем пользователю. Вполне допустимо возбуждать программные исключения и для уведомления о внутренних фатальных ошибках в приложении.

ГЛАВА 25 Необработанные исключения и исключения С++

В предыдущей главе мы обсудили, что происходит, когда фильтр возвращает значе ние EXCEPTION_CONTШNUE_SEARCH. Оно заставляет систему искать дополнительные фильтры исключений, продвшаясь вверх по дереву вызовов. А что будет, если все фильтры вернут EXCEPTION_CONTINUE_SEARCH? Тогда мы получим необработанное исключение (unhandled exception).

Как Вы помните из главы 6, выполнение потока начинается с функции BaseProcess Start или BaseThreadStart в Kernel32.dll Единственная разница между этими функция ми в том, что первая используется для запуска первичного потока процесса, а вто рая — для запуска остальных потоков процесса.

VOID BaseProcessStart(PPROCESS_START_ROUTINE pfnStartAddr)

{

__try

{

ExitThread({pfnStartAddr)());

}

_except (UnhandledExceptionFilter(GetExceptionInformation()))

{

ExitProcess(GetExecptionCode());

}

// Примечание, сюда мы никогда не попадем

}

VOID BaseThreadStart(PTHREAD_START_ROUTINE pfnStartAddr, PVOID pvParam)

{

__try

{

ExitThread((pfnStartAddr)(pvParam));

}

__except (UnhandledExceptionFilter(GetExceptionInformation())}

{

ExitProcess(GetExceptionCode());

}

// Примечание, сюда мы никогда не попадем

}

Обратите внимание, что обе функции содержат SEH-фрейм: поток запускается из блока try. Если поток возбудит исключение, в ответ на которое все Ваши фильтры вер нут EXCEPTION_CONTINUE_SEARCH, будет вызвана особая функция фильтра, предос тавляемая операционной системой:

LONG UnhandledExceptionFilter(PEXCEPTION_POINTERS pExceptionInfo);

Она выводит окно, указывающее на то, что поток в процессе вызвал необрабаты ваемое им исключение, и предлагает либо закрыть процесс, либо начать его отладку. В Windows 98 это окно выглядит следующим образом.

А в Windows 2000 оно имеет другой вид.

ВWindows 2000 первая часть текста в этом окне подсказывает тип исключения и адрес вызвавшей его инструкции в адресном пространстве процесса. У меня окно появилось изза нарушения доступа к памяти, поэтому система сообщила адрес, по которому произошла ошибка, и тип доступа к памяти — чтение UnhandledException Filter получает эту информацию из элемента Exceptionlnformation структуры EXCEP TION_RECORD, инициализированной для этого исключения.

Вданном окне можно сделать одно из двух. Во-первых, щелкнуть кнопку OK, и тогда

UnhandledExceptionFilter вернет EXCEPTION_EXECUTE_HANDLER. Это приведет к глобальной раскрутке и соответственно к выполнению всех имеющихся блоков finally, а затем и к выполнению обработчика в BaseProcessStart или BaseThreadStart. Оба обработчика вызывают ExitProcess, поэтому-то Ваш процесс и закрывается Причем кодом завершения процесса становится код исключения. Кроме того, процесс закры вается его жe потоком, а не операционной системой!А это означает, что Вы можете вмешаться в ход завершения своего процесса.

Во-вторых, Вы можете щелкнуть кнопку Cancel (сбываются самые смелые мечты программистов). В этом случае UnbandledExceptionFilter попытается запустить отлад чик и подключить его к процессу Тогда Вы сможете просматривать состояние гло бальных, локальных и статических переменных, расставлять точки прерывания, пе резапускать процесс и вообще делать все, что делается при отладке процесса.

Но самое главное, что сбой в программе можно исследовать в момент его возник новения. В большинстве других операционных систем для отладки процесса сначала запускается отладчик. При генерации исключения в процессе, выполняемом в любой из таких систем, этот процесс надо завершить, запустить отладчик и прогнать про грамму уже под отладчиком Проблема, правда, в том, что ошибку надо сначала вос произвести; лишь потом можно попытаться ее исправить. А кто знает, какие значе ния были у переменных, когда Вы впервые заметили ошибку? Поэтому найти ее та

ким способом гораздо труднее Возможность динамически подключать отладчик к уже запущенному процессу — одно из лучших качеств Windows.

WINDOWS 2000

В этой книге рассматривается разработка приложений, работающих только в пользовательском режиме. Но, наверное, Вас интересует, что происходит, ког да необработанное исключение возникает в потоке, выполняемом в режиме ядра. Так вот, исключения в режиме ядра обрабатываются так же, как и исклю чения пользовательского режима. Если низкоуровневая функция для работы с виртуальной памятью возбуждает исключение, система проверяет, есть ли фильтр режима ядра, готовый обработать это исключение Если такого филь тра нет, оно остается необработанным В этом случае необработанное исклю чение окажется в операционной системе или (что вероятнее) в драйвере уст ройства, а не в приложении А это уже серьезно!

Так как дальнейшая работа системы после необработанного исключения в режиме ядра небезопасна, Windows не вызывает UnhandledExceptionFilter. Вме сто этого появляется так называемый "синий экран смерти" экран переклю чается в текстовый режим, окрашивается в синий фон, выводится информа ция о модуле, вызвавшем необработанное исключение, и система останавли вается Вам следует записать эту информацию и отправить ее в Microsoft или поставщику драйвера устройства. Прежде чем продолжить работу, придется перезагрузить машину; при этом все несохраненные данные теряются.

Отладка по запросу

Windows позволяет подключать отладчик к любому процессу в любой момент време ни — эта функциональность называется отладкой по запросу (just-in-time debugging). В этом разделе я расскажу, кяк она работает Щелкнув кнопку Cancel, Вы сообщаете функции UnhandledExceptionFilter о том, что хотиге начать отладку процесса.

Для активизации отладчика UnhandledExceptionFilter просматривает раздел реестра.

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows

NT\CurrentVersion\AeDebug

Если Вы установили Visual Studio, то содержащийся в этом разделе параметр Debug ger имеет следующее значение:

"C:\Program Files\Microsoft Visual

Studio\Common\MSDev98\Bin\msrtev Rxe" -p %ld -e %ld

WINDOWS 98

В Windows 98 соответствующие значения хранятся не в реестре, а в файле Win.ini.

Строка, приведенная выше, сообщает системе, какой отладчик надо запустить (в данном случае — MSDev.exe). Естественно, Вы можете изменить это значение, указав другой отладчик. UnhandiedExceptionFilter передает отладчику два параметра в коман дной строке Первый — это идентификатор процесса, который нужно отладить, а второй — наследуемое событие со сбросом вручную, которое создается функцией UnhandiedExceptionFilter в занятом состоянии. Отладчик должен распознавать ключи -p и -e как идентификатор процесса и описатель события

Сформировав командную строку из идентификатора процесса и описателя собы тия,

UnhandledExceptionFiltet запускает отладчик вызовом CreateProcess. Отладчик про

веряет аргументы в командной строке и, обнаружив ключ -p, подключается к соот ветствующему процессу вызовом DebugActiveProcess-

BOOL DebugActiveProcess(DWORD dwProcessID);

После этого система начинает уведомлять отладчик о состоянии отлаживаемого процесса, сообщая, например, сколько в нем потоков и кякие DLL спроецированы на его адресное пространство. На сбор этих данных отладчику нужно какое-то время, в течение которого поток UnhandledExceptionFilter должен находиться в режиме ожи дания. Для этого функция вызывает WaitForSingleObject и передает описатель создан ного ею события со сбросом вручную. Как Вы помните, оно было создано в занятом состоянии, поэтому поток отлаживаемого процесса немедленно приостанавливается и ждет освобождения этого события

Закончив инициализацию, отладчик вновь проверяет командную строку — на этот раз он ищет ключ -e. Найдя его, отладчик считывает оиисатель события и вызывает SetEvent. Он может напрямую использовать этот наследуемый описатель, поскольку процесс отладчика является дочерним по отношению к отлаживаемому процессу, который и породил его,

вызвав UnhandledExceptionFilter.