调试的艺术——Debug技巧总结

(本文从写好的wiki里粘出来的,格式稍乱不影响阅读)

用Q+编号代表问题,A+编号代表答案。用这种方式组织。如无特别说明,这些技巧都是针对Visual Studio 2003的
汇编级的问题我作为一个逻辑程序只能说略知皮毛,内容仅为抛砖引玉,说法不严谨之处希望能毫不客气的指出,以便改正。但大部分信息都是有经验或参考资料确认的,有问题可以与我探讨。

Q1:Release版本不能调试吗?
A1: Release版本、Debug版本的区别,据我目前所知有3处:编译是否“编译器优化”过;是否有完整调试信息;_DEBUG宏和NDEBUG宏;
先说这三个选项的位置:
“编译器优化”,VS2003里,位于 工程属性(在Solution Explorer面板的某个工程上点右键)-> C/C++ -> Optimization -> 第一项Optimization。一般Debug版设置为Disabled,Release版设置成Full Optimization。
“是否有调试信息”,VS2003里,位于 工程属性 -> C/C++ -> General -> Debug Infomation Format。Release版默认是Disabled,Debug版用最完整的Program Database for Edit & Continue。
_DEBUG宏和NDEBUG宏,VS2003里,位于 工程属性 -> C/C++ -> Preprocesser -> Preprocesser Definitions。Release版一般设置为NDEBUG,Debug版设置为_DEBUG “编译器优化”,是指编译器在编译过程中,并不按照源码逐行翻译成汇编,而是为了效率对代码做出更改,比如:消除不必要的局部变量,去掉完全没有影响的代码。现代编译器已经发展的非常完善,甚至可以做到调整循环次序、把循环内的语句提到循环外、消除函数调用等等,可以在保证结果完全一致的情况下达到最高效。
值得注意的是: 1.越是高级的编译器,优化功能越强大,VS2010和VS2003就有不小的区别,可以写些简单的算法,然后看看汇编结果。2.甚至对于“高手”来说,编译器优化的代码往往比人工优化的代码要更好(因为写编译器的是优化砖家),所以写代码时,应该更关注算法方面、函数调用结构方面的问题,不必过于关注局部变量之类的细节,局部问题编译器会做的很好。
所以有些人用Release版调试的时候,发现执行顺序非常怪异,和源码对不上,其实就是编译器优化造成的。 “是否有调试信息”,决定了pdb文件是不是完全,pdb文件既要记录源代码的位置,以便跟踪,还得记录一些额外的地址信息,以便显示出局部变量的值。有时候调试Release版本,看不到变量的值,原因——要么是这个变量已经被优化没了,要么是调试信息不全造成的。
_DEBUG宏和NDEBUG宏,这个比较简单,DEBUG版本在编译时,会自动定义一个宏#define _DEBUG,所以有些代码希望只在debug版生效时,只需要
#ifdef _DEBUG
//debug版才编译的代码
#else
//release版需要的代码
#endif
这样写一下就可以达到目的了,最常见的是 assert 宏。assert在寻仙底层代码非常常见,assert函数只在debug版被执行,release版被忽略。所以是个开发时测试用的好东西。如果在release版也暂时需要用assert,就修改一下VC设置,把NDEBUG宏改为_DEBUG就行了,不过这样修改也可能会影响其他代码。(另外还有一些现象,我还没有搞清:为什么DEBUG版的局部变量会被初始化成0,而release版不初始化,是通过哪个选项控制的,求解答。)调试Release的意义在于:有些BUG只有在Release方式下能重现,所以搞清楚Release和Debug的区别,适当修改设置,关键时刻显得非常重要。

