编写可移植的ARM64汇编代码
摘要
一份关于编写可在Apple Darwin和Linux/BSD系统间移植的ARM64汇编代码的指南,涵盖ABI、符号命名和向量助记符的差异。
暂无内容
查看缓存全文
缓存时间: 2026/06/03 09:44
# 编写可移植的 ARM64 汇编代码
来源:https://ariadne.space/2023/04/12/writing-portable-arm-assembly.html
苹果基于 ARM 的计算机日益流行,其中一个不幸的副作用是出现了大量针对 64 位 ARM ISA 的不可移植汇编代码。这是因为开发者编写这些汇编代码是为了在苹果的 ARM 计算机上加速程序运行,却没有考虑到其他 64 位 ARM 设备,例如 SBC 和运行 Linux 或 BSD 的服务器。
好消息是,编写同时适用于苹果计算机和其他运行非 Darwin 操作系统的 64 位 ARM 设备的汇编代码非常容易。只需注意 Mach-O 与 ELF ABI 之间的几个差异,并知道避免使用哪些苹果特有的语法扩展。遵循本博客的指导,你将能够编写出在苹果工具链、官方 ARM 汇编工具链和 GNU 工具链之间可移植的汇编代码。
## ELF 与 Mach-O ABI 的区别
现代 UNIX 系统(包括基于 Linux 的系统)主要使用 ELF 二进制格式(https://en.wikipedia.org/wiki/Executable_and_Linkable_Format)。出于历史原因,苹果在 Darwin 中使用的是 Mach-O(https://en.wikipedia.org/wiki/Mach-O)。这并不是苹果使用 Mach 内核所强制的,实际上,Darwin、MkLinux 和 OSF/1 所基于的 OSFMK 内核完全可以很好地支持 ELF 二进制文件。只是苹果决定使用 Mach-O 格式。
在编写针对 Darwin 的汇编代码(实际上,任何链接代码)时,需要注意的主要区别是所有符号都带有一个下划线前缀。例如,如果一个函数在 C 语言中声明如下:
```
extern void unmask(const char *payload, const char *mask, size_t len);
```
那么在 Darwin 上,你的汇编代码中该函数必须定义为 `_unmask`。
另一个主要区别是 ELF 定义了不同的数据类别,例如 `STT_FUNC` 和 `STT_OBJECT`。Mach-O 中没有对应物,因此在为 ELF 目标编写汇编时使用的 `.type` 指令在 Mach-O 中不受支持。
### 关于平台 ABI 的简要说明
你还需要注意 Darwin ABI 与其他平台 ABI 之间的细微差异。一个显著的例子是 `x18` 寄存器在 Darwin ABI 中被保留,并且在某些上下文切换中会被显式清零。这个寄存器在 Android 上也被保留,但在 GNU/Linux 或 Alpine 上则没有。
## 苹果特有的向量助记符
另一个需要注意的主要问题是苹果为 NEON 自定义的助记符。为了使编写 NEON 代码更加便捷,苹果引入了一套助记符,允许简化 NEON 指令的指定。例如,如果你只针对苹果设备,你可能会这样编写一个异或 NEON 指令:
```
eor v2.16b, v2.16b, v0.16b
```
这是苹果对 ARM 汇编语法的扩展。官方 ARM 汇编手册(https://developer.arm.com/documentation/dui0802/b/A64-SIMD-Vector-Instructions/EOR--vector-)规定必须为每个寄存器指定内存布局:
```
eor v2.16b, v2.16b, v0.16b
```
## 使用宏抽象 ABI 细节
好消息是,ABI 细节可以很容易地通过几个宏来抽象。至于使用 NEON 函数,答案很简单:坚持按照 ARM 手册的规定编写,而不是使用苹果的助记符。
你需要两个宏。如果需要,可以将它们放在头文件中。
第一个宏允许你处理 Darwin ABI 的下划线要求:
```
#ifdef __APPLE__
# define PROC_NAME(__proc) _ ## __proc
#else
# define PROC_NAME(__proc) __proc
#endif
```
第二个宏是可选的,但它允许你在苹果工具链之外定义正确的 ELF 符号类型:
```
#ifdef __clang__
# define TYPE(__proc, __typ)
#else
# define TYPE(__proc, __typ) .type __proc, __typ
#endif
```
然后像往常一样编写汇编代码,但使用这些宏:
```
.global PROC_NAME(unmask)
.align 2
TYPE(unmask, @function)
PROC_NAME(unmask):
...
```
就是这样。只要遵循这些指导原则,你的汇编代码就能在 64 位 ARM 上的任何类 UNIX 环境中移植。
相似文章
用 aarch64 汇编构建 Web 服务器,给我的生活(缺乏)意义
本文介绍了 'ymawky',一个完全用 aarch64 汇编为 macOS 编写的最小 HTTP Web 服务器,使用原始系统调用而无需 libc 包装器,以探索底层系统机制。
用 x86_64 汇编写成的 Linux 桌面
一位开发者借助 Claude Code,用纯 x86_64 汇编重建了完整的 Linux 桌面栈——从 shell、终端、窗口管理器到各种工具,实现微秒级启动,并延长数小时续航。
Show HN: 用汇编语言构建 Web 服务器,为我的生命赋予(些许缺乏的)意义
ymawky 是一个专为 macOS 编写的 ARM64 汇编 Web 服务器,其特点是不依赖 libc 仅使用系统调用,并具备基本的 HTTP 功能。
让编写跨平台 SIMD 代码变得愉快
作者详细介绍了 bx 库跨平台 SIMD 抽象的第三次迭代,倡导无类型方法和 SSA 风格编码,以简化不同 CPU 架构上的底层性能优化。
SBCL: 终极汇编代码面包板 (2014)
一篇技术博客文章,探讨如何使用SBCL作为汇编代码的面包板,重点介绍基于堆栈的虚拟机技术,如旋转堆栈和高效的原语操作分发,并引用了F18处理器和x87堆栈。