深入探索Win32结构化异常处理 - 图文

2019-08-17 13:54

深入探索Win32结构化异常处理

在Win32操作系统提供的所有功能中,使用最广泛而又没有公开的恐怕要数结构化异常处理( Structured Exception Handling ,SEH ) 了。当你考虑Win32结构化异常处理时,也许会想到__try、__finally和__except等术语。可能你在任何一本讲解Win32的好书上 都能找到关于SEH较为详细的描述,甚至Win32 SDK文档也对使用__try、__finally和__except进行结构化异常处理进行了相当完整的描述。

既 然已经有了这些文档,那为什么我还说SEH并未公开呢?本质上来说,Win32结构化异常处理是操作系统提供的服务。你可能找到的所有关于SEH方面的文 档都只是描述了某个特别的编译器的运行时库对操作系统实现的封装。关键字__try、__finally或者__except并没有什么神奇的。 Microsoft的操作系统和编译器开发小组定义了这些关键字和它们的作用。其它C++编译器厂商完全按照它们的语义来就可以了。当编译器的SEH支持 层把原始的操作系统SEH的复杂性封装起来的时候,它同时也把原始的操作系统SEH的细节隐藏了起来。

我 曾经接到大量来自想自己实现编译器层面SEH的人发来的电子邮件,他们苦于找不到关于操作系统SEH实现方面的任何文档。按说,我应该能够告诉他们 Visual C++或Borland C++的运行时库源代码就是他们想要的。但是不知出于什么原因,编译器层面的SEH看起来好像是个大秘密。无论是Microsoft还是Borland都 没有提供他们的SEH支持层最底层的源代码。(现在Microsoft仍然没有提供这些源代码,它提供的是编译过的目标文件,而Borland则提供了相 应的源代码。)

在 本文中,我会剥掉结构化异常处理外面的包装直至其最基本的概念。在此过程中,我会把操作系统提供的支持与编译器通过代码生成和运行时库提供的支持分开来 说。当我挖掘到关键的操作系统例程时,我使用的是运行于Intel处理器上的Windows NT 4.0。但是我这里讲的大部分内容同样也适用于其它处理器。

我会避免涉及到真实的C++异常处理,它使用的是catch()而不是__except。从内部来讲,真实的C++异常处理的实现与我这里要讲的非常相似。但是真实的C++异常处理有一些其它的复杂问题,它会混淆我这里要讲的一些概念。

在 挖掘组成Win32 SEH的晦涩的.H和.INC文件的过程中,我发现最好的信息来源之一是IBM OS/2头文件(特别是BSEXCPT.H)。如果你涉足这方面已经有一段时间了,就不会感到太奇怪。这里描述的SEH机制是早在Microsoft还工 作在OS/2上时就已经定义好的。由于这个原因,你会发现Win32下的SEH和OS/2下的SEH极其相似。(现在我们可能已经没有机会体验这一点 了,OS/2已经永远成为历史了。)

浅析SEH

如 果我把SEH的所有细节一股脑儿全倒给你,你可能无法接受。因此我先从一小部分开始,然后层层深入。如果你以前从未接触过结构化异常处理,那正好,因为你 头脑中没有一些自己设想的概念。如果你以前接触过SEH,最好把头脑中有关__try、GetExceptionCode和 EXCEPTION_EXECUTE_HANDLER之类的词统统忘掉,假设它对你来说是全新的。深呼吸。准备好了吗?让我们开始吧!

设想我告诉过你,当一个线程出现错误时,操作系统给你一个机会被告知这个错误。说得更明白一些就是,当一个线程出现错误时,操作系统调用用户定义的一个回调函数 。这个回调函数可以做它想做的一切。例如它可以修复错误,或者它也可以播放一段音乐。无论回调函数做什么,它最后都要返回一个值来告诉系统下一步做什么。(这不是十分准确,但就此刻来说非常接近。)

当你的某一部分代码出错时,系统再回调你的其它代码,那么这个回调函数看起来是什么样子呢?换句话说,你想知道关于异常什么类型的信息呢?实际上这并不重要,因为Win32已经替你做了决定。异常回调函数看起来像下面这个样子: EXCEPTION_DISPOSITION

__cdecl _except_handler( struct _EXCEPTION_RECORD *ExceptionRecord, void * EstablisherFrame, struct _CONTEXT *ContextRecord, void * DispatcherContext);

这个原型来自标准的Win32头文件EXCPT.H,乍看起来有些费解。但如果你仔细看,它并不是很难理解。首先,忽略掉返回值的类型(EXCEPTION_DISPOSITION)。你得到的基本信息就是它是一个叫作 _except_handler 并且带有四个参数的函数。

这个函数的第一个参数是一个指向 EXCEPTION_RECORD 结构的指针。这个结构在WINNT.H中定义,如下所示:

typedef struct _EXCEPTION_RECORD { DWORD ExceptionCode; DWORD ExceptionFlags;

struct _EXCEPTION_RECORD *ExceptionRecord; PVOID ExceptionAddress; DWORD NumberParameters;

DWORD ExceptionInformation[EXCEPTION_MAXIMUM_PARAMETERS]; } EXCEPTION_RECORD;

这 个结构中的ExcepitonCode成员是赋予异常的代码。通过在WINNT.H中搜索以“STATUS_”开头的#define定义,你可以得到一个 异常代码列表。例如所有人都非常熟悉的

STATUS_ACCESS_VIOLATION的代码是0xC0000005。一个更全面的异常代码列表可以在 Windows NT DDK的NTSTATUS.H中找到。此结构的第四个成员是异常发生的地址。其它成员暂时可以忽略。

_except_handler 函数的第二个参数是一个指向establisher帧结构的指针。它是SEH中一个至关重要的参数,但是现在你可以忽略它。

_except_handler 回调函数的第三个参数是一个指向 CONTEXT 结 构的指针。此结构在WINNT.H中定义,它代表某个特定线程的寄存器值。图1显示了CONTEXT结构的成员。当用于SEH时,CONTEXT结构表示 异常发生时寄存器的值。顺便说一下,这个CONTEXT结构就是GetThreadContext和SetThreadContext这两个API中使用 的那个CONTEXT结构。 图1 CONTEXT结构 typedef struct _CONTEXT {

DWORD ContextFlags; DWORD Dr0; DWORD Dr1; DWORD Dr2; DWORD Dr3; DWORD Dr6; DWORD Dr7;

FLOATING_SAVE_AREA FloatSave; DWORD SegGs; DWORD SegFs;

DWORD SegEs; DWORD SegDs; DWORD Edi; DWORD Esi; DWORD Ebx; DWORD Edx; DWORD Ecx; DWORD Eax; DWORD Ebp; DWORD Eip; DWORD SegCs; DWORD EFlags; DWORD Esp; DWORD SegSs; } CONTEXT;

_except_handler 回调函数的第四个参数被称为DispatcherContext。它暂时也可以被忽略。 到现在为止,你头脑中已经有了一个当异常发生时会被操作系统调用的回调函数的模型了。这个回调函数带四个参数,其中三个指向其它结构。在这些结构中,一些域比较重要,其它的就不那么重要。这里的关键是_exept_handler 回调函数接收到操作系统传递过来的许多有价值的信息 ,例如异常的类型和发生的地址。使用这些信息,异常回调函数就能决定下一步做什么。

对 我来说,现在就写一个能够显示_except_handler作用的样例程序是再诱人不过的了。但是我们还缺少一些关键信息。特别是,当错误发生时操作系 统是怎么知道到哪里去调用这个回调函数的呢?答案是还有一个称为EXCEPTION_REGISTRATION的结构。通篇你都会看到这个结构,所以不要 跳过这一部分。我唯一能找到的EXCEPTION_REGISTRATION 结构的正式定义是在Visual C++运行时库源代码中的EXSUP.INC文件中: _EXCEPTION_REGISTRATION struc prev dd ? handler dd ? _EXCEPTION_REGISTRATION ends

这 个结构在WINNT.H的NT_TIB结构的定义中被称为_EXCEPITON_REGISTARTION_RECORD。唉,没有一个地方能够找到 _EXCEPTION_REGISTRATION_RECORD的定义,所以我不得不使用EXSUP.INC中这个汇编语言的结构定义。这是我前面所说 SEH未公开的一个证据。(读者可以使用内核调试器,如KD或SoftICE并加载调试符号来查看这个结构的定义。 下图是在KD中的结果:

下图是在SoftICE中的结果:

译者注)

