SEH链和展开操作


每次我们定义了一个新的SEH异常处理回调函数,EXCEPTION_REGISTRATION结构的prev字段都被要求填写上一个EXCEPTION_REGISTRATION结构的地址,随着应用程序对模块的调用一层层深入下去的时候,那么最后回调函数会形成一个SEH链

 

当程序中有多个线程在运行的时候,每个线程中都会存在各自的SEH链,这些SEH链中指定了多个回调函数,除他们以外,系统中可能还会存在一个全局性的筛选器,再者如果进程被调试,调试器进程也相当于一个异常处理的程序存在.那么当一个异常发生的时候,系统究竟该听谁的呢?

 

在这种情况下,系统按一定的步骤选择一个回调函数并执行他,如果这个回调函数可以执行这个异常,那么其他的回调函数都不会执行,否则系统执行下一个回调函数

(1).系统查看产生异常的进程是否正在被调试,如果正在被调试的话,那么向调试器发送EXCEPTION_DEBUG_EVENT事件

(2).如果进程没有被调试或者调试器不去处理这个异常,那么系统 检查异常所处的线程,并在这个线程环境中查看fs:[0]来确定是否安装有SEH异常处理回调函数,如果有则调用它.

(3).回调函数尝试处理这个异常,如果可以正确处理的话,则修正错误并将返回值设置为ExceptionContinueExecution,这时系统将结束整个查找过程

(4).如果回调函数返回ExceptionContinueSearch,告知系统他无法处理这个异常,那么系统将根据SEH链中的prev字段得到上一个回调函数地址,并重复步骤(3)过程,直到链中某个回调函数返回ExceptionContinueExecution为止,查找结束 

(5).如果到了SEH链的尾部,却没有一个回调函数愿意处理这个异常,那么系统将再次检测进程是否正在被调试,如果被调试的话,则再次通知调试器

(6).如果调试器还是不去处理这个异常或者进程没有被调试 ,那么系统检查有没有安装筛选器回调函数,如果有,则去调用他,筛选器回调函数返回的时候,系统默认的异常处理程序根据这个返回值将做出相应的动作

(7).如果没有安装筛选器回调函数,系统直接 调用默认的异常处理程序终止进程

一个 比较形象的比喻就是:

Windows拿着一份异常处理的活挨个问每个回调函数
"你干不干" ,"不干","你呢","我也不干"... ... 当问到某一个的时候,
他说:"那我来干好了!"那么Windows就不会在去问其他人了,于是相安无事

有时,问完一圈后,谁都不愿意干(当然是干不了)Windows大怒:"谁都不干,看我炒了你们!"于是就把整个进程终止掉,所有的异常处理回调函数全部完蛋啦
(筛选器-全局的-进程的  SEH-线程的)

 完整的异常处理回调函数

<span style="font-family:Microsoft YaHei;font-size:13px;">_Handler1	proc	C _lpExceptionRecord,_lpSEH,_lpContext,_lpDispatcherContext
		;C调用方式-调用者自己平衡堆栈
		.if	(异常代码 == 0c0000027h) || (异常标志 & EXCEPTION_UNWINDING) || (异常标志 & EXCEPTION_UNWINDING_FOR_EXIT)
			;进行资源释放等扫尾工作
			mov	eax,ExceptionContinueSearch
		.elseif 异常代码 == 可以处理的异常代码
			;处理异常,对CONTEXT进行修正
			;进行展开操作
			mov	eax,ExceptionContinueExecution
		.else
			;其他无法处理的异常代码
			mov eax,ExceptionContinueSearch
		.endif
		ret

_Handler1	endp</span>


 

二.展开操作(Unwinding)

如果把第一个SEH处理的函数的返回值改成ExceptionContinueSearch这个时候函数将会进行循环查找

这个时候回调函数应该进行一些扫尾工作,因为其将被卸载

注册 Unwinding操作可以使用RtlUnwinding函数

invoke RtlUnwinding,lpLastStackFrame,lpCodeLabel,lpExceptionRecord,dwRet

