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.