Разница между исключением C++ и структурированным исключением

Может кто-нибудь объяснить разницу между исключением C++ и структурированным исключением в MFC?

6 ответов

Решение

На самом деле у вас есть три механизма:

  • C++ исключения, реализованные компилятором (try/catch)
  • Структурная обработка исключений (SEH), предоставляемая Windows (__try / __except)
  • Макросы исключений MFC (TRY, CATCH - построен на основе исключений SEH / C++ - см. также комментарий TheUndeadFish)

Исключения в C++ обычно гарантируют автоматическую очистку при разматывании стека (т. Е. Работают деструкторы локальных объектов), другие механизмы этого не делают.

Исключения C++ возникают только тогда, когда они явно выбрасываются. Структурированные исключения могут возникать для многих операций, например, из-за неопределенного поведения, передачи недопустимых указателей в API, размонтирования резервного хранилища файла, отображенного в памяти, и многих других.

MFC действительно представил макросы исключений для поддержки исключений, даже если компиляторы их не реализовали.

Это сложная деталь реализации, но в Windows исключение C++ также является исключением SEH. Код исключения: 0xE04D5343 (последние три байта = 'MSC'). И вся обычная поддержка SEH используется для размотки стека, запуска кода автоматической очистки и фильтрации исключения, чтобы было выбрано правильное предложение catch. Получение брошенного объекта исключения в выражении фильтра - это добавление, добавляемое CRT, помимо того, что предоставляет SEH. SEH также поддерживает предложение __finally, но оно не используется в стандарте C++.

Еще одна деталь реализации - настройка компилятора /EH. Значение по умолчанию (/EHsc) позволяет компилятору оптимизировать сгенерированный код и подавить фильтры исключений, необходимые для запуска автоматической очистки. Если он может видеть, что ни один из испускаемого кода C++ не может вызвать исключение. Это прежде всего оптимизация пространства, небольшая оптимизация времени для кода x86, но не для кода x64. Чтобы получить автоматическую очистку для исключений SEH, вы должны скомпилировать с /EHa, чтобы эта оптимизация была подавлена.

Хорошей стратегией объединения исключений C++ с SEH является использование _set_se_translator(), чтобы вы могли преобразовать исключение SEH в исключение C++. Хотя не всегда разумно ловить исключения из SEH, они почти всегда противны.

Исключение C++ - это особенность языка программирования C++. Структурированное исключение - это другая концепция операционной системы Windows. Эти два используют похожий синтаксис, но технически различны. Структурированные исключения Windows могут использоваться не только с C++, но и, например, с C.

Иногда решение объединяет обработку обоих: в приложении Windows вы можете предоставить функцию-обработчик, которая перехватывает все структурированные исключения и генерирует исключение C++ (определенное вами).

Оба предоставляют механизмы для раскрутки стека при возникновении ошибок.

Структурированные исключения предоставляются Windows при поддержке ядра. Они вызываются Windows, если вы делаете такие вещи, как доступ к неверной ячейке памяти. Они также используются для поддержки таких функций, как автоматический рост стека. Они используются довольно редко сами по себе, но языковые исключения в C++, .NET и подобных языках часто строятся поверх них. Вы используете специальные ключевые слова, такие как __try а также __catch иметь дело с этими исключениями. Однако работать с ними сравнительно сложно и подвержено ошибкам, потому что вы можете нарушить такие функции, как автоматическое расширение стека, а также потенциальные нарушения исключений языка C++.

Исключения C++ определяются языком C++. Типы данных, которые выбрасываются и перехватываются, являются объектами C++ (включая возможность примитивных типов). Компилятор и среда выполнения реализуют их поверх базового механизма структурированных исключений. Это то, что вы получаете, если вы используете try, catch а также throw ключевые слова языка C++.

Исключения SEH имеют больше возможностей, чем исключения C++, такие как поддержка возобновления и так называемые "vectored" обработчики (которые получают уведомления об исключениях, но не обязательно предотвращают разматывание стека), но если вы точно не знаете, что хотите их использовать, я избегать их Вероятно, наиболее распространенным из них является написание аварийного дампа с использованием MiniDumpWriteDump, если ваша программа делает что-то недопустимое или неопределенное.

C++ исключения будут работать кроссплатформенно. К сожалению, SEH серьезно ограничивает переносимость (за исключением того, что может быть в разных версиях Windows).

Кроме того, SEH, похоже, фиксирует множество собственных исключений Windows (таких как Access Violation, был указан указатель Invalid) и т. Д.

В Windows C++ исключения и SEH в основном одно и то же. Исключение C++ — это лишь одно из многих возможных исключений SEH, которые могут быть сгенерированы. Часть кидается из железа, часть из софта. С++ - это исключение, вызванное программным обеспечением. Вот очень классная запись в блоге от Рэймонда Чена, которая поможет вам немного глубже заглянуть в кроличью нору исключений SEH и понять, как на их основе строятся исключения C++.

