Linux程序调试-常用调试技巧

程序调试阶段:

测试:找出程序的错误或缺陷

固化:让程序错误可重现

定位:确定相关代码行

纠正:修改代码 修正错误

验证:确定修改解决了问题

1 gcc -Wall -pedantic -ansi   //gcc 编译  产生编译的警告信息

 

1取样法:在程序中添加printf等输出程序执行过程中的信息,程序错误修复后需要删除

1 #ifdef DEBUG
2     printf("….\n");
3 #endif

定义调试级别,输出不同类型的内容

 

 

1 #define BASIC_DEBUG 1
2 #define EXTRA_DEBUG 2
3 #define SUPER_DEBUG 4
4 #if (DEBUG & EXTRA_DEBUG)
5     printf...
6 #endif

 

C语言预处理器定义的一些宏可以帮助我们调试(符号前后各有两个下划线)

   

无需编译的调试技巧 定义全局变量debug 用户在调用程序执行时使用 -d 调试选项,决定是否打开调试模式

将调试信息存储于文件,可以方便自身或用户自行调试代码,查找问题

1 if (debug) {
2     sprintf(msg, ...)
3     write_debug(msg)
4 }

程序的受控执行

商业版本常见的调试器有adb、sdb、idebug、dbx等 能用那些调试器取决于UNIX系统

GNU使用调试器gdb,一些gdb的前端程序提供非常友好的界面,xxfdb,KDbg,ddd等

-g 选项是对程序进行调试性编译的常用选项,需要在编译每个文件时都加上这个选项,对链接器也要加上-g选项。(编译器会把这个标志自动传递给链接器)

调试信息会使可执行程序的长度成倍增加(最多10倍),虽然可执行程序的大小增加,但使用内存的数量和原来是一样的。

调试完后才能后,可以不经过编译将可执行文件中的调试信息删除

1 strip <file>

 

使用gdb进行调试

2019123

10:30

开始调试

 
1 //进入调试器
2 gdb ./xxx    //xxx为可执行文件  此时进入gdb软件  help可查看gdb提供的命令选项
3 //运行
4 run [option]  //  [option]将作为参数传递给程序  在程序执行错误,gdb将在出错位置退出  若编译时使用了 -g选项
5 //则在程序停止后输出程序终止的位置

栈跟踪

在到达错误位置时,输入backtrance 简写 bt 或 where 输出调用出错函数的函数和出错函数的位置

检查变量

print 可以给出变量和其他表达式的内容,并将表达式的值赋给伪变量 $<number>

最后一次操作的结果总是以$开头,而倒数第二次的结果为$$

列出程序源代码

list

设置断点

1 break 21 //在21行处打断点 打断点之后可以用print 输出当前关注的变量值
2 cont //程序继续执行到下一个断点处
3 display //程序每次停在断点位置时,自动打印关心的变量值
4 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)

 

1 assert //宏对表达式进行求值,如果结果为0,就向标准错误输出一些诊断信息,然后调用abort函数结束程序运行
2 
3 #define NDEBUG //关闭断言宏

在产品中保留assert并不可取,因为不希望客户在看到一条assert之后程序强制退出,好的方法是编写自己的错误中断陷阱例程

   

   

内存调试

2019123

13:50

   

内存调试

1.内存泄露,malloc申请内存后赋给指针,指针的值被改变,此时没有任何指针指向申请的内存,程序运行时间长了之后将会越来越慢,导致内存耗尽

2.在一个已分配的内存块尾部的后面(或在它的前面)写数据,就很可能会损坏malloc库用于记录内存分配的数据结构。之后,一个malloc或free调用都会导致段错误(Segmentation fault)。此时检查错误发生的地点是很困难的。

ElectricFence可以使用Linux的虚拟内存机制来保护malloc 和 free使用的内存。

使用虚拟内存,在出现非法的内存访问时,引发段冲突信号并停止程序的运行

valgrind 可以检测出前边所说的很多问题,特别是可以检测出数组访问错误和内存泄露

在程序运行结束时进行内存泄露的检查 使用 valgrind --leak-check=yes 选项

 

posted @ 2020-03-12 13:32  MrWang_tju  阅读(1141)  评论(0编辑  收藏  举报