Структурированная обработка исключений

23 января 2012

В операционных системах семейства Windows реализован метод обработки исключительных состояний, направленный на предотвращение возможных системных сбоев. Механизм структурированной обработки исключений тесно связан с компилятором. Он легко позволяет организовать защиту секций программы – если в защищенной секции возникают какие-либо проблемы, управление передаются обработчикам исключений. В общем случае рекомендуется всегда защищать прямые ссылки на виртуальную память пользовательского режима блоками структурированных исключений.

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

В компиляторах Microsoft реализованы специфические расширения С/С++, скрывающие некоторые сложности работы с низкоуровневыми примитивами операционной системы. Команда __try обозначает составную команду как защищенный блок для кадра исключения; далее либо команда __finally назначает завершающий обработчик, либо команда __except назначает обработчик исключения.

Всегда лучше записывать слова __try __finally и __except с начальными символами подчеркивания. В модулях компиляции С заголовочный файл DDK WARNING.H определяет макросы try, finally и except как слова с символами подчеркивания. В примерах DDK имена макросов часто используются вместо имен с символами подчеркивания. Для вас такой подход может создать проблемы: в единицах компиляции C++ команда try используется в сочетании с catch для активизации совершенно иного механизма обработки исключений, реализованного на уровне языка C++. Исключения C++ не работают в драйверах, если только вам каким-то образом не удастся продублировать часть инфраструктуры из библиотеки времени выполнения. С точки зрения Microsoft, делать этого не стоит из-за увеличения объема драйвера и дополнительных затрат ресурсов, связанных с об­работкой операции throw.

Блоки Try-Finally

Начать знакомство со структурированной обработкой исключений проще всего с описания блока try-finally, используемого для определения кода зачистки:

__try

{

//защищенный фрагмент кода

}

__finally

{

// завершающий обработчик

}

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

На семантическом уровне блок try-finally работает следующим образом: сначала выполняется защищенный фрагмент. Когда управление по каким-либо причинам переедается за пределы защищенного фрагмента, происходит выполнение завершающего обработчика.

Блоки Try-Except

Другой способ использования структурированной обработки исключений основан на применении блока try-except

__try

{

//защищенный фрагмент

}

__except (<фильтрующее выражение>)

{

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

}

Защищенный фрагмент в блоке try-except содержит код, при выполнении которого может произойти исключение. Допустим, вы собираетесь вызвать сервисную Функцию режима ядра вроде MmProbeAndLockPages, эта функция использует указатели, переданные из пользовательского режима, не проверяя их на действительность. А может быть, у вас есть другие причины. Так или иначе, если весь защи­щенный фрагмент будет выполнен без ошибок, управление продолжится с точки, следующей за кодом обработчика исключения. Этот сценарий представляет нормальное течение событий. Но если в вашем коде или в любой из вызванных функции произойдет исключение, операционная система начинает раскручивать стек исполнения, проверяя фильтрующие выражения в командах __except. Результат фильтрующего выражения представляет собой одну из трех величин:

  • EXCEPTION_EXECUTE_HANDLER (числовое значение 1) - означает, что операционная система должна передать управление вашему обработчику исключения. Если выполнение обработчика благополучно доходит до завершающей фигурной скобки, управление передается команде, следующей непосредственно за скобкой (в документации Platform SDK утверждалось, что управление возвращается в точку возникновения исключения, но это неверно);
  • EXCEPTION_CONTINUE_SEARCH (числовое значение 0) – сообщает операционной системе, что Ваш код не может обработать исключение. Система продолжает перебор стека в поисках другого обработчика. Если обработчик исключения не определен, происходит системный сбой;
  • EXCEPTION_CONTINUE_EXECUTION (числовое значение -1) – инициирует в операционной системе возврат управления в точку, в которой возникло исключение.
Рейтинг@Mail.ru