分享
 
 
 

Windows 系统编程初探 (四)结构化异常处理之一:SEH的基本原理与进程相关异常处理

王朝system·作者佚名  2006-01-09
窄屏简体版  字體: |||超大  

上面的内容只是一些基础知识,虽然简单,但有必要了解一下。现在,我将正式开始我的第一个专题:结

构化异常处理(SEH)。SEH 是 Windows 系统提供的功能,跟开发工具无关。值得一提的是,VC 将 SEH 进行

了封装,也就是我们平常用到的 __try{}__except(){} 和 __try{}__finally{},我没有研究过它的实现方法,这里也

不进行讨论,而我将要讲述的是 SEH 的手动实现,也就是 SEH 的本来面貌。

1.SEH 的工作原理。

Windows 程序设计中最重要的理念就是消息传递,事件驱动。当GUI应用程序触发一个消息时,系统将把

该消息放入消息队列,然后去查找并调用窗体的消息处理函数(CALLBACK),传递的参数当然就是这个消息。

我们同样可以把异常也当作是一种消息,应用程序发生异常时就触发了该消息并告知系统。系统接收后同样会

找它的“回调函数”,也就是我们的异常处理例程。当然,如果我们在程序中没有做异常处理的话,系统也不

会置之不理,它将弹出我们常见的应用程序错误框,然后结束该程序。所以,当我们改变思维方式,以

CALLBACK 的思想来看待 SEH,SEH 将不再神秘。

2.进程相关异常处理。

SEH 可分为进程相关和线程相关,我们先来了解进程相关的 SEH,所谓进程相关,就是说在应用程序的

任何地方发生的异常都可以(并不必须)用该处理例程来处理。 按照前面的思路,做异常处理就是设置一个回调

函数,可如何设置呢?Windows 为设置窗体回调函数提供了一个API:SetWindowLong(),它同样也为异常处

理提供了类似的API:SetUnhandledExceptionFilter(),传递给该函数的参数就是我们的异常处理例程。所以,

我们只需要编写一个函数,然后再程序开始的时候调用 SetUnhandledExceptionFilter()将它设置为异常处理函

数就OK了! 下一步,就是怎样编写异常处理函数了。首先,我们看一下异常处理函数的定义:

long __stdcall ExceptionFilterProc(EXCEPTION_POINTERS *);

返回值是 long;调用规则是 __stdcall;函数名无所谓,愿意怎么起都行;参数只是一个结构指针。所有

的都很简单,只有参数看起来陌生一点,那么我们先来观察一下参数,这个结构在 WINNT.H 中定义如下:

typedef struct _EXCEPTION_POINTERS {

PEXCEPTION_RECORD ExceptionRecord;

PCONTEXT ContextRecord;

}EXCEPTION_POINTERS;

又嵌套了两个结构指针,呵呵!

EXCEPTION_RECORD 结构定义:

typedef struct _EXCEPTION_RECORD {

DWORD ExceptionCode;

DWORD ExceptionFlags;

struct _EXCEPTION_RECORD *ExceptionRecord;

PVOID ExceptionAddress;

DWORD NumberParameters;

DWORD ExceptionInformation[EXCEPTION_MAXIMUM_PARAMETERS];

}EXCEPTION_RECORD, * PEXCEPTION_RECORD;

这个结构有必要说明一下,内容比较多,没必要都记住,用到时翻出文档参考一下就行了。

DWORD ExceptionCode;

异常代码,指出异常原因。常见异常代码有:

EXCEPTION_ACCESS_VIOLATION = C0000005h

读写内存冲突

EXCEPTION_INT_DIVIDE_BY_ZERO = C0000094h

非法除0

EXCEPTION_STACK_OVERFLOW = C00000FDh

堆栈溢出或者越界

EXCEPTION_GUARD_PAGE = 80000001h

由Virtual Alloc建立起来的属性页冲突

EXCEPTION_NONCONTINUABLE_EXCEPTION = C0000025h

不可持续异常,程序无法恢复执行,异常处理例程不应处理这个异常

EXCEPTION_INVALID_DISPOSITION = C0000026h

在异常处理过程中系统使用的代码

EXCEPTION_BREAKPOINT = 80000003h

调试时因代码中 INT 3 中断

EXCEPTION_SINGLE_STEP = 80000004h

处于被单步调试状态(INT 1)

DWORD ExceptionFlags;

异常标志

= 0

可修复异常

EXCEPTION_NONCONTINUABLE = 1

