版本
第一版 2021年02月06日 11:53:06 2021年3月9日22:22:25
windbg下载
x64,http://download.microsoft.com/download/A/6/A/A6AC035D-DA3F-4F0C-ADA4-37C8E5D34E3D/setup/WinSDKDebuggingTools_amd64/dbg_amd64.msi
x86,http://download.microsoft.com/download/A/6/A/A6AC035D-DA3F-4F0C-ADA4-37C8E5D34E3D/setup/WinSDKDebuggingTools/dbg_x86.msi
学习windbg时
使用帮助文档
可使用windbg调试windbg
第30章 WindDBG用法详解
30.0 概述
WinDBG支持多种调试服务
用户态调试
内核态调试
转储文件调试
远程调试
非常好的灵活性和扩展性
30.1 工作空间
workspace
描述和存储,调试项目的属性、参数、调试器设置等
分类
默认的工作空间
基础工作空间(base workspace),调试会话尚未建立
默认的内核态工作空间(default kernel-mode workspace),开始内核调试但未连接调试目标
默认的远程调试工作空间(remote default workspace),通过调试服务器(DbgSrv或KdSrv)进行远程调试时
特定处理器的工作空间(processor-specific workspace),开始内核调试且已连接调试目标,并知道对方处理器类型后
默认的用户态工作空间(default user-mode workspace),附加到进程时
命名的工作空间(显式的工作空间,explicit)
包含的信息
调试会话状态
断点、打开的源文件、自定义别名
调试器设置
符号文件路径,可执行映像文件路径,源文件路径
源文件选项,日志文件设置,内核调试连接设置
打开文件对话框使用的设置
WinDBG图形界面信息
工作空间的保存和打开
使用注册表保存配置
路径,HKEY_CURRENT_USER\Software\Microsoft\WinDBG\Workspace
子键表示类型,User,Kernel,Dump,Explicit
保存到文件
WinDBG,以增量的方式来应用配置
30.2 命令概览
30.2.1 标准命令
基本调试功能
实现在WinDBG内部
分类(按相关对象分类)
调试目标控制
恢复执行,g系列
跟踪执行,t系列,trace into
单步执行,p系列,step over
追踪监视,wt
寄存器
通用寄存器,r
MSR寄存器,rdmsr,wtmsr
设置显示掩码,rm
IO端口
ib,iw,id
ob,ow,od
内存
查看,d
编辑,e
搜索,s
栈,k系列
断点
设置,bp软件断点,ba硬件断点
列举,bl
控制,bc、bd、be,参数、禁用、启用
线程,~,控制和显示
进程,|,显示
表达式评估
?,汇编
??,C++
汇编控制
a,汇编
u,反汇编
段选择子,dg,显示
命令文件,$,执行
选项设置
调试事件处理方式,sx
静默模式的禁用和启用,sq
内核选项,so
符号后缀,ss
版本
version,调试器和调试目标
vertarget,调试目标所在系统
符号,x,检查
源程序,ls,控制和显示
结束调试会话
基本,q
结束远程,qq
结束调试会话,分离调试目标,qd
其他
ld,加载调试符号
ln,搜索相邻符号
lm,列举模块
帮助,?
B[C|D|E][] - clear/disable/enable breakpoint(s)
BL - list breakpoints
BA - set processor breakpoint
BP - set soft breakpoint
D[type][] - dump memory
DT [-n|y] [[mod!]name] [[-n|y]fields]
[address] [-l list] [-a[]|c|i|o|r[#]|v] - dump using type information
DV [] - dump local variables
E[type] [] - enter memory values
G[H|N] [= [...]] - go
K - stacktrace
KP - stacktrace with source arguments
LM[k|l|u|v] - list modules
LN - list nearest symbols
P [=] [] - step over
Q - quit
R [[ [= ]]] - view or set registers
S[] - search memory
SX [{e|d|i|n} [-c "Cmd1"] [-c2 "Cmd2"] [-h] {Exception|Event|*}] - event filter
T [=] [] - trace into
U [] - unassemble
version - show debuggee and debugger version
X [<*|module>!]<*|symbol> - view symbols
? - display expression
?? - display C++ expression
$< - take input from a command file
30.2.2 元命令
meta-command,dot command
常用调试功能
实现在WinDBG内部
分类(按相关对象分类)
调试会话和调试器选项,显示和设置
符号选型,.symopt
符号路径,.sympath和.symfix
源文件,.srcpath,.srcnoise,.srcfix
扩展命令模块路径,.extpath
匹配扩展命令,.extmatch
可执行文件,.exepath
反汇编,.asm
表达式评估器,.expr
调试会话和调试目标,控制
创建新进程,.create
附加旧进程,.attach
打开转储文件,.opendump
分离附加目标,.detach
杀掉进程,.kill
重新开始调试会话,.restart
放弃用户态调试目标(进程),.abandon
扩展命令模块,管理
加载,.load
卸载,.unload,.unloadall
显示,.chain
调试器日志文件,管理
新建打开,.logopen
追加打开,.logappend
打开状态,.logfile
关闭,.logclose
远程调试
远程调试客户端,remote.exe
启动,.remote
远程调试服务器,调试引擎服务器
启动服务器,.server
列举服务器,.servers
向服务器发送文件,.send_file
结束服务器,.endsrv
远程进程服务器
结束服务器,.endpsrv
调试器,控制
睡眠,.sleep
唤醒,.wake
启动新调试器,调试当前调试器,.dbgdbg
命令程序,编写
类似于C语言的关键字
.if, .else, .elif
.foreach, .do, .while, .continue, .break
.catch, .leave, .printf, .block
其他
产生转储文件,.dump
保存内存数据,.writemem
显示调试会话时间,.time
显示线程时间,.ttime
显示任务列表,.tlist
格式化数字,.formats
元命令帮助,.help
. commands:
.abandon - abandon the current process
.allow_exec_cmds [0|1] - control execution commands
.allow_image_mapping [0|1] - control on-demand image file mapping
.apply_dbp [] - add current data breakpoint state to a
register context
.asm [] - set disassembly options
.asm- [] - clear disassembly options
.attach - attach to at next execution
.block { } - brackets a set of commands for nested execution
.bpsync [0|1] - special breakpoint behavior for multithreaded debuggees
.break - break out of the enclosing loop
.breakin - break into KD
.cache [] - virtual memory cache control
.call (, , ...) - run a function in the debuggee
.catch { } - catch failures in commands
.chain - list current extensions
.childdbg <0|1> - turn child process debugging on or off
.clients - list currently active clients
.closehandle [] [] - close the given handle
.continue - continue the enclosing loop
.copysym [] - copy current symbol files to a directory
.create - create a new process
.createdir [] [] - control process creation options
.cxr - dump context record at specified address
k* after this gives cxr stack
.dbgdbg - attach a debugger to the current debugger
.debug_sw_wow [0|1] - allow interaction with software WOW emulation
.detach - detach from the current process/dump
.dml_file - output DML content from file
.dml_flow - show basic block code flow
.dml_start [] - navigable overview of debugger activities
.do { } () - execute until is zero
.drivers - This command was removed -- use 'lm' or .reload -l)
.dump [] - create a dump file on the host system
.dvalloc [] - VirtualAlloc memory in the debuggee
.dvfree [] - VirtualFree memory in the debuggee
.echo [""|] - echo string
.echotime - output debugger time
.echotimestamps [0|1] - toggle timestamp output on events
.ecxr - dump context record for current exception
.effmach [] - change current machine type
.else { } - if/then/else conditional execution
.elsif () { } [] - if/then/else conditional
execution
.enable_long_status [0|1] - dump LONG types in default base
.enable_unicode [0|1] - dump USHORT array/pointers and unicode strings
.endsrv - disable the given engine server
.endpsrv - cause the current session's remote server to exit
.enumtag - enumerate available tagged data
.event_code - display cached event instructions
.eventlog - display log of recent events
.events - display and select available events
.eventstr - display any event strings registered by debuggee
.exepath [[;...]] - set executable search path
.exepath+ [[;...]] - append executable search path
.expr - control expression evaluator
.exptr - do .exr and .cxr for EXCEPTION_POINTERS
.exr - dump exception record at specified address
.extmatch [] - display all extensions matching pattern
.extpath [[;...]] - set extension search path
.extpath+ [[;...]] - append extension search path
.f+ - set current stack frame to caller of current frame
.f- - set current stack frame to callee of current frame
.fiber - sets context of fiber at address
resets context if no address specified
.fiximports - attempts to link imports for images
.fnent - dump function entry for the given code address
.fnret [] - display formatted return value
.for ( ; ; ) { } - execute and
until is
zero
.force_radix_output [0|1] - dump integer types in default base
.force_system_init [] - force pending systems to initialize if possible
.force_tb - forcibly allow branch tracing
.foreach [opts] ( { } ) { } - execute for
each token in the
output of
.fpo - control override FPO information
.frame [] - set current stack frame for locals
.formats - displays expression result in many formats
.help [] - display this help
.holdmem [range] - hold and compare memory data
.if () { } [] - if/then/else conditional
execution
.ignore_missing_pages [0|1] - control kernel summary dump missing
page error message
.imgscan - scan memory for PE images
.jdinfo - interpret AeDebug information
.kframes - set default stack trace depth
.kill - kill the current process
.lastevent - display the last event that occurred
.leave - exit the enclosing .catch
.lines - toggle line symbol loading
.load - add this extension DLL to the extension chain
.loadby - add the extension DLL in the module
directory to the extension chain
.locale [] - set the current locale
.logfile - display log status
.logopen [] - open new log file
.logappend [] - append to log file
.logclose - close log file
.netsyms [0|1] - allow/disallow net symbol paths
.netuse [] - manage net connections
.noshell - disable shell commands
.noversion - disable extension version checking
.ofilter - filter debuggee output against the given pattern
.ocommand - treat output with the given prefix as a command
.opendump - open a dump file
.outmask - set bits in the current output mask
.outmask- - clear bits in the current output mask
.pcmd [] - control per-prompt command
.pop [] - pop state
.prefer_dml [0|1] - control DML mode default
.printf "", - formatted output
.process [] - sets implicit process
resets default if no address specified
.process_info - display security related information of current process
.prompt_allow [] - control what information can be displayed
at the prompt
.push [] - push state
.quit_lock [] - locks session against unexpected quit
.readmem - read raw memory from a file
.record_branches [0|1] - controls recording of processor branching
.reload [[=,]] - reload symbols
.restart - request a session restart
.remote - start remote.exe server
.secure [0|1] - disallow operations dangerous for the host
.send_file - send files to remote server
.server - start engine server
.servers - list active remoting servers
.setdll - debugger will search for extensions in this DLL first
.shell [] - execute shell command
.show_read_failures [] - control extra read failure output
.show_sym_failures [] - control extra symbol failure output
.sleep - debugger sleeps for given duration
useful for allowing access to a machine that's
broken in on an ntsd -d
.srcfix [] - fix source search path
.srcfix+ [] - append fixed source search path
.srcnoisy [0|1] - control verbose source loading output
.srcpath [[;...]] - set source search path
.srcpath+ [[;...]] - append source search path
.step_filter [] ["[;...]"] - Set symbol patterns
to skip when stepping
.symfix [] - fix symbol search path
.symfix+ [] - append fixed symbol search path
.symopt - set symbol options
.symopt+ - set symbol options
.symopt- - clear symbol options
.sympath [[;...]] - set symbol search path
.sympath+ [[;...]] - append symbol search path
.thread [] - sets context of thread at address
resets default context if no address specified
.time - displays session time information
.timezone - display timezone information
.ttime - displays thread time information
.tlist - list running processes
.typeopt - set/clear type options
.unload - remove this extension DLL from the list of extension DLLs
.unloadall - remove all extension DLLs from the list of extensions DLLs
.wake - wake up a .sleep'ing debugger
.while () { } - execute while is non-zero
.writemem - write raw memory to a file
Use ".hh " or open debugger.chm in the debuggers directory to get
detailed documentation on a command.
30.2.3 扩展命令
extension command
语法,![extension_module].extension_command [parameters]
省略extension_module,则自动搜索
特殊调试功能,针对特殊调试目标
实现在动态链接库(DLL)中
利用WinDBG的SDK,编写扩展模块和扩展命令
WinDBG程序包中,包含了常用的扩展命令模块
NT4CHK目录,调试目标为Windows NT 4.0 Checked版本时的扩展命令模块
NT4FRE目录,调试目标为Windows NT 4.0 Free版本时的扩展命令模块
W2KCHK目录,调试目标为Windows 2000 Checked版本时的扩展命令模块
W2KFRE目录,调试目标为Windows 2000 Free版本时的扩展命令模块
WINXP目录,调试目标为Windows XP或者更高版本时的扩展命令模块
acpikd.dll,用于ACPI调试,追踪调用ASL程序的过程,显示ACPI对象
exts.dll,关于堆(!heap),进程/线程结构(!teb/!peb),安全信息(!token,!sid,!acl),应用程序验证(!avrf)等
kdexts.dll,用于调试内核
fltkd.dll,用于调试过滤驱动程序(FsFilter)
minipkd.dll,用于调试AIC78xx小端口(miniport)驱动程序
ndiskd.dll,用于调试网络有关驱动程序
ntsdexts.dll,实现了!handle,!locks,!dp,!dreg
rpcexts.dll,用于RPC调试
scsikd.dll,用于调试SCSI有关的驱动程序
traceprt.dll,用于格式化ETW信息
vdmexts.dll,调试运行在VDM中的DOS程序和WOW程序
wow64exts.dll,调试运行在64位Windows中的32位程序
wmitrace.dll,显示WMI追踪有关的数据结构、缓冲区和日志文件
WINEXT目录,适用于所有Windows版本的扩展命令模块
ext.dll,适用于所有调试目标的常用扩展命令
kext.dll,适用于内核态调试的常用扩展命令
uext.dll,适用于用户态调试的常用扩展命令
logexts.dll,用于监视和记录API调用
sos.dll,用于调试托管代码和.Net程序
ks.dll,用于调试内核流(kernel stream)
wdfkd.dll,调试使用WDF(Windows Driver Foundation)编写的驱动程序
扩展模块的加载
自动加载
当调试目标被激活时,WinDBG会根据以下条件自动加载命令空间中指定的扩展模块
调试目标的类型
当前的工作空间
完整语法方式
!extension_module.extension_command
自动搜索和加载
手动加载
.load 模块完整路径
.load 模块名称,会在扩展模块搜索路径(EXTPATH)中寻找
.loadby target_module refer_module
在已加载模块(refer_module)所在目录,搜索和加载target_module
列举已经加载的扩展模块
.chain
扩展模块的卸载
.unload
.unloadall
查询模块信息
!extension_module.help
30.3 用户界面
30.3.1 窗口概述
框架窗口(Frame Window)
菜单和工具栏
工作窗口
Command,命令输入和显示结果
Watch,观察指定变量
Locals,观察局部变量
Registers,观察寄存器
Memory,观察内存
CallStack,观察调用栈
Disassembly,反汇编
ScratchPad,白板,做调试笔记
ProcessesAndThreads,观察进程和线程
CommandBrowser,执行和浏览命令
状态栏
30.3.2 命令窗口
组成
信息显示区
命令横条
命令提示符,命令编辑框
信息显示区
展示的信息包含
命令执行结果
调试事件
错误信息
调试引擎的提示信息
命令横条
未连接调试目标时
命令提示符,空
命令编辑框,Debuggee not connected
等待命令输入时
命令提示符,调试目标描述>
用户态目标
[||system_index:]process_index:thread_index>
内核态目标或内核态转储文件目标
[||system_index:][processor_index:]kd>
本地内核态调试
[||system_index:][processor_index:]lkd>
其中
system_index
同一Windows中多个用户态目标,同属于一个系统
每个内核目标,独属于一个系统
process_index
线程序号,始于0
processor_index
处理器序号,始于0
线程序号和处理器序号,统一编号
每个内核态目标,被分配一个进程序号
切换
系统
||system_index s
进程或处理器
|process_or_processor_index s
自动切换系统
线程
~thread_index s
只能在统一系统内切换
调试目标繁忙
命令提示符,*BUSY*
命令编辑框,Debuggee is running
WinDBG执行命令繁忙
命令提示符,*BUSY*
命令编辑框,空
执行.abandon放弃调试目标
命令提示符,NoTarget>
命令编辑框,空
等待用户输入
命令提示符,Input>
命令编辑框,空
30.4 输入和执行命令
30.4.1 要点
命令分隔符,;
重复命令,回车键
历史命令,方向键
终止命令,Ctrl+Break
使用KD或CDB时,使用Ctrl+C
详细输出模式切换,Ctrl+Alt+V
显示WinDBG与内核调试引擎之间的数据通信,Ctrl+Alt+D
帮助,F1或.hh command
30.4.2 表达式
参考
帮助文档,MASM Numbers and Operators
语言
?宏汇编语法
??C++表达式
运算符
基本,加减乘除、移位、求余、比较、位操作、正负号
特殊
hi或low,取得32位数的高16位或低16位
by或wo或dwo或qwo,从指定地址读数单字节或单字或双字或四字
poi,从指定地址读数指针长度
类函数运算符
$iment(Address),返回参数代表模块的入口地址
$scmp("String1", "String2"),比较字符串
$sicmp("String1", "String2"),比较字符串,忽略大小写
$spat("String", "Pattern"),判断字符串符合模式
$vvalid(Address, Length),判断有效内存区
$fnsucc(FnAddress, RetVal, Flag),判断函数执行成功
注释
可被记到日志中
*之后皆注释
*中间皆注释;
30.4.3 伪寄存器
参考
帮助文档,Pseudo-Register Syntax
WinDGB定义
Pseudo-register Description
$ea The effective address of the last instruction that was executed. If this instruction does not have an effective address, the debugger displays "Bad register error". If this instruction has two effective addresses, the debugger displays the first address.
$ea2 The second effective address of the last instruction that was executed. If this instruction does not have two effective addresses, the debugger displays "Bad register error".
$exp The last expression that was evaluated.
$ra The return address that is currently on the stack.
This address is especially useful in execution commands. For example, g @$ra continues until the return address is found (although gu (Go Up) is a more precise effective way of "stepping out" of the current function).
$ip The instruction pointer register.
x86-based processors: The same as eip.
Itanium-based processors: Related to iip. (For more information, see the note following this table.)
x64-based processors: The same as rip.
$eventip The instruction pointer at the time of the current event. This pointer typically matches $ip, unless you switched threads or manually changed the value of the instruction pointer.
$previp The instruction pointer at the time of the previous event. (Breaking into the debugger counts as an event.)
$relip An instruction pointer that is related to the current event. When you are branch tracing, this pointer is the pointer to the branch source.
$scopeip The instruction pointer for the current local context (also known as the scope).
$exentry The address of the entry point of the first executable of the current process.
$retreg The primary return value register.
x86-based processors: The same as eax.
Itanium-based processors: The same as ret0.
x64-based processors: The same as rax.
$retreg64 The primary return value register, in 64-bit format.
x86 processor: The same as the edx:eax pair.
$csp The current call stack pointer. This pointer is the register that is most representative of call stack depth.
x86-based processors: The same as esp.
Itanium-based processors: The same as bsp.
x64-based processors: The same as rsp.
$p The value that the last d* (Display Memory) command printed.
$proc The address of the current process (that is, the address of the EPROCESS block).
$thread The address of the current thread. In kernel-mode debugging, this address is the address of the ETHREAD block. In user-mode debugging, this address is the address of the thread environment block (TEB).
$peb The address of the process environment block (PEB) of the current process.
$teb The address of the thread environment block (TEB) of the current thread.
$tpid The process ID (PID) for the process that owns the current thread.
$tid The thread ID for the current thread.
$bpNumber The address of the corresponding breakpoint. For example, $bp3 (or $bp03) refers to the breakpoint whose breakpoint ID is 3. Number is always a decimal number. If no breakpoint has an ID of Number, $bpNumber evaluates to zero. For more information about breakpoints, see Using Breakpoints.
$frame The current frame index. This index is the same frame number that the .frame (Set Local Context) command uses.
$dbgtime The current time, according to the computer that the debugger is running on.
$callret The return value of the last function that .call (Call Function) called or that is used in an .fnret /s command. The data type of $callret is the data type of this return value.
$lastclrex Managed debugging only: The address of the last-encountered common language runtime (CLR) exception object.
$ptrsize The size of a pointer. In kernel mode, this size is the pointer size on the target computer.
$pagesize The number of bytes in one page of memory. In kernel mode, this size is the page size on the target computer.
用户定义
用户定义的伪寄存器(user-defined pseudo-register)
名称,$t0~$t19
初始值为0,可保存任意整数值
30.4.4 别名
分类
用户定义别名(user-named alias)
as new_name old_name
固定名称别名(fixed-name alias)
引用,$u<0~9>
修改,r $.u<0~9>=old_name
自动定义别名(automatic alias)
Alias name Alias equivalent
$ntnsym 内核态下为nt,用户态下为ntdll
$ntwsym ntdll32,ntdll
$ntsym 与当前调试目标的机器模型匹配的NT模块名称
$CurrentDumpFile 转储文件名称
$CurrentDumpPath 转出文件路径
$CurrentDumpArchiveFile 最近加载的CAB文件名称
$CurrentDumpArchivePath 最近加载的CAB文件路径
查看别名的取值
.echo
别名引用的形式
宽度,适合固定名称别名
空格,适合所有
分隔符,${alias},适合用户定义别名和自动定义别名
30.4.5 循环和分支
z命令
语法,z(condition)
功能,condition不为0和false,则重新执行命令
例子
r ecx=2
r ecx=ecx-1; r ecx; z(ecx), r ecx=ecx+1
!for_each_XXX命令
!for_each_frame !for_each_local dt @#Local
打印每个栈帧中的每个局部变量
j命令
j expression command1; command2
j expression 'commands1'; 'commands2'
.if.elif.else
.if (expression) {commands1} .elif (expression) {commands2} .else {commands3}
30.4.6 进程和线程限定符
条件 进程 线程
当前 |. ~.
导致当前调试事件 |# ~#
当前系统所有 |* ~*
序号 |Number ~number
ID |~ID ~~ID
30.4.7 记录到文件
.logopen,打开日志文件
.logfile,查看日志文件状态
.logclose,关闭日志文件
30.5 建立调试会话
30.5.1 附加到已有进程
菜单方式
File > Attach a Process
设置为JIT调试器
WinDBG.exe -I
当程序崩溃后,在错误对话框中选择Debug,便会启动WinDBG并附加
命令行方式
WinDBG.exe -p PID
WinDBG.exe -pn name
windbg的命令行选项
帮助文档中搜索,WinDbg Command-Line Options
windbg [ -server ServerTransport | -remote ClientTransport ] [-lsrcpath ]
[ -premote SmartClientTransport ] [-?] [-ee {masm|c++}]
[-clines lines] [-b] [-d] [-aExtension] [-e Event]
[-failinc] [-g] [-G] [-hd] [-j] [-n] [-noshell] [-o]
[-Q | -QY] [-QS | -QSY] [-robp] [-secure] [-ses] [-sdce]
[-sicv] [-sins] [-snc] [-snul] [-sup] [-sflags 0xNumber]
[-T Title] [-v] [-log{o|a} LogFile] [-noinh]
[-i ImagePath] [-y SymbolPath] [-srcpath SourcePath]
[-k [ConnectType] | -kl | -kx ExdiOptions] [-c "command"]
[-pb] [-pd] [-pe] [-pr] [-pt Seconds] [-pv]
[-W Workspace] [-WF Filename] [-WX] [-zp PageFile]
[ -p PID | -pn Name | -psn ServiceName | -z DumpFile | executable ]
windbg -I[S]
windbg -IU KeyString
windbg -IA[S]
.attach
需要已经有一个调试会话
常用于同时调试多个目标时
被.abandon抛弃的被调试进程,可以重新附加
WinDBG.exe -pe -p PID
30.5.2 创建进程并调试
菜单方式
File > Open Executable
命令行方式
windbg executable
.create
需要已经有一个调试会话
常用于同时调试多个目标时
30.5.3 非侵入式调试
调试用户态进程的一种特殊方式
windbg与目标进程,没有真正建立调试与被调试的关系,不能接收到任何调试事件
不能使用控制调试目标执行的命令,如单步、继续等
只能使用行观目标进程的命令
好处
减少调试器对目标进程的干预,最大程度减少海森伯效应
不影响其他调试器附加到目标进程进行调试
方式
只能用于附加方式
菜单方式
复选Noninvasive
命令行方式
WinDBG.exe -pv PID
.attach
加上-v开光
JIT不支持该方式
用途
Windows NT和Windows 2000不支持调试器和调试目标分离(detach)
一旦建立调试关系,结束调试会话将会结束调试进程
若使用非侵入方式附加调试,分析结束时只需执行分离命令,即可恢复
30.5.4 双机内核调试
步骤
1. 选择通信方式
串口方式,兼容性好,可靠性高
1394端口,速度快但不稳定
USB2
2. 启用目标系统的内核调试引擎
Vista之前的系统,修改boot.ini
Vista之后的系统,使用BCDEdit工具修改启动选项
3. 启动调试会话
菜单方式
File > Kernel Debug
命令行方式
windbg -k com:port=Com1, baud=115200
4. 后续
windbg显示"Waiting to reconnect..."并进入等待状态
等待目标系统的调试数据
并按照一定时间间隔(10s)发送复位数据包(PACKET_TYPE_KD_RESET)
目标系统在启动早期,初始化内核调试引擎时,会向调试器发送信息
windbg收到信息后,开始与目标系统对话,并建立调试连接
30.5.5 本地内核调试
系统条件
Windows 2000或更早的系统,不支持
Windows XP没有要求
Windows Vista,需要以调试选项启动系统
启动方式
菜单
File > Kernel Debug > Local
命令行
windbg -kl
.attach -k
需要已经有一个调试会话
常用于同时调试多个目标时
30.5.6 调试转储文件
菜单
File > Open Crash Dump
命令行
windbg -z
.opendump
需要已经有一个调试会话
常用于同时调试多个目标时
30.5.7 远程调试
本质,本地调试+远程通信
实现方式
方式一
服务器
DbgSrv(远程用户态调试)
KdSrv(远程内核态调试)
客户端
windbg
方式二(使用)
服务器和客户端,都是windbg
启动服务器
命令行方式
windbg -server 服务器通信字符串
命令方式
建立调试会话
.server 服务器通信字符串
服务器通信字符串
npipe:pipe=pipe_name
tcp:port=port_number,password=password
启动客户端
命令行方式
windbg -remote 客户端通信字符串
菜单方式
File > Connect to Remote Session
输入客户端通信字符串或直接浏览
客户端通信字符串
npipe:server=pc_name,pipe=pipe_name
tcp:server=pc_name,port=port_number,password=password
30.6 终止调试会话
30.6.1 停止调试
方式
菜单
debug > stop debugging
命令
q
结果
windbg
恢复到赋闲(dormant)状态
调试目标
活动的用户态目标,被终止
活动的内核目标,保持被中断到调试器的状态,可重新与其建立连接
30.6.2 分离调试目标
目的,使调试目标继续运行
方式
菜单
debug > detach debuggee
命令
.detach
结果
windbg
没有其他调试目标时,则恢复到赋闲(dormant)状态
调试目标继续运行
对于进程,系统会修改进程属性,使其脱离被调试状态而成为普通进程
条件
Windows XP或更高版本
依赖于Windows XP才引入的操作系统支持(DebugSetProcessKillOnExit API)
30.6.3 抛弃被调试进程
目的,重新附加调试目标
方式
命令
.abandon
结果
windbg
没有其他调试目标时,则恢复到无调试目标状态
被调试进程
被抛弃后,仍处于挂起状态
调试器中仅执行了注销操作,没有恢复进程状态
重新附加
windbg -pe -p PID
注意
没有-pe,调试器附加失败,并报告DebugPort不为空
有了-pe,调试器不会报错,调试器引擎会产生一个异常,触发调试器进入命令模式,使继续调试
30.6.4 杀死被调试进程
方式
命令
.kill,会调用系统TerminateProcess API来终止进程
结果
windbg
仍可观察调试目标的数据
执行g时,会结束当前调试会话
没有其他调试目标时,则恢复到赋闲(dormant)状态
被调试进程
被终止
30.6.5 调试器终止或僵死
调试器终止
建立的调试会话会被终止
活动的被调试进程,被终止
调试器僵死
可使用-pe -p PID启动windbg,重新附加被调试进程,后终止僵死的调试器
30.6.6 重新开始调试
方式
菜单
debug > restart
命令
.restart
结果
对于调试会话源于"创建进程并调试",关闭被调试进程并重新运行和调试
对于调试会话源于"附加到已有进程",不支持
对于内核态调试
.restart,重启调试器后再建立调试连接
.reboot,目标系统重启
30.7 理解上下文
30.7.0 概述
上下文,操作的执行环境,讨论的背景信息
30.7.1 登录会话上下文(Login Session Context)
含义,登录会话语境,当前操作或陈述的
Windows支持同时多个登录对话,每个对话拥有自身的输入输出设备和桌面
例子
Windows XP系统中,一般只有一个会话,被远程桌面登录后就会有两个
Windows Vista引入对话隔离技术(Session Isolation),所有系统服务运行在会话0以增强系统服务安全性,故至少存在两个会话
目前会话上下文仅在内核调试时有意义,相关扩展命令仅在调试内核目标时有意义
!session
!session,显示状态
!session -s index,切换
改变会话后,默认进程会变成新会话中的进程,以前缓存的用户空间数据不再有效
为了避免用户观察到错误的数据,可使用.cache命令在缓存选项中加入forcedecodeuser或forcedecodeptes选项禁止缓存功能,让调试器每次都重新读取内存数据
!spross,列出会话中所有进程
进程的EPROCESS结构的Session字段记录着进程的所属会话
每个会话都包含了Windows子系统服务器进程(CSRSS)
会话管理器不属于任一会话
30.7.2 进程上下文
含义,进程语境,当前操作或陈述的
在Windows操作系统中
所有进程的内核空间是共享的,用户空间是独立的
在32系统中,单个进程共4GB进程空间,低2GB是用户空间,高2GB是内核空间
在内核调试时
观察内核数据,不需要关心当前进程
观察用户空间数据,需要注意当前进程,同一用户态地址对不同进程的含义不同(实际的物理地址不同)
当调试目标中断到调试器中后,WinDBG会根据调试事件设置默认进程
若要观察其他进程的用户空间,需要先切换进程上下文
.process process_EPROCESS_address,根据进程的EPROCESS结构切换进程
.process 0 0,列出所有进程的基本信息
.context
设置和显示页目录基址(base of page directory)(用于翻译用户态地址)
页目录基址是进程的一个重要属性,使用.process设置进程上下文时自动设置
对于x86系统,cr3寄存器存放页目录基址,一个进程只有一个页目录基址
对于安腾系统,一个进程可使用多个页目录基址
.process和.context仅用在内核调试中
调试用户态目标时
所有虚地址都是基于当前进程的,不需要切换进程上下文
在一个调试会话中调试多个用户态目标时,应使用"lNumber s"切换进程
30.7.3 寄存器上下文(register context)
含义,寄存器语境,当前操作或陈述的
在多任务系统中
CPU寄存器保存的是当前正在执行的线程的寄存器值
对于没执行的线程,其寄存器值保存在内存中,当线程恢复执行时,寄存器值从内存加载到寄存器中
在调试器中观察一个线程的寄存器(不含MSR)时,该线程处于挂起状态,观察和修改的寄存器值源于内存
系统会在以下情况中,将寄存器值保存到当前线程的上下文记录(context record)中
线程切换时,该上下文常被称为线程上下文
中断或异常时,该上下文常被称为异常上下文
.thread
.thread thread_ETHREAD_address,根据线程的ETHREAD结构切换线程
.thread,查看当前线程
.process process_EPROCESS_address f
列出一个进程的所有线程
.crx或.thread
将线程上下文恢复成以前的情况
.ecrx
在调试用户态转储文件时,可将其中保存的异常上下文设置为寄存器上下文
在不同寄存器上下文中,观察到的寄存器和栈不同
r
kv
30.7.4 局部(变量)上下文(local context)
含义,局部变量语境,当前操作或陈述的
一个运行中的函数,对应一个局部上下文
运行中的函数,其局部变量信息存放在调用栈中
使用栈帧号代表局部上下文
.frame
.frame,观察当前局部上下文
.frame frame_index(十六进制),切换当前局部上下文到指定栈帧
dv,查看当前局部上下文中的参数和局部变量
例子
源码
void test_windbg(int a)
{
int b = 2;
getchar();
}
int main()
{
test_windbg(1);
return 0;
}
启动
windbg -g .\Test.exe
线程查看和切换
~* 查看所有线程
~0 s 切换到0号线程
~. 查看当前线程
栈帧查看和切换
k
.frame
.frame a
查看函数参数和局部变量
dv
30.7.5 上下文的关系
操作或陈述核心,作为操作或陈述的主体
操作或陈述语境,作为操作或陈述的客体
两者是多对多或一对多的关系,两者的组合整体作为新的上一层次的可标识实体(操作或陈述核心)
关系
会话进程
会话上下文
进程
进程上下文
线程(多个)
线程上下文
寄存器上下文
调用栈
局部(变量)上下文(多个)
线程变量
CPU时间片
30.8 调试符号
30.8.1 重要意义
调试符号(debug symbols)
其有无和版本是否准确,严重影响调试器的工作
30.8.2 符号搜索路径
背景
一个调试目标,可能存在多个符号文件,且不在同一位置
需要告诉调试器多个目录并按一定顺序搜索符号文件
符号搜索路径
符号文件搜索路径的列表
多个路径使用分号分隔
简称符号路径(symbol path)
路径种类
文件系统路径
符号服务器
设置方法
符号环境变量
_NT_SYMBOL_PATH
_NT_ALT_SYMBOL_PATH
命令行参数,-y
.sympath命令,增删显示
.symfix命令,自动设置
菜单,File>Symbol File Path
显示,.sympath命令
30.8.3 符号服务器
背景
一个调试目标,可能存在多个模块,一个模块可能包含多个版本,每个版本对应一个符号文件
这项符号文件查询工作,无聊和繁琐,可以交给程序完成
符号服务器(symbol server),存储符号文件的文件服务器
可以从中获取指定特征(名称和版本)的符号文件
符号服务器架构
示意图
windbg.exe
V
DbgEng.dll
V
DbgHelp.dll
V
符号服务器Dll(SymSrv.dll)
|
|------------> 下游符号库
|----(网络)--> 中央符号库(centralized store)(符号服务器)
DbgHelp.dll
Windows操作系统的调试辅助库模块
windbg通过它,读取和解析调试符号
符号服务器Dll
符号服务器的本地模块
负责从符号服务器查找、下载和管理符号文件
具体dll不固定,只要实现了DbgHelp.dll依赖的符号服务器API
windbg开发工具包中DbgHelp帮助文档(sdk\help\dbghelp.chm),描述了符号服务器API
用户可以自己实现符号服务器dll
windbg工具包中包含了一个符号服务器(SymSrv.dll)
下游符号库(downstream store)
用于缓存从符号服务器下载的符号文件
工作流程
DbgHelp请求符号服务器Dll,获取指定符号文件
符号服务器Dll先在下游符号库查找,失败后在中央符号库查找
路径表示
完整表示
symsrv*ServerDll*[DownstreamStore*]ServerPath
基于SymSrv.dll的表示
symsrv*SymSrv.dll*[DownstreamStore*]ServerPath
简写为srv*[DownstreamStore*]ServerPath
30.8.4 加载符号文件
设置符号路径
.sympath srv*D:\Symbols*http://msdl.microsoft.com/download/symbols
调试windbg的.reload命令
Child-SP RetAddr Call Site
00000000`0675af58 00007ff9`326b8ba3 ntdll!ZwWaitForSingleObject+0x14
00000000`0675af60 00007ff9`20e34af4 KERNELBASE!WaitForSingleObjectEx+0x93
00000000`0675b000 00007ff9`20e2f737 WININET!InternetFindNextFileW+0xe9d4
00000000`0675b030 00007ff9`20dc69bc WININET!InternetFindNextFileW+0x9617
00000000`0675b060 00007ff9`20d1f069 WININET!UrlCacheServer+0x2cb7c
00000000`0675b1f0 00000000`678b80d2 WININET!InternetReadFile+0xd9
00000000`0675b290 00000000`678b2040 symsrv!EulaDlgProc+0x1742
00000000`0675b2c0 00000000`678b15d1 symsrv!RunDllEntry+0x8d00
00000000`0675b570 00000000`678a6a50 symsrv!RunDllEntry+0x8291
00000000`0675b5c0 00000000`678a7d05 symsrv+0x6a50
00000000`0675b7e0 00000000`678a7a66 symsrv!SymbolServerByIndexW+0x185
00000000`0675be80 00000000`67daffd8 symsrv!SymbolServerW+0xc6
00000000`0675c0f0 00000000`67d9591a dbghelp!SymGetFileLineOffsets64+0x1248
00000000`0675c980 00000000`67d96d75 dbghelp+0x2591a
00000000`0675d6d0 00000000`67dc5234 dbghelp+0x26d75
00000000`0675d9e0 00000000`67dc2ee6 dbghelp!ImagehlpApiVersionEx+0x28b4
00000000`0675dc70 00000000`67dc2add dbghelp!ImagehlpApiVersionEx+0x566
00000000`0675e180 00000000`67db7011 dbghelp!ImagehlpApiVersionEx+0x15d
00000000`0675e1c0 00000000`68200484 dbghelp!SymSetScopeFromAddr+0x81
00000000`0675e200 00000000`680aba61 dbgeng!DebugCreate+0x1a1274
00000000`0675e240 00000000`680af90e dbgeng!DebugCreate+0x4c851
00000000`0675e270 00000000`68159441 dbgeng!DebugCreate+0x506fe
00000000`0675e2b0 00000000`6815aae0 dbgeng!DebugCreate+0xfa231
00000000`0675e3c0 00000000`68067134 dbgeng!DebugCreate+0xfb8d0
00000000`0675e410 00000000`68067420 dbgeng!DebugCreate+0x7f24
00000000`0675e8d0 00007ff7`9c433beb dbgeng!DebugCreate+0x8210
00000000`0675e930 00007ff7`9c4342eb windbg+0x33beb
00000000`0675ea70 00007ff7`9c436d35 windbg+0x342eb
00000000`0675fae0 00007ff9`34427bd4 windbg+0x36d35
00000000`0675fb20 00007ff9`3520ce71 KERNEL32!BaseThreadInitThunk+0x14
00000000`0675fb50 00000000`00000000 ntdll!RtlUserThreadStart+0x21
SymbolServer函数
符号服务器API
功能,向服务器请求指定的符号文件,返回文件的完整路径
典型实现
在下游符号库中查找符号文件
找到,则返回完整路径
未找到,则进行远程查找
若找到,则下载到下游符号库中,后返回完整路径
函数原型
BOOL CALLBACK SymbolServer(
[in] LPCSTR params,
[in] LPCSTR filename,
[in] PVOID id,
[in] DWORD two,
[in] DWORD three,
[out] LPSTR path );
params
符号服务器参数信息
DownstreamStore和ServerPath
filename
符号文件名
id
符号文件第一标识信息
.dbg和pe文件(.exe和.dll)
PE文件头定义的映像时间戳(TimeDateStamp)
.pdb
PDB签名
two
符号文件第二标识信息
.dbg和pe文件(.exe和.dll)
PE文件头定义的映像文件大小()SizeOfImage
.pdb
PDB年龄(Age)
three
符号文件第三标识信息
.dbg和pe文件(.exe和.dll)和.pdb
未使用为0
path
符号文件的完整路径
最大长度为MAX_PATH
流程
使用文件特征标识,调用SymbolServerGetIndexStringW函数,生成索引串
文件特征标识的唯一性序列化
调用SymbolServerByIndexW函数,获取文件名和索引串指定的符号文件
调用cascade函数
调用StoreUNC函数,在下游符号库中查找
使用StoreWinInet类,在中央符号库中查找
触发调试器加载符号
ld
.reload
其他使用符号的命令,如
栈回溯命令(k*)
反汇编命令
windbg采用懒惰式符号加载策略(lazy symbol loading)
故在查看模块列表时,会发现许多模块的符号状态为deferred,即推迟加载
30.8.5 观察模块信息
lm
lm(list loaded module),模块概要信息列表
例子
lm
start end module name
00007ff7`90e80000 00007ff7`90ea8000 Test C (deferred)
00007ff8`dad10000 00007ff8`daec9000 ucrtbased (deferred)
00007ff8`f2300000 00007ff8`f23f7000 MSVCP140D (deferred)
00007ff9`1b650000 00007ff9`1b673000 VCRUNTIME140D (deferred)
00007ff9`31ab0000 00007ff9`31abc000 CRYPTBASE (deferred)
00007ff9`32150000 00007ff9`321d1000 bcryptPrimitives (deferred)
00007ff9`32680000 00007ff9`32922000 KERNELBASE (deferred)
00007ff9`33450000 00007ff9`33570000 RPCRT4 (deferred)
00007ff9`34410000 00007ff9`344c2000 KERNEL32 (private pdb symbols) d:\symbols\kernel32.pdb\47A44F0CC47FB6A1F009E4343B711F231\kernel32.pdb
00007ff9`34700000 00007ff9`34797000 sechost (deferred)
00007ff9`35010000 00007ff9`350ae000 msvcrt (deferred)
00007ff9`350b0000 00007ff9`35153000 ADVAPI32 (deferred)
00007ff9`351a0000 00007ff9`35390000 ntdll (private pdb symbols) d:\symbols\ntdll.pdb\B54F3499813EBCF139AEFDD664E98FDD1\ntdll.pdb
含义
start,模块在进程空间中的起始地址
end,模块在进程空间中的结束地址
module name,模块名称
符号文件加载状态
缩写 含义
deferred 模块已加载,但尚未尝试加载符号
# 符号文件和可执行文件之间的时间戳或校验和不匹配
T 时间戳,丢失、不可访问、等于零
C 校验和,丢失、不可访问、等于零
DIA 通过调试接口访问(DIA,debug interface access)加载了符号文件
Export 未找到符号文件,使用映像文件的输出信息(如dll的export)作为符号
M 符号文件和可执行文件之间的时间戳或校验和不匹配,但仍然加载了该符号文件
PERF 执行文件包含性能优化代码。对地址的加减运算可能出错
Stripped 调试信息是从映像文件中抽取出来的
PDB 符号文件是.pdb格式的
COFF 符号文件是COFF格式的(Common Object File Format)
符号文件完整路径或空白
PDB文件
分为私有(private)PDB文件和公共(public)PDB文件
公共 = 私有 - 私有信息
lm v,模块详细信息列表
例子
lm v
start end module name
00007ff7`90e80000 00007ff7`90ea8000 Test C (deferred)
Image path: Test.exe
Image name: Test.exe
Timestamp: Sun Feb 21 13:59:10 2021 (6031F6AE)
CheckSum: 00000000
ImageSize: 00028000
Translations: 0000.04b0 0000.04e4 0409.04b0 0409.04e4
00007ff8`dad10000 00007ff8`daec9000 ucrtbased (deferred)
Image path: C:\Windows\SYSTEM32\ucrtbased.dll
Image name: ucrtbased.dll
Timestamp: Wed Jun 17 13:23:10 2015 (5581043E)
CheckSum: 001C1915
ImageSize: 001B9000
File version: 10.0.10150.0
Product version: 10.0.10150.0
File flags: 0 (Mask 3F)
File OS: 40004 NT Win32
File type: 2.0 Dll
File date: 00000000.00000000
Translations: 0409.04b0
CompanyName: Microsoft Corporation
ProductName: Microsoft® Windows® Operating System
InternalName: ucrtbase.dll
OriginalFilename: ucrtbase.dll
ProductVersion: 10.0.10150.0
FileVersion: 10.0.10150.0 (th1.150616-1659)
FileDescription: Microsoft® C Runtime Library
LegalCopyright: © Microsoft Corporation. All rights reserved.
...
模块过滤
m 名称模式
M 路径
o,已加载的模块
l,已加载符号的模块
e,有符号问题的模块
!lmi module_name
显示模块最详细信息
例子
!lmi Test
Loaded Module Info: [test]
Module: Test
Base Address: 00007ff790e80000
Image Name: Test.exe
Machine Type: 34404 (X64)
Time Stamp: 6031f6ae Sun Feb 21 13:59:10 2021
Size: 28000
CheckSum: 0
Characteristics: 22
Debug Data Dirs: Type Size VA Pointer
CODEVIEW 3f, 1f2e8, d8e8 RSDS - GUID: {FAE50400-BD21-47AC-99E6-79A9B7D92FB1}
Age: 1, Pdb: G:\user\vs2015\Test\x64\Debug\Test.pdb
?? 14, 1f328, d928 [Data not mapped]
Symbol Type: DEFERRED - No error - symbol load deferred
Load Report: no symbols loaded
菜单,Debug > Modules
模块概要信息列表
30.8.6 检查符号
语法
x [option] module_name!symbol_name
名称支持通配符
? 任意符号出现一次
* 任意符号出现0次或多次
# 指定符号出现
[]# 指定符号串出现
选项
控制结果显示顺序
/a,/A,地址升序和降序
/n,/N,名称升序和降序
/z,/Z,符号大小升序和降序
显示符号的数据类型
/t
条件,需要私有符号,否则显示
显示符号的符号类型和符号大小
/v
符号类型
访问种类
prv,private,私有符号
pub,public,公共符号
信息种类
local
global
parameter
function
unknown
符号大小
函数类符号,函数在内存中的大小
其他类型符号,数据类型的大小
条件,需要私有符号,否则显示0
按大小过滤符号
/s size
控制显示格式
/p,去掉函数名与括号间的空格
/q,符号名的引号格式,@!"符号名"
结果含义
[符号类型]
符号地址
函数的入口地址
变量的起始地址
[符号大小]
[符号数据类型]
module_name!symbol_name
符号的类型或取值
例子
x /t /v Test!test*
prv func 00007ff7`90e93b40 3f Test!test_windbg (int)
x /t /v Test!g_*
prv global 00007ff7`90e9d220 8 * Test!g_MyClass$initializer$ = 0x00007ff7`90e91870
prv global 00007ff7`90ea1170 1 class MyClass Test!g_MyClass = class MyClass
30.8.7 搜索符号
ln(list nearest symbols)
例子
ln 00007ff790e9d220
(00007ff7`90e9d220) Test!g_MyClass$initializer$ | (00007ff7`90e9d330) Test!__xc_z
Exact matches:
Test!g_MyClass$initializer$ = 0x00007ff7`90e91870
30.8.8 设置符号选项
符号选项
windbg使用32的dword记录符号选项
标志 选项名 含义 默认值
0x1 SYMOPT_CASE_INSENSITIVE 不分大小写 On
0x2 SYMOPT_UNDNAME 显示未装饰的符号名 On
0x4 SYMOPT_DEFERRED_LOADS 延迟加载符号 On
0x8 SYMOPT_NO_CPP 关闭C++翻译(使用时,类成员的__或被替换成::) Off
0x10 SYMOPT_LOAD_LINES 加载源码行信息 KD和CDB中为Off,WinDbg 中为On
0x20 SYMOPT_OMAP_FIND_NEAREST 允许为优化过的代码使用最相近的符号 On
0x40 SYMOPT_LOAD_ANYTHING 降低匹配符号的挑剔度 Off
0x80 SYMOPT_IGNORE_CVREC 忽略映像文件的cv记录 Off
0x100 SYMOPT_NO_UNQUALIFIED_LOADS 禁止符号处理器自动加载模块 Off
0x200 SYMOPT_FAIL_CRITICAL_ERRORS 显示关键错误 On
0x400 SYMOPT_EXACT_SYMBOLS 严格评估所有符号文件 Off
0x800 SYMOPT_ALLOW_ABSOLUTE_SYMBOLS 允许位于内存绝对地址的符号 Off
0x1000 SYMOPT_IGNORE_NT_SYMPATH 忽略环境变量中的符号和镜像路径 Off
0x2000 SYMOPT_INCLUDE_32BIT_MODULES 对于安腾处理器,强制列举32位模块 Off
0x4000 SYMOPT_PUBLICS_ONLY 忽略全局、局部和作用域相关的符号 Off
0x8000 SYMOPT_NO_PUBLICS 不搜索公共符号表 Off
0x10000 SYMOPT_AUTO_PUBLICS 其他方法失败时,才使用PDB文件中的公共符号 On
0x20000 SYMOPT_NO_IMAGE_SEARCH 不搜索镜像文件 On
0x40000 SYMOPT_SECURE (内核调试)Secure Mode Off
0x80000 SYMOPT_NO_PROMPTS (远程调试)不显示代理服务器的认证对话框 KD和CDB中为On,WinDbg 中为Off
0x80000000 SYMOPT_DEBUG 显示符号加载过程 Off
增加设置
.symopt + option_value
option_value为十六进制标志位,如0x1,0x2,0x4
取消设置
.symopt - option_value
显示设置
.symopt
友好设置
!sym
!sym noisy,即+ 0x80000000
!sym quiet,即- 0x80000000
!sym prompts,即+ 0x80000
!sym prompts off,即- 0x80000
30.8.9 加载不严格匹配的符号文件
方式
.reload /i module_name
设置符号选项,.symopt +0x40
注意
加载前,最好开启"嘈杂"模式(!sym noisy),以观察加载细节
30.9 事件处理
30.9.0 概述
Windows的调试模型是事件驱动的
整个调试过程,就是调试事件的产生、发送、接收和处理
调试目标,产生调试事件
调试器,接收和处理调试事件
调试子系统,发送调试事件给调试器,为调试器提供服务
30.9.1 调试事件和异常的关系
Windows定义了9类调试事件
EXCEPTION_DEBUG_EVENT(1)
CREATE_THREAD_DEBUG_EVENT(2)
CREATE_PROCESS_DEBUG_EVENT(3)
EXIT_THREAD_DEBUG_EVENT (4)
EXIT_PROCESS_DEBUG_EVENT(5)
LOAD_DLL_DEBUG_EVENT(6)
UNLOAD_DLL_DEBUG_EVENT(7)
OUTPUT_DEBUG_STRING_EVENT(8)
RIP_EVENT(9)
其他的调试器事件
专供调试使用
异常是一种调试事件,EXCEPTION_DEBUG_EVENT(1)
异常调试事件,包含子类,其他类型调试事件不包含
Win32异常
Windows操作系统定义的
类型
CPU产生的异常
系统内核定义的异常
典型
非法访问、除零等
异常代码
ntstatus.h
Visual C++异常
Visual C++编译器的throw关键字所抛出的异常
throw关键字调用RaiseException API产生异常
异常代码都是 0xe06d7363(.msc)
托管异常
.Net程序使用托管方法抛出的异常
异常代码都是0xe0636f6d(.com)
其它异常
类型
用户程序直接调用RaiseException API抛出的异常
其它C++编译器抛出的异常等
30.9.2 两轮机会
Windows的异常分发流程
第一轮
分发给调试器,调试器判断接收和返回处理结果
调试器通常返回没有处理异常,使之继续分发
对于断点异常和调试异常,调试器会返回已经处理
尝试分发给异常处理器(VEH、SEH)
第二轮
分发给调试器,调试器判断接收和返回处理结果
调试器通常返回已经处理,让系统恢复程序执行,通常会导致重复循环分发
终极处理
异常源自应用程序,系统启动"应用程序错误报告过程",终止应用程序
异常源自内核代码,系统启动"蓝屏机制"
其他调试事件只有一轮机会
分发流程,实现了一个异常处理链
调试用异常,实现调试目的
有处理器的异常,实现程序逻辑
无处理器的异常,拦截未处理的异常
所有异常,实现异常的兜底处理
30.9.3 定制事件处理方式
每个异常事件都存在四个选项
第一轮机会是否中断到调试器
第一轮机会的处理结果
第二轮机会是否中断到调试器
第二轮机会的处理结果
Visual C++和Visual Studio都有异常设置
windbg
图形界面
Debug > Event Filters
Execution组单选,控制是否中断
Enabled
异常事件两次都中断,其他调试事件会中断
Disabled
异常事件第二次中断,其他调试事件不中断
Output
输出信息通知用户
Ignore
忽略
Continue组单选,控制第一次异常事件的处理结果
Handled,返回已处理
Not Handled,返回未处理
大多异常默认设置为未处理
对于第二次异常事件的处理结果
默认设置为返回已处理
在调试时,可使用gn命令,表示异常未处理
支持为事件定义关联命令(Commands)
异常事件有两次关联,其他调试事件有一次关联
支持设置事件参数以细化过滤条件(Arguments)
支持事件处理方式的增删
命令配置
信息要素
条件对象
事件和参数
中断方式
Enabled,Disabled,Output,Ignore
处理结果
已处理和未处理
关联命令
第一次和第二次
命令
设置中断方式和关联命令
sx{e|d|i|n} [-c "cmd1"] [-c2 "cmd2"] {Exception|Event|*}
设置处理结果
已处理,sxe -h {Exception|Event|*}
未处理,sx{d|i|n} -h {Exception|Event|*}
查看当前设置
sx
恢复默认设置
sxr
Exception|Event的表示
事件码,Event Code
sx命令查看
帮助文档搜索"Controlling Exceptions and Events"
30.9.4 GH和GN命令
在调试中,使用g命令恢复调试目标的执行,此时调试器会返回配置的或默认的处理结果
可使用命令临时指定返回的处理结果
已处理(handled),gh
未处理(Not Handled),gn
30.9.5 实验
程序(dbgee.exe)
int _tmain(int argc, _TCHAR* argv[])
{
if (argc == 1)
{
*(int*)0 = 1;
printf("test\n");
}
return 0;
}
运行,windbg dbgee.exe
43f4.4988): Break instruction exception - code 80000003 (first chance)
*** ERROR: Symbol file could not be found. Defaulted to export symbols for ntdll.dll -
ntdll!LdrInitShimEngineDynamic+0x35c:
00007ff9`352711dc cc int 3
sxe av,设置两轮中断
sxd -h av,设置第一轮返回不处理
sx,查看设置
av - Access violation - break - not handled
g,恢复执行,触发第一次中断
(43f4.4988): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
*** WARNING: Unable to verify checksum for dbgee.exe
dbgee!wmain+0x36:
00007ff6`945118f6 c704250000000001000000 mov dword ptr [0],1 ds:00000000`00000000=????????
gh,临时返回已处理,由于异常条件仍存在,导致循环重复触发第一次中断
(43f4.4988): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
dbgee!wmain+0x36:
00007ff6`945118f6 c704250000000001000000 mov dword ptr [0],1 ds:00000000`00000000=????????
g,恢复执行,按配置返回未处理
由于没有在程序中找到异常处理器(VEH、SEH等),系统会执行缺省的异常处理器(kernel32.dll中UnhandledExceptionFilter函数)
判断当前程序是否处于调试状态
否,启动应用程序错误对话框,通知用户终止程序
是,返回EXCEPTION_CONTINUE_SEARCH,导致进入异常的第二次分发
(43f4.4988): Access violation - code c0000005 (!!! second chance !!!)
dbgee!wmain+0x36:
00007ff6`945118f6 c704250000000001000000 mov dword ptr [0],1 ds:00000000`00000000=????????
g,恢复执行,按配置返回已处理,由于异常条件仍存在,导致循环重复触发第一次中断
(43f4.4988): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
dbgee!wmain+0x36:
00007ff6`945118f6 c704250000000001000000 mov dword ptr [0],1 ds:00000000`00000000=????????
g,进入异常的二次分发
(43f4.4988): Access violation - code c0000005 (!!! second chance !!!)
dbgee!wmain+0x36:
00007ff6`945118f6 c704250000000001000000 mov dword ptr [0],1 ds:00000000`00000000=????????
gn,返回未处理,系统终止调试目标
30.10 控制调试目标
30.10.1 初始断点
目的,为了让调试人员尽早分析调试目标
手段
Windows操作系统的进程加载器支持
在完成最基本的用户态初始化工作后
系统的模块加载函数,主动执行断点指令,触发断点,让调试目标中断到调试器中
初始断点,进入后的首个默认断点
创建调试目标的情况分析
创建新进程时,会在父进程的环境中完成很多创建工作
进程对象,进程空间,初始线程,通知子系统等
初始线程在新进程环境中执行
内核态的KiThreadStartup
将线程的IRQL(中断级别)降到APC级别
调用PspUserThreadStartup,为线程在用户态执行做准备
初始化一个对用户态代码的异步过程调用(APC),并插入APC队列
等待APC完成
将线程的IRQL降到0(PASSIVE)
调用线程上下文中的进程启动函数BaseProcessStart
BaseProcessStart调用应用程序入口函数
异步过程调用
该APC调用NTDLL.dll中的LdrpInitialize函数
LdrpInitialize函数,算是新进程的初始化线程,在用户态执行的最早代码
LdrpInitialize工作
初始化加载器和读取执行选项
调用LdrpInitializeProcess函数
加载Exe文件依赖的动态链接库
判断是否处于调试状态,是则调用DbgBreakPoint通知调试器
注意,此时还未调用DLL的DllMain函数
异步过程调用结束后
KiUserApcDispatcher调用ZwContinue,返回到内核态的PspUserThreadStartup函数中
附加调试目标的情况分析
windbg在目标进程中,创建一个远程线程,来触发一个初始断点
初始断点发生在新建线程的上下文中
kn
# Child-SP RetAddr Call Site
00 0000006c`7a27f8f8 00007ff9`3526d4db ntdll!DbgBreakPoint
01 0000006c`7a27f900 00007ff9`34427bd4 ntdll!DbgUiRemoteBreakin+0x4b
02 0000006c`7a27f930 00007ff9`3520ce71 KERNEL32!BaseThreadInitThunk+0x14
03 0000006c`7a27f960 00000000`00000000 ntdll!RtlUserThreadStart+0x21
注意,该线程不是目标进程的自建线程
而是调试器为了达到调试目的而创建的
恢复执行后,线程结束
其他
当windbg使用-g选项启动时
对于新建调试
windbg收到初始断点事件,不中断
对于附加调试
windbg不创建远程线程来触发断点
初始断点不是最早的中断机会
但是足够使用,初始断点可用于
跟踪和分析程序和DLL的入口函数
设置断点等准备工作
其他较早的中断机会
进程创建事件
EXE模块的加载事件
30.10.2 俘获调试目标
中断调试目标的方式
windbg中主动中断
快捷键,Ctrl+Break
菜单,Bebug > Break
调试目标中主动中断
对于调试中的图形界面程序,按F12
调试目标,遇到断点和触发异常
windbg中主动中断的分析
方式,windbg调试windbg的调试
启动新windbg附加旧windbg
设置符号路径(符号服务器)
重新加载模块符号,确保相关符号下载完成
windbg.pdb
dbgeng.pdb
注意下载部分就失败的情形
下游符号库中,出现未下载完整的符号文件
注意开启"显示符号加载过程",!sym noisy
有下载的加载失败
SYMSRV: dbgeng.pdb from https://msdl.microsoft.com/download/symbols: 4213760 bytes - 34 percentSYMSRV: /download/symbols/dbgeng.pdb/3905A3278DCD49AEA0C08989F59DB6D91/dbgeng.pdb
函数不正确。
DBGHELP: C:\Program Files\Debugging Tools for Windows (x64)\sym\dbgeng.pdb\3905A3278DCD49AEA0C08989F59DB6D91\dbgeng.pdb - drive not ready
DBGHELP: C:\Program Files\Debugging Tools for Windows (x64)\dbgeng.pdb - file not found
DBGHELP: dbgeng.pdb - file not found
*** ERROR: Symbol file could not be found. Defaulted to export symbols for C:\Program Files\Debugging Tools for Windows (x64)\dbgeng.dll -
DBGHELP: dbgeng - export symbols
无下载的加载失败
DBGHELP: d:\symbols\dbgeng.pdb\3905A3278DCD49AEA0C08989F59DB6D91\dbgeng.pdb - drive not ready
DBGHELP: C:\Program Files\Debugging Tools for Windows (x64)\dbgeng.pdb - file not found
DBGHELP: dbgeng.pdb - file not found
*** ERROR: Symbol file could not be found. Defaulted to export symbols for C:\Program Files\Debugging Tools for Windows (x64)\dbgeng.dll -
DBGHELP: dbgeng - export symbols
有下载的加载成功
SYMSRV: dbgeng.pdb from https://msdl.microsoft.com/download/symbols: 4213760 bytes - copied
DBGHELP: dbgeng - public symbols
d:\symbols\dbgeng.pdb\3905A3278DCD49AEA0C08989F59DB6D91\dbgeng.pdb
加载模块的符号状态
失败情形
lm
start end module name
...
00000000`67f70000 00000000`683f5000 dbgeng (export symbols) C:\Program Files\Debugging Tools for Windows (x64)\dbgeng.dll
成功情形
lm
start end module name
...
00007ff7`5a300000 00007ff7`5a3b1000 windbg (pdb symbols) d:\symbols\windbg.pdb\83321AFD3A334F6FA751209AC0F960E91\windbg.pdb
...
设置断点
bu ntdll!RtlpCreateUserThreadEx
bu ntdll!NtCreateThreadEx
普通情况(创建远程线程中断目标)
windbg执行Break命令的过程kn
kn
# Child-SP RetAddr Call Site
00 00000000`00f7c7c8 00007ff9`351a589c ntdll!NtCreateThreadEx
01 00000000`00f7c7d0 00007ff9`3526d463 ntdll!RtlpCreateUserThreadEx+0x13c
02 00000000`00f7c910 00007ff9`344430aa ntdll!DbgUiIssueRemoteBreakin+0x43
03 00000000`00f7c990 00000000`68300d56 KERNEL32!DebugBreakProcess+0xa
04 00000000`00f7c9c0 00000000`6828eecf dbgeng!LiveUserDebugServices::RequestBreakIn+0x26
05 00000000`00f7ca40 00000000`680c105b dbgeng!LiveUserTargetInfo::RequestBreakIn+0x6f
06 00000000`00f7ca80 00007ff7`5a34fe28 dbgeng!DebugClient::SetInterrupt+0x13b
07 00000000`00f7cac0 00007ff9`3415681d windbg!FrameWndProc+0x1ae8
08 00000000`00f7d8c0 00007ff9`341563ec USER32!UserCallWinProcCheckWow+0x2bd
09 00000000`00f7da50 00007ff9`34162d03 USER32!DispatchClientMessage+0x9c
0a 00000000`00f7dab0 00007ff9`3523fdb4 USER32!_fnDWORD+0x33
0b 00000000`00f7db10 00007ff9`329912c4 ntdll!KiUserCallbackDispatcherContinue
0c 00000000`00f7db98 00007ff7`5a363d92 win32u!ZwUserTranslateAccelerator+0x14
0d 00000000`00f7dba0 00007ff7`5a364120 windbg!ProcessNonDlgMessage+0x22
0e 00000000`00f7dbf0 00007ff7`5a36b053 windbg!ProcessPendingMessages+0x70
0f 00000000`00f7dc60 00007ff7`5a3766e6 windbg!wmain+0x2c3
10 00000000`00f7fd50 00007ff9`34427bd4 windbg!_CxxFrameHandler3+0x28a
11 00000000`00f7fd90 00007ff9`3520ce71 KERNEL32!BaseThreadInitThunk+0x14
12 00000000`00f7fdc0 00000000`00000000 ntdll!RtlUserThreadStart+0x21
窗口过程函数
收到Break命令后,调用全局变量g_DbgClient的方法SetInterrupt
HRESULT IDebugControl::SetInterrupt(IN ULONG Flags);
Flags取值
DEBUG_INTERRUPT_PASSIVE(1)
希望中断到命令模式,但不强制
函数内部将dbgeng!g_UserInterruptCount加一,将dbgeng!g_EngStatus设置为0x1005
DEBUG_INTERRUPT_EXIT(2)
让调试引擎取消等待调试事件,强制返回
通常会导致没有调试目标
将dbgeng!g_EngStatus设置为0x1008
DEBUG_INTERRUPT_ACTIVE(0)
通过全局变量dbgeng!g_CmdState,判断调试器是否处于命令模式
否,则要求调试目标中断到调试器以进入命令模式
是,则递增dbgeng!g_UserInterruptCount
UI线程使用的是0
远程线程的线程函数,DbgUiRemoteBreakin
调用DbgBreakPoint函数,执行断点指令,产生断点异常
极限情况(超时则强制挂起)
远程线程一产生,还未执行断点指令,就被挂起
windbg的处理
等待一段时间后,显示提示信息
Break-in sent,waiting 30 seconds
再等待30秒,windbg产生一个异常事件(事件码为 0x8000 0007)
SynthesizeWakeEvent
这个事件触发调试器等待函数返回,并处理该事件
会引发dbgeng!SyspendExecution函数调用
挂起调试目标所有线程,使调试目标被中断
调试器进入命令模式并显示信息
Wake debugger - code 80000007 (first chance)
30.10.3 继续运行
从指定地址继续,同时设定临时断点
g [a] [= start_address] [break_address ... [; break_commands]]
指定地址,start_address
断点
硬件断点与普通断点,有无a
断点地址,break_address
断点触发后命令,break_commands
可实现执行到光标
g,缺省时,从当前地址继续,无断点
继续并指定异常处理结果
g,返回配置的或默认的处理结果
gh,已处理(handled)
gn,未处理(Not Handled)
继续到上一级函数
gu
条件断点中的继续
gc
30.11 单步执行
30.11.1 概览
单步的单位
源码模式,一次执行一行源码
Debug > Source Mode 被选中
l+t
汇编模式,一次执行一条汇编指令
Debug > Source Mode 未被选中
l-t
单步的对象为函数调用时
单步进入,step into,p命令
单步越过,step over,t命令,trace
非函数调用时,p和t作用相同
实现
汇编模式下的单步执行
通过CPU的陷阱机制实现,对于x86CPU,就是设置标志寄存器的T标志
t命令过程
windbg收到t命令
工作线程解析命令(ParseStepTrace),调用SetExecStep和SetNextStepTraceState函数
两个函数将用户命令,转化并设置到内部对象、变量和线程的上下文结构中
调试引擎报告命令执行完成,最后由ProcessEngineCommands返回到EngineLoop
EngineLoop继续执行,调用DebugClient::WaitForEvent以等待下一调试事件
恢复目标运行,调试引擎会将线程上下文通过KERNELBASE!SetThreadContext设置到系统
设置过程
Child-SP RetAddr : Args to Child : Call Site
00000000`0961e628 00000000`6830059f : 00000000`0961e6c0 00000000`680ad865 00000000`0961e6d0 00000001`680ae0aa : KERNELBASE!SetThreadContext
00000000`0961e630 00000000`680f8aa8 : 00000000`04373970 00000000`000007b8 00000000`043839e0 00000003`000004d0 : dbgeng!LiveUserDebugServices::SetContext+0xaf
00000000`0961eb60 00000000`680f5de7 : 00000000`043a6bc0 00000000`043b3220 00000000`000007b8 00000000`043839e0 : dbgeng!LiveUserTargetInfo::SetTargetContext+0x78
00000000`0961eba0 00000000`6819f1b9 : 00000000`043a6bc0 00000000`043b3220 00000000`000007b8 00000000`043839e0 : dbgeng!TargetInfo::SetContext+0xe7
00000000`0961f660 00000000`6819efec : 00000000`043838e0 00000000`0961f6a0 00000001`00000001 00000001`00000000 : dbgeng!ArmCeMachineInfo::KdSetContext+0x49
00000000`0961f690 00000000`68281314 : 00000000`043838e0 00000000`043b3fa0 00000000`043b3220 00000000`0961f700 : dbgeng!MachineInfo::SetContext+0x18c
00000000`0961f6d0 00000000`68282672 : 00000000`043a6bc0 00000000`00000000 00001946`00000001 00000000`0961f740 : dbgeng!TargetInfo::ChangeRegContext+0xe4
00000000`0961f720 00000000`6814a5a9 : 00000000`043a6bc0 00000000`043b3f00 00007ff9`3523fd90 00000000`0961f790 : dbgeng!TargetInfo::PrepareForExecution+0x22
00000000`0961f760 00000000`6814a862 : 00000000`043a6b00 00000000`681a4145 00000000`043838e0 00000e07`af20b559 : dbgeng!PrepareOrFlushPerExecution+0x49
00000000`0961f7a0 00000000`6814b55f : 00000000`0961f860 00000000`043b3fa0 00000000`0961f830 00000000`00000000 : dbgeng!ResumeExecution+0x32
00000000`0961f7e0 00000000`6814a355 : 00000000`0437c3c0 00007ff9`00000005 00000000`00000000 00007ff9`326b8c64 : dbgeng!PrepareForExecution+0x6df
00000000`0961f900 00000000`680ca97b : 00000000`0437c3c0 00000000`00000000 00000000`0961f970 00000e07`00000001 : dbgeng!PrepareForWait+0x35
00000000`0961f940 00000000`680caf8e : 00000000`0437c3c0 00007ff7`00000000 00000e07`ffffffff 00000000`00000246 : dbgeng!RawWaitForEvent+0x2b
00000000`0961f9c0 00007ff7`5a336aba : 00000000`0437c3d0 00007ff7`00000000 00007ff7`ffffffff 00000000`00000000 : dbgeng!DebugClient::WaitForEvent+0xce
00000000`0961fa00 00007ff9`34427bd4 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : windbg!EngineLoop+0x16a
00000000`0961fa40 00007ff9`3520ce71 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : KERNEL32!BaseThreadInitThunk+0x14
00000000`0961fa70 00000000`00000000 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : ntdll!RtlUserThreadStart+0x21
查看设置参数
SetThreadContext的参数MSVCR90!_CONTEXT
dt MSVCR90!_CONTEXT 043839e0
+0x044 EFlags : 0x344
.formats 0x344
Evaluate expression:
Binary: 00000000 00000000 00000000 00000000 00000000 00000000 00000011 01000100
位8为跟踪标志,当前为1,代表单步执行
位8为中断标志,当前为1,代表启用中断
事件代码为8000 0003,为断点异常
事件代码为8000 0004,为单步异常,由t命令和非越过的p命令触发
针对CALL指令单步越过的p命令
是在CALL指令的下一相邻指令,设置临时软件断点
源码模式下的单步执行
通过多次设置陷阱标志实现,即多次汇编模式下的单步执行
完整命令
p|t [r] [= start_address] [count] ["command"]
[r],是否显示寄存器,缺省为显示
[= start_address],指定执行的起始地址,缺省为当前地址
[count],指定单步执行的次数,缺省为1
["command"],指定每次单步执行后的命令,缺省为空
30.11.2 单步执行到指定地址
语法
pa [r] [=start_address] stop_address
反复执行单步越过,直到stop_address
pa,step to address
ta [r] [=start_address] stop_address
反复执行单步进入,直到stop_address
ta,trace to address
实现gu,继续到上一级函数
pa $ra
$ra,代表当前函数的返回地址
被中断的情形
单步中遇到断点或异常
30.11.3 单步执行到下一个函数调用
语法
pc [r] [=start_address] [Count]
反复执行单步越过,直到CALL指令
pc,step to call
tc [r] [=start_address] [Count]
反复执行单步进入,直到CALL指令
ta,trace to call
[count],指定单步执行到函数调用的次数,缺省为1
当处理CALL指令时,当作普通单步执行
pc,执行完CALL指令
tc,进入函数
30.11.4 执行到下一分支
语法
tb [r] [=start_address] [Count]
一次执行到下一分支指令
tb,trace to branch
实现
设置好标志寄存器和MSR寄存器后,便恢复程序运行
当CPU执行到分支指令时,便报告异常停下来
注意
在安腾系统和x64系统上,tb可用于内核调试和用户态调试
在x86系统上,tb仅用于内核调试,使用ph和th可克服局限
ph|th [r] [=start_address] [Count]
分别用来单步执行和单步追踪到下一分支指令
两者处理CALL指令的方式不同
30.11.5 追踪并监视
wt(trace and watch data)
执行函数,打印统计
使用
在函数入口处执行,否则功能为单步执行
统计含义
第一部分,追踪目标
目标函数名称和返回地址
第二部分,执行路径
一次函数栈切换(CALL和RET),一次执行状态记录
第一列,当前函数执行的指令数
可能情形
[函数首指令,函数中CALL指令]
[函数首指令,函数中RET指令]
第二列,当前函数调用的指令数
= 内部函数的执行指令数 + 内部函数的调用指令数
第三列,函数调用深度
第四列,调用深度缩进+函数名称
第三部分,统计指令数和调试事件数
第四部分,统计函数的指令执行情况
调用总次数
单次最大指令执行数
单次最小指令执行数
单次平均指令执行数
第五部分,系统服务调用情况
第六部分,追踪完成后的状态
寄存器状态
当前程序指针位置
注意
使用wt追踪复杂函数或顶层函数时,由于范围广,可能需要较长的时间
为了提高效率,可以限制追踪范围
-l,最大调用深度,基于目标函数
-m,指定追踪的模块
-i,指定忽略的模块
wt命令,可被各种调试事件中断
例子
源码(wtee.cpp)
#include
#pragma warning(disable: 4996)
int GetRandom(int n)
{
int m = 2 * n, nTick;
nTick = GetTickCount();
m *= nTick;
m *= GetVersion();
return m*n;
}
int main(int argc, char* argv[])
{
int n = argc * 100;
n = GetRandom(n);
return n;
}
测试
windbg /wtee.exe
bp wtee!main
g
wt -m wtee
Tracing wtee!main to return address 00007ff7`c6d01278
5 0 [ 0] wtee!main
6 0 [ 1] KERNEL32!GetTickCountKernel32
7 6 [ 0] wtee!main
1 0 [ 1] KERNEL32!GetVersionStub
21 0 [ 1] KERNELBASE!GetVersion
15 28 [ 0] wtee!main
43 instructions were executed in 42 events (0 from other threads)
Function Name Invocations MinInst MaxInst AvgInst
KERNEL32!GetTickCountKernel32 1 6 6 6
KERNEL32!GetVersionStub 1 1 1 1
KERNELBASE!GetVersion 1 21 21 21
wtee!main 1 15 15 15
0 system calls were executed
wtee!__scrt_common_main_seh+0x124:
00007ff7`c6d01278 8bd8 mov ebx,eax
30.11.6 程序指针飞跃
调试时,可以修改程序指针
cs:$eip或cs:$rip
下一指令的地址
实现
windbg会把地址,设置到线程上下文的程序指令寄存器中
当恢复目标程序时,系统会完成线程上下文的设置
使用
g命令和单步命令,支持设置起始执行地址
寄存器命令r,直接修改程序指针寄存器
注意
由于修改了程序指针,导致部分指令重复执行或跳过
可能导致程序逻辑出错,如栈操作和对象析构操作等
30.11.7 归纳
标准命令
p step
t trace
pa step to address
ta trace to address
pc step to next call
tc trace to next call
tb trace to next branch
pt step to next return
tt trace to next return
ph step to next branch
th trace to next branch
wt trace and watch data
g go
gh go handled
gn go not handled
gu go up
注意调试目标必须是活动目标
windbg设计了元命令和扩展命令,辅助使用标准命令
连续单步跟踪时,界面更新速度频繁,影响速度和让人眼花缭乱,可暂停刷新界面
.suspend_ui
30.12 使用断点
30.12.0 概述
设断点,即埋地雷,讲究埋设的地点和时机
30.12.1 软件断点
实现
中断指令(INT3,0xCC)
运行时设置(中断指令)
中断时恢复(原本指令)
继续时,当前指令先执行后设置
验证试验
源程序(test_bp)
int main()
{
printf("please set breakpoint at start");
getchar();
return 0;
}
调试查看test_bp
windbg .\test_bp.exe
u test_bp!main
test_bp!main [g:\user\vs2015\test\test_bp\test_bp.cpp @ 8]:
00007ff7`017b1000 4883ec28 sub rsp,28h
00007ff7`017b1004 488d0d15120000 lea rcx,[test_bp!`string' (00007ff7`017b2220)]
00007ff7`017b100b e820000000 call test_bp!printf (00007ff7`017b1030)
00007ff7`017b1010 ff1562110000 call qword ptr [test_bp!_imp_getchar (00007ff7`017b2178)]
00007ff7`017b1016 33c0 xor eax,eax
00007ff7`017b1018 4883c428 add rsp,28h
00007ff7`017b101c c3 ret
00007ff7`017b101d cc int 3
bp test_bp!main
g
查看test_bp内存
使用64位WinHex
Tools > Open Memory
test_bp > test_bp.exe
查看00007ff7`017b1000附近的内存
Offset 0 1 2 3 4 5 6 7 8 9 A B C D E F
7FF7017B1000 CC 83 EC 28 48 8D 0D 15 12 00 00 E8 20 00 00 00 虄?H ?
7FF7017B1010 FF 15 62 11 00 00 33 C0 48 83 C4 28 C3 CC CC CC b 3繦兡(锰烫
7FF7017B1020 48 8D 05 01 26 00 00 C3 CC CC CC CC CC CC CC CC H & 锰烫烫烫?
可以发现运行时,原指令被替换
00007ff7`017b1000 4883ec28 sub rsp,28h
7FF7017B1000 CC 83 EC 28
中断查看test_bp
Ctrl + Break
windbg
代码恢复
winhex
4883EC28488D0D15120000E820000000
FF156211000033C04883C428C3CCCCCC
488D0501260000C3CCCCCCCCCCCCCCCC
可以发现中断时,中断指令被恢复
注意
为了避免破坏数据和代码,只能在指令首字节位置设置断点
语法
bp [id] [options] [address [passes]] ["command_string"]
address,断点地址,缺省为当前程序指针
passes,第几次遇到时触发中断,缺省为1
command_string,中断触发后执行的命令,多个命令使用分号分隔
bu [id] [options] [address [passes]] ["command_string"]
设置一个以后落实的断点
用于对一个尚未加载的模块设置断点
如调试动态加载模块的入口函数或初始化代码
bm [options] symbol_pattern [passes] ["command_string"]
设置一批断点,如同执行多次的bp和bu
支持通配符*
注意
背景,在数据区设置软件断点,会导致数据意外变化
bm命令在设置断点时,只对函数类型的匹配符号设置断点
这要求调试符号有类型信息,这通常需要私有符号,但若只有公共符号文件将报错
方法一,\a,强制匹配
bm不检查符号类型,由用户保证
方法二
为模块使用完全或DLL输出符号
options
/1
临时断点,一次命中断点
命中一次后自动删除
/p EPROCESS_ADDRESS
仅当断点事件发生在对应进程中,才中断
仅用在内核调试时
/t ETHREAD_ADDRESS
仅当断点事件发生在对应线程中,才中断
仅用在内核调试时
/c Number
仅当断点事件发生的函数调用深度,小于Number时才中断
/C Number
仅当断点事件发生的函数调用深度,大于Number时才中断
id,不设置则自动编排
30.12.2 硬件断点
定义,通过设置CPU寄存器而实现的断点
在x86 CPU中,就是DR0~DR7
特点
数量有限
可实现软件断点不具有的功能,如监视数据访问和I/O访问等
语法
ba [id] access size [options] [address [passes]] ["command_string"]
触发断点的访问方式
e,对目标内存的执行,被称为代码访问断点
r,对目标内存的读写,被称为数据访问断点
w,对目标内存的写,被称为数据访问断点
i,对目标内存的输入输出访问,被称为I/O访问断点
触发断点的目标内存
size
对于e,为1
对于其他,依赖于平台
x86,1、2、4
x64,1、2、4、8
触发条件
实际访问包含目标内存
address
需要按照size对齐
注意1
硬件断点数量有限
在恢复目标执行时才把硬件断点落实到上下文的寄存器中
所以数量超过限制时,ba不会报错,恢复执行时会报错
注意2
在初始断点触发时,不能设置硬件断点
设置会报错提示
初始断点后,系统会重新设置线程上下文,故不能设置硬件断点
建议在程序入口点或初始化代码处设置
30.12.3 条件断点
背景
基本中断条件(如地址)范围太广,导致频繁中断
希望在满足指定条件时,才中断
方法
利用中断设置命令中的命令参数
中断触发时,会先执行命令参数
不合条件则直接恢复(gc)
"command_string"
"jc (condition) 'optional_commands'; 'gc'"
".if (condition) {option_commands} .else {gc}"
condition中读取变量数据
对地址按指定长度进行解引用
poi,by,wo,dwo,qwo
帮助文档搜索,MASM Numbers and Operators
30.12.4 地址表达方法
内存地址
模块符号,module!function[+N]
源码行,`module!file:line`,注意使用重音符号
源码类方法,class__method或class::method
30.12.5 设置针对线程的断点
线程限定符 断点设置命令
~.,~#,~*,~number,~~ID
30.12.6 管理断点
显示,bl
break list
列1 断点序号,N
列2 断点状态,(e|d)[u],分别表示enable,disable,unresolved
列3 断点描述,断点地址[访问方式 访问长度]
列4 剩余穿越次数,N
列5 初始穿越次数,(N)
列6 关联的进程和线程,进程号:线程号,线程号表示为****(任意)或~NNN(指定)
列7 断点地址的符号表示
列8 关联命令,可能不存在
取消,bc 断点号
break cancel
断点号
指定单个 N
指定集合 N1,N2,N3
任意 *
范围 N-M
组合 N1,N2-N3
启用和禁用,be|bd 断点号
break enable|disable
30.13 控制进程和线程
30.13.0 概述
讨论多进程和多线程的调试知识
30.13.1 MulThrds
功能
创建线程,CreateThread
挂起线程,SuspendThread
增加其挂起计数(Suspend Count)
恢复线程,ResumeThread
降低其挂起计数(Suspend Count)
调试中断,DebugBreak
当程序处于被调试状态,调用DebugBreak会中断到调试器,否则会触发异常导致关闭
挂起计数
线程的一个自带属性
挂起计数大于0,则线程处于挂起状态(Suspended),不会被执行
挂起计数最多降低到-1
30.13.2 控制线程执行
通常情况
当进程中断到调试器时,所有线程会挂起
当调试目标中断到调试器时,windbg会对所有线程调用SuspendThread
当进程恢复时,所有线程会恢复
当调试目标恢复时,windbg会对所有线程调用ResumeThread
调试需求,独立控制各个线程的执行
方法,控制线程的挂起计数
方式一,修改挂起计数
挂起计数+1,线程限定符 n
实现,调用SuspendThread
挂起计数-1,线程限定符 m
实现,调用ResumeThread
方式二,修改线程的冻结状态(windbg为线程记录的)
调试器引擎内部,调用ThreadInfo::ChangeFreeze方法
冻结(freeze),线程限定符 f
解冻(unfreeze),线程限定符 u
实现
当调试目标恢复时,windbg只会为未冻结的线程调用ResumeThread
方式三,恢复指定线程
线程限定符 g
实现
windbg对指定线程调用ResumeThread
注意
当调试含多线程的进程时,在只恢复当个线程的情况下,执行Ctrl+Break
默认使用的远程中断线程方法会失败,超时后windbg会使用挂起方法
windbg收到Ctrl+Break命令,会在目标进程中创建一个远程线程
windbg收到新线程创建事件
打印信息,当前只有两个线程未被冻结
windbg恢复调试目标继续执行时
由于调试目标只恢复了一个线程
所以调试器不会对新建线程调用ResumeThread,新建线程也被冻结
windbg等待超时后,会强行挂起调试目标,后进入命令模式
类似的,对指定线程进行单步执行
如,线程限定符 t
相关命令
~,查看所有线程
线程序号 Id: 进程ID.线程ID Suspend: 挂起计数 Teb: ted_address (Unfrozen|Frozen)
Teb,线程环境块
30.13.3 多进程调试
增加新的调试目标
.attach [-premote RemoteOptions] AttachOptions PID
.create [-premote RemoteOptions] [-f] CommandLine
其他调试命令
查看所有的进程和线程
|*
~和~*
查看当前的进程和线程
|
进程和线程的切换
|N s
~N s
线程执行控制
~N g|t
30.14 观察栈
30.14.0 概述
当今使用的计算机系统都是基于栈架构
栈是进行函数调用的基础
30.14.1 显示栈回溯
函数栈的使用情形和内容
创建局部变量
函数调用时,参数入栈和返回地址入栈
寄存器的入栈和出栈
其他信息,如中断保护(CC)
栈回溯(stack backtrace)
从栈顶到栈底遍历栈帧,追溯函数调用过程
基于子rsp,通过代码反推父rsp,栈顶栈帧的rsp在寄存器中
函数调用栈
宏观上,是栈帧组成的栈结构
微观上,是栈数据组成的栈结构
栈回溯
通过定位栈帧sp,以分离栈帧,进而分析栈数据
栈帧,记录了一个运行中函数的调用输入、运行状态、返回地址
主要包含了(自底向上看)
其他信息
局部变量
寄存器
返回地址
栈参数
典型sp操作
初始化sp指向栈底
调用线程入口函数
入栈返回地址(0)和寄存器,创建局部变量
调用子函数
入栈栈参数、返回地址(父函数call指令的下一相邻指令的地址)、寄存器
创建局部变量
sp定位方法
从栈顶到栈底回溯
栈顶sp,反算自寄存器sp
父栈帧sp,反算自子栈帧sp
例子
int main()
{
printf("please set breakpoint at start");
getchar();
return 0;
}
k
Child-SP RetAddr Call Site
0000003c`ac33f518 00007ff8`d2925227 ntdll!ZwReadFile+0x14
0000003c`ac33f520 00007ff8`d35960ad KERNELBASE!ReadFile+0x77
0000003c`ac33f5a0 00007ff8`d35961e2 ucrtbase!_read_nolock+0x119
0000003c`ac33f640 00007ff8`d3595308 ucrtbase!_read+0xa2
0000003c`ac33f680 00007ff8`d359752e ucrtbase!common_refill_and_read_nolock+0x70
0000003c`ac33f6b0 00007ff7`017b1016 ucrtbase!fgetc+0xce
0000003c`ac33f6f0 00007ff7`017b12c8 test_bp!main+0x16 [g:\user\vs2015\test\test_bp\test_bp.cpp @ 12]
0000003c`ac33f720 00007ff8`d4337bd4 test_bp!__scrt_common_main_seh+0x124 [f:\dd\vctools\crt\vcstartup\src\startup\exe_common.inl @ 264]
0000003c`ac33f760 00007ff8`d57ace71 KERNEL32!BaseThreadInitThunk+0x14
0000003c`ac33f790 00000000`00000000 ntdll!RtlUserThreadStart+0x21
调用栈解释
每一行,描述当前线程的用户态栈上的一个栈帧
函数调用,自底向上
栈回溯过程,从子栈帧到父栈帧,直到返回地址为0
查看线程在当前栈帧的执行状态
r
rax=0000000000000006 rbx=0000003cac33f658 rcx=0000000000000054
rdx=0000000000000000 rsi=0000000000000054 rdi=0000000000000000
rip=00007ff8d57dc124 rsp=0000003cac33f518 rbp=0000000000001000
r8=0000000000000000 r9=0000000000000000 r10=000001d9fc781fc0
r11=0000003cac33f590 r12=0000000000000000 r13=0000000000001000
r14=0000000000000000 r15=000001d9fc781fc0
iopl=0 nv up ei pl zr na po nc
cs=0033 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000244
ntdll!ZwReadFile+0x14:
00007ff8`d57dc124 c3 ret
当前栈rsp
child rsp为 0000003cac33f518
当前即将返回,说明当前rsp指向返回地址
u ntdll!ZwReadFile
ntdll!NtReadFile:
00007ff8`d57dc110 4c8bd1 mov r10,rcx
00007ff8`d57dc113 b806000000 mov eax,6
00007ff8`d57dc118 f604250803fe7f01 test byte ptr [SharedUserData+0x308 (00000000`7ffe0308)],1
00007ff8`d57dc120 7503 jne ntdll!ZwReadFile+0x15 (00007ff8`d57dc125)
00007ff8`d57dc122 0f05 syscall
00007ff8`d57dc124 c3 ret
查看函数调用,发现没有栈参数
dd 0000003cac33f518
0000003c`ac33f518 d2925227 00007ff8 00001000 00000000
ln 00007ff8d2925227
(00007ff8`d29251b0) KERNELBASE!ReadFile+0x77
u KERNELBASE!ReadFile
KERNELBASE!ReadFile+0x54:
00007ff8`d2925204 4489442430 mov dword ptr [rsp+30h],r8d
00007ff8`d2925209 4c89542428 mov qword ptr [rsp+28h],r10
00007ff8`d292520e 488d442450 lea rax,[rsp+50h]
00007ff8`d2925213 4889442420 mov qword ptr [rsp+20h],rax
00007ff8`d2925218 4533c9 xor r9d,r9d
00007ff8`d292521b 4533c0 xor r8d,r8d
00007ff8`d292521e 33d2 xor edx,edx
00007ff8`d2925220 48ff1519171800 call qword ptr [KERNELBASE!_imp_NtReadFile (00007ff8`d2aa6940)]
0:000>
KERNELBASE!ReadFile+0x77:
00007ff8`d2925227 0f1f440000 nop dword ptr [rax+rax]
当前栈rsp = 返回值地址 + 8 = 0000003cac33f518 + 8 = 0000003cac33f520
查看当前栈帧的数据
栈基址通常情况下,附近的数据
栈基址 父栈帧RBP
栈基址+8*1 返回地址
栈基址+8*2 第一个栈参数
栈基址+8*N 第N个栈参数
产生的栈帧信息
Child-SP 0000003cac33f518
因为未执行"sub rsp, N",所以子栈帧RSP等于当前ESP
RetAddr 00007ff8 d2925227
Call Site ntdll!ZwReadFile+0x14
cs:rip
cs=0033
rip=00007ff8d57dc124
ntdll!ZwReadFile+0x14
u ntdll!ZwReadFile
ntdll!NtReadFile:
00007ff8`d57dc110 4c8bd1 mov r10,rcx
00007ff8`d57dc113 b806000000 mov eax,6
00007ff8`d57dc118 f604250803fe7f01 test byte ptr [SharedUserData+0x308 (00000000`7ffe0308)],1
00007ff8`d57dc120 7503 jne ntdll!ZwReadFile+0x15 (00007ff8`d57dc125)
00007ff8`d57dc122 0f05 syscall
00007ff8`d57dc124 c3 ret
cs选择子,指向的段描述符,段基址为0
00007ff8d57dc124 = 00007ff8`d57dc110 + 0x14 = ntdll!ZwReadFile+0x14
恢复和查看线程在上一栈帧的执行状态
当前栈rsp
child rsp为 0000003cac33f520
查看函数体,发现有寄存器入栈和局部变量,共0x78
u KERNELBASE!ReadFile
KERNELBASE!ReadFile:
00007ff8`d29251b0 48895c2410 mov qword ptr [rsp+10h],rbx
00007ff8`d29251b5 4c894c2420 mov qword ptr [rsp+20h],r9
00007ff8`d29251ba 56 push rsi
00007ff8`d29251bb 57 push rdi
00007ff8`d29251bc 4156 push r14
00007ff8`d29251be 4883ec60 sub rsp,60h
00007ff8`d29251c2 498bd9 mov rbx,r9
00007ff8`d29251c5 4c8bd2 mov r10,rdx
查看函数调用,发现没有栈参数
返回地址的栈地址
= child rsp + 寄存器入栈长度 + 局部变量长度
= 0000003cac33f520 + 78
= 0000003cac33f598
返回地址的值
dd 0000003cac33f598
0000003c`ac33f598 d35960ad 00007ff8 00000000 00000000
函数调用
ln 00007ff8d35960ad
(00007ff8`d3595f94) ucrtbase!_read_nolock+0x119 | (00007ff8`d3596140) ucrtbase!_read
u ucrtbase!_read_nolock+0x110
ucrtbase!_read_nolock+0x110:
00007ff8`d35960a4 498bd7 mov rdx,r15
00007ff8`d35960a7 ff15b3000a00 call qword ptr [ucrtbase!_imp_ReadFile (00007ff8`d3636160)]
00007ff8`d35960ad 85c0 test eax,eax
当前栈rsp = 返回值地址 + 8 = 0000003cac33f598 + 8 = 0000003cac33f5a0
查看上一栈帧的数据
当前栈,包含寄存器入栈和局部变量,没有栈参数
产生的栈帧信息
Child-SP 0000003cac33f520
RetAddr 00007ff8d35960ad
Call Site KERNELBASE!ReadFile+0x77
子函数返回地址
恢复和查看线程在上二栈帧的执行状态
当前栈rsp
child rsp为 0000003cac33f5a0
查看函数体,发现有寄存器入栈和局部变量,共0x98
u ucrtbase!_read_nolock
ucrtbase!_read_nolock:
00007ff8`d3595f94 4889542410 mov qword ptr [rsp+10h],rdx
00007ff8`d3595f99 53 push rbx
00007ff8`d3595f9a 55 push rbp
00007ff8`d3595f9b 57 push rdi
00007ff8`d3595f9c 4154 push r12
00007ff8`d3595f9e 4155 push r13
00007ff8`d3595fa0 4156 push r14
00007ff8`d3595fa2 4157 push r15
0:000> u
ucrtbase!_read_nolock+0x10:
00007ff8`d3595fa4 4883ec60 sub rsp,60h
查看函数调用,发现没有栈参数
返回地址的栈地址
= child rsp + 寄存器入栈长度 + 局部变量长度
= 0000003cac33f5a0 + 98
= 0000003cac33f638
返回地址的值
dd 0000003cac33f638
0000003c`ac33f638 d35961e2 00007ff8 00001000 00000000
函数调用
ln 00007ff8d35961e2
(00007ff8`d3596140) ucrtbase!_read+0xa2 | (00007ff8`d3596230) ucrtbase!_acrt_stdio_free_buffer_nolock
u ucrtbase!_read+0x92
ucrtbase!_read+0x92:
00007ff8`d35961d2 01741145 add dword ptr [rcx+rdx+45h],esi
00007ff8`d35961d6 8bc7 mov eax,edi
00007ff8`d35961d8 498bd5 mov rdx,r13
00007ff8`d35961db 8bce mov ecx,esi
00007ff8`d35961dd e8b2fdffff call ucrtbase!_read_nolock (00007ff8`d3595f94)
00007ff8`d35961e2 8bd8 mov ebx,eax
00007ff8`d35961e4 eb13 jmp ucrtbase!_read+0xb9 (00007ff8`d35961f9)
00007ff8`d35961e6 e8c5adffff call ucrtbase!_errno (00007ff8`d3590fb0)
当前栈rsp = 返回值地址 + 8 = 0000003cac33f638 + 8 = 0000003cac33f640
查看上二栈帧的数据
当前栈,包含寄存器入栈和局部变量,没有栈参数
产生的栈帧信息
Child-SP 0000003cac33f5a0
RetAddr 00007ff8d35961e2
Call Site ucrtbase!_read_nolock+0x119
子函数返回地址
列含义
第一列,Child-SP,子栈帧开始的栈地址
相邻SP,之间构成一个栈帧
第二列,RetAddr,返回地址
栈帧开始后,显示栈参数,然后是返回地址
第三列,Call Site,函数名+偏移
栈顶,当前执行的函数位置,源自rip
ln rip
栈中,调用子函数的函数位置,源自子函数的返回地址
ln child_ret
相关命令
k,栈查看基本命令
kL,不显示源码行数
kb,显示三个栈参数,与调用协议相关
kp,显示所有栈参数,包括类型、名称、数值,需要有完全的调试符号(私有符号)
kP,类似kp,但每个栈参数各占一行
kv,在kb的基础上,显示FPO(frame pointer omission,栈指针省略)和调用协议
kn,会显示栈帧的序号
kf,会显示栈帧间sp差值,或每个栈帧的长度
30.14.2 观察栈变量
dbgee.exe
TCHAR g_szGlobal[] = _T("A global var.");
int _tmain(int argc, _TCHAR* argv[])
{
int nRet = 0;
TCHAR szBuffer[MAX_PATH];
printf("dbgee enters main.\n");
if (argc == 1)
{
*(int *)0 = 1;
printf("test\n");
nRet = -1;
}
for (int i = 0; i u
dbgee!wmain+0x29 [g:\user\vs2015\test\dbgee\dbgee.cpp @ 10]:
00007ff6`f3c31029 8bf1 mov esi,ecx
00007ff6`f3c3102b 33db xor ebx,ebx
00007ff6`f3c3102d 488d0dec110000 lea rcx,[dbgee!`string' (00007ff6`f3c32220)]
00007ff6`f3c31034 8beb mov ebp,ebx
00007ff6`f3c31036 488bfa mov rdi,rdx
00007ff6`f3c31039 e862010000 call dbgee!printf (00007ff6`f3c311a0)
00007ff6`f3c3103e 83fe01 cmp esi,1
00007ff6`f3c31041 7516 jne dbgee!wmain+0x59 (00007ff6`f3c31059)
0:000>
dbgee!wmain+0x43 [g:\user\vs2015\test\dbgee\dbgee.cpp @ 18]:
00007ff6`f3c31043 488d0dea110000 lea rcx,[dbgee!`string' (00007ff6`f3c32234)]
00007ff6`f3c3104a 89342500000000 mov dword ptr [0],esi
00007ff6`f3c31051 e84a010000 call dbgee!printf (00007ff6`f3c311a0)
00007ff6`f3c31056 83cdff or ebp,0FFFFFFFFh
00007ff6`f3c31059 85f6 test esi,esi
00007ff6`f3c3105b 7e2e jle dbgee!wmain+0x8b (00007ff6`f3c3108b)
00007ff6`f3c3105d 0f1f00 nop dword ptr [rax]
00007ff6`f3c31060 4c8b0f mov r9,qword ptr [rdi]
0:000>
dbgee!wmain+0x63 [g:\user\vs2015\test\dbgee\dbgee.cpp @ 23]:
00007ff6`f3c31063 488d15d6110000 lea rdx,[dbgee!`string' (00007ff6`f3c32240)]
00007ff6`f3c3106a 448bc3 mov r8d,ebx
00007ff6`f3c3106d 488d4c2420 lea rcx,[rsp+20h]
00007ff6`f3c31072 e8b9000000 call dbgee!_swprintf (00007ff6`f3c31130)
00007ff6`f3c31077 488d4c2420 lea rcx,[rsp+20h]
00007ff6`f3c3107c e84f000000 call dbgee!wprintf (00007ff6`f3c310d0)
00007ff6`f3c31081 ffc3 inc ebx
00007ff6`f3c31083 488d7f08 lea rdi,[rdi+8]
0:000>
dbgee!wmain+0x87 [g:\user\vs2015\test\dbgee\dbgee.cpp @ 24]:
00007ff6`f3c31087 3bde cmp ebx,esi
00007ff6`f3c31089 7cd5 jl dbgee!wmain+0x60 (00007ff6`f3c31060)
00007ff6`f3c3108b 488d0dce110000 lea rcx,[dbgee!`string' (00007ff6`f3c32260)]
00007ff6`f3c31092 e809010000 call dbgee!printf (00007ff6`f3c311a0)
00007ff6`f3c31097 8bc5 mov eax,ebp
00007ff6`f3c31099 488b8c2430020000 mov rcx,qword ptr [rsp+230h]
00007ff6`f3c310a1 4833cc xor rcx,rsp
00007ff6`f3c310a4 e867010000 call dbgee!__security_check_cookie (00007ff6`f3c31210)
0:000> u
dbgee!wmain+0xa9 [g:\user\vs2015\test\dbgee\dbgee.cpp @ 28]:
00007ff6`f3c310a9 4c8d9c2440020000 lea r11,[rsp+240h]
00007ff6`f3c310b1 498b5b10 mov rbx,qword ptr [r11+10h]
00007ff6`f3c310b5 498b6b20 mov rbp,qword ptr [r11+20h]
00007ff6`f3c310b9 498b7328 mov rsi,qword ptr [r11+28h]
00007ff6`f3c310bd 498be3 mov rsp,r11
00007ff6`f3c310c0 5f pop rdi
00007ff6`f3c310c1 c3 ret
00007ff6`f3c310c2 cc int 3
0:000> u
dbgee!wmain+0xc3:
00007ff6`f3c310c3 cc int 3
00007ff6`f3c310c4 cc int 3
00007ff6`f3c310c5 cc int 3
00007ff6`f3c310c6 cc int 3
00007ff6`f3c310c7 cc int 3
00007ff6`f3c310c8 cc int 3
00007ff6`f3c310c9 cc int 3
00007ff6`f3c310ca cc int 3
可以发现
可以看出这里使用的_fastcall
从右至左压栈,第二一参分别存入xdx和xcx,被调清栈
nRet,使用ebp存储
szBuffer,首地址rsp+20h(000000ca5c30fc40),长度260(0x104)
[000000ca5c30fc40, 000000ca5c30fd44)
i,使用ebx存储
argc,通过rcx传入,后保存在esi中
argv,通过rdx传入,后保存在edi中
栈的初始化和收尾分析
初始化
代码
dbgee!wmain [g:\user\vs2015\test\dbgee\dbgee.cpp @ 10]:
00007ff6`f3c31000 48895c2408 mov qword ptr [rsp+8],rbx
00007ff6`f3c31005 48896c2418 mov qword ptr [rsp+18h],rbp
00007ff6`f3c3100a 4889742420 mov qword ptr [rsp+20h],rsi
00007ff6`f3c3100f 57 push rdi
00007ff6`f3c31010 4881ec40020000 sub rsp,240h
00007ff6`f3c31017 488b05e21f0000 mov rax,qword ptr [dbgee!__security_cookie (00007ff6`f3c33000)]
00007ff6`f3c3101e 4833c4 xor rax,rsp
00007ff6`f3c31021 4889842430020000 mov qword ptr [rsp+230h],rax
工作
寄存器入栈,保存即将使用到的寄存器
00007ff6`f3c31000 48895c2408 mov qword ptr [rsp+8],rbx
00007ff6`f3c31005 48896c2418 mov qword ptr [rsp+18h],rbp
00007ff6`f3c3100a 4889742420 mov qword ptr [rsp+20h],rsi
00007ff6`f3c3100f 57 push rdi
分配局部空间
00007ff6`f3c31010 4881ec40020000 sub rsp,240h
计算和存储栈帧特征值
目的
__security_cookie机制,防止栈溢出
动作
子栈帧sp与模块特征值(dbgee!__security_cookie)的异或
设计
判断栈破坏
函数调用栈是否运行正常,体现为sp值变化是否正常
栈帧初始化后的sp和栈帧收尾前的sp,应该一致
方法
初始化后,记录当前sp到当前栈帧中
收尾前,比较当前的sp和栈中记录的sp是否一致
方式
初始化后,记录当前栈帧特征值(子栈帧sp(当前sp)与模块特征值的异或),到当前栈帧中
收尾前,计算模块特征值(子栈帧sp(当前sp)与栈中存储的当前栈帧特征值的异或),判断是否一致
规律
处于异或关系的三个数,其中任意两个数,能够异或出第三个数
代码
00007ff6`f3c31017 488b05e21f0000 mov rax,qword ptr [dbgee!__security_cookie (00007ff6`f3c33000)]
00007ff6`f3c3101e 4833c4 xor rax,rsp
00007ff6`f3c31021 4889842430020000 mov qword ptr [rsp+230h],rax
分析
dq 00007ff6`f3c33000
00007ff6`f3c33000 0000db42`dc6c8ae8 ffff24bd`23937517
rsp 此时为 000000ca`5c30fc20
xor rax,rsp
hex(int('0000db42dc6c8ae8', 16)^int('000000ca5c30fc20', 16))
0xdb88805c76c8
dq 000000ca5c30fe50
000000ca`5c30fe50 0000db88`805c76c8 00000000`00000000
收尾
代码
00007ff6`f3c31099 488b8c2430020000 mov rcx,qword ptr [rsp+230h]
00007ff6`f3c310a1 4833cc xor rcx,rsp
00007ff6`f3c310a4 e867010000 call dbgee!__security_check_cookie (00007ff6`f3c31210)
dbgee!wmain+0xa9 [g:\user\vs2015\test\dbgee\dbgee.cpp @ 28]:
00007ff6`f3c310a9 4c8d9c2440020000 lea r11,[rsp+240h]
00007ff6`f3c310b1 498b5b10 mov rbx,qword ptr [r11+10h]
00007ff6`f3c310b5 498b6b20 mov rbp,qword ptr [r11+20h]
00007ff6`f3c310b9 498b7328 mov rsi,qword ptr [r11+28h]
00007ff6`f3c310bd 498be3 mov rsp,r11
00007ff6`f3c310c0 5f pop rdi
00007ff6`f3c310c1 c3 ret
00007ff6`f3c310c2 cc int 3
工作
使用栈帧特征值,判断栈破坏
计算模块特征值(子栈帧sp(当前sp)与栈中存储的当前栈帧特征值的异或),判断是否一致
代码
00007ff6`f3c31099 488b8c2430020000 mov rcx,qword ptr [rsp+230h]
00007ff6`f3c310a1 4833cc xor rcx,rsp
00007ff6`f3c310a4 e867010000 call dbgee!__security_check_cookie (00007ff6`f3c31210)
回收局部空间
00007ff6`f3c310a9 4c8d9c2440020000 lea r11,[rsp+240h]
...
00007ff6`f3c310c0 5f pop rdi
寄存器入栈,保存即将使用到的寄存器
00007ff6`f3c310b1 498b5b10 mov rbx,qword ptr [r11+10h]
00007ff6`f3c310b5 498b6b20 mov rbp,qword ptr [r11+20h]
00007ff6`f3c310b9 498b7328 mov rsi,qword ptr [r11+28h]
...
00007ff6`f3c310c0 5f pop rdi
没有私有符号,手动推断
查看栈帧内存
内存观察窗口
内存查看命令
查看代码
局部变量一般引用方式
[rsp + N]
安全Cookie值(栈帧特征值)位置
[rsp + local_length - 0x10]
获取变量指针
lea xxx, [rsp + N]
其他
遍历对象,执行命令
!for_each_local
遍历局部变量,通过@#Local访问
例子
!for_each_local dv @#Local
argc = 0n1
argv = 0x0000022c`2a78b8d0
i = 0n0
nRet = 0n0
szBuffer = wchar_t [260] "\Release\dbgee.exe"
!for_each_frame
遍历栈帧
例子
!for_each_frame dv
30.15 分析内存
30.15.1 显示内存区域
按数据格式显示内存区域
语法
d{a|b|c|d|Df|p|q|u|w|W|yb|yd|} [options] [Range]
数据格式
字节
a,ASCII码
b,字节和ASCII码
yb,二进制和字节
字
w,WORD(uint16)
W,WORD和ASCII码
u,UNICODE字符
UNICODE,一种字符的数值映射方案
utf8,一种UNICODE的编码方案,常用于保存
uint16,一种UNICODE的编码方案,常用于计算
双字
c,DWORD和ASCII码
d,DWORD(uint32)
f,单精度浮点数
yd,二进制和双字
四字
q,uint64
D,双精度浮点数
指针宽度
p
省略
最近使用的数据格式
内存范围,Range
start_address end_address
start_address {L|l}N
N,表示元素个数
end_address {L|l}-N
例子
windbg dbgee
bp dbgee!wmain
g
u dbgee!wmain+0x87
dbgee!wmain+0x87 [g:\user\vs2015\test\dbgee\dbgee.cpp @ 24]:
00007ff7`aa911087 3bde cmp ebx,esi
00007ff7`aa911089 7cd5 jl dbgee!wmain+0x60 (00007ff7`aa911060)
00007ff7`aa91108b 488d0dce110000 lea rcx,[dbgee!`string' (00007ff7`aa912260)]
00007ff7`aa911092 e809010000 call dbgee!printf (00007ff7`aa9111a0)
00007ff7`aa911097 8bc5 mov eax,ebp
00007ff7`aa911099 488b8c2430020000 mov rcx,qword ptr [rsp+230h]
00007ff7`aa9110a1 4833cc xor rcx,rsp
00007ff7`aa9110a4 e867010000 call dbgee!__security_check_cookie (00007ff7`aa911210)
db 00007ff7`aa912260
00007ff7`aa912260 64 62 67 65 65 20 65 78-69 74 73 2e 0a 00 00 00 dbgee exits.....
00007ff7`aa912270 00 00 00 00 8e f6 42 60-00 00 00 00 02 00 00 00 ......B`........
00007ff7`aa912280 42 00 00 00 74 23 00 00-74 17 00 00 00 00 00 00 B...t#..t.......
00007ff7`aa912290 8e f6 42 60 00 00 00 00-0c 00 00 00 14 00 00 00 ..B`............
00007ff7`aa9122a0 b8 23 00 00 b8 17 00 00-00 00 00 00 8e f6 42 60 .#............B`
00007ff7`aa9122b0 00 00 00 00 0d 00 00 00-6c 02 00 00 cc 23 00 00 ........l....#..
00007ff7`aa9122c0 cc 17 00 00 00 00 00 00-8e f6 42 60 00 00 00 00 ..........B`....
00007ff7`aa9122d0 0e 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
30.15.2 显示字符串
简单字符串
da 以0结尾的ASCII字符串的地址
du 以0结尾的UNICODE字符串的地址
ntdll!_STRING变量
ds 变量地址
ntdll!_UNICODE_STRING变量
dS 变量地址
例子
da 00007ff7`aa912260
00007ff7`aa912260 "dbgee exits.."
30.15.3 显示数据类型
符号列举
dt [module_name!]symbol
结果可能多个
支持通配符*
符号显示
dt [options] [module_name!]symbol
结果唯一
省略module_name,则自动搜索
symbol
数据类型及其typedef别名、全局变量、静态变量、函数
options
-rN,显示数据类型时,指定显示深度
-ny name,显示数据类型时,显示name开头的字段
变量显示
dt [options] [module_name!]symbol address
按指定数据类型,解释变量
链表遍历显示
例子
dt dbgee!wmain
wmain int (
int argc = 1,
wchar_t** argv = 00000254`afbddcc0 )
dt dbgee!g_szGlobal
Symbol dbgee!g_szGlobal not found.
由于生成的是Release版的程序,同时全局变量g_szGlobal定义了但并未使用,导致其未被生成
尝试使用Debug版本程序调试
dt dbgee!g_szGlobal
[14] "A global var."
du dbgee!g_szGlobal
00007ff6`1994c000 "A global var."
!address dbgee!g_szGlobal
Usage: Image
Allocation Base: 00007ff6`19930000
Base Address: 00007ff6`1994c000
End Address: 00007ff6`1994d000
Region Size: 00000000`00001000
Type: 01000000 MEM_IMAGE
State: 00001000 MEM_COMMIT
Protect: 00000004 PAGE_READWRITE
More info: lmv m dbgee
More info: !lmi dbgee
More info: ln 0x7ff61994c000
30.15.4 搜索内存
在指定范围内,搜索字符串
s-[[Flags]]sa|su Range
Range,内存范围
sa|su,搜索ASCII字符串或UNICODE字符串
Flags
lN,指定最小长度
在指定范围内,搜索与指定对象类型相同的对象
s-[[Flags]]v Range Object
Object,含虚函数表的使用面向对象语言编写的类型的对象
在指定范围内,搜索数组
s[-[[Flags]]Type] Range data_array
Type,数组元素类型
b字节,w字,d双字,q四字,aASCII字符,uUNICODE字符
默认为b
data_array,数组内容
例子
对于 printf("dbgee exits.\n");
字符串"dbgee exits.\n",作为字符串常量,放在代码段中
lm
start end module name
00007ff7`aa910000 00007ff7`aa917000 dbgee C (private pdb symbols) G:\user\vs2015\Test\x64\Release\dbgee.pdb
s-a 00007ff7`aa910000 00007ff7`aa917000 "dbgee exits."
00007ff7`aa912260 64 62 67 65 65 20 65 78-69 74 73 2e 0a 00 00 00 dbgee exits.....
!for_each_module s-a @#Base @#End "dbgee exits.\n"
00007ff7`aa912260 64 62 67 65 65 20 65 78-69 74 73 2e 0a 00 00 00 dbgee exits.....
!for_each_XXX命令
!for_each_frame
!for_each_local
!for_each_module
!for_each_process
!for_each_thread
30.15.5 修改内存
写内存
内存区间
待写入数据
写字符串
e{a|u|za|zu} address "string"
a|u,按照ASCII或UNICODE方式写入,结尾不加0
za|zu,按照ASCII或UNICODE方式写入,结尾要加0
内存区间,[address, address+字符串实际字节长度)
待写入数据,"string"
例子
da 00007ff7`aa912260
00007ff7`aa912260 "dbgee exits.."
eza 00007ff7`aa912260 "test1 exits.\n"
^ Memory access error in 'eza 00007ff7`aa912260 "test1 exits.
'
错误,试图修改只读的代码段
p
u dbgee!wmain+0x63
dbgee!wmain+0x63 [g:\user\vs2015\test\dbgee\dbgee.cpp @ 23]:
00007ff7`aa911063 488d15d6110000 lea rdx,[dbgee!`string' (00007ff7`aa912240)]
00007ff7`aa91106a 448bc3 mov r8d,ebx
00007ff7`aa91106d 488d4c2420 lea rcx,[rsp+20h]
00007ff7`aa911072 e8b9000000 call dbgee!_swprintf (00007ff7`aa911130)
00007ff7`aa911077 488d4c2420 lea rcx,[rsp+20h]
00007ff7`aa91107c e84f000000 call dbgee!wprintf (00007ff7`aa9110d0)
00007ff7`aa911081 ffc3 inc ebx
00007ff7`aa911083 488d7f08 lea rdi,[rdi+8]
db [rsp+20h]
000000dc`15b8f830 5c 00 52 00 65 00 6c 00-65 00 61 00 73 00 65 00 \.R.e.l.e.a.s.e.
000000dc`15b8f840 5c 00 64 00 62 00 67 00-65 00 65 00 2e 00 65 00 \.d.b.g.e.e...e.
000000dc`15b8f850 78 00 65 00 00 00 00 00-a0 00 00 00 00 00 00 00 x.e.............
000000dc`15b8f860 00 00 00 00 00 00 00 00-50 f9 b8 15 dc 00 00 00 ........P.......
000000dc`15b8f870 a0 84 be af 54 02 00 00-50 35 00 00 00 00 00 00 ....T...P5......
000000dc`15b8f880 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
000000dc`15b8f890 30 95 be af 54 02 00 00-00 00 00 00 00 00 00 00 0...T...........
000000dc`15b8f8a0 00 00 00 00 00 00 00 00-cd 07 ea bf fe 7f 00 00 ................
da [rsp+20h]
000000dc`15b8f830 "\"
eza [rsp+20h] "write data in szBuffer"
db [rsp+20h]
000000dc`15b8f830 77 72 69 74 65 20 64 61-74 61 20 69 6e 20 73 7a write data in sz
000000dc`15b8f840 42 75 66 66 65 72 00 00-65 00 65 00 2e 00 65 00 Buffer..e.e...e.
000000dc`15b8f850 78 00 65 00 00 00 00 00-a0 00 00 00 00 00 00 00 x.e.............
000000dc`15b8f860 00 00 00 00 00 00 00 00-50 f9 b8 15 dc 00 00 00 ........P.......
000000dc`15b8f870 a0 84 be af 54 02 00 00-50 35 00 00 00 00 00 00 ....T...P5......
000000dc`15b8f880 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
000000dc`15b8f890 30 95 be af 54 02 00 00-00 00 00 00 00 00 00 00 0...T...........
000000dc`15b8f8a0 00 00 00 00 00 00 00 00-cd 07 ea bf fe 7f 00 00 ................
da [rsp+20h]
000000dc`15b8f830 "write data in szBuffer"
写数组
e{b|w|d|p|q|D|f} address data_array
b字节,w字,d双字,p指针长度,q四字,D双精度浮点,f单精度浮点
例子
ed [rsp+20h] 1 2 3 4 5
交互式写字符串和数组
30.15.6 使用物理内存地址
命令,虚拟地址相关命令前加!
条件,内核模式
30.15.7 观察内存属性
!address address
显示指定内存区的属性
含义
行1,Usage,内存区的用途
VAR,虚拟分配块、SBH堆、自定义分配器分配的内存、其他分类的区域
Free,未被预留的内存区域
Image,存储可执行文件映像
Stack,函数栈
Teb,thread environment blocks (TEBs)
Peb,process environment block (PEB)
Heap,堆
PageHeap,整页堆(full-page heap)
CSR,CSR shared memory
Actx,激活上下文数据的内存
NLS,用于National Language Support (NLS) tables
FileMap,内存映射文件
行2,Allocation Base,较大内存区的开始地址
行3,Base Address,较小内存区的开始地址
行4,End Address,较小内存区的结束地址
行5,Region Size,内存区大小
行6,Type,内存区的类型
MEM_IMAGE,映射可执行文件映像
MEM_MAPPED,映射其他类型文件
MEM_PRIVATE,非映射私有内存,不共享
行7,State,内存区的状态
MEM_COMMIT,已提交
MEM_FREE,释放的内存,含未保留的内存
MEM_RESERVE,保留的内存
行8,Protect,内存区的访问保护
PAGE_NOACCESS,不可访问
PAGE_READONLY,只读
PAGE_READWRITE,可读可写
PAGE_WRITECOPY,写时会复制
PAGE_EXECUTE,可执行,不可读写
PAGE_EXECUTE_READ,可执行,只读
PAGE_EXECUTE_READWRITE,可执行,可读可写
PAGE_EXECUTE_WRITECOPY,可执行写时会复制
PAGE_GUARD,哨兵页
PAGE_NOCACHE,读不可缓存
PAGE_WRITECOMBINE,启用了写联合访问
例子
!address dbgee!wmain
Usage: Image
Allocation Base: 00007ff7`aa910000
Base Address: 00007ff7`aa911000
End Address: 00007ff7`aa912000
Region Size: 00000000`00001000
Type: 01000000 MEM_IMAGE
State: 00001000 MEM_COMMIT
Protect: 00000020 PAGE_EXECUTE_READ
More info: lmv m dbgee
More info: !lmi dbgee
More info: ln 0x7ff7aa911000
!address
显示当前进程所有内存区的属性
!address -summary
显示当前进程所有内存区的统计
其他类似命令
显示指定内存区的属性
!vprot dbgee!wmain
BaseAddress: 00007ff7aa911000
AllocationBase: 00007ff7aa910000
AllocationProtect: 00000080 PAGE_EXECUTE_WRITECOPY
RegionSize: 0000000000001000
State: 00001000 MEM_COMMIT
Protect: 00000020 PAGE_EXECUTE_READ
Type: 01000000 MEM_IMAGE
显示当前进程所有内存区的属性
!vadump
30.16 遍历链表
30.16.0 背景
Windows操作系统中,很多重要信息使用链表方式组织
查看链表类型
dt ntdll!*List*
30.16.1 结构定义
链表节点组成
链接结构,用于节点连接
链接结构中的指针,指向下一链接结构,而非指向链表节点
负载,用于数据保存
链表分类
双向链表
链接结构
dt ntdll!_LIST_ENTRY
+0x000 Flink : Ptr64 _LIST_ENTRY
+0x008 Blink : Ptr64 _LIST_ENTRY
单项链表
链接结构
dt ntdll!_SINGLE_LIST_ENTRY
+0x000 Next : Ptr64 _SINGLE_LIST_ENTRY
30.16.2 双向链表示例
Windows内核使用双向链表,管理所有进程的EPROCESS结构
30.16.3 单项链表示例
TEB结构使用单向链表,保存异常处理器
遍历单向链表
dt 链表节点类型 -l 下一节点地址的符号表示 头结点地址
例子
dt -r2 _PEB
dbgee!_PEB
+0x000 Reserved1 : [2] UChar
+0x002 BeingDebugged : UChar
+0x003 Reserved2 : [1] UChar
+0x008 Reserved3 : [2] Ptr64 Void
+0x018 Ldr : Ptr64 _PEB_LDR_DATA
+0x000 Reserved1 : [8] UChar
+0x008 Reserved2 : [3] Ptr64 Void
+0x020 InMemoryOrderModuleList : _LIST_ENTRY
+0x000 Flink : Ptr64 _LIST_ENTRY
+0x008 Blink : Ptr64 _LIST_ENTRY
!peb
PEB at 000000dc15cbf000
InheritedAddressSpace: No
ReadImageFileExecOptions: No
BeingDebugged: Yes
ImageBaseAddress: 00007ff7aa910000
Ldr 00007ffebffc53c0
Ldr.Initialized: Yes
Ldr.InInitializationOrderModuleList: 00000254afbd4f80 . 00000254afbdb6b0
Ldr.InLoadOrderModuleList: 00000254afbd5130 . 00000254afbdb690
Ldr.InMemoryOrderModuleList: 00000254afbd5140 . 00000254afbdb6a0
Base TimeStamp Module
7ff7aa910000 6042f68e Mar 06 11:27:10 2021 G:\user\vs2015\Test\x64\Release\dbgee.exe
7ffebfe60000 a52b7c6a Oct 24 03:22:18 2057 C:\Windows\SYSTEM32\ntdll.dll
7ffebe160000 2e3dfcea Aug 02 15:59:38 1994 C:\Windows\System32\KERNEL32.DLL
7ffebd010000 1a30e11b Dec 05 02:36:11 1983 C:\Windows\System32\KERNELBASE.dll
7ffebdf20000 96f604e5 Apr 05 01:18:29 2050 C:\Windows\System32\ADVAPI32.DLL
7ffebf6d0000 f5bdefd7 Aug 25 16:27:03 2100 C:\Windows\System32\msvcrt.dll
7ffebee30000 f7e420f7 Oct 17 00:23:19 2101 C:\Windows\System32\sechost.dll
7ffebef50000 0530c620 Oct 05 05:35:28 1972 C:\Windows\System32\RPCRT4.dll
7ffebce70000 5cbddb81 Apr 22 23:19:29 2019 C:\Windows\System32\ucrtbase.dll
7ffea83c0000 5c82fae2 Mar 09 07:29:38 2019 C:\Windows\SYSTEM32\VCRUNTIME140.dll
dt _PEB_LDR_DATA -l InMemoryOrderModuleList.Flink poi(000000dc15cbf018)
dbgee!_PEB_LDR_DATA
InMemoryOrderModuleList.Flink at 0x7ffebffc53c0
---------------------------------------------
+0x000 Reserved1 : [8] "X"
+0x008 Reserved2 : [3] (null)
+0x020 InMemoryOrderModuleList : [ 0x00000254`afbd5140 - 0x254`afbdb6a0 ]
+0x000 Flink : 0x00000254`afbd5140 _LIST_ENTRY [ 0x00000254`afbd4f70 - 0x7ffe`bffc53e0 ]
+0x008 Blink : 0x00000254`afbdb6a0 _LIST_ENTRY [ 0x00007ffe`bffc53e0 - 0x254`afbdb7f0 ]
InMemoryOrderModuleList.Flink at 0x254afbd5120
---------------------------------------------
+0x000 Reserved1 : [8] ""
+0x008 Reserved2 : [3] 0x3000afdb`fe1e8a7e Void
+0x020 InMemoryOrderModuleList : [ 0x00000254`afbd4f70 - 0x7ffe`bffc53e0 ]
+0x000 Flink : 0x00000254`afbd4f70 _LIST_ENTRY [ 0x00000254`afbd5690 - 0x254`afbd5140 ]
+0x008 Blink : 0x00007ffe`bffc53e0 _LIST_ENTRY [ 0x00000254`afbd5140 - 0x254`afbdb6a0 ]
InMemoryOrderModuleList.Flink at 0x254afbd4f50
---------------------------------------------
+0x000 Reserved1 : [8] "???"
+0x008 Reserved2 : [3] 0x3000afd9`fe1e8a7e Void
+0x020 InMemoryOrderModuleList : [ 0x00000254`afbd5690 - 0x254`afbd5140 ]
+0x000 Flink : 0x00000254`afbd5690 _LIST_ENTRY [ 0x00000254`afbd5d50 - 0x254`afbd4f70 ]
+0x008 Blink : 0x00000254`afbd5140 _LIST_ENTRY [ 0x00000254`afbd4f70 - 0x7ffe`bffc53e0 ]
30.16.4 DI命令
dl[b] Address MaxCount Size
Address,链表起始地址,指向_LIST_ENTRY或_SINGLE_LIST_ENTRY
MaxCount,链表节点最大个数
Size,链表节点负载大小(单位为指针长度),负载位于链接结构后
例子
dl 0x7ffebffc53e0 5 2
00007ffe`bffc53e0 00000254`afbd5140 00000254`afbdb6a0
00000254`afbd5140 00000254`afbd4f70 00007ffe`bffc53e0
00000254`afbd4f70 00000254`afbd5690 00000254`afbd5140
00000254`afbd5690 00000254`afbd5d50 00000254`afbd4f70
00000254`afbd5d50 00000254`afbd58e0 00000254`afbd5690
30.16.5
!list -t [Module!]Type.Field -x "Commands" [-a "Arguments"] [Options] StartAddress
!list " -t [Module!]Type.Field -x \"Commands\" [-a \"Arguments\"] [Options] StartAddress "
-t [Module!]Type.Field
描述链表节点类型,[Module!]Type
描述链接字段,Field
-x "Commands" [-a "Arguments"]
描述对每个节点的操作命令
伪寄存器$extret表示当前节点的地址
[Options]
-e,回显对每个节点执行的操作
-m N,遍历的最大节点数目
StartAddress
链接头节点的地址
例子
!list -t _PEB_LDR_DATA.InMemoryOrderModuleList.Flink -x "db @$extret l40" -e -m3 poi(000000dc15cbf018)
db @$extret l40
00007ffe`bffc53c0 58 00 00 00 01 00 00 00-00 00 00 00 00 00 00 00 X...............
00007ffe`bffc53d0 30 51 bd af 54 02 00 00-90 b6 bd af 54 02 00 00 0Q..T.......T...
00007ffe`bffc53e0 40 51 bd af 54 02 00 00-a0 b6 bd af 54 02 00 00 @Q..T.......T...
00007ffe`bffc53f0 80 4f bd af 54 02 00 00-b0 b6 bd af 54 02 00 00 .O..T.......T...
db @$extret l40
00000254`afbd5120 00 00 00 00 00 00 00 00-7e 8a 1e fe db af 00 30 ........~......0
00000254`afbd5130 60 4f bd af 54 02 00 00-d0 53 fc bf fe 7f 00 00 `O..T....S......
00000254`afbd5140 70 4f bd af 54 02 00 00-e0 53 fc bf fe 7f 00 00 pO..T....S......
00000254`afbd5150 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
db @$extret l40
00000254`afbd4f50 ee fe ee fe ee fe ee fe-7e 8a 1e fe d9 af 00 30 ........~......0
00000254`afbd4f60 80 56 bd af 54 02 00 00-30 51 bd af 54 02 00 00 .V..T...0Q..T...
00000254`afbd4f70 90 56 bd af 54 02 00 00-40 51 bd af 54 02 00 00 [email protected]...
00000254`afbd4f80 60 5d bd af 54 02 00 00-f0 53 fc bf fe 7f 00 00 `]..T....S......
30.17 调用目标程序的函数
30.17.1 调用实例
windbg dbgee.exe
bp wmain
g
?poi(rdx)
*** ERROR: Symbol file could not be found. Defaulted to export symbols for C:\Windows\System32\ADVAPI32.DLL -
Evaluate expression: 2450935962848 = 0000023a`a72adce0
从字符串数组argv中,获取第一个字符串的地址
即argv[0]的值,*(argv+0),poi(argv)
由于argv存放在rdx,由于_fastcall调用约定
windbg使用C++表达式评估器,来解析函数参数
.call dbgee!wprintf(0x23aa72adce0)
Value unavailable error for _Format
Value unavailable error for _Result
Value unavailable error for _Stream
Value unavailable error for _Format
Thread is set up for call, 'g' will execute.
WARNING: This can have serious side-effects,
including deadlocks and corruption of the debuggee.
设置函数调用
~.g
System 0: 3 of 4 threads are frozen
System 0: 3 of 4 threads were frozen
.call returns:
int 0n9
dbgee!wmain:
00007ff7`aa911000 48895c2408 mov qword ptr [rsp+8],rbx ss:000000ee`bdbff780=0000000000000000
执行函数调用
30.17.2 工作原理
方法
修改线程的执行环境,使调用指定函数
调用完成后,恢复线程的执行环境
方式
在栈顶写入,函数返回后执行的代码
用于再次中断到调试器,以恢复线程的执行环境
在栈顶建立新栈帧
为子函数开辟空间
入栈参数和返回地址(指向新写入的代码)
修改程序指针,指向指定函数入口
例子
.call执行前
k
Child-SP RetAddr Call Site
000000ee`bdbff778 00007ff7`aa911438 dbgee!wmain [g:\user\vs2015\test\dbgee\dbgee.cpp @ 10]
000000ee`bdbff780 00007ffe`be177bd4 dbgee!__scrt_common_main_seh+0x124 [f:\dd\vctools\crt\vcstartup\src\startup\exe_common.inl @ 264]
000000ee`bdbff7c0 00007ffe`bfecce71 KERNEL32!BaseThreadInitThunk+0x14
000000ee`bdbff7f0 00000000`00000000 ntdll!RtlUserThreadStart+0x21
r
rax=0000023aa72b3c00 rbx=00007ffebcf5b590 rcx=0000000000000001
rdx=0000023aa72adcd0 rsi=0000000000000000 rdi=00007ffebcf5c000
rip=00007ff7aa911000 rsp=000000eebdbff778 rbp=0000000000000000
r8=0000023aa72b3c00 r9=00007ffebd031ec0 r10=0000000000000013
r11=0000023aa72a8040 r12=0000000000000000 r13=0000000000000000
r14=0000000000000000 r15=0000000000000000
iopl=0 nv up ei pl nz na po nc
cs=0033 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000206
dbgee!wmain:
00007ff7`aa911000 48895c2408 mov qword ptr [rsp+8],rbx ss:000000ee`bdbff780=0000000000000000
db esp
000000ee`bdbff778 38 14 91 aa f7 7f 00 00-00 00 00 00 00 00 00 00 8...............
000000ee`bdbff788 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
000000ee`bdbff798 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
000000ee`bdbff7a8 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
000000ee`bdbff7b8 d4 7b 17 be fe 7f 00 00-00 00 00 00 00 00 00 00 .{..............
000000ee`bdbff7c8 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
000000ee`bdbff7d8 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
000000ee`bdbff7e8 71 ce ec bf fe 7f 00 00-00 00 00 00 00 00 00 00 q...............
.call dbgee!wprintf(0x23aa72adce0)
k
Child-SP RetAddr Call Site
000000ee`bdbff748 000000ee`bdbff770 dbgee!wprintf [c:\program files (x86)\windows kits\10\include\10.0.10150.0\ucrt\corecrt_wstdio.h @ 609]
000000ee`bdbff750 0000023a`a72adce0 0xee`bdbff770
000000ee`bdbff758 0000023a`a72adcd0 0x23a`a72adce0
000000ee`bdbff760 0000023a`a72b3c00 0x23a`a72adcd0
000000ee`bdbff768 00007ffe`bd031ebf 0x23a`a72b3c00
000000ee`bdbff770 cccccccc`ccfdebcc KERNELBASE!BasepLoadLibraryAsDataFileInternal+0x6bf
000000ee`bdbff778 00007ff7`aa911438 0xcccccccc`ccfdebcc
000000ee`bdbff780 00007ffe`be177bd4 dbgee!__scrt_common_main_seh+0x124 [f:\dd\vctools\crt\vcstartup\src\startup\exe_common.inl @ 264]
000000ee`bdbff7c0 00007ffe`bfecce71 KERNEL32!BaseThreadInitThunk+0x14
000000ee`bdbff7f0 00000000`00000000 ntdll!RtlUserThreadStart+0x21
r
rax=0000023aa72b3c00 rbx=00007ffebcf5b590 rcx=0000023aa72adce0
rdx=0000023aa72adcd0 rsi=0000000000000000 rdi=00007ffebcf5c000
rip=00007ff7aa9110d0 rsp=000000eebdbff748 rbp=0000000000000000
r8=0000023aa72b3c00 r9=00007ffebd031ec0 r10=0000000000000013
r11=0000023aa72a8040 r12=0000000000000000 r13=0000000000000000
r14=0000000000000000 r15=0000000000000000
iopl=0 nv up ei pl nz na po nc
cs=0033 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000206
dbgee!wprintf:
00007ff7`aa9110d0 48894c2408 mov qword ptr [rsp+8],rcx ss:000000ee`bdbff750=0000023aa72adce0
db esp
000000ee`bdbff748 70 f7 bf bd ee 00 00 00-e0 dc 2a a7 3a 02 00 00 p.........*.:...
000000ee`bdbff758 d0 dc 2a a7 3a 02 00 00-00 3c 2b a7 3a 02 00 00 ..*.:....<+.:...
000000ee`bdbff768 c0 1e 03 bd fe 7f 00 00-cc eb fd cc cc cc cc cc ................
000000ee`bdbff778 38 14 91 aa f7 7f 00 00-00 00 00 00 00 00 00 00 8...............
000000ee`bdbff788 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
000000ee`bdbff798 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
000000ee`bdbff7a8 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
000000ee`bdbff7b8 d4 7b 17 be fe 7f 00 00-00 00 00 00 00 00 00 00 .{..............
查看变化
栈变化
db esp
000000ee`bdbff748 70 f7 bf bd ee 00 00 00-e0 dc 2a a7 3a 02 00 00 p.........*.:...
000000ee`bdbff758 d0 dc 2a a7 3a 02 00 00-00 3c 2b a7 3a 02 00 00 ..*.:....<+.:...
000000ee`bdbff768 c0 1e 03 bd fe 7f 00 00-cc eb fd cc cc cc cc cc ................
dq esp
000000ee`bdbff748 000000ee`bdbff770 0000023a`a72adce0
000000ee`bdbff758 0000023a`a72adcd0 0000023a`a72b3c00
000000ee`bdbff768 00007ffe`bd031ec0 cccccccc`ccfdebcc
新栈帧
返回地址,000000ee`bdbff770
参数,放在rcx中
为子函数开辟空间
u dbgee!wprintf
dbgee!wprintf [c:\program files (x86)\windows kits\10\include\10.0.10150.0\ucrt\corecrt_wstdio.h @ 609]:
00007ff7`aa9110d0 48894c2408 mov qword ptr [rsp+8],rcx
00007ff7`aa9110d5 4889542410 mov qword ptr [rsp+10h],rdx
00007ff7`aa9110da 4c89442418 mov qword ptr [rsp+18h],r8
00007ff7`aa9110df 4c894c2420 mov qword ptr [rsp+20h],r9
长度0x20
查看函数返回后的执行代码
u 000000ee`bdbff770
000000ee`bdbff770 cc int 3
000000ee`bdbff771 ebfd jmp 000000ee`bdbff770
000000ee`bdbff773 cc int 3
000000ee`bdbff773 cc int 3
000000ee`bdbff774 cc int 3
000000ee`bdbff775 cc int 3
000000ee`bdbff776 cc int 3
000000ee`bdbff777 cc int 3
一共八字节,以对齐
寄存器
程序指针,rip
原
rip=00007ff7aa911000
dbgee!wmain:
00007ff7`aa911000 48895c2408 mov qword ptr [rsp+8],rbx ss:000000ee`bdbff780=0000000000000000
新
rip=00007ff7aa9110d0
dbgee!wprintf:
00007ff7`aa9110d0 48894c2408 mov qword ptr [rsp+8],rcx ss:000000ee`bdbff750=0000023aa72adce0
栈指针,rsp
执行指定函数
~.g
System 0: 3 of 4 threads are frozen
System 0: 3 of 4 threads were frozen
.call returns:
int 0n9
dbgee!wmain:
00007ff7`aa911000 48895c2408 mov qword ptr [rsp+8],rbx ss:000000ee`bdbff780=0000000000000000
查看变化
k,恢复
r,恢复
db esp,恢复
30.17.3 限制条件和常见错误
.call命令只能调试,用户态活动目标
被调函数,有私有符号,即包含类型信息的函数符号
每个线程一次只能设置一个函数,设置后,要么取消,要么执行
.call /C,清除当前线程设置的函数
30.18 命令程序
30.18.0 概述
调试器命令程序,Debuger Command Program
概念,调试器命令集合,形成的文件
组成
调试器命令,标准命令、元命令、扩展命令
流程控制符号
变量
30.18.1 流程控制符号(control flow token)
分支
.if, .else, .elseif
循环
.do, .while, !for_each_xxx, .break, .continue
异常
.catch,捕获异常
.leave,退出.catch块
代码块
.block
30.18.2 变量
分类
伪寄存器
自动的伪寄存器
由windbg定义和赋值
如$ip
用户赋值的伪寄存器
$t0~$t19
指定为整数类型并赋值
r $txx = expression
指定为值的类型并赋值
r? $txx = expression
别名
用户定义的别名
as,增加别名
ad,删除别名
自动别名
$ntsym、$CurrentDump等
固定名称的别名
$u0~$u9
定义,r $.ux = expression
引用
伪寄存器
MASM表达式评估器,@$xxx或$xxx
C++表达式评估器,@$xxx
别名
${$xxx}
30.18.3 命令程序示例
代码
$$ Get module list LIST_ENTRY in $t0
r? $t0 = &@$peb->Ldr->InMemoryOrderModuleList
$$ Iterate over all module in list
.for (r? $t1 = *(ntdll!_LDR_DATA_TABLE_ENTRY**)@$t0;
(@$t1 != 0) & (@$t1 != @$t0);
r? $t1 = *(ntdll!_LDR_DATA_TABLE_ENTRY**)@$t1->InLoadOrderLinks.Flink)
{
$$ Get base address in $Base
as /x ${/v:$Base} @@c++(@$t1->DllBase)
$$ Get full name into $Mode
as /msu ${/v:$Mod} @@c++(&@$t1->FullDllName)
.block
{
.echo ${$Mod} at ${$Base}
}
ad ${/v:$Base}
ad ${/v:$Mod}
}
解释
$$和*,行注释
$$也可以分号结束注释
常用$$,因为将程序合并成一行时,换行符会被替换成分号
使用$$可以正常注释
使用*不能正常注释
当前进程_PEB结构的地址,使用伪寄存器$peb表示
r? $t0 = &@$peb->Ldr->InMemoryOrderModuleList
$t0,存储地址,指向@$peb->Ldr->InMemoryOrderModuleList
dt -r2 _PEB
dbgee!_PEB
+0x000 Reserved1 : [2] UChar
+0x002 BeingDebugged : UChar
+0x003 Reserved2 : [1] UChar
+0x008 Reserved3 : [2] Ptr64 Void
+0x018 Ldr : Ptr64 _PEB_LDR_DATA
+0x000 Reserved1 : [8] UChar
+0x008 Reserved2 : [3] Ptr64 Void
+0x020 InMemoryOrderModuleList : _LIST_ENTRY
+0x000 Flink : Ptr64 _LIST_ENTRY
+0x008 Blink : Ptr64 _LIST_ENTRY
r? $t1 = *(ntdll!_LDR_DATA_TABLE_ENTRY**)@$t0;
$t1,存储地址,与$t0相同
$t0,指向_LIST_ENTRY,位于_PEB_LDR_DATA中
$t1,指向_LIST_ENTRY,位于_LDR_DATA_TABLE_ENTRY中
也是指向_LDR_DATA_TABLE_ENTRY
dt ntdll!_LDR_DATA_TABLE_ENTRY
+0x000 InLoadOrderLinks : _LIST_ENTRY
+0x010 InMemoryOrderLinks : _LIST_ENTRY
+0x020 InInitializationOrderLinks : _LIST_ENTRY
+0x030 DllBase : Ptr64 Void
+0x038 EntryPoint : Ptr64 Void
+0x040 SizeOfImage : Uint4B
+0x048 FullDllName : _UNICODE_STRING
+0x058 BaseDllName : _UNICODE_STRING
(@$t1 != 0) & (@$t1 != @$t0);
链表遍历,判断结束
r? $t1 = *(ntdll!_LDR_DATA_TABLE_ENTRY**)@$t1->InLoadOrderLinks.Flink)
获取下一节点的地址
as /x ${/v:$Base} @@c++(@$t1->DllBase)
/x,取表达式64位值
/v,阻止别名替换,不管其是否已经定义
@@c++,强制使用C++表达式评估器
as /msu ${/v:$Mod} @@c++(&@$t1->FullDllName)
/msu,使别名等价于地址表达式指向的UNICODE_STRING
.block;{;.echo ${$Mod} at ${$Base};}
强制评估别名并回显
30.18.4 执行命令程序
$< Filename
同"$$>a< Filename [arg1 arg2 arg3 ... ]
支持参数
30.19 本章总结
本章内容,介绍调试任务所需的一般知识
是对windbg帮助文档的补充
对帮助文档进行了归纳和浓缩,便于入门
介绍帮助文档中较少或较难的内容
windbg中的文档
debugger.chm
根目录
windbg帮助文档
kernel_debugging_tutorial.doc
根目录
介绍内核调试
symhttp.doc
symproxy子目录
介绍如何建立符号服务器
srcsrv.doc
srcsrv子目录
介绍源文件服务器的概况以及如何建立和配置源文件服务器
dml.doc
根目录
介绍DML(Debugger Markup Language)的用途和编写方法
DML是一种标记语言,用于标记windbg或扩展命令的信息输出
themes.doc
themes子目录
介绍了主题(theme)的概念
一个主题,代表一套特定风格的界面布局和工作空间配置
介绍了如何加载及使用该目录中的四套主题配置
adplus.doc
pooltag.txt
triage子目录
包含了Windows内核模块和驱动程序所使用的内存分配标记(Pool Tag)
在启用了Windows操作系统的内存池标记(Pool Tagging)功能后,系统会为每个内存块维护一个分配标记,以标识其使用者
用于显示内存池使用情况的扩展命令!poolused,就是使用该文件来查找每个分配标记对应的模块