GDB调试:教你简单了解并使用GDB调试程序

什么是GDB?

GDB(GNU symbolic debugger) 是由 GNU 软件系统社区提供的调试工具。当下的 GDB 支持调试多种编程语言编写的程序,包括 C、C++、Go、Objective-C、OpenCL、Ada 等。实际场景中,GDB 更常用来调试 C 和 C++ 程序,同 GCC 配套组成了一套完整的开发环境。

何谓调试?就是让代码一步一步慢慢执行,跟踪程序的运行过程。比如,可以让程序停在某个地方,查看当前所有变量的值,或者内存中的数据;也可以让程序一次只执行一条或者几条语句,看看程序到底执行了哪些代码。

所以GDB可以帮我们:
程序启动时,可以按照我们自定义的要求运行程序,例如设置参数和环境变量;
可使被调试程序在指定代码处暂停运行,并查看当前程序的运行状态(例如当前变量的值,函数的执行结果等),即支持断点调试;
程序执行过程中,可以改变某个变量的值,还可以改变代码的执行顺序,从而尝试修改程序中出现的逻辑错误。

使用GDB前的准备工作

我们需要在用gcc/g++编译器编译可执行程序时需要加入 -g 参数,才能去生成可供GDB调试的可执行程序。

参数 -g :在编译的时候,加入调试信息,让该程序可以被调试器调试。

所谓调试信息就是各行代码所在的行号、包含程序中所有变量名称的列表(又称为符号表)等

注意⚠️:一旦生成了可调试程序文件,就不要再去修改源文件里面的信息,否则调试的时候就会出现一些错误。如果需要修改源文件,则修改后要重新生成调试文件以供调试。

这个时候,我们输入命令

gdb 文件名

就可以进入到gdb调试页面了,这个时候会生成一系列的免责条款,加上参数:–silet 就可以屏蔽掉这些免责信息。

GDB中的一些基础调试命令。

在使用gdb调试命令的过程中,为了操作方便,有一些时候我们可以按下tab键去进行自动补全操作。

启动和退出

上文已经讲过如何生成可供gdb调试的调试文件,和如何启动gdb调试界面来调试程序了,接下来我们讲一下,如何退出gdb。

在gdb调试页面中,我们输入 q/quit ,即可退出gdb调试界面回到终端。

查看代码信息

我们刚刚进入到gdb调试界面时,除了有一堆免责信息,其他的什么都是没有的。如果需要查看源代码,我们就要输入 l/list 去查看源代码。默认设置是查看主函数文件的前10行。

l/list //查看主函数文件的前10行
l/list 行号 //查看包括当前行号的前5行与后5行代码
l/list 函数名 //查看当前函数所在文件的前10行
l/list 文件名:行号 //查看指定文件行号的上下5行文件

我们可以通过命令: set list/listsize 行号 来去修改默认查看的总行数。使用命令:show list/listsize,来查看当前一次性可以查看多少行。

清空调试信息

当我们有时候觉得调试信息太杂乱,或者不想看到当前的这些调试信息时,可以使用命令:

!clear

来将当前调试界面上的所有调试信息清空。

一系列的断点操作

设置断点的作用

我们在源代码的对应位置设置断点后,可以使被调试程序在断点处暂停运行,并查看当前程序的运行状态(例如当前变量的值,函数的执行结果等)。

注意⚠️:停在断点处的那一行代码是还未执行的代码。

设置断点

通过命令

b/break 行号 //在主文件(有main函数的文件)对应行上设置断点。
b/break 函数名 //在主函数指定的函数体内第一行处打上断点。
b/break 文件名:行号 //在指定文件的对应行上打上断点。
b/break 文件名:函数名 //在指定文件内的函数体内的第一行代码处打上断点

查看设置了哪些断点

使用命令:

i/info b/break

就可以查看所有已设置的断点信息,如在第几行,断点的编号是多少等

(gdb) i b
Num     Type           Disp Enb Address            What
1       breakpoint     keep y   0x0000000000000b09 in main() at main.cpp:8
2       breakpoint     keep y   0x00000000000009b5 in bubbleSort(int*, int) at bubble.cpp:8

删除断点

使用命令:

d/del 断点编号

就可以删除错误设置的断点

设置断点不生效/生效

断点设置完成时,默认生效。当我们希望某些断点不要生效时,就使用命令:

dis/disable 断点编号

就可以使对应编号的断点无效化。如果想要无效化的断点重新生效了话,就要使用命令:

ena/enable 断点编号

来使对应断点重新生效。

设置条件断点

当我们设置的断点在某个循环内,我们可以通过设置条件断点,来让程序停在对应的条件处:

b 行号 if 变量名==值

运行调试程序

运行程序命令run与start的区别

之前讲过了如何去在程序上设置断点,现在就可以开始讲如何运行调试程序了。我们使用 r/run 命令 和 start 命令都可以开始运行调试程序。
它们的区别在于:

  • start命令会使被调试的程序停在主函数入口处。
  • run命令则是使程序停在第一个断点那一行

继续执行程序命令next、continue、step的区别

next、continue、step命令都是关于程序继续运行的命令,但是他们都之间都略有区别

  • 执行命令 c/continue 的意思是使程序继续运行,只到下一处断点才会停下

  • 执行命令 n/next 则是使程序向下执行一行,如果这一行是一个被调用的函数,使用next命令,不会进入到函数题内。

  • 执行命令 s/step 也是使程序向下执行一行与 next 不同的便是遇到被调用的函数,会进入到函数体内部。而我们如果想要出这个函数体,则可以使用 finish 命令去跳出函数体。

