9.GDB调试
文章目录
gdb 是用来调试解决bug的工具 , GDB是一套字符界面的程序集,可以使用命令 gdb 加载要调试的程序
同gcc配套组成一套完整的开发环境,可移植性好。gdb/gcc 是Linux和类Unix系统的标准开发环境。
9.1 调试前准备
如果项目程序为了调试而编译,
必须打开调试选项(-g)
不影响程序的情况下
关闭编译器优化选择(-O0)
打开所有warning (可以避免一些bug)
(-Wall)
# -g 将调试信息写入到可执行程序中
gcc -g hello.c -o Hello
#编译不加 -g 不能用于调试
gcc hello.c -o Hello2
9.2 启动 推出 传参 gdb
9.2.1 启动gdb
gdb是一个程序用于调式的进程,需要先打开!gdb进程启动之后 ,需要被调试的程序并没有执行,打开终端,切换到被调试程序路径,执行如下命令
#gdb 程序启动了 ,但是可执行程序并没有执行
$ gdb 可执行程序名字
#举例
$ gdb Hello
(gdb) #gdb 等待输入调试的相关命令
9.2.2 命令行传参
部分程序在启动时需要传入命令行参数 ,在调试这类程序时必须在 程序启动前 通过调试程序的gdb进程传递进去
(set args .....) ,
执行以下命令
举例需要传参函数
// args.c
#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;
}
#第一步 :编译出带调试信息的可执行程序
$ gcc -g args.c -o app
#第二步 : 启动gdb进程 ,指定需要调试的应用程序名
$ gdb app
(gdb)
#启动app前 , 设置命令参数 , set args .....
$ (gdb) set args 参数1 参数2 ...........
#查看设置的命令行参数
(gdb) show args
操作实例
liu@liu-Ubuntu:~/StutyLinux/GDB$ gcc -g args.c -o app
liu@liu-Ubuntu:~/StutyLinux/GDB$ gdb app
GNU gdb (Ubuntu 8.1.1-0ubuntu1) 8.1.1
Copyright (C) 2018 Free Software Foundation, Inc.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from app...done.
(gdb) set args 1 abc #设置命令参数
(gdb) show args #查看命令参数
Argument list to give program being debugged when it is started is "1 abc".
9.2.3 gdb 中启动 、退出
在gdb中启动需要调试的应用程序有两种方式 :
run 命令 、 start 命令
在整个gdb调试过程中 ,启动命令只能使用一次
run :
缩写r
,如果程序中设置断点,会停在第一个断点处,如没有断点,程序执行完start:
启动程序 , 阻塞在 main 函数第一行 ,等待输入的后续其他gdb命令continue:
缩写c
,在使用start命令后,或在断点处继续运行 , 可以使用 continue 命令quit:
缩写q
, 退出gdb调试,终止gdb线程
liu@liu-Ubuntu:~/StutyLinux/GDB$ gdb app
GNU gdb (Ubuntu 8.1.1-0ubuntu1) 8.1.1
Copyright (C) 2018 Free Software Foundation, Inc.
Reading symbols from app...done.
(gdb) set args 1 abc #设置 参数命令
(gdb) show args #查看 参数
Argument list to give program being debugged when it is started is "1 abc".
(gdb) start #开启gdb调试
Temporary breakpoint 1 at 0x659: file args.c, line 13.
Starting program: /home/liu/StutyLinux/GDB/app 1 abc
Temporary breakpoint 1, main (argc=3, argv=0x7fffffffdf38) at args.c:13
13 printf("参数个数: %d\n", argc); #阻塞在main 函数第一行
(gdb) c
Continuing.
参数个数: 3
10
参数 0: /home/liu/StutyLinux/GDB/app
10
参数 1: 1
10
参数 2: abc
(gdb)q #推出gdb 调试
9.3 查看代码
gdb调试没有IDE完善的可视化窗口*(IDE牛逼,将对应可执行文件和源码对应可视化)* , 默认情况下通过
list
,查看位于入口函数main
对应的文件代码信息 , 如果不切换main函数
所在的文件就是当前文件,反之切换文件就对于当前文件夹
9.3.1 当前文件夹
项目中有很多文件夹, 默认情况下,gdb 通过 list
查看的是位于入口函数 main 对于的文件
#使用 list 或者缩写 l
#默认显示第一行
(gdb) list
#显示以 行数中心 的上下文 10行(默认)
(gdb) list 行号
#显示以 函数名中心 的上下文 10行(默认)
(gdb) list 函数名
通过 list 去查看文件代码,默认只显示 10 行,如果还想继续查看后边的内容,可以继续执行 list 命令,也可以直接回车(再次执行上一次执行的那个 gdb 命令)。
9.3.2 切换文件
查看文件内容的时候,需要频繁切换文件,
在 list 命令后指定需要查看的文件名
,在完成此操作后,这个文件会变成当前文件
#切换到指定的文件 ,指定行号对应的上下文 (默认 10行 内容)
(gdb) list 文件名 :行号
#切换到指定的文件 ,指定函数对应的上下文 (默认 10行 内容)
(gdb) list 文件名 :函数名
9.3.2 修改默认查看行数
默认查看行数 10 行,如果想继续查看后面内容 ,回车键(再次执行上次操作)
当然也可以修改默认一次显示行数 ,
set listsize 命令
可以缩写为set list
,查看当前 一次显示行数
show listsize 命令
,可以缩写show list
#查看当前一次显示的行数
(gdb) show list
#修改当前一次显示的行数
(gdb) set list 行数
9.4 断点操作
在程序调式中,调试某一行或得到某一个变量的运行状态下的实际值 ,需要在这一行设置断点,程序运行到此行会阻塞,在通过gdb 的调试命令获取信息。
设置断点的命令 break
,或缩写 b
9.4.1 设置断点
断点可以设置在某一行 或 某个函数上 ,断点设置有两种方式 :常规断点 、 条件断点
常规断点:
程序运行到这个位置就会被阻塞条件断点:
只有满足指定的条件才会在此断点阻塞
设置普通断点
#设置普通断点在 当前文件夹 使用break 或 b
(gdb) b 行号1 行号2 / 行数1 - 行数2
(gdb) b 函数名 #阻塞在函数第一行
#设置普通断点在 指定文件夹
(gdb) b 文件名 :行号
(gdb) b 文件名 :函数名
设置条件断点
# 必须要满足某个条件, 程序才会停在这个断点的位置上
# 通常情况下, 在循环中条件断点用的比较多
(gdb) b 行数 if 变量名==某个值
9.4.2 查看断点
断点设置完成之后,可通过 info break 命令
查看设置的断点信息 ,可以缩写为 i b
# info == i
# 查看设置的断点信息
(gdb) i b #info break
# 举例
(gdb) i b
Num Type Disp Enb Address What
1 breakpoint keep y 0x0000000000400cb5 in main() at test.cpp:12
2 breakpoint keep y 0x0000000000400cbd in main() at test.cpp:13
3 breakpoint keep y 0x0000000000400cec in main() at test.cpp:18
4 breakpoint keep y 0x00000000004009a5 in insertionSort(int*, int)
at insert.cpp:8
5 breakpoint keep y 0x0000000000400cdd in main() at test.cpp:16
6 breakpoint keep y 0x00000000004009e5 in insertionSort(int*, int)
at insert.cpp:16
断点信息中重点属性
Num:
断点的编号,删除断点或者设置断点状态的时候都需要使用(重要)Enb:
当前断点的状态,y 表示断点可用,n 表示断点不可用What:
描述断点被设置在了哪个文件的哪一行或者哪个函数上
9.4.3 删除断点
如果不需要某一个断点,可以通过
delete 断点编号
,其中delete
可以简写为del
/d
删除方式两种:
删除(一个或多个)指定断点 、 删除一个连续的断点区间
#删除断点 delete 或者 del \ d
(gdb) d 断点编号1 断点编号2 断点编号3 .。。。。。
#删除一个范围 num1 - numN
(gdb) d num1 - num5 #删除断点1号到5号
9.4.4 设置断点状态
相比删除断点,设置断点状态更加灵活 , 在不需要断点时可将其状态设置为 不可用状态,设置命令为
disable 断点编号
; 在需要的时候在将其设置为可用状态,设置命令为enable 断点编号
设置断点无效
#将断点设置失效 ,gdb调试过程中不会在此断点处阻塞
(gdb) disable 断点编号1 断点编号2 ......
(gdb) dis num1 - numN #删除断点编号1 - 编号N
无效断点生效
#查看断点信息
(gdb) i b
#断点生效
(gdb) enable 断点编号1 断点编号2 ......
(gdb) ena num1 - numN #删除断点编号1 - 编号N
9.5 调试命令
当调试的程序在断点阻塞但是又希望程序继续执行时, 可以使用
continue 命令
,缩写 c
,程序会继续执行,知道遇到下一个有效断点
#阻塞后继续执行 Continue / c
(gdb) continue
9.5.1 手动打印变量信息
程序阻塞后,可以通过
print 命令
,缩写 p
打印变量的名字或变量类型 ,并且跟踪打印某变量的值
打印变量值
在gdb调试的时候如需要打印变量的值 ,如打印的变量是整数 还可以指定输出的整数格式;
格式化输出的整数对应的字符表如下:
格式化字符 | 说明 |
---|---|
/x | 以十六进制的形式打印出整数。 |
/d | 以有符号、十进制的形式打印出整数 |
/u | 以无符号、十进制的形式打印出整数。 |
/o | 以八进制的形式打印出整数。 |
/t | 以二进制的形式打印出整数。 |
/f | 以浮点数的形式打印变量或表达式的值。 |
/c | 以字符形式打印变量或表达式的值。 |
#使用 print 打印变量
(gdb) p 变量名
#如果变量名是一个整形 , 默认以十进制输出 , 其他输出格式参考上表
(gdb) p/t 变量名
举例
# 举例
(gdb) p i # 10进制
$5 = 3
(gdb) p/x i # 16进制
$6 = 0x3
(gdb) p/o i # 8进制
$7 = 03
打印变量类型
如果调试过程中需要获得某变量名的类型 , 可以通过 ptype 命令
#使用 ptype 输出变量类型
(gdb) ptype 变量名
#举例
(gdb) ptype i
type = int
(gdb) ptype array[i]
type = int
(gdb) ptype array
type = int [12]
9.5.2 自动打印信息
display 命令
用于调试时查看变量或表达式的值 , 和print的区别是每当程序暂停执行(例如单步执行)时,GDB 调试器都会自动帮我们打印出来,而 print 命令则不会。因此,当我们想频繁查看某个变量或表达式的值从而观察它的变化情况时,使用 display 命令可以一劳永逸。display 命令没有缩写形式,常用的语法格式如下 2 种:
#在变量的范围内 , 自动打印变量的值 (设置一次后自动打印)
(gdb) display 变量名
#以指定的输出格式打印变量的值
(gdb) display/f 变量名
查看自动显示列表
使用 display
自动显示的变量都会记录在一张列表中 , 通过 info diaplay 命令
查看该列表信息
# 打印 自定显示的变量信息
(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 :
被自动打印值的变量或表达式的名字。
取消自动显示
当不在需要打印变量/表达式的值 , 可以将其删除或禁用
删除自动显示的变量
# 命令中的 num 是通过 info display 得到的编号, 编号可以是一个或者多个
(gdb) undisplay num [num1 ...]
# num1 - numN 表示一个范围
(gdb) undisplay num1-numN
#删除自动显示的变量
(gdb) delete display num [num1 ...]
(gdb) delete display num1-numN
禁止自动显示状态
当不想删除自动显示变量,可以通过更灵活的方式改变其状态,达到相同目的
#使自动显示的变量状态设置为禁用
(gdb) disable display num [num1 ...]
# num1 - numN 表示一个范围
(gdb) disable display num1-numN
启动自动显示状态
# 将禁止状态下的自动变量设置为 启动状态
(gdb) enable display num [num1 ...]
# num1 - numN 表示一个范围
(gdb) disable display num1-numN
9.6 单步调试
当程序阻塞在某个断点上后, 可以通过以下命令单步调试
step 命令
step 命令 可缩写为 s
,该命令被执行一次代码向下执行一行,如果这一行被函数调用,程序进入函数体内部
# 从当前代码行位置, 一次调试当前行下的每一行代码
# step == s
# 如果这一行是函数调用, 执行这个命令, 就可以进入到函数体的内部
(gdb) step
next 命令
next 命令 缩写 n
,该命令和 step 命令很相似 ,但是使用next不会进入函数体内部
# next == n
# 如果这一行是函数调用, 执行这个命令, 不会进入到函数体的内部
(gdb) next
finsh 命令
如果通过单步调试进入函数体内部,想要跳出函数体,可以通过
finish 命令
如果想跳出函数体 ,需要保证函数体内不能有有效断点 ,否则无法跳出
# 如果通过 s 单步调试进入到函数内部, 想要跳出这个函数体
(gdb) finish
until 命令
通过until 命令
可以直接跳出某个循环体 ,提高调试效率
使用时需要满足条件:
- 需要跳出的循环体内部不能有有效的断点
- 必须在循环体的开始/结束行执行该命令
#跳出循环体
(gdb) until
设置变量值调试
在调试程序的时候,我们需要在某个变量等于某个特殊值的时候查看程序的运行状态,但是通过程序运行让变量等于这个值又非常困难,这种情况下就可以在 gdb 中直接对这个变量进行值的设置,或者是在单步调试的时候通过设置循环因子的值直接跳出某个循环,值设置的命令格式为
: set var 变量名=值
# 可以在循环中使用, 直接设置循环因子的值
# 假设某个变量的值在程序中==90的概率是5%, 这时候可以直接通过命令将这个变量值设置为90
(gdb) set var 变量名=值
=
如果通过单步调试进入函数体内部,想要跳出函数体,可以通过
finish 命令
如果想跳出函数体 ,需要保证函数体内不能有有效断点 ,否则无法跳出
# 如果通过 s 单步调试进入到函数内部, 想要跳出这个函数体
(gdb) finish
until 命令
通过until 命令
可以直接跳出某个循环体 ,提高调试效率
使用时需要满足条件:
- 需要跳出的循环体内部不能有有效的断点
- 必须在循环体的开始/结束行执行该命令
#跳出循环体
(gdb) until
设置变量值调试
在调试程序的时候,我们需要在某个变量等于某个特殊值的时候查看程序的运行状态,但是通过程序运行让变量等于这个值又非常困难,这种情况下就可以在 gdb 中直接对这个变量进行值的设置,或者是在单步调试的时候通过设置循环因子的值直接跳出某个循环,值设置的命令格式为
: set var 变量名=值
# 可以在循环中使用, 直接设置循环因子的值
# 假设某个变量的值在程序中==90的概率是5%, 这时候可以直接通过命令将这个变量值设置为90
(gdb) set var 变量名=值