第八章 Windows 下的异常处理

8.1 异常处理的基本概念

中断:由外部硬件或异步事件产生的

异常:内部事件产生:分为故障,陷阱(均可恢复) 以及终止(不可恢复)

8.1.1 异常列表

8.1.2 异常处理的基本过程

当有异常发生时,CPU会通过中断描述符表 (Interrupt Descriptor Table, IDT)来寻找处理函数。

1.IDT

存于物理内存,线性表,256项,32位模式每个IDT的长度是八字节,64位下位64字节。 操作系统在启动阶段会初始化这给表。

CPU的IDTR 寄存器描述了IDT的位置和长度,高32位表示基址,低16位是表的长度。

读取指令:SIDT,LIDT (LIDT是特权指令,只能在Ring0下使用)

IDT的每一项都是一个门结构,包括如下三种门描述符

任务门(task-gate) 主要用 CPU的任务切换

中断门(Interrupt-gate)描述中断处理程序的入口

陷阱门(Trap-gate)异常处理程序的入口

2. 异常处理的准备工作

当中断发生时, CPU会根据中断类型号转而执行对应的中断处理程序

通常也会将异常信息进行封装,以便进行后续处理。

封装包含两部分,一部分是异常记录,包含本次异常的信息。结构如下

Excpetion code 记录了异常原因

另一部分为陷阱帧,它精确描述了发生异常时线程的状态,该结构与处理器相关,不同平台有不同定义

x86 定义如下


typedef struct _KTRAP_FRAME
{
     ULONG DbgEbp;   //用户EBP指针的拷贝,用于支持回溯命令KB 
     ULONG DbgEip;   //调用 系统调用 时的EIP,同上用于KB
     ULONG DbgArgMark; //标记显示这里没有参数
     ULONG DbgArgPointer;//指向实际参数
//当需要调整栈帧时,使用以下值作为临时变量
     WORD TempSegCs;
     ULONG TempEsp;
//调试寄存器
     ULONG Dr0;
     ULONG Dr1;
     ULONG Dr2;
     ULONG Dr3;
     ULONG Dr6;
     ULONG Dr7;
//段寄存器
     ULONG SegGs;
     ULONG SegEs;
     ULONG SegDs;
//易失寄存器
     ULONG Edx;
     ULONG Ecx;
     ULONG Eax;
//调试系统使用
     ULONG PreviousPreviousMode;
     PEXCEPTION_REGISTRATION_RECORD ExceptionList;
     ULONG SegFs;
//非易失寄存器
     ULONG Edi;
     ULONG Esi;
     ULONG Ebx;
     ULONG Ebp;
//控制寄存器
     ULONG ErrCode;
     ULONG Eip;
     ULONG SegCs;
     ULONG EFlags;
//其他特殊变量
     ULONG HardwareEsp;
     ULONG HardwareSegSs;
     ULONG V86Es;
     ULONG V86Ds;
     ULONG V86Fs;
     ULONG V86Gs;
} KTRAP_FRAME, 

该结构仅供系统内核自身使用,或者调试系统使用。当需要把控制权交给用户注册的异常处理程序,会将其转化为 名为CONTEXT的结构

typedef struct _CONTEXT
{
//标志位,表示整个结构中哪个部分是有效的
     ULONG ContextFlags;
//当ContextFlags 包含 CONTEXT_DEBUG_REGISTERS时,以下部分有效
     ULONG Dr0;
     ULONG Dr1;
     ULONG Dr2;
     ULONG Dr3;
     ULONG Dr6;
     ULONG Dr7;
//当ContextFlags包含CONTEXT_FLOATING_POINT时,以下部分有效
     FLOATING_SAVE_AREA FloatSave;
//当ContextFlags包含CONTEXT_SEGMENTS时,以下部分有效
     ULONG SegGs;
     ULONG SegFs;
     ULONG SegEs;
     ULONG SegDs;
//当ContextFlags包含CONTEXT_INTEGER时,以下部分有效
     ULONG Edi;
     ULONG Esi;
     ULONG Ebx;
     ULONG Edx;
     ULONG Ecx;
     ULONG Eax;
//当ContextFlags包含CONTEXT_CONTROL时,以下部分有效
     ULONG Ebp;
     ULONG Eip;
     ULONG SegCs;
     ULONG EFlags;
     ULONG Esp;
     ULONG SegSs;
//当ContextFlags包含CONTEXT_EXTENDED_REGISTERS时,以下部分有效
     UCHAR ExtendedRegisters[MAXIMUUM_SUPPORTED_EXTENSION];
} CONTEXT, *PCONTEXT;

