Project Icon

HelloSilicon

Apple silicon Mac汇编语言开发教程

HelloSilicon项目为开发者提供了Apple silicon Mac汇编语言编程的入门指南。该项目基于《Programming with 64-Bit ARM Assembly Language》一书,调整示例代码以适配Apple ARM64架构。内容涵盖基础知识、系统调用、内存管理、与C/Python交互等方面,并详细解释了Apple平台的特殊性。对于希望深入了解Apple silicon底层编程的开发者而言,这是一个全面且实用的学习资源。

HelloSilicon

苹果芯片Mac汇编语言入门。

简介

在这个仓库中,我将按照《64位ARM汇编语言程序设计》这本书进行编程,并调整所有示例代码以适用于苹果的ARM64系列计算机。虽然苹果的营销材料似乎避免为这个平台命名,只谈论M1处理器,但开发者文档使用了"Apple silicon"这个术语。我将在下文中使用这个术语。

原始源代码可以在这里找到。

先决条件

虽然我基本假设能找到这里的人都满足大部分(如果不是全部)必要的先决条件,但列出它们也无妨。

  • 你需要Xcode 12.2或更高版本,为了方便起见,应该安装命令行工具。这确保了工具能在默认位置(即/usr/bin)找到。如果你不确定工具是否已安装,请在Xcode中查看"偏好设置→位置"或运行xcode-select --install

  • 所有应用程序示例还需要至少macOS Big SuriOS 14或它们各自的watchOS或tvOS等效版本。特别是对于后三个系统,这并非绝对必要(Xcode 12.2也不是),但它会使事情变得简单得多。

  • 最后,虽然所有示例都可以调整为在iPhone和苹果所有其他ARM64设备上工作,但为了获得最佳结果,你应该有权访问一台Apple silicon Mac

致谢

我要感谢@claui@jannau@jrosengarden@m-schmidt@saagarjha@zhuowei!当我遇到困难时,他们帮助了我,或者提出了让我改进内容的问题。

与书中的变化

除了现有的iOS示例外,这本书基于Linux操作系统。苹果的操作系统(macOS、iOS、watchOS和tvOS)实际上只是Darwin操作系统的不同版本,所以它们共享一组通用的核心组件。

Linux和Darwin,它们都受到AT&T Unix System V的启发,在我们关注的层面上有显著不同。对于书中的列表,这主要涉及系统调用(即当我们想让内核为我们做些什么时),以及Darwin访问内存的方式。

本文件的组织方式使你可以阅读这本书,同时了解苹果芯片的差异。本文档中的标题与书中的标题一致。

第1章:入门

计算机和数字

macOS上的默认计算器应用也有"程序员模式"。你可以通过"查看→程序员"(⌘3)来启用它。

CPU寄存器

苹果对寄存器做出了特定的平台选择:

  • 苹果保留X18供自己使用。不要使用这个寄存器。
  • 帧指针寄存器(FPX29)必须始终指向有效的帧记录。

关于GCC汇编器

这本书使用Linux GNU工具,如GNU as汇编器。虽然macOS上有as命令,但它默认会调用集成的LLVM Clang汇编器。即使有-Q选项可以使用基于GNU的汇编器,这也仅是x86_64的选项——而且在撰写本文时已经被弃用。

% as -Q -arch arm64
/usr/bin/as: can't specifiy -Q with -arch arm64

因此,GNU汇编器语法不是一个选项,代码将需要调整以适应Clang汇编器语法。

同样,虽然macOS上有gcc命令,但这只是调用Clang C编译器。为了透明起见,所有对gcc的调用将被替换为clang

% gcc --version
Configured with: --prefix=/Applications/Xcode-beta.app/Contents/Developer/usr --with-gxx-include-dir=/Applications/Xcode-beta.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/c++/4.2.1
Apple clang version 12.0.0 (clang-1200.0.32.27)
Target: arm64-apple-darwin20.1.0
Thread model: posix
InstalledDir: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin

Hello World

如果你正在阅读这个,我假设你已经知道macOS终端可以在"应用程序→实用工具→终端.app"中找到。但如果你不知道,我很荣幸告诉你,并祝你在这个旅程中玩得开心!不要害怕提问。 要在 Apple Silicon 上运行"Hello World",首先需要应用第78页(第3章)的更改,以适应 Darwin 和 Linux 内核之间的差异。

为了消除警告,我插入了 .align 4(或 .p2align 2),因为 Darwin 喜欢将内容对齐到偶数边界。书中在第5章第114页的"对齐数据"部分提到了这一点。

