10.5. Обработка прерываний и исключений

До сих пор все наши программы работали в защищенном режиме с полностью отключенными прерываниями - ими нельзя было управлять с клавиатуры, они не могли работать с дисками и вообще не делали ничего, кроме чтения или запи­си в те или иные области памяти. ни одна программа не может вы­полнить что-то серьезное в таком режиме - нам рано или поздно придется обра­батывать прерывания.

В реальном режиме адрес обработчика прерывания считывался процессором

из таблицы, находящейся по адресу О в памяти. В защищенном режиме эта табли­ца, называемая IDT - таблицей дескрипторов прерываний, может находиться где угодно. Достаточно того, чтобы ее адрес и размер были загружены в регистр IDTR.

Содержимое IDT - не просто адреса обработчиков, как это было в реальном ре­жиме, а дескрипторы трех типов: шлюз прерывания, шлюз ловушки и шлюз зада­чи (форматы данных дескрипторов рассматривались в предыдущем разделе).

Шлюзы прерываний и ловушек указывают точку входа обработчика, а также

его разрядность и уровень привилегий. При передаче управления обработчику процессор помещает в стек флаги и адрес возврата, так же как и в реальном режи­ме, но после этого для некоторых исключений в стек помещается дополнитель­ный код ошибки, следовательно, не все обработчики можно завершать простой

командой IRETD (или IRET для 16-битного варианта). Единственное различие между шлюзом прерывания и ловушки состоит в том, что при передаче управле­ния через шлюз прерывания автоматически запрещаются дальнейшие прерыва­ния, пока обработчик не выполнит IRETD. Этот механизм считается предпочти­тельным для обработчиков аппаратных прерываний, а шлюз ловушки, который

не запрещает прерывания на время исполнения обработчика, лучше использовать

для обработки программных прерываний (которые фактически и являются ис­ключениями типа ловушки). Кроме того, в защищенном режиме при вызове об­работчика прерывания сбрасывается флаг трассировки ТЕ

Сначала рассмотрим пример программы, обрабатывающей только аппаратное прерывание клавиатуры с помощью шлюза прерываний. Для этого надо составить IDT, загрузить ее адрес командой LIDT и не забыть загрузить то, что содержится в регистре IDTR в реальном режиме, - адрес 0 и размер 4x256, соответствующие:

таблице векторов прерываний реального режима.

• ; pm2.asm

; Программа, демонстрирующая обработку аппаратных прерываний в защищенном режиме.

; Переключается в 32-битный защищенный режим и позволяет набирать текст при помощи

; клавиш от 1 до +.   Нажатие Backspace стирает предыдущий символ,

; нажатие Esc - выход из программы.

Процессоры Intel в защищенном

Компиляция TASM:

tasm /т /D_TASM_ pm2.asm (или, для версий 3.x, достаточно tasm /т pm2.asm)

tlink /х /3 pm2.obj Компиляция WASM:

wasm /D pm2.asffl wlink file pm2.obj  form DOS

Варианты того, как разные ассемблеры записывают смещение из 32-битного сегмента в 16-битную переменную: ifdef _TASM_

so equ        small offset ;   TASM 4.x

else

so equ        offset ; WASM

endif

; Для MASM, по-видимому, придется добавлять лишний код, который преобразует ; смещения, используемые в IDT.

. 38бр

RM_seg segment para public "CODE" usel6

assume cs:RM_seg,ds:PM_seg,ss:stack_seg

start:

; Очистить экран.

mov

ax,3

int

10h

; Подготовить

сегментные регистр

push

PM_seg

pop

ds

; Проверить,

не находимся ли мы

mov

еах,сгО

test

al,1

jz

no_V86

;  Сообщить и

выйти.

mov

•   dx.so v86_msg

err_exit:

 

mov

ah,9

int

21h

mov

ah,4Ch

int

21h

; Может быть, это Windows 95 делает вид, что РЕ = 0? no_V86:

mov ах,1600h

int 2Fh

test al.al

jz no_windows

; Сообщить и выйти. mov

jrnp short err_exit

Обработка и исключений

S01

Итак, мы точно no_windows:

і Вычислить

xor

находимся в реальном режиме.

базы для всех eax,eax

mov ax,RM_seg

shl eax,4

mov word ptr GDT_16bitCS+2,ax

shr eax,16

mov byte ptr G0T_16bitCS+4,al

mov ax,PM_seg

shl eax, 4

mov word ptr GDT_32bitCS+2,ax

mov word ptr GDT_32bitSS+2,ax

mov word ptr GDT_32bitDS+2,ax

