违反 macOS ARM64 调用约定的后果

swi*_*one 12 macos assembly abi calling-convention arm64

我正在将一些 AArch64/ARM64/Apple Silicon 汇编代码从 Linux 移植到 macOS。

\n

此代码使用所有 31 个可用寄存器(堆栈指针不算在内)来避免几乎所有溢出情况;Linux 调用约定允许我使用那么多寄存器。

\n

如果迫不得已,我承认溢出一个额外的寄存器(从而将其减少到使用 30 个寄存器)是可行的,因为性能受到的影响最小,但如果限制为 29 个或更少的可用寄存器,性能将受到更大的影响。因此,我真的希望至少有 30 个可用寄存器,最好是 31 个。

\n

我刚刚从 Apple 官方文档中了解到保留了两个额外的寄存器,超出了 Linux 调用约定的要求:

\n
\n

尊重特定 CPU 寄存器的用途

\n

ARM 标准将某些决策委托给平台设计者。Apple 平台遵循以下选择:

\n
    \n
  • 该平台保留寄存器x18。不要\xe2\x80\x99 使用该寄存器。
  • \n
  • 帧指针寄存器 (x29) 必须始终寻址有效的帧记录。某些函数 \xe2\x80\x94 例如叶函数或尾部调用 \xe2\x80\x94 可能会选择不在该列表中创建条目。因此,即使没有调试信息,堆栈跟踪也始终是有意义的。
  • \n
\n
\n

尽管有这些说法,我的代码在没有它的情况下似乎运行良好。

\n

现在,我完全明白忽略此类 ABI 要求是一件非常糟糕的事情 (TM)。但是,我想确切地了解代码如何因使用 x18 和 x29 而中断。

\n

例如,通过阅读上述文档,我的理解是 x29 是为了支持调试或故障转储。假设我不关心调试这个函数(实际上我不关心),或者任何生成的故障转储是否有意义。那么,使用x29有什么坏处吗?

\n

至于x18,知道它有什么用吗?我假设(零支持证据)如果在该代码运行时执行中断或上下文切换,则不会保存 x18,因此一旦返回就会破坏我的函数的结果。这将是一个更严重的情况,我会听取建议,在这种情况下不要使用 x18。

\n

另请注意,所讨论的代码是叶函数,因此破坏从其中调用的任何函数都没有问题。

\n

Sig*_*uza 13

x29如果损坏的回溯对您来说是可以接受的损失,那么您可以放心地破坏。

x18这是一个不同的故事。
在 macOS 上,Rosetta 使用它,因此 Apple 不能再破坏它,至少要重构它。他们还进行了内核测试,以确保x18“在支持它的硬件上”恢复。到目前为止,这是所有支持 arm64 macOS 的硬件,并且所有支持 Apple Silicon 的 macOS 版本都启用了此行为。
但在 iOS 上,有些硬件不支持它,特别是 A11 芯片系列及更早版本。在这些芯片上,内核配置了__ARM_KERNEL_PROTECT__,这使得在内核有机会溢出任何寄存器之前,可以x18 在所有异常处理程序(甚至异步处理程序)上使用 Spectre 缓解措施。因此,除非您在关闭中断的情况下运行,否则您x18可以在任何时间点归零。此外,即使在 A12 及更高版本上,iOS 14.0 之前的 iOS 版本也会x18故意破坏。

现在,如果您检查了链接测试,您可能会想在运行时检查 sysctl hw.optional.arm_kernel_protect,但不幸的是,它仅导出到DEVELOPMENTXNUDEBUG的配置上。

因此,如果您的目标是 iOS,则无法使用x18. 如果您的目标是 macOS,那么您暂时可以使用它,但将来可能会发生变化。您可以尝试通过执行与测试相同的操作来检测此类更改:设置x18为某个值,调用sched_yield(),然后检查该值。但同样,这依赖于所有异常处理x18相同的情况,虽然目前这样做,但将来也可能会发生变化。

更新
macOS 13 确实改变了这一点!虽然 macOS 11 和 12 将无条件保留x18,但 macOS 13 现在有更复杂的规则。x18现在仅保留在 Rosetta 下运行的进程以及针对 macOS 12 SDK 或更早版本构建的进程,或者持有com.apple.private.uexccom.apple.private.custom-x18-abientitlements 的进程。

同样的规则也适用于 iOS 16,但需要注意的是,没有 Rosetta,并且您无法运行针对 macOS SDK 构建的二进制文件。因此,从 iOS 16 开始,x18必须再次考虑对所有设备禁用。


mst*_*sjo 6

我认为如果你不想使用回溯,那么使用 x29 来做你喜欢的任何事情都可以。

当 Apple 在 iOS 上开始使用 aarch64 时,他们还没有想到 x18 的任何具体用途,但他们希望保留它,以确保人们不会意外地最终依赖它。所以那时候,内核破坏了 x18,在每次上下文切换时将其设置为某个非零的虚假值,这样每个人都非常清楚你不能使用它。

自从 macOS 登陆 Apple Silicon 以来,他们确实删除了故意破坏该寄存器的代码片段。我不确定这是否是因为他们实际上在某个地方找到了寄存器的特定用途(因此他们不能让内核破坏它),或者只是选择更好地对待它。(它确实有助于例如使用 Wine 模拟 Windows 可执行文件的用户空间 - 在 Windows 中,x18 应该始终是指向 TEB(线程环境块)的指针。)

因此,虽然 x18 目前在 macOS 上确实可以使用,但不确定它是否可以在 iOS 上使用,并且它可以用于某些用途,所以我建议不要使用它。