时间轴

2025-06-24

init


启动gdb

编译阶段:加入调试信息

为了让 GDB 能看到函数名、变量名、源码行号,编译时必须加上 -g 参数

1
2
gcc -g hello.c -o hello        # 对 C 程序
g++ -g hello.cpp -o hello # 对 C++ 程序

否则 GDB 只能看到汇编和内存地址,无法进行源码级调试。


启动方式

调试一个程序

1
gdb ./program

设置程序运行参数

  • 设置运行参数(如命令行参数)

    1
    set args 10 20 30
  • 查看当前设置的参数

    1
    show args

设置运行环境变量

  • 设置程序运行路径(用于找可执行文件)

    1
    path /your/bin/dir
  • 查看运行路径设置

    1
    show paths
  • 设置环境变量(例如传给 main() 程序用的环境):

    1
    set environment USER=yourname
  • 查看环境变量

    1
    2
    show environment
    show environment USER

设置工作目录

设置工作目录指的是程序运行时的当前目录

  • 切换当前目录(等同于 shell 的 cd):

    1
    cd /path/to/dir
  • 查看当前目录

    1
    pwd

控制程序的输入输出

  • 查看程序绑定的终端信息

    1
    info terminal
  • 重定向输出(如保存输出到文件)

    1
    run > output.txt
  • 指定程序输入输出使用的终端设备

    1
    tty /dev/pts/1

调试 core dump 文件

core dump是程序崩溃后的转储

1
gdb ./program core

打开core文件生成功能

Linux 默认没有打开core文件生成功能,也就是发生段错误时不会core dumped。可以通过以下命令打开core文件的生成:

1
2
# 不限制产生 core 的大小
ulimit -c unlimited

unlimited 意思是系统不限制core文件的大小,只要有足够的磁盘空间,会转存程序所占用的全部内存,如果需要限制系统产生 core的大小,可以使用以下命令:

1
2
# core 最大限制大小为 409600 字节
ulimit -c 409600

关闭core文件生成功能

把核心转储功能关闭,只需要将限制大小设为0 即可:

1
ulimit -c 0

注意,如果只是输入命令“ulimit -c unlimited”,这只会在当前终端有效,退出终端或者打开一个新的终端时是无效的

例子:

编写一个简单的C程序,人为制造一个Segmentation fault错误:

1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>
#include <stdlib.h>

int main(int argc, char **argv)
{
int *p = NULL;

// 给一个NULL指令赋值,会产生 Segmentation fault 错误
*p = 100;

return 0;
}

上述代码中定义了一个空指针变量P,然后给空指针P赋值,运行程序就会产生一个段错误

开启了核心转储后,就会产生一个core 文件。

1
2
3
4
5
# 编译 hello.c 生成 hello 程序
gcc -o hello hello.c -g

# 运行该程序
./hello

运行后,我们可以看到 Segmentation fault (core dumped) 提示信息,表示已经在当前目录下产生了一个core文件:

调试正在运行的程序

1
gdb ./program <PID>
  1. 直接指定 PID 启动 GDB(需要有可执行程序路径):
1
gdb ./program <PID>
  1. 在 GDB 内 attach 到某个 PID
1
2
(gdb) attach <PID>
(gdb) detach # 取消 attach

常用启动参数

