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

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

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

命令格式

示例

说明

bp 函数名

bp Usage

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

bp 指令地址

bp 010113c0

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

bp `源文件地址`

bp `nativedebug.cpp:21`

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

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

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

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

bp Usage

#

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

# windbg不会显示任何信息。

#

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

bp UsageA

#

# 输出结果

#

Bp expression 'UsageA' could not be resolved, adding deferred bp

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

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

lm vmnativedebug

#

# 输出结果

#

start    end        module name

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

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

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

Loaded symbol image file: nativedebug.exe

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

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

# SysWow64文件夹里加载的。

Image path: nativedebug.exe

Image name: nativedebug.exe

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

CheckSum:         00000000

ImageSize:        0001B000

Translations:     0000.04b0 0000.04e4 0409.04b0 0409.04e4

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

lm vmntdll

#

# 输出结果

#

start    end        module name

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

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

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

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

# 重要。

File version:     6.1.7600.16385

Product version: 6.1.7600.16385

File flags:       0 (Mask 3F)

# 模块要求的子系统

File OS:          40004 NT Win32

File type:        2.0 Dll

File date:        00000000.00000000

Translations:     0409.04b0

CompanyName:      Microsoft Corporation

ProductName:      Microsoft® Windows® Operating System

InternalName:     ntdll.dll

OriginalFilename: ntdll.dll

ProductVersion:   6.1.7600.16385

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

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

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

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

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

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

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

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

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

#

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

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

# 符号文件的。

#

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

FileVersion:      6.1.7600.16385 (win7_rtm.090713-1255)

FileDescription: NT Layer DLL

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

LegalCopyright:   © Microsoft Corporation. All rights reserved.

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

x nativedebug!UsageA

#

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

#

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

x nativedebug!Usage

#

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

#

010113c0 nativedebug!Usage (void)

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

#ifdef _UNICODE

#   define _ttol       _wtol

#else

#   define _ttol       atol

#endif

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

x MSVCR90D!*tol*

#

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

#

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

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

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

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

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

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

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

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

65ca0d50 MSVCR90D!atol (char *)

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

65ca12d0 MSVCR90D!_wtol (wchar_t *)

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 *)

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

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

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 *)

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

65cdd8e0 MSVCR90D!CPtoLCID (int)

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

65c6109c MSVCR90D!_imp__FileTimeToLocalFileTime = <no type information>

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

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

bm *tol*

#

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

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

#

4: 65cd1bb0 @!"MSVCR90D!__STRINGTOLD"

5: 65d47c80 @!"MSVCR90D!_ld12told"

6: 65cd1900 @!"MSVCR90D!_atoldbl"

7: 65cd6790 @!"MSVCR90D!_wcstol_l"

8: 65cd4400 @!"MSVCR90D!strtol"

9: 65d4bac0 @!"MSVCR90D!__mtold12"

10: 65cd5030 @!"MSVCR90D!_tolower_l"

11: 65cd6300 @!"MSVCR90D!wcstol"

12: 65ca0d50 @!"MSVCR90D!atol"

13: 65cd4940 @!"MSVCR90D!_strtol_l"

14: 65ca12d0 @!"MSVCR90D!_wtol"

15: 65d4a980 @!"MSVCR90D!__wstrgtold12_l"

16: 65d544e0 @!"MSVCR90D!_ftol"

17: 65cd5010 @!"MSVCR90D!_tolower"

18: 65cd5210 @!"MSVCR90D!tolower"

19: 65ca12f0 @!"MSVCR90D!_wtol_l"

20: 65d48fd0 @!"MSVCR90D!__dtold"

21: 65ce3c30 @!"MSVCR90D!_mbctolower_l"

22: 65d48dc0 @!"MSVCR90D!__STRINGTOLD_L"

23: 65cd17f0 @!"MSVCR90D!_atoldbl_l"

24: 65ce3d80 @!"MSVCR90D!_mbctolower"

25: 65ca0d70 @!"MSVCR90D!_atol_l"

26: 65d38670 @!"MSVCR90D!__lc_strtolc"

27: 65cdd8e0 @!"MSVCR90D!CPtoLCID"

28: 65d47d40 @!"MSVCR90D!__strgtold12_l"

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

bl

#

# 输出结果

# 第一列是断点的编号;

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

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

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

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

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

#

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

1 eu             0001 (0001) (UsageA)

# 这个断点没有设置正确

2 eu             0001 (0001) (`22`)

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

bc 1

#

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

#

bc 2

#

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

#

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

bd 4-10

#

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

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

#

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

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

