A report arrived on an MS-DOS game that crashed after you go through some preliminary game steps and then attempt a planetary landing. When you do this, the program blows up and starts corrupting memory.
Debugging the game revealed that
performing a planetary landing causes the
application to allocate some more XMS memory,
and this happens to be its 32nd request for XMS memory.
When run from a custom-made MS-DOS boot disk,
this 32nd request succeeds,
the default MS-DOS XMS driver,
gives you 32 handles.
But when run under Windows 95,
the 32nd call to allocate XMS memory fails.
Windows 95 could have been extra awesome, like it was with EMM memory, and offer a large number of handles. But it was also constrained by the MS-DOS XMS driver that it tries to take over from. If any MS-DOS drivers had allocated XMS memory from the MS-DOS XMS driver, then Windows 95 needs to respect those allocations when those drivers try to use that memory after the transition to 32-bit mode.
Those drivers don't even realize that the operating
system changed out from under them.
They allocated an XMS handle from one driver
then passed it to another driver
(Windows 95's MS-DOS emulator),
and the handle still worked.
The way this worked is that when a request to access XMS memory occurs, the Windows 95 XMS driver first checks if it is a handle that it knows about. If so, then it services the request. if not, then the Windows 95 XMS driver passes the XMS handle through to the MS-DOS driver to see if that driver recognizes it.
In order for this trick to work, Windows 95's XMS driver must not generate an XMS handle that happens to match a handle that was produced by the MS-DOS XMS driver. But how do you make sure that you don't generate a number that is different from an unknown set of numbers?
Here's the trick: One of the things that Windows 95 does when it starts up is allocate zero bytes of XMS memory from the MS-DOS XMS driver until it reports "no more handles". All of those handles that came out are now known to be different from any handles that the MS-DOS XMS driver handed to any 16-bit device driver. When an MS-DOS application asks for some XMS memory, Windows 95 hands out one of those "known not to be a global handle" handles.
What this means is that Windows 95 won't be able to give out any more XMS handles than the MS-DOS XMS driver did. On the other hand, each virtual machine gets its own set of handles, so it's not like there are 32 handles total. Rather, the calculation goes like this: If MS-DOS device drivers had allocated N XMS handles prior to the transition to 32-bit mode, then those handles are global. The remaining 32 − N handles are local.
Okay, back to our story.
The deal is that for some reason I have long forgotten, the value of N was 1, which meant that each MS-DOS virtual machine could allocate 31 more handles before it ran out.
Okay, so the game tries to allocate its 32nd handle, and the call fails. How does this lead to the program blowing up and corrupting memory?
The game had a helper library that provided a C interface to the ugly assembly-language-based XMS interface. The function to allocate a block of XMS memory returned the memory block handle on success, or zero on failure.
The program evidently never checked whether the call actually succeeded. It just assumed that the call succeeded, and used zero as the handle.
The excitement comes into play when the program tries to use that XMS memory handle. XMS has a single function for transferring memory, and the source and destination could be an XMS handle or conventional memory. (Technically, you could set both the source and destination to be conventional memory, but there'd be no need for that in practice, because you already can transfer that memory yourself without needing the help of the XMS driver. In practice, therefore, at least the source or the destination, and possibly both, will be an XMS handle.)
And the way you specified that the source or destination is conventional memory rather than an XMS handle is to set the corresponding handle field to zero.
Since the program never checked whether its XMS
its attempt to copy memory into the XMS memory
block that it just "allocated"
actually copied memory into conventional memory.
And since it's going to start by copying into
the start of the XMS memory block (offset zero),
what it actually ended up doing was copying
into the start of conventional memory
which is the hardware interrupt table.
Scrambling the hardware interrupt table is a great way to blow up your system.
I marked the game as requiring MS-DOS mode
with a custom configuration that
ensuring that the game got all the handles
it needed and then some.
I made a note of the problem,
and the plan was that if more games appeared
with the same problem,
then maybe we would take a closer look at what
we could do to get the program running
under Windows 95 instead of requiring MS-DOS mode.
It turns out that no other games were found that had this problem.