强制 ARM64X 可执行文件以特定架构运行

The Old New Thing (Raymond Chen) 新闻

摘要

本文介绍如何在 Windows 上强制 ARM64X 可执行文件以特定架构运行,使用 PROC_THREAD_ATTRIBUTE_MACHINE_TYPE 属性在需要插件兼容性时重新启动进程。

<p>ARM64X 是一种 <a href="https://en.wikipedia.org/wiki/Fat_binary">胖二进制文件</a> Windows 可执行文件和 DLL 格式,用于 64 位 ARM 系统。对于 DLL,选择很明确,因为只有一个会起作用:加载的 DLL 版本是匹配主机进程的那个。如果主机进程使用 Windows ARM64 ABI,则使用 DLL 的 ARM64 版本,如果主机进程基于 x86-64 或使用 Windows ARM64EC ABI¹</p> <p>对于可执行文件,系统有选择权。它可以选择以 ARM64 运行进程,或者以 ARM64EC 运行。如何强制系统选择您偏好的架构?</p> <p>如果您有一个程序被编译为 ARM64X,因为您有插件模型,并且希望支持以 ARM64 或 x86-64 编写的插件,那么您可能需要这样做。您为 ARM64 插件编译 ARM64 版本,为 x86-64 插件编译 ARM64EC 版本。在运行时,您发现用户传递了另一架构的插件,因此您希望以匹配的架构重新启动自己。</p> <p>您可以使用 <code>PROC_<wbr />THREAD_<wbr />ATTRIBUTE_<wbr />MACHINE_<wbr />TYPE</code> 属性来实现。</p> <p>以下是一个程序,它接受命令行上的 DLL。它尝试以本机架构加载它,但如果失败,并且本机架构是 ARM64,则它会以 x86-64 重新启动自己以重试。</p> <pre>#include &lt;windows.h&gt; #include &lt;stdio.h&gt; #include &lt;wil/result_macros.h&gt; #include &lt;wil/resource.h&gt; #include &lt;wil/stl.h&gt; #include &lt;wil/win32_helpers.h&gt; int wmain(int argc, wchar_t** argv) { if (argc &lt; 2) { printf("哎呀\n"); return 0; } wil::unique_hmodule dll{ LoadLibraryExW(path, nullptr, 0) }; if (dll) { return RunPlugin(dll); } if (GetLastError() != ERROR_BAD_EXE_FORMAT) { printf("无法加载 DLL,抱歉\n"); return 0; } SYSTEM_INFO info{}; GetSystemInfo(&amp;info); if (info.wProcessorArchitecture != PROCESSOR_ARCHITECTURE_ARM64) { printf("无法加载 DLL,抱歉\n"); return 0; } printf("再次尝试以 x86-64 运行\n"); <span style="border: solid 1px currentcolor; border-bottom: none;">WORD arch = IMAGE_FILE_MACHINE_AMD64; </span> <span style="border: 1px currentcolor; border-style: none solid;">auto single = <a title="A little helper class for managing LPPROC_THREAD_ATTRIBUTE_LISTs" href="https://devblogs.microsoft.com/oldnewthing/20260813-00/?p=112611">make_proc_thread_attribute_list</a>({</span> <span style="border: 1px currentcolor; border-style: none solid;"> {PROC_THREAD_ATTRIBUTE_MACHINE_TYPE, &amp;arch}</span> <span style="border: solid 1px currentcolor; border-top: none;">}); </span> wchar_t self[MAX_PATH + 1]; std::wstring self; THROW_IF_FAILED(wil::GetModuleFileNameW(nullptr, self)); wil::unique_process_information pi; STARTUPINFOEXW info{ sizeof(STARTUPINFOEXW) }; <span style="border: solid 1px currentcolor;">info.lpAttributeList = single.get();</span> if (!CreateProcessW(self.data(), GetCommandLineW(), nullptr, nullptr, false, EXTENDED_STARTUPINFO_PRESENT, nullptr, nullptr, &amp;info.StartupInfo, &amp;pi)) { printf("无法以 x86-64 重新启动,抱歉\n"); return 0; } WaitForSingleObject(pi.hProcess, INFINITE); // 析构函数将关闭句柄 } </pre> <p>如果我们能加载 DLL,那就太好了!我们照常运行它。</p> <p>如果我们无法加载 DLL,因为它格式错误,并且当前进程以 ARM64 运行,那么我们将以 x86-64 重试。为此,我们创建一个属性列表,其中包含 <code>PROC_<wbr />THREAD_<wbr />ATTRIBUTE_<wbr />MACHINE_<wbr />TYPE</code> 属性,其值为我们想要尝试的架构,即 AMD64(Windows 中对 x86-64 的称呼),并以相同的命令行重新启动自己。²</p> <p>如果 DLL 即使以 x86-64 也无法加载,那么我们程序的 x86-64 版本会直接放弃,而不会再次尝试以 ARM64 运行。(您不希望 x86-64 版本再次尝试以 ARM64 运行,因为那会导致无限循环。)</p> <p>¹ 您可以将 ARM64EC 视为 &#8220;ARM64 上的预编译 x86-64&#8221;。它类似于获取一个 x86-64 二进制文件并将其编译成 ARM64 代码,该代码与(但可能性能优于)模拟器从您的 x86-64 版本实时创建的版本等效。与其发布模拟器必须翻译成 ARM64 的 x86-64 版本,不如直接发布翻译后的版本。</p> <p>² 在现实生活中,您可能需要添加一些安全预防措施以防止意外的 fork 炸弹。在撰写本文时,我曾不小心几次 fork 炸弹了我的机器。</p> <p>文章 <a href="https://devblogs.microsoft.com/oldnewthing/20260814-00/?p=112613">强制 ARM64X 可执行文件以特定架构运行</a> 最初发布在 <a href="https://devblogs.microsoft.com/oldnewthing">The Old New Thing</a>。</p>
查看原文
查看缓存全文

