Как компилятор C++ реализует обработку исключений - Структурная обработка исключений - обзор

ОГЛАВЛЕНИЕ

 

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

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

Windows определяет специальную структуру для регистрации, называемую EXCEPTION_REGISTRATION:struct EXCEPTION_REGISTRATION

{
   EXCEPTION_REGISTRATION *prev;
   DWORD handler;
};

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

mov FS:[0], exc_regp

поле prev обозначает связанный список структур EXCEPTION_REGISTRATION. При регистрации структуры EXCEPTION_REGISTRATION адрес ранее зарегистрированной структуры сохраняется в поле prev.

Как выглядит функция обратного вызова исключения? Windows требует, чтобы сигнатура обработчика исключения, определенная в EXCPT.h, представляла собой:

 EXCEPTION_DISPOSITION (*handler)(
    _EXCEPTION_RECORD *ExcRecord,
    void * EstablisherFrame,
    _CONTEXT *ContextRecord,
    void * DispatcherContext);

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

 #include <iostream>
#include <windows.h>

using std::cout;
using std::endl;


struct EXCEPTION_REGISTRATION
{
   EXCEPTION_REGISTRATION *prev;
   DWORD handler;
};


EXCEPTION_DISPOSITION myHandler(
    _EXCEPTION_RECORD *ExcRecord,
    void * EstablisherFrame,
    _CONTEXT *ContextRecord,
    void * DispatcherContext)
{
    cout << "В обработчике исключения" << endl;
    cout << "Только демо. выходим..." << endl;
    exit(0);
    return ExceptionContinueExecution; //не дойдет досюда
}

int g_div = 0;

void bar()
{
    //инициализируем структуры EXCEPTION_REGISTRATION
    EXCEPTION_REGISTRATION reg, *preg = &reg;
    reg.handler = (DWORD)myHandler;
   
    //получаем текущую голову цепочки обработки исключений   
    DWORD prev;
    _asm
    {
        mov EAX, FS:[0]
        mov prev, EAX
    }
    reg.prev = (EXCEPTION_REGISTRATION*) prev;
   
    //регистрируем ее!
    _asm
    {
        mov EAX, preg
        mov FS:[0], EAX
    }

    //генерируем исключение
    int j = 10 / g_div;  //Исключение. Деление на 0.
}

int main()
{
    bar();
    return 0;
}

/*-------вывод-------------------
В обработчике исключения
Только демо. Выходим...
---------------------------------*/


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