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

Рихтер Дж., Назар К. - Windows via C C++. Программирование на языке Visual C++ - 2009

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

800 Часть V. Структурная обработка исключений

Поэтому в функции фильтра следует выполнять минимальное число действий, также нельзя использовать динамическое выделение памяти, поскольку куча может быть повреждена. При установке нового фильтра для необработанных исключений SetUnhandledExceptionFilter возвращает адрес ранее установленного фильтра. Заметьте, что в случае приложений, использующих библиотеку C/C++, перед исполнением входной функции программы библиотека устанавливает собственный глобальный фильтр для необработанных исключений — __CxxUnhandledExceptionFilter. Эта функция просто проверяет, является ли возникшее исключение исключением C++ (подробнее об этом см. ниже в этой главе), и, если это так, исполняет функцию abort, которая, в свою очередь, вызывает UnhandledExceptionFilter из Kernel32.dll. Прежние версии библиотеки C/C++ просто прерывают исполнение процесса. Функция _set_abort_behavior служит для настройки отчетов об ошибках, которые выводит abort. Если возникшее исключение не опознано как С++-исключение, фильтр возвращает идентификатор ЕХСЕРТION_CONTINUE_SEARCH, который говорит операционной системе, что ей придется самой позаботиться об этом исключении.

Табл. 25-1. Значения, возвращаемые фильтром исключений верхнего уровня

Идентификатор

Действия системы

EXCEPTION_EXECUTE_

Исполнение процесса прерывается без уведомления пользователя.

HANDLER

При этом происходит глобальная раскрутка, а значит, код в блоке

 

finally будет исполнен

EXCEPTION_CONTINUE_

Исполнение возобновляется с команды, генерировавшей исключе-

EXECUTION

ние. Можно модифицировать сведения об исключении, на которые

 

указывает параметр PEXCEPTION_POINTERS. Если ошибка не

 

устранена и исключение возникает повторно, процесс может по-

 

пасть в бесконечный цикл

EXCEPTION_CONTINUE_

Исключение действительно становится необработанным, подроб-

SEARCH

нее об этой ситуации насказывается в следующем разделе

Таким образом, вызвав SetUnhandledExceptionFilter, чтобы установить собственный фильтр, вы получите адрес функции __CxxUnhandledExceptionFilter, что и показывает IntelliSense при отладке кода в Visual Studio. В противном случае фильтром исключений по умолчанию становится функция UnhandledExceptionFilter.

Примечание. Чтобы сбросить фильтр исключений, достаточно вызвать SetUnhandledExceptionFilter и передать ей NULL — глобальным фильтром необработанных исключений станет UnhandledExceptionFilter.

Если ваш фильтр возвращает EXCEPTION_CONTINUE_SEARCH, так и хочется вызвать ранее установленный фильтр, адрес которого возвращает

Глава 25. Необработанные исключения, векторная обработка исключений и исключения

C++.docx 801

функция SetUnhandledExceptionFilter. Однако так поступать не стоит, поскольку какие-нибудь сторонние программы вполне могли установить собственные фильтры исключений. Если эти фильтры располагаются в динамически загружаемых модулях, к моменту возникновения исключения они могут быть уже выгружены. Есть и еще одна причина, по которой не следует вызывать ранее установленный фильтр, о ней рассказывается ниже на стр. 708.

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

VOID BaseThreadStart(PTHREAD_START_ROUTINE pfnStartAddr, PVOID pvParam) { __try {

ExitThread((pfnStartAddr)(pvParam));

}

__except (UnhandledExceptionFilter(GetExceptionlnformation())) { ExitProcess(GetExceptionCode())

}

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

}

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

LONG UnhandledExceptionFilter(PEXCEPTION_POINTERS pExceptionlnfo);

Подобно любому фильтру исключений, эта функция возвращает один из трех EXCEPTION_*-идентификаторов, но действия системы при их получении несколько отличаются (см. табл. 25-2).

Табл. 25-2. Значения, возвращаемые функцией UnhandledExceptionFilter

Идентификатор

Действия системы

EXCEPTION_EXECUTE_

Идет глобальная раскрутка и выполняется код каждого из необра-

HANDLER

ботанных блоков finally. В случае необработанного исключения

 

обработчик BaseThreadStart вызывает ExitProcess и «молча» за-

 

вершается. Заметьте, что код исключения становится кодом за-

 

вершения процесса

EXCEPTION_CONTINUE_

Исполнение продолжается с команды, вызвавшей исключение.

EXECUTION

Подробнее см. в разделе о функции UnhandledExceptionFilter ниже

802 Часть V. Структурная обработка исключений

