assembly-riscv

Installation
SKILL.md

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 交叉编译环境
Related skills

More from killvxk/low-level-dev-skills-zh

Installs
1
First Seen
Mar 21, 2026