shr eax,16

mov .   byte ptr GDT_32bitCS+4,al

mov byte ptr G0T_32bitSS+4,al

mov byte ptr GDT_32bitDS+4,al

используемых дескрипторов сегментов.

Базой 16bitCS будет

Базой всех PM_seg.

32bit« будет

eax,eax ax,PM_seg eax, 4

еах

eax,offset GDT dword ptr gdtr+2,eax

Вычислить линейный адрес GDT. xor mov shl push add mov

Загрузить GDT.

lgdt      fword ptr gdtr Вычислить линейный адрес IDT.

pop еах

add      eax, offset IDT

mov      dword ptr idtr+2,eax Загрузить IDT.

lidt      fword ptr idtr Есж мы собираемся работать с -32-битной памятью,

in al,92h

or al,2

out 92h,al Отключить прерывания,

cli

включая NMI.

стоит открыть А20.

in

al,70h

or

al,80h

out ,

70h,al

и в РМ.

 

mov

eax,crO

or

al,l

mov

crO,eax

Загрузить

RM return:

SEL_32bitCS ■ db

db dd

dw

RM.

в CS. 66h

OEAh

offset PM_entry SEL_32bitCS

Перейти в

mov eax.crO and al.OFEn mov crO.eax

Сбросить очередь и загрузить CS реальным числом, db OEAh dw $+4 dw RM_seg

Установить регистры для работы

в реальном режиме.

mov

mov mov mov mov mov mov ах,PM_seg ds, ах es, ax

ax,

bx,stack_l ss, ax sp, bx

• Загрузить        для реального режима.

mov mov

lidt      fword ptr idtr_real

;   Разрешить NMI.

in al,70h

and al,07FH

out 70h,al

; Разрешить прерывания

sti

• и выйти.

fflov ah,4Ch int 21h RM_seg ends

; 32-битный сегмент. PM_seg segment para public assume "CODE"

;  Таблицы GDT и IDT должны быть выравнены,  так что будем их размещать ; в начале сегмента. GDT        label byte

db 8 dup(O)

; 32-битный 4-гигабайтный сегмент с базой = 0. GDTJlatDS db        OFFh, OFFh, 0, 0, 0, 10010010b, 11001111b, 0

; 16-битный 64-килобайтный сегмент кода с базой RM_seg. G0T_16bitCS db OFFh,OFFh,0,0,0,10011010b,0,О

Обработка прерываний и

мамі

; 32-битный 4-гигабайтный сегмент кода с базой pm_seg.

GDT_32bitCS

,

db

OFFh,OFFh,0,0,0,10011010b,11001111b,0

 

 

; 32-битный

4-гигабайтный сегмент данных с базой

 

 

GDT_32bitDS

db

О

 

 

; 32-битный

сегмент данных с базой

 

 

GDT_32bitSS

 

db

OFFh,OFFh,0,0,0,10010010b,11001111b,0

 

 

gdt_size =

S-GDT

 

 

 

 

gdtr

 

dw

gdt_size-1                     ; Лимит GDT.

 

 

 

 

dd

?                              ' ;  Линейный адрес GDT.

 

 

Имена для

селекторов.

 

 

 

SEL_flatDS

 

equ

001000b

 

 

SELJ6bitCS

 

equ

010000b

 

 

SEL_32bitCS

 

equ

011000b

 

 

SEL_32bitDS

 

equ

100000b

 

 

SEL_32bitSS

 

equ

101000b

 

 

Таблица дескрипторов

прерываний IDT. '

 

 

IDT

 

label

byte

 

 

Все эти дескрипторы

имеют тип OEIv- 32-битный шлюз прерывания.

 

 

; INT 00 -

07

 

 

 

 

 

 

dw

8 dup(so int_handler,SEL_32bitCS,8E00h,0)

 

 

INT 08 (irqO)

 

 

 

 

 

 

dw

so irqO_7_handler,SEL_32bitCS, 8E00h, 0

 

 

INT 09

 

 

 

 

 

 

dw

so irq1_handler,SEL_32bitCS,8EO0h, О

 

 

;  INT OAh -

OFh

(IRQ2

- IRQ8)

 

 

 

 

dw

6 dup(so irqO_7_handler,SEL_32bitCS,8E0Oh,O)

 

 

;   INT 10h -

6Fh

 

 

 

 

 

 

dw

97 dup(so int_handler,SEL_32bitCS,8E00h,0)

 

 

INT 70h -

78h

(IRQ8

- IRQ15)

 

 

 

 

dw

8 dup(so irq8_15_handler,SEL_32bitCS,8EOOh,0)

 

 