Windbg教程-调试非托管程序的基本命令中相关推荐

  1. Windbg 教程-调试非托管程序的基本命令下

    前面的文章调试非托管程序的基本命令中讲到如何使用windbg在程序中设置断点,既然断点已经设置好了,下一步就是直接执行程序,程序中断以后,第一件事情就是查看堆栈.在windbg中查看堆栈使用k命令就可 ...

  2. Windbg教程-调试非托管程序的基本命令上

    Windbg是跟visual studio差不多的一个调试器,可以用来调试非托管程序(native application),也可以调试托管程序(managed application).它比VS强的 ...

  3. 托管程序与非托管程序的区别

    原始地址:http://www.cnblogs.com/springcsc/archive/2008/12/25/1362515.html 一般一个可执行文件的内部都包含一个PE头,系统根据PE的信息 ...

  4. .Net使用非托管程序

    开发过程中我们或多或少都要使用到非托管组件,例如常见的ActiveX(一般是vb写的.ocx组件)或Com组件(一般是c++编写).我们都知道.Net程序是可以使用托管代码的,常见的方式大概可以分为两 ...

  5. 非托管资源在虚拟机中的管理

    为什么80%的码农都做不了架构师?>>>    非托管资源不属于虚拟机本身直接管理的资源.它是OS直接管理的资源. 对虚拟机来讲,它可以管好所有在自己托管范围内的资源.对OS来讲,它 ...

  6. 非托管C++程序中调用C#的dll

    刚去的新公司分配了我一个项目需求,将PPT文件(包括*.ppt和*.pptx)转换成多张png图片.由于以前只有native C++的经验,在网上逛了多圈后,发现都是使用C#实现这个功能的,被这个需求 ...

  7. C#编程(七十四)----------释放非托管资源

    释放非托管资源 在介绍释放非托管资源的时候,我觉得有必要先来认识一下啥叫非托管资源,既然有非托管资源,肯定有托管资源. 托管资源指的是.net可以自棕进行回收的资源,主要是指托管堆上分配的内存资源.托 ...

  8. 斗地主你什么时候才会托管?(.NET中的托管于非托管)

    文章部分引自<.NET4.0面向对象编程漫谈(基础篇)>第1章.NET面向对象编程基础(作者:金旭亮) 无意间看到一位四五岁左右小朋友在玩斗地主,总开始到结束,她一直都在使用"提 ...

  9. 利用IDisposable接口构建包含非托管资源对象

    托管资源与非托管资源 在.net中,对象使用的资源分为两种:托管资源与非托管资源.托管资源由CLR进行管理,不需要开发人员去人工进行控制,.NET中托管资源主要指"对象在堆中的内存" ...

最新文章

  1. mysql 中修改对象_在MySQL中,创建一个数据库后,还可以对象其进行修改,不过这里的修改是指可以修改被创建数据库的相关参数,也可以修改数据库名。...
  2. Oracle管理表空间和数据文件详解
  3. java String format占位符
  4. fork() || fork()和fork() fork()笔试题
  5. java程序中可以有几个构造方法_java中多个构造方法可以相互引用么?
  6. css画钟表_纯Shading Language绘制HTML5时钟
  7. java求sum的前n项和_【LeetCode-面试算法经典-Java实现】【015-3 Sum(三个数的和)】...
  8. iApp对接hybbs社区APP源码
  9. 第二章 在Linux上部署.net core
  10. Day13 - Ruby比一比: instance_eval 和 class_eval方法
  11. nginx-rtmp协议解读
  12. MapABC Flex地图官方API应用整理
  13. 61种u盘问题解决工具合集解决无法格式化,u盘写保护等问题。
  14. 如何做好数据分析的数据采集工作?
  15. 高通SDX55平台:Modem Loopback测试指导
  16. 用python预测小孩的身高_Python 孩子身高预测
  17. 全排列、排列组合(去重区别)
  18. 如何启动 WordPress 博客 – 简易指南 – 创建博客(2021)
  19. 什么叫单模光纤_石家庄某小区光纤熔接示意图
  20. sdhc卡文件丢失常见原因和两种恢复方法

热门文章

  1. 机器人彩铅画_彩铅画嗔
  2. mysql导入多条数据语句_MySQL插入多条记录和REPLACE语句
  3. 如何在 IntelliJ IDEA 中快速生成 JavaDoc 注释模板
  4. egg(87)--egg之redis的安装使用
  5. 探析“Java序列化”之serialVersionUID
  6. kotlin使用spring data redis(二)
  7. selenium webdirver之rdoc使用
  8. zabbixproxy安装
  9. 如何使用验证控件对DropDownList进行验证
  10. 设计模式(Design Patterns)详解