RISC-V GDB 命令行调试基础教程
基础概念
GDB 调试器支持 RISC-V 架构,通常使用交叉编译的 GDB(如 riscv64-unknown-elf-gdb 或 riscv-none-embed-gdb),并配合 OpenOCD、JTAG 仿真器等硬件使用。
启动与连接
加载可执行文件:
riscv64-unknown-elf-gdb freertos_demo.elf
连接 OpenOCD:
(gdb) target remote :3333
默认 OpenOCD 在 3333 端口等待 GDB 连接。
加载初始内存布局:
(gdb) monitor reset halt
(gdb) load
(gdb) monitor reset
常用命令速查
断点相关
(gdb) break main # 设置源码断点
(gdb) break 0x8000 # 设置地址断点
(gdb) info breakpoints # 查看断点
(gdb) delete 1 # 删除断点
(gdb) disable 2 # 禁用断点
(gdb) clear main # 清除指定断点
(gdb) ignore 1 5 # 忽略前 5 次命中
(gdb) watch *0x20000000 # 监视变量
(gdb) tbreak main # 一次性断点
执行控制
(gdb) continue # 继续执行到下一断点
(gdb) step # 逐行执行,进入函数
(gdb) next # 逐行执行,不进入函数
(gdb) stepi / step 1 # 单条指令执行
(gdb) nexti # 单条指令,不进入
(gdb) finish # 执行完当前函数
(gdb) return # 提前退出当前函数
(gdb) run # 从头开始
(gdb) restart # 重启(需硬件支持)
查看变量和寄存器
(gdb) print x # 打印变量
(gdb) print *p # 打印指针指向的内容
(gdb) print/x x # 十六进制
(gdb) print/d x # 十进制
(gdb) p/x *(int *)0x20000000 # 打印内存地址
(gdb) display pc # 自动显示 PC
(gdb) undisplay # 取消显示
寄存器(RISC-V 通用寄存器)
(gdb) print/x $a0 # 打印参数寄存器
(gdb) set $a0 = 123 # 设置寄存器
(gdb) info registers # 显示所有寄存器
(gdb) info registers x0-x31 # 显示通用寄存器
RISC-V 寄存器约定:x0=zero、x1=ra、x2=sp、x3=gp、x4=tp、x5-x7=t0、x16-17=a0-a1、x18-27=a2-a11、x28-31=t1-t4。
内存操作
(gdb) x/10wx 0x20000000 # 查看 10 字
(gdb) x/20bx 0x20000000 # 查看 20 字节
(gdb) x/5dh 0x20000000 # 5 个十六进制
(gdb) x/s 0x20000000 # 查看字符串
(gdb) set {int}0x20000000 = 42 # 修改内存
(gdb) monitor mdw 0x20000000 5 # OpenOCD 读内存
查看上下文
(gdb) backtrace / bt # 调用栈
(gdb) frame 2 # 切换到栈帧
(gdb) list # 源码
(gdb) info locals # 局部变量
(gdb) info args # 函数参数
(gdb) disassemble # 反汇编当前函数
(gdb) disassemble /m main # 源码+反汇编
调试宏
(gdb) define hook-stop # 停止时自动执行
print/x $pc
bt
end
(gdb) source ~/.gdbinit
符号表加载与代码行号
启动时直接加载符号表
load 命令加载的是带完整符号表(-g 编译)的可执行文件。如果 ELF 文件符号表缺失,可单独加载调试符号:
(gdb) symbol-file freertos_demo_debug.elf # 加载全符号表
(gdb) exec-file freertos_demo.elf # 或单独指定执行文件
(gdb) add-symbol-file freertos_demo.elf 0x8000 # 动态添加符号表
确保编译带调试信息
RISC-V IAR ICC5 编译器在生成 .elf 时需开启调试选项,对应命令行标志 -D 或在 .icd 配置中勾选调试符号。
显示当前行代码
(gdb) list # 列出当前行前后 10 行
(gdb) list main # 列出指定函数
(gdb) list 42 # 列出第 42 行附近
(gdb) list *.c:main # 列出指定源文件的函数
(gdb) list 1,10 # 列出第 1~10 行
自动显示每次停止时的代码
在 .gdbinit 或调试会话中设置:
(gdb) define hook-stop
printf "\n--- Stopped at "
print /x $pc
printf " ---\n"
list
end
(gdb) source ~/.gdbinit
之后每次 continue 停在断点、单步、异常,都会自动打印 PC 值并显示当前代码段。
显示执行行号
(gdb) print $pc # 打印当前 PC
(gdb) info frame # 当前帧详细信息(含文件+行号)
(gdb) info line *0x8000 # 地址对应的源代码行
源码+反汇编混合视图
(gdb) disassemble /m main # 源码与反汇编交替显示
常用调试会话流程
(gdb) target remote :3333
(gdb) symbol-file freertos_demo.elf # 确保符号表
(gdb) info file # 验证符号表已加载
(gdb) list main # 查看 main 函数源码
(gdb) break main
(gdb) run
(gdb) continue # 如果加了 hook-stop,自动显示
查看已加载文件与符号
(gdb) info files # 加载的符号文件和内存布局
(gdb) info sources # 已知的源码文件
(gdb) info function main # main 函数地址范围
常见问题
- 符号表没加载:
info sources为空 → 用add-symbol-file或重新加载symbol-file - 源码路径不对:编译时用了相对路径 → 用
directory /path/to/src追加搜索路径 - 行号不精确(优化级别高):
-O2以上可能行号偏移,可用disassemble /m交叉确认
GDB Hook 详解:每次停止自动显示代码
什么是 Hook
Hook 是 GDB 在执行某些操作(如程序停止)之前或之后自动执行的命令集。最常用的是 hook-stop。
基本语法
define hook-stop
<gdb命令>
<gdb命令>
end
最简单的配置
define hook-stop
list
end
每次 GDB 因断点、单步、信号等停止时,自动执行 list 显示当前行附近代码。
完整的 hook-stop 配置
define hook-stop
# 打印当前程序计数器和源文件行号
printf "\n=== Stopped at "
info line *$pc
printf "\n\n"
# 显示当前帧
frame
# 显示当前函数内的代码
list
# 显示寄存器
printf "\n"
printf "pc=%08x sp=%08x ra=%08x\n", $pc, $sp, $ra
# 列出局部变量
info locals
end
其他常用 Hook
# 程序启动时执行
define hook-run
printf "Program starting...\n"
info registers
end
# 程序终止时执行
define hook-exit
printf "Program exited!\n"
end
# 单步(step/next)时执行
define hook-step
printf "Stepping...\n"
end
# 反断钩(pre-command hook):某命令执行前调用
define hook.pre-continue
printf "About to continue...\n"
end
如何保存配置
- 当前会话:直接在 GDB 中输入
define ... end块 - 永久生效:将配置写入
~/.gdbinit(全局)或.gdbinit(项目目录) - 项目专用:在工程目录下放一个
.gdbinit,GDB 启动时自动加载
# 检查 GDB 自动加载的 gdbinit
echo $HOME
# 通常 ~/.gdbinit 被加载
# GDB 启动流程:先加载 ~/.gdbinit,再加载当前目录的 .gdbinit
验证配置是否生效
# 查看所有定义的 hook
show hooks
# 测试:打断点后继续,看 hook 是否触发
(gdb) break main
(gdb) run
(gdb) continue # 应该看到自动输出的代码
高级用法:按条件显示
define hook-stop
if $pc == 0x8000
printf "At entry point!\n"
end
# 只在特定寄存器变化时显示
if $sp != $gdb_prev_sp
printf "Stack changed: %08x -> %08x\n", $gdb_prev_sp, $sp
set $gdb_prev_sp = $sp
end
list
end
# 初始化全局变量
set $gdb_prev_sp = 0
完整项目模板(IAR ICC5 + FreeRTOS)
# 清除旧配置
set confirm off
set pagination off
set print pretty on
set print sevenbit off
set width 200
# 定义 hook-stop
define hook-stop
printf "\n"
printf "================================================\n"
printf "Stopped: "
info line *$pc
printf "\nPC: %08x\n", $pc
printf "SP: %08x\n", $sp
printf "RA: %08x\n", $ra
printf "\n--- Source Context ---\n"
list
printf "\n--- Local Variables ---\n"
info locals
printf "\n--- Registers ---\n"
info registers a0-a7 ra sp
end
# 断点时显示更多
define hook.breakpoint
printf "Hit breakpoint!\n"
end
常见问题
- hook 不触发:确认定义后没有用
clear清除过,可用show hooks查看 - 多次显示:如果有多个 hook 定义叠加,GDB 会全部执行
- 命令报错:hook 内命令失败不会中断其他命令,但会打印错误
- 自定义变量:
set定义的变量在 hook 间持久,info定义的临时变量每次重算
RISC-V GDB 高阶用法
1. Python 扩展
GDB 内置 Python 接口,可编写完整插件:
import gdb
# 自定义命令
class MyPrint(gdb.Command):
def __init__(self):
super(MyPrint, self).__init__("myprint", gdb.COMMAND_USER)
def invoke(self, arg, from_tty):
val = gdb.parse_and_eval(arg)
print(f"value = {val}")
MyPrint()
# 事件钩子(程序暂停时执行)
class MyStop(gdb.Breakpoint):
def __init__(self):
super(MyStop, self).__init__("*0x8000", gdb.BP_BREAKPOINT)
def stop(self):
print("Stopped at 0x8000!")
return True # True = 停止,False = 继续
# 在 .gdbinit 中加载
# python
# exec(file=gdb.current_progspace().dirname + "/gdb_cmds.py")
# end
2. 条件断点与动作断点
# 条件断点:仅当 i > 100 时触发
(gdb) break main if i > 100
# 动作断点:命中后执行多条命令
(gdb) commands
>print x
>print y
>continue
>end
# 条件 + 动作组合
(gdb) break 42 if x == 0
>commands
> printf "x is 0 at line 42\n"
> continue
>end
3. 捕获断点(Catch Points)
catch throw # C++ 异常捕获
catch fork # fork 调用
catch exec # exec 调用
catch signal SIGINT # 信号捕获
catch syscall 60 # 系统调用(Linux)
catch load # 动态库加载
4. 监视点(Watchpoint)
watch *0x20000000 # 内存地址变化时停止
watch x # 变量值变化
watch x # 首次访问(r/watch)
info watchpoints # 查看监视点
5. 多线程调试(FreeRTOS 场景)
info threads # 列出所有线程
thread 2 # 切换到线程 2
info thread 2 # 线程 2 详情
thread apply all bt # 所有线程的调用栈
thread apply 1 2 bt # 仅线程 1 和 2
set scheduler-locking on # 只调度当前线程(单步有用)
set scheduler-locking off
set scheduler-locking step # step 时锁定其他线程
6. TUI 模式(终端图形界面)
# 启动时进入 TUI
gdb -tui freertos_demo.elf
# 或在 GDB 中切换
(gdb) tui enable
(gdb) tui disable
(gdb) layout src # 源码窗口
(gdb) layout asm # 汇编窗口
(gdb) layout regs # 寄存器窗口
(gdb) layout split # 源码+汇编
(gdb) focus src # 聚焦源码
(gdb) focus cmd # 聚焦命令
(gdb) focus next # 切换焦点
7. 记录与回放(Record / Reverse Debug)
(gdb) record # 开始记录
(gdb) continue
(gdb) reverse-continue # 反向执行
(gdb) reverse-step # 反向单步
(gdb) record-full # 全状态记录(开销大)
(gdb) info record # 查看记录状态
反向调试非常实用——可以在断点命中后,一步步往回走看变量如何变化。
8. Core Dump 分析
# 生成 core dump(需内核支持)
# 或使用 GDB 保存
(gdb) generate-core-file core.elf
# 分析 core
gdb freertos_demo.elf core.elf
# 直接分析
gdb -c core.elf freertos_demo.elf
9. Pretty Printer(漂亮打印)
# 自动展开结构体
set print pretty on
# 自定义打印格式
set print elements 0 # 不限数组长度
set print array on # 显示数组索引
set print array-indexes on
# C++ 容器
print my_vec # 显示 vector 内容
print my_map # 显示 map 内容
10. 函数调用(在暂停时调用函数)
# 在当前上下文中调用函数
(gdb) call foo()
(gdb) call printf("hello %d\n", 42)
(gdb) call my_reset_function()
(gdb) call (void)my_init(1, 2, 3)
11. 多目标调试(Target)
# 连接到多个 GDB Server
target remote :3333
target remote :3334 # 第二核
set remotebaud 115200
set remotetimeout 60
# 多进程调试
set follow-fork-mode child # 跟踪子进程
set detach-on-fork on
set non-stop on # 非停止模式(多线程独立)
12. Log 和 Trace 功能
# 记录 GDB 输入日志
set logging on
set logging file gdb_session.log
# 记录命令输出
show logging
set logging overwrite on
13. GDB Script(批处理脚本)
# my_debug.gdb
file freertos_demo.elf
target remote :3333
break main
run
commands 1
printf "In main: x = %d\n", x
continue
end
continue
# 执行
gdb -x my_debug.gdb
14. 自定义命令(define + 宏)
# 快速查看 FreeRTOS 任务列表
define rvtasks
print (void)pvTaskList((void*)0, 1000, 1)
end
define print_stack
printf "Task Stack: sp=%08x\n", $sp
x/16xw ($sp)
end
15. 性能分析(gprof / perf)
# 结合 perf
perf record -g ./freertos_demo.elf
perf report
# GDB 内查看
(gdb) maintenance info symtabs
(gdb) maintenance info binsyms
综合实战:完整的 GDB 会话
# 启动 GDB
set confirm off
set pagination on
set print pretty on
set width 200
# 连接目标
target remote :3333
monitor reset halt
load
# 定义常用命令
define mybt
bt 50
end
define mywatch
set $watched = *(int *)0x20000000
printf "Watched value: %08x\n", $watched
end
# 设置断点
b main
b xTaskCreate if pxCurrentTCB != NULL
watch *0x20000000
# 启动并调试
run
mybt # 调用栈
mywatch # 看变量
c
总结
GDB 是 RISC-V 嵌入式开发中最强大的调试工具之一。从基础的断点、单步,到 Python 扩展、反向调试,GDB 能满足从入门到高级的各种调试需求。配合 OpenOCD 和 JTAG 硬件,调试 RISC-V 芯片变得非常直观高效。