Linux 和 macOS 的系统调用因各系统的独特约定而存在几个差异。以下是一些主要区别:

  • 函数编号:两个系统的函数编号不同,Linux 使用64,而 macOS 使用4。Darwin(Apple)系统调用的表格可以在这个链接找到:Darwin 系统调用。请注意,这是特定版本(撰写时的最新版本),更新的版本可以在这里找到。

[!注意] Darwin 函数编号被 Apple 视为私有,可能会发生变化。此处提供仅供教育目的使用。

  • 存储函数编号的地址:用于存储函数编号的地址也不同。在 Linux 中,它在 X8 上,而在 macOS 中,它在 X16 上。
  • 中断调用:Linux 中的中断调用是0,而在 Apple Silicon 上是 0x80。

要使链接器工作,还需要一些额外的设置,其中大部分对 Mac/iOS 开发者来说应该很熟悉。这些更改需要应用到 makefilebuild 文件中。完整的链接器调用如下所示:

ld -o HelloWorld HelloWorld.o \
	-lSystem \
	-syslibroot `xcrun -sdk macosx --show-sdk-path` \
	-e _start \
	-arch arm64

我们已经知道 -o 开关,让我们来看看其他的:

  • -lSystem 告诉链接器将我们的可执行文件与 libSystem.dylib 链接。我们这样做是为了向可执行文件添加 LC_MAIN 加载命令。通常,Darwin 不支持静态链接的可执行文件。虽然不太优雅,但可以创建不使用 libSystem.dylib 的可执行文件。时间允许的话,我会深入讨论这个话题。对于阅读过《Mac OS X 内部原理》的人,我只补充说这从 MacOS X 10.7 开始取代了 LC_UNIXTHREAD
  • -sysroot:为了找到 libSystem.dylib,必须告诉我们的链接器在哪里找到它。在 macOS 10.15 上似乎不需要这样做,因为"从 macOS Big Sur 11 测试版开始,系统附带了所有系统提供的库的内置动态链接器缓存。作为此更改的一部分,动态库的副本不再存在于文件系统中。"我们使用 xcrun -sdk macosx --show-sdk-path 来动态使用当前活跃的 Xcode 版本。
  • -e _start:Darwin 期望一个入口点 _main。为了尽可能保持示例与书中相近,并允许在《第3章》的 C 示例中使用它,我选择保留 _start 并告诉链接器这是我们想要使用的入口点。
  • -arch arm64 为了万无一失,让我们加入从 Intel Mac 交叉编译的选项。在 Apple silicon 上运行时可以省略这个选项。

逆向工程我们的程序

虽然 objdump 命令行程序在 Darwin 上同样有效并产生预期的输出,但也可以尝试使用 --macho(或 -m)选项,这会使 objdump 使用 Mach-O 特定的对象文件解析器。

第2章:加载和相加

需要应用第1章的更改(makefile、对齐、系统调用)。

寄存器和移位

gcc 汇编器接受 MOV X1, X2, LSL #1,这在 ARM 编译器用户指南中未定义,而是使用 LSL X1, X2, #1(等)。毕竟,这两者都只是指令 ORR X1, XZR, X2, LSL #1 的别名。

寄存器和扩展

Clang 要求源寄存器为32位。这是有道理的,因为使用这些扩展时,64位寄存器的高32位永远不会被触及:

ADD X2, X1, W0, SXTB

GNU 汇编器似乎忽略了这一点,允许你指定64位源寄存器。

第3章:工具准备

开始使用 GDB

在 macOS 上,gdb 已被 LLVM 项目的 LLDB 调试器 lldb 取代。语法并不总是相同,所以我会在这里注明差异。

要开始调试我们的 movexamps 程序,输入以下命令:

lldb movexamps

这会产生简略的输出:

(lldb) target create "movexamps"
Current executable set to 'movexamps' (arm64).
(lldb)

runlist 这样的命令工作方式相同,还有一个很好的 GDB 到 LLDB 命令映射

要反汇编我们的程序,lldb 使用略有不同的语法:

disassemble --name start

