Разница между исключением 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
в моем случае), поэтому очевидно, откуда возникло это исключение.