从 ARM 汇编到机器代码:裸机入门
Source: Dev.to
ARMv7 程序员可见寄存器
ARMv7 公开了十六个通用寄存器(R0–R15)以及若干状态寄存器。
| 寄存器 | 作用 |
|---|---|
| R0–R12 | 通用数据、参数、临时值 |
| R13 (SP) | 堆栈指针 |
| R14 (LR) | 链接寄存器 |
| R15 (PC) | 程序计数器 |
| CPSR | 当前程序状态寄存器 |
| SPSR | 保存的程序状态寄存器 |
该架构还定义了多种处理器模式(User、IRQ、FIQ、Supervisor 等)。某些寄存器(例如 SP 和 LR)在不同模式之间是 banked 的,从而在异常入口时无需保存完整寄存器集合即可快速切换。
从汇编源码到机器码
ARM 处理器执行从内存中取出的 32 位指令字。汇编器根据 ARM 指令集体系结构(ISA)把人类可读的助记符翻译成这些字。本文的示例假设:
- 所有指令都是 32 位宽且字对齐。
- 指令看到的 PC 值等于当前指令地址 + 8 字节。
一个最小的启动示例
// startup.s
ldr r2, str1 @ load literal into r2
b . @ infinite loop
str1: .word 0xDEADBEEF
这个小程序:
- 将 32 位常量
0xDEADBEEF加载到r2。 - 进入一个有意的无限循环。
- 将文字常量放置在内存中。
标签和偏移量的解析方式
因为源码位于单一段中,汇编器可以直接计算绝对地址——不需要链接器重定位。
| 地址 | 源码 | 机器码 |
|---|---|---|
| 0x00 | ldr r2, str1 | 0xE59F2000 |
| 0x04 | b . | 0xEAFFFFFE |
| 0x08 | str1: .word 0xDEADBEEF | 0xDEADBEEF |
编码 ldr r2, str1
LDR Rd, [PC, #offset] 形式使用 PC 相对寻址。
- 执行时的 PC = 指令地址 + 8 →
0x00 + 0x08 = 0x08 - 目标地址 =
str1的地址 =0x08 - 所需偏移 =
0x08 – 0x08 = 0
| 字段 | 位数 | 二进制值 | 含义 |
|---|---|---|---|
| 条件码 | 31‑28 | 1110 (E) | 永久 |
| 操作码 | 27‑20 | 01011001 | LDR(立即数) |
| 基址寄存器 (Rn) | 19‑16 | 1111 (PC) | — |
| 目的寄存器 (Rd) | 15‑12 | 0010 (R2) | — |
| 偏移 | 11‑0 | 000000000000 | 0 |
最终指令字: 0xE59F2000
编码 b .
分支指令同样使用 PC 相对寻址。
- 指令地址 =
0x04 - 有效 PC =
0x04 + 0x08 = 0x0C - 目标地址(
.) =0x04 - 字节偏移 =
0x04 – 0x0C = -8→ 按字计为-8 / 4 = -2
-2 的 24 位二进制补码表示为 0xFFFFFFFE。
最终指令字: 0xEAFFFFFE
为什么 b . 成为无限循环
执行时:
- 取指后 PC 为
0x0C。 - 解码得到的偏移为
-2条指令 →-8字节。 - 分支目标 =
0x0C + (-8) = 0x04,即分支指令本身的地址。
于是控制流不断返回到同一条指令,形成一个紧凑的无限循环,不会消耗堆栈、寄存器或额外内存。
为什么在裸机代码中使用这种模式
- 在不可恢复的错误发生时 进行停机。
- 等待调试器 连接。
- 硬件早期启动阶段 的占位符。
- 最小示例 中的显式程序结束标记。
编码 .word 0xDEADBEEF
.word 是汇编器的 指令(directive),而非可执行指令。
- 它在当前位置保留 4 字节。
- 文字常量
0xDEADBEEF原样写入。 - 除非执行跳入该区域,否则 CPU 只会把它当作数据。
指令在内存中的表现(小端序)
在小端序 ARM 中,每个 32 位字节序是逆序存放的。
| 地址偏移 | 字节 |
|---|---|
| +0 | 00 |
| +1 | 20 |
| +2 | 9F |
| +3 | E5 |
所有指令和数据字都遵循相同的顺序。
从源码到原始二进制
# Assemble and link
arm-none-eabi-as -o startup.o startup.s
arm-none-eabi-ld -o first-hang.elf startup.o
arm-none-eabi-objcopy -O binary first-hang.elf first-hang.bin
# Hex dump of the final binary
hexdump -C first-hang.bin
# 00000000 00 20 9f e5 fe ff ff ea ef be ad de |. ..........|
# 0000000c
# Interpreted as 32‑bit words (little‑endian)
xxd -e first-hang.bin
# 00000000: e59f2000 eafffffe deadbeef
输出与前面推导的编码相匹配。