Some years ago, when I was just getting into Windows kernel development,
I stumbled upon an unusual function:
DbgPrompt. Somewhere, some
driver developer needed the ability to read input from the currently attached
kernel debugger, and so this function was added to the kernel's public API.
For me, it sparked an idea: if a driver can read text from- and write text to-, the
attached debugger, why not use it to play a little game?
Specifically, let's play Zork:
The idea is simple: if the system crashes (BSoDs) while the driver is loaded, and a kernel debugger happens to be attached, then before the system reboots the driver will ask whether the human on the other side wants to play some interactive fiction.
For the impatient, the code is available here. Below, I present some of the challenges in implementing this kind of thing.
A little history
For those unfamiliar, Zork is an interactive fiction game. Put simply - the game presents a textual description of the environment, and the player types commands such as "get lamp" or "hit troll with sword". This is a gross oversimplification of the genre, but for our purposes it will do.
In 1980, when Zork came out, there were a lot of different home computer systems on the market. Manually porting the game to every single system would have taken a huge amount of time, so instead Infocom opted to develop a virtual machine - the Z-Machine. This way, they would only have to implement a custom interpreter for each system, not port the whole game codebase.
Nowadays, if one want to play any of the old Infocom games (or indeed any of the thousands of new games built upon the Z-Machine long after Infocom closed down), there are several modern interpreters available. One of the most popular is Frotz. Since it's open source, and since I did not know of any other interpreters at the time, that's what I based my implementation on.
Checking for bugs
So the idea is to run Zork when the system crashes, or "bug checks",
to use the technical term. The Windows kernel allows drivers to register a callback,
to be notified when a bug check occurs
KeRegisterBugCheckCallback). The intended use case for
this facility is for drivers to save additional information into the crash dump,
to assist debugging. In our case, the game will be run inside the callback.
Running inside a bug check callback presents its own challenges, however. For one,
the IRQL is set to
HIGH_LEVEL, making it impossible to use pretty much any kernel API.
More on that below.
It is pitch black. You are likely to encounter linker errors.
Lucky for me, the Frotz codebase already contained an implementation of a "dumb" interface - no graphics support, no sound, minimal support for terminal features such as colors. Taking that as a starting point, I set off to porting the code for the Windows kernel.
Frotz is already written to be portable (it even supports DOS!), so most of it
compiled without errors. The main problem was its reliance on userland APIs that
are not available in the kernel. The most troublesome:
fopen et al.,
How do we solve this problem? Easy - we
#define all the troublemakers to point
to our own functions. Slight problem: although the kernel does have a memory allocator
ExAllocatePoolWithTag), it cannot be called at
Same with the file handling routines.
To work around the memory allocation problem, I decided to use a third-party allocator. Specifically, this one by Eli Bendersky. It allocates memory from a static buffer, and is simple enough to be included in kernel code.
What about file handling? Although I would've liked to present the user with a choice
of which game to load, it is just not possible to access files at
writing the crash dump is technically accessing a file, but that's a really
special case.) Instead, I hard-coded the game file (the Z-Machine "image") as a static
exit. When Frotz encounters a fatal error, it calls the function
which should terminate the process. (Did I mention that all platform-specific
functionality is wrapped into
os_ functions, so all a new Frotz port has to
implement is these functions?) The Dumb implementation simply calls
In our case, there is no process to terminate, but we still need to somehow return from
our bug check callback and let the system shut down. My solution?
ExRaiseStatus(STATUS_UNSUCCESSFUL). This raises a SEH exception, which is caught
by the bug check callback, which then discards it and returns cleanly. There are
no resources to clean up, and even if there were - the system is wrecked anyway :)
Final hurdle - how to handle screen output? Although we can output strings to the debugger, most games require somewhat fancier features, such as printing a character at a specific point on the screen. The workaround here is to maintain an internal screen buffer. Next question: when do we output the contents of the buffer? Doing so every time the buffer is modified is out of the question, since we're using a serial port for debugging, and the bitrate is not that high. Instead, we can output the contents whenever user input is requested. Since classic IF games are very serial in their flow (print text, ask for input, repeat), this works out nicely.
Pretty much. There realy isn't that much code involved in porting Frotz to the kernel (apart from the memory allocator). True, this version won't play the fancier Z-Machine games, but that was never the intention.
Next time (whenever that may be): I port this to the Windows 98 kernel. Accepting bets on how many goats will be sacrificed in the process. Donations of goats also welcome.