网站Logo 网友马大帅的博客

RISC-V GDB 命令行调试基础教程

ughostx
12
2026-04-27

RISC-V GDB 命令行调试基础教程

基础概念

GDB 调试器支持 RISC-V 架构,通常使用交叉编译的 GDB(如 riscv64-unknown-elf-gdbriscv-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=zerox1=rax2=spx3=gpx4=tpx5-x7=t0x16-17=a0-a1x18-27=a2-a11x28-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

如何保存配置

  1. 当前会话:直接在 GDB 中输入 define ... end
  2. 永久生效:将配置写入 ~/.gdbinit(全局)或 .gdbinit(项目目录)
  3. 项目专用:在工程目录下放一个 .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 芯片变得非常直观高效。


动物装饰