(1).lpLastStackFrame:这个参数设置为NULL,那么他将对所有的SEH链进行展开操作,这时所有的回调函数参数中的异常标志在带有EXCEPTION_UNWINDING的同时也带有EXCEPTION_UNWINDING_FOR_EXIT标志位,这种方式称为展开退出

指定为当前回调函数的EXCEPTION_REGISTRATION结构的地址的话,表示对当前回调函数之后的所有其他回调函数进行展开操作,这样RtlUnwinding函数调用的每一个回调函数时,异常标记位都会带有EXCEPTION_UNWINDING标记位

(2).lpCodeLabel参数指明函数将要返回的位置,如果位NULL,那么RtlUnwinding函数将返回到其后面的一条指令,否则函数直接返回到lpCodeLabel指定的位置

(3).lpExceptionRecord指定一个EXCEPTION_RECORD结构,这个结构将在调用的时候传给每一个回调函数

(4).dwRet参数一般不被使用,它可以指明为NULL

 

使用RtlUnwinding函数时要注意的是:这个函数并不像其他API函数一样保存esi,edi和ebx寄存器的值,在函数返回时候这些寄存器的值可能会改变,所以如果程序用到这些寄存器的话,必须手动去保存和恢复他们

 

 

<span style="font-family:Microsoft YaHei;font-size:13px;">		.386
		.model flat,stdcall
		option casemap:none
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; Include 文件定义
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
include		windows.inc
include		user32.inc
includelib	user32.lib
include		kernel32.inc
includelib	kernel32.lib

L macro var:VARARG
	LOCAL @lbl
	.const
	@lbl db var,0
	.code
	exitm <offset @lbl>
endm
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 数据段
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
		.data
szMsg1		db	'这是外层异常处理程序(将处理异常)',0dh,0ah
		db	'异常发生位置:%08X,异常代码:%08X,标志:%08X',0
szMsg2		db	'这是内层异常处理程序(对异常不进行处理)',0dh,0ah
		db	'异常发生位置:%08X,异常代码:%08X,标志:%08X',0
szCaption	db	'提示信息',0
szBeforeUnwind	db	'现在将开始 Unwind,当前的 FS:[0] = %08X',0
szAfterUnwind	db	'Unwind 返回,当前的 FS:[0] = %08X',0
szSafe1		db	'回到了外层子程序的安全位置!',0
szSafe2		db	'回到了内层子程序的安全位置!',0
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 代码段
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
		.code
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 外层错误 Handler,将处理异常
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
_Handler1	proc	C _lpExceptionRecord,_lpSEH,_lpContext,_lpDispatcherContext
		local	@szBuffer[256]:byte

		pushad
		mov	esi,_lpExceptionRecord
		mov	edi,_lpContext
		assume	esi:ptr EXCEPTION_RECORD,edi:ptr CONTEXT,fs:nothing
		invoke	wsprintf,addr @szBuffer,addr szMsg1,\
			[edi].regEip,[esi].ExceptionCode,[esi].ExceptionFlags
		invoke	MessageBox,NULL,addr @szBuffer,NULL,MB_OK
;********************************************************************
; 将 EIP 指向安全的位置并恢复堆栈
;********************************************************************
		mov	eax,_lpSEH
		push	[eax + 8]
		pop	[edi].regEip
		push	_lpSEH
		pop	[edi].regEsp
;********************************************************************
; 对前面的 Handler 进行 Unwind 操作
;********************************************************************
		invoke	wsprintf,addr @szBuffer,addr szBeforeUnwind,dword ptr fs:[0]
		invoke	MessageBox,NULL,addr @szBuffer,addr szCaption,MB_OK

		invoke	RtlUnwind,_lpSEH,NULL,NULL,NULL

		invoke	wsprintf,addr @szBuffer,addr szAfterUnwind,dword ptr fs:[0]
		invoke	MessageBox,NULL,addr @szBuffer,addr szCaption,MB_OK
;********************************************************************
		assume	esi:nothing,edi:nothing
		popad
		mov	eax,ExceptionContinueExecution
		ret

