Win32结构化异常处理(SEH)探秘(上)
2010-10-15 09:07:37 来源:Web开发网代码中只有两个函数,main 函数使用了三部分内联汇编块 ASM。第一个 ASM 块通过两个 PUSH 指令(即:“PUSH handler”和“PUSH FS:[0]”)在堆栈上建立一个 EXCEPTION_REGISTRATION 结构。PUSH FS:[0] 保存以前 FS:[0] 的值,它是结构的一部分,但目前这个值对我们不重要。重要的是在堆栈上有一个 8-byte 的 EXCEPTION_REGISTRATION 结构。紧接着的指令(MOV FS:[0],ESP)是让线程信息块中的第一个 DWORD 指到新的 EXCEPTION_REGISTRATION 指令。
如果你想知道为什么我要在堆栈上建立这个 EXCEPTION_REGISTRATION 结构,而不是使用全局变量,有一个很好的理由。当你使用编译器的 _try/_except 时,编译器也会在堆栈上建立 EXCEPTION_REGISTRATION 结构。我只是向你简要地揭示你使用 _try/_except 时编译器所做的事情。让我们回到 main 函数,下一个 __asm 块是通过把 EAX 寄存器清零(MOV EAX,0),然后把此寄存器的值作为内存地址让下一条指令(MOV [EAX],1)向此地址写入数据而故意引发一个错误。最后一个 __asm 块是清除这个简单的异常处理例程:首先它恢复以前的 FS:[0] 内容,然后它将 EXCEPTION_REGISTRATION 结构记录从堆栈中弹出(ADD ESP,8)。
现在,假设你正在运行 MYSEH.EXE 并会看到所发生的事情。当 MOV [EAX],1 指令执行时,它导致一个数据访问违例。系统察看 TIB 中的 FS:[0] 并找到 EXCEPTION_REGISTRATION 结构指针。此结构中则有一个指向 MYSEH.CPP 中 _except_handler 函数的指针。系统则将四个必须的参数(我在前面描述过这四个参数)压入堆栈并调用 _except_handler 函数。
一旦进入 _except_handler,代码首先通过 printf 指示“哈!这里是我干的!”。接着,_except_handler 修复导致出错的问题。即 EAX 寄存器指向某个不能写入的内存地址(地址 0)。修复方法是在改变 CONTEXT 结构中的 EAX 的值,以便它指向某个允许进行写入操作的位置。在这个简单的程序中,DWORD 变量(scratch)是故意为此而设计的。_except_handler 函数最后一个动作时返回 ExceptionContinueExecution 值,它在标准的 EXCPT.H 文件中定义。
当操作系统看到返回值为 ExceptionContinueExecution。它就认为你已经修复了问题,并且引起错误的指令应该被重新执行。因为我的 _except_handler 函数强制 EAX 寄存器指向合法内存,MOV EAX,1 指令再次执行,函数 main 一切正常。看,这并不复杂,不是吗?
进一步深入
有了前面的最简单的例子,让我们再回过头去填补一些空白。虽然这个异常回调机制很棒,但它并不是一个完美的解决方案。对于稍微复杂一些的应用程序来说,仅用一个函数就能处理程序中任何地方都可能发生的异常是相当困难的。一个更实用的方案应该是有多个异常处理例程,每个例程针对程序的特定部分。不知你是否知道,实际上,操作系统提供的正是这个功能。
还记得系统用来查找异常回调函数的 EXCEPTION_REGISTRATION 结构吗?这个结构的第一个成员,称为 prev,前面我们曾把它忽略掉了。它实际上是一个指向另外一个 EXCEPTION_REGISTRATION 结构的指针。这第二个 EXCEPTION_REGISTRATION 结构可以有一个完全不同的处理函数。然后呢,它的 prev 域可以指向第三个 EXCEPTION_REGISTRATION 结构,依次类推。简单地说,就是有一个 EXCEPTION_REGISTRATION 结构链表。线程信息块的第一个 DWORD(在基于 Intel CPU 的机器上是 FS:[0])总是指向这个链表的头部。
操作系统要这个 EXCEPTION_REGISTRATION 结构链表做什么呢?原来,当异常发生时,系统遍历这个链表以便查找其中的一个EXCEPTION_REGISTRATION 结构,其例程回调(异常处理程序)同意处理该异常。在 MYSEH.CPP 的例子中,异常处理程序通过返回ExceptionContinueExecution 表示它同意处理这个异常。异常回调函数也可以拒绝处理这个异常。在这种情况下,系统移向链表的下一个EXCEPTION_REGISTRATION 结构并询问它的异常回调函数,看它是否愿意处理这个异常。图四显示了这个过程:
图四 查找处理异常的 EXCEPTION_REGISTRATION 结构
更多精彩
赞助商链接