VC调试器高级应用(高级断点篇)

来源:互联网 发布:做陶瓷软件 编辑:程序博客网 时间:2024/05/16 16:01

一.高级断点语法
 
高级断点语法由两部分组成:1.上下文部分.2.位置,表达式,变量或Windows消息条件.
用函数,源文件和二进制模块来指定上下文,上下文的表示方法:
{[函数],[源文件],[二进制模块]}  
必须指定唯一的,足够的上下文信息才能获取断点位置.如在TEST.CPP的20行设一位置断点,语法为:{,TEST.CPP,}.20,如A.DLL或B.DLL都使用了该行,又只想在B.DLL的调用中触发,则必须使用:{,TEST.CPP,B.DLL}.20.
VC调试器中可直接输入上下文语法:Breakpoints对话框的Location选项卡BreakAt编辑框中.更容易的方法是使用BreatAt框右的箭头打开菜单,选择Advanced项,然后在Context框中输入断点的相应信息.
如想在一个绝对地址上中断,直接在BreakAt框中输入地址就行.


二.任何函数上快速中断
 
将函数名输入BreadAt框中.如果是C++代码,同时还需要类限定符.支持重载了的函数,调试器会列出所有满足条件的函数供选择,如输入时提供足够的信息,完全可略过选择过程.如输入:"CString::operator=(const  char  *)"可唯一确定要中断的函数.
 
三.在系统或DLL输出的函数中设置断点
 
在程序中从DLL输入的函数中设置一个断点可能是毫无作用的,调试器需要知道在何处可以找到该函数上下文信息,同时,函数名取决于是否加载了DLL的符号.只有在W2K以上版本中才能在系统DLL中设置断点--原因在于其它系统没有提供边写入边复制保护的功能,若一定要启用这种方法,必须要有COFF(Common  Object  File  Format),并在调试器中输出启动的装载----在Options对话框的Debug页,将Load  COFF  &  Exports选中.
VC调试器用分级的符号信息法,完整的符号的级别高于不太完整的.PDB(Program  Database)文件具有所有可能的源码行,函数,变量和类型信息,优先级便高于COFF/DBG文件,后者只有公用函数符号,而COFF/DBG文件高于输出名称,输入的名称是一种伪符号.
调试时,如DEBUG窗口输出:装载DLL的符号,则说明符号已被装入;否则说明没有装载DLL的符号.
没有装入符号时,使用的位置字符串是DLL输出的名称,可能用DUMPBIN程序查看这个名称:DUMPBIN  /EXPORTS  DLLname.例:在LoadLibraryA中设置中断:"{,,Kernel32.dll}LoadLibraryA".
如装入了符号,则要根据输出函数和调用协议来计算函数名.如上例,LoadLibraryA使用__stdcall调用协议,据该协议,函数名以下划线为前缀,所跟有进栈的字节数为后缀的@号.一般说来,参数个数*4,就是参数占用栈空间的总字节数,LoadLibary的名称便是:_LoadLibraryA@4,故最后的语法是:或"{,,Kernel32.dll}_LoadLibraryA@4"
 
