结构化异常处理 
    当某一线程发生异常时,程序的控制权会立即进入Ring0异常处理程序,这是属于操作系统的部分, 如果发生的异常是如页异常之类的异常,Ring0处理程序可以处理完它后重新回到程序中执行,而被中断 过的进程可能根本就不知道发生过异常。 
    但事情并不总是如此,有时进程会发生一些始料不及的异常,例如访问不存在的内存,被0除等, 这些异常Ring0处理程序不知该如何处理它,而进程本身也可能想自己处理这些情况,这是就要用到结构化异常处理(SEH)。在C/C++中也有异常处理的语句如_try,_catch等,这些语句的实现也与SEH紧密联系。 
    当系统遇到一个它不知道如何处理的异常时,它就查找异常处理链表,注意每个线程都有它自己的异 常处理链表。异常链表以FS:[0]所指向的位置为链表头。 
    异常处理开始时,系统把一些与当前线程和与异常有关的内容传给链头所指向的处理程序;处理程序 由用户编写或编译器生成,它的返回值可以是告诉系统:异常处理以完成,可以继续执行程序,或未处理 异常,可由链表的下一个处理程序处理等,可以一次传递下去。 

  下面我以以前写的一个WinMD5程序来说明:


下面是未运用SEH链的情况下的部分关键代码:

引用:

GetMd5Thread PROC PFile:DWORD
       
        LOCAL   @FileText[MAX_PATH]:BYTE
  LOCAL   @TEMP[MAX_PATH]:BYTE
  LOCAL  @hFileRead,@hMapFile,@pMemory,@FileSize    
       
      invoke CreateFile, PFile,\ 
                                                GENERIC_READ ,\ 
                                                0,\ 
                                                NULL,OPEN_EXISTING,FILE_ATTRIBUTE_ARCHIVE,\ 
                                                NULL 
        .if eax==0 
          jmp reterr
        .endif

        
      
      mov    @hFileRead,eax
      invoke ExtractFileName,PFile,addr @FileText  ; 取短文件名 
      
      invoke CreateFileMapping,@hFileRead,NULL,PAGE_READONLY,0,0,NULL 
         .if eax==0
             invoke CloseHandle,@hFileRead
             jmp reterr
          ret  
         .endif
      
      mov @hMapFile,eax

      invoke MapViewOfFile,@hMapFile,FILE_MAP_READ,0,0,0 
       .if eax==0  
             invoke CloseMapFile,@hMapFile,@hFileRead
             jmp reterr
         .endif
      
                        mov @pMemory,eax  
                        invoke  SendDlgItemMessage,hWinMain,IDC_PGB,PBM_SETPOS,1,0   ;进度开始(注意:这里并不是实时进度)                      
                        invoke  GetFileSize,@hFileRead,0  
                        mov @FileSize,eax                               
      invoke   _MD5,@pMemory,eax                                                 
                        invoke wsprintf ,addr  @TEMP,addr  szReceive, addr @FileText, @FileSize,eax                                                                                                                                 
      invoke  SendDlgItemMessage,hWinMain,IDC_EDT_OUT,EM_REPLACESEL,0,addr @TEMP      
      invoke  SendDlgItemMessage,hWinMain,IDC_PGB,PBM_SETPOS,100,0  ;进度结束  
      invoke  RtlZeroMemory,addr FileName,MAX_PATH                  ;  清空数据    
      INVOKE SendDlgItemMessage,hWinMain,IDC_EDT_FILE,WM_SETTEXT,0, addr FileName          
      invoke UnmapViewOfFile,@pMemory  
      invoke CloseMapFile,@hMapFile,@hFileRead
                        ret
       
       reterr:
       
       invoke MessageBox,0,CTEXT("文件打开失败,请检查是否为有效文件!"),CTEXT("WinMd5 for ASM v1.1"),MB_ICONERROR OR MB_OK
       
        ret

GetMd5Thread endp


上面红色的部分,是我们自己预防错误的代码,这个程序不算复杂,如果复杂点的程序,其代码肯定会显得很臃肿,并且可能会忽略些错误,给用户带来不好的使用感受,那么有没有一种机制可以统一处理这些错误了?当然有,这就是我们的SHE机制了
下面是上面的程序采用了SEH机制的全部源码:
引用:

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
;  Programmed by nohacks, nohacks@163.com
;  Website: http://hi.baidu.com/nohacks
;           Win32 ASM is Masm
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
;  版本信息
;  WinMD5 for ASM V1.1   - 可以拖放取得文件的MD5值
;     
;                         2007年11月21日
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>


