Forcing an ARM64X executable to run as a specific architecture

The Old New Thing (Raymond Chen) News

Summary

The article explains how to force an ARM64X executable to run as a specific architecture on Windows, using the PROC_THREAD_ATTRIBUTE_MACHINE_TYPE attribute to relaunch the process if needed for plug-in compatibility.

<p>ARM64X is a <a href="https://en.wikipedia.org/wiki/Fat_binary"> fat binary</a> Windows executable and DLL format for 64-bit ARM systems. For DLLs, the choice is clear, since only one of them will work: The version of the DLL that is loaded is the one that matches the host process. If the host process uses the Windows ARM64 ABI, then the ARM64 version of the DLL is used, and if the host process is x86-64-based or uses the Windows ARM64EC ABI¹</p> <p>For executables, the system has a choice. It could run the process as ARM64 or it could run it as ARM64EC. How can you force the system to choose the architecture you prefer?</p> <p>You may want to do this if you have a program that is compiled as ARM64X because you have a plug-in model, and you want to be able to support plug-ins that are written either as ARM64 or x86-64. You compile an ARM64 version for ARM64 plug-ins, and you compile an ARM64EC version for x86-64 plug-ins. At run time, you realize that the user passed a plug-in for the other architecture, so you want to relaunch yourself as the matching architecture.</p> <p>You can do it with the <code>PROC_<wbr />THREAD_<wbr />ATTRIBUTE_<wbr />MACHINE_<wbr />TYPE</code> attribute.</p> <p>Here&#8217;s a program that takes a DLL on the command line. It tries to load it as the native architecture, but if that fails, and the native architecture is ARM64, then it relaunches itself as x86-64 to try again.</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("Oops\n"); return 0; } wil::unique_hmodule dll{ LoadLibraryExW(path, 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(&amp;info); if (info.wProcessorArchitecture != PROCESSOR_ARCHITECTURE_ARM64) { printf("Can't load DLL, sorry\n"); return 0; } printf("Trying again as 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("Can't relaunch as x86-64, sorry\n"); return 0; } WaitForSingleObject(pi.hProcess, INFINITE); // destructors will close the handles } </pre> <p>If we can load the DLL, then great! We run it as usual.</p> <p>If we can&#8217;t load the DLL because it&#8217;s in the wrong format, then we will retry as x86-64 if the current process is running as ARM64. To do that, we create an attribute list with the <code>PROC_<wbr />THREAD_<wbr />ATTRIBUTE_<wbr />MACHINE_<wbr />TYPE</code> attribute whose value is the architecture we want to try, namely AMD64 (which is the Windows name for x86-64), and relaunch ourselves with the same command line.²</p> <p>If the DLL fails to load even as x86-64, then the x86-64 version of our program just gives up without trying again as ARM64. (You don&#8217;t want to have the x86-64 version try again as ARM64 because that would create an infinite loop.)</p> <p>¹ You can think of ARM64EC as &#8220;pre-jitted x86-64 on ARM64.&#8221; It is like taking an x86-64 binary and compiling it to ARM64 code that is equivalent to (but presumably has better performance than) the version the emulator would have created on the fly from your x86-64 version. Instead of shipping an x86-64 version that the emulator has to translate to ARM64, just ship the translated version.</p> <p>² In real life, you probably would add some safety precautions to prevent accidental fork bombs. While writing up this article, I fork bombed my machine a few times by mistake.</p> <p>The post <a href="https://devblogs.microsoft.com/oldnewthing/20260814-00/?p=112613">Forcing an ARM64X executable to run as a specific architecture</a> appeared first on <a href="https://devblogs.microsoft.com/oldnewthing">The Old New Thing</a>.</p>
Original Article
View Cached Full Text

Cached at: 08/15/26, 03:22 AM

