SEH IN ASM 研究
第二部分提高篇
By Hume[AfO]/冷雨飘心
PART IV 关于异常处理的嵌套和堆栈展开
在实际程序设计过程中,不可能只有一个异常处理例程,这就产生了异常处理程序嵌套的问题,可能很多处理例程分别监视若干子程序并处理其中某种异常,另外一个监视所有子程序可能产生的共性异常,这作起来实际很容易,也方便调试.你只要依次建立异常处理框架就可以了.关于VC++异常处理可以嵌套很多人可能比较熟悉,用起来更容易不过实现比这里也就复杂得多,在VC++中一个程序所有异常只指向一个相同的处理句例程,然后在这个处理例程里再实现对各个子异常处理例程的调用,他的大致方法是建立一个子异常处理例程入口的数组表,然后根据指针来调用子处理例程,过程比较烦琐,原来打算大致写一点,现在发现自己对C/C++了解实在太少,各位有兴趣还是自己参考MSDN Matt Pietrek 1996年写的一篇文章<<A Crash Course on the Depths of Win32? Structured Exception Handling>>,里面有非常详细的说明,对于系统的实现细节也有所讨论,不过相信很多人都没有兴趣.hmmm...:)实际上Kernel的异常处理过程和VC++的很相似.我们的程序中当然也可以采用这种方法,不过一般应用中不必牛刀砍蚂蚁.
有异常嵌套就涉及到我们可能以前经常看到并且被一些污七八糟的不负责任的”翻译家”搞得头晕脑涨不知为何的”异常展开”的问题,也许你注意到了如果按照我前面的例子包括final都不处理异常的话,最后系统在终结程序之前会来一次展开,在试验之后发现,展开不会调用final只是对per_thread例程展开(right?).什么是堆栈展开?为什么要进行堆栈展开?如何进行堆栈展开?
我曾经为堆栈展开迷惑过,原因是各种资料的描述很不一致,Matt Pietrek说展开后前面的ERR结构被释放,并且好像seh链上后面的处理例程如果决定处理异常必须对前面的例程来一次展开,很多C/C++讲述异常处理的书也如斯说这使人很迷惑,我们再来看看Jeremy Gordon的描述,堆栈展开是处理异常的例程自愿进行的.呵呵,究竟事实如何?
在迷惑好久之后我终于找到了答案:Matt Pietrek讲的没有错,那是VC++以及系统kernel的处理方法,Jeremy Gordon说的也是正确的,那是我门asm Fans的自由!
好了,现在来说堆栈展开,堆栈展开这个词似乎会使人有所误解,堆栈怎么展开呢?事实上,堆栈展开是异常处理例程在决定处理某个异常的时候给前面不处理这个异常的处理例程的一个清洗的机会,前面拒绝处理这个异常的例程可以释放必要的句柄对象或者释放堆栈或者干点别的工作...那完全是你的自由,叫stack unwind似乎有点牵强.堆栈展开有一个重要的标志就是EXCEPTION_RECORD.ExceptionFlag为2,表示正在展开,你可以进行相应的处理工作,但实际上经常用的是6这是因为还有一个UNWIND_EXIT equ 4什么的,具体含义未知,不过kernel确实就是检测这个值,因此我们也就检测这个值来判断展开.
注意在自己的异常处理例程中,unwind不是自动的,必须你自己自觉地引发,如果所有例程都不处理系统最后的展开是注定的. 当然如果没有必要你也可以选择不展开.
win32提供了一个api RtlUnwind来引发展开,如果你想展开一下,就调用这个api吧,少候讲述自己代码如何展开
RtlUnwind调用描述如下:
PUSH Return value ;返回值,一般不用
PUSH pExceptionRecord ;指向EXCEPTION_RECORD的指针
PUSH OFFSET CodeLabel ;展开后从哪里执行
PUSH LastStackFrame ;展开到哪个处理例程终止返回,通常是处理异常的Err结构
CALL RtlUnwind
调用这个api之前要注意保护ebx,esi和edi,否则...嘿嘿
MASM格式如下:
Invoke
RtlUnwind,pFrame,OFFSET return_code_Address,pExceptionRecord,Return_value
这样在展开的时候,就以pExceptionRecord.flag=2 依次调用前面的异常处理例程,到决定异常的处理例程停止,Jeremy Gordon手动展开代码和我下面的例子有所不同.他描述最后决定处理异常的ERR结构的prev成员为-1,好像
我的结果和他的有所差异,因此采用了另外的方法,具体看下面的例子.
最后一点要注意在嵌套异常处理程序的时候要注意保存寄存器,否则你经常会得到系统异常代码为C00000027h的异常调用,原因是异常处理中又产生异常,而这个异常又无法解决,只好由操作系统终结你的程序.
一下给出一点垃圾代码演示可能有助于理解,注意link的时候要加入 /section:.text,RWE 否则例子里面的代码段不能写,SMC功能会产生异常以致整个程序不能进行.
注意:2K/XP下非法指令异常的代码不一致,另外用下面的例子在2K下不能工作,具体错误未知.因此只能在9X下演示,为了在2k/Xp下也能运行我加了点代码,有兴趣看看,另外帮我解决一下2K/Xp下SMC的问题?thx!
下面例子很烂,不过MASM格式写起来容易一点,也便于理解.看来比较长实际很简单,耐心看下去.
;-----------------------------------------
;Ex5,演示堆栈展开和异常嵌套处理 by Hume,2002
;humewen@21cn.com
;hume.longcity.net
;-----------------------------------------
.586
.model flat, stdcall
option casemap :none ; case sensitive
include hd.h
include mac.h
;;--------------
per_xHandler1 proto C :DWORD,:DWORD,:DWORD,:DWORD
per_xHandler2 proto C :DWORD,:DWORD,:DWORD,:DWORD
per_xHandler3 proto C :DWORD,:DWORD,:DWORD,:DWORD
;-----------------------------------------
.data
sztit db "except Mess,by hume[AfO]",0
count dd 0,0
Expt1_frm dd 0 ;ERR结构指针,用于堆栈展开手动代码
Expt2_frm dd 0
Expt3_frm dd 0
;;-----------------------------------------
.CODE
_Start:
assume fs:nothing
push offset per_xHandler3
push fs:[0]
mov fs:[0],esp
mov Expt3_frm,esp
push offset per_xHandler2
push fs:[0]
mov fs:[0],esp
mov Expt2_frm,esp
push offset per_xHandler1
push fs:[0]
mov fs:[0],esp
mov Expt1_frm,esp
;--------------------------
;install xhnadler
;-----------------------------------------
1 2 3 4 下一页 |