参数 含义
-s-symbols <file> 指定符号表文件
-se <file> 指定符号表文件,并关联到可执行文件
-c-core <file> 指定 core dump 文件用于调试
-d-directory <dir> 添加源码搜索路径(默认用 $PATH

退出输入quit(q)即可

gdb中运行Shell

在 GDB 中可以直接运行操作系统的命令,方法是:

1
(gdb) shell <命令字符串>

example

1
2
(gdb) shell ls -l
(gdb) shell cat input.txt

这会在 GDB 内部启动你系统的 shell(由环境变量 SHELL 决定),然后执行你写的命令。

GDB 也内置了一个命令:

1
(gdb) make <参数>

它本质上等价于:

1
(gdb) shell make <参数>

也就是说,它会调用系统的 make 工具来重新编译程序,非常方便调试时快速修改代码后重新 build。

调试程序

断点 (breakpoint)

设置断点

按函数名设置断点

1
break function
  • 在指定函数的入口处停下。
  • 对于 C++ 可以写成:
    • break ClassName::Function
    • break function(type1, type2)(如果重载)

按行号设置断点

1
break 42
  • 在当前源文件的第 42 行 设断点。

相对当前行设置断点

1
2
break +5     // 当前行之后5行
break -3 // 当前行之前3行

指定文件 + 行号

1
break filename.c:42
  • filename.c 的第 42 行设置断点。

指定文件 + 函数名

1
break filename.c:func
  • filename.cfunc 函数的入口处设置断点。

按地址设置断点

常用于汇编调试

1
break *0x4007d0
  • 在程序内存地址 0x4007d0 处设置断点。

设置条件断点

1
break func if i == 100
  • 当变量 i == 100 且执行到 func 函数时才停下。

设置下一条语句的断点(无参数)

1
break
  • 在“下一条将要执行”的语句处设断点。

查看断点

查看所有断点

1
info breakpoints

查看指定编号的断点

1
info break 3

删除断点

删除某一个断点

1
delete <编号>
  • 例如:delete 1 表示删除编号为 1 的断点。

删除多个断点

1
delete 1 2 3
  • 同时删除断点 1、2 和 3。

删除所有断点

1
delete
  • 不加参数表示删除所有断点。GDB 会提示你确认(输入 y)。

观察点 (watchpoint)

设置观察点

watch <expr>

  • 用途:当表达式或变量 expr值被改变 时,程序会暂停。

  • 示例

    1
    watch x

    当变量 x 的值发生变化时暂停。

rwatch <expr>

  • 用途:当表达式或变量 expr读取时,程序暂停。

  • 示例

    1
    rwatch y

    当变量 y 被读取时暂停程序。

awatch <expr>

  • 用途:当表达式或变量 expr读取或写入时,程序暂停。

  • 示例

    1
    awatch z

    当变量 z 被读取或写入时都暂停。

查看当前观察点

1
info watchpoints
  • 显示所有设置的观察点(类似 info breakpoints)。

删除观察点

1
delete <编号>
  • 与删除断点的方式一样。

注意事项

  • 观察点依赖于 目标架构是否支持硬件观察点(大多数支持)。
  • 不支持的情况下,GDB 可能无法设置 watchrwatch 等。
  • 观察点数量受限,一般比断点少(通常是 4 个)。

捕捉点 (catchpoint)

命令格式

1
catch <event>

也可以使用一次性的捕捉点:

1
tcatch <event>

常见 catchpoint 类型

事件类型 描述说明
throw 捕捉 C++ 程序抛出异常的位置。
catch 捕捉 C++ 程序捕获异常的位置。
exec 捕捉程序调用 exec() 系统调用(替换进程映像)。
fork 捕捉程序调用 fork() 系统调用(创建子进程)。
vfork 捕捉 vfork() 调用(特殊类型的 fork())。
load 捕捉动态链接库的加载事件。
unload 捕捉动态链接库的卸载事件。

示例

1
catch throw

在 C++ 抛出异常时中断程序。

1
catch fork

在程序调用 fork() 时中断。

1
tcatch exec

设置一次性的捕捉点,在程序调用 exec() 系统调用时暂停,之后自动移除。

程序停止点清除

清除停止点(clear)

1
2
3
4
5
clear                    # 清除当前位置所有停止点
clear <function> # 清除函数上的所有停止点
clear <filename:function># 指定源文件和函数
clear <linenum> # 清除当前文件某行的断点
clear <filename:linenum> # 指定文件+行号清除

说明:clear 是基于“位置”清除,而非编号。

删除断点(delete)

1
2
3
4
delete                   # 删除所有断点
delete <bnum> # 删除指定编号的断点
delete <range> # 如 delete 3-5,删除编号 3 到 5 的断点
d # delete 的简写

禁用/启用断点(disable / enable)

1
2
3
4
5
6
7
8
disable                  # 禁用所有断点
disable <bnum> [range] # 禁用特定断点
dis # disable 的简写

enable # 启用所有断点
enable <bnum> # 启用某个断点
enable <bnum> once # 执行一次后自动 disable
enable <bnum> delete # 执行一次后自动删除

推荐使用 disable/enable 管理调试状态,灵活又不丢失断点信息。


设置 / 修改 条件(condition)

设置条件断点(设置时)

1
2
break foo if x > 5
watch var if var == 0

修改断点条件(维护时)

1
2
condition <bnum> x > 100     # 修改断点编号为 bnum 的条件
condition <bnum> # 清除断点条件

忽略断点次数(ignore)

1
ignore <bnum> <count>   # 忽略断点号 bnum 的触发 count 次

例如:

1
ignore 2 3

忽略断点 2 的前三次命中,第 4 次才真正中断。

为断点添加命令序列

1
2
3
commands <bnum>
...gdb命令序列...
end

示例:

1
2
3
4
5
break foo if x > 0
commands
printf "x is %d\n", x
continue
end

作用:x > 0 时断点命中,打印后自动继续,不用手动按 c

清除已有命令

1
2
commands <bnum>
end

example

  • 调试循环或大函数中的问题时,建议使用:
    break <line> if i == 9999

    ignore <bnum> 9998

  • 在你定位 bug 后,不要删断点,直接:

    1
    disable <bnum>  # 保留断点以后复用
  • 想测试多个变量变化时:

    1
    2
    watch a
    watch b
  • 想搞自动化调试:

    1
    2
    3
    4
    silent
    printf "Reached here\n"
    continue
    end

调试程序执行

恢复程序运行(继续执行)

命令 说明
continue / c / fg 从当前断点处继续运行
continue <ignore-count> 忽略接下来的 <count> 次断点命中
run / r 重新启动程序(从头开始)

适用于程序刚停下,想跳过一些断点或继续往下执行。

单步调试(源代码级)

命令 说明
step / s 单步执行,会进入函数(Step Into)
next / n 单步执行,不进入函数(Step Over)
step <count> / next <count> 连续执行 <count>

用于逐行查看程序逻辑,step 会进函数内部,next 则略过。

退出当前函数(函数级跳出)

命令 说明
finish 继续运行到当前函数返回,并打印返回值和返回地址

非常实用,适合跟踪完某个函数后退出它。

跳出循环体 / 块(until)

命令 说明
until <location> / u 执行直到某个位置或当前块结束(适合退出循环)

示例:

1
2
until 42            # 运行到当前文件的第 42 行
until main.c:100 # 运行到 main.c 第 100 行

用于快速跳出 for/while 循环等结构块。

汇编级单步(指令级调试)

命令 说明
stepi / si 单步执行一条机器指令(Step Into)
nexti / ni 单步执行一条机器指令(Step Over)

用于底层跟踪,比如跟踪系统调用、libc 内部逻辑或 boot code。

汇编查看建议:

1
display/i $pc     # 实时显示当前执行指令

设置 step-mode 模式

主要用于控制是否进入无符号函数

命令 说明
set step-mode on 即使没有 debug 符号也停住(默认 off)
set step-mode off 遇到无符号函数就跳过(默认)

对调试汇编或只含部分符号的库文件时很有用。

查看运行时数据

程序暂停时,使用print命令(简写p)或者用同义命令inspect查看当前程序的运行数据,格式为:

1
2
print <expr>
print/<f> <expr>

  • 为要调试的程序语言的表达式
  • 是format的意思,比如按16进制输出就是/x

输出格式

一般来说,GDB 会根据变量的类型输出变量的值。但你也可以自定义 GDB 的输出的格式。例如,你想输出一个整数的十六进制,或是二进制来查看这个整
型变量的中的位的情况。要做到这样,你可以使用 GDB 的数据显示格式:

  • x 按十六进制格式显示变量。 (hex)
  • d 按十进制格式显示变量。 (decimal)
  • u 按十六进制格式显示无符号整型。 (unsinged hex)
  • o 按八进制格式显示变量。 (octal)
  • t 按二进制格式显示变量。 (two)
  • a 按十六进制格式显示变量。 (address)
  • c 按字符格式显示变量。 (char)
  • f 按浮点数格式显示变量。 (float)
1
2
3
4
5
6
7
8
9
10
11
(gdb) p i 
$21 = 101
(gdb) p/a i
$22 = 0x65
(gdb) p/c i
$23 = 101 'e'
(gdb) p/f i
$24 = 1.41531145e-43 (gdb) p/x i
$25 = 0x65
(gdb) p/t i
$26 = 1100101

表达式

表达式可以是当前程序运行中的const常量,变量,函数等内容,但不能是程序中定义的宏

程序变量

在 GDB 中,你可以随时查看以下三种变量的值:

  1. 全局变量(所有文件可见的)
  2. 静态全局变量(当前文件可见的)
  3. 局部变量(当前 Scope 可见的)

用 print 显示出的变量的值会是函数中的局部变量的值。如果此时你想查看全局变量的值时,你可以使用“::”操作符:

1
2
file::variable 
function::variable

example
1
gdb) p 'f2.c'::x 

