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

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

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

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

EXCEPTION_CONTINUE_EXECUTION

Давайте приглядимся к тому, как фильтр исключений получает один из трех идентификаторов, определенных в файле Excpt.h. В Funcmeister2 идентификатор EXCEPTION_EXECUTE_HANDLER «зашит> (простоты ради) в код самого фильтра, но вы могли бы вызывать там функцию, которая определяла бы нужный идентификатор. Взгляните:

TCHAR g_szBuffer[100];

voidFunclinRoosevelt1() { int x = 0;

TCHAR *pchBuffer = NULL;

__try {

*pchBuffer = TEXT(„J‟); x = 5 / x;

}

__except (OilFilter1(&pchBuffer)) {

MessageBox(NULL, TEXT(“An exception occurred”), NULL, MB_0K);

}

MessageBox(NULL, TEXT(“Function completed”), NULL, MB_0K);

}

LONG OilFilter1(TCHAR **ppchBuffer) { if (*ppchBuffer == NULL) {

*ppchBuffer = g_szBuffer; return(EXCEPTION_CONTINUE_EXECUTION);

}

return(EXCEPTION_EXECUTE_HANDLER);

}

В первый раз проблема возникает, когда мы пытаемся поместить J в буфер, на который указывает pchBuffer. К сожалению, мы не определили pchBuffer как указатель на наш глобальный буфер g_szBuffer — вместо этого он указывает на NULL. Процессор генерирует исключение и вычисляет выражение в фильтре исключений в блоке except, связанном с блоком try, в котором и произошло исключение. В блоке except адрес переменной pchBuffer передается функции OilFilter1.

Получая управление, OilFilter1 проверяет, не равен ли pchBuffer значению NULL, и, если да, устанавливает его так, чтобы он указывал на глобальный буфер g_szBuffer. Тогда фильтр возвращает EXCEPTION_CONTINUE_ EXECUTION.

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

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

Когда выполнение кода продолжится, мы опять столкнемся с проблемой в блоке try ~- теперь это деление на нуль. И вновь система вычислит выражение фильтра исключений. На этот раз pchBuffer не равен NULL, и поэтому OilFilter1 вернет EXCEPTION_EXECUTE_HANDLER, что подскажет системе выполнить код в блоке except, и на экране появится окно с сообщением об исключении.

Как видите, внутри фильтра исключений можно проделать массу всякой работы. Разумеется, в итоге фильтр должен вернуть один из трех идентификаторов, но он может выполнять и другие нужные вам действия. Учтите, однако, что процесс, в котором возникло исключение, может быть нестабильным, поэтому код внутри фильтра должен быть как можно более простым. Например, в случае повреждения кучи фильтр, пытающийся выполнить множество операций, может привести к зависанию либо «тихому» завершению процесса.

Будьте осторожны с EXCEPTION_CONTINUE_EXECUTION

Будет ли удачной попытка исправить ситуацию в только что рассмотренной функции и заставить систему продолжить выполнение программы, зависит от типа процессора, от того, как компилятор генерирует машинные команды при трансляции операторов С/С++, и от параметров, заданных компилятору. Компилятор мог сгенерировать две машинные команды для оператора:

*pchBuffer = TEXT(„J‟);

которые выглядят так:

MOV EAX, DWORD PTR[pchBuffer]

// адрес помещается в регистр EAX

MOV WORD PTR[EAX], 'J'

// символ J записывается по адресу

 

// из регистра EAX

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

Выполнение программы благополучно возобновится, если компилятор оптимизирует код, но может прерваться, если компилятор код не оптимизирует. Обнаружить такой «жучок» очень трудно, и — чтобы определить, откуда он взялся в программе, — придется анализировать ассемблерный текст, сгенерированный для исходного кода. Вывод: будьте крайне осторожны, возвращая EXCEPTION_CONTINUE_EXECUTION из фильтра исключений.

EXCEPTION_CONTINUE_EXECUTION всегда срабатывает лишь в од-

ной ситуации: при передаче памяти зарезервированному региону. О том, как зарезервировать большую область адресного пространства, а потом передавать ей память лишь по мере необходимости, я рассказывал в главе 15.

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

Соответствующий алгоритм демонстрировала программа-пример VMAlloc. На основе механизма SEH то же самое можно было бы реализовать гораздо эффективнее (и не пришлось бы все время вызывать функцию VirtualAlloc).

В главе 16 мы говорили о стеках потоков. В частности, я показал, как система резервирует для стека потока регион адресного пространства размером 1 Мб и как она автоматически передает ему новую память по мере разрастания стека. С этой целью система создает SEH-фрейм. Когда поток пытается задействовать несуществующую часть стека, генерируется исключение. Системный фильтр определяет, что исключение возникло из-за попытки обращения к адресному пространству, зарезервированному под стек, вызывает функцию VirtualAlloc для передачи дополнительной памяти стеку потока и возвращает EXCEPTION_CONTINUE_EXECUTION. После этого машинная команда, пытавшаяся обратиться к несуществующей части стека, благополучно выполняется, и поток продолжает свою работу.

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

