01构建结构
这次使用 riscv64-unknown-elf-as 和 ld 汇编、链接最小裸机程序,再交给 QEMU 系统模式的 virt 机器执行。
| 文件 | 职责 |
|---|---|
start.S | 定义 _start、逐字符发送循环与串口寄存器写入。 |
linker.ld | 指定入口与链接地址,安排 .text、.rodata、.data、.bss。 |
Makefile | 提供 all、run、dump、clean 目标。 |
CROSS := riscv64-unknown-elf-
AS := $(CROSS)as
LD := $(CROSS)ld
OBJDUMP := $(CROSS)objdump
QEMU := qemu-system-riscv64
BUILD := build
TARGET := $(BUILD)/baremetal.elf
OBJECT := $(BUILD)/start.o
.PHONY: all run dump clean
all: $(TARGET)
$(BUILD):
mkdir -p $(BUILD)
$(OBJECT): start.S | $(BUILD)
$(AS) start.S -o $(OBJECT)
$(TARGET): $(OBJECT) linker.ld
$(LD) -T linker.ld $(OBJECT) -o $(TARGET)
run: $(TARGET)
$(QEMU) -machine virt -nographic -bios none -kernel $(TARGET)
dump: $(TARGET)
$(OBJDUMP) -d $(TARGET)
clean:
rm -rf $(BUILD)02从入口到 UART
程序入口位于 0x80000000,QEMU virt 的 UART MMIO 基地址为 0x10000000。启动代码直接向设备映射地址写入字符。
# PUBLIC EXCERPT: identity lines removed for website use.
# This is intentionally not a byte-for-byte copy of the lab ELF's source.
.section .text.boot
.globl _start
_start:
li t0, 0x10000000
la t1, message
print_loop:
lbu t2, 0(t1)
beqz t2, done
sb t2, 0(t0)
addi t1, t1, 1
j print_loop
done:
wfi
j done
.section .rodata
message:
.asciz "kaiwai-os bare metal\n"lbu从.rodata的字符串逐字节读取。beqz检查零结束符;未结束时用sb向 UART 写入当前字节。addi推进字符串指针,继续发送。- 字符串输出完成后,以
wfi进入等待循环。
03链接与 ELF 布局
链接脚本用 ENTRY(_start) 指定入口,并从 0x80000000 开始安排各节。
ENTRY(_start)
SECTIONS
{
. = 0x80000000;
.text : {
*(.text.boot)
*(.text*)
}
.rodata : {
*(.rodata*)
}
.data : {
*(.data*)
}
.bss : {
*(.bss*)
*(COMMON)
}
}通过 objdump 检查原始实验二进制,文件格式为 elf64-littleriscv,架构为 riscv:rv64,入口地址为 0x0000000080000000。
| 节 | VMA / LMA | 大小(字节) |
|---|---|---|
.text | 0x80000000 | 0x28 |
.rodata | 0x80000028 | 0x3c |
04QEMU 运行结果
原始实验执行 make -C experiments/0-2-baremetal run 后成功启动。对应的 QEMU 命令如下:
qemu-system-riscv64 -machine virt -nographic -bios none -kernel build/baremetal.elf串口输出包含 kaiwai-os bare metal。原始运行中的身份输出行已从公开资料中移除,下方记录仅展示无身份内容的终端片段。
# Real experiment evidence excerpt, identity fields removed for public display.
$ make -C experiments/0-2-baremetal
mkdir -p build
riscv64-unknown-elf-as start.S -o build/start.o
riscv64-unknown-elf-ld -T linker.ld build/start.o -o build/baremetal.elf
$ riscv64-unknown-elf-objdump -f build/baremetal.elf
file format elf64-littleriscv
architecture: riscv:rv64
start address 0x0000000080000000
# Original unredacted ELF data, for this run only:
.text size 0x28 VMA 0x80000000
.rodata size 0x3c VMA 0x80000028
$ qemu-system-riscv64 -machine virt -nographic -bios none -kernel build/baremetal.elf
kaiwai-os bare metal
[identity lines omitted]
QEMU: Terminated
# Temporary test: UART write address changed from 0x10000000 to 0x10000001
# Result: ELF assembled/linked, but no UART text appeared after launch.05UART 地址对照
为了检查设备地址的意义,使用 sed 临时复制源码,将 UART 写入地址从 0x10000000 改为 0x10000001。diff 确认只有这一处地址变化,写入位置偏移了一个字节。两次运行使用相同的 QEMU 参数。
| UART 写入地址 | 汇编与链接 | 实际运行 |
|---|---|---|
0x10000000 | 通过 | 出现串口文字 |
0x10000001 | 通过 | 未出现串口文字 |
改址版能汇编、能链接,但没有出现 UART 输出。裸机 MMIO 地址必须与目标机器的设备映射相符。
06退出与终端恢复
测试改址版时,QEMU 在 -nographic 模式下没有响应尝试的快捷键与 Ctrl+C。随后在另一个终端找到 QEMU 进程;发送 TERM 后进程仍存在,最终以 KILL 结束。
回到原终端后执行 stty sane,恢复显示与输入。该次实验的 git status --short 仍为空:临时测试文件始终位于 /tmp,正式版本未改动。