; INT 79h -

FFh

 

 

 

 

 

 

dw

135 dup(so int_handler,SEL_32bitCS, 8E00h, 0)

 

 

idt_size =

$-IDT

 

;   Размер IDT.

 

 

idtr

 

dw

idt_size-1                     ; Лимит IDT.

 

 

 

 

dd

?                            Линейный адрес начала

IDT.

;  Содержимое регистра IDTR в реальном режиме.

 

 

idtr_real

 

dw

3FFh,0,0

 

 

; Сообщения об ошибках при старте.

 

 

v86_msg

 

db

"Процессор в режиме V86 - нельзя переключиться

в

РМ$"

winjnsg

db

"Программа запущена под Windows - нельзя перейти

в

кольцо

; Таблица для перевода

ОЕ скан-кодов в ASCII.

 

 

scan2ascii

 

db

0, 1Bh,'1' , ' 2' , '3' , ' 4' , ' 5', ' 6' , ' 7',' 8' , ' 9' ,' 0' , '

-' ,

'=' ,8

screen_addr

 

dd

0                          ; Текущая позиция на

экране.

; Точка входа в 32-битный защищенный режим. РМ_егЛгу:

; Установить 32-битный стек и другие регистры. №v ax.SEL_flat.DS

Процессоры Intel в защищены ом режиме

mov mov

mov mov mov mov ds, ах es, ах

ax,SEL_32bitSS

ebx,stack_l

ss, ах esp,ebx

;  Разрешить прерывания

sti

; и войти в вечный цикл, jmp short $

; Обработчик обычного прерывания. int_handler: iretd

; Обработчик аппаратного прерывания IRQO irq0_7_handler:

push еах

mov al,20h

out   . 20h,al

pop eax

iretd

; Обработчик аппаратного прерывания irq8_15_handler:

push      eax ■

mov        al,20h , out 0A1h,al

pop eax iretd

; Обработчик IRQ1 - прерывания irq1_handler:

IR07.

IRQ15.

от клавиатуры.

push

eax Это

аппаратное прерывание - сохранить регистры.

push

ebx

 

push

es

 

push

ds

 

in

al,60h

; Прочитать скан-код нажатой клавиши.

cmp

al.OEh

; Если он больше, чем-

ja

skip_translate

; обслуживаемый нами,- не обрабатывать.

cmp

al,1

; Если это Esc,

ie

esc_pressed

выйти в реальный режим.

mov

bx,SEL_32bitDS

; Иначе:

mov

ds, bx

; OS:EBX - таблица для перевода скан-кода

mov

' ebx,offset scan2ascii

; в ASCII,

xlatb

 

Преобразовать.

mov

bx,SEL_flatDS

 

mov

es, bx

; ES:EBX - адрес текущей

mov

ebx,screen_addr

позиции на экране.

cmp

al,8

; Если не была нажата Backspace,

je

bs_pressed

 

Обработка прерываний и исключений

es:[ebx+OB8000h],al

ptr

short skip_translate

зі 1 1 ebx,2

es:[ebx+0B8000h],al screen addr, ebx

mov

add imp

bs_pressed: mov

sub mov mov

skip.translate:

; Разрешить работу клавиатуры.

in al,61h

or al,80h

out 61h,al ; Послать EOI контроллеру прерываний.

mov al,20h

out 20h,al ; Восстановить регистры и выйти.

pop ds

pop es

pop ebx

pop eax

iretd

;   Сюда передается управление и esc.pressed:

;   Разрешить работу клавиатуры, а1,61п al,80h

послать символ на экран. Увеличить адрес позиции на 2.

Иначе:

нарисовать пробел

в позиции предыдущего символа

и сохранить адрес

как текущий.

предыдущего символа

обработчика       если нажата Esc.

послать EOI и восстановить регистры.

in

or

out

mov

out

pop pop pop pop

61h,al al,20h

20h,al

ds

es

ebx

eax

Вернуться в реальный режим. cli

db OEAh

dd offset RM_return

dw SEL 16bitCS

ends

; Сегмент стека.  Используется как 16-битный в 16-битной части программы и как ;  32-битный (через селектор SEL_32bitSS)  в 32-битной части. stack_seg segment para stack "STACK" stack.start        ebb lOOh dup(?)

stack_1 = $-stack_start   • ; Длина стека для инициализации ESP.

stack_seg ends end start

