Emulating win32

January 21, 2023

This post is part of a series on retrowin32.

The most common question about retrowin32, my Windows emulator — after "why even do this?" — is about how it actually works. This is the kind of thing that now feels obvious to me but which was a great mystery before I understood it, so here I hope to present it in a way that will make it obvious to you too.

Emulating the Windows API

To start with, imagine you're on an x86 machine that is running some OS other than Windows and you want to run a Windows program somehow. The main observation of Wine (which, as the acronym expands, is not an emulator) is that a Windows executable ultimately contains a bunch of x86 instructions and your x86 is already capable of executing them directly. To execute a Windows .exe then you just load it into memory (which requires unpacking the .exe file format) and tell the processor to jump to the first instruction.

The only remaining piece — a massive one — is how this exe interacts with the operating system, to e.g. open files or put something on the screen. The mechanism here differs a lot between operating systems but ultimately depends on the kernel's interface. In the specific case of Windows the kernel interface is pretty fiddly (the syscall ids vary between Windows releases) and the stable API boundary is generally understood to be in DLLs with names you might recognize like kernel32.dll. (This is in direct contrast to Linux, which famously cares a lot about having a very stable interface at the kernel boundary instead.)

How this works is the .exe file format can declare "hey, I will need to call kernel32.dll's function named WriteFile()", and when the .exe is loaded the OS will put the appropriate code at the appropriate place such that this function call works. On Windows the kernel32 function then calls through to the appropriate kernel interface. For our goal of running on a non-Windows OS this is convenient because all we need to do is provide our own implementations of those functions without even thinking about the kernel interface.

And that is just what Wine does: it loads .exe files and it provides implementations of all the Windows DLLs. It is of course significantly more complicated than that in practice, and Wine has seen programmer-centuries of effort to provide all the nooks and crannies of the Windows interface, which itself has seen decades of Hyrum's law.

For one random example of just how deep that rabbit hole can go, check out this snippet found in a blob of x86 assembly in Wine's syscall dispatcher:

/* Legends of Runeterra hooks the first system call return instruction, and
 * depends on us returning to it. Adjust the return address accordingly. */
"subq $0xb,0x70(%rcx)\n\t"

The great how Wine works goes much depeer in detail on Wine. The above intentionally elides a bunch of details.

(By the way, one neat application of Wine — entirely unrelated to this post's goal of running a random exe — is that if you have the source code of a Windows program you can compile it against Wine's implementation of the Windows API and get a native executable out the other side.)

Emulating x86

The above is all well and good if you are on x86 hardware, but what if you're on some other archicture, like these fancy new ARM-based Macs? You must then emulate the x86 instruction set, in just the same way a Game Boy emulator might emulate the Game Boy's CPU.

That is no small feat! Apple reportedly even added special x86 support to their ARM processors to make emulation faster. But once you have that in place, there are two broad approaches you can take for the Windows part of it.

One is to additionally emulate all the hardware found on an x86 machine, such as the BIOS and disk interfaces, such that you can install the actual Windows OS into your emulator. This is the approach that qemu takes. On the web, v86 can run many different OSes, including Windows.

This approach is great because it runs a real actual copy of Windows and will consequently run Windows programs correctly. The main downside of this approach is that it requires you to install a real actual copy of Windows, which also requires a bunch of disk space and time to boot that Windows before it can do anything.

The second approach is to instead emulate just the x86 instruction set, and use Wine as described above as an implementation of the massive Windows API backing onto something more tractable to then emulate, such as the Linux kernel API. After publishing retrowin32 I learned about BoxedWine which does this on the web and which can execute many sophisticated Windows programs.

retrowin32's take

My retrowin32 project is mostly about exploring whatever I find interesting. What that currently means is that I have a not very good x86 emulator, a not very good win32 implementation, and some minor explorations of related tasks.

In all, I think if you're looking to actually run a Windows program on the web then BoxedWine and v86 likely cover it. But if you're curious about my side quests I plan to write a subsequent post about retrowin32 status in particular.