Windows异常处理

概述

异常机制,就是为了让计算机能够更好的处理程序行期间产生的错误,从编程的角度来看,能够将错误的处理与程序的逻辑分隔开。使得我们可以集中精力开发关键功能,而把

程序可能出现的异常统一管理。

windows提供了异常处理的机制,使得你有机会挽救自己即将崩溃的程序,大体上来说

它提供了以下处理异常的机制:

SEH-结构化异常处理

VEH-向量化异常处理

VCH-向量化异常处理

异常&中断

中断:外部硬件产生的,改变CPU执行流程的机制

异常:CPU内部产生的,改变CPU执行流程的机制

都是通过IDT统一管理

异常分类

错误:可以修复,EIP指向当前错误指令的位置

陷阱:可以修复,EIP指向错误指令下一条指令的位置

终止:不可修复

结构化异常处理SEH

Structed Exception Handler(结构化异常处理)称SEH,是微软提供的一种处理异常

的机制。

在VC++中,通过提供四个微软关键字使得程序员能够良好的使用这一机制,分别是: try, finally, except, _leave接下来,我们简要的介绍一下它的用法。

try与 finally

首先出场的是_try与 __finally组合

1
2
3
4
5
6
7
8
__try
{
//被保护的代码块
}
__finally
{
//终结处理块
}

在这段代码中,try内的是被保护的代码块, finally内的是终结处理器,无论try内的代码以何种方式离开被保护区域,均会执行终结处理器。

离开被保护的代码块,就会执行终结处理块:

1正常方式:自己执行完代码或者执行_ leave

2非正常方式:产生了异常或者由于 return goto break, continue等流程控制语句离开了保护代码块。

在终结处理块中我们可以使用下面这个函数获取是正常方式还是非正常方式

1
int __cdecl AbnormalTermination (void);

当获取到离开方式后,我们可以做出相应的判断,是继续执行程序,重新启动,释放资源还是尝试解决错误。

注意:在被保护区尽量使用_ leave,而不是 return

try与except

1
2
3
4
5
6
7
8
__try
{
//被保护的代码
}
__except(/*过滤表达式*/)
{
//异常处理块
}

在这里过滤表达式的值共有三个

1
2
3
//EXCEPTION_EXECUTE_HANDLER      1  : 执行处理块内容,不会回到原来位置
//EXCEPTION_CONTINUE_SEARCH 0 : 异常处理不了了,去找其它的处理
//EXCEPTION_CONTINUE_EXECUTION (-1) : 不相信,回到异常位置继续执行

只要被保护的代码块产生异常,就会执行except,过滤表达式的形式是任意的,比如可以是数值,可以是函数调用,可以是运算表达式,只要表达式的值为以上三个即可

函数异常处理

有两个函数在异常处理中非常有用

第一个: 获取异常代码
1
unsigned long GetExceptionCode(void)

该函数的返回值代表异常类型

1
2
3
4
5
6
EXCEPTION_ACCESS_VIOLATION				//表示产生的是内存访问方面的异常
EXCEPTION_DATATYPE_MISALIGNMENT //
EXCEPTION_BREAKPOINT //表示产生了断点方面的异常
EXCEPTION_SINGLE_STEP //表示产生了单步断点方面的异常
EXCEPTION_...
......
第二个:获取异常信息
1
2
3
4
5
6
_EXCEPTION_POINTERS * GetExceptionInformation()
typedef struct _EXCEPTION_POINTERS
{
PEXCEPTION_RECORD ExceptionRecord;
PCONTEXT ContextRecord;
}EXCEPTION_POINTERS,*PEXCEPTION_POINTERS;
异常错误码
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
/* compatibility macros */
#define STILL_ACTIVE STATUS_PENDING
#define EXCEPTION_ACCESS_VIOLATION STATUS_ACCESS_VIOLATION
#define EXCEPTION_DATATYPE_MISALIGNMENT STATUS_DATATYPE_MISALIGNMENT
#define EXCEPTION_BREAKPOINT STATUS_BREAKPOINT
#define EXCEPTION_SINGLE_STEP STATUS_SINGLE_STEP
#define EXCEPTION_ARRAY_BOUNDS_EXCEEDED STATUS_ARRAY_BOUNDS_EXCEEDED
#define EXCEPTION_FLT_DENORMAL_OPERAND STATUS_FLOAT_DENORMAL_OPERAND
#define EXCEPTION_FLT_DIVIDE_BY_ZERO STATUS_FLOAT_DIVIDE_BY_ZERO
#define EXCEPTION_FLT_INEXACT_RESULT STATUS_FLOAT_INEXACT_RESULT
#define EXCEPTION_FLT_INVALID_OPERATION STATUS_FLOAT_INVALID_OPERATION
#define EXCEPTION_FLT_OVERFLOW STATUS_FLOAT_OVERFLOW
#define EXCEPTION_FLT_STACK_CHECK STATUS_FLOAT_STACK_CHECK
#define EXCEPTION_FLT_UNDERFLOW STATUS_FLOAT_UNDERFLOW
#define EXCEPTION_INT_DIVIDE_BY_ZERO STATUS_INTEGER_DIVIDE_BY_ZERO
#define EXCEPTION_INT_OVERFLOW STATUS_INTEGER_OVERFLOW
#define EXCEPTION_PRIV_INSTRUCTION STATUS_PRIVILEGED_INSTRUCTION
#define EXCEPTION_IN_PAGE_ERROR STATUS_IN_PAGE_ERROR
#define EXCEPTION_ILLEGAL_INSTRUCTION STATUS_ILLEGAL_INSTRUCTION
#define EXCEPTION_NONCONTINUABLE_EXCEPTION STATUS_NONCONTINUABLE_EXCEPTION
#define EXCEPTION_STACK_OVERFLOW STATUS_STACK_OVERFLOW
#define EXCEPTION_INVALID_DISPOSITION STATUS_INVALID_DISPOSITION
#define EXCEPTION_GUARD_PAGE STATUS_GUARD_PAGE_VIOLATION
#define EXCEPTION_INVALID_HANDLE STATUS_INVALID_HANDLE
#define EXCEPTION_POSSIBLE_DEADLOCK STATUS_POSSIBLE_DEADLOCK
#define CONTROL_C_EXIT STATUS_CONTROL_C_EXIT
示例代码
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
#include <Windows.h>
#include <stdio.h>

DWORD Filter(DWORD ExceptionCode, PEXCEPTION_POINTERS pExceptionInfomation)
{
// 异常信息结构体
//typedef struct _EXCEPTION_RECORD {
// DWORD ExceptionCode; // 异常错误码
// DWORD ExceptionFlags; // 异常标志
// struct _EXCEPTION_RECORD* ExceptionRecord; //异常信息
// PVOID ExceptionAddress; // 异常错误地址
// DWORD NumberParameters; // 附加参数(内存错误时有效)
// ULONG_PTR ExceptionInformation[EXCEPTION_MAXIMUM_PARAMETERS];
//} EXCEPTION_RECORD;

// 如果异常是整形除零错误
if (ExceptionCode == EXCEPTION_INT_DIVIDE_BY_ZERO)
{
// 异常信息
pExceptionInfomation->ExceptionRecord;
// 线程上下文 把a变量中内容赋值位1
*(DWORD*)(pExceptionInfomation->ContextRecord->Ebp - 0x20) = 1;
//return EXCEPTION_CONTINUE_EXECUTION; // 不相信,继续执行
}
// 找下一个
return EXCEPTION_CONTINUE_SEARCH;
}


int main()
{
__try { // 外层seh

// 2. __try,__except组合
__try {
int a = 0;
int c = 10 / a;
printf("c = %d", c);
}
__except (Filter(GetExceptionCode(),GetExceptionInformation())) {
printf("这是SEH处理块\n"); //处理块处理
}
}
__except (EXCEPTION_EXECUTE_HANDLER)
{
printf("我是外部SEH\n");
}
return 0;
}

执行结果

1
c = 10
示例代码2

结构化异常处理的嵌套

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

#include <windows.h>
#include <stdio.h>

static unsigned int nStep = 1;
void Fun_B() {
int x, y = 0;
__try { x = 5 / y; } // 引发异常
__finally { printf("Step %d :执行Fun_B的finally块的内容\n", nStep++); }
}
void Fun_A() {
__try { Fun_B(); }
__finally { printf("Step %d :执行Fun_A的finally块的内容\n", nStep++); }
}
long MyExcepteFilter() {
printf("Step %d :执行main的异常过滤器\n", nStep++);
return EXCEPTION_EXECUTE_HANDLER;
}


int main() {

__try {

}
__finally
{
printf("finally\n");

}

//__try { Fun_A(); }
//__except (MyExcepteFilter()) {
// printf("Step %d :执行main的except块的内容\n", nStep++);
//}
system("pause");
return 0;
}

执行结果

1
2
3
4
5
finally
Step 1 :执行main的异常过滤器
Step 2 :执行Fun_B的finally块的内容
Step 3 :执行Fun_A的finally块的内容
Step 4 :执行main的except块的内容