包装完毕,异常处理函数会进一步调用系统内核的nt!KiDispatchException

第一个和第三个参数为上面封装的两个结构

3.内核态的异常处理过程

PreviousMode为KernelMode时,表示是内核模式下产生的异常,此时KiDispatchException会按照以下步骤分发异常

  • 检测当前系统是否正在被内核调试器调试,如果内核调试器不在,就跳过,如果存在,将异常处理的控制权转交给内核调试器,并注明是第一次处理机会。内核调试器取得控制权后,会根据用户对异常处理的设置来确定是否要处理该异常,如果无法确定,就会发生中断,把控制权交给用户,让用户处理。如果调试器正确处理了该异常,那么发生异常的线程就会回到原来产生异常的位置继续执行。
  • 如果不存在内核调试器,或者第一次处理机会时调试器选择不处理该异常,系统就会调用nt!RtlDispatchException函数,根据线程注册的结构化异常处理 SEH,来处理异常。
  • 如果nt!RtlDispatchExcpetion 函数没有处理该异常,系统会给调试器第二次处理机会,此时调试器可再次取得对异常的处理权。
  • 如果不存在内核调试器,或者在第二次机会调试器仍不处理,系统认为不能继续运行,系统回到用KeBugCheckEx 产生一个错误码为“KERNEL_MODE_EXCEPTION_NOT_HANDELD”的BSOD

4.用户态的异常处理过程

当PreviousMode为UserMode时,表示是用户模式下产生的异常,此时KiDispatchException函数仍会检测内核寄存器是否存在,如果调试器存在,会优先把控制权交给调试器处理,在大多数情况下,内核调试器对用户态的异常不感兴趣,此时nt!KiDispatchException函数仍然像处理内核态异常一样按两次处理机会分发,主要过程如下:

  • 如果发生异常的程序正在被调试,将异常信息发送给调试器,给调试器第一次处理机会,如果没有则跳过。
  • 在栈上放置EXCEPTION_RECORD和CONTEXT两个结构,并将控制权返回用户态ntdll.dll中的KiUserExceptionDispatcher函数,由他调用ntdll!RtlDispatchException函数进行用户态的异常处理。这一部分涉及SEH 和VEH两种异常处理机制,其中SEH部分应用程序调用API函数SetUnhandledExceptionFilter设置的顶级异常处理,但如果有调试器存在,顶级异常处理会被跳过,进行下一阶段的处理,否则将由顶级异常处理程序进行中介处理,如果没有调试器能够附加与其上,或调试器处理不了异常,系统就调用ExitProcess来终结程序。
  • 如果ntdll!RtlDispatchException 函数在调用用户态的异常处理中未能处理该异常,那么异常处理程序会再度返回 nt!KiDispatchException,他将再次把异常信息发送给用户态的调试器,如果没有调试器直接结束进程。

如果第二次调试器仍不处理,nt!KiDispatchException 会再次尝试把异常分发给的进程异常端口进行处理,该端口通常由csrss.exe进行监听,会显示一个应用程序错误对话框。

8.2 SEH的概念及基本知识

SEH Structured Exception Handling 结构化异常处理

一种错误和保护和修复机制,告诉系统出现异常或错误时应该由谁来处理。

系统在终结程序之前,给程序提供的一个执行其预先设定的回调函数的机会。

8.2.1 SEH的相关数据结构

  1. TIB 结构

