GDB 调试器常用详解

本文详细介绍了GDB调试器的基本原理、安装步骤、关键命令以及在C/C++程序开发中的应用,涵盖了断点设置、代码查看、数据追踪和条件断点等实用技巧。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

引言:GDB 调试器,是 Linux 平台下最常用的一款程序调试器。GDB 编译器通常以 gdb 命令的形式在终端(Shell)中使用,也是我们进行 Linux 下 C/C++ 开发的必备技能之一。

一、GDB 调试器介绍

为什么需要 GDB 调试器?

工作中,首先,我们需要写代码,然后使用 GCC 进行编译,接着就需要调试代码是否符合我们的预期。要知道,哪怕是开发经验再丰富的程序员,编写的程序也避免不了出错。程序中的错误主要分为 2 类,分别为语法错误和逻辑错误

  • 程序中的语法错误几乎都可以由编译器诊断出来,很容易就能发现并解决;
  • 逻辑错误指的是代码思路或者设计上的缺陷,程序出现逻辑错误的症状是:代码能够编译通过,没有语法错误,但是运行结果不对。对于这类错误,只能靠我们自己去发现和纠正。

也就是说,程序中出现的语法错误可以借助编译器解决;但逻辑错误则只能靠自己解决。实际场景中解决逻辑错误最高效的方法,就是借助调试工具对程序进行调试。

所谓调试(Debug),就是让代码一步一步慢慢执行,跟踪程序的运行过程。也就是说,通过调试程序,我们可以监控程序执行的每一个细节,包括变量的值、函数的调用过程、内存中数据、线程的调度等,从而发现隐藏的错误或者低效的代码。

那什么是 GDB 调试器呢?

GDB(GNU Debugger)是 GNU 工具集中的调试器,该程序是一个交互式工具,工作在字符模式。

除 GDB 外,linux下比较有名的调试器还有 xxgdb,ddd,kgdb,ups,目前 GDB 已经是 Linux 平台下最常用的一款程序调试器。

一般来说,GDB主要帮忙你完成下面四个方面的功能:

  1. 启动程序,可以按照你的自定义的要求随心所欲的运行程序。
  2. 可让被调试的程序在你所指定的调置的断点处停住(断点可以是条件表达式)。
  3. 当程序被停住时,可以检查此时你的程序中所发生的事。
  4. 动态的改变你程序的执行环境:可以改变程序,将一个 BUG 产生的影响修正从而测试其他 BUG。

二、GDB 准备工作

GDB 在线安装

GDB 的安装有很多种方法,这里采用最简单的在线安装方法:

sudo apt-get update  # 更新安装资源
sudo apt install gdb # 安装 GDB 

gdb --version        # 查看 GDB 版本,查看 GDB 是否安装成功

生成调试信息

从上面关于 GDB 的介绍可以了解到,GDB 的主要功能就是监控程序的执行流程。这也就意味着,只有当源程序文件编译为可执行文件并执行时,GDB 才会派上用场。

