QEMU模拟ARM裸机搭建C运行环境
QEMU模拟ARM裸机搭建C运行环境
链接脚本
ENTRY(_start)
MEMORY {
/* 0x0000 0000 - 0x0000 ffff */
FLASH (rx) : ORIGIN = 0x00000000, LENGTH = 64K
/* 0x0001 0000 - 0x0801 0000 */
RAM (rw) : ORIGIN = 0x00010000, LENGTH = 128M
}
SECTIONS {
.text : { /* 未指定代码段LMA起始地址,默认LMA=VMA */
KEEP(*(.init)) /* 保留启动入口(通常含 _start) */
*(.text*) /* 所有代码段 */
*(.rodata*) /* 只读数据(如字符串常量) */
} > FLASH /* VMA:运行地址在 FLASH 区 0x0000 0000 开始 */
.data : AT(ADDR(.text) + SIZEOF(.text)) { /* 指定数据块LMA */
__data_load_addr = LOADADDR(.data); /* 数据段在LMA的起始地址 = 代码段LMA起始地址(此处等于VMA起始地址) + 代码段长度 */
__data_start = .; /* 数据段在VMA的起始地址 */
*(.data*) /* 所有数据段 */
__data_end = .; /* 数据段在VMA的结束地址 */
} > RAM /* VMA 指定在RAM区 0x0001 0000 开始 */
.bss : { /* bss段默认不加载到LMA */
__bss_start = .; /* bss在VMA的起始地址 */
*(.bss*) /* 所有bss段 */
__bss_end = .; /* bss在VMA的结束地址 */
} > RAM /* VMA 指定在RAM区 接着data段 开始 */
__stack_top = ORIGIN(RAM) + LENGTH(RAM); /* 定义栈顶地址 */
}
-
MEMORY
/* * 裸机 ARM 启动链接脚本(用于 QEMU 仿真) * 内存模型:整个 256MB 物理内存从 0x00000000 开始,均为 RAM * 逻辑上划分为: * - 低 64KB 作为“Flash”区域(存放代码和 .data 初始值) * - 剩余空间作为运行时 RAM(存放 .data 副本、.bss、栈等) */-
FLASH
/* * 逻辑“Flash”区域:64KB,从 0x00000000 开始 * (rx) 表示可读、可执行(不可写,但实际是 RAM,写操作不会报错) */ -
RAM
/* * 运行时 RAM 区域:128M,从 0x00010000(64KB 之后)开始 * (rw) 表示可读写 */
-
-
SECTIONS
/* * 段布局定义(SECTIONS 命令) * 控制各段在内存中的位置(VMA)和在镜像中的位置(LMA) */-
.text
/* * .text 段:包含可执行代码和只读数据 * 放置在 FLASH 区域(即物理地址 0x00000000 起) * KEEP(*(.init)) 确保启动函数(如 _start)不会被优化掉 */ -
.data
/* * .data 段:已初始化的全局/静态变量 * LMA(加载地址):紧跟 .text 之后,仍在 FLASH 区(即 bin 文件中) * VMA(运行地址):映射到 RAM 区,以便运行时可修改 * * AT(...) 指定 LMA;> RAM 指定 VMA */
-
-
__stack_top
/* * 定义栈顶地址 * 栈从高地址向低地址生长,因此栈顶设为 RAM 区最高地址 * 注意:ORIGIN(RAM) + LENGTH(RAM) = 0x0001 0000 + 128M = 0x0801 0000 */
汇编启动文件
/*
* 裸机 ARM 启动代码(ARMv5T / arm926ej-s 兼容)
* 功能:
* - 设置栈指针
* - 初始化 .data 段(从镜像复制到 RAM)
* - 清零 .bss 段
* - 跳转到 C 语言 main() 函数
*
* 依赖链接脚本中定义的符号:
* __stack_top, __data_load_addr, __data_start, __data_end,
* __bss_start, __bss_end
*/
/* 将 _start 放入 .init 段,确保它被链接到程序最开始 */
.section .init
/* 声明 _start 为全局符号,供链接器作为入口点使用 */
.global _start
@ 入口
_start:
@ 设置栈指针
ldr sp, =__stack_top
@ 初始化.data区
ldr r0, =__data_load_addr @ 程序镜像中.data区
ldr r1, =__data_start @ RAM中.data区开始地址
ldr r2, =__data_end @ RAM中.data区结束地址
@ .data循环复制
data_loop:
cmp r1, r2
beq bss_init
ldr r3, [r0], #4
str r3, [r1], #4
b data_loop
@ 初始化.bss区
bss_init:
ldr r1, =__bss_start
ldr r2, =__bss_end
@ .bss循环清空
bss_loop:
cmp r1, r2
beq call_main
mov r3, #0
str r3, [r1], #4
b bss_loop
@ 调用main函数
call_main:
bl main
@ main返回后死循环
halt:
b halt
C代码
int c = 9;
int main(int argc, const char *argv[]) {
int a = 10;
int b = 20;
return a + b + c;
}
Makefile
# =============================================================================
# ARM 裸机开发 Makefile(适用于 QEMU 仿真)
# 功能:
# - 编译 C 和汇编源文件
# - 链接生成 ELF 和 raw binary
# - 支持反汇编、QEMU 仿真、GDB 调试
# =============================================================================
# ===== 工具链 =====
# 指定交叉编译工具链前缀
CROSS_COMPILE = arm-none-eabi-
CC = $(CROSS_COMPILE)gcc
AS = $(CROSS_COMPILE)as
LD = $(CROSS_COMPILE)ld
OBJCOPY = $(CROSS_COMPILE)objcopy
OBJDUMP = $(CROSS_COMPILE)objdump
GDB = $(CROSS_COMPILE)gdb
# ===== 目标 =====
TARGET = main
# ===== 源文件 =====
# C 源文件(主程序)
SRCS_C = main.c
# 汇编源文件
SRCS_S = startup.S
# ===== 中间目标文件 =====
# 自动将 .c → .o,.S → .o
OBJS = $(SRCS_C:.c=.o) $(SRCS_S:.S=.o)
# ===== 编译选项 =====
# gcc参数
CFLAGS = \
-nostdlib \ # 不链接标准 C 库(裸机无 libc)
-ffreestanding \ # 告诉编译器这是独立环境(可省略某些内建假设)
-g \ # 生成调试信息(供 GDB 使用)
-mcpu=arm9e \ # 指定 CPU 架构(arm926ej-s 属于 arm9e)
-Wall # 启用所有警告
# as参数
ASFLAGS = \
-g \ # 生成调试信息
-mcpu=arm9e # 与 C 一致的 CPU 架构
# ===== 链接脚本 =====
LDSCRIPT = linker.ld
# ===== 默认目标 =====
# 执行 `make` 时,默认构建 bin 和 dump 文件
main: $(TARGET).bin $(TARGET).dump
# ===== 编译规则 =====
# 通用规则:将 .c 文件编译为 .o
%.o: %.c
$(CC) $(CFLAGS) -c $< -o $@
# 通用规则:将 .S 文件汇编为 .o
%.o: %.S
$(AS) $(ASFLAGS) -c $< -o $@
# ===== 链接 ELF =====
$(TARGET).elf: $(OBJS)
$(LD) -T $(LDSCRIPT) -o $@ $^
# ===== ELF → BIN =====
$(TARGET).bin: $(TARGET).elf
$(OBJCOPY) -O binary $< $@
# ===== 反汇编 =====
$(TARGET).dump: $(TARGET).elf
$(OBJDUMP) -d $< > $@
# ===== QEMU =====
QEMU = qemu-system-arm
QEMU_OPTS = \
-M none \ # 不使用任何预设开发板(纯自定义内存)
-cpu arm926 \ # 指定 CPU 型号(arm926ej-s 的简称)
-nographic \ # 禁用图形界面
-device loader,addr=0x00000000,file=$(TARGET).bin \ # 从 0x0 加载 bin
-m 256M \ # 分配 256MB 连续 RAM(从 0x0 开始)
-S \ # 启动时暂停(-S)
-s # 开启 GDB 远程调试默认端口1234
# ===== 运行 QEMU =====
qemu: $(TARGET).bin
sudo $(QEMU) $(QEMU_OPTS)
# ===== 运行GDB =====
gdb:
$(GDB) $(TARGET).elf
# ===== 清理 =====
clean:
rm -f *.o *.elf *.bin *.dump
# ===== 声明伪目标(phony targets)=====
# 防止与同名文件冲突
.PHONY: main clean qemu gdb

浙公网安备 33010602011771号