Почему нельзя использовать POPF для восстановления состояния флага прерывания?

Мой вопрос не в том, что BX использовался в качестве возвращаемого значения, а не помещался в глобальную память или в стек. Я заметил, что этот код недавно был опубликован в комментарии. Код был для обработчика мыши в реальном режиме, использующего BIOS. Две небольшие функции, сохраняющие/восстанавливающие состояние регистра FLAGS, выглядели следующим образом:

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

Я хотел бы понять, почему функция restore_if_flag делает это:

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

Вместо того, чтобы просто использовать POPF следующим образом:

restore_if_flag:
    push bx
    popf
    ret

Зачем явно сохранять/восстанавливать флаг прерывания с помощью STI/CLI, а не просто восстанавливать предыдущий регистр FLAGS с помощью POPF?


person Michael Petch    schedule 25.02.2019    source источник
comment
Вы не упоминаете контекст, в котором используется этот код. Вне защищенного режима в расширителе DOS я не думаю, что это было бы полезно.   -  person Ross Ridge    schedule 26.02.2019
comment
@RossRidge: В этом-то и смысл. Предполагается, что вы не знаете контекст, поэтому написание кода таким образом поможет облегчить проблемы при перемещении из контекста в контекст. Поскольку pushf и popf используются для копирования и вставки, как код резака для печенья, который оказался в коде, работающем через расширители DOS, этот код в конечном итоге не работал должным образом, и многие обходные пути (взломы) не всегда успешны. Что касается расширителей DOS, именно по этой причине я упомянул DPMI в ответе.   -  person Michael Petch    schedule 26.02.2019
comment
@RossRidge: Если кому-то интересно, комментарий, к которому относится этот вопрос, в котором была ссылка на этот код, был написан мной. Меня спросили о коде и почему я сделал это в электронном письме неделю назад, поэтому я подумал, что могу объяснить причину. Но да, одна из причин заключалась в том, что если бы кто-то портировал код для запуска в качестве 32-битной программы DOS с защищенным режимом с использованием расширителя - по крайней мере, у него было бы больше шансов работать. Я до сих пор работаю с устаревшим приложением, которое несколько лет назад не удалось из-за этой проблемы (было написано для использования DOS/4GW).   -  person Michael Petch    schedule 26.02.2019


Ответы (1)


Код, на который вы смотрите, заслуживает внимания. Вероятно, человек, написавший это, знает, что POPF не обрабатывает прерывание. Отметьте (IF) одинаковым образом во всех различных режимах работы. введите здесь описание изображения введите здесь описание изображения


Вероятно, они пытаются избежать этих двух шаблонов кода:

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

В этих двух случаях IF был фактически изменен, и ожидается, что POPF восстановит прежнее значение IF. Если вы посмотрите на первую диаграмму, я обвел N. Это случаи, когда вы находитесь в защищенном режиме, режиме совместимости или 64-разрядном режиме, а текущий уровень привилегий (CPL) равен 1,2,3 и IOPL (уровень привилегий ввода-вывода) ‹ CPL. В такой ситуации POPF не будет генерировать общую ошибку защиты и просто проигнорирует изменение на IF.

Поскольку ошибки нет, ядро ​​не знает, что была попытка изменить IF, поэтому не имеет возможности виртуализировать IF. STI и CLI действуют как привилегированные инструкции, когда у них нет надлежащих привилегий IOPL, и они передают ошибку ядру, где IF может быть виртуализирована с помощью ПРОЦЕССОР.

На вопрос, почему в исходном коде используется явный STI/CLI? STI/CLI гарантирует ошибку, которую ядро ​​может перехватить в тех случаях, когда у вас нет надлежащих привилегий IOPL для обновления IF. POPF может допустить рассинхронизацию IF с представлением о том, каким, по мнению программы, должен быть флаг. Используя STI и CLI для изменения IF, вы позволяете ядру легче поддерживать синхронизацию.


Эта проблема обсуждается в спецификации DPMI (интерфейс защищенного режима DOS). описал так:

2.3 Управление флагом прерывания Инструкции popf и iret не могут изменять состояние флага прерывания, поскольку большинство реализаций DPMI будут запускать программы с IOPL ‹ DPL. Программы должны выполнять cli или sti, чтобы изменить состояние флага прерывания.

Это означает, что следующая последовательность кода оставит прерывания отключенными:

     ;
     ; (Assume interrupts are enabled at this point)
     ;
     pushf
     cli
     .
     .
     popf            ; Interrupts are still OFF!

Обратите внимание, что поскольку некоторые реализации DPMI будут поддерживать состояние виртуального прерывания для программ DOS с защищенным режимом, текущее значение флага прерывания может не отражать текущее состояние виртуального прерывания. Программы защищенного режима должны использовать виртуальные службы состояния прерывания для определения текущего состояния флага прерывания (см. стр. 99).

Поскольку cli и sti являются привилегированными инструкциями, они вызовут нарушение защиты, и провайдер DPMI смоделирует инструкцию. Из-за накладных расходов, связанных с обработкой исключения, cli и sti следует использовать как можно реже. В общем, вы должны ожидать, что любая из этих инструкций потребует не менее 300 тактов.

person Michael Petch    schedule 25.02.2019
comment
Мне кажется, последний комментарий в двух примерах шаблонов выглядит наоборот. например первый должен сказать, что может быть выключен после cli / popf, потому что это было бы удивительно. Если бы в этот момент были включены прерывания, это не было бы неподвижным, потому что оно просто изменилось бы из-за popf. - person Peter Cordes; 25.02.2019
comment
@PeterCordes: Да, спасибо за улов. Изначально у меня был немного другой код, и я не обновлял комментарии соответственно. - person Michael Petch; 25.02.2019