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

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

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

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

EXCEPTION_RECORD содержит информацию об исключении, независимую от типа процессора, а CONTEXT — машинно-зависимую информацию об этом исключении. В структуре EXCEPTION_POWTERS всего два элемента — указатели на помещенные в стек структуры EXCEPTION_ RECORD и CONTEXT:

typedef Struct _EXCEPTION_P0INTERS { PEXCEPTION_RECORD ExceptionRecord; PCONTEXT ContextRecord;

} EXCEPTION_P0INTERS, *PEXCEPTION_POINTERS;

Чтобы получить эту информацию и использовать ее в программе, вызовите

GetExceptionInformation:

PEXCEPTION_POINTERS GetExceptionInformation();

PEXCEPTION_POINTERS GetExceptionInfornation();

Эта встраиваемая функция возвращает указатель на структуру EXCEPTION_POINTERS.

Самое важное в GetExceptionInformation то, что ее можно вызывать только в фильтре исключений и больше. Нигде, потому что структуры CONTEXT, EXCEPTION_RECORD и EXCEPTION_POINTERS существуют лишь во время обра-

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

Если вам нужно получить доступ к информации об исключении из обработчика — сохраните структуру EXCEPTION_RECORD и/или CONTEXT (на которые указывают элементы структуры EXCEPTION_POINTERS) в объявленных вами переменных. Вот пример сохранения этих структур:

void FuncSkunk() {

//объявляем переменные, которые мы сможем потом использовать

//для сохранения информации об исключении (если оно произойдет)

EXCEPTION_RECORD SavedExceptRec; CONTEXT SavedContext;

__try {

}

__except ( SavedExceptRec =

*(GetExceptionInformation())->ExceptionRecord, SavedContext =

*(GetExceptionInformation())->ContextRecord, EXCEPTION_EXECUTE_HANDLER) {

//мы можем теперь использовать переменные SavedExceptRec

//и SavedContext в блоке обработчика исключений

Глава 24. Фильтры и обработчики исключений.docx 793

switch (SavedExceptRec.ExceptionCode) {

}

}

}

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

ВFuncSkunk сначала вычисляется выражение слева, что приводит к сохранению находящейся в стеке структуры EXCEPTION_RECORD в локальной переменной SavedExceptRec. Результат этого выражения является значением SavedExceptRec. Но он отбрасывается, и вычисляется выражение, расположенное правее. Это приводит к сохранению размещенной в стеке структуры CONTEXT в локальной переменной SavedContext. И снова результат — значение SavedContext — отбрасывается, и вычисляется третье выражение. Оно равно EXCEPTION_EXECUTE_HANDLER — это и будет результатом всего выражения в скобках.

Так как фильтр возвращает EXCEPTION_EXECUTE_HANDLER, выполняется код в блоке except. К этому моменту переменные SavedExceptRec и SavedContext уже инициализированы, и их можно использовать в данном блоке. Важно, чтобы переменные SavedExceptRec и SavedContext были объявлены вне блока try.

Вероятно, вы уже догадались, что элемент ExceptionRecord структуры EXCEPTION_POINTERS указывает на структуру EXCEPTION_RECORD:

typedef struct _EXCEPTION_RECORD { DWORD ExceptionCode;

DWORD ExceptionFlags;

struct _EXCEPTION_RECORD *ExceptionRecord; PVOID ExceptionAddress;

DWORD NumberParameters;

ULONG_PTR ExceptionInformation[EXCEPTION_MAXIMUM_PARAMETERS]; } EXCEPTION_RECORD;

Структура EXCEPTION_RECORD содержит подробную машиннонезависимую информацию о последнем исключении. Вот что представляют собой ее элементы.

ExceptionCode — код исключения. Это информация, возвращаемая функцией

GetExceptionCode.

ExceptionMags — флаги исключения. На данный момент определено только два значения: 0 (возобновляемое исключение) и EXCEPTION_NONCONTINUABLE (невозобновляемое исключение). Любая попытка возоб-

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

новить работу программы после невозобновляемого исключения генерирует исключение EXCEPTION_NONCONTINUABLE_EXCEPTION.

ExceptionRecord — указатель на структуру EXCEPTION_RECORD, содержащую информацию о другом необработанном исключении. При обработке одного исключения может возникнуть другое. Например, код внутри фильтра исключений может попытаться выполнить деление на нуль. Когда возникает серия вложенных исключений, записи с информацией о них могут образовывать связанный список. Исключение будет вложенным, если оно генерируется при обработке фильтра. В отсутствие необработанных исключений ExceptionRecord равен NULL.

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

NumberParameters — количество параметров, связанных с исключением (0-15). Это число заполненных элементов в Массиве ExceptionInformation. Почти для всех исключений значение этого элемента равно 0.

ExceptionInformation — массив дополнительных аргументов, описывающих исключение. Почти для всех исключений элементы этого массива не определены.

Последние два элемента структуры EXCEPTION_RECORD сообщают фильтру дополнительную информацию об исключении. Сейчас такую информацию дает только один тип исключений: EXCEPTION_ACCESS_VIOLATION. Все остальные дают нулевое значение в элементе NumberParameters. Проверив его, вы узнаете, надо ли просматривать массив ExceptionInformation.

