从 ARM 汇编到机器代码:裸机入门

发布: (2025年12月7日 GMT+8 04:36)
5 min read
原文: Dev.to

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

这个小程序:

  1. 将 32 位常量 0xDEADBEEF 加载到 r2
  2. 进入一个有意的无限循环。
  3. 将文字常量放置在内存中。

标签和偏移量的解析方式

因为源码位于单一段中,汇编器可以直接计算绝对地址——不需要链接器重定位。

地址源码机器码
0x00ldr r2, str10xE59F2000
0x04b .0xEAFFFFFE
0x08str1: .word 0xDEADBEEF0xDEADBEEF

编码 ldr r2, str1

LDR Rd, [PC, #offset] 形式使用 PC 相对寻址。

  • 执行时的 PC = 指令地址 + 8 → 0x00 + 0x08 = 0x08
  • 目标地址 = str1 的地址 = 0x08
  • 所需偏移 = 0x08 – 0x08 = 0
字段位数二进制值含义
条件码31‑281110 (E)永久
操作码27‑2001011001LDR(立即数)
基址寄存器 (Rn)19‑161111 (PC)
目的寄存器 (Rd)15‑120010 (R2)
偏移11‑00000000000000

最终指令字: 0xE59F2000

编码 b .

分支指令同样使用 PC 相对寻址。

  • 指令地址 = 0x04
  • 有效 PC = 0x04 + 0x08 = 0x0C
  • 目标地址.) = 0x04
  • 字节偏移 = 0x04 – 0x0C = -8 → 按字计为 -8 / 4 = -2

-2 的 24 位二进制补码表示为 0xFFFFFFFE

最终指令字: 0xEAFFFFFE

为什么 b . 成为无限循环

执行时:

  1. 取指后 PC 为 0x0C
  2. 解码得到的偏移为 -2 条指令 → -8 字节。
  3. 分支目标 = 0x0C + (-8) = 0x04,即分支指令本身的地址。

于是控制流不断返回到同一条指令,形成一个紧凑的无限循环,不会消耗堆栈、寄存器或额外内存。

为什么在裸机代码中使用这种模式

  • 在不可恢复的错误发生时 进行停机。
  • 等待调试器 连接。
  • 硬件早期启动阶段 的占位符。
  • 最小示例 中的显式程序结束标记。

编码 .word 0xDEADBEEF

.word 是汇编器的 指令(directive),而非可执行指令。

  • 它在当前位置保留 4 字节。
  • 文字常量 0xDEADBEEF 原样写入。
  • 除非执行跳入该区域,否则 CPU 只会把它当作数据。

指令在内存中的表现(小端序)

在小端序 ARM 中,每个 32 位字节序是逆序存放的。

地址偏移字节
+000
+120
+29F
+3E5

所有指令和数据字都遵循相同的顺序。

从源码到原始二进制

# 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

输出与前面推导的编码相匹配。

参考资料

Back to Blog

相关文章

阅读更多 »

8086 微代码浏览器

请提供您希望翻译的文章摘录或摘要文本,我才能为您进行简体中文翻译。

用 Rust 超越全栈

封面图片:Going beyond full stack with Rust https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev...