1. 前言
- GDB(GNU Debugger) 是一个用来调试C/C++程序的功能强大的调试器,是Linux系统开发C/C++最常用的调试器
- 程序员可以使用GDB来跟踪程序中的错误,从而减少程序员的工作量。
- Linux 开发C/C++ 一定要熟悉 GDB
- VSCode是通过调用GDB调试器来实现C/C++的调试工作的;
GDB主要功能:
- 设置断点(断点可以是条件表达式)
- 使程序在指定的代码行上暂停执行,便于观察
- 单步执行程序,便于调试
- 查看程序中变量值的变化
- 动态改变程序的执行环境
- 分析崩溃程序产生的core文件
2. 常用调试命令参数
调试开始:执行gdb [exefilename] ,进入gdb调试程序,其中exefilename为要调试的可执行文件名
1 ## 以下命令后括号内为命令的简化使用,比如run(r),直接输入命令 r 就代表命令run
2
3 $(gdb)help(h) # 查看命令帮助,具体命令查询在gdb中输入help + 命令
4
5 $(gdb)run(r) # 重新开始运行文件(run-text:加载文本文件,run-bin:加载二进制文件)
6
7 $(gdb)start # 单步执行,运行程序,停在第一行执行语句
8
9 $(gdb)list(l) # 查看原代码(list-n,从第n行开始查看代码。list+ 函数名:查看具体函数)
10
11 $(gdb)set # 设置变量的值
12
13 $(gdb)next(n) # 单步调试(逐过程,函数直接执行)
14
15 $(gdb)step(s) # 单步调试(逐语句:跳入自定义函数内部执行)
16
17 $(gdb)backtrace(bt) # 查看函数的调用的栈帧和层级关系
18
19 $(gdb)frame(f) # 切换函数的栈帧
20
21 $(gdb)info(i) # 查看函数内部局部变量的数值
22
23 $(gdb)finish # 结束当前函数,返回到函数调用点
24
25 $(gdb)continue(c) # 继续运行
26
27 $(gdb)print(p) # 打印值及地址
28
29 $(gdb)quit(q) # 退出gdb
30
31 $(gdb)break+num(b) # 在第num行设置断点
32
33 $(gdb)info breakpoints # 查看当前设置的所有断点
34
35 $(gdb)delete breakpoints num(d) # 删除第num个断点
36
37 $(gdb)display # 追踪查看具体变量值
38
39 $(gdb)undisplay # 取消追踪观察变量
40
41 $(gdb)watch # 被设置观察点的变量发生修改时,打印显示
42
43 $(gdb)i watch # 显示观察点
44
45 $(gdb)enable breakpoints # 启用断点
46
47 $(gdb)disable breakpoints # 禁用断点
48
49 $(gdb)x # 查看内存x/20xw 显示20个单元,16进制,4字节每单元
50
51 $(gdb)run argv[1] argv[2] # 调试时命令行传参
52
53 $(gdb)set follow-fork-mode child # Makefile项目管理:选择跟踪父子进程(fork())
3. 命令详解
当程序运行时出现问题时,通过GDB调试可以快速找到问题点,当然不是所有问题都能通过调试找到问题点。
在GCC编译程序时,需要加 -g
参数,可以通过在-g选项后附加数字1
、2
或3
来指定在代码中加入调试信息的多少。默认的级别是2(-g2)
,此时产生的调试信息包括扩展的符号表、行号、局部或外部变量信息
。级别3(-g3)包含级别2中的所有调试信息,以及源代码中定义的宏。级别1(-g1)不包含局部变量和与行号有关的调试信息
,因此只能够用于回溯跟踪和堆栈转储之用。
#include <stdio.h>
int main()
{
int a;
while(1)
{
printf("请输入a的值:\n");
scanf("%d",&a);
int b = 10/a;
printf("b = %d\n",b);
}
return 0;
}
3.1 编译
gcc -g demo.c -o demo
3.2 启动GDB
终端输入命令:
gdb demo # demo 是可执行文件
- 若有参数:
gdb --args demo paramet
,当GDB提示符出现的时候,表明GDB已经做好准备进行调试了,现在可以通过run命令让程序开始在GDB的监控下运行,如下图:
- 输入:
run
run
和start
指令都可以用来在GDB
调试器中启动程序,它们之间的区别是:- 默认情况下,
run
指令会一直执行程序,直到执行结束。如果程序中手动设置有断点,则 run 指令会执行程序至第一个断点处; start
指令会执行程序至 main() 主函数的起始位置,即在 main() 函数的第一行语句处停止执行(该行代码尚未执行)。
如果进程已经运行,可以根据进程号启动:gdb –p pid
3.3 断点
打断点
- 断点命令:
break
,也可以简写为b
- 某一行进行打断点(第
3
行):b 3
; 多个文件
,对某个文件的某一行打断点:b demo.cpp:4
,表示对demo.c文件4行打断点;对函数打断点
:b fun ,表示对fun()函数打断点多个文件
,对某个函数打断点:b demo.c:fun
;
查询断点
-
查询所有断点
:info b -
查询第n个断点
:info b n -
条件断点:
b demo.c:8 if Value = 10
删除断点:delete,简写d
-
删除所有的断点
:delete break -
删除某个断点 n为断点号
:delete break n - 删除设在n行的断点(多个文件与打断点一样):clear n
-
en/enable
启用某个断点 dis/disable
禁用某个断点
3. 4 打印
打印断点处某变量值,用命令 p;
- 16进制:p/x
- 二进制:p/t
3.5 单独调试
-
单步调试:
next ,简写为n
,一条一条简单语句执行。
-
单步调试
:step,简写s,若在某个函数断点处,则进入此函数。
退出当前断点进入下一个断点
,或继续运行。
命令:continue
,简写c
finish 退出进入的函数
,并且打印函数返回时的堆栈地址和返回值及参数值等信息,回到上一层调用函数until
在一个循环体内单步跟踪时,这个命令可以运行程序直到退出循环体,可简写为u
until + 行号 运行至某行
call + 行数 + (参数)
调用程序中可见的函数,并传递参数
如:call gdb_test(66)
3.6 显示源码
查看源码:list(l)
,默认显示10行
- l 函数名
- l 数组
3.7 设置/查看变量值(set/show)
-
修改变量值
:set 变量名=value -
查看变量值
:show 变量名
3.8 查看堆栈信息
当程序崩溃可以使用,where命令,查看死在那个位置
。
bt
,查看函数栈
f/frame
切换到当前调用线程的指定堆栈f 2
进入第2栈
3.9 查看线程
-
i threads
【查看所有线程】 -
t num
【进入num线程】 -
thread
【查当前线程】 -
thread 2
【切换到第二个线程】
4.【实战】命令行调试
4.1 准备代码
建一个C++源代码文件 sum.cpp,添加以下代码
#include <iostream>
using namespace std;
int main(int argc, char const *argv[])
{
int N = 100;
int sum = 0;
int i = 1;
while(i <= N){
sum = sum + i;
i = i + 1;
}
cout << "sum = " << sum <<endl;
cout << "The program is over " <<endl;
return 0;
}
4.2. 进入调试模式
在进入调试模式之前,先编译源代码,如下过程。
- 普通编译方式
g++ sum.cpp -o no_g.out
- 编译出
用于调试
的可执行文件
g++ -g sum.cpp -o yes_g.out
使用gdb
命令进行调试,如果我们调试 no_g.out
,将会提示错误
。因为no_g.out这个可执行文件不包含用于调试的信息
,输入quit
再按回车即可,通过gdb yes_g.out指令执行包含调试信息的可执行文件,看到以下输出
此时可以正常进入调试
4.3 调试过程
直接执行
run
或者使用简写r
,运行结果如下图
在第11行打断点,
也可以使用简写的方式,在第12行打断点
b 12
结果如下图
使用info breakpoints
指令或者指令简写i b
查看当前的断点,如下图结果
使用run
指令或者r
指令执行程序,此时命中了第11行断点,如下图内容
此时可以查看变量的值,如查看i的值,可以使用print i
或者p i
指令,如下图
此时程序执行到第11行处,如果我们需要继续执行,输入continue
指令并按回车即可,程序将执行到代码的第12行,如下图
因为我们是在while
循环体内,i的值将不断变化,如果我们需要跟踪i值的变化,需要输入display i
指令即可,再输入continue
指令让程序单步执行,每次都会打印i
的值,如下图
可以观察到,通过循环,又回到了第一个断点,此时i的值已经变成了2。在gdb调试中,如果我们只按回车键,gdb将执行我们最后一次输入的指令,所以我们可以一直按回车键继续让程序单步执行。当断点执行都某个位置时,我们想要查看断点附近5行
的代码,可以执行list
指令进行查看,如下图