Q2:想输出调试信息,不知道应该输出到哪里
A2:控制台程序很容易通过printf或者std::cout输出信息,但是windows窗口程序比较麻烦。写windows小工具时,可以用MessageBox输出信息,不过对于寻仙这种大型游戏恐怕太不合适了。
能输出LOG,就用LOG输出
逻辑层的模块,大部分可以使用LOG模块,建议多输出LOG进行调试。查看log文件务必采用UltraEdit、Notepad++、Vim之类能够自动检测到文件变化并重新打开log文件的工具,以便随时查看输出,用记事本可能会给自己带来麻烦。不能输出LOG,用OutputDebugString
底层模块不能输出LOG,想输出LOG的话源码修改太多过于繁琐。这时候也有绝招:windows.h 头文件,提供了一个输出信息的函数:OutputDebugString(包括两个,OutputDebugStringA和OutputDebugStringW,分别支持MultiByte和WideChar)。需要使用时,只要在你需要调试的cpp文件里包含windows.h,然后自己拼装字符串,输出即可。
查看OutputDebugString的输出结果。OutputDebugString的结果,输出到哪里了呢?答案是“系统调试信息”,这玩意如果用VC调试程序,可以在Output窗口里看到。如果不调试,如何看到呢?答案是用“DebugView”小工具。上网搜一下应该能找到,也可以问别人要。
OutputDebugString也失效。由于Windows.h文件过于庞大,有可能造成和源文件冲突编译不过,这种情况。。。只能具体问题,具体解决了。问题总比方法多(龚大侠语录)。不使用调试器的原因是:调试器很可能会在关键时刻不给力,特别是内存已经写乱的情况。过于依赖调试器不是什么好事。

Q3:我在调试core dump文件,程序是release版的,代码被优化过,变量的值全都看不见,执行顺序也不清楚,只能看到汇编代码,怎么办?
A3:这种情况,如果不想(不能)放弃的话,就埋头看汇编代码吧!
如果运气不是太差,源码能和core dump文件对应上,VS2003就可以打开 汇编、C++ 的混合视图,可以对照着看。 1.崩溃基本上肯定是访问了不可访问的地址。所以……专心看访问哪个地址时出错了,这个地址的值是怎么一路算过来的。
2.分析每一句C++代码和哪些汇编代码对应。特别的,if、for、while在汇编里最终肯定表现为跳转(jmp、jne之类),所以看懂了跳转,就明白了执行流程。还有函数调用(call)也可以帮你定位。
3.如果发现C++和汇编不容易对应上,也不要惊慌,很可能是优化导致一些函数被展开了,考虑到优化以后再分析分析即可。
非常有效的方法:找个和你水平差不多或者比你更懂汇编的人,一起研究。可以拓宽思路,也可以互相鼓励。

Q4:来不及下断点怎么办?下断点断不下来怎么办?
A4:我以前一直认为,如果自己有代码,想用断点就总是可以断下来的,实在不行启动程序以后用attach也总是可以调试的。但是事实证明,如果程序员有N种调试程序的方法,那么大自然就有N2种调戏程序员的方法。例如:
1.程序逻辑和系统时间密切相关。比如调试冷却时间CoolDown这种的,如果CoolDown时间只有1秒,基本你断下来断点就2秒了,你可能总是无法进入你想进入的那条逻辑分支。还有比如网络层,调试时间非常有限,5秒就超时,非常尴尬。
2.操作系统抽风了,怎么都断不下来。
3.错误是在程序刚启动时出现的,如果从VS2003里启动,就不会出错;如果不从VS2003启动,就会出错。但是你启动程序再Attach也来不及,因为从启动到出错只有半秒时间。
4.错误重现概率极低,重复十次出现一次,而且直接从VC2003启动程序就更难重现了。
一般来说,前两种现象还是可以解决的,第一种情况可以改改逻辑,给自己多一些时间。第二种情况查一下系统服务,甚至重装一下VS,还是有办法的。第三种情况我在这个月刚刚遇到,发生在关键的变量是个随机值的情形。(而且这个现象说明从VS启动程序、直接启动程序、和attach之后,三种情形程序内存布局有着微妙的区别。)第四种情况曾发生在内存访问越界的情形。是因为访问非法内存时,操作系统有时候可以检测到异常,有时候不会。有几种方法,列举出来,但具体怎么用还得发挥聪明才智 :)
1.强制断点 INT 3。 具体做法:在程序里加一句__asm int 3 ( 如果你在用 gcc 可以加 asm ("int $3"); ),程序执行到这里会崩溃,然后windows会问你是否需要调试,然后你就可以从容的用VS2003进去调试了。很好的办法,自己写工具时不妨一试。可惜的是这种做法在咱们的项目里无效,因为这个异常会被底层catch到(JException::init()),然后打印一堆信息以后退出……没有调试的机会。
2.assert(表达式)、OutputDebugString等。如果你对问题已经有了大概想法,比如“这个地方如果index是3,那么就会越界错误了”,那么赶紧加一条assert(index==3),在DEBUG模式下试试看你猜想的对不对。而且assert不要删除,因为它在release模式下不会被运行。写一些有意义的assert对代码健壮性很有帮助。
3.加一句if。加一句if,并在if的{}里加一些没意义的代码,比如int i=0; ++i;然后把断点下在++i上。很常见的做法,但是如果你遇到非常诡异的情况,debug不容易重现,关闭release的优化选项也不容易重现,你就只能开着优化,但是优化会把你那句无用的代码整个删掉。这时候……不妨用下面while(true);的方法。
4.while(true);,用assert的不方便之处在于,一旦代码进入assert里面,就不好回到之前的地方了;用上一条办法在开着优化的情况下也不好办。而往往捕捉到错误之后你希望能继续单步调试。所以,不妨在捕捉到错误之后,让程序进入死循环,然后程序会卡住等你进去慢慢调试了。开始调试的时候,VS2003可以拖曳那个箭头改变当前执行的代码,从死循环里跳出来。或者你用一个很多次数的空循环,比如100000000次,或者有些程序可以用sleep(10000),思路都是一样的。

