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

Структурированная обработка исключений (англ. SEH — Structured Exception Handling) — механизм обработки программных и аппаратных исключений в операционной системе Microsoft Windows, позволяющий программистам контролировать обработку исключений, а также являющийся отладочным средством[1].

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

править

Исключение — это событие при выполнении программы, которое приводит к её ненормальному или неправильному поведению. Существует два вида исключений: аппаратные, которые генерируются процессором, и программные, генерируемые операционной системой и прикладными программами. Механизм структурной обработки исключений позволяет однотипно обрабатывать как программные, так и аппаратные исключения.

Реализация

править

Ключевые слова

править

Механизм поддерживается Microsoft только на уровне компилятора с помощью реализации нестандартных синтаксических конструкций __try, __except и __finally. Ключевое слово __try используется для выделения участка кода, в котором генерация исключения будет обработана одним или несколькими блоками __except. Код, находящийся в блоке __finally, выполнится всегда и независимо от других блоков __try и __except[2].

Пример использования в языках C и C++

__try {
	// защищенный код,
	// который помещается в SEH-фрейм
}
__except (фильтр исключений) {
	// обработчик исключений
}
__finally {
        // выполняющийся в любом случае код
}

В качестве фильтра исключений могут выступать обычные функции, возвращающие три константных выражения:[3]

  • EXCEPTION_EXECUTE_HANDLER — указывает на возможность данного обработчика обработать исключение. При получении такого значения операционная система прекращает поиск релевантных обработчиков исключения и, выполнив раскрутку стека, передаёт управление первому, вернувшему значение EXCEPTION_EXECUTE_HANDLER

  • EXCEPTION_CONTINUE_EXECUTION — указывает на исправление ошибки. Система снова передаст управление на инструкцию, которая вызвала исключение, поскольку предполагается, что в этот раз она не вызовет исключение.[4]
  • EXCEPTION_CONTINUE_SEARCH — указывает, что подходящий обработчик может быть найден выше по стеку. В то же время возвращение этого значения может быть свидетельством того, что ошибка не обработана.[3]

Используемые структуры и механизмы

править

Каждый поток любого процесса использует регистр (16-разрядный селектор) fs для хранения указателя на структуру данных Thread Information Block, содержащей информацию об этом потоке. В этой структуре хранится указатель на последнюю из связанного списка зарегистрированную структуру _EXCEPTION_REGISTRATION_RECORD, включающую указатель на обработчик исключения и указатель на предыдущую запись _EXCEPTION_REGISTRATION_RECORD.[5] При создании потока операционная система добавляет обработчик исключения по умолчанию, вызываемый функцией kernel32!UnhandledExceptionFilter.

Прототип callback функции-обработчика следующий:

EXCEPTION_DISPOSITION
 __cdecl _except_handler(
     struct _EXCEPTION_RECORD *ExceptionRecord,
     void * EstablisherFrame,
     struct _CONTEXT *ContextRecord,
     void * DispatcherContext
     );

Каждый раз, когда программист использует конструкцию __try, происходит добавление нового экземпляра структуры _EXCEPTION_REGISTRATION_RECORD, указывающей на функцию _except_handler3 библиотеки msvcrt.dll, в стек потока. Код, заключающийся в блоках __except и __finally вызывается из _except_handler3. В конце блока __try компилятор добавляет код, который удаляет текущую запись _EXCEPTION_REGISTRATION_RECORD и восстанавливает значение указателя fs:0 на предыдущую запись.

Когда происходит исключение, система последовательно перебирает всю цепочку обработчиков прерываний. Каждый обработчик возвращает значение, указывающее на то, может ли он обработать это исключение или нет. Указателем конца списка доступных обработчиков исключения является значение FFFFFFFF, располагаемое в стеке за последним обработчиком. Если система находит нужный обработчик, то управление передаётся ему. При этом, после нахождения релевантного обработчика возникшего исключения, операционная система не сразу передаёт ему управление, а ещё раз последовательно вызывает все обработчики по цепочке с флагом EH_UNWINDING для проведения очистки (вызова деструктора).[4] Если ни один из установленных программистом фильтров обработчиков исключений не вернул EXCEPTION_EXECUTE_HANDLER или EXCEPTION_CONTINUE_EXECUTION, то происходит выполнение UnhandledExceptionFilter — фильтра обработчика исключений по умолчанию, который регистрируется при подготовке потока к запуску.

Вызов обработчика

править

При возникновении исключения операционная система не вызывает напрямую фильтр исключений (который отвечает за то, будет ли конкретный обработчик обрабатывать возникшее исключение или нет), а передаёт его адрес функции _except_handler3, откуда и вызывается функция-фильтр. Она использует следующую структуру данных:[6]

struct _EXCEPTION_REGISTRATION{
     struct _EXCEPTION_REGISTRATION *prev;
     void (*handler)(PEXCEPTION_RECORD,
                     PEXCEPTION_REGISTRATION,
                     PCONTEXT,
                     PEXCEPTION_RECORD);
     struct scopetable_entry *scopetable;
     int trylevel;
     int _ebp;
     PEXCEPTION_POINTERS xpointers;
};

Поле *scopetable указывает на адрес массива структур scopetable_entry, а целочисленное поле trylevel — индекс в этом массиве. Поле _ebp содержит значение указателя кадра стека, существовавшего до создания структуры EXCEPTION_REGISTRATION.[7] Функция _except_handler3 вызывает нужный фильтр и до вызова обработчика производит раскрутку (очистку) стека функцией ntdll.dll!RtlUnwind.

Если ни один из установленных программистом обработчиков не согласился обработать исключение, то вызывается функция UnhandledExceptionFilter, которая проверяет, запущен ли процесс под отладчиком, и информирует его, если он доступен.[7] После этого функция вызывает фильтр умалчиваемого обработчика (который устанавливается функцией SetUnhandledExceptionFilter и который всегда возвращает EXCEPTION_EXECUTE_HANDLER).[7] Затем, в зависимости от настроек операционной системы, вызывается либо отладчик, либо функция NtRaiseHardError, которая отображает сообщение об ошибке.[7]

Примечания

править
  1. Structured Exception Handling (Windows). Дата обращения: 5 мая 2010. Архивировано 25 сентября 2010 года.
  2. About structured Exception Handling (Windows). Дата обращения: 5 мая 2010. Архивировано 28 февраля 2011 года.
  3. 1 2 Введение в обработку структурированных исключений SEH. Дата обращения: 26 декабря 2012. Архивировано из оригинала 27 марта 2014 года.
  4. 1 2 WASM.IN Win32 SEH изнутри (ч.1). Дата обращения: 5 апреля 2018. Архивировано 5 апреля 2018 года.
  5. Эксплуатирование SEH в среде Win32. Дата обращения: 1 мая 2010. Архивировано 24 сентября 2015 года.
  6. WASM.IN Win32 SEH изнутри (ч.2). Дата обращения: 5 апреля 2018. Архивировано 5 апреля 2018 года.
  7. 1 2 3 4 WASM.IN Win32 SEH изнутри (ч.3). Дата обращения: 5 апреля 2018. Архивировано 5 апреля 2018 года.