2. 开发环境搭建:QEMU模拟器安装与配置、RISC-V GCC工具链、OpenOCD与GDB调试
做嵌入式开发,环境搭建是第一道坎。很多人觉得这事简单,照着教程敲命令就行。但我在带团队时发现,恰恰是这一步埋下的坑最多——工具链版本不匹配、QEMU启动参数写错、GDB连不上目标……每个问题都能耗掉半天。
这一章,我把自己的经验梳理出来。你跟着走一遍,应该能少踩不少坑。
2.1 整体环境概览
先看看我们要搭什么。说白了就是四样东西:
- QEMU:模拟硬件,跑我们的RISC-V程序
- GCC工具链:把C代码编译成RISC-V指令
- OpenOCD:桥接调试器和目标(这里连QEMU)
- 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
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
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:
qemu-system-riscv64 -machine virt -cpu rv64 -m 128M -kernel test.elf -s -S - 终端2:
openocd -f qemu-riscv.cfg - 终端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 # 打印变量值
.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
如果能看到单步执行,说明环境全部就绪。
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执行 |
到这步,你的开发环境就齐了。后面每一章的驱动代码,都会在这个环境里编译、运行、调试。嗯,工具准备好了,接下来就是真刀真枪地写驱动了。