В этом примере обрабатываются только 13 скан-кодов клавиш для сокращения размеров программы. Полную информацию преобразования скан-кодов в ASCII можно найти в таблицах приложения 1 (см. рис. 18, табл. 25 и 26). Кроме того, в этом примере курсор все время остается в нижнем левом углу экрана - для его перемещения можно воспользоваться регистрами OEh и OFh контроллера CRT (см. раздел 5.10.4).

Как уже упоминалось в разделе 5.8, кроме прерываний от внешних устройств процессор может вызывать исключения при различных внутренних ситуациях

(их механизм обслуживания похож на механизм обслуживания аппаратных пре­рываний). Номера прерываний, на которые отображаются аппаратные прерыва­ния, вызываемые первым контроллером по умолчанию, совпадают с номерами

отдельных исключений. Конечно, можно из обработчика опрашивать контроллер

прерывание, чтобы определить, выполняется ли аппаратное прерывания или это исключение, но Intel рекомендует перенастраивать контроллер прерываний (см. раздел 5.10.10) так, чтобы никакие аппаратные прерывания не попадали на об­ласть от 0 до lFh. В нашем примере исключения не обрабатывались, но, если про­грамма планирует запускать другие программы или задачи, без обработки исклю­чений обойтись нельзя.

Часть исключений (исключения типа ошибки) передает в качестве адреса воз­врата команду, вызвавшую исключение, а часть - адрес следующей команды. Кро­ме того, некоторые исключения помещают в стек код ошибки, который нужно считать, прежде чем выполнять IRETD. Поэтому пустой обработчик из одной ко­манды! IRETD в нашем примере не был корректным и многие исключения приве­ли бы к немедленному зависанию системы.

Рассмотрим исключения в том виде, в каком они определены для защищенного режима.

Формат кода ошибки: биты        биты 15-3 селектора, вызвавшего исключение бит 2: TI - установлен, если причина исключения - дескриптор, находящий­ся в LDT; и сброшен, если в GDT бит 1: IDT - установлен, если причина исключения — дескриптор, находящий­ся в IDT

бит 0: ЕХТ — установлен, если причина исключения - аппаратное прерывание INT 00 - ошибка #DE «Деление на ноль»

Вызывается командами DIV или IDIV, если делитель - ноль или если проис­ходит переполнение.

INT 01 - исключение #DB «Отладочное прерывание»

Вызывается как ловушка при пошаговой трассировке (флаг TF = 1), при пере­ключении на задачу с установленным отладочным флагом и при срабатывании точки останова во время доступа к данным, определенной в отладочных регистрах.

Вызывается как ошибка при срабатывании точки останова по выполнению ко­манды адресом, определенным в отладочных регистрах.

INT 02 - прерывание NMI Немаскируемоепрерывание.

INT 03 - ловушка #ВР «Точка останова» Вызывается однобайтной командой INT3.

INT 04 - ловушка #OF «Переполнение» Вызывается командой INTO, если флаг OF = 1.

INT 05 - ошибка #BR «Переполнение при BOUND»

Вызывается командой BOUND при выходе операнда за допустимые границы. INT 06 - ошибка #UD «Недопустимая операция»

Вызывается, когда процессор пытается исполнить недопустимую команду или команду с недопустимыми операндами.

INT 07 - ошибка #NM «Сопроцессор отсутствует» •

Вызывается любой командой FPU, кроме WAIT, если бит ЕМ регистра CRO установлен в 1, и командой WAIT, если МР и TS установлены в 1.

INT 08 - ошибка #DF «Двойная ошибка»

Вызывается, если одновременно произошли два исключения, которые не мо­гут быть обслужены последовательно. К ним относятся #DE, #TS, #NP, #SS, #GP и#РЕ

Обработчик этого исключения получает код ошибки, который всегда равен нулю. Если при вызове обработчика #DF происходит еще одно исключение, процес­сор отключается и может быть выведен из этого состояния только сигналом NMI

или перезагрузкой.

INT 09h - зарезервировано

Эта ошибка вызывалась сопроцессором 80387, если происходило исключение #PF или #GP при передаче операнда команды FPU.

INT OAh - ошибка #TS «Ошибочный TSS»

Вызывается при попытке переключения на задачу с ошибочным TSS.

Обработчик этого исключения должен вызываться через шлюз задачи. Обработчик этого исключения получает код ошибки.

Бит ЕХТ кода ошибки установлен, если переключение пыталось выполнить ап­паратное прерывание, использующее шлюз задачи. Индекс ошибки равен селектору TSS, если TSS меньше 67h байт, селектору LDT, если LDT отсутствует или оши­бочен, селектору сегмента стека, кода или данных, если ими нельзя пользоваться