请注意,由于我们正在链接动态可执行文件,列表会很长,并包含其他start函数。我们的代码将列在movexamps`start行下。

同样,lldb希望断点名称不带下划线:b start

要在lldb中获取寄存器信息,我们使用register read(或re r)。如果不带参数,此命令将打印所有寄存器,或者您可以指定只想查看的寄存器,如re r SP X0 X1

我们可以使用breakpoint list(或br l)查看所有断点。我们可以使用breakpoint delete(或br de)删除断点,指定要删除的断点编号。

lldb甚至有更强大的机制来显示内存。主要命令是memory read(或m read)。首先,以下是本书使用的参数:

memory read -fx -c4 -s4 $address

其中

  • -f是显示格式
  • -s是数据大小
  • -c是计数

清单3-1

作为练习,我添加了代码来查找macOS上的默认Xcode工具链。在书中,他们使用这个来后续从Linux切换到Android工具链。对于macOS和iOS来说,这个过程有很大不同:它通常不涉及不同的工具链,而是涉及不同的软件开发工具包(SDK)。你可以在清单1-1中看到,其中设置了-sysroot

话虽如此,虽然可以使用命令行构建iOS可执行文件,但这并不是一个简单的过程。因此,对于构建应用程序,我将坚持使用Xcode。

Apple Xcode

由于第10章专注于构建一个可以在iOS上运行的应用程序,我选择在这里简单地创建一个命令行工具,现在使用相同的HelloWorld.s文件。

请注意,函数编号不仅不同,而且在Darwin上,它们被视为私有的,可能会发生变化。

第4章:控制程序流程

除了常见的变化外,我们还面临一个在书中第5章描述的新问题:Darwin不喜欢LDR X1, =symbol,它会产生错误ld: Absolute addressing not allowed in arm64 code。如果我们使用书中第3章建议的ASR X1, symbol,我们的数据必须在只读的.text部分。然而,在这个示例中,我们希望数据是可写的。

Apple文档告诉我们,在Darwin上:

所有大型或可能非本地的数据都通过全局偏移表(GOT)条目间接访问。GOT条目使用RIP相对寻址直接访问。

默认情况下,在Darwin上,包含在.data部分的所有数据(数据可写)都是"可能非本地的"。

完整的答案可以在这里找到:

ADRP指令加载当前指令+/-4GB(33位)范围内任何4KB页面的地址(占用偏移量的21个高位)。这由@PAGE运算符表示。然后,我们可以使用LDRSTR来读取或写入该页面内的任何地址,或使用ADD使用偏移量的剩余12位(由@PAGEOFF表示)计算最终地址。

所以这个:

	LDR	X1, =outstr // 输出字符串的地址

变成了这个:

	ADRP	X1, outstr@PAGE	// 输出字符串4k页面的地址
	ADD	X1, X1, outstr@PAGEOFF // 页面内outstr的偏移量

练习

有人问我如何读取命令行,我很乐意回答这个问题。

示例代码可以在第4章的case.s文件中找到。

第5章:感谢内存

Darwin的内存寻址重要区别已在上文讨论。

清单5-1

对于llvm汇编器,quadoctafill关键字必须是小写。(见本文件底部)

清单5-10

变化与第4章相同。

第6章:函数和栈

正如我们在第5章所学,所有汇编器指令(如.equ)必须是小写。

第7章:Linux操作系统服务

Apple SDK中不存在asm/unistd.h,可以使用sys/syscalls.h代替。

**警告:**请注意,Darwin中的系统调用号在官方上被视为私有的,可能会发生变化。这里仅出于教育目的展示它们。

同样重要的是要注意,虽然调用和定义看起来相似,但Linux和Darwin并不相同:AT_FDCWD在Linux上是-100,但在Darwin上必须是-2。

与Linux不同,错误通过设置进位标志来表示,错误代码是非负的。因此,我们将结果MOV到所需的寄存器中,而不是ADDS(我们不需要检查负数,并需要保留条件标志),并使用B.CC跳转到成功路径。

第8章:编程GPIO引脚

这一章专门针对Raspberry Pi 4,所以这里没有需要做的事情。

第9章:与C和Python交互

为了透明度,我将 gcc 替换为 clang

代码清单9-1

除了常规更改外,Apple在可变参数函数上偏离了ARM64标准ABI(即函数调用约定)。可变参数函数是接受不定数量参数的函数,printf就是其中之一。在Linux中,参数可以通过寄存器传递,但在Darwin中我们必须将它们传递到栈上。

str     X1, [SP, #-32]! // 将栈指针向下移动四个双字(32字节)并将X1压入栈
str     X2, [SP, #8]    // 将X2压入当前栈指针上方一个双字
str     X3, [SP, #16]   // 将X3压入当前栈指针上方两个双字
adrp    X0, ptfStr@PAGE // printf格式字符串
add     X0, X0, ptfStr@PAGEOFF  // 添加格式字符串偏移
bl      _printf // 调用printf
add     SP, SP, #32 // 清理栈

首先,我们将栈向下扩展32字节,为三个64位值腾出空间。我们为第四个值创建了空间用于填充,因为正如书中第137页指出的,ARM硬件要求栈指针始终保持16字节对齐。

在同一条指令中,X1被存储在栈指针的新位置。

然后,我们填充刚刚创建的剩余空间,将X2存储在高8字节处,将X3存储在高16字节处。注意,X2X3str指令并不移动SP

我们可以用不同方式填充栈;重要的是printf函数期望参数按顺序以双字值的形式从当前栈指针向上排列。因此,在debug.s文件中,它期望%c的参数位于SP位置,%32ld的参数位于上方一个双字,最后%016lx的参数位于当前栈指针上方两个双字(16字节)处。

我们实际上做的是在栈上分配内存。作为调用者,我们"拥有"该内存,因此需要在函数分支之后释放它,在这种情况下,只需将栈(向上)收缩32字节即可。指令add SP, SP, #32就是这样做的。

代码清单9-5

mytoupper前缀加了_,因为这对Darwin上的C来说是必要的,以便找到它。

代码清单9-6

无需更改。

代码清单9-7

不是创建共享的.so ELF库,而是创建动态Mach-O库。更多信息可以在这里找到:创建动态库

代码清单9-8

在内联汇编中,我们必须通过在cont标签前加前缀L来将其声明为局部标签。虽然在纯汇编中(如第5章)这不是必需的,但llvm C前端会自动将指令.subsections_via_symbols添加到代码中:

Darwin的特殊技巧:此标志告诉链接器没有全局符号包含落入其他全局符号的代码(例如,多入口点的明显实现)。如果不发生这种情况,链接器可以安全地执行死代码剥离。由于LLVM从不生成这样的代码,因此始终可以安全地设置它。 (来自llvm源代码

虽然我们使用LLVM工具链,但在汇编(包括内联汇编)中,所有安全检查都被关闭,因此我们必须采取额外的预防措施,特别声明前向标签为局部标签。

此外,一个变量的大小必须从int改为long,以使编译器完全满意并消除所有警告。

从Python调用汇编

代码清单9-9

虽然uppertst5.py文件只需要做最小的更改,但调用代码却更具挑战性。在Apple silicon Mac上,Python是一个包含两种架构的Mach-O通用二进制文件,分别是x86_64和arm64e:

% lipo -info /usr/bin/python3 
Architectures in the fat file: /usr/bin/python3 are: x86_64 arm64e

值得注意的是,我们之前一直在构建的arm64架构不存在。这使得我们的dylib无法与Python一起使用。

arm64e是Armv-8架构,Apple自A12芯片以来一直在使用。如果你想支持A12之前的设备,你必须坚持使用arm64。第一批使用ARM64的Mac运行在基于A14架构的M1 CPU上,因此Apple决定利用新特性。

那么,我们该怎么办?我们可以将所有内容编译为arm64e,但这会使库在iPhone X或更旧的设备上无法使用,而我们希望也能支持这些设备。 上面,你读到了关于"通用二进制"的内容。长期以来,Mach-O可执行文件格式一直支持在单个文件中包含多种处理器架构。这包括但不限于Motorola 68k(在NeXT计算机上)、PowerPC、Intel x86以及ARM代码,每种架构都有其适用的32位和64位变体。在这种情况下,我正在构建一个包含arm64和arm64e代码的通用动态库。更多信息可以在这里找到。

虽然大多数适用于Linux的Python IDE也可用于macOS,但截至本文撰写时,唯一以arm64架构运行(因此将加载arm64库)的Python IDE是Python.org的IDLE,版本3.10或更新。

图9-1. 我们的Python程序在IDLE IDE中运行

图9-1. 我们的Python程序在IDLE IDE中运行

另外,你也可以使用命令行来测试程序。(从macOS 12.3开始,Apple移除了Python 2,开发者应该使用Python 3)

% python3 uppertst5.py 
b'This is a test!'
b'THIS IS A TEST!'
16

最后注意:虽然Apple的python3二进制文件是arm64e架构,但IDLE使用的Python框架是arm64架构。本章构建的库是包含这两种架构的通用二进制文件,因此可以在任一环境中使用。

第10章:与Kotlin和Swift的接口

核心代码无需更改,但我不仅创建了iOS应用,还创建了一个可在macOS、iOS、watchOS(Series 4及以后)和tvOS上运行的SwiftUI应用。

第11章:乘法、除法和累加

到这里,所做的更改应该是不言自明的。通常的makefile调整、.align 4、地址模式更改和_printf调整。

第12章:浮点运算

与第11章一样,所有更改都已经介绍过了。这里没有什么新内容。

第13章:Neon协处理器

示例使用了非标准语法来引用单个向量元素,GNU汇编器接受这种语法,但Clang不接受。原示例使用V3.4H[0]来引用第一个16位元素,正确的标准语法是V3.H[0],这种语法既被GNU汇编器接受,也被Clang接受。

此时,对代码的所有其他更改应该都是微不足道的。

第14章:代码优化

这里没有不寻常的更改。

第15章:阅读和理解代码

复制一页内存

在Darwin内核中阅读ARM64代码的一个起点可以在bcopy.s中找到。该目录和整个仓库中还有更多内容。

GCC创建的代码

无需更改。Mach-O可执行文件不支持"tiny"代码模型:

% clang -O3 -mcmodel=tiny -o upper upper.c
fatal error: error in backend: tiny code model is only supported on ELF

第16章:代码黑客

可以说的是,clang自动启用位置无关可执行文件,而-no-pie选项不起作用。因此,无法复现upper.s文件中展示的漏洞利用。

附加参考

最后一件事…

"C语言是区分大小写的。编译器也区分大小写。Unix命令行、ufs和nfs文件系统都区分大小写。我也对大小写敏感,尤其是对产品名称。这个IDE叫做Xcode。大写X,小写c。不是XCode或xCode或X-Code。请记住这一点。" — Chris Espinosa

项目侧边栏1项目侧边栏2
推荐项目
Project Cover

豆包MarsCode

豆包 MarsCode 是一款革命性的编程助手,通过AI技术提供代码补全、单测生成、代码解释和智能问答等功能,支持100+编程语言,与主流编辑器无缝集成,显著提升开发效率和代码质量。

Project Cover

AI写歌

Suno AI是一个革命性的AI音乐创作平台,能在短短30秒内帮助用户创作出一首完整的歌曲。无论是寻找创作灵感还是需要快速制作音乐,Suno AI都是音乐爱好者和专业人士的理想选择。

Project Cover

有言AI

有言平台提供一站式AIGC视频创作解决方案,通过智能技术简化视频制作流程。无论是企业宣传还是个人分享,有言都能帮助用户快速、轻松地制作出专业级别的视频内容。

Project Cover

Kimi

Kimi AI助手提供多语言对话支持,能够阅读和理解用户上传的文件内容,解析网页信息,并结合搜索结果为用户提供详尽的答案。无论是日常咨询还是专业问题,Kimi都能以友好、专业的方式提供帮助。

Project Cover

阿里绘蛙

绘蛙是阿里巴巴集团推出的革命性AI电商营销平台。利用尖端人工智能技术,为商家提供一键生成商品图和营销文案的服务,显著提升内容创作效率和营销效果。适用于淘宝、天猫等电商平台,让商品第一时间被种草。

Project Cover

吐司

探索Tensor.Art平台的独特AI模型,免费访问各种图像生成与AI训练工具,从Stable Diffusion等基础模型开始,轻松实现创新图像生成。体验前沿的AI技术,推动个人和企业的创新发展。

Project Cover

SubCat字幕猫

SubCat字幕猫APP是一款创新的视频播放器,它将改变您观看视频的方式!SubCat结合了先进的人工智能技术,为您提供即时视频字幕翻译,无论是本地视频还是网络流媒体,让您轻松享受各种语言的内容。

Project Cover

美间AI

美间AI创意设计平台,利用前沿AI技术,为设计师和营销人员提供一站式设计解决方案。从智能海报到3D效果图,再到文案生成,美间让创意设计更简单、更高效。

Project Cover

稿定AI

稿定设计 是一个多功能的在线设计和创意平台,提供广泛的设计工具和资源,以满足不同用户的需求。从专业的图形设计师到普通用户,无论是进行图片处理、智能抠图、H5页面制作还是视频剪辑,稿定设计都能提供简单、高效的解决方案。该平台以其用户友好的界面和强大的功能集合,帮助用户轻松实现创意设计。

投诉举报邮箱: service@vectorlightyear.com
@2024 懂AI·鲁ICP备2024100362号-6·鲁公网安备37021002001498号