@会网络的老鼠

涂飞平的博客空间

delphi中异常的处理! [原]

12 年前 0

很多时候,我们编写程序都会有这样的那样的错误。而且很多情况下,程序出错后就是显示一个系统错误对话框然后系统毫不留情的把它杀死。有没有温柔一点的办法,或者更进一步,出错后让我们更正的机会呢?答案是肯定的,而且是在操作系统一级就为我们留下了这个接口。
这里讲的是windows操作系统下的异常处理方式,由于只是自己的笔记,如果想知道详细的细节,可以参考《Windows核心编程》一书。
在windows操作系统中,每一个任务的LTD结构存放在用户空间里面的,FS寄存器指向一个复杂的结构,但是在偏移为0的地方,指向的是一个异常处理程序的结构链。
而异常处理程序是一个回调函数,并且每个参数的格式以及参数的顺序都是固定的,这个windows的规定,毕竟在异常出现后,我们程序自己是没有处理机会的,只有交给系统了,然后系统再来调用我们程序中已经按格式定义好的异常处理例程。
下面是异常处理程序的声明:

function (ExceptionRecord: PExceptionRecord; EstablisherFrame: Pointer;
ContextRecord: PContext; DispatcherContext: Pointer): TExceptionDisposition; Cdecl;
注意这里采用的Cdecl参数传递方式。而其中的参数就是处理异常的关键了:
PExceptionRecord中的域ExceptionCode包含有到底出现的是什么错误(因为我们有时候只要处理一种或者几种异常,并不要处理所有的异常),例如堆栈溢出:STATUS_STACK_OVERFLOW。ContextRecord结构其实就是一个CPU寄存器组,我们可以在这里修改各个寄存器的值,甚至包括EIP寄存器,当然还有ESP了,这个就是我们异常处理可以还原现场的关键了^_^
函数的返回值是一个TExceptionDisposition(一个枚举),它作如下定义:TExceptionDisposition = (
ExceptionContinueExecution = 0,//继续执行遇到异常的线程(回调已经作了修复工作)
ExceptionContinueSearch = 1,//回调未作处理, 请在寻找其他回调
ExceptionNestedException = 2,
ExceptionCollidedUnwind = 3 ) ;
有了这些垫底,我相信作一个自己的异常处理钩子应该不复杂吧^_^
有些时候为了方便,我们还会定义一个TExcFrame结构:
TExcFrame = record
PStruct: PExcFrame; // 上一节点位置
Handler: Pointer; // 异常回调地址
SafeEip: Pointer; // 安全指令地址
end;
然后可以在栈中构建好结构后(利用push反向操作,因为栈是向小地址递增的),然后直接一个MOV FS[0],[ESP]就可以安装好这个钩子了,其实我们在异常例程中使用到的EstablisherFrame参数就是这里传入的,是不是很简单啊!这里借用别人的一个例子来看看如何为一个过程添加保护代码:
asm
// 在堆栈中构建异常结构
PUSH OFFSET @@SafeEip // TExcFrame.SafeEip安全的指令地址,其实这里有个小的技巧,由于这个
//结构是在栈中构造的,所以我们传入参数的同时实际上也得到了栈的安全地址了,同时保护到栈了,
//如果一个结构可以同时保护到ESP和EIP,那么函数再出错都可以恢复现场了^_^
//这里栈是函数调用前的栈,也就是干净的栈,恢复到这里最好不过了
PUSH OFFSET ExceptFilter // TExcFrame.Handler
PUSH FS:[0] // TExcFrame.PStruct
// 将该结构插入链表首部
MOV FS:[0], ESP // FS:[0]乃TIB.ExceptionList
// 参数/返回值均在EAX中
CALL CustomFunc //需要保护的函数或者过程
// 遇到异常时的跳转位置
@@SafeEip:
// 从链表去除我们的结构并恢复原来的异常处理例程,因为处理例程可能有很多,
//而我们的只是其中一个
MOV EDX , [ESP] // 上个节点地址
MOV FS:[0], EDX // 设其为首节点
// 清除异常结构占用堆栈
ADD ESP , 12 // 修改栈顶指针!
end;

编写评论