Getting MS-DOS games to run on Windows 95: The interrupt flag


In the flags register of the 80386 processor is a flag called the interrupt flag. If the flag is set, then the CPU will respond to hardware interrupts. If the flag is clear, then the CPU will ignore hardware interrupts. It was common for a game that used an MS-DOS extender to disable interrupts temporarily by doing this:

    pushf   ; save flags (including whether interrupts are enabled)
    cli     ; disable interrupts
    ..do something..
    popf    ; restore original flags
            ; (this re-enables interrupts if interrupts were previously enabled)

There were other variations of this pattern, but most of them boiled down to the same basic idea.

One of the quirks of the 80386 architecture is that if the application does not have I/O privilege, the popf instruction does not restore the interrupt flag. Furthermore, the instruction doesn't trap, so the operating system doesn't know that the application tried and failed to change the interrupt flag. It just silently fails. This means that the above code fragment behaves differently depending on whether the application has I/O privilege. If it has I/O privilege, then the popf will restore interrupts to their previous state. But if it doesn't, then the popf instruction has no effect on the interrupt flag, and interrupts remain disabled.

MS-DOS extenders typically granted the client I/O privilege because they were designed to run in a single-tasking environment, so the client was allowed full control of the system. Windows, on the other hand, did not grant the client I/O privilege because it had to worry about multitasking. This means that in Windows, programs that used the above technique would hang because they disabled interrupts and never re-enabled them.

The DPMI specification specifically calls out this coding pattern as problematic and provides special services for managing the interrupt flag so that a client application can manipulate its interrupt flag in a way that the DPMI server understands. In practice, however, client applications were written on the assumption that they were running under the MS-DOS extender that they were packaged with, and they took shortcuts and just used the popf instruction because they knew that it would work. It never occurred to them that their preferred DPMI extender would not actually be the one in charge.

Except, of course, that it didn't work when the DPMI server was Windows.

One of my colleagues came up with a clever solution that addressed many cases of this problem. Since the cli instruction is privileged, it will trap. The trap handler for the cli instruction inspects the code preceding the cli instruction to see if it matches the pattern above or one of the common variations. If so, and the value on the top of the stack matches the virtual machine's current flags, then it assumes that the instruction that most recently executed was in fact the pushf. In that case, it copies the pushed interrupt flag to the pushed trap flag. In other words, if interrupts were previously enabled, then set the trap flag in the pushed flags.

In the 80386 architecture, the trap flag causes the processor to raise a trap exception after one instruction is executed. Its intended purpose is to allow debuggers to single-step through assembly code. The trick is to repurpose the flag: When the popf occurrs, the processor doesn't pop the interrupt flag, but it does pop the trap flag. After one instruction, the trap interrupt fires, and the kernel regains control. It recognizes that this trap interrupt is being used to regain control when somebody does a popf instruction in a failed attempt to re-enable interrupts. The kernel would then re-enable interrupts.

Result: As far as the application is concerned, the popf instruction successfully restored the interrupt flag despite all the warnings that said it wouldn't work. It was restored one instruction late, but it did eventually get restored.

That one weird trick rescued a lot of games from the "Doesn't work" category.

Related reading: What did Windows 3.1 do when you hit Ctrl+Alt+Del?, another case where the operating system used the trap flag.

