!sym
!sym 扩展控制显示详细的符号加载和符号提示。
都那么聪明,一个是noisy-quiet,一个是prompts off-prompt on,掌握了
.reload
.reload 命令删除指定模块的所有符号信息,并且按需要重新加载这些符号。某些情况下,该命令也会重新加载或卸载模块本身。
好吧,我们发现没有立即显示加载符号
我们发现,第一次lm查询时GDI32(deferred),调用.reload /f加载后,再次lm,我们可以看到GDI32 (pdb symbols),OK,那我们也猜到了,如.reload /f不带模块,那么是不是会重新加载所有的symbols:
果然如此!
/i由于符号服务器对每个版本的二进制文件的符号使用不同的名字,除非确认下游存储被破坏了,否则不需要使用该选项。
reload /u 命令进行更广泛的搜索。调试器首先尝试使用Module 匹配精确的模块名,不管路径是什么。如果找不到匹配项,Module 被当作已加载的映像名。例如,如果HAL在内存中的名字为halacpi.dll,下面两个命令都可以卸载它的符号。
如果在进行用户模式调试,并且希望加载一个不在目标程序模块列表中的模块,必须像下面的例子一样使用/s选项。
如果一个dll被内嵌于exe中,默认只会加载exe的pdb,.reload提供了强制加载的方式
1..sympath+ 增加pdb路径文件夹
2..reload /i 模块名=基地址,大小 /i忽略.pdb文件版本不匹配的情况。(如果没有包含该参数,调试器不会加载不匹配的符号文件
实例如下:
其实在ad0000后附带了个内嵌的maincode_org.dll
设置pdb路径操作:如果下述方式不行,就加到file->symbol file path中,记得不要有中文路径
加载 此方式也可强制加载其他的pdb,比如有时你需要用到某个pdb的某个结构体时
1.符号路径基本语法:
SRV* 【cache】*toppath
例如:Microsoft公有符号存储地址:http://msdl.microsoft.com/download/symbols
设置符号路径就是:SRV*c:\mysymbols*http://msdl.microsoft.com/download/symbols
c:\mysymbols作为符号缓存以加快符号的访问速度
2.查看已加载的模块和符号文件基本语法:
lm [option] [-a Address] [-m Pattern] [-M Pattern]
3.重新加载符号基本语法:
.reload 抛弃所有已加载的符号信息,任何解析符号的动作将从硬盘上重新加载符号文件
.reload <module>抛弃module的符号信息,任何解析符号的动作将从硬盘上重新加载符号文件
.reload /f <module> 强制调试器立刻加载并且解析与模块module相关的符号文件
.reload nt 加载与当前windows NT内核相对应的符号文件
.reload /user 当前活跃的进程加载所有的用户态符号
.reload <module>=start, size通过指定起始地址来强制加载符号
eg:
0:000> .reload /f WS2_32.dll
4.验证符号基本语法:
!chksym Address
eg:(参考71a20000 71a37000 WS2_32 )
:000> !chksym 71a20011
5.使用符号基本语法:
x [option] module!symbols
例如:
*为通配符,在调试陌生代码时很有用
x *!*some*
x module!*
6. 查看目标系统
vertarget 是version命令的一个功能子集
vertarget显示调试目标所在的操作系统版本
version则会显示调试环境的其它信息
7. 查看寄存器值
r :查看所有寄存器,如果要在表达式中使用寄存器的值,在寄存器名前加@符号(比如@eax)
reax:查看eax寄存器
8.处理器当前执行代码
u . 当前eip指向地址上8条指令
uf . 当前eip指向地址整个函数
ub . 当前eip指向地址之前8条指令
u .L2之后2条指令
ub .L2之前2条指令
9. 查看当前调用栈k 显示调用栈
kP 5 显示在调用栈中前五个函数以及它们的参数
kb 5 显示在调用栈中五个函数的前三个参数
kf 5 显示在调用栈中五个函数所使用的栈大小
10. 在代码中设置断点
bl:列出所有断点
bc * : 清除所有断点
bp module!myclass:memfun:设置断点
bp+address/符号地址 在address指令处加断点,但是这个地址所在的模块必须已经被加载
bu+address/符号地址 在address指令处加断点,但是这个地址所在的模块可以没有被加载,即延迟加载的模块。
ba: 内存访问断点,当访问这个内存地址时(一般是数据),程序会断住。
bl: 列出所有已经加载的断点和地址
bc: 清理断点。 bc *,清理所有的断点。 bc 1,清理1号断点。
bd: 使一个断点无效。
be: 使一个断点有效,与bd左右相反。
反汇编:
ub address/符号 反汇编这个地址之前的语句
u address/符号 反汇编这个地址之后(包括这条语句)的语句
uf address/符号 反汇编整个函数
查看内容:
dd + address: 将内存地址中内容以四字节为单位显示出来
da : 将内存中内容,以ascii码的形式显示出来,主要用于观察字符串
du : 将内存中内容以unicode码形式显示出来,也用于显示字符串
dv: 不用加内存地址,显示当前栈上面的所有的变量
dt+ 格式 + (address): 把内存地址所在的内容,以制定的格式显示出来,这个格式一般是结构体等。
dds: 把制定地址开始的内容,列出来,如果能对应到代码符号,将符号显示出来。
11. 查看变量的值
dv显示局部变量的值
dv /i显示值以及存储位置
dt this 已知符号this指针
dt KBTest 0x1111111 :解析地址0x1111111,类型为KBTest 变量值
12. 查看内存命令
d[type] [AddressRange]
13 综合分析一个dump文件:
!analyze -v :列出堆栈
~0s; .ecxr ; kb :列出寄存器cxr 和调用堆栈
kn:给栈列出序号
快捷键alt+3: 打开locals窗口
.frame 05:列出当前栈结构内容
~*kb会显示所有线程的调用堆栈。如果堆栈太长,Windbg只会显示堆栈的一部分。
dt xxx - 显示出诸如PEB等的数据结构
.lastevent
!analyze –v
以上兩行指令都可以顯示 exception 發生時的 exception record 和 stack trace of the function。你也可以使用 .exr, .cxr 和 .ecxr 指令來顯示 exception 和 context records。
14.窗口分析
spy++http://jingyan.baidu.com/article/3a2f7c2e76584a26aed61174.html
15 当EBP和ESP的值已经不可信时,此时无法使用k系列命令来查看栈回溯,应该通过配合使用“!teb”(得到栈的内存位置)和“dds <AddressScope>”(显示和分析栈内存)来手动分析栈回溯(通过排除不是函数的字符串行)。
16WinDbg提供dt命令来显示符号类型信息(Dump symbolic Type information),有以下三种用法:
(1)“dt [ModuleName]!TypeName”,若省略模块名,则自动查找所有已加载的模块,如“dt ntdll!*”显示ntdll里的所有类型信息。其中,-b开关指定递归的显示所有子类型的信息。-r加数字指定递归显示的深度,如-r0表示不显示子类型信息。若不想显示全部字段,可以使用开关-ny附加字段过滤搜索信息,如“dt _TEB -ny LastError”。
(2)第二种用法是在上一种用法之后加上内存地址,按照指定的内存地址的内容来显示具体类型的变量。
(3)第三种用法是显示类型的实例,如全局变量、静态变量和函数。同样可以枚举函数符号,此时同x命令的功能相似,如“dt dbgee!*wmain*”,若是指定的函数,则会显示该函数的参数取值和返回值类型。
17 可以使用如下方法搜索内存内容:
(1)“s-[[Flags]]sa|su Range”用来搜索ASCII(UNICODE)字符串,可以用l加整数指定字符串的长度。例如“s-[l5] sa (注意这里sa之前不能有空格)0X600000 0X800000”。
(2)“s-[[Flags]]v Range Object”,在指定内存地址范围内与指定对象相同类型的对象。
(3)“s [-[[Flags]]Type] Range Pattern”,Type决定了匹配搜索内容的方式,可以为b、w、d、q、a、u,Pattern参数用来指定要搜索的内容,可以用空格分隔依次搜索的数值,如:
“s -w -0X400000 l2A000 41 64 76 44 62 67”,要搜索的内容也可以表示为ASCII码,如:“s -w 0X400000 l2A000 'A' 'd' 'v' 'D' 'b' 'g'”
(其中l后面跟数字表示在起始地址之后多少范围内进行搜索)
54、修改内存:
(1)以字符串方式编辑:“e{a|u|za|zu} Address "String"”
其中,za|zu代表以0结尾的ASCII(UNICODE)字符串。
(2)以数值方式编辑:“e{b|w|d|D|f|p|q} Address Value”,其中,Value参数决定用户需要修改多少数值。若不键入Value,则会进入交互式的修改内存界面。
是否显示所有的信息都是正确的?
这就是函数,感叹号前是所属模块(dll or exe),感叹号后是函数名+偏移地址。
函数参数是不能直接显示的,就是显示出来也不一定对,因为windbg只是依赖寄存器ebp+xx来猜你的函数参数,
而这个推导过程是否正确取决于你的编译选项,是否选中了帧指针优化等等因素,因为有可能函数参数只是存在了某个寄存器里而已。
命令 | 操作 |
.hh | 调出帮助文件 |
!help | 显示部分命令 |
!help <command> | 显示某命令的详细信息 |
|
|
lm | 显示进程中加载的模块信息 |
lmvm | 可以查看任意一个DLL/EXE的详细信息 |
lml | 查看模块的加载情况 |
ld | 加载某块 |
|
|
!thread | 查看所有线程 |
~ns | 切换到第n号线程 |
!runaway | 显示每一个线程所消耗CPU的时间 |
|
|
!sym noisy | 开启WinDbg log日志,显示具体信息 |
!analyze -v | 调用WinDbg智能解析,一般程序崩溃的情况下首选调用 |
!locks | 查看锁,一般调试死锁的情况下使用 |
|
|
~*kb | 显示所有调用堆栈 |
kb | 显示当前调用堆栈 |
dv | 显示局部变量 |
dt | 显示数据结构 |
dd | 察看堆内容 |
!address | 显示某一地址上的页信息 |
u | 显示汇编代码 |
uf | 反汇编代码 |
.exr | 打印出异常的信息 |
.cxr | 切换上下文 |
|
|
x | 查找某一符号的地址 |
dds | 把某一地址对应到符号(于x的用法相反) |
死锁场景描述:
针对之前一个版本反馈回来的问题,对数据通讯模块升级,做了精简和重构
因为ABA问题的存在,将之前以Socket为key改为以只增的Int为key。使用的锁为临界区锁。
修改完成,联调后进行压力测试,发现当后台的线程池满的时候会必然发生死锁。
死锁定位过程:
第一步:精简线程模型,将授权检测线程、超时检测等辅助线程通通屏蔽。
大压力测试,仍然死锁。
第二步: 在第一步的基础上,将通讯工作线程减到1个。
大压力测试,仍然死锁。
因为涉及模块比较多,为了快速定位问题,使用windbg来检测死锁点
第三步:使用windbg
当应用程序挂死后,使用windbg Attach到进程。
1. 使用 ~*kb,查看堆栈信息
其中,上图中的8是线程编号,368是进程ID,bd4是线程ID。
可以看到ntdll!NtWaitForSingleObject ,没有拿到锁,说明死锁发生了。
要看下比如 ntdll!NtWaitForSingleObject参数定义:
所以 00000604 00000000 00000000分别对应上面的Handle,Alrtable,Timeout.
如上所述,handle是00000604,
再看一个网上的例子:
0:004> ~1kb
ChildEBP RetAddr Args to Child
0051fe48 7c92df5a 7c939b23 00000034 00000000 ntdll!KiFastSystemCallRet
0051fe4c 7c939b23 00000034 00000000 00000000 ntdll!NtWaitForSingleObject+0xc
0051fed4 7c921046 00417140 00411420 00417140 ntdll!RtlpWaitForCriticalSection+0x132
*** WARNING: Unable to verify checksum for D:\Project1\test2\Debug\test2.exe
0051fedc 00411420 00417140 00000000 00000000 ntdll!RtlEnterCriticalSection+0x46
0051ffb4 7c80b729 00000000 00000000 00000000 test2!thread1+0x50 [d:\project1\test2\test2\test2.cpp @ 10]
0051ffec 00000000 00411122 00000000 00000000 kernel32!BaseThreadStart+0x37
0:004> !cs 00417140
-----------------------------------------
Critical section = 0x00417140 (test2!cs2+0x0)
DebugInfo = 0x7c99ea00
LOCKED
LockCount = 0x2
OwningThread = 0x00001f60
RecursionCount = 0x1
LockSemaphore = 0x34
SpinCount = 0x00000000
可以看出该临界区的持有者(OwningThread)是 0x00001f60
这个地址是线程在内核中的ID,需要转换为用户态的ID,使用~~[]命令。
0:003> ~~[0x0000185c]
2 Id: 1a98.185c Suspend: 1 Teb: 7ffdd000 Unfrozen
Start: test2+0x1030 (00401030)
Priority class: 32; Affinity: f
通过转换,可以看到锁的持有者是2号线程。
这样就可以很明确的找到锁的持有者了,如果pdb加载正确,通过~2kb就已经可以对应到代码行了。
查找死锁很困难,但是一旦找到点,解决起来就容易多了。
以上的例子部分来自网贴,在此对原作者表示感谢。
---------------------------------
对于死锁的小结:
1. 对上层提供的函数,需要通过设计,规避操作带锁。
2.在锁中避免调用回调,因为回调的操作不可预知,易发生死锁。
参考自:
http://blog.youkuaiyun.com/chollima/article/details/7704249
http://cuijingbing.blog.163.com/blog/static/46825825201187105423613/
http://blog.youkuaiyun.com/agan4014/article/details/2180699