(из-за нарушений защиты или ошибок в селекторе). . INT     - ошибка #NP «Сегмент недоступен»

Вызывается при попытке загрузить в регистр CS, DS, ES, FS или GS селектор сегмента, в дескрипторе которого сброшен бит присутствия сегмента (загрузка в SS вызывает исключение #SS), а также при попытке использовать шлюз, ченный как отсутствующий, или при загрузке таблицы локальных дескрипторов командой LLDT (загрузка при переключении задач приводит к исключению #TS).

Если операционная система реализует виртуальную память на уровне сегмен­тов, обработчик этого исключения может загрузить отсутствующий сегмент в па­мять, установить бит присутствия и вернуть управление.

Обработчик этого исключения получает код ошибки.

Бит ЕХТ кода ошибки устанавливается, если причина ошибки - внешнее преры­вание, бит IDT устанавливается, если причина ошибки - шлюз из IDT, помеченный как отсутствующий. Индекс ошибки равен селектору отсутствующего сегмента.

INT ОСЬ - ошибка #SS «Ошибка стека»

Это исключение вызывается при попытке выхода за пределы сегмента стека во время выполнения любой команды, работающей со стеком, - как явно PUSH, ENTER, LEAVE), так и неявно (MOV АХ, [BP + 6]), а также при попытке загрузить в регистр SS селектор сегмента, помеченного как отсутствующий (не только во время выполнения MOV, POP и LSS, но и во время переключе-

ния задач, вызова и возврата из процедуры на другом уровне привилегий).

Обработчик этого исключения получает код ошибки.

Код ошибки равен селектору сегмента, вызвавшего ошибку, если она произош­ла из-за отсутствия сегмента или при переполнении нового стека в вой команде GALL. Во всех остальных случаях код ошибки - ноль.

INT     - исключение #GP «Общая ошибка защиты»

Все ошибки и ловушки, не приводящие к другим исключениям, вызывают

#GP - в основном нарушения привилегий.

Обработчик этого исключения получает код ошибки.

Если ошибка произошла при загрузке селектора в сегментный регистр, код ошибки равен        селектору, во всех остальных случаях код ошибки - ноль.

INT     - ошибка     «Ошибка страничной адресации» Вызывается, если в режиме страничной адресации программа пытается обра­титься к странице, которая помечена как отсутствующая или привилегированная.

Обработчик этого исключения получает код ошибки.

Код ошибки использует формат, отличающийся для других исключений:

бит 0: 1, если причина ошибки - нарушение привилегий;

О, если было обращение к отсутствующей странице;

бит       если выполнялась операция записи; 0, если чтения;

бит 2: 1, если операция выполнялась из CPL = 3; 0, если CPL < 3;

бит 3: 0, если ошибку вызвала попытка установить зарезервированный бит в ка­талоге страниц.

Остальные биты зарезервированы.

Кроме кода ошибки обработчик этого исключения может прочитать из регистра CR2 линейный адрес, преобразование которого в физический вызвало исключение.

Исключение #PF - основное исключение для создания виртуальной памяти с использованием механизма страничной адресации.

INTOFh- зарезервировано

INT 10h - ошибка #MF «Ошибка сопроцессора»

Вызывается, только если бит NE в регистре CRO установлен в 1 при выполне­нии любой команды FPU, кроме управляющих команд и WAIT/F WAIT, при усло­вии, что в FPU произошло одно из исключений FPU (см. раздел 2.4.3).

FNT llh - ошибка #АС «Ошибка выравнивания»

Вызывается, только если бит AM в регистре CRO и флаг АС из ЕFLAGS уста­новлены в если CPL 3 и произошло невыравненное обращение к памяти. (Вы­равнивание должно быть по границе слова при обращении к слову, к границе

двойного слова, к двойному слову и т. д.)

Обработчик этого исключения получает код ошибки равный нулю.

INT    - останов #МС ошибка»

Вызывается (начиная с Pentium) при обнаружении некоторых аппаратных ошибок с помощью специальных машинно-зависимых регистров MCG_*. Нали­чие кода ошибки, так же как и способ вызова этого исключения, зависит от моде­ли процессора.

INT    -     - зарезервировано Intel для будущих исключений

INT 20h -        - выделены для использования программами

Обычно для отладочных целей многие программы, работающие с защищен­ным режимом, устанавливают обработчики всех исключений, которые выдают список регистров процессора и их содержимое, а также иногда участок кода, вызвавший исключение. В качестве примера обработчика исключения типа

ошибки можно рассматривать программу, обрабатывающую #BR (см. раз­дел 5.8.1).