有人能解释一下C++异常和MFC中结构化异常的区别吗?
实际上有三种机制:
try
/catch
)__try
/__except
)TRY
、CATCH
),建立在SEH/C++异常之上 - 另请参阅TheUndeadFish的评论)C++异常通常在堆栈展开期间保证自动清理(即运行局部对象的析构函数),而其他机制则不会。
只有在显式抛出时,C++异常才会发生。结构化异常可能会因许多操作而发生,例如由于未定义的行为、向API传递无效指针、卸载内存映射文件的备份存储等等。
MFC引入异常宏以支持即使编译器没有实现异常也可以使用。
这是一个 MSVC++ 的重要实现细节,但在 Windows 上,C++ 异常也是 SEH 异常。异常代码为 0xE04D5343(最后三个字节为“MSC”)。所有常规的 SEH 支持都用于展开堆栈、运行自动清理代码,并过滤异常以选择适当的 catch 子句。如此处所示。
在过滤器表达式中获取抛出的异常对象是 CRT 添加的管道,超出了 SEH 提供的范畴,这必然是 C++ 特有的。
另外一个实现细节是 /EH 编译器设置。默认值 (/EHsc) 允许编译器优化生成的代码并抑制运行自动清理所需的异常过滤器。如果它可以看到没有任何发射的 C++ 代码可能会引发异常。要为 SEH 异常获得自动清理,必须使用 /EHa 进行编译,以抑制此优化。
将 C++ 异常与 SEH 结合的一种策略是使用 _set_se_translator(),以便您可以将 SEH 异常转换为 C++ 异常。尽管捕获 SEH 异常通常不明智,它们几乎总是很麻烦的。通常会选择使用 __try/__catch,如链接的答案所示。
0xE06D7363
(小写的“msc”),我刚刚用我的自定义SEH处理程序捕获了其中一个。这里有更多相关信息。而且,这是Raymond Chen的文章。 - ScienceDiscovererC++异常是C++编程语言的一个特性。结构化异常是Windows操作系统的另一个概念。这两者使用类似的语法,但在技术上有所不同。Windows结构化异常不仅可以与C++一起使用,还可以与C等其他语言一起使用。
有时候统一处理两种异常的解决方案是:在Windows应用程序中,您可以提供一个处理程序函数,它捕获所有结构化异常并抛出一个由您定义的C++异常。
两者都提供了在错误发生时进行堆栈展开的机制。
结构化异常是由Windows提供的,由内核提供支持。如果您访问无效的内存位置等操作,则Windows会引发此类异常。它们还用于支持自动堆栈增长等功能。它们通常不单独使用,但C ++,.NET和类似语言中的语言异常通常建立在其之上。您可以使用特殊关键字,例如__try
和__catch
来处理这些异常。但是,处理它们相对困难且容易出错,因为您可能会破坏自动堆栈扩展等功能,以及潜在地破坏C ++语言异常。
C++异常由C++语言指定。被抛出和捕获的数据类型是C++对象(包括原始类型的可能性)。编译器和运行时在基础结构化异常机制之上实现这些。如果您使用C++语言的try
,catch
和throw
关键字,则会得到这种异常。
SEH异常具有比C++异常更多的功能,例如支持恢复和所谓的“向量”处理程序(接收异常通知,但不一定防止堆栈展开),但除非您明确知道要使用它们,否则我会避免使用它们。它们最常见的用途可能是在程序执行非法或未定义操作时编写崩溃转储,使用MiniDumpWriteDump。
C++异常将跨平台工作。不幸的是,SEH会严重限制可移植性(除非在不同的Windows版本之间)。
另外,SEH似乎捕获了许多本地Windows异常(例如访问冲突,指定了无效的句柄等)。
到2022年最新的Windows 10上仍然能够正常工作!我的解决方案还提供了从异常中提取的完整信息,因此您甚至不需要调试器,只需使用请注意,此信息属于实现细节类别。不能保证该方法将来仍能正常工作,因此不要编写依赖它的代码。这只是一个调试提示。
__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
--------------------------------------------------------------------
正如您所看到的,lust number 不是指针,而是异常的实际类名(在 Little Endian 中解释为数字)。异常的 HINSTANCE
参数与标准 C++ 库相同(在我的情况下是 msvcp140.dll
),因此很明显这个异常的来源。