My question isn't about the fact that BX was used as a return value rather than placing it at a global memory location or on the stack. I observed this code being posted recently in a comment. The code was for a real-mode mouse handler using the BIOS. Two small functions that save/restore the state of the FLAGS register were as follows:
EFLAGS_IF equ 0x200 ; Bit mask for IF flag in FLAGS register
; Function: save_if_flag
; save the current state of the Interrupt Flag (IF)
;
; Inputs: None
; Returns: BX = 0x200 if interrupt flag is set, 0 otherwise
save_if_flag:
pushf
pop bx ; Get FLAGS into BX
and bx, EFLAGS_IF ; BX=0 if IF is clear, BX=0x200 if set
ret
; Function: restore_if_flag
; restore Interrupt Flag (IF) state
;
; Inputs: BX = save Interrupt Flag state
; Clobbers: None
; Returns: EFLAGS IF flag restored
restore_if_flag:
test bx, bx ; Is saved Interrupt Flag zero?
jz .if_off ; If zero, then disable interrupts & finish
sti ; Otherwise enable interrupts
ret ; We're finished
.if_off:
cli ; Disable interrupts
ret
I'd like to understand why the restore_if_flag
function does this:
restore_if_flag:
test bx, bx ; Is saved Interrupt Flag zero?
jz .if_off ; If zero, then disable interrupts & finish
sti ; Otherwise enable interrupts
ret ; We're finished
.if_off:
cli ; Disable interrupts
ret
Rather than simply using POPF
like this:
restore_if_flag:
push bx
popf
ret
Why explicitly save/restore the interrupt flag using STI/CLI rather than simply restoring the previous FLAGS register with POPF?
The code you are looking at has merit. Likely the person who wrote this is aware that POPF
doesn't treat the Interrupt Flag (IF) the same way in all the different operating modes.
They are likely trying to avoid these two patterns of code:
sti
pushf ; Save flags including interrupts (IF)
cli
; Do work here with interrupts off
popf ; Restore interrupts (re-enable IF)
; Interrupts may still be off at this point depending on mode and IO Privileges
or
cli
pushf ; Save flags including interrupts (IF)
sti
; Do work here with interrupts on
popf ; Restore interrupts to previous state
; Interrupts may still be on at this point depending on mode and IO privileges
These 2 cases are where IF was actually changed and POPF is being expected to restore IF to its previous value. If you review the first diagram I have circled N . These are the cases where you are in protected mode, compatibility mode, or 64-bit mode and the Current Privilege Level (CPL) is 1,2,3 and IOPL (IO Privilege Level) < CPL. In these situation POPF will not generate a general protection fault and will silently ignore the change to IF.
Because there is no fault, the kernel doesn't have any idea that there was an attempt to change IF so has no chance to virtualize IF. STI and CLI act as privileged instructions when they don't have proper IOPL privileges and will fault to the kernel where IF can be virtualized by the CPU.
To the question as to why explicit STI/CLI is being done in the original code? An STI/CLI will guarantee a fault that the kernel can intercept in the cases where you don't have proper IOPL privileges to update IF. POPF may allow IF to become out of sync with the notion of what the program thinks the flag should be. By using STI and CLI to change IF you allow the kernel to more easily keep things synchronized.
The DPMI (DOS Protected Mode Interface) specification discusses this issue and is described this way:
2.3 Interrupt Flag Management The popf and iret instructions may not modify the state of the interrupt flag since most DPMI implementations will run programs with IOPL < DPL. Programs must execute cli or sti to modify the interrupt flag state.
This means that the following code sequence will leave interrupts disabled:
; ; (Assume interrupts are enabled at this point) ; pushf cli . . popf ; Interrupts are still OFF!
Note that since some implementations of DPMI will maintain a virtual interrupt state for protected mode DOS programs, the current value of the interrupt flag may not reflect the current virtual interrupt state. Protected mode programs should use the virtual interrupt state services to determine the current interrupt flag state (see page 99).
Since cli and sti are privileged instructions, they will cause a protection violation and the DPMI provider will simulate the instruction. Because of the overhead involved in processing the exception, cli and sti should be used as little as possible. In general, you should expect either of these instructions to require at least 300 clocks.