软件调试实战

出版社:人民邮电出版社
出版日期:2010-2
ISBN:9787115218858
作者:Thorsten Grotker,Ulrich Holtmann,Holger Keding,Markus Wloka
页数:190页

章节摘录

插图:有了源代码调试器(以下简称调试器)以后,就可以逐行地走查源代码,查看程序的条件语句和循环语句都经过了哪些路径,显示哪些函数被调用了,以及显示目前正处于函数调用栈的什么位置。我们可以检查变量值,并在个别的代码行中设置断点,然后让程序运行,直至到达此行。这是在复杂程序中进行导航的便利方法。调试器将显示程序都执行了哪些操作,这是修复任何bug的先决条件。如果代码是自己编写的,调试器可以帮助实现预期行为,即代码的功能。如果是别人编写的,调试将呈现出代码执行的动态视图,以补充静态代码检查。本章将介绍基本的源代码调试器功能,并讲述如何用它们来查找C和C++程序中的bug。同时介绍独立于特定的计算机平台或工具。示例中将使用两个非常常见的调试器:GDB和VisualStudio。同时列出GDB和VisualStudio访问每个被讨论的特性的命令。为了节省篇幅,这里只展示节选的GDB输出并简要介绍Visual Studio如何输出结果,不会给出屏幕截图。GNU调试器GDB代表从具有命令行接口的命令行解释器(command shell)运行的调试器。GDB与GCC编译器一起使用,已经被植入很多操作系统中,如Windows、Solaris、15NIX、Linux和用于嵌入式系统的操作系统。有关GDB的下载信息和文档,参见附录B.2.3。

前言

在所有软件开发工作中,调试或许是最令人苦恼的。调试工作极易受到指责,因为技术失败即意味着做人失败,而且矛头直指调试人员,昭示出他们所犯下的错误。由于必须反复思考每个假设,反复斟酌从需求到实现的每个步骤,因此调试将耗费大量时间。最糟糕的是,调试还无法预测,我们永远无法知道修复一个bug需要多长时间,甚至根本不知道是否能够修复它。问一下开发人员他们生活中最沮丧的时刻,大多数回答将与调试有关。也许现在正是深夜11点,你仍在忙着调试,正当对程序进行走查时,你的家人打电话给你,问你到底什么时候才能回家,而你只希望尽快放下电话,因为好不容易得到的观察结果和推断正要从脑中溜走。此时,你可能最后有两种选择:一是重新调试,二是过后再试图重修旧好。据我个人估计,调试是导致程序员离婚的第一大原因。然而,调试也蕴含着乐趣,就像解出难题、猜出谜语或破获谋杀案一样令人激动,但前提条件是必须采用系统性的方式并配备正确的工具。这正是本书的用武之地。本书四位作者直接与那些固持己见的开发者对话,直截了当地提出解决调试问题的建议,并给出了真正快速的解决方案。无论是解决内存问题,调试并行程序,还是处理工具链引入的问题,本书都能够提供“急救措施”,书中的建议都是经过反复尝试和验证的。如果我最初开始调试程序时就能有这样一本书,该多好啊!我想我会屏息注视,看看这些调试工具将带给我什么惊喜,而且采纳书中的建议,必然会节省大量手工调试的时间,并可将这些时间投入到其他工作中。譬如说,可以使代码更可靠,这样最后可能根本不必做任何调试了。当然,这是专业编程的长期目标,即从一开始就编写正确的代码,通过某种确认或验证方法来杜绝所有错误(或至少检测到错误)。目前,断言和单元测试已经在提高程序可信度方面提供了很多帮助。未来可能会有一些用于行业级系统的成熟验证方法。我们现在尚未实现这样的方法,这可能需要很多年,而且当达到这个目标时,所实现的方法肯定不会适用于现在的编程语言。在处理当今的程序,特别是那些C和C++程序时,我们仍将在调试上花费一定时间——这正是本书的宝贵价值所在。

