刘收获

导航

< 2025年3月 >
23 24 25 26 27 28 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31 1 2 3 4 5

统计

SEH X86

( windows 提供的异常处理机制实际上只是一个简单的框架,一般情况下开发人员都不会直接用到。我们通常所用的异常处理(比如 C++ 的 throw、try、catch)都是编译器在系统提供的异常处理机制上进行加工了的增强版本。这里先抛开增强版的不提,主要叙述原始版本。)

 0x01  系统如何找到异常链表

结构化异常处理是以线程为基础的。线程的内核数据结构体现是 _ETHREAD,从它开始进入,通过windbg,找到所关注的异常链表(win7 x86):


 

查看 _KTHREAD结构(dt _KTHREAD):

查看TEB:

查看TIB:

 

 _NT_TIB 的第一个域成员 ExceptionList 就是异常链表头。

 

但是系统不是这么一步一步找的,而是借助 FS 寄存器来加速寻找。先来说说系统对 FS 的使用。
在应用层,FS 寄存器“指向”当前执行线程的 _TEB 结构体。在内核层,FS 寄存器“指向”另一个跟 CPU 相关的结构体:_KPCR,来看看它的结构,

 


  与 _TEB 一样,它的第一个域成员也是 _NT_TIB,只不过此时是 nt!_NT_TIB,而在应用层是 ntdll!_NT_TIB,但它们的结构是一样的。

  这样,不论在应用层还是在内核层,系统都可以使用 FS:[0] 找到异常链表。

 

 

0x02  异常处理相关的结构

(1)_EXCEPTION_REGISTRATION_RECORD 

1
2
3
4
typedef struct _EXCEPTION_REGISTRATION_RECORD {
    struct _EXCEPTION_REGISTRATION_RECORD *Next;
    PEXCEPTION_ROUTINE Handler;
} EXCEPTION_REGISTRATION_RECORD;

 EXCEPTION_REGISTRATION_RECORD 结构就是登记信息。数据成员:
  1. EXCEPTION_REGISTRATION_RECORD::Next 域指向下一个 EXCEPTION_REGISTRATION_RECORD,由此构成一个异常登记信息(从字面上说,应该叫做“异常注册记录”更恰当)链表。链表中的最后一个结点会将 Next 置为 EXCEPTION_CHAIN_END,表示链表到此结束。
  2. EXCEPTION_REGISTRATION_RECORD::Handler 指向异常处理函数。

 

(2)异常的回调函数

1
2
3
4
5
EXCEPTION_DISPOSITION
__cdecl _except_handler( struct _EXCEPTION_RECORD *ExceptionRecord,
                        void * EstablisherFrame,
                        struct _CONTEXT *ContextRecord,
                        void * DispatcherContext);

 函数参数:

 1..第一个参数ExceptionRecord,是一个指向EXCEPTION_RECORD结构的指针

 2.第二个参数是一个指向establisher帧结构的指针。这个异常帧结构就是_EXCEPTION_REGISTRATION_RECORD。

 3.第三个参数是一个指向CONTEXT结 构的指针。此结构在WINNT.H中定义,它代表某个特定线程的寄存器值。当用于SEH时,CONTEXT结构表示 异常发生时寄存器的值。顺便说一下,这个CONTEXT结构就是GetThreadContext和SetThreadContext这两个API中使用 的那个CONTEXT结构。

 4.第四个参数是_DispatcherContext,用于存放下一个异常帧,全局展开在执行当前异常帧局部展开中发生嵌套展开时使用。

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
typedef struct _CONTEXT { 
   
    // 
    // The flags values within this flag control the contents of 
    // a CONTEXT record. 
    // 
    // If the context record is used as an input parameter, then 
    // for each portion of the context record controlled by a flag 
    // whose value is set, it is assumed that that portion of the 
    // context record contains valid context. If the context record 
    // is being used to modify a threads context, then only that 
    // portion of the threads context will be modified. 
    // 
    // If the context record is used as an IN OUT parameter to capture 
    // the context of a thread, then only those portions of the thread's 
    // context corresponding to set flags will be returned. 
    // 
    // The context record is never used as an OUT only parameter. 
    // 
   
    DWORD ContextFlags; 
   
    // 
    // This section is specified/returned if CONTEXT_DEBUG_REGISTERS is 
    // set in ContextFlags.  Note that CONTEXT_DEBUG_REGISTERS is NOT 
    // included in CONTEXT_FULL. 
    // 
   
    DWORD   Dr0; 
    DWORD   Dr1; 
    DWORD   Dr2; 
    DWORD   Dr3; 
    DWORD   Dr6; 
    DWORD   Dr7; 
   
    // 
    // This section is specified/returned if the 
    // ContextFlags word contians the flag CONTEXT_FLOATING_POINT. 
    // 
   
    FLOATING_SAVE_AREA FloatSave; 
   
    // 
    // This section is specified/returned if the 
    // ContextFlags word contians the flag CONTEXT_SEGMENTS. 
    // 
   
    DWORD   SegGs; 
    DWORD   SegFs; 
    DWORD   SegEs; 
    DWORD   SegDs; 
   
    // 
    // This section is specified/returned if the 
    // ContextFlags word contians the flag CONTEXT_INTEGER. 
    // 
   
    DWORD   Edi; 
    DWORD   Esi; 
    DWORD   Ebx; 
    DWORD   Edx; 
    DWORD   Ecx; 
    DWORD   Eax; 
   
    // 
    // This section is specified/returned if the 
    // ContextFlags word contians the flag CONTEXT_CONTROL. 
    // 
   
    DWORD   Ebp; 
    DWORD   Eip; 
    DWORD   SegCs;              // MUST BE SANITIZED 
    DWORD   EFlags;             // MUST BE SANITIZED 
    DWORD   Esp; 
    DWORD   SegSs; 
   
    // 
    // This section is specified/returned if the ContextFlags word 
    // contains the flag CONTEXT_EXTENDED_REGISTERS. 
    // The format and contexts are processor specific 
    // 
   
    BYTE    ExtendedRegisters[MAXIMUM_SUPPORTED_EXTENSION]; 
   
} CONTEXT; 
   
   
   