不可修复异常

EXCEPTION_NONCONTINUABLE_EXCEPTION = C0000025H

不可修复异常继续执行导致的异常

struct _EXCEPTION_RECORD *ExceptionRecord;

当异常处理程序中发生异常时,此字段被填充,否则为NULL

PVOID ExceptionAddress;

发生异常的地址(EIP)

DWORD NumberParameters;

规定与异常相关的参数数量(0-15),现在版本的Windows总是0

DWORD ExceptionInformation[EXCEPTION_MAXIMUM_PARAMETERS];

异常描述信息,目前只有 EXCEPTION_ACCESS_VIOLATION 异常有描述信息

ExceptionInformation[0]

描述导致异常的操作类型

= 0 读异常

= 1 写异常

ExceptionInformation[1]

发生读写异常的内存地址

CONTEXT 结构定义:

typedef struct _CONTEXT{

...

}CONTEXT, * PCONTEXT;

这个结构非常庞大,这里就不一一罗列了,可以参看 WINNT.H,但我们必须清楚的一点是:CONTEXT 结构描述

的是异常发生时 CPU 中各个寄存器的状态。

再来看看返回值的意义,返回值可以有三个,分别是:

EXCEPTION_EXECUTE_HANDLER = 1

已经处理了异常,结束程序,这样程序将无疾而终。

EXCEPTION_CONTINUE_SEARCH = 0

不处理异常,转交系统处理,弹出常见的错误消息框。

EXCEPTION_CONTINUE_EXECUTION = -1

修复错误,从异常发生处继续执行,最理想的做法,不过非常困难。

了解了这些之后,我们来看看一个异常处理函数的简单流程:

1.C/C++ 写法

long WINAPI ExceptionFilter(EXCEPTION_POINTERS * lParam){

...

return 1;//(0,-1)

}

2.ASM 写法

ExceptionFilter PROC

;取得参数

MOV ESI,DWORD PTR [ESP + 4]

;处理异常

...

...

;设置返回值,高级语言约定返回值存放于 EAX 中。

MOV EAX,_return_Value

RET 4

ExceptionFilter ENDP

说了这么多,也当不住一个例子有说服力。下面,我将给出一个 ASM 写的例程,程序启动后,将生成两个

线程,主线程中将产生一个除0异常,子线程中将产生一个非法内存访问异常,异常处理程序会处理他们。仔细

研究一下吧!

;****************************************************************

;进程相关异常处理实例

;****************************************************************

.386

.MODEL FLAT

;包含常用结构的头文件,和 C/C++ 的 .H 类似

include ..\INCLUDE\PERELATION.INC

;API 申明

EXTRN MessageBoxA:PROC

EXTRN CreateThread:PROC

EXTRN VirtualProtect:PROC

EXTRN WaitForSingleObject:PROC

EXTRN CloseHandle:PROC

EXTRN SetUnhandledExceptionFilter:PROC

EXTRN ExitProcess:PROC

;数据定义

.Data

ddTemp DD 0

ddHandle DD 0

ddThreadID DD 0

szTitle DB "提示",0

szExcDivZero DB "应用程序发生除 0 错误",0

szExcAccess DB "应用程序发生非法内存访问错误,是否修复?",0

;代码开始(主线程)

.Code

_Header:

PUSH EBP

;设置异常处理函数

PUSH OFFSET ExceptionFilter

CALL SetUnhandledExceptionFilter

;触发除 0 异常

XOR EBX,EBX

DIV BL

;***********************************************

;此间执行顺序将被打乱,进入异常处理例程

;***********************************************

;创建子线程

PUSH OFFSET ddThreadID

PUSH 0

PUSH NULL

PUSH OFFSET ThreadProc

PUSH 0

PUSH NULL

CALL CreateThread

;创建线程失败

TEST EAX,EAX

JE _Error_Exit

;保存线程句柄

MOV ddHandle,EAX

;等待子线程结束

PUSH 0FFFFFFFFH

PUSH EAX

CALL WaitForSingleObject

;关闭线程句柄

PUSH ddHandle

CALL CloseHandle

_Error_Exit:

POP EBP

;退出程序

PUSH 0

CALL ExitProcess

;********************************************************************

;代码段内定义的字符串。Windows 程序的代码段默认是不可写的,

;下面的线程函数将以尝试将该字符串按字翻转,从而导致非法内存

;访问异常。

;********************************************************************

szMessage DB "落花人独立,微雨燕双飞。当时明月在,曾照彩云归。",0

