windbg常用命令浅析

!sym

!sym 扩展控制显示详细的符号加载和符号提示。

[cpp]  view plain  copy
  1. 0:001> !sym  
  2. !sym <noisy/quiet - prompts/prompts off> - quiet mode - symbol prompts on  


[cpp]  view plain  copy
  1. 0:001> !sym noisy  ///< 激活详细符号加载  
  2. noisy mode - symbol prompts on  
  3. 0:001> !sym quiet ///< 禁止详细符号加载  
  4. quiet mode - symbol prompts on  

[cpp]  view plain  copy
  1. 0:001> !sym prompts ///< 当SymSrv接收到认证请求时,允许弹出对话框  
  2. quiet mode - symbol prompts on  
  3. 0:001> !sym prompts off ///< <span style="font-family: Arial, Helvetica, sans-serif;">当SymSrv接收到认证请求时,禁止弹出对话框</span>  
  4. quiet mode - symbol prompts off  

都那么聪明,一个是noisy-quiet,一个是prompts off-prompt on,掌握了


.reload

.reload 命令删除指定模块的所有符号信息,并且按需要重新加载这些符号。某些情况下,该命令也会重新加载或卸载模块本身。

[cpp]  view plain  copy
  1. 0:001> .reload /d ntdll.dll///< d 重新加载调试器模块列表中的所有模块  
  2. Reloading current modules  
  3. ................................  
  4. DBGHELP: C:\WINDOWS\symbols\ntdll.pdb - file not found  
  5. DBGHELP: ntdll - public symbols    
  6.          C:\WINDOWS\symbols\dll\ntdll.pdb  

好吧,我们发现没有立即显示加载符号

扫描二维码关注公众号,回复: 1001913 查看本文章

[cpp]  view plain  copy
  1. 0:001> lm m gdi32     ///< deferred状态  
  2. start    end        module name  
  3. 77200000 77290000   GDI32      (deferred)               
  4.   
  5. 0:001> .reload /f gdi32.dll      ///<f 强制调试器立即加载符号,注意是gdi32.dll不是gdi32  
  6. SYMSRV:  c:\mysymbol\wgdi32.pdb\6E2E9DA4A20241F7B6BB0F1B89FD7A522\wgdi32.pdb not found  
  7. SYMSRV:  wgdi32.pdb from http://msdl.microsoft.com/download/symbols: 216845 bytes - copied           
  8. DBGHELP: c:\mysymbol\wgdi32.pdb\6E2E9DA4A20241F7B6BB0F1B89FD7A522\wgdi32.pdb already cached  
  9. DBGHELP: GDI32 - public symbols    
  10.          c:\mysymbol\wgdi32.pdb\6E2E9DA4A20241F7B6BB0F1B89FD7A522\wgdi32.pdb  
  11.   
  12. 0:001> lm m gdi32 ///<加载成功  
  13. start    end        module name  
  14. 77200000 77290000   GDI32      (pdb symbols)          c:\mysymbol\wgdi32.pdb\6E2E9DA4A20241F7B6BB0F1B89FD7A522\wgdi32.pdb  

我们发现,第一次lm查询时GDI32(deferred),调用.reload /f加载后,再次lm,我们可以看到GDI32 (pdb symbols),OK,那我们也猜到了,如.reload /f不带模块,那么是不是会重新加载所有的symbols:

[cpp]  view plain  copy
  1. 0:001> .reload /f  
  2. Reloading current modules  
  3. .  
  4. DBGHELP: C:\WINDOWS\symbols\calc.pdb - file not found  
  5. DBGHELP: calc - public symbols    
  6.          C:\WINDOWS\symbols\exe\calc.pdb  
  7. .  
  8. DBGHELP: C:\WINDOWS\symbols\safemon.pdb - file not found  
  9. DBGHELP: C:\WINDOWS\symbols\dll\safemon.pdb - file not found  
  10. DBGHELP: C:\WINDOWS\symbols\symbols\dll\safemon.pdb - file not found  
  11. SYMSRV:  C:\MyLocalSymbols\safemon.pdb\84C1B55127174ACAA421A85A983FA63B1\safemon.pdb not found  
  12. SYMSRV:  http://msdl.microsoft.com/download/symbols/safemon.pdb/84C1B55127174ACAA421A85A983FA63B1/safemon.pdb not found  
  13. DBGHELP: C:\Program Files\360\360Safe\safemon\safemon.pdb - file not found  
  14. DBGHELP: E:\repos\safemon_8_1_1\Release\safemon.pdb - file not found  
  15. *** ERROR: Symbol file could not be found.  Defaulted to export symbols for C:\Program Files\360\360Safe\safemon\safemon.dll -   
  16. DBGHELP: safemon - export symbols  
  17. .  
  18. DBGHELP: C:\WINDOWS\symbols\AcGenral.pdb - file not found  
  19. DBGHELP: AcGenral - public symbols    
  20.          C:\WINDOWS\symbols\DLL\AcGenral.pdb  
  21. DBGHELP: comctl32 - public symbols    
  22.          C:\MyLocalSymbols\MicrosoftWindowsCommon-Controls-6.0.2600.6028-comctl32.pdb\E882C2C890724D598449E20A4FE6F07C1\MicrosoftWindowsCommon-Controls-6.0.2600.6028-comctl32.pdb  
  23. .  
  24. ............................................  
  25. 0:001> lm  
  26. start    end        module name  
  27. 01000000 0101f000   calc       (pdb symbols)          C:\WINDOWS\symbols\exe\calc.pdb  
  28. 10000000 100b0000   safemon    (export symbols)       C:\Program Files\360\360Safe\safemon\safemon.dll  
  29. ..................................  
  30. 77fc0000 77fd1000   Secur32    (pdb symbols)          C:\MyLocalSymbols\secur32.pdb\7867B3F28B5C41CE847895E3FC013DC52\secur32.pdb  
  31. 7c800000 7c91e000   kernel32   (pdb symbols)          C:\MyLocalSymbols\kernel32.pdb\072FF0EB54D24DFAAE9D13885486EE092\kernel32.pdb  
  32. 7c920000 7c9b3000   ntdll      (pdb symbols)          C:\WINDOWS\symbols\dll\ntdll.pdb  
  33. 7d590000 7dd84000   SHELL32    (pdb symbols)          C:\MyLocalSymbols\shell32.pdb\DF59C75CA10B4BF89B447BB924C4292C2\shell32.pdb  

果然如此!

/i
忽略.pdb文件版本不匹配的情况。(如果没有包含该参数,调试器不会加载不匹配的符号文件。) 使用  /i时,即使没有明确指定,也会使用 /f
/l
列出模块但是不重加载它们的符号。(内核模式下,使用该参数的输出和 !drivers 扩展命令一样。)
/n
仅重加载内核符号。该参数不会重加载任何用户模式符号。(只能在内核模式调试时使用该选项。)
/o
强制覆盖符号服务器的下游存储(downstream store)中的缓存文件。使用该标志时,还需要包含 /f。默认情况下,下游存储中的文件永远不会被覆盖。

由于符号服务器对每个版本的二进制文件的符号使用不同的名字,除非确认下游存储被破坏了,否则不需要使用该选项。

/s
重新加载系统的模块映像列表中所有模块。(省略所有参数时,在内核模式下这是默认行为。) 如果在用户模式调试时使用名字来单独加载某个系统模块,则必须包含 /s
/u
卸载指定模块和它的所有符号。调试器卸载任何名字匹配Module 的模块,不管它的全路径是什么。映像名也会被搜索。更多信息,查看下面的注释节。
/unl
基于已卸载模块列表中的映像信息重新加载符号。
/user
仅重加载用户模式符号。(只能在内核模式调试时使用该选项。)
/v
打开详细显示。
/w 将Module 当作一个字面上的字符串。这样可以避免调试器展开通配符。


 reload /u 命令进行更广泛的搜索。调试器首先尝试使用Module 匹配精确的模块名,不管路径是什么。如果找不到匹配项,Module 被当作已加载的映像名。例如,如果HAL在内存中的名字为halacpi.dll,下面两个命令都可以卸载它的符号。

[cpp]  view plain  copy
  1. <nobr>kd> .reload /u halacpi.dll  
  2. kd> .reload /u hal</nobr>  

如果在进行用户模式调试,并且希望加载一个不在目标程序模块列表中的模块,必须像下面的例子一样使用/s选项。

[cpp]  view plain  copy
  1. <nobr>0:000> lm m dmmain  
  2. start    end        module name  
  3. 0f5f0000 0f6ac000   DmMain     (export symbols)       I:\GameDL\Tools\PPSeedTool\build\Debug\DmMain.dll  
  4. 0:000> .reload /u DmMain  
  5. Unloaded DmMain  
  6. 0:000> lm m dmmain  
  7. start    end        module name  
  8. 0:000> .reload /s /f DmMain.dll  
  9.   
  10. *** ERROR: Symbol file could not be found.  Defaulted to export symbols for I:\GameDL\Tools\PPSeedTool\build\Debug\DmMain.dll -   
  11. DBGHELP: DmMain - export symbols  
  12. 0:000> lm m dmmain  
  13. start    end        module name  
  14. 0f5f0000 0f6ac000   DmMain     (export symbols)       I:\GameDL\Tools\PPSeedTool\build\Debug\DmMain.dll</nobr>  