Thread Information Block, 保存线程基本信息的结构,在用户模式下,位于TEB的头部(Thread Environment Block 线程环境块)

x86 平台用户模式 TEB 由 fs:[0] 指向

x64 由gs:[0]指向

当线程运行在内核模式,FS段选择器指向内核中的KPCRB结构(Processsor Control Region Block 处理器控制块)该结构的头部也是使用的上述的 NT_TIB结构

2. _EXCEPTION_REGISTRATION_RECORD

TEB 偏移量为0 的_EXCEPTION_REGISTRATION_RECORD 主要用于描述线程处理异常处理过程的地址

typedef struct _EXCEPTION_REGISTRATION_RECORD
{
     PEXCEPTION_REGISTRATION_RECORD Next; //指向下一个结构的指针
     PEXCEPTION_DISPOSITION Handler; 、、当前异常处理回调函数的地址
} EXCEPTION_REGISTRATION_RECORD, *PEXCEPTION_REGISTRATION_RECORD;

当程序运行过程中产生异常时,系统的异常分发器就会从fs:[0]处取得异常处理的链表头,然后查找异常处理链表并依次调用各个节点中的函数,由于TEB是线程的私有数据结构,每个线程有自己的异常处理链表

3.EXCEPTION_RECORD 和CONTEXT结构

前面有说。此处不再赘述。

4._EXCEPTION_POINTERS结构

typedef struct _EXCEPTION_POINTERS {
  PEXCEPTION_RECORD ExceptionRecord;
  PCONTEXT          ContextRecord;
} EXCEPTION_POINTERS, *PEXCEPTION_POINTERS;

当异常发生,操作系统会将异常信息转交给用户态,由于用户态和内核态使用不同的栈,操作系统把.EXCEPTION_RECORD 和CONTEXT结构 放到用户态栈中,同时放置._EXCEPTION_POINTERS 指向这两个结构。

8.2.2 SEH处理程序的安装和卸载

安装:填写一个新的EXCEPTION_REGISTRATION_RECORD结构,并插入fs:[0]链表的头部

在安装之前,需要准备一个符合SEH标准的回调函数,然后用如下代码进行安装

assume fs:nothing
    push offset SEHandler
    push fs:[0]
    mov fs:[0],esp

卸载SEH

mov esp,dword ptr ptr fs:[0]
pop dword ptr fs:[0]

8.3 SEH 异常处理程序原理及设计

8.3.1 异常分发的详细过程

用户态异常分发是从 ntdll!KiUserExceptionDispatcher 函数开始的,此时栈中有EXCEPTION_RECORD 和 CONTEXT 两个结构

(书中代码部分略)

  1. 调用 VEH ExceptionHandler 进行异常处理,若返回继续执行,则直接返回,否则继续执行SEH部分的处理
  2. 遍历当前进程的异常链表,逐一调用RtlpExecuteHandlerForException 该函数会调用SEH异常处理函数,根据不同返回值进行不同的处理
    • ExceptionContinueExecution 结束遍历并返回,对标记为EXCEPTION_NONCONTINUABLE的异常不允许再次恢复执行,会调用RtlRaiseException
    • ExceptionContinueSearch 继续遍历下一个节点
    • ExceptionNestedException 从指定的新异常开始继续遍历
  3. 调用VEHContinueHandler进行异常处理

8.3.2线程异常处理

1.线程异常处理的工作细节

回调函数的原型定义

      Handler proc C pExcept,pFrame,pContext,pDispatch                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         

pExcept : 指向包含异常处理信息的EXCEPTION_RECORD结构地址

pFrame: 指向SEH链中当前_EXCEPTION_REGISTRATION结构的地址

pContext 指向与线程相关的寄存器映像CONTEXT结构的地址

pDispatch: 用于内嵌异常的处理

//异常程序的返回值
typedef enum _EXCEPTION_DISPOSITION {
    ExceptionContinueExcution // 0
    ExceptionContinueSearch   // 1
    ExceptionNestedException //2
    ExceptionCoillidedUnwind // 3
} EXCEPTION_DISPOSITION;


