gdb 是由 GNU 软件系统社区提供的调试器。可以监控程序执行的每一个细节,包括变量的值、函数的调用过程、内存中数据、线程的调度等,从而发现隐藏的错误或者低效的代码作用

// q.cpp
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
 
#define NUM 10
 
// argc, argv 是命令行参数
// 启动应用程序的时候
int main(int argc, char* argv[])
{
    printf("参数个数: %d\n", argc);
    for(int i=0; i<argc; ++i)
    {
        printf("%d\n", NUM);
        printf("参数 %d: %s\n", i, argv[i]);
    }
    return 0;
}

调试准备

如果是为了进行调试而编译时, 必须要打开调试选项 (-g)。即

g++ -g q.cpp -o q.exe

 程序分为debug版本和release版本。只要debug版本才能进行调试。在Linux中,用gcc/g++生成的软件程序是release版本!(这里顺便复习一下:gcc默认:动态链接、默认release)所以,我们需要将其变成debug。我们在编译软件的时候,加上-g。

启动 gdb

gdb 可执行程序的名字

gdb q.exe

一定要注意 gdb进程启动之后, 需要的被调试的应用程序是没有执行的.

命令行传参

有些程序在启动的时候需要传递命令行参数,如果要调试这类程序,这些命令行参数必须要在应用程序启动之前通过调试程序的 gdb 进程传递进去.

先执行完前面2步(调试准备和启动gdb),然后调用set argc设置传递参数。

 (gdb) set args 参数1 参数2 .... ..   

具体可以是 set argc aa bb 33

另外调用 show args  (gdb)     查看设置的命令行参数

gdb 中启动程序

前面调用的gdb  q.exe 不算是启动程序。在 gdb 中启动要调试的应用程序有两种方式,一种是使用 run 命令,另一种是使用 start 命令启动。在整个 gdb 调试过程中,启动应用程序的命令只能使用一次。

(gdb) run  或者 (gdb) start

如果想让程序 start 之后继续运行,或者在断点处继续运行,可以使用 continue 命令,可以简写为 c。

(gdb) continue

退出 gdb

退出 gdb 调试,就是终止 gdb 进程,需要使用 quit 命令,可以缩写为 q。

(gdb) quit

查看代码

查看代码的命令叫做 list 可以缩写为 l, 通过这个命令我们可以查看项目中任意一个文件中的内容,并且还可以通过文件行号,函数名等方式查看。

查看当前文件的代码

默认情况下通过 list 查看到代码信息位于程序入口函数 main 对应的的那个文件中。

# 使用 list 和使用 l 都可以
# 从第一行开始显示
(gdb) list 
# 列值这行号对应的上下文代码, 默认情况下只显示10行内容
(gdb) list 行号
# 显示这个函数的上下文内容, 默认显示10行
(gdb) list 函数名

设置显示的行数

通过 set listsize 设置,同样如果想查看当前显示的行数可以通过 show listsize 查看

# 以下两个命令中的 listsize 都可以写成 list
(gdb) set listsize 行数
# 查看当前list一次显示的行数
(gdb) show listsize

切换文件

只需要在 list 命令后边将要查看的文件名指定出来就可以了,切换命令执行完毕之后,这个文件就变成了当前文件。

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

设置断点

设置断点的命令叫做 break 可以缩写为 b

断点的设置有两种方式一种是常规断点,程序只要运行到这个位置就会被阻塞,还有一种叫条件断点,只有指定的条件被满足了程序才会在断点处阻塞。

调试程序的断点可以设置到某个具体的行,也可以设置到某个函数上。

设置普通断点到当前文件
(gdb) b 行号
(gdb) b 函数名  
     # 停止在函数的第一行

设置普通断点到某个非当前文件上
(gdb) b 文件名:行号
(gdb) b 文件名:函数名        # 停止在函数的第一行

设置条件断点
# 通常情况下, 在循环中条件断点用的比较多
(gdb) b 行数 if 变量名==某个值

查看断点

断点设置完毕之后,可以通过 info break 命令查看设置的断点信息,其中 info 可以缩写为 i。所以整个缩写是 (gdb)  i b

(gdb) i b
Num     Type           Disp Enb Address            What
1       breakpoint     keep y   0x0000000000000659 in main(int, char**) at q.cpp:13
3       breakpoint     keep y   0x00000000000006c7 in main(int, char**) at q.cpp:19

在显示的断点信息中有一些属性需要在其他操作中被使用,下面介绍一下:

  • Num: 断点的编号,删除断点或者设置断点状态的时候都需要使用
  • Enb: 当前断点的状态,y 表示断点可用,n 表示断点不可用
  • What: 描述断点被设置在了哪个文件的哪一行或者哪个函数上

  删除断点

删除命令是 delete 断点编号 , 这个 delete 可以简写为 del 也可以再简写为 d

删除断点的方式有两种: 删除(一个或者多个)指定断点或者删除一个连续的断点区间。

(gdb) d 2
(gdb) i b
Num     Type           Disp Enb Address            What
1       breakpoint     keep y   0x0000000000000659 in main(int, char**) at q.cpp:13

设置断点状态 

设置断点为无效状态命令为 disable(简写dis) 断点编号,当需要的时候再将其设置回可用状态,设置命令为 enable(ena) 断点编号。

(gdb) dis 1
(gdb) i b
Num     Type           Disp Enb Address            What
1       breakpoint     keep n   0x0000000000000659 in main(int, char**) at q.cpp:13
(gdb) ena 1
(gdb) i b
Num     Type           Disp Enb Address            What
1       breakpoint     keep y   0x0000000000000659 in main(int, char**) at q.cpp:13

打印变量值 

在 gdb 调试的时候如果需要打印变量的值, 使用的命令是 print, 可缩写为 p。如果打印的变量是整数还可以指定输出的整数的格式,格式化输出的整数对应的字符表如下:

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

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

(gdb) p argc
$1 = 4

打印变量类型

如果在调试过程中需要查看某个变量的类型,可以使用命令 ptype
(gdb) ptype 变量名

设置变量名自动显示

display 命令也用于调试阶段查看某个变量或表达式的值,它们的区别是,使用 display 命令查看变量或表达式的值,每当程序暂停执行(例如单步执行)时,GDB 调试器都会自动帮我们打印出来,而 print 命令则不会。因此,当我们想频繁查看某个变量或表达式的值从而观察它的变化情况时,使用 display 命令可以一劳永逸

# 在变量的有效取值范围内, 自动打印变量的值(设置一次, 以后就会自动显示)
(gdb) display 变量名
 
# 以指定的整形格式打印变量的值, 关于 fmt 的取值, 请参考 print 命令
(gdb) display /fmt 变量名

(gdb) display argc
1: argc = 4

 查看自动显示列表

对于使用 display 命令查看的目标变量或表达式,都会被记录在一张列表(称为自动显示列表)中。通过执行 info dispaly 命令 简称 i d

(gdb) info display
Auto-display expressions now in effect:
Num Enb Expression
1:   y  argc
  • Num : 变量或表达式的编号,GDB 调试器为每个变量或表达式都分配有唯一的编号
  • Enb : 表示当前变量(表达式)是处于激活状态还是禁用状态,如果处于激活状态(用 y 表示),则每次程序停止执行,该变量的值都会被打印出来;反之,如果处于禁用状态(用 n 表示),则该变量(表达式)的值不会被打印。
  • Expression :被自动打印值的变量或表达式的名字。

 取消自动显示

对于不需要再打印值的变量或表达式,可以将其删除或者禁用。

删除自动显示列表中的变量或表达式

# 命令中的 num 是通过 info display 得到的编号, 编号可以是一个或者多个
(gdb) undisplay num [num1 ...]
# num1 - numN 表示一个范围
(gdb) undisplay num1-numN
 
或者是 (gdb) delete display num [num1 ...]
(gdb) delete display num1-numN

如果不想删除自动显示的变量,也可以禁用自动显示列表中处于激活状态下的变量或表达式

# 命令中的 num 是通过 info display 得到的编号, 编号可以是一个或者多个
(gdb) disable display num [num1 ...]
# num1 - numN 表示一个范围
(gdb) disable display num1-numN

当需要启用自动显示列表中被禁用的变量或表达式时

# 命令中的 num 是通过 info display 得到的编号, 编号可以是一个或者多个
(gdb) enable  display num [num1 ...]
# num1 - numN 表示一个范围
(gdb) disable display num1-numN

单步调试

step

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

# 从当前代码行位置, 一次调试当前行下的每一行代码
# step == s
# 如果这一行是函数调用, 执行这个命令, 就可以进入到函数体的内部
(gdb) step

finish

如果通过 s 单步调试进入到函数内部,想要跳出这个函数体, 可以执行 finish 命令。如果想要跳出函数体必须要保证函数体内不能有有效断点,否则无法跳出。

# 如果通过 s 单步调试进入到函数内部, 想要跳出这个函数体
(gdb) finish

next

next 命令和 step 命令功能是相似的,只是在使用 next 调试程序的时候不会进入到函数体内部,next 可以缩写为 n

# next == n
# 如果这一行是函数调用, 执行这个命令, 不会进入到函数体的内部
(gdb) next

until

通过 until 命令可以直接跳出某个循环体,这样就能提高调试效率了。如果想直接从循环体中跳出,必须要满足以下的条件,否则命令不会生效:

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

(gdb) until

设置变量值

set var 变量名=值

1: argc = 4
(gdb) set var argc = 5
(gdb) p argc
$3 = 5

Logo

更多推荐