打印变量值命令print与display的区别

我们可以使用命令:

p/print 变量名
display 变量名

去获取对应变量名的值
它们直接的区别是,print只会在执行print命令的时候,将对应变量的值打印一遍。而使用display命令则是打印对应的变量值,当程序继续执行后,继续输出对应变量当前的值。

被display设置过的变量也称之为自动变量,多用于循环体内。
如这么一段代码:

Breakpoint 3, main () at main.cpp:16
16              cout << array[i] << " ";
(gdb) print array[i]
$1 = 12
(gdb) n
15          for(int i = 0; i < len; i++) {
(gdb) display array[i]
1: array[i] = 12
(gdb) n

Breakpoint 3, main () at main.cpp:16
16              cout << array[i] << " ";
1: array[i] = 22
(gdb) 
15          for(int i = 0; i < len; i++) {
1: array[i] = 22
(gdb) 

Breakpoint 3, main () at main.cpp:16
16              cout << array[i] << " ";
1: array[i] = 27
(gdb) 
15          for(int i = 0; i < len; i++) {
1: array[i] = 27
(gdb) 

Breakpoint 3, main () at main.cpp:16
16              cout << array[i] << " ";
1: array[i] = 55
(gdb) 
15          for(int i = 0; i < len; i++) {
1: array[i] = 55
(gdb) 

Breakpoint 3, main () at main.cpp:16
16              cout << array[i] << " ";
1: array[i] = 67
(gdb) 
15          for(int i = 0; i < len; i++) {
1: array[i] = 67
(gdb) 
18          cout << endl;
(gdb) 

我们就可以看出在循环体内,每向下执行一步,display变量都会输出一遍变量的值,而print只输出了一遍。

我们可以使用命令:

i/info display

去查看自己设置了哪些自动变量,也可以使用命令:

undisplay 自动变量编号 去删除刚刚设置的自动变量

使用命令:

ptype 变量名

则可以查看对应变量的类型

跳出循环命令until与跳出函数体命令finish

当我们程序停在一个循环体内的某一条输出语句时,如何跳出循环体呢?
首先我们将循环体内的断点删除或是停用。使用命令c 即可调到这个循环体外的下一个断点处。
使用n则是程序向下执行一行。不会跳出这个循环,使用命令s与n一样。
这个时候使用until命令则会回到未循环体的第一行,这个时候就可以重新选择是进入循环还是跳出循环了。

(gdb) run
Starting program: /home/nowcoder/Linux/lession08/mainApp 

Breakpoint 2, main () at main.cpp:8
8           int array[] = {12, 27, 55, 22, 67};
(gdb) c
Continuing.

Breakpoint 3, main () at main.cpp:16
16              cout << array[i] << " ";
(gdb) display array[i]
2: array[i] = 12
(gdb) c
Continuing.

Breakpoint 3, main () at main.cpp:16
16              cout << array[i] << " ";
2: array[i] = 22
(gdb) disa 3
(gdb) until
15          for(int i = 0; i < len; i++) {
2: array[i] = 22
(gdb) 

finish命令与until命令很相似,如果此时程序停在了函数体内部,如果函数体内部向下执行没有断点,则使用finish则可以跳出函数体,将程序停在执行完函数体的下一行。
如果函数体内部向下有断点,使用finish命令则就会停在断点处。

改变变量值的操作

我们可以使用命令:

set var 变量名=变量值

去修改某些变量的值,从而达到修改输出的目的。

Breakpoint 3, main () at main.cpp:16
16              cout << array[i] << " ";
2: array[i] = 22
(gdb) set var array[i] = 23
(gdb) c
Continuing.

Breakpoint 3, main () at main.cpp:16
16              cout << array[i] << " ";
2: array[i] = 27
(gdb) 
Continuing.

Breakpoint 3, main () at main.cpp:16
16              cout << array[i] << " ";
2: array[i] = 55
(gdb) 
Continuing.

Breakpoint 3, main () at main.cpp:16
16              cout << array[i] << " ";
2: array[i] = 67
(gdb) 
Continuing.
冒泡排序之后的数组: 12 23 27 55 67 
===================================
选择排序之后的数组: 11 25 36 47 80 
[Inferior 1 (process 1113) exited normally]
(gdb) 

GDB多进程调试

使用GDB调试的时候,GDB默认只能跟踪一个进程,可以在fork函数调用之前,通过指令设置GDB调试工具追踪父进程或者是跟踪子进程,默认跟踪父进程

设置调试父进程或者子进程:set follow-fork-mode parent/child

输入命令: show follow-fork-mode gdb会告诉你,你现在正在调试的是父进程还是子进程

设置调试模式:set detach-on-fork on/off 这个是设置的意思是,使用了fork函数后,另一个进程是否脱离gdb的调试。默认是on,也就是开启状态。
换句话说也就是,处于on状态下,表示调试当前进程的时候,其他的进程继续运行,处于off状态下,其他进程被gdb挂起。

输入命令: show fdetach-on-fork on/off gdb会告诉你,另一个进程是否会脱离。

查看调试的进程:info inferiors //会把调试的所有进程的进程号标识在这里,当前调试的会有一个*号,并且会有一个Num去记录gdb调试进程的id。

切换当前调试的进程:inferior id

使进程脱落GDB调试:detach inferiors id

你可能感兴趣的:(Linux,linux,c++,运维)