一、环境准备与工具链解析

1.1 核心组件

一个标准的 RISC-V GDB 调试环境包含以下组件:

     注意硬件代理可根据目标架构灵活调整,比如RV架构,基本组网拓扑为:GDB+debugserver+CklinkLite.

二、GDB 基础与 RISC-V 架构适配

2.1 启动与连接

针对 RISC-V,GDB 启动时通常需要显式指定架构,避免因识别错误导致的 Remote 'g' packet reply is too long错误。

# 启动 GDB
$ gdb-multiarch firmware.elf

# 进入 GDB Shell 后
(gdb) set architecture riscv:rv64
(gdb) target remote :1234  //启动degserver 获取IP地址

注意 debugserver获取IP时必须处于上电状态,已获取CLink枚举到的硬件IP和端口号:如下图所示

2.2 RISC -V寄存器视角

不同于通用寄存器,RISC-V 拥有丰富的 CSR (Control and Status Registers)

# 查看通用寄存器
(gdb) info registers

# 查看 CSR (RISC-V 调试的重点)
(gdb) info registers csr
(gdb) p $mstatus
(gdb) p $mie
(gdb) p $mcause

2.3 调试环境导入

在上面的bash环境中继续操作一下指令:

1. 导入需要的符号文件(不触碰硬件)
(gdb) file firmware.elf
2. 【关键】将代码加载到目标板内存中
(gdb) load

注意如果是 QEMU 使用 -kernel 启动了,通常不需要这步;
                  如果是 OpenOCD/JTAG,必须执行;

三、硬件调试架构里两类(Trigger)

注意:常规指令不在列举  自学参考官网:Top (Debugging with GDB)

3.1   I-trigger(Instruction Trigger)工作原理

3.1.1  软件 I‑trigger —— 软件断点(最常见的 break

以 RISC-V 为例:

  • GDB 把你要断下来的那条指令暂存,写入一个 ebreak(或等价编码)

  • CPU 正常取指执行到那里 → 执行 ebreak→ 进入调试异常 / Debug Mode

  • 调试器接管:

    • ebreak还原为原指令(才能继续单步/继续执行)

    • 停住并报告给用户

  • c时,调试器通常会做一次 re‑insert(再设断点):要么单步一条再重新写 ebreak,要么利用硬件能力跳过

关键特征

  • 不占用硬件寄存器(数量几乎不受限,取决于内存可写性)

  • 代价:修改代码区(在某些 ROM/XIP Flash 上不可用);多线程/多核时需要小心 race(写指令的同时别的核可能在取指)

3.1.2  硬件 I‑trigger —— 硬件指令断点(PC 比较器)

     很多芯片的 Debug Module 内置了少量 PC‑match 寄存器

  • RISC-V Debug Spec 里体现为 trigger(tdata1/tdata2),其中 type=execute

  • 匹配地址 + enable → 当 PC == 该地址​ 时硬件直接把 CPU 拉进 Debug Mode

  • GDB 层面对应 hb <addr>(hardware break)

关键特征

特征:精准、开销低;数量极少(常见 1~4 个)

    b. MMU/MPU 辅助(访问权限 trick)

特征:更通用(尤其当芯片没那么多硬件 watch 寄存器时),但实现更复杂、可能更慢、对 MMU 状态强依赖。

3.2.2  软件 D‑trigger(GDB 的纯软件 watch)

没有任何硬件 watch 可用​ 时,GDB 会退化为:

特征:慢;只能用于非常简单/可控场景。

3.2.3 小结

I‑trigger 的分类:

Software I‑trigger(软件断点)

  • 不改代码,适合 ROM / XIP / 不可写代码区

  • 数量极少(常见 2~6 个)

  • 对 debug module 的实现质量依赖大;

  • 3.2  D‑trigger(Data Trigger / Watchpoint)工作原理

  • D‑trigger比 I‑trigger"脏"得多,因为 CPU 没有一条固定 PC 能直接告诉你下次会访问哪个数据地址——访问分散在各处
  • 3..2.1 硬件 D‑trigger(地址匹配 / 访问类型匹配)

    两种主流硬件实现路子:

    a.专用地址匹配寄存器(更"正统"的 D‑trigger)

  • Debug Module 里有少量 地址比较器 + 掩码 + 访问类型选择

  • RISC-V:trigger 的 type=load/ type=store(或 load-store),你把监视地址写入 tdata2,配置 tdata1里 match/action 字段

  • 当 LSU(Load-Store Unit)发出访问且 地址命中​ 时 → Debug Mode / watchpoint exception

  • 调试器把目标页/区域的访问权限改成"不可读/不可写"

  • CPU 一旦访问 → Access Fault / Page Fault / Bus Error

  • 异常处理路径里调试 stub 判断:是不是"我设的 watch"?是 → 停下来;否 → 恢复

  • 单步每条指令(或每个跳转区间),然后检查是否有访问落在你 watch 的地址范围内

  • 所以你会看到 GDB 提示类似:Watchpoint 1 (var) deleted because it's a software watchpoint that can't be evaluated

  • 原理:代码区指令 ⇒ trap 指令(RISC-V ebreak

  • GDB:break/ b

Hardware I‑trigger(硬件 PC‑match)

  • 原理:Debug Module 的比较器匹配 PC

  • GDB:hbreak/ hb

D‑trigger 的分类

  • Hardware D‑trigger(Addr‑Match)

    • 原理:LSU 访问地址匹配(可选 mask)+ 方向过滤(R/W/RW)

    • GDB:watch/ rwatch/ awatch(走硬件寄存器)

  • MMU/MPU fault‑based D‑trigger

    • 原理:改权限 ⇒ fault ⇒ 调试器判定

  • Software D‑trigger(纯单步模拟)

    • 原理:GDB 自己模拟"有没有访问那段地址"

    • 特征:慢,且不总是成立(尤其优化/OOO/DMA)

    • Hybrid(常见):GDB 优先用硬件,不够再 fallback 到软件(不同端口行为不完全一致)

    • 注意:

    • I‑trigger 锚定的是 PC:执行管道里 PC 天然就是一级一级出来的,拦它就一个匹配点。

    • D‑trigger 锚定的是"数据地址":Load/Store 散布在指令流里,地址可能来自寄存器间接寻址、变址、跨函数别名……

    • 四 GDB映射

    • # ===== I-trigger(指令触发)=====
      break main        
      hbreak *_start   // 强制走硬件 PC-match( target 必须实现)
      
      # ===== D-trigger(数据触发)=====
      watch  foo          # 写触发(最常见)
      rwatch foo          # 读触发(硬件支持时才真硬件)
      awatch foo          # 读写都触发
      watch *(int *)0x80001000

      查看:

    • info  +指令

    • info breakpoints   # 可看到哪些是 hw 哪些是 sw

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