剖析SEH

我们都知道异常产生后,是先发给内核层,二Windows提供的异常处理机制,一定是在内核层发出来之后才处理的

Windows仅通过try,except等几个关键字就实现了接管异常,并且能够四处穿梭,而不造成程序奔溃,到底怎么实现的

对于线程而言,在内核层他有一个数据结构叫做ETHREAD,在用户层也有一个数据结构叫做TEB

在双机调试场景下,使用dt _TEB 查看TEB结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
kd> dt _TEB
nt!_TEB
+0x000 NtTib : _NT_TIB
+0x01c EnvironmentPointer : Ptr32 Void
+0x020 ClientId : _CLIENT_ID
+0x028 ActiveRpcHandle : Ptr32 Void
+0x02c ThreadLocalStoragePointer : Ptr32 Void
+0x030 ProcessEnvironmentBlock : Ptr32 _PEB
+0x034 LastErrorValue : Uint4B
+0x038 CountOfOwnedCriticalSections : Uint4B
+0x03c CsrClientThread : Ptr32 Void
+0x040 Win32ThreadInfo : Ptr32 Void
+0x044 User32Reserved : [26] Uint4B
+0x0ac UserReserved : [5] Uint4B
+0x0c0 WOW32Reserved : Ptr32 Void
+0x0c4 CurrentLocale : Uint4B
+0x0c8 FpSoftwareStatusRegister : Uint4B
+0x0cc SystemReserved1 : [54] Ptr32 Void
+0x1a4 ExceptionCode : Int4B
+0x1a8 ActivationContextStackPointer : Ptr32 _ACTIVATION_CONTEXT_STACK
+0x1ac SpareBytes : [36] UChar
+0x1d0 TxFsContext : Uint4B
......

可以看出TEB第一个字段是NtTib,而NtTib的第一个字段是ExceptionLIst,这是一个链表的头节点地址,链表元素定义如下:

1
2
3
4
typedef struct _EXCEPTION_REGISTRATION_RECORD {
struct _EXCEPTION_REGISTRATION_RECORD *Next; // 指向下一个SEH
PEXCEPTION_ROUTINE Handler; // 异常处理函数
} EXCEPTION_REGISTRATION_RECORD;

注:输入 dt _NT_TEB可查看该字段

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
00051810 55 push ebp
00051811 8B EC mov ebp,esp
00051813 6A FE push 0FFFFFFFEh
00051815 68 D8 8E 05 00 push 58ED8h
0005181A 68 60 1C 05 00 push offset _except_handler4 (051C60h)//异常处理函数
0005181F 64 A1 00 00 00 00 mov eax,dword ptr fs:[00000000h]//异常链
00051825 50 push eax
00051826 81 C4 2C FF FF FF add esp,0FFFFFF2Ch
0005182C 53 push ebx
0005182D 56 push esi
0005182E 57 push edi
0005182F 8D BD 1C FF FF FF lea edi,[ebp-0E4h]
00051835 B9 33 00 00 00 mov ecx,33h
0005183A B8 CC CC CC CC mov eax,0CCCCCCCCh
0005183F F3 AB rep stos dword ptr es:[edi]
00051841 A1 04 A0 05 00 mov eax,dword ptr [__security_cookie(05A004h)]
00051846 31 45 F8 xor dword ptr [ebp-8],eax
00051849 33 C5 xor eax,ebp
0005184B 50 push eax
0005184C 8D 45 F0 lea eax,[ebp-10h]
0005184F 64 A3 00 00 00 00 mov dword ptr fs:[00000000h],eax
00051855 89 65 E8 mov dword ptr [ebp-18h],esp
00051858 B9 15 C0 05 00 mov ecx,offset _AF661F63_main@cpp(05C015h)
0005185D E8 B5 F9 FF FF call @__CheckForDebuggerJustMyCode@4(051217h)
00051862 C7 45 FC 00 00 00 00 mov dword ptr [ebp-4],0
00051869 C7 45 E0 00 00 00 00 mov dword ptr [ebp-20h],0
00051870 8B 45 E0 mov eax,dword ptr [ebp-20h]
00051873 C7 00 64 00 00 00 mov dword ptr [eax],64h
00051879 C7 45 FC FE FF FF FF mov dword ptr [ebp-4],0FFFFFFFEh
00051880 EB 1D jmp $LN6+17h (05189Fh)
$LN5:
00051882 B8 01 00 00 00 mov eax,1
$LN7:
00051887 C3 ret

$LN6:
00051888 8B 65 E8 mov esp,dword ptr [ebp-18h]
0005188B 68 30 7B 05 00 push offset string "__except" (057B30h)
00051890 E8 B1 F7 FF FF call _printf (051046h)
00051895 83 C4 04 add esp,4
00051898 C7 45 FC FE FF FF FF mov dword ptr [ebp-4],0FFFFFFFEh
0005189F 8B F4 mov esi,esp
000518A1 FF 15 7C B1 05 00 call dword ptr [__imp__getchar(05B17Ch)]
000518A7 3B F4 cmp esi,esp
000518A9 E8 73 F9 FF FF call __RTC_CheckEsp (051221h)
000518AE 33 C0 xor eax,eax
000518B0 8B 4D F0 mov ecx,dword ptr [ebp-10h]
000518B3 64 89 0D 00 00 00 00 mov dword ptr fs:[0],ecx
000518BA 59 pop ecx
000518BB 5F pop edi
000518BC 5E pop esi
000518BD 5B pop ebx
000518BE 81 C4 E4 00 00 00 add esp,0E4h
000518C4 3B EC cmp ebp,esp
000518C6 E8 56 F9 FF FF call __RTC_CheckEsp (051221h)
000518CB 8B E5 mov esp,ebp
000518CD 5D pop ebp
000518CE C3 ret

当异常产生时,系统会通过 FS:[0]找到这个链表,依此调用去处理异常,这才是SEH的核心原理,微软提供的关键字只是让你使用起来更加快捷

手工注册SEH

了解了SEH的大致机理,我们自己就能注册SEH

hander处理函数原型