注意:如果你的程序编译时开启了优化选项,那么在用 GDB调试被优化过的程序时,可能会发生某些变量不能访问,或是取值错误码的情况。这个是很正常的,因为优化程序会删改你的程序,整理你程序的语句顺序,剔除一些无意义的变量等,所以在 GDB 调试这种程序时,运行时的指令和你所编写指令就有不一样,也就会出现你所想象不到的结果。对付这种情况时,需要在编译程序时关闭编译优化。一般来说,几乎所有的编译器都支持编译优化的开关,例如,GNU 的 C/C++编译器 GCC,你可以使用“-gstabs”选项来解决这个问题。

数组

1
int *array = (int *) malloc (len * sizeof (int)); 

在 GDB 调试过程中,你可以以如下命令显示出这个动态数组的取值:

1
p *array@len 

@的左边是数组的首地址的值,也就是变量 array 所指向的内容,右边则是数据的长度,其保存在变量 len 中,其输出结果,大约是下面这个样子的:
1
2
(gdb) p *array@len 
$1 = {2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40} 如果是静态数组的话,可以直接用 print 数组名,就可以显示数组中所有数据的内容了。

如果是静态数组的话,可以直接用 print 数组名,就可以显示数组中所有数据的内容了。

查看内存

使用 examine 命令(简写是 x)来查看内存地址中的值。x 命令的语法如下所示:

1
x/<n/f/u> <addr>  

  • n:显示的个数,即从内存地址 开始,显示几个单位(默认为 1)。

  • f:显示格式,比如:

    • x 十六进制
    • d 十进制
    • t 二进制
    • c 字符
    • f 浮点数
    • s 字符串
    • i 指令
  • u:读取单位的大小,决定每次读取多少字节:

    • b = 1 字节(byte)
    • h = 2 字节(half word)
    • w = 4 字节(word,默认)
    • g = 8 字节(giant/quad word)

查看寄存器

要查看寄存器的值,很简单,可以使用info registers (i r)

1
2
3
4
# 查看寄存器的情况。(除了浮点寄存器) 
info all-registers
# 查看所有寄存器的情况。(包括浮点寄存器)
info registers <regname ...>

也可以使用 print 命令来访问寄存器的情况,只需要在寄存器名字前加一个$符号就可以了。如:
1
p $eip

查看所指定的寄存器的情况。
寄存器中放置了程序运行时的数据,比如程序当前运行的指令地址(ip),程序的当前堆栈地址(sp)等等。你同样可以使用 print 命令来访问寄存器的情况,只需要在寄存器名字前加一个$符号就可以了。如:p $eip。

自动显示

你可以设置一些自动显示的变量,当程序停住时,或是在你单步跟踪时,这些变量会自动显示。相关的 GDB 命令是 display。

1
2
3
display <expr>  
display/<fmt> <expr>
display/<fmt> <addr>

  • expr 是一个表达式
  • fmt 表示显示的格式
  • addr 表示内存地址
    当你用display 设定好了一个或多个表达式后,只要你的程序被停下来,GDB 会自动显示你所设置的这些表达式的值。

格式 i 和 s 同样被 display 支持,一个非常有用的命令是:

1
display/i $pc 

$pc 是 GDB 的环境变量,表示着指令的地址,/i 则表示输出格式为机器指令码,也就是汇编。于是当程序停下后,就会出现源代码和机器指令码相对应的情形

删除自动显示

要删除自动显示可以用下面的命令

1
2
undisplay <dnums...> 
delete display <dnums...>

  • dnums 意为所设置好了的自动显式的编号。
    如果要同时删除几个,编号可以用空格分隔,如果要删除一个范围内的编号,可以用减号表示(如:2-5)

隐藏自动显示

1
2
disable display <dnums...> 
enable display <dnums...>

disable 和 enalbe 不删除自动显示的设置,而只是让其失效和恢复。

查看设置的自动显示的信息

1
info display 

查看 display 设置的自动显示的信息。GDB 会打出一张表格,向你报告当然调试中设置了多少个自动显示设置,其中包括,设置的编号,表达式,是否 enable。