RISC-V开发环境搭建
目录
- [1.1 QEMU模拟器](#1.1 QEMU模拟器)
- [1.1.1 QEMU简介](#1.1.1 QEMU简介)
- [1.1.2 QEMU Virt实验平台特性](#1.1.2 QEMU Virt实验平台特性)
- [1.1.3 QEMU优势](#1.1.3 QEMU优势)
- [1.2 开发环境配置](#1.2 开发环境配置)
- [1.2.1 系统要求](#1.2.1 系统要求)
- [1.2.2 安装QEMU](#1.2.2 安装QEMU)
- [1.2.3 安装RISC-V编译器](#1.2.3 安装RISC-V编译器)
- [1.2.3.1 官方工具链安装](#1.2.3.1 官方工具链安装)
- [1.2.3.2 验证编译器安装](#1.2.3.2 验证编译器安装)
- [1.2.3.3 编译测试程序](#1.2.3.3 编译测试程序)
- [1.2.4 验证安装](#1.2.4 验证安装)
- [1.3 调试工具](#1.3 调试工具)
- [1.3.1 GDB调试器](#1.3.1 GDB调试器)
- [1.3.1.1 远程调试](#1.3.1.1 远程调试)
- [1.3.2 VS Code调试配置](#1.3.2 VS Code调试配置)
- [1.3.2.1 安装必要扩展](#1.3.2.1 安装必要扩展)
- [1.3.2.2 配置launch.json](#1.3.2.2 配置launch.json)
- [1.3.2.3 调试步骤](#1.3.2.3 调试步骤)
- [1.3.1 GDB调试器](#1.3.1 GDB调试器)
- [VS Code调试界面示例](#VS Code调试界面示例)
1.1 模拟器选择
1.1.1 QEMU模拟器
QEMU Virt实验平台模拟的是一款通用的RISC-V开发板,为RISC-V架构学习和开发提供了完整的仿真环境。
1.1.2 NEMU模拟器
NEMU是一个轻量级的RISC-V模拟器,由中科院计算所开发,专门用于教学和学习RISC-V架构。
1.1.3 QEMU Virt实验平台特性
QEMU Virt平台支持以下硬件特性:
- ✅ 最多支持8个RV32GC/RV64GC通用处理器核心
- ✅ 支持CLINT本地中断控制器
- ✅ 支持PLIC中断控制器
- ✅ 支持NOR Flash
- ✅ 支持兼容NS16550的串口
- ✅ 支持RTC
- ✅ 支持8个VirtIO-MMIO传输设备
- ✅ 支持1个PCIe主机桥接设备
- ✅ 支持fw_cfg,用于从QEMU获取固件配置信息
1.1.4 QEMU优势
QEMU基本能满足我们学习RISC-V体系结构的要求,而且免费,调试方便
主要优势:
- 免费开源:无需购买昂贵的硬件开发板
- 功能完整:支持完整的RISC-V指令集和硬件特性
- 调试便利:提供丰富的调试功能和接口
- 学习友好:适合初学者理解RISC-V架构
- 可扩展性:支持多种RISC-V配置和扩展
1.1.5 NEMU优势
主要优势:
- 轻量级:代码简洁,易于理解和学习
- 教学友好:专门为教学设计,注释详细
- 调试便利:提供丰富的调试功能和接口
- 开源免费:完全开源,适合学习研究
- 中文支持:有中文文档和社区支持
1.2 开发环境配置
1.2.1 系统要求
- 操作系统:Ubuntu 24.04 LTS
- 内存:建议8GB以上
- 存储:至少10GB可用空间
1.2.2 安装模拟器
1.2.2.1 安装QEMU
bash
sudo apt update
sudo apt install qemu-system-misc
1.2.2.2 安装NEMU
NEMU是一个轻量级的RISC-V模拟器,适合教学和学习使用:
bash
# 克隆NEMU源码
git clone https://github.com/OpenXiangShan/NEMU.git
cd NEMU
# 安装依赖
sudo apt install build-essential libreadline-dev
# 配置和编译
make menuconfig
make xxxx_defconfig
make -j$(nproc)
# 安装到系统路径(可选)
sudo make install
验证NEMU安装:
bash
# 检查NEMU版本
riscv64-nemu-interpreter --version
1.2.3 安装RISC-V编译器
1.2.3.1 官方工具链安装
bash
# 安装RISC-V GNU工具链
sudo apt update
sudo apt install gcc-riscv64-linux-gnu g++-riscv64-linux-gnu
# 安装RISC-V GDB调试器
sudo apt install gdb-multiarch
# 安装其他开发工具
sudo apt install make cmake git
1.2.3.2 验证编译器安装
bash
# 检查RISC-V GCC版本
riscv64-linux-gnu-gcc --version
# 检查RISC-V GDB版本
riscv64-linux-gnu-gdb --version
# 检查工具链路径
which riscv64-linux-gnu-gcc
1.2.3.3 编译测试程序
创建C语言环境初始化汇编文件:start.s
c
.section .text.boot
.globl _start
_start:
/* Disable M-mode interrupts */
csrw mie, zero
/* Set up stack, stack size is 4KB */
la sp, stacks_start
li t0, 4096
add sp, sp, t0
csrw mscratch, sp
tail main
.section .data
.align 12
.global stacks_start
stacks_start:
.skip 4096
创建测试文件 hello.c
:
c
#define UART 0x10000000
#define UART_SIZE 4096
/* THR:transmitter holding register */
#define UART_DAT (UART+0x00) /* Data register */
#define UART_IER (UART+0x01) /* Interrupt enable register */
#define UART_IIR (UART+0x02) /* Interrupt identification register (read only) */
#define UART_FCR (UART+0x02) /* FIFO control register (write only) */
#define UART_LCR (UART+0x03) /* Line control register */
#define UART_MCR (UART+0x04) /* Modem control register */
#define UART_LSR (UART+0x05) /* Line status register */
#define UART_MSR (UART+0x06) /* Modem status register */
#define UART_DLL (UART+0x00) /* Divisor latch register low 8 bits */
#define UART_DLM (UART+0x01) /* Divisor latch register high 8 bits */
#define UART_LSR_ERROR 0x80 /* Error */
#define UART_LSR_EMPTY 0x40 /* Transmit FIFO and shift register empty */
#define UART_LSR_TFE 0x20 /* Transmit FIFO empty */
#define UART_LSR_BI 0x10 /* Break interrupt */
#define UART_LSR_FE 0x08 /* Framing error (no stop bit received) */
#define UART_LSR_PE 0x04 /* Parity error */
#define UART_LSR_OE 0x02 /* Overrun error */
#define UART_LSR_DR 0x01 /* Data ready in FIFO */
#define __arch_getl(a) (*(volatile unsigned int *)(a))
#define __arch_putl(v,a) (*(volatile unsigned int *)(a) = (v))
#define __arch_getb(a) (*(volatile unsigned char *)(a))
#define __arch_putb(v,a) (*(volatile unsigned char *)(a) = (v))
#define __arch_getq(a) (*(volatile unsigned long *)(a))
#define __arch_putq(v,a) (*(volatile unsigned long *)(a) = (v))
#define dmb() __asm__ __volatile__ ("" : : : "memory")
#define readl(c) ({ unsigned int __v = __arch_getl((unsigned long)c); dmb(); __v; })
#define writel(v,c) ({ unsigned int __v = v; dmb(); __arch_putl(__v, (unsigned long)c);})
#define readb(c) ({ unsigned char __v = __arch_getb(c); dmb(); __v; })
#define writeb(v,c) ({ unsigned char __v = v; dmb(); __arch_putb(__v,c);})
#define readq(c) ({ unsigned long __v = __arch_getq(c); dmb(); __v; })
#define writeq(v,c) ({ unsigned long __v = v; dmb(); __arch_putq(__v,c);})
#define UART_DEFAULT_CLOCK 1843200
#define UART_DEFAULT_BAUD 115200
// UART send character function
void uart_send(char c)
{
while((readb(UART_LSR) & UART_LSR_EMPTY) == 0)
;
writeb(c, UART_DAT);
}
// UART send string function
void uart_send_string(char *str)
{
int i;
for (i = 0; str[i] != '\0'; i++)
uart_send((char) str[i]);
}
int main(void)
{
unsigned int divisor = UART_DEFAULT_CLOCK / (16 * UART_DEFAULT_BAUD);
/* disable interrupt */
writeb(0, UART_IER);
/* Enable DLAB (set baud rate divisor) */
writeb(0x80, UART_LCR);
writeb((unsigned char)divisor, UART_DLL);
writeb((unsigned char)(divisor >> 8), UART_DLM);
/* 8 bits, no parity, one stop bit */
writeb(0x3, UART_LCR);
/* Enable FIFO, clear FIFO, set 14-byte threshold */
writeb(0xc7, UART_FCR);
/* Enable receive buffer full interrupt */
writeb(0x1, UART_IER);
// Output hello world
uart_send_string("hello world\n");
// Infinite loop to keep program running
while(1) {
// Other functionality can be added here
}
}
创建链接脚本
c
ENTRY(_start)
SECTIONS
{
/* 设置sbi的加载入口地址为0x80000000 */
. = 0x80000000,
.text.boot : { *(.text.boot) }
.text : { *(.text) }
.rodata : { *(.rodata) }
.data : { *(.data) }
. = ALIGN(0x8);
bss_begin = .;
.bss : { *(.bss*) }
bss_end = .;
}
编译测试:
bash
# 编译为RISC-V可执行文件
riscv64-linux-gnu-gcc -save-temps=obj -g -O0 -Wall -nostdlib -mcmodel=medany -mabi=lp64 -march=rv64imafd -fno-PIE -fno-omit-frame-pointer -Wno-builtin-declaration-mismatch -c hello.c -o hello.o
riscv64-linux-gnu-as -g -march=rv64imafd -mabi=lp64 -o start.o start.s
riscv64-linux-gnu-ld -T linker.ld -o hello.elf hello.o start.o
# 查看文件信息
file hello.elf
# 使用QEMU运行
qemu-system-riscv64 -nographic -machine virt -m 128M -bios hello.elf
1.2.4 验证安装
验证QEMU:
bash
qemu-system-riscv64 --version
验证NEMU:
bash
riscv64-nemu-interpreter --version
验证编译器:
bash
riscv64-linux-gnu-gcc --version
riscv64-linux-gnu-gdb --version
1.3 调试工具
1.3.1 GDB调试器
1.3.1.1 远程调试
QEMU支持GDB远程调试:
bash
# 启动QEMU并开启GDB调试端口
qemu-system-riscv64 -machine virt -cpu rv64 -m 128M -nographic -bios hello.elf -s -S
# 在另一个终端连接GDB
riscv64-linux-gnu-gdb
(gdb) target remote :1234
1.3.2 VS Code调试配置
VS Code提供了强大的调试功能,可以方便地调试RISC-V程序。通过配置launch.json文件,可以实现图形化的调试体验。
1.3.2.1 安装必要扩展
在VS Code中安装以下扩展:
- C/C++ (Microsoft) - 提供C/C++语言支持
- C/C++ Extension Pack - 包含调试、智能感知等功能
- RISC-V (可选) - RISC-V汇编语言支持
安装方法:
- 打开VS Code
- 按
Ctrl+Shift+X
打开扩展面板 - 搜索并安装上述扩展
1.3.2.2 配置launch.json
在项目根目录创建 .vscode/launch.json
文件:
json
{
"version": "0.2.0",
"configurations": [
{
"name": "调试RV64裸机程序(QEMU)",
"type": "cppdbg",
"request": "launch",
"program": "${workspaceFolder}/hello.elf",
"cwd": "${workspaceFolder}",
"MIMode": "gdb",
"miDebuggerPath": "gdb-multiarch",
"miDebuggerArgs": "--interpreter=mi",
"stopAtEntry": true,
"externalConsole": false,
"setupCommands": [
{
"description": "设置架构为RISC-V 64位",
"text": "set architecture riscv:rv64",
"ignoreFailures": true
},
{
"description": "加载符号文件",
"text": "file ${workspaceFolder}/hello.elf",
"ignoreFailures": true
},
{
"description": "连接到QEMU GDB服务器",
"text": "target remote localhost:1234",
"ignoreFailures": false
},
{
"description": "在_start函数设置断点",
"text": "break _start",
"ignoreFailures": true
},
{
"description": "在汇编文件设置断点",
"text": "break sbi_boot.S:_start",
"ignoreFailures": true
},
{
"description": "为gdb启用整齐打印",
"text": "set print pretty on",
"ignoreFailures": true
},
{
"description": "设置反汇编格式",
"text": "set disassembly-flavor intel",
"ignoreFailures": true
},
{
"description": "启用汇编单步调试",
"text": "set step-mode on",
"ignoreFailures": true
},
{
"description": "显示汇编代码",
"text": "set disassemble-next-line on",
"ignoreFailures": true
},
{
"description": "设置寄存器显示格式",
"text": "set print registers on",
"ignoreFailures": true
},
{
"description": "设置源码目录",
"text": "directory ${workspaceFolder}",
"ignoreFailures": true
},
{
"description": "启用源码和汇编混合显示",
"text": "set disassemble-next-line auto",
"ignoreFailures": true
}
]
}
]
}
配置说明:
program
: 指定要调试的ELF文件路径miDebuggerPath
: 使用gdb-multiarch调试器setupCommands
: 自动执行的GDB命令- 设置RISC-V 64位架构
- 加载符号文件
- 连接到QEMU的GDB服务器(端口1234)
- 在程序入口点设置断点
1.3.2.3 调试步骤
步骤1:启动QEMU调试服务器
在终端中运行以下命令启动QEMU并开启GDB调试端口:
bash
qemu-system-riscv64 -machine virt -cpu rv64 -m 128M -nographic -bios hello.elf -s -S
参数说明:
-s
: 开启GDB调试服务器(默认端口1234)-S
: 启动时暂停,等待GDB连接
步骤2:在VS Code中开始调试
- 在VS Code中打开项目文件夹
- 按
F5
或点击调试面板的"开始调试"按钮 - 选择"调试RV64裸机程序(QEMU)"配置
- VS Code将自动连接到QEMU的GDB服务器
步骤3:调试功能使用
- 设置断点: 在代码行号左侧点击设置断点
- 单步执行 : 使用
F10
(Step Over) 或F11
(Step Into) - 继续执行 : 使用
F5
(Continue) - 查看变量: 在"变量"面板查看当前作用域的变量
- 查看寄存器 : 在"调试控制台"输入
info registers
查看RISC-V寄存器 - 查看内存 : 在"调试控制台"输入
x/10x $sp
查看栈内存
调试技巧:
- 程序入口调试 : 程序从
_start
开始执行,这是设置第一个断点的好地方 - 汇编调试: 可以查看RISC-V汇编指令的执行过程
- 内存布局 : 使用
info proc mappings
查看内存映射 - 寄存器观察 : 重点观察
pc
(程序计数器)、sp
(栈指针)等关键寄存器
VS Code调试界面示例
下图展示了在VS Code中调试RISC-V程序的界面:
界面说明:
- 左侧面板: 显示变量、监视、调用栈等调试信息
- 中央代码区: 显示源代码,支持断点设置和单步执行
- 底部调试控制台: 可以输入GDB命令进行高级调试
- 调试工具栏: 提供继续、单步、重启等调试控制按钮