发布时间:2012-7-5 11:26
分类名称:SEH
原文:http://blog.csdn.net/cuagain/article/details/5082630
最近读了Matt Pietrek的《A Crash Course on the Depths of Win32 Structured Exception Handling》,有如醍醐灌顶,尤其是编译器级的SEH,如果是SEH的初学者,推荐读一下这篇文章,不要先去看《windows核心编程》的异常处 理章节,那样只会越搞越乱。如果把SEH帧链和_SCOPETABLE链这两条链搞清了基本就OK了,至于细节部分值得深究的地方还是很多的。但在这篇文 章中一个没有具体提到的问题就是SEH的局部展开,也就是_local_unwind函数,由于这是一个与编译器相关的函数,所以对研究系统的SEH没有 障碍。后来我在张银奎的《软件调试》补编的异常编译章节中找到了一段该函数的伪码,摘录如下:
这段伪码中最主要的是12行开始的那个while循环,第18行判断pCurScopeEntry->lpfnFilter是否为NULL, 原因是在VC中try_finally块和try_except块一样,都对应一个scopetable中的_SCOPETABLE表项,区别在于 try_finally对应表项的lpfnFilter值为NULL,也就是说没有过滤表达式。因此以上代码可以看出,局部展开的任务主要是搜索 _SCOPETABLE链中的try_finally节点并执行finally处理代码。为了验证这个说法,我写了一小段代码
#include <windows.h>
#include <stdio.h>
void ViewScopeTable(DWORD* pSEHFrame)
{
DWORD* Scopetable = (DWORD*)*(pSEHFrame+2);
DWORD TryLevel = *(pSEHFrame+3);
DWORD* pCurTable = (DWORD*)(Scopetable + TryLevel*3);
while( TRUE )
{
printf("prevTryLevel:%x/nlpfnfilter:%x/nlpfnHandler:%x/n/n",
*pCurTable,
*(pCurTable+1),
*(pCurTable+2)
);
) == 0xFFFFFFFF)
break;
pCurTable = (DWORD*)(Scopetable + TryLevel*3);
}
}
void ShowSEHFrame()
{
DWORD pSEHHead;
_asm mov eax, fs:[0]
_asm mov pSEHHead, eax
printf("fs:[0]:%x/n/n", pSEHHead);
DWORD* pHead = (DWORD*)pSEHHead;
while(TRUE)
{
printf("Frame addr:%x/nprev:%x/nhandler:%x/nscopetable:%x/ntrylevel:%x/n/n",
Head,
*pHead,
*(pHead+1),
*(pHead+2),
*(pHead+3)
);
ViewScopeTable(pHead);
break;
pHead = (DWORD*)*pHead;
}
}
void func1()
{
_try{
_try{
_try{
ShowSEHFrame();
}_except( EXCEPTION_CONTINUE_SEARCH ) //except块
{
}
}_finally //有意设置成finally块
{
}
}_except( EXCEPTION_CONTINUE_SEARCH ) //except块
{
}
}
int main()
{
func1();
}
执行结果如下:
可见try_finally块和try_except一样被插入到_SCOPETABLE链中,只是lpfnFilter为0。
在Matt Pietrek的_except_handler3的伪码中有这样一句:
if ( pRegistrationFrame->scopetable[trylevel].lpfnFilter )
.......
即判断_SCOPETABLE项的lpfnFilter,若是try_finally块则直接略过该节点,查询下一节点。而在上述的_local_unwind2伪码中,若是try_except节点则略过,查询下一节点。由此可以总结出:
try_except节点在发生异常时搜索异常处理块时被使用,而try_finally节点则用于局部展开的时候。
由于VC中的异常handler都指向_except_handler3,不是一个异常处理块对应一个异常处理帧,故没有办法在全局展开时直接用 _except_handler3完成清理工作,所以有了try_finally结构,这样看来这个结构的出现也是在情理之中的。