SEH结构体异常处理

异常的主要结构体信息

一般当程序发生异常时,用户代码停止执行,并将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
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值