Табл. 25-2. (окончание)

Идентификатор

Действия системы

EXCEPTION_CONTINUE_

К сбойному процессу будет подключен специальный отлад-

SEARCH

чик либо отладчик по умолчанию. В обоих случаях система

 

уведомляет отладчик об исключении, в результате отладчик

 

продолжает исполнять код с команды, в которой возникло

 

исключение. Подробнее об этом рассказывается в разделе,

 

посвященном отладке по запросу. Если отладчик не под-

 

ключается, Windows считает, что возникло необработанное

 

исключение пользовательского режима.

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

тификатор EXCEPTION_NESTED_CALL (так происходит в Windows Vista,

в прежних версиях Windows UnhandledExceptionFilter вовсе не возвращала управление в этом случае и процесс попросту завершался).

Однако до возврата этих значений исполняется изрядное количество кода, и теперь настало время разобраться в том, как работает UnhandledExceptionFilter.

Как работает функция UnhandledExceptionFilter

Функция UnhandledExceptionFilter обрабатывает исключения в пять этапов. Ниже мы разберем их все по порядку. Выполнив эти действия, UnhandledExceptionFilter передает управление службе Windows Error Reporting (WER), как описано в следующем разделе.

Этап 1. Разрешение доступа к ресурсу для записи и продолжение исполнения

Если нарушение доступа возникло при попытке потока выполнить запись, UnhandledExceptionFilter проверяет, не пытается ли поток модифицировать .ехеили

.dll-модуль. По умолчанию ресурсы являются (и совершенно обоснованно) неизменяемыми, поэтому попытка их модификации приводит к нарушению доступа. Однако 16-разрядные версии Windows разрешают модификацию ресурсов, и для преемственной совместимости в 32- и 64-разрядных версиях также необходим доступ для записи к ресурсам. Чтобы разрешить его, UnhandledExceptionFilter вызовом VirtualProtect изменяет атрибут защиты страниц, хранящих ресурс, на

PAGEREADWRITE, и возвращает EXCEPTION_CONTINUE_EXECUTION, чтобы система повторила исполнение команды, вызвавшей сбой.

Этап 2. Уведомление отладчика о необработанном исключении

Далее UnhandledExceptionFilter проверяет, не работает ли приложение под контролем отладчика. Если это так, UnhandledExceptionFilter возвращает ЕХСЕР-

Глава 25. Необработанные исключения, векторная обработка исключений и исключения

C++.docx 803

TION_CONTINUE_SEARCH. Поскольку исключение до сих пор не обработано, Windows уведомляет подключенный к процессу отладчик. Отладчик получает значение поля ExceptionInformation созданной для исключения структуры EXCEPTION_RECORD. Затем отладчик использует эту информацию для поиска команды, вызвавшей исключение, и уведомления пользователя о характере исключения. Чтобы выяснить, не работает ли ваша программа под контролем отладчика, вызовите функцию IsDebuggerPresent.

Этап 3. Уведомление глобального фильтра исключений

Если вызовом SetUnhandledExceptionFilter была установлена функция глобального фильтра исключений, UnhandledExceptionFilter вызывает ее. Если фильтр воз-

вращает EXCEPTION_EXECUTE_HANDLER или EXCEPTION_CONTINUE_

EXECUTION, функция UnhandledExceptionFilter передает это значение системе. Если же фильтр необработанных исключений возвращает EXCEPTION_CONTINUE_SEARCH, выполняется четвертый этап (см. ниже). Но постойте-ка, я только что говорил, что глобальный фильтр исключений C/C++,

__CxxUnhandkdExceptionFilter, явно вызывает UnhandledExceptionFilter! Подоб-

ные серии рекурсивных вызовов быстро приведут к переполнению стека и возникновению нового исключения, которое скроет исходное исключение. Это еще одна причина, по которой не следует вызывать ранее установленный глобальный фильтр исключений. Чтобы предотвратить рекурсию,

__CxxUnhandledExceptionFilter вызывает SetUnhandledExceptionFilter (NULL) непосредственно перед UnhandledExceptionFilter.

Если программа использует библиотеку C/C++, то библиотека заключает вызов входной функции в блок try/except, и устанавливает фильтр исключений, вызывающий библиотечную C/C++- функцию _XcpFilter. Сама _XcpFilter вызывает UnhandledExceptionFilter, а та — функцию глобального фильтра исключений, если таковая задана. Таким образом, если установлен глобальный фильтр, он будет вызван в случае возникновения необработанного исключения, замеченного

_XcpFilter. Если ваш фильтр вернет EXCEPTION_CONTINUE_SEARCH, то необ-