EXCEPTION_CONTINUE_SEARCH

Приведенные до сих пор примеры были ну просто детскими. Чтобы немного встряхнуться, добавим вызов функции:

TCHAR g_szBuffer[100];

void FuncllnRoosevelt2() { TCHAR *pchBuffer = NULL;

__try { FuncAtude2(pchBuffer);

}

__except (OilFilter2(&pchBuffer)) { MessageBox(…);

}

}

void FuncAtude2(TCHAR *sz) { *sz = TEXT('\0');

}

LONG OilFilter2 (TCHAR **ppchBuffer) { if (*ppchBuffer == NULL) {

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

*ppchBuffer = g_szBuffer; return(EXCERTION_CONTINUE_EXECUTION);

}

return(EXCEPTION_EXECUTE_HANDLER);

}

При выполнении FunclinRoosevelt2 вызывается FuncAtude2, которой передается NULL. Последняя приводит к исключению. Как и раньше, система проверяет выражение в фильтре исключений, связанном с последним исполняемым блоком try. В нашем примере это блок try в FunclinRoosevelt2, поэтому для оценки выражения в фильтре исключений система вызывает OilFilter2 (хотя исключение воз-

никло в FuncAtude2).

Замесим ситуацию еще круче, добавив другой блок try-except.

TCHAR g_szBuffer[100];

void FunclinRoosevelt3() {

TCHAR *pchBuffer = NULL;

__try { FuncAtude3(pchBuffer);

}

__except (OilFilter3(&pchBuffer)) { MessageBox(…);

}

}

void FuncAtude3(TCHAR *sz) { _try {

*sz = TEXT('\0');

}

__except (EXCEPTION_CONTINUE_SEARCH) { // этот код никогда не выполняется

}

}

LONG OilFilter3(TCHAR **ppchBuffer) { if (*ppchBuffer == NULL) {

*ppchBuffer = g_szBuffer; return(EXCEPTION_CONTINUE_EXECUTION);

}

return(EXCEPTION_EXECUTE_HANDLER);

}

Теперь, когда FuncAtude3 пытается занести 0 по адресу NULL, по-прежнему возбуждается исключение, но в работу вступает фильтр исключений из

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

FuncAtude3. Значение этого очень простого фильтра — EXCEPTION_CONTINUE_SEARCH. Данный идентификатор указывает системе перейти к предыдущему блоку try, которому соответствует блок except, и обработать его фильтр.

Так как фильтр в FuncAtude3 дает EXCEPTION_CONTINUE_SEARCH, систе-

ма переходит к предыдущему блоку try(B функции FunclinRoosevelt3) и вычисляет его фильтр OUMlter3. Обнаружив, что значение pchBuffer равно NULL, OilFilter3 меняет его так, чтобы оно указывало на глобальный буфер, и сообщает системе возобновить выполнение с инструкции, вызвавшей исключение. Это позволяет выполнить код в блоке try функции FuncAtude3, но, увы, локальная переменная sz в этой функции не изменена, и возникает новое исключение. Опять бесконечный цикл! OilFilter3 увидит, что pchBuffer не равен NULL и вернет EXCEPTION_EXECUTE_HANDLER, позволив системе возобновить исполнение с блока except Таким образом, код в блоке except функции FunclinRoosevelt3 будет исполнен.

Заметьте, я сказал, что система переходит к последнему исполнявшемуся блоку try, которому соответствует блок except, и проверяет его фильтр. Это значит, что система пропускает при просмотре цепочки блоков любые блоки try, которым соответствуют блоки finally (а не except). Причина этого очевидна: в блоках finally нет фильтров исключений, а потому и проверять в них нечего. Если бы в последнем примере FuncAtude3 содержала вместо except блок finally, система начала бы проверять фильтры исключений с OilFilter3 в FunclinRoosevelt3.

Дополнительную информацию об EXCEPTION_CONTINUE_SEARCH см. в главе 25.

Функция GetExceptionCode

Часто фильтр исключений должен проанализировать ситуацию, прежде чем определить, какое значение ему вернуть. Например, ваш обработчик может знать, что делать при делении на нуль, но не знать, как обработать нарушение доступа к памяти. Именно поэтому фильтр отвечает за анализ ситуации и возврат соответствующего значения.

Этот фрагмент иллюстрирует метод, позволяющий определять тип исключения:

_try {

x = 0;

У = 4 / x; // переменная у используется, поэтому она осталась в коде

}

