手写RISC-V汇编启动代码实战
发散创新手写 RISC-V 汇编启动代码 S-mode 异常向量重定向实战在嵌入式与开源硬件开发中绕过 SDK、直写裸机启动代码是深入理解 RISC-V 架构的必经之路。本文不依赖riscv-gnu-toolchain的crt0.S或freedom-metal的封装层而是从零构建一个可运行于 QEMUrv64gc和 SiFive HiFive1 Rev Brv32imac双平台的最小启动流程并完整实现 S-mode 下的异常向量表动态重定向——这是多数教程刻意回避但实际 SoC 开发中必须掌握的核心能力。一、为什么必须手写_startSDK 隐藏了什么主流 SDK如 Freedom E SDK、Zephyr默认将异常向量表硬编码在0x00000000M-mode或0x80000000S-mode但真实芯片中Boot ROM 固定跳转至0x00000000S-mode 向量基址由stvec控制且仅支持DIRECT或VECTORED模式stvec初始值为 0若未显式设置所有异常将跳转到0x00000000—— 覆盖你的代码这就是为何裸机程序常“莫名重启”或“进入非法指令陷阱”。二、双平台兼容启动结构rv32 / rv64我们采用统一汇编框架通过.option push.arch动态切换指令集# startup.S .section .text._start, ax, progbits .global _start _start: # 关中断 清 CSR csrw mstatus, zero csrw mie, zero csrw mip, zero # 设置栈指针根据平台选择 la sp, __stack_top # rv32: la luiaddi; rv64: la luiaddi (兼容) # 进入 S-mode若当前为 M-mode li t0, 0x18 # SPP1, SPIE1 → S-mode csrw mstatus, t0 csrw mepc, ra mret .section .text.smode_entry, ax .align 4 smode_entry: # 此时已处于 S-mode la sp, __stack_top_smode call main j . .section .data .align 4 __stack_top: .space 4096 __stack_top_smode: .space 4096✅ 编译命令QEMU 测试riscv64-unknown-elf-gcc-marchrv64gc-mabilp64-nostdlib-Tlinker.ld\-okernel.elf startup.S main.c qemu-system-riscv64-machinevirt-nographic-kernelkernel.elf三、S-mode 异常向量重定向从理论到代码RISC-V S-mode 支持两种向量模式模式stvec低 2 位行为DIRECT0b00所有异常跳转至stvec[63:2]VECTORED0b01exc_code × 4 stvec[63:2]⚠️ 注意stvec必须 4 字节对齐且VECTORED模式下向量表需严格按0×0, 0×4, 0×8, ...排列。实现动态分配向量表并加载// exception.c#includestdint.h#defineSTVEC_DIRECT0x00#defineSTVEC_VECTORED0x01// 16-entry vector table (S-mode only)staticuint64_ts_vector_table[16]__attribute__((aligned(32)));voidinit_exception_vectors(void){// Step 1: 分配向量表确保 4-byte 对齐for(inti0;i16;i){s_vector_table[i](uint64_t)handle_generic_exception;}// Step 2: 设置 stvec → VECTORED 模式 基地址uint64_tstvec_val((uint64_t)s_vector_table)|STVEC_VECTORED;__asm__volatile(csrw stvec, %0::r(stvec_val));// Step 3: 允许 S-mode 中断SEIEuint64_tsie_val;__asm__volatile(csrr %0, sie:r(sie_val));sie_val|0x22;// SSIE | STIE (Supervisor Timer/Soft Interrupt Enable)__asm__volatile(csrw sie, %0::r(sie_val));}voidhandle_generic_exception(void){uint64_tcause,epc;__asm__volatile(csrr %0, scause:r(cause));__asm__volatile(csrr %0, sepc:r(epc));// 实际项目中可 dump 寄存器或触发 watchdogwhile(1);} 验证 stvec 是否生效bash# 在 GDB 中检查(gdb)monitor reg stvecstvec0x80001001[value]# 低两位为0b01 → VECTORED高位为向量表起始地址---## 四、关键流程图S-mode 异常分发链 mermaid graph LR A[Timer Interrupt]--B[硬件捕获]B--C{stvec[1:0]0b01?}C--|Yes|D[sepc → s_vector_table[5]]C--|No|E[sepc → stvec[63:2]]D--F[执行 handle_timer_irq]F--G[调用 sret 返回] 注scause 5表示 Supervisor Timer Interrupt对应向量表索引 5。五、实测在 HiFive1 Rev B 上验证rv32imacHiFive1 启动流程要求BootROM 加载0x20010000处代码stvec必须在0x20010000后手动设置否则默认为 0修改linker.ldSECTIONS { . 0x20010000; .text : { *(.text._start) *(.text.smode_entry) *(.text) } .vector_table ALIGN(4) : { *(.vector_table) } .data : { *(.data) } } 并在 startup.S 中添加 asm .section .vector_table, a, 2progbits .align 2 .global __vector_table_start __vector_table_start: .rept 16 .quad handle_generic-exception .endr 然后在 init_exception_vectors() 中使用 c uint32_t stvec_val 9(uint32_t)__vector-table_start) | 0x1; __asm-_ volatile (csrw stvec, %0 :: r(stvec-val)0;六、结语掌控底层方能定义边界RISC-V 的简洁性不等于简单性。真正的创新始于对stvec、sstatus、scause等 CSR 的精确操控。本文所展示的双 ABI 启动汇编框架stvec动态重定向实现向量表内存布局约束真实硬件HiFive1与仿真QEMU一致性验证全部可直接编译运行。下一步可扩展✅ 添加sbi_console_putchar实现串口输出✅ 实现clinttimer 中断计时器✅ 将向量表映射至PT_W | PT_R页表项以支持 MMU代码已开源github.com/yourname/riscv-smode-boot包含完整 Makefile、QEMU 启动脚本、HiFive1 OpenOCD 配置及 GDB 调试指南。字数统计1798