1
2
3
4
5
6
7
LONG NTAPI
EXCEPTION_ROUTINE (
_Inout_ struct _EXCEPTION_RECORD *ExceptionRecord, //异常信息
_In_ PVOID EstablisherFrame, //陷阱帧
_Inout_ struct _CONTEXT *ContextRecord, //线程上下文
_In_ PVOID 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
#include <Windows.h>
#include <stdio.h>
// 异常链中节点
//typedef struct _EXCEPTION_REGISTRATION_RECORD {
// struct _EXCEPTION_REGISTRATION_RECORD* Next; // 指向下一个SEH02
// PEXCEPTION_ROUTINE Handler; // 异常处理函数
//} EXCEPTION_REGISTRATION_RECORD;
int g_number;
// 异常处理函数
EXCEPTION_DISPOSITION NTAPI SEH_Handler(
_Inout_ struct _EXCEPTION_RECORD* ExceptionRecord, //异常信息
_In_ PVOID EstablisherFrame, //陷阱帧
_Inout_ struct _CONTEXT* ContextRecord, //线程上下文
_In_ PVOID DispatcherContext //异常分发上下文(系统使用)
) {
ContextRecord->Eax = (DWORD)&g_number;
// ExceptionContinueExecution 0 继续执行
// ExceptionContinueSearch, 1 查找下一个异常
return ExceptionContinueExecution;
}
// SEH基于栈上的异常处理
int main()
{
// 1.构建一个_EXCEPTION_REGISTRATION_RECORD结构体
// 2.将这个结构体插入到异常链中
// [next]
// [hanlder]
_asm
{
push SEH_Handler; // 异常处理函数
mov eax, fs: [0] ; // 获取next节点
push eax; // 压入next节点
mov fs:[0],esp; // 插入到异常链表中
}
// 添加SEH
int* p = NULL;
*p = 100;
// 3. 卸载SEH异常节点
_asm
{
mov eax, fs: [0] ; // 获取第一个异常节点
mov eax, [eax]; // 异常节点的第一个字段(Next)
mov fs : [0] , eax; // 删除第一个节点
add esp, 0x08; // 平衡堆栈
}
getchar();
return 0;
}

向量化异常处理之VEH

相对于结构化异常处理的作用范围,向量化异常处理就显得很大气了。这种异常处理方式是全局的,一经注册,全进程有效,使用方便,居家旅行

VEH是向量化异常处理, 使用AddVectorExceptionHandle来设置

使用向量化异常只需要我们提供一个回调函数,然后用一个API注册一下即可。以后不管进程内发生了什么异常,都会来调用你的回调函数。

常见的向量化有两种形式:VEH和VCH。

对于VEH来说,我们首先需要知道一个API

注册向量化函数

1
2
3
4
5
6
7
8
PVOID
WINAPI
AddVectoredExceptionHandler(
_In_ ULONG First,
_In_ PVECTORED_EXCEPTION_HANDLER Handler
);
//参数1:异常处理函数被调用的顺序
//参数2:异常处理回调函数

函数回调的原型

1
2
3
4
5
6
7
8
typedef LONG (NTAPI *PVECTORED_EXCEPTION_HANDLER)(
struct _EXCEPTION_POINTERS *ExceptionInfo
);
/*
回调函数的返回值只有两种情况
EXCEPTION_CONTINUE_EXECUTION (-1) : 继续执行
EXCEPTION_CONTINUE_SEARCH (0) :继续搜索
*/

示例代码

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
#include <windows.h>
#include <stdio.h>


int a = 0;


LONG NTAPI veh_handler1(
struct _EXCEPTION_POINTERS* ExceptionInfo
)
{
printf("VEH 1 执行 \n");
// 如果异常是整形除零错误
if (ExceptionInfo->ExceptionRecord->ExceptionCode == EXCEPTION_INT_DIVIDE_BY_ZERO)
{
// 线程上下文 把a变量中内容赋值位1
a = 100;
return EXCEPTION_CONTINUE_EXECUTION; // 不相信,继续执行
}

// 返回值 两种方式
return EXCEPTION_CONTINUE_SEARCH; // 0处理不了,找其VEH处理
return EXCEPTION_CONTINUE_EXECUTION; // -1,处理了,回到异常位置继续执行
}

LONG NTAPI veh_handler2(
struct _EXCEPTION_POINTERS* ExceptionInfo
)
{
printf("VEH 2 执行 \n");
// 返回值 两种方式
return EXCEPTION_CONTINUE_SEARCH; // 0处理不了,找其VEH处理
}

// SEH处理函数
DWORD SEH_handler(PEXCEPTION_POINTERS pInfomation)
{

printf("SEH 异常处理执行 \n");
return EXCEPTION_CONTINUE_SEARCH;
}

// 异常处理顺序-》 VEH->SEH

int main()
{
// 使用veh注册向量化异常处理函数
// 说全进程有效
AddVectoredExceptionHandler(
TRUE, //调用VEH顺序
veh_handler1 //veh回调函数
);

AddVectoredExceptionHandler(
TRUE, //调用VEH顺序
veh_handler2 //veh回调函数
);

__try {

int c = 10 / a;
printf("c = %d", c);
}
__except (SEH_handler(GetExceptionInformation()))
{
printf("SEH处理块\n");
}

return 0;
}

执行结果

1
2
3
VEH 2  执行
VEH 1 执行
c = 0

向量化异常处理值VCH

同VEH一样,也是需要一个API和同一个回调函数,不过仅仅只是函数名字不一样而已

1
2
3
4
5
6
PVOID
WINAPI
AddVectoredContinueHandler(
_In_ ULONG First,
_In_ PVECTORED_EXCEPTION_HANDLER Handler
);

回调函数

1
2
3
typedef LONG (NTAPI *PVECTORED_EXCEPTION_HANDLER)(
struct _EXCEPTION_POINTERS *ExceptionInfo
);

示例代码

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
#include <windows.h>
#include <stdio.h>


int a = 0;


LONG NTAPI vch_handler1(
struct _EXCEPTION_POINTERS* ExceptionInfo
)
{
printf("VCH 1 执行 \n");
// 如果异常是整形除零错误
//if (ExceptionInfo->ExceptionRecord->ExceptionCode == EXCEPTION_INT_DIVIDE_BY_ZERO)
//{
// // 线程上下文 把a变量中内容赋值位1
// a = 100;
// return EXCEPTION_CONTINUE_EXECUTION; // 不相信,继续执行
//}

// 返回值 两种方式
return EXCEPTION_CONTINUE_SEARCH; // 0处理不了,找其VEH处理
return EXCEPTION_CONTINUE_EXECUTION; // -1,处理了,回到异常位置继续执行
}


// SEH处理函数
DWORD SEH_handler(PEXCEPTION_POINTERS pInfomation)
{

printf("SEH 异常处理执行 \n");
return EXCEPTION_EXECUTE_HANDLER; // 在exception处理块处理

return EXCEPTION_CONTINUE_SEARCH;
}


// 异常处理流程: SEH->VCH

int main()
{
// 使用vch注册向量化异常处理函数
// 说全进程有效
AddVectoredContinueHandler(
TRUE, //调用VCH顺序
vch_handler1 //vch回调函数
);

__try {

int c = 10 / a;
printf("c = %d", c);
}
__except (SEH_handler(GetExceptionInformation()))
{
printf("SEH处理块\n");
}

return 0;
}

执行结果

1
2
SEH 异常处理执行
SEH处理块

默认的异常处理函数

1
2
3
SetUnhandledExceptionFilter(
_In_opt_ LPTOP_LEVEL_EXCEPTION_FILTER lpTopLevelExceptionFilter
);

回调函数原型

1
2
3
typedef LONG (WINAPI *PTOP_LEVEL_EXCEPTION_FILTER)(
_In_ struct _EXCEPTION_POINTERS *ExceptionInfo
);

UEH异常处理

示例代码

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
#include <windows.h>
#include <stdio.h>


int a = 0;


LONG NTAPI ueh_handler1(
struct _EXCEPTION_POINTERS* ExceptionInfo
)
{
printf("ueh 1 执行 \n");


// 返回值 两种方式
return EXCEPTION_CONTINUE_SEARCH; // 0处理不了,找其VEH处理
return EXCEPTION_CONTINUE_EXECUTION; // -1,处理了,回到异常位置继续执行
}

// SEH处理函数
DWORD SEH_handler(PEXCEPTION_POINTERS pInfomation)
{

printf("SEH 异常处理执行 \n");
return EXCEPTION_CONTINUE_SEARCH;
}

int main()
{
// 1.设置一个默认SEH处理函数(UEH)
// 2. UEH只有在没有调试器的情况下执行(反调试)
SetUnhandledExceptionFilter(ueh_handler1);

__try {

int c = 10 / a;
printf("c = %d", c);
}
__except (SEH_handler(GetExceptionInformation()))
{
printf("SEH处理块\n");
}

return 0;
}

一直没有处理

执行结果,(不会结束)

1
SEH 异常处理执行

SetUnHandleExceptionFilter

设置一个最后的异常处理函数. 这个异常处理函数在所有的VEH,SEH都没有成功处理异常时会被调用.
注意:在进程被调试时,被设置的UEH异常处理函数不会被调用,因为这个差异,此机制也会被用于反调试.

异常调用顺序

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
#include <Windows.h>
#include <stdio.h>


int a = 0;
LONG NTAPI VEH_handler1(
struct _EXCEPTION_POINTERS* ExceptionInfo
)
{
printf("VEH 1 执行 \n");
a = 100;
//return EXCEPTION_CONTINUE_EXECUTION; //处理了
return EXCEPTION_CONTINUE_SEARCH; // 0处理不了,找其VEH处理
}

LONG NTAPI VCH_handler1(
struct _EXCEPTION_POINTERS* ExceptionInfo
)
{
printf("VCH 1 执行 \n");

return EXCEPTION_CONTINUE_SEARCH; // 0处理不了,找其VEH处理
}

LONG NTAPI SEH_handler1(
struct _EXCEPTION_POINTERS* ExceptionInfo
)
{
printf("SEH 1 执行 \n");
a = 100;
return EXCEPTION_CONTINUE_EXECUTION;
return EXCEPTION_CONTINUE_SEARCH; // 0处理不了,找其VEH处理
}

LONG NTAPI UEH_handler1(
struct _EXCEPTION_POINTERS* ExceptionInfo
)
{
printf("UEH 1 执行 \n");
a = 100;
return EXCEPTION_CONTINUE_EXECUTION; // 0处理不了,找其VEH处理
}


int main()
{
// 执行顺序(最重要)
// VEH -> SEH -> UEH -> VCH
// 没有调试器条件下
// VEH 可以处理 - 会调用VCH
// VEH 不能处理,SEH能执行 - 调用VCH
// VEH 不能处理,SEH不能处理,UEH能执行 - 调用VCH
// VEH,SEH,UEH都不能处理,VCH不会调用
// 有调试器条件下
// VEH 处理了-》会调用VCH
// VEH 没有处理,SEH 处理了/没有处理 -》 都调用VCH
// VEH 没有处理,SEH 处理了/没有处理 -》 都调用VCH


AddVectoredExceptionHandler(TRUE, VEH_handler1);
//AddVectoredContinueHandler(TRUE, VCH_handler1);
SetUnhandledExceptionFilter(UEH_handler1);


__try {

int c = 10 / a;
printf("c = %d", c);

}
__except (SEH_handler1(GetExceptionInformation()))
{
printf("SEH处理块\n");

}

getchar();

}

执行结果

1
2
3
VEH 1 执行
SEH 1 执行
c = 0

小结

一、当异常交由用户处理时,按照一下顺序调用异常处理方式:VEH -> SEH -> UEH -> VCH

二、当VEH表示处理了异常,就不会传递给SEH,但是会传递给VCH

三、当VEH没有处理了,就会传递给SEH

四、当SEH所有异常函数没有能够处理异常,会调用默认的SEH处理函数

五、当SEH处理了异常,从except开始执行,就不会在将异常传递给VCH

六、当SEH返回异常产生处执行,在返回之前会调用VCH

剖析Windows异常流程

CPU会根据中断类型号转而执行对应的中断处理程序。CPU会在IDT中查找对应的函数来处理,各个异常处理函数不仅仅处理异常还需要将异常信息封装,以便对后续处理。

例如:处理int3异常

1、分析int3

​ 通过中断描述符表(IDT)的第三项对应的处理函数(KiTrap03)

2、KiTrap03

​ 在开始处理之初,先构造TRAP_FRAME陷阱帧结构,陷阱帧是一个结构体,用来保存系统调用、中断、异常发生时的寄存器现场,方便以后回到用户空间/回到中断处时,恢复寄存器的值,从而继续执行。结构构造完成后会调用ComminDispathException函数

3、ComminDispathException

​ 注意:eax保存异常码 0x80000003

​ ebx保存异常地址

​ ecx保存异常附加参数个数

​ ComminDispathException函数会在栈中分配一个EXCEPTION_RECORD结构,并把异常信息存储到该结构中,之后他会调用_KiDispatchException函数来分发异常

​ EXCEPTION_POINTERS结构体 , 这个结构体中, 有两个结构体, 一个是CONTEXT结构体,保存的是产生异常时所有寄存器的值. 另一个结构体是EXCEPTION_RECORD,保存有异常类型(ExceptionCode),异常产生的地址(ExceptionAddress).

4、_KiDispatchException

​ 参数1:ecx 保存 ExceptionRecord结构体地址

​ 参数2: 0 空异常异常栈帧

​ 参数3:ebp保存陷阱帧

​ 参数4:eax保存异常发生的模式(内核、用户)

​ 参数5:1 异常分发次数

​ 不管什么异常最后都会调用内核中的KiDispatchExcption函数进行分发,也就是说Windows用统一的方式来管理。异常封装完成后,系统会调用nt!KiDispatchException来处理异常,所以分析KiDispatchException函数就可以了解异常是如何被处理的。

  • 构建一个ContextFrame ,从陷阱帧中获取寄存器信息

  • 判断内核模式还是用户模式:

    • 内核模式
      • 判断是否第一次触发
        • 是否有内核调试器,有就发给内核调试器
        • 再发给内核的SEH处理
      • 第二次触发
        • 发给内核调试器
        • 内核调试器没有处理就蓝屏
    • 用户模式
      • 判断是否第一次触发
        • 判断是否有内核调试器
        • 有就调用内核调试器
        • 没有就发给用户调试器
        • 将内核的CONTEXT,EXCEPTION_RECORD拷贝到用户栈
        • 用户层异常处理KeUserExceptionDispatcher
      • 第二次触发
        • 将异常发给调试器
        • 结束进程

KeUserExceptionDispatcher

  • 用户从异常分发

      • 交给VEH->SEH->UEH->VCH
      • 有一个异常处理机制处理了,通过ZwContinue恢复程序继续执行
      • 没有处理,通过ZwRaiseException主动抛出第二次异常

_KiTrap03源代码

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
_KiTrap03       proc
push 0 ; push dummy error code

; ENTER_TRAP 是一个宏, 用于开辟栈帧保存一些东西
ENTER_TRAP kit3_a, kit3_t

cmp ds:_PoHiberInProgress, 0
jnz short kit03_01

lock inc ds:_KiHardwareTrigger ; trip hardware analyzer

kit03_01:
mov eax, BREAKPOINT_BREAK ; BREAKPOINT_BREAK equ 00000H

KiTrap03DebugService:
;
; If caller is user mode, we want interrupts back on.
; . all relevant state has already been saved
; . user mode code always runs with ints on
;
; If caller is kernel mode, we want them off!
; . some state still in registers, must prevent races
; . kernel mode code can run with ints off
;
;
; Arguments:
; eax - ServiceClass - which call is to be performed
; ecx - Arg1 - generic first argument
; edx - Arg2 - generic second argument
;

.errnz (EFLAGS_V86_MASK AND 0FF00FFFFh)
test byte ptr [ebp]+TsEFlags+2,EFLAGS_V86_MASK/010000h ;检查是否在虚拟8086模式产生
jnz kit03_30 ; fault occured in V86 mode => Usermode

.errnz (MODE_MASK AND 0FFFFFF00h)
test byte ptr [ebp]+TsSegCs,MODE_MASK;
jz kit03_10

cmp word ptr [ebp]+TsSegCs,KGDT_R3_CODE OR RPL_MASK
jne kit03_30

kit03_05:
sti
kit03_10:

; 调用CommonDispatchException函数, 并将参数通过寄存器传递过去,
; CommonDispatchException函数的参数是:
; (eax) = 异常代码
; (ebx) = 异常地址
; (ecx) = 附加参数的个数
; (edx) = Parameter1(附加参数1)
; (esi) = Parameter2(附加参数2)
; (edi) = Parameter3(附加参数3)
;
; Set up exception record and arguments for raising breakpoint exception
;

mov esi, ecx ; ExceptionInfo 2 附加参数2
mov edi, edx ; ExceptionInfo 3 附加参数3
mov edx, eax ; ExceptionInfo 1 附加参数1

mov ebx, [ebp]+TsEip
dec ebx ; (ebx)-> int3 instruction,陷阱异常,需要将异常发生的地址减int3指令的长度(1)
mov ecx, 3 ; 附加参数的个数
mov eax, STATUS_BREAKPOINT ; STATUS_BREAKPOINT equ 0x80000003h
call CommonDispatchException ; Never return

CommonDispatchException源代码

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
 proc            ; 函数开始位置
cPublicFpo 0, ExceptionRecordLength/4
;
; Set up exception record for raising exception
;

sub esp, ExceptionRecordLength ; 在栈中开辟异常记录所需的栈空间
; allocate exception record
mov dword ptr [esp]+ErExceptionCode, eax ; 设置异常代码,写入到栈空间中异常记录结构体变量中.
; set up exception code
xor eax, eax
mov dword ptr [esp]+ErExceptionFlags, eax ; 设置异常标志(清0)
; set exception flags
mov dword ptr [esp]+ErExceptionRecord, eax
; set associated exception record
mov dword ptr [esp]+ErExceptionAddress, ebx ; 设置异常地址
mov dword ptr [esp]+ErNumberParameters, ecx; 附加参数个数
; set number of parameters
cmp ecx, 0
je short de00

lea ebx, [esp + ErExceptionInformation] ; 取结构体变量的首地址
mov [ebx], edx ; 将附加参数1存入到结构体中
mov [ebx+4], esi ; 将附加参数2存入到结构体中
mov [ebx+8], edi ; 将附加参数3存入到结构体中
de00:
;
; set up arguments and call _KiDispatchException
;
; 将异常记录结构体首地址存入ecx中.
mov ecx, esp ; (ecx)->exception record

; 判断异常发生的模式
.errnz (EFLAGS_V86_MASK AND 0FF00FFFFh)
test byte ptr [ebp]+TsEFlags+2,EFLAGS_V86_MASK/010000h
jz short de10

mov eax,0FFFFh
jmp short de20

de10: mov eax,[ebp]+TsSegCs
de20: and eax,MODE_MASK

; 调用函数来分发异常
; ecx - exception record addr 异常记录结构体地址
; 0 - Null exception frame 空的异常栈帧
; ebp - trap frame addr 异常记录帧
; eax - PreviousMode 异常发生的模式(内核层或用户层)
; 1 - first chance TRUE 第一次异常
stdCall _KiDispatchException,<ecx, 0, ebp, eax, 1>

; 恢复栈顶
mov esp, ebp ; (esp) -> trap frame
jmp _KiExceptionExit

CommonDispatchException endp

_KiDispatchException源代码

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
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
VOID
KiDispatchException (
IN PEXCEPTION_RECORD ExceptionRecord,
IN PKEXCEPTION_FRAME ExceptionFrame,
IN PKTRAP_FRAME TrapFrame,
IN KPROCESSOR_MODE PreviousMode,
IN BOOLEAN FirstChance
)

/*++

Routine Description:

This function is called to dispatch an exception to the proper mode and
to cause the exception dispatcher to be called. If the previous mode is
kernel, then the exception dispatcher is called directly to process the
exception. Otherwise the exception record, exception frame, and trap
frame contents are copied to the user mode stack. The contents of the
exception frame and trap are then modified such that when control is
returned, execution will commense in user mode in a routine which will
call the exception dispatcher.
该函数用于分发异常, 根据异常所产生的模式找到异常的处理函数. 如果异常发生在
内核模式, 函数会直接调用一个异常处理函数直接处理异常. 如果异常发生在用户模
式, 则函数会将异常记录, 异常栈帧, 和陷阱栈帧拷贝到用户模式的线程的栈 (这些
信息在用户态可以被修改, 被修改后会重新设置到线程环境上), 随后函数会进入到用
户态, 到了用户态之后会又专门的函数去处理异常.
Arguments:

ExceptionRecord - Supplies a pointer to an exception record.

ExceptionFrame - Supplies a pointer to an exception frame. For NT386,
this should be NULL.

TrapFrame - Supplies a pointer to a trap frame.

PreviousMode - Supplies the previous processor mode.

FirstChance - Supplies a boolean value that specifies whether this is
the first (TRUE) or second (FALSE) chance for the exception.

Return Value:

None.

--*/

{
CONTEXT ContextFrame; // 线程环境块
EXCEPTION_RECORD ExceptionRecord1, ExceptionRecord2; // 异常记录块
LONG Length;
ULONG UserStack1; // 保存用户态中栈上的一个地址
ULONG UserStack2; // 保存用户态中栈上的一个地址

//
// Move machine state from trap and exception frames to a context frame,
// and increment the number of exceptions dispatched.
// 获取发生异常的线程的线程上下文,输出到ContextRecord结构体变量中.

KeGetCurrentPrcb()->KeExceptionDispatchCount += 1;
ContextFrame.ContextFlags = CONTEXT_FULL | CONTEXT_DEBUG_REGISTERS;

if ((PreviousMode == UserMode) || KdDebuggerEnabled) {
//
// For usermode exceptions always try to dispatch the floating
// point state. This allows exception handlers & debuggers to
// examine/edit the npx context if required. Plus it allows
// exception handlers to use fp instructions without destroying
// the npx state at the time of the exception.
//
// Note: If there's no 80387, ContextTo/FromKFrames will use the
// emulator's current state. If the emulator can not give the
// current state, then the context_floating_point bit will be
// turned off by ContextFromKFrames.
//

ContextFrame.ContextFlags |= CONTEXT_FLOATING_POINT;
if (KeI386XMMIPresent) {
ContextFrame.ContextFlags |= CONTEXT_EXTENDED_REGISTERS;
}
}

//陷阱帧中获取寄存器信息
KeContextFromKframes(TrapFrame, ExceptionFrame, &ContextFrame);

//
// if it is BREAK_POINT exception, we subtract 1 from EIP and report
// the updated EIP to user. This is because Cruiser requires EIP
// points to the int 3 instruction (not the instruction following int 3).
// In this case, BreakPoint exception is fatal. Otherwise we will step
// on the int 3 over and over again, if user does not handle it
//
// if the BREAK_POINT occured in V86 mode, the debugger running in the
// VDM will expect CS:EIP to point after the exception (the way the
// processor left it. this is also true for protected mode dos
// app debuggers. We will need a way to detect this.
//
//

switch (ExceptionRecord->ExceptionCode) {
case STATUS_BREAKPOINT: // 断点异常就将eip减到int3指令所在的首地址
ContextFrame.Eip--;
break;

case KI_EXCEPTION_ACCESS_VIOLATION: // 内存访问异常, 设置附加参数
ExceptionRecord->ExceptionCode = STATUS_ACCESS_VIOLATION;
if (PreviousMode == UserMode) {
if (KiCheckForAtlThunk(ExceptionRecord,&ContextFrame) != FALSE) {
goto Handled1;
}

if ((SharedUserData->ProcessorFeatures[PF_NX_ENABLED] == TRUE) &&
(ExceptionRecord->ExceptionInformation [0] == EXCEPTION_EXECUTE_FAULT)) {

if (((KeFeatureBits & KF_GLOBAL_32BIT_EXECUTE) != 0) ||
(PsGetCurrentProcess()->Pcb.Flags.ExecuteEnable != 0) || // 启用执行属性
(((KeFeatureBits & KF_GLOBAL_32BIT_NOEXECUTE) == 0) &&
(PsGetCurrentProcess()->Pcb.Flags.ExecuteDisable == 0))) { // 禁用执行属性
ExceptionRecord->ExceptionInformation [0] = 0; // 设置附加参数
}
}
}
break;
}

//
// Select the method of handling the exception based on the previous mode.
//

ASSERT ((
!((PreviousMode == KernelMode) &&
(ContextFrame.EFlags & EFLAGS_V86_MASK))
));

// 如果异常是在内核模式下被触发
if (PreviousMode == KernelMode) {

//
// Previous mode was kernel.
//
// If the kernel debugger is active, then give the kernel debugger the
// first chance to handle the exception. If the kernel debugger handles
// the exception, then continue execution. Else attempt to dispatch the
// exception to a frame based handler. If a frame based handler handles
// the exception, then continue execution.
//
// If a frame based handler does not handle the exception,
// give the kernel debugger a second chance, if it's present.
//
// If the exception is still unhandled, call KeBugCheck().
//

// 先判断是否是第一次触发
if (FirstChance == TRUE) {
// 调用KiDebugRoutine函数指针(实际就是内核调试器,如果没有内核调试器,
// 函数指针保存的是KdpTrap函数的地址,如果有,则保存KdpStub函数地址)
if ((KiDebugRoutine != NULL) &&
(((KiDebugRoutine) (TrapFrame, // 线程环境
ExceptionFrame, // 异常栈帧
ExceptionRecord, // 异常信息记录
&ContextFrame, //
PreviousMode,
FALSE)) != FALSE)) {
// 如果调试处理了异常, 则跳转到函数末尾.
goto Handled1;
}
// 没有调试器, 或者有调试器但没有处理得了异常
// 则将异常交给内核得SEH来处理
if (RtlDispatchException(ExceptionRecord, &ContextFrame) == TRUE) {
// 如果SEH处理成功, 则跳转到函数末尾
goto Handled1;
}
}


// 如果调试器和SEH异常处理都没有处理得了异常, 则进行第二次异常分发:
// This is the second chance to handle the exception.
//
// 判断有无内核调试器,并调用(再给内核调试器一次处理异常得机会)
if ((KiDebugRoutine != NULL) &&
(((KiDebugRoutine) (TrapFrame,
ExceptionFrame,
ExceptionRecord,
&ContextFrame,
PreviousMode,
TRUE)) != FALSE)) {

goto Handled1;
}
// 没有内核调试器, 或内核调试器不处理, 则调用函数KeBugCheckEx记录异常,之后蓝屏死机
KeBugCheckEx(
KERNEL_MODE_EXCEPTION_NOT_HANDLED,
ExceptionRecord->ExceptionCode,
(ULONG)ExceptionRecord->ExceptionAddress,
(ULONG)TrapFrame,
0);

}
else // 异常在在用户模式下触发
{

//
// Previous mode was user.
//
// If this is the first chance and the current process has a debugger
// port, then send a message to the debugger port and wait for a reply.
// 如果异常是第一次分发并且进程具有调试端口(被调试状态), 则发送一个消息
// 到调试端口,并等待回复.
// If the debugger handles the exception, then continue execution. Else
// 如果调试器处理了这个异常, 则结束异常的分发. 否则,
// transfer the exception information to the user stack, transition to
// 将异常信息拷贝到用户态的栈中, 并转到
// user mode, and attempt to dispatch the exception to a frame based
// 用户模式 , 在用户模式下尝试将异常派发给异常处理程序.
// handler. If a frame based handler handles the exception, then continue
// 如果异常处理程序处理了异常, 则结束异常分发.
// execution with the continue system service. Else execute the
// 如果用户层的异常处理程序处理不了, 则调用NtRaiseException函数
// NtRaiseException system service with FirstChance == FALSE, which
// 主动触发异常, 并将FirstChance设置为TRUE. 这个函数(KiDispatchException)
// will call this routine a second time to process the exception.
// 将会被第二次调用以继续处理异常.
// If this is the second chance and the current process has a debugger
// 如果这次处理是第二次异常处理,并且进程有一个调试
// port, then send a message to the debugger port and wait for a reply.
// 端口, 则发送一个消息到调试端口,并等待调试器回复.
// If the debugger handles the exception, then continue execution. Else
// 如果调试器回复已经处理了异常, 则结束异常分发. 否则
// if the current process has a subsystem port, then send a message to
// 如果当前触发异常的进程有子系统端口,则发送一个消息到
// the subsystem port and wait for a reply. If the subsystem handles the
// 子系统端口,并等其回复. 若子系统处理了
// exception, then continue execution. Else terminate the process.
// 异常, 则异常分发结束, 否则直接结束掉当前进程.
// If the current process is a wow64 process, an alignment fault has
// occurred, and the AC bit is set in EFLAGS, then clear AC in EFLAGS
// and continue execution. Otherwise, attempt to resolve the exception.
//

//
if (FirstChance == TRUE) {

//
// This is the first chance to handle the exception.
//
// 检查是进程是否被调试,如果当前有内核调试器, 并且进程没有被调试,则将异常交给
// 内核调试器去处理.
if ((KiDebugRoutine != NULL) &&
((PsGetCurrentProcess()->DebugPort == NULL &&
!KdIgnoreUmExceptions) ||
(KdIsThisAKdTrap(ExceptionRecord, &ContextFrame, UserMode)))) {
//
// Now dispatch the fault to the kernel debugger.
//
// 将异常信息交给内核调试器处理
if ((((KiDebugRoutine) (TrapFrame,
ExceptionFrame,
ExceptionRecord,
&ContextFrame,
PreviousMode,
FALSE)) != FALSE)) {
// 处理成功则异常分发结束
goto Handled1;
}
}
// 将异常交给调试子系统去处理. DbgkForwardException函数会将
// 异常记录发送给3环的调试器进程, 并等待3环的调试器回复.
// 如果调试器回复了异常被处理, 则异常分发到此结束.
if (DbgkForwardException(ExceptionRecord, TRUE, FALSE)) {
goto Handled2;
}
// 如果没有用户调试器,或用户调试器没有处理异常则接着往下走.
// 函数会试图将异常记录, 线程环境
//
// Transfer exception information to the user stack, transition
// to user mode, and attempt to dispatch the exception to a frame
// based handler.

ExceptionRecord1.ExceptionCode = 0; // satisfy no_opt compilation

repeat:
try {

//
// If the SS segment is not 32 bit flat, there is no point
// to dispatch exception to frame based exception handler.
//

if (TrapFrame->HardwareSegSs != (KGDT_R3_DATA | RPL_MASK) ||
TrapFrame->EFlags & EFLAGS_V86_MASK ) {
ExceptionRecord2.ExceptionCode = STATUS_ACCESS_VIOLATION;
ExceptionRecord2.ExceptionFlags = 0;
ExceptionRecord2.NumberParameters = 0;
ExRaiseException(&ExceptionRecord2);
}

//
// Compute length of context record and new aligned user stack
// pointer.
//
// 为将线程上下文块整个结构体拷贝到用户的栈空间, 需要找到栈空间上一个空闲
// 的地址, 此处是计算esp(栈顶位置) - 一个线程上下文块的大小, 也就是相当于
// sub esp , sizeof(CONTEXT)
UserStack1 = (ContextFrame.Esp & ~CONTEXT_ROUND) - CONTEXT_ALIGNED_SIZE;

//
// Probe user stack area for writability and then transfer the
// context record to the user stack.
//
// 将指定地址设置为可写入
ProbeForWrite((PCHAR)UserStack1, CONTEXT_ALIGNED_SIZE, CONTEXT_ALIGN);
// 将线程上下文拷贝到用户的栈空间中.
RtlCopyMemory((PULONG)UserStack1, &ContextFrame, sizeof(CONTEXT));

//
// Compute length of exception record and new aligned stack
// address.
//
// 计算处异常信息结构体的在用户栈空间中的位置, 也是为了将异常信息写入
// 到用户栈空间中.
Length = (sizeof(EXCEPTION_RECORD) - (EXCEPTION_MAXIMUM_PARAMETERS -
ExceptionRecord->NumberParameters) * sizeof(ULONG) +3) &
(~3);
UserStack2 = UserStack1 - Length;

//
// Probe user stack area for writeability and then transfer the
// context record to the user stack area.
// N.B. The probing length is Length+8 because there are two
// arguments need to be pushed to user stack later.
//
// 将地址设置为可写
ProbeForWrite((PCHAR)(UserStack2 - 8), Length + 8, sizeof(ULONG));
// 将异常信息拷贝用户的栈空间中.
RtlCopyMemory((PULONG)UserStack2, ExceptionRecord, Length);

//
// Push address of exception record, context record to the
// user stack. They are the two parameters required by
// _KiUserExceptionDispatch.
//
// 构造处一个EXCEPTION_POINTERS的结构体, 并保存线程上下文,异常信息两个结构体
// 变量的首地址.
*(PULONG)(UserStack2 - sizeof(ULONG)) = UserStack1;
*(PULONG)(UserStack2 - 2*sizeof(ULONG)) = UserStack2;

//
// Set new stack pointer to the trap frame.
//

KiSegSsToTrapFrame(TrapFrame, KGDT_R3_DATA);
KiEspToTrapFrame(TrapFrame, (UserStack2 - sizeof(ULONG)*2));

//
// Force correct R3 selectors into TrapFrame.
//
// 设置段选择子----用户层
TrapFrame->SegCs = SANITIZE_SEG(KGDT_R3_CODE, PreviousMode);
TrapFrame->SegDs = SANITIZE_SEG(KGDT_R3_DATA, PreviousMode);
TrapFrame->SegEs = SANITIZE_SEG(KGDT_R3_DATA, PreviousMode);
TrapFrame->SegFs = SANITIZE_SEG(KGDT_R3_TEB, PreviousMode);
TrapFrame->SegGs = 0;

//
// Set the address of the exception routine that will call the
// exception dispatcher and then return to the trap handler.
// The trap handler will restore the exception and trap frame
// context and continue execution in the routine that will
// call the exception dispatcher.
//
// 将发生异常的线程的eip的地址设置为KeUserExceptionDispatcher函数的地址
// 这个函数是ntdll中的导出函数,这个导出函数就是负责用户层的异常分发的,
// 在这个函数中,它会把异常发给进程的异常处理机制(VEH,SEH)去处理.
// 这样一来, 当执行流从0环回到3环的时候, eip指向何处, 那个地方的代码就开始
// 被执行.
TrapFrame->Eip = (ULONG)KeUserExceptionDispatcher;
return;

} except (KiCopyInformation(&ExceptionRecord1,
(GetExceptionInformation())->ExceptionRecord)) {

//
// If the exception is a stack overflow, then attempt
// to raise the stack overflow exception. Otherwise,
// the user's stack is not accessible, or is misaligned,
// and second chance processing is performed.
//

if (ExceptionRecord1.ExceptionCode == STATUS_STACK_OVERFLOW) {
ExceptionRecord1.ExceptionAddress = ExceptionRecord->ExceptionAddress;
RtlCopyMemory((PVOID)ExceptionRecord,
&ExceptionRecord1, sizeof(EXCEPTION_RECORD));
goto repeat;
}
}
}

//
// This is the second chance to handle the exception.
//

if (DbgkForwardException(ExceptionRecord, TRUE, TRUE)) {
goto Handled2;
} else if (DbgkForwardException(ExceptionRecord, FALSE, TRUE)) {
goto Handled2;
} else {
ZwTerminateProcess(NtCurrentProcess(), ExceptionRecord->ExceptionCode);
KeBugCheckEx(
KERNEL_MODE_EXCEPTION_NOT_HANDLED,
ExceptionRecord->ExceptionCode,
(ULONG)ExceptionRecord->ExceptionAddress,
(ULONG)TrapFrame,
0);
}
}

//
// Move machine state from context frame to trap and exception frames and
// then return to continue execution with the restored state.
//

Handled1:
// 将异常栈帧, 线程环境设置到线程中.
KeContextToKframes(TrapFrame, ExceptionFrame, &ContextFrame,
ContextFrame.ContextFlags, PreviousMode);

//
// Exception was handled by the debugger or the associated subsystem
// and state was modified, if necessary, using the get state and set
// state capabilities. Therefore the context frame does not need to
// be transferred to the trap and exception frames.
//

Handled2:
return;
}

用户层异常分发源代码

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
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
/* 
* exception handling routines (xp 32-bit, partial/incomplete)
*
* ntdll 5.1.2600.5755
* v2 (updated jan 2011)
*
* - hawkes <hawkes@sota.gen.nz>
*
* useful link: http://www.eeye.com/html/resources/newsletters/vice/VI20060830.html
*
*/

#define DISPOSITION_DISMISS 0
#define DISPOSITION_CONTINUE_SEARCH 1
#define DISPOSITION_NESTED_EXCEPTION 2
#define DISPOSITION_COLLIDED_UNWIND 3

#define EH_NONCONTINUABLE 0x01
#define EH_UNWINDING 0x02
#define EH_EXIT_UNWIND 0x04
#define EH_STACK_INVALID 0x08
#define EH_NESTED_CALL 0x10

#define STATUS_NONCONTINUABLE_EXCEPTION 0xC0000025
#define STATUS_INVALID_DISPOSITION 0xC0000026

#define EXCEPTION_CONTINUE_EXECUTION -1

#define PAGE_EXEC_MASK (PAGE_EXECUTE|PAGE_EXECUTE_READ|PAGE_EXECUTE_READWRITE|PAGE_EXECUTE_WRITECOPY)

typedef struct _EXCEPTION_RECORD {
DWORD ExceptionCode;
DWORD ExceptionFlags;
struct _EXCEPTION_RECORD *ExceptionRecord; // var_CW
PVOID ExceptionAddress;
DWORD NumberParameters;
ULONG_PTR ExceptionInformation[15];
} EXCEPTION_RECORD, *PEXCEPTION_RECORD;

typedef struct _EXCEPTION_REGISTRATION
{
struct _EXCEPTION_REGISTRATION* pNext;
PEXCEPTION_HANDLER handler;
} EXCEPTION_REGISTRATION, *PEXCEPTION_REGISTRATION;

typedef struct _VECTORED_EXCEPTION_NODE {
LIST_ENTRY ListEntry;
PVECTORED_EXCEPTION_HANDLER handler;
} VECTORED_EXCEPTION_NODE, *PVECTORED_EXCEPTION_NODE;

typedef enum _EXCEPTION_DISPOSITION
{
ExceptionContinueExecution,
ExceptionContinueSearch,
ExceptionNestedException,
ExceptionCollidedUnwind
} EXCEPTION_DISPOSITION;

/* 7C97E3FA */
UCHAR LogExceptions = 0;

/* 7C97E3C0 */
LIST_ENTRY RtlpCalloutEntryList;

/* 7C9805E0 */
RTL_CRITICAL_SECTION RtlpCalloutEntryLock;
RTL_CRITICAL_SECTION LdrpLoaderLock;

/* 7C90E47C */
/*
* 用户层的异常派发源头
*/
VOID KeUserExceptionDispatcher(PCONTEXT ContextRecord,
PEXCEPTION_RECORD ExceptionRecord)
{

NTSTATUS Status;
// 调用RtlDispatchException函数去派发异常.
if (RtlDispatchException(ContextRecord, ExceptionRecord)) {
/* 7C90E48E modify the execution context of the current thread to whatever the chosen exception handler gives us */
// 结束异常分发
Status = ZwContinue(ContextRecord, 0);
}
else {
/* 7C90E49A no exception handler found so re-raise the exception for a debugger or the NT exception handler */
// 在此触发一个异常, 并触发时最后一个参数传FALSE,表明这是一个第二次触发的异常.
Status = ZwRaiseException(ExceptionRecord, ContextRecord, FALSE);
}

/* 7C90E4A5 build an exception record with 20 bytes on the stack, second chance exception? */
PEXCEPTION_RECORD exception = (PEXCEPTION_RECORD) alloca(0x14);
exception->ExceptionCode = Status;
exception->ExceptionFlags = 1;
exception->ExceptionRecord = ContextRecord;
exception->NumberParameters = 1;

return RtlRaiseException(exception);
}

LPVOID RtlpGetRegistrationHead()
{
_asm mov eax , fs:[0]
_asm ret
}

/* 7C92A970 如果异常被处理返回TRUE */
BOOLEAN RtlDispatchException(PCONTEXT ContextRecord, PEXCEPTION_RECORD ExceptionRecord) {
BOOLEAN ret = 0;
LPVOID StackBase, StackLimit;
PEXCEPTION_REGISTRATION head;
DWORD kProcess_Flags;
DWORD dispatch, highest;
EXCEPTION_RECORD exRec;


// 先将异常交给VEH处理. 如果能够处理,则结束异常分发
if (RtlCallVectoredExceptionHandlers(ExceptionRecord, ContextRecord)) {
/* 7C95010A */
ret = 1;
}
else { /* 否则交给SEH处理 */

/* 7C92A991 */
RtlpGetStackLimits(&StackLimit, &StackBase);

// 获取SEH链表头节点
/* 7C92A99F */
head = (PEXCEPTION_REGISTRATION)RtlpGetRegistrationHead();

highest = 0;

while (head != (PEXCEPTION_REGISTRATION) -1) {

// 判断获取出来的节点的地址是否正确
if (head < StackLimit || head + sizeof(EXCEPTION_REGISTRATION) > StackBase || head & 3) {

ExceptionRecord->ExceptionFlags |= EH_STACK_INVALID;
goto exit;
}

// 判断异常处理函数的地址是否正确
if (head->handler >= StackLimit && head->handler < StackBase) {

ExceptionRecord->ExceptionFlags |= EH_STACK_INVALID;
goto exit;
}

// 判断异常处理函数是否为有效处理函数
if (!RtlIsValidHandler(head->handler)) {
/* 7C92A336 */
ExceptionRecord->ExceptionFlags |= EH_STACK_INVALID;
goto exit;
}
else if (LogExceptions) {
/* 7C950113 */
RtlpLogExceptionHandler(ContextRecord, ExceptionRecord, 0, head, 0x10);
}

// 执行SEH的 _except_handler4()
hret = RtlpExecuteHandlerForException(ContextRecord,
head,
ExceptionRecord,
&dispatch,
head->handler);

if (LogExceptions) {
/* 7C950129 there is a second parameter to this function that I don't understand yet */
RtlpLogLastExceptionDisposition(highest, hret);
}

/* 7C92AA1E */
if (head == NULL) {
ExceptionRecord->ExceptionFlags &= ~EH_NESTED_CALL;
}

/* 7C92AA27 */
if (hret == DISPOSITION_DISMISS) {
if (ExceptionRecord->ExceptionFlags & EH_NONCONTINUABLE) {
/* 7C950181 */
exRec.ExceptionCode = STATUS_NONCONTINUABLE_EXCEPTION;
exRec.ExceptionFlags = EH_NONCONTINUABLE;
exRec.ExceptionRecord = ExceptionRecord;
exRec.NumberParameters = 0;
RtlRaiseException(&exRec);

/* 7C92A31C a little fudging with this block */
if (ExceptionRecord->ExceptionFlags & EH_STACK_INVALID) {
goto exit;
}
}
else {
/* 77EDBD64 */
ret = 1;
break;
}
}
// 如果SEH过滤函数返回的是搜索其它过滤函数处理
else if (hret == DISPOSITION_CONTINUE_SEARCH) {
/* 7C92A31C a little fudging with this block */
if (ExceptionRecord->ExceptionFlags & EH_STACK_INVALID) {
goto exit;
}
}
else if (hret == DISPOSITION_NESTED_EXCEPTION) {
/* 7C950169 */
ExceptionRecord->ExceptionFlags |= EH_NESTED_CALL;

if (dispatch > highest) {
highest = dispatch;
}
}
else {
/* 7C950147 */
exRec.ExceptionCode = STATUS_INVALID_DISPOSITION;
exRec.ExceptionFlags = EH_NONCONTINUABLE;
exRec.ExceptionRecord = ExceptionRecord;
exRec.NumberParameters = 0;
RtlRaiseException(&exRec);
}

/* 7C92A326 */
head = head->pNext;
}
}

exit:
/* 7C92AA43 */
return ret;
}

/* 7C92A934 */
// 调用VEH函数
BOOLEAN RtlCallVectoredExceptionHandlers(PEXCEPTION_RECORD ExceptionRecord, PCONTEXT ContextRecord) {
BOOLEAN ret = FALSE;
struct {
PEXCEPTION_RECORD eRec;
PCONTEXT cRec;
} Rec;
PVECTORED_EXCEPTION_NODE veh;
PVECTORED_EXCEPTION_HANDLER handler;
DWORD disposition

if (RtlpCalloutEntryList.Flink == &RtlpCalloutEntryList) {
return FALSE;
}

eRec = ExceptionRecord;
cRec = ExceptionRecord;

// 进入临界区
RtlEnterCriticalSection(&RtlpCalloutEntryLock);

// 开始遍历VEH的双向链表
for (veh = (PVECTORED_EXCEPTION_NODE) RtlpCalloutEntryList.Flink);
veh != (PVECTORED_EXCEPTION_NODE) RtlpCalloutEntryList;
veh = (PVECTORED_EXCEPTION_NODE) veh->ListEntry.Flink) {

// 解密处理函数地址
handler = RtlDecodePointer(veh->handler);

// 调用处理函数
disposition = handler(&Rec);

// 判断处理函数是否处理了异常.如果是则结束循环.
// 如果没有,则继续找到下一个处理函数.
if (disposition == EXCEPTION_CONTINUE_EXECUTION) {
// 将返回值设置为TRUE.
ret = 1;
break;
}
}

// 离开临界区
RtlLeaveCriticalSection(&RtlpCalloutEntryLock);

return ret;
}

