「Linux 系统编程」gdb 调试指令
1 gdb 调试工具
1.1 什么是 gdb
gdb(GNU Debugger)是 Unix/Linux 系统下的 命令行调试工具,主要用于分析程序运行时的行为、定位崩溃(Segmentation Fault)、内存泄漏、逻辑错误等问题。支持 C、C++、Fortran、Go 等多种语言编译的程序。
1.2 生成可调式文件
使用 -g
选项编译程序,生成调试表:
1 |
|
加载可执行文件:
1 |
|
2 gdb 调试指令
2.1 显示源码与断点设置
-
list
显示源码1
2list # 显示源码
l # 显示源码,list 简写为 llist <行号>
显示第<行号>
附近的一部分代码:1
list 10 # 显示第 10 行附近的代码
list <函数名>
显示对应函数附近的代码:1
list main # 显示 main 函数附近的代码
list <起始行号>, <终止行号>
显示指定范围的代码:1
2
3list 1, 3 # 显示 1 ~ 3 行的代码
list 1, # 以第 1 行为开头
list , 10 # 以第 10 行为结尾
另外,可以通过使用
set listsize <行数>
修改单次 list 显示的行数(默认 10 行)。1
set listsize 20 # 改为每次显示 20 行
-
break
设置断点1
2break [位置] # 在当前文件指定位置设置断点
b [位置] # break 简写为 b 设置断点break <行号>
在 指定行 设置断点:1
break 17
break <函数名>
在 函数入口位置 设置断点:1
break work
可以通过
info breakpoints
输出当前所有断点的信息。
-
break
设置 条件断点1
break [位置] if [条件]
例如,设置在第 8 行设置一个断点,当
i == 1
时暂停:1
break 8 if i == 1
-
tbreak
设置 临时断点1
tbreak [位置]
临时断点在触发之后就会自动删除。
-
其他断点相关指令
info breakpoints
:查看所有断点信息disable <断点编号>
:禁用断点enable <断点编号>
:启用断点delete <断点编号>
:删除指定编号的断点clear <位置>
:删除指定位置的断点condition <断点编号> <条件>
:为已有断点添加条件
2.2 程序运行
-
run
启动程序run
命令会从头开始执行程序,直到遇到断点、程序结束 或 发生错误。如果 main 函数有参数,
run
命令可以携带参数执行,形如run arg1 arg2
。1
2
3run # 从头开始执行,遇到断点暂停(无断点直接执行到结束)
r # run 简写为 r
run arg1 arg2 # main 带有参数,携带参数执行
-
start
启动程序并在 main 函数的入口处自动暂停start
命令相当于执行break main
+run
(main 函数入口处设置断点再执行程序)。1
2start # 启动并在 main 开头暂停
start arg1 arg2 # 带参数启动并在 main 暂停
-
next
执行下一行源代码(不进入函数内部)next
命令将函数调用视为 单条语句 直接执行,不会进入被调用函数的内部。next
命令适合跳过不需要深入分析的库函数(例如printf
、scanf
等)或已知正确的函数。1
2next # 执行下一行源代码(不进入函数内部)
n # next 简写为 n例如,示例程序的主函数如下:
1
2
3
4
5
6
7
8
9
10int main(){
int a = 3;
int b = 4;
cout << "a + b = " << a + b << endl;
work(a); // next 不进入函数内部
return 0;
}执行
next
不会进入work
函数内部,直接将work(a)
这一行执行。
-
step
执行下一行源代码(进入函数内部)step
命令遇到函数调用会进入函数内部,并在被调用函数的第一条指令处暂停。如果没有遇到函数调用,
step
命令和next
命令的效果是一样的。1
2step # 执行下一行源代码(进入函数内部)
s # step 命令的简写例如,有如下示例程序:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22#include <iostream>
#include <cstring>
using namespace std;
void work(int n){
int sum = 0;
for(int i = 1; i <= n; i ++ ){
sum += i;
}
cout << sum << endl;
}
int main(){
int a = 3;
int b = 4;
cout << "a + b = " << a + b << endl;
work(a); // s 会进入函数内部
return 0;
}step
命令在执行到work(a)
这一行时,会进入函数内部。
-
其它程序运行相关指令
skip
标记需要step
命令跳过的函数
1
2skip function [函数名] # 标记跳过的函数
step # 后续 step 时会跳过,不进入该函数finish
直接执行完当前函数并返回
1
2step # 进入不需要深入分析的函数
finish # 立即执行完该函数并回到调用点如果由于
step
不小心进入了库函数,可以通过finish
命令退出:1
2
3
4
5
6
7
8
9
10
11
12int main(){
int a = 3;
int b = 4;
printf("printf function\n"); // step 不小心进入 printf 函数
cout << "a + b = " << a + b << endl;
work(a);
return 0;
}
next [次数]
/step [次数]
连续执行多次next
/step
1
2next 5 # 连续执行5次 next
step 3 # 连续执行3次 step
2.3 变量操作
-
print
打印变量值1
print a
-
display
每次暂停时自动显示值1
display b
可以通过
undisplay
取消设置跟踪变量:1
undisplay [跟踪变量编号]
-
set var
修改变量值1
set var a = 7 # 将变量 a 修改为 7
-
ptype
查看变量类型1
ptype a # 查看变量 a 的类型
2.4 函数堆栈操作
当进入函数内部之后,发现无法访问函数外部的变量。此时可以通过 backtrace
查看调用栈(简写 bt
),切换到上层的栈帧,从而看到当前栈帧的变量:
1 |
|
2.5 循环调试
-
until
运行到指定行until
运行到当前函数中下一个更高层级的源码行(常用于跳出循环):1
until
通常配合断点和
continue
使用:1
2
3until # 先跳出当前循环
break 100 # 在循环外设断点
continue # 运行到新断点
until [行号]
运行到当前函数内的指定行(必须大于当前行)1
until 10 # 运行到第 10 行
-
continue
继续运行到下一个断点1
2continue # 运行到下一个断点
continue 2 # 忽略前 2 次触发断点
2.6 退出调试
1 |
|