VC++调试技巧



本文内容来源于我在2004年给所在部门的新员工做的一次内部培训。在对新员工的培训过程中,发现对于新员工来说,在进入工作岗位后,关注编程的技巧比较多,而对于VC/Windows环境下的程序调试,以及相关工具的使用掌握的不好。从我自己学习的过程来看,调试和工具的使用主要靠长期的积累和摸索,相关的资料非常少,部分调试技术,对于工作多年的老员工来说,可能也从来没有接触过。下面我就将自己在日常工作中使用的一些调试经验作一些介绍,希望对大家有所帮助。


一、      调试
1.          基本技巧
1)      断点的设置与跟踪
a)     普通断点设置  F9
b)     断点进入条件(Ctrl+B)


 2)      跟踪
Step Into  进入函数、模块调试


Step Over 不进入函数调试


Step Out  从函数中返回


Run to Cursor  运行到鼠标所在行


3)      察看数据
a)       Watch
可以自由添加需要查看的变量


b)      Call Stack
运行堆栈,对出错(红叉)时的错误定位非常有效


c)      Memory
 内存查看器。主要用于查看内存块内的数据。图中查看了b变量在内存中的值(第一个字节0A为变量b的值,即10)


d)      Variables
 变量查看器。自动显示当前代码用到的变量的值。


e)       Registers
寄存器查看器。用于查看寄存器内的数据值。用于底层代码调试。


f)       Disassembly
查看汇编代码。


下面为函数aa()的汇编代码


8:    void aa()

9:    {


00401000   push        ebp
00401001   mov         ebp,esp
00401003   sub         esp,48h
00401006   push        ebx
00401007   push        esi
00401008   push        edi
00401009   lea         edi,[ebp-48h]
0040100C   mov         ecx,12h
00401011   mov         eax,0CCCCCCCCh
00401016   rep stos    dword ptr [edi]


10:       for(int i=0;i<100000;i++)
00401018   mov         dword ptr [ebp-4],0
0040101F   jmp         aa+2Ah (0040102a)
00401021   mov         eax,dword ptr [ebp-4]
00401024   add         eax,1
00401027   mov         dword ptr [ebp-4],eax
0040102A   cmp         dword ptr [ebp-4],186A0h
00401031   jge         aa+3Bh (0040103b)


11:       {
12:           int b=i;
00401033   mov         ecx,dword ptr [ebp-4]
00401036   mov         dword ptr [b],ecx
13:       }
00401039   jmp         aa+21h (00401021)
14:   }


4)      高级技巧
a)       伪寄存器
可以直接在Watch窗口中查看,特别是前2个,可以大大减少GetLastError的使用,免于调试过程中反复改动代码。


@hr    最后出错编号。等于GetLastError()
@hr,err  最后出错编号的文字描述
@EIP      指令寄存器
@寄存器名称              对应的寄存器


b)      逆向运行
X86 CPU运行时,当前运行代码所对应的地址保存于IP寄存器,32位CPU保存在EIP,所以,我们可以理论上可以依靠修改EIP的值来让我们的代码跳转到任意代码行(考虑到函数调用时的参数需要压栈,如果要跨函数恢复到完全一样运行环境,比较困难,所以在本函数体内跳转比较合适,超出函数范围就不建议使用本技巧了)


实例:


程序运行情况如下图。


1. 当前运行到第23行代码a=2。


2. 我们可以在编辑区和Watch的@EIP伪寄存器中看到23行代码对应的内存地址是0x401096。


3. 现在我们想让代码跳转到0x0040108F,让程序再运行一次22行的代码。那我们只需要将@EIP值改为0x0040108F。


4.在下图中,我们可以看到代码被反向运行了。此技巧主要用于反复运行同一行代码或反复调用同一个函数。


c)      性能测试(Profilling)


可以统计每个函数在运行期间被调用了多少次,占用了多少CPU百分比。


但是此功能在VC6中不是很稳定,经常无法运行。另外,BoundsCheck的最新版本中,增加了Performance测试,可以进行函数和代码行级的性能统计。所以推荐使用BoundsCheck来进行测试。(7.0以上版本才包含此功能,但是由于版权问题,不能大范围使用,可以使用专用电脑来进行性能统计测试)

d)      远程调试(Debugger remote connection)


需要设置:


1.对方IP


2.附加DLL




要求2台调试机器的操作系统(包括补丁)版本完全一样。否则无法调试。一般用于调试图形程序等调试器(VC)会对代码运行产生影响的程序。


2.          错误定位
1)      基本技巧
开发机:运行碰到错误。


调试运行。碰到红叉后根据Stack信息定位到调试代码具体位置。


2)      高级技巧
测试机、用户机器:运行错误。


有代码行数时:直接调出源代码查看。


只有出错内存地址: 可以使用.map文件定位错误函数即行数。在大型程序中,往往不能提供完备的.map文件,无法定位到代码行数或函数。这个时候可以使用ProcessInfo工具查看程序模块映射关系来推断出错模块。


 


例如:在ATNotes.exe运行过程中出错,出错地址为 7C920100,则根据上图,可以发现出错地址位于ntdll.dll模块中,因为ntdll.dll的基地址(BaseAddr)等于7C920000,模块所占内存大小为94000,说明7C920000~7D9B400为ntdll.dll的代码段。所以可以定位此模块出错。


