VC++调试程序.docx
- 文档编号:12506021
- 上传时间:2023-04-19
- 格式:DOCX
- 页数:24
- 大小:39.04KB
VC++调试程序.docx
《VC++调试程序.docx》由会员分享,可在线阅读,更多相关《VC++调试程序.docx(24页珍藏版)》请在冰豆网上搜索。
VC++调试程序
VC调试
现在的项目中遇到个大问题,就是有很多BUG,可我找不到原因.当然原因之一是那几十也可能几百万的代码里,居然没有一点说明文档.除了代码,我没任何可以参考的东西.原因之二,就是最近太累,读代码时,大脑根本不听使唤,太累.呵呵,从中我也明白一个道理.大脑是有情绪的,并不是你想做的东西,它就会听你的话去做.也许我还不知道,大脑里有理性的一部分,即知道我们该做什么,但大脑也有感性的一部分,即让它感到累或感到不能动(我认为想不出问题,大脑就是不动了)的时候,它就会拒绝去做.但我的大脑里感性大大的大于理性.所以经常无法做自己想做的事情.言归正传.
总结:
为什么程序调试的进展很慢?
1,睡的太晚,影响了记忆力和思考能力.2,还不会真正的调试程序.
到底要怎么样去调试程序呢?
在调试过程中经常会遇到这样的问题,变量的值在哪里设置的?
一般变量都是在使用前被设置的,但是有时候程序需要读取硬件信息或外存的数据时,可能就会先去读好.但似乎还是很难找从所有代码中去找到答案.还有个问题就是我们找到了,数据是在哪里设置的,但是我们却跟踪不到那个出错的数据,在几万次的循环中,要确定那个导致BUG的数据在哪次出现是不可能的.当然其实我这个问题并没那么复杂,只到第二次就发生了,但是问题还是没解决,因为我是多线程,4个同样的线程再执行着同样一个函数,而且发生BUG时,我不知道是哪个线程出错了,这种多线程的调试问题该怎么样解决呢?
DEBUG是可以设置条件的,当某个条件发生时,中断就会发生.
如何设置各种断点,如何打LOG(就是把一些有用的信息加到输出文件中),如何更随心所欲的知道想知道的信息,如何快速的搭建一个测试环境,这都成了现在要解决的东西.
1.利用工具(ParasoftC++Test)检查你的代码,评估一下自己形成良好的习惯没有。
(这个一般也不会去用,但知道一下有这个软件也好.)
主动调试指在写代码的时候,通过加入适量的调试代码,帮助我们在软件错误发生的时候迅速弹出消息框,告知开发人员错误发生地点,并中止程序。
各种开发语言和开发工具都提供这些调试语句,标准C++提供了assert函数,MFC提供了ASSERT调试宏帮助我们进行主动调试,在实际工作中,建议统一使用MFC的ASSERT调试宏。
2.2.1参数检查
对于编写的函数,除了明确的指定契约外,在函数开始处应该对传入的参数进行检查,确保非法参数传入时立即报告错误信息。
例如:
BOOLGetPathItem(inti,LPTSTRszItem,intiLen)
{ASSERT(i>0);ASSERT(NULL!
=szItem);ASSERT((iLen>0)&&(iLen ASSERT(FALSE==IsBadWriteStringPtr(szItem,iLen));} 对指针的检查尤其要注意,通常程序员会这样进行检查: //Anexampleofcheckingonlyapartoftheerrorcondition BOOLEnumerateListItems(PFNELCALLBACKpfnCallback) {ASSERT(NULL! =pfnCallback); } 这样的检查只能够排除指针为空的情况,但是如果指针指向的是非法地址,或者指针指向的 对象并不是我们需要的类型,上面的例子就没有办法检查出来,而是统统认为是正确的。 完 整的检查应该如下: //Anexampleofcompletelycheckingtheerrorcondition BOOLEnumerateListItems(PFNELCALLBACKpfnCallback) {ASSERT(FALSE==IsBadCodePtr(pfnCallback));} 2.2.2内部检查 恰当地在代码中使用ASSERT,对bug检测和提高调试效率有极大的帮助,下面举个简单的例子加以说明。 switch(nType){ caseGK_ENTITY_POINT: //dosomethingbreak; caseGK_ENTITY_PLINE: //dosomething break; default: ASSERT(0); } 在上面的例子中,switch语句仅仅处理了GK_ENTITY_POINT和GK_ENTITY_PLINE两种情况,应该是系统中当时只需要处理这两种情况,但是如果后期系统需要处理更多的情况,而此时上面这部分代码又没有及时更新,或者是因为开发人员一时疏忽遗漏了。 一个可能导致系统错误或者崩溃的bug就出现了,而使用ASSERT可以及时地提醒开发人员他的疏忽,尽可能快的消灭这个bug。 还有一些情况,在开发人员编写代码时,如果能够确信在某一点出现情况A就是错误的,那么就可以在该处加上ASSERT,排除情况A。 综上所述,恰当、灵活的使用ASSERT进行主动调试,能够极大提高程序的稳定性和安全性,减少调试时间,提高工作效率。 3.2调试过程 确定一个适用于解决所有错误的调试过程有一定的难度,但JohnRobbins提出的调试过程应该说是最实用的: 1.复制错误2.描述错误3.始终假定错误是自己的问题4.分解并解决错误5.进行有创见的思考6.使用调试辅助工具 7.开始调试工作8.校验错误已被更正9.学习和交流 按下Alt+F9快捷键弹出Breakpoints对话框,浏览一下对话框发现该对话框分为Location、Data和Messages三页,分别对应三种断点: 2.表达式和变量断点: 调试器会让程序一直运行,直到满足所设的条件或者指定数据更改为止。 在IntelCPU上,这两种断点都尽可能通过CPU的特定调试寄存器使用一个硬件断点,如果能够使用调试寄存器,那么程序将能够全速运行,否则调试器将单步执行每个汇编指令,并每步都检查条件,程序的运行速度将极其缓慢甚至无法运行。 各种高级断点的设置在MSDN中有详细的介绍,请在VisualC++子集下搜索主题UsingBreakpoints: AdditionalInformation并阅读相关内容。 3.4调用堆栈 有时候我们并不清楚应该在哪里设置断点,只知道程序正在运行就突然崩溃了,这时候如何定位到出错地点呢? 这时的选择就是查看调用堆栈,调用堆栈可以帮助我们确定某一特定时刻,程序中各个函数之间的相互调用关系。 方法是当程序执行到某断点处或者程序崩溃,控制权转到调试器后,按下Alt+7快捷键,弹出CallStack窗,你可以看到当前函数调用情况,当前函数在最上面,下面的函数依次调用其上面的函数。 在CallStack窗口的弹出菜单上选择Parametervalues和ParameterTypes可以显示各个函数的参数类型和传入值。 3.5使用跟踪工具 有些时候,我们希望了解程序中不同函数之间的协作关系,或者由于文档的缺失,希望能够确认函数在不同情况下被调用时的传入参数值。 这时使用断点功能就过分麻烦,而调用堆栈只能查看当前函数的被调用情况,一种较好的方法就是使用TRACE宏以及相对应的工具。 程序(Debug版)运行中,一旦运行到TRACE宏,就会向当前Windows系统的调试器输出TRACE宏内指定的字符串并显示出来,当在VisualC++环境中调试运行(按F5键)程序时,可以在Output窗口的Debug页看到TRACE宏的输出内容。 实际上,TRACE宏是封装了WindowsAPI函数OutputDebugString的功能,有些辅助工具可以在不惊动VisualC++调试器的前提下,拦截程序中TRACE宏的输出内容,比如《深入浅出MFC》的附录中提到的MicrosoftSystemJournal(MSJ)1996年1月的C/C++专栏介绍的TraceWin工具(在较老版本的MSDN中可以找到源 代码和文档)以及功能强大的免费工具DebugView。 使用TRACE宏,我们可以轻松了解程序中各个函数之间的相互协作关系和被调用的先后顺序和时间(批: 真的可以吗? ),进一步说,你能够完全掌握程序的执行流程。 (批: 真的就太好了.)最后请注意,TRACE宏会对程序效率有所影响,所以,当前不用的TRACE宏最好删除或者注释 掉。 将该模块去除,然后重新编译连接程序,运行程序,看程序运行是否正常。 但无论采用什么方法探究阅读程序,都不要指望能够不费任何气力,花费一两个钟头就能够将上万行的程序探究个明白。 (批: 有同感.) 5参考资料 《程序设计实践》,机械工业出版社《高质量C++编程指南》,林锐《应用程序调试技术》,JohnRobbins,清华大学出版社 《面向对象软件构造》,BertrandMeyer,清华大学出版社 以前有一个D,它是第一个Windows调试器.它属于标准MS-DOS包中的一部分. 80年代末期,Softice出现在舞台上,给受保护程序及它们的开发者带来不少的麻烦.从那时候起,Softice就作为黑客调试工具传了下来.但最近Olly在年轻一代破解者中越来越常用.现在由于分析软件的可能性,与黑客对抗就成了无用的挣扎. 调试器如何工作 所有的调试器都无非属于下面两类: .使用处理器的调试能力 .独立的模仿处理器,监视被测试程序的运行 调试器会检查标志寄存器的陷阱位是否为1.如果是,就在每条指令后自动生成一个INT1调试异常,并且将控制权交给调试器.由此可知,代码可以通过分析标志寄存器来检测跟踪(调试).有四个调试寄存器: 1.DR02.DR13.DR24.DR3. 它们存储了四个检测点的线性地址.当然还有一个寄存器保存了每个点的条件,它就是DR7.当任何一个条件为真时,处理器就会抛出INT1异常,控制权也就交给了调试器,有四个条件: 1.一条指令被执行2.某个内存地址的内容被改变了3.某个内存地址被读取或改变,但没有被执行4.一个输入输出端口被引用 现在要讨论的是软件断点.如果将一个字节的代码--0xCC插入到一条指令前,再试图执行它就会引发INT0x3异常.为了发现是否至少有一个点被设置了断点,程序仅需计算其校验和就可以了.为了做到这一点,可以使用MOV,MOVS,LODS,POP,CMP,CMPS或其他任何指令; windows平台的调试器主要分为两大类: 1用户模式(user-mode)调试器: 它们都基于win32DebuggingAPI,有使用方便的界面,主要用于调试用户模式下的应用程序。 这类调试器包括VisualC++调试器、WinDBG、BoundChecker、BorlandC++Builder调试器、NTSD等。 2内核模式(kernel-mode)调试器: 内核调试器位于CPU和操作系统之间,一旦启动,操作系统也会中止运行,主要用于调试驱动程序或用户模式调试器不易调试的程序。 这类调试器包括WDEB386、WinDBG和softice等。 其中WinDBG和softice也可以调试用户模式代码。 国外一位调试高手曾说,他70%调试时间是在用VC++,其余时间是使用WinDBG和softice。 (批: 很显然,VC6的调试器应该过时了,VS2005的调试器应该更先进.但是,使用方法上还是有点可以参考的.) 由于是在循环体内,如果在E行设置断点,可能需要按F5(GO)许多次。 这样手要不停的按,很痛苦。 使用VC6断点修饰条件就可以轻易解决此问题。 然后选择D行所在的断点,然后点击condition按钮,在弹出对话框的最下面一个编辑框中输入一个很大数目,具体视应用而定,这里1000就够了。 3按F5重新运行程序,程序中断。 Ctrl+B打开断点框,发现此断点后跟随一串说明: ...487timesremaining。 意思是还剩下487次没有执行,那就是说执行到513(1000-487)次时候出错的。 因此,我们按步骤2所讲,更改此断点的skip次数,将1000改为513。 (批: 此功能是新功能).在“Entertheexpressiontobeevaluated: ”下面,可以输入一些条件,当这些条件满足时,断点才启动。 譬如,刚才的程序,我们需要i为100时程序停下来,我们就可以输入在编辑框中输入“i==100”。 另外,如果在此编辑框中如果只输入变量名称,则变量发生改变时,断点才会启动。 这对检测一个变量何时被修改很方便,特别对一些大程序。 二数据断点(DataBreakpoint): 我们可以首先在A行设置普通断点,F5运行程序,程序停在A行。 然后我们再设置一个数据断点。 F5继续运行,程序停在B行,说明B处代码修改了szName1。 B处明明没有修改szName1呀? 但调试器指明是这一行,一般不会错,所以还是静下心来看看程序,哦,你发现了: szName2只有4个字节,而strcpy了7个字节,所以覆写了szName1。 数据断点不只是对变量改变有效,还可以设置变量是否等于某个值。 (批: 这个功能和上面好象有点重复了.但可以看出,数据断点相对位置断点一个很大的区别是不用明确指明在哪一行代码设置断点。 ) 三其他 1在callstack窗口中设置断点,选择某个函数,按F9设置一个断点。 这样可以从深层次的函数调用中迅速返回到需要的函数。 2SetNextStateMent命令(debug过程中,右键菜单中的命令) 此命令的作用是将程序的指令指针(EIP)指向不同的代码行。 譬如,你正在调试上面那段代码,运行在A行,但你不愿意运行B行和C行代码,这时,你就可以在D行,右键,然后“SetNextStateMent”。 调试器就不会执行B、C行。 只要在同一函数内,此指令就可以随意跳前或跳后执行。 灵活使用此功能可以大量节省调试时间。 watch窗口支持丰富的数据格式化功能。 如输入0x65,u,则在右栏显示101。 实时显示windowsAPI调用的错误: 在左栏输入@err,hr。 在watch窗口中调用函数。 提醒一下,调用完函数后马上在watch窗口中清除它,否则,单步调试时每一步调试器都会调用此函数。 4messages断点不怎么实用。 基本上可以用前面讲述的断点代替。 调试最重要的还是你要思考,要猜测你的程序可能出错的地方,然后运用你的调试器来证实你的猜测。 一.高级断点语法 高级断点语法由两部分组成: 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 *)"可唯一确定要中断的函数.(批: 我个人觉得函数中断似乎意义不大.F9就可以了) 可能用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。 (批: 在64位系统中好象只剩一种了,但知道点也好.) 2.条件表达式. 只有表达式为真时触发.Breakpoint框Condition按钮,选第一个编辑框,输入表达式即可.规则: .只可使用C类型比较运算符..表达式中不能调用任何函数..表达式中不能包含任何宏值.(批: 上面没
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- VC 调试 程序