Linux GDB 调试详解

文章目录

    • 一、GDB 调试器介绍
    • 二、GDB 准备工作
      • GDB 在线安装
      • 生成调试信息
    • 三、GDB 命令
      • 启动、退出、查看代码
      • 设置断点
      • 调试命令

一、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。
    详细可以参看[[04_Linux GCC 编译详解|]]《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 是执行文件的名字。
    • 方式二:gdb attach pid,pid 也就是运行进程的 pid。这种方式适用于已经运行的程序。
    • 方式三:gdb program corename,program 是执行文件的名字,corename 是 core dump 的文件名。有时候已经运行的程序突然崩溃了,产生了一个core 文件,此时可以使用 gdb 调试 core 文件,以此可以帮助我们快速定位程序出现段错误的位置。
    • 查看是否成功启动 gdb
      • 当我们成功加载程序进入 gdb 时,会读取符号信息并打印:Reading symbols from /root/flamingoserver/mychatserver...done.
      • 当我们加载程序进入 gdb 失败时,会打印:No symbol table info available.
  • 设置运行参数
    • set args 参数1 参数2 …:args 为运行时所需参数。如:set args 10 20
    • show args: 用于查看设置好的运行参数。
    • set 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 函数入口处设置断点。
  • 设置临时断点:临时断点在触发一次之后就会被删除
    • tbreak 可以添加临时断点,方法与 break 相同

(2)查询所有断点

  • info break,简写为info b
  • i break,简写为i b
    Linux GDB 调试详解_第1张图片
    【注意】上图中 End 表示断点是否有效,即是否被禁用,y 表示断点可用,n 表示断点禁用

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

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

比删除更好的一种方法是 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 运行程序并停在第一行;
  • ctrl + c,中断已经运行的程序。
  • continue 继续运行程序,停在下一个断点的位置,可简写为 c。
  • next 向下执行一行代码,函数调用当作一条简单语句执行(即不会进入函数体),可简写为 n。
  • 向下单步调试
    • step 向下单步调试,遇到函数会进入被调用函数体内,可简写为 s;
      【注意】gdb链式调用函数有个问题:例如,a.fcn1().fcn2()
      • 如果我想进入 fcn2,必须要先 step 进入 fcn1直到 fcn1 返回前再输入 step 才能进入 fcn2;
      • 但是如果我直接在 fcn1 最后输入 next(或finish),则调用堆栈会回到 a.fcn1().fcn2(),这时候再 step 会跳到下一行,而不是fcn2
    • finish 跳出函数体,即退出进入的函数。我们想要跳出一个函数,需要满足条件:函数体中不能有断点或者断点不可用;
  • until 跳出循环,可简写为 u。我们想要跳出一个循环,首先需要满足:
    1. 循环体中不能有断点或者断点不可用;
    2. 需要运行到循环体的最后一行(其实循环的最后一行就是循环的开始那一行)。
  • until + 行号,继续执行直到指定行数。
  • jump + 行号,使程序从当前要执行的代码处,直接跳转到指定位置处继续执行后续的代码,可简写为 j。

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

  • print 变量名:打印变量、字符串、表达式等的值,可简写为 p。print 可以格式化显示信息。
  • print *指针对象:打印指针所指向的内存的内容。
  • ptype 变量名:打印变量类型
    【注意】一般情况下,打印并不存在问题。但当一个待打印的字符串比较长时,打印出来的内容不完整,内容的最后为"…"。可以通过命令:set print elements 0 使打印的字符串长度不受限制。

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

  • display 变量名/表达式:自动打印指定变量的值。【注意】超出变量的作用域后就不会自动打印。
  • info display :查看 display 设置的自动显示的信息。
  • 删除编号为 num… 的自动显示:
    • delete display num…: 删除编号为 dnums 的变量/表达式的自动显示。
    • undisplay num…:删除编号为 dnums 的变量/表达式的自动显示。
    • 如果要同时删除几个,编号可以用空格分隔;如果要删除一个范围,可以用减号表示(如:2-5)
  • 禁用/激活自动显示
    • enable display nums…:激活编号为 nums 的变量/表达式的自动显示。
    • disable display nums…:禁用编号为 nums 的变量/表达式的自动显示。
    • 如果要同时禁用/激活几个,编号可以用空格分隔;如果要禁用/激活一个范围,可以用减号表示,如:2-5

可以通过设置一些自动监控的变量,当变量的值发生变化时,这些变量会自动显示

  • 相关的 GDB 命令是 watch:watch 变量名:当变量的值发生变化时,这些变量会自动显示。【注意】超出变量的作用域后就不会自动打印。
  • info watch :查看 watch 设置的自动显示的信息。

(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]

你可能感兴趣的:(#,Linux基础知识,linux,运维,服务器)