3. 案例3
任务说明
本案例面向RISC-V架构(RV32/RV64),目标是为Bootloader建立异常向量表并完成mtvec寄存器初始化,使处理器在发生异常或中断时能跳转到正确的处理入口。实现范围限定为:向量表基址设置、异常入口的栈帧保存与恢复、以及一个可验证的中断处理框架。
环境依赖
- RISC-V交叉编译器(如
riscv64-unknown-elf-gcc) - QEMU模拟器(
qemu-system-riscv32或qemu-system-riscv64) - 目标平台:支持机器模式(M-mode)的RISC-V处理器
核心实现
以下代码实现完整的异常向量表与mtvec初始化逻辑。向量表采用直接跳转模式(mtvec.MODE=0),所有异常入口共用同一个处理函数,通过mcause寄存器区分异常类型。
// trap_vector.S - 异常向量表与入口处理
.section .text.init
.globl _start
.globl trap_vector
.globl trap_handler
.align 4 // 向量表必须4字节对齐(RV32)
_start:
// 设置栈指针(Bootloader阶段使用临时栈)
la sp, _stack_top
// 初始化异常向量表
la t0, trap_vector
csrw mtvec, t0
// 开启全局中断(M-mode)
li t0, 0x8 // MIE位
csrw mstatus, t0
// 触发软件中断用于验证(可选)
li t0, 0x2 // MSIP位
csrw msip, t0
// 主循环
main_loop:
wfi
j main_loop
// 异常向量表入口(直接跳转模式)
.align 4
trap_vector:
// 保存上下文到栈
addi sp, sp, -128
sd x1, 0(sp) // ra
sd x2, 8(sp) // sp(原始值)
sd x3, 16(sp) // gp
sd x4, 24(sp) // tp
sd x5, 32(sp) // t0
sd x6, 40(sp) // t1
sd x7, 48(sp) // t2
sd x8, 56(sp) // s0/fp
sd x9, 64(sp) // s1
sd x10, 72(sp) // a0
sd x11, 80(sp) // a1
sd x12, 88(sp) // a2
sd x13, 96(sp) // a3
sd x14, 104(sp) // a4
sd x15, 112(sp) // a5
sd x16, 120(sp) // a6
// 注意:x17~x31可按需扩展,此处为最小框架
// 保存原始sp值(当前sp已偏移)
addi t0, sp, 128
sd t0, 8(sp)
// 调用C处理函数
csrr a0, mcause // 异常原因
csrr a1, mepc // 异常PC
mv a2, sp // 栈帧指针
call trap_handler
// 恢复上下文
ld x1, 0(sp)
ld x3, 16(sp)
ld x4, 24(sp)
ld x5, 32(sp)
ld x6, 40(sp)
ld x7, 48(sp)
ld x8, 56(sp)
ld x9, 64(sp)
ld x10, 72(sp)
ld x11, 80(sp)
ld x12, 88(sp)
ld x13, 96(sp)
ld x14, 104(sp)
ld x15, 112(sp)
ld x16, 120(sp)
// 恢复原始sp(不加载x2,避免破坏当前栈指针)
ld sp, 8(sp)
// 返回触发异常的指令
mret
// trap_handler.c - 异常处理函数
.section .text
.globl trap_handler
trap_handler:
// 参数:a0=mcause, a1=mepc, a2=栈帧指针
// 此处仅做最小处理:死循环标记(实际Bootloader可记录错误码)
li t0, 0xdeadbeef
sw t0, 0(a2) // 写入栈帧标记
1: j 1b // 异常后暂停(便于观察)
.section .bss
.align 4
_stack_bottom:
.space 1024
_stack_top:
链接脚本(linker.ld)
OUTPUT_ARCH(riscv)
ENTRY(_start)
SECTIONS
{
. = 0x80000000; /* QEMU默认加载地址 */
.text : { *(.text.init) *(.text) }
.bss : { *(.bss) }
}
编译与运行
# 编译(RV32示例)
riscv64-unknown-elf-gcc -march=rv32im -mabi=ilp32 -nostdlib -T linker.ld \
trap_vector.S trap_handler.c -o trap_test.elf
# QEMU运行(观察异常触发)
qemu-system-riscv32 -machine virt -kernel trap_test.elf -nographic
# 预期输出:程序启动后进入wfi,软件中断触发后进入trap_handler死循环
# 可通过Ctrl+A X退出QEMU
验证方法
- 向量表对齐检查:
mtvec寄存器低2位必须为0(直接模式),可通过GDB读取csr mtvec确认。 - 异常触发验证:代码中通过写
msip寄存器触发软件中断,若处理器跳转到trap_vector并执行trap_handler中的死循环,说明向量表工作正常。 - 上下文保存验证:在
trap_handler中检查栈帧标记0xdeadbeef是否写入,确认栈帧结构正确。 - 通过标准:QEMU运行后无未处理异常(如Illegal Instruction),且程序停在
trap_handler死循环处(可通过GDB或QEMU monitor确认PC值)。
关键说明
- 本实现采用直接向量表模式(
mtvec.MODE=0),所有异常共用入口,通过mcause区分类型。若需向量中断模式(MODE=1),需为每个中断源单独设置入口。 - 栈帧保存了16个通用寄存器(x1~x16),实际Bootloader可根据需要扩展至全部32个寄存器。恢复时未加载
x2(sp),因为当前sp指向栈帧,需先恢复原始sp值。 - 异常处理函数当前仅做死循环标记,实际Bootloader中应在此处记录错误状态并执行恢复策略(如重启或跳转回主流程)。
- 若目标平台使用RV64,需将
sd/ld指令改为sd/ld(64位),并将栈帧大小调整为256字节。