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) finishGDB 会:
-  继续执行当前函数直到 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 6gdb会连续执行第 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可中断当前程序运行。
