WEB开发网
开发学院软件开发C++ Win32结构化异常处理(SEH)探秘(上) 阅读

Win32结构化异常处理(SEH)探秘(上)

 2010-10-15 09:07:37 来源:Web开发网   
核心提示:虽然__except_handler3的代码看起来很多,但是记住一点:它只是一个我在文章开头讲过的异常处理回调函数,Win32结构化异常处理(SEH)探秘(上)(8),它同MYSEH.EXE和 MYSEH2.EXE中的异常回调函数都带有同样的四个参数,__except_handler3大体上可以由第一个if语句分为两部

虽然__except_handler3的代码看起来很多,但是记住一点:它只是一个我在文章开头讲过的异常处理回调函数。它同MYSEH.EXE和 MYSEH2.EXE中的异常回调函数都带有同样的四个参数。__except_handler3大体上可以由第一个if语句分为两部分。这是由于这个函数可以在两种情况下被调用,一次是正常调用,另一次是在展开阶段。其中大部分是在非展开阶段的回调。

__except_handler3一开始就在堆栈上创建了一个EXCEPTION_POINTERS结构,并用它的两个参数来对这个结构进行初始化。我在伪代码中把这个结构称为 exceptPrts,它的地址被放在[EBP-14h]处。你回忆一下前面我讲的编译器为 GetExceptionInformation和 GetExceptionCode 函数生成的汇编代码就会意识到,这实际上初始化了这两个函数使用的指针。

接着,__except_handler3从EXCEPTION_REGISTRATION帧中获取当前的trylevel(在[EBP-04h]处)。 trylevel变量实际是scopetable数组的索引,而正是这个数组才使得一个函数中的多个__try块和嵌套的__try块能够仅使用一个 EXCEPTION_REGISTRATION结构。每个scopetable元素结构如下:

typedef struct _SCOPETABLE
{
DWORD previousTryLevel;
DWORD lpfnFilter;
DWORD lpfnHandler;
} SCOPETABLE, *PSCOPETABLE;

SCOPETABLE结构中的第二个成员和第三个成员比较容易理解。它们分别是过滤器表达式代码的地址和相应的__except块的地址。但是prviousTryLevel成员有点复杂。总之一句话,它用于嵌套的__try块。这里的关键是函数中的每个__try块都有一个相应的SCOPETABLE结构。

正如我前面所说,当前的 trylevel 指定了要使用的scopetable数组的哪一个元素,最终也就是指定了过滤器表达式和__except块的地址。现在想像一下两个__try块嵌套的情形。如果内层__try块的过滤器表达式不处理某个异常,那外层__try块的过滤器表达式就必须处理它。那现在要问,__except_handler3是如何知道SCOPETABLE数组的哪个元素相应于外层的__try块的呢?答案是:外层__try块的索引由 SCOPETABLE结构的previousTryLevel域给出。利用这种机制,你可以嵌套任意层的__try块。previousTryLevel 域就好像是一个函数中所有可能的异常处理程序构成的线性链表中的结点一样。如果trylevel的值为0xFFFFFFFF(实际上就是-1,这个值在 EXSUP.INC中被定义为TRYLEVEL_NONE),标志着这个链表结束。

回到__except_handler3的代码中。在获取了当前的trylevel之后,它就调用相应的SCOPETABLE结构中的过滤器表达式代码。如果过滤器表达式返回EXCEPTION_CONTINUE_SEARCH,__exception_handler3 移向SCOPETABLE数组中的下一个元素,这个元素的索引由previousTryLevel域给出。如果遍历完整个线性链表(还记得吗?这个链表是由于在一个函数内部嵌套使用__try块而形成的)都没有找到处理这个异常的代码,__except_handler3返回DISPOSITION_CONTINUE_SEARCH(原文如此,但根据_except_handler函数的定义,这个返回值应该为ExceptionContinueSearch。实际上这两个常量的值是一样的。我在伪代码中已经将其改正过来了),这导致系统移向下一个EXCEPTION_REGISTRATION帧(这个链表是由于函数嵌套调用而形成的)。

如果过滤器表达式返回EXCEPTION_EXECUTE_HANDLER,这意味着异常应该由相应的__except块处理。它同时也意味着所有前面的EXCEPTION_REGISTRATION帧都应该从链表中移除,并且相应的__except块都应该被执行。第一个任务通过调用__global_unwind2来完成的,后面我会讲到这个函数。跳过这中间的一些清理代码,流程离开__except_handler3转向__except块。令人奇怪的是,流程并不从__except块中返回,虽然是 __except_handler3使用CALL指令调用了它。

当前的trylevel值是如何被设置的呢?它实际上是由编译器隐含处理的。编译器非常机灵地修改这个扩展的EXCEPTION_REGISTRATION 结构中的trylevel域的值(实际上是生成修改这个域的值的代码)。如果你检查编译器为使用SEH的函数生成的汇编代码,就会在不同的地方都看到修改这个位于[EBP-04h]处的trylevel域的值的代码。

__except_handler3是如何做到既通过CALL指令调用__except块而又不让执行流程返回呢?由于CALL指令要向堆栈中压入了一个返回地址,你可以想象这有可能破坏堆栈。如果你检查一下编译器为__except块生成的代码,你会发现它做的第一件事就是将EXCEPTION_REGISTRATION结构下面8个字节处(即[EBP-18H]处)的一个DWORD值加载到ESP寄存器中(实际代码为MOV ESP,DWORD PTR [EBP-18H]),这个值是在函数的 prolog 代码中被保存在这个位置的(实际代码为MOV DWORD PTR [EBP-18H],ESP)。