работанное исключение (на данном этапе оно действительно является необработанным) передается фильтру except функции BaseThreadStart, а тот вызывает UnhandledExceptionFilter. В результате заданный вами глобальный фильтр вызывается повторно.

Этап 4. Повторное уведомление отладчика о необработанном исключении

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

Этап 5. «Тихое» завершение процесса

Если один из потоков процесса вызвал функцию SetErrorMode и передал ей флаг

SEM_NOGPFAULTERRORBOX, UnhandledExeptionFilter вернет EXCEP-

804 Часть V. Структурная обработка исключений

TION_EXECUTE_HANDLER. В случае необработанного исключения возврат этого значения инициирует глобальную раскрутку, в ходе которой выполняется код из блока finally, сразу после этого процесс «тихо» завершается. Аналогичным образом, если процесс включен в задание (см. главу 5), для которого установлен флаг JOB_OBJECT_LIMIT_DIE_ON_UNHANDLED_EXCEPTION, функция UnhandledExceptionFilter вернет EXCEPTION_EXECUTE_HANDLER с теми же по-

следствиями.

Все эти операции UnhandledExceptionFilter выполняет «тихо», незаметно для пользователя пытаясь устранить вызвавший исключение сбой, уведомить подключенный к процессу отладчик (если таковой имеется), либо, при необходимости, просто завершить приложение. Если же обработать исключение не удается, возвращается значение EXCEPTION_CONTINUE_SEARCH и управление переходит к ядру, которое уведомляет пользователя о том, что в приложении возник ка- кой-то сбой. Но перед разбором действий ядра Windows при завершении UnhandledExceptionFilter и маршрутизации исключений в системе (см. следующий раздел) давайте посмотрим, какие окна выводит система в различных сценариях обработки исключений.

На рис. 25-1 показано, что происходит в Windows XP при передаче исключе-

ния функций UnhandledExceptionFilter.

Рис. 25-1. Сообщение о необработанном исключении в Windows XP

В подобной ситуации Windows Vista поочередно выводит два окна, показанных на рис. 25-2 и 25-3.

Рис. 25-2. Первое сообщение о необработанном исключении в Windows Vista

Глава 25. Необработанные исключения, векторная обработка исключений и исключения

C++.docx 805

Рис. 25-3. Второе сообщение о необработанном исключении в Windows Vista

Взаимодействие UnhandledExceptionFilter с WER

На рис. 25-4 показана схема обработки необработанных исключений в Windows с использованием инфраструктуры Windows Error Reporting; первые два этапа этого процесса мы обсудили в предыдущем разделе. В Windows Vista функция UnhandledExceptionFilter, в отличие от предыдущих версий Windows, не посылает отчет об ошибке на сервер Майкрософт, а выполняет действия, описанные в предыдущем разделе, просто возвращая EXCEPTION_CONTINUE_SEARCH (см. описание 3 этапа работы UnhandledExceptionFilter выше). В этом случае ядро определяет, что поток пользовательского режима не смог обработать исключение (этап 4). В итоге уведомление об исключении направляется специальной службе WerSvc.

Передача этого уведомления осуществляется с использованием недокументированного механизма ALPC (Advanced Local Procedure Call), блокирующего исполнение потока до завершения обработки уведомления службой WerSvc (этап 5). Вызовом CreateProcess эта служба порождает процесс WerFault.exe (этап 6) и ожидает завершения нового процесса. Создание и отправку отчета (этап 7) выполняет приложение WerFault.exe. Диалоговые окна, в которых пользователь может выбрать между завершением приложения и подключением отладчика также выводятся в контексте приложения WerFault.exe (этап 8). Если пользователь решает закрыть программы, WerFault.exe вызывает TerminateProcess, в результате исполнение программы прерывается без дополнительных уведомлений (этап 9). Как видите, все громоздкие операции выполняются «за пределами» сбойного приложения, что обеспечивает стабильность системы и возможность создания отчетов.

Соответствующие окна настраиваются с помощью соответствующих разделов реестра, описанных в статье, доступной по этой ссылке: http://msdn2.microsoft.com/en-us/library/bb513638.aspx. Если параметр DontShowUI в разделе HKEY_CURRENT_USER\Software\Microsoft\Windows\Windows Error Reporting установлен в 1, никакие окна не открываются, система «молча» генерирует отчет и отправляет его на один из серверов Майкрософт. Чтобы позволить пользователю отменить отправку отчета, измените значение DWORD-параметра DefaultConsent в разделе Consent. Однако лучше открыть Control Panel и выбрать аплет WER Console в категории Problem Reports And Solutions. После этого щелк-