Вот моя практическая реализация метода из статьи выше. Я на самом деле в шоке, чем через 12 лет и:

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

он по-прежнему работает на последней версии Windows 10 в 2022 году! Мое решение также содержит полную информацию, извлеченную из исключения, поэтому теперь вам даже не понадобится отладчик, просто поймайте исключение с помощью__try\__exceptи используйте эту функцию для печати информации для любого исключения:

      #define EXCEPTION_CPP_LOWERCASE 0xE06D7363
#define EXCEPTION_CPP_UPPERCASE 0xE04D5343

void pexcept(DWORD c, _EXCEPTION_RECORD *er)
{
    cout << "--------------------------------------------------------------------" << endl;
    switch(c)
    {
    case STILL_ACTIVE:
        cout << "STILL_ACTIVE" << endl;
        break;
    case EXCEPTION_ACCESS_VIOLATION:
        if(er->ExceptionInformation[0] == 0)
        {
            cout << "READ ";
        }
        else if(er->ExceptionInformation[0] == 1)
        {
            cout << "WRITE ";
        }
        else if(er->ExceptionInformation[0] == 8)
        {
            cout << "DEP "; // Data Execution Prevention
        }
        cout << "ACCESS_VIOLATION" << endl;
        break;
    case EXCEPTION_DATATYPE_MISALIGNMENT:
        cout << "DATATYPE_MISALIGNMENT" << endl;
        break;
    case EXCEPTION_BREAKPOINT:
        cout << "BREAKPOINT" << endl;
        break;
    case EXCEPTION_SINGLE_STEP:
        cout << "SINGLE_STEP" << endl;
        break;
    case EXCEPTION_ARRAY_BOUNDS_EXCEEDED:
        cout << "ARRAY_BOUNDS_EXCEEDED" << endl;
        break;
    case EXCEPTION_FLT_DENORMAL_OPERAND:
        cout << "FLOAT_DENORMAL_OPERAND" << endl;
        break;
    case EXCEPTION_FLT_DIVIDE_BY_ZERO:
        cout << "FLOAT_DIVIDE_BY_ZERO" << endl;
        break;
    case EXCEPTION_FLT_INEXACT_RESULT:
        cout << "FLOAT_INEXACT_RESULT" << endl;
        break;
    case EXCEPTION_FLT_INVALID_OPERATION:
        cout << "FLOAT_INVALID_OPERATION" << endl;
        break;
    case EXCEPTION_FLT_OVERFLOW:
        cout << "FLOAT_OVERFLOW" << endl;
        break;
    case EXCEPTION_FLT_STACK_CHECK:
        cout << "FLOAT_STACK_CHECK" << endl;
        break;
    case EXCEPTION_FLT_UNDERFLOW:
        cout << "FLOAT_UNDERFLOW" << endl;
        break;
    case EXCEPTION_INT_DIVIDE_BY_ZERO:
        cout << "INTEGER_DIVIDE_BY_ZERO" << endl;
        break;
    case EXCEPTION_INT_OVERFLOW:
        cout << "INTEGER_OVERFLOW" << endl;
        break;
    case EXCEPTION_PRIV_INSTRUCTION:
        cout << "PRIVILEGED_INSTRUCTION" << endl;
        break;
    case EXCEPTION_IN_PAGE_ERROR:
        if(er->ExceptionInformation[0] == 0)
        {
            cout << "READ ";
        }
        else if(er->ExceptionInformation[0] == 1)
        {
            cout << "WRITE ";
        }
        else if(er->ExceptionInformation[0] == 8)
        {
            cout << "DEP "; // Data Execution Prevention
        }
        cout << "IN_PAGE_ERROR" << endl;
        cout << "DATA VADDRESS:       " << er->ExceptionInformation[1] << endl;
        cout << "NTSTATUS CAUSE:      " << er->ExceptionInformation[2] << endl;
        break;
    case EXCEPTION_ILLEGAL_INSTRUCTION:
        cout << "ILLEGAL_INSTRUCTION" << endl;
        break;
    case EXCEPTION_NONCONTINUABLE_EXCEPTION:
        cout << "NONCONTINUABLE_EXCEPTION" << endl;
        break;
    case EXCEPTION_STACK_OVERFLOW:
        cout << "STACK_OVERFLOW" << endl;
        break;
    case EXCEPTION_INVALID_DISPOSITION:
        cout << "INVALID_DISPOSITION" << endl;
        break;
    case EXCEPTION_GUARD_PAGE:
        cout << "GUARD_PAGE_VIOLATION" << endl;
        break;
    case EXCEPTION_INVALID_HANDLE:
        cout << "INVALID_HANDLE" << endl;
        break;
    // case EXCEPTION_POSSIBLE_DEADLOCK:
        // cout << "POSSIBLE_DEADLOCK" << endl;
        // break;
    case CONTROL_C_EXIT:
        cout << "CONTROL_C_EXIT" << endl;
        break;
    case EXCEPTION_CPP_LOWERCASE:
    case EXCEPTION_CPP_UPPERCASE:
        cout << "CPP_EXCEPTION" << endl;
        cout << "PARAMS NUM: " << er->NumberParameters << endl;
        for(ui64 i = 0; i < er->NumberParameters; ++i)
        {
            cout << dec << "PARAM" << setw(2) << left << i << " ";
            switch(i)
            {
            case 0:
                cout << "      SOME INTERNAL VALUE";
                break;
            case 1:
                cout << "    POINTER TO THROWN OBJ";
                break;
            case 2:
                cout << "    POINTER TO OBJECT INF";
                break;
            case 3:
                cout << "DLL/EXE THROWER HINSTANCE";
                break;
            }
            
            cout << ": 0x" << hex
                << uppercase << er->ExceptionInformation[i] << endl;
        }
    {
        ui64 hinst = er->ExceptionInformation[3];
        DWORD *obj_inf = (DWORD *)er->ExceptionInformation[2];
        cout << hex << uppercase << obj_inf[0] << endl;
        cout << obj_inf[1] << endl;
        cout << obj_inf[2] << endl;
        cout << obj_inf[3] << endl;
        cout << "-----------------------------" << endl;
        obj_inf = (DWORD *)(hinst + obj_inf[3]);
        cout << obj_inf[0] << endl;
        cout << obj_inf[1] << endl;
        cout << "-----------------------------" << endl;
        obj_inf = (DWORD *)(hinst + obj_inf[1]);
        cout << obj_inf[0] << endl;
        cout << obj_inf[1] << endl;
        cout << "-----------------------------" << endl;
        ui64 *class_inf = (ui64 *)(hinst + obj_inf[1]);
        cout << class_inf[0] << endl;
        cout << class_inf[1] << endl;
        cout << class_inf[2] << endl;
        char *class_name = (char *)(class_inf + 2);
        
        cout << class_name << endl;
    }   
        
        cout << "CURRENT EXE HIN:  " << hex << uppercase << "0x" << GetModuleHandle(NULL) << endl;
        cout << "UCRTBASE HIN:     " << hex << uppercase << "0x" << LoadLibrary(L"ucrtbase.dll") << endl;
        cout << "VCRUNTIME140 HIN: " << hex << uppercase << "0x" << LoadLibrary(L"vcruntime140.dll") << endl;
        cout << "STD MSVCP140 HIN: " << hex << uppercase << "0x" << LoadLibrary(L"msvcp140.dll") << endl;
        break;
    default:
        cout << "UNKNOWN_EXCEPTION [" << hex << uppercase << c << "]" << endl;
    }
    
    cout << "CONTINUE EXECUTION:  "
        << (er->ExceptionFlags == EXCEPTION_NONCONTINUABLE ? "NOT " : "" )
        << "POSSIBLE" << endl;
    
    cout << "INSTRUCTION ADDRESS: 0x" << hex << uppercase
        << er->ExceptionAddress << endl;
        
    if(er->ExceptionRecord != NULL)
    {
        cout << "CHAINED EXCEPTION!" << endl;
        pexcept(er->ExceptionRecord->ExceptionCode,
            er->ExceptionRecord);
    }
    else
    {
        cout << "--------------------------------------------------------------------" << endl;
    }
}

