源码调试
自动加载
强制加载
WinDbg认识
用户态、内核态的调试程序
BUSY 调试器正在忙,无法输入命令
0:0002 当前———进程号:线程号
0:kd> 正在进行内核调试,调试0号CPU
Lkd> 现正在进行本地内核调试
https://blog.csdn.net/huanongying131/article/details/90745827
WinDbg命令
常规命令:最为常用,比较短
d(查看数据)、t(单步)、u(查看反汇编)、b(断点)
元命令:最为常用,比较短
.reload(加载符号)、.cls(清除记录)
扩展命令:一些特殊功能会用到这些命令
!process(查看进程)
!handle(查看句柄)
b系列
pb | 设置软件断点 |
---|---|
bu | 设置符号断点 |
ba i|w|r|en | 设置硬件断点 |
be、bd、bc | 启用、禁用、删除断点 |
bl | 列出所哟一断点 |
bp命令为设置断点,后面的参数可以为地址、符号、运算式,windbg会自动计算地址
d、e系列
db、dw、dd、dq、da、du、df、dD、dyb、dyd | 以不同形式查看虚拟内存 |
---|---|
!db、!dc、!dd、!dp、!dq、!du、!dw | 以不同形式查看物理内存 |
dt | 查看结构体 |
e、ea、eb、ed、eD、ef、ep、eq、eu、ew、eza、ezu | 以不同形式编辑虚拟内存 |
!e、!ea、!eb、!ed、!eD、!ef、!ep、!eq、!eu | 以不同形式编辑物理内存 |
db以字节方式查看数据
dw以字的方式查看数据
dd以双字的方式查看数据
~系列
~* r | ~代表查看线程的寄存器,*代表所有的,r查看 |
---|---|
~1 r | 只查看第一个的 |
~* r eax | 查看特定寄存器eax的 |
r eax=ebx | 更改寄存器的值 |
g系列
g | 恢复执行(F5) |
---|---|
gh | 执行的时候处理异常 |
gn | 执行的时候不处理异常 |
gc | 有条件的执行 |
gu | 执行到父函数 |
p系列
p | 单步步入(F11) |
---|---|
pa | 执行到一个地址 |
pc | 执行到下一个call |
t系列
t | 单步步过 |
---|---|
ta | 执行到一个地址 |
tc | 执行到下一个call |
tb | 单步到下一个分支 |
中断和异常
1. 终止处理SEH
#include <iostream>
#include <windows.h>
// 终结处理器: 由 __try 、 __finally 和 __leave 构成。
// 能够保证无论 __try 中的指令以何种方式退出,都必然会
// 执行 __finally 块。【不会处理异常,只做清理操作】
// SEH 的使用范围是线程相关的,每个线程都有自己的函数
int main()
{
__try
{
// 被检查的代码块,通常是程序的逻辑部分
printf("__try { ... }\n");
// 如果当前以包括 return goto break
// 的跳转指令退出就会产生一些额外的函
// 数调用,用于执行 __finally 块
// 推荐是用 __leave 跳出当前的 __try
__leave;
}
__finally
{
// 终结处理块,通常编写的是用于清理当前程序的代码
// 无论 __try 以何种方式退出,都会执行这里的指令
printf("__finally { ... }\n");
// 使用 AbnormalTermination 判断 __try 的退出方
// 式,程序如果是正常退出的,那么返回值是 false
if (AbnormalTermination())
printf("异常退出\n");
else
printf("正常退出\n");
}
ret_tag:
return 0;
}
2. 异常处理SEH
#include <iostream>
#include <windows.h>
// 异常处理器: 由关键字 __try 和 __except 构成,能够保证 __try
// 中如果产生了异常,会执行过滤表达式中的内容,应该在过滤表达式提
// 供的过滤函数中处理想要处理的异常。
// 异常过滤表达式中最常见的情况就是编写一个一场过滤函数,对异常进行处理
DWORD ExceptionFilter(EXCEPTION_POINTERS* ExceptionInfo, DWORD ExceptionCode)
{
printf("ExceptionCode: %X\n", ExceptionCode);
// 如果当前产生的异常是除零异常,那么就通过修改寄存器处理异常
if (EXCEPTION_INT_DIVIDE_BY_ZERO == ExceptionCode)
{
// 通过查看汇编代码可以知道产生异常的指令是 idiv eax, ecx
// 在这个位置对寄存器执行的所有修改都会直接被应用到程序中
ExceptionInfo->ContextRecord->Eax = 30;
ExceptionInfo->ContextRecord->Edx = 0;
ExceptionInfo->ContextRecord->Ecx = 1;
// 异常如果被处理了,那么就返回重新执行当前的代码
return EXCEPTION_CONTINUE_EXECUTION;
}
// 如果不是自己能够处理的异常,就不处理只报告
return EXCEPTION_EXECUTE_HANDLER;
}
int main()
{
int number = 0;
__try
{
// __try 中保存的是可能产生异常的代码
number /= 0;
}
// __except 后的括号中会存在一个异常过滤表达式表达式的返回值必定是一下说明的几个之一
// - EXCEPTION_EXECUTE_HANDLER(1): 执行异常处理器报告异常,但是并不处理,不处理返回
// - EXCEPTION_CONTINUE_SEARCH(0): 将异常传递给上层的异常处理函数,通常无法处理返回
// - EXCEPTION_CONTINUE_EXECUTION(-1): 尝试重新执行指令,通常在处理了异常之后返回
// 通常会为一场过滤表达式提供一个异常处理函数用于处理异常,并返回处理结果
// - GetExceptionCode: 用于获取异常的类型,能在过滤表达式和异常处理器中使用
// - GetExceptionInformation: 用于获取异常的信息,只能卸载过滤表达式中
__except (ExceptionFilter(GetExceptionInformation(), GetExceptionCode()))
{
// 异常处理器,只有 __except 返回 EXCEPTION_EXECUTE_HANDLER 才会执行
printf("__try 中 产生了异常,但是并没有处理异常 %x\n", GetExceptionCode());
}
printf("numebr = %d\n", number);
return 0;
}
3. 顶层异常UEH
#include <iostream>
#include <windows.h>
// UEH: 全称顶层异常处理器,这个函数只能有一个,被保存在全局变量中,
// 由于只会被系统默认的最底层 SEH 调用,所以又会被称作是 SEH 的一
// 种,是整个异常处理中的最后一环,所以通常都不会再次执行异常处理操
// 作,而是进行内存 dump,将信息发送给服务器,进行异常的分析。
// [注意: UEH 在 win7 之后,只有在非调试模式下才会被调用,可以反调试]
LONG WINAPI TopLevelExceptionFilter(EXCEPTION_POINTERS* ExceptionInfo)
{
printf("ExceptionCode: %X\n", ExceptionInfo->ExceptionRecord->ExceptionCode);
// 如果当前产生的异常是除零异常,那么就通过修改寄存器处理异常
if (EXCEPTION_INT_DIVIDE_BY_ZERO == ExceptionInfo->ExceptionRecord->ExceptionCode)
{
// 通过查看汇编代码可以知道产生异常的指令是 idiv eax, ecx
// 在这个位置对寄存器执行的所有修改都会直接被应用到程序中
ExceptionInfo->ContextRecord->Eax = 30;
ExceptionInfo->ContextRecord->Edx = 0;
ExceptionInfo->ContextRecord->Ecx = 1;
// 异常如果被处理了,那么就返回重新执行当前的代码
return EXCEPTION_CONTINUE_EXECUTION;
}
// 如果不是自己能够处理的异常,就不处理只报告
return EXCEPTION_EXECUTE_HANDLER;
}
int main()
{
int number = 0;
// 通过一个函数可以直接的安装 UEH
SetUnhandledExceptionFilter(TopLevelExceptionFilter);
// 安装一个 SEH 处理器
__try
{
number /= 0;
}
// 异常一旦被 SEH 处理,就不会再传递给 UEH
__except (EXCEPTION_EXECUTE_HANDLER)
{
printf("这个地方永远不会执行\n");
}
printf("number = %d\n", number);
system("pause");
return 0;
}
4. 向量异常VEH
#include <iostream>
#include <windows.h>
// VEH: 向量化异常处理的一种,被保存在一个全局的链表中,进程内的所有线程都可
// 以使用这个函数,是第一个处理异常的函数。
LONG WINAPI VectoredExceptionHandler(EXCEPTION_POINTERS* ExceptionInfo)
{
printf("ExceptionCode: %X\n", ExceptionInfo->ExceptionRecord->ExceptionCode);
// 如果当前产生的异常是除零异常,那么就通过修改寄存器处理异常
if (EXCEPTION_INT_DIVIDE_BY_ZERO == ExceptionInfo->ExceptionRecord->ExceptionCode)
{
// 通过查看汇编代码可以知道产生异常的指令是 idiv eax, ecx
// 在这个位置对寄存器执行的所有修改都会直接被应用到程序中
ExceptionInfo->ContextRecord->Eax = 30;
ExceptionInfo->ContextRecord->Edx = 0;
ExceptionInfo->ContextRecord->Ecx = 1;
// 异常如果被处理了,那么就返回重新执行当前的代码
return EXCEPTION_EXECUTE_HANDLER;
}
// 如果不是自己能够处理的异常,就不处理只报告
return EXCEPTION_EXECUTE_HANDLER;
}
int main()
{
int number = 0;
// 通过一个函数可以直接的安装 VEH,参数一是布尔值,如果为 TRUE
// 的话,就将当前的函数添加到全局 VEH 函数的链表头部,否则尾部
AddVectoredExceptionHandler(TRUE, VectoredExceptionHandler);
// 安装一个 SEH 处理器
__try
{
number /= 0;
}
// 异常首先被 VEH 接收到,如果无法处理才会传递给 SEH
__except (EXCEPTION_EXECUTE_HANDLER)
{
printf("这个地方永远不会执行\n");
}
printf("number = %d\n", number);
system("pause");
return 0;
}
5. 向量异常VCH
#include <iostream>
#include <windows.h>
// 异常的传递过程: VEH -> SEH -> UEH -> VCH
// VCH: 和 VEH 类似,但是只会在异常被处理的情况下最后调用
LONG WINAPI VectoredContinueHandler(EXCEPTION_POINTERS* ExceptionInfo)
{
// VEH 不会对异常进行处理,调用的时机和异常处理的情况有关
printf("VCH: ExceptionCode: %X\n", ExceptionInfo->ExceptionRecord->ExceptionCode);
return EXCEPTION_CONTINUE_SEARCH;
}
LONG WINAPI VectoredExceptionHandler(EXCEPTION_POINTERS* ExceptionInfo)
{
printf("VEH: ExceptionCode: %X\n", ExceptionInfo->ExceptionRecord->ExceptionCode);
// 如果当前产生的异常是除零异常,那么就通过修改寄存器处理异常
if (EXCEPTION_INT_DIVIDE_BY_ZERO == ExceptionInfo->ExceptionRecord->ExceptionCode)
{
// 通过查看汇编代码可以知道产生异常的指令是 idiv eax, ecx
// 在这个位置对寄存器执行的所有修改都会直接被应用到程序中
ExceptionInfo->ContextRecord->Eax = 30;
ExceptionInfo->ContextRecord->Edx = 0;
ExceptionInfo->ContextRecord->Ecx = 1;
// 异常如果被处理了,那么就返回重新执行当前的代码
return EXCEPTION_CONTINUE_SEARCH;
}
// 如果不是自己能够处理的异常,就不处理只报告
return EXCEPTION_EXECUTE_HANDLER;
}
LONG WINAPI TopLevelExceptionFilter(EXCEPTION_POINTERS* ExceptionInfo)
{
printf("UEH: ExceptionCode: %X\n", ExceptionInfo->ExceptionRecord->ExceptionCode);
// 如果当前产生的异常是除零异常,那么就通过修改寄存器处理异常
if (EXCEPTION_INT_DIVIDE_BY_ZERO == ExceptionInfo->ExceptionRecord->ExceptionCode)
{
// 通过查看汇编代码可以知道产生异常的指令是 idiv eax, ecx
// 在这个位置对寄存器执行的所有修改都会直接被应用到程序中
ExceptionInfo->ContextRecord->Eax = 30;
ExceptionInfo->ContextRecord->Edx = 0;
ExceptionInfo->ContextRecord->Ecx = 1;
// 异常如果被处理了,那么就返回重新执行当前的代码
return EXCEPTION_CONTINUE_SEARCH;
}
// 如果不是自己能够处理的异常,就不处理只报告
return EXCEPTION_EXECUTE_HANDLER;
}
DWORD StructedExceptionFilter(EXCEPTION_POINTERS* ExceptionInfo)
{
printf("SEH: ExceptionCode: %X\n", ExceptionInfo->ExceptionRecord->ExceptionCode);
// 如果当前产生的异常是除零异常,那么就通过修改寄存器处理异常
if (EXCEPTION_INT_DIVIDE_BY_ZERO == ExceptionInfo->ExceptionRecord->ExceptionCode)
{
// 通过查看汇编代码可以知道产生异常的指令是 idiv eax, ecx
// 在这个位置对寄存器执行的所有修改都会直接被应用到程序中
ExceptionInfo->ContextRecord->Eax = 30;
ExceptionInfo->ContextRecord->Edx = 0;
ExceptionInfo->ContextRecord->Ecx = 1;
// 异常如果被处理了,那么就返回重新执行当前的代码
return EXCEPTION_CONTINUE_EXECUTION;
}
// 如果不是自己能够处理的异常,就不处理只报告
return EXCEPTION_EXECUTE_HANDLER;
}
int main()
{
int number = 0;
AddVectoredExceptionHandler(TRUE, VectoredExceptionHandler);
AddVectoredContinueHandler(TRUE, VectoredContinueHandler);
SetUnhandledExceptionFilter(TopLevelExceptionFilter);
// 安装一个 SEH 处理器
__try
{
number /= 0;
}
__except (StructedExceptionFilter(GetExceptionInformation()))
{
printf("SEH: 异常处理器\n");
}
printf("number = %d\n", number);
system("pause");
return 0;
}
6. 探究原理SEH
#include <iostream>
#include <windows.h>
// 带有异常处理函数的函数
void test1()
{
// 在 VS 的同一个函数中无论编写了多少个 SEH, 编译
// 器实际上只会安装一个叫做 except_handler4 的函数
__try
{
printf("__try { ... }\n");
__try
{
printf("__try { ... }\n");
}
__except (1)
{
printf("__except (1) { ... }\n");
}
}
__except (1)
{
printf("__except (1) { ... }\n");
}
}
// 没有异常处理函数的函数
void test2() { }
// 遍历当前程序中已经存在的异常处理函数
void ShowSEH()
{
// 定义一个结构体指针,用于保存 SEH 链表的头节点
EXCEPTION_REGISTRATION_RECORD* header = nullptr;
// 通过 FS:[0] 找到 ExceptionList 的头节点
__asm push fs:[0]
__asm pop header
// 遍历异常处理链表,链表以 -1 结尾
while (header != (EXCEPTION_REGISTRATION_RECORD*)-1)
{
printf("function: %08X\n", header->Handler);
header = header->Next;
}
printf("\n");
}
EXCEPTION_DISPOSITION NTAPI ExceptionRoutine(
// 产生的异常信息
_Inout_ struct _EXCEPTION_RECORD* ExceptionRecord,
_In_ PVOID EstablisherFrame,
// 产生异常时的线程上下文
_Inout_ struct _CONTEXT* ContextRecord,
_In_ PVOID DispatcherContext
)
{
printf("自定义SEH: ExceptionCode: %X\n", ExceptionRecord->ExceptionCode);
// 如果当前产生的异常是除零异常,那么就通过修改寄存器处理异常
if (EXCEPTION_INT_DIVIDE_BY_ZERO == ExceptionRecord->ExceptionCode)
{
// 通过查看汇编代码可以知道产生异常的指令是 idiv eax, ecx
// 在这个位置对寄存器执行的所有修改都会直接被应用到程序中
ContextRecord->Eax = 30;
ContextRecord->Edx = 0;
ContextRecord->Ecx = 1;
// 异常如果被处理了,那么就返回重新执行当前的代码
return ExceptionContinueExecution;
}
// 如果不是自己能够处理的异常,就不处理只报告
return ExceptionContinueSearch;
}
int main()
{
test1();
test2();
// 遍历到了异常处理函数
ShowSEH();
// 手动的安装一个异常处理函数,操作 FS:[0]
__asm push ExceptionRoutine // 新的异常处理函数
__asm push fs : [0] // 上一个节点
__asm mov fs : [0] , esp // esp 指向创建的结构体首地址
int number = 0;
number /= 0;
// 遍历到了异常处理函数
ShowSEH();
// 卸载一个异常处理函数
__asm mov eax, fs:[0] // 获取到了安装完之后的节点
__asm mov eax, [eax] // 上一个 SEH 节点,修改前的
__asm mov fs:[0], eax // 修改前的重新设置为 SEH 头节点
__asm add esp, 8
// 遍历到了异常处理函数
ShowSEH();
return 0;
}
/*
PEXCEPTION_REGISTRATION_RECORD ExceptionList = nullptr;
__asm push fs:[0]
__asm pop ExceptionList
__asm push ExceptionRoutine
__asm push fs : [0]
__asm mov fs : [0], esp
__asm mov eax, ExceptionList
__asm mov fs:[0], eax
__asm add esp, 0x08
*/