- 异常学习
- 一丶CPU异常
- 1.1 异常处理简介
- 1.2 异常的分类之CPU异常
- 1.3 CPU异常的 记录
- 1.4 CPU异常的分发
- 1.5 CPU 异常记录小总结
- 一丶CPU异常
异常学习
一丶CPU异常
1.1 异常处理简介
此为Windows异常处理。学习异常可以帮助我们更好的调试程序。或者更好的寻找异常点。
先说一下异常产生之后要进行的操作
-
异常记录
-
异常分发
-
异常处理
异常产生后第一步就是要进行异常记录
原因是首先要记录哪里产生了异常。 异常的类型是什么。 第二步就是要分发异常
分发异常指的就是去寻找异常处理函数
找到函数之后就会进行调用,调用的函数我们就称为异常处理
。
1.2 异常的分类之CPU异常
异常分类有两种,第一种就是CPU产生的异常。 第二种就是软件模拟的产生的异常。
CPU异常如下:
C++代码示例
- CPU异常示例
int main(int argc, char **argv)
{
int x = 10;
int y = 0;
int c = x / y;
return 0;
}
提示我们除零异常
这个是CPU可以检测出来的。
- 软件模拟的异常
void fun()
{
throw 1;
}
int main(int argc, char **argv)
{
fun();
return 0;
}
当我们使用高级语言编写代码的时候。多多少少高级语言都会提供异常处理机制。
比如C++ 我们可以使用 throw
关键字来抛出一个异常。
这个就是软件模拟的异常。
1.3 CPU异常的 记录
CPU 检测到异常之后会进行如下操作。
XP下
WIN10 64位下
这里说下64位下的把。
其实最重要的就是 KiExceptionDisPatch
此函数会先 进行异常记录
此函数结构大概为:
__int64 __fastcall KiExceptionDispatch(
int arg_ExceptionCode,
unsigned int arg_ExceptionNumberParameters,
void *arg_ExceptionAddress,
unsigned __int64 arg_ExceptionInformation,
char a5)
参数 | 说明 |
---|---|
arg_ExceptionCode | 异常记录的代码 |
arg_ExceptionNumberParameters | 异常记录的附加参数大小 |
arg_ExceptionAddress | 异常记录出错的地址 |
arg_ExceptionInformation | 异常记录附加参数数组信息 |
a5 | 暂时未知 |
在调用函数之前代码汇编代码如下:
.text:00000001401C72F3 B9 03 00 00 10 mov ecx, 10000003h
.text:00000001401C72F8 33 D2 xor edx, edx
.text:00000001401C72FA 4C 8B 85 E8 00+ mov r8, [rbp+0E8h]
.text:00000001401C72FA 00 00
.text:00000001401C7301 E8 7A 71 00 00 call KiExceptionDispatch
传递了一个 10000003
值。此值就是我们的除零异常的的SWITCH值
注意 除零异常的值 应该是 0xC0000094
这个后面说。
下图为 KiExceptionDisPatch
异常记录区域。可以看到会把我们的 switch 值
进行记录 并且会记录我们出现异常的地址
异常记录结构体
为如下:
#defien EXCEPTION_MAXIMUM_PARAMETERS 15
typedef struct _EXCEPTION_RECORD32 {
DWORD ExceptionCode;
DWORD ExceptionFlags;
DWORD ExceptionRecord;
DWORD ExceptionAddress;
DWORD NumberParameters;
DWORD ExceptionInformation[EXCEPTION_MAXIMUM_PARAMETERS];
} EXCEPTION_RECORD32, *PEXCEPTION_RECORD32;
typedef struct _EXCEPTION_RECORD64 {
DWORD ExceptionCode;
DWORD ExceptionFlags;
DWORD64 ExceptionRecord;
DWORD64 ExceptionAddress;
DWORD NumberParameters;
DWORD __unusedAlignment;
DWORD64 ExceptionInformation[EXCEPTION_MAXIMUM_PARAMETERS];
} EXCEPTION_RECORD64, *PEXCEPTION_RECORD64;
含义如下:
参数 | 说明 |
---|---|
ExceptionCode |
记录异常的代码 |
ExceptionFlags |
记录异常的状态 确定我们是CPU异常还是 软件模拟的异常 还是嵌套异常。根据不同场合值也不一样。 |
ExceptionRecord |
指向下一个异常记录。当出现嵌套异常的时候会记录下一个异常。 |
ExceptionAddress |
记录异常发生的地址 |
NumberParameters | 附加参数 |
ExceptionInformation | 附加参数指针 |
观看结构体,其中最重要的其实就是前4个成员。 此结构体 我称为异常记录结构体
,此结构体也是我所说的异常产生的时候进行的第一步。记录异常信息。此结构体中我们重点还需要知道 ExceptionCode ExceptionAddress
这两个成员分别记录的异常代码和异常地址。这在我们调试的时候很有帮助。
假设我们是 DIV 0 的异常
那么CPU 回去 IDT表中寻找 KiDivideErrorFaultShadow 此函数会最终调用到 KiDivideErrorFault
。
如果在WINDBG
中调试的时候 输入 !IDT
并且有符号的情况下则可以看到 IDT表中的符号名称。
反汇编查看 KiDivideErrorFault
则可以看到异常流程。
1.4 CPU异常的分发
异常的分发就是去寻找异常处理函数。 我们继续反汇编
KiExceptionDisPatch
可以看到异常分发函数
反汇编
再上图上我们可以看到会继续调用 KiDispatchException
此函数如下:
void KiDispatchException (
IN PEXCEPTION_RECORD ExceptionRecord,
IN PKEXCEPTION_FRAME ExceptionFrame,
IN PKTRAP_FRAME TrapFrame,
IN KPROCESSOR_MODE PreviousMode,
IN BOOLEAN FirstChance
)
参数 | 说明 |
---|---|
ExceptionRecord | 异常记录结构体 |
ExceptionFrame | 异常信息框架 |
TrapFrame | 陷阱框架 |
PreviousMode | 先前模式 |
FirstChance | 机会。第几次异常 |
继续反汇编
在反汇编过程中 可以看到。 他会对比异常记录结构体中记录的switch值 然后进行修复
如果我们是除零异常
则会修复异常错误代码为 0xC0000094
;
在后面会着重说一下 异常分发和处理。 这里先抛出问题。知道我们 CPU产生异常了 如何进行 异常记录 异常分发掉哟个你的函数是哪个
1.5 CPU 异常记录小总结
CPU产生除零
异常后会调用 IDT表中 KiDivideErrorFaultShadow
然后此函数继续调用 KiDivideErrorFault
这两个函数都不会进行异常处理只是继续往内核层面下发发。
KiDivideErrorFault
继续调用 KiExceptionDispatch
然后继续调用 KiDispatchException
其中异常记录
是在 KiExceptionDispatch
中产生的。 KiExceptionDispatch
会生成一个 结构体 我们称为异常记录结构体(EXCEPTION_RECORD)
此结构体来保存上层传递过来的 异常代码 异常地址 等信息。
最中会交给 KiDispatchException
去分发。
还了解两个函数。 这两个函数可以在调试中帮助我们来快速定位问题。
void KiDispatchException (
IN PEXCEPTION_RECORD ExceptionRecord,
IN PKEXCEPTION_FRAME ExceptionFrame,
IN PKTRAP_FRAME TrapFrame,
IN KPROCESSOR_MODE PreviousMode,
IN BOOLEAN FirstChance
)
__int64 __fastcall KiExceptionDispatch(
int arg_ExceptionCode,
unsigned int arg_ExceptionNumberParameters,
void *arg_ExceptionAddress,
unsigned __int64 arg_ExceptionInformation,
char a5)