Linux快速入门之 GDB调试 (09)

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 命令 可以直接跳出某个循环体 ,提高调试效率

使用时需要满足条件:

  1. 需要跳出的循环体内部不能有有效的断点
  2. 必须在循环体的开始/结束行执行该命令
#跳出循环体
(gdb) until 

设置变量值调试

在调试程序的时候,我们需要在某个变量等于某个特殊值的时候查看程序的运行状态,但是通过程序运行让变量等于这个值又非常困难,这种情况下就可以在 gdb 中直接对这个变量进行值的设置,或者是在单步调试的时候通过设置循环因子的值直接跳出某个循环,值设置的命令格式为: set var 变量名=值

# 可以在循环中使用, 直接设置循环因子的值
# 假设某个变量的值在程序中==90的概率是5%, 这时候可以直接通过命令将这个变量值设置为90
(gdb) set var 变量名=

=

如果通过单步调试进入函数体内部,想要跳出函数体,可以通过 finish 命令 如果想跳出函数体 ,需要保证函数体内不能有有效断点 ,否则无法跳出

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

until 命令

通过until 命令 可以直接跳出某个循环体 ,提高调试效率

使用时需要满足条件:

  1. 需要跳出的循环体内部不能有有效的断点
  2. 必须在循环体的开始/结束行执行该命令
#跳出循环体
(gdb) until 

设置变量值调试

在调试程序的时候,我们需要在某个变量等于某个特殊值的时候查看程序的运行状态,但是通过程序运行让变量等于这个值又非常困难,这种情况下就可以在 gdb 中直接对这个变量进行值的设置,或者是在单步调试的时候通过设置循环因子的值直接跳出某个循环,值设置的命令格式为: set var 变量名=值

# 可以在循环中使用, 直接设置循环因子的值
# 假设某个变量的值在程序中==90的概率是5%, 这时候可以直接通过命令将这个变量值设置为90
(gdb) set var 变量名=
  • 6
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值