前面的文章调试非托管程序的基本命令上讲到如何在windbg里面启动一个程序并且加载调试符号文件。一旦符号文件加载完毕以后,就可以进行调试了,例如设置断点,查看堆栈信息等等。

 #调试命令窗口

Windbg是跟visual studio差不多的一个调试器,可以用来调试非托管程序(native
application),也可以调试托管程序(managed
application)。它比VS强的地方是你可以使用它来调试Windows操作系统,因此它也被叫做kernel mode
debugger,同样为kernel mode
debugger的调试器还有随着windbg一起安装的kd.exe(只不过这是一个命令行工具而已)。然而它在调试托管程序方面会比VS难用很多,当然如果你想看.NET程序的CLR内部结构,例如内存相关的信息的话,使用windbg还是可以的。

 

图片 1

Windbg与VS在调试方面最大的区别是,Windbg是采用命令驱动,而VS是采用良好的用户界面(UI)驱动。粗看起来,好像windbg是可以通过类似批处理的方式来实现一些调试步骤自动化(的确是这样的,会在后续的文章中讲到),可能你会感觉windbg功能会比VS强一些。实际上VS也提供了宏支持,你可以编写宏来控制VS的调试过程—请参考我以前的文章Visual Studio调试之断点技巧篇。

因为是刚刚启动程序(main函数还没有机会执行),可以查看源代码了解要设置断点的地方。设置断点可以使用bp、bu和bm来做,其中bp可以根据函数名、指令地址以及源代码文件地址来设置断点。

 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

本篇文章里,以下面这个简单的程序为例,我介绍一些在windbg中,调试托管程序常用的命令:

 

#使用gflags.exe工具(在windbg所在目录下),让某个进程启动时,拉取windbg进行调试

Nativedebug.cpp

 

#include "stdafx.h"

#include <tchar.h>

 

void Usage()

{

#ifdef _UNICODE

                   wprintf(L"[Usage]: nativedebug.exe <digital numbers>\n");

#else

                   printf("[Usage]: nativedebug.exe <digital numbers>\n");

#endif

}

 

int _tmain(int argc, _TCHAR* argv[])

{

         int result = 0;

         if ( argc != 2 )

         {

                   Usage();

                   return -1;

         }

 

         result = _ttol(argv[1]);

#ifdef _UNICODE

         wprintf(L"%s * %s = %d\n", argv[1], argv[1], result * result);

         wprintf(L"Press any key to exit …\n");

         _getwch();

#else

         printf("%s * %s = %d\n", result * result);

         printf("Press any key to exit …\n");

         _getch();

#endif

 

         return 0;

}

bp命令是在设置断点过程用的比较多的一个命令,下面的表格演示了它的简单用法:

如下截图:当名称为captcomm.exe的进程启动时,拉起windbg调试

 

命令格式

示例

说明

bp 函数名

bp Usage

在函数Usage的入口中断程序的执行。

bp 指令地址

bp 010113c0

在执行地址在010113c0的指令前中断程序的执行。

bp `源文件地址`

bp `nativedebug.cpp:21`

