异常的主要结构体信息
一般当程序发生异常时,用户代码停止执行,并将CPU的控制权转交给操作系统,操作系统接到控制权后,将当前线程的寄存器环境保存到结构体CONTEXT中,然后查找针对此异常的处理函数
系统利用结构EXCEPTION_RECORD保存了异常描述信息,它与CONTEXT一同构成了结构体EXCEPTION_POINTERS,一般在异常处理中经常使用这个结构体。
两个结构定义如下
EXCEPTION_POINTERS结构体包含EXCEPTION_RECORD和CONTEXT
typedef struct _EXCEPTION_POINTERS {
PEXCEPTION_RECORD ExceptionRecord;
PCONTEXT ContextRecord;
} EXCEPTION_POINTERS, *PEXCEPTION_POINTERS
异常信息EXCEPTION_RECORD的定义
typedef struct _EXCEPTION_RECORD
{
DWORD ExceptionCode; //异常码
DWORD ExceptionFlags; //标志异常发生后是否还可以继续执行
struct _EXCEPTION_RECORD* ExceptionRecord; //指向下一个异常节点的指针,这是一个链表结构
PVOID ExceptionAddress; //异常发生的地址
DWORD NumberParameters; //异常附加信息
ULONG_PTR ExceptionInformation[EXCEPTION_MAXIMUM_PARAMETERS]; //异常的字符串
} EXCEPTION_RECORD, *PEXCEPTION_RECORD;
系统处理异常的顺序
如果按筛选器异常,调试器,SEH(结构化异常处理)这三个排序的话则是
调试器(优先级最高)----SEH(第二)--------筛选器异常(最后)
异常默认处理时SEH的注意点:如果你没有没有处理这个异常,系统会调用默认的系统处理程序,通常显示一个对话框,你可以选择关闭或者最后将其附加到调试器上的调试按钮.如果没有调试器能被附加于其上或者调试器也处理不了,系统就调用ExitProcess终结程序,不过在终结之前,系统仍然对发生异常的线程异常处理句柄来一次展开的信息(也就是在进行通知一次异常处理函数),这是线程异常处理例程最后清理的机会
异常展开
异常展开:假如fun1调fun2,fs[0]指向fun2的异常处理,这时候fun2的异常处理不了就会找到fun1,fun1可以处理,假如这时候fun1又发生异常的话,因为SEH没有把fun2的节点去掉,也就是没有清栈,这时候这时候调用了一个错误的地址, 那么现在我们应该把链表的位置重置为fun1的异常链表,fun2的不在需要了.进行清栈,这就是异常展开
异常展开的时候,我们自己也可以去做,也可以交给操作系统做,而操作系统做的时候也是调用的API RtIUnwind
SEH结构化异常
1FS段存有线程信息结构体TIB
2储异常信息的链表指针为TIB结构体的第一个成员,这样fs:[0]也就获取到了ExceptionList头节点
3通过dt _EXCEPTION_REGISTRATION_RECORD发现链表指针的结构
前4个字节:指向下一个异常处理结构体指针的位置,形成链表
后4个字节:指向一个异常回调函数
4异常回调函数
//异常回调函数声明
int _except_handler3(
PEXCEPTION_RECORD exception_record, //异常记录
PEXCEPTION_REGISTRATION registration, //异常回调链表指针
PCONTEXT context, //寄存器信息
PEXCEPTION_REGISTRATION dispatcher
)
{
return ExceptionContinueExecution
//返回值说明
//ExceptionContinueExecution = 00 //表示已经处理程序继续执行
//ExceptionContinueSearch = 01 //根据链表往下搜索找回调处理
//ExceptionNestedException = 02 //异常嵌套异常
//ExceptionCollidedUnwind = 03
}
//微软官方给EXCEPTION_REGISTRATION结构体的定义
typedef struct _EXCEPTION_REGISTRATION PEXCEPTION_REGISTRATION;
_EXCEPTION_REGISTRATION struc
prev dd ? //上一个异常处理结构体指针的位置
handler dd ? //异常回调函数
_EXCEPTION_REGISTRATION ends
也就说明_EXCEPTION_REGISTRATION就是_EXCEPTION_REGISTRATION_RECORD
/*
微软定义的参考,表明结构体可以无限扩展
由使用者定义需要的参数,但是前两个参数必须为是prev和handler,这样扩展才不影响用fs:[0]去遍历异常链表,fs:[0]也就是执行链表的头结点
struct _EXCEPTION_REGISTRATION
{
struct _EXCEPTION_REGISTRATION *prev;
void (*handler)(PEXCEPTION_RECORD, PEXCEPTION_REGISTRATION, PCONTEXT, PEXCEPTION_RECORD);
struct scopetable_entry *scopetable;
int trylevel;
int _ebp;
PEXCEPTION_POINTERS xpointers;
};
*/
示例代码
使用微软提供的SEH,模拟c中的try和catch,并且手动异常展开进行清栈
.386
.model flat, stdcall ;32 bit memory model
option casemap :none ;case sensitive
include windows.inc
include kernel32.inc
include user32.inc
include msvcrt.inc
includelib KERNEL32.LIB
includeLIB user32.lib
includelib msvcrt.lib
.const
MAIN db "main: ", 0
FUN1 db "fun1: ", 0
FUN2 db "fun2: ", 0
MAIN_EXCE db "main except", 0
FORMAT db "%s ExceptionAddress=%p ExceptionCode=%p ExceptionFlags=%p NumberParameters:%d", 0
SAFETEXIT db "SafetyExit",0
.data
BUFFER db 256 dup(0)
MYSTACK db 256 dup(0)
FIRST dd 0
.code
;SEH结构化注册的宏
ExceptRegist macro Fun_Name,ContinueAddr
assume fs :nothing
lea eax,[esp-18h]
push eax ;拓展参数 4:保存 esp的地址
push ebp ;拓展参数 3:保存 ebp的地址
push offset ContinueAddr ;拓展参数 2:异常处理完后面应该执行的代码位置
push offset Fun_Name ;拓展参数 1:函数名
push offset except_handler ;结构体第二个参数:回调函数指指针
push fs:[0] ;结构体第一个参数:指向下一个异常结构的指针存放指向头结点(fs:[0]指向的就是头结点),这样就形成了链表头插入,也就形成了栈结构,后进先出
mov fs:[0], esp ;将栈里模拟的 EXCEPTION_REGISTRATION_RECORD结构体指针放在原来的 FS:[0]位置,也就是FS:[0]指向当前函数的异常结构
endm
;SEH结构化注销的宏
ExceptUnLoad macro
mov eax, [esp] ;拿出上一个异常处理结构体指针
mov fs:[0], eax ;还原至FS:[0]处
add esp, 18h
endm
except_handler proc c, ExceptionInfo:ptr EXCEPTION_RECORD, registration: ptr EXCEPTION_REGISTRATION, context:ptr CONTEXT, dispatcher:PVOID
mov esi, ExceptionInfo
assume esi :ptr EXCEPTION_RECORD
.if [esi].ExceptionFlags == 2
mov eax, ExceptionContinueExecution
ret
.endif
;遍历异常链表,拿到当前函数在第几层
mov ebx, registration
;字符串拼接显示当前函数的栈结构
.while ebx != NULL
mov edx, [ebx+8]
invoke crt_strcat, offset MYSTACK, edx
.if ebx == FIRST
.break
.endif
mov ebx, [ebx]
.endw
invoke wsprintf, offset BUFFER, offset FORMAT,
offset MYSTACK,
[esi].ExceptionAddress,
[esi].ExceptionCode,
[esi].ExceptionFlags,
[esi].NumberParameters
invoke MessageBox,NULL, offset BUFFER, NULL, MB_OK
;恢复栈,并继续执行下一层的代码
mov esi,context
assume esi :ptr CONTEXT
mov ebx,registration
mov edx,[ebx+0ch]
mov [esi].regEip,edx ;还原执行代码的位置
mov edx,[ebx+10h]
mov [esi].regEbp,edx ;还原ebp
mov edx,[ebx+14h]
mov [esi].regEsp,edx ;还原esp
mov eax, ExceptionContinueExecution ;继续执行代码
ret
except_handler endp
fun2 proc
;注册SEH
ExceptRegist FUN2,fun2_except_end
;异常代码
mov eax, 0
mov dword ptr [eax], 0
fun2_except_end:
;注销SEH
ExceptUnLoad
ret
fun2 endp
fun1 proc
;注册SEH
ExceptRegist FUN1,fun1_except_end
invoke fun2
fun1_except_end:
;注销SEH
ExceptUnLoad
ret
fun1 endp
START:
;注册SEH
ExceptRegist MAIN,main_except_end
mov FIRST, esp
invoke fun1
main_except_end:
invoke MessageBox,NULL,offset SAFETEXIT, NULL, MB_OK ;printf("SafetyExit");
invoke ExitProcess,0
;注销SEH
ExceptUnLoad
ret
end START