一个很有用的调试线程死锁的命令!locks在windbg 最新的版本6.11.1.40X(X为任意数字)不可用了,运行!locks会提示下面错误:
0:001> !locks
NTSDEXTS: Unable to resolve ntdll!RTL_CRITICAL_SECTION_DEBUG type
NTSDEXTS: Please check your symbols
解决方案:回退到版本6.10.3.233,
下载地址:http://msdl.microsoft.com/download/symbols/debuggers/dbg_x86_6.10.3.233.msi
安装上面版本,或者简单替换C:Program FilesDebugging Tools for Windows (x86)winxp目录下的ntsdexts.dll为老版本.
vertarget
vertarget:显示目标机的Microsoft Windows操作系统版本
用户模式:
0:000> vertarget
Windows 7 Version 7601 (Service Pack 1) MP (4 procs) Free x86 compatible
Product: WinNt, suite: SingleUserTS
kernel32.dll version: 6.1.7601.18847 (win7sp1_gdr.-1512)
Machine Name:
Debug session time: Tue Aug 11 10:52:03.810 2015 (GMT+8)
System Uptime: 7 days 1:19:36.017
Process Uptime: 0 days 0:00:15.234
Kernel time: 0 days 0:00:00.031
User time: 0 days 0:00:00.000
内核模式:
lkd> vertarget
Windows XP Kernel Version 2600 (Service Pack 3) UP Free x86 compatible
Product: WinNt, suite: TerminalServer SingleUserTS
Built by: 2600.xpsp.080413-2111
Machine Name:
Kernel base = 0x804d8000 PsLoadedModuleList = 0x80554fc0
Debug session time: Tue Aug 11 10:53:08.240 2015 (GMT+8)
System Uptime: 0 days 0:22:54.406
lm
lm:显示指定的已加载/未加载模块。输出中包含模块状态和路径
0:001> lm o ///< o 仅显示已加载模块
start end module name
012a0000 0 PPSeedTool (deferred)
.............
77dd0000 77f50000 ntdll (pdb symbols) C:Program Files (x86)Debugging Tools for Windows (x86)symwntdll.pdbF5B1BA4A16B0DC8199E9623C3C2wntdll.pdb
0:001> lm l ///< l 仅显示已加载符号信息的模块。
start end module name
77dd0000 77f50000 ntdll (pdb symbols) C:Program Files (x86)Debugging Tools for Windows (x86)symwntdll.pdbF5B1BA4A16B0DC8199E9623C3C2wntdll.pdb
0:001> lm v ///< 显示详细信息。输出中包含符号文件名、映像文件名、校验和信息、时间戳和该模块是否是托管代码(CLR)的信息
start end module name
012a0000 0 PPSeedTool (deferred)
Image path: PPSeedTool.exe
Image name: PPSeedTool.exe
Timestamp: Wed Aug 05 11:21:35 2015 (55C1813F)
CheckSum: 00000000
ImageSize: 00095000
Translations: 0000.04b0 0000.04e4 0409.04b0 0409.04e4
5c 5c6fc000 DmMain (deferred)
Image path: I:GameDLToolsPPSeedToolbuildDebugDmMain.dll
Image name: DmMain.dll
Timestamp: Fri Jul 24 14:49:39 2015 (55B1E003)
CheckSum: 000ADDF0
ImageSize: 000BC000
Translations: 0000.04b0 0000.04e4 0409.04b0 0409.04e4
lkd> lm k ///<(仅内核模式)仅显示内核模式符号信息
start end module name
804d8000 806d0480 nt (pdb symbols) c:mysymbol tkrnlpa.pdb30B5FB31AE7E4ACAABA750AA241FF3311 tkrnlpa.pdb
Unloaded modules:
afef5000 aff20000 kmixer.sys
..................................
babe8000 babed000 Cdaudio.SYS
ba56e000 ba Sfloppy.SYS
0:001> lm 1m ///< 仅显示名字,用于windbg script传入
PPSeedTool
DmMain
uxtheme
MSVCR90
MSIMG32
COMCTL32
比如用于.foreach中
0:001> .foreach (module {lm1m}) {lm vm ${module}}
start end module name
012a0000 0 PPSeedTool (deferred)
Image path: PPSeedTool.exe
Image name: PPSeedTool.exe
Timestamp: Wed Aug 05 11:21:35 2015 (55C1813F)
CheckSum: 00000000
ImageSize: 00095000
Translations: 0000.04b0 0000.04e4 0409.04b0 0409.04e4
start end module name
5c 5c6fc000 DmMain (deferred)
Image path: I:GameDLToolsPPSeedToolbuildDebugDmMain.dll
Image name: DmMain.dll
Timestamp: Fri Jul 24 14:49:39 2015 (55B1E003)
CheckSum: 000ADDF0
ImageSize: 000BC000
Translations: 0000.04b0 0000.04e4 0409.04b0 0409.04e4
0:001> lm sm ///< 按文件名排序
start end module name
768b0000 ADVAPI32 (deferred)
COMCTL32 (deferred)
// 查找地址所在模块
a Address指定包含在模块中的一个地址。只有包含该地址的模块会被显示出来。如果Address是一个表达式,它必须用圆括号括起来。
0:001> lm a
start end module name
COMCTL32 (deferred)
// 通配符
0:001> lm m ker*
start end module name
76b50000 76c60000 kernel32 (deferred)
773c7000 KERNELBASE (deferred)
可以注意到,上面有deferred这种符号状态缩写符号状态
缩写 含义
deferred 模块已经加载,但是调试器还没有尝试加载它的符号。符号将在需要的时候被加载。
# 符号文件和可执行文件的时间戳或者校验和有一些不匹配。
T 时间戳丢失、不能访问或者等于0。
C 校验和丢失、不可访问或者等于0。
DIA 符号文件通过调试接口访问(Debug Interface Access (DIA))被加载。
Export 没有找到实际的符号文件,所以符号信息是从二进制文件的导出表中获得的。
M 符号文件和可执行文件得时间戳或校验和有些不匹配。但是,因为符号选项的原因符号文件仍然被加载了。
PERF 该二进制文件包含性能优化后的代码。标准的地址计算方法可能产生不正确的结果。
Stripped 调试信息已经从映像文件中剥离出来了。
PDB 符号是.pdb格式的。
COFF 符号是通用对象文件格式(common object file format (COFF))的。
r 命令显示或修改寄存器、浮点寄存器、标志位、伪寄存器和预定义别名。
0:000> r ///<直接用r,会显示当前线程的寄存器状态
eax=00000000 ebx=00000000 ecx=a5cd0000 edx=0011e128 esi=fffffffe edi=00000000
eip=77e7129b esp=0022f740 ebp=0022f76c iopl=0 nv up ei pl zr na pe nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000246
0:002> ~0s ///< 切换到0号线程
eax=00000000 ebx=003bf8ec ecx=00000006 edx=00000000 esi=00000003 edi=552a6740
eip=76c07cb0 esp=003bf79c ebp=003bf824 iopl=0 nv up ei pl zr na pe nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000246
kernel32!VDMConsoleOperation+0x1c8:
76c07cb0 83c404 add esp,4
0:000> r ///<<span style="font-family: Arial, Helvetica, sans-serif;">直接用r,会显示当前线程的寄存器状态</span><span style="font-family: Arial, Helvetica, sans-serif;"> </span>
eax=00000000 ebx=003bf8ec ecx=00000006 edx=00000000 esi=00000003 edi=552a6740
eip=76c07cb0 esp=003bf79c ebp=003bf824 iopl=0 nv up ei pl zr na pe nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000246
kernel32!VDMConsoleOperation+0x1c8:
76c07cb0 83c404 add esp,4
0:000> ~0 r ///< 显示0号线程
eax=00000000 ebx=003bf8ec ecx=00000006 edx=00000000 esi=00000003 edi=552a6740
eip=76c07cb0 esp=003bf79c ebp=003bf824 iopl=0 nv up ei pl zr na pe nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000246
kernel32!VDMConsoleOperation+0x1c8:
76c07cb0 83c404 add esp,4
0:000> ~* r ///< 显示所有线程
eax=00000000 ebx=003bf8ec ecx=00000006 edx=00000000 esi=00000003 edi=552a6740
eip=76c07cb0 esp=003bf79c ebp=003bf824 iopl=0 nv up ei pl zr na pe nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000246
kernel32!VDMConsoleOperation+0x1c8:
76c07cb0 83c404 add esp,4
eax=00000001 ebx=00000000 ecx=00000000 edx=0 esi=00000000 edi=006cfeb0
eip=011213de esp=006cfde4 ebp=006cfeb0 iopl=0 nv up ei pl nz na po nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000202
test1!ThreadProc+0x1e:
011213de b mov eax,1
eax=7efd7000 ebx=00000000 ecx=00000000 edx=77e6fb5a esi=00000000 edi=00000000
eip=77de000c esp=0092fb8c ebp=0092fbb8 iopl=0 nv up ei pl zr na pe nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000244
ntdll!DbgBreakPoint:
77de000c cc int 3
改变0号线程寄存器的值
0:000> ~0 r eax=0x12345
0:000> ~0 r
eax=00012345 ebx=003bf8ec ecx=00000006 edx=00000000 esi=00000003 edi=552a6740
0:000> ~0 r eax
eax=00012345
改变所有线程寄存器的值
0:000> ~* r eax=0x11111
0:000> ~* r eax
eax=00011111
eax=00011111
eax=00011111
0x10 显示MMX寄存器。
0:000> ~0 rM 10
mm0=0000000000000000 mm1=0000000000000000
mm2=0000000000000000 mm3=0000000000000000
mm4=0000000000000000 mm5=0000000000000000
mm6=0000000000000000 mm7=0000000000000000
d*命令显示给定范围内存的内容。
如果省略掉Range ,命令将会从上一条内存查看命令结束的位置开始。这使得可以连续的进行内存查看。
d*命令显示给定范围内存的内容。这种显示的格式和最近一次d*命令的格式相同。如果之前没有使用过d*命令,d 和db 的效果相同
0:000> d
76c07e36 90 90 90 b8 10 20 00 00-b9 00 00 00 00 8d 54 24 ..... ........T$
da ASCII 字符
遇NULL结束
0:000> da 0x002e573c
002e573c "I am string"
db 字节值和ASCII字符
0:000> db 0x002e573c
002e573c 49 20 61 6d 20 73 74 72-69 6e 67 00 25 73 0a 00 I am string.%s..
002e574c 00 00 01 00 00 00 00 00-01 00 00 00 02 00 00 00 ................
dc 双字值(4字节)和ASCII字符
0:000> dc 0x002e573c
002e573c 6d 00676e69 000a7325 I am string.%s..
002e574c 00010000 00000000 00000001 00000002 ................
dd 双字值(4字节)
0:000> dd 0x002e573c
002e573c 6d 00676e69 000a7325
002e574c 00010000 00000000 00000001 00000002
dD 双精度浮点数(8字节)
0:000> dD 0x002e573c
002e573c 2.e+243 1.e-308 3.e-319
002e5754 4.e-314 1.e-313 6.e-313
df 单精度浮点数(4字节)
0:000> df 0x002e573c
002e573c 4.e+027 4.e+030 9.e-039 9.e-040
002e574c 9.e-041 0 1.e-045 2.e-045
dp 指针大小的值。该命令根据目标机的处理器是32位还是64位的,分别等于dd 或dq。
0:000> dp 0x002e573c
002e573c 6d 00676e69 000a7325
002e574c 00010000 00000000 00000001 00000002
dq 四字值(Quad-word values) (8 bytes)。
0:000> dq 0x002e573c
002e573c `6d 000a7325`00676e69
002e574c 00000000`00010000 00000002`00000001
du Unicode字符 。
遇NULL结束
0:000> .dvalloc 100
Allocated 1000 bytes starting at 00030000
0:000> ezu 00030000 "I am unicode"
0:000> du 00030000
00030000 "I am unicode"
dw WORD值(2字节)。
0:000> dw 00030000
00030000 0049 0020 0061 006d 0020 0075 006e 0069
00030010 0063 006f 0064 0065 0000 0000 0000 0000
dW WORD值(2字节)和ASCII字符。
0:000> dW 00030000
00030000 0049 0020 0061 006d 0020 0075 006e 0069 I. .a.m. .u.n.i.
00030010 0063 006f 0064 0065 0000 0000 0000 0000 c.o.d.e.........
如果尝试显示一个非法地址,它的内容会显示为问号(?)。
e
e命令和d命令非常相似,一个读一个写嘛!
e 输入数据的格式和前一次e* 命令一样
ea ASCII 字符串(不以NULL结尾)。
0:000> .dvalloc 100
Allocated 1000 bytes starting at 00080000
0:000> ea 00080000 "i am ansi "
0:000> db 00080000
00080000 69 20 61 6d 20 61 6e 73-69 20 00 00 00 00 00 00 i am ansi ......
eb 字节值。
0:000> eb 00080000 'i' 'i' 's'
0:000> db 00080000
00080000 69 69 73 6d 20 61 6e 73-69 20 00 00 00 00 00 00 iism ansi ......
ed 双字值(4字节)。
注意字节序从高到低
0:000> ed 00080000 6d
0:000> db 00080000
00080000 69 20 61 6d 20 61 6e 73-69 20 00 00 00 00 00 00 i am ansi ......
eu Unicode字符串(非NULL结尾)。
eza NULL结尾的ASCII字符串。
ezu NULL结尾的Unicode字符串。
0:000> ezu 00080000 "hi unicode"
0:000> du 00080000
00080000 "hi unicode"
如果省略Values 参数,会提示进行输入。指定的地址和它的内容会显示出来,并且出现Input> 提示符。这时可以进行如下这些操作:
通过键入值和ENTER键来输入新的值。
通过按下SPACE然后按下ENTER来保持内存的当前值。
按下ENTER来退出当前命令
!address
!address 扩展显示目标进程或目标机使用的内存信息
这个学习起来比较简单:我们直接使用!address -?就可以找到它的使用说明:
0:000> !address -?
!address - prints information on the entire address space
!address -? - prints this help
!address <address> - prints available information about the region
of the address space containing this address
!address -summary - prints only summary information
!address -RegionUsageXXX - fiters the output limiting the dispaly to one
of the following types:
RegionUsageIsVAD - `busy` region that could be charcterized better
this includes Virtual-Alloc-ed blocks, SBH heap,
memory from custom allocators, etc
RegionUsageFree - availalble (neither committed nor reserved) region
RegionUsageImage - region used by mapped images of binaries
RegionUsageStack - stack of threads
RegionUsageTeb - TEB of threads
RegionUsageHeap - region in used by a heap
RegionUsagePageHeap - region in use by full page-heap
RegionUsagePeb - PEB of the process
RegionUsageProcessParametrs - parameters of the process
RegionUsageEnvironmentBlock - environment block
那么一个个说明吧:
!address显示整个地址空间和使用摘要的信息
这个太长了,它会把从0-7ffefff的全打印出来,熟悉核心编程的应该知道,正常的2G用户地址空间是这样划分的:0-ffff为64K空指针区,10000-7ffeffff为用户模式分区
之后64K为禁入分区,之后就是内核模式分区,要看它们的信息,需要用到以下的表,
Filter 值 显示的内存区域
RegionUsageIsVAD "busy" 区域。包括所有 虚拟分配块、SBH堆、自定义内存分配器(custom allocators)的内存、以及地址空间中所有属于其他分类的内存块。
RegionUsageFree 目标的虚拟地址空间中所有可用内存。包括所有非提交(committed)和非保留(reserved)的内存。
RegionUsageImage 用来映射二进制映像的内存区域。
RegionUsageStack 用作目标进程的线程的堆栈的内存区域。
RegionUsageTeb 用作目标进程中所有线程的线程环境块(TEB)的内存区域。
RegionUsageHeap 用作目标进程的堆的内存区域。
RegionUsagePageHeap 用作目标进程的整页堆(full-page heap)的内存区域。
RegionUsagePeb 目标进程的进程环境块(PEB)的内存区域。
RegionUsageProcessParametrs 用作目标进程启动参数的内存区域。
RegionUsageEnvironmentBlock 用作目标进程的环境块的内存区域。
下面这些Filter值按照内存类型来指定内存。
Filter 值 显示的内存类型
MEM_IMAGE 映射的文件属于可执行映像一部分的内存。
MEM_MAPPED 映射的文件不属于可执行映像一部分的内存。这种内存包含哪些从页面文件映射的内存。
MEM_PRIVATE 私有的(即不和其他进程共享)并且未用来映射任何文件的内存。
下面的Filter 值按照状态来指定内存:
Filter 值 显示的内存状态
MEM_COMMIT 当前已提交给目标使用的所有内存。已经在物理内存或者页面文件中为这些内存分配了物理的存储空间。
MEM_RESERVE 所有为目标以后的使用保留的内存。这种内存还没有分配物理上的存储空间。
MEM_FREE 目标虚拟地址空间中所有可用内存。包括所有未提交并且未保留的内存。该Filter 值和RegionUsageFree一样。
比如一般30000不会被分配:
0:000> !address 30000
TEB 7efdd000 in range 7efdb000 7efde000
TEB 7efda000 in range 7efd8000 7efdb000
TEB 7efd7000 in range 7efd5000 7efd8000
ProcessParametrs 00641a40 in range 00 00
Environment 00 in range 00 00
00030000 : 00030000 - 00010000
Type 00000000
Protect 00000001 PAGE_NOACCESS
State 00010000 MEM_FREE
Usage RegionUsageFree
表示输出表明这是以地址0x30000开头的一个大的内存区域,该区域中包含一个以0x30000 开头,大小为0x10000的小一些的区域。因此,这个小区域是从0x30000 到0x40000。它的内存类型为0、状态为 MEM_FREE、使用方式为RegionUsageFree。 (这些值的含义,查看前面的表格。)
我们调用.dvalloc来强制分配
0:000> .dvalloc /b 30000 100
Allocated 1000 bytes starting at 00030000
0:000> !address 30000
TEB 7efdd000 in range 7efdb000 7efde000
TEB 7efda000 in range 7efd8000 7efdb000
TEB 7efd7000 in range 7efd5000 7efd8000
ProcessParametrs 00641a40 in range 00 00
Environment 00 in range 00 00
00030000 : 00030000 - 00001000
Type 00020000 MEM_PRIVATE
Protect 00000040 PAGE_EXECUTE_READWRITE
State 00001000 MEM_COMMIT
Usage RegionUsageIsVAD
!vadump
这个会显示所有的虚拟内存区域和它的保护属性
0:000> !vadump
BaseAddress: 00000000
RegionSize: 00010000
State: 00010000 MEM_FREE
Protect: 00000001 PAGE_NOACCESS
BaseAddress: 00010000
RegionSize: 00010000
State: 00001000 MEM_COMMIT
Protect: 00000004 PAGE_READWRITE
Type: 00040000 MEM_MAPPED
BaseAddress: 00020000
RegionSize: 00010000
State: 00010000 MEM_FREE
Protect: 00000001 PAGE_NOACCESS
!vprot
!vprot扩展命令显示虚拟内存保护信息。可以用于活动调试和dump文件调试。
0:001> x test1!g_char
00a67004 test1!g_char = 0x00a6573c "I am string"
0:001> !vprot 00a67004
BaseAddress: 00a67000
AllocationBase: 00a50000
AllocationProtect: 00000080 PAGE_EXECUTE_WRITECOPY
RegionSize: 00002000
State: 00001000 MEM_COMMIT
Protect: 00000004 PAGE_READWRITE
Type: 0 MEM_IMAGE
0:001> !vprot 30000
BaseAddress: 00030000
AllocationBase: 00000000
RegionSize: 00010000
State: 00010000 MEM_FREE
Protect: 00000001 PAGE_NOACCESS
!runaway
!runaway命令显示每个线程消费的时间
Bit 0 (0x1) 让调试器显示每个线程消耗的用户模式时间(user time),默认不加就是0x1
Bit 1 (0x2) 显示每个线程消耗的内核时间(kernel time)。
Bit 2 (0x4) 显示每个线程从创建开始经历了多少时间。
就是三者的组合:1 2 3 4 5 6 7
0:002> !runaway
User Mode Time
Thread Time
0:890 0 days 0:00:00.031
2:a00 0 days 0:00:00.000
1:1174 0 days 0:00:00.000
0:002> !runaway 1
User Mode Time
Thread Time
0:890 0 days 0:00:00.031
2:a00 0 days 0:00:00.000
1:1174 0 days 0:00:00.000
0:002> !runaway 2
Kernel Mode Time
Thread Time
0:890 0 days 0:00:00.062
2:a00 0 days 0:00:00.000
1:1174 0 days 0:00:00.000
0:002> !runaway 3
User Mode Time
Thread Time
0:890 0 days 0:00:00.031
2:a00 0 days 0:00:00.000
1:1174 0 days 0:00:00.000
Kernel Mode Time
Thread Time
0:890 0 days 0:00:00.062
2:a00 0 days 0:00:00.000
1:1174 0 days 0:00:00.000
0:002> !runaway 4
Elapsed Time
Thread Time
0:890 0 days 0:38:34.825
1:1174 0 days 0:38:34.793
2:a00 0 days 0:38:24.528
0:002> !runaway 7
User Mode Time
Thread Time
0:890 0 days 0:00:00.031
2:a00 0 days 0:00:00.000
1:1174 0 days 0:00:00.000
Kernel Mode Time
Thread Time
0:890 0 days 0:00:00.062
2:a00 0 days 0:00:00.000
1:1174 0 days 0:00:00.000
Elapsed Time
Thread Time
0:890 0 days 0:38:41.825
1:1174 0 days 0:38:41.793
2:a00 0 days 0:38:31.528
该扩展命令可以用来快速找出哪些线程循环失去控制消耗了太多CPU时间。输出中以调试器的内部线程号和16进制线程ID来标识每个线程。还会显示调试器ID,当然,主要用于分析dump文件
~ (Thread Status)
波形符(~) 命令显示指定线程或当前进程中的所有线程的信息
0:002> ~
0 Id: 433c.4154 Suspend: 1 Teb: 7efdd000 Unfrozen
1 Id: 433c.33e4 Suspend: 1 Teb: 7efda000 Unfrozen
. 2 Id: 433c.c50 Suspend: 1 Teb: 7efd7000 Unfrozen
0:002> ~* ///<显示线程和入口函数
0 Id: 433c.4154 Suspend: 1 Teb: 7efdd000 Unfrozen
Start: test1!ILT+120(_wmainCRTStartup) (00a6107d)
Priority: 0 Priority class: 32 Affinity: f
1 Id: 433c.33e4 Suspend: 1 Teb: 7efda000 Unfrozen
Start: test1!ILT+35(?ThreadProcYAKPAXZ) (00a61028)
Priority: 0 Priority class: 32 Affinity: f
. 2 Id: 433c.c50 Suspend: 1 Teb: 7efd7000 Unfrozen
Start: ntdll!DbgUiRemoteBreakin (77e6fb5a)
Priority: 0 Priority class: 32 Affinity: f
0:002> ~0
0 Id: 433c.4154 Suspend: 1 Teb: 7efdd000 Unfrozen
Start: test1!ILT+120(_wmainCRTStartup) (00a6107d)
Priority: 0 Priority class: 32 Affinity: f
0:002> ~0s ///<切换线程
eax=00000000 ebx=0044f5e4 ecx=00000006 edx=00000000 esi=00000003 edi=71b16740
eip=76c07cb0 esp=0044f494 ebp=0044f51c iopl=0 nv up ei pl zr na pe nc
我们可以发现,~和~*还是有点区别的,~*会把入口函数和优先级都打印出来
//暂停、恢复线程----------------------------------------------------------------------------------------------------
0 Id: 433c.4154 Suspend: 1 Teb: 7efdd000 Unfrozen意思是0号线程,进程ID为433c,线程ID为4154,暂停数为1,未冻结状态,每个线程都包含了一个暂停计数(Suspend Count),以及一个冻结/解冻(Frozen/Unfrozen)的状态。
暂停计数由Windows内核使用的值,可以通过SuspendThread和ResumeThread这两个系统函数来控制的,核心编程里说一个线程创建时,暂停次数为1,
当线程完全初始化后,系统就要查看CREATE_SUSPEND标志是否已经传递给CreateThread。如果该标志没有传递,系统便将线程的暂停计数递减为0,
该线程可以调度到一个进程中
也可以用~<tid>n增加暂停计数,用~<tid>m减少暂停计数,如下:
0:001> ~1n
0:001> ~1
. 1 Id: 433c.33e4 Suspend: 2 Teb: 7efda000 Unfrozen
Start: test1!ILT+35(?ThreadProcYAKPAXZ) (00a61028)
Priority: 0 Priority class: 32 Affinity: f
0:001> g
此时g运行,会发现1号线程被暂停了,必须使用~1m来恢复
冻结状态是调试器的状态,在window操作系统中是不支持这个概念,对于每个被冻结的线程,调试器将记住这个状态,并且在调试事件被处理之前增加线程的挂起计数,当调试事件被处理完毕时,挂起计数将被递减,对应的命令为:
冻结~<tid>f
解冻~<tid>u
冻结命令数量必须和解冻命令数量相等
| (Process Status)
这个在多进程切换时比较有效:
先模拟一个多进程调试:
windbg先打开test1
再附加到test2上:
0:002> | ///< <span style="font-family: 'Courier New', Courier, mono;">查看总进程列表,目前只有一个</span>
. 0 id: 433c create name: test1.exe
0:002> .attach 0n7860 ///< 附加test2
Attach will occur on next execution
0:002> g ///< 运行
* wait with pending attach
Symbol search path is: srv*
Executable search path is:
ModLoad: 00 0031b000 D:windbg est2Debug est2.exe
ModLoad: 77dd0000 77f50000 C:WindowsSysWOW64 tdll.dll
eax=7efda000 ebx=00000000 ecx=00000000 edx=77e6fb5a esi=00000000 edi=00000000
eip=77de000c esp=0079fe54 ebp=0079fe80 iopl=0 nv up ei pl zr na pe nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000246
ntdll!DbgBreakPoint:
77de000c cc int 3
1:004> |<span style="white-space:pre"> </span> ///< 再次查看总进程列表,目前已有两个
0 id: 433c create name: test1.exe
. 1 id: 1eb4 attach name: D:windbg est2Debug est2.exe
和线程切换相同:使用:
1:004> |0s
切换回0号进程
k*命令显示给定线程的调用堆栈,以及其他相关信息
~0 k表示打印0号线程的调用堆栈,直接用k表示打印当前线程的调用堆栈
0:002> ~0k
ChildEBP RetAddr
0007fddc 77d191be ntdll!KiFastSystemCallRet
0007fdfc 010021b0 USER32!NtUserGetMessage+0xc
0007ff1c 010125e9 calc!WinMain+0x25f
0007ffc0 7c calc!WinMainCRTStartup+0x174
0007fff0 00000000 kernel32!BaseProcessStart+0x23
0:002> k
ChildEBP RetAddr
00bfffc8 7c ntdll!DbgBreakPoint
00bffff4 00000000 ntdll!DbgUiRemoteBreakin+0x2d
0:002> ~2k
ChildEBP RetAddr
00bfffc8 7c ntdll!DbgBreakPoint
00bffff4 00000000 ntdll!DbgUiRemoteBreakin+0x2d
我们注意到2号线程的堆栈,这是windbg创建一个远程线程来执行DbgUiRemoteBreakin函数,它内部会调用DbgBreakPoint执行断点指令,以触发断点异常,强制把程序断了下来,所以windbg打印出来的线程总多了一条,所以不要惊讶为什么线程多了点。
其实我想弄清楚那个ChildEBP/RetAddr分别具体指什么:先K看下堆栈:
0:000> k
ChildEBP RetAddr
0012fb1c 7c95e612 ntdll!DbgBreakPoint
0012fc94 7c94108f ntdll!LdrpInitializeProcess+0xffa
0012fd1c 7c92e437 ntdll!_LdrpInitialize+0x183
00000000 00000000 ntdll!KiUserApcDispatcher+0x7
再打开反汇编窗口:
ntdll!DbgBreakPoint:
7c92120e cc int 3
当前运行到这一行:再用r ebp查看下值:
0:000> r ebp
ebp=0012fc94
这个值是LdrpInitializeProcess前面的ChildEBP,F10单步调试到ret(也就是7c92120f)
ntdll!DbgBreakPoint:
7c92120e cc int 3
7c92120f c3 ret
再F10调试一步,退回到LdrpInitializeProcess中(7c95e612):
7c95e60d e8fc2bfcff call ntdll!DbgBreakPoint (7c92120e)
7c95e612 8b4368 mov eax,dword ptr [ebx+68h] ds:0023:7ffd3068=00000070
我们发现这个7c95e612就是DbgBreakPoint的返回地址,也就是返回地址应该是指函数退出后下个EIP的值,我以前还一直以为是那个ret/leave对应的地方,原来是ret运行后的值.
结论:
ChildEBP指的是当前堆栈运行时的ebp值
RetAddr指当前堆栈中函数退出时的下个EIP的值
kb 显示传递给堆栈回溯中的每个函数的前三个参数kp 显示传递给堆栈回溯中的每个函数的所有参数。参数列表包含参数的数据类型、名字和值。p命令是区分大小写的。使用该参数需要完整符号信息。 (事实上我看到的结果和k一样)kP 和p参数一样,显示传递给堆栈回溯中的每个函数的所有参数。但是,使用P ,函数参数在第二行中显示,而不是作为数据的结尾在行末显示。 (事实上我看到的结果和k一样)
int Add(int a,int b,int c,int d,int e,int f)
{
return a+b;
}
int _tmain(int argc, _TCHAR* argv[])
{
int c = Add(0x12,0x34,0x56,0x78,0x9a,0xbc);
return 0;
}
显示的堆栈如下:
0:000:x86> kb
ChildEBP RetAddr Args to Child
0015fce4 010e1415 00000012 00000034 00000056 test1!Add+0x1e [f: est1 est1 est1.cpp @ 7]
0:000:x86> kp
ChildEBP RetAddr
0015fce4 010e1415 test1!Add(int a = 0n18, int b = 0n52, int c = 0n86, int d = 0n120, int e = 0n154, int f = 0n188)+0x1e [f: est1 est1 est1.cpp @ 7]
0:000:x86> kP
ChildEBP RetAddr
0015fce4 010e1415 test1!Add(
int a = 0n18,
int b = 0n52,
int c = 0n86,
int d = 0n120,
int e = 0n154,
int f = 0n188)+0x1e [f: est1 est1 est1.cpp @ 7]
0:000:x86> kv
ChildEBP RetAddr Args to Child
0015fce4 010e1415 00000012 00000034 00000056 test1!Add+0x1e (FPO: [Non-Fpo]) (CONV: cdecl) [f: est1 est1 est1.cpp @ 7]
在某些情况下,只有一部分栈是可用的,此时调试器的k命令无法解析栈,这时因为当前的栈基指针ebp和栈顶指针esp所指向的地址是不可访问的,在这种情况下,可以使用命令K的一种变化形式,在这种形式中可以接受栈基指针、栈顶指针以及指令指针做为参数。
在手动重新构造栈的过程中,最困难的任务就是从内存中找出两个值来表示调用栈中正确的栈帧。找出这两个值的方法之一就是识别出一系列的值,这些值表示当前栈的某个地址,并且在这些值之后是一个可执行的地址,每个地址都可能是一个栈帧,此时需要通过命令k来进行验证,将这个操作重复应用于其他可能的栈帧,直到将栈重构出来并且在执行命令K时能够显示一个正确的栈,如下所示:
0:000> dc esp
000dfc94 010017eb 00000001 00000000 000dfcb4 ................
000dfca4 0 00000000 00000001 00000002 ................
000dfcb4 000dfcc8 0 00000002 00000001 ................
000dfcc4 00000003 000dfcdc 0 00000003 ................
000dfcd4 00000001 00000004 000dfcf0 0 ................
000dfce4 00000004 00000001 00000005 000dfd04 ................
000dfcf4 0 00000005 00000001 00000006 ................
000dfd04 000dfd18 0 00000006 00000001 ................
0:000> *使用被保存的ebp,存储ebp的地址以及返回值
0:000> k=000dfcc8 000dfcb4 0
# ChildEBP RetAddr
00 000dfcc8 0 02sample!KBTest::Fibonacci_stdcall+0x42 [c:awdchapter2sample.cpp @ 119]
01 000dfcdc 0 02sample!KBTest::Fibonacci_stdcall+0x42 [c:awdchapter2sample.cpp @ 119]
02 000dfcf0 0 02sample!KBTest::Fibonacci_stdcall+0x42 [c:awdchapter2sample.cpp @ 119]
03 000dfd04 0 02sample!KBTest::Fibonacci_stdcall+0x42 [c:awdchapter2sample.cpp @ 119]
04 000dfd18 0 02sample!KBTest::Fibonacci_stdcall+0x42 [c:awdchapter2sample.cpp @ 119]
05 000dfd2c 0 02sample!KBTest::Fibonacci_stdcall+0x42 [c:awdchapter2sample.cpp @ 119]
06 000dfd40 0 02sample!KBTest::Fibonacci_stdcall+0x42 [c:awdchapter2sample.cpp @ 119]
07 000dfd54 0 02sample!KBTest::Fibonacci_stdcall+0x42 [c:awdchapter2sample.cpp @ 119]
08 000dfd68 0 02sample!KBTest::Fibonacci_stdcall+0x42 [c:awdchapter2sample.cpp @ 119]
因为栈是自高向低增长,而返回值是一个可执行的地址,所以猜0是一个函数地址,做为返回值,低位接着就是EPB了,
u
u 命令显示指定的内存中的程序代码的反汇编。
如果要反汇编某一个地址,直接用u 命令加地址
0:002> u 77d2929a
USER32!SendMessageW:
77d2929a 8bff mov edi,edi
77d2929c 55 push ebp
77d2929d 8bec mov ebp,esp
77d2929f 56 push esi
77d292a0 8b750c mov esi,dword ptr [ebp+0Ch]
77d292a3 f7c60000feff test esi,0FFFE0000h
77d292a9 0f85be jne USER32!SendMessageW+0x11 (77d4136d)
77d292af 8b4d08 mov ecx,dword ptr [ebp+8]
如果存在符号文件,也可以这样直接加函数名:
0:002> u user32!SendMessagew
USER32!SendMessageW:
77d2929a 8bff mov edi,edi
77d2929c 55 push ebp
77d2929d 8bec mov ebp,esp
77d2929f 56 push esi
77d292a0 8b750c mov esi,dword ptr [ebp+0Ch]
77d292a3 f7c60000feff test esi,0FFFE0000h
77d292a9 0f85be jne USER32!SendMessageW+0x11 (77d4136d)
77d292af 8b4d08 mov ecx,dword ptr [ebp+8]
注意的是,函数只支持全名,你要是写成u user32!SendMessage,windbg是认不出来的,当然你可以按TAB来让windbg自动匹配
ub 指示要反汇编的区域是向后计算的。如果使用了ubAddress ,反汇编区域是以Address结束的8或9条指令。如果用ubAddressLLength语法指定区域,则反汇编以Address结尾的指定长度的内容。
0:002> ub USER32!SendMessageW
USER32!SendMessageWorker+0x4ed:
77d29290 5b pop ebx
77d29291 c9 leave
77d29292 c21400 ret 14h
77d29295 90 nop
77d29296 90 nop
77d29297 90 nop
77d29298 90 nop
77d29299 90 nop
我们可以发现ub的结束后一条刚好是u的开始
同样如果存在符号文件,我们可以用uf来反汇编整个函数:
uf 命令显示内存中指定函数的反汇编代码。
x
x命令显示所有上下文中匹配指定模板的符号。可用字符通配符
x user32!send*
77d53948 USER32!SendNotifyMessageA = <no type information>
77d2fb6b USER32!SendMessageTimeoutA = <no type information>
77d6b88f USER32!SendOpenStatusNotify = <no type information>
77d6b49e USER32!SendIMEMessageExA = <no type information>
77d2d64f USER32!SendNotifyMessageW = <no type information>
77d2cdaa USER32!SendMessageTimeoutW = <no type information>
77d65b26 USER32!SendHelpMessage = <no type information>
77d6b823 USER32!SendMessageToUI = <no type information>
77d6b48d USER32!SendIMEMessageExW = <no type information>
77d2cd08 USER32!SendMessageTimeoutWorker = <no type information>
77d203fc USER32!SendRegisterMessageToClass = <no type information>
77d3c2e7 USER32!SendDlgItemMessageA = <no type information>
77d2d6db USER32!SendMessageCallbackW = <no type information>
77d6b129 USER32!SendMessageCallbackA = <no type information>
77d273cc USER32!SendDlgItemMessageW = <no type information>
77d61930 USER32!SendWinHelpMessage = <no type information>
77d291b3 USER32!SendMessageWorker = <no type information>
77d2929a USER32!SendMessageW = <no type information>
77d2f3c2 USER32!SendMessageA = <no type information></span>
所以,这个可以用来定位函数,
这里介绍下字符串通配符语法
一个星号(*)表示零个或多个字符。这个前面的例子用到了,
一个问号(?)表示任意单个字符,如下例:
<span style="color:#000000;">0:002> x user32!sendMessage?
77d2929a USER32!SendMessageW = <no type information>
77d2f3c2 USER32!SendMessageA = <no type information></span>
一个井号(#)匹配零个或多个前一个字符。例如,Lo#p 将匹配 "Lp", "Lop", "Loop", "Looop" 等等
一个加号(+)匹配一个或多个前一个字符
如果你需要使用 #、 ?、 [, ]、*、+ 字符本身,必须在这些字符前面加一个反斜杠()。
x还有个作用,在函数断下来后输入x,会自动打印出当前的局部变量,可以配合.frame使用
0:000:x86> kn
# ChildEBP RetAddr
00 0039fd18 010e19a8 test1!wmain+0x1e [f: est1 est1 est1.cpp @ 12]
01 0039fd68 010e17ef test1!__tmainCRTStartup+0x1a8 [f:ddvctoolscrt_bldself_x86crtsrccrtexe.c @ 583]
0:000:x86> x
0039fd20 argc = 0n1
0039fd24 argv = 0x00033438
0039fd10 c = 0n-
/v 命令可以帮助你更好理解二进制文件的内容,它将按照对象或函数所占的字节数以升序来列出序号的类型和大小
0:000> x /v /t 02sample!w*
prv func 01001c60 21 <function> 02sample!wmain (unsigned long, wchar_t )
prv func 0 a <function> 02sample!wmainCRTStartup (void)
以下默认windbg加载calc程序
d*s
dds、dps和dqs命令显示给定范围内存的内容,它们是把内存区域转储出来,并把内存中每个元素都视为一个符号对其进行解析,dds是四字节视为一个符号,dqs是每8字节视为一个符号,dps是根据当前处理器架构来选择最合适的长度
比如要看看当前stack 中保存了哪些函数地址,就可以检查ebp 指向的内存
0:000> dds ebp
0007fdfc 0007ff1c
0007fe00 010021b0 calc!WinMain+0x25f
0007fe04 0007fee8
0007fe08 00000000
0007fe0c 00000000
0007fe10 00000000
0007fe14 7c80b741 kernel32!GetModuleHandleA
0007fe18 000a232f
0007fe1c 00000000
由于 COM Interface 和C++ Vtable 里面的成员函数都是顺序排列的,所以这个命令可以方便
地找到虚函数表中具体的函数地址。比如用下面的命令可以找到OpaqueDataInfo 类型中虚
函数对应的实际函数地址:
0:002> x ole32!OpaqueData*
76aa6a41 ole32!OpaqueDataInfo::GetOpaqueData = <no type information>
76aa6b3b ole32!OpaqueDataInfo::UnSerialize = <no type information>
76aa6c16 ole32!OpaqueDataInfo::SerializableQueryInterface = <no type information>
76aa5748 ole32!OpaqueDataInfo::QueryInterface = <no type information>
76aa6393 ole32!OpaqueDataInfo::CopyOpaqueData = <no type information>
76aa5757 ole32!OpaqueDataInfo::AddRef = <no type information>
76a57107 ole32!OpaqueDataInfo::UnSerializeCallBack = <no type information>
76aa5766 ole32!OpaqueDataInfo::Release = <no type information>
769a697c ole32!OpaqueDataInfo::`vftable' = <no type information>
76aa69cb ole32!OpaqueDataInfo::AddOpaqueData = <no type information>
769bfae2 ole32!OpaqueDataInfo::GetOpaqueDataCount = <no type information>
76aa6b24 ole32!OpaqueDataInfo::Serialize = <no type information>
769c9df3 ole32!OpaqueDataInfo::AddRef = <no type information>
769c9ebc ole32!OpaqueDataInfo::Release = <no type information>
76aa6a97 ole32!OpaqueDataInfo::DeleteOpaqueData = <no type information>
76aa6bc9 ole32!OpaqueDataInfo::GetCLSID = <no type information>
76aa57c0 ole32!OpaqueDataInfo::OpaqueDataInfo = <no type information>
769c1cb0 ole32!OpaqueDataInfo::GetAllOpaqueData = <no type information>
76aa54b9 ole32!OpaqueDataInfo::~OpaqueDataInfo = <no type information>
76aa6be9 ole32!OpaqueDataInfo::SetParent = <no type information>
76aa5693 ole32!OpaqueDataInfo::`scalar deleting destructor' = <no type information>
76aa6b78 ole32!OpaqueDataInfo::GetSize = <no type information>
76aa6540 ole32!OpaqueDataInfo::QueryInterface = <no type information>
769a69a0 ole32!OpaqueDataInfo::`vftable' = <no type information>
0:002> dds 769a69a0
769a69a0 76aa6540 ole32!OpaqueDataInfo::QueryInterface
769a69a4 769c9df3 ole32!InstanceInfo::AddRef
769a69a8 769c9ebc ole32!InstantiationInfo::Release
769a69ac 76aa69cb ole32!OpaqueDataInfo::AddOpaqueData
769a69b0 76aa6a41 ole32!OpaqueDataInfo::GetOpaqueData
769a69b4 76aa6a97 ole32!OpaqueDataInfo::DeleteOpaqueData
769a69b8 769bfae2 ole32!ServerLocationInfo::GetRemoteServerName
769a69bc 769c1cb0 ole32!CComProcessInfo::GetProcessName
769a69c0 76a57107 ole32!InstanceInfo::UnSerializeCallBack
769a69c4 00000021
769a69c8 76a2d73d ole32!CClassMoniker::QueryInterface
769a69cc 76a339fb ole32!CErrorObject::AddRef
769a69d0 76a0679a ole32!CClassMoniker::Release
769a69d4 76a06a39 ole32!CClassMoniker::GetUnmarshalClass
769a69d8 76a06a56 ole32!CClassMoniker::GetMarshalSizeMax
769a69dc 76a06a99 ole32!CClassMoniker::MarshalInterface
769a69e0 76a2d2b9 ole32!CClassMoniker::UnmarshalInterface
769a69e4 76a07099 ole32!CClassMoniker::ReleaseMarshalData
769a69e8 769e288e ole32!CDdeObject::COleItemContainerImpl::IsRunning
769a69ec 76a2d72e ole32!CClassMoniker::QueryInterface
769a69f0 76a339dd ole32!CErrorObject::AddRef
769a69f4 76a06ab8 ole32!CClassMoniker::Release
769a69f8 76a069d1 ole32!CClassMoniker::GetComparisonData
769a69fc
769a6a00 76a066c9 ole32!CClassMoniker::QueryInterface
769a6a04 76a05efd ole32!CSCMergedEnum<IEnumCATEGORYINFO,tagCATEGORYINFO>::AddRef
769a6a08 76a067a6 ole32!CClassMoniker::Release
769a6a0c 76a068f3 ole32!CClassMoniker::GetClassID
769a6a10 769acee9 ole32!CDdeServerCallMgr::AddRef
769a6a14 76a2d7f2 ole32!CClassMoniker::Load
769a6a18 76a06931 ole32!CClassMoniker::Save
769a6a1c 76a07055 ole32!CClassMoniker::GetSizeMax
PE文件解析
start end module name
0 0124b000 test1 C (private pdb symbols)
1.dos头:
0:001> dt IMAGE_DOS_HEADER 0
test1!IMAGE_DOS_HEADER
+0x000 e_magic : 0x5a4d
+0x002 e_cblp : 0x90
+0x004 e_cp : 3
+0x006 e_crlc : 0
+0x008 e_cparhdr : 4
+0x00a e_minalloc : 0
+0x00c e_maxalloc : 0xffff
+0x00e e_ss : 0
+0x010 e_sp : 0xb8
+0x012 e_csum : 0
+0x014 e_ip : 0
+0x016 e_cs : 0
+0x018 e_lfarlc : 0x40
+0x01a e_ovno : 0
+0x01c e_res : [4] 0
+0x024 e_oemid : 0
+0x026 e_oeminfo : 0
+0x028 e_res2 : [10] 0
+0x03c e_lfanew : 224
来确认下这个是PE文件:
0:001> da 0 +0
0 "MZ."
2.nt头
e_lfanew定义了真正的PE文件头的相对偏移量RVA
0:001> da 0 +0n224
012300e0 "PE"
0:001> dt IMAGE_NT_HEADERS 0 +0n224
test1!IMAGE_NT_HEADERS
+0x000 Signature : 0x4550
+0x004 FileHeader : _IMAGE_FILE_HEADER
+0x018 OptionalHeader : _IMAGE_OPTIONAL_HEADER
3.文件头
0:001> dt IMAGE_FILE_HEADER 0 +0n224+0x4
test1!IMAGE_FILE_HEADER
+0x000 Machine : 0x14c
+0x002 NumberOfSections : 7
+0x004 TimeDateStamp : 0x55cae429
+0x008 PointerToSymbolTable : 0
+0x00c NumberOfSymbols : 0
+0x010 SizeOfOptionalHeader : 0xe0
+0x012 Characteristics : 0x102
由Characteristics : 0x102可以得出这是个#define IMAGE_FILE_EXECUTABLE_IMAGE 0x0002 // 文件可执行#define IMAGE_FILE_32BIT_MACHINE 0x0100 // 32位机器
4.扩展文件头
0:001> dt _IMAGE_OPTIONAL_HEADER 0 +0n224+0x18
test1!_IMAGE_OPTIONAL_HEADER
+0x000 Magic : 0x10b
+0x002 MajorLinkerVersion : 0x9 ''
+0x003 MinorLinkerVersion : 0 ''
+0x004 SizeOfCode : 0x3600
+0x008 SizeOfInitializedData : 0x4200
+0x00c SizeOfUninitializedData : 0
+0x010 AddressOfEntryPoint : 0x1107d
+0x014 BaseOfCode : 0x1000
+0x018 BaseOfData : 0x1000
+0x01c ImageBase : 0x
+0x020 SectionAlignment : 0x1000
+0x024 FileAlignment : 0x200
+0x028 MajorOperatingSystemVersion : 5
+0x02a MinorOperatingSystemVersion : 0
+0x02c MajorImageVersion : 0
+0x02e MinorImageVersion : 0
+0x030 MajorSubsystemVersion : 5
+0x032 MinorSubsystemVersion : 0
+0x034 Win32VersionValue : 0
+0x038 SizeOfImage : 0x1b000
+0x03c SizeOfHeaders : 0x400
+0x040 CheckSum : 0
+0x044 Subsystem : 3
+0x046 DllCharacteristics : 0x8140
+0x048 SizeOfStackReserve : 0x
+0x04c SizeOfStackCommit : 0x1000
+0x050 SizeOfHeapReserve : 0x
+0x054 SizeOfHeapCommit : 0x1000
+0x058 LoaderFlags : 0
+0x05c NumberOfRvaAndSizes : 0x10
+0x060 DataDirectory : [16] _IMAGE_DATA_DIRECTORY
5.区块表
紧接着IMAGE_NT_HEADERS后的是区块表
0:001> ?? sizeof(IMAGE_NT_HEADERS)
unsigned int 0xf8
0:001> ? 0 +0n224 + 0xf8
Evaluate expression: = 012301d8
这是起始地址
注意查找结构体时使用dt,不要使用x
0:001> dt *!*IMAGE_SECTION*
test1!IMAGE_SECTION_HEADER
test1!PIMAGE_SECTION_HEADER
test1!_IMAGE_SECTION_HEADER
test1!_IMAGE_SECTION_HEADER
test1!_IMAGE_SECTION_HEADER::<unnamed-type-Misc>
MSVCR90D!IMAGE_SECTION_HEADER
MSVCR90D!PIMAGE_SECTION_HEADER
MSVCR90D!_IMAGE_SECTION_HEADER
0:001> dt IMAGE_SECTION_HEADER 012301d8
test1!IMAGE_SECTION_HEADER
+0x000 Name : [8] ".textbss"
+0x008 Misc : _IMAGE_SECTION_HEADER::<unnamed-type-Misc>
+0x00c VirtualAddress : 0x1000
+0x010 SizeOfRawData : 0
+0x014 PointerToRawData : 0
+0x018 PointerToRelocations : 0
+0x01c PointerToLinenumbers : 0
+0x020 NumberOfRelocations : 0
+0x022 NumberOfLinenumbers : 0
+0x024 Characteristics : 0xe00000a0
0:001> ?? sizeof(IMAGE_SECTION_HEADER)
unsigned int 0x28
0:001> dt IMAGE_SECTION_HEADER 012301d8+0x28
test1!IMAGE_SECTION_HEADER
+0x000 Name : [8] ".text"
+0x008 Misc : _IMAGE_SECTION_HEADER::<unnamed-type-Misc>
+0x00c VirtualAddress : 0x11000
+0x010 SizeOfRawData : 0x3600
+0x014 PointerToRawData : 0x400
+0x018 PointerToRelocations : 0
+0x01c PointerToLinenumbers : 0
+0x020 NumberOfRelocations : 0
+0x022 NumberOfLinenumbers : 0
+0x024 Characteristics : 0x
0:001> dt IMAGE_SECTION_HEADER 012301d8+0x28*2
test1!IMAGE_SECTION_HEADER
+0x000 Name : [8] ".rdata"
+0x008 Misc : _IMAGE_SECTION_HEADER::<unnamed-type-Misc>
+0x00c VirtualAddress : 0x15000
+0x010 SizeOfRawData : 0x1e00
+0x014 PointerToRawData : 0x3a00
+0x018 PointerToRelocations : 0
+0x01c PointerToLinenumbers : 0
+0x020 NumberOfRelocations : 0
+0x022 NumberOfLinenumbers : 0
+0x024 Characteristics : 0x
0:001> dt IMAGE_SECTION_HEADER 012301d8+0x28*3
test1!IMAGE_SECTION_HEADER
+0x000 Name : [8] ".data"
+0x008 Misc : _IMAGE_SECTION_HEADER::<unnamed-type-Misc>
+0x00c VirtualAddress : 0x17000
+0x010 SizeOfRawData : 0x200
+0x014 PointerToRawData : 0x5800
+0x018 PointerToRelocations : 0
+0x01c PointerToLinenumbers : 0
+0x020 NumberOfRelocations : 0
+0x022 NumberOfLinenumbers : 0
+0x024 Characteristics : 0xc0000040
0:001> dt IMAGE_SECTION_HEADER 012301d8+0x28*4
test1!IMAGE_SECTION_HEADER
+0x000 Name : [8] ".idata"
+0x008 Misc : _IMAGE_SECTION_HEADER::<unnamed-type-Misc>
+0x00c VirtualAddress : 0x18000
+0x010 SizeOfRawData : 0xa00
+0x014 PointerToRawData : 0x5a00
+0x018 PointerToRelocations : 0
+0x01c PointerToLinenumbers : 0
+0x020 NumberOfRelocations : 0
+0x022 NumberOfLinenumbers : 0
+0x024 Characteristics : 0xc0000040
0:001> dt IMAGE_SECTION_HEADER 012301d8+0x28*5
test1!IMAGE_SECTION_HEADER
+0x000 Name : [8] ".rsrc"
+0x008 Misc : _IMAGE_SECTION_HEADER::<unnamed-type-Misc>
+0x00c VirtualAddress : 0x19000
+0x010 SizeOfRawData : 0xe00
+0x014 PointerToRawData : 0x6400
+0x018 PointerToRelocations : 0
+0x01c PointerToLinenumbers : 0
+0x020 NumberOfRelocations : 0
+0x022 NumberOfLinenumbers : 0
+0x024 Characteristics : 0x
0:001> dt IMAGE_SECTION_HEADER 012301d8+0x28*6
test1!IMAGE_SECTION_HEADER
+0x000 Name : [8] ".reloc"
+0x008 Misc : _IMAGE_SECTION_HEADER::<unnamed-type-Misc>
+0x00c VirtualAddress : 0x1a000
+0x010 SizeOfRawData : 0x600
+0x014 PointerToRawData : 0x7200
+0x018 PointerToRelocations : 0
+0x01c PointerToLinenumbers : 0
+0x020 NumberOfRelocations : 0
+0x022 NumberOfLinenumbers : 0
+0x024 Characteristics : 0x
0:001> dt IMAGE_SECTION_HEADER 012301d8+0x28*7
test1!IMAGE_SECTION_HEADER
+0x000 Name : [8] ""
+0x008 Misc : _IMAGE_SECTION_HEADER::<unnamed-type-Misc>
+0x00c VirtualAddress : 0
+0x010 SizeOfRawData : 0
+0x014 PointerToRawData : 0
+0x018 PointerToRelocations : 0
+0x01c PointerToLinenumbers : 0
+0x020 NumberOfRelocations : 0
+0x022 NumberOfLinenumbers : 0
+0x024 Characteristics : 0
5.导入表:
导入表的入口在_IMAGE_DATA_DIRECTORY[1]中,所以取得地址
0:001> dt _IMAGE_OPTIONAL_HEADER 0 +0n224+0x18
test1!_IMAGE_OPTIONAL_HEADER
+0x058 LoaderFlags : 0
+0x05c NumberOfRvaAndSizes : 0x10
+0x060 DataDirectory : [16] _IMAGE_DATA_DIRECTORY
0:001> ?? sizeof(_IMAGE_DATA_DIRECTORY)
unsigned int 8
0:001> dt _IMAGE_DATA_DIRECTORY 0 +0n224+0x18+0x68
test1!_IMAGE_DATA_DIRECTORY
+0x000 VirtualAddress : 0x18000
+0x004 Size : 0x3c
发现IMAGE_IMPORT_DESCRIPTOR dt不到
typedef struct _IMAGE_IMPORT_DESCRIPTOR {
union {
DWORD Characteristics; // 0 for terminating null import descriptor
DWORD OriginalFirstThunk; // RVA to original unbound IAT (PIMAGE_THUNK_DATA)
};
DWORD TimeDateStamp; // 0 if not bound,
// -1 if bound, and real date ime stamp
// in IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT (new BIND)
// O.W. date/time stamp of DLL bound to (Old BIND)
DWORD ForwarderChain; // -1 if no forwarders
DWORD Name;
DWORD FirstThunk; // RVA to IAT (if bound this IAT has actual addresses)
} IMAGE_IMPORT_DESCRIPTOR;
大小明显是20,那么我们用dd来划分吧:
0:001> dd 0 +0x18000 L5
0 0001803c 00000000 00000000 00018324
0 000181a8
0:001> da 00018324+0
0 "KERNEL32.dll"
0:001> dd 0 +0x18000+0n20 L5
0 000180e8 00000000 00000000 00018346
0 00018254
0:001> da 00018346+0
0 "MSVCR90D.dll"
0:001> dd 0 +0x18000+0n20*2 L5
0 00000000 00000000 00000000 00000000
0 00000000
Name为0表示没有其他导入表了
6.查找导入函数
这是在运行中的PE文件,所以使用FirstThunk
0:001> dds 0x181a8+0x
012481a8 76b63485 kernel32!CreateThread
012481ac 76b617e9 kernel32!GetCurrentProcess
012481b0 76b7d7d2 kernel32!TerminateProcess
012481b4 76b63478 kernel32!FreeLibrary
012481b8 76b64412 kernel32!VirtualQuery
012481bc 76b64908 kernel32!GetModuleFileNameW
012481c0 76b614c9 kernel32!GetProcessHeap
012481c4 77dfe046 ntdll!RtlAllocateHeap
012481c8 76b614a9 kernel32!HeapFree
012481cc 76b634b9 kernel32!GetSystemTimeAsFileTime
012481d0 76b611f8 kernel32!GetCurrentProcessId
012481d4 76b61430 kernel32!GetCurrentThreadId
012481d8 76b6110c kernel32!GetTickCount
012481dc 76b61705 kernel32!QueryPerformanceCounter
012481e0 76b68781 kernel32!SetUnhandledExceptionFilter
012481e4 76b6498f kernel32!LoadLibraryA
012481e8 76b61222 kernel32!GetProcAddress
012481ec 76b65a03 kernel32!lstrlen
012481f0 76b6190e kernel32!MultiByteToWideChar
012481f4 76b616ed kernel32!WideCharToMultiByte
012481f8 76be4755 kernel32!DebugBreak
012481fc 76b6585e kernel32!RaiseException
0 76b64a15 kernel32!IsDebuggerPresent
0 76b61464 kernel32!InterlockedCompareExchange
0 76b610ff kernel32!Sleep
0c 76b61442 kernel32!InterlockedExchange
0 76b876f7 kernel32!UnhandledExceptionFilter
0 00000000
0:001> dds 00018254+0x
0 6a4df8d0 MSVCR90D!__dllonexit [f:ddvctoolscrt_bldself_x86crtsrconexit.c @ 267]
0 6a44d430 MSVCR90D!_lock [f:ddvctoolscrt_bldself_x86crtsrcmlock.c @ 333]
0c 6a44d480 MSVCR90D!_unlock [f:ddvctoolscrt_bldself_x86crtsrcmlock.c @ 371]
0 6a44e170 MSVCR90D!_decode_pointer [f:ddvctoolscrt_bldself_x86crtsrc idtable.c @ 161]
0 6a4eca80 MSVCR90D!_except_handler4_common
0 6a4ec880 MSVCR90D!_crt_debugger_hook [f:ddvctoolscrt_bldself_x86crtsrcdbghook.c @ 62]
0c 6a4df460 MSVCR90D!_invoke_watson [f:ddvctoolscrt_bldself_x86crtsrcinvarg.c @ 137]
0 6a4fbc20 MSVCR90D!_controlfp_s
0 6a4c0280 MSVCR90D!terminate [f:ddvctoolscrt_bldself_x86crtprebuildehhooks.cpp @ 95]
0 6a44c040 MSVCR90D!_initterm_e [f:ddvctoolscrt_bldself_x86crtsrccrt0dat.c @ 938]
0c 6a44c010 MSVCR90D!_initterm [f:ddvctoolscrt_bldself_x86crtsrccrt0dat.c @ 889]
0 6a4de7b0 MSVCR90D!_CrtDbgReportW [f:ddvctoolscrt_bldself_x86crtsrcdbgrpt.c @ 252]
0 6a4e3010 MSVCR90D!_CrtSetCheckCount [f:ddvctoolscrt_bldself_x86crtsrcdbgheap.c @ 3241]
0 6a MSVCR90D!__winitenv
0c 6a44b9d0 MSVCR90D!exit [f:ddvctoolscrt_bldself_x86crtsrccrt0dat.c @ 411]
0 6a44ba10 MSVCR90D!_cexit [f:ddvctoolscrt_bldself_x86crtsrccrt0dat.c @ 426]
0 6a4e32e0 MSVCR90D!_XcptFilter [f:ddvctoolscrt_bldself_x86crtsrcwinxfltr.c @ 206]
0 6a44b9f0 MSVCR90D!_exit [f:ddvctoolscrt_bldself_x86crtsrccrt0dat.c @ 419]
0c 6a44c600 MSVCR90D!__wgetmainargs [f:ddvctoolscrt_bldself_x86crtsrccrtlib.c @ 98]
012482a0 6a44ba50 MSVCR90D!_amsg_exit [f:ddvctoolscrt_bldself_x86crtsrccrt0dat.c @ 460]
012482a4 6a44ad60 MSVCR90D!__set_app_type [f:ddvctoolscrt_bldself_x86crtsrcerrmode.c @ 87]
012482a8 6a44e070 MSVCR90D!_encode_pointer [f:ddvctoolscrt_bldself_x86crtsrc idtable.c @ 86]
012482ac 6a44cf90 MSVCR90D!__p__fmode [f:ddvctoolscrt_bldself_x86crtsrccrtlib.c @ 748]
012482b0 6a44cef0 MSVCR90D!__p__commode [f:ddvctoolscrt_bldself_x86crtsrccrtlib.c @ 714]
012482b4 6a5266cc MSVCR90D!_adjust_fdiv
012482b8 6a44ada0 MSVCR90D!__setusermatherr
012482bc 6a4e6d80 MSVCR90D!_configthreadlocale [f:ddvctoolscrt_bldself_x86crtsrcsetlocal.c @ 420]
012482c0 6a4eb420 MSVCR90D!_CRT_RTC_INITW
012482c4 6a MSVCR90D!getchar [f:ddvctoolscrt_bldself_x86crtsrcfgetchar.c @ 45]
012482c8 6a46abb0 MSVCR90D!printf [f:ddvctoolscrt_bldself_x86crtsrcprintf.c @ 49]
012482cc 6a4df660 MSVCR90D!_onexit [f:ddvctoolscrt_bldself_x86crtsrconexit.c @ 85]
012482d0 6a4cd680 MSVCR90D!operator new [f:ddvctoolscrt_bldself_x86crtsrc ew.cpp @ 57]
也可以通过
OriginalFirstThunk
来查找,先找到列表:1803c为Kernel32项第一个DWORD
typedef struct _IMAGE_THUNK_DATA32 {
union {
DWORD ForwarderString; // PBYTE 指向一个转向者字符串的RVA
DWORD Function; // PDWORD被输入函数的内存地址
DWORD Ordinal; // 被输入的API序数号
DWORD AddressOfData; // PIMAGE_IMPORT_BY_NAME
} u1;
} IMAGE_THUNK_DATA32;
这个函数同样在我的exe上没有找到,但很明显,这是一个4字节的数组
0:001> dd 0x1803c+0x
0c 00018314 000186e4 000186d0 000186c2
0c 000186b2 0001869c 0001868a 0001867e
0c 00018672 00018658 00018642 0001862c
0c 0001861c 00018602 000185e4 000185d4
0c 000185c2 000185b6 000185a0 0001858a
0c 0001857c 0001856a 00018556 00018538
0c 00018530 0001851a 000186f8 00000000
012480ac 00000000 00000000 00000000 00000000
然后这里面的内存又指向以下结构
typedef struct _IMAGE_IMPORT_BY_NAME {
WORD Hint;
BYTE Name[1];
} IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;
所以有:
0:001> da 00018314+0x+2
0 "CreateThread"
0:001> da 000186e4+0x+2
012486e6 "GetCurrentProcess"
使用一个debug程序:
.cxr
设备上下文的常用含义是一组寄存器,表示处理器在某个特定时刻的状态,因此也称为寄存器上下文,当生成异常时,寄存器上下文可以通过异常分发器的保码保存到栈上,并且在引发异常时可以用来恢复寄存器的值。
如何找到这个上下文?最简单的方式是从异常分发过程中使用的各种函数的参数中获取上下文,或者通过栈中搜索上下文,无论通过何种方式找到寄存器上下文,都可以通过.cxr <context address>来将其设置为当前的上下文
KiUserExceptionDispatcher( PEXCEPTION_RECORD pExcptRec, CONTEXT * pContext )
第二个参数就是上下文件地址了
0:005> dt CONTEXT
02sample!CONTEXT
+0x000 ContextFlags : Uint4B
+0x004 Dr0 : Uint4B
+0x008 Dr1 : Uint4B
+0x00c Dr2 : Uint4B
+0x010 Dr3 : Uint4B
+0x014 Dr6 : Uint4B
第一个参数ContextFlags的取值是定义的长量,一盘为0x0001003f,表示常量CONTEXT_ALL,所以我们在栈中进行搜索某个内存块的含义时,这个标志是非常有用的,我们可以通过这个标志来找到上下文,并将其设置为当前线程的上下文,从而了解在异常发生之前处理器的状态是什么
0:000> *在地址空间的前256M中搜索完整的上下文
0:000> s -d 0 L/4 0001003f
0009fd24 0001003f 00000000 00000000 00000000 ?...............
001c3b08 0001003f 0001004d 00000000 00000400 ?...M...........
005efd24 0001003f 00000000 00000000 00000000 ?...............
007efd24 0001003f 00000000 00000000 00000000 ?...............
0:000> *将找到的上下文设置到这个地址上
0:000> .cxr 0009fd24
eax=00000000 ebx=00000000 ecx=00000000 edx=00000000 esi=00000000 edi=00000000
eip=7746e8ac esp=000dfd34 ebp=000dfe50 iopl=0 nv up ei pl nz na po nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000202
ntdll!NtDeviceIoControlFile+0xc:
7746e8ac c22800 ret 28h
0:000> k
* Stack trace for last set context - .thread/.cxr resets it
# ChildEBP RetAddr
WARNING: Stack unwind information not available. Following frames may be wrong.
00 000dfe50 74a3d108 ntdll!NtDeviceIoControlFile+0xc
01 000dfe78 74aa16b4 KERNELBASE!GetConsoleOutputCP+0x58
02 000dfea8 765fa582 KERNELBASE!SetConsoleMode+0x24
03 000dfee4 765fa4a8 msvcrt!getwch+0x112
04 000dff18 01001d63 msvcrt!getwch+0x38
05 000dff30 01001c7b 02sample!AppInfo::Loop+0xb3 [c:awdcommonmenu.h @ 47]
06 000dff3c 0 02sample!wmain+0x1b [c:awdchapter2sample.cpp @ 228]
07 000dff80 73fd8674 02sample!__wmainCRTStartup+0x102 [d:vistartmbasecrtscrtw32dllstuffcrtexe.c @ 711]
08 000dff94 77464b47 KERNEL32!BaseThreadInitThunk+0x24
09 000dffdc 77464b17 ntdll!RtlGetAppContainerNamedObjectPath+0x137
0a 000dffec 00000000 ntdll!RtlGetAppContainerNamedObjectPath+0x107
0:000> *恢复到默认的上下文
0:000> .cxr
Resetting default scope
0:000> k
# ChildEBP RetAddr
00 000dff18 01001d63 02sample!RaiseAV+0x10 [c:awdchapter2sample.cpp @ 55]
01 000dff30 01001c7b 02sample!AppInfo::Loop+0xb3 [c:awdcommonmenu.h @ 47]
02 000dff3c 0 02sample!wmain+0x1b [c:awdchapter2sample.cpp @ 228]
03 000dff80 73fd8674 02sample!__wmainCRTStartup+0x102 [d:vistartmbasecrtscrtw32dllstuffcrtexe.c @ 711]
WARNING: Stack unwind information not available. Following frames may be wrong.
04 000dff94 77464b47 KERNEL32!BaseThreadInitThunk+0x24
05 000dffdc 77464b17 ntdll!RtlGetAppContainerNamedObjectPath+0x137
06 000dffec 00000000 ntdll!RtlGetAppContainerNamedObjectPath+0x107
.frame
.frame命令指定使用哪个局部上下文(作用域)来解析局部变量,或者显示当前的局部上下文
所谓局部上下文,是指局部变量所基于的语境,局部变量是指定义在函数内部的变量,这些变量的含义与当前的执行位置密切相关,在调试时,调试器默认显示的是当前函数(程序指针)所对应的局部上下文,因为当前函数和局部变量都是与栈帧密切相关的,所以windbg通常使用栈帧号来表示局变上下文
帧序号(frame number)是堆栈帧在堆栈回溯中的位置。可以使用k (Display Stack Backtrace)命令或者Calls 窗口查看堆栈回溯。第一行 (当前帧) 的帧序号是0。后面的行分别是1、2、3等等。
0:000> kn
# ChildEBP RetAddr
00 0012f78c 7c92daea ntdll!KiFastSystemCallRet
01 0012f790 7c ntdll!ZwRequestWaitReplyPort+0xc
02 0012f7b0 7c872a51 ntdll!CsrClientCallServer+0x8c
03 0012f8ac 7c872b98 kernel32!ReadConsoleInternal+0x1be
04 0012f934 7c8018b7 kernel32!ReadConsoleA+0x3b
05 0012f98c 102c207c kernel32!ReadFile+0x64
06 0012fa20 102c19c9 MSVCR90D!_read_nolock+0x62c [f:ddvctoolscrt_bldself_x86crtsrc ead.c @ 233]
07 0012fa70 10253e43 MSVCR90D!_read+0x219 [f:ddvctoolscrt_bldself_x86crtsrc ead.c @ 93]
08 0012fa98 e8 MSVCR90D!_filbuf+0x113 [f:ddvctoolscrt_bldself_x86crtsrc_filbuf.c @ 136]
09 0012faf0 MSVCR90D!getc+0x208 [f:ddvctoolscrt_bldself_x86crtsrcfgetc.c @ 76]
0a 0012fafc a MSVCR90D!_fgetchar+0x10 [f:ddvctoolscrt_bldself_x86crtsrcfgetchar.c @ 37]
0b 0012fb04 0041160b MSVCR90D!getchar+0xa [f:ddvctoolscrt_bldself_x86crtsrcfgetchar.c @ 47]
0c 0012fbe4 004114b2 test2!MyCls::hold+0x2b [d:project1 est2 est2 est2.cpp @ 28]
0d 0012fcec 0041167a test2!foo1+0xa2 [d:project1 est2 est2 est2.cpp @ 39]
0e 0012fdc0 004116ea test2!foo2+0x3a [d:project1 est2 est2 est2.cpp @ 45]
0f 0012fe94 00 test2!foo3+0x3a [d:project1 est2 est2 est2.cpp @ 51]
10 0012ff68 00411ce8 test2!main+0x23 [d:project1 est2 est2 est2.cpp @ 56]
11 0012ffb8 00411b2f test2!__tmainCRTStartup+0x1a8 [f:ddvctoolscrt_bldself_x86crtsrccrtexe.c @ 586]
12 0012ffc0 7c test2!mainCRTStartup+0xf [f:ddvctoolscrt_bldself_x86crtsrccrtexe.c @ 403]
13 0012fff0 00000000 kernel32!BaseProcessStart+0x23
0:000> .frame d
0d 0012fcec 0041167a test2!foo1+0xa2 [d:project1 est2 est2 est2.cpp @ 39]
0:000> x
0012fce4 pcls = 0x00392a28
0012fcd8 rawptr = 0x00392a28
.frame 帧号,可以把局部上下文切换到指定的栈帧
之后跟x,可以显示当前这个函数里面的局部变量
对应d帧的代码:
void foo1()
{
MyCls *pcls=new MyCls();
void *rawptr=pcls;
pcls->set("abcd");
pcls->output();
pcls->hold();
};
x显示的是pcls和rawptr
dt
dt命令显示局部变量、全局变量或数据类型的信息。它也可以仅显示数据类型。即结构和联合(union)的信息
dt最方便处是查找结构体,查找结构体一定要使用dt,不要使用x
0:000:x86> dt *!*IMAGE_DOS*
test1!IMAGE_DOS_HEADER
test1!PIMAGE_DOS_HEADER
test1!_IMAGE_DOS_HEADER
ntdll!_IMAGE_DOS_HEADER
ntdll32!_IMAGE_DOS_HEADER
MSVCR90D!IMAGE_DOS_HEADER
MSVCR90D!PIMAGE_DOS_HEADER
MSVCR90D!_IMAGE_DOS_HEADER
dt -b this可以打印this指针,只在类中起作用
0:000:x86> dt -b this
Local var @ 0x1cfd00 Type CLook*
0x00
+0x000 m_i : 0n
+0x004 m_j : 37 'Unknown format character
如果只想显示某个字段,可以用-ny 加上搜索选项,如:
0:000> dt -v ntdll!_PEB -ny BeingDebugged @$peb
struct _PEB, 91 elements, 0x248 bytes
+0x002 BeingDebugged : 0x1 ''
-v是详细输出。这会输出结构的总大小和字段数量这样的附加信息
也可以直接这样看大小:
0:000:x86> ?? sizeof(IMAGE_DOS_HEADER)
unsigned int 0x40
也可以用-y 来连接通配符,如:
0:000> dt -v ntdll!_PEB -y B* @$peb
struct _PEB, 91 elements, 0x248 bytes
+0x002 BeingDebugged : 0x1 ''
+0x003 BitField : 0x8 ''
0:000> dt pcls
Local var @ 0x12fce4 Type MyCls*
0x00392a28
+0x000 str : 0x00 "abcd"
+0x004 inobj : innner
0:000> dt rawptr
Local var @ 0x12fcd8 Type void*
0x00392a28
0:000> dt -b-r pcls
Local var @ 0x12fce4 Type MyCls*
0x00392a28
+0x000 str : 0x00 "abcd"
+0x004 inobj : innner
+0x000 arr : "abcd"
[00] 97 'a'
[01] 98 'b'
[02] 99 'c'
[03] 100 'd'
[04] 0 ''
[05] 0 ''
[06] 0 ''
[07] 0 ''
[08] 0 ''
[09] 0 ''
.dump 命令创建一个用户模式或内核模式崩溃转储文件。分析工具:https://msdn.microsoft.com/en-us/library/windows/desktop/ee(v=vs.85).aspx#writing_a_minidump
程序崩溃(crash)的时候, 为了以后能够调试分析问题, 可以使用WinDBG要把当时程序内存空间数据都保存下来,生成的文件称为dump 文件。 步骤:
1) 打开WinDBG并将之Attach 到crash的程序进程
2) 输入产生dump 文件的命令
直接用.dump -?可以看到它的简单说明:
0:000> .dump -?
Usage: .dump [options] filename
Options are:
/a - Create dumps for all processes (requires -u)
/b[a] - Package dump in a CAB and delete dump
/c <comment> - Add a comment (not supported in all formats)
/j <addr> - Provide a JIT_DEBUG_INFO address
/f - Create a legacy style full dump
/m[acdfFhiprRtuw] - Create a minidump (default)
/o - Overwrite any existing file
/u - Append unique identifier to dump name
/o :覆盖具有相同名字的dump文件。如果没有使用该选项又存在一个相同名字的文件,则dump文件不会被写入:比如我的C盘原有一个myapp.dmp文件:
0:000> .dump c:/myapp.dmp
Unable to create file 'c:/myapp.dmp' - Win32 error 0n80
"文件存在。"
0:000> .dump /o c:/myapp.dmp
Creating c:/myapp.dmp - mini user dump
Dump successfully written
/f (用户模式:) 创建一个完整用户模式dump,这里要注意不要字面理解,
完整用户模式dump是基本的用户模式dump文件。这种dump文件包含进程的完整内存空间、程序本身的可执行映像、句柄表和其他对调试器有用的信息
注意 和名字无关,最大的"minidump"文件实际上可以提供比完整用户模式dump更多的信息。例如,.dump /mf 或.dump /ma将创建比.dump /f更大更完整的文件。
用户模式下,使用.dump /m[MiniOptions] 是最好的选择。通过这个开关创建的dump文件可以很小也可以很大。通过指定合适的MiniOptions 可以控制究竟需要包含哪些信息。
0:000> .dump /o/f c:/myapp.dmp
*
* .dump /ma is the recommend method of creating a complete memory dump *
* of a user mode process. *
*
Creating c:/myapp.dmp - user full dump
Dump successfully written
我们看到了,系统给出了提示:.dump /ma是创建完整dump的推荐方式(用户模式下)
/m[MiniOptions] 创建一个小内存dump(内核模式)或者minidump(用户模式)。如果没有指定 /f 和/m ,/m 是默认选项。用户模式下,/m 后面可以跟附加的MiniOptions 用来指定dump文件中包含的数据。如果没有使用MiniOptions ,dump文件包含模块、线程和调用堆栈信息,但是没有其他附加信息
MiniOption 作用
a 创建一个包含所有附加选项的minidump。/ma选项相当于/mfFhut —它会在minidump中添加完整的内存数据、句柄数据、已卸载模块信息、基本内存信息和线程时间信息。
f 在minidump中包含完整内存数据。目标程序拥有的所有 可访问的已交付的页面(committed pages)都会包含进去。
F 在minidump中添加所有基本内存信息。这会将一个流加入到包含完整基本内存信息的minidump中,而不单是可使用的内存。这样可以使得调试器能够重建minidump生成时进程的完整虚拟内存布局。
h 在minidump中包含和目标进程相关的句柄信息。
u 在minidump中包含已卸载模块信息。仅在Windows Server 2003和之后版本的Windows中可用。
t 在minidump中包含附加的线程信息。包括可以在调试minidump时使用!runaway扩展命令或.ttime (Display Thread Times)命令进行显示的线程时间。
i 在minidump中包含次级内存(secondary memory)。次级内存是由堆栈中的指针或备份存储(backing store)中引用到的任何内存,加上该地址周围的一小段区域。
p 在minidump中包含进程环境块(PEB)和线程环境块(TEB)。这在想访问程序的进程和线程相关的Windows系统信息时很有用。
w 将所有已交付的可读写的私有页面包含进minidump。
d 在minidump中包含可执行映像中所有可读写的数据段。
c 加入映像中的代码段。
r 从minidump中去掉对重建调用堆栈无用的堆栈和存储内存部分。局部变量和其他数据类型值也被删除。这个选项不会使得minidump变小(因为这些内存节仅仅是变成0),但是当想保护其他程序中的机密信息时有用。
R 在minidump中去掉完整的模块路径。仅包含模块名。如果想保护用户的目录结构时该选项有用。
选项(1): /m
命令行示例:.dump /m C:/dumps/myapp.dmp
注解: 缺省选项,生成标准的minidump, 转储文件通常较小,便于在网络上通过邮件或其他方式传输。 这种文件的信息量较少,只包含系统信息、加载的模块(DLL)信息、 进程信息和线程信息。
选项(2): /ma
命令行示例:.dump /ma C:/dumps/myapp.dmp
注解: 带有尽量多选项的minidump(包括完整的内存内容、句柄、未加载的模块,等等),文件很大,但如果条件允许(本机调试,局域网环境), 推荐使用这中dump。
选项(3):/mFhutwd
命令行示例:.dump /mFhutwd C:/dumps/myapp.dmp
注解:带有数据段、非共享的读/写内存页和其他有用的信息的minidump。包含了通过minidump能够得到的最多的信息。是一种折中方案。
Fhutwd按字母对应上面的MiniOptions表示
//-xp自动生成dump-----------------------------------------------------------------------------------------------------------------------------------------------------------------
那怎么自动生成dump文件呢,比如对方的电脑没有windbg,这里用到一个window XP系统自带工具,Dr.Watson
运行方式很简单:
直接run-输入drwtsn32 -i就可以了,会提示这样的:
这个命令真难记,实话,记华生医生吧,福尔摩斯中的
如果有程序崩溃,会自动生成dump,这时再输入drwtsn32就会运行这个程序:
找到对应路径的DMP文件就行了,一般放在如下路径:
C:Documents and SettingsAll UsersApplication DataMicrosoftDr Watson
//-win7自动生成dump-----------------------------------------------------------------------------------------------------------------------------------------------------------------
win7下需打开regedit--> 找到:
[HKEY_LOCAL_MACHINESOFTWAREMicrosoftWindowsWindows Error Reporting]
在它下面加一项LocalDumps,并做如下项配置:
Value 描述 Type 默认值
DumpFolder 文件保存路径 REG_EXPAND_SZ %LOCALAPPDATA%CrashDumps
DumpCount dump文件的最大数目 REG_DWORD 10
DumpType 指定生成的dump类型:
0:Custom dump
1:Mini dump
2:Full dump REG_DWORD 1
CustomDumpFlags 仅在DumpType为0时使用
为MINIDUMP_TYPE的组合 REG_DWORD
MiniDumpWithDataSegs|
MiniDumpWithUnloadedModules|
MiniDumpWithProcessThreadData
可以写成.bat:
@echo off
echo 设置Dump...
reg add "HKEY_LOCAL_MACHINESOFTWAREMicrosoftWindowsWindows Error ReportingLocalDumps"
reg add "HKEY_LOCAL_MACHINESOFTWAREMicrosoftWindowsWindows Error ReportingLocalDumps" /v DumpFolder /t REG_EXPAND_SZ /d "C:MyDump" /f
reg add "HKEY_LOCAL_MACHINESOFTWAREMicrosoftWindowsWindows Error ReportingLocalDumps" /v DumpType /t REG_DWORD /d 2 /f
reg add "HKEY_LOCAL_MACHINESOFTWAREMicrosoftWindowsWindows Error ReportingLocalDumps" /v DumpCount /t REG_DWORD /d 10 /f
echo Dump已经设置
pause
@echo on
@echo off
echo 正在取消设置Dump...
reg delete "HKEY_LOCAL_MACHINESOFTWAREMicrosoftWindowsWindows Error ReportingLocalDumps" /f
echo Dump已经取消设置
pause
@echo on
LocalDumps是全局的,如果想针对指定进程单独设置,如test1.exe,则在/LocalDumps下新建子项test1.exe,同时在test1目录下复制上表的选项,这样,系统就会先读全局设置,再读子项test1.exe的设置
.lastevent
.lastevent 命令显示最近一次发生的异常或事件。
0:000> .lastevent
Last event: 1534.f4c: Break instruction exception - code (first chance)
debugger time: Tue May 22 10:47:26.962 2012 (GMT+8)
0:000> ~
. 0 Id: 1534.e8c Suspend: 1 Teb: 7ffdf000 Unfrozen
1 Id: 1534.1338 Suspend: 1 Teb: 7ffde000 Unfrozen
# 2 Id: 1534.f4c Suspend: 1 Teb: 7ffdd000 Unfrozen
我们可以看出,当前为2号线程发生异常,线程0前面的点号(.)表示它是当前线程。线程2前面的数字号(#)表示它是产生异常或调试器附加到进程时活动的线程。如果使用CTRL+C、 CTRL+BREAK或Debug | Break中断到调试器,总是会产生一个 0x异常代码。
0:000> .lastevent
Last event: 1664.4184: Access violation - code c0000005 (first/second chance not available)
debugger time: Thu Aug 13 11:20:44.037 2015 (GMT+8)
异常错误码查询
异常
值
描述
EXCEPTION_ACCESS_VIOLATION
0xC0000005
程序企图读写一个不可访问的地址时引发的异常。例如企图读取0地址处的内存。
EXCEPTION_ARRAY_BOUNDS_EXCEEDED
0xC000008C
数组访问越界时引发的异常。
EXCEPTION_BREAKPOINT
0x
触发断点时引发的异常。
EXCEPTION_DATATYPE_MISALIGNMENT
0x
程序读取一个未经对齐的数据时引发的异常。
EXCEPTION_FLT_DENORMAL_OPERAND
0xC000008D
如果浮点数操作的操作数是非正常的,则引发该异常。所谓非正常,即它的值太小以至于不能用标准格式表示出来。
EXCEPTION_FLT_DIVIDE_BY_ZERO
0xC000008E
浮点数除法的除数是0时引发该异常。
EXCEPTION_FLT_INEXACT_RESULT
0xC000008F
浮点数操作的结果不能精确表示成小数时引发该异常。
EXCEPTION_FLT_INVALID_OPERATION
0xC0000090
该异常表示不包括在这个表内的其它浮点数异常。
EXCEPTION_FLT_OVERFLOW
0xC0000091
浮点数的指数超过所能表示的最大值时引发该异常。
EXCEPTION_FLT_STACK_CHECK
0xC0000092
进行浮点数运算时栈发生溢出或下溢时引发该异常。
EXCEPTION_FLT_UNDERFLOW
0xC0000093
浮点数的指数小于所能表示的最小值时引发该异常。
EXCEPTION_ILLEGAL_INSTRUCTION
0xC000001D
程序企图执行一个无效的指令时引发该异常。
EXCEPTION_IN_PAGE_ERROR
0xC0000006
程序要访问的内存页不在物理内存中时引发的异常。
EXCEPTION_INT_DIVIDE_BY_ZERO
0xC0000094
整数除法的除数是0时引发该异常。
EXCEPTION_INT_OVERFLOW
0xC0000095
整数操作的结果溢出时引发该异常。
EXCEPTION_INVALID_DISPOSITION
0xC0000026
异常处理器返回一个无效的处理的时引发该异常。
EXCEPTION_NONCONTINUABLE_EXCEPTION
0xC0000025
发生一个不可继续执行的异常时,如果程序继续执行,则会引发该异常。
EXCEPTION_PRIV_INSTRUCTION
0xC0000096
程序企图执行一条当前CPU模式不允许的指令时引发该异常。
EXCEPTION_SINGLE_STEP
0x
标志寄存器的TF位为1时,每执行一条指令就会引发该异常。主要用于单步调试。
EXCEPTION_STACK_OVERFLOW
0xC00000FD
栈溢出时引发该异常。
!analyze
!analyze扩展显示当前异常或bug check的信息。一般使用!analyze -v
分析参考:https://msdn.microsoft.com/en-us/library/windows/hardware/ff(v=vs.85).aspx
如下述为一个对NULL指针赋值生成的dump,自动生成dump可以参考16.windbg-.dump(转储文件)
0:000> !analyze -v
*
* *
* Exception Analysis *
* *
*
*
* *
* *
* Your debugger is not using the correct symbols *
* *
* In order for this command to work properly, your symbol path *
* must point to .pdb files that have full type information. *
* *
* Certain .pdb files (such as the public OS symbols) do not *
* contain the required information. Contact the group that *
* provided you with these symbols if you need this command to *
* work. *
* *
* Type referenced: kernel32!pNlsUserInfo *
* *
*
*
* *
* *
* Your debugger is not using the correct symbols *
* *
* In order for this command to work properly, your symbol path *
* must point to .pdb files that have full type information. *
* *
* Certain .pdb files (such as the public OS symbols) do not *
* contain the required information. Contact the group that *
* provided you with these symbols if you need this command to *
* work. *
* *
* Type referenced: kernel32!pNlsUserInfo *
* *
*
首先被提示,这是没有pdb文件 的
FAULTING_IP:
test2+1002
0 8900 mov dword ptr [eax],eax
FAULTING_IP:出现错误时的指令,这句明显就是把eax赋到[eax]中
EXCEPTION_RECORD: ffffffff -- (.exr 0xffffffffffffffff)
EXCEPTION_RECORD:崩溃时的异常记录,可以使用.exr查看
ExceptionAddress: 0 (test2+0x00001002)
ExceptionCode: c0000005 (Access violation)
ExceptionFlags: 00000000
NumberParameters: 2
Parameter[0]: 00000001
Parameter[1]: 00000000
Attempt to write to address 00000000
这个就很详细了,尝试向0地址写入,它其实就是调试中用到的结构体:可以直接通过MSDN查到它的定义
typedef struct _EXCEPTION_RECORD {
DWORD ExceptionCode;
DWORD ExceptionFlags;
struct _EXCEPTION_RECORD *ExceptionRecord;
PVOID ExceptionAddress;
DWORD NumberParameters;
ULONG_PTR ExceptionInformation[EXCEPTION_MAXIMUM_PARAMETERS];
} EXCEPTION_RECORD;
注意的是:上面的NumberParameter:2表示ExceptionInformation有两个附加异常码,1和0,对于多数异常来说,这些附加异常码是没有什么用的,MSDN:For most exception codes, the array elements are undefined.只有以下两个被定义了
当ExceptionCode为EXCEPTION_ACCESS_VIOLATION时,ExceptionInformation[0]=0表示线程试图读取不可访问的数据ExceptionInformation[0]=1
表示线程试图写入不可访问的地址,很明显,这里表示第二种,具体参考MSDN
DEFAULT_BUCKET_ID: NULL_POINTER_READ
DEFAULT_BUCKET_ID:表示了本次错误属于哪种通用失败
BUGCHECK_STR: APPLICATION_FAULT_NULL_POINTER_READ_NULL_POINTER_WRITE
The BUGCHECK_STR field shows the exception code. The name is a misnomer—the term bug check actually signifies a kernel-mode crash. In user-mode debugging, the exception code will be displayed—in this case, 0x.
LAST_CONTROL_TRANSFER: from 76b6337a to 0
堆栈的最后调用:
The LAST_CONTROL_TRANSFER field shows the last call on the stack. In this case, the code at address0x76b6337acalled a function at 0x. You can use these addresses with the ln (List Nearest Symbols) command to determine what modules and functions these addresses reside in.
STACK_TEXT:
WARNING: Stack unwind information not available. Following frames may be wrong.
0020fbb8 76b6337a 7efde000 0020fc04 77e092b2 test2+0x1002
0020fbc4 77e092b2 7efde000 596d1564 00000000 kernel32!BaseThreadInitThunk+0xe
0020fc04 77e09285 012112b6 7efde000 00000000 ntdll!__RtlUserThreadStart+0x70
0020fc1c 00000000 012112b6 7efde000 00000000 ntdll!_RtlUserThreadStart+0x1b
堆信息
STACK_COMMAND: ~0s; .ecxr ; kb
打印堆栈的命令
SYMBOL_NAME: test2+1002
对应的符号名称
IMAGE_NAME: test2.exe
对应的模块名称
3.符号文件简介:
符号文件对于调试程序是相当重要的,通常符号文件中包含以下内容
全局变量的名字和地址
函数名,地址及其原型
帧指针优化数据
局部变量的名字和地址
源文件路径以及每个符号的行号
变量,结构等的类型信息
ln
ln 命令显示给定地址处的或者最近的符号。
ln表示list near,ln命令将尽可能地给出与特定地址相关的符号,如果没有符号能够精确地与这个地址匹配,那么调试器将通过指针算法对靠近这地址的符号进行运逄,
并返回运算结果符号
0:000> ln 0
(0) calc!WinMainCRTStartup | (0c) calc!__CxxFrameHandler
Exact matches:
calc!WinMainCRTStartup = <no type information>
0:000> ln 0+1
(0) calc!WinMainCRTStartup+0x1 | (0c) calc!__CxxFrameHandler
我们发现,第一个显示为Exact matches:表示精确匹配了一个地址,如果不是精确匹配,我们要小心,是否模块进行了优化,在优化后,一个函数,可能被拆分为多个部分
分别位于不同的地址,经过优化的映像可以通过lm查看:会有perf标识
当你在查看某部分数据,却不知道这部分数据所表示的内容时,这个命名能带来极大的帮助
伪寄存器
伪寄存器都是通过r来操作的
输入Pseudo-Register Syntax查询
对于那些偶尔使用调试器的用户是很难记得所有平台的指令指针寄存器名字(或其他的名字),为了克服这个问题,调试器的开发团队引入了各种伪寄存器,由调试器把这些伪寄存器对应到不同的硬件架构上,形式为$name,与标准的寄存器一样,如果要在表达式中使用伪寄存器,那么必须使用转义字符@
$exentry
当前进程的入口地址
0:002> r $exentry
$exentry=0
一般可以直接在这下断点,
这个就对应PE文件中的ImageBase+AddressOfEntryPoint(_IMAGE_OPTIONAL_HEADER)
$ip
指令指针寄存器
在X86架构上,$ip = eip
在x64架构上,$ip = rip
在Itanium架构上, $ip = iip
x86/x64/ia-64的区别
0:000> r @$ip
$ip=7c92120e
0:000> r eip
eip=7c92120e
注意到下面显示的分别是$ip,eip,虽然它们在X86下是同一个东东.
$ra
当前函数的返回地址
0:000> r $ra
$ra=7c95e612
0:000> kb
ChildEBP RetAddr Args to Child
0012fb1c 7c95e612 7ffdd000 7ffde000 00000000 ntdll!DbgBreakPoint
0012fc94 7c94108f 0012fd30 7c 0012fce0 ntdll!LdrpInitializeProcess+0xffa
0012fd1c 7c92e437 0012fd30 7c 00000000 ntdll!_LdrpInitialize+0x183
00000000 00000000 00000000 00000000 00000000 ntdll!KiUserApcDispatcher+0x7
其实也是对应当前线程,如果要看所有线程的当前函数的返回地址:
0:000> ~* r $ra
$ra=77d191be
$ra=7c92df2c
$ra=7c92df3c
$ra=7c
$retreg
主要的值寄存器,在函数调用返回后,函数的结果将放在这个寄存器中,根据处理器架构的不同,$retreg的值分别为
在x86架构上,$retreg = eax
在x64架构上,$retreg = rax
在Itanium架构上,$retreg = ret0
0:000> r $retreg
$retreg=00251eb4
0:000> r eax
eax=00251eb4
$csp
当前的栈指针,根据处理器架构的不同,$csp的值分别为
在x86架构上,$csp = esp
在x64架构上,$csp = rsp
在Itanium架构上,$csp = bsp
0:000> r $csp
$csp=0012fb24
0:000> r esp
esp=0012fb24
$tpid
当前进程的标识(PID)
0:000> r $tpid
$tpid=000013f4
$tid
当前线程的标识(TID0
0:000> r $tid
$tid=000014a0
伪寄存器 描述
$ea 最后一条被执行指令的有效地址(effective address)。如果这条指令没有一个有效地址,将显示"Bad register error"。如果这条指令有两个有效地址,则显示第一个地址。
$ea2 最后一条被执行指令的第二个有效地址,如果这条指令没有两个有效地址,将显示"Bad register error"。
$exp 最后一个被求值的表达式。
$ra 当前堆栈的返回地址。
这个在执行命令中特别有用。例如,g @$ra 将一直执行到返回地址处(虽然,对于“步出(stepping out)”当前函数gu (Go Up)是一个更加准备有效的方法)。
$ip 指令指针寄存器:
x86 处理器:和 eip 相同
Itanium 处理器:涉及 iip(请看表后的注解)
x64处理器:和rip相同
$eventip 当前事件发生时的指令指针,通常和 $ip 匹配,除非你切换了线程或者手动改变了指令指针的值。
$previp 前一个事件发生时的指令指针。(中断进入调试器算做一个事件。)
$relip 和当前事件相关的指令指针,当你正在跟踪分支指令时,这个是分支来源指针。
$scopeip 当前局部上下文(也称为作用域)的指令指针。
$exentry 当前进程的第一个可执行的入口点地址。
$retreg 主要的返回值寄存器:
x86 处理器:和 eax 相同
Itanium 处理器:和 ret0 相同
x64 处理器:和 rax 相同
$retreg64 主要的返回值寄存器,以64位格式。
x86处理器:和edx:eax 相同
$csp 当前调用堆栈指针,是一个通常表示调用堆栈深度的寄存器。
x86 处理器:和 esp 相同
Itanium 处理器:和 bsp 相同
x64 处理器:和 rsp 相同
$p 最后一条 d* (Display Memory)命令打印的值。
$proc 当前进程的地址(换句话说,就是 EPROCESS 块的地址)。
$thread 当前线程的地址(换句话说,就是 ETHREAD 块的地址)。
$peb 当前进程的进程环境块(PEB)的地址。
$teb 当前线程的线程环境块(TEB)的地址。
$tpid 当前线程所在进程的进程 ID(PID)。
$tid 当前线程的线程 ID。
$bpNumber 对应断点的地址。例如,$bp3(或者 $bp03)引用断点 ID 为 3 的断点。Number 总是一个十进制数,如果没有哪个断点的 ID 为Number,则$bpNumber 求值为 0,详细请看使用断点。
$frame 当前帧索引,这个和.frame (Set Local Context) 命令常用的 frame number 相同。
$dbgtime 当前时间,根据调试器运行的计算机。
$callret 被.call (Call Function)命令调用的或者被.fnret /s命令使用的最后函数得到的返回值,$callret 的数据类型就是返回值的数据类型。
$lastclrex 仅托管代码调试: 最近一次遇到的公共语言运行时(CLR)异常对象的地址。
$ptrsize 指针大小。在内核模式下,指目标计算机上的指针大小。
$pagesize 一个内存页的大小(也就是占用的字节数目),在内核模式下,指目标计算机上的页大小。
自定义伪寄存器
有二十个自定义伪寄存器:$t0, $t1, ..., $t19。它们是可以通过调试器读写的变量。能用来保存任意整数值。做为循环变量时非常有用
0:000> x
0026fb80 argc = 1
0026fb84 argv = 0x002d33a0
0026fb70 p = 0x002d1b90
0:000> r $t0
$t0=00000000
0:000> r $t0 = poi(p)
0:000> r $t0
$t0=002d1b90
除非 r 命令使用了 ? 开关选项,否则一个伪寄存器总是具有整数类型。如果用到了 ? 选项,则伪寄存器可以获得赋给它的任意类型,例如,下面的命令把 UNICODE_STRING 类型和 0x0012FFBC 值赋给了 $t15(译注:这里好像是 UNICODE_STRING 类型吧!另外,如果 UNICODE_STRING 解析不了,可以用 _UNICODE_STRING)。
0:000> r? $t15 = * (UNICODE_STRING*) 0x12ffbc
下面可以看出r和r?的区别,比如我的代码有个char* g_char = "I am string";
00bc7004 test1!g_char = 0x00bc573c "I am string"
0:000> r? $t0 = test1!g_char
0:000> r $t0
$t0=00bc573c
0:000> r $t1 = test1!g_char
0:000> r $t1
$t1=00bc7004
可以看到$t0保存的是真实的test1!g_char的指针地址,而$t1只是保存了它的对象地址
所以:
0:000> da $t0
00bc573c "I am string"
0:000> da $t1
00bc7004 "<W."
0:000> da test1!g_char
00bc7004 "<W."
0:000> da poi(test1!g_char)
00bc573c "I am string"
dv(display Local variable)
dv 命令显示当前作用域的所有局部变量的名字和值。
0:000> x Simple1Demo!CSimple1DemoApp::InitInstance
00 Simple1Demo!CSimple1DemoApp::InitInstance (void)
0:000> bp 00
0:000> bl
0 e 00 0001 (0001) 0: Simple1Demo!CSimple1DemoApp::InitInstance
0:000> g
ModLoad: 62c20000 62c29000 C:WINDOWSsystem32LPK.DLL
ModLoad: 73fa0000 7400b000 C:WINDOWSsystem32USP10.dll
ModLoad: 5adc0000 5adf7000 C:WINDOWSsystem32倞me.dll
Breakpoint 0 hit
eax=0062c312 ebx=7ffdd000 ecx=00b24d28 edx=00a765dc esi=0342f76e edi=0342f6f2
eip=00 esp=0012fedc ebp=0012fefc iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202
Simple1Demo!CSimple1DemoApp::InitInstance:
00 55 push ebp
0:000> dv
this = 0x0012fecc
dlg = class CSimple1DemoDlg
InitCtrls = struct tagINITCOMMONCONTROLSEX
nResponse = -
我的代码是这样的:
BOOL CSimple1DemoApp::InitInstance()
{
INITCOMMONCONTROLSEX InitCtrls;
InitCtrls.dwSize = sizeof(InitCtrls);
InitCtrls.dwICC = ICC_WIN95_CLASSES;
InitCommonControlsEx(&InitCtrls);
CWinAppEx::InitInstance();
AfxEnableControlContainer();
SetRegistryKey(_T("应用程序向导生成的本地应用程序"));
m_hDll = LoadLibrary(TEXT("SkinHgy.dll"));
if (m_hDll)
{
#ifdef UNICODE
SkinLoadFileSkin = (fun_SkinLoadFileSkin)GetProcAddress(m_hDll, "SkinLoadFileSkinW");
#else
SkinLoadFileSkin = (fun_SkinLoadFileSkin)GetProcAddress(m_hDll, "SkinLoadFileSkinA");
#endif
if (SkinLoadFileSkin)
{
TCHAR wcPath[MAX_PATH] = {0};
::GetModuleFileName(NULL, wcPath, MAX_PATH);
CString szSkinPath = wcPath;
szSkinPath = szSkinPath.Left(szSkinPath.ReverseFind('\'));
szSkinPath += TEXT("\skin\skin.xml");
SkinLoadFileSkin(szSkinPath);
}
}
m_bDragFull = IsDragFullWindows();
if (m_bDragFull)
{
::SystemParametersInfo(SPI_SETDRAGFULLWINDOWS, FALSE, NULL, 0);
}
CSimple1DemoDlg dlg;
m_pMainWnd = &dlg;
INT_PTR nResponse = dlg.DoModal();
if (nResponse == IDOK)
{
}
else if (nResponse == IDCANCEL)
{
}
return FALSE;
}
我们可以看到,的确显示的四个局部变量,当然,出现的顺序可能并不一致,
/i 使得输出中显示变量的类型:局部、全局、参数、函数或未知:
0:000> dv /i
prv local this = 0x0012fecc
prv local dlg = class CSimple1DemoDlg
prv local InitCtrls = struct tagINITCOMMONCONTROLSEX
prv local nResponse = -
数据结构和陌生的数据类型不会完整显示,而只显示他们的类型名。要显示整个结构或结构中的特定成员,使用dt (Display Type)命令。
0:000> dt InitCtrls
Local var @ 0x12feb0 Type tagINITCOMMONCONTROLSEX
+0x000 dwSize : 0xb1c238
+0x004 dwICC : 4
贴一下tagINITCOMMONCONTROLSEX的定义:
typedef struct tagINITCOMMONCONTROLSEX {
DWORD dwSize; // size of this structure
DWORD dwICC; // flags indicating which classes to be initialized
} INITCOMMONCONTROLSEX, *LPINITCOMMONCONTROLSEX;
我们继续F10单步调试,到了系统函数COMCTL32!InitCommonControlsEx中,F11进入,这时再dv
0:000> t
eax=0012feb0 ebx=7ffdd000 ecx=00b24d28 edx=00a765dc esi=0012f7c0 edi=0012fecc
eip= esp=0012f7b8 ebp=0012fed8 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202
COMCTL32!InitCommonControlsEx:
8bff mov edi,edi
0:000> dv
Unable to enumerate locals, HRESULT 0x
Private symbols (symbols.pri) are required for locals.
Type ".hh dbgerr005" for details.
显示不了local,这是因为dv命令是显示当前函数的局部变量,而当前函数是系统的函数(比如ntdll!DbgBreakPoint),那么因为没有系统模块的私有符号,所以会出错。
建议你切换到0号线程(~0s),然后使用单步或者gu命令执行到CSimple1Demo模块中的函数,然后再用dv命令
我们dt this看看:
0:000> dt this
Local var @ 0x12fec0 Type CSimple1DemoApp*
0x0012fecc
+0x000 __VFN_table : 0x0012fed8
=00a85844 CObject::classCObject : CRuntimeClass
=00a78d54 CCmdTarget::classCCmdTarget : CRuntimeClass
=00a78d18 CCmdTarget::_commandEntries : [1] AFX_OLECMDMAP_ENTRY
=00a78d24 CCmdTarget::commandMap : AFX_OLECMDMAP
=00a78c8c CCmdTarget::_dispatchEntries : [1] AFX_DISPMAP_ENTRY
=00b1c484 CCmdTarget::_dispatchEntryCount : 0xffffffff
=00b1c488 CCmdTarget::_dwStockPropMask : 0xffffffff ......................未完
我们可以看到,第一个就是指向虚表的指针:__VFN_table
补充点小细节,用dt /f <addr>可以用它来查看任何地方的任何代码处有些什么参数和局部变量。它会关闭对值得显示并隐含/V。/f 必须是最后一个标志
0:000> dv /i /v /f VerifyTxSignDemo!WinMain
prv param @ebp+0x08 hInstance
prv param @ebp+0x0c hPrevInstance
prv param @ebp+0x10 lpCmdLine
prv param @ebp+0x14 nCmdShow
也可以使用通配符,注意用双引号
0:000> dv /f VerifyTxSignDemo!WinMain "*cmd*"
@ebp+0x10 lpCmdLine
@ebp+0x14 nCmdShow
以下以skinhgy为例,windbg附加运行
bp
bp 命令是在某个地址下断点, 可以 bp 0x7783FEB 也可以 bp MyApp!SomeFunction 。
对于后者,WinDBG 会自动找到MyApp!SomeFunction 对应的地址并设置断点。 但是使用bp的问题在于:
1)当代码修改之后,函数地址改变,该断点仍然保持在相同位置,不一定继续有效;
2)WinDBG 不会把bp断点保存工作空间中
bp Address或bp 伪寄存器或bp符号名称:
0:000> x Simple1Demo!CSimple1DemoApp::InitInstance
00 Simple1Demo!CSimple1DemoApp::InitInstance (void)
0:000> bp 00
0:000> bl
0 e 00 0001 (0001) 0: Simple1Demo!CSimple1DemoApp::InitInstance
0:000> x Kernel32!LoadLibraryW
7c80aeeb kernel32!LoadLibraryW = <no type information>
0:000> bp Kernel32!LoadLibraryW
0:000> bl
0 e 00 0001 (0001) 0: Simple1Demo!CSimple1DemoApp::InitInstance
1 e 7c80aeeb 0001 (0001) 0: kernel32!LoadLibraryW
0:000> bp $exentry
0:000> bl
0 e 00 0001 (0001) 0: Simple1Demo!CSimple1DemoApp::InitInstance
1 e 7c80aeeb 0001 (0001) 0: kernel32!LoadLibraryW
2 e 0061c895 0001 (0001) 0: Simple1Demo!ILT+14480(_wWinMainCRTStartup)
上例说明三种用法作用是一样的,都是bp Address(windbg内部会换成符号文件对应的地址,或伪寄存器的地址)
bp /1 Address表示该断点为一次性断点,有点类似于F4作用于OD,一旦激活就自动删除了:
如bp /1 00
bp Address Passes表示指定断点激活之前要忽略的次数
默认情况下,断点在第一次执行断点位置的代码时被激活。这种默认情况和把Passes 设置为1是一样的。要使得断点在程序至少执行该代码一次之后才激活,可以将这个值设置为2或更大。例如,值为2时,使得断点在第二次执行到该代码时被激活。该参数创建一个在每次执行断点处的代码时被减少1的计数器。要查看Passes 计数器的初始值和当前值,使用bl (Breakpoint List)。Passes 仅当程序响应g (Go)命令并执行通过断点时才减少。单步或跟踪(tracing)通过它是不会减少的。当Passes 到达1时,可以通过清除并重设断点来重置它。
我们来试试,用bc把以前断点都删除,再设置在第三次运行LoadLibraryW时激活该处断点
0:000> bc*
0:000> bl
0:000> bp 7c80aeeb 3
0:000> bl
0 e 7c80aeeb 0003 (0003) 0: kernel32!LoadLibraryW
我们注意到这个断点显示的是0003 (0003) F5运行:
0:000> g
Breakpoint 0 hit
eax=00000002 ebx=7ffdc000 ecx=00000000 edx=00a8660c esi=0263f76e edi=0263f6f2
eip=7c80aeeb esp=0012fd68 ebp=0012fdb0 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202
kernel32!LoadLibraryW:
7c80aeeb 8bff mov edi,edi
0:000> bl
0 e 7c80aeeb 0001 (0003) 0: kernel32!LoadLibraryW
我们注意到这个断点现在显示的是0001 (0003),表示前面忽略了两次,
bu 命令是针对某个符号下断点。 比如 bu MyApp!SomeFunction 。 在代码被修改之后, 该断点可以随着函数地址改变而自动更新到最新位置。 而且bu 断点会保存在WinDbg工作空间中, 下次启动 Windbg 的时候该断点会自动设置上去。另外,在模块没有被加载的时候,bp 断点会失败(因为函数地址不存在),而bu 断点则可以成功。 新版的WinDBG中 bp失败后会自动被转成bu
bm 命令也是针对符号下断点。 但是它支持匹配表达式。 很多时候你下好几个断点。 比如,把MyClass 所有的成员函数都下断点:bu MyApp!MyClass::* , 或者把所有以CreateWindow开头的函数都下断点:bu user32!CreateWindow*
这个函数比较有用,比如我想对Draw开头的函数都下断点:
0:000> bc*
0:000> bl
0:000> bm *!draw*
1: 00 @!"Simple1Demo!DrawState"
2: 0175c790 @!"SkinLog!DrawState"
3: 019f65d0 @!"SkinScroll!DrawState"
4: 10119d10 @!"SkinHgy!DrawState"
0:000> bl
1 e 00 0001 (0001) 0: Simple1Demo!DrawState
2 e 0175c790 0001 (0001) 0: SkinLog!DrawState
3 e 019f65d0 0001 (0001) 0: SkinScroll!DrawState
4 e 10119d10 0001 (0001) 0: SkinHgy!DrawState
bl(breakpoint list) 命令列出已存在的断点的信息
对于每个断点,该命令显示以下信息:
断点ID。该ID是一个可以在其他命令中引用这个断点的十进制数字。
断点状态。它可以是e (启用) 或d (禁用)。
如果出现字母"u",说明断点是未定的。即,该断点中的符号引用还没有和任何当前已加载的模块匹配。
断点位置的虚拟地址或符号表达式。如果启用了源码行号加载,bl 命令显示文件和行号信息而不是地址偏移。如果该断点未定,则它的地址会被省略并出现在列表末尾。
(仅数据断点) 数据断点的类型和大小信息会显示出来。类型可以是e (执行)、 r (读/写)、w (写)或 i (输入/输出)。类型后面是以字节为单位的大小。关于这种类型断点的更多信息,查看ba (Break on Access)。
断点被激活前需要忽略的剩余次数,后面是在圆括号中的初始次数。(这种断点的更多信息,查看bp, bu, bm (Set Breakpoint)中对Passes参数的说明。)
关联的进程和线程。如果线程是用三个星号("*")表示的,说明这不是一个指定线程的断点。
符合断点地址的模块和函数以及偏移。如果是未定断点,这里会用括号括起来的断点地址替代。如果断点设置在合法地址,但是没有符号信息,这个域为空。
该断点触发时要自动执行的命令。这个命令以引号括起来。
bc(breakpoint clear) 命令在系统中移除先前设置的断点。
使用星号(*)来指定所有断点
内存断点(硬件断点)
ba 命令就是针对数据下断点的命令, 该断点在指定内存被访问时触发。 命令格式为
ba Access Size [地址]
Access 是访问的方式, 比如 e (执行), r (读/写), w (写)
Size 是监控访问的位置的大小,以字节为单位。 值为 1、2或4,还可以是 8(64位机)。
如果Access是e,Size必须是1
比如要对内存0x0483DFE进行写操作的时候下断点,可以用命令 ba w4 0x0483DFE
在Access 和Size 之间不能加入空格
0:000> bc*
0:000> ba r4 00a76748
0:000> bl
0 e 00a76748 r 4 0001 (0001) 0: Simple1Demo!`string'
有时我们只想让程序断在某个线程上:
可以用:
0:005> ~1 bp Simple1Demo!DrawState
0:005> bl
0 e 0134bfc0 0001 (0001) 0:~001 Simple1Demo!DrawState
0:005> bp Simple1Demo!DrawState
0:005> bl
0 e 0134bfc0 0001 (0001) 0:~001 Simple1Demo!DrawState
前面~1 表示只有当指定的线程ID为1执行到达断点的地址上时,调试器才会停止.
在X86下dr0-dr3记录了断点地址值,dr6是断点的状态寄存器,dr7是断点的控制寄存器。
另外,在初始断点命中时,尚不能设置硬件断点,如果设置,会得到如下错误:
0:000> ba r1 7c92120f
^ 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.
初始断点后系统会重设线程上下文,因此不能设置硬件断点,建议执行到程序的入口后再设置
0:002> ba e1 00bc1b3a
breakpoint 0 redefined
0:002> r dr0
dr0=00bc1b3a
!gle
!gle 扩展显示当前线程的最后一个错误码。
这个太好记了,getlasterror取首字母:
<span style="font-size:18px;">0:002> !gle
LastErrorValue: (Win32) 0 (0) - <Unable to get error code text>
LastStatusValue: (NTSTATUS) 0 - STATUS_WAIT_0
</span>
-all 显示目标系统中每个用户模式线程的最终错误。如果在用户模式下省略该参数,调试器显示当前线程的最终错误。如果内核模式下省略该参数,调试器显示当前的寄存器上下文指定的线程的最终错误。
<span style="font-size:18px;">0:002> !gle
LastErrorValue: (Win32) 0 (0) - <Unable to get error code text>
LastStatusValue: (NTSTATUS) 0 - STATUS_WAIT_0
0:002> !gle -all
Last error for thread 0:
LastErrorValue: (Win32) 0 (0) - <Unable to get error code text>
LastStatusValue: (NTSTATUS) 0xc0000135 - {
Last error for thread 1:
LastErrorValue: (Win32) 0 (0) - <Unable to get error code text>
LastStatusValue: (NTSTATUS) 0 - STATUS_WAIT_0
Last error for thread 2:
LastErrorValue: (Win32) 0 (0) - <Unable to get error code text>
LastStatusValue: (NTSTATUS) 0 - STATUS_WAIT_0
</span>
!gle扩展显示GetLastError的值并尝试解码它。
g
g(go)命令开始指定进程或线程的执行。这种执行将会在程序结束、遇到BreakAddress 或者其他造成调试器停止的事件发生时停止。
这个我们太经常用到了,
1.如果直接用g不带参数,表示无条件恢复调试目标的执行
2.g Address,相当于设了一个一次性断点,然后将调试目标执行到断点
3.gu 用于使调试目标执行完当前函数并且返回到调用者,由于这个命令知道当前的栈指针,因此它可以从递归函数调用中返回
4.运行到光标处,可以使用Ctrl+F10
5.gc 命令使用和遇到断点时一样的方式(单步、跟踪或自由执行)来从一个条件断点恢复执行。
6.gn和gN 命令继续给定线程的执行,但是不将异常标记为已处理。这样使得应用程序的异常处理器可以处理该异常
7.gh命令将给定线程的异常标识为已处理,并且允许该线程从产生异常的指令继续执行。
---------------------
1.sx
sx* 命令用来控制被调试的程序发生某个异常或特定事件时,调试器要采取的动作
sx 命令显示当前进程的异常列表和所有非异常的事件列表,并且显示调试器遇到每个异常和事件时的行为。
sxr 命令将所有异常和事件过滤器的状态重设为默认值。命令被清除、中断和继续选项被重设为默认值,等等。
sx这个命令的输出信息可以分为三个部分:
第一部分是事件处理与相应处理模式的交互,第二部分是标准的异常交互和处理行为,最后一部分是用户自定义的异常交互和处理行为
以下面为例,我们先输入sxr再输入sx看下默认的处理行为都是怎么样的:
0:000> sxr
sx state reset to defaults
0:000> sx
ct - Create thread - ignore
et - Exit thread - ignore
cpr - Create process - ignore
epr - Exit process - ignore
ld - Load module - output
ud - Unload module - ignore
ser - System error - ignore
ibp - Initial breakpoint - ignore
iml - Initial module load - ignore
out - Debuggee output - output
av - Access violation - break - not handled
asrt - Assertion failure - break - not handled
aph - Application hang - break - not handled
bpe - Break instruction exception - break
bpec - Break instruction exception continue - handled
eh - C++ EH exception - second-chance break - not handled
clr - CLR exception - second-chance break - not handled
clrn - CLR notification exception - second-chance break - handled
cce - Control-Break exception - break
cc - Control-Break exception continue - handled
cce - Control-C exception - break
cc - Control-C exception continue - handled
dm - Data misaligned - break - not handled
dbce - Debugger command exception - ignore - handled
gp - Guard page violation - break - not handled
ii - Illegal instruction - second-chance break - not handled
ip - In-page I/O error - break - not handled
dz - Integer divide-by-zero - break - not handled
iov - Integer overflow - break - not handled
ch - Invalid handle - break
hc - Invalid handle continue - not handled
lsq - Invalid lock sequence - break - not handled
isc - Invalid system call - break - not handled
3c - Port disconnected - second-chance break - not handled
svh - Service hang - break - not handled
sse - Single step exception - break
ssec - Single step exception continue - handled
sbo - Stack buffer overflow - break - not handled
sov - Stack overflow - break - not handled
vs - Verifier stop - break - not handled
vcpp - Visual C++ exception - ignore - handled
wkd - Wake debugger - break - not handled
wob - WOW64 breakpoint - break - handled
wos - WOW64 single step exception - break - handled
* - Other exception - second-chance break - not handled
随便找个出来ld - Load module - output,说明在加载模块时的行为是输出,OK,我们把它断掉:
sxe Break
(Enabled) 当发生该异常时,在任何错误处理器被激活之前目标立即中断到调试器中。这种处理类型称为第一次处理机会。
sxd Second chance break
(Disabled) 发生该类异常时,调试器不会在第一次处理机会时中断(虽然会显示信息)。如果其他错误处理器没有处理掉该异常,执行会停止下来并中断到调试器。这种处理类型称为第二次处理机会。
sxn Output
(Notify) 当该异常发生时,目标程序不中断到调试器中。但是,会通过一条消息提示发生了异常。
sxi Ignore 异常发生时,目标程序不中断到调试器,并且不会显示信息。
使用sxe ld试试
:000> sxe ld
0:000> sx
ct - Create thread - ignore
et - Exit thread - ignore
cpr - Create process - ignore
epr - Exit process - ignore
ld - Load module - break
再次调用sx查看,我们发现现在变成了ld - Load module - break,处理行为变成了break,运行试试
0:000> g
ModLoad: 73fa0000 7400b000 C:WINDOWSsystem32USP10.dll
eax=77ef23d4 ebx=00000000 ecx=77ef7c79 edx=62c25200 esi=00000000 edi=00000000
eip=7c92e514 esp=0012e8c0 ebp=0012e9b4 iopl=0 nv up ei ng nz ac pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000296
ntdll!KiFastSystemCallRet:
7c92e514 c3 ret
0:000> kb
ChildEBP RetAddr Args to Child
0012e8bc 7c92d52a 7c93adfb 000007d0 ffffffff ntdll!KiFastSystemCallRet
0012e8c0 7c93adfb 000007d0 ffffffff 0012e998 ntdll!NtMapViewOfSection+0xc
0012e9b4 7c93c880 001531a0 7ffdfc00 00000000 ntdll!LdrpMapDll+0x330
0012ec14 7c9446f2 001531a0 62c202d4 62c20000 ntdll!LdrpLoadImportModule+0x174
0012ec58 7c94469b 7ffd9000 001531a0 00 ntdll!LdrpHandleOneNewFormatImportDescriptor+0x53
0012ec78 7c9447d5 7ffd9000 001531a0 00 ntdll!LdrpHandleNewFormatImportDescriptors+0x20
0012ecf4 7c 001531a0 00 c0 ntdll!LdrpWalkImportDescriptor+0x19e
0012efa4 7c93643d 00000000 001531a0 0012f298 ntdll!LdrpLoadDll+0x24e
0012f24c 7c801bbd 001531a0 0012f298 0012f278 ntdll!LdrLoadDll+0x230
0012f2b4 7c80aefc 77ef1a1c 00000000 00000000 kernel32!LoadLibraryExW+0x18e
0012f2c8 77f1da06 77ef1a1c 00000000 7ffdf000 kernel32!LoadLibraryW+0x11
0012f2f0 77f14361 0000001f 00000000 77d712a0 GDI32!GdiInitializeLanguagePack+0x15
0012f304 77d1a03d 00000000 00000000 7c GDI32!GdiProcessSetup+0x11d
0012f444 77d1a143 7c92e473 0012f458 00000000 USER32!ClientThreadSetup+0x33
0012f448 7c92e473 0012f458 00000000 77ef67c4 USER32!__ClientThreadSetup+0x5
0012f454 77ef67c4 77ef6553 0012f5a8 0012f9f0 ntdll!KiUserCallbackDispatcher+0x13
0012f464 77d1b473 77d10000 00000001 0012fd30 GDI32!NtGdiInit+0xc
0012f9f0 7c92118a 77d10000 00000001 0012fd30 USER32!_UserClientDllInitialize+0x315
0012fa10 7c93b5d2 77d1b217 77d10000 00000001 ntdll!LdrpCallInitRoutine+0x14
0012fb18 7c93fbdc 0012fd30 7ffdf000 7ffd9000 ntdll!LdrpRunInitializeRoutines+0x344
0:000> du 77ef1a1c
77ef1a1c "LPK.DLL"
果然断下来了,断在加载LPK.dll模块时,那么如果我们只想让它在加载SkinHgy.dll时断下来怎么办呢?需要介绍下ld了
两轮机会
对于每个异常Windows系统会最多给予两轮处理机会,对于每一轮机会Windows都会试图先分发给调试器,然后再寻找异常处理器(VEH、SEH等)。这样看来,对于每个异常,调试器最多可能收到两次处理机会,每次处理后调试器都应该向系统返回一个结果,说明它是否处理了这个异常。
对于第一轮异常处理机会,调试器通常是返回没有处理异常,然后让系统继续分发,交给程序中的异常处理器来处理。对于第二轮机会,如果调试器不处理,那么系统便会采取终极措施:如果异常发生在应用程序中,那么系统会启动应用程序错误报告过程并终止应用程序;如果发生在内核代码中,那么便启用蓝屏机制停止整个系统。所以对于第二轮处理机会,调试器通常是返回已经处理,让系统恢复程序执行,这通常会再次导致异常,又重新分发异常,如此循环。值得说明的是,对于断点异常和调试异常,调试器是在第一轮就返回已经处理的。
ld
ld(load symbols)命令加载指定模块的符号并刷新所有模块信息。
ModuleName 指定要加载符号的模块名。ModuleName 可以包含各种通配符和修饰符。
可以包含通配符,这是个好消息
0:000> lm
start end module name
00 00b89000 Simple1Demo (deferred)
7c 7c9b6000 ntdll (pdb symbols) c:mysymbol tdll.pdbCEFC0863B1F84130A11E0F54180CD21A2 tdll.pdb
0:000> ld Simple1Demo
* WARNING: Unable to verify checksum for Simple1Demo.exe
Symbols loaded for Simple1Demo
0:000> lm
start end module name
00 00b89000 Simple1Demo C (private pdb symbols) D:project代码新控件库SkinHgyDebugSimple1Demo.pdb
7c 7c9b6000 ntdll (pdb symbols) c:mysymbol tdll.pdbCEFC0863B1F84130A11E0F54180CD21A2 tdll.pdb
再加载次试试:
0:000> ld Simple1Demo
Symbols already loaded for Simple1Demo
提示已加载,看来还是.reload好用啊。
那么接着1,我们来设置只在ld skinhgy时断下来:
0:000> sx
ct - Create thread - ignore
et - Exit thread - ignore
cpr - Create process - ignore
epr - Exit process - ignore
ld - Load module - break
ud - Unload module - ignore
ser - System error - ignore
ibp - Initial breakpoint - ignore
iml - Initial module load - ignore
out - Debuggee output - output
av - Access violation - break - not handled
asrt - Assertion failure - break - not handled
aph - Application hang - break - not handled
bpe - Break instruction exception - break
bpec - Break instruction exception continue - handled
eh - C++ EH exception - second-chance break - not handled
clr - CLR exception - second-chance break - not handled
clrn - CLR notification exception - second-chance break - handled
cce - Control-Break exception - break
cc - Control-Break exception continue - handled
cce - Control-C exception - break
cc - Control-C exception continue - handled
dm - Data misaligned - break - not handled
dbce - Debugger command exception - ignore - handled
gp - Guard page violation - break - not handled
ii - Illegal instruction - second-chance break - not handled
ip - In-page I/O error - break - not handled
dz - Integer divide-by-zero - break - not handled
iov - Integer overflow - break - not handled
ch - Invalid handle - break
hc - Invalid handle continue - not handled
lsq - Invalid lock sequence - break - not handled
isc - Invalid system call - break - not handled
3c - Port disconnected - second-chance break - not handled
svh - Service hang - break - not handled
sse - Single step exception - break
ssec - Single step exception continue - handled
sbo - Stack buffer overflow - break - not handled
sov - Stack overflow - break - not handled
vs - Verifier stop - break - not handled
vcpp - Visual C++ exception - ignore - handled
wkd - Wake debugger - break - not handled
wob - WOW64 breakpoint - break - handled
wos - WOW64 single step exception - break - handled
* - Other exception - second-chance break - not handled
0:000> sex ld skinhgy.dll
Couldn't resolve error at 'ex '
0:000> sxe ld skinhgy.dll
0:000> sx
ct - Create thread - ignore
et - Exit thread - ignore
cpr - Create process - ignore
epr - Exit process - ignore
ld - Load module - break
(only break for skinhgy.dll)
ud - Unload module - ignore
ser - System error - ignore
ibp - Initial breakpoint - ignore
iml - Initial module load - ignore
out - Debuggee output - output
av - Access violation - break - not handled
asrt - Assertion failure - break - not handled
aph - Application hang - break - not handled
bpe - Break instruction exception - break
bpec - Break instruction exception continue - handled
eh - C++ EH exception - second-chance break - not handled
clr - CLR exception - second-chance break - not handled
clrn - CLR notification exception - second-chance break - handled
cce - Control-Break exception - break
cc - Control-Break exception continue - handled
cce - Control-C exception - break
cc - Control-C exception continue - handled
dm - Data misaligned - break - not handled
dbce - Debugger command exception - ignore - handled
gp - Guard page violation - break - not handled
ii - Illegal instruction - second-chance break - not handled
ip - In-page I/O error - break - not handled
dz - Integer divide-by-zero - break - not handled
iov - Integer overflow - break - not handled
ch - Invalid handle - break
hc - Invalid handle continue - not handled
lsq - Invalid lock sequence - break - not handled
isc - Invalid system call - break - not handled
3c - Port disconnected - second-chance break - not handled
svh - Service hang - break - not handled
sse - Single step exception - break
ssec - Single step exception continue - handled
sbo - Stack buffer overflow - break - not handled
sov - Stack overflow - break - not handled
vs - Verifier stop - break - not handled
vcpp - Visual C++ exception - ignore - handled
wkd - Wake debugger - break - not handled
wob - WOW64 breakpoint - break - handled
wos - WOW64 single step exception - break - handled
* - Other exception - second-chance break - not handled
我们已经restart了,但ld的状态还没有变,所以WinDBG会自动把一些东西记录到工作空间(Workspace)里,因为工作空间是隐式管理的,所以容易让初用WinDBG的人摸不着头脑,像MJ说的那样操作一下,并且保存到工作空间中(结束调试时,WinDBG询问要不要保存工作空间时选YES),或者干脆删除工作空间就可以了,当然也可以写成
0:000> sxe ld skin*
0:000> sx
ct - Create thread - ignore
et - Exit thread - ignore
cpr - Create process - ignore
epr - Exit process - ignore
ld - Load module - break
(only break for skin*)
ud - Unload module - ignore
ser - System error - ignore
ibp - Initial breakpoint - ignore
iml - Initial module load - ignore
out - Debuggee output - output
av - Access violation - break - not handled
asrt - Assertion failure - break - not handled
aph - Application hang - break - not handled
bpe - Break instruction exception - break
bpec - Break instruction exception continue - handled
eh - C++ EH exception - second-chance break - not handled
clr - CLR exception - second-chance break - not handled
clrn - CLR notification exception - second-chance break - handled
cce - Control-Break exception - break
cc - Control-Break exception continue - handled
cce - Control-C exception - break
cc - Control-C exception continue - handled
dm - Data misaligned - break - not handled
dbce - Debugger command exception - ignore - handled
gp - Guard page violation - break - not handled
ii - Illegal instruction - second-chance break - not handled
ip - In-page I/O error - break - not handled
dz - Integer divide-by-zero - break - not handled
iov - Integer overflow - break - not handled
ch - Invalid handle - break
hc - Invalid handle continue - not handled
lsq - Invalid lock sequence - break - not handled
isc - Invalid system call - break - not handled
3c - Port disconnected - second-chance break - not handled
svh - Service hang - break - not handled
sse - Single step exception - break
ssec - Single step exception continue - handled
sbo - Stack buffer overflow - break - not handled
sov - Stack overflow - break - not handled
vs - Verifier stop - break - not handled
vcpp - Visual C++ exception - ignore - handled
wkd - Wake debugger - break - not handled
wob - WOW64 breakpoint - break - handled
wos - WOW64 single step exception - break - handled
* - Other exception - second-chance break - not handled
这样所有的Skin*模块都会触发断点
sx{e|d|i|n} [-c "Cmd1"] [-c2 "Cmd2"] [-h] {Exception|Event|*}
-c "Cmd1"
指定一个当异常或事件发生时要执行的命令。该命令在异常的第一次处理机会时执行(也就是第一轮异常),不管该异常是否会中断到调试器。Cmd1 字符串必须包含在引号中。该字符串可以包含多条用分号分隔的命令。-c和括起来的命令字符串之间的空格是可选的。
-c2 "Cmd2"
指定当异常或事件发生并且没有在第一次处理机会被处理时执行的命令。该命令在异常的第二次处理机会时执行,(也就是第二轮异常),不管它是否会中断到调试器。Cmd2 字符串必须包含在引号中。该字符串可以包含多条用分号分隔的命令。-c2 和括起来的命令字符串之间的空格是可选的。
-h
改变指定事件的处理状态而不是中断状态。如果Event 是cc、hc、bpec或ssec,-h 选项不是一定需要。
比如我要在第一次加载SkinHgy.dll时断下来并打印MSG:
0:000> sxe -c".echo 'skinhgy.dll loading'" ld:skinhgy.dll
0:000> sx
ct - Create thread - ignore
et - Exit thread - ignore
cpr - Create process - ignore
epr - Exit process - break
ld - Load module - break
Command: ".echo 'skinhgy.dll loading'"
(only break for skinhgy.dll)
ud - Unload module - ignore
运行后:
0:000> g
ModLoad: D:project代码新控件库SkinHgyDebugSkinHgy.dll
'skinhgy.dll loading'
这里介绍种GUI使用的方法:
Debug--Event Filters打开:
我们看到我们先前加载的SkinHgy.dll都在,事件(-c和-c2)对应Commands按钮来修改,中断状态可以通过"Execution"来修改,还可以通过Add和Remove来增加或删除异常码.
至于
Enabled对应Break
Disabled对应Second chance break
sxe Handled 执行返回时,事件被标识为已处理。
sxd,
sxn,
sxi Not Handled 执行返回时,事件被标识为未处理。
windbg帮助上写得很清楚了.
补充点:
av - Access violation - break - not handled
eh - C++ EH exception - second-chance break - not handled
Sxe av //当access violation发生就停止
Sxd eh//当C++ exception发生,调试器什么都不做
这两个很有用,今天在调程序时发现内存访问一直被断,用sxi av就行了,也就是忽视av这种状态
到此这篇C7000纸盒不显示(m7400显示纸盒无纸)的文章就介绍到这了,更多相关内容请继续浏览下面的相关推荐文章,希望大家都能在编程的领域有一番成就!版权声明:
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若内容造成侵权、违法违规、事实不符,请将相关资料发送至xkadmin@xkablog.com进行投诉反馈,一经查实,立即处理!
转载请注明出处,原文链接:https://www.xkablog.com/bcyy/53637.html