Q5:调试的时候,在watch里查看对象的成员变量的值,但是感觉watch的语法与C++不同,有什么技巧吗?
A5:watch我也用的不是很好。用watch需要解决几个问题:
调试STL容器,我只会调试vector,调试vector的方法类似于调试C++数组,比如有个数组a[10],监视里面的10个值:
a[0],10
像这样写就行,vector也差不多。VS2003调试map、list时不是很给力。求解。调试对象的成员变量里面的成员变量里面的成员变量……;由于watch里面对"->"的支持不是很好,写一大串语句watch识别不了。超哥传授了一个绝招:使用quick watch,在quick watch里选择成员变量时,quick watch输入框里的文字也会变……原来输入框里的东西就是你想要的变量的表示方法。把它粘到watch窗口里试试吧!
在watch里面调用函数,可能会造成诡异的崩溃。因为watch里调用函数也可能会遭遇异常。调试时遇到奇怪的崩溃可以看看是不是自己在watch里写了不该写的东西。

Q6:遇到非常费解的客户端或服务器很卡的现象,不知从何入手
A5:如果程序速度突然变得非常明显的慢,而且可以重现,那就可以运行以后用VS attach到进程里,然后Break All,多试几次(碰概率)。
看看是否多次卡在同一个地方。如果多次卡在同一个地方,那么就可以大胆的猜测和试验了。虽然这种方法很傻,但是至今用这种方法调试出来的问题有:1、技能系统错误填写,导致循环10000000……帧;2、客户端在外服运行遭遇大量LOG导致卡死。据我所知已经有这两个成功的经验,实乃神技。