媒体关注与评论

“如果我最初开始调试程序时就能有这样一本书,该多好啊!我想我会屏息注视,看看这些调试工具将带给我什么惊喜,而且采纳书中的建议必然会节省大量手工调试的时间,可以将这些时间投入到其他工作中。譬如说,我可以使代码更可靠,这样最后可能根本不必做任何调试了。”  ——Andreas Zeller,GNU DDD创始人“逐页阅读完本书之后,我必须承认这是我读过的最好的一本软件调试图书,在很多方面都是其他书不能匹敌的……强烈推荐软件调试人员阅读。”  ——www.dumpanalysis.org(崩溃转储分析和调试门户网站)

内容概要

本书4位作者均拥有德国或美国著名高等学府的博士学位,目前都任职于EDA(电子设计自动化)软件领导厂商Synopsys(新思)公司,分别担任研发主管、资深软件工程师等职位,负责开发编译器和调试工具,具有解决各种调试问题的丰富经验。

书籍目录

第1章 谁编写软件,谁制造bug(为什么需要本书)
1
第2章 系统性调试方法
3
2.1 为什么要遵循结构化的过程
3
2.2 充分利用机会
3
2.3 13条黄金规则
5
2.3.1 理解需求
5
2.3.2 制造失败
6
2.3.3 简化测试用例
6
2.3.4 读取恰当的错误消息
6
2.3.5 检查显而易见的问题
6
2.3.6 从解释中分离出事实
7
2.3.7 分而治之
7
2.3.8 工具要与bug匹配
8
2.3.9 一次只做一项更改
9
2.3.10 保持审计跟踪
9
2.3.11 获得全新观点
9
2.3.12 bug不会自己修复
9
2.3.13 用回归测试来检查bug修复
10
2.4 构建一个好的工具包
10
2.4.1 工具箱
11
.2.4.2 每天运行测试,防止出现bug
11
2.5 认清敌人——遇到bug家族
13
2.5.1 常见bug
13
2.5.2 偶发性bug
13
2.5.3 heisenbug
13
2.5.4 隐藏在bug背后的bug
14
2.5.5 秘密bug——调试与机密性
14
2.5.6 更多读物
15
第3章 查找根源——源代码调试器
17
3.1 可视化程序行为
17
3.2 准备简单的可预测的示例
18
3.3 使调试器与程序一起运行
18
3.4 学习在程序崩溃时执行栈跟踪
21
3.5 学习使用断点
21
3.6 学习在程序中导航
22
3.7 学习检查数据:变量和表达式
22
3.8 一个简单示例的调试会话
23
第4章 修复内存问题
27
4.1 c/c++中的内存管理——功能强大但很危险
27
4.1.1 内存泄漏
27
4.1.2 内存管理的错误使用
28
4.1.3 缓冲区溢出
28
4.1.4 未初始化的内存bug
28
4.2 有效的内存调试器
28
4.3 示例1:检测内存访问错误
29
4.3.1 检测无效的写访问
30
4.3.2 检测对未初始化的内存的读取操作
30
4.3.3 检测内存泄漏
31
4.4 示例2:对内存分配/释放的不完整调用
31
4.5 结合使用内存调试器和源代码测试器
33
4.6 减少干扰,排查错误
33
4.7 何时使用内存调试器
34
4.8 约束
34
4.8.1 测试用例应该有很好的代码覆盖率
34
4.8.2 提供更多计算机资源
35
4.8.3 可能不支持多线程
35
4.8.4 对非标准内存处理程序的支持
35
第5章 剖析内存的使用
37
5.1 基本策略——主要步骤
37
5.2 示例:分配数组
38
5.3 第1步:查找泄漏
38
5.4 第2步:设置期望值
38
5.5 第3步:测量内存使用
39
5.5.1 使用多个输入
39
5.5.2 在固定时间间隔停止程序
39
5.5.3 用简单工具测量内存使用
40
5.5.4 使用top
40
5.5.5 使用windows task manager
41
5.5.6 为testmalloc选择相关输入值
42
5.5.7 确定机器上的内存是如何被释放的
42
5.5.8 使用内存剖析工具
43
5.6 第4步:查明大部分内存被哪些数据结构占用了
44
5.7 综合练习——genindex示例
45
5.7.1 核实没有大的内存泄漏
46
5.7.2 估计内存使用
46
5.7.3 测量内存使用
46
5.7.4 查找使用内存的数据结构
47
第6章 解决性能问题
51
6.1 分步查找性能bug
51
6.1.1 执行前期分析
51
6.1.2 使用简单的时间测量方法
52
6.1.3 创建测试用例
52
6.1.4 使测试用例具有可再现性
53
6.1.5 检查程序的正确性
53
6.1.6 创建可扩展的测试用例
53
6.1.7 排除对测试用例的干扰
54
6.1.8 用time命令测量时可能会发生错误和偏差
54
6.1.9 选择一个能够揭示运行时间瓶颈的测试用例
55
6.1.10 算法与实现之间的差异
56
6.2 使用剖析工具
58
6.2.1 不要编写自己的剖析工具
58
6.2.2 剖析工具的工作原理
58
6.2.3 了解gprof
59
6.2.4 了解quantify
63
6.2.5 了解callgrind
64
6.2.6 了解vtune
66
6.3 分析i/o性能
68
第7章 调试并行程序
71
7.1 用c/c++编写并行程序
71
7.2 调试竞争条件
72
7.2.1 使用基本调试器功能来查找竞争条件
73
7.2.2 使用日志文件来查找竞争条件
74
7.3 调试死锁
76
7.3.1 如何确定正在运行的是哪个线程
77
7.3.2 分析程序的线程
78
7.4 了解线程分析工具
78
7.5 异步事件和中断处理程序
80
第8章 查找环境和编译器问题
83
8.1 环境变更——问题的根源
83
8.1.1 环境变量
83
8.1.2 本地安装依赖
84
8.1.3 当前工作目录依赖
84
8.1.4 进程id依赖
84
8.2 如何查看程序正在做什么
84
8.2.1 用top来查看进程
84
8.2.2 用ps来查找应用程序的多个进程
85
8.2.3 使用/proc/[pid]来访问进程
85
8.2.4 使用strace跟踪对操作系统的调用
85
8.3 编译器和调试器也有bug
87
8.3.1 编译器bug
87
8.3.2 调试器和编译器兼容性问题
88
第9章 处理链接问题
89
9.1 链接器的工作原理
89
9.2 构建并链接对象
89
9.3 解析未定义的符号
91
9.3.1 丢失链接器参数
91
9.3.2 搜索丢失的符号
91
9.3.3 链接顺序问题
92
9.3.4 c++符号和名称改编
93
9.3.5 符号的反改编
94
9.3.6 链接c和c++代码
94
9.4 具有多个定义的符号
95
9.5 信号冲突
96
9.6 识别编译器和链接器版本不匹配
96
9.6.1 系统库不匹配
97
9.6.2 对象文件不匹配
97
9.6.3 运行时崩溃
98
9.6.4 确定编译器版本
98
9.7 解决动态链接问题
100
9.7.1 链接或载入dll
100
9.7.2 无法找到dll文件
101
9.7.3 分析载入器问题
102
9.7.4 在dll中设置断点
103
9.7.5 提供dll问题的错误消息
104
第10章 高级调试
107
10.1 在c++函数、方法和操作符中设置断点
107
10.2 在模板化的函数和c++类中设置断点
109
10.3 进入c++方法
110
10.3.1 用step-into命令进入到隐式函数中
112
10.3.2 用step-out命令跳过隐式函数
112
10.3.3 利用临时断点跳过隐式函数
113
10.3.4 从隐式函数调用返回
113
10.4 条件断点和断点命令
114
10.5 调试静态构造/析构函数
116
10.5.1 由静态初始化程序的顺序依赖性引起的bug
117
10.5.2 识别静态初始化程序的栈跟踪
118
10.5.3 在静态初始化之前连接调试器
118
10.6 使用观察点
119
10.7 捕捉信号
120
10.8 捕获异常
122
10.9 读取栈跟踪
124
10.9.1 带调试信息编译的源代码的栈跟踪
124
10.9.2 不带调试信息编译的源代码的栈跟踪
124
10.9.3 不带任何调试信息的帧
125
10.9.4 实际工作中的栈跟踪
125
10.9.5 改编后的函数名称
126
10.9.6 被破坏的栈跟踪
126
10.9.7 核心转储
127
10.10 操纵正在运行的程序
128
10.10.1 修改变量
130
10.10.2 调用函数
131
10.10.3 修改函数的返回值
132
10.10.4 中止函数调用
132
10.10.5 跳过或重复执行个别语句
133
10.10.6 输出和修改内存内容
133
10.11 在没有调试信息时进行调试
135
10.11.1 从栈读取函数参数
137
10.11.2 读取局部/全局变量和用户定义的数据类型
138
10.11.3 在源代码中查找语句的大概位置
139
10.11.4 走查汇编代码
140
第11章 编写可调试的代码
143
11.1 注释的重要性
143
11.1.1 函数签名的注释
144
11.1.2 对折中办法的注释
144
11.1.3 为不确定的代码加注释
144
11.2 采用一致的编码风格
144
11.2.1 仔细选择名称
145
11.2.2 不要使用“聪明过头”的结构
145
11.2.3 不要压缩代码
145
11.2.4 为复杂表达式使用临时变量
145
11.3 避免使用预处理器宏
146
11.3.1 使用常量或枚举来替代宏
146
11.3.2 使用函数来替代预处理器宏
148
11.3.3 调试预处理器输出
149
11.3.4 使用功能更强的预处理器
150
11.4 提供更多调试函数
151
11.4.1 显示用户定义的数据类型
151
11.4.2 自检查代码
152
11.4.3 为操作符创建一个函数,以便帮助调试
153
11.5 为事后调试做准备
153
第12章 静态检查的作用
155
12.1 使用编译器作为调试工具
155
12.1.1 不要认为警告是无害的
156
12.1.2 使用多个编译器来检查代码
158
12.2 使用lint
158
12.3 使用静态分析工具
158
12.3.1 了解静态检查器
158
12.3.2 将静态检查器检测到的错误减至(接近)零
160
12.3.3 完成代码清理后重新运行所有测试用例
160
12.4 静态分析的高级应用
161
第13章 结束语
163
附录a 调试命令
165
附录b 工具资源
167
附录c 源代码
179
参考文献
189

