Linux下GDB的使用

发布时间 2023-11-16 00:03:30作者: Beasts777

环境:Ubuntu 18.04.6

文章参考:爱编程的大丙 (subingwen.cn)

简介:

gdb是由软件系统社区提供的调试器,同gcc配套组成了一套完整的开发环境,可移植性很好,支持非常多的体系结构并被移植到各种系统中(包括各种Unix系统与Windos系统里的MinGW和Cygwin)。此外,除了C语言之外,gcc/gdb还支持包括C++、Objecttive——C、Ada和Pascal等各种语言后端的编译和调试。gcc/gdb是Linux和许多类Unix系统中的标准开发环境。Linux内核也是专门针对gcc进行编码的。

1. 调试准备

参数:

项目程序如果是为了调试而进行的编译,那么必须要打开调试选项-g(该指令在编译阶段起作用)。此外还有一些其它选项,如:

  • 在尽量不影响程序行为的情况下关闭编译器的优化选项-O0
  • 使用-Wall选项打开所有的warning输出。

-g的作用是在可执行程序中加入源代码的信息,比如可执行文件中的第几条机器指定对应源代码的第几行,但并不是将整个源文件嵌入到可执行文件中,因此在调试时必须能保证gdb能找到源文件

EG:

  1. 编写一个测试文件:

    #include <stdio.h>
    #include <string.h>
    
    int main(int argc, char* argv[]){
        printf("输入了%d个参数\n", argc);
        for(int i=0; i<argc; i++){
            printf("第%d个参数为:%s\n", i, argv[i]);	
        }
        return 0;
    }
    
  2. 使用gcc进行编译,一个正常编译,一个通过-g参数说明编译时加入gdb支持:

    gcc args.c -o app			# 正常编译
    gcc -g args.c -o appGdb		# 加入gdb支持
    # 展示文件大小
    ll
    -rwxrwxr-x 1 beasts777 beasts777 16696 11月 15 03:24 app*
    -rwxrwxr-x 1 beasts777 beasts777 19336 11月 15 03:25 appGdb*
    

    可以看出:加入调试支持的可执行程序更大一些。

2. 启动和退出gdb

2.1 启动gdb

命令格式:

gdb 程序名

需要注意的是:此时gdb进程启动,但程序并未启动。如果想要启动程序,需要其它指令。

EG:

gdb appGdb

2.2 命令行传参

有些程序启动时需要传参,在gdb中传参,步骤如下:

  1. 启动gdb:

    gdb 程序名
    
  2. 在gdb启动后,程序启动前设置参数:

    # 语法:
    set args 参数1 参数2...
    
  3. 查看设置的参数:

    show args
    

EG:

当前调试程序为:appGdb

# 输入
gdb appGdb
set args a b c d
show args
# 输出
Argument list to give program being debugged when it is started is "a b c d".

2.3 gdb中启动程序

在gdb中启动程序有两种方法:

  • run(等价于r):如果程序设置了断点,会停在第一个断点的位置。如果没有,那么程序将会直接执行完毕。
  • start:启动程序,但会阻塞在程序第一行,等待用户的后续操作。

如果想要在start阻塞后继续运行,或遇到断点后继续运行,应当使用指令:

  • continue(等价于c):让程序在当前阻塞处或断点处继续运行。

注意:在整个gdb调试过程中,启动应用程序的命令只能执行一次

2.4 退出gdb

通过命令:

  • quit(等价于q):通过该命令终止gdb进程。

3. 查看代码

简介:

gdb作为一个命令行程序,并不提供GUI界面,对于代码的查看工作通过指令完成,即:

  • list(等价于l):通过该命令可以查看不同文件中的代码,还可以根据文件行号、函数名来查看指定位置的代码。

3.1 当前文件

一个程序往往由多个源文件组成,默认情况下当前文件为main函数所在的原文件,list查看到的代码信息也默认是main函数所在的文件的信息,除非进行文件切换。

list:

