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

2023-11-08

调试的艺术——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导致卡死。据我所知已经有这两个成功的经验,实乃神技。



本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

调试的艺术——Debug技巧总结 的相关文章

随机推荐

  • 傅里叶变换,高通,低通滤波

    傅里叶变换 傅里叶变换的作用 高频 变化剧烈的灰度分量 例如边界 低频 变化缓慢的灰度变量 例如一片大海 滤波 低通滤波器只保留低频 会使头像模糊 高通滤波器只保留高频 会使图像细节增强 opencv主要就是cv2 dft 和cv2 idf
  • 机械臂机器人——使用Matlab Robotic ToolBox建立四轴机械臂模型并实现运动控制仿真

    文章目录 四轴机械臂实物 Robotic ToolBox机械臂建模 1 建立机械臂的D H表 建立机械臂坐标系 根据坐标系建立D H表 2 代码建模 机械臂运动学仿真 1 正运动学仿真 2 逆运动学仿真 为了能够实现机械臂的运动轨迹规划 同
  • Github Page 个人主页——自定义域名

    1 前言 看本文前请确认已部署好基础的静态网页 参考上篇文章 Github Page 个人主页 项目部署 部署好的个人网页若使用 username github io 访问 会让别人很难记住 这时就可以使用自定义域名来绑定仓库了 本文将介绍
  • Centos 安装 Kafka 后台启动 教程

    安装 官网 http kafka apache org downloads 1 下载 Python wget http mirrors tuna tsinghua edu cn apache kafka 0 11 0 0 kafka 2 1
  • html自动图片尺寸,关于html:CSS背景图像适合宽度,高度应按比例自动缩放

    我有 body background url images background svg 期望的效果是该背景图像的宽度等于页面的宽度 高度变化以保持比例 例如 如果原始图像恰好是100 200 任何单位 且正文宽度为600px 则背景图像最
  • 2021-11-02 B1026 简单的时间转换显示和将小数四舍五入

    B1026 简单的时间转换显示和将小数四舍五入 原题 1026 程序运行时间 15 分 要获得一个 C 语言程序的运行时间 常用的方法是调用头文件 time h 其中提供了 clock 函数 可以捕捉从程序开始运行到 clock 被调用时所
  • java没有报错,但编译失败。

    这段代码总是编译失败 但是idea没有报错 这东西是抽象类 解决办法 jdk版本问题 虽然都是1 8 换成了333就没问题了 个人经验 换成版本高的试试
  • C++: 'identifier': identifier not found [Compiler Error C3861]

    问题 模板类继承模板类时 子类无法直接访问父类成员 会报如下错误 严重性 代码 说明 项目 文件 行 禁止显示状态 错误 C3861 insertAsLast 找不到标识符 DSA D Code DSA DSA Queue h 8 环境 V
  • 解决Vscode中unresolved import “XXX” 问题

    正常导入同级目录的 py文件 报黄色警告 原因 在Vscode中 import后面跟着的 必须是全路径 将import后面的文件路径 修改成全路径 便不会有unresolved import XXX 警告
  • adele心理学

    人生中最大的谎言就是不活在此时此刻 纠结过去 关注未来把微弱而模糊的光打向人生整体 自认为看到了些什么 你之前就一直忽略 此时此刻 只关注根本不存在的过去和未来 对自己的人生和无可替代的刹那撒了一个大大的谎言 我们应该更加认真地过好 此时此
  • 华为OD机试 C++ 异常的打卡记录

    描述 我们的目标是查找出那些异常的员工打卡记录 异常的定义是 打卡所用的设备号与员工注册的设备号不匹配 同一个员工两次打卡的时间间隔小于60分钟 但距离超过5km 你会得到一个字符串数组 里面包含每条打卡记录 格式为 工号 时间 分钟 距离
  • gcc与g++的安装和使用

    首先大致介绍一下gcc和g 在此之前 你需要先安装gcc与g 然后才能进行指令操作 命令如下 sudo yum install y gcc c 在命令行输入以上指令后 gcc g 便成功安装了 gcc是一个专门用来编译链接c语言的编译器 g
  • @EnableAspectJAutoProxyAop注解实现

    转载 https www cnblogs com foreveravalon p 8653832 html 在这个注解比较流行的年代里 当我们想要使用spring 的某些功能时只需要加上一行代码就可以了 比如 EnableAspectJAu
  • Git学习:git 生成 patch的命令

    个人理解 patch就是打补丁 通过git工具把代码的差分 生成patch文件 然后通过git工具可以直接把patch文件的内容 merge到代码里面 生成patch的命令 git diff gt patch 本地变更 git diff 的
  • python-面向对象

    一 什么是面向对象 1 什么是面向对象 概念 根据模板 创建一个类 实例化一个类 面向对象 2 面向对象的好处 可重复使用 实例化类 容易修改 只用修改类的模板 3 面向对象的特点 封装 继承 多态 4 类名首字母必须大写 5 面向对象的案
  • 详细解说 STL 排序(Sort)

    详细解说 STL 排序 Sort 0 前言 STL 为什么你必须掌握 1 STL提供的Sort 算法 1 1 所有sort算法介绍 1 2 sort 中的比较函数 1 3 sort 的稳定性 1 4 全排序 1 5 局部排序 1 6 nth
  • 避障雷达IO输出记录

    避障雷达的使用首先需要在软件上划分区域 区域一共可以划分为3个 代码实现部分 第四个雷达处理算法 后边雷达 int alarm int a int b int array 8 for int i 0 i lt 8 i b a 1 a uns
  • MISRA C_2012规则翻译、解读、示例

    目录 一 MISRA C 2012介绍 二 MISRA C 2012每条规则翻译 解读 示例 Rule 1系列 标准C环境准则 Rule 1 1 程序不应包含任何违反标准C语法和约束的内容 也不应超过实现的转换限制 Rule 1 2 不应使
  • 表结构设计的基本思路

    首先在开启一个项目时 至关重要的第一步就是设计表结构 很多小伙伴在初入职场的时候找不清如何下手只是感觉很乱 那么今天我提供几个基本的思路 切入点 找到一个主表 比如你要设计一个某某产品的的管理系统 那么ok这张产品表就是我们的主表 它作为主
  • 调试的艺术——Debug技巧总结

    调试的艺术 Debug技巧总结 本文从写好的wiki里粘出来的 格式稍乱不影响阅读 用Q 编号代表问题 A 编号代表答案 用这种方式组织 如无特别说明 这些技巧都是针对Visual Studio 2003的 汇编级的问题我作为一个逻辑程序只