编辑推荐

《软件调试实战》是4位深谙软件调试之道的资深开发人员的实战经验总结,不仅讲述了简单的源代码调试,还涵盖了各个领域的最常见的实际问题,包括程序链接、内存存取、并行处理和性能分析。最后几章讨论了静态检查器,介绍了一些较好地运用了调试技巧的代码编写方法。书中讲述的调试技术不仅可以用于C/C++程序,还可以用于其他语言编写的程序。软件调试是软件开发中最令人苦恼的环节。反复思考每个假设,反复斟酌从需求到实现的每个步骤,将耗费大量时间。最糟糕的是,调试根本无法预测,我们永远无法知道修复一个bug需要多长时间,甚至根本不知道是否还能修复它。然而,如果采用系统的方式并配备合适的工具,调试也会充满乐趣,成功的调试就像解出难题、猜出谜语或破获奇案一样令人激动。这本书就能帮你实现这一惊天逆转。Amazon五星图书Synopsys公司专家的调试经验总结软件调试权威指南

作者简介

《软件调试实战》主要讲述C/C++程序的调试和分析,书中的调试技术也可以应用于其他语言编写的程序。《软件调试实战》在讲述简单的源代码分析和测试的基础上,讲述了现实的程序中经常遇到的一些问题(如程序链接、内存访问、并行处理和性能分析)并给出了解决方案。
《软件调试实战》适合软件开发人员、调试人员阅读和使用。