ните ссылку Change Settings — откроется окно, показанное на рис. 25-5.

806 Часть V. Структурная обработка исключений

Рис. 25-4. Обработка необработанных исключений в Windows с использованием инфраструк-

туры Windows Error Reporting

Глава 25. Необработанные исключения, векторная обработка исключений и исключения

C++.docx 807

Рис. 25-5. Включение окна, в котором пользователь может указать, следует ли отправлять в Майкрософт отчет об ошибке

Если включить параметр Ask Me To Check If A Problem Occurs, WER откроет одно окно, показанное на рис. 25-6, вместо обычных двух (см. рис. 25-2 и 25-3).

Рис. 25-6. Пользователь может отменить автоматическую отправку отчета об ошибке в Майкрософт

На «рабочих» компьютерах включать этот параметр не рекомендуется, так вам необходимо знать о возникающих ошибках. Однако при отладке отключение этого окна поможет вам сэкономить массу времени, поскольку вам не придется ждать завершения генерации и отправки отчета. Экономия будет особенно ощутимой, если компьютер не подключен к сети, поскольку в этом случае окно WER открывается только после тайм-аута WerSvc.

808 Часть V. Структурная обработка исключений

Появление окон WER также нежелательно во время автоматизированного тестирования, так при выводе окна тест прерывается. Если присвоить параметру ForceQueue из раздела Reporting системного реестра значение 1, WER будет генерировать отчеты «молча». По завершении теста вы сможете изучить ошибки и их описание с помощью консоли WER (см. рис. 26-2 и 26-3 в следующей главе).

А теперь поговорим о последней функции, которую WER предоставляет при возникновении необработанных исключений. Мечты разработчиков воплотились в механизме, названном «отладка по запросу» (just-in-time debugging). Если пользователь выбирает отладку сбойного процесса (этап 10), приложение WerFault.exe создает событие с ручным сбросом в состоянии «занято» и параметром bInheritHandles, установленным в TRUE. Это позволит дочерним процессам WerFault.exe, например, отладчику, наследователь описатель этого события. Далее WER находит и запускает отладчик по умолчанию, который подключается к сбойному процессу (см. раздел «Отладка по запросу» ниже). После подключения отладчика к процессу вы сможете изучать глобальные, локальные и статические переменные, устанавливать точки прерывания, анализировать дерево вызовов, перезапускать процесс и выполнять любые другие действия, обычные для отладки.

Примечание. Предметом этой книги является разработка программ для пользовательского режима. Однако некоторых читателей может заинтересовать, что происходит, если исключение провоцируется потоком, работающим в режиме ядра. Необработанное исключение в режиме ядра свидетельствует о серьезной ошибке в операционной системе либо в драйвере устройства или приложении (что более вероятно). Поскольку при исключении в режиме ядра велика вероятность повреждения памяти, позволить системе продолжать работу слишком рискованно. Однако перед выводом пресловутого «синего экрана смерти» система приказывает специальному драйверу CrashDmp.sys создать дамп краха (crash dump) в страничном файле, после чего работа компьютера останавливается (поэтому ее возобновление возможно только после перезагрузки) и все несохраненные данные пропадают. Во время перезапуска Windows проверяет наличие дампа краха в страничном файле. Обнаружив дамп, система сохраняет его содержимое и порождает процесс WerFault.exe, который генерирует отчет и, при желании пользователя, отправляет его на сервер Майкрософт. Эти действия также позволяют вывести список сбоев Windows в окне консоли WER.

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

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

Глава 25. Необработанные исключения, векторная обработка исключений и исключения

C++.docx 809

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

стей Windows.

А теперь немного о самой процедуре отладки. Для активизации отладчика UnhandledExceptionFilter просматривает раздел реестра:

HKEY_LOCAL_MACHINE\SOFTWARE\Micro30ft\Window8 NT\CurrentVersion\AeDebug

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

"C:\Windows\system32\vsjitdebugger.exe" -р %ld -e %ld

Строка, приведенная выше, сообщает системе, какой отладчик надо запустить

(в данном случае — vsjitdebugger.exe). На самом деле, vsjitdebugger.ехе не отлад-

чик, а программа, которая позволяет выбрать настоящий отладчик в этом окне:

Естественно, вы можете изменить это значение, указав другой отладчик WerFault.exe передает отладчику два параметра в командной строке. Первый — это идентификатор процесса, который нужно отладить, а второй -- наследуемое событие со сбросом вручную, которое создается службой WerSvc в занятом состоянии. Отладчик должен распознавать ключи и как идентификатор процесса и описатель события.

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