Вот как может выглядеть ваш вывод (рисунок ASCII, созданный вручную):

      --------------------------------------------------------------------
CPP_EXCEPTION
PARAMS NUM: 4
PARAM0        SOME INTERNAL VALUE: 0x19930520
PARAM1      POINTER TO THROWN OBJ: 0x14FA30
PARAM2      POINTER TO OBJECT INF: 0x7FFE68B246B0 ---->
PARAM3  DLL/EXE THROWER HINSTANCE: 0x7FFE68AC0000
     +-----+
 --> |0    |
     +-----+
     |4D0C |
     +-----+
     |0    |
     +-----+     +-----+
     |646D0|---->|3    |
     +-----+     +-----+     +-----+
                 |646F0|---->|0    |
                 +-----+     +-----+     +----------------+
                             |84578|---->|0               |
                             +-----+     +----------------+
                                         |7FFE68B1C218    |
                                         +----------------+
                                         |5F74756F56413F2E|
                                         +----------------+
                                    ..... _ t u o V A ? .
                                    
.?AVout_of_range@std@@
CURRENT EXE HIN:  0x0000000140000000
UCRTBASE HIN:     0x00007FFE99D50000
VCRUNTIME140 HIN: 0x00007FFE95040000
STD MSVCP140 HIN: 0x00007FFE68AC0000
CONTINUE EXECUTION:  NOT POSSIBLE
INSTRUCTION ADDRESS: 0x00007FFE99F54FD9
--------------------------------------------------------------------

Как видите, число похоти — это не указатель, а фактическое имя класса исключения (интерпретируемое как число в Little Endian). HINSTANCEпараметр исключения такой же, как и в стандартной библиотеке C++ (msvcp140.dllв моем случае), поэтому очевидно, откуда возникло это исключение.

Другие вопросы по тегам