程序调试技巧研发部2008年培训培训内容•调试方法•断点技巧•线程调试•服务调试•DLL调试•辅助工具调试方法•集成调试,单步跟踪运行,查看各个变量数据,堆栈等•最便宜的调试工具:ODS,Trace,MessageBox•输出日志•利用辅助工具修改变量•对运行结果不一致的,可以修改数据变量的值,来验证猜测的问题和检验结果•一般集成调试工具都支持修改变量的值•例如怀疑一个函数的返回值不正确导致其他的地方出错,你可以强行修改函数的返回值来验证你的想法缩小范围•定位有问题的代码块或者函数•采用替代函数检验是否某些函数、过程有问题•注释代码块,然后逐步一行行取消注释来判断问题出在何处代码断点•断点是最基本的集成调试手段•普通的断点方式,在源代码中某一行下断点即可•代码断点关键是要知道在什么地方下即可•也可以在源代码中下中断断点:int3断点•源代码中的int3中断是效率最高的中断方式,特别适合循环中条件中断或不断调用中的条件中断条件断点•断点可以设定一些条件,满足某些条件才真正中断程序运行,帮助加快调试的步伐•条件可以为运行中,通过断点处多少次才中断(经常用于循环中);也可以为一个表达式,例如某个变量满足一个条件才中断(经常针对特定的数据)•条件断点会导致程序运行效率低下,但在Delphi中可以利用汇编的int3中断加快速度,例如如果一个100万次的循环,运行到第90万次的时候程序出错,那么我们如果用其他的方式来中断的话,等待90万次运行是耗时很久的,而如果用int3来中断,则非常快,和正常运行没有区别!数据/地址断点•数据断点和地址断点在某些情况下非常有用,但很少人用•数据断点非常适合某个变量数据被误修改、对象被错误释放,或者指定地址被错误访问的情况•数据/地址断点就是针对某个变量下断点,变量的值被读取或写入的时候,就会在读取或者修改该变量的对应的源代码行中断•数据断点可以为读取断点或者写入断点•对于AV错误(内存访问错误),非常有效和有用•例子:对解码数据转换的时候,发现转换函数在某种情况下出错,利用地址断点很容易就发现了一个指针初始化不正确的错误组合断点•若干个断点一起的时候起作用,例如有A,B两个断点,A和B可以分别设定中断动作,例如可以设定A中断的时候,B断点暂时失效,这样可以在一些互斥的断点间提高调试效率•比较少用汇编代码•大部分的集成开发环境,都支持查看CPU代码•利用CPU代码窗口,可以查看到原始的CPU的执行指令,可以发现一些蛛丝马迹•CPU代码和源代码不是一一对应,一条高级代码可能对应一条或多条汇编指令•CPU代码查看时需要注意编译器的优化指令•CPU代码查看和调试,属于非常底层调试,调试时注意查看各个寄存器和堆栈数据•无源代码也可以调试!•破解经常需要汇编调试procedureTForm1.Button1Click(Sender:TObject);VarX:TButton;BeginX.Free;//请注意看会发生什么?为什么呢?End;线程调试•线程调试比较麻烦,要考虑清楚线程之间的关系,理清思路去调试•查看当前运行代码所在的线程句柄,配合调用堆栈,检查代码真正运行的线程和代码所在对象的关系•检查线程对象创建和调用是否是同一个线程?是否注意了线程同步?线程死锁?是否考虑到了在多CPU下的情况?•检查线程对象中创建了的其他的窗口或对象是否其运行线程和创建线程是否一致?窗口的创建线程和消息处理必须是同一个线程!•例:调用报表COM接口的时候,发现自己编写的测试程序可以正常调用,但是在后台服务当中运行不正常,调试发现测试程序是在主线程,而以服务运行的时候,创建COM是一个线程,而执行COM中的方法是在另外的线程,导致出错,结果把报表COM线程模式更改为支持多线程解决问题服务的调试•服务程序以后台运行,服务运行方式和普通应用程序不一样,一般后台运行的服务没有桌面交互,无须用户干预•服务程序可以Attach,注意运行前必须Build或者Link一次,否则Attach后的断点可能不正常•如果要调试程序初始化的时候的错误中断,那么可以在服务程序运行的最开始调用Sleep(6000),以便你有足够的时间Attach上去;•为方便调试,建议把服务做成两栖程序,既可以作为桌面程序运行,又可以作为服务运行(强烈推荐),Delphi要实现两栖程序非常简单,但带来的好处却非常大!DLL调试•DLL无法直接运行,必须利用宿主程序来调用•宿主程序必须调用DLL,或者创建COM对象的时候,集成调试器才能中断或允许中断•宿主程序也可以Attach上去•Delphi中如果DLL无法调试,可以把DLL所在的工程和宿主程序所在的工程放到一个工程组(ProjectGroup)中•请注意使用绝对路径,某些版本Delphi和Windows不兼容,可能无法使用相对目录内存泄露•VC在调试程序退出的时候会提示内存泄露,注意查看输出的提示信息•Delphi程序可以利用MemoryLog.pas单元或者其他的工具来分析内存泄露,例如MemoryProof,BoundsCheck等•良好的习惯是对付内存泄露的根本方法•内存泄露要抓住主要的部分:长期的缓慢的内存泄露,而程序启动一次,运行结束前不再继续泄露的,可以放过过滤异常•在集成调试时,一般碰到异常或错误,会中断运行,自动进入调试状态,这样可能干扰正常的调试过程;•在Delphi中,对于不想被某些异常打扰或者已经处理过的异常中断打扰,那么可以加入忽略列表,这样集成调试环境抓到这个异常后就会忽略继续处理(DebugOptions,Languageexceptions)•小心!所有同类型异常被忽略!调试完成后需要修改回来!MAP文件•大多数编译器和连接器允许嵌入调试信息到可执行文件中,这些信息可以指出错误发生的单元文件和所在的行•Delphi可以对工程生成MAP文件。projectoptions,打开linker页面,mapfile选择detailed;VC同样也可以生成MAP文件,请在LINK选项中修改即可•MAP文件可以和当时编译的源代码对应,利用程序出错报告的地址,结合MAP文件,利用错误的地址可以准确指出错误发生的代码行•MAP的好处是可以在运行期(脱离调试环境)报的错也可以找到出错的代码行•建议每次Release时封存一份源代码和一份MAP文件利用MAP文件查找错误行代码偏移地址:0x0044D95B–0x00400000–0x1000=0x0004C95BPE加载到内存中,其地址空间是从0x00400000开始的PE代码段偏移量,PE的代码段一般会有一定偏移124DBGHelp•Dbghelp,ImageHelp是微软提供的用于调试程序和运行时获取调试信息的工具,可以提取堆栈信息,源代码行定位等•很多工具软件使用了dbghelp•Dbghelp可以配合微软的SymbolServer使用•具体请查看MSDN或微软的资料错误代码定位•C/C++:_FILE_,_LINE_•Delphi:JCLDebug,Jedi工具包辅助工具•DbgView,非常方便的调试输出信息查看工具,配合ODS使用,不影响程序运行效率,又可以支持运行时的调试信息输出,不用写LOG文件等•ProcessExplorer,任务管理器,进程管理器,可以查看程序的线程信息及其调用堆栈,内存信息,CPU切换信息(例如如果一个线程的ContextSwitch太多说明可能加锁比较多,效率是很低的),查看程序调用堆栈,Socket及其堆栈,文件句柄,加载的DLL,互斥量,信号量等等•例如如果用ProcessExplorer发现线程的CPUCSSwitchDelta太多,说明线程经常被切换出去,那么你得考虑一下程序中线程是否合理了•强烈推荐研发人员使用ProcessExplorer,并用好ProcessExplorer,好处不多说,就靠自己摸!辅助工具•BoundsCheck•MemoryProof•其他工具用的不多,了解一下即可•Dump工具–Dump要求对堆栈,CPU,寄存器,汇编更熟悉•汇编调试工具:SoftICE,w32dasm,IDA•WinDBG:微软的Debug工具,类似debug命令总结1.碰到Bug,首先想到合适的手段来调试2.对于Bug,可以猜测一下,并验证猜测3.逐步注释缩小范围,直至找到问题点4.不要放过蛛丝马迹,很可能就是问题所在5.调试程序,不外乎Watch,Modify,Inspect,CallStack6.靠平时积累经验,碰到的问题多了,解决问题的能力自然增强7.碰到问题,可以咨询身边同事,不要钻牛角尖,耽误进度,可以在空闲的时候去研究鼎利通信DingliCommunicationsInc.Thanks!