gdb的基本使用
什么是gdb?
- 它是一种针对多种语言的Debug工具,包括C和C++
- 它可以让你在程序的特定位置检查程序正在做什么
- 像段错误这样的简单错误可以通过gdb工具比较简单的找出来
在编写程序时的一个额外步骤
通常,我们编译一个程序通过一下命令实现:
gcc [flag] <source files> -o <output file>
例如:
gcc -Wall p.c -o p.x
现在添加一个额外的-g
选项,这样可以让程序支持debug,从而我们就可以使用gdb对程序进行调试。
例如:
gcc -Wall -g p.c -o p.x
开始使用gdb
在shell中输入gdb
或者gdb p.x
,其中p.x
是我们编译出来的可执行文件,输入之后回车,电脑中的画面应该与这个相似:
(gdb)
如果在打开gdb的时候没有指定一个程序来debug,即你是直接输入gdb
然后回车进入gdb,那么还需要给它载入一个程序让gdb进行debug:
(gdb) file p.x
这里file
命令是载入程序的指令,而p.x
就是我们要载入的程序
小提示
gdb本身自带一个交互shell,他与linux下的命令行shell的使用非常相似,它也可以通过方向键来查看我们之前输入的命令,也可以通过TAB键对命令进行补全。
此外,如果对某一个命令有不明白的地方或者想要对这个命令有更多的了解,可以通过gdb下的help命令,这样就能得到对命令比较完整的描述:
(gdb) help [command]
例如:
(gdb) help file
运行程序
运行程序只需要执行下面的这条指令:
(gdb) run
运行之后程序会出现两种问题:
- 如果这个程序在shell中能够正常运行(例如程序没有段错误、非法访问内存等问题),那么在执行
run
指令之后,程序也应该能够运行。 - 如果程序有问题,那么在你执行程序,在程序崩溃的时候我们应该会从gdb中得到一些错误提示,像这样:
(gdb) run
Starting program: /root/a
Program received signal SIGSEGV, Segmentation fault.
f () at a.cpp:2
2 f();
如果程序确实有bug应该怎么办?
现在已经在gdb中成功的运行了程序,并且也知道了程序中存在问题,但现在的问题是...即使通过shell直接运行程序也能知道程序有问题,因为
(base) chant@mouxiangyus-MacBook-Pro ~ % ./a
zsh: segmentation fault ./a
如果程序有问题shell会直接报错的。
我们肯定不想让这么一个有问题的程序在没有任何中断的情况下运行,因为如果这样做的话,程序会直接跳过错误的地方;或者程序遇到异常,在没有异常处理的情况下(当然c语言没有异常处理,但是c++有)程序会直接崩溃停止运行,程序究竟是在哪一行代码有问题,出了什么具体问题我们都不得而知。
因此,我们需要借助gdb这个有力的工具,来看一下究竟是哪一行代码出现了问题,出现了什么问题。
断点
什么是断点呢?
我们可以在程序中设置断点,那么当程序运行到断点的时候,程序会进入调试模式,这个时候我们就可以一步一步的执行程序,并随时查看程序中各个变量的值,从而让我们找到程序的问题所在。
那么如何设置断点呢?
例如程序中有一个函数,他的声明为void f(int a, char *b);
那么通过下面的命令就可以让程序在调用f
函数的时候都进入调试模式:
(gdb) break f
现在要做什么呢?
一旦我们在程序中设置了断点,那么当我们再次运行
(gdb) run
他应该会在我们设置断点的地方停止运行并进入调试模式(除非在程序运行到断点之前,程序就已经奔溃停止运行了)
-
重新运行这个程序:
(gdb) run
-
将程序运行到下一个断点处
(gdb) continue
-
一行代码一行代码的运行程序
通过next
命令你可以一行一行的执行代码,需要注意的是这个命令并不会跳进函数内部,也就是说如果在断点的位置是一个函数调用,那么调试并不会进入函数,而仅仅是把它看作一条简单的语句
(gdb) next
-
next命令的加强版——step
这个命令与next
命令非常相似,唯一的不同是step
会跳进函数中调试而next
命令则不会跳进函数调试
(gdb) step
小提示
如果每次向下调试都需要重新输入一次step/next/continue
,那操作就太繁琐了,所以我们可以在输入一次命令之后,在不改变命令的情况下我们可以一直按<enter>键从而不断的执行我们刚刚给出的命令。
查看程序中的变量值
在调试的过程中,我们可能需要查看程序中某一个变量的值从而具体判断程序问题到底出在什么地方。
print
或print/x
命令就可以满足我们的要求,他可以在命令行中输出变量的值。这两个命令的区别在于第一个命令是以十进制的形式输出,而第二个是以十六进制的形式输出。
例如现在程序中有一个变量叫做my_var
,那么可以通过下面的命令查看变量值:
(gdb) print my_var
(gdb) print/x my_var
监视某个变量的变量值
有时我们不仅仅是要在某一个时刻查看变量值,而是想在变量被改变的时候查看他的值,那么使用print/print\x
命令就不能满足要求了,因为他们是在特定的位置查看变量值。
这时候我们就可以监视某一个变量,如果变量的值被改变了那么程序就会暂停。
(gdb) watch my_var
在执行这个命令之后,无论何时只要my_var
这个变量的值被改变了,程序都会中断并在命令行中输出变量改变前后的值。
小提示
思考这样一个问题,如果在程序中有多个函数中都有my_var
变量,甚至在全局变量中都有my_var
变量,那么当我们watch
了my_var
变量之后,gdb究竟在监视哪一个my_var
呢?
是全部吗?很不幸,gdb不会监视所有叫做my_var
的变量,它究竟监视哪一个my_var
取决于当你执行watch
命令时你处于程序中的位置。
换句话说,你现在执行程序到了某一个函数中,函数中watch
了my_var
变量,那么gdb仅仅监视这个函数中的my_var
而不会监视其它函数中的my_var
变量。
更简单的说,你现在要watch
一个变量my_var
,你可以假设你现在在要添加watch
的这个位置上写了一条语句my_var = 1
(假设my_var
就是一个int类型的变量),那么这条语句究竟作用到了哪个变量上,watch
命令就监视哪个变量。
其他的一些有用的命令
finish
:将程序执行到当前运行的函数结束的位置delete
:删除一个之前创建的断点info breakpoints
:查看程序中所有的断点
有条件的断点
有时我们并不是想要在任何情况下都中断程序,最简单的一个例子,在一个for
循环中通过简单的排查,程序仅仅在i >= 100
的时候才出现问题,那面当i = 100
的时候就不需要中断程序。通过下面的命令就可以实现这样的功能:
(gdb) break p.c:6 if i >= 100
这条命令在文件p.c
的第六行上设置了一个断点,触发断点的条件是变量i
的值大于等于100