跳转至

共通 - 程序和二进制文件

目标程序说明

上一节的目标程序可以分成三个部分:

  1. 计算1+2+3+...+100的结果:t0 是循环计数器,t1 保存上限 100,t2 保存累加结果。beq t0, t1, next 用于判断是否已经累加到 100。
  2. 将结果存入0x80000100处:lui t0, 0x80000 得到 0x8000_0000,随后 sw t2, 0x100(t0) 将 t2 中保存的计算结果写到 0x8000_0100。
  3. 在串口输出done!:输出每个字符前,程序先用 lb 读取 UART 状态字节,再用 andi ... , 0x20 保留 bit 5。如果结果为零,说明串口发送端不可用,继续轮询;否则用 sb 写入数据寄存器,发送一个字符。这与实验 4 中“先读状态,再写数据”的状态机流程相对应。

为什么要在代码中打 5 遍几乎相同的代码来输出'done!'?

如果使用类似于实验 1 中的方式,将写串口封装为函数,则会向程序中加入两条相对较难实现的指令jal和jalr。为了让实验 5 更加简单,我们选择不在实验 5 中要求大家实现这两条指令。

获得程序

在本实验中,程序需要预先装载至 SRAM 当中。CPU 读取的应该是指令的二进制编码,而不是汇编源文件中的代码,因此需要先生成目标程序对应的二进制文件。

在提供的 rv-2025.zip 中(简介#代码示例),有 asmcode 这个文件夹。在这个文件夹中提供了两份样例汇编以及一个 makefile 文件用于生成之后需要用到的二进制文件。

其中 makefile 文件如下:

BUILD_DIR = .
ARM_SOURCES = $(wildcard *.s)
TARGETS = $(addprefix $(BUILD_DIR)/,$(notdir $(ARM_SOURCES:.s=.bin)))
all: $(TARGETS)
%.elf: %.s
    riscv64-unknown-elf-gcc $< -march=rv32i -mabi=ilp32 -static -fno-pic -fno-builtin -nostdlib  -nostdinc -g -o $@ -Wl,-Ttext,0x0
%.bin: %.elf
    riscv64-unknown-elf-objcopy -j .text -O binary -S $< $@

可以看到,这个 makefile 默认对文件夹下的每一个.s 文件都会生成对应的.bin 文件。

关于生成 elf 这条命令中的参数,在RISC-V 汇编语言程序的调试一节中已经做了详细说明,在此不多赘述。

在生成 elf 之后,通过工具链中的 objcopy 工具,将 elf 文件中的.text 段提取了出来,并以二进制的方式写入到了.bin 文件中。

将上一节中的汇编放入该文件夹,保存为扩展名为 s 的汇编文件(如 sum.s)。在 实验环境 中,进入该文件夹执行 make 指令,我们就获得了可用于实验的二进制文件了(使用 sum.s 会获得 sum.bin)。

当你需要获得 ELF 文件进行反汇编时,可以单独生成它。例如,在同一文件夹中执行:

make sum.elf
riscv64-unknown-elf-objdump -d sum.elf

反汇编结果可以帮助你把仿真中的指令编码与汇编语句对应起来。

链接地址与装载地址

上面的命令将 .text 链接在 0x0,而实验将 .bin 装入 BaseRAM 起始地址 0x8000_0000,CPU 复位后也从这个地址取指。.bin 本身不包含装载地址信息,需要由装载工具指定位置。

目标程序中的 beq 使用相对于当前指令地址的偏移,因此将整段程序平移到 BaseRAM 后,分支仍能跳到正确位置。查看反汇编时,指令地址 0x0 对应实际运行地址 0x8000_0000,后续地址依此类推。

三个文件的关系如下:

文件 内容 用途
sum.s 人可以阅读和编辑的汇编代码 编写目标程序
sum.elf 机器码以及段信息、符号等 反汇编,检查指令编码和跳转位置
sum.bin 从 ELF 的 .text 段提取的原始字节 装入 SRAM,供 CPU 取指

总结

本节说明了目标程序的三个主要功能,以及从汇编源文件生成二进制文件的过程。

学完这节内容我需要做什么?

生成目标程序的二进制文件,理解求和、保存结果和轮询串口的流程。

作者:于越洋 (49.3%), cuibst (46.48%), 张英奇 (2.82%), Jiajie Chen (1.41%)