ava*_*kar 7 c++ winapi seh structured-exception
在下面的代码中,函数以foo递归方式调用一次.内部调用导致引发访问冲突.外部调用捕获异常.
#include <windows.h>
#include <stdio.h>
void foo(int cont)
{
__try
{
__try
{
__try
{
if (!cont)
*(int *)0 = 0;
foo(cont - 1);
}
__finally
{
printf("inner finally %d\n", cont);
}
}
__except (!cont? EXCEPTION_CONTINUE_SEARCH: EXCEPTION_EXECUTE_HANDLER)
{
printf("except %d\n", cont);
}
}
__finally
{
printf("outer finally %d\n", cont);
}
}
int main()
{
__try
{
foo(1);
}
__except (EXCEPTION_EXECUTE_HANDLER)
{
printf("main\n");
}
return 0;
}
Run Code Online (Sandbox Code Playgroud)
这里的预期输出应该是
inner finally 0
outer finally 0
inner finally 1
except 1
outer finally 1
Run Code Online (Sandbox Code Playgroud)
但是,outer finally 0从实际输出中明显缺失.这是一个错误还是有一些我忽略的细节?
为了完整性,使用VS2015进行编译,编译为x64.令人惊讶的是,它不会发生在x86上,让我相信它确实是一个bug.
存在并且更简单的示例(我们可以删除内部try/finally块:
void foo(int cont)
{
__try
{
__try
{
if (!cont) *(int *)0 = 0;
foo(cont - 1);
}
__except (cont? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH)
{
printf("except %d\n", cont);
}
}
__finally
{
printf("finally %d\n", cont);
}
}
Run Code Online (Sandbox Code Playgroud)
带输出
except 1
finally 1
Run Code Online (Sandbox Code Playgroud)
所以finally 0块没有执行。但在非递归情况下 - 没有错误:
__try
{
foo(0);
}
__except(EXCEPTION_EXECUTE_HANDLER)
{
printf("except\n");
}
Run Code Online (Sandbox Code Playgroud)
输出:
finally 0
except
Run Code Online (Sandbox Code Playgroud)
这是下一个函数中的错误
EXCEPTION_DISPOSITION
__C_specific_handler (
_In_ PEXCEPTION_RECORD ExceptionRecord,
_In_ PVOID EstablisherFrame,
_Inout_ PCONTEXT ContextRecord,
_Inout_ PDISPATCHER_CONTEXT DispatcherContext
);
Run Code Online (Sandbox Code Playgroud)
这个函数的旧实现有错误:
//
// try/except - exception filter (JumpTarget != 0).
// After the exception filter is called, the exception
// handler clause is executed by the call to unwind
// above. Having reached this point in the scan of the
// scope tables, any other termination handlers will
// be outside the scope of the try/except.
//
if (TargetPc == ScopeTable->ScopeRecord[Index].JumpTarget) { // bug
break;
}
Run Code Online (Sandbox Code Playgroud)
如果我们安装了最新的 VC 编译器/库,请搜索chandler.c(在我的安装中位于\VC\crt\src\amd64\chandler.c)
现在可以在文件中查看下一个代码:
if (TargetPc == ScopeTable->ScopeRecord[Index].JumpTarget
// Terminate only when we are at the Target frame;
// otherwise, continue search for outer finally:
&& IS_TARGET_UNWIND(ExceptionRecord->ExceptionFlags)
) {
break;
}
Run Code Online (Sandbox Code Playgroud)
因此添加了附加条件IS_TARGET_UNWIND(ExceptionRecord->ExceptionFlags)来修复此错误
__C_specific_handler在不同的crt库中实现(在某些情况下使用静态链接,在某些情况下将从vcruntime*.dll或导入msvcrt.dll(转发到ntdll.dll))。也ntdll.dll导出这个函数 - 但是在最新的 win10 版本(14393)中它仍然没有修复