您的位置:首页 > 编程语言 > C语言/C++

VC++调试技巧

2012-09-18 23:26 295 查看
本文内容来源于我在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:快速观察的功能。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息