2. 开发环境搭建:QEMU模拟器安装与配置、RISC-V GCC工具链、OpenOCD与GDB调试

做嵌入式开发,环境搭建是第一道坎。很多人觉得这事简单,照着教程敲命令就行。但我在带团队时发现,恰恰是这一步埋下的坑最多——工具链版本不匹配、QEMU启动参数写错、GDB连不上目标……每个问题都能耗掉半天。

这一章,我把自己的经验梳理出来。你跟着走一遍,应该能少踩不少坑。

2.1 整体环境概览

先看看我们要搭什么。说白了就是四样东西:

  • QEMU:模拟硬件,跑我们的RISC-V程序
  • GCC工具链:把C代码编译成RISC-V指令
  • OpenOCD:桥接调试器和目标(这里连QEMU)
  • GDB:调试器,设断点、看变量、单步执行

它们的关系是这样的:

RISC-V 开发调试环境架构 GCC 交叉编译器 riscv64-unknown-elf-gcc QEMU 模拟器 qemu-system-riscv64 GDB 调试器 riscv64-unknown-elf-gdb OpenOCD 调试桥接层 编译 .c → .elf 加载 .elf 到模拟器 GDB 远程调试

工作流程很简单:GCC编译 → QEMU运行 → OpenOCD桥接 → GDB调试。你写代码、编译、在QEMU上跑,GDB随时可以停下来看状态。

2.2 安装RISC-V GCC工具链

工具链的选择,我建议直接用官方的预编译包。自己从源码编译?除非你有特殊需求,否则别折腾。我在项目里试过一次,编译了三个多小时,最后发现跟预编译的没区别。

2.2.1 下载与安装

以Ubuntu 22.04为例,直接apt安装:

sudo apt update
sudo apt install gcc-riscv64-unknown-elf

验证安装:

riscv64-unknown-elf-gcc --version

如果看到版本信息,说明装好了。macOS用户可以用Homebrew:

brew install riscv-tools
我的习惯:装完后我会写一个最小的hello.c,编译成.elf文件,确认工具链能正常工作。这一步花不了两分钟,但能排除很多后续问题。

2.2.2 工具链组件说明

组件 用途 常用命令
编译器 C/C++ → 汇编 riscv64-unknown-elf-gcc
汇编器 汇编 → 机器码 riscv64-unknown-elf-as
链接器 合并目标文件、重定位 riscv64-unknown-elf-ld
反汇编器 机器码 → 汇编 riscv64-unknown-elf-objdump
二进制工具 格式转换、大小查看 riscv64-unknown-elf-objcopy

你想想看,这些工具其实跟普通GCC没区别,只是目标架构变成了RISC-V。编译时加个 -march=rv64gc 指定指令集就行。

2.3 安装与配置QEMU

QEMU是我们最重要的调试平台。没有开发板?没关系,QEMU能模拟出完整的RISC-V硬件环境。

2.3.1 安装QEMU

Ubuntu下:

sudo apt install qemu-system-riscv64

macOS:

brew install qemu

验证:

qemu-system-riscv64 --version
注意:QEMU版本建议6.0以上。我遇到过老版本对RISC-V扩展指令集支持不全的问题,折腾了半天才发现是版本太旧。

2.3.2 启动QEMU的基本参数

启动一个RISC-V虚拟机,最少需要这些参数:

qemu-system-riscv64 \
  -machine virt \
  -cpu rv64 \
  -m 128M \
  -nographic \
  -kernel your_program.elf

参数说明:

  • -machine virt:使用通用虚拟平台,包含UART、CLINT、PLIC等外设
  • -cpu rv64:64位RISC-V CPU
  • -m 128M:128MB内存,够用了
  • -nographic:无图形界面,纯终端
  • -kernel:直接加载ELF内核文件

我个人习惯把参数写到一个脚本里,每次改改内核文件名就行。省得每次敲一长串。

2.3.3 添加调试支持

要配合GDB调试,需要加两个参数:

qemu-system-riscv64 \
  -machine virt \
  -cpu rv64 \
  -m 128M \
  -nographic \
  -kernel your_program.elf \
  -s -S

-s 是 -gdb tcp::1234 的简写,监听1234端口。-S 让CPU在启动时暂停,等GDB连上来再开始执行。

核心要点:调试时一定要加 -s -S。不加的话,程序直接跑完了,你连断点都来不及设。

2.4 OpenOCD与GDB调试

OpenOCD在这里的角色有点特殊。QEMU本身支持GDB直接连接,但OpenOCD提供了更灵活的桥接能力——比如你可以用同一套调试脚本,既连QEMU也连真实开发板。

2.4.1 安装OpenOCD

sudo apt install openocd

macOS:

brew install openocd

2.4.2 配置OpenOCD连接QEMU

创建一个配置文件 qemu-riscv.cfg:

# 使用远程目标(QEMU)
adapter driver remote
remote_port 3333

# RISC-V 目标配置
set _CHIPNAME riscv
set _TARGETNAME $_CHIPNAME.cpu
target create $_TARGETNAME riscv -chain-position $_TARGETNAME

# 初始化
init
targets

启动顺序很重要:先启动QEMU,再启动OpenOCD,最后启动GDB。

具体步骤:

  1. 终端1:qemu-system-riscv64 -machine virt -cpu rv64 -m 128M -kernel test.elf -s -S
  2. 终端2:openocd -f qemu-riscv.cfg
  3. 终端3:riscv64-unknown-elf-gdb test.elf

2.4.3 GDB调试实战

进入GDB后,连接OpenOCD:

(gdb) target remote localhost:3333

然后就可以正常调试了:

(gdb) load              # 加载程序到QEMU
(gdb) break main        # 在main函数设断点
(gdb) continue          # 运行到断点
(gdb) info registers    # 查看寄存器
(gdb) step              # 单步执行
(gdb) print var_name    # 打印变量值
我的调试小技巧:在GDB里写一个 .gdbinit 文件,把常用的命令放进去。比如我每次都会设 set pagination off 和 set confirm off,省得老要按回车确认。

2.5 验证环境:跑一个最小程序

写一个最简单的RISC-V程序,验证整个链路:

// test.c
void _start() {
    volatile int i;
    for(i = 0; i < 100; i++);
    while(1);
}

编译:

riscv64-unknown-elf-gcc -march=rv64gc -nostdlib -o test.elf test.c

启动QEMU并调试:

# 终端1
qemu-system-riscv64 -machine virt -cpu rv64 -m 128M -kernel test.elf -s -S

# 终端2
riscv64-unknown-elf-gdb test.elf
(gdb) target remote localhost:1234
(gdb) break _start
(gdb) continue
(gdb) stepi

如果能看到单步执行,说明环境全部就绪。

曾经踩过的坑:我第一次调试时,GDB连上后 load 命令报错。查了半天,发现是QEMU的 -kernel 参数已经加载了程序,不需要再 load。直接 target remote 后设断点就能跑。

2.6 环境验证清单

检查项 验证方法 预期结果
GCC工具链 riscv64-unknown-elf-gcc --version 显示版本号
QEMU qemu-system-riscv64 --version 显示版本号
OpenOCD openocd --version 显示版本号
GDB riscv64-unknown-elf-gdb --version 显示版本号
编译测试 编译test.c并生成.elf 无报错,生成test.elf
调试链路 GDB连接QEMU,设断点,单步 能停在断点,stepi执行

到这步,你的开发环境就齐了。后面每一章的驱动代码,都会在这个环境里编译、运行、调试。嗯,工具准备好了,接下来就是真刀真枪地写驱动了。