Wine64 是如何处理 macOS 的?

How did Wine64 manage to handle macOS?

十年来这一直是一个主要障碍。 reported as impossible. Forum talks referred to issues related to setting and restoring GS. Wine HQ FAQ still refers to ABI incompatibility page 不是实时维基页面,而是新闻存档 link。

Wine 2.0 announced macOS 64-bit support。但是……怎么办?这不是所有 macOS 黑客都应该知道的事情吗?对于任何 x86-64 黑客来说,也许一些优雅的(或肮脏的)技巧本身就很有趣。

主要障碍是 OS 控制下的 CPU 维护的 GS 段基地址 (GS.base) 发生冲突。

在 64 位 Windows 上,GS.base 用于保存每个线程的线程环境块 (TEB) 结构的地址。 Windows 应用程序希望使用 %gs 相对地址访问 TEB。这是硬编码到应用程序代码中,而不是在 API 函数后面。

在 macOS 上,GS.base 用于保存线程 struct _pthread 的线程局部存储区的基址,这是 Pthreads 实现的内部实现细节。 Mac 应用程序在其中嵌入硬编码 %gs 相对访问的情况并不常见,但有些应用程序确实如此,系统库也是如此。

在 Linux 上,GS.base 可供 64 位应用程序用于其自身用途。所以,在那里,Wine 只是使用 OS 提供的机制来设置它。 Wine 不能在 macOS 上做到这一点。 OS 不仅没有提供任何机制来做到这一点,而且如果 Wine 可以的话,它会破坏系统库。 (它还会在上下文切换时给内核带来潜在问题 and/or 内核可能无法恢复 Wine 可能设置的任何值。)

我们想出的解决方案只是部分解决方案。 TEB 结构中最常访问的字段是 "self" 字段 (%gs:0x30) 和线程本地存储实现的字段 (%gs:0x58)。通常,如果应用程序需要访问其他字段,它们会先读取自身字段,然后引用该字段。

在 macOS 上,%gs:0x30%gs:0x58 对应于线程本地存储区域的特定插槽。它们属于 Apple 保留的部分(而不是应用程序使用)。我们发现其中一个插槽未被使用。另一个用于 C 库中的 ttyname() 函数。碰巧的是,Wine 从不调用该函数,也没有理由期望它使用的任何系统库都这样做。

因此,Wine 只是在那些 %gs 相对位置戳出适当的值。因此,当 64 位 Windows 应用程序代码读取它们时,它会得到它需要的东西。 Wine 分配的实际 TEB 位于其他地方(在堆分配的内存中),但是应用程序在他们期望成为 TEB 自身字段的地方找到了 TEB 的地址,所以他们那样找到它。

Apple 从那以后慷慨地永久保留了这两个插槽,供 Wine 之类的用途使用。 ttyname() 现在使用不同的插槽。

也就是说,如上所述,此解决方案只是部分解决方案。某些应用程序使用 %gs 相对地址直接访问 TEB 的其他字段,偏移量不同于 0x300x58。当他们这样做时,他们得到垃圾值 and/or 覆盖系统其他部分使用的值。因此,Wine 对 64 位 Windows 应用程序的支持在 macOS 上并不完整。某些此类应用程序会崩溃或出现其他异常情况。幸运的是,这种情况很少发生,因此在实践中并不是什么大问题。

作为参考,以下是实现此解决方案的提交:

http://source.winehq.org/git/wine.git/?a=commit;h=7501942008f91a9a137fe598ce5ce7cb47de5522 http://source.winehq.org/git/wine.git/?a=commit;h=3d8efb238808a519902e047d8673237debb0f0a2