# 使用list或l均可
# 默认从第一行进行展示,默认展示地行数为10行
(gdb) list
# 可以指定行号,显示该行的上下文,默认10行
(gdb) list 7
# 可以指定函数名,显示该函数的上下文,默认10行
(gdb) list add

如果想查看后续文件,可以:

  • 继续输入l
  • 直接按回车键

3.2 切换文件

有时我们可能需要查看除了main函数所在文件以外的其他文件,这就需要我们切换当前文件位置,切换方式如下:

# 切换到指定的文件,并列出这行号对应的上下文代码, 默认情况下只显示10行内容
(gdb) l 文件名:行号

# 切换到指定的文件,并显示这个函数的上下文内容, 默认显示10行
(gdb) l 文件名:函数名

注意:切换文件时必须指定行号或函数名。

3.3 一次显示的行数

默认一次显示10行。

关键字为listsize,使用时等价于list

查看当前默认行数:

(gdb) show listsize 行数

修改默认行数:

(gdb) set listsize 行数

4. 断点操作

想要令程序阻塞到指定的代码位置,从而获取该位置程序的一些信息,如变量的值,我们就需要断点操作,并在程序阻塞到这里时通过指令对代码进行调试。

设置断点的命令为break,也可以缩写为b

4.1 设置断点

断点分为下面两种:

常规断点:程序运行到这里时就会阻塞。

  • 设置断点到当前文件的指定位置:

    (gdb) b 行号
    (gdb) b 函数		# 程序将会阻塞在该函数的第一行
    
  • 设置断点到其它文件的指定位置:

    (gdb) b 文件名:行号
    (gdb) b 文件名:函数		# 程序将会阻塞在该函数的第一行
    

条件断点:只有满足响应条件,程序才会在该位置阻塞。

  • (gdb) b 行号 if 变量名=某个值
    

4.2 查看断点

指令为:info break,可以简写为:i break

EG:

  1. 设置断点:

    # info == i
    # 当前文件第六行设置断点
    (gdb) b 6
    # 输出
    Breakpoint 1 at 0x11c4: file args.c, line 6.
    # add.c源文件第5行,当i=2时,断点发挥作用
    (gdb) b add.c:5 if i=2
    # 输出
    Breakpoint 2 at 0x1164: file add.c, line 5.
    
  2. 查看断点:

    Num     Type           Disp Enb Address            What
    1       breakpoint     keep y   0x00000000000011c4 in main at args.c:6
    2       breakpoint     keep y   0x0000000000001164 in add at add.c:5
    	stop only if i=2
    

断点表中的字符:

  • Num:表示断点的编号,后续操作断点都是通过编号操纵。如删除断点、修改断点状态等。
  • Enb:当前断点是否可用。y表示可用,n表示不可用。
  • What:描述断点被作用在了哪个文件的哪一行或哪个函数上。

4.3 删除断点

指令为:delete 断点编号。其中delete可以简写为del或者d指令。

同时,可以分别删除多个断点,或删除一定范围内连续的断点。具体操作如下:

  • 删除指定编号的断点:

    (gdb) d 2 5
    
  • 删除一定范围内连续的断点:

    (gdb) d 2-5 	# 将会删除2、3、4、5断点
    

4.4 设置断点状态