图书封面


 软件调试实战下载 精选章节试读 更多精彩书评



发布书评

 
 


精彩书评 (总计3条)

  •     1. 与测试用例相关a. 如果不能达到“测试先行”,至少应该在写完代码后有相对完整的测试用例。对于正确性的保证和以后重构代码都是有好处的。b. 每次添加新功能或修复了一个bug时,都应该增加测试用例!A历经千辛万苦终于fix 了一个bug,很久很久以后,B觉得这段代码需要改改,于是改了改,后来的结果还是改了,而且顺利提交到了库里(因为A当时遇到的bug 并没有出现!)c. 回归测试修改代码后进行回归测试。每次提交一个版本后自动进行回归测试,保证库里的代码的正确性。 d. 简化测试用例 好处:可以排除不起作用的因素;减少测试用例的运行时间;最重要的是,使用测试用例更容易调试(谁愿意处理那些填充了数百或数千项的数据容器呢?) 方法如: 如果测试例子比较好改,可以将其改小;将输入集改小 e. 完成代码,清理后重新运行所有测试用例。 2. 关于程序的编译 a. 重视编译期间的warning,最好把warning数减为0. 不要忽略编译器警告,即使它们可能是无害的。 eg: int add(int a,int b){ return a +b ; } 结果头文件里声明成了 extern int add(long a,int b) 会调试死人啊,调程序的时候一看程序定义是对的啊,怎么传的参数一下就变了; b. 如果出现莫名其妙的错误 如果是用Makefile组织工程时,考虑make clean,有可能修改数据结构或头文件后改变了一些东西,但是由于一些未知原因该文件并未重新编译。如果函数是C函数,有可能调用者和被 调用者的参数的成员和类型不同。如果一个类方法,则访问任何类成员 都将发生错误,因为这两个类的内存而已几乎是完全不同的。这可能导致Segmentation falut,或是很久之后才能检测到的内存破坏。 3. 关于链接 a. 链接器的基本工作原理 编译器或汇编程序将源代码转换为机器代码,并输出对象谁的。对象文件中包含符号(函数或变量),这些符号有的在本模块定义的,有的在其他模块定义的,链接器就在链接对象文件时把这些未定义的符号与定义它的模块对应起来。 b. 链接顺序 有库和归档文件时 链接算法是不一样的。 链接器参数顺序很重要,对于编译单元(如对象文件和库)和搜索路径来说都是如此。 c. C++中使用C代码时,用extern c{} 把C代码包装一下。 关于 c++符号和名称改编:C++允许重载函数,为了生成C++代码元素的唯一符号,编译器使一种称为名称改编(name mangling)的技术,它将对象的准确规格说明(如会员名空间和函数参数的个数及类型)编码到符号中。(可以用c++filt解析出来~ eg: c++filt _Z9factoriali的结果为factorial(int)) d. 环境变量 LD_LIBRARY_PATH会影响动态加载的库,用LDD可以看到程序依赖哪个动态库 4. 自动化测试 让一切自动化起来。如果重复的做一件事,就很有必要考虑自动化了。 5. 关于那些怪异的错误 在一些显而易见有内存问题的情况下,如:间歇故障和无法解释的随机行为,这时考虑使用内存调试器了! 如valgrind,很好用,也很简单。 valgrind –tool=massif your_program 进行内存剖析(检测内存分配情况,优化内存使用) valgrind –tool=memcheck your_program 进行内存检查(检测无效的写访问,检测对未初始化的内存的读取操作,检测内存泄露等) valgrind –tool=helgrind your_program 查找竞争条件,可以用来辅助调试多线程程序 valgrid –-db-attac=yes的功能很好用,可以将内存高度器和源代码测试器(如gdb)结合起来,这样就可以即时查看当时的变量的值,很好用! 6. 静态检查器 作为常规软件构建过程中的一部分运行,用于查找一些可通过静态源代码分析发现的特定bug。 7. 关于运行时剖析工具 不要编写自己的运行时剖析时工具:自己霞友云朋一的剖析 工具通常使用系统调用time()或ctime()来测量时间。这些系统调用的问题是开销很高,而且准确度低。另处在剖析期间要收集大量数据,可能会影响程序本身的行为。 8. 环境变量 如程序的行为可能 依赖于当前工作目录。在linux上,目录被注册到环境变量CWD上。这个bug碰到过,还导致了死锁。 9. 读取恰当的错误消息 某个地方出错时,满屏都是错误消息时,应该重点关注哪些消息? Answer: 首先出现的那些消息!因为后面的消息有可能是前面导致的。这和编译出错时的情景一致:编译错误有很多,我们肯定会直觉地去寻找第一个出错的 地方,谁知道是不是少了个括号导致后面一连串的错误。 10. bug不会自动消失 如果某个版本有bug,update后,bug消失了,“真好!”,一定要弄清楚bug出现的原因是什么。以前遇到过一个bug,增加一条printf语句后,bug消失了!最后发现问题是数组越界了,而修改源代码会导致代码段,数据段的布局等改变,所以会导致偶尔对。(这种情况可以求助于内存调试工具或者静态检查的工具) 11. 学习使用gcc, gdb,strace 等工具。(熟悉以后可以再挖掘挖掘,可能有惊喜) (本书对于gdb的使用讲解很多)12. cvs/svn commit之前一定要diff一下,看做了哪些修改,以避免不小心删掉一些东西后,然后”被提交”了。 最后,最强大的工具不在计算机中,而是调试者的判断力和分析技巧。
  •     工欲善其事,必先利其器。这本书特别针对C++程序,讲了调试相关的一些内容,如内存管理,Name Mangling等。个人觉得这本书的亮点在于,作为一本篇幅较短的小册子,针对C++的程序调试,介绍了许多工具,以及工具的使用。工具确实有学习成本,但一般来说,还是值得学习的。
  •     都是实战出来的干货,示例代码较多,讲解清晰。valgrind的tool suite 有很多好东西,比如callgrind, helgrind。还有神器Kcachegrind。gprof 介绍的不错,总算明白函数调用图那几个数字代表的意义。但没讲如何profiler多线程。gdb 中的几个很难被用但很有用的tip,例如handle,signal,macro都介绍了,以前视乎只在gdb文档里看到过。另外对符号冲突、弱引用,链接库的执行顺序、m4也有一定的介绍。翻译糟糕,-1分

精彩短评 (总计12条)

  •     对于一个在Linux下写c/c++程序的人来说,这本书算很不错了,挺实用的,不明白为什么豆瓣平均分才7.4,大家都在挑什么?
  •     前十章简略翻过,debug只看不练是不行的
  •     还行吧 就是贵了点
  •     基本上是很仔细的读完的. 对偶来说只有最前面和最后(10章以后)的部分还比较有用. 中间是大块的鸡肋 :(
  •     工具和思路都介绍得很好。
  •     的确一般,不怎么推荐,感觉像工具说,只是大概介绍一些工具,但又没怎么深入
  •     还行
  •     书中讲述的调试技术不仅可以用于C/C++程序,还可以用于其他语言编写的程序。
  •     废话较少,条理清晰
  •     GDB
  •     对初学者比较有意义,介绍了很多常用的工具,包括内存剖析,性能检测,gdb调试器等。对编写易于调试的代码给出了建议。书挺薄,200页不到,有些内容不够深入。
  •     还不错,提纲挈领,但有点贵
 

外国儿童文学,篆刻,百科,生物科学,科普,初中通用,育儿亲子,美容护肤PDF图书下载,。 零度图书网 

零度图书网 @ 2024