Windbg主要有两种方式,一是静态分析dump文件,二是动态调试程序,例如attach到已运行的程序或者使用Windbg启动调试程序
1、dump文件的分类
dump文件有两种,一是全dump文件,二是minidump文件。二者的区别是全dump文件会dump所有的内存,所以这个dump文件会很大,而minidump文件只会dump一些关键的信息,因此会比较小,在生产环境中我们一般生成minidump文件。全dump文件可以通过任务管理器来生成。
2、dump文件生成机制
windows中,其dump文件生成和linux下不一样,linux下会自动生成core dump文件,而windows需要我们在程序中自己配置
2.1 SetUnhandledExceptionFilter
调用SetUnhandledExceptionFilter
设置回调函数,在回调函数中调用MiniDumpWithDump
生成dump文件。但这种方式需要为每个dll单独设置回调函数,当dll很多的时候代码就很繁琐。
2.2 使用异常捕获库
异常捕获库感知到异常,自动生成包含异常上下文的文件,例如CrashRpt库或者Chrome浏览器开源的Breakpad/qBreakpad/Crashpad等等。
CrashRpt库尝试给每个模块的线程挂载异常回调函数,但是实现方式有缺陷,有些线程没有挂载上。因为它只能保证在CrashRpt之前加载到进程空间的库才能挂载异常回调函数。后续可以用detous开源代码来捕获。
2.3 使用windbg的dump命令
当有时候我们程序的捕获机制失效导致未生成dump文件的时候,我们可以使用windbg attach到程序进行dump。
2.4 使用任务管理器导出dump文件
在任务管理器中找到对应的程序,右键点击进程,导出转储文件。一般在客户环境无法使用windbg且无法生成dump文件的时候。
3 pdb符号库文件简介
- PDB - Program DataBase File, 程序数据库文件,存放二进制文件中所有函数及变量的符号,还有一些调试用信息,查看完整的函数调用信息以及变量信息都需要用到PDB文件。
- Visual Studio的工程配置中是默认生成pdb文件的,Debug和Release版本都是如此。
- PDB文件的加载需要和原始的exe以及dll文件的时间戳相匹配,若不匹配则可能加载失败。
- Windbg设置加载路径后才能加载PDB文件。
4 Windbg的下载和安装
4.1 windbg.appinstaller
安装
下载该安装包之后,直接打开,即可在app store中直接安装
4.2 windbg.msixbundle
通过打开windbg.appinstaller
得到windbg.msixbundle
的下载地址,下载完成之后,通过PoweShell命令安装(当我们没有app store的时候)。
Add-AppxPackage -Path C:\Path\App-Package.msixbundle
![[Pasted image 20250422143855.png]]
4.3 windows sdk setup
通过winsdkSetup
来安装10.0版本的windbg。这个可以不通过app store来安装。
windows-sdk
5 Windbg静态分析的步骤
-
设置source path以及symbol path,即源代码的路径以及pdb文件的路径![[Pasted image 20250422150938.png]]
- 导入dump文件,可以在界面上看到初步的问题分析![[Pasted image 20250422151537.png]]
- 使用
.excr
来切换到发生异常的线程以及汇编代码,如下图所示 ![[Pasted image 20250422151801.png]] - 使用
kn/kv/kp
来查看函数调用堆栈 如果没有pdb文件,函数调用堆栈中看不到具体的函数名以及行号。DataProc!PCG::DP::DataProcessImplEds::ProcessData
中感叹号前面表示的是模块的名称发,后面的是模块的函数名。 ![[Pasted image 20250422152617.png]] - 如果没有堆栈,那么根据函数调用堆栈中的的模块名称,使用lm命令查看模块时间戳,然后去查找对应版本的pdb文件。如下图所示,
lm vm DataProcCalcCp
显示了DataProcCalcCp
的模块信息。![[Pasted image 20250422153233.png]] - 将pdb文件路径设置到windbg中,使用.ecxr重新切换到发生异常的线程中,然后使用
kn/kv/kp
命令重新查看函数调用堆栈(堆栈会显示具体的函数名和行号) - 如果堆栈中没有显示具体的函数名和行号,应该是pdb文件加载失败。 可能是pdb版本不对;可能是windbg没有加载,可以使用lm命令确认;有时候需要使用
.reload /f
命令强制加载所有pdb文件。 - minidump文件可能无法看到内存中的变量,但是全dump文件是可以的
6 Windbg动态调试
- 将windbg附加到进程上,两种方式,一是程序先启动,将windbg附加到程序进程上;二是直接使用windbg启动程序
- 成功attach到进程之后,windbg会自动中断进程,此时输入命令
g
使程序继续运行 - 程序在运行过程中发生异常时,windbg会在异常处停止,此时可以通过命令查看异常处的函数调用堆栈以及变量
- 加载pdb,查看更详细的函数调用堆栈(这个步骤也可以在一开始做)
- 若客户机器有时间限制,那么可以dump文件,(全dump文件或者折中的minidump文件),待后续分析
7 常用命令
bp – 断点快捷指令
▸ 功能:在指定位置埋下”陷阱”,程序运行到这里自动暂停
▸ 示例:bp MyApp!main
→ 在MyApp程序的main函数处设卡
bu – 延迟断点
▸ 功能:给尚未加载的代码提前设卡(比如等待DLL加载后触发)
▸ 示例:bu MyPlugin!Init
→ 当MyPlugin模块载入时在Init函数暂停
bm – 批量设卡
▸ 功能:用*号通配符批量拦截多个函数
▸ 示例:bm MyApp!*
→ 拦截MyApp所有函数入口
ba – 内存监视
▸ 功能:当指定内存被读写时自动暂停(最多监控4字节范围)
▸ 示例:ba r4 0x12345678
→ 监控4字节内存区域的读取行为
lm – 模块清单
▸ 功能:显示已加载的代码模块信息(配合x指令使用更佳)
x – 符号探测器
▸ 功能:快速查找函数/变量地址(支持通配符搜索)
▸ 示例:x MyApp!*Print*
→ 列出所有含Print关键字的符号
g – 放行指令
▸ 功能:让暂停的程序继续奔跑(就像点击绿色播放按钮)
p – 步过
▸ 功能:执行当前行代码,遇到函数调用不进入
n – 冻结当前线程
▸ 效果:当前线程挂起,其他线程照常运行
m – 解冻当前线程
▸ 效果:被挂起的线程恢复执行
wt – 智能追踪
▸ 功能:记录函数内部所有调用轨迹(排查复杂调用链神器)
.cls – 清屏
▸ 功能:长时间调试后记得清理屏幕信息
d系列 – 内存检测
▸ db → 以字节形式显示内存(类似十六进制查看器)
▸ dt → 按数据结构解析内存(需提前加载符号表)
▸ dv → 查看局部变量值(快速定位变量异常)
r系列 - 打印寄存器的值
r rax
-> 打印寄存器rax
的值r eax
-> 打印寄存器eax
的值
dump - dump文件生成
▸ .dump /m C:/dumps/myapp.dmp
→ 缺省选项,生成标准的minidump, 转储文件通常较小,便于在网络上通过邮件或其他方式传输。 这种文件的信息量较少,只包含系统信息、加载的模块(DLL)信息、 进程信息和线程信息。
▸ .dump /ma C:/dumps/myapp.dmp → 带有尽量多选项的minidump(包括完整的内存内容、句柄、未加载的模块,等等),文件很大,但如果条件允许(本机调试,局域网环境), 推荐使用这种dump。
▸.dump /mFhutwd C:/dumps/myapp.dmp → 带有数据段、非共享的读/写内存页和其他有用的信息的minidump。包含了通过minidump能够得到的最多的信息。是一种折中方案。
汇编代码窗口的开启
To open or switch to the Disassembly window, choose Dissasembly from the View menu. (You can also press ALT+7
or select the Disassembly button on the toolbar. ALT+SHIFT+7 will close the Disassembly Window.)