缓存时间: 2026/08/15 03:22

# 强制ARM64X可执行文件以特定架构运行 来源:https://devblogs.microsoft.com/oldnewthing/20260814-00?p=112613 ARM64X是一种适用于64位ARM系统的通用二进制(fat binary)Windows可执行文件和DLL格式。对于DLL而言,选择很明确,因为只有一个版本能正常工作:加载的DLL版本必须与宿主进程匹配。若宿主进程使用Windows ARM64 ABI,则加载ARM64版本的DLL;若宿主进程基于x86-64架构或使用Windows ARM64EC ABI¹,则加载对应的兼容版本。 对于可执行文件,系统有选择余地——既可能以ARM64架构运行进程,也可能以ARM64EC架构运行。如何强制系统选择你偏好的架构?这种需求常见于采用插件模式的程序:你希望同时支持ARM64和x86-64插件,因此将主程序编译为ARM64X格式(包含ARM64和ARM64EC两个版本)。运行时若发现用户加载了其他架构的插件,就需要以匹配的架构重新启动程序。 这可以通过`PROC_THREAD_ATTRIBUTE_MACHINE_TYPE`属性实现。以下示例程序接受命令行传入的DLL路径,首先尝试以本机架构加载,若失败且当前为ARM64架构,则自动切换至x86-64架构重试: ```cpp #include <windows.h> #include <stdio.h> #include <wil/resource.h> int wmain(int argc, wchar_t** argv) { if (argc < 2) { printf("Oops\n"); return 0; } auto dll = wil::unique_hmodule{ LoadLibraryExW(argv[1], nullptr, 0) }; if (dll) { return RunPlugin(dll); } if (GetLastError() != ERROR_BAD_EXE_FORMAT) { printf("Can't load DLL, sorry\n"); return 0; } SYSTEM_INFO info{}; GetSystemInfo(&info); if (info.wProcessorArchitecture != PROCESSOR_ARCHITECTURE_ARM64) { printf("Can't load DLL, sorry\n"); return 0; } printf("Trying again as x86-64\n"); WORD arch = IMAGE_FILE_MACHINE_AMD64; auto attributeList = wil::make_proc_thread_attribute_list( { {PROC_THREAD_ATTRIBUTE_MACHINE_TYPE, &arch} } ); wchar_t self[MAX_PATH + 1]; THROW_IF_FAILED(wil::GetModuleFileNameW(nullptr, self)); wil::unique_process_information pi; STARTUPINFOEXW startupInfo{ sizeof(STARTUPINFOEXW) }; startupInfo.lpAttributeList = attributeList.get(); if (!CreateProcessW(self, GetCommandLineW(), nullptr, nullptr, false, EXTENDED_STARTUPINFO_PRESENT, nullptr, nullptr, &startupInfo.StartupInfo, &pi)) { printf("Can't relaunch as x86-64, sorry\n"); return 0; } WaitForSingleObject(pi.hProcess, INFINITE); // 析构函数会自动关闭句柄 } ``` 若成功加载DLL则直接执行;若因格式错误加载失败,且当前进程运行在ARM64架构下,则创建包含`PROC_THREAD_ATTRIBUTE_MACHINE_TYPE`属性的列表(值设为AMD64,即x86-64的Windows标识),以相同命令行重新启动自身²。 即使切换至x86-64架构后仍加载失败,程序也不会再次尝试ARM64架构(避免陷入无限循环)。 --- 1. ARM64EC可理解为“在ARM64上预编译的x86-64”,类似于将x86-64二进制文件预先编译为等效的ARM64代码(性能通常优于运行时模拟转换)。 2. 实际应用中建议添加安全防护措施以防意外触发fork bomb。撰写本文时,作者曾不慎多次导致系统卡死。 ### 分类 ## 作者 **Raymond Chen** Raymond参与Windows系统演进已超过30年。2003年他创立了“The Old New Thing”博客,其受欢迎程度远超预期(这至今仍让他感到不安)。该博客衍生出同名著作(Addison-Wesley出版社,2007年)。他偶尔会在Windows开发文档Twitter账号上分享些无关紧要的故事。

相似文章

驯服 Steam arm64 客户端(在 pmOS 上)

Lobsters Hottest

一篇博客文章,详细介绍了在 postmarketOS 上运行非官方 Steam arm64 客户端的各种蹊跷与挑战,包括客户端未意识到自己是 arm64、缺少 Proton/运行时下载,以及提及 FEX 和图形提供商清单(与 Valve 即将推出的 Steam Frame 相关)。

编写可移植的ARM64汇编代码

Hacker News Top

一份关于编写可在Apple Darwin和Linux/BSD系统间移植的ARM64汇编代码的指南,涵盖ABI、符号命名和向量助记符的差异。

ARM64 指令的真正编码方式

Lobsters Hottest

一篇科普文章,解释 ARM64 (AArch64) 指令如何以 32 位固定长度字编码,破除常见误解,并通过 Apple Silicon 上的 ADD immediate 指令提供动手解码示例。

Windows堆栈限制检查回顾,后续

The Old New Thing (Raymond Chen)

Raymond Chen跟进了他之前关于ARM64堆栈限制检查的文章,指出了堆栈探测函数中x15寄存器的非常规使用细节,并比较了多个架构的寄存器使用。

面向 Itanium 的 Windows XP 2002:难以抑制的愤怒

Hacker News Top

一篇复古计算博客文章报道称,一个 QEMU 分支现在能够模拟 Intel Itanium(Merced),足以运行适用于 IA-64 的 Windows XP 2002,并记录了在 macOS 上构建交叉编译器以帮助其运行的过程。