January 21, 2023
The most common question about
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
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)nt"
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.)
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
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,
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
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.
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.