可以通过调整断点装填来令断点失效或生效。指令为:

  • 失效断点:disable 断点编号。其中disable==dis
  • 生效断点:enable 断点编号。其中enable==ena`

与删除断点类似,设置断点状态也可以批量操作断点。

EG:

# disable==dis
# 令编号为1、2的断点失效
(gdb) dis 1 2
# 令编号为3、4、5的断点失效
(gdb) dis 3-5
# enabel==ena
# 令编号为1、2的断点生效
(gdb) ena 1 2
# 令编号为3、4、5的断点生效
(gdb) ena 3-5

5. 调试命令

当程序在阻塞时,就可以使用调试命令来对程序进行调试。

5.1 继续运行gdb

命令:continue(可以缩写为c

使用场景:当程序阻塞在某处时,可以使用该指令,让程序继续运行。

# continue == c
(gdb) c

5.2 打印信息

应用场景:当程序阻塞在断点时,可以通过指令打印变量的值或变量类型。

打印变量的值:

  • 指令:print(可缩写为p

  • 输出的变量需要格式化,具体格式化方式和C中输出格式化方式一致:

    格式化字符 说明
    /x 以十六进制的形式打印出整数。
    /d 以有符号、十进制的形式打印出整数。
    /u 以无符号、十进制的形式打印出整数。
    /o 以八进制的形式打印出整数。
    /t 以二进制的形式打印出整数。
    /f 以浮点数的形式打印变量或表达式的值。
    /c 以字符形式打印变量或表达式的值。
  • 语法格式如下:

    # print == p
    (gdb) p 变量名		# 默认是10进制
    
    # 如果变量是一个整形, 默认对应的值是以10进制格式输出, 其他格式请参考上表
    (gdb) p/fmt 变量名
    
  • 实例:

    # 默认输出
    (gdb) p argc
    $2 = 5
    # 格式化输出
    (gdb) p/x argc
    $3 = 0x5
    

打印变量类型

  • 指令:ptype

  • 语法格式:

    (gdb) ptype 变量名
    
  • EG:

    (gdb) ptype argc
    type = int
    

5.3 自动打印信息

原因:在一些应用场景中,比如for循环,每次都手动输出变量进行追踪是较为复杂的,这时就需要自动打印信息的功能。

5.3.1 变量名自动显示

打印时机:每当程序暂停执行时,gdb就会帮我们把设置自动显示的变量打印出来。

指令:display

语法:

# 在变量有效范围内,打印变量的值(设置一次,以后就会自动显现)
(gdb) display 变量名
# 按照指定格式打印变量(这里的format和print的format表一致)
(gdb) display/fmt 变量名

5.3.2 查看自动显示列表

说明:所有通过display设置为自动打印的变量,都会被记录到一张表内,可以通过info diaplay查看。

指令:info diaplay(其中的info可以简写为i

EG:

(gdb) info display
Auto-display expressions now in effect:
Num Enb Expression
1:   y  i
2:   y  array[i]
3:   y  /x array[i]

展现出的信息含义:

  • Num:变量或表达式的编号,GDB编译器为每一个变量或表达式都分配有唯一的编号。后续操作也是通过这个编号。
  • Enb:当前变量的撞他。生效表示为y,失效表示为n
  • Expression:被自动打印的变量或表达式的名字。

5.3.3 取消自动显示

有两种方式:

  1. 删除自动显示的变量或表达式:

    # 通过undisplay
    # 指定编号删除
    (gdb) undisplay num1 num2...
    # 删除一定范围内连续的变量或表达式
    (gdb) undisplay num1-num2
    # 通过delete
    (gdb) delete display num1 num2...
    (gdb) delete display num1-num2
    
  2. 修改自动显示变量的状态,

    • 禁用:

      # 通过disable禁用
      (gdb) disable display num1 num2...
      (gdb) disable display num1-num2
      
    • 启用:

      # 通过enable启用
      (gdb) enable display num1 num2...
      (gdb) enable display num1-num2
      

5.4 单步调试

简介:所谓单步调试,就是程序阻塞到了某个断点后,对程序进行的一些操作。

5.4.1 step

step可以缩写为s,命令执行一次,代码向下执行一行。如果这一行是函数调用,那么程序会进入到函数内部。

5.4.2 finish

如果step单步进入函数内部,通过该命令可以跳出函数体。前提是该函数内没有有效的断点

5.4.3 next

next可以缩写为n。其功能与step相似,但next不会进入到函数的内部,而是直接将函数运行完毕。

5.4.4 until

通过until可以跳出循环体。这需要满足以下条件:

  • 循环体内不能有有效的断点。
  • 必须在循环体的开始/结束部分执行该命令。

5.5 设置变量值

语法:set var 变量名=变量值

应用场景:在调试程序时,有时我们需要程序中的某个变量等于某个值,但短时间内又很难达到,这时我们就可以通过该指令修改该变量的值,从而达到目的。例如在for循环中,我们可以通过设置循环因子的值达到跳出循环的目的。