Comments (28)
  1. Brian_EE says:

    To me, it seems fundamentally broken that the 80386 would allow an application that lacks I/O privilege to disable the interrupt if it doesn't allow it to re-enable the interrupt. Is there some reason why the handling of the interrupt flag was not symmetric?

    1. Falcon says:

      I think this sentence answers your question:

      "Since the cli instruction is privileged, it will trap."

    2. Karellen says:

      If my reading of Raymond's article is correct, the application is in fact not allowed to disable the interrupt. "Since the cli instruction is privileged, it will trap."

      1. bkuhns says:

        > "But if it doesn’t, then the popf instruction has no effect on the interrupt flag, and interrupts remain disabled."

        That makes it sound like interrupts were allowed to be disabled, but not re-enabled. Though, I suspect there may have been an instruction to explicitly enable interrupts, but the code was being lazy. The pushf starts off by saving whether or not interrupts were /already/ disabled. The cli ensures interrupts are now disabled regardless of whether they were already so. The popf then re-enables interrupts if they were enabled before entry of that code fragment.

      2. Boris says:

        No, it did disable interrupts:

        "If it has I/O privilege, then the popf will restore interrupts to their previous state. But if it doesn’t, then the popf instruction has no effect on the interrupt flag, and interrupts ________remain disabled________."

        1. Pietro Gagliardi (andlabs) says:

          This is how I read it. Note that I'm not an x86 expert, so I might have some details wrong.

          cli seems to be a privileged instruction, as Raymond mentioned that there is a cli trap. The DPMI extender would receive the trap and oblige to clear interrupts on behalf of the client. So in this case, cli "works" because the extender allows it to. The DPMI extender could just as well do nothing.

          popf, on the other hand, is unprivileged. This is reasonable: there are lots of unprivileged flags a program might want to save and restore, such as the zero flag. Therefore, instead of having a separate popf instruction for privileged flags, the x86 architecture simply only pops whatever flags the currently executing code has privilege to.

          Because of this, there's no "need" for a popf trap from the perspective of the CPU designer. However, this lack of a trap means that the bad code block above becomes a bug that's difficult for the OS author to fix (and the OS author must fix, because the game developer likely won't, or worse, can't), hence that "one weird trick." ("Phar Lap hates them!")

          1. smf says:

            >Because of this, there’s no “need” for a popf trap from the perspective of the CPU designer

            There is a need, you tried to do something that you didn't have permission to do. Popf should trap in that circumstance.

            I assume there was a performance or cost reason why it doesn't trap, because if it was just down to the whim of the CPU designer then that would be disappointing.

            At the point where the chip exists then you have to work round all the stupid bugs. Turning on tracing is inspired, to me it seems weird that popf has permission to turn trace on but not turn irq's on.

        2. You're both right. CLI traps, and then the VMM disables virtual interrupts for that virtual machine, then resumes execution. The program then does a POPF, which does not trap and therefore has no effect on virtual interrupts.

    3. Antonio Rodríguez says:

      What is really broken is that one method of setting the flag (STI/CLI) triggers an exception, while the other (POPF) fails silently. The POPF instruction should have trapped if the IF bit were to be changed.

      Now, processors in 1985 (the year the 80386 was introduced) were simpler and silicon more limited, but failures like those make difficult creating a complete virtual machine, as it clearly was the 80386's engineering team intention.

      1. Euro Micelli says:

        We're not seeing the big picture. The processor's architecture assumes that a program running without I/O privilege is DESIGNED to run without I/O privileges -- why wouldn't it? If such a program tried to change Interrupt Flag, it would "obviously" be a bug in the program. The processor is not necessarily designed with the idea of fooling a program intended to run with I/O privileges so that it runs without them.

        With that in mind, let's look at what happens:

        PUSHF ; Flags pushed. Maybe IF is pushed too, but probably not. Irrelevant. Nothing wrong here
        CLI ; Raise trap! OS should terminate the program because it issued an invalid instruction. Windows instead simulates/changes the flag on behalf of the program and resumes
        .. do something ..
        POPF ; Restore flags, except leave IF alone. Nothing wrong with this. Of course the program didn't mean to restore IF - "it's not privileged code".

        The trick described by Raymond allows Windows to be notified when the program "meant" to restore the Interrupt Flag (which obviously it couldn't do)

        (Note that under the more direct approach of calling CLI, doing something and then calling STI, both instructions would have trapped)

        1. Josh B says:

          Obviously, it would be more correct for a game to write:
          pushf
          cli
          ....
          pop ax
          and ax, 0x200
          test ax, 0x200
          jz SkipSTI
          sti
          SkipSTI:
          ....

          But most games are creaky hacks piled on top of hacks already, and future-proof correctness isn't a big virtue, especially when it's a bit slower and more complicated.

          1. Joshua says:

            If you're gonna do that you might as well write cli/sti. The reason you do pushf/cli/popf is handling the case of called with interrupts already disabled.

          2. smf says:

            >Obviously, it would be more correct for a game to write:

            No it wouldn't, you are throwing away all the other flags too. You can't restore the flags first and then do the and check because you're still going to mess up the flags. You could probably enable the interrupt and then restore the flags, as long as your interrupt handler doesn't do anything funky. You're much better off using pushf/cli/popf.

            It's just unfortunate that Intel forgot to make popf trap if you tried to change the interrupt state, I think modern day Intel wouldn't have made that mistake.

          3. Euro Micelli says:

            @smf:
            > Intel forgot to make popf trap if you tried to change the interrupt state

            The processor has no way to know. If the IF bit in the DWORD is 0, does that mean that the program is trying to clear the flag? And if it's 1, does that mean that the program trying to set IF? You can't put "null" in a bit.

            What actually happens -- the only thing that can happen -- is that the processor ignores the IF bit when doing POPF.

          4. > No it wouldn’t, you are throwing away all the other flags too.

            In practice, the game doesn't care about the other flags. All it wants to do is disable interrupts temporarily.

            > If the IF bit in the DWORD is 0, does that mean that the program is trying to clear the flag?

            Yes. Because that's what happens in real mode.

          5. Erik F says:

            As this game was already written to run inside a DPMI server, if the programmers really wanted to have DPMI portability they should be using the INT 31h/090[01]h services. This particular sequence of code was called out as being problematic by the DPMI Committee itself and they released this document (http://www.tenberry.com/web/dpmi/02.html#03) in 1990!

            Realizing this, my best guess is that programmers didn't read the spec, and really I can't blame them too much as this is supposed to be a low-level interface. The compiler suite should have had a fix for this however, because the extender libraries basically had to re-implement the whole language library in any case AFAICT.

          6. Yuhong Bao says:

            As I said before, I think most DOS games were deliberately not supporting anything other than running under pure DOS without Windows.

          7. Pseudonym says:

            @smf "I think modern day Intel wouldn’t have made that mistake."

            Modern day Intel supports actual virtualisation, so the whole question is moot.

          8. smf says:

            > In practice, the game doesn’t care about the other flags. All it wants to do is disable interrupts temporarily.

            Sure, that is what the programmer was thinking when he wrote those lines of code. What the code relies on is another matter. I certainly wouldn't suggest changing them without doing a code review and putting the game through QA again.

            The performance overhead of a branch on such an old cpu is also worth considering, as is overwriting ax.

            I'm curious how often this patching didn't work. If you really wanted your code to not work on windows you could easily create false positives.

            jmp x
            pushf
            x: sti

            I still blame intel.

            >If the IF bit in the DWORD is 0, does that mean that the program is trying to clear the flag?

            Yes, why wouldn't it? What circumstance would there be any ambiguity?

          9. > Modern day Intel supports actual virtualisation, so the whole question is moot.

            Let's go fire up that time machine and add VT-x support to the 80386.

          10. > jmp x; pushf; x: sti

            That wouldn't be a false positive because the value on the top of the stack doesn't match the current flags.

          11. smf says:

            There is a 1 in 65536 chance that it will & you can improve the odds further if you are purposefully trying to deceive it, although I admit it's contrived. It would only be useful if you were trying to obsfucate a check for running under windows.

            More importantly, were there any games doing something similar that weren't able to be detected?

          12. The processor can make a reasonable guess, though (albeit at the expense of added complexity). If POPF would change the value of IF, then it can trap; thus, if the saved value of IF is 1, and the current value is also 1, don't trap - similarly, if the saved value is 0, and the current value is also 0, don't trap.

            That way, you trap on implicit attempts to change IF, just as you do on explicit attempts. It does need more logic (you end up calculating the moral equivalent of (SAVED_FLAGS ^ FLAGS) & FLAGS_TRAP_MASK == 0), but it would have avoided the need for the clever workaround.

          13. smf says:

            Yeah, IMO it should have done that. However there may be reasons why it would have been a bad idea & we unfortunately can't know that. I don't know how complex the 386 pipelining is, but it's possible that popf would be slower if it could be restarted after the value is fetched from the stack.

  2. Myria says:

    This problem was solved on later revisions of the 486, but Intel kept it secret for years, requiring an NDA to find out about it ("Appendix H"). The feature, "Protected Virtual Interrupts", makes it so that attempts to do cli/sti in user mode change a "virtual interrupt flag" instead of the real one.

    1. Yuhong Bao says:

      AFAIK PVI don't change PUSHF/POPF unlike VME.

  3. Henri Hein says:

    I like all these Games-savior techniques and your articles on them. For some reason, I found this particularly neat.

    1. Yukkuri says:

      Agreed!

Comments are closed.

Skip to main content