/* 7C9033DC */
VOID RtlpGetStackLimits(LPVOID **StackLimit, LPVOID **StackBase) {
PTEB teb = _TEB; // fs:18h

*StackLimit = teb->NtTib->StackLimit;
*StackBase = teb->NtTib->StackBase;

return;
}

/* 7C92AA50 */
BOOLEAN RtlIsValidHandler(PEXCEPTION_HANDLER handler) {
DWORD table_sz;
LPVOID safeseh_table, base_addr;
DWORD ret, result_len, exec_flags, high, low;
MEMORY_BASIC_INFORMATION mbi;

safeseh_table = RtlLookupFunctionTable(handler, &base_addr, &table_sz);

if (safeseh_table == NULL || table_sz == 0) {
/* 7C9500D1 ProcessExecuteFlags*/
if (ZwQueryInformationProcess(INVALID_HANDLE_VALUE, 22, &exec_flags, 4, NULL) >= 0) {
/* 7C92CF94 0x10 = ExecuteDispatchEnable */
if (!(exec_flags & 0x10)) {
return 1;
}
}

/* 7C935E8E */
if (NtQueryVirtualMemory(INVALID_HANDLE_VALUE, handler, NULL, &mbi, sizeof(MEMORY_BASIC_INFORMATION), &result_len) < 0) {
return 1;
}

/* 7C935EA9 */
if (!(mbi.Protect & PAGE_EXEC_MASK)) {
RtlInvalidHandlerDetected(handler, -1, -1);
return 0;
}
else if (mbi.Type != SEC_IMAGE) {
return 1;
}

RtlCaptureImageExceptionValues(mbi.AllocationBase, &safeseh_table, &table_sz);

/* 7C935ED0 */
if (var_10 == NULL || table_sz == 0) {
return 1;
}

return 0;
}
else if (safeseh_table == (LPVOID) -1 && table_sz == -1) {
return 0;
}

/* 7C9500A6 */
if (table_sz < 0) {
RtlInvalidHandlerDetected(handler, safeseh_table, table_sz);
return 0;
}

rel_addr = handler - base_addr;
high = table_sz;
low = 0;

/* 7C9500B1 binary search through SafeSEH table */
do {
idx = (high + low) / 2;

if (rel_addr < safeseh_table[idx]) {
if (idx == 0) {
RtlInvalidHandlerDetected(handler, safeseh_table, table_sz);
return 0;
}

high = idx - 1;

if (high < low) {
RtlInvalidHandlerDetected(handler, safeseh_table, table_sz);
return 0;
}
}
else if (rel_addr > safeseh_table[idx]) {
low = idx + 1;

if (high < low) {
RtlInvalidHandlerDetected(handler, safeseh_table, table_sz);
return 0;
}
}
else {
break;
}
} while(1);

return 1;
}