所以,在为调试而编译时,我们会关掉编译器的优化选项(-O), 并打开调试选项(-g。另外,-Wall在尽量不影响程序行为的情况下选项打开所有 warning,也可以发现许多问题,避免一些不必要的 BUG。详细可以参看《Linux 下 GCC 编译常用总结》,这里给出一些简单示例:

gcc -g -Wall program.c -o program

gcc -g hello.c -o hello

g++ -g hello.cpp -o hello
  • -g 选项的作用是在可执行文件中加入源代码的信息,比如可执行文件中第几条机器指令对应源代码的第几行,但并不是把整个源文件嵌入到可执行文件中,所以在调试时必须保证 gdb 能找到源文件
  • 如果没有 -g,你将看不见程序的函数名、变量名,所代替的全是运行时的内存地址。

三、GDB 命令

启动、退出、查看代码

(1)启动与退出GDB

  • 启动gdb:gdb program,program 也就是你的执行文件,一般在当前目录下。

  • 设置运行参数

    set args:args 为运行时所需参数。如:set args 10 20

    show args: 用于查看设置好的运行参数。

  • gdb 使用帮助:help

  • 退出调试:quit

(2)显示源代码

用 list 命令来打印程序的源代码。默认打印10行。

查看当前文件代码

  • list/l :从默认位置显示( 显示当前行后面的源程序)。
  • list/l - :从默认位置显示(显示当前行前面的源程序)。
  • list/l linenum: 打印第 linenm 行的上下文内容(一般是打印 linenm 行的上 5 行和下 5 行)。
  • list/l function: 显示函数名为 function 的函数的源程序(一般是打印函数名行的上 5 行和下 5 行)。

查看非当前文件代码

  • list/l 文件名:行号
  • list/l 文件名:函数名

设置显示的行数

一般是打印当前行的上5行和下5行,当然也可以定制显示的范围,使用下面命令可设置一次显示源程序的行数。

  • set list/listsize count:设置一次显示源代码的行数。
  • show list/listsize: 查看当前 listsize 的设置。

示例

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

设置当前文件断点

  • b/break 行号,比如 b 10,在源程序第 10 行设置断点;
  • b/break 函数名,将在函数入口处设置断点;

设置非当前文件断点

  • break filename:linenum – 在源文件 filename 的 linenum 行处设置断点;
  • break filename:function – 在源文件 filename 的 function 函数的入口处设置断点;
  • break class::function或function(type,type) – 在类 class 的 function 函数的入口处设置断点;
  • break namespace::class::function – 在名称空间为 namespace 的类 class 的 function 函数入口处设置断点

(2)查询所有断点

  • info break,简写为info b

  • i break,简写为i b

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XeEljk4L-1658582978902)(assets/1651761864569-16584788551372.png)]

    【注意】上图中 End 表示断点是否有效,即是否被禁用,y 表示断点可用,n 表示断点禁用

(3)删除断点/无效断点

delete [range…] 删除指定的断点,其简写命令为d/del。

  • range 表示断点号的范围(如:3-7)。【注意】range 中的元素是断点编号,不是断点所在的行号。
  • 如果不指定断点号,则表示删除所有的断点。
  • 如果要同时删除几个,编号可以用空格分隔,如果要删除一个范围内的编号,可以用减号表示(如:2-5)

比删除更好的一种方法是 disable 停止点,对于 disable 停止点,GDB不会删除,当你还需要时,enable 即可,就好像回收站一样。

  • disable [range…] 使指定断点无效,简写命令是 dis。如果什么都不指定,表示disable所有的停止点。
  • enable [range…] 使无效断点生效,简写命令是 ena。如果什么都不指定,表示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)调试代码

  • 运行GDB程序

    • run 运行程序,如果有断点,停在第一个断点处,可简写为 r

      【注意1】启动 GDB 时已经 run 了一次,这次再次输入 run 命令则是重启程序。

      【注意2】run 只是停在断点处,但是并没有执行断点处所在的行。

    • start 运行程序并停在第一行;

  • continue 继续运行程序,停在下一个断点的位置,可简写为 c

  • next 向下执行一行代码,函数调用当作一条简单语句执行(即不会进入函数体),可简写为 n

  • 向下单步调试

    • step 向下单步调试,函数调进入被调用函数体内,可简写为 s
    • finish 跳出函数体,即退出进入的函数。我们想要跳出一个函数,需要满足条件:函数体中不能有断点或者断点不可用
  • until 跳出循环,可简写为 u。我们想要跳出一个循环,首先需要满足:

    1. 循环体中不能有断点或者断点不可用;
    2. 需要运行到循环体的最后一行(其实循环的最后一行就是循环的开始那一行)。

(2)数据查看:查看运行时数据

  • print 变量名:打印变量、字符串、表达式等的值,可简写为 p
  • ptype 变量名:打印变量类型

(3)自动显示

你可以设置一些自动显示的变量,当程序停住时,或是在你单步跟踪时,这些变量会自动显示。相关的GDB命令是 display。

  • display 变量名/表达式:自动打印指定变量的值

  • info display :查看 display 设置的自动显示的信息。

  • 删除编号为 num… 的自动显示

    • delete display num…: 删除编号为 dnums 的变量/表达式的自动显示。
    • undisplay num…:删除编号为 dnums 的变量/表达式的自动显示。
    • 如果要同时删除几个,编号可以用空格分隔;如果要删除一个范围,可以用减号表示(如:2-5)
  • 禁用/激活自动显示

    • enable display nums…:激活编号为 dnums 的变量/表达式的自动显示。

    • disable display nums…:禁用编号为 dnums 的变量/表达式的自动显示。

    • 如果要同时禁用/激活几个,编号可以用空格分隔;如果要禁用/激活一个范围,可以用减号表示,如:2-5

(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]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值