GDB调试器基础使用:命令行调试完全指南¶
学习目标¶
完成本教程后,你将能够:
理解GDB调试器的工作原理和基本概念
掌握GDB的基本命令和操作流程
使用断点、单步执行等调试功能
查看和修改变量、内存和寄存器
分析程序调用栈和线程状态
进行远程调试和自动化调试
前置要求¶
在开始本教程之前,你需要:
知识要求:
- 了解C/C++编程基础
- 熟悉编译和链接的基本概念
- 了解程序执行流程
- 掌握基本的命令行操作
技能要求:
- 能够使用GCC编译器
- 会使用基本的Shell命令
- 了解调试的基本概念
- 能够阅读汇编代码(可选)
准备工作¶
软件准备¶
操作系统: Linux、macOS 或 Windows (WSL/MinGW)
GDB: GNU Debugger 8.0+
编译器: GCC 或 ARM GCC工具链
文本编辑器: 任何文本编辑器
安装GDB¶
Linux系统:
# Ubuntu/Debian
sudo apt install gdb
# CentOS/RHEL
sudo yum install gdb
# Arch Linux
sudo pacman -S gdb
macOS系统:
# 使用Homebrew
brew install gdb
# 配置代码签名(macOS需要)
# 参考:https://sourceware.org/gdb/wiki/PermissionsDarwin
Windows系统:
# 使用MinGW-w64
# 下载并安装MinGW-w64,包含gdb
# 或使用WSL
wsl --install
sudo apt install gdb
验证安装¶
gdb --version
预期输出:
GNU gdb (GDB) 10.2
Copyright (C) 2021 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later
系统要求¶
内存: 至少2GB RAM
磁盘: 100MB可用空间
权限: 普通用户权限(某些功能需要root)
步骤1: 理解GDB调试器¶
1.1 什么是GDB?¶
GDB (GNU Debugger) 是GNU项目开发的强大的命令行调试器,支持多种编程语言和处理器架构。
GDB的主要特点:
- 开源免费,功能强大
- 支持多种语言(C、C++、Fortran等)
- 支持多种架构(x86、ARM、MIPS等)
- 可以调试本地和远程程序
- 支持脚本自动化
- 可扩展性强
GDB的核心功能:
- 启动程序并指定运行参数
- 在指定条件下停止程序
- 检查程序停止时的状态
- 修改程序的变量和内存
- 分析程序崩溃原因
1.2 GDB工作原理¶
调试架构:
GDB调试器
↓
ptrace系统调用
↓
被调试程序
关键概念:
1. 符号表: 包含变量名、函数名和地址的映射
2. 断点: 程序执行到指定位置时暂停
3. 单步执行: 逐行或逐指令执行程序
4. 调用栈: 函数调用的层次关系
5. 观察点: 监视变量或内存的变化
1.3 调试信息¶
要使用GDB调试,程序必须包含调试信息。
编译时添加调试信息:
# 添加调试信息(-g选项)
gcc -g program.c -o program
# 添加详细调试信息(-g3)
gcc -g3 program.c -o program
# 禁用优化(便于调试)
gcc -g -O0 program.c -o program
调试信息级别:
- -g0: 无调试信息
- -g1: 最小调试信息(行号)
- -g2: 默认调试信息(推荐)
- -g3: 最详细调试信息(包括宏定义)
优化级别对调试的影响:
- -O0: 无优化,最适合调试
- -O1: 基本优化,部分变量可能被优化
- -O2: 标准优化,调试困难
- -O3: 激进优化,不推荐调试
1.4 GDB vs 图形化调试器¶
特性
GDB
图形化调试器
界面
命令行
图形界面
学习曲线
陡峭
平缓
功能
强大完整
功能有限
远程调试
原生支持
需要配置
自动化
脚本支持
较困难
资源占用
低
高
适用场景
服务器、嵌入式
桌面开发
选择建议:
- 使用GDB: 远程调试、嵌入式开发、服务器调试、自动化测试
- 使用图形化: 桌面应用开发、初学者、快速调试
步骤2: GDB基本操作¶
2.1 创建测试程序¶
创建 test.c:
#include
#include
int add(int a, int b) {
return a + b;
}
int multiply(int a, int b) {
int result = 0;
for (int i = 0; i < b; i++) {
result = add(result, a);
}
return result;
}
int main(int argc, char *argv[]) {
int x = 5;
int y = 3;
printf("x = %d, y = %d\n", x, y);
int sum = add(x, y);
printf("x + y = %d\n", sum);
int product = multiply(x, y);
printf("x * y = %d\n", product);
return 0;
}
编译程序:
gcc -g -O0 test.c -o test
2.2 启动GDB¶
方法1: 直接启动
gdb ./test
方法2: 附加到运行中的进程
# 先运行程序
./test &
# 获取进程ID
ps aux | grep test
# 附加到进程
gdb -p
方法3: 调试core文件
# 启用core dump
ulimit -c unlimited
# 运行程序(如果崩溃会生成core文件)
./test
# 调试core文件
gdb ./test core
2.3 GDB界面¶
启动GDB后,你会看到:
GNU gdb (GDB) 10.2
Copyright (C) 2021 Free Software Foundation, Inc.
...
Reading symbols from ./test...
(gdb)
GDB提示符: (gdb) 表示等待输入命令
2.4 基本命令¶
运行程序¶
# 运行程序
(gdb) run
# 或简写
(gdb) r
# 带参数运行
(gdb) run arg1 arg2
# 带输入重定向
(gdb) run < input.txt
# 带输出重定向
(gdb) run > output.txt
退出GDB¶
# 退出GDB
(gdb) quit
# 或简写
(gdb) q
# 强制退出(不确认)
(gdb) quit -y
获取帮助¶
# 查看所有命令
(gdb) help
# 查看特定命令的帮助
(gdb) help run
(gdb) help break
# 查看命令类别
(gdb) help breakpoints
(gdb) help data
步骤3: 断点管理¶
3.1 设置断点¶
在函数处设置断点:
# 在main函数设置断点
(gdb) break main
# 或简写
(gdb) b main
# 在add函数设置断点
(gdb) break add
在行号处设置断点:
# 在当前文件的第10行设置断点
(gdb) break 10
(gdb) b 10
# 在指定文件的第10行设置断点
(gdb) break test.c:10
(gdb) b test.c:10
在地址处设置断点:
# 在内存地址0x400500设置断点
(gdb) break *0x400500
3.2 条件断点¶
设置条件断点:
# 当i等于5时才触发断点
(gdb) break 12 if i == 5
# 当指针不为空时触发
(gdb) break function if ptr != 0
# 复杂条件
(gdb) break 15 if x > 10 && y < 20
修改断点条件:
# 为断点1添加条件
(gdb) condition 1 i == 10
# 删除断点条件
(gdb) condition 1
3.3 临时断点¶
# 设置临时断点(触发一次后自动删除)
(gdb) tbreak main
(gdb) tb main
# 在第15行设置临时断点
(gdb) tbreak 15
3.4 查看断点¶
# 查看所有断点
(gdb) info breakpoints
# 或简写
(gdb) i b
# 输出示例:
# Num Type Disp Enb Address What
# 1 breakpoint keep y 0x0000000000401142 in main at test.c:16
# 2 breakpoint keep y 0x0000000000401126 in add at test.c:4
断点信息说明:
- Num: 断点编号
- Type: 断点类型
- Disp: 触发后的处理(keep保留/del删除)
- Enb: 是否启用(y启用/n禁用)
- Address: 内存地址
- What: 断点位置
3.5 管理断点¶
禁用断点:
# 禁用断点1
(gdb) disable 1
# 禁用所有断点
(gdb) disable
启用断点:
# 启用断点1
(gdb) enable 1
# 启用所有断点
(gdb) enable
删除断点:
# 删除断点1
(gdb) delete 1
(gdb) d 1
# 删除所有断点
(gdb) delete
忽略断点:
# 忽略断点1的前10次触发
(gdb) ignore 1 10
3.6 观察点(Watchpoint)¶
观察点用于监视变量或内存的变化。
设置观察点:
# 监视变量x的变化
(gdb) watch x
# 监视表达式
(gdb) watch x + y
# 监视内存地址
(gdb) watch *(int*)0x12345678
读观察点:
# 当变量被读取时触发
(gdb) rwatch x
访问观察点:
# 当变量被读取或写入时触发
(gdb) awatch x
查看观察点:
(gdb) info watchpoints
步骤4: 程序执行控制¶
4.1 单步执行¶
逐语句执行(Step Into):
# 执行一行代码,进入函数内部
(gdb) step
# 或简写
(gdb) s
# 执行5行
(gdb) step 5
逐过程执行(Step Over):
# 执行一行代码,不进入函数内部
(gdb) next
# 或简写
(gdb) n
# 执行5行
(gdb) next 5
单步执行汇编指令:
# 执行一条汇编指令(进入函数)
(gdb) stepi
(gdb) si
# 执行一条汇编指令(不进入函数)
(gdb) nexti
(gdb) ni
4.2 继续执行¶
继续执行到下一个断点:
# 继续执行
(gdb) continue
# 或简写
(gdb) c
# 继续执行并忽略当前断点N次
(gdb) continue 5
执行到函数返回:
# 执行到当前函数返回
(gdb) finish
# 或简写
(gdb) fin
执行到指定位置:
# 执行到第20行
(gdb) until 20
(gdb) u 20
# 执行到指定函数
(gdb) until add
4.3 跳转执行¶
跳转到指定行:
# 跳转到第15行(不执行中间代码)
(gdb) jump 15
(gdb) j 15
# 跳转到指定函数
(gdb) jump add
⚠️ 注意: 跳转可能导致程序状态不一致,谨慎使用。
4.4 信号处理¶
查看信号处理:
# 查看所有信号的处理方式
(gdb) info signals
(gdb) info handle
设置信号处理:
# 捕获SIGINT信号(Ctrl+C)
(gdb) handle SIGINT stop print
# 忽略SIGPIPE信号
(gdb) handle SIGPIPE nostop noprint
# 传递信号给程序
(gdb) handle SIGUSR1 pass
发送信号:
# 向程序发送SIGUSR1信号
(gdb) signal SIGUSR1
步骤5: 查看和修改数据¶
5.1 查看变量¶
打印变量值:
# 打印变量x的值
(gdb) print x
# 或简写
(gdb) p x
# 打印表达式
(gdb) print x + y
(gdb) p x * 2
# 打印函数返回值
(gdb) print add(3, 5)
不同格式打印:
# 十六进制
(gdb) print/x x
(gdb) p/x x
# 二进制
(gdb) print/t x
# 八进制
(gdb) print/o x
# 十进制
(gdb) print/d x
# 字符
(gdb) print/c x
# 浮点数
(gdb) print/f x
# 地址
(gdb) print/a x
打印数组:
# 打印数组
(gdb) print array
# 打印数组的前10个元素
(gdb) print array[0]@10
# 打印指针指向的10个元素
(gdb) print *ptr@10
打印结构体:
# 打印结构体
(gdb) print my_struct
# 打印结构体成员
(gdb) print my_struct.member
# 打印指针指向的结构体
(gdb) print *ptr_struct
5.2 查看内存¶
查看内存内容:
# 查看地址0x12345678的内存
(gdb) x/10x 0x12345678
# 格式:x/[数量][格式][大小] 地址
格式说明:
- 数量: 显示多少个单元
- 格式: x(十六进制) d(十进制) u(无符号) o(八进制) t(二进制) a(地址) c(字符) s(字符串)
- 大小: b(字节) h(半字) w(字) g(双字)
示例:
# 查看10个字节(十六进制)
(gdb) x/10xb 0x12345678
# 查看5个字(十六进制)
(gdb) x/5xw 0x12345678
# 查看字符串
(gdb) x/s 0x12345678
# 查看变量的内存
(gdb) x/10x &variable
5.3 修改变量¶
修改变量值:
# 修改变量x的值
(gdb) set variable x = 10
(gdb) set var x = 10
# 修改表达式
(gdb) set var x = x + 5
# 修改数组元素
(gdb) set var array[5] = 100
# 修改结构体成员
(gdb) set var my_struct.member = 20
修改内存:
# 修改内存地址的值
(gdb) set {int}0x12345678 = 100
# 修改字节
(gdb) set {char}0x12345678 = 0xFF
# 修改指针指向的值
(gdb) set *ptr = 50
5.4 查看寄存器¶
查看所有寄存器:
# 查看通用寄存器
(gdb) info registers
(gdb) i r
# 查看所有寄存器(包括浮点和向量)
(gdb) info all-registers
查看特定寄存器:
# 查看rax寄存器
(gdb) info registers rax
(gdb) i r rax
# 打印寄存器值
(gdb) print $rax
(gdb) p $rax
# x86-64常用寄存器
(gdb) p $rip # 指令指针
(gdb) p $rsp # 栈指针
(gdb) p $rbp # 基址指针
修改寄存器:
# 修改寄存器值
(gdb) set $rax = 0x100
# ARM寄存器
(gdb) p $r0
(gdb) p $pc
(gdb) p $sp
5.5 自动显示¶
设置自动显示:
# 每次停止时自动显示变量x
(gdb) display x
# 自动显示表达式
(gdb) display x + y
# 自动显示内存
(gdb) display/10x &array
查看自动显示列表:
(gdb) info display
删除自动显示:
# 删除显示1
(gdb) undisplay 1
# 删除所有自动显示
(gdb) undisplay
步骤6: 调用栈分析¶
6.1 查看调用栈¶
显示调用栈:
# 显示完整调用栈
(gdb) backtrace
# 或简写
(gdb) bt
# 显示前5层调用栈
(gdb) backtrace 5
(gdb) bt 5
# 显示完整信息(包括参数)
(gdb) backtrace full
(gdb) bt full
调用栈输出示例:
#0 add (a=5, b=3) at test.c:4
#1 0x0000000000401165 in multiply (a=5, b=3) at test.c:10
#2 0x00000000004011a5 in main (argc=1, argv=0x7fffffffe0b8) at test.c:23
栈帧信息说明:
- #0: 栈帧编号(0是当前帧)
- add: 函数名
- (a=5, b=3): 函数参数
- at test.c:4: 源文件和行号
- 0x0000000000401165: 返回地址
6.2 切换栈帧¶
选择栈帧:
# 切换到栈帧1
(gdb) frame 1
(gdb) f 1
# 切换到上一层栈帧
(gdb) up
# 切换到下一层栈帧
(gdb) down
# 向上移动2层
(gdb) up 2
查看当前栈帧:
# 显示当前栈帧信息
(gdb) frame
(gdb) f
# 显示当前栈帧的详细信息
(gdb) info frame
(gdb) i f
6.3 查看局部变量¶
查看当前函数的局部变量:
# 查看所有局部变量
(gdb) info locals
(gdb) i locals
# 查看函数参数
(gdb) info args
(gdb) i args
查看指定栈帧的变量:
# 切换到栈帧1并查看局部变量
(gdb) frame 1
(gdb) info locals
6.4 查看源代码¶
显示当前位置的源代码:
# 显示当前行附近的代码
(gdb) list
(gdb) l
# 显示第10行附近的代码
(gdb) list 10
# 显示函数的代码
(gdb) list add
# 显示指定文件的代码
(gdb) list test.c:10
设置显示行数:
# 设置每次显示10行
(gdb) set listsize 10
# 查看当前设置
(gdb) show listsize
反汇编:
# 反汇编当前函数
(gdb) disassemble
(gdb) disas
# 反汇编指定函数
(gdb) disassemble add
# 反汇编地址范围
(gdb) disassemble 0x400500,0x400550
# 显示源代码和汇编混合
(gdb) disassemble /m
步骤7: 多线程调试¶
7.1 查看线程¶
查看所有线程:
# 显示所有线程
(gdb) info threads
(gdb) i threads
输出示例:
Id Target Id Frame
* 1 Thread 0x7ffff7fc0740 (LWP 12345) main () at test.c:20
2 Thread 0x7ffff6fbf700 (LWP 12346) thread_func () at test.c:10
3 Thread 0x7ffff5fbe700 (LWP 12347) thread_func () at test.c:10
线程信息说明:
- *: 当前线程
- Id: GDB内部线程ID
- Target Id: 系统线程ID
- Frame: 当前执行位置
7.2 切换线程¶
切换到指定线程:
# 切换到线程2
(gdb) thread 2
(gdb) t 2
# 查看当前线程
(gdb) thread
对所有线程执行命令:
# 对所有线程执行backtrace
(gdb) thread apply all backtrace
(gdb) thread apply all bt
# 对线程1和2执行命令
(gdb) thread apply 1 2 print x
7.3 线程断点¶
在特定线程设置断点:
# 在线程2的main函数设置断点
(gdb) break main thread 2
# 在所有线程的add函数设置断点
(gdb) break add
线程特定的条件断点:
# 只在线程2且x==5时触发
(gdb) break 10 thread 2 if x == 5
7.4 调度器锁定¶
锁定调度器:
# 锁定调度器(只运行当前线程)
(gdb) set scheduler-locking on
# 解锁调度器(所有线程运行)
(gdb) set scheduler-locking off
# 单步时锁定,其他时候解锁
(gdb) set scheduler-locking step
# 查看当前设置
(gdb) show scheduler-locking
应用场景:
- 调试多线程竞态条件
- 隔离单个线程的行为
- 避免其他线程干扰调试
步骤8: 远程调试¶
8.1 远程调试架构¶
开发机(GDB客户端)
↓ 网络连接
目标机(gdbserver)
↓
被调试程序
8.2 使用gdbserver¶
在目标机上启动gdbserver:
# 启动程序并等待连接
gdbserver :1234 ./program
# 附加到运行中的进程
gdbserver :1234 --attach
# 指定监听地址
gdbserver 192.168.1.100:1234 ./program
在开发机上连接:
# 启动GDB
gdb ./program
# 连接到远程目标
(gdb) target remote 192.168.1.100:1234
# 或使用extended-remote(支持运行新程序)
(gdb) target extended-remote 192.168.1.100:1234
8.3 嵌入式远程调试¶
使用OpenOCD进行ARM调试:
启动OpenOCD:
# 启动OpenOCD(在目标机或调试器主机)
openocd -f interface/stlink.cfg -f target/stm32f4x.cfg
连接GDB:
# 启动ARM GDB
arm-none-eabi-gdb firmware.elf
# 连接到OpenOCD
(gdb) target extended-remote localhost:3333
# 加载程序
(gdb) load
# 复位并停止
(gdb) monitor reset halt
# 开始调试
(gdb) continue
8.4 远程调试技巧¶
文件传输:
# 设置sysroot(远程文件系统根目录)
(gdb) set sysroot /path/to/sysroot
# 设置远程文件路径
(gdb) set remote exec-file /path/on/target/program
符号文件:
# 加载符号文件
(gdb) symbol-file /path/to/program.elf
# 添加额外的符号文件
(gdb) add-symbol-file /path/to/library.so 0x12345678
远程命令:
# 发送命令到远程调试器
(gdb) monitor reset
(gdb) monitor halt
# OpenOCD特定命令
(gdb) monitor flash write_image erase firmware.bin 0x08000000
步骤9: GDB脚本和自动化¶
9.1 命令文件¶
创建GDB命令文件 debug.gdb:
# 连接到远程目标
target extended-remote localhost:3333
# 加载程序
load
# 复位
monitor reset halt
# 设置断点
break main
break error_handler
# 设置自动显示
display x
display y
# 运行
continue
使用命令文件:
# 启动时执行命令文件
gdb -x debug.gdb program
# 在GDB中执行命令文件
(gdb) source debug.gdb
9.2 初始化文件¶
用户初始化文件 ~/.gdbinit:
# 设置历史记录
set history save on
set history size 10000
set history filename ~/.gdb_history
# 美化输出
set print pretty on
set print array on
set print array-indexes on
# 禁用分页
set pagination off
# 设置反汇编风格
set disassembly-flavor intel
# 自定义命令
define hook-stop
info registers
x/10i $pc
end
9.3 Python脚本¶
GDB支持Python脚本扩展。
简单Python命令:
# 执行Python代码
(gdb) python print("Hello from Python")
# 执行Python脚本
(gdb) source script.py
Python脚本示例 script.py:
import gdb
class HelloCommand(gdb.Command):
"""打印Hello World"""
def __init__(self):
super(HelloCommand, self).__init__("hello", gdb.COMMAND_USER)
def invoke(self, arg, from_tty):
print("Hello, World!")
HelloCommand()
class PrintVarCommand(gdb.Command):
"""打印变量的详细信息"""
def __init__(self):
super(PrintVarCommand, self).__init__("pvar", gdb.COMMAND_USER)
def invoke(self, arg, from_tty):
try:
val = gdb.parse_and_eval(arg)
print(f"Variable: {arg}")
print(f"Type: {val.type}")
print(f"Value: {val}")
print(f"Address: {val.address}")
except Exception as e:
print(f"Error: {e}")
PrintVarCommand()
使用自定义命令:
(gdb) source script.py
(gdb) hello
Hello, World!
(gdb) pvar x
Variable: x
Type: int
Value: 5
Address: 0x7fffffffe0a4
9.4 条件断点脚本¶
使用命令列表:
# 设置断点并添加命令
(gdb) break main
(gdb) commands 1
> silent
> printf "x = %d, y = %d\n", x, y
> continue
> end
说明:
- silent: 不显示断点触发信息
- printf: 打印信息
- continue: 自动继续执行
- end: 结束命令列表
9.5 自动化测试¶
测试脚本示例 test.gdb:
# 设置断点
break test_function
# 运行程序
run
# 检查结果
if result == expected
printf "Test PASSED\n"
else
printf "Test FAILED: expected %d, got %d\n", expected, result
end
# 退出
quit
批处理模式运行:
# 批处理模式(不进入交互)
gdb -batch -x test.gdb program
# 静默模式
gdb -batch -silent -x test.gdb program
步骤10: 高级调试技巧¶
10.1 调试优化代码¶
问题: 优化后的代码难以调试,变量可能被优化掉。
解决方法:
使用volatile关键字:
volatile int debug_var = 0;
部分禁用优化:
#pragma GCC push_options
#pragma GCC optimize ("O0")
void debug_function() {
// 这个函数不会被优化
}
#pragma GCC pop_options
编译时保留调试信息:
gcc -g -O2 -fno-omit-frame-pointer program.c -o program
10.2 调试宏¶
查看宏定义:
# 查看宏定义(需要-g3编译)
(gdb) info macro MACRO_NAME
# 展开宏
(gdb) macro expand MACRO_NAME
编译时包含宏信息:
gcc -g3 program.c -o program
10.3 调试内联函数¶
问题: 内联函数没有独立的栈帧。
解决方法:
禁用内联:
gcc -g -O0 -fno-inline program.c -o program
或在代码中禁用:
__attribute__((noinline))
void my_function() {
// 这个函数不会被内联
}
10.4 反向调试¶
GDB支持反向执行(需要记录执行历史)。
启用记录:
# 开始记录
(gdb) record
(gdb) rec
# 或使用完整记录
(gdb) record full
反向执行命令:
# 反向继续
(gdb) reverse-continue
(gdb) rc
# 反向单步
(gdb) reverse-step
(gdb) rs
# 反向next
(gdb) reverse-next
(gdb) rn
# 反向执行到函数开始
(gdb) reverse-finish
应用场景:
- 找出变量何时被修改
- 追踪bug的根源
- 理解程序执行流程
10.5 checkpoint(检查点)¶
创建检查点:
# 创建当前状态的检查点
(gdb) checkpoint
# 输出:checkpoint 1: fork returned pid 12345
恢复检查点:
# 恢复到检查点1
(gdb) restart 1
# 查看所有检查点
(gdb) info checkpoints
删除检查点:
# 删除检查点1
(gdb) delete checkpoint 1
10.6 调试core dump¶
生成core文件:
# 启用core dump
ulimit -c unlimited
# 设置core文件格式
echo "core.%e.%p" > /proc/sys/kernel/core_pattern
调试core文件:
# 加载core文件
gdb program core
# 或
gdb program
(gdb) core-file core
# 查看崩溃时的状态
(gdb) bt
(gdb) info registers
(gdb) info locals
分析崩溃原因:
# 查看崩溃位置
(gdb) where
# 查看信号信息
(gdb) info signal
# 查看内存映射
(gdb) info proc mappings
故障排除¶
问题1: 找不到调试符号¶
现象:
Reading symbols from ./program...
(No debugging symbols found in ./program)
原因:
- 编译时没有添加-g选项
- 程序被strip删除了符号
解决方法:
# 重新编译,添加调试信息
gcc -g program.c -o program
# 检查是否包含调试信息
file program
# 应该显示:not stripped
# 如果已经strip,需要重新编译
问题2: 源代码找不到¶
现象:
(gdb) list
1 in program.c
原因:
- 源文件路径改变
- 编译时使用了相对路径
解决方法:
# 设置源代码搜索路径
(gdb) directory /path/to/source
(gdb) dir /path/to/source
# 查看当前搜索路径
(gdb) show directories
# 替换源代码路径
(gdb) set substitute-path /old/path /new/path
问题3: 断点无法设置¶
现象:
(gdb) break main
No symbol "main" in current context.
原因:
- 程序未加载
- 符号表损坏
- 函数名错误
解决方法:
# 确保程序已加载
(gdb) file program
# 查看所有函数
(gdb) info functions
# 使用地址设置断点
(gdb) break *0x400500
# 延迟断点(动态库)
(gdb) set breakpoint pending on
(gdb) break library_function
问题4: 变量显示为optimized out¶
现象:
(gdb) print x
$1 =
原因:
- 编译器优化导致变量被优化
解决方法:
# 重新编译,禁用优化
gcc -g -O0 program.c -o program
# 或使用volatile
volatile int x = 0;
# 查看汇编代码确认变量位置
(gdb) disassemble
问题5: 远程调试连接失败¶
现象:
(gdb) target remote 192.168.1.100:1234
192.168.1.100:1234: Connection refused.
原因:
- gdbserver未启动
- 防火墙阻止
- 网络不通
解决方法:
# 检查gdbserver是否运行
ps aux | grep gdbserver
# 检查端口是否监听
netstat -an | grep 1234
# 检查防火墙
sudo iptables -L
# 测试网络连接
ping 192.168.1.100
telnet 192.168.1.100 1234
问题6: 多线程调试混乱¶
现象:
- 线程切换频繁
- 难以跟踪特定线程
解决方法:
# 锁定调度器
(gdb) set scheduler-locking on
# 只在特定线程设置断点
(gdb) break function thread 2
# 对所有线程执行命令
(gdb) thread apply all bt
问题7: GDB响应缓慢¶
原因:
- 符号表过大
- 自动显示过多
- 记录模式开启
解决方法:
# 禁用自动显示
(gdb) undisplay
# 关闭记录模式
(gdb) record stop
# 禁用分页
(gdb) set pagination off
# 限制backtrace深度
(gdb) set backtrace limit 10
实用技巧¶
技巧1: 快速命令¶
命令缩写:
r # run
c # continue
s # step
n # next
b # break
p # print
bt # backtrace
i # info
重复上一条命令:
# 按Enter键重复上一条命令
(gdb) step
(gdb)
技巧2: 命令历史¶
使用历史记录:
# 上一条命令
Ctrl+P 或 ↑
# 下一条命令
Ctrl+N 或 ↓
# 搜索历史
Ctrl+R
# 查看历史
(gdb) show commands
保存历史:
# 在~/.gdbinit中添加
set history save on
set history size 10000
set history filename ~/.gdb_history
技巧3: 条件表达式¶
复杂条件:
# 字符串比较
(gdb) break function if strcmp(str, "test") == 0
# 指针检查
(gdb) break function if ptr != 0 && *ptr == 100
# 范围检查
(gdb) break function if x >= 10 && x <= 20
技巧4: 便捷函数¶
调用函数:
# 调用printf
(gdb) call printf("x = %d\n", x)
# 调用自定义函数
(gdb) call my_debug_function()
# 修改全局状态
(gdb) call reset_state()
技巧5: 日志记录¶
记录GDB会话:
# 开始记录
(gdb) set logging on
(gdb) set logging file debug.log
# 记录所有输出
(gdb) set logging overwrite on
# 停止记录
(gdb) set logging off
技巧6: TUI模式¶
启用文本用户界面:
# 启动时开启TUI
gdb -tui program
# 在GDB中切换TUI
(gdb) tui enable
Ctrl+X Ctrl+A
# TUI布局
(gdb) layout src # 源代码
(gdb) layout asm # 汇编
(gdb) layout split # 源代码+汇编
(gdb) layout regs # 寄存器
# 刷新屏幕
Ctrl+L
技巧7: 便捷宏¶
在~/.gdbinit中定义宏:
# 打印数组
define parray
set $i = 0
while $i < $arg1
print $arg0[$i]
set $i = $i + 1
end
end
# 使用:parray array 10
# 打印结构体数组
define pstruct_array
set $i = 0
while $i < $arg1
print $arg0[$i]
set $i = $i + 1
end
end
# 十六进制dump
define hexdump
set $addr = $arg0
set $len = $arg1
set $i = 0
while $i < $len
if $i % 16 == 0
printf "\n0x%08x: ", $addr + $i
end
printf "%02x ", *(unsigned char*)($addr + $i)
set $i = $i + 1
end
printf "\n"
end
技巧8: 调试技巧总结¶
调试流程:
1. 编译时添加-g选项
2. 设置断点在关键位置
3. 运行程序到断点
4. 检查变量和状态
5. 单步执行分析问题
6. 修改代码重新测试
常用调试模式:
- 断点调试: 在关键位置停止
- 单步调试: 逐行执行分析
- 观察点调试: 监视变量变化
- 条件调试: 特定条件触发
- 反向调试: 回溯执行历史
总结¶
通过本教程,你学习了:
✅ GDB调试器的基本概念和工作原理
✅ 编译程序时添加调试信息的方法
✅ GDB的启动和基本操作命令
✅ 断点、观察点的设置和管理
✅ 程序执行控制(单步、继续、跳转)
✅ 变量、内存和寄存器的查看和修改
✅ 调用栈分析和栈帧切换
✅ 多线程程序的调试技巧
✅ 远程调试和嵌入式调试
✅ GDB脚本和自动化调试
✅ 高级调试技巧和故障排除
关键要点:
1. 编译时必须添加-g选项才能使用GDB调试
2. 断点是最常用的调试工具,合理设置断点很重要
3. 单步执行可以详细分析程序执行流程
4. 观察点用于监视变量变化,找出修改位置
5. 远程调试适用于嵌入式和服务器调试
6. GDB脚本可以实现自动化调试和测试
7. 熟练使用命令缩写可以提高调试效率
下一步学习¶
建议继续学习以下内容:
初级进阶¶
串口调试技巧大全 - 学习串口调试方法
逻辑分析仪使用入门 - 硬件信号分析
常见Bug调试方法 - 调试思路和方法
中级进阶¶
OpenOCD调试工具使用 - 开源调试方案
J-Link调试器高级功能 - 专业调试工具
内存泄漏检测与分析 - 内存问题调试
高级进阶¶
单元测试框架搭建 - 自动化测试
硬件在环(HIL)测试系统 - 系统级测试
实践项目建议¶
项目1: 简单程序调试¶
难度: ⭐
目标: 使用GDB调试简单的C程序
任务:
- 设置断点并运行程序
- 单步执行观察变量变化
- 修改变量值测试不同路径
- 分析调用栈
学习要点:
- 基本GDB命令
- 断点使用
- 变量查看
- 程序流程控制
项目2: 多线程程序调试¶
难度: ⭐⭐
目标: 调试多线程程序的竞态条件
任务:
- 查看所有线程状态
- 切换线程进行调试
- 使用调度器锁定
- 找出竞态条件
学习要点:
- 多线程调试
- 线程切换
- 调度器控制
- 竞态条件分析
项目3: 远程嵌入式调试¶
难度: ⭐⭐⭐
目标: 使用GDB调试ARM嵌入式程序
任务:
- 配置OpenOCD和GDB
- 远程连接到目标板
- 设置断点调试固件
- 查看寄存器和内存
学习要点:
- 远程调试配置
- OpenOCD使用
- 嵌入式调试技巧
- 硬件寄存器操作
项目4: 自动化调试脚本¶
难度: ⭐⭐⭐⭐
目标: 编写GDB脚本实现自动化测试
任务:
- 编写GDB命令脚本
- 使用Python扩展GDB
- 实现自动化测试
- 生成测试报告
学习要点:
- GDB脚本编写
- Python扩展
- 自动化测试
- 测试报告生成
常见问题FAQ¶
Q1: GDB和LLDB有什么区别?¶
A:
- GDB: GNU项目,历史悠久,功能完整,支持广泛
- LLDB: LLVM项目,现代化设计,macOS默认调试器
- 建议: Linux用GDB,macOS可以用LLDB,命令类似
Q2: 如何调试没有源代码的程序?¶
A:
- 使用反汇编:disassemble
- 查看符号表:info functions
- 设置地址断点:break *0x400500
- 分析调用栈:backtrace
- 使用反编译工具辅助
Q3: GDB可以调试Python程序吗?¶
A:
可以,但需要特殊配置:
- 安装python-dbg包
- 使用gdb的Python扩展
- 或使用pdb(Python调试器)
Q4: 如何在GDB中查看C++对象?¶
A:
# 打印对象
(gdb) print object
# 打印虚函数表
(gdb) info vtbl object
# 调用成员函数
(gdb) call object.method()
# 查看类型信息
(gdb) ptype ClassName
Q5: GDB支持哪些架构?¶
A:
GDB支持众多架构:
- x86/x86-64
- ARM/ARM64
- MIPS
- PowerPC
- RISC-V
- AVR
- 等等
Q6: 如何调试动态库?¶
A:
# 设置延迟断点
(gdb) set breakpoint pending on
(gdb) break library_function
# 查看已加载的库
(gdb) info sharedlibrary
# 加载符号
(gdb) add-symbol-file library.so 0x12345678
Q7: GDB可以调试内核吗?¶
A:
可以,使用KGDB:
- 配置内核支持KGDB
- 通过串口或网络连接
- 使用特殊的GDB命令
- 需要两台机器(调试机和目标机)
Q8: 如何提高GDB调试效率?¶
A:
- 熟练使用命令缩写
- 配置.gdbinit文件
- 使用TUI模式
- 编写自定义脚本
- 使用条件断点减少停止次数
- 善用命令历史
参考资料¶
官方文档¶
GDB官方文档 - 完整的GDB手册
GDB用户手册 - 在线版本
GDB Wiki - 社区维护的Wiki
教程和文章¶
GDB Tutorial - 交互式教程
Debugging with GDB - GNU官方教程
GDB Cheat Sheet - 快速参考卡片
视频教程¶
GDB Tutorial for Beginners - YouTube教程
Advanced GDB Usage - 高级技巧
书籍推荐¶
"The Art of Debugging with GDB, DDD, and Eclipse" - Norman Matloff
"Debugging with GDB" - Richard Stallman
"Advanced Linux Programming" - Mark Mitchell
在线资源¶
Stack Overflow - GDB标签
GDB邮件列表
GDB源代码
工具和插件¶
GDB Dashboard: 增强的GDB界面
GEF: GDB增强框架
PEDA: Python Exploit Development Assistance
pwndbg: CTF和漏洞分析工具
附录¶
附录A: GDB命令速查表¶
程序控制¶
命令
简写
说明
run
r
运行程序
continue
c
继续执行
step
s
单步进入
next
n
单步跳过
finish
fin
执行到返回
until
u
执行到指定位置
jump
j
跳转到指定位置
quit
q
退出GDB
断点管理¶
命令
简写
说明
break
b
设置断点
tbreak
tb
临时断点
watch
-
设置观察点
delete
d
删除断点
disable
-
禁用断点
enable
-
启用断点
info breakpoints
i b
查看断点
数据查看¶
命令
简写
说明
p
打印变量
display
-
自动显示
x
-
查看内存
info registers
i r
查看寄存器
info locals
i locals
查看局部变量
info args
i args
查看参数
栈帧操作¶
命令
简写
说明
backtrace
bt
查看调用栈
frame
f
选择栈帧
up
-
上一层栈帧
down
-
下一层栈帧
info frame
i f
查看栈帧信息
源代码¶
命令
简写
说明
list
l
显示源代码
disassemble
disas
反汇编
info functions
i functions
查看函数
info variables
i variables
查看变量
附录B: 常用格式说明¶
打印格式¶
/x - 十六进制
/d - 十进制
/u - 无符号十进制
/o - 八进制
/t - 二进制
/a - 地址
/c - 字符
/f - 浮点数
/s - 字符串
内存查看格式¶
b - 字节(1字节)
h - 半字(2字节)
w - 字(4字节)
g - 双字(8字节)
附录C: .gdbinit配置示例¶
# 历史记录
set history save on
set history size 10000
set history filename ~/.gdb_history
# 输出美化
set print pretty on
set print array on
set print array-indexes on
set print elements 200
# 禁用分页
set pagination off
# 反汇编风格(Intel语法)
set disassembly-flavor intel
# 自动加载.gdbinit
set auto-load safe-path /
# 启动时显示信息
set verbose off
# 确认操作
set confirm off
# 线程调试
set non-stop on
set target-async on
# Python支持
python
import sys
sys.path.insert(0, '/path/to/gdb/scripts')
end
# 自定义命令
define hook-stop
info registers
x/5i $pc
end
反馈与支持:
- 如果你在学习过程中遇到问题,欢迎在评论区留言
- 发现文档错误或有改进建议,请提交Issue
- 想要分享你的GDB使用经验,欢迎投稿
版本历史:
- v1.0 (2024-01-15): 初始版本发布
许可证: 本文档采用 CC BY-SA 4.0 许可协议