/* 7C92AAA4 */
LPVOID RtlLookupFunctionTable(PEXCEPTION_HANDLER handler, DWORD *base_addr, DWORD *table_sz) {
MEMORY_BASIC_INFORMATION mbi;
LPVOID safeseh_table, base;

if (LdrpInLdrInit && RtlTryEnterCriticalSection(&LdrpLoaderLock) == 0) {
/* 7C92DD29 3 = MemoryBasicVlmInformation */
if (NtQueryVirtualMemory((HANDLE) -1, handler, 3, &mbi, sizeof(MEMORY_BASIC_INFORMATION), NULL) < 0) {
return NULL;
}
else if (mbi.Type != SEC_IMAGE) {
return NULL;
}

base = mbi.BaseAddress;

/* 7C92DD51 */
RtlCaptureImageExceptionValues(base, &safeseh_table, table_sz);
}
else {
if (_TEB != NULL) {
/* 7C92AAD9 */
PLDR_MODULE node = _PEB->Ldr->InLoadOrderModuleList.Flink

if (_PEB->Ldr != 0 && node != NULL) {
while (node != &_PEB->Ldr->InLoadOrderModuleList) {
/* 7C92AB00 */
if (handler < node->BaseAddr) {
node = node->InLoadOrderModuleList.Flink;
continue;
}

if (handler >= node->BaseAddr + node->SizeOfImage) {
node = node->InLoadOrderModuleList.Flink;
continue;
}

/* 7C92AB14 */
base = node->BaseAddr;
RtlCaptureImageExceptionValues(base, &safeseh_table, table_sz);

if (safeseh_table == NULL && NtDllBase != NULL && (base = RtlNtImageHeader(NtDllBase)) != NULL) {
if (header > base && header < base + ((PIMAGE_NT_HEADER) base)->OptionalHeaders.SizeOfImage) {
/* 7C950D7D */
RtlCaptureImageExceptionValues(base, &safeseh_table, table_sz);
}
}

break;
}
}
}

/* 7C92AB2C */
if (LdrpInLdrInit) {
RtlLeaveCriticalSection(&LdrpLoaderLock);
}
}

*base_addr = base;
return safeseh_table;
}

