Why Windows XP Binaries Still Run On Windows 11
Try the interactive lab for this articleTake the quiz (6 questions · ~5 min)Pull an old CD out of a drawer in Athens, the kind with a holographic Windows XP sticker on the disc, insert it into the laptop on your desk, wait for autorun to fail, navigate to setup.exe in Explorer, and double-click it. On a Windows 11 machine running on a 13th-generation Intel Core or a Ryzen 7000 CPU, a compressed 2003-era installer will unpack itself, ask for administrator permission, install a program into C:\Program Files, and run. The program it drops will almost certainly work. Many of them work better than they did on the hardware they were written for, because the machine underneath is thirty times faster.
That experience is not an accident. It is the visible surface of one of the longest running engineering projects in consumer software: the Microsoft compatibility effort, which has been maintaining a stable contract between Windows user-mode binaries and the operating system beneath them for almost three decades. Every version of Windows since NT 3.1 has added features, removed defects, and rewritten internals, and every version has been tested against tens of thousands of old applications to make sure those applications still run. Nothing else in mainstream computing preserves binary compatibility at this scale, for this long. Linux upgrades routinely break userspace when distributions move between glibc versions. macOS dropped 32-bit support in 10.15 and broke a generation of apps overnight. Android apps rely on a virtual machine because the underlying Linux is allowed to change. Windows kept the promise that if you compiled a normal Win32 executable in 2001, it would still execute in 2026.
This article is a walk through what makes that promise technically possible. We will go from the CPU silicon up: the x86_64 instruction set and its compatibility story, the PE executable format, the NT kernel's personality subsystems, the WOW64 thunk layer, the stable Win32 DLLs, the shim engine, and the application compatibility database. By the end it should be clear why a 2003 binary still works, what kinds of 2003 binaries do not work, and what the price tag for this compatibility is.
The Binary That Reaches The CPU
Before we can trace an old Windows application through the modern OS, we have to agree on what that application actually is. A typical XP-era installer is a single executable file with a .exe extension. Open it in a hex viewer and the first two bytes are MZ, the initials of Mark Zbikowski, the Microsoft engineer who designed the DOS executable format in 1983. Those bytes are the start of a DOS header, which is still present in every 64-bit Windows executable in 2026, because the loader walks the DOS header to find a pointer to the real header further down the file.
After the DOS stub comes the PE signature (PE\0\0), the COFF file header (machine type, section count, timestamp, characteristics), and the optional header (which is not optional at all and holds most of the interesting metadata). The optional header contains the entry point address, the image base, the address of the imports table, the address of the exports table, the address of the relocation table, the address of the exception table, the subsystem type (Windows GUI, Windows console, native, POSIX), and the required OS version. The last field is important: it says "this binary expects Windows 5.1 or newer", which is XP. Windows 11 reads that field, checks that the request is satisfiable, and carries on.
Below the headers are the sections: typically .text for machine code, .data for writable globals, .rdata for read-only data, .rsrc for resources like icons and version info, and .reloc for relocation fixups. Each section has a virtual address, a virtual size, a raw file offset, and a raw file size. The loader maps the file into memory following these tables, copies each section into the right virtual address, applies relocations if the image could not be loaded at its preferred base, walks the imports table to resolve each DLL!function reference against the matching DLL that the OS provides, fills in the Import Address Table, and jumps to the entry point.
Everything in that paragraph is the same today as it was in 2001. Microsoft has added flags, extended fields, added new section types, introduced new subsystems, but the loader for a 2003-era 32-bit PE image follows the same procedure the loader in Windows XP followed. The format itself is part of the compatibility contract: change the layout and every old binary stops working. So nothing fundamental has changed.
x86_64: The Silicon Level Contract
The CPU in a modern Windows 11 machine is an x86_64 processor. The instruction set was defined by AMD in 2000, released in the Opteron in 2003, and adopted by Intel as Intel 64 in 2004. The design choice that matters for this article was absolute: the x86_64 ISA is a strict superset of x86, with a mode bit that switches the processor between 64-bit long mode and legacy 32-bit compatibility mode. Every 32-bit instruction the Pentium Pro understood in 1995 is still decoded and executed by every consumer x86_64 chip you can buy in 2026.
There are two submodes of long mode on a 64-bit chip. 64-bit mode runs 64-bit code with 64-bit registers, 64-bit pointers, and the extended register set (r8 through r15, the rex prefix, the wider xmm file). Compatibility mode inside long mode runs 32-bit code on a 64-bit kernel: the CPU uses 32-bit addressing, 32-bit register widths, and the legacy encoding, but it still holds the 64-bit page tables that the kernel set up. The CPU flips between these submodes on every transition between 32-bit user mode and the 64-bit kernel, using the cs segment descriptor to decide which encoding applies.
This design choice is the silicon-level reason an old binary can still run on a new chip. If AMD had decided to drop 32-bit encoding in long mode, the story would have ended in 2003. Instead, the architecture lets 32-bit code execute with essentially the same semantics it had on a 1999 Pentium III, but underneath it there is a 64-bit kernel with a 64-bit address space, 64-bit pointers, and access to all the hardware features the old code never knew about. The compatibility mode is so transparent that most old applications do not notice anything has changed.
Intel tried to abandon x86 backward compatibility at least twice (the Itanium in 2001 and the x86S proposal in 2023), and neither attempt moved the needle in consumer markets. The market valued the old binaries too much. The installed base of software, not the hardware, was the real constraint.
The NT Architecture And The Win32 Personality
Windows is not strictly a Win32 operating system. The kernel underneath is called NT, and NT was designed to host multiple "subsystems", each of which presents its own personality to the programs that run on top. In the original NT design there were three: OS/2 (removed in Windows 2000), POSIX (later replaced by Services for Unix, later by the Windows Subsystem for Linux, later superseded by WSL2), and Win32. Each subsystem was a user-mode server process that translated its API into calls on the NT kernel.
Over time the Win32 subsystem absorbed everything. The user-mode Win32 server process (csrss.exe) still exists and still handles console windows, some security operations, and process creation bookkeeping, but most of the Win32 API is implemented by a stack of DLLs inside each Win32 process: kernel32.dll, advapi32.dll, user32.dll, gdi32.dll, and their friends, which in turn call ntdll.dll, which in turn issues real NT system calls. The NT kernel itself knows almost nothing about Win32 as such; it knows about files, sections, processes, threads, synchronisation objects, and a few thousand generic operations, and Win32 is a set of conventions on top of those primitives.
This architectural separation is the reason Win32 is a stable ABI. The stable surface is not the kernel syscall table; it is the exported set of functions from kernel32.dll, user32.dll, and the rest. As long as CreateFileA in kernel32.dll keeps the same behaviour, an old program that calls it works, even if the underlying implementation has been rewritten ten times and the actual NT syscall has moved three times. Microsoft is free to renumber, add, or remove NT system calls between releases, as long as the user-mode DLLs hide the change from user code. Linux made the opposite architectural choice, where the kernel syscall interface is itself the stable ABI and glibc is free to change, and the two philosophies explain most of the cultural differences between the ecosystems.
WOW64: Running 32-Bit Code On A 64-Bit Kernel
The piece of the Windows stack that specifically handles old 32-bit applications on a 64-bit OS is called WOW64 (Windows on Windows 64-bit). The name comes from the earlier NTVDM (NT Virtual DOS Machine), which did the same thing for 16-bit DOS code on NT. WOW64 is smaller than NTVDM because x86_64 includes 32-bit support natively, so there is no need to emulate the instruction set. What WOW64 has to do is bridge the calling conventions, the pointer widths, and the syscall layer.
When a 64-bit Windows kernel creates a 32-bit process, it maps three special WOW64 DLLs into the process alongside the usual ntdll.dll. The three are wow64.dll, wow64cpu.dll, and wow64win.dll. wow64.dll is the main thunk engine that translates 32-bit calls into 64-bit calls. wow64cpu.dll contains the tiny assembly routines that flip the processor between 32-bit compatibility mode and 64-bit mode, using a retf instruction to a different code segment. wow64win.dll handles the subset of win32k syscalls that go through the kernel-mode graphics driver.
The layout of a 32-bit process on 64-bit Windows is unusual. The process has two copies of ntdll.dll: a 32-bit one at a low address, and a 64-bit one at a high address above 4 GiB. The 32-bit application only knows about the 32-bit ntdll.dll, which is a stub. When it calls into the stub, the stub packs its arguments, transitions the CPU into 64-bit mode, jumps into the 64-bit wow64.dll thunk, which unpacks the 32-bit arguments, widens pointers and handles, and issues the real 64-bit syscall against the kernel. The result travels back the same way, compressed back down to 32-bit widths where necessary, and returned to the application.
Thunking is not free. The cost of each syscall for a 32-bit process is a few dozen extra nanoseconds compared to a native 64-bit process, and the cost of handle translation can matter for applications that do enormous numbers of small I/O operations. For almost all XP-era applications, the cost is invisible, because those applications were written for CPUs that were thirty times slower and I/O subsystems that were hundreds of times slower.
The 32-bit address space is limited to 4 GiB, and the OS reserves a chunk of it for its own DLLs, so a 32-bit process running under WOW64 typically has 2 to 4 GiB of usable address space depending on whether the /LARGEADDRESSAWARE flag was set at link time. XP-era games that ran out of 2 GiB on 32-bit XP and crashed on startup will often run fine under WOW64 on Windows 11, because the OS can grant them almost the full 4 GiB, and because WOW64 itself places its internal structures at high addresses that the old 32-bit code cannot see.
The Shape Of The Stable Surface
Microsoft's promise is not that every function in every DLL will behave the same forever. The promise is that a specific set of documented, exported functions from a specific set of DLLs will behave according to their MSDN documentation, for values of "according to their documentation" that sometimes stretch into bug compatibility. When you read a 2003 MSDN entry for CreateWindowEx, the 2026 implementation honours that documentation, including the edge cases and quirks that were never meant to be part of the contract but became part of it once enough software depended on them.
The stable surface is roughly the set of functions visible via dumpbin /exports on kernel32.dll, user32.dll, gdi32.dll, advapi32.dll, ole32.dll, shell32.dll, comdlg32.dll, comctl32.dll, and a handful of others. Between them these DLLs expose somewhere around ten thousand functions. Every one of them is a compatibility contract. Microsoft adds new functions, rarely removes old ones, and when it does remove one it typically does so by marking it deprecated for a decade first, then shipping a stub that returns an error code, then eventually removing the stub long after any reasonable application has stopped calling it.
The same stability rule applies to data structures that cross the API boundary. A WNDCLASSEXA from 2001 has the same layout in 2026 because old code passes pointers to it and reads back fields at hardcoded offsets. If Microsoft wants to add a field, it defines a new structure with a new name (WNDCLASSEXA becomes WNDCLASSEXW for wide characters, or an internal XX2 variant), and keeps the old structure layout stable. The same discipline produced OPENFILENAME, OPENFILENAME_NT4, and OPENFILENAMEA, all of which still exist side by side.
There is a second, quieter promise: unexported "quirk" behaviours. When an old application depends on a specific window-painting order, a specific race between a close message and a destroy message, a specific error code that was documented wrong in the original MSDN page, Microsoft does not change that behaviour even if nobody wrote it down. The compatibility team's stated position is that if shipping software depends on it, the behaviour is part of the contract, regardless of whether it was intended. This is why old Windows DLLs are so full of comments like "do not fix this, it breaks SimCity", and why Raymond Chen's blog "The Old New Thing" has been a steady stream of such stories for over twenty years.
The App Compatibility Database And The Shim Engine
Stable ABIs are not enough on their own. Some applications depend on behaviours the OS actually needs to change for security, correctness, or hardware reasons. To keep those applications working, Windows ships an extensive shim infrastructure that intercepts specific API calls and rewrites them at process load time for specific applications that are known to need it.
At the core of the infrastructure is the Application Compatibility Database, a binary file called sysmain.sdb that lives in C:\Windows\AppPatch\. The file is a set of records, each keyed by a tuple that identifies an application: product name, file name, file version, file size, checksum, company name, and sometimes more. Each record has a list of shims to apply to that application and arguments for each shim.
When a process starts, the Windows loader in ntdll.dll consults the database. If the executable matches a record, the shim engine loads the corresponding shim DLL (AcGenral.dll, AcXtrnal.dll, and others under AppPatch) into the process and rewrites specific entries in the Import Address Table so that calls the application makes to certain Win32 functions land in the shim engine's wrappers instead of the real DLL. The wrapper does whatever the shim needs (often adjusting arguments, suppressing errors, or lying about the Windows version), then forwards the call to the real function.
A concrete example clarifies the pattern. The HeapCreate shim called HeapManager intercepts HeapCreate and filters the flag bits the application passes. Some old applications passed undocumented flag values that corresponded to internal debugging flags, which Microsoft later repurposed, and the old applications would crash on heap allocation after the repurpose. The shim strips the undocumented bits and lets the rest through. Another example: the Win9xCallerOfWnd shim patches GetCaller to return a specific fake call stack so that code which sniffs the caller for "am I being called from shell32?" sees what it expects. Another: the VersionLie shim intercepts GetVersion and GetVersionEx and returns a Windows XP or Windows 7 version record instead of the real Windows 11 version, because some old installers refused to run unless the OS identified as XP.
The shim engine supports several hundred shim types, and the compatibility database holds records for tens of thousands of applications. Every mainstream commercial application from the XP era is in there, along with large numbers of internal enterprise tools, shareware titles, and games that Microsoft's compatibility testers got working long after the original developers stopped answering email. The database is updated through Windows Update, so a new shim can ship years after a release to fix a new incompatibility.
The full weight of the shim system is staggering. In 2020, independent analysis of sysmain.sdb on Windows 10 found roughly 13,000 application entries and over 400 distinct shim types. Every one of those is a piece of code Microsoft is maintaining to keep exactly one old application working. That is the engineering budget that produces the compatibility experience, and it is the kind of budget that is only possible when the maintainer is a trillion-dollar company that sees the installed base as a strategic asset.
DLL Redirection, Side-By-Side Assemblies, And The "DLL Hell" Fix
Before Windows XP, the standard way an application got a specific version of a shared library was to drop its own copy into C:\Windows\System32 (or its own install directory) and hope nothing overwrote it. Multiple applications that shipped different versions of mfc42.dll would fight, the last installer would win, and one of the applications would start behaving strangely. This was the "DLL Hell" problem that defined Windows application support in the 1990s.
Windows XP introduced side-by-side assemblies as a partial fix. An assembly is a versioned, signed bundle of DLLs with a manifest. Applications declare which assembly version they want in their own embedded manifest. The loader reads the application manifest, resolves each assembly reference against the matching versioned directory under C:\Windows\WinSxS\, and maps the right DLL into the process. Two applications that want different versions of Microsoft.VC90.CRT get different DLLs, mapped from two directories, and they coexist without overwriting each other.
The interesting consequence for this article is that WinSxS on a Windows 11 machine still holds a decade-plus of old assembly versions. A 2007 MFC application that depends on Microsoft.VC80.CRT will find that assembly in WinSxS and load it cleanly, because Microsoft has not dropped the old bundles. They still ship the Visual C++ 2005 runtime (the XP-era MFC redistributable) with modern Windows updates, because removing it would break some subset of installed software. The cost is disk space (a typical Windows 11 install has several gigabytes of old assemblies under WinSxS) and the benefit is that the compatibility promise extends into the runtime libraries as well as the OS DLLs.
A second mechanism, .local redirection and application manifests, lets an application tell the loader to prefer a DLL in its own directory over one in System32. This was historically used to isolate applications that shipped private copies of shared libraries, and it is still honoured by the loader today, so a 2003 game that ships its own d3dx9_30.dll next to its executable continues to pick up that specific DirectX helper rather than whatever version of the SDK happens to be installed system-wide.
What Windows 11 Has Actually Broken
The list of things that no longer work on Windows 11 is short relative to the list of things that do, but it is worth naming them because they define the shape of the compatibility promise.
16-bit code is gone. Any .exe that was produced by a 16-bit Windows 3.1 or DOS compiler fails to start on 64-bit Windows, because NTVDM was not ported to the 64-bit kernel. There is no 16-bit compatibility layer in long mode; a 64-bit CPU running a 64-bit kernel cannot enter 16-bit virtual 8086 mode without switching into legacy mode, and the Windows kernel does not do that. The workaround is DOSBox or a VM. 32-bit Windows 10 still had NTVDM as an optional feature for users who needed 16-bit apps; Windows 11 is 64-bit only and has no path.
Unsigned kernel-mode drivers are gone. XP-era applications that shipped kernel drivers (which was more common than you might think: copy protection, CD emulation, some game anti-cheat, some hardware dongles) will fail on Windows 11 unless the driver is signed with a certificate that chains to Microsoft's root and has been attested through the Windows Hardware Dev Center. Most old drivers do not meet this bar, and many old applications will not start without their driver. This is the single biggest source of "XP app does not run on Windows 11" reports, and it is not fixable through the shim engine because the driver loads long before user-mode code runs.
Some obscure CPU instructions old software used are gone, but only by accident. Windows 11 requires CPUs that support a specific baseline feature set (CMPXCHG16B, SSE4.2, POPCNT, LAHF/SAHF, and others), and that requirement effectively removes first-generation x86_64 chips from the support list. Software that runs on Windows 11 inherits the baseline and gains a few new instructions for free, so nothing is actually broken, but the CPU requirement is what shuts out a lot of old physical hardware from running Windows 11 in the first place.
Finally, software that depended on removed features (the old "My Computer" namespace quirks, the Help viewer that parsed .hlp files, the 32-bit ODBC drivers that were replaced) has to be updated or shimmed. The help file viewer is a classic case: Microsoft removed WinHlp32.exe from Windows Vista and later, and the shim engine redirects calls to the old help API to a stub that shows a "this help file is no longer supported" dialog. The application still launches; the help button just does not work. That is the compromise the compatibility team makes when a feature genuinely has to go.
The Registry And The File System Illusion
There are two more compatibility illusions that matter enough to deserve their own mention. Both of them exist to hide architectural changes from applications that would otherwise break.
The first is the file system redirector. On 64-bit Windows, the canonical system directory for 64-bit DLLs is C:\Windows\System32, which is the same path it was on 32-bit Windows, because renaming it would have broken every application that hardcoded the string. The 32-bit system directory is C:\Windows\SysWOW64, which is the reverse of what most people guess: the directory named "System32" holds 64-bit DLLs and the directory named "SysWOW64" holds 32-bit DLLs. This naming was chosen to preserve compatibility with 32-bit applications that open C:\Windows\System32 by name and expect to find 32-bit copies of system DLLs there.
When a 32-bit application under WOW64 references C:\Windows\System32, the WOW64 file system redirector rewrites the path transparently to C:\Windows\SysWOW64 before the call reaches the real file system. A 32-bit program that opens kernel32.dll from System32 ends up opening the 32-bit kernel32.dll from SysWOW64 without knowing it. The same redirection rule applies to C:\Program Files, which redirects to C:\Program Files (x86) for 32-bit processes, and to a handful of other system paths. 64-bit processes see the real paths. Tools that explicitly want to bypass the redirection call Wow64DisableWow64FsRedirection, which disables the redirector for the current thread; 32-bit installers that need to write to the true System32 use it.
The second illusion is the registry redirector. The Windows registry has two parallel views: the native 64-bit view and the WOW64 32-bit view. Keys under HKEY_LOCAL_MACHINE\Software are redirected for 32-bit processes to HKEY_LOCAL_MACHINE\Software\WOW6432Node, which is a parallel tree that holds 32-bit COM registrations, file associations, and application settings. A 32-bit COM server registers itself at HKLM\Software\Classes\CLSID\{guid}\InprocServer32 and expects that a later 32-bit client that reads the same path will find its DLL. The registry redirector makes that work by writing to WOW6432Node when the 32-bit process thinks it is writing to Software. A 64-bit process reading the same logical path sees the native side and does not collide.
Both redirectors are silent, and both are essential. Without them, a 32-bit application that ran perfectly on XP would crash on the first file open or registry query on 64-bit Windows. With them, the application never learns that the file system or the registry have a 64-bit side at all.
Appcompat Telemetry And How Shims Get Written
One question that does not have an obvious answer is how Microsoft knows which applications need shims in the first place. The short answer is that the compatibility telemetry in Windows reports application crashes, hang signatures, and specific API misuse patterns back to Microsoft, along with the file metadata needed to identify the application (name, version, signer, hash). When a new version of Windows is in development, the compatibility team gets a dashboard that shows every application crashing on the new build, sorted by install base. The team works top-down: whichever application has the largest installed base gets fixed first, either by rolling back the offending change or by writing a new shim.
This dashboard is also fed by the Windows Insider programme. Before a new release ships, insiders run unreleased builds on their regular machines, and the appcompat telemetry reports which of their installed applications crash. By the time the build reaches General Availability, the top-offending applications already have shims. This is why new Windows releases feel smooth for users with mainstream software and rough for users with niche or enterprise software that never made it into the insider telemetry.
The shims themselves are not secret. Microsoft's Application Compatibility Toolkit (ACT) used to ship publicly and let enterprise administrators author their own shims against their own sysmain.sdb-style files called "custom compat databases". These custom databases live under C:\Windows\AppPatch\Custom\ and can be registered with sdbinst.exe. Enterprises use them to fix in-house applications that nobody at Microsoft knows about: a billing system written in 2004 by a contractor who has long since vanished, running on the AP department's laptops, needing a version lie and a heap flag scrub to start on Windows 11. The enterprise writes the shim, signs the database, pushes it through group policy, and the application starts working. This is one of the few cases in modern Windows where a system administrator can reach into the binary loader and modify its behaviour without the application vendor's involvement, and it is a key reason large organisations can upgrade their Windows fleets without abandoning their old software.
A Concrete Trace: Running A 2003 Application
To make this concrete, consider what happens when you double-click setup.exe from a 2003 CD on Windows 11. The following trace is abridged but accurate.
Explorer reads the PE header, sees the subsystem is Windows GUI, the machine type is IMAGE_FILE_MACHINE_I386, and the required OS version is 5.1. It asks the kernel to create a process. The kernel allocates a process object, maps the executable into the new address space with the 32-bit ntdll.dll and the WOW64 helper DLLs, and begins execution at the NT-mode entry in the 64-bit ntdll.dll. Early in initialisation, ntdll.dll consults the application compatibility database. It computes a hash of the executable, looks up the hash, and finds a matching record that specifies a version-lie shim and a heap shim. It loads AcGenral.dll, hooks the Import Address Table entries for GetVersion and HeapCreate, and continues.
The 32-bit ntdll.dll stub initialises the process environment, sets up the Thread Environment Block, and transitions the CPU into 32-bit compatibility mode via wow64cpu.dll. It then invokes the 32-bit entry point of the executable, which lives in the .text section of setup.exe. The executable does whatever C runtime startup work its linker produced, eventually calling main or WinMain. The code starts calling Win32 functions. Each call is a normal 32-bit function call into a 32-bit kernel32.dll at a low address. kernel32.dll forwards to the 32-bit ntdll.dll, which packs arguments and transitions the CPU back into 64-bit mode to call the real kernel.
Along the way, the version-lie shim catches the application's GetVersion call and returns 0x0A280105, which decodes as "Windows XP SP2". The application sees XP, accepts the environment, and continues. The heap shim catches an allocation with an undocumented flag and strips it. The application allocates memory successfully. Eventually it calls LoadLibrary("mscoree.dll"), fails because it is a .NET 1.1 installer and .NET 1.1 was never supported past Windows 8, and the shim engine catches that too by redirecting the call to a stub that returns a specific error. The installer has a fallback path for "no .NET on this machine" and prompts the user to install .NET 1.1 first. The user declines, the installer exits with a friendly error, and the process ends cleanly. That exit was engineered. Every layer on the way to it was engineered. The illusion that the application "just runs" is the visible product of the engineering.
For an application that did not depend on mscoree.dll (most XP-era tools did not), the same trace would continue through file copies, registry writes, and shortcut creation, and the program would end up installed in C:\Program Files (x86) (note the (x86) suffix, which exists precisely to hold 32-bit applications separately from 64-bit ones) and runnable from the Start Menu. The resulting installation is indistinguishable, as a file system state, from one that happened on XP itself.
Windows 11 On ARM And The Second Compatibility Layer
Windows 11 runs on ARM64 silicon as well as on x86_64, and ARM64 is a completely different instruction set from x86. Any compatibility story for ARM64 Windows has to answer a harder question: not just "how does a 32-bit application run on a 64-bit kernel" but "how does an x86 or x86_64 application run on a CPU that does not understand its instructions at all". The answer is a full dynamic binary translator, and its existence is a separate piece of engineering from WOW64.
On ARM64 Windows, two translation layers stack on top of the normal loader. WOW64 handles 32-bit on 64-bit as it does on x86_64, but here the 32-bit code is ARM32, not x86. For x86 code there is a layer called xtajit.dll (and its successor xtajit64.dll for x86_64 code), which is a just-in-time translator that reads x86 instructions, compiles them to ARM64 machine code on the fly, caches the translations in a per-application code cache at C:\Windows\XtaCache, and executes them. The first run of an x86 application is slow because the JIT is doing real work; subsequent runs hit the cache and are much closer to native speed.
The translator has to emulate not just the instruction set but the entire x86 memory model and the x86 calling convention. ARM64 has a weaker memory ordering model than x86, so the translator has to insert extra barrier instructions whenever the translated x86 code would have relied on x86's stronger ordering. It has to provide a shim for SIMD instructions (SSE, AVX) using NEON, which has different register widths and different semantics. It has to intercept system calls and forward them to native ARM64 ntdll.dll, which then hits the kernel normally. Each of these bridges is a small performance tax and a large engineering investment.
The x86_64 translator (xtajit64.dll, added in Windows 11 to support 64-bit x86 applications on ARM) is the more recent piece. Microsoft originally shipped ARM64 Windows with only 32-bit x86 emulation, which was enough for most consumer applications but left 64-bit desktop software stranded. The 64-bit translator closed that gap and made ARM64 Windows a serious target for the Windows software base, which is why modern Copilot+ laptops from Lenovo, Dell, and Asus can plausibly run most of the same software as their Intel counterparts.
The architectural point worth noting is that the compatibility story on ARM64 Windows reuses the same stable Win32 ABI on top. Above the translator, every DLL is native ARM64, every Win32 function behaves the way it is documented, and the shim engine runs the same database against the same identifiers. The translator is a hardware-abstraction layer below Win32, not a redesign of Win32. That is why the same XP-era .exe that runs on Intel Windows 11 also runs on ARM Windows 11, just more slowly on the first launch. The API contract is what travels; the instruction set is just a detail of the hardware you happen to be running on today.
Why This Is So Rare
The contrast with other ecosystems is sharp and worth looking at, because it explains why Windows binary compatibility is a strategic anomaly rather than a default engineering outcome.
Linux does not try. The Linux kernel ABI (system calls, the /proc interface, ioctl numbers) is stable within reason: an executable built for an older kernel will usually run on a newer kernel because the syscall numbers and semantics are preserved. But everything above the kernel is allowed to change. glibc, the GNU C library, evolves across versions with ABI breakage, and a binary built against glibc 2.31 may or may not run against glibc 2.39 depending on which symbol versions it imports. Applications that ship statically linked binaries (Go, Rust with musl) avoid this entirely; applications that dynamically link against distribution-provided libraries often do not.
The Linux world responds with containerisation, snap, flatpak, and AppImage. Containers bundle the exact set of shared libraries an application expects, and the kernel's stable syscall interface is the only thing they share with the host. The net effect is close to Windows compatibility, but the mechanism is completely different: Windows makes the OS stable and lets applications be thin; Linux makes the kernel stable and lets the rest of the OS travel with each application.
macOS is at the opposite extreme. Apple reserves the right to break binary compatibility at major OS releases, and does. macOS 10.15 Catalina removed 32-bit support entirely; any application that had not been updated to 64-bit stopped working. macOS 11 Big Sur introduced the Apple Silicon transition and began shipping a translation layer (Rosetta 2) for x86_64 binaries, which was explicitly a temporary bridge. Each release deprecates frameworks, removes symbols, and forces application authors to recompile. The philosophy is that the platform moves forward, the software catches up, and compatibility is a moving target rather than a frozen promise. Apple can do this because the population of macOS applications is much smaller than the population of Windows applications, because Apple controls the hardware and can force upgrades, and because Apple's commercial strategy does not rely on preserving an installed base of corporate line-of-business software.
Android sidesteps the problem entirely by running applications on a virtual machine (ART, formerly Dalvik) that abstracts the underlying CPU and kernel. The apps are compiled to bytecode and recompiled to native machine code at install or first run. The kernel and native libraries below ART are free to change on each device, because apps do not see them directly. iOS does something closer to macOS: applications are native Mach-O binaries, and Apple forces recompilation every few years by bumping the SDK target.
Windows is alone in maintaining a stable, binary, native, user-mode ABI for decades across arbitrary hardware generations. The reason it is alone is partly architectural (Win32 as a personality on top of NT was a good design choice) and partly institutional: no other organisation has sustained an engineering team dedicated solely to compatibility testing at the scale Microsoft does. The closest comparison is probably the IBM mainframe ecosystem, where 1964-era System/360 code still runs on z/OS in 2026, and the same lesson applies: compatibility is a property of institutions, not just technology.
The NT Syscall Numbers Are Not Stable, And That Is The Point
A small but important detail that confuses people who come to Windows from Linux: the NT system call numbers change between Windows releases. The NtCreateFile syscall is number 0x55 on one version, 0x56 on another, 0x59 on a third, and so on, roughly drifting forward as new calls are inserted into the middle of the table. Security software that hooks syscalls by number frequently breaks between releases for exactly this reason, and is expected to handle it. Microsoft does not consider the syscall number table to be a stable interface.
The reason this does not cause application breakage is that applications never call the NT kernel directly. They call into ntdll.dll, which is a user-mode library that knows the current syscall numbers for the current kernel it was built against. When you run an old application, the old application is calling into the new ntdll.dll that ships with the new OS, and the new ntdll.dll knows the new numbers. The old application never sees the change because the stable surface is the exported function in ntdll.dll, not the raw syscall instruction below it.
Compare this to Linux, where the syscall numbers and the syscall ABI (argument passing, register layout, error conventions) are frozen forever, and glibc is a thin wrapper on top. The Linux kernel's commitment to stable syscalls means an old statically linked binary from 2005 can still invoke the kernel correctly on a 2026 kernel, which is a different kind of stability promise, and one that Linus Torvalds has defended fiercely over the years. Windows has the inverse promise: the kernel is free to change, and the DLLs in the middle hold the line.
Both promises are real. Neither is better in the abstract. They are designed for different software distribution models. Linux ships open source that is usually recompilable, so the kernel is the stable boundary and everything else can change. Windows ships closed source binaries that cannot easily be recompiled, so the user-mode DLL surface has to be stable and the kernel can change freely. The two models are consistent with their respective ecosystems, and each one has its own failure modes.
The Cost Of The Promise
Maintaining binary compatibility for two decades is not free. The costs fall into four buckets.
Engineering. A team of compatibility engineers tests thousands of old applications against every insider build of Windows, files bugs when something regresses, and writes shims when the change cannot be rolled back. That team has been a line item in Microsoft's Windows division budget for decades. Its output is invisible until it stops, at which point a wave of angry enterprise customers appear at once.
Security surface area. Every old API that has to behave the way it did in 2001 is an old API that cannot be hardened without risking compatibility. Functions like CreateProcessWithLogonW, or the long list of shell extension entry points, or the win32k graphics syscalls, were designed in an era when the threat model was different. Modern mitigations (code integrity, strict handle validation, mandatory signing of certain DLL classes) have to be layered on carefully so that they do not break the applications the compatibility contract protects. Every security researcher who looks at win32k finds something surprising, because win32k is the largest accretion of legacy behaviour in the entire kernel, and it cannot be rewritten without a compatibility test pass that dwarfs most other software projects.
Binary size and complexity. Windows ships every version of the Visual C++ runtime back to VC++ 2005, several versions of the .NET Framework, dozens of registry helpers, a full 32-bit subset of every system DLL for WOW64, and the entire WinSxS store. A fresh install of Windows 11 is more than 15 GiB on disk before the user does anything, and a large fraction of that size is compatibility material for software that may never run on this particular machine.
Performance opportunity cost. Old APIs constrain what the OS can do under the hood. The GetTickCount API returns a 32-bit millisecond counter that wraps every 49 days, because some applications ignored the documentation and assumed the return value had specific upper bits. Microsoft introduced GetTickCount64 to give new applications a wider counter, but cannot remove the old one. Multiply this by every such API and the tax is real, though hard to measure directly.
Against those costs, the benefit is obvious and enormous. Enterprise Windows installations are full of custom line-of-business applications written against specific API versions, tested once, and running unchanged for fifteen years. The commercial value of "you can upgrade the OS without rewriting the applications" is the single strongest reason corporate fleets stay on Windows. Every architectural decision in the Windows compatibility story, from the MZ header at byte zero of every .exe to the sysmain.sdb shim database, exists because that commercial value was considered more important than the engineering cost. The fact that a Polish accounting program from 2003 can still run on a gaming laptop in Berlin in 2026 is the small-scale consequence of that decision.
The SimCity Story, And Why It Matters
The most famous compatibility anecdote in the Windows world is the SimCity story, told originally by Joel Spolsky and later elaborated by Raymond Chen. The short version is that an early version of SimCity on Windows 3.1 had a use-after-free bug in its heap allocator: it would free a chunk of memory and then read from it again a few lines later, and on Windows 3.1 the memory would still contain the original bytes because the allocator did not reuse the chunk immediately. When Microsoft changed the allocator in Windows 95 to be more aggressive about reuse, SimCity started reading garbage and crashing on the Windows 95 beta.
Microsoft's compatibility team traced the crash, identified the bug, and decided that fixing SimCity was not an option because the shipped binary could not be changed and because Windows 95 could not afford to ship with a regression against one of the most popular games of the era. Instead, they added a shim: when the loader detected that the executable was SimCity, it marked the process to use the old allocator behaviour, and the game ran correctly. The rule was not "SimCity is broken, so we do not support it". The rule was "SimCity worked on Windows 3.1, so it will work on Windows 95, even if that means running a different allocator underneath". The shim is still in the database today, a small piece of engineering artefact honouring a promise made thirty years ago to a program that almost nobody still runs.
The reason the story matters is that it is representative, not exceptional. The shim database is full of entries like it. Each one is a decision that the commercial value of preserving compatibility is higher than the cost of keeping a workaround in the system loader, and each one compounds with all the others into the compatibility surface that a new Windows release inherits from its predecessor. It is the only way an operating system can keep binary compatibility at this scale for this long: not by hoping nothing changes, but by budgeting for the surprises that change causes and honouring them one by one.
What This Means For The Lab And The Quiz
The lab paired with this article walks through the full path of a 32-bit XP-era application starting on Windows 11: PE parse, WOW64 process creation, compat database lookup, shim load, IAT patch, first syscall thunk, and the cross into 64-bit kernel mode. The beginner view shows what the user sees (double-click, splash screen, installed program). The advanced view opens up the DLL layout, the WOW64 thunk, and the shim engine's Import Address Table rewrites.
The companion quiz focuses on three facts that tend to surprise people when they see them for the first time: that Win32 is a personality subsystem above the NT kernel rather than the kernel itself, that WOW64 does not emulate the CPU because the CPU already knows how to run 32-bit code in long mode, and that the shim engine is a deliberate redirection layer rather than a runtime bug fix. Understanding those three is most of what you need to predict whether any given old application will or will not run on Windows 11, and to diagnose it when it does not.
Compatibility at this scale is boring engineering done for a very long time. That is most of what is remarkable about it. Nothing in the Windows compatibility story is clever in the sense of a new algorithm or a surprising protocol. Everything in it is a decision made carefully and held against pressure across decades. The double-click that launches an XP installer in 2026 is a small tribute to that discipline.