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选项后附加数字123来指定在代码中加入调试信息的多少。默认的级别是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的监控下运行,如下图:

ggelua vs code打开_linux

  • 输入:run
  • ggelua vs code打开_GDB_02

  • runstart 指令都可以用来在 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,一条一条简单语句执行。

ggelua vs code打开_ggelua vs code打开_03

  • 单步调试:step,简写s,若在某个函数断点处,则进入此函数。

ggelua vs code打开_c++_04

  • 退出当前断点进入下一个断点,或继续运行。
    命令: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指令执行包含调试信息的可执行文件,看到以下输出

ggelua vs code打开_ggelua vs code打开_05


此时可以正常进入调试

4.3 调试过程

直接执行

run

或者使用简写r,运行结果如下图

ggelua vs code打开_GDB_06


在第11行打断点,

ggelua vs code打开_vscode_07


也可以使用简写的方式,在第12行打断点

b 12

结果如下图

ggelua vs code打开_ggelua vs code打开_08


使用info breakpoints指令或者指令简写i b查看当前的断点,如下图结果

ggelua vs code打开_c++_09


使用run指令或者r指令执行程序,此时命中了第11行断点,如下图内容

ggelua vs code打开_ggelua vs code打开_10


此时可以查看变量的值,如查看i的值,可以使用print i或者p i指令,如下图

ggelua vs code打开_vscode_11


此时程序执行到第11行处,如果我们需要继续执行,输入continue指令并按回车即可,程序将执行到代码的第12行,如下图

ggelua vs code打开_c++_12

因为我们是在while循环体内,i的值将不断变化,如果我们需要跟踪i值的变化,需要输入display i指令即可,再输入continue指令让程序单步执行,每次都会打印i的值,如下图

ggelua vs code打开_vscode_13


可以观察到,通过循环,又回到了第一个断点,此时i的值已经变成了2。在gdb调试中,如果我们只按回车键,gdb将执行我们最后一次输入的指令,所以我们可以一直按回车键继续让程序单步执行。当断点执行都某个位置时,我们想要查看断点附近5行的代码,可以执行list指令进行查看,如下图

ggelua vs code打开_GDB_14