GDB
时间轴
2025-06-24
init
启动gdb
编译阶段:加入调试信息
为了让 GDB 能看到函数名、变量名、源码行号,编译时必须加上 -g
参数:
1 | gcc -g hello.c -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
2show 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 | # 不限制产生 core 的大小 |
unlimited
意思是系统不限制core文件的大小,只要有足够的磁盘空间,会转存程序所占用的全部内存,如果需要限制系统产生 core
的大小,可以使用以下命令:
1 | # core 最大限制大小为 409600 字节 |
关闭core文件生成功能
把核心转储功能关闭,只需要将限制大小设为0
即可:
1 | ulimit -c 0 |
注意,如果只是输入命令“ulimit -c unlimited
”,这只会在当前终端有效,退出终端或者打开一个新的终端时是无效的。
例子:
编写一个简单的C程序,人为制造一个Segmentation fault
错误:
1 |
|
上述代码中定义了一个空指针变量P,然后给空指针P赋值,运行程序就会产生一个段错误。
开启了核心转储后,就会产生一个core
文件。
1 | # 编译 hello.c 生成 hello 程序 |
运行后,我们可以看到 Segmentation fault (core dumped)
提示信息,表示已经在当前目录下产生了一个core
文件:
调试正在运行的程序
1 | gdb ./program <PID> |
- 直接指定 PID 启动 GDB(需要有可执行程序路径):
1 | gdb ./program <PID> |
- 在 GDB 内 attach 到某个 PID:
1 | (gdb) attach <PID> |
常用启动参数
参数 | 含义 |
---|---|
-s 或 -symbols <file> |
指定符号表文件 |
-se <file> |
指定符号表文件,并关联到可执行文件 |
-c 或 -core <file> |
指定 core dump 文件用于调试 |
-d 或 -directory <dir> |
添加源码搜索路径(默认用 $PATH ) |
退出输入quit(q)即可
gdb中运行Shell
在 GDB 中可以直接运行操作系统的命令,方法是:
1 | (gdb) shell <命令字符串> |
example
1 | (gdb) shell ls -l |
这会在 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 | break +5 // 当前行之后5行 |
指定文件 + 行号
1 | break filename.c:42 |
- 在
filename.c
的第 42 行设置断点。
指定文件 + 函数名
1 | break filename.c:func |
- 在
filename.c
中func
函数的入口处设置断点。
按地址设置断点
常用于汇编调试
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 可能无法设置
watch
、rwatch
等。 - 观察点数量受限,一般比断点少(通常是 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 | clear # 清除当前位置所有停止点 |
说明:
clear
是基于“位置”清除,而非编号。
删除断点(delete)
1 | delete # 删除所有断点 |
禁用/启用断点(disable / enable)
1 | disable # 禁用所有断点 |
推荐使用
disable/enable
管理调试状态,灵活又不丢失断点信息。
设置 / 修改 条件(condition)
设置条件断点(设置时)
1 | break foo if x > 5 |
修改断点条件(维护时)
1 | condition <bnum> x > 100 # 修改断点编号为 bnum 的条件 |
忽略断点次数(ignore)
1 | ignore <bnum> <count> # 忽略断点号 bnum 的触发 count 次 |
例如:
1 | ignore 2 3 |
忽略断点 2 的前三次命中,第 4 次才真正中断。
为断点添加命令序列
1 | commands <bnum> |
示例:
1 | break foo if x > 0 |
作用:x > 0 时断点命中,打印后自动继续,不用手动按
c
。
清除已有命令
1 | commands <bnum> |
example
调试循环或大函数中的问题时,建议使用:
break <line> if i == 9999
或
ignore <bnum> 9998
在你定位 bug 后,不要删断点,直接:
1
disable <bnum> # 保留断点以后复用
想测试多个变量变化时:
1
2watch a
watch b想搞自动化调试:
1
2
3
4silent
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 | until 42 # 运行到当前文件的第 42 行 |
用于快速跳出 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
2print <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 | (gdb) p i |
表达式
表达式可以是当前程序运行中的const常量,变量,函数等内容,但不能是程序中定义的宏
程序变量
在 GDB 中,你可以随时查看以下三种变量的值:
- 全局变量(所有文件可见的)
- 静态全局变量(当前文件可见的)
- 局部变量(当前 Scope 可见的)
用 print 显示出的变量的值会是函数中的局部变量的值。如果此时你想查看全局变量的值时,你可以使用“::”操作符:1
2file::variable
function::variable
example1
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
3display <expr>
display/<fmt> <expr>
display/<fmt> <addr>
- expr 是一个表达式
- fmt 表示显示的格式
- addr 表示内存地址
当你用display 设定好了一个或多个表达式后,只要你的程序被停下来,GDB 会自动显示你所设置的这些表达式的值。
格式 i 和 s 同样被 display 支持,一个非常有用的命令是:
1 | display/i $pc |
$pc 是 GDB 的环境变量,表示着指令的地址,/i 则表示输出格式为机器指令码,也就是汇编。于是当程序停下后,就会出现源代码和机器指令码相对应的情形
删除自动显示
要删除自动显示可以用下面的命令1
2undisplay <dnums...>
delete display <dnums...>
- dnums 意为所设置好了的自动显式的编号。
如果要同时删除几个,编号可以用空格分隔,如果要删除一个范围内的编号,可以用减号表示(如:2-5)
隐藏自动显示
1 | disable display <dnums...> |
disable 和 enalbe 不删除自动显示的设置,而只是让其失效和恢复。
查看设置的自动显示的信息
1 | info display |
查看 display 设置的自动显示的信息。GDB 会打出一张表格,向你报告当然调试中设置了多少个自动显示设置,其中包括,设置的编号,表达式,是否 enable。