При исключении EXCEPTION_ACCESS_VIOLATION элемент ExceptionInformation[0] содержит флаг, указывающий тип операции, которая вызвала нарушение доступа. Если его значение равно 0, поток пытался читать недоступные ему данные; 1 — записывать данные по недоступному ему адресу. Элемент ExceptionInformation[1] определяет адрес недоступных данных. Когда защитный механизм Data Execution Prevention (DEP) обнаруживает попытку исполнения кода, хранящегося в странице, для которой исполнение запрещено, генерируется исключение и в ExceptionInformation[0] записывается 2 для IA-64 и 8 в противном случае.

Эта структура позволяет писать фильтры исключений, сообщающие значительный объем информации о работе программы. Можно создать, например, такой фильтр:

_try {

}

__except(ExpFltr(GetExceptionInformation())) {

}

Глава 24. Фильтры и обработчики исключений.docx 795

LONG ExpFltr (LPEXCEPTION_POINTERS pep) {

TCHAR szBufl300], *p;

PEXCEPTION_RECORD pER = pep->ExceptionRecord;

DWORD dwExceptionCode = pER->ExceptionCode;

StringCchPrintf(szBuf, _countof(szBuf), TEXT("Code = %x, Address = %p"),

dwExceptionCode, pER->ExceptionAddress);

// находим конец строки

