assembly-riscv
RISC-V 汇编
用途
指导代理学习 RISC-V 汇编编程:RV32/RV64 指令集、寄存器命名与调用约定(psABI)、ISA 扩展命名、使用 GCC/Clang 编写内联汇编、压缩(RVC)指令,以及基于 QEMU 的仿真与 GDB 远程调试。
触发场景
- "如何编写 RISC-V 汇编?"
- "RISC-V 调用约定寄存器有哪些?"
- "如何在 C 中为 RISC-V 使用内联汇编?"
- "RISC-V 扩展字母代表什么(IMAFD)?"
- "如何使用 QEMU 仿真 RISC-V?"
- "如何使用 GDB 调试 RISC-V 代码?"
工作流程
1. 寄存器文件与调用约定
RISC-V 有 32 个整数寄存器(x0–x31),带有 ABI 名称:
| 寄存器 | ABI 名称 | 用途 | 由谁保存 |
|---|---|---|---|
| x0 | zero | 硬连线零 | — |
| x1 | ra | 返回地址 | 调用者 |
| x2 | sp | 栈指针 | 被调用者 |
| x3 | gp | 全局指针 | — |
| x4 | tp | 线程指针 | — |
| x5–x7 | t0–t2 | 临时寄存器 | 调用者 |
| x8 | s0/fp | 帧指针 | 被调用者 |
| x9 | s1 | 保存寄存器 | 被调用者 |
| x10–x11 | a0–a1 | 参数 / 返回值 | 调用者 |
| x12–x17 | a2–a7 | 参数 | 调用者 |
| x18–x27 | s2–s11 | 保存寄存器 | 被调用者 |
| x28–x31 | t3–t6 | 临时寄存器 | 调用者 |
浮点寄存器(F 扩展):f0–f31(参数使用 fa0–fa7)。
2. 基本指令
# 算术(R 型和 I 型)
add a0, a1, a2 # a0 = a1 + a2
sub a0, a1, a2 # a0 = a1 - a2
addi a0, a1, 42 # a0 = a1 + 42(立即数)
mul a0, a1, a2 # a0 = a1 * a2(M 扩展)
div a0, a1, a2 # 有符号除法(M 扩展)
rem a0, a1, a2 # 取余(M 扩展)
# 逻辑
and a0, a1, a2 # 按位与
or a0, a1, a2 # 按位或
xor a0, a1, a2 # 按位异或
sll a0, a1, a2 # 逻辑左移
srl a0, a1, a2 # 逻辑右移(无符号)
sra a0, a1, a2 # 算术右移(有符号)
# 加载 / 存储
lw a0, 0(sp) # 加载字(32 位)
ld a0, 0(sp) # 加载双字(64 位,RV64)
lh a0, 4(sp) # 加载半字(符号扩展)
lbu a0, 8(sp) # 加载字节(零扩展)
sw a0, 0(sp) # 存储字
sd a0, 0(sp) # 存储双字(RV64)
# 分支(比较并跳转)
beq a0, a1, label # 相等则跳转
bne a0, a1, label # 不等则跳转
blt a0, a1, label # 有符号小于则跳转
bltu a0, a1, label # 无符号小于则跳转
bge a0, a1, label # 有符号大于等于则跳转
# 跳转
j label # 无条件跳转(伪指令:jal x0, label)
jal ra, func # 跳转并链接(调用)
jalr zero, ra, 0 # 跳转到 ra(返回:伪指令 ret)
3. 最简函数(psABI 调用约定)
.section .text
.global add_numbers
# int add_numbers(int a, int b); — a 在 a0,b 在 a1,返回值在 a0
add_numbers:
add a0, a0, a1 # 结果 = a + b
ret # 返回(jalr zero, ra, 0)
.global factorial
# long factorial(int n); — n 在 a0
factorial:
addi sp, sp, -16 # 分配栈帧
sd ra, 8(sp) # 保存返回地址(RV64)
sd s0, 0(sp) # 保存 s0(被调用者保存)
mv s0, a0 # s0 = n
li a0, 1 # 默认返回 1
blez s0, .done # 若 n <= 0,返回 1
addi a0, s0, -1 # a0 = n - 1
call factorial # 递归调用:factorial(n-1)
mul a0, a0, s0 # a0 = 结果 * n
.done:
ld ra, 8(sp) # 恢复 ra
ld s0, 0(sp) # 恢复 s0
addi sp, sp, 16 # 释放栈帧
ret
4. ISA 扩展命名
RISC-V 扩展以字符串形式附加在基础 ISA 之后:
| 字母 | 扩展 | 描述 |
|---|---|---|
| I | Integer | 基础 32/64 位整数(RV32I, RV64I) |
| M | Multiply | 整数乘除法 |
| A | Atomic | 原子内存操作(lr/sc, AMO) |
| F | Float | 单精度浮点 |
| D | Double | 双精度浮点 |
| C | Compressed | 16 位压缩指令 |
| G | General | = IMAFD(简写) |
| V | Vector | 向量指令(SIMD) |
| Zicsr | CSR | 控制/状态寄存器访问 |
| Zifencei | Fence.i | 指令预取屏障 |
| Zba/Zbb/Zbc/Zbs | 位操作 | 位操作指令(B 扩展集) |
| Ztso | TSO | 全序存储内存模型 |
常用目标:
- 嵌入式:
rv32imac— 无浮点,有原子操作和压缩指令 - Linux 应用:
rv64gc— 完整通用 + 压缩 - 高性能:
rv64gcv— + 向量
5. 内联汇编(GCC/Clang)
// 读取 CSR 寄存器(例如周期计数器)
static inline uint64_t read_cycle(void) {
uint64_t val;
asm volatile ("rdcycle %0" : "=r"(val));
return val;
}
// 原子交换
static inline int atomic_swap(int *ptr, int new_val) {
int old;
asm volatile (
"amoswap.w.aqrl %0, %2, (%1)"
: "=r"(old)
: "r"(ptr), "r"(new_val)
: "memory"
);
return old;
}
// 内存屏障
static inline void memory_fence(void) {
asm volatile ("fence rw, rw" ::: "memory");
}
// CSR 读/写
#define csr_read(csr) ({ \
uint64_t _v; \
asm volatile ("csrr %0, " #csr : "=r"(_v)); \
_v; \
})
uint64_t mstatus = csr_read(mstatus);
6. 压缩指令(RVC)
当满足以下条件时,RVC 用 16 位版本替换常见的 32 位指令:
- 寄存器位于 x8–x15(用于
c.版本) - 立即数能放入更小的字段
- 指令模式与特定格式匹配
# 在 GCC 中启用 C 扩展
riscv64-linux-gnu-gcc -march=rv64gc prog.c -o prog
# 检查是否生成了压缩指令
riscv64-linux-gnu-objdump -d prog | grep "c\."
# c.addi, c.ld, c.sw, c.j 等
# 禁用压缩指令(用于调试或不支持 C 扩展的目标)
riscv64-linux-gnu-gcc -march=rv64g prog.c -o prog
7. QEMU 仿真与 GDB
# 安装 QEMU RISC-V
apt-get install qemu-user qemu-system-riscv64
# 用户态仿真(在 x86 主机上运行 RV64 二进制文件)
qemu-riscv64 ./prog
# 系统仿真(完整裸机虚拟机)
qemu-system-riscv64 \
-machine virt \
-nographic \
-kernel firmware.elf \
-gdb tcp::1234 \
-S # 启动时暂停
# GDB 远程调试会话
riscv64-linux-gnu-gdb prog
(gdb) target remote :1234
(gdb) load
(gdb) break main
(gdb) continue
关于 RISC-V psABI 调用约定的详细说明,请参阅 references/riscv-abi.md。
相关技能
- 使用
skills/low-level-programming/assembly-arm与 AArch64 进行对比 - 使用
skills/low-level-programming/assembly-x86了解 x86-64 汇编 - 使用
skills/embedded/openocd-jtag对真实 RISC-V 硬件进行调试 - 使用
skills/compilers/cross-gcc配置 RISC-V 交叉编译环境
More from killvxk/low-level-dev-skills-zh
binutils
GNU binutils 二进制操作与分析技能。适用场景:使用 ar 管理静态库、使用 strip 或 objcopy 处理二进制文件、使用 addr2line 将地址转换为源码位置、使用 strings 提取文本、或使用 c++filt 对 C++ 名称进行反混淆。触发条件:涉及 ar、strip、objcopy、addr2line、strings、c++filt、ranlib 或二进制后处理任务的查询。
1ebpf
Linux 可观测性和网络的 eBPF 技能。适用场景:使用 libbpf 或 bpftrace 编写 eBPF 程序、挂载 kprobe/tracepoint/XDP 钩子、调试验证器错误、使用 eBPF map,或实现跨内核版本的 CO-RE 可移植性。触发条件:查询 eBPF、bpftool、bpftrace、XDP 程序、libbpf、验证器错误、eBPF map 或使用 BPF 进行内核追踪相关问题。
1clang
C/C++ 项目的 Clang/LLVM 编译器技能。适用场景:使用 clang 或 clang++ 进行诊断、sanitizer 插桩、优化备注、通过 clang-tidy 进行静态分析、通过 lld 实现 LTO,或从 GCC 迁移到 Clang。触发条件:涉及 clang 标志、clang-tidy、clang-format、更好的错误信息、Apple/FreeBSD 工具链或 LLVM 特定优化的查询。涵盖标志选择、诊断调优及与 LLVM 工具的集成。
1gcc
C/C++ 项目的 GCC 编译器技能。适用场景:选择优化级别、警告标志、调试构建、LTO、sanitizer 插桩,或诊断 GCC 编译错误。涵盖调试与发布构建的标志选择、ABI 问题、预处理器宏、配置引导优化(PGO)及与构建系统的集成。触发条件:涉及 gcc 标志、编译错误、性能调优、警告抑制或跨标准编译的查询。
1cmake
C/C++ 项目的 CMake 构建系统技能。适用场景:编写或重构 CMakeLists.txt、配置源外构建、选择生成器(Ninja、Make、VS)、使用 target_link_libraries 管理目标和依赖、通过 find_package 或 FetchContent 集成外部包、启用 Sanitizer、为交叉编译配置工具链文件,或导出 CMake 包。触发条件:涉及 CMakeLists.txt、cmake 配置错误、目标属性、安装规则、CPack 或 CMake Presets 的查询。
1cpp-modules
现代 C++ 项目的 C++20 模块技能。适用场景:使用命名模块、模块分区、头文件单元、CMake MODULE_SOURCES、Clang -fmodules-ts、BMI 缓存问题,或从头文件迁移到模块。触发条件:涉及 C++20 模块、import 语句、模块接口单元、头文件单元或 BMI 文件的查询。
1