__except((GetExceptionCode() == EXCEPTION_INT_DIVIDE_BY_ZER0) ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH) {

// обработка деления на нуль

}

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

Встраиваемая функция GetExceptionCode возвращает идентификатор типа исключения:

DWORD GetExceptionCode();

Ниже приведен список всех предопределенных идентификаторов исключений с пояснением их смысла (информация взята из документации Platform SDK). Эти идентификаторы содержатся в заголовочном файле WmBase.h. Я сгруппировал исключения по категориям.

Исключений, связанные с памятью

EXCEPTION_ACCESS_VIOLATION. Поток пытался считать или записать по виртуальному адресу, не имея на то необходимых прав. Это самое распространенное исключение.

EXCEPTION_DATATYPE_MISALIGNMENT. Поток пытался считать или записать невыровненные данные на оборудовании, которое не поддерживает автоматическое выравнивание. Например, 16-битные значения должны быть выровнены по двухбайтовым границам, 32-битные — по четырехбайтовым и т. д.

EXCEPTION_ARRAY_BOUNDS_EXCEEDED. Поток пытался обратиться к элементу массива, индекс которого выходит за границы массива; при этом оборудование должно поддерживать такой тип контроля.

EXCEPTION_IN_PAGE_ERROR. Ошибку страницы нельзя обработать, так как файловая система или драйвер устройства сообщили об ошибке чтения.

EXCEPTION_GUARD_PAGE. Поток пытался обратиться к странице памяти с атрибутом защиты PAGE_GUARD. Страница становится доступной, и генерируется данное исключение.

EXCEPTION_STACK_OVERFLOW. Стек, отведенный потоку, исчерпан.

EXCEPTION_ILLEGAL_INSTRUCTION. Поток выполнил недопустимую инструкцию. Это исключение определяется архитектурой процессора; можно ли перехватить выполнение неверной инструкции, зависит от типа процессора.

EXCEPTION_PRIV_INSTRUCTION. Поток пытался выполнить инструкцию, недопустимую в данном режиме работы процессора.

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

EXCEPTION_INVALID_DISPOSITION. Фильтр исключений вернул значение, отличное от EXCEPTION_EXECUTE_HANDLER, EXCEPTION_CONTINUE_SEARCH или EXCEPTION_CONTINUE_EXECUTION.

EXCEPTION_NONCONTINUABLE_EXCEPTION. Фильтр исключений вер-

нул EXCEPTION_CONTINUE_EXECUTION в ответ на невозобновляемое ис-

ключение (noncontinuable exception).

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

Исключения, связанные с отладкой

EXCEPTION_BREAKPOINT. Встретилась точка прерывания (останова).

EXCEPTION_SINGLE_STEP. Трассировочная ловушка или другой механизм пошагового исполнения команд подал сигнал о выполнении одной команды.

EXCEPTION_INVALID_HANDLE. В функцию передан недопустимый описатель.

Исключения, связанные с операциями над целыми числами

EXCEPTION_INT_DIVIDE_BY_ZERO. Поток пытался поделить число целого типа на делитель того же типа, равный 0.

EXCEPTION_FLOAT_OVERFLOW. Операция над целыми числами вызвала перенос старшего разряда результата.

Исключения, связанные с операциями над вещественными числами

EXCEPTION_FLT_DENORMAL_OPERAND Один из операндов в операции над числами с плавающей точкой (вещественного типа) не нормализован. Ненормализованными являются значения, слишком малые для стандартного представления числа с плавающей точкой.

EXCEPTION_FLT_DIVIDE_BY_ZERO. Поток пытался поделить число вещественного типа на делитель того же типа, равный 0.

EXCEPTION_FLT_INEXACT_RESULT. Результат операции над числами с плавающей точкой нельзя точно представить в виде десятичной дроби.

EXCEPTION_FLT_INVALID_OPERATION. Любое другое исключение, от-

носящееся к операциям над числами с плавающей точкой и не включенное в этот список.

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

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

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

Встраиваемую функцию GetExceptionCode можно вызвать только из фильтра исключений (между скобками, которые следуют за except) или из обработчика исключений. Скажем, такой код вполне допустим:

__try {

y = 0;

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

x = 4 / y;

}

__except (

((GetExceptionCode() (EXCEPTION_ACCESS_VIOLATION) || (GetExceptionCode() == EXCEPTION_INT_DIVIDE_BY_ZERO)) ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH) {

switch (GetExceptionCode()) {

case EXCEPTION_ACCESS_VIOLATION:

// обработка нарушения доступа к памяти

break;

саsе EXCEPTION_INT_DIVIDE_BY_ZERO:

// обработка деления целого числа на нуль

break;

}

}

Однако GetExceptionCode нельзя вызывать из функции фильтра исключений. Компилятор помогает вылавливать такие ошибки и обязательно сообщит о таковой, если вы попытаетесь скомпилировать, например, следующий код:

__try {

y = 0;

x = 4 / у;

}

__except (CoffeeFilter()) {

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

}

LONG CoffeeFilter (void) {

// ошибка при компиляции: недопустимый вызов GetExceptionCode return((GetExceptionCode() == EXCEPTION_ACCESS_VIOLATION) ?

EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH);

}

