返回开发记录/ LOG

DEVELOPMENT LOG / LAB 0-2

最小裸机启动

从 Linux 用户程序走向裸机:手写启动汇编与链接脚本,在 QEMU virt 上执行最小 ELF 并向 UART 输出。再用一次地址对照实验,确认构建成功之外的运行行为。

01构建结构

这次使用 riscv64-unknown-elf-as 和 ld 汇编、链接最小裸机程序,再交给 QEMU 系统模式的 virt 机器执行。

文件职责
start.S定义 _start、逐字符发送循环与串口寄存器写入。
linker.ld指定入口与链接地址,安排 .text、.rodata、.data、.bss。
Makefile提供 all、run、dump、clean 目标。
MakefileMAKE
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。启动代码直接向设备映射地址写入字符。

start.SASM
# 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"
  1. lbu 从 .rodata 的字符串逐字节读取。
  2. beqz 检查零结束符;未结束时用 sb 向 UART 写入当前字节。
  3. addi 推进字符串指针,继续发送。
  4. 字符串输出完成后,以 wfi 进入等待循环。

03链接与 ELF 布局

链接脚本用 ENTRY(_start) 指定入口,并从 0x80000000 开始安排各节。

linker.ldLD
ENTRY(_start)

SECTIONS
{
    . = 0x80000000;

    .text : {
        *(.text.boot)
        *(.text*)
    }

    .rodata : {
        *(.rodata*)
    }

    .data : {
        *(.data*)
    }

    .bss : {
        *(.bss*)
        *(COMMON)
    }
}

通过 objdump 检查原始实验二进制,文件格式为 elf64-littleriscv,架构为 riscv:rv64,入口地址为 0x0000000080000000。

节VMA / LMA大小(字节)
.text0x800000000x28
.rodata0x800000280x3c

04QEMU 运行结果

原始实验执行 make -C experiments/0-2-baremetal run 后成功启动。对应的 QEMU 命令如下:

QEMU 启动BASH
qemu-system-riscv64 -machine virt -nographic -bios none -kernel build/baremetal.elf

串口输出包含 kaiwai-os bare metal。原始运行中的身份输出行已从公开资料中移除,下方记录仅展示无身份内容的终端片段。

构建与运行记录LOG
# 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,正式版本未改动。