;子线程函数体

ThreadProc PROC

PUSHAD

;**********************************************************

;这段指令将完成扫描 NULL-T 字符串长度的功能,

;估计是函数 strlen() 的原始码,十分精彩!

;指令说明(REPNE SCASB):

;EDI 寄存器指向字符串头,然后按子节与寄存器AL

;比较,相等则结束,每比较一个字符EDI将自加1,

;ECX 寄存器中设置扫描次数,也就是循环计数器,

;因为字符串长度不定,所以将ECX设置为0FFFFFFFF

;**********************************************************

CLD

XOR EAX,EAX

XOR ECX,ECX

DEC ECX

LEA EDI,szMessage

REPNE SCASB

;ECX取反则得到字符串长度(包含0)

NOT ECX

;回复EDI到字符串头

SUB EDI,ECX

;按字翻转字符串,0保留在末尾

XOR EBX,EBX

DEC ECX

_Rever_Loop:

DEC ECX

DEC ECX

CMP EBX,ECX

JGE _Rever_Over

;分别读取头、尾的两个字

MOV AX,WORD PTR [EDI + EBX]

MOV DX,WORD PTR [EDI + ECX]

;翻转写入,本条指令将导致非法内存访问异常

MOV WORD PTR [EDI + ECX],AX

MOV WORD PTR [EDI + EBX],DX

INC EBX

INC EBX

JMP _Rever_Loop

_Rever_Over:

;反转完成,显示反转后的字符串

PUSH MB_OK

PUSH OFFSET szTitle

PUSH OFFSET szMessage

PUSH NULL

CALL MessageBoxA

POPAD

RET 4

ThreadProc ENDP

;异常处理函数

ExceptionFilter PROC

;从栈中取得参数 EXCEPTION_POINTERS *

;此时栈的状态是:

;[ESP + 4] EXCEPTION_POINTERS *

;[ESP] Return address

MOV EAX,DWORD PTR [ESP + 4]

PUSHAD

;PEXCEPTION_RECORD => ESI

MOV ESI,[EAX].ExceptionRecord

;PCONTEXT => EDI

MOV EDI,[EAX].ContextRecord

;取异常代码

MOV EAX,[ESI].ExceptionCode

;是否非法除0异常

CMP EAX,0C0000094H

JE _IsDivZero

;是否非法内存访问异常

CMP EAX,0C0000005H

JE _IsAccessViolation

;其它异常

JMP _ExceptOther

;除 0 异常处理

_IsDivZero:

;MessageBox 提示一下

PUSH MB_OK

PUSH OFFSET szTitle

PUSH OFFSET szExcDivZero

PUSH NULL

CALL MessageBoxA

;*********************************************************

;修复方法:前面代码中,我们以BL作为除数,要修

;复异常,只需BL != 0 就可以了,所以这里改变寄

;存器EBX的值,从而达到修复的目的。

;这里使用到了结构 CONTEXT,注意一下。

;*********************************************************

INC [EDI].C_Ebx

JMP _Filter_Exit

;非法内存访问错误

_IsAccessViolation:

;消息提示是否修复

PUSH MB_YESNOCANCEL

PUSH OFFSET szTitle

PUSH OFFSET szExcAccess

PUSH NULL

CALL MessageBoxA

;选择“YES”则修复异常

CMP EAX,IDYES

JE _FixException

;选择“NO”则不修复,结束进程

CMP EAX,IDNO

JE _ExceptOther

;选择“CANCEL”则转交系统处理,会弹出错误框

;返回值 EAX = 0

POPAD

XOR EAX,EAX

RET 4

;修非法复内存访问异常

_FixException:

;*****************************************************************

;修复方法:调用函数 VirtualProtect 更改内存的

;址的保护属性,让它可写(PAGE_EXECUTE_READWRITE)

;*****************************************************************

MOV EAX,[ESI].ExceptionInformation[4]

PUSH OFFSET ddTemp

PUSH PAGE_EXECUTE_READWRITE

PUSH 01000H

PUSH EAX

CALL VirtualProtect

TEST EAX,EAX

JNE _Filter_Exit

;如果发生其他异常,直接退出程序

_ExceptOther:

;返回值 EAX = 1

POPAD

XOR EAX,EAX

INC EAX

RET 4

;异常修复,继续执行

_Filter_Exit:

;返回值 EAX = -1

POPAD

XOR EAX,EAX

DEC EAX

RET 4

ExceptionFilter ENDP

END _Header

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有