c/c++ 程序调试方法

总结一下自己常用的c/c++程序调试方法,首先介绍一下我的开发环境

系统

Linux xx 3.2.0-4-686-pae #1 SMP Debian 3.2.60-1+deb7u1 i686 GNU/Linux

编辑器

VIM - Vi IMproved 7.3 (2010 Aug 15, compiled Feb 10 2013 06:46:29)

编译器

Thread model: posix
gcc version 4.7.2 (Debian 4.7.2-5)
交叉编译工具链忽略不计。

调试器

GNU gdb (GDB) 7.4.1-debian

总的来说, 按照使用率来排序,本人最常用的方法如下:

1. printf 大法(日志)
2. core-dump/map 调试
3. gdb

4. 注释


一、日志

首先来说printf大法,虽然说用printf调试听起来有点low,但是不可否认的是,现实情况中绝大多数的逻辑错误、异常情况通过查看打印输出、日志就能定位bug所在,当然,直接用printf的话稍微有点麻烦。不如自己封装一下:

#ifdefine DEBUG_EN
#define DEBUG(fmt, args...)     \
        do {                    \
                printf("DEBUG:%s-%d-%s "fmt, __FILE__, __LINE__, __FUNCTION__, ##args);\
        }while(0)

#define ERROR(fmt, args...)     \
        do {                    \
                printf("ERROR:%s-%d-%s "fmt, __FILE__, __LINE__, __FUNCTION__, ##args);\
        }while(0)
#else
#define DEBUG(fmt, args) do{}while(0)
#define ERROR(fmt, args) do{}while(0)
#endif
 这样编译的时候, 就可以通过gcc -DDEBUG_EN 打开调试信息输出,当调试完成之后,去掉这个参数即可。这两个宏调用方式与printf完全一样, 输出内容如下图:

当然,对于多线程的情况,可以考虑增加打印tid、加锁、解锁等操作。

二、core-dump/map 调试

当程序运行的过程中异常终止或崩溃,操作系统会将程序当时的内存状态记录下来,保存在一个文件中,这种行为就叫做Core Dump.

MAP 文件是程序的全局符号、源文件和代码行号信息的唯一的文本表示方法,是整个程序工程信息的静态文本,通常由linker生成。

core-dump

注意,在编译可执行文件前,要使用-g选项,否则找不到符号,无法精确定位。一般使用"ulimit -c unlimited"打开这个机制,其实这个命令是限制core-dump文件大小。"ulimit -c 0"将禁用这个机制。可通过修改/proc/sys/kernel/core_pattern修改保存路径、文件名等等参数。默认情况下, 这个文件保存在崩溃的可执行程序目录下。得到这个文件之后,执行"gdb execute-file core-file"即可看到程序崩溃原因以及奔溃前执行的代码。

例如: main.c

#include 

int crash_func(int i, int j)
{
        int ret = 0;
        ret     = i / j;
        return ret;
}

int main(void)
{
        crash_func(5, 0);
        return 0;
}
编译运行这个程序,提示“Floating point exception (core dumped)”,这时执行"gdb main.out core"


map文件

编译可执行程序时增加一个gcc参数"gcc -Wl,-Map,map-file" (-Wl表示将后面的参数传递给链接器ld)。是程序链接的内存映像,表示了某个符号(函数和全局变量等)的地址。值得注意的是,Linux内核在编译时也会产生一个System.map文件,便于在oops时帮助定位bug。

三、gdb

与第二种方法中用到的gdb不同的是,第三种方法通过运行程序,打断点、单步、查看变量的值等方式在运行时定位bug。第二种方式更像是破案,由犯罪现场推导“凶手”。

按照命令使用的顺序, 介绍gdb常用命令

file <文件名> 加载被调试的可执行程序文件
b <行号>/<函数名称> 在第几行或者某个函数第一行代码前设置断点
r 运行
s 单步执行一行代码
n 执行一行代码,执行函数调用(如果有)
c 继续运行程序至下一个断点或者结束
p<变量名称> 查看变量值
q 退出

四、 注释

注意,这种办法意味着你用尽了浑身解数,都无法定位出问题的地方。也就意味着你对你的代码失去了掌控,这几乎是最后一招了。这种方法就是不断注释掉可疑代码片段,或者直接回滚到出问题之前的版本,对比代码不同之处,定位bug。


最后附上一个牛人对精通编程的判断标准:“精通就是,没有你调不出的程序”。

你可能感兴趣的:(c/c++)