///回调函数原型

EXCEPTION_DISPOSITION __cdecl _except_handler {
  struct _EXCEPTION_RECORD *ExceptionRecord,
  void * EstablisherFrame,
  struct _CONTEXT *ContextRecord,
  void * DispatcherContext
};

8.3.3 异常处理的栈展开

在异常无法解决时,系统在终结程序之前会给所有注册的回调函数一个调用,在调用之前将EXCEPTION_RECORD结构中的ExceptionFlags 置为2,将ExceptionCode域置为STATUS_UNWIND 这个回调的目的是给他们一个清理的机会,释放重要的系统资源,保存异常发生时关键变量的值等善后工作。

这面每太弄懂怎么回事…待续…

8.3.4 MSC编译器对线程异常处理的增强

  1. 增强的数据结构及定义
typedef struct _EXCEPTION_REGISTRATION PEXCEPTION_REGISTRATION;

typedef EXCEPTION_DISPOSITION (
(__CDECL *PEXCEPTION_ROUTINE)(
    struct _EXCEPTION_RECORD *_ExceptionRecord,
    void* _EstablisherFrame,
    struct _CONTEXT *_ContextRecord,
    void* *_DispatcherContext
);


typedef struct _SCOPETABLE_ENTRY {
    DWORD EnclosingLevel //上一层__try块
    PVOID FilterFunc;   //过滤块表达式
    PVOID HandlerFunc;  //__except块或__finally块代码
}SCOPETABLE_ENTRY,*PSCOPETABLE_ENTRY

struct _EH3_EXCEPTION_REGISTRATION
{
 struct _EH3_EXCEPTION_REGISTRATION *Next;
__try 
{
  //可能产生异常的代码
}
__finally
{
 //终结处理代码
  FinallyHandler();
}

__try/__finally 本身不能处理异常但是__finally块总会被执行,收尾和清理工作。

FilterFunc 称为异常筛选器

可以通过GetExceptionCode 和GetExceptionInformation 函数获取异常的信息,以判断是否能处理。

根据判断结果返回不同的值,通常有3中返回值

#define EXCEPTION_EXECUTE_HANDLER 1 //异常在意料之中,执行Handler
#define EXCEPTION_CONTINUE_SEARCH 0  // 不处理异常,寻找其他程序
#define EXCEPTION_CONTINUE_EXECUTION -1 //已被修复,回到异常现场

2. 编译器的SEH增强设计

MSC编译器每个使用__try/__except的函数,无论内部嵌用了多少个 __try/__except只注册一个EXCEPTION_REGISTRATION_RECORD结构,如果内部处理出现了多个__try/__except块,MSC提供了一个代理函数_EH3_EXCEPTION_REGISTRATION::ExceptionHandler,开发人员提供的多个__try/__except块被保存在_EH3_EXCEPTION_REGISTRATION::ScopeTable的数组中。

3.编译器的实际工作

  1. 准备工作,把FilterFunc 和HandlerFunc 编译成单独的函数,并按照try块出现的顺序及嵌套关系生成正确的SCOPETABLE
  2. 在函数开头不知CPPEH_RECORD结构,并安装SEH异常处理函数,VC6.0下这个函数名字为_except_handler3
  3. 在进入每个try块后,先设置当前Try块的TryLevel值,在执行Try块颞部的代码,退出Try块保护区域后,恢复为原来的TryLevel
  4. 在函数返回前,卸载SEH异常处理函数

8.3.5 顶层异常处理

1.顶层处理的由来

XP系统中,进程的实际启动位置是kernal32!BaseProcessStartThunk,然后才跳转到kernal32!BaseProcessStart

同样在使用CreateThread创建线程的时候,线程也不是直接从线程函数处开始运行的,他的起点是kernal32!BaseThreadStartThunk 然后跳转到kernal32!BaseThreadStart

操作系统在执行任意一个用户线程之前,都已经为它安装了一个默认的SEH处理程序,所以该SEH一定是最后一个

2.初识顶层处理的功能

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