如果一个dll被内嵌于exe中,默认只会加载exe的pdb,.reload提供了强制加载的方式

1..sympath+ 增加pdb路径文件夹

2..reload /i 模块名=基地址,大小                         /i忽略.pdb文件版本不匹配的情况。(如果没有包含该参数,调试器不会加载不匹配的符号文件

实例如下:

[html]  view plain  copy
  1. 0:001> lm  
  2. start    end        module name  
  3. 00400000 00ad0000   test011    (deferred)               
  4. 02810000 02b7a000   SOGOUWB    (deferred)      

其实在ad0000后附带了个内嵌的maincode_org.dll

设置pdb路径操作:如果下述方式不行,就加到file->symbol file path中,记得不要有中文路径

[html]  view plain  copy
  1. 0:001> .symfix+ E:\项目SVN  
加载

[html]  view plain  copy
  1. 0:001> .reload /i maincode_org=00AD0000,0024E000  
  2. *** WARNING: Unable to verify timestamp for maincode_org  
[html]  view plain  copy
  1. 0:001> x maincode_org!*  
  2. 00ceb628 maincode_org!g_timeGetTime = 0x00000000  
  3. 00cf8814 maincode_org!g_szMessage = 0x00000000 ""  
  4. 00cfb504 maincode_org!g_pSetWindowPos = 0x0000000  
此方式也可强制加载其他的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,查看堆栈信息

        

[plain]  view plain  copy
  1. 8  Id: 368.bd4 Suspend: 1 Teb: 7ffd6000 Unfrozen ChildEBP RetAddr  Args to Child               0614fd4c 7c92df5a 7c939b23 00000604 00000000 ntdll!KiFastSystemCallRet 0614fd50 7c939b23 00000604 00000000 00000000 ntdll!NtWaitForSingleObject+0xc 0614fdd8 7c921046 0012fe08 5f401b5f 0012fe08 ntdll!RtlpWaitForCriticalSection+0x132 0614fde0 5f401b5f 0012fe08 0012fdfc 0614fedc ntdll!RtlEnterCriticalSection+0x46 0614fdf0 0040b207 ffffffff 77d2b326 03103ee0 MFC42D!CCriticalSection::Lock+0x14 0614fedc 5f438514 0012fd30 ffffffff 77d2b326 RecExtraction!CRecExtractionModule::ThreadAnalysis+0x185 [G:\Code\vc2012\VSIP\DigitalViewer_V1\RecExtraction\RecExtractionModule.cpp @ 331] 0614ff80 1020c323 0012f4f4 ffffffff 77d2b326 MFC42D!_AfxThreadEntry+0x2c4 0614ffb4 7c80b729 03103ee0 ffffffff 77d2b326 MSVCRTD!_beginthreadex+0x133 0614ffec 00000000 1020c2b0 03103ee0 00000000 kernel32!BaseThreadStart+0x37   

       其中,上图中的8是线程编号,368是进程ID,bd4是线程ID。
      可以看到ntdll!NtWaitForSingleObject ,没有拿到锁,说明死锁发生了。

[plain]  view plain  copy
  1. 0614fd4c 7c92df5a 7c939b23 00000604 00000000 ntdll!KiFastSystemCallRet  
  2. 0614fd50 7c939b23 00000604 00000000 00000000 ntdll!NtWaitForSingleObject+0xc  
  3. 0614fdd8 7c921046 0012fe08 5f401b5f 0012fe08 ntdll!RtlpWaitForCriticalSection+0x132  
  4. 这里的第一个是ebp,第二个是返回函数地址,接下来才是函数的参数。  

     要看下比如 ntdll!NtWaitForSingleObject参数定义:

[cpp]  view plain  copy
  1. NTSTATUS WINAPI NtWaitForSingleObject(   
  2.   _In_  HANDLE Handle,   
  3.   _In_  BOOLEAN Alertable,   
  4.   _In_  PLARGE_INTEGER Timeout   
  5. );   

    所以 00000604 00000000 00000000分别对应上面的Handle,Alrtable,Timeout.

2. 如果线程比较多,可以使用 !cs -l 仅显示死锁的临界区信息。3.找到了死锁的临界区Handle,就可以查找锁的持有者了。

    如上所述,handle是00000604,

[plain]  view plain  copy
  1. !cs 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,使用~~[]命令。

[plain]  view plain  copy
  1. ~~[线程内核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.csdn.net/chollima/article/details/7704249

http://cuijingbing.blog.163.com/blog/static/46825825201187105423613/

http://blog.csdn.net/agan4014/article/details/2180699



猜你喜欢

转载自blog.csdn.net/qq1263575666/article/details/79137859