Windbg教程-调试非托管程序的基本命令中
前面的文章调试非托管程序的基本命令上讲到如何在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教程-调试非托管程序的基本命令中相关推荐
- Windbg 教程-调试非托管程序的基本命令下
前面的文章调试非托管程序的基本命令中讲到如何使用windbg在程序中设置断点,既然断点已经设置好了,下一步就是直接执行程序,程序中断以后,第一件事情就是查看堆栈.在windbg中查看堆栈使用k命令就可 ...
- Windbg教程-调试非托管程序的基本命令上
Windbg是跟visual studio差不多的一个调试器,可以用来调试非托管程序(native application),也可以调试托管程序(managed application).它比VS强的 ...
- 托管程序与非托管程序的区别
原始地址:http://www.cnblogs.com/springcsc/archive/2008/12/25/1362515.html 一般一个可执行文件的内部都包含一个PE头,系统根据PE的信息 ...
- .Net使用非托管程序
开发过程中我们或多或少都要使用到非托管组件,例如常见的ActiveX(一般是vb写的.ocx组件)或Com组件(一般是c++编写).我们都知道.Net程序是可以使用托管代码的,常见的方式大概可以分为两 ...
- 非托管资源在虚拟机中的管理
为什么80%的码农都做不了架构师?>>> 非托管资源不属于虚拟机本身直接管理的资源.它是OS直接管理的资源. 对虚拟机来讲,它可以管好所有在自己托管范围内的资源.对OS来讲,它 ...
- 非托管C++程序中调用C#的dll
刚去的新公司分配了我一个项目需求,将PPT文件(包括*.ppt和*.pptx)转换成多张png图片.由于以前只有native C++的经验,在网上逛了多圈后,发现都是使用C#实现这个功能的,被这个需求 ...
- C#编程(七十四)----------释放非托管资源
释放非托管资源 在介绍释放非托管资源的时候,我觉得有必要先来认识一下啥叫非托管资源,既然有非托管资源,肯定有托管资源. 托管资源指的是.net可以自棕进行回收的资源,主要是指托管堆上分配的内存资源.托 ...
- 斗地主你什么时候才会托管?(.NET中的托管于非托管)
文章部分引自<.NET4.0面向对象编程漫谈(基础篇)>第1章.NET面向对象编程基础(作者:金旭亮) 无意间看到一位四五岁左右小朋友在玩斗地主,总开始到结束,她一直都在使用"提 ...
- 利用IDisposable接口构建包含非托管资源对象
托管资源与非托管资源 在.net中,对象使用的资源分为两种:托管资源与非托管资源.托管资源由CLR进行管理,不需要开发人员去人工进行控制,.NET中托管资源主要指"对象在堆中的内存" ...
最新文章
- mysql 中修改对象_在MySQL中,创建一个数据库后,还可以对象其进行修改,不过这里的修改是指可以修改被创建数据库的相关参数,也可以修改数据库名。...
- Oracle管理表空间和数据文件详解
- java String format占位符
- fork() || fork()和fork() fork()笔试题
- java程序中可以有几个构造方法_java中多个构造方法可以相互引用么?
- css画钟表_纯Shading Language绘制HTML5时钟
- java求sum的前n项和_【LeetCode-面试算法经典-Java实现】【015-3 Sum(三个数的和)】...
- iApp对接hybbs社区APP源码
- 第二章 在Linux上部署.net core
- Day13 - Ruby比一比: instance_eval 和 class_eval方法
- nginx-rtmp协议解读
- MapABC Flex地图官方API应用整理
- 61种u盘问题解决工具合集解决无法格式化,u盘写保护等问题。
- 如何做好数据分析的数据采集工作?
- 高通SDX55平台:Modem Loopback测试指导
- 用python预测小孩的身高_Python 孩子身高预测
- 全排列、排列组合(去重区别)
- 如何启动 WordPress 博客 – 简易指南 – 创建博客(2021)
- 什么叫单模光纤_石家庄某小区光纤熔接示意图
- sdhc卡文件丢失常见原因和两种恢复方法
热门文章
- 机器人彩铅画_彩铅画嗔
- mysql导入多条数据语句_MySQL插入多条记录和REPLACE语句
- 如何在 IntelliJ IDEA 中快速生成 JavaDoc 注释模板
- egg(87)--egg之redis的安装使用
- 探析“Java序列化”之serialVersionUID
- kotlin使用spring data redis(二)
- selenium webdirver之rdoc使用
- zabbixproxy安装
- 如何使用验证控件对DropDownList进行验证
- 设计模式(Design Patterns)详解