调试符号

查看动态连接库或静态链接库的导出函数

1
2
dumpbin.exe /exports xxx.dll /out:xxx.txt
dumpbin /LINKERMEMBER xxx.lib > xxxx.txt

FLIRT签名识别库函数

1
2
3
4
//创建模式文件 
pcf.exe xxx.lib xxx.pat
//创建签名文件
sigmake.exe xxx.pat xxx.sig

WinDbg

用户层会话调试建立

1,直接创建一个进程,并调试它:File-Open Executetable

2,附加到已经打开的进程:File-Attatch to a Process

​ 侵入式附加:接管正在运行的程序,能够进行调试

​ 非侵入式附加:只能够读取进程信息,不能接收目标进程的调试事件

内核层调试会话建立

正常情况下是无法用自己的系统调试自己的系统的,我们都需要知道调试器是要下断点的,加入我们中断了自己的操作系统,那么我们就什么都做不了了,所以一般调试内核都是用一台机器调试另一台机器

1,使用两台机器过于繁琐,可以使用物理机调试虚拟机,建立双机调试环境

2,类似于用户层非侵入式的调试,是可以不接管本系统的调试,仅仅查看本系统内核中的信息,这种方式叫本地内核调试

双机环境的建立

虚拟机进行双击内核调试有多种链接方式,以下使用的是VirtualKD工具来复制创建双机调试环境