typedef CONTEXT *PCONTEXT; 

  

(3)EXCEPTION_RECORD结构

1
2
3
4
5
6
7
8
typedef struct _EXCEPTION_RECORD {
       DWORD ExceptionCode;                        //操作系统提供给异常的一个数,代表异常发生的原因。
       DWORD ExceptionFlags;
       struct _EXCEPTION_RECORD *ExceptionRecord;
       PVOID ExceptionAddress;
       DWORD NumberParameters;
       ULONG_PTR ExceptionInformation[EXCEPTION_MAXIMUM_PARAMETERS];
   } EXCEPTION_RECORD;

  参数 ExceptionCode 是操作系统分配给异常的号。在 WINNT.H 文件中查找开头为“STATUS_” 的宏就能找到一大堆这样的异常 代号。例如,大家熟知的 STATUS_ACCESS_VIOLATION 的代号就是 0xC0000005。更为完整的异常代号可以 从 Windows NT DDK 中的 NTSTATUS.H 文件里找到。

  EXCEPTION_RECORD 结构体的第四个元素是异常发生处的地 址。其余的 EXCEPTION_RECORD 域目前都可以忽略掉。

 

 

0x03  处理过程

 当接收到异常后,系统找到当前线程(前面说过异常是线程相关的。系统接收到的异常就是当前正在运行的线程触发的。其实这个说法还不准确,DPC 也会触发异常,而它是线程无关的,这里为了方便理解,当前先只考虑线程)的异常链表,从链表中的第一个结点开始遍历,找到一个 EXCEPTION_REGISTRATION_RECORD 就调用它的 Handler,并把该异常(由第一个类型为 EXCEPTION_RECORD 的参数表示)传递给该 Handler,Handler 处理并返回一个类型为 EXCEPTION_DISPOSITION 的枚举值。该返回值指示系统下一步该做什么
  ExceptionContinueExecution ——已修正了此异常的故障,重新执行异常产生出代码。
  ExceptionContinueSearch     ——没有处理此异常,请继续搜索其他的解决方案
  ExceptionNestedException 和 ExceptionCollidedUnwind 暂不解释。
  这样系统根据不同的返回值来继续遍历异常链表或者回到触发点继续执行。

 

(1)内核处理流程

    在windows kernel中,存在一张中断描述符表(IDT, Interupt Descriptor Table). IDT是一张位于内核态物理内存中的线性表,其有256个表项。IDT中的每个表项叫做门描述符(Gate Descriptor)。门描述符的基本作用就是将CPU异常对应的中断号与其对应的异常处理函数KiTrapXX关联起来。

例如,0号中断(即除0错误)对应的处理例程为nt!KiTrap00

    可以在windbg中用!idt -a命令查看。

    

 

   首先,CPU 执行的指令触发了异常,CPU 改执行 IDT 中 KiTrapXX,KiTrapXX 会调用 KiDispatchException。该函数原型如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
VOID
KiDispatchException (
    IN PEXCEPTION_RECORD ExceptionRecord,
       // 指向 ExceptionRecord的指针   
    IN PKEXCEPTION_FRAME ExceptionFrame,
        // 对于X86 为空
    IN PKTRAP_FRAME TrapFrame,
        //函数异常记录块指针,由调用者 KiKernelTrapHandler 传递
    IN KPROCESSOR_MODE PreviousMode,
        // 指示内核态还是用户态
    IN BOOLEAN FirstChance
        // 是否是第一次尝试
    );   

  

 

  

posted on   沉疴  阅读(361)  评论(0编辑  收藏  举报

编辑推荐:
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
阅读排行:
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 零经验选手,Compose 一天开发一款小游戏!
· 通过 API 将Deepseek响应流式内容输出到前端
· AI Agent开发,如何调用三方的API Function,是通过提示词来发起调用的吗
历史上的今天:
2017-01-18 通过注册表获取计算机相关信息
2017-01-18 GetTickCount()函数
点击右上角即可分享
微信分享提示