只有出错框,没有显示是哪个程序出错。有时候会有多个程序同时运行,当出错时,如果出错框没有显示程序名称,很难定位到底哪个程序出错了。


 


使用Spy++查看对话框信息,获得进程PID


 


打开进程管理器,查看进程和PID对应关系

可以发现出错对话框属于TestESP(8B0 = 2224)



3.          工具
1)      BoundsCheck
主要提供详细的调试信息。常用来检查内存、资源泄漏。


2)      Process Explorer
主要用来查看进程详细信息,如进程加载了哪些DLL等。


3)      VC Tools
VC所带的工具,常用的有:


SPY++:查看窗口信息


Dependency:查看DLL接口


OleView:查看COM组件信息


TestContainer:用于调试控件


ErrorLookup: 用于查看出错代码对应的错误说明


4)      DebugView
用于查看TRACE输出的调试信息,目前被软件开发部门大量使用。


 


5)      SoftIce/Ollydbg
这2个工具功能非常强大,在调试中,主要用于已经在运行的程序调试。可以在指定的函数入口设置断点(包括系统API)。当函数被调用时,就可以进入断点开始汇编级的跟踪。


 


举例:在一次程序故障中,发现某台计算机中的程序出现运行不正常情况,但是出现概率极小,同时,在不稳定时,不允许退出程序进行代码调试,最后,经过分析,在开发机上大致定位了出错可能的范围,然后在出错机器上使用Ollydbg,定位到了可能出错的代码区,加入断点,跟踪运行期的汇编代码,最后查明了故障原因。


 


 


OllyDbg


 


本节涉及到的工具可以在我的ftp下载:ftp://10.10.20.60/tools


二、      单元测试
1.      DLL、OCX模块介绍
1)      DLL的类型:
常规DLL:生成时自动生成CwinApp类,帮助管理DLL


扩展DLL:没有生成CwinApp,DLL入口主要依靠DLLMain的DLL_PROCESS_ATTACH和DLL_PROCESS_DETACH消息


 2)      DLL的常用加载方法:
静态加载
       VC工程设置加载(命令行加载)


        代码加载


#pragma comment (lib,"vfprojectd.lib")


 动态加载
调用ci_r_IsServer()函数


HINSTANCE  hDLLHandle = AfxLoadLibrary (“vfprojectd.dll”)


if(hDLLHandle)


{


              typedef BOOL (*CI_R_ISSERVER)(void);


              CI_R_ISSERVER ci_r_IsServer = (CI_R_ISSERVER)GetProcAddress(hTempDll,"ci_r_IsServer");


              if(ci_r_IsServer)


              {


                     m_bSelfDS = ci_r_IsServer();


              }


              FreeLibrary(hDLLHandle);


}


 


3)      OCX常用调试方法:
Ocx主要使用TestContainer或VB调试,不建议使用VC


2.      DLL单元测试举例
模块名称:VFProjectD.dll


头文件:OtherUseDll.h


库文件:VFProjectD.lib


加载方式:静态加载


 VFProject共有10个接口函数。


对一个接口的测试,需要包含:边界测试、中值测试、随机数测试、非法数据测试


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


*


* 函数名称:  CTestVFProjectDlg::OnTest1()


* 描    述: test FileDlg()


* 创    建: 何军[2006-4-20 13:15:32]


*


* 返    回: [void] -


*


* 函数参数 :


*


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


void CTestVFProjectDlg::OnTest1()


{


       // open


       CString pathn,filen;


       FileDlg(TRUE,"pic","pic",pathn,filen);


       Trace("Test1.1 Open Dialog:pathname=%s,filename=%s\n",pathn,filen);


 


       // save


       FileDlg(FALSE,"pic","pic",pathn,filen);


       Trace("Test1.2 Save Dialog:pathname=%s,filename=%s\n",pathn,filen);


 


       // 非法参数


       FileDlg(TRUE,"","",pathn,filen);


       Trace("Test1.3 Save Dialog:pathname=%s,filename=%s\n",pathn,filen);


 


       FileDlg(FALSE,"","",pathn,filen);


       Trace("Test1.4 Save Dialog:pathname=%s,filename=%s\n",pathn,filen);


}


 


 


测试结果


一些补充:


Go命令(F5):向下执行程序直到断点,或结束;
Restart命令(Ctrl+Shift+F5):重新开始调试,直到第一个断点或结束;
Stop Debugging(Shift+F5):结束调试工作,返回原工作环境;
Break:停止调试;


Step Into命令(F11):单步向下运行,该命令可以深入到Call调用的函数里,
执行每一步汇编指令,此时可以打开Registers窗口,观察各寄存器的变化;


Step Over命令(F10): 单步状态结束;


Step Out(Shift+F11):从Call函数中跳出来;


Run to Cursor(Ctrl+F10):执行到光标处;


Step Into Specific Function :单步执行特殊的功能;
Exception命令:将列出异常中断程序执行的情况,可以进行修改;
Threads命令:将列出被调试程序中的线程信息,包括线程的标号、名称、地址、
优先级等。
Show Next Statement命令:将当前正在调试的语句标识出来
Quick Watch:快速观察的功能。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值