返回开发记录/ LOG

DEVELOPMENT LOG / LAB 0-1

开发环境与基础编程

在 x86_64 的 WSL2 Debian 中配置 RISC-V 交叉编译环境,让 C 与汇编程序在 QEMU 用户模式下运行,再沿着反汇编理解程序如何输出一行文字。

01环境与工具链

这次实验从 Linux 用户程序开始。宿主开发环境是 x86_64 / WSL2 Debian,编译目标为 RISC-V 64 位 ELF。先检查两套交叉编译器与 QEMU,再分别验证 C 程序和手写汇编程序。

工具版本用途
riscv64-linux-gnu-gcc14.2.0Linux 目标交叉编译
riscv64-unknown-elf-gcc14.2.0裸机目标工具链
qemu-riscv6410.0.13用户模式运行
qemu-system-riscv6410.0.13系统模式;本次检查版本
riscv64-linux-gnu-objdump—ELF 反汇编观察

02C → ELF → 运行

先用一个只输出字符串的 C 程序,验证交叉编译与用户模式运行这条链路。

hello.cC
#include <stdio.h>

int main(void) {
    printf("Subete Anata no Sei Desu.\n");
    return 0;
}
编译与运行BASH
riscv64-linux-gnu-gcc -static hello.c -o hello
file hello
qemu-riscv64 hello

file hello 显示目标文件为静态链接的 RISC-V 64 位 ELF。通过 qemu-riscv64 运行后,程序输出 Subete Anata no Sei Desu.。

03观察 main 的反汇编

使用 riscv64-linux-gnu-objdump -d 观察生成的 ELF。下面是实际程序中 main 的反汇编摘录。

main 反汇编OBJDUMP
# Excerpt from the actual C ELF, as printed by objdump; no personal fields.
000000000001047a <main>:
   1047a: 1141                 addi  sp,sp,-16
   1047c: e406                 sd    ra,8(sp)
   1047e: e022                 sd    s0,0(sp)
   10480: 0800                 addi  s0,sp,16
   10482: 00040517             auipc a0,0x40
   10486: 52650513             addi  a0,a0,1318  # 509a8
   1048a: 043000ef             jal   10ccc <_IO_puts>
   1048e: 4781                 li    a5,0
   10490: 853e                 mv    a0,a5
   10492: 60a2                 ld    ra,8(sp)
   10494: 6402                 ld    s0,0(sp)
   10496: 0141                 addi  sp,sp,16
   10498: 8082                 ret
  • 函数序言:保存 ra 与 s0,建立 16 字节栈帧。
  • 参数与调用:auipc 配合 addi 计算字符串地址,jal 调用 _IO_puts;a0 用于参数与返回值。
  • 函数返回:恢复寄存器与栈指针,再通过 ret 返回。
  • 指令长度:反汇编中同时出现 2 字节与 4 字节编码,反映 RVC 压缩指令扩展的使用。

04直接编写汇编

绕过 C 源码,直接编写 RISC-V 汇编,再依次通过 as 与 ld 完成汇编和链接。这段程序使用 Linux 系统调用输出相同的字符串。

hello.SASM
.section .rodata
message:
    .ascii "Subete Anata no Sei Desu.\n"
message_end:
    .equ message_len, message_end - message

    .section .text
    .globl _start

_start:
    # write(1, message, message_len)
    li a0, 1
    la a1, message
    li a2, message_len
    li a7, 64
    ecall

    # exit(0)
    li a0, 0
    li a7, 93
    ecall
  1. a7 = 64 选择 Linux write。a0 = 1 指向标准输出,a1 保存缓冲区地址,a2 保存长度,由 ecall 发起调用。
  2. a7 = 93、a0 = 0 发起 exit(0)。

实际运行输出与 C 程序相同,退出状态为 0。

05问题与排查

stdio.h 缺失:选择匹配目标的工具链

最初使用现有的 riscv64-unknown-elf-gcc 编译包含 stdio.h 的程序,报错 stdio.h: No such file or directory。本次要运行的是 Linux 用户程序,切换到 riscv64-linux-gnu-gcc 后完成编译。

动态加载器路径:为 QEMU 指定目标 sysroot

去掉 -static 后,生成的动态 ELF 指定了 RISC-V 加载器 /lib/ld-linux-riscv64-lp64d.so.1。直接运行时,QEMU 提示找不到这个加载器。

改用 qemu-riscv64 -L /usr/riscv64-linux-gnu 指定目标 sysroot 后,同一个 ELF 正常运行。对应报错与成功输出保留在下方的真实记录中。

长度符号的位置:在使用前定义 .equ

汇编时曾把长度符号的定义放在 li a2,message_len 之后,遇到 illegal operands。将字符串起止符号及 .equ 移至使用位置之前后,汇编成功;上方的公开源码保留了调整后的顺序。

06运行记录与结果

编译与运行记录LOG
# Real experiment output excerpt; personal fields omitted.
# On host: x86_64 WSL2 Debian; target: RISC-V Linux ELF.

riscv64-linux-gnu-gcc version 14.2.0
qemu-riscv64 version 10.0.13
ELF 64-bit LSB executable, UCB RISC-V, RVC, ... statically linked

$ qemu-riscv64 hello
Subete Anata no Sei Desu.

$ qemu-riscv64 hello-asm
Subete Anata no Sei Desu.
$ echo $?
0

# Dynamic ELF path issue (same file works after specifying target sysroot):
$ qemu-riscv64 /tmp/hello-dynamic
qemu-riscv64: Could not open '/lib/ld-linux-riscv64-lp64d.so.1': No such file or directory
$ qemu-riscv64 -L /usr/riscv64-linux-gnu /tmp/hello-dynamic
Subete Anata no Sei Desu.