1.下载工具链
1.1安装交叉编译工具链
sudo apt update
sudo apt install build-essential gcc-aarch64-linux-gnu gdb-multiarch
1.2安装安装模拟器 (QEMU)
sudo apt install qemu-system-arm
可以使用下面两条命令判断是否安装成功
aarch64-linux-gnu-gcc --version
qemu-system-aarch64 --version
2.编写测试代码
2.1 编写.S文件
在工程目录下创建test.S文件
asm
/* test.S */
.global _start
_start:
mov x0, #123 /* 把 123 赋值给 X0,调试时我们要看这个值 */
b . /* 死循环 (跳转到当前地址) */
2.2 创建链接脚本 linker.ld
ENTRY(_start)
/* 新增:定义一个名为 'segment' 的加载段 */
PHDRS
{
segment PT_LOAD FLAGS(7); /* 7 = Read + Write + Execute */
}
SECTIONS
{
. = 0x40000000;
/* 在每个段后面加上 :segment,显式归类 */
.text : { *(.text) } :segment
.data : { *(.data) } :segment
.bss : { *(.bss) } :segment
}
-
Section (.text, .data):是你房间里的零碎物品(衣服、书、牙刷)。编译器只负责制造这些物品。
-
Segment (PHDRS):是搬家用的大纸箱。操作系统(或 QEMU)根本不看你有几件衣服,它只管搬运箱子。
所以这个链接就是申请一个Segment 并规定这个Section 的内容和起始地址
过程 A:编译 (Compile) ------ 制造碎片
每个文件都会生成自己的 .text (代码) 和 .data (数据)。
start.o: 里面有一小段 .text。
main.o: 里面有一小段 .text,也许还有一点 .data (全局变量)。
uart.o: 里面有一小段 .text。
过程 B:链接 (Link) ------ 分类合并
链接器(Linker)根据你的脚本 (linker.ld),会执行"归类"操作:
合并代码:它把 start.o + main.o + uart.o 里的所有 .text 凑在一起,拼成一个巨大的 .text 节。
合并数据:它把所有文件的 .data 凑在一起,拼成一个巨大的 .data 节。
过程 C:装箱 (Segment) ------ 最终打包
最后,根据 PHDRS 的指令,这些巨大的节被放入 Segment 中。
在你目前的脚本里: 你强制把这个巨大的 .text、巨大的 .data、巨大的 .bss 全部塞进了同一个 Segment 里。
3 编译成elf文件并在qemu中运行
3.1 elf文件
一个标准的 ELF 文件由四部分组成:
ELF Header (快递单)
位于文件最开头(第 0 字节)。
内容:魔数(Magic Number,7F 45 4C 46 即 .ELF)、是 32 位还是 64 位、是小端还是大端、入口地址(Entry Point, 你的 _start 地址)。
作用:操作系统拿到文件,先看这里,确认"这货我能不能拆"。
Program Headers (装箱单 - 给 OS 看)
描述了 Segments。
作用:告诉操作系统"请把文件中从偏移量 A 开始的 X 字节,搬到内存地址 Y 去"。
你在 Linker Script 里写的 PHDRS 就是在控制这里。
Section Headers (物品清单 - 给编译器/链接器看)
描述了 Sections。
作用:告诉链接器"这里存放的是代码(.text),那里存放的是调试符号(.debug)"。
Data (实际货物)
实际的代码指令、字符串常量、变量值等。
3.2 编译elf文件
aarch64-linux-gnu-gcc -g -nostdlib -Wl,-n -T linker.ld test.S -o test.elf
3.3 运行elf文件
在qemu中打开并等待远程链接
qemu-system-aarch64 -M virt -cpu cortex-a57 -nographic -kernel test.elf -S -s
使用gdb远程连接
target remote :1234
-
打断点 break _start
-
查看寄存器的值info registers x0
(gdb) info registers x0
x0 0x7b 123
4 Makefile
为了方便我写成了一个Makefile
# ==========================================
# ARMv8 Mini-Kernel Makefile
# ==========================================
# 1. 工具链定义 (Toolchain Definitions)
CROSS_COMPILE = aarch64-linux-gnu-
CC = $(CROSS_COMPILE)gcc
OBJCOPY = $(CROSS_COMPILE)objcopy
GDB = gdb-multiarch
# 2. 编译参数 (Compiler Flags)
# -g: 生成调试信息
# -nostdlib: 不链接标准库 (裸机必备)
# -Wl,-n: 关闭页对齐 (解决 segment 错误)
CFLAGS = -g -nostdlib -Wl,-n
# 3. 链接脚本 (Linker Script)
LDSCRIPT = linker.ld
# 4. 源文件与目标文件 (Files)
# 以后添加 .c 文件直接在 SRCS 后面追加即可,例如: test.S uart.c
SRCS = test.S
ELF = test.elf
BIN = test.bin
# 5. QEMU 模拟器参数 (Emulator Flags)
QEMU = qemu-system-aarch64
MACH = -M virt -cpu cortex-a57
# -S: 冻结 CPU 等待调试
# -s: 开启 GDB 端口 1234
QEMU_FLAGS = $(MACH) -nographic -kernel $(ELF) -S -s
# ==========================================
# 编译规则 (Build Rules)
# ==========================================
# 默认目标: 输入 'make' 时执行
all: $(ELF) $(BIN)
# 生成 ELF 文件
$(ELF): $(SRCS) $(LDSCRIPT)
@echo " CC $@"
$(CC) $(CFLAGS) -T $(LDSCRIPT) $(SRCS) -o $@
# 生成 BIN 文件 (给真机用的)
$(BIN): $(ELF)
@echo " OBJCOPY $@"
$(OBJCOPY) -O binary $< $@
# ==========================================
# 调试规则 (Debug Rules)
# ==========================================
# 启动 QEMU (终端 1)
qemu: $(ELF)
@echo "Starting QEMU... (Waiting for GDB connection on port 1234)"
$(QEMU) $(QEMU_FLAGS)
# 启动 GDB (终端 2)
# -ex 参数会自动帮你执行 GDB 命令,不用手敲了!
gdb: $(ELF)
$(GDB) $(ELF) -ex "target remote :1234" -ex "break _start" -ex "continue"
# ==========================================
# 清理规则 (Clean)
# ==========================================
clean:
rm -f *.elf *.bin *.o
.PHONY: all clean qemu gdb