GDB简介
常在河边走,哪有不湿鞋。经常编写程序的人,又怎么可能不遇到bug呢?没有遇到过bug的程序员不是真正的程序员。当程序出现了bug,我们就需要debug,常见的程序错误按错误类型来分,一般可分为两种:
- 编译错误
- 运行错误
编译错误是指程序在编译阶段遇到的错误,比如语法错误、语义错误等。这种类型的错误编译器一般会帮助我们检查,当遇到此类错误时,编译器就会停止编译,给出错误或警告的信息,我们根据错误的提示,就可以很快解决掉。
运行错误是指程序在运行阶段遇到的错误,比如段错误。在运行阶段,为了能够定位出现的错误,我们经常使用的方法是打印:将程序运行过程中的一些观察变量打印出来,看是否符合正常的程序逻辑。打印方法简单方便,但对于一些隐藏比较深的bug,或者一些跟内存相关的bug,此时再用“打印大法”可能就爱莫能助了。此时,我们经常使用的另一种方法是:单步调试。所谓单步调试,就是我们可以获取到控制程序运行的权限,在人工操作下,程序可以“放慢脚步”,一步一步地执行,甚至可以暂停执行,方便我们观察各个变量、内存、寄存器的值,看是否符合我们的预期。
在Windows下调试程序,我们一般是使用集成开发环境(IDE,Integrated Development Environment )内部集成的调试器(debugger),通过菜单栏的“调试(debug)”选项,就可以让程序进入调试模式:可以单步执行程序、暂停程序运行、观察变量或内存的值。
在Linux下调试程序,因为早期没有成熟、好用的IDE,一般都是使用GDB来调试程序。GDB是“GNU Symblic Debugger”的缩写,是GNU自由软件下的一个产品,随着版本不断地更新迭代,目前最新的GDB版本不仅可以调试C语言程序,还可以调试C++、Go、Object-C等编译型语言。
在Linux下开发程序,学会使用GDB是一项基本技能。在Windows下开发程序,在很多IDE内部,往往也集成了GDB,用户可以通过图形界面来调用GDB调试程序,也可以直接在DOS环境下直接通过GDB命令来调试程序。
本教程将会带领大家熟悉和掌握程序调试常用的GDB命令,让我们的开发工作更加高效。
GDB安装
很多Linux操作系统默认都已经安装了GDB,所以在安装GDB之前,首先要确实你的当前系统有没有已经安装:
# gdb -v
GNU gdb (Ubuntu 9.2-0ubuntu1~20.04) 9.2
Copyright (C) 2020 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
如果出现上面的版本信息,如果GDB已经安装好了;若没有出现上面的信息,可以使用下面的命令直接安装:
# apt-get install gdb Debian系列操作系统安装命令
# yum install gdb RedHat系列操作系统安装命令
Linux操作系统因为开源特性,很多操作系统都是基于开源版本不断演化,发展成不同的版本和分支。但总的来说,可以分为三大系:Fedora系、Debian系和SlackWare系。
- RedHat系:Fedora、RHEL、CentOS等,使用yum命令安装软件包
- Debian系:Ubuntu、Kali、Knopix等,使用apt-get安装命令
- SlackWare系:SUSE、OpenSUSE
安装成功后,再次使用# gdb -v 命令查看是否安装成功,如果出现gdb的版本信息,说明软件安装成功。
GDB源码安装
使用yum或apt-get命令,从官方服务器安装的软件包,一般都是稳定版,版本比较旧。如果想尝鲜最先版本的GDB,可以使用源码安装。GDB因为是GNU开源工程,所以安装步骤也是基本的三步:配置、编译、安装。
首先,要到GDB官网下载最新版本的源码包:GDB源码包官网地址
接下来,解压这个源码包,进入源码包目录,按照经典的三步走:配置、编译、安装即可。
# tar xvf gdb-10.1.tar.gz
# cd gdb-10.1
# ./configure
# make
# make install
# gdb -v
GNU gdb (GDB) 10.1
Copyright (C) 2020 Free Software Foundation, Inc.
...
快速上手
安装好GDB后,接下来就要快速上手GDB的使用。GDB和shell类似,采用命令行形式的命令来调试程序,GDB常用的命令也就10来个,掌握了这些常用的调试命令,基本上就可以满足日常程序的调试需求。
第01步:编译要调试的程序
首先,要将需要调试的源程序编译为可执行文件。以下面的C语言程序为例:
#include
int main(void)
{
int sum = 0;
int i, num;
printf("breakpoint 1\\n");
printf("input num: ");
scanf("%d", &num);
printf("breakpoint 2\\n");
if(num <= 0)
printf("input invalid!\\n");
else
{
for(i = 1; i <= num; i++)
sum += i;
printf("sum = %d\\n", sum);
}
printf("breakpoint 3\\n");
printf("goodbye!\\n");
return 0;
}
麻雀虽小,五脏俱全,上面的C语言程序实现了数据求和的简单功能,但包含了程序的三种基本结构:顺序程序结构、选择程序结构和循环程序结构。为方便演示,我们分别在第8行、第12行、第23行设置了三个打印断点的函数。接下来我们编译这个程序:
# gcc -g -O0 main.c -o a.out
生成的可执行文件a.out里都是二进制的机器指令,如果你想要源码级调试,就需要在编译程序时选中:debug选项(-g),生成的debug模式下的可执行文件a.out就包含了各种调试信息,其中最重要的一个信息就是:二进制机器指令和源程序代码之间的对应关系,有了这个调试信息,当程序运行二进制机器指令的时候,就可以实时显示对应的源代码,更方便我们阅读和调试。如果你在编译的时候不使用debug模式,而使用release模式,那么程序在运行时,显示给你的就是二进制的机器代码或汇编指令,不适合人类阅读。
为提高程序的运行效率和性能,编译器在编译程序时,往往会对源程序进行优化,比如常量折叠、缓存、内联展开等。我们在编译程序时,也可以通过参数来控制编译优化级别,一般可分为如下等级:
- -O0:不对程序进行编译优化,默认的优化等级
- -O1:可以减少目标文件的体积和程序执行时间
- -O2:在-O1优化的基础上,编译器不执行循环展开和函数内联,增加了代码的性能
- -Os:在-O2优化的基础上,专门优化目标文件的大小
- -O3:在-O2优化的基础上,打开了-finline-functions, -funswitch-loops等标签
源程序经过编译器优化后,生成的二进制和源代码之间可能就不是一一对应的关系了。为了更好的演示源码级调试,我们在编译时选择-O0选项,不对程序进行编译优化。
第02步:进入GDB调试环境
需要调试的可执行程序a.out生成以后,接下来就可以使用gdb进行调试了,可以使用下面的命令来进入GDB调试环境,调试a.out(编译生成的a.out文件位于/home/gdb目录下):
root@ubuntu:/home/gdb# gdb a.out
GNU gdb (Ubuntu 9.2-0ubuntu1~20.04) 9.2
Copyright (C) 2020 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later //gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
//www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
//www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from a.out...
(gdb) r
Starting program: /home/gdb/a.out
breakpoint 1
input num: 3
breakpoint 2
sum = 6
breakpoint 3
goodbye!
[Inferior 1 (process 2768) exited normally]
(gdb) q
root@ubuntu:/home/gdb#
当我们使用# gdb a.out时,就会进入交互式的gdb调试环境,运行run(可简写为 r)命令即可启动a.out的运行。程序在运行过程中的输出、输入和正常运行模式下一样,程序结束后,输入quit(可简写为q)命令即可退出GDB调试环境,重新返回到SHELL环境下。
第03步:设置断点、打印变量
GDB作为一个调试器,调试器该有的功能他都有,比如:设置断点、打印变量、单步等。我们在调试程序时,如果想让程序在某行暂停,观察此时的一些寄存器、变量值,此时可以通过GDB的断点设置和变量打印功能来实现。
接下来就给大家演示下如何设置断点和打印变量:
root@ubuntu:/home/gdb# gdb a.out
GNU gdb (Ubuntu 9.2-0ubuntu1~20.04) 9.2
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from a.out...
(gdb) b 8
Breakpoint 1 at 0x11cb: file main.c, line 8.
(gdb) b 12
Breakpoint 2 at 0x1200: file main.c, line 12.
(gdb) b 23
Breakpoint 3 at 0x1252: file main.c, line 23.
(gdb) r
Starting program: /home/gdb/a.out
Breakpoint 1, main () at main.c:8
8 printf("breakpoint 1\\n");
(gdb) print sum
$1 = 0
(gdb) c
Continuing.
breakpoint 1
input num: 3
Breakpoint 2, main () at main.c:12
12 printf("breakpoint 2\\n");
(gdb) c
Continuing.
breakpoint 2
sum = 6
Breakpoint 3, main () at main.c:23
23 printf("breakpoint 3\\n");
(gdb) print sum
$2 = 6
(gdb) c
Continuing.
breakpoint 3
goodbye!
[Inferior 1 (process 2780) exited normally]
(gdb) q
root@ubuntu:/home/gdb#
重新进入GDB调试环境,使用break(可简写为b)命令可以在源代码的具体某一行设置断点。设置好断点后,使用run命令即可运行程序,程序在运行过程中遇到断点会暂停下来,程序暂停后,你可以使用print命令来打印某一个具体变量的值。
如果想要程序继续运行,可以使用continue命令(可简写为c),程序会继续运行,直到遇到下一个断点暂停下来。如果没有再遇到断点,程序则会一直执行下去,直到运行结束。
如果想要程序继续运行,可以使用continue命令(可简写为c),程序会继续运行,直到遇到下一个断点暂停下来。如果没有再遇到断点,程序则会一直执行下去,直到运行结束。
在设置断点的时候,如果想查看某一行代码的具体行数,可以使用list(可简写为l)命令查看部分源代码。多次输入list命令,GDB就会依次显示不同的代码片段,直到你找到要设置的断点的行数为止。
在GDB交互环境下,当你不输入任何命令,直接敲击回车键(Enter键)时,GDB会默认执行上一次你输入的命令。所以,当你需要多次运行同一个命令时,不需要每次都输入相同的命令,直接回车,就可以多次重复执行一个命令了。
-
运行
+关注
关注
0文章
25浏览量
15401 -
编译
+关注
关注
0文章
657浏览量
32869 -
DEBUG
+关注
关注
3文章
93浏览量
19915
发布评论请先 登录
相关推荐
评论