程序调试阶段:
测试: 找出程序的错误或缺陷
固化: 让程序错误可重现
定位: 确定相关代码行
纠正: 修改代码 修正错误
验证: 确定修改解决了问题
1 gcc -Wall -pedantic -ansi //gcc 编译 产生编译的警告信息
1 取样法: 在程序中添加 printf 等输出程序执行过程中的信息, 程序错误修复后需要删除
- #ifdef DEBUG
- printf("....\n");
- #endif
定义调试级别, 输出不同类型的内容
- #define BASIC_DEBUG 1
- #define EXTRA_DEBUG 2
- #define SUPER_DEBUG 4
- #if (DEBUG & EXTRA_DEBUG)
- printf...
- #endif
C 语言预处理器定义的一些宏可以帮助我们调试(符号前后各有两个下划线)
无需编译的调试技巧 定义全局变量 debug 用户在调用程序执行时使用 -d 调试选项, 决定是否打开调试模式
将调试信息存储于文件, 可以方便自身或用户自行调试代码, 查找问题
- if (debug) {
- sprintf(msg, ...)
- write_debug(msg)
- }
程序的受控执行
商业版本常见的调试器有 adb,sdb,idebug,dbx 等 能用那些调试器取决于 UNIX 系统
GNU 使用调试器 gdb, 一些 gdb 的前端程序提供非常友好的界面, xxfdb,KDbg,ddd 等
-g 选项是对程序进行调试性编译的常用选项, 需要在编译每个文件时都加上这个选项, 对链接器也要加上 - g 选项.(编译器会把这个标志自动传递给链接器)
调试信息会使可执行程序的长度成倍增加(最多 10 倍), 虽然可执行程序的大小增加, 但使用内存的数量和原来是一样的.
调试完后才能后, 可以不经过编译将可执行文件中的调试信息删除
1 strip <file>
使用 gdb 进行调试
2019 年 12 月 3 日
10:30
开始调试
- // 进入调试器
- gdb ./xxx //xxx 为可执行文件 此时进入 gdb 软件 help 可查看 gdb 提供的命令选项
- // 运行
- run [option] // [option]将作为参数传递给程序 在程序执行错误, gdb 将在出错位置退出 若编译时使用了 -g 选项
- // 则在程序停止后输出程序终止的位置
栈跟踪
在到达错误位置时, 输入 backtrance 简写 bt 或 where 输出调用出错函数的函数和出错函数的位置
检查变量
print 可以给出变量和其他表达式的内容, 并将表达式的值赋给伪变量 $<number>
最后一次操作的结果总是以 $ 开头, 而倒数第二次的结果为 $$
列出程序源代码
list
设置断点
- break 21 // 在 21 行处打断点 打断点之后可以用 print 输出当前关注的变量值
- cont // 程序继续执行到下一个断点处
- display // 程序每次停在断点位置时, 自动打印关心的变量值
- command // 指定程序在到达断点时执行的命令, 以 end 结束 此时设置 > cont>end
程序每次运行到断点处, 自动打印关心的内容, 并自动调用程序继续执行指令 设定完成后程序将一直执行到最后 并在过程中输出值
使用调试器打补丁 gcc 可以在程序进行调试时 直接更改变量的值来进行调试
程序下一次调试, 使用 info display 和 info break 来查看当前显示与断点的内容
1 set variable n = n+1 // 设置在调试时, 将变量 n 的值 +1
深入学习 gdb 强大的功能
1. 在支持硬件断点的 CPU 上, gdb 支持可以在符合某个条件时暂停程序运行
2.gdb 可监控表达式的, 即当某个表达式取一个特定的值时, gdb 可以暂停程序的运行(这样会对性能造成影响)
3. 断点, 计数, 条件可以结合在一起设置
4.gdb 还可以将自己附在正在运行的程序上, 对异常的程序可以在调试过程中直接进行修改, 而不必停下编译并重启
可以在编译时用 gcc -O -g 来同时获得程序优化和调试信息 但优化可能会改变程序执行顺序
5. 调试崩溃的程序时, Linx 通常会产生一个 核心转存储(core dump). 这个文件是程序的内存映像文件
一些工具
lint splint LClint 等 清理程序中的垃圾, 严格编译程序, 产生警告
函数调用工具
ctags 为程序中所有的函数创建索引, 每个函数对应一个列表, 列表列出函数调用位置
cxref 程序分析 C 语言源代码并生成一个交叉引用的表格
cflow 程序打印出一个函数调用树(function call tree), 显示函数之间的调用关系, 可以理清程序调用架构, 理解操作流程, 了解函数的改动将会产生什么样的影响
prof/gprof 产生执行存档
想要查找程序的性能问题时, 一种常用的技巧是执行存档(execution profiling), 需要特殊的编译选项, 执行存档可以显示执行它所花费的时间具体用在哪些步骤上.
给编译器加上 -p 标志(针对 prof 程序) -pg 标志(针对 gprof)
之后执行程序时, 将生成 mon.out(gmon.out)文件
断言 assert
测试某个假设是否成立, 如果不成立就停止程序的运行
- #include <assert.h>
- void assert(int expression)
- assert // 宏对表达式进行求值, 如果结果为 0, 就向标准错误输出一些诊断信息, 然后调用 abort 函数结束程序运行
- #define NDEBUG // 关闭断言宏
在产品中保留 assert 并不可取, 因为不希望客户在看到一条 assert 之后程序强制退出, 好的方法是编写自己的错误中断陷阱例程
内存调试
2019 年 12 月 3 日
13:50
内存调试
1. 内存泄露, malloc 申请内存后赋给指针, 指针的值被改变, 此时没有任何指针指向申请的内存, 程序运行时间长了之后将会越来越慢, 导致内存耗尽
2. 在一个已分配的内存块尾部的后面 (或在它的前面) 写数据, 就很可能会损坏 malloc 库用于记录内存分配的数据结构. 之后, 一个 malloc 或 free 调用都会导致段错误(Segmentation fault). 此时检查错误发生的地点是很困难的.
ElectricFence 可以使用 Linux 的虚拟内存机制来保护 malloc 和 free 使用的内存.
使用虚拟内存, 在出现非法的内存访问时, 引发段冲突信号并停止程序的运行
valgrind 可以检测出前边所说的很多问题, 特别是可以检测出数组访问错误和内存泄露
在程序运行结束时进行内存泄露的检查 使用 valgrind --leak-check=yes 选项
来源: http://www.bubuko.com/infodetail-3458181.html