导读:
万一真的发生了嵌套异常,则CPU照样还是通过上述的路线经RtlDispatchException()进入
RtlpExecuteHandlerForException(),并且有了一个新的异常纪录块。但是实际的调用参数却不同了:首先异常纪录块是新的,
并且此时的RegistrationFrame指向临时的保护节点,而保护节点的处理函数RegistrationFrame->handler则
指向RtlpExceptionProtector()、而不再是_SEHFrameHandler()。在深入到普通节点的处理函数之前,我们不妨先看
一下RtlpExceptionProtector()的代码:
[_KiTrap14() > KiPageFaultHandler() > KiKernelTrapHandler() > KiDispatchException()
> RtlDispatchException() > RtlpExecuteHandlerForException() > _RtlpExecuteHandler2()
> RtlpExceptionProtector()]
_RtlpExceptionProtector:
/* Assume we'll continue */
mov eax, ExceptionContinueSearch
/* Put the exception record in ECX and check the Flags */
mov ecx, [esp+4] /* 指向异常纪录块 */
test dword ptr [ecx+EXCEPTION_RECORD_EXCEPTION_FLAGS],
EXCEPTION_UNWIND
jnz return
/* Save the frame in ECX and Context in EDX */
mov ecx, [esp+8] /* 使ECX指向当前节点,这是一个保护节点 */
mov edx, [esp+16] /* 使EDX指向第四个参数DispatcherContext */
/* Get the nested frame */
mov eax, [ecx+8] /* 当前节点中的第三个指针,指向所保护的节点 */
/* Set it as the dispatcher context */
mov [edx], eax /* 通过第四个参数返回指向所保护节点的指针 */
/* Return nested exception */
mov eax, ExceptionNestedException
return:
ret 16
这里的常数EXCEPTION_UNWIND定义为
(EXCEPTION_UNWINDING + EXCEPTION_EXIT_UNWIND)。
所以,代码中的test指令所测试的是两个标志位,只要其中有任何一个为1就执行转移指令,从而返回常数
ExceptionContinueSearch。否则就返回ExceptionNestedException,并将第二个参数、即指针
RegistrationFrame、复制到第四个参数DispatcherContext所指的地方。
前面RtlDispatchException()中的while循环显然在扫描ExceptionList,这是为了替新发生的异常寻找能够认领、处理
本次异常的节点。凡是新发生的异常,其异常纪录块中的上述两个标志位都是0,所以RtlpExceptionProtector()会返回
ExceptionNestedException,这就表明发生了嵌套异常。那么在什么时候会返回ExceptionContinueSearch呢?
后面读者将看到,在执行长程跳转之前,还有个“展开”的过程,在此过程中又要扫描ExceptionList中位于目标节点之前的那些节点,以调用它们的
善后函数。到那时候,就至少要把异常纪录块中的标志位EXCEPTION_UNWINDING设成1,于是这个函数就返回
ExceptionContinueSearch了,因为保护节点本身是不具备认领和处理一次异常的能力的。
所以,真正解决问题还得靠代表着SEH框架的普通节点,而普通节点的处理函数是_SEHFrameHandler():
[_KiTrap14() > KiPageFaultHandler() > KiKernelTrapHandler() > KiDispatchException()
> RtlDispatchException() > RtlpExecuteHandlerForException() > _RtlpExecuteHandler2()
> _SEHFrameHandler()]
static int __cdecl
_SEHFrameHandler(struct _EXCEPTION_RECORD * ExceptionRecord,
void * EstablisherFrame, struct _CONTEXT * ContextRecord, void * DispatcherContext)
{
_SEHPortableFrame_t * frame;
_SEHCleanHandlerEnvironment();
frame = EstablisherFrame;
if(ExceptionRecord->ExceptionFlags & (4 | 2)) /* Unwinding */
_SEHLocalUnwind(frame, NULL);
else /* Handling */
{
int ret;
_SEHPortableTryLevel_t * trylevel;
if(ExceptionRecord->ExceptionCode)
frame->SPF_Code = ExceptionRecord->ExceptionCode;
else
frame->SPF_Code = 0xC0000001;
for (trylevel = frame->SPF_TopTryLevel; trylevel != NULL; trylevel = trylevel->SPT_Next)
{
_SEHFilter_t pfnFilter = trylevel->SPT_Handlers->SH_Filter;
switch((UINT_PTR)pfnFilter)
{
case (UINT_PTR)_SEH_STATIC_FILTER(_SEH_EXECUTE_HANDLER):
case (UINT_PTR)_SEH_STATIC_FILTER(_SEH_CONTINUE_SEARCH):
case (UINT_PTR)_SEH_STATIC_FILTER(_SEH_CONTINUE_EXECUTION):
{
ret = (int)((UINT_PTR)pfnFilter) - 2;
break;
}
default:
{
if(trylevel->SPT_Handlers->SH_Filter)
{
EXCEPTION_POINTERS ep;
ep.ExceptionRecord = ExceptionRecord;
ep.ContextRecord = ContextRecord;
ret = pfnFilter(&ep, frame);
}
else
ret = _SEH_CONTINUE_SEARCH;
break;
}
}
if(ret < 0) /* _SEH_CONTINUE_EXECUTION */
return ExceptionContinueExecution;
else if(ret > 0) /* _SEH_EXECUTE_HANDLER */
_SEHCallHandler(frame, trylevel);
else /* _SEH_CONTINUE_SEARCH */
continue;
} /* end for */
/* FALLTHROUGH */
} /* end if */
return ExceptionContinueSearch;
}
这个函数实际上把本来是两个函数的代码合在了一起,成为一个if语句,这也跟在异常处理的整个过程中要先后两次扫描ExceptionList有关。如果
异常纪录块中的标志位EXCEPTION_UNWINDING或EXCEPTION_EXIT_UNWIND为1,就说明这个SEH框架已经在“展开”的
过程中,这是为展开而调用本节点的这个函数,所以调用_SEHLocalUnwind()。否则便是在搜寻能够认领和处理本次异常的SEH框架的过程中,
是从RtlpDispatchException()中经由RtlpExecuteHandlerForException()调用下来的。所处的阶段不
同,调用的路线也就不同,这里的操作也就随之而不同。此刻我们是在后一种情景中。
所谓搜寻SEH框架,就是依次执行节点中各局部SEH框架的过滤函数,看看是否应该执行这个SEH框架的长程跳转,如果是就加以实施,不是就加以拒绝。如
果节点中的所有SEH框架都拒绝,就退回RtlpDispatchException()继续考察链表中的下一个节点,如果不再有下一个节点,搜寻就失败
了。
过滤函数根据什么信息确定是否应该执行实施函数进行长程跳转呢?我们在前面看到,作为参
万一真的发生了嵌套异常,则CPU照样还是通过上述的路线经RtlDispatchException()进入
RtlpExecuteHandlerForException(),并且有了一个新的异常纪录块。但是实际的调用参数却不同了:首先异常纪录块是新的,
并且此时的RegistrationFrame指向临时的保护节点,而保护节点的处理函数RegistrationFrame->handler则
指向RtlpExceptionProtector()、而不再是_SEHFrameHandler()。在深入到普通节点的处理函数之前,我们不妨先看
一下RtlpExceptionProtector()的代码:
[_KiTrap14() > KiPageFaultHandler() > KiKernelTrapHandler() > KiDispatchException()
> RtlDispatchException() > RtlpExecuteHandlerForException() > _RtlpExecuteHandler2()
> RtlpExceptionProtector()]
_RtlpExceptionProtector:
/* Assume we'll continue */
mov eax, ExceptionContinueSearch
/* Put the exception record in ECX and check the Flags */
mov ecx, [esp+4] /* 指向异常纪录块 */
test dword ptr [ecx+EXCEPTION_RECORD_EXCEPTION_FLAGS],
EXCEPTION_UNWIND
jnz return
/* Save the frame in ECX and Context in EDX */
mov ecx, [esp+8] /* 使ECX指向当前节点,这是一个保护节点 */
mov edx, [esp+16] /* 使EDX指向第四个参数DispatcherContext */
/* Get the nested frame */
mov eax, [ecx+8] /* 当前节点中的第三个指针,指向所保护的节点 */
/* Set it as the dispatcher context */
mov [edx], eax /* 通过第四个参数返回指向所保护节点的指针 */
/* Return nested exception */
mov eax, ExceptionNestedException
return:
ret 16
这里的常数EXCEPTION_UNWIND定义为
(EXCEPTION_UNWINDING + EXCEPTION_EXIT_UNWIND)。
所以,代码中的test指令所测试的是两个标志位,只要其中有任何一个为1就执行转移指令,从而返回常数
ExceptionContinueSearch。否则就返回ExceptionNestedException,并将第二个参数、即指针
RegistrationFrame、复制到第四个参数DispatcherContext所指的地方。
前面RtlDispatchException()中的while循环显然在扫描ExceptionList,这是为了替新发生的异常寻找能够认领、处理
本次异常的节点。凡是新发生的异常,其异常纪录块中的上述两个标志位都是0,所以RtlpExceptionProtector()会返回
ExceptionNestedException,这就表明发生了嵌套异常。那么在什么时候会返回ExceptionContinueSearch呢?
后面读者将看到,在执行长程跳转之前,还有个“展开”的过程,在此过程中又要扫描ExceptionList中位于目标节点之前的那些节点,以调用它们的
善后函数。到那时候,就至少要把异常纪录块中的标志位EXCEPTION_UNWINDING设成1,于是这个函数就返回
ExceptionContinueSearch了,因为保护节点本身是不具备认领和处理一次异常的能力的。
所以,真正解决问题还得靠代表着SEH框架的普通节点,而普通节点的处理函数是_SEHFrameHandler():
[_KiTrap14() > KiPageFaultHandler() > KiKernelTrapHandler() > KiDispatchException()
> RtlDispatchException() > RtlpExecuteHandlerForException() > _RtlpExecuteHandler2()
> _SEHFrameHandler()]
static int __cdecl
_SEHFrameHandler(struct _EXCEPTION_RECORD * ExceptionRecord,
void * EstablisherFrame, struct _CONTEXT * ContextRecord, void * DispatcherContext)
{
_SEHPortableFrame_t * frame;
_SEHCleanHandlerEnvironment();
frame = EstablisherFrame;
if(ExceptionRecord->ExceptionFlags & (4 | 2)) /* Unwinding */
_SEHLocalUnwind(frame, NULL);
else /* Handling */
{
int ret;
_SEHPortableTryLevel_t * trylevel;
if(ExceptionRecord->ExceptionCode)
frame->SPF_Code = ExceptionRecord->ExceptionCode;
else
frame->SPF_Code = 0xC0000001;
for (trylevel = frame->SPF_TopTryLevel; trylevel != NULL; trylevel = trylevel->SPT_Next)
{
_SEHFilter_t pfnFilter = trylevel->SPT_Handlers->SH_Filter;
switch((UINT_PTR)pfnFilter)
{
case (UINT_PTR)_SEH_STATIC_FILTER(_SEH_EXECUTE_HANDLER):
case (UINT_PTR)_SEH_STATIC_FILTER(_SEH_CONTINUE_SEARCH):
case (UINT_PTR)_SEH_STATIC_FILTER(_SEH_CONTINUE_EXECUTION):
{
ret = (int)((UINT_PTR)pfnFilter) - 2;
break;
}
default:
{
if(trylevel->SPT_Handlers->SH_Filter)
{
EXCEPTION_POINTERS ep;
ep.ExceptionRecord = ExceptionRecord;
ep.ContextRecord = ContextRecord;
ret = pfnFilter(&ep, frame);
}
else
ret = _SEH_CONTINUE_SEARCH;
break;
}
}
if(ret < 0) /* _SEH_CONTINUE_EXECUTION */
return ExceptionContinueExecution;
else if(ret > 0) /* _SEH_EXECUTE_HANDLER */
_SEHCallHandler(frame, trylevel);
else /* _SEH_CONTINUE_SEARCH */
continue;
} /* end for */
/* FALLTHROUGH */
} /* end if */
return ExceptionContinueSearch;
}
这个函数实际上把本来是两个函数的代码合在了一起,成为一个if语句,这也跟在异常处理的整个过程中要先后两次扫描ExceptionList有关。如果
异常纪录块中的标志位EXCEPTION_UNWINDING或EXCEPTION_EXIT_UNWIND为1,就说明这个SEH框架已经在“展开”的
过程中,这是为展开而调用本节点的这个函数,所以调用_SEHLocalUnwind()。否则便是在搜寻能够认领和处理本次异常的SEH框架的过程中,
是从RtlpDispatchException()中经由RtlpExecuteHandlerForException()调用下来的。所处的阶段不
同,调用的路线也就不同,这里的操作也就随之而不同。此刻我们是在后一种情景中。
所谓搜寻SEH框架,就是依次执行节点中各局部SEH框架的过滤函数,看看是否应该执行这个SEH框架的长程跳转,如果是就加以实施,不是就加以拒绝。如
果节点中的所有SEH框架都拒绝,就退回RtlpDispatchException()继续考察链表中的下一个节点,如果不再有下一个节点,搜寻就失败
了。
过滤函数根据什么信息确定是否应该执行实施函数进行长程跳转呢?我们在前面看到,作为参