附:常用的调用协议
     1、__stdcall调用约定相当于16位动态库中经常使用的PASCAL调用约定。在32位的VC++5.0中PASCAL调用约定不再被支持(实际上它已被定义为__stdcall。除了__pascal外,__fortran和__syscall也不被支持),取而代之的是__stdcall调用约定。两者实质上是一致的,即函数的参数自右向左通过栈传递,被调用的函数在返回前清理传送参数的内存栈,但不同的是函数名的修饰部分(关于函数名的修饰部分在后面将详细说明)。
       _stdcall是Pascal程序的缺省调用方式,通常用于Win32  Api中,函数采用从右到左的压栈方式,自己在退出时清空堆栈。VC将函数编译后会在函数名前面加上下划线前缀,在函数名后加上"@"和参数的字节数。
       2、C调用约定(即用__cdecl关键字说明)按从右至左的顺序压参数入栈,由调用者把参数弹出栈。对于传送参数的内存栈是由调用者来维护的(正因为如此,实现可变参数的函数只能使用该调用约定)。另外,在函数名修饰约定方面也有所不同。
       _cdecl是C和C++程序的缺省调用方式。每一个调用它的函数都包含清空堆栈的代码,所以产生的可执行文件大小会比调用_stdcall函数的大。函数采用从右到左的压栈方式。VC将函数编译后会在函数名前面加上下划线前缀。是MFC缺省调用约定。
       3、__fastcall调用约定是“人”如其名,它的主要特点就是快,因为它是通过寄存器来传送参数的(实际上,它用ECX和EDX传送前两个双字(DWORD)或更小的参数,剩下的参数仍旧自右向左压栈传送,被调用的函数在返回前清理传送参数的内存栈),在函数名修饰约定方面,它和前两者均不同。
       _fastcall方式的函数采用寄存器传递参数,VC将函数编译后会在函数名前面加上"@"前缀,在函数名后加上"@"和参数的字节数。        
       4、thiscall仅仅应用于“C++”成员函数。this指针存放于CX寄存器,参数从右到左压。thiscall不是关键词,因此不能被程序员指定。
       5、naked  call采用1-4的调用约定时,如果必要的话,进入函数时编译器会产生代码来保存ESI,EDI,EBX,EBP寄存器,退出函数时则产生代码恢复这些寄存器的内容。naked  call不产生这样的代码。naked  call不是类型修饰符,故必须和_declspec共同使用。
       关键字  __stdcall、__cdecl和__fastcall可以直接加在要输出的函数前,也可以在编译环境的Setting.../C/C++  /Code  Generation项选择。当加在输出函数前的关键字与编译环境中的选择不同时,直接加在输出函数前的关键字有效。它们对应的命令行参数分别为/Gz、/Gd和/Gr。缺省状态为/Gd,即__cdecl。
       要完全模仿PASCAL调用约定首先必须使用__stdcall调用约定,至于函数名修饰约定,可以通过其它方法模仿。还有一个值得一提的是WINAPI宏,Windows.h支持该宏,它可以将出函数翻译成适当的调用约定,在WIN32中,它被定义为__stdcall。使用WINAPI宏可以创建自己的APIs。
 
四.位置断点修饰符
 
1.跳跃计数.
功能是执行断点但不在断点处停止,直到执行完了一个特定的次数为止.
使用中首先设置一个标准的位置断点,打开BreadPoint对话框,选中该断点,单击Condition,然后在弹出的对话框最下面的编辑控件中输入次数.
只有当程序全速运行时,未执行的循环次数才有用.单步执行跨过断点时不会更新跳跃计数.
例:已知循环可能崩溃,但不清楚在哪次循环时,输入远远大于总循环次数的跳跃计数修饰符,则在崩溃时可打开Breakpoint框,其中将列出还未执行的循环次数,与总次数相减就可得已执行的次数.
 
2.条件表达式.
只有表达式为真时触发.Breakpoint框Condition按钮,选第一个编辑框,输入表达式即可.规则:
.只可使用C类型比较运算符.
.表达式中不能调用任何函数.
.表达式中不能包含任何宏值.
表达式为@TIB=Thread  Infomation  Block  Linear  Address,则程序只在该特定线程中才会中断.例:线程@TIB地址值为0E000,则输入"@TIB==0xE000",则在切换到该线程时中断.对W98,可用@FS=thread  specific  value.
如在某特定错误后中断,则可用@ERR,如"@ERR=2"表示在最后错误为ERROR_FILE_NOT_FOUND.除@CLK外,所有可在WATCH窗口中使用的伪寄存器均可用于条件表达式.
 
条件表达式可与跳跃断点组合使用.
 