.386
.model  flat,  stdcall
option  casemap  :none

include    windows.inc
include    user32.inc
include         shell32.inc
include    kernel32.inc
includelib  user32.lib
includelib  kernel32.lib
include    comdlg32.inc
includelib  comdlg32.lib
includelib      shell32.lib
include          debug.inc
include        Stdlib.Inc
includelib  Stdlib.lib

m2m  Macro M1,M2
  push  M2
  pop    M1
endm
  
  
  
SEH struct 
PrevLink dd ?    ; 用来保存SHE原地址
CurrentHandler dd ?    ; SEH处理函数地址
SafeOffset dd ?    ; 执行恢复地址 
PrevEsp dd ?      ; 用来保存 SEH前的esp 
PrevEbp dd ?     ;  用来保存 SEH前的ebp 
SEH ends

    
    
.const          
 ICO_ICO                EQU             1
 DLG_MAIN    EQU    1000
 IDC_EDT_FILE           EQU             1001
 IDC_EDT_OUT            EQU             1002
 IDC_PGB                EQU             1003
 IDC_BTN_EXIT           EQU             1004
.data? 
hWinMain    dd    ?
hInstance    dd    ?
hMenu HANDLE dword ? 
TheThread DWORD ?
 ValidPE dd ?                           
.data
FileName          db      MAX_PATH  dup(0)
temp              db      '%s',0DH,0AH,0
FilterString    db   "全部文件(*.*)",0,"*.*",0,0
szReceive         db       '文件名   :%s',0dh,0ah
                  db       '文件大小 :%d','字节',0dh,0ah
                  db       '文件MD5值:%s ',0dh,0ah,0
TEMP              db       '.',0
 
include    md5.asm



_SetWindowCenter proc _hWnd:DWORD
LOCAL swidth,sheight,dwidth,dheight
LOCAL rect:RECT
invoke GetSystemMetrics,SM_CXSCREEN    
        mov swidth,eax                          
        invoke GetSystemMetrics,SM_CYSCREEN    
        mov sheight,eax                          
        invoke GetWindowRect,_hWnd,addr  rect   
        mov eax,rect.right                      
        sub eax,rect.left                       
        mov dwidth,eax                         
        sub swidth,eax                         
        mov eax,rect.bottom                 
        sub eax,rect.top                    
        mov dheight,eax                       
        sub sheight,eax                         
        shr sheight,1                           
        shr swidth,1                            
        invoke SetWindowPos,_hWnd,HWND_NOTOPMOST,swidth,sheight,dwidth,dheight,SWP_SHOWWINDOW
ret
_SetWindowCenter endp

;********************************************************************
CloseMapFile PROC hMapFile:DWORD,hFileRead:DWORD
        invoke CloseHandle,hMapFile 
        ;mov    hMapFile,0 
        invoke CloseHandle,hFileRead 
        ret 
CloseMapFile endp 

