c/c++开发调试工具之gdb
调试
调试就是让代码一步一步的慢执行,跟踪程序的运行过程。比如,可以让程序停在某个地方,查看当前所有变量的值,或者内存中的数据;也可以让程序一次只执行一条或者几条语句,看看程序到底执行了哪些代码。帮助我们发现代码中的错误,改进代码。
gdb的功能
灵活控制程序的启动方式
可以给程序设置启动参数,也可以设置环境变量,让程序在指定的条件下开始运行
支持断点设置与程序执行控制
可以在指定的行、函数设置断点,使程序在特定位置暂停
支持单步执行、逐过程执行、继续运行等操作便于逐步排查程序逻辑
实时查看程序运行的状态
程序暂停时可以查看当前执行位置的上下文,可查看局部变量、全局变量、寄存器等的当 前值,可查看函数调用栈、线程信息等,帮助定位问题来源
动态修改程序的执行过程
可在程序暂停时修改变量的值,观察不同的值对程序的影响,也可改变程序的执行路径, 比如跳到其他代码行继续执行
总结:gdb 能让程序按你设定的方式启动,比如加参数或设环境变量;还能在你关心的地方通过加断点的方式停下来方便检查;程序停住后,你可以查看变量值、调用关系等运行状态;不仅能查看,还能改变变量值、改变执行流程,帮你快速定位并尝试修复问题。
注意:
使用gdb调试需要一个可执行文件,这个可执行文件可以由gcc/g++直接生成,但是gcc/g++默认生成的是release(发布)版本的可执行文件,但是gdb需要的是debug(调试)版本的可执行文件,所以在使用gcc/g++生成可执行文件时要加上选项-g,此时生成的就是debug版本的可执行文件。
g++ -g -o test_threads test_threads.cpp
常用的gdb指令
基本运行控制
命令 | 含义 |
---|
r (run) | 启动程序。若有断点则停在第一个断点处,否则直接运行完 |
c (continue) | 从当前停住的位置继续运行,直到下一个断点或程序结束 |
n (next) | 单步调试,跳过函数调用,即执行该行但不进入函数内部 |
s (step) | 单步调试,会进入函数体内部 |
finish | 快速执行完当前函数,停在调用者处下一行 |
until 行号 | 执行到指定行号再停住,适合跳出循环等 |
示例:
1: int add(int x, int y) { return x + y; }
2: int main() {
3: int a = 1;
4: int b = 2;
5: int c = add(a, b);
6: return 0;
}
①理解 next
vs step
停在第5行时执行命令:
-
n
:直接跳到第6行,add
被调用但不进入 -
s
:进入add()
函数,从return x + y
开始调试
②理解finish:
假设你用 step
进入了 add(a, b)
函数
(gdb) step
你现在在:
1: int add(int x, int y) {
此时你想直接执行完 add
函数,并跳回 main()
的第 5 行之后继续调试,不想在 add
内一步步执行。
使用:
(gdb) finish
GDB 会:
-
继续执行当前函数直到 return
-
返回上一层调用点
-
自动打印返回值,例如:
Run till exit from #0 add (x=1, y=2) at test.cpp:2
0x0000000000401136 in main () at test.cpp:6
Value returned is $1 = 3
③理解until:
目标:
从第 3 行开始执行,跳过第 4、5 行,直接停在第 6 行(return 0;
)
(gdb) b 3 # 在第3行设置断点
(gdb) r # 运行程序,停在第3行
执行:
(gdb) until 6
gdb会连续执行第 3、4、5 行,不会进入 add(a, b)
函数内部,然后停在:
6: return 0;
查看和操作变量
命令 | 含义 |
---|---|
p 变量名 | 打印变量值(print) |
set var 变量名=值 | 修改变量值 |
display 变量名 | 每次中断都自动显示变量值(追踪变量) |
undisplay 编号 | 取消某个 display 的变量 |
info locals | 查看当前函数内所有局部变量 |
info args | 查看当前函数的参数 |
watch 变量名 | 设置“观察点”,变量值改变时自动中断程序 |
示例:
1: int add(int x, int y) {
2: int result = x + y;
3: return result;
4: }5: int main() {
6: int a = 10;
7: int b = 20;
8: int c = add(a, b);
9: return 0;
}
(gdb) b add # 在 add 函数开头设置断点
(gdb) r # 启动程序并在 add() 进入时暂停
(gdb) info args # 查看传入的参数值
输出为:
x = 10
y = 20
在 add()
函数中,继续查看局部变量 result
(gdb) n # 执行一行,让 result 被计算出来
(gdb) info locals # 查看所有局部变量
输出为:
result = 30
在 main()
中设置对变量 a
的观察,当它值变化时中断程序
(gdb) b 6 # 在第6行设置断点(a赋值前)
(gdb) r # 启动程序并暂停在第6行
(gdb) watch a # 设置观察点,监视变量a
输出:
Hardware watchpoint 1: a
然后执行:
(gdb) c # 继续运行程序
GDB 会在 a
的值第一次变化时自动中断,并提示:
Hardware watchpoint 1: aOld value = <uninitialized>
New value = 10
断点管理
命令 | 含义 |
---|
b 行号 | 在当前文件的某一行设置断点 |
b 文件名:行号 | 在指定文件中设置断点 |
b 文件名:函数名 | 在某函数第一行设置断点 |
info b | 查看所有断点信息 |
d 编号 | 删除某个断点(使用断点编号) |
d 或 delete breakpoints | 删除所有断点 |
disable 编号 / enable 编号 | 禁用 / 启用某个断点 |
disable / enable | 禁用 / 启用所有断点 |
调用栈相关(定位函数调用)
命令 | 含义 |
---|
bt (backtrace) | 打印函数调用栈,查看函数调用路径 |
frame 编号 | 切换栈帧,进入某一层函数栈中 |
up / down | 在函数栈中向上/向下切换调用上下文 |
示例:多层函数调用
1: void foo(int x) {
2: int y = x + 1;
3: }4: void bar(int a) {
5: int b = a * 2;
6: foo(b);
7: }8: int main() {
9: int val = 10;
10: bar(val);
11: return 0;
}
调试目标:观察函数调用栈,切换上下文,查看各层变量状态
首先,我们在 foo()
函数的 int y = x + 1;
行设置断点,这样当程序执行到 foo
函数时会停止。
(gdb) b 2 # 在 foo 函数的第2行设置断点
(gdb) r # 运行程序
程序停在 foo()
的第2行时,GDB 会提示你当前停在哪个位置。
查看当前调用栈:使用 bt
命令
(gdb) bt
输出如下:
#0 foo (x=20) at test.cpp:2
#1 bar (a=10) at test.cpp:6
#2 main () at test.cpp:10
可以看到函数调用的顺序是:
main → bar → foo
如果我们想查看 bar()
函数中的状态,可以使用 frame
命令切换到 bar
的栈帧。
(gdb) frame 1 # 切换到 bar 的栈帧
然后,我们可以查看 bar()
函数中的变量 a
和 b
:
(gdb) info locals # 查看 bar 函数中的局部变量
输出是:
b = 20
a = 10
如果我们想查看更上层的栈帧,即 main()
函数中的变量,可以使用 up
命令:
(gdb) up # 切换到上一层,进入 main 函数
然后,我们查看 main()
函数中的变量 val
:
(gdb) info locals # 查看 main 函数中的局部变量
输出是:
val = 10
如果我们想回到 foo()
函数的栈帧,可以使用 down
命令:(up
和 down
命令可以用来在栈帧之间循环访问。当你到达栈帧的最顶层或者最底层时,继续执行 up
或 down
会让你跳转到相反的方向。)
(gdb) down # 切换回 foo 的栈帧
多线程调试
命令 | 含义 |
---|---|
info threads | 查看当前所有线程 |
thread 编号 | 切换到某个线程 |
查看内存和字符串内容
命令格式 | 含义 |
---|---|
x/NFU 地址 | 查看内存内容 |
x/s 地址 | 查看以地址为起点的 C 风格字符串(遇 \0 停) |
-
N
:查看的单元个数,如4
-
F
:格式,如x
(十六进制)、d
(十进制)、c
(字符)、s
(字符串) -
U
:单位大小,b
(字节)、h
(半字)、w
(字)、g
(巨字)
示例:
(gdb) x/4xb ptr # 查看 ptr 开头的4个字节,按16进制显示
(gdb) x/10dc ptr # 以10进制+字符形式查看10个字节
(gdb) x/g ptr # 查看一个8字节的 long long 类型数据
(gdb) x/s str # 查看 C 字符串内容
手动调用函数
命令 | 含义 |
---|
call 函数名(参数) | 在 GDB 中手动调用一个函数进行测试或打印返回值 |
(gdb) call strlen("hello")
注意:
-
GDB 会自动记住上次输入的命令,按【回车】可重复执行上条命令。
-
使用
TAB
可自动补全命令、变量、文件名等。 -
GDB 中按
Ctrl + C
可中断当前程序运行。