3.变量更改
在变量更改时中断程序.只有当位置断点执行时才能检查变量.常用用调用栈高层的函数中发现出错,需要深入调用栈,压缩范围找出根源时.
添加时在Breakpoint框第一个编辑框中输入变量名(可以是指针指向听对象:*p),在第二个编辑框中输入要查看的项目数量.
 
五.全局表达式和条件断点.
 
调试器可监控某一地址和该地址上的1,2或4字节的内容.如可用硬件调试寄存器,则不影响速度;否则程序将单步执行ASM指令并在每一步中检查条件,这将严重影响程序运行速度.
总共有4个调试寄存器.硬件调试寄存器不能处理超过1个双字长的引用.确保利用硬件调试寄存器的最好方法是使用表达式和数据更改位置的实际地址值.例如:g_szGlobal是全局数组指针,地址为0x5000,则在Breakpoint对话框中DATA选项卡中将表达式断点设为"*(char*))0x5000=='G'",但如果写为"WO(0x5000)=='G',则用不到硬件调试寄存器,会单步执行每条指令.
与全局表达式断点类似,使用变量的16进制地址给定长指针计算地址,并将要查看的单元数设为1,则全局变量断点可发挥最付佳功效.如上例要在变量改动时中断,则输入:"*(long*)0x5000".
 
六.WINDOWS消息断点.
 
Breakpoint框的Message页.需要指定一个窗口过程,注意:MFC世界中AfxWndProc是多数窗口的一个窗口过程,所以总会在该断


该文章转载自网络大本营:http://www.xrss.cn/Dev/C/20061257107.Html