Нужного эффекта можно добиться, переписав код так:

_try {

y = 0;

x = 4 / у;

}

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

__except (CoffeeFilter(GetExceptionCode())) {

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

}

LONG CoffeeFilter (DWORD dwExceptionCode) { return((dwExceptionCode == EXCEPTION_ACCESS_VIOLATION) ?

EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH);

}

Коды исключений формируются по тем же правилам, что и коды ошибок, определенные в файле WinError.h. Каждое значение типа DWORD разбивается на поля, как показано в таблице 24-2.

Табл. 24-2. Поля кода ошибки

Биты

 

31-30

29

28

27-16

15

Содержимое:

Код степени

Кем опреде-

Зарезервирован

Код подсис-

Код исключе-

 

«тяжести» (sever-

лен — Майк-

 

темы (facility

ния

 

ity)

рософт или

 

code)

 

 

 

 

пользователем

 

 

 

Значение

0

= успех

0 = Майкро-

Должен быть 0

Определяется

Определяется

 

1

= информация

софт

 

Майкрософт

Майкрософт

 

 

 

1 = пользова-

 

 

 

 

 

 

тель

 

 

 

или

2

= предупреж-

 

(см. таблицу

 

пользователем

 

дение

 

ниже)

 

 

 

3

= ошибка

 

 

 

 

На сегодняшний день определены такие коды подсистемы.

Табл. 24-3. Коды подсистемы

Код подсистемы

Значение

Код подсистемы

Значение

FACILITY_RPC

1

FACILITY_HTTP

25

FACIUTY_DISPATCH

2

FACILITY_USERMODE_ COMMONLOG

26

FACILITY_STORAGE

3

FACILITY_USERMODE_FILTER_MANAGER

31

FACILITY_ITF

4

FACILITY_BACKGROUNDCOPY

32

FACILITY_WIN32

7

FACILITY_CONFIGURATION

33

FACILITY_WINDOWS

8

FACILITY_STATE_MANAGEMENT

34

FACILITY_SECURITY

9

FACILITY METADIRECTORY

35

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

Табл. 24-3. (окончание)

Код подсистемы

Значение

Код подсистемы

Значение

FACILITY_CONTROL

10

FACILITY_WINDOWSUPDATE

36

FACILITY_CERT

11

FACILITY_DIRECTORYSERVICE

37

FACILITY_INTERNET

12

FACILITY_GRAPHICS

38

FACILITY_MEDIA_SERVER

13

FACILITY_SHELL

39

FACILITY_MSMQ

14

FACILITY_TPM_SERVICES

40

FACILITY_SETUPAPI

15

FACILITY_TPM_SOFTWARE

41

FACILITY_SCARD

16

FACILITY_PLA

48

FACILITY_COMPLUS

17

FACILITY_FVE

49

FACILITY_AAF

18

FACILITY_FWP

50

FACILITY_URT

19

FACILITY_WINRM

51

FACILITY_ACS

20

FACILITY_NDIS

52

FACILITY_DPLAY

21

FACILITY_USERMODE_HYPERVISOR

53

FACILITY_UMI

22

FACILITY_CMI

54

FACILITY_SXS

23

FACILITY_WINDOWS_DEFENDER

80

Разберем

на

 

части,

например,

код

исключения

EXCEP-

TION_ACCESS_VIOLATION. Если вы посмотрите его значение в файле

WmBase.h, то увидите, что оно равно 0xC0000005:

 

 

С

0

0

0

0

0

0

5

(в шестнадцатеричном виде)

 

1100

0000

0000

0000

0000

0000

0000

0101

(в двоичном виде)

 

Биты 30 и 31 установлены в 1, указывая, что нарушение доступа является ошибкой (поток не может продолжить выполнение). Бит 29 равен 0, а это значит, что данный код определен Майкрософт. Бит 28 равен 0, так как зарезервирован на будущее. Биты 16-27 равны 0, сообщая код подсистемы FACILITY_NULL (нарушение доступа может произойти в любой подсистеме операционной системы, а не в какой-то одной). Биты 0-15 дают значение 5, которое означает лишь то, что Майкрософт присвоила исключению, связанному с нарушением доступа, код 5.

Функция GetExceptionInformation

Когда возникает исключение, операционная система заталкивает в стек соответ-

ствующего потока структуры EXCEPTION_RECORD, CONTEXT и EXCEPTION_POINTERS.

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