The x86 supports hardware interrupts (like nearly every other CPU does) – the ability for a device such as a keyboard, disk, network card, internal timer, etc. to notify the CPU when something needs to be actioned (e.g. a key was pressed, some data is ready). An interrupt will cause the CPU to stop what it’s doing, and jump to some other code to handle the interrupt.
Before it jumps off to the interrupt handler, it pushes several registers to the stack (eip
, cs
, eflags
, esp
and ss
if you’re curious), so that after the interrupt is handled, it can go back to executing whatever it was doing before. (The OS is responsible for pushing all the other registers at the start of the interrupt handler).
But there are times we don’t want interrupts to occur (for instance, when initialising the interrupt handler code itself when the computer is starting up!)
Interrupts are enabled or disabled depending on the state of the (e)flags
register. If bit 9 is set (the IF
flag), then interrupts are enabled.
Now, there’s no way to directly set eflags
, as it isn’t a general purpose register. But you can use the pushf
and popf
instructions to push or pop the flags from the stack.
We could use this to enable interrupts directly:
pushf
pop eax ; any register works
or eax, 0x200 ; set IF flag
push eax
popf
And the same would work for disabling, except we use and eax, ~0x200
instead of the or
.
This is a bit clumsy, so Intel also provides convenient instructions to set these bits directly:
; clear IF (disable interrupts)
cli
; set IF (enable interrupts)
sti
But there’s another way to disable interrupts – but only temporarily! Can you guess how?
You probably weren’t expecting the answer to be “set the ss
register“?!
As it turns out, both of these instructions:
mov ss, <register>
pop ss
Cause the CPU to inhibit interrupts for the next instruction only. Why is this?
We can find the answer when we consider what the ss
register is for. It’s the stack segment. And so it’s used to adjust where the stack happens to be.
But there’s two parts to where the stack is: the segment it is in (ss
), and the offset itself (esp
). Therefore to change where the stack points, two registers need to be updated (ss
and esp
).
Recall that when an interrupt occurs, information gets pushed to the stack. What would happen if an interrupt occurred in between setting ss
and esp
? The CPU would be pushing information to an invalid stack, probably causing a page/segmentation fault! And that fault will try to push even more information to the same stack, causing a double fault! And that fault will try to push yet more information to (possibly the same*) stack – and when a double fault faults, it’s a triple fault – which causes the CPU to reboot. Not good.
So by inhibiting interrupts for a single instruction when ss
is set, it means we don’t have to worry about this occurring! Sure, you could wrap the stack changing code with cli
/sti
, but I suppose Intel thought it’d be easier to put a special case into the CPU hardware…
I’m tapped… If you want to
hear another story sometime,
you know where to find me.
* Some shenanigans can be set up to make fault handlers use different stacks (using task gates), so the OS could give a ‘known good’ stack for the double fault handler to prevent it escalating into a triple-fault reboot. Though, a double fault usually means “things are cooked”, and so it’s probably only useful for showing a error message (e.g. a BSoD, etc.)
Leave a Reply