I have run into what looks like an emulation bug in the Windows 11 ARM64 guest: the Win32 API IsWow64Process2 cannot distinguish an emulated x64 process from a native ARM64 one. It reports every process as "running natively." This breaks any tool that uses this API to detect a target process's architecture.
Environment
On physical Windows-on-ARM hardware, calling IsWow64Process2(hProcess, &processMachine, &nativeMachine) on an emulated x64 process returns:
What actually happens in the Parallels guest
For a confirmed x64 process, the call returns:
Minimal repro (C#)
Compile as a console app and pass a PID. Run it against a known x64 process (any x64 app running under emulation) and against a native ARM64 process and compare.
using System;
using System.Runtime.InteropServices;
class Program
{
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool IsWow64Process2(IntPtr hProcess, out ushort processMachine, out ushort nativeMachine);
[DllImport("kernel32.dll", SetLastError = true)]
static extern IntPtr OpenProcess(uint access, bool inherit, int pid);
[DllImport("kernel32.dll")]
static extern bool CloseHandle(IntPtr h);
static string Name(ushort m) => m switch
{
0x0 => "UNKNOWN(0)",
0x14c => "I386/x86",
0x8664 => "AMD64/x64",
0xAA64 => "ARM64",
_ => "0x" + m.ToString("X")
};
static void Main(string[] args)
{
int pid = int.Parse(args[0]);
IntPtr h = OpenProcess(0x1000 /* QueryLimitedInformation */, false, pid);
if (h == IntPtr.Zero) { Console.WriteLine("OpenProcess failed: " + Marshal.GetLastWin32Error()); return; }
bool ok = IsWow64Process2(h, out ushort pm, out ushort nm);
CloseHandle(h);
Console.WriteLine($"ok={ok} processMachine={Name(pm)} nativeMachine={Name(nm)}");
}
}
Observed output
Any software that relies on IsWow64Process2 to determine another process's architecture misbehaves. In my case a WPF debugging tool picks an injector/DLL by target architecture; because every process looks like native ARM64, it loads an ARM64 DLL into an x64 process, and Windows correctly refuses to load it. Debuggers, profilers, and DLL-injection based tools would all be affected. The same API on real Windows-on-ARM hardware works correctly, so this appears specific to the Parallels x64 emulation layer rather than to Windows itself.
Questions
Environment
- Host: MacBook Air M5 with Parallels App Store Edition 1.26.3 (23929)
- Parallels Tools: 1.26.3 (23929), VM type "Parallels ARM Virtual Machine"
- Guest: Windows 11 Pro 25H2, ARM64, build 26200.8737
On physical Windows-on-ARM hardware, calling IsWow64Process2(hProcess, &processMachine, &nativeMachine) on an emulated x64 process returns:
- processMachine = IMAGE_FILE_MACHINE_AMD64 (0x8664)
- nativeMachine = IMAGE_FILE_MACHINE_ARM64 (0xAA64)
What actually happens in the Parallels guest
For a confirmed x64 process, the call returns:
- processMachine = IMAGE_FILE_MACHINE_UNKNOWN (0)
- nativeMachine = IMAGE_FILE_MACHINE_ARM64 (0xAA64)
Minimal repro (C#)
Compile as a console app and pass a PID. Run it against a known x64 process (any x64 app running under emulation) and against a native ARM64 process and compare.
using System;
using System.Runtime.InteropServices;
class Program
{
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool IsWow64Process2(IntPtr hProcess, out ushort processMachine, out ushort nativeMachine);
[DllImport("kernel32.dll", SetLastError = true)]
static extern IntPtr OpenProcess(uint access, bool inherit, int pid);
[DllImport("kernel32.dll")]
static extern bool CloseHandle(IntPtr h);
static string Name(ushort m) => m switch
{
0x0 => "UNKNOWN(0)",
0x14c => "I386/x86",
0x8664 => "AMD64/x64",
0xAA64 => "ARM64",
_ => "0x" + m.ToString("X")
};
static void Main(string[] args)
{
int pid = int.Parse(args[0]);
IntPtr h = OpenProcess(0x1000 /* QueryLimitedInformation */, false, pid);
if (h == IntPtr.Zero) { Console.WriteLine("OpenProcess failed: " + Marshal.GetLastWin32Error()); return; }
bool ok = IsWow64Process2(h, out ushort pm, out ushort nm);
CloseHandle(h);
Console.WriteLine($"ok={ok} processMachine={Name(pm)} nativeMachine={Name(nm)}");
}
}
Observed output
- Against a native ARM64 process: processMachine=UNKNOWN(0) nativeMachine=ARM64 (correct)
- Against an emulated x64 process: processMachine=UNKNOWN(0) nativeMachine=ARM64 (wrong; should be processMachine=AMD64/x64)
Any software that relies on IsWow64Process2 to determine another process's architecture misbehaves. In my case a WPF debugging tool picks an injector/DLL by target architecture; because every process looks like native ARM64, it loads an ARM64 DLL into an x64 process, and Windows correctly refuses to load it. Debuggers, profilers, and DLL-injection based tools would all be affected. The same API on real Windows-on-ARM hardware works correctly, so this appears specific to the Parallels x64 emulation layer rather than to Windows itself.
Questions
- Is this a known limitation of the current x64 emulation, and is a fix planned?
- Is there a supported way to correctly detect an emulated x64 (or x86) guest process's architecture from another process?