Getting MS-DOS games to run on Windows 95: Not enough XMS handles

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, because HIMEM.SYS, 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 (HIMEM.SYS), 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 allocation succeeded, 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 (0000:0000), 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 set /NUMHANDLES=64, 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.

Comments (18)
  1. pc says:

    It’s really something that at the time, the number of available games was low enough that to a first approximation, you could just test them all out and debug them all.

    How much game (or other application) testing is done for new versions of Windows nowadays? Presumably major current titles get tested to some degree, but I imagine it can’t be anywhere near the percentage of titles that got tested for Windows 95.

    1. Simon says:

      It’s not like the old days where patching the actual games was almost impossible, so I suspect the work is mostly left to the game publishers these days… they’re the ones with the buggy code, and if they *can* fix it, then Microsoft shouldn’t be cramming workarounds for buggy apps into Windows…

      1. MarcK4096 says:

        These days aren’t much different than the old days. Many games tend to be abandoned fairly quickly after the peak sales period ends.

    2. Antonio Rodríguez says:

      At the time, DOS/Windows was in the process of surpassing the Apple II as the platform with more software available. I’m not sure, but I recall a number in the low hundred of thousands of titles. In any case, it was definitely not possible to test them all.

      On the other hand, Raymond has told this story of a program manager from the Windows 95 team who went to a computer store and bought one copy of each title they had. The idea was to make sure the most popular software titles for DOS and Windows 3.x ran without problem in Windows 95, debug the problems and try to make them work, and only discard them if the fix wasn’t possible or feasible. So I guess they faced this impossible task in a very pragmatic way.

    3. smf says:

      >It’s really something that at the time, the number of available games was low enough that to a first approximation,
      > you could just test them all out and debug them all.

      Windows 95 betas were available to MSDN and TechNet subscribers back when it was called Chicago. Microsoft were not the only people testing it. I didn’t personally run any of the Windows 95 betas as I went from MSDOS to OS/2 Warp but I ran a Windows 98 beta for a short while.

      >How much game (or other application) testing is done for new versions of Windows nowadays?

      Windows 7, 8 and 10 betas have all been made available to the public in one form or other. I’m not saying they’ve outsourced all their testing, but there is no way that they can test more internally than can be tested externally.

  2. Koro says:

    Why couldn’t Windows 95 “suck the brains out” of the MS-DOS XMS driver like it did for the other stuff?

    1. Boris says:

      No clue, but ψ debugging tells me that 31 handles were more than enough for anyone, so it was deemed to be more trouble than it’s worth, especially given that other applications would likely handle the 0 correctly.

    2. henke37 says:

      My psychic debugging skills tells me that at least one other XMS driver exists that Window didn’t learn about.

    3. Joshua says:

      What surprises me is that Windows 95 didn’t just ship with an XMS driver and demand that only its own be used to start. Oh well.

      1. Paul says:

        Demanding a game uses the Windows 95 XMS driver rather than the one it was actually built to use isn’t going to be a good way of getting compatibility with existing software. Can you really see the company behind this game bothering to update and release a new version just to get Win95 compatibility when as far as they were concerned a boot-disk was a viable solution.

        1. smf says:

          He wasn’t demanding the game used the HIMEM.SYS bundled with Windows 95, rather that Windows 95 should have demanded that it run with the HIMEM.SYS that was bundled and then it could have worked round the issue of avoiding already allocated handles in a different way. So the user could run the game under Windows 95 if they chose to.

          It may have been because other memory managers were popular at the time and Microsoft were really struggling with anti-trust lawsuits. Or it might have been because the designers came up with the solution and thought it was good enough and didn’t expect any problems. Personally I’d have hoped for more awesomeness, but there are always constraints.

  3. Nawak says:

    “But how do you make sure that you don’t generate a number that is different from”
    I think that should read:
    “But how do you make sure that you don’t generate a number that is equal to”
    or “But how do you make sure that you generate a number that is different from”

  4. Yukkuri says:

    Was the boot disk the game publisher’s recommended fix? :D

    1. Antonio Rodríguez says:

      Yes, at that time, most game publishers recommended using a boot disk to run their games if you had memory problems. Many people had lots of drivers and TSRs installed in DOS, and didn’t use MemMaker or QEMM to move them above the 640 KB barrier, so many times they ended with a lot less than 600 KB of free RAM, sometimes near 500 KB. And many games didn’t run with less than 590 KB of free conventional memory. MemMaker and QEMM were relatively difficult to install or tune up (specially when you had an incompatible driver or TSR), but creating a boot disk with the SYS command was easy and fool-proof.

  5. kme says:

    It seems like this means that the game would have failed under MSDOS if any driver had allocated an XMS handle before the game was started, too.

  6. Stephen E. says:

    I bet the actual number of handles to set was 40.

    I’ve never even heard of that game. No wonder, with that kind of programming talent.

    I looked at the I-Q list and found that Lemmings wouldn’t run under Windows 95. I remember that I got it to work in OS/2. Har har.

    1. Scarlet Manuka says:

      I assume 40 is “the 32 it needs, plus the one that is already assigned, plus a safety margin in case some others were allocated before Windows started”. But a higher number would also work, of course.

    2. Ivan K says:

      Thanks for that nostalgic link to the games list. I’ve never seen it before! I like how one of my favourite games of all time (R-Z list) doesn’t even bother with an explanation of why it doesn’t run in Windows 95. It just says “The requires MS-DOS mode.”. Damn that guardian.

Comments are closed.

Skip to main content