GetMd5Thread PROC PFile:DWORD
        LOCAL seh:SEH
        LOCAL md5 ,ProcessId
        LOCAL   @FileText[MAX_PATH]:BYTE
  LOCAL   @TEMP[MAX_PATH]:BYTE
  LOCAL  @hFileRead,@hMapFile,@pMemory,@FileSize  
   
          ;SEH异常处理 
           
           assume fs:nothing 
                 push fs:[0] 
                 pop seh.PrevLink
                 mov seh.CurrentHandler,offset SEHHandler 
                 mov seh.SafeOffset,offset FinalExit 
                 lea eax,seh 
                 mov fs:[0], eax 
                 mov seh.PrevEsp,esp 
                 mov seh.PrevEbp,ebp 


           invoke CreateFile, PFile,\ 
                                                GENERIC_READ ,\ 
                                                0,\ 
                                                NULL,OPEN_EXISTING,FILE_ATTRIBUTE_ARCHIVE,\ 
                                                0   
      
      mov    @hFileRead,eax
                            
      invoke ExtractFileName,PFile,addr @FileText  ; 取短文件名 
      
      invoke  GetFileSize,@hFileRead,0     ;取文件尺寸
                        mov @FileSize,eax  
          
      invoke CreateFileMapping,@hFileRead,NULL,PAGE_READONLY,0,0,NULL 
      mov @hMapFile,eax
                        invoke MapViewOfFileEx,@hMapFile,FILE_MAP_READ,0,0,0,0
                        mov @pMemory,eax
                        invoke  SendDlgItemMessage,hWinMain,IDC_PGB,PBM_SETPOS,1,0   ;进度开始(注意:这里并不是实时进度)                      
                        invoke   _MD5,@pMemory,  @FileSize
      
      ;MD5转为大写
      mov md5,eax
      invoke lstrlen, md5
      invoke CharUpperBuff,md5,eax
                                                       
                        invoke wsprintf ,addr  @TEMP,addr  szReceive, addr @FileText, @FileSize,md5                                                                                                                               
      invoke  SendDlgItemMessage,hWinMain,IDC_EDT_OUT,EM_REPLACESEL,0,addr @TEMP      
      invoke  SendDlgItemMessage,hWinMain,IDC_PGB,PBM_SETPOS,100,0  ;进度结束  
      invoke  RtlZeroMemory,addr FileName,MAX_PATH                  ;  清空数据    
      INVOKE SendDlgItemMessage,hWinMain,IDC_EDT_FILE,WM_SETTEXT,0, addr FileName          
      invoke UnmapViewOfFile,@pMemory  
      invoke CloseMapFile,@hMapFile,@hFileRead
      
      ;断开SHE链
      
       push seh.PrevLink 
                        pop fs:[0] 
    
                       
                        ret
       

      SEH执行恢复地址: 

       FinalExit:
           
       invoke MessageBox,0,CTEXT("文件打开失败,请检查是否为有效文件!"),CTEXT("WinMd5 for ASM v1.1"),MB_ICONERROR OR MB_OK
       
        ret

GetMd5Thread endp

_ProcDlgMain  proc  uses ebx edi esi, \
                hWnd:DWORD,wMsg:DWORD,wParam:DWORD,lParam:DWORD
     
  LOCAL  @hMem,@hFile,@Size,@Read  
        mov  eax,wMsg
  cmp     eax ,WM_DROPFILES
  je      GetFile  
  cmp  eax,WM_COMMAND                                  
  je  Exit
  cmp  eax,WM_INITDIALOG                             
  je  boxStart
  cmp  eax,WM_CLOSE
  je  boxClose  
          
retFalse:
  mov  eax,FALSE
  ret

boxClose:
                                           
   invoke  EndDialog,hWnd,NULL
   jmp  retTrue

boxStart:

        push  hWnd
  pop  hWinMain
    invoke LoadIcon,hInstance,ICO_ICO
    invoke SendMessage,hWnd,WM_SETICON,ICON_BIG,eax    ;设置窗口图标  
    invoke SetWindowPos,hWnd,HWND_TOPMOST,0,0,0,0, SWP_NOMOVE or SWP_NOSIZE ;窗口置顶
    INVOKE _SetWindowCenter,hWinMain     ;使窗体出现在屏幕中心
    invoke DragAcceptFiles,hWnd,TRUE     ;允许拖放文件
    jmp  retTrue

GetFile:
 
     invoke DragQueryFile,wParam,0,addr FileName,MAX_PATH                          ;取拖放文件名
     invoke SendDlgItemMessage,hWnd,IDC_EDT_FILE,EM_REPLACESEL,0, addr FileName    ;输出到编辑框 
     invoke  DragFinish,wParam                                                     ;释放拖放资源
     invoke CreateThread,NULL,0,addr  GetMd5Thread,addr FileName,0,NULL             ;创建线程
     
     
  Exit:
   mov     eax,wParam
    .if          eax==IDC_BTN_EXIT
   invoke  ExitProcess,NULL
   .endif
   
              
retTrue:
  mov  eax,TRUE
  ret
_ProcDlgMain  endp
;--------------------------------------------------------------------;
;函数功能:SEH回调函数
;-------------------------------------------------------------------;
SEHHandler proc uses edx pExcept:DWORD, pFrame:DWORD, pContext:DWORD, pDispatch:DWORD 
    mov edx,pFrame 
    assume edx:ptr SEH 
    mov eax,pContext 
    assume eax:ptr CONTEXT 
    push [edx].SafeOffset 
    pop [eax].regEip 
    push [edx].PrevEsp 
    pop [eax].regEsp 
    push [edx].PrevEbp 
    pop [eax].regEbp 
    mov eax,ExceptionContinueExecution 
    ret 
SEHHandler endp 



Start:
  invoke  GetModuleHandle,NULL
  mov    hInstance,eax
  invoke  DialogBoxParam,hInstance,DLG_MAIN,NULL,_ProcDlgMain,0
  invoke  ExitProcess,NULL