gdb调试的艺术——Debug技巧的更多相关文章

  1. 《软件调试的艺术》学习笔记——GDB使用技巧摘要

    <软件调试的艺术>学习笔记——GDB使用技巧摘要 <软件调试的艺术>,因为名是The Art of Debugging with GDB, DDD, and Eclipse. ...

  2. GDB调试技巧:总结篇

    目录 一 写在开头 1.1 本文内容 二 学习资料 三 常用命令 四 调试技巧 注:原创不易,转载请务必注明原作者和出处,感谢支持! 一 写在开头 1.1 本文内容 总结GDB调试的一些常用命令和调试 ...

  3. gdb调试运行时的程序小技巧

    使用gdb调试运行时的程序小技巧 标签: 未分类 gdb pstack | 发表时间:2012-10-15 04:32 | 作者:士豪 分享到: 出处:http://rdc.taobao.com/bl ...

  4. JS调试必备的5个debug技巧_javascript技巧

    JS调试必备的debug调试javascript技巧 1. debugger; 我以前也说过,你可以在JavaScript代码中加入一句debugger;来手工造成一个断点效果.需要带有条件的断点吗? ...

  5. Xcode常用技巧(1)-使用Xcode进行代码分析及GDB调试

    1. 使用Xcode分析代码,分析分为静态分析及动态分析 静态分析:(Xcode-Product-Analyze)  检测代码是否有潜在的内存泄露  编译器认为不太合适的代码 运行结果: 若程序有 ...

  6. gdb调试技巧 找到php执行进程当前执行的代码

    假设线上有一段php脚本,突然在某天出问题了,不处理但是进程没有退出.这种情况可能是异常休眠或者是有段死循环代码,但是我们怎么定位呢,我们这个时候最想知道的应该是这个脚本在此刻在做什么吧.这个是gdb ...

  7. 100个gdb调试技巧

    找到的一个有参考价值的关于GDB调试的站点:https://gitlore.com/subject/15

  8. ffmpeg 使用 gdb 调试相关技巧

    本文说明了,在ffmpeg二次开发或调用库的过程,如何借助于ffmpeg源码进行调试. 注:ffmpeg版本是4.0. 1. 编写代码 编写将pcm数据转换为mp2的代码 pcm_to_mp2.c # ...

  9. gdb 调试入门,大牛写的高质量指南

    引用自:http://blog.jobbole.com/107759/ gdb 调试 ncurses 全过程: 发现网上的“gdb 示例”只有命令而没有对应的输出,我有点不满意.gdb 是 GNU 调 ...

随机推荐

  1. Java的按值传递和按引用传递解说

    在网上看到的一个帖子解释Java的按值传递和按引用传递,感觉挺全面,就转过来,以供以后学习参考: 1:按值传递是什么 指的是在方法调用时,传递的参数是按值的拷贝传递.示例如下: public clas ...

  2. 使用c++控制sqlite3

    首先,到官网下载相关的压缩包 https://www.sqlite.org/download.html 但是要自己再重新编译一个, 博主自己收集了一下,密码:hixo https://pan.baid ...

  3. 交换机基础设置之vtp管理vlan设置

    vtp的设置有三种模式1:server模式,负责创建,删除vlan(服务器模式) 2:client模式,负责接收并转发来自server的信息(客户机模式) 3:transparent模式,只负责转发, ...

  4. 一个优秀的SSH远程终端工具

    SSH远程终端工具是一款在Windows界面下用来访问远端不同系统下的服务器,从而比较好的达到远程控制终端的目的.向我们操控集群的时候,如果每台机器都安装一个显示器和键盘也是一个不小的花费,而远程终端 ...

  5. java-反射和代理

    1.类的编译和运行简易过程: java的源码文件(也称为编译单元,以.java为后缀的文件) ↓ 文件内最多只能有一个public修饰的类,否则编译器报错:某个类被public修饰,该类名必需与文件名 ...

  6. php结合redis实现高并发下的抢购、秒杀功能【转】

    抢购.秒杀是如今很常见的一个应用场景,主要需要解决的问题有两个:1 高并发对数据库产生的压力2 竞争状态下如何解决库存的正确减少("超卖"问题)对于第一个问题,已经很容易想到用缓存 ...

  7. 《算法》第四版 IDEA 运行环境的搭建

    <算法>第四版 IDEA 运行环境的搭建 新建 模板 小书匠 在搭建之初,我是想不到会出现如此之多的问题.我看了网上的大部分教程,都是基于Eclipse搭建的,还没有使用IDEA搭建的教程 ...

  8. SPOJ1026 概率DP

    Favorite Dice BuggyD loves to carry his favorite die around. Perhaps you wonder why it's his favorit ...

  9. Codeforces Round 97B 点分治

    B. Superset time limit per test 2 seconds memory limit per test 256 megabytes input standard input o ...

  10. Clion 不能杀死进程

    描述 自己使用时发现点了结束按钮后,打开任务管理器,发现刚才运行的程序还在,并没有被杀死. 有时如果一个程序写了死循环,就会出现疯狂占用内存,最后不得不关机重启. 解决方案 这是他的社区有人也有这样的 ...