为什么需要 GDB 调试器?
我们需要写代码,然后使用 GCC 进行编译,接着就需要调试代码是否符合我们的预期。要知道,哪怕是开发经验再丰富的程序员,编写的程序也避免不了出错。
程序中的错误主要分为 2 类,分别为语法错误和逻辑错误:
也就是说,程序中出现的语法错误可以借助编译器解决;但逻辑错误则只能靠自己解决。实际场景中解决逻辑错误最高效的方法,就是借助调试工具对程序进行调试。
所谓调试(Debug),就是让代码一步一步慢慢执行,跟踪程序的运行过程。也就是说,通过调试程序,我们可以监控程序执行的每一个细节,包括变量的值、函数的调用过程、内存中数据、线程的调度等,从而发现隐藏的错误或者低效的代码。
那什么是 GDB 调试器呢?
GDB(GNU Debugger)是 GNU 工具集中的调试器,该程序是一个交互式工具,工作在字符模式。除 GDB 外,linux下比较有名的调试器还有 xxgdb,ddd,kgdb,ups,目前 GDB 已经是 Linux 平台下最常用的一款程序调试器。
一般来说,GDB主要帮忙你完成下面四个方面的功能:
GDB 的安装有很多种方法,这里采用最简单的在线安装方法:
sudo apt-get update # 更新安装资源
sudo apt install gdb # 安装 GDB
gdb --version # 查看 GDB 版本,查看 GDB 是否安装成功
从上面关于 GDB 的介绍可以了解到,GDB 的主要功能就是监控程序的执行流程。这也就意味着,只有当源程序文件编译为可执行文件并执行时,GDB 才会派上用场。
-O
), 并打开调试选项(-g
)。-Wall
在尽量不影响程序行为的情况下选项打开所有 warning,也可以发现许多问题,避免一些不必要的 BUG。gcc -g -Wall program.c -o program
gcc -g hello.c -o hello
g++ -g hello.cpp -o hello
【注意】
-g
选项的作用是在可执行文件中加入源代码的信息,比如可执行文件中第几条机器指令对应源代码的第几行,但并不是把整个源文件嵌入到可执行文件中,所以在调试时必须保证 gdb 能找到源文件。-g
,你将看不见程序的函数名、变量名,所代替的全是运行时的内存地址。(1)启动与退出 GDB
Reading symbols from /root/flamingoserver/mychatserver...done.
No symbol table info available.
(2)显示源代码
用 list 命令来打印程序的源代码。默认打印10行。
示例:
yxm@192:~$ gcc -o test test.c -g
yxm@192:~$ ls
company_project install myshare test test.c
yxm@192:~$ gdb test
GNU gdb (Ubuntu 8.1.1-0ubuntu1) 8.1.1
Copyright (C) 2018 Free Software Foundation, Inc.
...
...
(gdb) set args 10 20
(gdb) show args
Argument list to give program being debugged when it is started is "10 20 ".
(gdb) l test
26
27 printf("THE END !!!\n");
28 return 0;
29 }
30
31 int test(int a) {
32 int num = 0;
33 for(int i = 0; i < a; ++i) {
34 num += i;
35 }
(gdb) show list
Number of source lines gdb will list by default is 10.
(gdb) quit
yxm@192:~$
(1)设置断点:break 设置断点,可以简写为 b
(2)查询所有断点
(3)删除断点/无效断点
delete [range…] 删除指定的断点,其简写命令为d/del。
比删除更好的一种方法是 disable 停止点,对于 disable 停止点,GDB 不会删除,当你还需要时,enable 即可,就好像回收站一样。
(4)条件断点
一般来说,为断点设置一个条件,我们使用 if 关键词,后面跟其断点条件。
设置一个条件断点:b test.c:23 if i == 5
【注意】if 后面跟着的是条件,可能是 == ,也可能是 =,可以根据需要自行设置条件。
示例:
yxm@192:~/test$ g++ bubble.cpp main.cpp select.cpp -o main -g
yxm@192:~/test$ ls
bubble.cpp main main.cpp select.cpp sort.h
yxm@192:~/test$ gdb main # 进入 gdb 调试
GNU gdb (Ubuntu 8.1.1-0ubuntu1) 8.1.1
Copyright (C) 2018 Free Software Foundation, Inc.
......
Reading symbols from main...done.
(gdb) b 9 # 本文件设置断点
Breakpoint 1 at 0x400a2c: file main.cpp, line 9.
(gdb) i b # 显示断点
Num Type Disp Enb Address What
1 breakpoint keep y 0x0000000000400a2c in main() at main.cpp:9
(gdb) b main # 本文件函数设置断点
Breakpoint 2 at 0x4009fa: file main.cpp, line 6.
(gdb) break bubble.cpp:11 # 非本文件设置断点
Breakpoint 3 at 0x400924: file bubble.cpp, line 11.
(gdb) b bubble.cpp:bubbleSort # 非本文件函数设置断点
Breakpoint 4 at 0x4008c1: file bubble.cpp, line 8.
(gdb) i b
Num Type Disp Enb Address What
1 breakpoint keep y 0x0000000000400a2c in main() at main.cpp:9
2 breakpoint keep y 0x00000000004009fa in main() at main.cpp:6
3 breakpoint keep y 0x0000000000400924 in bubbleSort(int*, int) at bubble.cpp:11
4 breakpoint keep y 0x00000000004008c1 in bubbleSort(int*, int) at bubble.cpp:8
(gdb) d 4 # 删除断点
(gdb) del 3
(gdb) delete 2
(gdb) i b
Num Type Disp Enb Address What
1 breakpoint keep y 0x0000000000400a2c in main() at main.cpp:9
(gdb) b 14
Breakpoint 5 at 0x400a45: file main.cpp, line 14.
(gdb) dis 5 # 设置断点无效
(gdb) i b
Num Type Disp Enb Address What
1 breakpoint keep y 0x0000000000400a2c in main() at main.cpp:9
5 breakpoint keep n 0x0000000000400a45 in main() at main.cpp:14
(gdb) enable 5 # 设置断点有效
(gdb) b 16 if i=3 # 设置条件断点
Breakpoint 6 at 0x400a63: file main.cpp, line 16.
(gdb) i b
Num Type Disp Enb Address What
1 breakpoint keep y 0x0000000000400a2c in main() at main.cpp:9
5 breakpoint keep y 0x0000000000400a45 in main() at main.cpp:14
6 breakpoint keep y 0x0000000000400a63 in main() at main.cpp:16
stop only if i=3
(1)调试代码
a.fcn1().fcn2()
(2)数据查看:查看运行时数据
set print elements 0
使打印的字符串长度不受限制。(3)自动显示
可以设置一些自动显示的变量,当程序停住时,或是在你单步跟踪时,这些变量会自动显示。相关的 GDB 命令是 display。
可以通过设置一些自动监控的变量,当变量的值发生变化时,这些变量会自动显示。
(4)修改变量的值
你可以使用 set var 命令来设置某个变量值,让它等于我们想要的值,以便往下继续运行调试,找出程序中 bug:set var width=47 // 将变量var值设置为47
。【注意】width 不是你 GDB 的参数,而是程序的变量名
示例:
yxm@192:~/test$ g++ bubble.cpp main.cpp select.cpp -o main -g
yxm@192:~/test$ ls
bubble.cpp main main.cpp select.cpp sort.h
yxm@192:~/test$ gdb main # 进入 gdb 调试
(gdb) start
Temporary breakpoint 7 at 0x4009fa: file main.cpp, line 6.
......
Temporary breakpoint 7, main () at main.cpp:6
6 int main() {
(gdb) c # 没有设置断点,所以直接运行到最后
Continuing.
冒泡排序之后的数组: 12 22 27 55 67
===================================
选择排序之后的数组: 11 25 36 47 80
[Inferior 1 (process 3381) exited normally]
(gdb) b 8 # 设置断点
Breakpoint 8 at 0x400a09: file main.cpp, line 8.
(gdb) b bubble.cpp:bubbleSort
Breakpoint 9 at 0x4008c1: file bubble.cpp, line 8.
(gdb) b 16
Breakpoint 10 at 0x400a63: file main.cpp, line 16.
(gdb) i b
Num Type Disp Enb Address What
8 breakpoint keep y 0x0000000000400a09 in main() at main.cpp:8
9 breakpoint keep y 0x00000000004008c1 in bubbleSort(int*, int) at bubble.cpp:8
10 breakpoint keep y 0x0000000000400a63 in main() at main.cpp:16
(gdb) run
Starting program: /home/yxm/test/main
Breakpoint 8, main () at main.cpp:8
8 int array[] = {12, 27, 55, 22, 67};
(gdb) c
Continuing.
Breakpoint 9, bubbleSort (array=0x7fffffffe310, len=5) at bubble.cpp:8
8 for (int i = 0; i < len - 1; i++) {
(gdb) next
9 for (int j = 0; j < len - 1 - i; j++) {
(gdb) c
Continuing.
Breakpoint 10, main () at main.cpp:17
17 cout << array[i] << " ";
(gdb) i b
Num Type Disp Enb Address What
8 breakpoint keep y 0x0000000000400a09 in main() at main.cpp:8
breakpoint already hit 1 time # 表示断点已经被击中过
9 breakpoint keep y 0x00000000004008c1 in bubbleSort(int*, int) at bubble.cpp:8
breakpoint already hit 1 time
10 breakpoint keep y 0x0000000000400a63 in main() at main.cpp:16
breakpoint already hit 1 time
(gdb) print i # 打印变量的值
$1 = 2
(gdb) ptype i # 打印变量的类型
type = int
(gdb) del 10 # 删除循环体内断点
(gdb) until # 退出循环体
15 for(int i = 0; i < len; i++)
(gdb) s
19 cout << endl;
(gdb) c
Continuing.
冒泡排序之后的数组: 12 22 27 55 67
===================================
选择排序之后的数组: 11 25 36 47 80
[Inferior 1 (process 3763) exited normally]