写与2014年6月3日
Default Workspace 默认工作空间
implicit Workspace 隐含工作空间
Named Workspace 命名工作空间
explicit Workspace 显示工作空间
Base Workspace 基础工作空间
dormant 状态
Default Kernel-mode Workspace 默认内核态工作空间
Remote Default Workspace 默认远程调试工作空间
DbgSrv | KdSrv 调试服务器
Processor-specific Workspace 特定处理器工作空间
Default User-mode Workspace 默认用户态工作空间
----------------------------------------------------------------------------------------------------------------------------------*/
工作空间保存
/*----------------------------------------------------------------------------------------------------------------------------------
1.保存到注册表
HKEY_CURRENT_USER\Software\Microsoft\WinDBG\Workspaces 默认保存工作空间的注册表路径
User 用户态调试工作空间
Kernel 内核态调试工作空间
Dump 转储文件调试工作空间
Explicit 命名工作空间
2.Save Workspace as ... 另存当前工作空间
----------------------------------------------------------------------------------------------------------------------------------*/
加载工作空间
/*----------------------------------------------------------------------------------------------------------------------------------
Save Workspace to File 另存当前工作空间为*.WEW文件 (二进制与注册表数据一样)
1.启动WinDBG加-w开关,指定要使用的工作空间名
2.File菜单加载指定的命名工作空间
3.WinDBG子目录Themes,有4种定制工作空间设置
将.reg文件导入注册表
Open Workspace in File菜单打开*.WEW文件,可以应用对应主题
----------------------------------------------------------------------------------------------------------------------------------*/
删除工作空间
/*----------------------------------------------------------------------------------------------------------------------------------
注册表编辑器(regedit)
删除注册表中工作空间的键值
全部删除Workspaces子键全部键值
File菜单Delete Workspaces 删除工作空间
----------------------------------------------------------------------------------------------------------------------------------*/
WinDBG命令
/*----------------------------------------------------------------------------------------------------------------------------------
/*----------------------------------------------------------------------------------------------------------------------------------
标准命令
----------------------------------------------------------------------------------------------------------------------------------*/
g命令 恢复运行
t命令 (trace into)跟踪执行
p命令 (step over)单步执行
wt命令 追踪监视
r命令 观察和修改通用寄存器
rdmsr命令 读MSR寄存器
wrmsr命令 写MSR寄存器
rm命令 设置寄存器显示掩码
ib命令 读写IO端口
iw命令 读写IO端口
id命令 读写IO端口
ob命令 读写IO端口
ow命令 读写IO端口
od命令 读写IO端口
d命令 观察内存数据
e命令 编辑内存数据
s命令 搜索内存数据
k命令 观察栈回溯
bp命令 设置、维护内存断点
ba命令 硬件断点
bl命令 管理断点
bc 清除断点
bd 禁止断点
be 开启断点
~(取反)命令 显示和控制线程
|(或)命令 显示进程
?(问号)命令 评估表达式
??(双问号)命令 评估C++(加加)表达式
a命令 汇编
u命令 反汇编
dg命令 显示段选择子
$命令 执行命令文件
sx命令 设置调试事件处理方式
sq命令 启用、禁止静默模式
so命令 设置内核选项
ss命令 设置符号后缀
version命令 显示调试器和目标版本
vertarget命令 显示调试目标所在系统信息
x命令 检查符号
ls命令 控制盒显示源程序
ld命令 加载调试符号
ln命令 搜索相邻符号
lm命令 显示模块列表
q命令 结束调试会话
qq命令 结束远程调试
qd命令 结束调试会话并分离调试目标
?(问号) 命令 显示主要标准命令和每个命令简单介绍
----------------------------------------------------------------------------------------------------------------------------------*/
元命令 以 .(点)开始,又称Dot Command点命令
/*----------------------------------------------------------------------------------------------------------------------------------
.symopt 符号选项命令
.sympath .symfix 符号路径命令
.srcpath .srcnoise .srcfix 源程序文件命令
.extpath 扩展命令模块路径
.extmatch 匹配扩展命令
.exepath 执行文件命令
.asm 设置反汇编选项命令
.expr 控制表达式评估器命令
.restart 重新打开调试会话命令
.abandon 放弃用户态调试进程命令
.create 创建新进程命令
.attach 附加存在进程命令
.opendump 转存文件命令
.detach 分离调试目标命令
.unload .unloadall 卸载命令
.chain 显示已经加载模块
.logfile 显示调试器日志信息命令
.logopen 打开调试器日志信息命令
.logappend 追加调试器日志命令
.logclose 关闭调试器日志命令
.remote 启动remote.exe远程调试服务命令
.server 启动调试引擎服务器命令
.servers 列出可用服务器命令
.send_file 向远程服务器发送文件命令
.endpsrv 结束远程服务器命令
.endsrv 结束引擎服务器命令
.sleep 调试器睡眠命令
.wake 调试器唤醒命令
.dbgdbg 启动另一调试器调试当前调试器命令
.dump 产生转储文件命令
.writemem 原始内存数据写到文件命令
.time 显示调试会话时间命令
.ttime 显示线程时间命令
.tlist(task list) 显示任务列表命令
.formats 以不同格式显示数字命令
.help 列出所有元命令和每个元命令的简单说明
----------------------------------------------------------------------------------------------------------------------------------*/
Extension Command 扩展命令
1.扩展命令,以叹号(!)开始,又称Band Command
2.扩展命令格式 ![扩展模块名].<扩展命令名>[参数]
3.实现对特定调试目标的调试功能,实现在动态加载的DLL模块中
4.WinDBG 包含了常用扩展命令模块,用户可以根据WinDBG的SDK自己编写扩展模块和扩展命令
/*----------------------------------------------------------------------------------------------------------------------------------
WINXP 调试目标位Windows XP或更高版本时的扩展命令模块
WINEXT 使用与所有Windows版本的扩展命令模块
----------------------------------------------------------------------------------------------------------------------------------*/
加载扩展模块
/*----------------------------------------------------------------------------------------------------------------------------------
1.调试目标被激活(Debuggee Activation)时,WinDBG会根据调试目标的类型和当前工作空间自动加载指定扩展模块
2.手动加载扩展模块
.load 命令加上扩展模块名或完整路径来加载扩展命令,无路径在扩展模块路径(EXTPATH)寻找文件
.loadby 命令加上扩展模块名称和一个已经加载的程序模块的名称
.loadby sos mscorwks 在mscorwks模块所在目录中加载SOS扩展模块
!扩展模块名.扩展命令名 指定模块没加在WinDBG会自动搜搜加载
.chain 列出当前加载所有模块
.unload 卸载指定模块
.unloadall 卸载所有模块
!ext.help 显示ext模块中所有扩展命令
----------------------------------------------------------------------------------------------------------------------------------*/
用户界面
窗口布局保存到工作空间中,下次打开工作空间,子窗口恢复到保存时状态
/*----------------------------------------------------------------------------------------------------------------------------------
----------------------------------------------------------------------------------------------------------------------------------*/
命令窗口和命令提示符
1.命令提示符由文字、大于号、星号组成
----------------------------------------------------------------------------------------------------------------------------------
一.等待状态,尚未建立调试目标连接时,显示空白提示符
----------------------------------------------------------------------------------------------------------------------------------
二.命令模式等待用户输入命令时,提示符是 提示目标+大于号
4.WinDBG支持同时调试多系统多调试目标,因此
1.用户态目标,命令提示符完整格式:
[||system_index:]process_index:thread_index>
2.双击内核调试时内核目标和内核转储文件目标, 命令提示符完整格式:
[||system_index:][processor_index]kd>
3.本地内核调试,命令提示符完整格式:
[||system_index:][processor_index]lkd>
system_index系统序号,多个用户态目标属于一个系统,每个内核目标属于一个单独的系统
process_index进程序号
processor_index处理器序号
thread_index线程序号
序号从0开始全局编排,当调试目标既有内核目标又有用户态目标,处理器需奥和线程序号同等编排,每个内核目标也被分配一个进程序号。
/*----------------------------------------------------------------------------------------------------------------------------------
||
||0 s 把当前系统切换到1号
||1 s 把当前系统切换到0号
|
|1 s
~
~0 s 切换到0号线程
~1 s 切换到1号线程
----------------------------------------------------------------------------------------------------------------------------------
三.BUSY提示符 *BUSY* 表示WinDBG处于执行任务未返回状态,用户无法输入命令
----------------------------------------------------------------------------------------------------------------------------------*/
四.命令提示符:No Target> 使用.abandon命令放弃所有调试目标后,
/*----------------------------------------------------------------------------------------------------------------------------------
五.命令提示符:Input> 表示调试目标遇到断言时,内核调试引擎通过WinDBG征求处理方式的状态,四中处理方式
b 中断到调试器
g 继续运行
p 终止进程
t 终止线程
----------------------------------------------------------------------------------------------------------------------------------*/
六.输入提示符 使用a命令、e命令编辑变量时没有指定变量值的时候
/*----------------------------------------------------------------------------------------------------------------------------------
输入和执行命令
----------------------------------------------------------------------------------------------------------------------------------*/
1.*BUSY*提示符状态下,一般不能输入命令,即使可以输入命令,命令也不会马上被执行
2.可以同一行输入输入多条命令,用 ; 分割
3.直接回车重复执行上一条命令
4.上下键浏览和选择以前输入过的命令
5.大多数命令不区分大小写,但有些命令的选项区分大小写
6.元命令以 . 开始,扩展命令以 ! 开始
7.ctrl+Pasue Break 终止长时间未完成的命令
ctrl+c 终止DD或CDB执行的命令
8.Ctrl+Alt+V 切换启用或禁止(Verbose Output模式)详细输出模式
9.Ctrl+Alt+D 切换显示或禁止与内核调试引擎之间的数据通信
10.F1 打开帮助文档
11. .hh 空格 希望了解的命令 打开帮助文档并直接跳转到相关的主题,比如 .hh sleep
12. n命令 改变默认的数制,WinDBG默认使用十六进制数制,比如 n 8,n 16
13. 数字前加0x、0n、0y 显示指定十六进制、十进制、二进制的数字
14. 末尾加h 也可以用于显示十六进制数
15. 64位长的数字 31位前加 `
16. 64位长的数字 也可以什么也不加,比如FFFFFFFF`80000000
17. 选中数字 可以直接拖拽复制的文字到命令行输入窗口中
18. 选中WinDBG中的数字 可以直接拖拽复制的文字到各种文本编辑器重
/*----------------------------------------------------------------------------------------------------------------------------------
表达式
WinDBG支持宏汇编(MASM)和C++语法编写的表达式,但默认支持MASM表达式
1. .expr 查看和设置默认表达式语法
Current expression evaluator: MASM - Microsoft Assembler expressions
2. @@masm(...)或@@C++(...) 显示指定括号中表达式使用的语法规则
比如对于C++表达式:
0:001> ? @@C++(18
Unexpected token '>(0x7c9840cb))'
??专用于评估C++表达式,所以可以也可以写成如下形式:
1:000> ?? @@C++(18
Unexpected token '>(0x7c9840cb))'
----------------------------------------------------------------------------------------------------------------------------------
可以使用的MSAN表达式
加减乘除(+、-、*、/)
移位(<<、>>、>>>)
求余(%或mod)
比较(=或==,>、<、>=、<=、!=)
按位于(&或and)
按位异或(^或xor)
按位或(|或or)
正负号等运算符
hi或low 分别取32位的高16位或者低16位
by 从指定地址取一字节(BYTE)
wo 从指定地址取一个字(WORD)
dwo 从指定地址取一个双字(DWORD)
qwo 从指定地址取一个四字(QWORD)
poi 从指定地址取指针长度的数据
比如:poi(ebp+8) 返回栈指针+8处的DWORD值
----------------------------------------------------------------------------------------------------------------------------------*/
WinDBG还定义了类似函数的特殊表达式,都以$符号开头
1. $iment(Adress) 返回参数Adress代表模块的入口地址
2. $scmp("string1","string2")
比较两个字符串,类似strcmp函数
3. $sicmp("string1","string2")
比较两个字符串,忽略大小写,类似stricmp函数
4. $spat("string1","string2")
判断参数1指定的字符串,是否符合参数2的指定的模式,模式字符串中可以包含?、*、#等特殊呼号。
5. $vvalid(Address,Length)
判断指定区域是不是有效的内存区,有效返回1,无效返回0
6. $funsucc(FunAdress,RetVal,Flag)
根据函数的返回值,评估函数执行是否成功
7. 在MSAM表达式中,使用如下格式指定源文件的行:
`[[Module!]Filename][:LineNumber]`
两端是重音符号,行号十进制,模块名和源文件名可以省略,省略WinDBG使用当前程序指针所对应的源文件。
比如:
bp `d4dtest!d4dtestdlg.cpp:196`
在d4dtestdlg.cpp文件的196行设置一个断点
----------------------------------------------------------------------------------------------------------------------------------*/
在C++表达式中,可以使用C/C++语言定义的各种运算符
取地址(&)
引用指针(*)
索引结构中的字段( -> 或 . )
指定类名 ( :: )
类型转换 (dynamic_cast、static_cast、const_cast、reinterpret_cast)等各种运算符。
比如:
??&(this->m_Button1)
可以显示出m_Button1对象的所有成员
----------------------------------------------------------------------------------------------------------------------------------
在C++表达式中,还可以使用以下#号开始的宏
##CONTAINING_RECORD(Adress,Type,Field)
根据Field的地址(Adress),返回这个字段所属的Type结构的基地址
#FIELD_OFFSET(Adress,Type)
返回Field字段在Type结构中的偏移地址
#RTL_CONTAINS_FIELD(Struct,Size,Field)
判断在Size指定的长度范围内是否包含了Field字段
#RTL_FIELD_SIZE(Type,Field)
返回结构(Type)中指定字段(Field)的长度
#RTL_NUMBER_OF(Array)
返回数组元素个数
#RTL_SIZEOF_THROUGH_FIELD(Type,Field)
返回截止到指定字段(Field)的结构(Type)长度,包含这个字段
/*----------------------------------------------------------------------------------------------------------------------------------
WinDBG中添加注释的方法 特殊命令,所以执行使用前在前一条命令后加 ; 分号作为分隔
* 命令 *号以后所有内容都被当作注释
$$ 命令 $$后的注释用 ; 分号结束,后边可以写其他命令
比如:
1:000> r eax ;$$ adress of var_a;r ebx;* var_b;r ecx
eax=00001708
ebx=00000001
1.命令执行结果可以写入到记录文件中,就是做调试笔记。这是调试的好习惯。
2.另一用途是在命令程序中
----------------------------------------------------------------------------------------------------------------------------------*/
Pseudo-Register 伪寄存器
1.为了方便地引用被调试程序中的数据和寄存器而定义
2.可以在命令编辑框和命令文件中使用
3.解析命令时,WinDBG的调试器引擎会自动替换(展开)位合适的值
4.$@ 快速使用伪寄存器
/*----------------------------------------------------------------------------------------------------------------------------------
----------------------------------------------------------------------------------------------------------------------------------
比如:
1:000> ? @$pagesize
Evaluate expression: 4096 = 00001000
----------------------------------------------------------------------------------------------------------------------------------*/
User-Defined Pseudo-Registers
WinDBG为用户准备了20个用户伪寄存器
$t0~$t19 可以用r命令设置任意的整数值,初始值都是0
/*----------------------------------------------------------------------------------------------------------------------------------
Alias 别名
User-Named Alias 用户命名别名
Fixed-Named Alias 固定名称别名,固定为$u0~$u9
Automatic Aliases WinDBG自动定义别名
----------------------------------------------------------------------------------------------------------------------------------*/
/*----------------------------------------------------------------------------------------------------------------------------------
.echo 显示某别名的取值
比如:
1:002> .echo $ntnsym *在内核态调试回话中
nt *在内核态调试回话中
1:002> .echo $ntnsym *在用户态调试会话中
ntdll *在用户态调试会话中
as 定义或修改用户命名别名
格式:
as 别名名称 别名实体
比如:
1:002> as v version 为内部命令version定义别名v
然后执行v命令:
1:002> v
---------------------------------------------------------------------------------------
Windows XP Version 2600 (Service Pack 3) MP (2 procs) Free x86 compatible
Product: WinNt, suite: SingleUserTS
kernel32.dll version: 5.1.2600.5781 (xpsp_sp3_gdr.090321-1317)
Machine Name:
Debug session time: Sat Nov 10 15:20:20.015 2012 (UTC + 8:00)
---------------------------------------------------------------------------------------
----------------------------------------------------------------------------------------------------------------------------------*/
修改固定别名代表的实体格式:
----------------------------------------------------------------------------------------------------------------------------------*/
r $.u<0~9>=<别名实体>
比如:
||1:0: kd> r $.u9=nt!KiServiceTable *将u9定义为nt!KiServiceTable
||1:0: kd> .echo $u9 *显示u9的内容
nt!KiServiceTable *在dd命令中使用u9这个别名
||1:0: kd>dd $u9
----------------------------------------------------------------------------------------------------------------------------------*/
别名替换规则
/*----------------------------------------------------------------------------------------------------------------------------------
一.用户别名和命令联合使用时
1.必须用${用户别名} 将用户别名包围起来
2.使用空格 将别名与其他部分分隔开
比如:
0:000> as SST nt!KiServiceTable *定义用户别名
0:000> dd SST 14 *空格方式调用用户别名
0:000> dd SST +8 14 *空格方式调用多参数用户别名
0:000> dd ${SST}+8 14 *使用${}方式调用用户别名
----------------------------------------------------------------------------------------------------------------------------------*/
二.固定别名长度是确定的,所以可以直接使用,不需要大括号
比如:
dd $u9+8 14
/*----------------------------------------------------------------------------------------------------------------------------------
al 列出目前定义所有用户命名别名
ad 删除指定用户命名别名
ad * 全部删除用户别名
----------------------------------------------------------------------------------------------------------------------------------*/
30.4.5 循环和条件执行
/*----------------------------------------------------------------------------------------------------------------------------------
z命令 循环执行它前面的指令,执行后测试循环条件,知道条件为假结束循环,然后执行它后边的命令
比如:
0:000> r ecx=3 *设置循环次数为3
0:000> r ecx=ecx-1; r ecx; z(ecx);r ecx=ecx+1 *递减ecx直到为0,然后再递增一次
ecx=00000002
redo [1] r ecx=ecx-1; r ecx; z(ecx);r ecx=ecx+1
ecx=00000001
redo [2] r ecx=ecx-1; r ecx; z(ecx);r ecx=ecx+1
ecx=00000000
0:000> r ecx *执行后用r命令确认ecx的值是否为1
ecx=00000001
----------------------------------------------------------------------------------------------------------------------------------*/
!for_each_XXX 对每个栈帧执行一个操作
!for_each_local 对每个局部变量
比如:
!for_each_frame !for_each_local dt @#Local
打印每个栈帧的每个局部变量
0:000> !for_each_frame !for_each_local dt @#Local
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
00 0007fddc 77d191be ntdll!KiFastSystemCallRet
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
01 0007fdfc 010021b0 USER32!GetProcessWindowStation+0x29
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
02 0007ff1c 010125e9 calc+0x21b0
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
03 0007ffc0 7c817077 calc+0x125e9
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
04 0007fff0 00000000 kernel32!BaseProcessStart+0x23
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
00 0007fddc 77d191be ntdll!KiFastSystemCallRet
/*----------------------------------------------------------------------------------------------------------------------------------
j命令 判断一个条件,然后选择执行后面的命令
格式:
j <条件表达式> [Command1>] ; [Command2] 条件为真,执行Command1,否则执行Command2
如果执行的是一组命令 使用单引号
j Expression ['Command1'] ;['Command2']
比如:
0:000> r ecx;j (ecx<2) 'r ecx';'r eax'
ecx=00000001
ecx=00000001
0:000> r ecx=3;r ecx;j(ecx<2) 'r ecx';'r eax'
ecx=00000003
eax=0007fcf0
又比如:
bp 'my.cpp:122' "j (poi(MyVar)>5) '.echo MyVar Too Big' ; '.echo MyVar Acceptable'"
在my.cpp设置一断点,断点命中时,WinDBG自动执行双引号包围的命令,执行完后立即恢复目标运行
-----------------------------------------------------------------------------------------------------
使用源命令中的 .if .else .elseif
比如:
r ecx; .if(ecx>2) {r ecx} .else {r eax}
每个大括号可以包含多个分号分隔的命令
----------------------------------------------------------------------------------------------------------------------------------*/
30.4.6 进程和线程限定符
/*----------------------------------------------------------------------------------------------------------------------------------
----------------------------------------------------------------------------------------------------------------------------------*/
比如:
~0r; ~0k; *显示0号线程的寄存器和栈回溯,可以跨线程执行
0:000> ~0r;~0k;
eax=0007fcf0 ebx=00000000 ecx=00000003 edx=7c92e514 esi=0007fee8 edi=01014018
eip=7c92e514 esp=0007fde0 ebp=0007fdfc iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
ntdll!KiFastSystemCallRet:
7c92e514 c3 ret
ChildEBP RetAddr
0007fddc 77d191be ntdll!KiFastSystemCallRet
WARNING: Stack unwind information not available. Following frames may be wrong.
0007fdfc 010021b0 USER32!GetProcessWindowStation+0x29
0007ff1c 010125e9 calc+0x21b0
0007ffc0 7c817077 calc+0x125e9
0007fff0 00000000 kernel32!BaseProcessStart+0x23
~0e r; k; *显示0号线程的寄存器和栈回溯另一种写法,可以跨线程执行
0:000> ~0e r; k;
eax=0007fcf0 ebx=00000000 ecx=00000003 edx=7c92e514 esi=0007fee8 edi=01014018
eip=7c92e514 esp=0007fde0 ebp=0007fdfc iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
ntdll!KiFastSystemCallRet:
7c92e514 c3 ret
ChildEBP RetAddr
0007fddc 77d191be ntdll!KiFastSystemCallRet
WARNING: Stack unwind information not available. Following frames may be wrong.
0007fdfc 010021b0 USER32!GetProcessWindowStation+0x29
0007ff1c 010125e9 calc+0x21b0
0007ffc0 7c817077 calc+0x125e9
0007fff0 00000000 kernel32!BaseProcessStart+0x23
~0 r; k; *显示当前线程的寄存器和栈回溯
0:000> ~0 r; k;
eax=0007fcf0 ebx=00000000 ecx=00000003 edx=7c92e514 esi=0007fee8 edi=01014018
eip=7c92e514 esp=0007fde0 ebp=0007fdfc iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
ntdll!KiFastSystemCallRet:
7c92e514 c3 ret
ChildEBP RetAddr
0007fddc 77d191be ntdll!KiFastSystemCallRet
WARNING: Stack unwind information not available. Following frames may be wrong.
0007fdfc 010021b0 USER32!GetProcessWindowStation+0x29
0007ff1c 010125e9 calc+0x21b0
0007ffc0 7c817077 calc+0x125e9
0007fff0 00000000 kernel32!BaseProcessStart+0x23
~* 对当前进程所有线程执行一系列操作
比如:
~*e r; k; 对当前进程的每一个线程分别执行r和k命令
0:000> ~*e r; k;
eax=0007fcf0 ebx=00000000 ecx=00000003 edx=7c92e514 esi=0007fee8 edi=01014018
eip=7c92e514 esp=0007fde0 ebp=0007fdfc iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
ntdll!KiFastSystemCallRet:
7c92e514 c3 ret
ChildEBP RetAddr
0007fddc 77d191be ntdll!KiFastSystemCallRet
WARNING: Stack unwind information not available. Following frames may be wrong.
0007fdfc 010021b0 USER32!GetProcessWindowStation+0x29
0007ff1c 010125e9 calc+0x21b0
0007ffc0 7c817077 calc+0x125e9
0007fff0 00000000 kernel32!BaseProcessStart+0x23
/*----------------------------------------------------------------------------------------------------------------------------------
30.4.7 记录到文件
LogFile 日志文件
菜单:Open/Close Log File 启用/关闭日志文件
.logopen 打开日志文件
.logclose 关闭日志文件
.logfile 显示日志文件
----------------------------------------------------------------------------------------------------------------------------------*/
30.5.1 附加进程 用于调试自动启动的系统服务和程序
/*----------------------------------------------------------------------------------------------------------------------------------
1.菜单Attach to Process或F6
2.执行WinDBG.exe -I设置为JIT调试器
3.启动WinDBG时 -p [要附加进程PID]
4.启动WinDBG时 -pn [要附加进程名]
比如:
C:\Documents and Settings\Administrator>C:\WinDDK\7600.16385.1\Debuggers\windbg.exe -pn calc.exe
4. .attach命令 在当前调试会话,增加一调试会话,用于调试多目标
5. winddbg.exe -pe -p PID方式启动 用于重新附加被上一次启动WinDBG时用.abandon命令抛弃的被调试进程
也可以再运行窗口直接输入:
----------------------------------------------------------------------------------------------------------------------------------*/
创建并调试新进程
/*----------------------------------------------------------------------------------------------------------------------------------
1.菜单Open Executatble或者Ctrl+E热键,可指定命令行参数和启动目录
2.启动WinDBG时将程序文件作命令行参数传递
3.在注册表 HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\Current-Version\Image File Execution
1.创建无路径程序文件名命名的子键
2.在子键建立名为Debugger的REG_SZ类型的键值
3.键值为WinDBG的完整路径
4. .create命令,在当前会话中增加调试会话
----------------------------------------------------------------------------------------------------------------------------------*/
30.5.3 非入侵式调试 只适用已运行程序
/*----------------------------------------------------------------------------------------------------------------------------------
1.图形模式,选中对话框中Noninvasive复选框
2.命令行加 -pv
3. .attach命令,加-v
4.不支持JIT调试
5.在NT和2000系统可以避免,重新启动被调试服务
----------------------------------------------------------------------------------------------------------------------------------*/
30.5.4 调试内核目标
/*----------------------------------------------------------------------------------------------------------------------------------
1.选择通信方式
2.目标系统启用内核调试引擎 Vista以前修改Boot.ini文件,Vista用BCDEdit工具修改
3.主机启动WinDBG内核调试会话
1.File菜单,Kernel Debug,或者Ctrl+K,设置通讯电缆和目标机器类型和参数
2.命令行启动,使用-k开关,比如:windbg -k com:port=com_1, buad=115200
4.错过调试引擎发送数据时,Ctrl+Break触发调试器主动向目标系统发送消息
----------------------------------------------------------------------------------------------------------------------------------*/
30.5.5 本地内核调试
1.File菜单Kernel Debug,选Local
2.命令行-kl参数
3. .attach -k命令,多会话才可用
4.Viata系统需以调试选项启动,XP无要求,2000以下不可用
/*----------------------------------------------------------------------------------------------------------------------------------
30.5.6 调试转储文件
打开转储文件
1.File菜单Open Crash Dump,
2.命令行-z开关加指定打开转储的文件
3. .opendump命令,多回话才可用
----------------------------------------------------------------------------------------------------------------------------------*/
30.5.7 远程调试
1.远程计算机运行DbgSrv 远程用户态调试,或DdSrv 远程内核态调试作为服务器,本地WinDBG链接到远程的服务器.
2.服务器端和客户端都运行WinDBG. 有局域网或互联网链接
1.服务端启动方式:
1.命令行-server指定连接方式:比如
windbg -server npipe:pipe=advdbg //创建使用命名管道名称为advdbg方式通信的服务器
WinDBG启动以后建立内核调试连接
2.启动WinDBG并建立内核调试会话,链接后,执行.server命令,比如:
.server npipe:pipe=advdbg
2.客户端启动方式:
1.命令行,-remote开关,比如:
WinDBG -remote npipe:server=ADVDBGPC,pipe=advdbg
2.启动WinDBG,File菜单Connect to Remote Session(Ctrl+R),
对话框输入链接字符串(npipe:Pipe=advdbg,Server=ADVDBGPC),或
Browse按钮,输入服务器机器名,在WinDBG搜索到的服务器中选择链接的目标
3.如果是TCP端口方式建立连接,服务器可以设置命令设置端口号和密码可以修改:
.server tcp:port=2002,password=2008,
/*----------------------------------------------------------------------------------------------------------------------------------
30.6 终止调试会话
----------------------------------------------------------------------------------------------------------------------------------*/
1.停止调试
1.菜单:Stop Debugging(或q命令),恢复Dormant(闲赋状态)
2.用户态目标会终止
2. 分离调试目标
1.菜单:Detach Debuggee(或命令 .detach)
2.用户态目标继续运行(XP以上系统,以下系统会终止)
内核目标,中断到调试器状态且可以重新连接
3. 抛弃被调试进程
.abandon命令分离调试进程,进程仍然保持调试状态被挂起,这样的进程可以用另一个调试器用-pe开关附加,会和首次附加略微不同,比如:
WinDBG -pe -p 2272
4.杀死被调试进程
.kill 内核态,可杀死指定进程
用户态可杀死当前进程
5. 调试器终止或僵死
直接关闭僵死的调试器,目标进程也会被关闭,用命令行 -pe开关启动,附加被调试进程,在关闭僵死调试器,可以方式以前的工作前工尽弃
6. 重新开始调试
Debug菜单Restart(或Ctrl+Shift+F5或 .restart命令)
1.用户态进程:如未运行,则关闭并重新运行,如果已经运行,WinDBG 提示如下信息
2.内核态目标:重新启动调试器并与目标重新连接,如要重新启动目标系统用 .reboot命令
/*----------------------------------------------------------------------------------------------------------------------------------
30.7 理解上下文
Logon Session 登录会话
Context 上下文
Session Isolation Vista引入的会话隔离技术
Login Session Context 登录会话上下文
----------------------------------------------------------------------------------------------------------------------------------*/
登录会话上下文
!session 观察和设置会话信息
!sprocess 列出指定会话所有进程
.cache 命令中在缓存选项中加入forcedecodeuser或者forcedecodeptes选项禁止缓存功能,
让调试器每次切换登录会话上下文都重新读取内存数据
/*----------------------------------------------------------------------------------------------------------------------------------
进程上下文
内核态调试:
.process 观察和设置默认进程
比如:
1:ld>.process 83f7fc78 切换进程到83f7fc78,83f7fc78为EPROCESS结构的地址
!process 0 0 列出系统中所有进程基本信息,其中包含EPROCESS结构的地址
.context 设置或显示Base of Page Directory(用户态地址的页目录基地址)
比如:
kd>.context
用户态调调试:
|<进程PID> s 切换默认进程
----------------------------------------------------------------------------------------------------------------------------------*/
30.7.3 Register Context 寄存器上下文
Context Record 当前线程的上下文记录 线程上下文,异常上下文
/*----------------------------------------------------------------------------------------------------------------------------------
.thread 显示或设置寄存器上线文所针对的线程
比如:
1:kd>.thread 显示当前隐含线程
!process<所属进程的EPROCESS结构地址>f 列出一个进程所有线程,包括每个线程ETHREAD结构,比如:
----------------------------------------------------------------------------------------------------------------------------------
0: kd> !process 8611c8a8 f
PROCESS 8611c8a8 SessionId: 0 Cid: 0778 Peb: 7ffdc000 ParentCid: 02ac
DirBase: 06d40240 ObjectTable: e1d20300 HandleCount: 280.
Image: vmtoolsd.exe
VadRoot 85cd84b8 Vads 209 Clone 0 Private 1051. Modified 37. Locked 0.
DeviceMap e1001100
Token e1aab978
ElapsedTime 00:02:10.859
UserTime 00:00:00.468
KernelTime 00:00:00.984
QuotaPoolUsage[PagedPool] 134628
QuotaPoolUsage[NonPagedPool] 10000
Working Set Sizes (now,min,max) (2622, 50, 345) (10488KB, 200KB, 1380KB)
PeakWorkingSetSize 2847
VirtualSize 83 Mb
PeakVirtualSize 83 Mb
PageFaultCount 3426
MemoryPriority BACKGROUND
BasePriority 13
CommitCharge 1843
THREAD 8611c2d0 Cid 0778.077c Teb: 7ffdf000 Win32Thread: e1a98eb0 WAIT: (Executive) UserMode Non-Alertable
860503dc NotificationEvent
IRP List:
0: kd> .thread 8611c2d0 *利用!process 8611c8a8 f命令枚举出来的线程ETHREAD结构体地址,将当先线程上线文设置为新的线程上下文
Implicit thread is now 8611c2d
00: kd> r *此时使用寄存器和栈命令,WinDBG会警告上下文,是经过上次设置的上下文
Last set context:
eax=00000000 ebx=00000000 ecx=00000000 edx=00000000 esi=00000000 edi=00000000
eip=805469ab esp=eec5b96c ebp=eec5b9bc iopl=0 nv up di pl nz na po nc
cs=0008 ss=0010 ds=0000 es=0000 fs=0000 gs=0000 efl=00000000
nt!KiSwapContext+0x2f:
805469ab 8b2c24 mov ebp,dword ptr [esp] ss:0010:eec5b96c=eec5b9bc
0: kd> kv *此时使用寄存器和栈命令,WinDBG会警告上下文,是经过上次设置的上下文
*** Stack trace for last set context - .thread/.cxr resets it
ChildEBP RetAddr Args to Child
eec5b978 80504846 00000000 860476a0 804fbd98 nt!KiSwapContext+0x2f (FPO: [Uses EBP] [0,0,4])
eec5b984 804fbd98 860476a0 00000002 00000004 nt!KiSwapThread+0x8a (FPO: [0,0,0])
eec5b9bc 805c1a43 00000002 eec5bbf0 00000001 nt!KeWaitForMultipleObjects+0x284 (FPO: [Non-Fpo])
eec5bd48 8054262c 00000002 02d5fed0 00000001 nt!NtWaitForMultipleObjects+0x297 (FPO: [Non-Fpo])
eec5bd48 7c92e4f4 00000002 02d5fed0 00000001 nt!KiFastCallEntry+0xfc (FPO: [0,0] TrapFrame @ eec5bd64)
02d5ff44 00000000 00000000 00000000 00000000 ntdll!KiFastSystemCallRet (FPO: [0,0,0])
输入.cxr命令(或不带参数的 .thread命令)可以恢复原线程上下文
0: kd> .cxr
Resetting default scope
0: kd> .thread
Implicit thread is now 8055ce60
----------------------------------------------------------------------------------------------------------------------------------
.ecxr 在调试用户态转储文件时,将转储文件中保存的异常上下文变量设置为寄存器上下文变量
----------------------------------------------------------------------------------------------------------------------------------*/
30.7.4 Local Context 局部(变量)上下文 WinDBG用栈顶帧代表局部变量上下文
/*----------------------------------------------------------------------------------------------------------------------------------
.frame 观察当前局部上下文
dv 显示当前函数的参数和局部变量
.frame 5 .frame加上栈帧号,切换局部上下文到指定的栈帧
dv 此时显示的就是.frame 5,切换后的局部上下文的参数和局部变量
----------------------------------------------------------------------------------------------------------------------------------*/
VC类型符号默认放在,VCx0.PDB文件,WinDBG不自动加载此文件,所以显示局部变量,有很多no type information错误
解决方法有两种:
1.Setting -> C++- >General -> Debug Info中设置: 将符号格式设置为C7 Compatable
2.链接选项中指定: /PDBTYPE:CON选项
/*----------------------------------------------------------------------------------------------------------------------------------
30.8 Debug Symbols 调试符号
----------------------------------------------------------------------------------------------------------------------------------*/
.symfix C:\symbols //设置符号文件的搜索路径
.reload //重新加载所有符号文件
k //显示栈回溯信息
/*----------------------------------------------------------------------------------------------------------------------------------
编写服务器DLL 实现自己的符号服务器
Symbol Path 符号文件路径,当多个符号文件在不同位置,分为磁盘目录符号路径,符号服务器
Symbol Server 符号服务器
Downstream Store 下游符号库,位于本地
Centralized Store 中央符号仓库,位于微软符号服务器
lazy symbol loading 懒惰式符号加载策略
Symbol Server API 符号服务器API,在DbgHelp目录(sdk\help\dbghelp.chm)中查询
DbgHelp.dll Windows操作系统调试辅助模块,读取和解析调试符号
Symsrv.dll 符号服务器的本地模块,从符号服务器查找,下载,管理符号文件
典型符号服务器: SRV*d:\symbols*http://msdl.microsoft.com/download/symbols;c:\work\debug;
c:\work\debug;为存储符号的本地目录,*d:\symbols*http://msdl.microsoft.com/download/symbols为符号服务器
本机配置: SRV*D:\WindowsSymbols*http://msdl.microsoft.com/download/symbols;D:\MySymbols;
----------------------------------------------------------------------------------------------------------------------------------*/
设置符号路径的方法:
1.环境变量:_NT_SYMBOL_PATH和_NT_ALT_SYMBOL_PATH
2.命令中加-y开关和参数
3.命令.sympath, 增加修改显示符号路径,比如: .sympath+c:\folder2
4.命令.symfix 自动设置符号服务器
5.File菜单,Symbol File Path->Symol Search Path对话框
6.增加符号服务器的方式:
symsrv*ServerDLL*[DownstreamStore*]ServerPath
ServerDLL符号服务器DLL,DwonstreamStore下游符号库目录,ServerPath符号服务器,比如:
symsrv*symsrv.dll*\\mybuilds\mysymbols
symsrv*symsrv.dll*\\localsrv\mycache*http://www.somecompany.com/manysymbols
srv相当于symsrv*SymSrv.dll
srv*\\mybuilds\mysymbols
srv*\\localsrv\mycache*http://www.somecompany.com/manysymbols
/*----------------------------------------------------------------------------------------------------------------------------------
ld kerner32 //从符号服务器加载kernel32的符号文件
BOOL CALLBACK SymbolServer(
[in] LPCSTR params,
[in] LPCSTR filename,
[in] PVOID id,
[in] DWORD two,
[in] DWORD three,
[out] LPSTR path
);
----------------------------------------------------------------------------------------------------------------------------------*/
30.8.5 观察模块信息 包含加载符号文件的信息
1. lm命令
2. !lmi扩展命令
3.菜单Debug->Moudules 模块列表对话框
/*----------------------------------------------------------------------------------------------------------------------------------
lm 显示模块列表
lm v 显示详细模块信息
lm m k* 显示以k开头模块信息
lm o 显示已经加载模块信息
lm l 显示已经加载符号的模块
lm e 显示有符号问题的模块
!lmi Windbg 显示Windbg模块的信息
0:000> !lmi secretary 显示secretary的模块信息
Loaded Module Info: [secretary]
如果secretary基地址为00400000
0:000> !lmi 00400000 利用secretary的基地址显示其模块信息
Loaded Module Info: [00400000]
Module: secretary
Base Address: 00400000
------- ---------------------------------------------------------------------------------------------------------------------------*/
30.8.6 检查符号
微软下载符号文件安装包的页面:
x命令格式:
x [选项] 模块名!符号名 //模块名和符号名可以包含通配符
通配符:
1. *代表0或任意多个字符
2. ?代表任意单一字符
3. #代表#前边字符可以出现任意次 lo#p,可以通配lop,loop,looop... ...
4. []#代表#前边多个字符可以出现任意次 m[ai]#n,可以通配min,main,maan,main,maiain
/*----------------------------------------------------------------------------------------------------------------------------------
x ntdll!dbg* 列出ntdll模块所有以dbg开头的符号
x secretary!arg* 列出secretary模块(或程序),所以以arg开头的符号
x *!_crtheap 检查所有模块,如果有符号_crtheap,就显示出来
----------------------------------------------------------------------------------------------------------------------------------*/
x命令的选项:
/a /A 地址升序和降序
/n /N 名称升序和降序
/z /Z 符号大小升序和降序
/t 显示符号数据类型
/s <符号大小> 按符号大小设置过滤
/p 去掉函数与括号之间的空格
/q 启用引号格式显示符号名
/v 显示符号的符号类型和大小
符号类型:
local 局部
global 全局
parameter 参数
function 函数
unknown 未知
/*----------------------------------------------------------------------------------------------------------------------------------
0:000> x /v /Z secretary!m* //查找小秘书中自定义函数mtow
prv func 0063a6f0 365 secretary!memcpy ()
prv func 0063ab30 365 secretary!memmove ()
prv func 0062a7d0 1b8 secretary!memcmp ()
prv func 00624180 184 secretary!memcpy_s ()
prv func 00624370 159 secretary!memmove_s ()
prv func 004f5060 c9 ecretary!mtow ()
0:000> x secretary!mtow
004f5060 secretary!mtow (char *)
0:000> x /v secretary!mtow
prv func 004f5060 c9 secretary!mtow (char *)
0:000> x /v /q secretary!mtow
prv func 004f5060 c9 @!"secretary!mtow" (char *)
0:000> x /v /q /p secretary!mtow
prv func 004f5060 c9 @!("secretary!mtow(char *)"
0:000> x /v /q /p /t secretary!mtow
prv func 004f5060 c9
----------------------------------------------------------------------------------------------------------------------------------*/
30.8.7 搜索符号
ln 搜索距离指定地址最近的符号命令(List Nearest Symbols)
0:000> ln 004f506f
e:\stack\project\vs2008工程\secretary\secretary\ttsapi.cpp(107)+0xf
(004f5060) secretary!mtow+0xf | (004f5160) secretary!AddXml
/*----------------------------------------------------------------------------------------------------------------------------------
30.8.8 设置符号选项
.sympot 显示和修改符号选项,格式:
.sympot [+/- 选项标志]
----------------------------------------------------------------------------------------------------------------------------------*/
!sym 扩展命令,可以使用参数设置符号选项
!sym noisy 启用吵杂模式
!sym quiet 关闭吵杂模式
/*----------------------------------------------------------------------------------------------------------------------------------
30.8.9 加载不严格匹配的符号文件
.reload /i 加载加载不严格匹配的符号文件
使用这种方式最好关闭吵杂模式
!sym quiet
或
.symopt+0x40
----------------------------------------------------------------------------------------------------------------------------------*/
30.9 事件处理 9种调试事件
EXCEPTION_DEBUG_ENVNT 异常事件
CREATE_THREAD_DEBUG_EVENT
CREATE_PROCESS_DEBUG_EVVNT
EXIT_THREAD_DEBUG_EVENT
EXIT_PROCESS_DEBUG_EVENT
LOAD_DLL_DEBUG_EVENT
UNLOAD_DLL_DEBUG_EVENT
OUTPUT_DEBUG_STRING_EVENT
RIP_EVENT
/*----------------------------------------------------------------------------------------------------------------------------------
异常事件子类:
Win32异常 Windows操作系统定义的异常,包括CPU异常,内核代码异常,非法访问,除零,定义在ntstatus.h中
Visual C++异常 Visual C++编译器的throw关键字抛出的异常,调用RaiseException API产生异常,所以
异常代码都在0xE06d7363(.msc)
托管异常 .Net程序托管方法抛出的异常,异常代码都砸0xE0636F6D(.com)
其他异常 用户程序调用RaiseException API抛出的异常,和其他C++编译器抛出的异常等
调试器定义异常 比如,WinDBG定义的Wake Debugger从睡眠状态唤醒的调试器异常事件
----------------------------------------------------------------------------------------------------------------------------------*/
30.9.2 两轮机会 只有异常事件有两轮处理机会
VEH 调试器 SEH 异常处理器
/*----------------------------------------------------------------------------------------------------------------------------------
30.9.3 定制事件处理方式
handled 已经处理状态
not handled 未处理状态
handling status 处理状态
continue status 继续状态
----------------------------------------------------------------------------------------------------------------------------------*/
设置:
1.异常事件第一,二论处理机会是否中断给用户
2.异常事件第一,二论处理机会的处理结果
VC6.0调试器设置界面: //不允许设置是否继续选项:
stop alaways 两轮机会都中断给用户
stop if not handled 只中断第二轮给用户
VS2002和VS2003的设置和VC6.0类似
VS2005调试器设置:
Thrown 第一轮机会中断给用户
User-unhandled 第二论机会中断给用户
WinDBG调试事件配置对话框:
菜单:Debug->Event Filters对话框,每一项异常处理设置都有Execution和Continue两组选项供用户设置:
---------------------------------------------------------------------------------------------------
Execution四个单选按钮:
Enabled 异常:两轮中断 其他调试事件:收到就中断
Disabled 异常:第二论中断 其他调试事件:不中断
Output 输出信息给用户
Ignore 忽略这个事件
---------------------------------------------------------------------------------------------------
Continue选项组: 只针对异常事件的第一轮机会进行设置
handled 已经处理异常
Not handled 没有处理异常
---------------------------------------------------------------------------------------------------
WinDBG默认异常配置:
1.对大多数异常 默认返回未处理
2.第二论异常 默认返回已经处理
3.第二轮异常 gn命令强制返回未处理
WinDBG允许为每个事件关联命令:
1.点击对话框Commands按钮弹出对话框,允许输入一系列命令
2.为每一种异常的每一轮机会输入一组命令
/*----------------------------------------------------------------------------------------------------------------------------------
对于大多数调试事件:
使用Aigument按钮设置参数,设置在满足次参数条件时中断的配置选项:
比如:
1.在列表选中,Load module,点Arguments按钮,对话框中输入kernel32.dll后关闭
2.Exception组中选择Enabled
这样程序在加载kernel32.dll时候会中断,二加载其他模块不会中断
----------------------------------------------------------------------------------------------------------------------------------*/
命令行设置方式语法:
sx命令
sxr命令 将所有事件处理选项恢复默认
sx命令无参数 列出各个事件的代码和目前的设置状态
----------------------------------------------------------------------------------------------------------------------------------
sxe Enabled 异常:两轮中断 其他调试事件:收到就中断 handled 已经处理异常
sxd Disabled 异常:第二论中断 其他调试事件:不中断 Not handled 没有处理异常
sxn Output 输出信息给用户 Not handled 没有处理异常
sxi Ignore 忽略这个事件 Not handled 没有处理异常
----------------------------------------------------------------------------------------------------------------------------------
sx {e|d|i|n}[-c"Cmdl"][-c2 "Cmd2"][-h]{Exception | Event |*}
e|d|i|n Enabled | Disabled | Output | Ignore
-c -c2 第一,第二轮机会关联命令
Exception | Event 设置调试事件(异常或其他事件)
-h开关 指定sx命令用来设置处理状态(handling status)
/*----------------------------------------------------------------------------------------------------------------------------------
sx- [-c "Cmd1"] [-c2 "Cmd2"] {Exception|Event|*}
---------------------------------------------------------------------------------------------------------------------------------
WinDBG为常用调试事件定义了简单的代码: 详见WinDB帮助文档中 Controlling Exceptions and Events介绍
av 非法访问代码
dz 除零异常代码
et 线程推出代码
----------------------------------------------------------------------------------------------------------------------------------*/
中断发生后的g,gn,gh命令:
g 返回调试器配置的处理状态返回给系统
gh 强制返回已经处理状态
gn 强制返回未处理状态
/*----------------------------------------------------------------------------------------------------------------------------------
实验:
Break instruction exception - code 80000003 (first chance)
//提示第一轮异常处理机会(first chance) 断点异常的异常代码code 80000003
0:000> sxe av //非法访问异常第一轮就中断
0:000> sxd -h av //非法访问异常处理状态设置为未处理
0:000> sx //查看并确认异常设置
... ...
bpec - Break instruction exception continue - handled
... ...
0:000>Access violation - code c0000005(first chance)
//提示第一轮异常处理机会(first chance) 断点异常的异常代码code c0000005
执行gh //如果异常未被排除继续中断在上面
执行gn //强制返回未处理,系统将执行以下操作
1.系统继续分发这个异常,寻找程序中的异常处理器
2.如果找不到异常处理器,系统执行UnhandledExceptionFilter函数(位于kernele32.dll)
3.UnhandledExceptionFilter返回EXCEPTION_CONTINUE_SEARCH,进入第二论异常分发,如果有调试器先分发给WinDBG
WinDBG会中断,并显示如下信息:
0:000>Access violation - code c0000005(!!! second chance !!!)
如果此时:
执行gh命令 //重新开始得到第一次处理机会
执行gn命令 //程序会突然消失,瞬间消失,这是系统强制终止了程序
执行g命令,
1.且当前配置为已处理状态,效果同gh
1.且当前配置未未处理状态,效果同gn
----------------------------------------------------------------------------------------------------------------------------------*/
设置源码级断点
一. [模块名!][函数名][(+偏移)]
bp degee!wmain
bp degee!wmain+3
二. 条件使用完全调试符号,调试符号,包含源代码行数信息,
bp `[模块名!][文件名][:行号]`
bp `degee!degee.cpp:16` //degee!可以省略
三. 对于C++的类方法,使用双::号,双__下划线,或者双@@()
bp classname::method
bp classname__method
bp @@(classname::method)
/*----------------------------------------------------------------------------------------------------------------------------------
30.10控制调试目标
1.菜单:Debug>Break,热键Ctrl+Break
2.有窗口界面的程序,热键F12
3.加入触发异常代码的程序,执行相应操作
----------------------------------------------------------------------------------------------------------------------------------*/
Initial Breakpoint(初始断点)由ntdll的LdrpInitializeProcess函数调用DbgBreakPoint触发
初始线程在新进程环境下执行:
1.新进程创建工作是在父进程环境下完成的,
2.初始线程真正在新进程环境下执行,从内核态KiThreadStartup开始
3.KiThreadStartup将线程IRQL(中断级别)降到APC级别,调用PspUserThreadStartup初始化APC(用户态代码异步过程调用),插入APC队列中
4.这个APC就是调用LdrpInitialize函数,LdrpInitialize函数是(新进程开始在用户态执行的最早代码):
4.LdrpInitialize函数:初始化加载器,读取执行选项后调用LdrpInitializeProcess
5.LdrpInitializeProcess函数,加载EXE文件以来的动态链接库,检查如果正在被调试(PEB的BeingDebugged字段)就调用DbgBreakPoint通知调试器.
5.1.此时尚未加载每个DLL得DllMain函数
6.LdrpInitialize执行完后KiUserApcDispatcher调用ZwContinue返回内核态的PspUserThreadStartup,
7.PspUserThreadStartup把线程IRQL降低到0(PASSIVE),
8.系统开始执行线程上下文中的进程启动函数BaseProcessStart
/*----------------------------------------------------------------------------------------------------------------------------------
WinGBG附加到进程:
1.默认目标进程创建远程线程触发断点
2.断点在新创建的线程上下文中
3.当恢复目标运行此线程推出
4.WinDBG加入-g开关:
4.1.忽略或不发起初始断点(接受到初始断点事件时不中断给用户)
4.2.附加到已创建进程,不发送远程线程触发断点
5.进程创建事件和EXE模块加载事件,比初始断点时间更早
----------------------------------------------------------------------------------------------------------------------------------*/
30.10.2 俘获调试目标
/*----------------------------------------------------------------------------------------------------------------------------------
调试器UI线程处理Break命令的过程:
0:000>kn
#ChildEBP RetAddr
00 ntdll!ZwCreateThread //创建远程线程
01 ntdll!RtlCreateUserThread //创建用户线程
02 ntdll!DbgUiIssueRemoteBreakIN //引发远程中断动作
03 kernel32!DebugBreakProcess //Windows的调试API
04 dbgeng!LiveUserDebugServices::RequestBreakIn //服务层
05 dbgeng!LiveUserTargetInfo::RequestBreakIn //目标层
06 dbgeng!DebugClient::SetInterrupt //调试引擎的接口函数
07 WinDBG!FrameWndProc //调试目标的窗口回调函数
----------------------------------------------------------------------------------------------------------------------------------*/
1.栈帧#7 WinDBG!FrameWndProc调试目标的窗口回调函数: 收到Break命令后,通过全局变量g_DbgClient调用SetInterrupt
2.栈帧#6 SetInterrupt函数: 让调试器进入命令模式
3.栈帧#5~4 调用调试目标类(LiveUserTargetInfo)和调试服务类(LiveUserDebugServices)的RequestBreakIn函数,将中断请求层层下传
4.栈帧#3 调用操作系统调试API: DebugBreakProcess
5.栈帧#2~0 DebugBreakProcess函数内部工作过程
6.栈帧#2 调用ntdll中的DbgUiIssueRemoteBreakin
7.栈帧#1~0 调用线程创建函数在调试目标中创建远程线程
8.这个远程线程就是ntdll中的DbgUiRemoteBreakin函数
9.DbgUiRemoteBreakin函数,代码很少,只是嗲用DbgBreakPoint API执行断点指令,产生一个断点异常
10.这个远程线程就是,调试器创建的远程线程
11.这个远程线程产生断点异常,触发调试目标中断在调试器中
12.如果远程线程创建后没有执行断点指令就被挂起,WinDBG就收不到断点事件,此时WinDBG等待一会,会显示如下提示信息:
Break-in sent ,waiting 30 seconds ...
//再等待30秒后,WinDBG会"人工合成"(Synthesize)一个代码为0x80000007的异常事件
13.这个异常事件的调试引擎函数名为SynthesizeWakeEvent
14.此函数调用过程如下:
00 dbgeng!SynthesizeWakeEvent
01 dbgeng!WaitForAnyTarget
02 dbgeng!RawWaitForEvent
03 dbgeng!DebugClient::WaitForEvent
04 WinDBG!EngineLoop
05 kernel32!BaseThreadStart
15.这个合成事件触发调试器的事件等待函数返回,并开始触发此事件,引发调用dbg!SuspendExecution函数
16.此函数一次挂起调试目标所有线程,使调试目标被中断
17.最后调试器进入命令模式,并显示如下信息:
(1440.1554):Wake debugger - code 80000007 (first chance)
18.总结:WinDBG先使用远程线程中断目标,如果超时,使用挂起方式,将被调试进程强制俘获到调试器中
19.SetInterrupt函数原型及参数:
HRESULT IDebugControl::SetInterrupt(IN ULONG Flags);
20.Flags参数标志:
DEBUG_INTERRUPT_PASSIVE(1):
1.中断到命令模式,但非强制.
2.函数内部将dbgeng!g_UserInterruptCount加1,将dbgeng!g_EngStatus设置为0x1005
DEBUG_INTERRUPT_EXIT(2):
1.让调试器引擎取消等待调试事件,强制返回,导致调试器没有中断到目标就进入命令模式
2.因没有合适进程和线程上下文,命令提示符会包含很多问号,大多数控制调试目标执行的命令无法执行
DEBUG_INTERRUPT_ACTIVE(0):
1.判断全局变量dbgeng!g_CmdState
2.如果调试器没在命令模式,要求调试目标中断到调试器以进入命令模式
3.如果调试器在命令模式,递增dbgeng!g_UserInterruptCount变量
/*----------------------------------------------------------------------------------------------------------------------------------
30.10.3 继续运行
1.菜单Debug>Go,快捷键F5
----------------------------------------------------------------------------------------------------------------------------------
2.g命令格式和参数:
g|gn|gh [a] {=StartAddress} {BreakAddress ... {;BreakCommands}}
1.开关a,BreakAdress设置断点为硬件断点,无此开关为软件断点
2.StartAddress 指定恢复执行的起始地址,默认当前位置
3. BreakAdress 指定断点地址,隐藏断点,命中后自动删除
4. BreakCommands 命中断点所执行的命令
5.汇编或源码窗口,使用运行到光标处,Ctrl+F10快捷键(菜单Debug>Run to Cursor),就是g命令加断点地址实现的
6.gh命令(go with Exception Handled)强制回复已经处理异常
7.gn命令(Go with Exception Not Handled)强制回复未处理异常
8.gu命令执行到上一级函数,无参函数
9.gc命令使用条件断点,无参函数
----------------------------------------------------------------------------------------------------------------------------------*/
30.11 单步执行
/*----------------------------------------------------------------------------------------------------------------------------------
Source Mode 源码模式
1.切换源码模式和汇编模式的单步执行
1.1.菜单:Debug>Source Code,1+t命令,进入源码模式
1.2.反选菜单:Debug>Source Code,执行1-t命令退出源码模式
1.3.Step Into 单步步入, t命令(Trace)
1.4.Step Over单步步入,p命令(pass)
----------------------------------------------------------------------------------------------------------------------------------*/
1.5.汇编级单步执行(p命令),t命令,和针对非函数调用执行t命令,依赖CPU陷阱机制,对于X86 CPU是设置标志寄存器T标志
/*----------------------------------------------------------------------------------------------------------------------------------
1.6.单步原理
1.WinDBG发出t命令后
2.线程解析出次命令(ParseStepTrace),然后调用SetExecStepTrace和SetNextStepTraceState函数
3.这两函数将用户命令转化并设置内部对象,变量和线程的上下文结构中
4.设置完毕后,调试引擎报告此命令执行完毕,
5.函数层层返回,直到ProcessEngineCommands返回到EngineLoop
6.EngineLoop继续执行,线调用DebugClient::WaitForEvent等待下一调试事件
7.等待前先恢复目标执行时(ResumeExecution)
8.调试引擎将线程上下文通过SetThreadContext API设置给系统
----------------------------------------------------------------------------------------------------------------------------------*/
执行过程:
00 kernel32!SetThreadContext //系统API
01 dbgeng!LiveUserDebugServices::SetContext
02 dbgeng!LiveUserTargetInfo::SetTargetContext
03 dbgeng!TargetInfo::SetContext
04 dbgeng!MachineInfo::UdSetContext
05 dbgeng!MachineInfo::SetContext
06 dbgeng!TargetInfo::ChangeRegContext
07 dbgeng!TargetInfo::PrepareForExecution
08 dbgeng!PrepareOrFlusPerExecution
09 dbgeng!ResumeExecution
0a dbgeng!PrepareForExecution
0b dbgeng!PrepareForWait
0c dbgeng!RawWaitForEvent
0d dbgeng!DebugClient::WaitForEvent
0e WinDBG!EngineLoop
0f kernel32!BaseThreadStart
/*----------------------------------------------------------------------------------------------------------------------------------
1.WinDBG发出t命令
传递给SetThreadContext函数的参数是一个CONTEXT结构,
0:004>dt _CONTEXT 01473e00
+0x0c0 EFlags :0x302
Binary: 00000000 00000000 00000011 00000010
1.位1是保留位永远为1
2.位8是跟踪标志位TF位,为1代表单步执行(Trap Flag)
3.位9是中断标志位IF位,为1是启用中断(Interrupt Enable Flag)
---------------------------------------------------------------------------------------------------------------------------------
2.WinDBG发出p命令
线程上下文中EFlags值为:
+0xc0 EFlags :0x246
Binary: 00000000 00000000 00000010 00000010
3.观察调试引擎的ProcessDebugEvent函数,收到的调试事件
3.1.执行p命令,所触发的异常代码是80000003就是断点异常
3.2.执行t命令,所触发的异常代码是80000004就是单步异常
4.事实上针对CALL指令的单步步过,是通过在CALL的下一条指令设置一个软件断点实现的
5.源代码级的单步是多次设置陷阱标志,就是多次执行汇编一级的单步实现的
----------------------------------------------------------------------------------------------------------------------------------*/
p和t命令语法:
p | t [r] [=StartAddress] [Count]["Command"]
1.r开关,禁止自动显示寄存器内容
2.=,指定一个新的起始地址,不可以跳过调整栈的代码
3.Count设定单步执行的次数
4.["Command"]设置,每次单步执行后要执行的命令,比如:p "kb" //单步习性后自动执行kb命令
/*----------------------------------------------------------------------------------------------------------------------------------
30.11.2 单步执行到指定地址
----------------------------------------------------------------------------------------------------------------------------------*/
pa命令(Step to Address)和ta命令格式:
pa | ta [r] [=StartAddress] StopAdress
1.单步执行到StopAddress参数代表的指令处,会显示程序执行的每一步.
2.利用伪寄存器$ra(return adress 总是代表当前函数的返回地址),可以单步执行返回到上一级函数.
3.如果到达底目标地址前遇到断点,报告断点,命令从此中断.
4.如果到达目标地址发生异常,命令也可能被中断.
/*----------------------------------------------------------------------------------------------------------------------------------
30.11.3 单步执行到下一个函数调用
pc和tc命令执行到下一个CALL函数调用指令,格式:
pc | tc [r] [=StartAddress] [Count]
1.从当前地址或StartAdress地址指定的地址恢复执行,遇到函数CALL执行停下来,
2.Count参数指定遇到的CALL指令个数,默认为1
/*----------------------------------------------------------------------------------------------------------------------------------
30.11.4
1.tb指令,设置标志寄存器和MSR寄存器,恢复运行,当CPU执行到分支指令时,报告异常停下来:
2.tb指令语法:
tb [r] [=StartAddress] [Count]
3.对于安腾系统和x64系统,tb命令可用在内核和用户态,x86平台只能用在内核态
4.ph和th命令,可以用在x86内核的用户态,语法格式:
ph ! th [r] [=StartAddress] [Count]
----------------------------------------------------------------------------------------------------------------------------------*/
30.11.5 追踪并监视wt函数
1.wt命令分析函数执行的路径和调用的函数,以及每个函数包含多少指令,并且将跟踪分析结果生成一份清单打印出来
2.比如在函数的入口处执行,否则相当于执行了一次p命令
3.清单词汇: Invocations 调用次数,MinInst 最少指令书,MaxInst最多指令书,AvgInst平均指令数
4.wt命令追踪复杂函数或位于顶层的函数,可以使用命令选项限制追踪范围
5.-l选项:指定追踪深度
6.-m开关,指定追踪模块
7.-i开关,忽略指定模块
8.追踪的过程中有断点或者其他调试事件,wt命令会被中断而停止.
/*----------------------------------------------------------------------------------------------------------------------------------
30.11.6 程序指针飞跃
1.绕过某函数调用,或跳过导致异常的指令
2.如果跨跃代码包含栈操作,会导致栈不平衡
3.程序指针飞跃的实现
3.1. g系列命令包含起始地址
3.2. r命令直接修改程序指针寄存器
----------------------------------------------------------------------------------------------------------------------------------*/
30.11.7归纳
tb在x86系统只能用于内核态,其他命令可以用于用户态和内核态,单步能是转存文件.
.suspend_ui命令暂时停止刷新信息窗口
/*----------------------------------------------------------------------------------------------------------------------------------
30.12.1 软件断点
指定位置指令置换为INT 3
----------------------------------------------------------------------------------------------------------------------------------*/
bp [ID] [Options] [Address [Passes]] ["CommandString"]
1.ID指定编号,不指定自动选择
2.Optinos
3.Address指定断点地址,不指定使用当前ip指针地址
4.Passes指定断点穿越次数,默认为1,每次命中计数递减1,为0时命中
5."CommandString"指定命中断点时,自动执行的指令
6.用双引号包围,多条指令 ; 分号分隔
7.比如:bp MSVCR80D!printf+3 2 "kv;da poi(ebp+8)" //在printf函数入口偏移3地址设置断点,穿越第二次命中,自动执行kv和da poi(ebp+8)
8.kv命令显示函数调用序列
9.da poi(ebp+8)显示printf第一个参数指定的字符串
10.加偏移3,要等入口处的栈帧建立代码执行好后,ebp+8才能只想第一个参数
/*----------------------------------------------------------------------------------------------------------------------------------
bu命令,设置延迟的以后落实的断点,对尚未加载模块中的代码设置断点
1.指定模块加载时,落实断点
2.调试动态加载的入口函数或初始化代码特别有用
3.调试即插即用设备的驱动程序时,由操作系统的I/O管理器动态加载,发现它加载时,入口函数(DriverEntry)和初始化代码已经执行完了
4.bu MyDriver!DriverEntry
----------------------------------------------------------------------------------------------------------------------------------*/
bm命令,设置一批断点
1.bm msvcr80d!print* //给msvcr80d模块中所有 print开头的函数设置断点
2.数据区设置软件断点,会导致数据意外变化
3.bm命令判断符号类型,只对函数型符号设置断点
4.要求目标模块的调试符号有类型信息,通常需要私有符号文件
5.对公共符号文件模块使用bm命令,会提示一下错误信息:
0:00>bm ntdll!DbgPrint*
No matching code symbols found,no breakpoints set.
If you are using public symbols,switch to full or export symbols.
6.bm命令使用/a开关,强制对所以匹配符号设置断点,无论符号是数据还是代码.
7.建议确信所有符号都是函数时使用
8.更可靠的是使用完全的符号或DLL输出符号
/*----------------------------------------------------------------------------------------------------------------------------------
bu [ID] [Options] [Address [Passes]] ["CommandString"]
bm [Options] SymbolPattern [Passes] ["CommandString"]
Options选项:
1. /1 命中一次就删除,又称一次命中断点
2. /p EPROCESS结构地址 指定进程时命中
3. /t ETHREAD结构地址 指定线程时命中
4. /c和/C 数字 指定调用最大和最小调用深度
5.比如:bp /c5 msvcr80d!printf //函数调用深度浅于5时命中断点
----------------------------------------------------------------------------------------------------------------------------------*/
30.12.2 硬件断点
通过硬件寄存器设置断点,可以监视数据访问和I/O访问
/*----------------------------------------------------------------------------------------------------------------------------------
ba命令设置硬件断点格式:
ba [ID] Access Size [Options] [Address [Passes]] ["CommandString"]
Access指定触发断点的访问方式:
1. e 读取和执行指令时触发,硬件访问断点
2. r 读取和写入数据时,触发断点,数据访问断点
3. w 指定地址写数据时触发断点,数据访问断点
4. i 指定地址执行输入输出访问(I/O)时触发断点,对于x86架构,IN和OUT 指令用于读写IO端口,又称I/O断点
Size指定访问长度
1.对于代码硬件断点,值为1
2.其他硬件断点,长度值随平台不同而不同
2.1. x86系统,值可以为1,2,4,指定地址的1字节访问,字访问和双字访问
2.2. x64系统,值可以为1,2,4,8(四字访问)
2.3 安腾系统,值可以为1到0x80000000间的任何2的次方值
3.CPU根据实际访问是否包含断点定义区域判断是否命中
4.实际访问长度大于断点定义访问长度,断点也命中
5.ba r1 0041717c //内存地址004171c的1字节访问,字访问,双字访问(读写)都会触发这个断点
Address参数
1. 指定断点的地址,要和Size参数的值做内存对齐.比如:Size是4,地址值应该是4的整数倍
硬件断点设置在CPU调试寄存器中的,x86 CPU中的DR0~DR7
1.dr0断点地址值,dr6断点状态寄存器,dr7断点控制寄存器,
2.硬件断点依靠CPU的调试寄存器,所以数量有限
3.WinDBG恢复目标执行才落实断点,所以ba命令设置硬件断点超过限制,不会报错,恢复目标执行报错:
0:00>g
Too many data breakpoints for thread 0
bp8 at 00417180 failed
WaitForEvent failed //数据断点数量太多,WaitForEvent调用失败,WinDBG返回命令模式
4.初始断点命中时,尚不能设置硬件断点:
0:000>ba rl kernel32!BaseCurrentTopLevelFilter
^Unable to set breakpoint error
The system resets thread contexts after the process breakpoint so hardware
breakpoints cannot be set .Go to the executable`s entry point and set it then.
提示信息:初始断点之后,系统重新设置线程上下文,建议执行到程序入口后再设置.
----------------------------------------------------------------------------------------------------------------------------------*/
30.12.3 条件断点
/*----------------------------------------------------------------------------------------------------------------------------------
bp | bu | bm | ba Address "j (Condition) 'OptionalCommands';'gc'"
bp | bu | bm | ba Address ".if (Condition){OptionanlCommands} .else {gc}"
实例:
bp dbgee!wmain "j (poi(argc)>1) 'dd argc l1;du poi(poi(argv)+4)';'gc'"
1.对dbgee程序wmain函数设置条件断点,当命令行参数个数argc大于1时,中断给用户,
2.中断时执行两条命令
3.dd argc l1,显示argc参数的值
4.du poi(poi(argv)+4),显示第一条命令行参数(即argv[1],argv[0]是程序文件)的字符串内容
5.WinDBG在dbgee!wmain函数入口设置软件断点,CPU执行到这个位置时,触发断点事件,并报告调试器
6.调试器收到断点事件,在内部维护的断点队列中找到断点,执行断点关联的命令
7.WinDBG执行j命令,判断小括号中的条件,不满足条件,执行分号后的gc命令
8.如果满足条件,执行单引号中的命令
使用.if命令的写法:
bp dbgee!wmain ".if(poi(argc)>1){dd argc l1;du poi(poi(argv+4))} .else {gc}"
MASM表达式支持的特殊运算符,(Pointer-sized data)指针长度的数据,argc代表一个地址
1. poi从指定地址取指针长度的数据
2.by从指定地址取一字节(Byte)的数据
3.wo从指定地址取一个字(Word)的数据
4.dwo从指定地址取(Double-Word)的数据
5.qwo从指定地址取四字(Quad-Word)的数据
6.更多MASM表达式的内容,参考WinDBG帮助文件中MASM Numbers and Operators介绍
实例针对函数IoGetDeviceProperty的参数设置条件断点:
IoGetDeviceProperty函数原型:
NTSTATUS IoGetDeviceProperty{ IN PDEVICE_OBJECT DeviceObject,
IN DEVICE_REGISTRY_PROPERTY DeviceProperty,
IN ULONG BufferLength,
OUT PVOID PropertyBuffer,
OUT PULONG ResultLength };
设置断定指令:
0: kd>bp nt!IoGetDeviceProperty+0x5 ".if poi(@ebp+0xc)=0xe {} .else {.echo Entered IoGetDeviceProperty with ;
dd {@ebp+0xc} l1; gc }"
1. bp nt!IoGetDeviceProperty+0x5让建立栈帧的代码执行好后再中断
2. poi(@ebp+0xc)代表第二个参数的取值
3. 恢复运行后,函数调动单第二个参数不等于14时,WinDBG执行.else块中的命令打印函数每次被调用信息:
Entered IoGetDeviceProperty with
f7ca1a00 00000007
----------------------------------------------------------------------------------------------------------------------------------*/
30.12.4 地址表达方法
1.模块名加函数符号方式,比如:bp dbgee!wmain,比如:bp dbgee!wmain+3
2.直接用内存地址,比如:bp 00411390
3.使用完全调试符号,调试符号包含源代码行信息,可使用此形式: `[[Module!]Filename][:LineNumber]`
3.1. Moudule模块名,Filename源程序文件名,LineNumber行号
3.2. 用重音符号包围( ` )而不是单引号( ' )
比如:
bp `dbgee!dbgee.cpp:16` //对dbgee.cpp的16行设置断点,dbgee!可以省略
4.对于C++的类方法,使用类名双冒号(::)或双下画线(__)来连接类名和方法名
比如:
bp MyClass__MyMethod
bp MyClass::MyMethod
bp @@(MyClass::MyMethod)
5.前两种方法设置软件断点,确保断点地址指向指令的起始处,而不是指令中间,否则,落实断点时,调试器把指令的中间字节替换为断点指令
6.CPU执行到这个位置时,认为这里是一条多字节指令,把原来指令和断点指令放在一起解码,导致难以预知的结果
/*----------------------------------------------------------------------------------------------------------------------------------
30.12.5 设置针对线程的断点
1.用户态调试,~加线程号,比如:~0 bp MSVCR80D!printf //0号线程执行到这个函数时才中断用户
2.内核态调试,/p指定进程上下文,/t选项指定线程上下文
----------------------------------------------------------------------------------------------------------------------------------*/
30.12.6 管理断点
bl命令列出所有已经设置的断点
1. 1列断点序号
2. 2列断点状态,e启用(enable),d暂时禁止使用(disable),eu,du是bu命令设置的尚未落实的断点(unresolved)
3. 3列断点地址,跟设置断点时表示方法一样,源文件断点加行号,内存断点,数据断点加访问方式(r)和访问长度(l)
4. 4列断点穿越次数(设置断点时pass参数)
5. 5列断点穿越计数(设置断点时Passes参数)
6. 6列断点关联的进程线程号,冒号前进程号,冒号后线程号,****代表针对所有线程
7. 7列断点地址的符号表示
8. 7列以后,断点关联的命令
/*----------------------------------------------------------------------------------------------------------------------------------
bc | bd | be 断点号 删除,禁止,启用断点,
1.使用*通配所有断点
2.-表示一个范围
3.逗号 , 指定多个断点
比如:
bd 0-2,4 //禁止,0,1,2,和4号断点
be * //启用所有断点
br 断点号 断点号 改变断点的编号
比如:
br 4 3 //将4号断点的编号改为3号
----------------------------------------------------------------------------------------------------------------------------------*/
30.13 控制进程和线程
/*----------------------------------------------------------------------------------------------------------------------------------
1.每个线程系统都维护一个关于它的挂起计数,挂起技术大于0,线程将被挂起,挂机技术等于0,线程恢复运行,挂起技术等于-1,计数不会再减少
2.SuspendThread API函数,增加线程的挂起技术,ResumeThread减少线程的挂起计数
3.DebugBreak API函数,当程序处于被调试状态时,中断到调试器汇总,相反会发生异常而被系统关闭
----------------------------------------------------------------------------------------------------------------------------------*/
30.13.2 控制线程执行
1.通常被调试程序中断时,所有线程被挂起,恢复时,所有线程恢复运行
2.但是可以根据需要,实现控制任意线程的运行和挂起状态
3.调试进程中断时,每个线程的挂起计数都是1
/*----------------------------------------------------------------------------------------------------------------------------------
一.使用命令实现:
1. ~命令:列出所有线程
2.ID:后面是进程ID和贤臣ID,后面是挂起计数,线程环境模块(Teb)地址,线程冻结状态
3. ~Thread n 命令增加线程挂起计数,~m命令,减少线程计数,比如:
0:000>~1 n
0:000>~1
1 Id: 1440.2c8 Suspend:2 Teb :7ffdd000 Unforzen //此线程挂起技术为2了
4.此时输入 g命令恢复运行,进程只有另一个线程恢复运行
5.~n命令相当于调用一次SuspendThread函数,~m命令就是调用ResumeThread函数
----------------------------------------------------------------------------------------------------------------------------------*/
~f命令,冻结(Freeze)一个线程,~u命令,解冻(Unfreeze)一个线程
比如:
0:000>~1 f
1 Id:1440.2c8 Suspend: 1 Teb: 7ffdd000 forzen //此线程被冻结了
0:000>g
System 0:1 of 3 Threads are frozen //提示1号线程没有恢复执行
1.~f和~u命令,是调试器内部维护的一个线程属性,~f和~u命令,调试引擎调用ThreadInfo类的ChangeFreeze方法,修改线程属性信息.
2. 通常恢复程序执行时,调试器对所有线程调用ResumeThread API函数,但不会对冻结状态的线程调用
3. 但如果恢复运行时,线程挂起计数为0,即使处于被冻结状态,线程依然会恢复运行
/*----------------------------------------------------------------------------------------------------------------------------------
在g命令前加入~线程ID只恢复指定进程,比如:~0 g
1.只恢复一个线程运行时,默认使用 Ctrl+Break(或者 菜单Debug>Break)来俘获调试目标时,默认使用的中断线程方法会失败,
2.超时候WinDBG会使用挂起方法俘获调试目标
3.因为用于俘获调试目标而创建的远程线程,一创建也被冻结了
----------------------------------------------------------------------------------------------------------------------------------*/
在单步执行命令前加上线程限定符,比如:~0 t
/*----------------------------------------------------------------------------------------------------------------------------------
30.13.3 多进程调试
在一个调试会话中,执行:
0:003> .attach 0n2788
Attach will occur on next execution
1.提示:调试器已经做了必要的登记,单真正的附加动作,需要等下一次恢复调试目标时发生
2.此时,调试目标已经被挂起了
3.执行任意一个恢复目标执行的命令,比如: g,WinDBG会提示:
*** wait pending attach
4.WinDBG很快就会进入命令模式
----------------------------------------------------------------------------------------------------------------------------------*/
|<进程号> s 命令切换当前进程
~<线程号> s 命令切换当前线程
如果省略进程号和线程号,可以观察当进程和线程
/*----------------------------------------------------------------------------------------------------------------------------------
30.14 观察栈
----------------------------------------------------------------------------------------------------------------------------------*/
30.14.1 显示栈回溯 Stack Backtrace
1. k 命令,显示栈回溯
2. k L 命令,显示栈回溯并关闭源文件信息
3. kb 命令,显示栈上的前三个参数
4. kb L 显示栈上前三个参数,并关闭源文件信息
5. kp 命令根据符号文件中函数原型信息显示栈回溯
6. kP 命令,P大写,每个参数占一行,根据符号文件函数原型,显示栈回溯信息
7. kv 命令,增加FPO(栈指针省略)信息和条用协议,显示栈上的前三个参数
8.kn 命令,显示栈回溯,并显示栈帧信息
9. k系列命令,f卡关 显示栈回溯,并增加显示栈帧内存差值
/*----------------------------------------------------------------------------------------------------------------------------------
30.14.2 观察栈变量
dv 命令,观察栈变量
/i 开关,观察栈变量,并显示符号类型信息
/t 开关,增加参数声明类型信息
/v 开关,增加显示参数在内存中起始地址
1. prv (private) 利用私有符号产生的信息
2. param 函数参数
3. local 局部变量
----------------------------------------------------------------------------------------------------------------------------------*/
.frame命令 栈中函数栈帧号 切换函数上下文到指定函数,然后再使用dv命令观察指定函数的参数
!for_each_local 扩展命令 枚举当前栈帧所有局部变量.并对每个变量执行一系列命令
!for_each_local dt @#Local 显示每个局部变量类型和取值
!for_each_frame dv 遍历当前线程的所有栈帧,并显示出每个栈帧所有局部变量
VC7开始的编译器,局部变量至少是从-0xC开始,EBP-4是安全Cookie,EBP-8是安全Cookie的屏障字段(0xCCCCCCCC),32位系统,栈空间是4字节为单位的
----------------------------------------------------------------------------------------------------------------------------------*/
30.15 分析内存
/*----------------------------------------------------------------------------------------------------------------------------------
30.15.1 显示内存区域
d {a|b|d|D|f|p|q|u|w|W} [Options] [Range]
dy {b|d} [Options][Range]
a表示ASCII码,b表示字节和ASCII码,c表示DWORD和ASCII码,d表示DWORD,D表示双精度浮点数,f表示单精度浮点数,p表示指针宽度显示,q表示四字(8字节)
u表示UNICODE字符,w表示字,W表示字和ASCII码,yb表示二进制和字节,yd表示二进制和双字
Range参数指定显示范围,三种显示方法:
dd 0012fd9c 0012fda8 //以双字为单位,显示从0012fd9c到0012fda8结束的16字节内存数据
dd 0012fd9c L4 //以双字为单位,显示从0012fd9c开始的四个数据
dd 0012fdac L-4 //以双字为单位,显示0012fdac前四个数据
db 0012fd9c l4 //以字节为单位,显示4个数据
----------------------------------------------------------------------------------------------------------------------------------*/
30.15.2 显示字符串
da 命令,显示单字节字符集的字符串
du 命令,显示UNICODE字符集的字符串
dS 命令,(S大写)显示(UNICODE_STRING结构)
ds 命令,以(STRING结构)显示字符串
!str 扩展命令,以(STRING结构)显示字符串
/*----------------------------------------------------------------------------------------------------------------------------------
30.15.3 显示数据类型
dt 命令(Dump symbolic Type information),显示数据类型和按指定数据类型显示内存中的数据
dt [模块名!]类型名 显示一个数据结构,省略模块名,调试器自动搜索所有符号文件
dt [模块名!]类型名 内存地址 按照数据类型显示指定地址的变量
dt [模块名!]实例名 显示数据类型的实例,包括全局变量,静态变量和函数
dt ntdll!* 列出ntdll模块中所有类型
---------------------------------------------------------------------------------------------------------------------------------
-b开关,递归显示所有子类型
-r开关指定显示深度
-r0开关,不显示子类型
-r1显示1级子类型
-ny开关,附加搜索选项
---------------------------------------------------------------------------------------------------------------------------------
dt -r1 -TEB
dt _TEB -ny lastError
dt _PEB 7ffdd000 //把内存地址处的数据按照_PEB结构显示出来
dt dbgee!g_szGlobal //显示dbgee程序的全局变量g_szGlobal
dt dbgee!*wmain* //枚举dbgee程序中所有包含wmain的符号
dt dbgee!wmain //函数名是确定的,则显示它的参数和取值
----------------------------------------------------------------------------------------------------------------------------------*/
30.15.4 搜索内存
------------------------------------------------------------------------------------------------------------
s -[[Flags]] sa | su Range
Range指定内存范围,sa开关搜索ASCII字符串,su搜索UNICODE字符串,
[Flags] 指定搜索选项
1.使用字母l加整数指定字符串的最小长度
2.使用字符s将搜索结果保存起来,再使用r在保存结果中搜索
实例:
lkd>s -[l5] sa poi(nt!PsInitialSystemProcess) 1200
搜索nt!PsInitialSystemProcess变量所指向地址开始的512字节内任何长度不小于5的ASCII码字符串
------------------------------------------------------------------------------------------------------------
s -[[Flags]] v Range Object
在指定地址范围内搜索与指定对象类型相同的对象,(虚拟函数表使用C++编写的Class类对象)
实例:
0:000>s -v 0x12fc30 11000 0x12fe4c
在MfcHello程序中,CMfcHelloDlg类定义了五个CButton类的对象m_Buttion1~m_Button5,m_Buttion1对象地址是0x12fe4c
CMfcHelloDlg类对象地址是0x12fc30,以上命令搜索同m_Buttion1类型相同的其它对象
/*----------------------------------------------------------------------------------------------------------------------------------
s [-[[Flags]]Type] Range Pattern 在指定范围内搜索某一内容模式
Type指定搜索内容的数据类型(宽度),b(字节),w(字),d(双字),q(四字),a(ASCII字符串),u(Unicode字符串),不指定类型,默认使用b按字节搜索指定内容.
Range指定搜索范围,Pattern指定搜索内容,多个数值,用空格分隔.
比如:
s -w 0x400000 12a000 41 64 76 44 62 67
s -w 0x400000 'A' 'd' 'v' 'D' 'd' 'g'
s -u 0x400000 12a000 "AdvDbg"
s -d 12fe4c 120 782e35fc
----------------------------------------------------------------------------------------------------------------------------------*/
借助!for_each_moudule扩展命令,在当前进程所有模块中搜索
0:000>!for_each_module s -a @#Base @#End "Debugger"
!for_each_module定义的别名
@#Base
@#End
@#ModuleName 模块名称
@#SymbolFileName 符号文件名称
@#Size 模块大小
@#SymbolType 符号文件类型
/*----------------------------------------------------------------------------------------------------------------------------------
30.15.5 修改内存
e 命令修改指定内存地址或区域的内容
一.按字符串方式编辑指定地址的内容:
e {a | u | za | zu} Address "String"
Address 要修改的内存地址,za以0结尾ASCII码字符串,zu以0结尾UNICODE字符串,a不是以0结尾ASCII码字符串,u不是以0结尾UNICODE字符串
实例:
s -u 0x0012fa20 1200 "AdvDbg"
ezu 12fc94 "DbgAdv" //12fc94上条指令搜索到的地址
----------------------------------------------------------------------------------------------------------------------------------*/
二.按数值方式编辑指定地址的内容:
e { b | d| D | f | p | q | w } Address [Values]
1.大括号内指定要修改数据的类型和内存修改方式,Address要修改的起始地址,Values指定新的值.Values参数多少决定要修改内存的长度
比如: ew 12fc94 41 41 41 41 41
2.1. 如果命令中没有指定Values参数,WinDBG以交互的方式让用户输入,命令提示符变为:"Input>",WinDBG显示要编辑的内存地址和当前取值
2.2. 取新值,输入加回车,保留当前值空格加回车
/*----------------------------------------------------------------------------------------------------------------------------------
30.15.6 使用物理内存地址 只适用于内核态调试
!d {b|c|d|p|q|u|w}
!e {b|d}
----------------------------------------------------------------------------------------------------------------------------------*/
30.15.7 观察内存属性
!address [Address] 显示指定内存地址的特征信息
!address 定参数,观察进程所有内存区域
内存类型:
MEM_IMAGE 从执行映像文件映射的内存
MEM_MAPPED 从其他文件映射的内存
MEM_PRIVATE 私有内存(不是从文件映射的,也不是同其他进程共享的)
内存状态:
MEM_COMMIT 提交的内存
MEM_RESERVE 保留的内存
MEM_FREE 释放的内存
内存的用途:
/*----------------------------------------------------------------------------------------------------------------------------------
!vprot [Address] 命令,显示一个内存地址的属性
!vadump 当前进程所有虚拟地址属性,无统计报表
!pte [Address] 显示镇定虚拟地址所属的页表表项(PTE)和页目录表项(PDE)
页属性中:G代表全局(Global),D代表数据,A代表访问过(Accessed),K代表内核态拥有的内存页,
W代表写,E代表执行,V代表Valid(对应的内存也已经在物理内存中)
----------------------------------------------------------------------------------------------------------------------------------*/
遍历链表
1.windows主要使用两种链表,双向链表,单向链表
2.链表的每个节点由两部分组成
3.一部分是起链接作用的List_entry结构或SINGLE_LIST_ENTRY结构,统称链表结构
4.另一部分是负载,也就是链表管理的内容
链表结构的链接结构:
typedef struct _LIST_ENTRY { //用于双向链表的链接结构
struct _LIST_ENTRY *Flink;//指向前向节点
struct _LIST_ENTRY *Blink;//指向后向节点
}LIST_ENTRY,*PLIST_ENTRY,*RESTRICTED_POINTER PRLIST_ENTRY;
typedef struct _SINGLE_LIST_ENTRY {//用于单向链表的链接结构
struct _SINGLE_LIST_ENTRY *Next;//指向下一个节点
}SINGLE_LIST_ENTRY,*PSINGLE_LIST_ENTRY;
5.windows内核使用EPROCESS结构来记录每个windows进程,用双向链表串联每个进程的EPROCESS,
全局变量PsActiveProcessHead记录着这个链表起始地址.
6.EPROCESS结构中的ActiveProcessLinks字段就是起链接作用的LIST_ENTRY结构
7.PsInitialSystemProcess记录了系统进程的EPROCESS结构的地址
----------------------------------------------------------------------------------------------------------------------------------*/
0:000> dt _EPROCESS -y ActiveProcess
ntdll!_EPROCESS
+0x088 ActiveProcessLinks : _LIST_ENTRY
0:000> dt nt!_EPROCESS -l ActiveProcessLinks.Flink -y Ima -yoi Uni
ntdll!_EPROCESS
UniqueProcessId : Ptr32 Void
ActiveProcessLinks :
ImageFileName : [16] UChar
----------------------------------------------------------------------------------------------------------------------------------*/
30.16.3 单向链表示例
0:000> dt -r2 _TEB //遍历TEB结构中异常处理器登记链表
----------------------------------------------------------------------------------------------------------------------------------*/
30.16.4 dl命令
使用dl命令遍历链表:
dl [b] Address MaxCount Size
1.Address链表的起始地址,指向List_entry结构或SINGLE_LIST结构
2.MaxCount用来指定要先是的最多字节书,
3.Size指定希望显示的结构长度
比如"
dl nt!PsActiveProcessHead 1000 //可以使用@@c++(sizeof(_EPROCESS)/sizeof(int *))来指定EPROCESS结构的长度
----------------------------------------------------------------------------------------------------------------------------------*/
30.16.5 !list命令
!list -t [Module!]Type.Field -x "Commands" [-a "Arguments"] [Options] StartAddress
!list " -t [Module!]Type.Field -x \"Commands\" [-a \"Arguments\"] [Options] StartAddress "
!list -h
1.-t开关指定的数据类型和它的链接字段
2.-x指定每个节点所执行的命令
3.-e或者-m指定执行选项,-e含义是显示(echo),-m加一个数字用来限制最多显示的字节数
----------------------------------------------------------------------------------------------------------------------------------*/
30.17 调用目标程序的函数
~. g命令 对于多线程程序,只恢复当前线程
.call 调用用户态活动调试目标的函数,
1.不能在内核态和调试转储文件中使用
2.被调用函数必须有私有符号,包含类型信息的函数符号
.call /C 清除当前线程的函数调用
----------------------------------------------------------------------------------------------------------------------------------*/
30.18 命令程序
加载命令程序:
$>< 读取后面文件,并缩成单一的命令,并执行,换行符替换为分号.
$> 读取后面文件,并一行一行的执行其中的命令
$$><或$$<执行一个命令文件,单允许文件名前有空格,并允许用双引号包围文件名
给命令文件指定参数的格式:
$$>a< Filename arg1 arg2 arg3 ... argn