ShowSEHFrames 程序

如果你现在觉得已经被EXCEPTION_REGISTRATION、scopetable、trylevel、过滤器表达式以及展开等等之类的词搞得晕头转向的话,那和我最初的感觉一样。但是编译器层面的结构化异常处理方面的知识并不适合一点一点的学。除非你从整体上理解它,否则有很多内容单独看并没有什么意义。当面对大堆的理论时,我最自然的做法就是写一些应用我学到的理论方面的程序。如果它能够按照预料的那样工作,我就知道我的理解(通常)是正确的。

下面是ShowSEHFrame.EXE的源代码。它使用__try/__except块设置了好几个 Visual C++ SEH 帧。然后它显示每一个帧以及Visual C++为每个帧创建的scopetable的相关信息。这个程序本身并不生成也不依赖任何异常。相反,我使用了多个__try块以强制Visual C++生成多个 EXCEPTION_REGISTRATION 帧以及相应的 scopetable。

//ShowSEHFrames.CPp
//=========================================================
// ShowSEHFrames - Matt Pietrek 1997
// Microsoft Systems Journal, February 1997
// FILE: ShowSEHFrames.CPp
// 使用命令行CL ShowSehFrames.CPP进行编译
//=========================================================
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <stdio.h>
#pragma hdrstop
//-------------------------------------------------------------------
// 本程序仅适用于Visual C++,它使用的数据结构是特定于Visual C++的
//-------------------------------------------------------------------
#ifndef _MSC_VEr
#error Visual C++ Required (Visual C++ specific information is displayed)
#endif
//-------------------------------------------------------------------
// 结构定义
//-------------------------------------------------------------------
// 操作系统定义的基本异常帧
struct EXCEPTION_REGISTRATION
{
EXCEPTION_REGISTRATION* prev;
FARPROC handler;
};
// Visual C++扩展异常帧指向的数据结构
struct scopetable_entry
{
DWORD previousTryLevel;
FARPROC lpfnFilter;
FARPROC lpfnHandler;
};
// Visual C++使用的扩展异常帧
struct VC_EXCEPTION_REGISTRATION : EXCEPTION_REGISTRATION
{
scopetable_entry * scopetable;
int trylevel;
int _ebp;
};
//----------------------------------------------------------------
// 原型声明
//----------------------------------------------------------------
// __except_handler3是Visual C++运行时库函数,我们想打印出它的地址
// 但是它的原型并没有出现在任何头文件中,所以我们需要自己声明它。
extern "C" int _except_handler3(PEXCEPTION_RECORD,
EXCEPTION_REGISTRATION *,
PCONTEXT,
PEXCEPTION_RECORD);
//-------------------------------------------------------------
// 代码
//-------------------------------------------------------------
//
// 显示一个异常帧及其相应的scopetable的信息
//
void ShowSEHFrame( VC_EXCEPTION_REGISTRATION * pVCExcRec )
{
printf( "Frame: %08X Handler: %08X Prev: %08X Scopetable: %08Xn",
pVCExcRec, pVCExcRec->handler, pVCExcRec->prev,
pVCExcRec->scopetable );
scopetable_entry * pScopeTableEntry = pVCExcRec->scopetable;
for ( unsigned i = 0; i <= pVCExcRec->trylevel; i++ )
{
printf( " scopetable[%u] PrevTryLevel: %08X "
"filter: %08X __except: %08Xn", i,
pScopeTableEntry->previousTryLevel,
pScopeTableEntry->lpfnFilter,
pScopeTableEntry->lpfnHandler );
pScopeTableEntry++;
}
printf( "n" );
}
//
// 遍历异常帧的链表,按顺序显示它们的信息
//
void WalkSEHFrames( void )
{
VC_EXCEPTION_REGISTRATION * pVCExcRec;
// 打印出__except_handler3函数的位置
printf( "_except_handler3 is at address: %08Xn", _except_handler3 );
printf( "n" );
// 从FS:[0]处获取指向链表头的指针
__asm mov eax, FS:[0]
__asm mov [pVCExcRec], EAX 
// 遍历异常帧的链表。0xFFFFFFFF标志着链表的结尾
while ( 0xFFFFFFFF != (unsigned)pVCExcRec )
{
ShowSEHFrame( pVCExcRec );
pVCExcRec = (VC_EXCEPTION_REGISTRATION *)(pVCExcRec->prev);
}
}
void Function1( void )
{
// 嵌套3层__try块以便强制为scopetable数组产生3个元素
__try
{
__try
{
__try
{
WalkSEHFrames(); // 现在显示所有的异常帧的信息
} __except( EXCEPTION_CONTINUE_SEARCH )
{}
} __except( EXCEPTION_CONTINUE_SEARCH )
{}
} __except( EXCEPTION_CONTINUE_SEARCH )
{}
}
int main()
{
int i;
// 使用两个__try块(并不嵌套),这导致为scopetable数组生成两个元素
__try
{
i = 0x1234;
} __except( EXCEPTION_CONTINUE_SEARCH )
{
i = 0x4321;
}
__try
{
Function1(); // 调用一个设置更多异常帧的函数
} __except( EXCEPTION_EXECUTE_HANDLER )
{
// 应该永远不会执行到这里,因为我们并没有打算产生任何异常
printf( "Caught Exception in mainn" );
}
return 0;
}

上一页  3 4 5 6 7 8 9  下一页

Tags:Win 结构化 异常

编辑录入:爽爽 [复制链接] [打 印]
赞助商链接