​ 需要:Windbg,虚拟机,VirtualKD

设置调试器路径

设置虚拟机

打开虚拟机运行Install VirtualKD程序

这个时候会重启系统

然后就能看到

虚拟机就跑起来了

本地内核调试的建立

本地内核调试不是真正的调试,只是查看信息而已,故而调试相关的命令都是受限的

需要注意的是:

本地调试需要是以调试模式启动的Windows操作系统,且需要以管理员的方式运行Windbg,操作系统版本要与Windbg版本对应

以调试模式进入系统

开机按F8-调试模式

进入系统后选择以管理员的方式运行-Kernel Debug-local

之后就进入本地内核调试了

界面概览

调试窗口

视图窗口

符号文件

符号文件是编译器编译可执行文件时生成的一些文件,通常在Windows平台遇到符号文件叫做pdb文件,其中包括:

- 全局变量的类型、名称、地址
- 局部变量的类型、名称、地址
- 函数名称、原型、地址
- 变量、结构体类型的定义

符号文件对于程序的调试非常重要,无论是源码调试还是非源码调试

设置符号路径

方法一

方法二

方法三

使用命令

.sympath [+] [路径]

加载符号

使用命令:.reload刷新符号路径加载位置

使用命令:Id+[模块名]来加载某一个模块的符号