在源代码nativedebug.cpp的第21行设置断点,请注意符号“`”(感叹号键左边的反引号)。

 

 

图片 2

编译好了以后,运行windbg.exe,点击菜单中的“File”-“Open Executable”,在弹出的对话框中选择nativedebug.exe。你应该可以看到类似下面的输出,我使用绿色的字符注释了各行输出的含义:

                                                                                                                       

也可通过脚本命令来实现:

#

# 注意黄色高亮的字体,它说明这个调试器是32位的调试器,只能调试32位的程序,

# 如果你需要调试x64或者IA 64的程序,需要运行对应的调试器。

#

Microsoft (R) Windows Debugger Version 6.10.0003.233 X86

Copyright (c) Microsoft Corporation. All rights reserved.

#

# 这一行注明了被调试程序的命令行

#

CommandLine: E:\临时文档\Windbg教程\nativedebug\Debug\nativedebug.exe

#

# 下面这几行说明当前没有设置好符号文件搜索路径,符号文件在调试中比源文件

# 还重要,关于符号文件的说明和其重要性请参考我的另外一篇文章

#                     Visual Studio调试之符号文件

#

Symbol search path is: *** Invalid ***

****************************************************************************

* Symbol loading may be unreliable without a symbol search path.           *

* Use .symfix to have the debugger choose a symbol path.                   *

* After setting your symbol path, use .reload to refresh symbol locations. *

****************************************************************************

#

# 可执行文件搜索路径告诉了windbg在哪里搜索可执行文件(也就是.exe和.dll文件)

# 大部分情况下,windbg知道怎样找到可执行文件–从程序的启动目录,从PATH环境

# 变量里面,从system32文件夹里面等等。因此大部分情况下,你不需要设置这个变量

# 然而在少数情况下,你需要使用.exepath设置它。请参看windbg帮助里面的.exepath

# 的说明来了解。一种少数情况基本上我们用不到—是内核模式的调试,另外一种情况

# 是在调试用户模式(user mode)mini dump文件的时候需要指明。

#

Executable search path is:

#

# ModLoad: 指明了当前程序加载的其所依赖的 dll文件,以及它们加载的地址。顺便

# 说一句,windbg有的时候会使用这些信息来将堆栈转换到对应的函数上去。

#

ModLoad: 00950000 0096b000   nativedebug.exe

ModLoad: 77d80000 77f00000   ntdll.dll

ModLoad: 76070000 76170000   C:\Windows\syswow64\kernel32.dll

ModLoad: 77670000 776b4000   C:\Windows\syswow64\KERNELBASE.dll

ModLoad: 6ed80000 6eea4000   C:\Windows\WinSxS\x86_microsoft.vc90.debugcrt_1fc8b3b9a1e18e3b_9.0.30729.4148_none_2a4cbfc25558bcd3\MSVCR90D.dll

#

# 下面这一行说明了当前是什么异常导致程序中断执行,是的 ,虽然我们刚刚启动被

# 调试的程序,的确是一个异常—断点异常导致程序中断执行。至于原因请参看我的

# 另外一篇文章:

#                 Visual Studio调试之断点基础篇

# 另外,下面一行中,第一个括号里面的是当前的线程ID,第二个括号指明了当前的异常

# 是第一次机会处理,还是第二次机会处理。First chance的含义请参看我的这篇文章:

#                  Visual Studio调试之避免单步跟踪调试模式

(ec4.d7c): Break instruction exception – code 80000003 (first chance)

#

# 当前CPU中各个寄存器的值

#

eax=00000000 ebx=00000000 ecx=f2260000 edx=0016ddd8 esi=fffffffe edi=77da3bdc

eip=77e207ff esp=002df598 ebp=002df5c4 iopl=0         nv up ei pl zr na pe nc

cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b            efl=00000246

#

# 没有symbol,所以在调试的时候比较麻烦,另外export symbol的含义在后一篇文章

# 中讲

#

*** ERROR: Symbol file could not be found. Defaulted to export symbols for ntdll.dll –

#

# 当前中断时,程序正在执行的函数,以及其位置。

#

ntdll!LdrVerifyImageMatchesChecksum+0x6ce:

#

# 当前执行的指令(或者是下一条要执行的指令)。

#

77e207ff cc              int     3

如果你有源代码的话,通过windbg的菜单“File”—“Open Source File”打开源文件,找到相应的代码行,按下键盘的F9就可以设置断点了(当然前提条件是你已经设置好正确的符号文件,符号文件请参考文章Visual
Studio调试之符号文件)。

// 运行captcomm.exe时,启动windbg调试
reg add "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\captcomm.exe" /v Debugger /t REG_SZ /d "C:\Program Files\Debugging Tools for Windows (x86)\windbg.exe" /f
// 解除启动时windbg调试
reg delete "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\captcomm.exe" /f

// 64位系统上,也可以设置以下注册表节点
reg add "HKLM\SOFTWARE\Wow6432Node\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\captcomm.exe" /v Debugger /t REG_SZ /d "C:\Program Files\Debugging Tools for Windows (x86)\windbg.exe" /f
// 解除启动时windbg调试
reg delete "HKLM\SOFTWARE\Wow6432Node\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\captcomm.exe" /f

// 测试发现:HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options和HKLM\SOFTWARE\Wow6432Node\Microsoft\Windows NT\CurrentVersion\Image File Execution Options指向的是同一数据,修改其中任何一个都可以

 

 

安全软件可能会禁止修改注册表的Image File Execution项:

Windbg跟VS不同的地方是,windbg在启动程序后,执行main函数之前,就中断了,而VS却是直接执行,原因是VS是一个整合的平台,你可以先在源代码里面设置好断点,然后VS从源代码编译完程序后,启动程序之前自动就可以把断点设置好。而windbg只是一个调试器,所以它需要给程序员设置断点的机会,实际上所有的调试器的使用模式都是类似的。

看起来好像没有什么特别的,只不过是设置断点的方法比Visual
studio复杂一些罢了,不过在windbg中,bp等命令允许在触发断点的时候执行一系列的调试命令。例如中断程序后,打印堆栈,保存内存文件然后退出,或者执行一个小的调试命令脚本程序等等,这个过程与visual
studio里面的跟踪断点(Trace
Point)非常相像,当然操作起来稍微复杂一些(visual
studio的跟踪断点的用法请参考文章Visual
Studio调试之断点技巧篇)。 在windbg中设置触发断点执行其他命令的方法会在后续的文章里面讲到。

— 对于360安全卫士,在“设置”中去掉“开启360自我保护”的勾选来关闭

 因为没有符号文件,不好调试,所以需要先加载符号文件。Windbg知道几个符号文件搜索路径(symbol
path)――就是微软的公开符号文件服务器,如果你想查看你的程序在运行过程中使用了哪些windows的API和函数的话,可以使用它。执行下面的命令来使用这个文件服务器(不要忽略了前面的点号):

 

图片 3

.symfix

例如在调试本文的示例程序(示例程序在文章调试非托管程序的基本命令上里面),可以执行以下的命令:

— 对于McAfee,需要禁止IPS(Intrusion Prevension Systems)。

上面的命令自动将symbol path设置为微软的公开符号文件服务器地址,如果你在微软内部混过,你会发现这个命令设置的路径有点不同,嘘……。

bp Usage

— 然而,电脑管家,则不会对该键值的注册表修改进行阻止。

Sympath可以设置和查看symbol的路径,执行下面这个命令来查看最新的符号文件搜索路径:

#

 

.sympath

# 没有输出结果,正所谓没有消息就是好消息,如果断点成功设置,

#重要说明

#

# windbg不会显示任何信息。

(1) windbg命令分为标准命令,元命令和扩展命令。

# 输出结果

#

标准命令提供最基本的调试功能,不区分大小写。如:bp g dt dv k等
元命令提供标准命令没有提供的功能,也内建在调试引擎中,以.开头。如.sympath
.reload等
扩展命令用于扩展某一方面的调试功能,实现在动态加载的扩展模块中,以!开头。如!analyze等

#

 

(2) 进入调试状态时,直接回车可重复执行上一条命令;按上下方向键可以浏览和选择以前输入过的命令

Symbol search path is:
SRV**

如果在设置断点时,出现类似下面的消息:

(3) 神奇的Tab键,进行命令补全;ESC清除当前命令输入框中的文本

 

 

(4) 选择Command窗口中的内容,然后右击进行复制

设置好符号文件搜索路径以后,接下来每次加载新的可执行(.exe或者.dll)文件,windbg都会先在这些路径里面搜索它的符号文件,但是对于已经加载了的可执行文件,例如ntdll.dll和kernel32.dll,我们需要手动重新加载,使用下面的命令来加载:

bp UsageA

(5) 使用;作为分隔符,可以在同一行输入多条命令

.reload

#

(6) 上图红色框中的“0:000”。【0为当前调试会话的进程号;000为调试会话的线程号

#

# 输出结果

(7) 当命令提示符显示*BUSY*时,即使命令输入框可以输入命令,但输入的命令不会立即被执行,要等windbg空闲时才能执行。

# 输出结果

#

     可使用Ctrl +
Break来终止一个长时间未完成的命令

#

Bp expression ‘UsageA’ could not be
resolved, adding deferred bp

(8) 一次可以执行多条命令,命令间用分号;分隔 【如:bp main;bp
`view.cpp:120`】,一次打2个断点

Reloading current modules

 

(9)按Ctrl + Alt + V开启/关闭verbose
Output(详细输出模式)

…..

那么有两个检查步骤,第一是检查符号文件是否正确加载,第二步是检查设置断点的函数名是否真的存在于程序当中。

(10)
为了保证windbg流畅运行,在调试时,尽量不要开启Watch、Locals、Registers、Call
Stack、Processes and Threads窗口,直接用command来获取信息

 

 

#启动调试

这个时候,点击windbg菜单里面的“View”-“Call Stack”,就可以看到堆栈了。因为我们还用到了MS
CRT里面的函数,即atoi,所以我们还需要VCRT的符号文件。幸运的是,VS自带了VCRT的私有符号文件,这样我们用下面的命令将这个符号文件的文件夹路径添加到windbg的符号文件搜索路径列表里面来,注意后面的加号,如果不写加号,就说明将搜索路径完全替换成新的:

第一步,检查符号文件是否正确加载,可以使用lm命令查看已加载模块的详细信息,例如在上面的例子中,我们相信UsageA命令应该在模块nativedebug.exe中,可以执行下面的命令来查看nativedebug模块的详细信息(请注意模块名是紧跟在vm选项后面的,没有空格,没有后缀名,也没有蛀牙):

windbg -I  // 将windbg设置成默认调试器

.sympath+
C:\Windows\symbols\dll

 

windbg “notepad.exe” arguments
 // 使用windbg启动调试notepad.exe

#

lm
vmnativedebug

windbg -p 4200  //
将windbg附加到一个正在运行的pid为4200的进程上

# 输出结果

#

windbg -pn “notepad.exe”  // 将windbg附加到一个正在运行的名为notepad.exe的进程上

#

# 输出结果

windbg -p
4200 -c “sxd av;sxi c0000005;sxi c0000008;g” //
将windbg附加到一个正在运行的pid为4200的进程上,附加成功后,执行sxd
av;sxi c0000005;sxi c0000008;g命令

Symbol search path is:
SRV**

#

windbg –z “c:\mydumpfile.dmp”
// 调试mydumpfile.dmp文件

执行下面的命令看一下加载的结果(注意前面没有点号):

start    end        module name

.opendump “c:\mydumpfile.dmp”  //
调试mydumpfile.dmp文件

lm

# 注意下面这一行里面的private pdb symbols,说明我们已经加载了正确的符号文件。

windbg -y d:\mySymbols // 指示symbol路径

#

# 至于private的含义,会在以后的文章里面讲到。

windbg -srcpath d:\Src  // 指示源文件路径

# 输出结果

01000000 0101b000   nativedebug C (private
pdb
symbols) D:\Debuggers\sym\nativedebug.pdb\E873A517513C4CC9BA5C805D1A709F206\nativedebug.pdb

windbg -i  d:\myApp  //指示exe、dll等可执行模块路径

#

    Loaded symbol image file:
nativedebug.exe

.attach 0n4220  // 4220为十进制pid,使用该命令附加调试时,必须先存在一个调试会话

start    end        module name

# Image path指明了模块加载的路径,在64位机器上调试程序的时候,

.detach   // 分离调试

00b60000 00b7b000   nativedebug  
(deferred)            

#这个信息是蛮有用的
。因为你需要知道一些系统模块是在system32还是

.restart  // 重启并调试

6fef0000 70014000   MSVCR90D   (private
pdb
symbols) D:\Debuggers\sym\msvcr90d.i386.pdb\EBEA784C96244F1E8F8D35E0391C898D1\msvcr90d.i386.pdb

# SysWow64文件夹里加载的。

.kill  // 强制结束当前调试

76070000 76170000   kernel32  
(deferred)            

    Image path: nativedebug.exe

q  //
结束当前调试会话,回到基础工作空间,并结束调试进程

77670000 776b4000   KERNELBASE  
(deferred)            

    Image name: nativedebug.exe

qd
 // 结束当前调试会话,回到基础工作空间,但不结束调试进程

77d80000 77f00000   ntdll      (pdb
symbols)         
D:\Debuggers\sym\wntdll.pdb\E06BEA155E9748BEA818E2D0DD2FED952\wntdll.pdb

    Timestamp:        Sat Feb 20 20:05:20
2010 (4B7FD000)

CTRL+ALT+V  // 打开或关闭 Verbose
模式开关,某些命令在此模式下可以给出更多详细信息

上面的输出结果会在后文讲到,至于如何设置断点,以及在命令行里面查看堆栈,在后面讲――写文章还是蛮辛苦的。

    CheckSum:         00000000

.time
// 调试会话时间信息

    ImageSize:        0001B000

.lastevent
 // 显示最新的异常信息或事件信息

    Translations:     0000.04b0 0000.04e4
0409.04b0 0409.04e4

version
 // 显示windbg和已加载的调试器扩展相关的信息

 

vercommand  //
显示windbg的启动路径及命令行信息

顺便说一下,因为nativedebug是我们自己编译的,有一些版本方面的信息在编译的时候没有加进去。如果你查看一个Windows自带的模块的详细信息的话,你可能会看到类似下面的输出:

.chain  //
显示已经加载进来的调试器扩展

 

.extmatch /D
/e wow64exts *  // 显示wow64exts调试器扩展中的命令

lm vmntdll

#获取帮助

#

?   // 打印出所有标准命令

# 输出结果

.help  // 打印出所有元命令

#

.hh  // 打开windbg的chm帮助文件

start    end        module name

.hh
bp  // 打开windbg的chm帮助文件bp命令介绍页

775f0000 7772c000   ntdll      (pdb
symbols)         
D:\Debuggers\sym\ntdll.pdb\F0164DA71FAF4765B8F3DB4F2D7650EA2\ntdll.pdb

command /?  // 打印命令command具体参数用法

    Loaded symbol image file:
ntdll.dll

#注释符

    Image path: ntdll.dll

*  // 注释整行

    Image name: ntdll.dll

$$ // 注释(遇到分号结束)

    Timestamp:        Tue Jul 14 09:09:47
2009 (4A5BDADB)

#配置调试环境

    CheckSum:         0014033F

注:如果被调试的模块(无论移动到本机的何处)是用本机代码编译产生的,都不需要进行符号和源代码的路径设置

ImageSize:        0013C000

.sympath   // 查看当前符号查找路径

# 模块的版本号,如果你的程序象微软的产品那样有多个版本,而且需要对多个

.sympath c:\symbols   // 将符号查找路径设为:c:\symbols

# 版本提供技术支持的话,下面的信息对于找到正确版本的符号文件非常非常非常

.sympath+ c:\symbols  // 将c:\symbols添加到符号查找路径集合中

# 重要。

.symfix //
将符号查找路径设为:SRV*WinDbg安装目录\Sym*

    File version:    
6.1.7600.16385

.symfix f:\symbols // 将符号查找路径设为:SRV*f:\symbols*

    Product version: 6.1.7600.16385

.symfix+ f:\symbols  // 将SRV*f:\symbols*

File flags:       0 (Mask 3F)

.srcpath // 查看当前源文件查找路径

# 模块要求的子系统

.srcpath f:\src // 将源文件查找路径设为:f:\src  
注:必须勾选上菜单“Debug”-“Source
Mode”;另外pdb须与exe、dll等执行模块匹配上

    File OS:          40004 NT
Win32

.srcpath+ f:\src  // 将f:\src添加到源文件查找路径集合中

    File type:        2.0 Dll

.exepath // 查看可执行文件查找路径

    File date:       
00000000.00000000

.exepath f:\bin // 将可执行文件查找路径设为:f:\bin

    Translations:     0409.04b0

.exepath+ f:\bin  // 将f:\bin添加到可执行文件查找路径集合中

    CompanyName:      Microsoft
Corporation

图片 4

    ProductName:      Microsoft® Windows®
Operating System

 

    InternalName:     ntdll.dll

#系统信息

    OriginalFilename: ntdll.dll

vertarget // os信息

ProductVersion:   6.1.7600.16385

!cpuid  // cpu信息

# 下面只显示了已发布的产品的信息,版本号已经在前面的注释里介绍过了。

#wow64模式  【x64版windbg调试win32程序】

# win7_rtm的意思是当前的模块是从win7_rtm这个源代码分支里编译出来的。

.load wow64exts  // [!load wow64exts] 加载wow64exts.dll模块
 注:!sw就是wow64exts中的命令

# 版本分支的概念在团队软件产品开发过程中是一个平常的做法,大部分版本

.unload wow64exts  // [!unload
wow64exts] 卸载wow64exts.dll模块

# 控制软件都支持代码分支的做法。这个过程解释起来有点复杂,现在你需要

.effmach  //
查看当前调试mode:x86、x64等

# 知道的是,如果你现在工作的公司没有采取版本分支的做法,那么祝贺你,

.effmach
x86 // 切换到x86栈环境  注:需要先执行.load
wow64exts来加载wow64exts.dll模块

# 至少在寻找符号文件的过程里,你会比较轻松(不需要考虑分支的影响),

.effmach
. // 切换回x64

# 虽然会在后面发布高质量的软件产品你的团队会死的比较难看。

!sw  // [!wow64exts.sw]
 在多个mode:x86、x64上进行循环切换
 注:如果win32程序在x64的mode下,会看到地址是64位的

# 如果你工作的公司正在采取版本分支的做法的话,那么你一定要在正确的分支

!k
 // [!wow64exts.k]  打印32位、64位堆栈

# 下寻找对应版本的符号文件,否则你会死的很难看。

!k 5
// [!wow64exts.k 5]  打印32位、64位堆栈,栈帧个数为5

#

!info
// [!wow64exts.info]  输出wow64相关的PEB、TEB和TLS基本信息

# 另外,下面一行的输出里还有一个重要的信息没有显示,那就是模块是否为调试版

!r
// [!wow64exts.r]  输出处理器当前上下文信息

# ,还是发布版。与软件分支一样,如果考虑进去,也是一样无法加载到正确的

!r
dumpTest!main  // [!wow64exts.r dumpTest!main]
 输出main函数地址的上下文信息

# 符号文件的。

注 —
32位windbg无法打开64位应用程序,会提示如下错误:

#

图片 5

# 如果使用类似微软的方法编译软件,会在后面的文章中讲到。

注 —
32位windbg无法attach上64位应用程序,会提示如下错误:

    FileVersion:      6.1.7600.16385
(win7_rtm.090713-1255)

图片 6

    FileDescription: NT Layer DLL

 

# 这个嘛,地球人都知道。

#符号加载与查看

    LegalCopyright:   © Microsoft
Corporation. All rights reserved.

除了使用ld和.reload命令直接加载符号文件,某些使用符号的命令也可以触发调试器来加载符号,如:栈回溯命令(k*)和反汇编命令(u)等。

 

值得说明的是,windbg缺省使用的是懒惰式符号加载策略,当它收到模块加载事件时,它通常是不会加载符号的,符号状态显示为deferred(延迟加载)。

既然知道符号文件已经被正确加载,那么下一步就是确认设置的函数名是否存在于模块中,可以使用x命令来检查符号文件保存的名字信息—就是函数名呀,全局变量名之类的信息。如果直接调用x命令,windbg会显示模块里面所有的名字。一般都是使用x加上一个匹配模式来查找指定的名字在模块中是否已定义。比如,为了检查UsageA这个名字在nativedebug.exe模块中是否已定义,可以执行下面的命令来查看(感叹号前面是告诉x命令要在哪一个模块中查找名字,感叹号后面就是要查找的名字):

.symopt // 显示当前所有符号选项

 

.symopt+ flags // 添加符号选项

x
nativedebug!UsageA

.symopt- flags // 删除符号选项

#

!sym noisy   // 激活详细符号加载(noisy symbol
loading)显示

# 输出结果—没有输出结果

!sym quiet   // 禁止详细符号加载显示

#

ld * // 为所有模块加载符号

 

ld kernel32 // 加载kernel32.dll的符号

如果x没有找到指定的名字,就不会输出任何信息,否则,会有类似下面的输出:

.reload // 为所有已加载模块载入符号信息

 

.reload
/i // 重新加载不匹配符号的模块【dmp文件没有对应的pdb时使用】

x
nativedebug!Usage

.reload /i
TGame.exe // 重新加载不匹配符号的TGame.exe

#

.reload /f /v // f:强制立即模式(不允许延迟载入)
 v:详细模式

# 输出结果,前面的地址是函数入口在内存中的地址,而后面则显示了函数的声明信息。

.reload /f @”c:\windows\System32\verifier.dll”
// 为指定模块加载符号信息

#

.reload /f
TGame.exe // 为TGame.exe加载符号信息

010113c0 nativedebug!Usage (void)

.reload /u
TGame.exe //
卸载TGame.exe及其载符号信息

 

x *! // 列出所有模块对应的符号信息

X命令允许你在查找过程中使用通配符进行匹配,例如,在我们的示例程序中,被用来执行转换的“函数”_ttol不是一个真实的函数,而是一个宏。下面是这个宏的定义:

lm // 列出所有模块(加载和未加载)对应的符号信息

 

lmv
// 列出所有模块(加载和未加载)对应的符号信息

#ifdef
_UNICODE

lmvm
ntdll  //
查看ntdll.dll的详细信息(注意exe、dll等都不要带后缀名)

#  
define
_ttol       _wtol

!lmi
ntdll  //
查看ntdll.dll的详细信息(注意exe、dll等都不要带后缀名)

#else

!dlls -l
 // 按照加载顺序(默认项)列出所有加载的模块

#  
define
_ttol       atol

!dlls
-i  // 按初始化顺序列出所有加载的模块

#endif

!dlls -v -c
ntdll
 // 查看ntdll.dll的详细信息(注意exe、dll等都不要带后缀名)

 

ln 0x65777588  //
查看地址0x65777588处或附近的符号信息

而宏是在编译期间就被编译器扩展,并不会被加到符号文件中去,因此如果你试图使用bp命令在_ttol入口设置断点的话,是会失败的。因此你可以使用类似下面的通配符来查找正确的函数名:

x ConsoleTest!* // 列出ConsoleTest模块中的所有符号

x
MSVCR90D!*tol*

x ConsoleTest!add* // 列出ConsoleTest模块中的所有add开头的符号

#

x /t /v ConsoleTest!* // 带数据类型、符号类型和大小信息,列出ConsoleTest模块中的所有符号

# 输出结果(注意黄色高亮的名字)

x kernel32!*LoadLib* // 列出kernel32模块中所有含LoadLib字样的符号

#

!itoldyouso
mono D:\mySymbols\mono.pdb  //
查看mono.dll与D:\mySymbols\mono.pdb符号是否匹配上

65cd1bb0 MSVCR90D!__STRINGTOLD (struct
_LDOUBLE *, char **, char *, int)

0:000> !itoldyouso mono
D:\mySymbols\mono.pdb

65d47c80 MSVCR90D!_ld12told (struct
_LDBL12 *, struct _LDOUBLE *)

mono.dll
Timestamp: 559AAA60
SizeOfImage: 230000
pdb:
C:\buildslave\mono-runtime-and-classlibs\build\builds\embedruntimes\win32\mono.pdb
pdb sig:
728334C1-72C3-4A51-B310-C44087FC4B2E
age: 1

65cd1900 MSVCR90D!_atoldbl (struct
_LDOUBLE *, char *)

mono.pdb
pdb sig:
728334C1-72C3-4A51-B310-C44087FC4B2E
age: 1

65cd6790 MSVCR90D!_wcstol_l (wchar_t
*, wchar_t **, int, struct localeinfo_struct *)

MATCH: mono.pdb and mono.dll

65cd4400 MSVCR90D!strtol (char *, char
**, int)


65d4bac0 MSVCR90D!__mtold12 (char *,
unsigned int, struct _LDBL12 *)

使用windbg提供的symchk.exe工具来查找模块的pdb

65cd5030 MSVCR90D!_tolower_l (int,
struct localeinfo_struct *)

symchk.exe Paladins.exe /v /s .\
 //在当前目录查找Paladins.exe的pdb,/s后面指定搜索路径
 带上/v会输出详细的log

65cd6300 MSVCR90D!wcstol (wchar_t *,
wchar_t **, int)

如果匹配上,会显示如下log:

65ca0d50
MSVCR90D!atol (char *)

[SYMCHK] [ 0x00000000 - 0x001f0001 ] Checked "G:\TGame\symbols\ClientPackaging\Paladins.exe"

SYMCHK: FAILED files = 0
SYMCHK: PASSED + IGNORED files = 1

65cd4940 MSVCR90D!_strtol_l (char *,
char **, int, struct localeinfo_struct *)

没有匹配上,则显示log为:

65ca12d0
MSVCR90D!_wtol (wchar_t *)

SYMCHK: Paladins.exe         FAILED  - ShippingPC-ChaosGameTencent.pdb mismatched or not found

SYMCHK: FAILED files = 1
SYMCHK: PASSED + IGNORED files = 0

65d4a980 MSVCR90D!__wstrgtold12_l
(struct _LDBL12 *, wchar_t **, wchar_t *, int, int, int, int,
struct localeinfo_struct *)

 

65d544e0 MSVCR90D!_ftol (void)

#进程

65cd5010 MSVCR90D!_tolower (int)

|   // 列出调试进程

65cd5210 MSVCR90D!tolower (int)

|*  // 列出调试进程

65ca12f0 MSVCR90D!_wtol_l (wchar_t *,
struct localeinfo_struct *)

|N  // 参看序数为N的调试进程

65d48fd0 MSVCR90D!__dtold (struct
_LDOUBLE *, double *)

|Ns // 切换序数为N的进程为当前调试进程

65ce3c30 MSVCR90D!_mbctolower_l
(unsigned int, struct localeinfo_struct *)

!dml_proc  // 显示当前进程信息 

65d48dc0 MSVCR90D!__STRINGTOLD_L
(struct _LDOUBLE *, char **, char *, int, struct localeinfo_struct
*)

#线程

65cd17f0 MSVCR90D!_atoldbl_l (struct
_LDOUBLE *, char *, struct localeinfo_struct *)

~   // 列出线程

65ce3d80 MSVCR90D!_mbctolower (unsigned
int)

~*  // 所有线程

65ca0d70 MSVCR90D!_atol_l (char *,
struct localeinfo_struct *)

~* k // 所有线程堆栈信息

65d38670 MSVCR90D!__lc_strtolc (struct
tagLC_STRINGS *, char *)

~* r // 所有线程寄存器信息

65cdd8e0 MSVCR90D!CPtoLCID (int)

~.  // 查看当前线程

65d47d40 MSVCR90D!__strgtold12_l
(struct _LDBL12 *, char **, char *, int, int, int, int, struct
localeinfo_struct *)

~0s
// 查看主线程

65c6109c
MSVCR90D!_imp__FileTimeToLocalFileTime = <no type
information>

~# // 查看导致当前事件或异常的线程

 

~N  // 查看序数为N的线程

在上面的输出,可以看到atol和_wtol在msvcr90d.dll这个模块中都定义了,而我们现在不是很确定当时程序编译的时候,_UNICODE这个宏是否被定义了。因此我们即可以采用一个笨方法,就是使用bp命令在atol和_wtol两个函数入口上都设置断点,运行看看到底程序会中断在哪一个函数上。

~~[n]  // 查看线程ID为n的线程  n为16进制

 

~Ns   // 切换序数为N的线程为当前调试线程

或者,可以使用bm命令,bm命令相当于bp命令的扩展,允许用户使用一个通配符设置断点。例如:

~~[n]s  //
切换线程ID为n的线程为当前调试线程  n为16进制

 

~N f  // 冻结序数为N的线程

bm *tol*

~N u // 解冻序数为N的线程

 

~N n  // Suspend序数为N的线程

#

~N m // Resume序数为N的线程

# 输出结果 – Windbg会在所有匹配的函数入口上设置断点。

~* e
!gle // 显示所有线程最后的一个错误信息
 e后可以为任意windbg命令

# 很多,的确很多,因此请慎用bm命令。

.ttime  // 查看当前线程时间信息

#

!runaway
 //显示当前进程的所有线程用户态时间信息

 4: 65cd1bb0
@!”MSVCR90D!__STRINGTOLD”

!runaway f  //显示当前进程的所有线程用户态、内核态、存活时间信息

 5: 65d47c80
@!”MSVCR90D!_ld12told”

!locks // 显示死锁

 6: 65cd1900 @!”MSVCR90D!_atoldbl”

!cs  // 列出CriticalSection(临界段)的详细信息

 7: 65cd6790
@!”MSVCR90D!_wcstol_l”

 

 8: 65cd4400 @!”MSVCR90D!strtol”

#断点

 9: 65d4bac0
@!”MSVCR90D!__mtold12″

bl   // 列出所有断点

 10: 65cd5030
@!”MSVCR90D!_tolower_l”

bc * // 清除所有断点

 11: 65cd6300 @!”MSVCR90D!wcstol”

bc 1 // 清除1号断点

 12: 65ca0d50 @!”MSVCR90D!atol”

bc 1 2 5  // 清除1号、2号、5号断点

 13: 65cd4940
@!”MSVCR90D!_strtol_l”

be *  // 启用所有断点

 14: 65ca12d0 @!”MSVCR90D!_wtol”

be 1  // 启用1号断点

 15: 65d4a980
@!”MSVCR90D!__wstrgtold12_l”

be 1 2 5 // 启用1号、2号、5号断点

 16: 65d544e0 @!”MSVCR90D!_ftol”

bd *  // 禁用所有断点

 17: 65cd5010
@!”MSVCR90D!_tolower”

bd 1  // 禁用1号断点

 18: 65cd5210 @!”MSVCR90D!tolower”

bd 1 2 5 // 禁用1号、2号、5号断点

 19: 65ca12f0
@!”MSVCR90D!_wtol_l”

bp 7c801b00  // 在7c801b00地址处放置一个断点

 20: 65d48fd0
@!”MSVCR90D!__dtold”

bp MyDll+0x1032  //
在模块MyDll.dll偏移0x1032处放置一个断点

 21: 65ce3c30
@!”MSVCR90D!_mbctolower_l”

bp `ConsoleTest.cpp:36`  // 在ConsoleTest.cpp的36行处放置一个断点

 22: 65d48dc0
@!”MSVCR90D!__STRINGTOLD_L”

bp main // 在main函数的起始处放置一个断点

 23: 65cd17f0
@!”MSVCR90D!_atoldbl_l”

bp
@$exentry  // 在进程的入口放置一个断点

 24: 65ce3d80
@!”MSVCR90D!_mbctolower”

bp CSecondLoader::CSecondLoader
 // 在CSecondLoader的构造函数处放置一个断点

 25: 65ca0d70
@!”MSVCR90D!_atol_l”

bp
TestCommon! CTest::add  //
在TestCommon.dll的Test.cpp文件的CTest::add()函数起始处放置一个断点

 26: 65d38670
@!”MSVCR90D!__lc_strtolc”

bp `ConsoleTest.cpp:40` “.if (poi(pVar)>5) {}; {g}” // “.if (Condition) {Optional
Commands}; {g}”    条件断点 pVar指针指向的值>5,执行空语句(;),断住  否则继续执行

 27: 65cdd8e0 @!”MSVCR90D!CPtoLCID”

bp `ConsoleTest.cpp:40` “j (poi(pVar)>5) ‘ ‘; ‘g'” // “j (Condition) ‘Optional Commands’; ‘g'”  
 j为条件表示式:条件断点 pVar指针指向的值>5,执行空语句(;),断住
 否则继续执行

 28: 65d47d40
@!”MSVCR90D!__strgtold12_l”

注:Condition表达式语法默认的是MASM表达式语法。使用复杂C++表达式时我们需要用@@c++()将表达式包围住;如:”j @@c++(*pVar>5) ‘ ‘;
‘g'”

 


设置好断点后,可以使用bl命令(breakpoint list)来查看已经设置好的断点:

x表示的一个地址
hi(x) 高16 bits
low(x) 低16 bits
by(x) 返回第一个byte
wo(x) 返回第一个word
dwo(x) 返回第一个dword
qwo(x) 返回第一个4
word(Quad-word)
poi(x) 返回第一个指针所指向的值

 


bl

bp `ConsoleTest.cpp:40` “j @eax = 0xa3
”; ‘g'” // j为条件表示式:条件断点
寄存器eax的值为0xa3时断住

#

bp
kernel32!CreateFileA  //
在系统API的CreateFileA函数处放置一个断点

# 输出结果

bp
kernel32!CreateFileA “.echo;.printf\”CreateFileA(%ma,%p,%p),
ret=\”,poi(esp+4),dwo(esp+8),dwo(esp+c);gu;.printf\”%N\”,eax;.echo;g”
// 不断住进程情况下,打印所有的CreateFileA调用

# 第一列是断点的编号;

bp
kernel32!CreateFileW “.echo;.printf\”CreateFileW(%mu,%p,%p),
ret=\”,poi(esp+4),dwo(esp+8),dwo(esp+c);kn;g;”  //
不断住进程,打印所有的CreateFileW调用及堆栈信息

# 第二列,e表示(enabled),u表示(unresolved),因此如果那一列的值为e,则说明

bp
advapi32!RegOpenKeyExA
“.echo;.printf\”RegOpenKeyExW(%p,\\\”%ma\\\”,%N,%N,%p) returned:
\”, dwo(esp+4), poi(esp+8), dwo(esp+c), dwo(esp+10),
dwo(esp+14);gu;.printf\”%N\”,eax;.echo;g”  //
不断住进程情况下,打印所有的RegOpenKeyExA调用(打开注册表键值)

# 断点是启用状态,如果为d表示(disabled),则表示禁用状态。如果有u,则基本上


# 说明这个断点是没有设置成功的,虽然windbg会在后续加载每一个模块的时候,都尝试

注意:有些函数Symbol
Name与导出函数名可能不一致,例如SetWindowPos,这时可以用Dependency查看相应的导出函数地址(Entry
Point列):0x00018E5E,然后在windbg菜单“Debug”—〉“Modules…”对话框中获得user32.dll模块起始地址0x77d10000,在两个值相加后的绝对地址处直接设置断点:bp
77d28E5E;也可以通过x
user32!*命令列出全部Symbols列表,查找77d28E5E,找到SetWindowPos对应的Symbol
Name为“NtUserSetWindowPos”,然后通过符号设置断点:bp
user32!NtUserSetWindowPos。通过符号设置断点的好处是当dll代码改变时,不需要修改,windbg会根据符号来自动匹配函数地址。

# 根据那个名字设置断点;


# 后面几列,放在后面的文章讲。

bu  // 保存断点,其用法和bp一样

#

bm add_*  // 匹配add_开头的函数,并在这些函数起始处都打上断点

 0 e 010113c0     0001 (0001) 0:****
nativedebug!Usage

ba w4 0x0483dfe0 // 当对0483dfe0地址写操作时停下,前面要带上0x,否则会报错

 1 eu             0001 (0001)
(UsageA)

                          // ba [r|w|e] [Size] Addr      [r=read/write,
w=write, e=execute], Size=[1|2|4 bytes]

# 这个断点没有设置正确

windbg无参脚本

 2 eu            
0001 (0001) (`22`)

//
当CreateFileW读取UnityLockfile文件时断住进程,在命令输入框输入:$$><d:\CreateFileWNoArguScript.txt,CreateFileWNoArguScript.txt内容如下:

 4 e 65cd1bb0     0001 (0001) 0:****
MSVCR90D!__STRINGTOLD

bp kernel32!CreateFileW "
r $t1=poi(esp+4)
as /mu $FileName $t1
.echo
.printf\"File:%mu\",$t1
.echo
.block
{
.if($spat(\"${$FileName}\",\"*UnityLockfile\")) 
{
  .echo 'find...';
  ad ${/v:$FileName}
}
.else 
{
  .echo no find...
  ad ${/v:$FileName}
  gc
}
}"

 5 e 65d47c80     0001 (0001) 0:****
MSVCR90D!_ld12told

windbg有参脚本

 6 e 65cd1900     0001 (0001) 0:****
MSVCR90D!_atoldbl

//
当CreateFileW读取mscorlib.dll文件时断住进程,在命令输入框输入:$$>a<d:\CreateFileWArguScript.txt
mscorlib.dll,CreateFileWArguScript.txt内容如下:

 7 e 65cd6790     0001 (0001) 0:****
MSVCR90D!_wcstol_l

bp kernel32!CreateFileW "
r $t1=poi(esp+4)
as /mu $FileName $t1
.echo
.printf\"File:%mu\",$t1
.echo
.block
{
.if($spat(\"${$FileName}\",\"*${$arg1}\")) 
{
  .echo 'find...';
  ad ${/v:$FileName}
}
.else 
{
  .echo no find...
  ad ${/v:$FileName}
  gc
}
}"

 8 e 65cd4400     0001 (0001) 0:****
MSVCR90D!strtol

 

 9 e 65d4bac0     0001 (0001) 0:****
MSVCR90D!__mtold12

#调试执行控制

10 e 65cd5030     0001 (0001) 0:****
MSVCR90D!_tolower_l

g  // Go(F5)

11 e 65cd6300     0001 (0001) 0:****
MSVCR90D!wcstol

gH // 执行gH命令强制让调试器返回已经处理了这个异常。【Go with Exception Handled】

12 e 65ca0d50     0001 (0001) 0:****
MSVCR90D!atol

     //
系统收到这个回复后会停止分发异常(因为调试器声称已经处理了异常),恢复调试目标继续执行,

13 e 65cd4940     0001 (0001) 0:****
MSVCR90D!_strtol_l

     //
 但由于异常条件仍在,所以还会产生异常,于是再次分发,WinDBG再次中断到命令模式。

14 e 65ca12d0     0001 (0001) 0:****
MSVCR90D!_wtol

gN // 【Go with
Exception Not Handled】

15 e 65d4a980     0001 (0001) 0:****
MSVCR90D!__wstrgtold12_l

     //
执行gN命令强制让调试器返回没有处理了这个异常,那么系统会进一步分发该异常,

16 e 65d544e0     0001 (0001) 0:****
MSVCR90D!_ftol

     //
如果没有其他调试器也不处理,最后系统会弹出程序终止对话框。

17 e 65cd5010     0001 (0001) 0:****
MSVCR90D!_tolower

gu  // 执行到当前函数完成时停下 【Go Up】

18 e 65cd5210     0001 (0001) 0:****
MSVCR90D!tolower

Ctrl+Break  // 暂停正在运行的程序

19 e 65ca12f0     0001 (0001) 0:****
MSVCR90D!_wtol_l

p    // 单步执行(F10)  【Step】

20 e 65d48fd0     0001 (0001) 0:****
MSVCR90D!__dtold

p 2 // 2为步进数目

21 e 65ce3c30     0001 (0001) 0:****
MSVCR90D!_mbctolower_l

pc   // 执行到下一个函数调用处停下 【Step to  Next Call】

22 e 65d48dc0     0001 (0001) 0:****
MSVCR90D!__STRINGTOLD_L

pa 7c801b0b // 执行到7c801b0b地址处停下  【Step to Adress】

23 e 65cd17f0     0001 (0001) 0:****
MSVCR90D!_atoldbl_l

t     // Step into(F11) 【Trace】

24 e 65ce3d80     0001 (0001) 0:****
MSVCR90D!_mbctolower

tc    // 执行到下一个进入点(Call指令)处停下 【Trace to Next Call】

25 e 65ca0d70     0001 (0001) 0:****
MSVCR90D!_atol_l

tb
 //
执行到分支指令停下【分支指令包括calls、returns、jumps、loops】

26 e 65d38670     0001 (0001) 0:****
MSVCR90D!__lc_strtolc

ta 7c801b12  // 执行到7c801b12地址处停下 【Trace to Adress】

27 e 65cdd8e0     0001 (0001) 0:****
MSVCR90D!CPtoLCID

WT
 Trace and Watch Data,一条强大指令,对执行流程做Profile

28 e 65d47d40     0001 (0001) 0:****
MSVCR90D!__strgtold12_l

#
查看句柄

 

!handle  // 查看所有句柄的ID

在上面的输出中,可以看到断点1和2是无效的断点,因此可以使用bc(breakpoint clear)这个命令删除掉这两个断点:

!handle 000007f8 1  // 查看ID为000007f8的句柄的类型

 

!handle 000007f8 4  // 查看ID为000007f8的句柄的名称

bc 1

!handle 0 5  // 查看所有句柄的类型和名称

#

!htrace -enable  //
使用windbg调试运行时,启用跟踪所有打开句柄或关闭句柄的调用以及相应的栈回溯

# 没有输出结果—没有消息就是好消息

!htrace  //
进程运行结束后,打印所有打开句柄或关闭句柄的调用以及相应的栈回溯

#

!htrace -diff  // 只打印没有关闭的句柄的信息及栈回溯

bc 2

Handle = 0x00000038 – OPEN
Thread ID = 0x00001f34, Process ID =
0x00000638

#

0x049ca83c: +0x049ca83c
0x049d5eb4: +0x049d5eb4
0x046c8313: +0x046c8313
0x76d2d90a: +0x76d2d90a
0x7451c1ff: +0x7451c1ff
0x7450d18f: +0x7450d18f
0x74492776: +0x74492776
0x7450d286: +0x7450d286
0x7450c69e: +0x7450c69e
0x76d210d6: +0x76d210d6
0x76d7dc30: +0x76d7dc30
0x76d0b17e: +0x76d0b17e
0x76ee0166:
ntdll!NtCreateFile+0x00000012
0x7542c76b:
KERNELBASE!CreateFileW+0x0000035e
0x752c3f66:
kernel32!CreateFileW+0x0000004a
0x752c53c4:
kernel32!CreateFileA+0x00000036

# 没有输出结果—没有消息就是好消息


#

Parsed 0x1 stack traces.
Dumped 0x1 stack traces.

 

 

因此在前面的bm命令中,设置了太多的断点,为了避免在不必要的函数上中断,我们既可以使用bc命令将它们删掉,也可以使用bd(breakpoint disabled)命令将其禁用。因为命令实在太多,所以我们可以使用一个小技巧—使用一个范围来禁用一批断点:

#
查看变量

 

===  0n(十进制)  0x(十六进制)  0t(8进制)
 0y(2进制)    可以使用n [8|10|16]命令来修改数值进制表示方式(输入n可查看当前进制,默认为16进制)===

bd 4-10


VC6.0的Link选项需要将/pdbtype:sept改为/pdbtype:con,
否则生成的pdb文件中将不包含如自定义结构体,类等信息

#

dt nRet  // 查看局部变量nRet的类型与值(函数参数变量请用dv命令)
–注:编译器很多时候会把一些局部变量优化掉,这个时候就会出现找不到nRet符号的情况

# 没有输出结果—没有消息就是好消息,

dt ntdll!*
// 显示ntdll里的所有类型信息

# 这个命令将从断点4到断点10的所有断点都禁用了。

dt
*!*IMAGE_DOS*  //
显示所有模块中含有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
myApp!g_app //
表示显示myApp进程里全局变量g_app的内存布局(注:vc6见上述说明)

bd和bc命令的语法是一样的,既可以根据指定的范围禁用或删除一批断点,也可以根据指定的通配符来操作一批断点,还可以使用一种稀奇古怪的语法来操作断点(这个稀奇古怪的语法会在后面的文章中讲到)。

dt
WindbgTest!CTest //
查看模块WindbgTest的CTest的内存布局,加上-b
-r参数可以显示内部类及数组的信息(注:vc6见上述说明)

设置好断点以后,可以继续进程的运行了,断点触发以后,我们才能查看进程的堆栈以及一些变量的数据。这些内容放在下一篇文章调试非托管程序的基本命令下讲解。

dt
WindbgTest!CTest 0x0041f8d4  //
将0x0041f8d4地址处内容按照模块WindbgTest的CTest的内存布局来解析(注:vc6见上述说明)

Windbg默认会用寄存器ECX里面的值作为this指针地址,其实这样是有时候是错误的。
有些C++编译器在做代码优化之后会把
this指针放在其他寄存器里面,比如ESI。
所以在调试的时候还需要读一下汇编代码来确定this
在哪个寄存器里面。
比如我们看到 MOV EAX, dword ptr [ESI +
0x48h](获取当前对象内存偏移为0x48h处的4字节成员变量),就可以判断ESI
是this 指针。
这时可以通过如下命令打印this的内存结构:dt
-b 模块名!类名 @esi

dt tagMSG
0x0336e54c  //
类型要使用tagMSG,不能使用typedef产生出的MSG

typedef struct tagMSG {
    HWND hwnd;
    UINT message;
    WPARAM wParam;
    LPARAM lParam;
    DWORD time;
    POINT pt;
} MSG, *PMSG;

dt
this // 查看this指针的类型及成员变量(注:vc6见上述说明)

dt -b
this  //
查看this指针的类型及成员变量,如果某一成员变量为结构体,则把其结构成员也一一打印出来

             
 // -b开关指定递归显示所有子类型信息,-r[n]指定递归显示的深度,如-r0表示不显示子类型信息

dt -v _PEB
@$PEB // 使用详细模式查看PEB(process’s environment
block)内存结构

dt _TEB ny
LastErrorValue // 只查看TEB(thread’s environment
block)结构成员LastErrorValue

??
this->m_nPen  // 查看成员变量的值(注:vc6见上述说明)

??
this // 查看this指针中的成员变量(注:vc6见上述说明)

?
nCount //
显示局部变量nCount的地址(前面4198608为10进制表示地址,004010d0为16进制表示地址)
形如:Evaluate expression: 4198608 = 004010d0

? HeapTest!CTest::Add  //
显示HeapTest模块中CTest类中的Add函数地址

dv   // 显示当前函数内所有局部变量,函数参数的值

dv n*  // 显示当前函数内n开头的所有局部变量,函数参数的值

dv
nCount // 查看局部变量nCount的值

dv a
// 查看函数参数变量a的值

dv /t /i /V /a|/n|/z

/***************************************** 

更加详细地显示当前函数内所有局部变量,函数参数信息
i = type (local, global,
parameter)
t = data type
V = memory address or register

location

a = sort by Addr, n = sort by name, z =
sort by size

*****************************************/

x
 //
用法和dv命令一致,显示当前函数内所有局部变量,函数参数变量的地址与值  –注:编译器很多时候会把一些局部变量优化掉,这个时候就会出现找不到符号的情况

#调用堆栈

k  // 显示当前调用堆栈

kn // 带栈编号显示当前调用堆栈

kb  // 打印出前3个函数参数的当前调用堆栈

02a9ffec 00000000 01e511f9 0174c570

00000000 kernel32!BaseThreadStart+0x37

kernel32!BaseThreadStart+0x37
当前函数kernel32!BaseThreadStart执行的指令地址。
01e511f9 0174c570 00000000
 参数相关的数值【从左到右 — 参数1  参数2:offset为sizeof(参数1)
 参数3:offset为sizeof(参数1)+sizeof(参数2)
 …】。注:如果是成员函数,this指针通过ecx或其他寄存器来传递
02a9ffec 00000000是 当前函数ebp 和
上层函数返回地址。

======================================================

Windows 7 Ultimate Service Pack 1 [Build
6.1.7601]
CPU: Intel(R) Core(TM) i3-2100 CPU @
3.10GHz
《Game》 20.15.1112

6EF76A43E6B1DD58907F3E066506E918

Type:
EXCEPTION_ACCESS_VIOLATION//非法地址访问异常
Error: Read
address 0x00074685//读取0x00074685地址出错(该地址对应的内存页,进程无读取权限)
Address:
64DCAF68//崩溃发生的指令地址

CallStack:

//0x64D90000为模块MSVCR90.dll的基地址,3AF68为崩溃指令在模块中的偏移值,(0492B5D0,00074685,0000000C,00462482)为参数信息
0x64D90000[3AF68] MSVCR90.dll:
(0492B5D0,00074685,0000000C,00462482)
0X00400000[11158A] tgame.exe:
(0492B5D0,00074685,00000000,00000000)

kb 5 // 只显示最上的5层调用堆栈

kv  
// 在kb的基础上增加了函数调用约定、FPO等信息

kp
 // 显示每一层函数调用的完整参数,包括参数类型、名字、取值(必须是完整符号的情况下,private
symbols);注意:若程序被优化,这些值不一定对

kd
 // 打印堆栈的地址

kD
 // 从当前esp地址处,向高地址方向搜索符号(注:函数是符号的一种)

dds 02a9ffec  //
从02a9ffec地址处,向高地址方向搜索符号(注:函数是符号的一种)

dds
 // 执行完dds 02a9ffec后,可通过dds命令继续进行搜索

.frame // 显示当前栈帧

.frame
n  // 显示编号为n的栈帧(n为16进制数)

.frame /r
n // 显示编号n的栈帧(n为16进制数) 并显示寄存器变量

.frame /c n // 设置编号n的栈帧为当前栈帧(n为16进制数)

!uniqstack // 显示所有线程的调用堆栈

!findstack
kernel32 2
// 显示包含kernel32模块(用星号标出)的所有栈的信息

 

+++++++++++++++++++++++++++++++++++++++++++

案例:某个Unity游戏在关闭时的崩溃堆栈

具体崩溃的原因:0x47e2740为游戏脚本中编写的新窗口处理函数地址,关闭程序时,游戏脚本从内存中被卸载,0x47e2740变成了野指针引发崩溃

注:0x47e2740是游戏脚本新窗口处理函数被虚拟机JIT产生出来的地址,在线程的Native栈上是没有符号的,托管代码都有这个特点

图片 7

 

#查看汇编

u .  // 反汇编当前eip寄存器地址的后8条指令

u $eip  // 反汇编当前eip寄存器地址的后8条指令

ub .  // 反汇编当前ip寄存器地址的前8条指令

ub $eip  // 反汇编当前eip寄存器地址的前8条指令

u main+0x29 L30 // 反汇编main+0x29地址的后30条指令

u  //
反编译下8条指令

uf CTest::add  // 反汇编CTest类的add函数

uf /c main  //
反汇编main函数,通过/c可以查看main函数中的函数调用(call)都有哪些

ub 000c135d L20  // 查看地址为000c135d指令前的20条指令内容

 

#寄存器

r // 显示所有寄存器信息及发生core所在的指令

r eax, edx // 显示eax,edx寄存器信息

r eax=5, edx=6  // 对寄存器eax赋值为5,edx赋值为6

#内存

— 注:windbg不支持中文显示

!address //
查看进程的所有内存页属性

!address -summary  //
显示进程的内存统计信息  要为fulldmp才会准确

!address -f:stack  //
查看栈的内存信息,红框为主线程、篮框为windbg注入的远程线程

图片 8

!address 7ffd8000  // 查看7ffd8000地址处内存页属性

dd /c 5 7c801e02  //
从7c801e02内存处开始以dword为单位显示内存(宽度为:5)【默认显示128字节长度的内容】

dd /c 5
7c801e02 L8  //
从7c801e02内存处开始以dword为单位显示内存(宽度为:5)【显示8个dword】

da /c 100
7c80ff03  //
从7c80ff03内存处开始显示Ascii字符串(宽度为:100)

du /c 100
7c8022f5  //
从7c8022f5内存处开始显示Unicode字符串(宽度为:100)

/*****************************************

d[a| u| b| w| W| d| c| q| f| D] [/c
列数] [地址]

a = ascii chars
u = Unicode chars
b = byte + ascii   —
和UE一样,左边为byte为单位的二进制内容,右边块为ascii形式的字符串内容
w = word (2b)
W = word (2b) + ascii
d = dword (4b)
c = dword (4b) + ascii
q = qword (8b)
f = floating point (single precision –
4b)
D = floating point (double precision –
8b)

*****************************************/

dyb /c 3 7c801e02  // 从7c801e02内存处开始,显示byte及二进制(宽度为:3)

/*****************************************

dy[b | d] ..   // b = binary+byte     d
= binary+dword

*****************************************/

s -w
522e0000 L0x100  0x1212 0x2212 0x1234 //
表示在起始地址522e0000之后的0x100个单位内搜索0x1212 0x2212
0x1234系列的起始地址

s -u
522e0000 527d1000 “web”
 //表示在522e0000 和527d1000之间搜索Unicode 字符串”web”

ea 0x445634
“abc”  //
表示在0x445634地址写入Ascii字符串abc, 不包含结束符0

eza 0x445634
“abc”  // 表示在0x445634地址写入Ascii字符串abc,
包含结束符0

eu 0x445634 “abc”  //
表示在0x445634地址写入Unicode字符串abc, 不包含结束符0

ezu 0x445634 “abc”  // 表示在0x445634地址写入Unicode字符串abc,
包含结束符0

ed nCounter
80  //
将变量nCounter的值修改为80(注:80为10进制还是16进制,还是其他,取决于当前进制)

.writemem
D:\Test\0041a5e4.bin 0041a5e4 L1000  //
将内存地址处0x0041a5e4后面0x1000长度的内容拷贝存储到D:\Test\0041a5e4.bin中

#查看堆(Heap)

!heap
-s  //
显示进程堆的个数(每一项是一个堆,也就是_HEAP结构指针,对应的API是HeapCreate)

Heap Flags Reserv Commit Virt Free List
UCR Virt Lock Fast
(k) (k) (k) (k) length blocks cont. heap


00140000 50000062 1024 12 12 1 1 1 0 0 L

00240000 50001062 64 24 24 15 1 1 0 0 L

00250000 50008060 64 12 12 10 1 1 0 0

00380000 50001063 64 12 12 4 2 1 0 bad


dt _HEAP
00140000  // 选取一个堆的地址,打印该堆的内存结构

ntdll!_HEAP
+0x000 Entry : _HEAP_ENTRY
+0x008 Signature : 0xeeffeeff
+0x00c Flags : 0x50000062
+0x010 ForceFlags : 0x40000060
+0x014 VirtualMemoryThreshold :
0xfe00
+0x018 SegmentReserve : 0x100000
+0x01c SegmentCommit : 0x2000
+0x020 DeCommitFreeBlockThreshold :
0x200
+0x024 DeCommitTotalFreeThreshold :
0x2000
+0x028 TotalFreeSize : 0xaf
+0x02c MaximumAllocationSize :
0x7ffdefff
+0x030 ProcessHeapsListIndex : 1
+0x032 HeaderValidateLength :
0x608
+0x034 HeaderValidateCopy : (null)

+0x038 NextAvailableTagIndex : 0
+0x03a MaximumTagIndex : 0
+0x03c TagEntries : (null)
+0x040 UCRSegments : (null)
+0x044 UnusedUnCommittedRanges :
0x00140598 _HEAP_UNCOMMMTTED_RANGE
+0x048 AlignRound : 0x17
+0x04c AlignMask : 0xfffffff8
+0x050 VirtualAllocdBlocks :
_LIST_ENTRY [ 0x140050 – 0x140050 ]
+0x058 Segments : [64] 0x00140640
_HEAP_SEGMENT
+0x158 u : __unnamed
+0x168 u2 : __unnamed
+0x16a AllocatorBackTraceIndex :
0
+0x16c NonDedicatedListLength :
1
+0x170 LargeBlocksIndex : (null)

+0x174 PseudoTagEntries : (null)

+0x178 FreeLists : [128] _LIST_ENTRY
[ 0x142a90 – 0x142a90 ]
+0x578 LockVariable : 0x00140608
_HEAP_LOCK
+0x57c CommitRoutine : (null)
+0x580 FrontEndHeap : 0x00140688
Void
+0x584 FrontHeapLockCount : 0
+0x586 FrontEndHeapType : 0x1 ”
+0x587 LastSegmentIndex : 0 ”

!heap -a 00140000 //
选取一个堆的地址,打印该堆的信息,比上面打印内存命令更详细直观

Index Address Name Debugging options
enabled
1: 00140000
Segment at 00140000 to 00240000 (00003000
bytes committed)
Flags: 50000062
ForceFlags: 40000060
Granularity: 8 bytes
Segment Reserve: 00100000
Segment Commit: 00002000
DeCommit Block Thres: 00000200
DeCommit Total Thres: 00002000
Total Free Size: 000000af
Max. Allocation Size: 7ffdefff
Lock Variable at: 00140608
Next TagIndex: 0000
Maximum TagIndex: 0000
Tag Entries: 00000000
PsuedoTag Entries: 00000000
Virtual Alloc List: 00140050
UCR FreeList: 00140598
FreeList Usage: 00000000 00000000
00000000 00000000
FreeList[ 00 ] at 00140178: 00142a90 .
00142a90
00142a88: 00050 . 00578 [14] –
free
Segment00 at 00140640:
Flags: 00000000
Base: 00140000
First Entry: 00140680
Last Entry: 00240000
Total Pages: 00000100
Total UnCommit: 000000fd
Largest UnCommit:000fd000
UnCommitted Ranges: (1)
00143000: 000fd000

Heap entries for Segment00 in Heap
00140000
00140000: 00000 . 00640 [01] – busy
(640)
00140640: 00640 . 00040 [01] – busy
(40)
00140680: 00040 . 01818 [07] – busy
(1800), tail fill – unable to read heap entry extra at 00141e90
00141e98: 01818 . 00040 [07] – busy
(22), tail fill – unable to read heap entry extra at 00141ed0
00141ed8: 00040 . 00050 [07] – busy
(36), tail fill – unable to read heap entry extra at 00141f20
00141f28: 00050 . 002f0 [07] – busy
(2d8), tail fill – unable to read heap entry extra at 00142210
00142218: 002f0 . 00330 [07] – busy
(314), tail fill – unable to read heap entry extra at 00142540
00142548: 00330 . 00330 [07] – busy
(314), tail fill – unable to read heap entry extra at 00142870
00142878: 00330 . 00040 [07] – busy
(24), tail fill – unable to read heap entry extra at 001428b0
001428b8: 00040 . 00028 [07] – busy
(10), tail fill – unable to read heap entry extra at 001428d8
001428e0: 00028 . 00058 [07] – busy
(40), tail fill – unable to read heap entry extra at 00142930
00142938: 00058 . 00058 [07] – busy
(40), tail fill – unable to read heap entry extra at 00142988
00142990: 00058 . 00060 [07] – busy
(44), tail fill – unable to read heap entry extra at 001429e8
001429f0: 00060 . 00020 [07] – busy
(1), tail fill – unable to read heap entry extra at 00142a08
00142a10: 00020 . 00028 [07] – busy
(10), tail fill – unable to read heap entry extra at 00142a30
00142a38: 00028 . 00050 [07] – busy
(36), tail fill – unable to read heap entry extra at 00142a80
00142a88: 00050 . 00578 [14] free
fill
00143000: 000fd000 – uncommitted
bytes.

#虚拟内存

!vadump  // 查看虚拟内存布局 

#设置事件发生时windbg行为

sx // 显示windbg遇到每个异常和事件时的行为

sxr // 将所有异常和事件过滤器的状态重设为默认值

sxe  ld // 当加载模块时,立即中断(Break)到调试器中(First-Chance)

sxe ld mono.dll  // 当加载mono.dll模块时,立即中断(Break)到调试器中(First-Chance)

sxd ud //
当卸载模块时,windbg不会在第一次处理机会时中断(虽然会显示信息)。如果其他错误处理器没有处理掉该异常,执行会停止下来并中断(Break)到windbg中(Second-Chance)

sxd
av // 所有异常和事件只有Second-Chance才中断到调试器

sxn et // 当线程退出时,windbg会打印出一条消息

sxi ct // 当线程创建时,windbg不中断也不打印消息

sxi c0000005  // 不中断也不打印code码为c0000005的异常

 

#dump输出

 .dump /ma “d:\mydmpfile.dmp”
// 将当前调试进程输出Dump文件

#其他元命令

.tlist  // 显示所有进程

.cls  // 清除屏幕

.shell
config.bat  // 执行config.bat批处理文件

.shell
tasklist  // 列出所有进程信息

.formats
‘a’  // 显示字母a的各种类型对应的信息

图片 9

.logopen
c:\1.log   // 将command内容输出到c:\1.log文件中

.logclose  // 关闭当前打开的日志

#其他扩展命令

!analyze -v  // 详细显示当前异常信息

!analyze
-hang  // 诊断线程调用栈上是否有任何线程阻塞了其他线程

!analyze
-f  // 查看异常分析信息,尽管调试器并未诊断出异常

!tls -1 // 显示当前线程所有的slot信息

!tls 2   // 显示当前线程索引为2的slot信息

!peb // 格式化输出PEB信息(process’s environment
block)

PEB at 7efde000
InheritedAddressSpace: No
ReadImageFileExecOptions: No
BeingDebugged: No
ImageBaseAddress: 00f20000
Ldr 76fa0200
Ldr.Initialized: Yes
Ldr.InInitializationOrderModuleList:
003a4d78 . 003e4a00
Ldr.InLoadOrderModuleList: 003a4ce8 .
003e49f0
Ldr.InMemoryOrderModuleList: 003a4cf0 .
003e49f8
Base TimeStamp Module
f20000 54772f74 Nov 27 22:04:36 2014
C:\Program Files (x86)\Citrix\ICA Client\wfcrun32.exe
76ea0000 5684255b Dec 31 02:41:31 2015
C:\windows\SysWOW64\ntdll.dll
74790000 568425ff Dec 31 02:44:15 2015
C:\windows\syswow64\kernel32.dll
75b00000 56842600 Dec 31 02:44:16 2015
C:\windows\syswow64\KERNELBASE.dll
… …
71d40000 55a5cad6 Jul 15 10:52:06 2015
C:\windows\System32\msxml6.dll
71d20000 4a5bdb38 Jul 14 09:11:20 2009
C:\windows\System32\bcrypt.dll
71ce0000 5600ce51 Sep 22 11:43:13 2015
C:\windows\SysWOW64\bcryptprimitives.dll
SubSystemData: 00000000
ProcessHeap: 003a0000
ProcessParameters: 003a2188
CurrentDirectory:
‘C:\windows\system32\’
WindowTitle: ‘”C:\Program Files
(x86)\Citrix\ICA Client\wfcrun32.exe”‘
ImageFile: ‘C:\Program Files
(x86)\Citrix\ICA Client\wfcrun32.exe’
CommandLine: ‘”C:\Program Files
(x86)\Citrix\ICA Client\wfcrun32.exe” -Embedding’
DllPath: ‘C:\Program Files
(x86)\Citrix\ICA
Client;C:\windows\system32;C:\windows\system;C:\windows;
Environment: 003a07f0
=::=::\
ALLUSERSPROFILE=C:\ProgramData

APPDATA=C:\Users\kekec\AppData\Roaming
CommonProgramFiles=C:\Program Files
(x86)\Common Files
CommonProgramFiles(x86)=C:\Program Files
(x86)\Common Files
CommonProgramW6432=C:\Program
Files\Common Files
COMPUTERNAME=KEKEC-PC1

ComSpec=C:\windows\system32\cmd.exe
DXSDK_DIR=D:\Program Files
(x86)\Microsoft DirectX SDK (June 2010)\
FP_NO_HOST_CHECK=NO
HOMEDRIVE=C:
HOMEPATH=\Users\kekec
include=D:\Program Files
(x86)\Microsoft Visual Studio\VC98\atl\include;D:\Program Files
(x86)\Microsoft Visual Studio\VC98\mfc\include;
lib=D:\Program Files (x86)\Microsoft
Visual Studio\VC98\mfc\lib;D:\Program Files (x86)\Microsoft Visual
Studio\VC98\lib

LOCALAPPDATA=C:\Users\kekec\AppData\Local
LOGONSERVER=\\GM-HEIJI
MSDevDir=D:\Program Files
(x86)\Microsoft Visual Studio\Common\MSDev98
NUMBER_OF_PROCESSORS=8
OS=Windows_NT

Path=C:\ProgramData\Oracle\Java\javapath;C:\windows\system32;C:\windows;C:\Program
Files\Intel\Intel(R) Management Engine Components\DAL;

PATHEXT=.COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC
PROCESSOR_ARCHITECTURE=x86
PROCESSOR_ARCHITEW6432=AMD64
PROCESSOR_IDENTIFIER=Intel64 Family 6
Model 60 Stepping 3, GenuineIntel
PROCESSOR_LEVEL=6
PROCESSOR_REVISION=3c03
ProgramData=C:\ProgramData
ProgramFiles=C:\Program Files
(x86)
ProgramFiles(x86)=C:\Program Files
(x86)
ProgramW6432=C:\Program Files

PSModulePath=C:\windows\system32\WindowsPowerShell\v1.0\Modules\
PUBLIC=C:\Users\Public
SESSIONNAME=Console
SystemDrive=C:
SystemRoot=C:\windows

TEMP=C:\Users\kekec\AppData\Local\Temp

TMP=C:\Users\kekec\AppData\Local\Temp
USERDNSDOMAIN=TENCENT.COM
USERDOMAIN=TENCENT
USERNAME=kekec
USERPROFILE=C:\Users\kekec
VS120COMNTOOLS=D:\Program Files
(x86)\Microsoft Visual Studio 12.0\Common7\Tools\
VS80COMNTOOLS=D:\Program Files
(x86)\Microsoft Visual Studio 8\Common7\Tools\
VS90COMNTOOLS=d:\Program Files
(x86)\Microsoft Visual Studio 9.0\Common7\Tools\
windir=C:\windows

!gle  // 打印当前线程最近的错误信息LastError

!gle -all  // 打印所有线程的最近的错误信息

!error  897// 显示错误码为897的详细描述信息

#帮助

中文在线帮助:

windbg
cmd:  【下载

WinDbg
命令三部曲:(一)WinDbg
命令手册

相关文章