# Forcing an ARM64X executable to run as a specific architecture - The Old New Thing Source: [https://devblogs.microsoft.com/oldnewthing/20260814-00?p=112613](https://devblogs.microsoft.com/oldnewthing/20260814-00?p=112613) ARM64X is a[fat binary](https://en.wikipedia.org/wiki/Fat_binary)Windows executable and DLL format for 64\-bit ARM systems\. For DLLs, the choice is clear, since only one of them will work: The version of the DLL that is loaded is the one that matches the host process\. If the host process uses the Windows ARM64 ABI, then the ARM64 version of the DLL is used, and if the host process is x86\-64\-based or uses the Windows ARM64EC ABI¹ For executables, the system has a choice\. It could run the process as ARM64 or it could run it as ARM64EC\. How can you force the system to choose the architecture you prefer? You may want to do this if you have a program that is compiled as ARM64X because you have a plug\-in model, and you want to be able to support plug\-ins that are written either as ARM64 or x86\-64\. You compile an ARM64 version for ARM64 plug\-ins, and you compile an ARM64EC version for x86\-64 plug\-ins\. At run time, you realize that the user passed a plug\-in for the other architecture, so you want to relaunch yourself as the matching architecture\. You can do it with the`PROC\_THREAD\_ATTRIBUTE\_MACHINE\_TYPE`attribute\. Here’s a program that takes a DLL on the command line\. It tries to load it as the native architecture, but if that fails, and the native architecture is ARM64, then it relaunches itself as x86\-64 to try again\. ``` #include <windows.h> #include <stdio.h> #include <wil/result_macros.h> #include <wil/resource.h> #include <wil/stl.h> #include <wil/win32_helpers.h> int wmain(int argc, wchar_t** argv) { if (argc < 2) { printf("Oops\n"); return 0; } wil::unique_hmodule dll{ LoadLibraryExW(path, 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 single = make_proc_thread_attribute_list({ {PROC_THREAD_ATTRIBUTE_MACHINE_TYPE, &arch} }); 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) }; info.lpAttributeList = single.get(); if (!CreateProcessW(self.data(), GetCommandLineW(), nullptr, nullptr, false, EXTENDED_STARTUPINFO_PRESENT, nullptr, nullptr, &info.StartupInfo, &pi)) { printf("Can't relaunch as x86-64, sorry\n"); return 0; } WaitForSingleObject(pi.hProcess, INFINITE); // destructors will close the handles } ``` If we can load the DLL, then great\! We run it as usual\. If we can’t load the DLL because it’s in the wrong format, then we will retry as x86\-64 if the current process is running as ARM64\. To do that, we create an attribute list with the`PROC\_THREAD\_ATTRIBUTE\_MACHINE\_TYPE`attribute whose value is the architecture we want to try, namely AMD64 \(which is the Windows name for x86\-64\), and relaunch ourselves with the same command line\.² If the DLL fails to load even as x86\-64, then the x86\-64 version of our program just gives up without trying again as ARM64\. \(You don’t want to have the x86\-64 version try again as ARM64 because that would create an infinite loop\.\) ¹ You can think of ARM64EC as “pre\-jitted x86\-64 on ARM64\.” It is like taking an x86\-64 binary and compiling it to ARM64 code that is equivalent to \(but presumably has better performance than\) the version the emulator would have created on the fly from your x86\-64 version\. Instead of shipping an x86\-64 version that the emulator has to translate to ARM64, just ship the translated version\. ² In real life, you probably would add some safety precautions to prevent accidental fork bombs\. While writing up this article, I fork bombed my machine a few times by mistake\. ### Category ## Author ![Raymond Chen](https://devblogs.microsoft.com/oldnewthing/wp-content/uploads/sites/38/2019/02/RaymondChen_5in-150x150.jpg) Raymond has been involved in the evolution of Windows for more than 30 years\. In 2003, he began a Web site known as The Old New Thing which has grown in popularity far beyond his wildest imagination, a development which still gives him the heebie\-jeebies\. The Web site spawned a book, coincidentally also titled The Old New Thing \(Addison Wesley 2007\)\. He occasionally appears on the Windows Dev Docs Twitter account to tell stories which convey no useful information\.

Similar Articles

On Binary Translation and Its Consequences

Hacker News Top

The article examines the performance impact of Windows 11's Prism binary translator, which converts x86-64 to aarch64 instructions, using Geekbench 7 tests on Snapdragon X2 Elite hardware and discusses implications for Arm's PC market competitiveness.

Taming the Steam arm64 client (on pmOS)

Lobsters Hottest

A blog post detailing the quirks and challenges of running the unofficial Steam arm64 client on postmarketOS, including the client's obliviousness to being arm64, missing Proton/runtime downloads, and references to FEX and graphics-provider manifests for Valve's upcoming Steam Frame.

Writing Portable ARM64 Assembly

Hacker News Top

A guide to writing ARM64 assembly code that is portable across Apple's Darwin and Linux/BSD systems, covering differences in ABI, symbol naming, and vector mnemonics.

The Scourge of x86 Emulation

Hacker News Top

This article discusses the challenges of emulating x86-TSO memory model on ARM's weak ordering memory model, covering issues like atomic instructions, split-lock, and uncached memory, and explores potential solutions.