加载系统符号

当进行双机调试,可以使用微软提供的符号文件辅助内核分析调试,

获取方式

在符号路径下添加一个微软的符号服务器,格式如下:

​ SRV*[本地路径]*服务器路径

​ 例:

    - srv* d:\\\ mysymbols* \\\symsrv\symbol假如自己公司有一个符号服务器的话) 
    - srv* d :\\\ Mmysymbols *http //msdl . microsoft .com/download /symbols (微软符号服务器)

通过后面的服务器地址下载到前面的本地路径

符号选项

使用 命令 .symopt

源码调试

Windbug有源码调试与反汇编调试两种模式

当既有符号文件,又有程序源代码是,才可以使用Windbug进行源码调试

  • 加载符号
  • 打开源文件,设置断点

工作空间

当前的Windows中的工作环境称之为工作空间,包括:

- 当前的界面布局等调试器设置信息
- 调试项目有关的属性,参数以及调试器设置等信息

每次Windbg打开都是默认的初始工作空间,其实可以把工作配置保存起来,这样每次调试程序的时候,就可以快速的进入工作状态

  • 默认工作空间

    • 基础工作空间
    • 默认内核态工作空间
    • 默认用户态工作空间
    • 默认转储文件工作空间

    默认工作空间是以单个分析过程区分的,例如使用Windbg分析一个应用程序时会根据可执行文件的路径和文件名为其建立一个默认的工作空间

  • 命名工作空间

    在Windbg中可以为工作空间命名,就是把自己的工作空间保存起来或者存储成文件。

命令系统

概述

Windebug是一个支持强大命令系统的调试器

标准命令

类型 代表命令
程序控制类 g系列 t系列 p系列
内存查看修改类 d系列 e系列 s等
断点设置类 b系列
观察堆栈 k系列
反汇编命令 u系列
其它命令 x q ls等

在命令输入框中输入? 可以看到主要的标准命令及简介

元命令

元命令作为标准命令的一个补充也没内置在了Windbg中,其特点是以一个.开头,通常都是一个单次:

.symopt .sympathy .asm .restart .reboot ……

输入 .help可以查看到元命令以及帮助说明

扩展命令

注意:

- 只有当程序暂停的时候才能输入命令
- 直接回车可以重复上一条命令
- 按上下方向键可以浏览之前输入的命令
- 当命令提示框为*BUSY\*的时候,无法立即输入命令

常用基本命令

命令 功能
lm 查看当前模块及符号加载情况
.reload 刷新符号路径,重新载入符号
ld 加载某一个模块符号
.restart 重新开始调试
.detach 分离调试
q 退出调试