End  Start


分析:


SEH struct 
PrevLink dd ?    ; 用来保存SHE原地址
CurrentHandler dd ?    ; SEH处理函数地址
SafeOffset dd ?    ; 执行恢复地址 
PrevEsp dd ?      ; 用来保存 SEH前的esp 
PrevEbp dd ?     ;  用来保存 SEH前的ebp 
SEH ends


程序开始先注册一个SEH结构

GetMd5Thread PROC PFile:DWORD
        LOCAL seh:SEH
        LOCAL md5 ,ProcessId
        LOCAL   @FileText[MAX_PATH]:BYTE
  LOCAL   @TEMP[MAX_PATH]:BYTE
  LOCAL  @hFileRead,@hMapFile,@pMemory,@FileSize  

   ;SEH异常处理 
           
   assume fs:nothing 
                 push fs:[0] 
                 pop seh.PrevLink                                                 ;备份当前SEH
                 mov seh.CurrentHandler,offset SEHHandler      ;SEH处理函数地址
                 mov seh.SafeOffset,offset FinalExit                   ;执行恢复地址
                 lea eax,seh                                                          
                 mov fs:[0], eax                                                  ;装载我们的SHE
                 mov seh.PrevEsp,esp                                        ;备份ESP
                 mov seh.PrevEbp,ebp 
                         ;备份EBP

程序打开文件前前建立SEH,一开始就假设寄存器 fs为空(assume fs:nothing)。 记住这一步不能省却,因为MASM假设fs寄存器为ERROR。接下来保存 Windows使用的旧SEH处理函数地址到我们自己定义的结构中,同时保存我们的SEH处理函数地址和异常处理时的执行恢复地址,这样一旦错误发生就能由异常处理函数安全地恢复执行了。同时还保存当前esp及ebp的值,以便我们的SEH处理函数将堆栈恢复到正常状态。
  
 invoke CloseMapFile,@hMapFile,@hFileRead
      
   ;断开SHE链
      
  push seh.PrevLink 
                pop fs:[0]   
  
                       

一旦SEH不再使用,必须从SEH链上断开。

FinalExit:
           
       invoke MessageBox,0,CTEXT("文件打开失败,请检查是否为有效文件!"),CTEXT("WinMd5 for ASM v1.1"),MB_ICONERROR OR MB_OK
       
  ret

这里就是我们在前面定义的SEH执行恢复地址,当程序从错误中恢复过来,就会执行这里。

在本例中,如果用户拖放的是个文件夹,程序就会发生异常,就会进入了我们指定的处理程序

SEHHandler proc uses edx esi pExcept:DWORD, pFrame:DWORD, pContext:DWORD, pDispatch:DWORD 
   
   LOCAL ExceptionCode  
 
    mov edx,pFrame 
    assume edx:ptr SEH 
    mov eax,pContext 
    assume eax:ptr CONTEXT 

    mov esi, pExcept

    m2m ExceptionCode ,[esi][EXCEPTION_RECORD.ExceptionCode]  

   ;you code                     错误代码记录在变量ExceptionCode里
 
    debug TEXTEQU <调试信息:SEH安装成功!>
 
    %echo debug
  
    我们处理的第一件事就是输出相应的调试信息,之后开始真正的异常处理。

   ;执行恢复地址
    push [edx].SafeOffset 
    pop [eax].regEip 
   ;恢复ESP 
    push [edx].PrevEsp 
    pop [eax].regEsp 
   ;恢复EBP
    push [edx].PrevEbp 
    pop [eax].regEbp 
 
     在本例中异常处理只是简单地恢复esp、ebp寄存器的值并将eip寄存器的值置为一个能使线程安全地继续执行的地址。这些处理信息都是由系统从我们预先填充的seh结构体中抽出并保存在了CONTEXT结构体中,CONTEXT结构体的指针又传递给了系统。

   mov eax,ExceptionContinueExecution 
    ret 

      我们以ExceptionContinueExecution的值来返回(该值为零),就是告诉系统应该恢复线程的上下文并继续执行。即eip的值等于标记FinalExit的地址,而esp和ebp寄存器的值恢复为原值,线程从标记FinalExit处继续其执行。

上面程序的SEH相关代码,您完全可以直接COPY到您的代码中。

经过上面2个代码的比较,我们应该知道了SEH的妙用,熟练运用能使您的代码更简洁高效。
上传的附件 Md5Win091118.zip