p = _tcschr(szBuf, TEXT('0‟));

//я использовал оператор switch на тот случай, если Майкрософт

//в будущем добавит информацию для других исключений

switch (dwExceptionCode) {

case EXCEPTION_ACCESS_VIOLATION: StringCchPrintf(p, _countof(szBuf),

TEXT("\n Attempt to %s data at address %p"), pER->ExceptionInformation[0] ? TEXT("write") : TEXT("read"), pER->ExceptionInformation[1]);

break;

default:

break;

}

MessageBox(NULL, szBuf, TEXT("Exception"), MB_OK | MB_ICONEXCLAMATION);

return(EXCEPTION_CONTINUE_SEARCH);

}

Элемент ContextRecord структуры EXCEPTION_POTNTERS указывает на структуру CONTEXT (см. главу 7), содержимое которой зависит от типа процессора.

С помощью этой структуры, в основном содержащей по одному элементу для каждого регистра процессора, можно получить дополнительную информацию о возникшем исключении. Увы, это потребует написания машинно-зависимого кода, способного распознавать тип процессора и использовать подходящую для него структуру CONTEXT. При этом вам придется включить в код набор директив #ifdef для разных типов процессоров. Структуры CONTEXT для различных процессоров, поддерживаемых Windows, определены в заголовочном файле WinNT.h.

Программные исключения

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

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

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

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

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

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

Если вы все же решились на уведомление об ошибках через исключения, я аплодирую этому решению и пишу этот раздел специально для вас. Давайте для начала посмотрим на семейство Heap-функций (HeapCreate, HeapAlloc и т. д.) Наверное, вы помните из главы 18, что они предлагают разработчику возможность выбора. Обычно, когда их вызовы заканчиваются неудачно, они возвращают NULL, сообщая об ошибке. Но вы можете передать флаг HEAP_GENERATE_EXCEPTIONS, и тогда при неудачном вызове Heap-функция не станет возвращать NULL; вместо этого она возбудит программное исключение STATUS_NO_MEMORY, перехватываемое с помощью SEH-фрейма.

Чтобы использовать это исключение, напишите код блока try так, будто выделение памяти всегда будет успешным; затем — в случае ошибки при выполнении данной операции — вы сможете либо обработать исключение в блоке except, либо заставить функцию провести очистку, дополнив блок try блоком finally. Очень удобно!

Программные исключения перехватываются точно так же, как и аппаратные. Иначе говоря, все, что я рассказывал об аппаратных исключениях, в полной мере относится и к программным исключениям.

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

Глава 24. Фильтры и обработчики исключений.docx 797

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

Возбудить программное исключение несложно — достаточно вызвать функ-

цию RaiseException:

VOID RaiseException(

DWORD dwExceptionCode,

DWORD dwExceptionFlags,

DW0RD nNumberOfArguments,

CONST ULONG_PTR *pArguments);

Ее первый параметр, dwExceptionCode, — значение, которое идентифицирует генерируемое исключение. HeapAlloc передает в нем STATUS_NO_MEMORY. Если вы определяете собственные идентификаторы исключений, придерживайтесь формата, применяемого для стандартных кодов ошибок в Windows (файл WinError.h). Не забудьте, что каждый такой код представляет собой значение типа DWORD; его поля описаны в таблице 24-1. Определяя собственные коды исключений, заполните все пять его полей:

биты 31 и 30 должны содержать код степени «тяжести»;

бит 29 устанавливается в 1 (0 зарезервирован для исключений, определяемых Майкрософт, вроде STATUS_NO_MEMORY для HeapAlloc);

бит 28 должен быть равен 0;

биты 27-16 должны указывать один из кодов подсистемы, предопределенных Майкрософт;

биты 15-0 могут содержать произвольное значение, идентифицирующее ту часть вашего приложения, которая возбуждает исключение.

Второй параметр функции RaiseException dwExceptionFlags — должен быть либо 0, либо EXCEPTION_NONCONTINUABLE. В принципе этот флаг указывает, может ли фильтр исключений вернуть EXCEPTION_CONTINUE_EXECUTION в ответ на данное исключение. Если вы передаете в этом параметре нулевое значение, фильтр может вернуть EXCEPTION_CONTINUE_EXECUTION. В нормальной ситуации это заставило бы поток снова выполнить машинную команду, вызвавшую программное исключение. Однако Майкрософт пошла на некоторые ухищрения, и поток возобновляет выполнение с оператора, следующего за вызовом RaiseException.

Но, передав функции RaiseException флаг EXCEPTION_NONCONTINUABLE,

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

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

Если возбуждается исключение EXCEPTION_NONCONTINUABLE, а фильтр все же возвращает EXCEPTION_CONTINUE_EXECUTION, система генерирует новое исключение EXCEPTION_NONCONTINUABLE_EXCEPTION.

При обработке программой одного исключения вполне вероятно возбуждение нового исключения. И смысл в этом есть. Раз уж мы остановились на этом месте, замечу, что нарушение доступа к памяти возможно и в блоке finally, и в фильтре исключений, и в обработчике исключений. Когда происходит нечто подобное, система создает список исключений. Помните функцию GetExceptionInformation? Она возвращает адрес структуры EXCEPTION_POINTERS. Ее элемент ExceptionRecord указывает на структуру EXCEPTION_RECORD, которая в свою очередь тоже содержит элемент ExceptionRecord. Он указывает на другую структуру EXCEPTION_RECORD, где содержится информация о предыдущем исключении.

Обычно система единовременно обрабатывает только одно исключение, и элемент ExceptionRecord равен NULL. Но если исключение возбуждается при обработке другого исключения, то в первую структуру EXCEPTION_RECORD помещается информация о последнем исключении, а ее элемент ExceptionRecord указывает на аналогичную структуру с аналогичными данными о предыдущем исключении. Если есть и другие необработанные исключения, можно продолжить просмотр этого связанного списка структур EXCEPTION_RECORD, чтобы определить, как обработать конкретное исключение.

Третий и четвертый параметры (nNumberOfArguments и pArguments) функции RaiseException позволяют передать дополнительные данные о генерируемом исключении. Обычно это не нужно, и в pArguments передается NULL; тогда RaiseException игнорирует параметр nNumberOfArguments. А если вы передаете дополнительные аргументы, nNumberOfArguments должен содержать число элементов в массиве типа ULONG_PTR, на который указывает pArguments. Значение nNumberOfArguments не может быть больше EXCEPTION_MAXIMUM_PARAMETERS (в файле WinNT.h этот идентификатор опре-

делен равным 15).

При обработке исключения написанный вами фильтр — чтобы узнать значе-

ния nNumberOfArguments и pArguments — может ссылаться на элементы NumberParameters и ExceptionInformation структуры EXCEPTION_RECORD.

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

Оглавление

 

Г Л А В А 2 5 Необработанные исключения, векторная обработка исключений и

 

исключения C++ ...........................................................................................................................

799

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

802

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

805

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

808

Программа-пример Spreadsheet............................................................................................

811

Векторная обработка исключений и обработчики возобновления................................

823

Исключения C++ и структурные исключения.....................................................................

825

Исключения и отладчик..........................................................................................................

827

Г Л А В А 2 5

Необработанные исключения, векторная обработка исключений и исключения C++

В предыдущей главе мы обсудили, что происходит, когда фильтр возвращает значение EXCEPTION_CONTINUE_SEARCH. Оно заставляет систему искать дополнительные фильтры исключений, продвигаясь вверх по дереву вызовов. А что будет, если все фильтры вернут EXCEPTION_CONTINUE_SEARCH? Тогда мы по-

лучим необработанное исключение (unhandled exception).

Windows поддерживает функцию SetUnhandledExceptionFilter, которая дает последний шанс на обработку исключения, прежде чем Windows объявит его необработанным:

PTOP_LEVEL_EXCEPTION_FILTER SetUnhandledExceptionFilter(

PTOP_LEVEL_EXCEPTION_FILTER pTopLevelExceptionFilter);

Как правило, эта функция вызывается во время инициализации процесса. В результате при возникновении необработанного исключения в любом из потоков вашего процесса будет вызвана функция фильтра верхнего уровня, заданная параметром SetUnhandledExceptionFilter. Прототип функции фильтра выглядит следующим образом:

LONG WINAPI TopLevelUnhandledExceptionFilter(PEXCEPTION_POINTERS pException Info);

Функция фильтра может выполнять любые нужные вам действия, но возвращать она должна один из трех идентификаторов EXCEPTION_*, перечисленных в табл. 25-1. Учите, что состояние процесса при вызове этой функции может быть повреждено из-за переполнения стека, а также синхронизирующих объектов или блоков памяти в куче, оставшихся занятыми.

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