_Handler1	endp
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 内层错误 Handler,不处理异常
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
_Handler2	proc	C _lpExceptionRecord,_lpSEH,_lpContext,_lpDispatcherContext
		local	@szBuffer[256]:byte

		pushad
		mov	esi,_lpExceptionRecord
		mov	edi,_lpContext
		assume	esi:ptr EXCEPTION_RECORD,edi:ptr CONTEXT
		invoke	wsprintf,addr @szBuffer,addr szMsg2,\
			[edi].regEip,[esi].ExceptionCode,[esi].ExceptionFlags
		invoke	MessageBox,NULL,addr @szBuffer,NULL,MB_OK
		assume	esi:nothing,edi:nothing
		popad
		mov	eax,ExceptionContinueSearch
		ret

_Handler2	endp
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
_Test2		proc

		assume	fs:nothing
		push	offset _SafePlace
		push	offset _Handler2
		push	fs:[0]
		mov	fs:[0],esp
;********************************************************************
; 会引发异常的指令
;********************************************************************
		pushad
		xor	eax,eax
		mov	dword ptr [eax],0
		popad		;这一句将无法被执行
_SafePlace:
		invoke	MessageBox,NULL,L("回到了内层子程序的安全位置!"),L("提示信息"),MB_OK
		pop	fs:[0]
		add	esp,8
		ret

_Test2		endp
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
_Test1		proc

		assume	fs:nothing
		push	offset _SafePlace
		push	offset _Handler1
		push	fs:[0]
		mov	fs:[0],esp
		invoke	_Test2
_SafePlace:
		invoke	MessageBox,NULL,L("回到了外层子程序的安全位置!"),L("提示信息"),MB_OK
		pop	fs:[0]
		add	esp,8
		ret

_Test1		endp
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
start:
		invoke	_Test1
		invoke	ExitProcess,NULL
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
		end	start
</span>

 

 

 

 

 

### Sjlj Seh 的含义及用途 #### 结构化异常处理 (SEH) 结构化异常处理(Structured Exception Handling, SEH)是 Windows 操作系统提供的一种机制,用于捕获并响应程序运行期间发生的错误条件。这种机制允许开发者编写能够优雅地处理各种类型的异常情况的代码[^4]。 在 C/C++ 编程语言中实现 SEH 主要通过 `__try`、`__except` `__finally` 关键字来完成。当发生未预见的情况时,比如除零错误或者非法内存访问,操作系统会触发一个异常事件,并按照预定义的方式寻找最近的一个可以处理该异常的位置来进行相应的恢复操作[^5]。 ```cpp #include <windows.h> #include <stdio.h> int main() { __try { // 可能引发异常的代码段 int a = 0; int b = 1 / a; // 将导致除以零异常 } __except(EXCEPTION_EXECUTE_HANDLER) { printf("Caught an exception\n"); } return 0; } ``` #### Set Jump Long Jump (SJLJ) Set Jump Long Jump(简称 SJLJ),也称为“设置跳跃/远程跳跃”,是一种较早形式的过程间控制转移方法,在某些编程环境中用来模拟 try/catch 或者类似的异常捕捉功能。它依赖于两个标准库函数:`setjmp()` `longjmp()` 来保存当前执行状态以及回溯到之前的状态[^6]。 与 SEH 不同的是,SJLJ 并不是由编译器自动管理堆栈帧信息,而是手动记录返回点并通过显式的调用方式来回退至这些位置。这种方式虽然简单直观但在现代多线程或多处理器架构下效率较低且容易出现问题[^7]。 ```c #include <stdio.h> #include <setjmp.h> jmp_buf env; void function_that_may_fail(void){ if(/* some condition */) longjmp(env, 1); /* 跳转 */ } int main(){ if(setjmp(env)==0){ function_that_may_fail(); }else{ puts("Function failed and we jumped back!"); } return 0; } ``` sjlj seh 是两种不同的异常处理机制,前者更底层且跨平台兼容性较好,后者则是特定于 Windows 系统下的高级特性。对于大多数应用程序开发而言,推荐使用更高层次的语言内置异常处理设施而非直接操纵 sjlj 或 seh[^8]。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值