2. GDB基础:GDB的安装与配置、断点设置与管理、单步执行与变量查看
调试,说白了就是让程序停下来,让你看看它到底在想什么。GDB 就是干这个的。我最早接触 GDB 是在调试一个用户态驱动的时候,那会儿程序跑着跑着就崩了,加了一堆 printk 也没用。后来同事扔给我一句「用 GDB 啊」,我才开始认真学这玩意儿。嗯,今天就把我这些年用 GDB 的经验,挑最核心的讲给你听。
2.1 GDB 的安装与配置
安装 GDB 其实很简单。大多数 Linux 发行版都自带,如果没有,一行命令搞定:
# Ubuntu/Debian
sudo apt-get install gdb
# CentOS/RHEL
sudo yum install gdb
# Fedora
sudo dnf install gdb
装完之后,验证一下:
$ gdb --version
GNU gdb (Ubuntu 9.2-0ubuntu1~20.04.1) 9.2
我个人习惯在 ~/.gdbinit 里加点配置,省得每次都要敲重复命令。举个例子:
# 设置汇编风格为 Intel(我更喜欢这个,看着顺眼)
set disassembly-flavor intel
# 显示源代码行号
set print pretty on
# 历史记录保存
set history save on
# 自动加载 .gdbinit 文件(安全考虑,默认是关闭的)
set auto-load safe-path /
auto-load safe-path 设得严格一点。我曾经见过有人因为加载了恶意的 .gdbinit 文件,导致调试信息泄露。安全无小事。
2.2 断点设置与管理
断点是调试的灵魂。没有断点,你就像在黑暗里摸象。GDB 的断点种类很多,我挑几个最常用的讲。
2.2.1 普通断点
最基础的用法,就是在函数入口或某一行停下:
(gdb) break main # 在 main 函数入口设断点
(gdb) break driver_open # 在 driver_open 函数入口设断点
(gdb) break 42 # 在当前文件的第 42 行设断点
(gdb) break file.c:100 # 在 file.c 的第 100 行设断点
你想想看,如果驱动有几十个文件,你一个个找行号多累?我一般先用 list 命令看看代码,再设断点:
(gdb) list driver_open, +20 # 显示 driver_open 函数的前 20 行
(gdb) break 128 # 然后在关键行设断点
2.2.2 条件断点
条件断点是个好东西。比如你只想知道当某个错误码出现时,程序是怎么走的:
(gdb) break driver_ioctl if error_code == -EINVAL
(gdb) break 256 if count > 1024 && flag == 1
我在项目中遇到过一个问题:一个驱动在特定条件下才会崩溃,但每次跑几千次才触发一次。用条件断点,我只在 count > 10000 时停下来,省去了大量手动检查的时间。
2.2.3 临时断点
有时候你只想停一次,之后自动删除。用 tbreak:
(gdb) tbreak driver_read # 只停一次
(gdb) tbreak 88 # 第 88 行只停一次
2.2.4 断点管理
断点多了,管理就变得重要。我常用的命令:
(gdb) info breakpoints # 查看所有断点
(gdb) disable 2 # 禁用 2 号断点(不删除,但暂时不用)
(gdb) enable 2 # 重新启用
(gdb) delete 2 # 删除 2 号断点
(gdb) delete # 删除所有断点
输出示例:
(gdb) info breakpoints
Num Type Disp Enb Address What
1 breakpoint keep y 0x0000555555555189 in main at test.c:10
2 breakpoint keep y 0x00005555555551a0 in driver_open at driver.c:42
stop only if error_code == -EINVAL
3 breakpoint keep n 0x00005555555551c0 in driver_read at driver.c:88
Enb 列是 n,表示这个断点被禁用了。这在调试多个场景时非常有用——你可以保留所有断点,只启用当前需要的。
2.3 单步执行与变量查看
断点停下来了,然后呢?你得一步一步走,看看变量是怎么变的。
2.3.1 单步执行
GDB 提供了几种单步方式,我习惯这样用:
(gdb) step # 步入:进入函数内部
(gdb) next # 步过:不进入函数,直接执行完当前行
(gdb) finish # 跳出:执行完当前函数,返回到调用处
(gdb) until # 运行到指定行(比如跳出循环)
举个例子,假设你在 driver_open 函数里停住了:
(gdb) step
driver_open () at driver.c:45
45 ret = check_permission();
(gdb) step
check_permission () at driver.c:120
120 if (user_id == 0) {
(gdb) finish
Run till exit from #0 check_permission () at driver.c:120
0x00005555555551b0 in driver_open () at driver.c:46
46 if (ret != 0) {
你看,step 进入了 check_permission,finish 又跳出来了。这样你就能看到函数内部是怎么跑的。
step 会直接跳过。我曾经在调试一个第三方库时,怎么 step 都进不去,后来才发现人家编译时没加 -g 选项。白忙活半小时。
2.3.2 变量查看
单步执行的同时,你得盯着变量。GDB 的变量查看命令很直观:
(gdb) print ret # 打印 ret 的值
$1 = 0
(gdb) print user_id # 打印 user_id
$2 = 1000
(gdb) print &user_id # 打印 user_id 的地址
$3 = (int *) 0x7fffffffe4a0
(gdb) print buffer # 打印数组或指针
$4 = 0x5555555592a0 "Hello World"
(gdb) print *buffer # 解引用指针
$5 = 'H'
(gdb) print buffer[0] # 打印数组第一个元素
$6 = 'H'
我常用的一个技巧是 display 命令,让 GDB 每次停下来都自动显示某个变量:
(gdb) display ret
1: ret = 0
(gdb) display user_id
2: user_id = 1000
(gdb) display buffer
3: buffer = 0x5555555592a0 "Hello World"
这样你每次 next 或 step 之后,GDB 都会自动打印这些变量,省得你手动敲 print。嗯,这个习惯我用了好多年,效率提升很明显。
2.3.3 查看复杂数据结构
驱动开发中经常遇到结构体。比如:
struct device {
int id;
char name[32];
struct device *parent;
void *private_data;
};
(gdb) print dev
$7 = {id = 1, name = "usb0", parent = 0x5555555592e0, private_data = 0x0}
(gdb) print dev->parent
$8 = (struct device *) 0x5555555592e0
(gdb) print *dev->parent
$9 = {id = 0, name = "usb_bus", parent = 0x0, private_data = 0x555555559300}
如果结构体嵌套很深,可以用 set print pretty on 让输出更易读:
(gdb) set print pretty on
(gdb) print dev
$10 = {
id = 1,
name = "usb0",
parent = 0x5555555592e0,
private_data = 0x0
}
2.4 实战小技巧
最后分享几个我在实际调试中积累的小技巧:
- 用
backtrace看调用栈: 程序崩了之后,先bt看看是怎么走到这一步的。我见过太多人一上来就乱设断点,其实看看调用栈就能定位问题。 - 用
frame切换栈帧:frame 2可以跳到调用栈的第二层,查看那里的局部变量。这在分析多层调用时特别有用。 - 用
watch监视变量变化:watch ret会在ret的值发生变化时停下来。我曾经用它抓到一个内存越界修改的问题,省了我两天时间。 - 用
info registers看寄存器: 调试底层代码时,寄存器里的值往往能告诉你更多信息。
list 一下代码,心里有个谱。然后设 2-3 个关键断点,用 display 盯住核心变量。这样调试起来思路清晰,不会乱。
好了,GDB 的基础就讲到这里。下一章我们会深入聊聊如何用 GDB 调试用户态驱动的具体场景,包括内存泄漏、死锁、性能问题等。嗯,那些才是真正考验功力的时候。