VC调试器高级应用----WATCH窗口篇 
  
  一.格式化数据和表达式赋值语句. 
  
  常用变量格式化符(表达式的值后跟逗号,接格式化符,如"(int)0xFFFF,d"): 
  d,I:有符号的十进制数. 
  u     :无符号的十进制数. 
  o     :无符号的八 
  x,X:十六进制数. 
  l,h:d,i,u,o,x,X的长前缀或短前缀. 
  f     :有符号浮点数. 
  e     :有符号的科学计数法. 
  g     :有符号的浮点或有符号的科学计数法,用其中较短的一个. 
  c     :单字符. 
  s     :字符串. 
  su   :双字节字符串. 
  st   :双字节字符串或ANSI字符串,取决于AUTOEXP.DAT中的Unicode   String设置. 
  hr   :Windows类标记. 
  wm   :Windows消息码. 
  
  常用内存转储对象的格式化符(用法同变量格式化符): 
  ma   :64个ASCII码字符. 
  m     :以16进制书写的16字节,后跟16个ASCII字符. 
  mb   :以16进制书写的16字节,后跟16个ASCII字符. 
  mw   :8个字长. 
  md   :4个双精度字. 
  mq   :4个四倍字长的字. 
  mu   :2字节字符(Unicode标准). 
  #     :将指针扩展到指定的数值数目的内存存储单元上.(#代表一个数字) 
  
  WATCH窗口允许重新设置数据变量的格式, 
  如:可用BY,DW表达式来定位指针的偏移量; 
  可用&和*运算符,且两运算符都可直接操作内存地址; 
  甚至可用上下说明符明确指定变量的上下文. 
  总之,所有格式化方法和指定方法在WATCH窗口都有效 
  
  WATCH窗口是一个完整的表达式求值程序,可以在其中查看任何条件语句. 
  
  表达式中可用的伪寄存器(可当普通变量进行查看): 
  @ERR:最后一个错误值,GetLastError   API返回相同的值. 
  @TIB:当前线程的线程信息块.(调试器不能处理"FS:0"格式). 
  @CLK:时钟寄存器. 
  @EAX,@EBX,@ECX,@EDX,@ESI,@EDI,@DIP,@ESP,@EBP,@EFL 
          :Intel   CPU寄存器. 
  @CS,@DS,@ES,@SS,@FS,@GS 
          :Intel   CPU段寄存器. 
  @ST0,@ST1,@ST2,@ST3,@ST4,@ST5,@ST6,@ST7 
          :Intel   CPU浮点寄存器. 
  
  二.适时编码 
  
  许多时候只想对两断点间的执行时间有个大致印象,可用@CLK得出两断点间所需执行时间(包括调试器占用的时间). 
  需要输入两个@CLK观察符,第一个是@CLK,第二个是@CLK=0.第二个的目的是重新运行时将定时器清0. 
  时间以微秒为单位,大多数情况下需要格式化为毫秒:"@CLK/1000,d". 
  
  三.在WATCH窗口中调用函数 
  
  大多数情况下用于执行专门编写的校验数据结构,保证数据的相关性的函数.在释放构件中,从未调用过的函数不会被链接,因此不必担心这类函数会对影响发布构件. 
  如函数没有参数,也要求使用括号"()",调用时像用普通函数一样传送参数.WATCH右边将显示函数返回值. 
  这里有些限制: 
  1.只能在一个单线程上下文中执行函数.如是多线程程序,将函数输入到WATCH窗口中检查结果后应立即从WATCH窗口清除,否则,如调试函数在第二个线程上下文中执行,会立即终止第二个线程的运行. 
  2.调试函数必须在20秒内执行.如执行过程中出现异常,程序会在调试器中中止. 
  3.(常识)只对数据验证进行内存读取,如有问题,调用OutputDebugString类的函数.如更改内存或调用API函数----尽管这是可能的,但无法预知可能会发生什么. 
  只要在WATCH窗口中重新计算表达式,已输入WATCH窗口的调试函数就会执行: 
  .程序处于运行状态并触发某一断点时. 
  .单步调试某一代码行或某一指令时. 
  .在WATCH窗口左边编辑完成调试函数的文本并按下回车时. 
  .在运行程序时出现异常情况,并让你返回调试器中时. 
  使用调试函数的建议:输入调试函数并查看值后,立即从WATCH窗口清除;只为最关键的数据结构编写调试函数;不要更改个别结构的转储内像. 
  
  四.自动扩展自己的类型 
  
  常见的自动扩展是RECT,输入RECT型的变量后直接显示其中的某些数据成员的值. 
  自定义类型扩展时,只需将自己的类型入口加入<VS   Common>/MSDev98/Bin目录的AUTOEXP.DAT文件中. 
  例: 
  扩展CreateProcess()所用到的PROCESS_INFORMATION结构 
  1.检查调试器将该类型识别为什么.将PROCESS_INFORMATION变量输入WATCH窗口,右击变量,选择Properties,在这里它被标注为_PROCESS_INFORMATION类型. 
  2.打开AUTOEXP.DAT文本文件,加入扩展入口.语法如下: 
  Type=[text]<member[format]> 
  本例中要查看hProcess和hThread值,故输入: 
  _PROCESS_INFORMATION=hProcess=<hProcess,X>   hThread=<hThread,X> 
  其中X表示以16进制查看.有个特殊的格式化符<,t>,用于通知调试器输入最易派生类型的类型名.如B派生至A,只有B有自动扩展规则,则B的自动扩展将会是后面跟随着类A的自动扩展规则的类型名B. 
  
  五.Set   Next   Statement命令 
  
  可以在调试时从菜单运行,但也可在WATCH窗口中直接设置EIP寄存器----小心,可能很容易摧毁程序.在最优化的释放构件中,最安全的方法是在Disassembly窗口中使用该命令.如代码在堆栈上创建了临时变量,更要多加小心. 
  最常用的情况是:在出问题的函数前设置一个断点,检查进入的参数,单步调试整个函数;如问题不是重复的,使用Set   Next   Statement设置返回到断点的执行点,并更改参数.这样可在一个调试会话中测试多个假设,节省测试时间,但它不能用于所有场合,因为函数执行会破坏其状态. 
  另一个常用地点是测试时填充数据结构,如表和数组,可用它输入额外的数据并查看代码如何处理--当某些数据条件难于复制时更为方便.

原创粉丝点击