在写程序的时候,经常会遇到一些问题,比如某些变量计算结果不是我们预期的那样,这时我们需要对程序进行调试。本文主要介绍调试C/C++在Linux操作系统下主要的调试工具。
在Linux下写程序,C/C++主要的编译器有GCC/G++,ICC等,像我等穷码农,最喜欢GCC了,很大原因是他免费!所以,我们以GCC/G++为例介绍主要的调试工具。
分以下几个内容介绍:
1、调试之前的工作
2、选择调试工具
3、调试步骤
调试之前的工作
编译器在编译阶段需要产生可供调试的代码,才能被调试器调试。可以如下做:
gcc -g -ggdb ./yourcode.c -o yourapp.exe
这样编译代码可以产生供gdb调试的符号信息。
选择调试工具
gdb当时我们要使用的调试工具,不过他是命令行中使用的,多少有点不方便。
CGDB和DDD都是基于GDB开发的,他们只是增加了更加容易交互的界面。其中CGDB也是在命令行中使用的,使用方式与GDB一样,只是增加了代码显示的界面。
DDD也是GNU开发的,他也是基于GDB的,只是使用了可视化界面,比CGDB更加容易操作。
在有些情况下,我的Linux上可能没有装X-Sever,这样就没有图形显示功能,此时我们只能用命令行方式使用调试工具,所以gdb和cgdb应用更广泛些。一般我会使用CGDB,他的操作跟gdb完全类似,很容易上手。
调试
使用下面的命令启动程序。
cgdb ./youapp.exe
注意,这里我们使用的是cgdb,如果你没有安装cgdb,首先应该安装它!ubuntu用户的安装方式如下:
sudo apt-get install cgdb
启动软件后,常用的几个命令因该熟练掌握:
1、设置断点命令,程序在执行时遇到断点会停下来,执行停止后可以进行变量值的检查。
break [line_number]
例如:break youcode.cpp:32,这个命令是说在youcode.cpp第32行代码处设置断点,程序运行至此时会停下来。
2、显示变量的值,程序在断点处停止时可以打印变量的值。
print [var]
例如:print a,这句命令表示打印变量a的值。print也可以缩写成p。
3、单步执行,程序在断点处停止后,使用单步调试命令让程序一步步往下执行。使用next命令可以完成这个任务,可以缩写为n。
4、进入内部,在调试过程中,如果想进入某个函数的内部,则可使用step命令。
5、继续执行到下一个断点处。使用continue命令可以完成这个任务。
以上命令是gdb调试时常用的命令,也是应该必须掌握的命令。如果你想知道更多如何使用这些命令,使用help command查看相关的帮助。
Linux主流调试器是gdb,但它是纯命令行界面的,调试起来不方便,我需要更强大的力量。在试用了各种工具之后,我相信我找到了,是的没错,就是它——宇宙最强调试器——DDD。
DDD介绍
DDD全称Data Display Debugger,当我第一次见到它时,它的界面着实让我吃了一惊,如此的简陋,如此的怪异,我甚至想立刻删了它,但是当我见识到它强大的功能时,我被深深的震撼了,如此的飘逸,如此的不羁,我的脑海中突然想到了一个词来形容它——犀利!
没错,就是这么犀利,它是gdb的最优图形化前端,它继承了gdb的所有功能,它还加入了数据结构可视化能力,什么一维二维栈数组,二叉三叉N叉树,DDD统统都能用图形显示出来。
小伙子,你还在为调试犯愁么,你还在为红黑树写了半天就是不对而沮丧么,如果你真的遇到了这样的问题,那么我想DDD一定适合你。
它含蓄深沉,它隽永内敛,它在百度上搜不到多少资料,它就是这么低调,这么孤寂,它等待着你去发现,去使用,去震撼这个世界,去拿起它的锋芒——傲视群雄!
DDD安装
嗯,说了这么多我想你一定累了,下面让我们来看看DDD的安装吧。 DDD可以从官网下载到。
下载解压我就不说了。在编译前先安装完依赖包,我的系统是CentOS 6.4,使用yum软件包管理器。
yum install openmotif.i686 ncurses-devel.i686 openmotif-devel.i686 gdb.i686
它当时开发时使用的gcc 3.x,现在大部分系统上都是gcc 4.4,不要告诉我你还在用零一年的机子。正因为这个缘故,必须修改一个地方才能编译成功,这也是官方推荐的修改方式。
编辑ddd/strclass.C文件,在头文件声明里加入#include <cstdio>
之后就很好办了,
- ./configure
- make
- sudo make install
安装完,运行命令ddd
就可以打开了。
更改DDD字体
刚打开DDD界面你也许会这么想,这字体这么小,看来我需要把眼睛升级为钛合金的。
下面教大家设置DDD字体。DDD似乎无法识别/usr/share/fonts/内的各种系统字体,只能手动调节了。
yum install xorg-x11-apps-7.7-6.el6 xorg-x11-fonts-misc
这两个包必须安装,缺少前面的会导致DDD无法改变字体和大小,因为这个软件包里面有个工具叫xfontsel,这是DDD调节字体所必须的。缺少后面的就会导致DDD字体大小可调范围很小,因为只有两种大小让你选。
打开DDD字体选择页面,Edit => Preferences => Fonts
,点一下Browse就会弹出xfontsel菜单,选择字体时fmly选fixed字体,ptSz开到230,只要更改这两项就可以了。你可以把Default Font、Variable Font之类的全改成这样的字体。然后重启DDD,世界瞬间变化了,字体够不够大?再将DDD全屏,怎么样,是不是从没有感觉到调试程序也是如此的痛快。
使用
DDD内嵌gdb,你可以在窗口下方使用gdb的所有功能。在窗口上方是数据结构可视化窗口。看下面俩图。
在变量上点右键都是功能菜单,Display一下就能把变量显示到数据显示区域了。要是对二维数组Display的话,整个数组就呈现一个矩阵显示出来,还能将数组转置显示,对二叉树Display的话会把当前节点显示出来,再在节点上Display就能把下一个节点显示出来,二叉树结点可以这样一个个的显示出来,不用再在纸上画了。犀利!太犀利了!
上几张图你们感受一下。(DDD至强之图放在最后一节)
你以为这就完了?
DDD实力证明它是宇宙最强调试器。看图!
结语
DDD——让天下没有难调的程序
犀利!
-----------------------
linux下的程序调试方法汇总
搞电子都知道,电路不是焊接出来的,是调试出来的。程序员也一定认同,程序不是写出来的,是调试出来的。那么调试工具就显得尤为重要,linux作为笔者重要的开发平台,在linux中讨论调试工具主要是为那些入门者提供一些帮助。调试工具能让我们能够监测、控制和纠正正在运行的程序。我们在运行一些程序的时候,可能被卡住或出现错误,或者运行过程或结果,没能如我们预期,此时,最迫切需要明白究竟发生了什么。为了修复程序,剖析和了解程序运行的细节, 调试工具就成为了我们的必备工具,工于善其事,必先利其器。在Linux下的用户空间调试工具主要有系统工具和专门调试工具:'print' 打印语句,这是新手最常用的,也是最不提倡使用的;查询 (/proc, /sys 等)系统的虚拟文件查看,这个方法有局限性;跟踪 (strace/ltrace)工具使用这个比较普遍,值得提倡;Valgrind (memwatch)内存排除工具,在内存排除方面比较独到,是内存排错的法宝;GDB大名鼎鼎的程序调试工具,这个是个全能的工具,没有完不成的,只有你不知道的。
1.'print' 语句
这是一个基本的调试问题的方法。 我们在程序中怀疑的地方插入print语句来了解程序的运行流程控制流和变量值的改变。 这是一个最简单的技术, 它的缺点。 需要进行程序编辑,添加'print'语句,必须重新编译,重新运行来获得输出。若需要调试的程序比较大,这将是一个耗时费力的方法。
2. 查询
在某些情况下,我们需要弄清楚在一个运行在内核中的进程的状态和内存映射。为了获得这些信息,我们不需要在内核中插入任何代码。 相反,可以用 /proc 文件系统。在/proc的伪文件系统,保留系统启动运行就收集的运行时信息 (cpu信息, 内存容量等)。
ls -l /proc'的输出结果,通过对 系统中运行的每一个进程在/proc文件系统中有一个以进程id命名的项。每个进程的细节信息可以在进程id对应的目录下的文件中获得。也可以'ls /proc/pid'的输出
解释/proc文件系统内的所有条目超出了本文的范围。一些有用的列举如下:
- /proc/cmdline -> 内核命令行
- /proc/cpuinfo -> 关于处理器的品牌,型号信息等
- /proc/filesystems -> 文件系统的内核支持的信息
- /proc/<pid>/cmdline -> 命令行参数传递到当前进程
- /proc/<pid>/mem -> 当前进程持有的内存
- /proc/<pid>/status -> 当前进程的状态
3. 跟踪
strace的和ltrace是两个在Linux中用来追踪程序的执行细节的跟踪工具。
strace:
strace拦截和记录系统调用及其接收的信号。对于用户,它显示了系统调用、传递给它们的参数和返回值。strace的可以附着到已在运行的进程或一个新的进程。它作为一个针对开发者和系统管理员的诊断、调试工具是很有用的。它也可以用来当做一个通过跟踪不同的程序调用来了解系统的工具。这个工具的好处是不需要源代码,程序也不需要重新编译。
使用strace的基本语法是:
strace 命令
strace有各种各样的参数。可以检查看strace的手册页来获得更多的细节。
strace的输出非常长,我们通常不会对显示的每一行都感兴趣。我们可以用'-e expr'选项来过滤不想要的数据。
用 '-p pid' 选项来绑到运行中的进程.
用'-o'选项,命令的输出可以被重定向到文件。
strace过滤成只有系统调用的输出
ltrace:
ltrace跟踪和记录一个进程的动态(运行时)库的调用及其收到的信号。它也可以跟踪一个进程所作的系统调用。它的用法是类似与strace。
ltrace command
'-i' 选项在调用库时打印指令指针。
'-S' 选项被用来现实系统调用和库调用
所有可用的选项请参阅ltrace手册。
ltrace捕捉'STRCMP'库调用的输出
4. Valgrind
Valgrind是一套调试和分析工具。它的一个被广泛使用的默认工具——'Memcheck'——可以拦截malloc(),new(),free()和delete()调用。换句话说,它在检测下面这些问题非常有用:
- 内存泄露
- 重释放
- 访问越界
- 使用未初始化的内存
- 使用已经被释放的内存等。
它直接通过可执行文件运行。
Valgrind也有一些缺点,因为它增加了内存占用,会减慢你的程序。它有时会造成误报和漏报。它不能检测出静态分配的数组的访问越界问题。
为了使用它,首先请下载并安装在你的系统上。可以使用操作系统上的包管理器来安装。
使用命令行安装需要解压缩和解包下载的文件。
- tar -xjvf valgring-x.y.z.tar.bz2 (where x.y.z is the version number you are trying to install)
进入新创建的目录(的valgrind-XYZ)内运行以下命令:
- ./configure
- make
- make install
让我们通过一个小程序(test.c)来理解valgrind怎么工作的:
- #include <stdio.h>
- void f(void)
- {
- int x = malloc(10 * sizeof(int));
- x[10] = 0;
- }
- int main()
- {
- f();
- return 0;
- }
编译程序:
- gcc -o test -g test.c
现在我们有一个可执行文件叫做'test'。我们现在可以用valgrind来检测内存错误:
- valgrind –tool=memcheck –leak-check=yes test
这是valgrind呈现错误的输出:
valgrind显示堆溢出和内存泄漏的输出
正如我们在上面看到的消息,我们正在试图访问函数f未分配的内存以及分配尚未释放的内存。
5. GDB
GDB是来自自由软件基金会的调试器。它对定位和修复代码中的问题很有帮助。当被调试的程序运行时,它给用户控制权去执行各种动作, 比如:
- 启动程序
- 停在指定位置
- 停在指定的条件
- 检查所需信息
- 改变程序中的数据 等。
你也可以将一个崩溃的程序coredump附着到GDB并分析故障的原因。
GDB提供很多选项来调试程序。 然而,我们将介绍一些重要的选择,来感受如何开始使用GDB。
如果你还没有安装GDB,可以在这里下载:GDB官方网站。
编译程序:
为了用GDB调试程序,必须使用gcc的'-g'选项进行编译。这将以操作系统的本地格式产生调试信息,GDB利用这些信息来工作。
下面是一个简单的程序(example1.c)执行被零除用来显示GDB的用法:
- #include
- int divide()
- {
- int x=5, y=0;
- return x / y;
- }
- int main()
- {
- divide();
- }
展示GDB用法的例子
调用 GDB:
通过在命令行中执行'gdb'来启动gdb:
调用 gdb
调用后, 它将等待终端命令并执行,直到退出。
如果一个进程已经在运行,你需要将GDB连接到它上面,可以通过指定进程ID来实现。假设程序已经崩溃,要分析问题的原因,则用GDB分析core文件。
启动程序:
一旦你在GDB里面,使用'run'命令来启动程序进行调试。
给程序传参数:
使用'set args'给你的程序传参数,当程序下次运行时将获得该参数。'show args'将显示传递给程序的参数。
检查堆栈:
每当程序停止,任何人想明白的第一件事就是它为什么停止,以及怎么停在那里的。该信息被称为反向跟踪。由程序产生每个函数调用和局部变量,传递的参数,调用位置等信息一起存储在堆栈内的数据块种,被称为一帧。我们可以使用GDB来检查所有这些数据。 GDB从最底层的帧开始给这些帧编号。
- bt: 打印整个堆栈的回溯
- bt 打印n个帧的回溯
- frame : 切换到指定的帧,并打印该帧
- up : 上移'n'个帧
- down : 下移'n'个帧 ( n默认是1)
检查数据:
程序的数据可以在里面GDB使用'print'命令进行检查。例如,如果'x'是调试程序内的变量,'print x'会打印x的值。
检查源码:
源码可以在GDB中打印。默认情况下,'list'命令会打印10行代码。
- list : 列出'linenum'行周围的源码
- list : 从'function'开始列出源码
- disas : 显示该函数机器代码
停止和恢复程序:
使用GDB,我们可以在必要的地方设置断点,观察点等来停止程序。
- break : 在'location'设置一个断点。当在程序执行到这里时断点将被击中,控制权被交给用户。
- watch : 当'expr'被程序写入而且它的值发生变化时GDB将停止
- catch : 当'event'发生时GDB停止
- disable : 禁用指定断点
- enable : 启用指定断点
- delete : 删除 断点/观察点/捕获点。 如果没有传递参数默认操作是在所有的断点
- step: 一步一步执行程序
- continue: 继续执行程序,直到执行完毕
退出 GDB:
用'quit'命令还从GDB中退出。
GDB还有更多的可用选项。里面GDB使用help选项了解更多详情。
在GDB中获得帮助
总结
在这篇文章中,我们已经看到不同类型的Linux用户空间的调试工具。总结以上所有内容,如下是什么时候使用该什么的快速指南:
- 基本调试,获得关键变量 - print 语句
- 获取有关文件系统支持,可用内存,CPU,运行程序的内核状态等信息 - 查询 /proc 文件系统
- 最初的问题诊断,系统调用或库调用的相关问题,了解程序流程 – strace / ltrace
- 应用程序内存空间的问题 – valgrind
- 检查应用程序运行时的行为,分析应用程序崩溃 – gdb