无 论正在干什么,现在让我们回到手头的问题上来。当异常发生时,操作系统是如何知道到哪里去调用回调函数的呢?实际 上,EXCEPTION_REGISTARTION结构由两个域组成,第一个你现在可以忽略。第二个域handler,包含一个指向 _except_handler回调函数的指针。这让你离答案更近一点,但现在的问题是,操作系统到哪里去找 EXCEPTION_REGISTATRION结构呢?

要回答这个问题,记住 结构化异常处理是基于线程的 这一点是非常有用的。也就是说,每个线程有它自己的异常处理回调函数。在1996年五月的Under The Hood专栏中,我介绍了一个关键的Win32数据结构—— 线程信息块(Thread Information/Environment Block,TIB或TEB) 。这个结构的某些域在Windows NT、Windows 95、Win32s和OS/2上是相同的。TIB 的 第一个DWORD是一个指向线程的EXCEPTION_REGISTARTION结构的指针。在基于Intel处理器的Win32平台上,FS寄存器总是 指向当前的TIB。因此在FS:[0]处你可以找到一个指向EXCEPTION_REGISTARTION结构的指针。(注:此结构体并非 _EXCEPTION_RECORD 结构体指针,而是

EXCEPTION_REGISTARTION 异常链结构体指针!此时栈顶ESP所指位置地址下的4个为_EXCEPTION_RECORD 结构 )

到 现在为止,我们已经有了足够的认识。当异常发生时,系统查找出错线程的TIB,获取

一个指向EXCEPTION_REGISTRATION结构的指针。在 这个结构中有一个指向_except_handler回调函数的指针。现在操作系统已经知道了足够的信息去调用_except_handler函数,如图 2所示。

图2 _except_handler函数

把 这些小块知识拼凑起来,我写了一个小程序来演示上面这个对操作系统层面的结构化异常处理的简化描述,如图3的MYSEH.CPP所示。它只有两个函数。 main函数使用了三个内联汇编块。第一个内联汇编块通过两个PUSH指令(“PUSH handler”和“PUSH FS:[0]”)在堆栈上创建了 一个EXCEPTION_REGISTRATION结构。PUSH FS:[0]这条指令保存了先前的FS:[0]中的值作为这个结构的一部分,但这在此 刻并不重要。重要的是现在堆栈上有一个8字节的

EXCEPTION_REGISTRATION结构。紧接着的下一条指令(MOV FS:[0],ESP)使线程信息块中的第一个DWORD指向了新的EXCEPTION_REGISTRATION结构。(注意堆栈操作) 图3 MYSEH.CPP

//================================================== // MYSEH - Matt Pietrek 1997

// Microsoft Systems Journal, January 1997 // FILE: MYSEH.CPP

// 用命令行CL MYSEH.CPP编译

//================================================== #define WIN32_LEAN_AND_MEAN #include #include DWORD scratch; EXCEPTION_DISPOSITION __cdecl

_except_handler( struct _EXCEPTION_RECORD *ExceptionRecord, void * EstablisherFrame, struct _CONTEXT *ContextRecord, void * DispatcherContext ) {

unsigned i;

// 指明是我们让流程转到我们的异常处理程序的 printf( \

// 改变CONTEXT结构中EAX的值,以便它指向可以成功进写操作的位置 ContextRecord->Eax = (DWORD)&scratch; // 告诉操作系统重新执行出错的指令

return ExceptionContinueExecution; }

int main() {

DWORD handler = (DWORD)_except_handler; __asm {

// 创建EXCEPTION_REGISTRATION结构: push handler // handler 函数的地址 push FS:[0] // 前一个handler函数的地址

mov FS:[0],ESP // 安装新的EXECEPTION_REGISTRATION结构 } __asm {

mov eax,0 // 将EAX清零

mov [eax], 1 // 写EAX指向的内存从而故意引发一个错误 }

printf( \


深入探索Win32结构化异常处理 - 图文.doc 将本文的Word文档下载到电脑 下载失败或者文档不完整,请联系客服人员解决!

下一篇:七年级数学下册《2.1 两条直线的位置关系(一)》教学设计(新版

相关阅读
本类排行
× 注册会员免费下载(下载后可以自由复制和排版)

马上注册会员

注:下载文档有可能“只有目录或者内容不全”等情况,请下载之前注意辨别,如果您已付费且无法下载或内容有问题,请联系我们协助你处理。
微信: QQ: