3. ОБРАБОТКА ПРЕРЫВАНИЙ В ЗАЩИЩЁННОМ РЕЖИМЕ
3.1. Прерывания в реальном режиме
3.2. Прерывания защищённого режима
3.3. Программа, которая работает с прерываниями
Механизм обработки прерываний в защищённом режиме сильно отличается
от механизма реального режима. До сих пор мы ничего не говорили
о прерываниях, в приведённом выше примере программы перед переходом
в защищённый режим мы просто замаскировали все прерывания. Однако
такой способ "решения" проблемы прерываний подходит
не всегда.
В этой главе мы расскажем вам о том, как надо работать с прерываниями
в защищённом режиме и приведём соответствующий пример программы.
Вы научитесь обрабатывать в защищённом режиме не только программные
прерывания, но и аппаратные, что необходимо, в частности, для
создания драйверов.
В главе 4 первой книги первого тома серии "Библиотека системного
программиста", который называется "Операционная система
MS-DOS", мы подробно рассказали об особенностях обработки
прерываний в реальном режиме.
Напомним только основные моменты.
В реальном режиме имеются программные и аппаратные прерывания.
Программные прерывания инициируются командой INT, аппаратные -
внешними событиями, асинхронными по отношению к выполняемой программе.
Обычно аппаратные прерывания инициируются аппаратурой ввода/вывода
после завершения выполнения текущей операции.
Кроме того, некоторые прерывания зарезервированы для использования
самим процессором - прерывания по ошибке деления, прерывания для
пошаговой работы, немаскируемое прерывание и т.д.
Для обработки прерываний в реальном режиме процессор использует
таблицу векторов прерываний. Эта таблица располагается в самом
начале оперативной памяти, т.е. её физический адрес - 00000.
Таблица векторов прерываний реального режима состоит из 256 элементов
по 4 байта, таким образом её размер составляет 1 килобайт. Элементы
таблицы - дальние указатели на процедуры обработки прерываний.
Указатели состоят из 16-битового сегментного адреса процедуры
обработки прерывания и 16-битового смещения. Причём смещение хранится
по младшему адресу, а сегментный адрес - по старшему.
Когда происходит программное или аппаратное прерывание, текущее
содержимое регистров CS, IP а также регистра флагов FLAGS записывается
в стек программы (который, в свою очередь, адресуется регистровой
парой SS:SP). Далее из таблицы векторов прерываний выбираются
новые значения для CS и IP, при этом управление передаётся на
процедуру обработки прерывания.
Перед входом в процедуру обработки прерывания принудительно сбрасываются
флажки трассировки TF и разрешения прерываний IF. Поэтому если
ваша процедура прерывания сама должна быть прерываемой, вам необходимо
разрешить прерывания командой STI. В противном случае, до завершения
процедуры обработки прерывания все прерывания будут запрещены.
Завершив обработку прерывания, процедура должна выдать команду
IRET, по которой из стека будут извлечены значения для CS, IP,
FLAGS и загружены в соответствующие регистры. Далее выполнение
прерванной программы будет продолжено.
Что же касается аппаратных маскируемых прерываний, то в компьютере
IBM AT и совместимых с ним существует всего шестнадцать таких
прерываний, обозначаемых IRQ0-IRQ15. В реальном режиме для обработки
прерываний IRQ0-IRQ7 используются вектора прерываний от 08h до
0Fh, а для IRQ8-IRQ15 - от 70h до 77h.
В защищённом режиме все прерывания разделяются на два типа - обычные
прерывания и исключения (exception - исключение, особый случай).
Обычное прерывание инициируется командой INT (программное прерывание)
или внешним событием (аппаратное прерывание). Перед передачей
управления процедуре обработки обычного прерывания флаг разрешения
прерываний IF сбрасывается и прерывания запрещаются.
Исключение происходит в результате ошибки, возникающей при выполнении
какой-либо команды, например, если команда пытается выполнить
запись данных за пределами сегмента данных или использует для
адресации селектор, который не определён в таблице дескрипторов.
По своим функциям исключения соответствуют зарезервированным для
процессора внутренним прерываниям реального режима. Когда процедура
обработки исключения получает управление, флаг IF не изменяется.
Поэтому в мультизадачной среде особые случаи, возникающие в отдельных
задачах, не оказывают влияния на выполнение остальных задач.
В защищённом режиме прерывания могут приводить к переключению
задач. О задачах и мультизадачности мы будем говорить в следующей
главе.
Теперь перейдём к рассмотрению механизма обработки прерываний
и исключений в защищённом режиме.
Таблица прерываний защищённого режима
Обработка прерываний и исключений в защищённом режиме по аналогии
с реальным режимом базируется на таблице прерываний. Но таблица
прерываний защищённого режима является таблицей дескрипторов,
которая содержит так называемые вентили прерываний, вентили исключений
и вентили задач.
Таблица прерываний защищённого режима называется дескрипторной
таблицей прерываний IDT (Interrupt Descriptor Table). Также как
и таблицы GDT и LDT, таблица IDT содержит 8-байтовые дескрипторы.
Причём это системные дескрипторы - вентили прерываний, исключений
и задач. Поле TYPE вентиля прерывания содержит значение 6, а вентиля
исключения - значение 7.
Формат элементов дескрипторной таблицы прерываний IDT показан
на рис. 12.
Рис. 12. Формат элементов дескрипторной таблицы прерываний IDT.
Где располагается дескрипторная таблица прерываний IDT?
Её расположение определяется содержимым 5-байтового внутреннего
регистра процессора IDTR. Формат регистра IDTR полностью аналогичен
формату регистра GDTR, для его загрузки используется команда LIDT.
Так же, как регистр GDTR содержит 24-битовый физический адрес
таблицы GDT и её предел, так и регистр IDTR содержит 24-битовый
физический адрес дескрипторной таблицы прерываний IDT и её предел.
Регистр IDTR обычно загружают перед переходом в защищённый режим.
Разумеется, это можно сделать и потом, находясь в защищённом режиме.
Однако для этого программа должна работать в привилегированном
нулевом кольце.
Исключения в защищённом режиме
Для обработки особых ситуаций - исключений - разработчики процессора
i80286 зарезервировали 31 номер прерывания. В таблице 3 приведён
полный список зарезервированных прерываний защищённого режима.
Таблица 4. Зарезервированные прерывания защищённого режима.
00h | Ошибка при выполнении команды деления.
|
01h | Прерывание для пошаговой работы, используется отладчиками.
|
02h | Немаскируемое прерывание.
|
03h | Прерывание по точке останова для отладчиков.
|
04h | Переполнение, генерируется командой INTO, если установлен флаг OF.
|
05h | Генерируется при выполнении машинной команды BOUND, если проверяемое значение вышло за пределы заданного диапазона.
|
06h | Недействительный код операции, или длина команды больше 10 байт.
|
07h | Отсутствие арифметического сопроцессора.
|
08h | Двойная ошибка, вырабатывается в том случае, если при обработке исключения возникло ещё одно исключение. Если во время обработки этого прерывания возникает третье исключение, процессор переходит в состояние отключения, что приводит к перезапуску процессора.
|
09h | Превышение сегмента арифметическим сопроцессором.
|
0Ah | Недействительный сегмент состояния задачи TSS.
|
0Bh | Отсутствие сегмента. Вырабатывается при попытке использовать для адресации дескриптор, у которого бит присуствия сегмента в памяти P сброшен в 0. Это прерывание используется для реализации механизма виртуальной памяти. В этом случае по прерыванию 0Bh операционная система может выполнить подкачку отсутствующего сегмента в память.
|
0Ch | Исключение при работе со стеком. Может возникать в случае отсутствия сегмента стека в памяти или в случае переполнения (антипереполнения) стека.
|
0Dh | Исключение по защите памяти. Возникает при любых попытках получения доступа к сегментам памяти, если программа обладает недостаточным уровнем привилегий.
|
0Eh | Отказ страницы для процессоров i80386 или i80486, зарезервировано для i80286.
|
0Fh | Зарезервировано. |
10h | Исключение сопроцессора.
|
11h - 1Ah | Зарезервированы.
|
Перед тем, как передать управление обработчику исключения, для
многих зарезервированных прерываний процессор помещает в стек
16-битовый код ошибки. Этот код ошибки программа может проанализировать
и тем самым получить некоторую дополнительную информацию об ошибке.
Формат кода ошибки приведён на рис. 13.
Рис. 13. Формат кода ошибки процессора i80286.
Поле индекса содержит индекс дескриптора, при обращении к которому
произошла ошибка. Поле I, равное 1, означает, что этот индекс
относится к таблице IDT. В этом случае произошла ошибка при обработке
прерывания или исключения.
Если бит I равен 0, поле TI выбирает таблицу дескрипторов (GDT
или LDT) по аналогии с соответствующим полем селектора.
Бит EXT устанавливается в том случае, когда ошибка произошла не
в результате выполнения текущей команды, а по внешним относительно
выполняемой программы причинам. Например, при обработке аппаратного
прерывания от устройства ввода/вывода произошло обращение к отсутствующему
в памяти сегменту (у которого в дескрипторе сброшен бит присутствия
P).
Как мы только что говорили, коды ошибок включаются в стек не для
всех исключений. Программа сможет проанализировать этот код только
для следующих исключений:
- 08h - двойная ошибка;
- 0Ah - недействительный TSS;
- 0Bh - отсутствие сегмента в памяти;
- 0Ch - исключение при работе со стеком;
- 0Dh - исключение по защите памяти.
Заметим, что аналога коду ошибки для зарезервированных прерываний
в реальном режиме нет.
Кроме того, новым при обработке прерываний в защищённом режиме
является свойство повторной запускаемости исключений. Свойством
повторной запускаемости обладают не все исключения.
Что такое повторная запускаемость?
Поясним это на конкретном примере. Пусть в нашей системе реализована
виртуальная память. Программа в некоторый момент времени обратилась
к отсутствующему в оперативной памяти сегменту, выдав какую-либо
команду, например MOV или ADD.
Возникло исключение 0Bh - отсутствие сегмента в памяти. Обработчик
этого исключения, входящий в состав операционной системы выполнил
свопинг соответствующего сегмента в оперативную память. Что дальше?
А дальше было бы неплохо повторить выполнение прерванной команды!
Это можно сделать, так как для всех повторно запускаемых исключений
(кроме 03h - прерывание по точке останова и 04h - переполнение)
в стек включается адрес не следующей за прерванной командой, а
адрес первого байта команды, которая вызвала исключение. Выполнив
команду IRET, программа обработки исключения вновь передаст управление
прерванной команде.
Свойством повторной запускаемости обладает большинство зарезервированных
прерываний, кроме следующих:
- 01h - прерывание для пошаговой работы;
- 08h - двойная ошибка;
- 09h - превышение сегмента сопроцессором;
- 0Dh - исключение по защите памяти;
- 10h - исключение сопроцессора.
Обработка аппаратных прерываний
Вспомните диапазон номеров прерываний, используемый в реальном
режиме в компьютерах IBM PC: для обработки прерываний IRQ0-IRQ7
используются номера прерываний от 08h до 0Fh, а для IRQ8-IRQ15
- от 70h до 77h.
Но в защищённом режиме номера от 08h до 0Fh зарезервированы для
обработки исключений!
К счастью, имеется простой способ перепрограммирования контроллера
прерываний на любой другой диапазон номеров векторов аппаратных
прерываний. Например, аппаратные прерывания можно расположить
сразу за прерываниями, зарезервированными для обработки исключений.
После возврата процессора в реальный режим необходимо восстановить
состояния контроллера прерываний. Если при подготовке к возврату
в реальный режим мы записали в CMOS-память байт состояния отключения
со значением 5, после сброса BIOS сам перепрограммирует контроллер
прерываний для работы в реальном режиме и нам не надо об этом
беспокоиться. В противном случае программа должна установить правильные
номера для аппаратных прерываний реального режима.
Следующая программа, которую мы вам представим, выполняет все
необходимые действия, связанные с обработкой прерываний и исключений
в защищённом режиме.
Наша первая программа (листинг 1) работала в защищённом режиме
с запрещёнными прерываниями. Она как бы пролетала через неизведанное
с завязанными глазами и сразу же возвращалась в хорошо освоенный
реальный режим.
Теперь же программа имеет возможность "осмотреться"
и может реагировать на такие события, как прерывания от клавиатуры
и таймера. Кроме того, в случае возникновения исключений вам будет
выдана некоторая диагностика, включающая код исключения, код ошибки
и содержимое регистров процессора на момент возникновения исключения.
Таблица IDT содержит дескрипторы для обработчиков исключений и
аппаратных прерываний. Вентили исключений содержат ссылки на программы
с именами exc_00...exc_1F. Вслед за вентилями исключений в таблице
IDT следуют вентили аппаратных прерываний. Из них задействованы
только IRQ0 и IRQ1 - прерывания от таймера и клавиатуры.
Те аппаратные прерывания, которые нас не интересуют, приводят
к выдаче в контроллеры прерывания команды конца прерывания. Для
таких прерываний предусмотрены заглушки - процедуры с именами
dummy_iret0 и dummy_iret1. Первая заглушка относится к первому
контроллеру прерывания, вторая - ко второму.
Вслед за вентилями аппаратных прерываний мы поместили в таблицу
IDT вентиль программного прерывания int 30h, предназначенного
для организации взаимодействия с клавиатурой на манер прерывания
BIOS INT 16h. При выдаче прерывания int 30h вызывается
процедура с именем Int_30h_Entry.
После запуска программа переходит в защищённый режим и размаскирует
прерывания от таймера и клавиатуры. Далее она вызывает в цикле
прерывание int 30h (ввод символа с клавиатуры), и выводит
на экран скан-код нажатой клавиши и состояние переключающих клавиш
(таких, как CapsLock, Ins, и т.д.).
Если окажется нажатой клавиша ESC, программа выходит из цикла.
Далее в тексте программы следует ряд команд, закрытых символом
комментария. Эти команды приводят к исключению. Вы можете попробовать
их работу, удалив соответствующий символ комментария. При возникновении
исключения на экран будет выведена диагностика, о чём позаботится
обработчик исключений.
Перед завершением работы программа устанавливает реальный режим
процессора и стирает экран.
Обработчик аппаратного прерывания клавиатуры - процедура с именем
Keyb_int. После прихода прерывания она выдаёт короткий звуковой
сигнал, считывает и анализирует скан-код клавиши, вызвавшей прерывание.
Скан-коды классифицируются на обычные и расширенные (для 101-клавишной
клавиатуры). В отличие от прерывания BIOS INT 16h, мы для
простоты не стали реализовывать очередь, а ограничились записью
полученного скан-кода в глобальную ячейку памяти key_code. Причём
прерывания, возникающие при отпускании клавиш игнорируются.
Запись скан-кода в ячейку key_code выполняет процедура Keyb_PutQ.
После записи эта процедура устанавливает признак того, что была
нажата клавиша - записывает значение 0FFh в глобальную переменную
key_flag.
Программное прерывание int 30h опрашивает состояние key_flag.
Если этот флаг оказывается установленным, он сбрасывается, вслед
за чем обработчик int 30h записывает в регистр AX скан-код
нажатой клавиши, в регистр BX - состояние переключающих клавиш
на момент нажатия клавиши, код которой передан в регистре AX.
Для того чтобы не загромождать программу второстепенными деталями,
мы не стали перекодировать скан-код в код ASCII, вы сможете при
необходимости сделать это сами.
Обработчик прерываний таймера - процедура с именем Timer_int.
Эта процедура примерно раз в секунду выдаёт звуковой сигнал, чем
её действия и ограничиваются.
Процедура rdump выводит на экран содержимое регистров процессора
и может быть использована для отладки программы.
Обратите внимание на использованный способ возврата процессора
в реальный режим:
DATASEG
; Пустой дескриптор для выполнения возврата
; процессора в реальный режим через перевод
; его в состояние отключения.
null_idt idt_struc <>
CODESEG
PROC set_rmode NEAR
mov [real_sp],sp
; Переводим процессор в состояние отключения,
; это эквивалентно аппаратному сбросу, но
; выполняется быстрее.
; Сначала мы загружаем IDTR нулями, затем
; выдаём команду прерывания.
lidt [FWORD null_idt]
int 3
rwait:
hlt
jmp rwait
LABEL shutdown_return FAR
Регистр IDTR загружается нулями, следовательно, предел дескрипторной
таблицы прерываний равен нулю. После этого мы выдаём команду программного
прерывания. При обработке прерывания возникает исключение, так
как регистр IDTR инициализирован неправильно. Но это исключение
не может быть обработано по той же причине, что вызывает новое
исключение. Теперь процессор переходит уже в состояние отключения
и выполняет рестарт в реальном режиме. Что нам и требовалось получить!
Для удобства мы вынесли в отдельный файл tiny-os.inc все необходимые
определения структур данных и констант (листинг 2). Файл tiny-os.asm
содержит саму программу, текст которой приведён в листинге 3.
Листинг 2. Структуры данных и константы.
-----------------------------------------------------------
; ------------------------------------------------------------
; Определения структур данных и констант
; ------------------------------------------------------------
STRUC desc_struc ; структура дескриптора
limit dw 0 ; предел
base_l dw 0 ; мл. слово физического адреса
base_h db 0 ; ст. байт физического адреса
access db 0 ; байт доступа
rsrv dw 0 ; зарезервировано
ENDS desc_struc
STRUC idt_struc ; вентиль прерывания
destoff dw 0 ; смещение обработчика
destsel dw 0 ; селектор обработчика
nparams db 0 ; кол-во параметров
assess db 0 ; байт доступа
rsrv dw 0 ; зарезервировано
ENDS idt_struc
STRUC idtr_struc ; регистр IDTR
idt_lim dw 0 ; предел IDT
idt_l dw 0 ; мл. слово физического адреса
idt_h db 0 ; ст. байт физического адреса
rsrv db 0 ; зарезервировано
ENDS idtr_struc
; ---------------------------------------------------------------
; Биты байта доступа
ACC_PRESENT EQU 10000000b ; сегмент есть в памяти
ACC_CSEG EQU 00011000b ; сегмент кода
ACC_DSEG EQU 00010000b ; сегмент данных
ACC_EXPDOWN EQU 00000100b ; сегмент расширяется вниз
ACC_CONFORM EQU 00000100b ; согласованный сегмент
ACC_DATAWR EQU 00000010b ; разрешена запись
ACC_INT_GATE EQU 00000110b ; вентиль прерывания
ACC_TRAP_GATE EQU 00000111b ; вентиль исключения
; ------------------------------------------------------------
; Типы сегментов
; сегмент данных
DATA_ACC = ACC_PRESENT OR ACC_DSEG OR ACC_DATAWR
; сегмент кода
CODE_ACC = ACC_PRESENT OR ACC_CSEG OR ACC_CONFORM
; сегмент стека
STACK_ACC = ACC_PRESENT OR ACC_DSEG OR ACC_DATAWR OR ACC_EXPDOWN
; байт доступа сегмента таблицы IDT
IDT_ACC = DATA_ACC
; байт доступа вентиля прерывания
INT_ACC = ACC_PRESENT OR ACC_INT_GATE
; байт доступа вентиля исключения
TRAP_ACC = ACC_PRESENT OR ACC_TRAP_GATE
; ------------------------------------------------------------
; Константы
STACK_SIZE EQU 0400 ; размер стека
B_DATA_SIZE EQU 0300 ; размер области данных BIOS
B_DATA_ADDR EQU 0400 ; адрес области данных BIOS
MONO_SEG EQU 0b000 ; сегмент видеопамяти
; монохромного видеоадаптера
COLOR_SEG EQU 0b800 ; сегмент видеопамяти
; цветного видеоадаптера
CRT_SIZE EQU 4000 ; размер сегмента видеопамяти
; цветного видеоадаптера
MONO_SIZE EQU 1000 ; размер сегмента видеопамяти
; монохромного видеоадаптера
CRT_LOW EQU 8000 ; мл. байт физического адреса
; сегмента видеопамяти
; цветного видеоадаптера
MONO_LOW EQU 0000 ; мл. байт физического адреса
; сегмента видеопамяти
; монохромного видеоадаптера
CRT_SEG EQU 0Bh ; ст. байт физического адреса
; сегмента видеопамяти
CMOS_PORT EQU 70h ; порт для доступа к CMOS-памяти
PORT_6845 EQU 0063h ; адрес области данных BIOS,
; где записано значение адреса
; порта контроллера 6845
COLOR_PORT EQU 03d4h ; порт цветного видеоконтроллера
MONO_PORT EQU 03b4h ; порт монохромного видеоконтроллера
STATUS_PORT EQU 64h ; порт состояния клавиатуры
SHUT_DOWN EQU 0feh ; команда сброса процессора
VIRTUAL_MODE EQU 0001h ; бит перехода в защищённый режим
A20_PORT EQU 0d1h ; команда управления линией A20
A20_ON EQU 0dfh ; открыть A20
A20_OFF EQU 0ddh ; закрыть A20
KBD_PORT_A EQU 60h ; адреса клавиатурных
KBD_PORT_B EQU 61h ; портов
INT_MASK_PORT EQU 21h ; порт для маскирования прерываний
EOI EQU 20 ; команда конца прерывания
MASTER8259A EQU 20 ; первый контроллер прерываний
SLAVE8259A EQU 0a0 ; второй контроллер прерываний
; ------------------------------------------------------------
; Селекторы, определённые в таблице GDT
DS_DESCR = (gdt_ds - gdt_0)
CS_DESCR = (gdt_cs - gdt_0)
SS_DESCR = (gdt_ss - gdt_0)
BIOS_DESCR = (gdt_bio - gdt_0)
CRT_DESCR = (gdt_crt - gdt_0)
MDA_DESCR = (gdt_mda - gdt_0)
; ------------------------------------------------------------
; Маски и инверсные маски для клавиш
L_SHIFT equ 0000000000000001b
NL_SHIFT equ 1111111111111110b
R_SHIFT equ 0000000000000010b
NR_SHIFT equ 1111111111111101b
L_CTRL equ 0000000000000100b
NL_CTRL equ 1111111111111011b
R_CTRL equ 0000000000001000b
NR_CTRL equ 1111111111110111b
L_ALT equ 0000000000010000b
NL_ALT equ 1111111111101111b
R_ALT equ 0000000000100000b
NR_ALT equ 1111111111011111b
CAPS_LOCK equ 0000000001000000b
SCR_LOCK equ 0000000010000000b
NUM_LOCK equ 0000000100000000b
INSERT equ 0000001000000000b
Листинг 3. Демонстрация обработки прерываний и исключений
в защищённом режиме для процессора 80286
-----------------------------------------------------------
IDEAL
RADIX 16
P286
MODEL LARGE
include 'tiny-os.inc'
STACK STACK_SIZE
DATASEG
DSEG_BEG = THIS WORD
real_ss dw ?
real_sp dw ?
real_es dw ?
GDT_BEG = $
LABEL gdtr WORD
gdt_0 desc_struc <0,0,0,0,0>
gdt_gdt desc_struc <GDT_SIZE-1,,,DATA_ACC,0>
gdt_idt desc_struc <IDT_SIZE-1,,,IDT_ACC,0>
gdt_ds desc_struc <DSEG_SIZE-1,,,DATA_ACC,0>
gdt_cs desc_struc <CSEG_SIZE-1,,,CODE_ACC,0>
gdt_ss desc_struc <STACK_SIZE-1,,,DATA_ACC,0>
gdt_bio desc_struc <B_DATA_SIZE-1,B_DATA_ADDR,0,DATA_ACC,0>
gdt_crt desc_struc <CRT_SIZE-1,CRT_LOW,CRT_SEG,DATA_ACC,0>
gdt_mda desc_struc <MONO_SIZE-1,MONO_LOW,CRT_SEG,DATA_ACC,0>
GDT_SIZE = ($ - GDT_BEG)
; Область памяти для загрузки регистра IDTR
idtr idtr_struc <IDT_SIZE,,,0>
; Таблица дескрипторов прерываний
IDT_BEG = $
; ---------------------- Вентили исключений --------------------
idt idt_struc <OFFSET exc_00,CS_DESCR,0,TRAP_ACC,0>
idt_struc <OFFSET exc_01,CS_DESCR,0,TRAP_ACC,0>
idt_struc <OFFSET exc_02,CS_DESCR,0,TRAP_ACC,0>
idt_struc <OFFSET exc_03,CS_DESCR,0,TRAP_ACC,0>
idt_struc <OFFSET exc_04,CS_DESCR,0,TRAP_ACC,0>
idt_struc <OFFSET exc_05,CS_DESCR,0,TRAP_ACC,0>
idt_struc <OFFSET exc_06,CS_DESCR,0,TRAP_ACC,0>
idt_struc <OFFSET exc_07,CS_DESCR,0,TRAP_ACC,0>
idt_struc <OFFSET exc_08,CS_DESCR,0,TRAP_ACC,0>
idt_struc <OFFSET exc_09,CS_DESCR,0,TRAP_ACC,0>
idt_struc <OFFSET exc_0A,CS_DESCR,0,TRAP_ACC,0>
idt_struc <OFFSET exc_0B,CS_DESCR,0,TRAP_ACC,0>
idt_struc <OFFSET exc_0C,CS_DESCR,0,TRAP_ACC,0>
idt_struc <OFFSET exc_0D,CS_DESCR,0,TRAP_ACC,0>
idt_struc <OFFSET exc_0E,CS_DESCR,0,TRAP_ACC,0>
idt_struc <OFFSET exc_0F,CS_DESCR,0,TRAP_ACC,0>
idt_struc <OFFSET exc_10,CS_DESCR,0,TRAP_ACC,0>
idt_struc <OFFSET exc_11,CS_DESCR,0,TRAP_ACC,0>
idt_struc <OFFSET exc_12,CS_DESCR,0,TRAP_ACC,0>
idt_struc <OFFSET exc_13,CS_DESCR,0,TRAP_ACC,0>
idt_struc <OFFSET exc_14,CS_DESCR,0,TRAP_ACC,0>
idt_struc <OFFSET exc_15,CS_DESCR,0,TRAP_ACC,0>
idt_struc <OFFSET exc_16,CS_DESCR,0,TRAP_ACC,0>
idt_struc <OFFSET exc_17,CS_DESCR,0,TRAP_ACC,0>
idt_struc <OFFSET exc_18,CS_DESCR,0,TRAP_ACC,0>
idt_struc <OFFSET exc_19,CS_DESCR,0,TRAP_ACC,0>
idt_struc <OFFSET exc_1A,CS_DESCR,0,TRAP_ACC,0>
idt_struc <OFFSET exc_1B,CS_DESCR,0,TRAP_ACC,0>
idt_struc <OFFSET exc_1C,CS_DESCR,0,TRAP_ACC,0>
idt_struc <OFFSET exc_1D,CS_DESCR,0,TRAP_ACC,0>
idt_struc <OFFSET exc_1E,CS_DESCR,0,TRAP_ACC,0>
idt_struc <OFFSET exc_1F,CS_DESCR,0,TRAP_ACC,0>
; --------------- Вентили аппаратных прерываний ---------------
; int 20h-IRQ0
idt_struc <OFFSET Timer_int,CS_DESCR,0,INT_ACC,0>
; int 21h-IRQ1
idt_struc <OFFSET Keyb_int,CS_DESCR,0,INT_ACC,0>
; int 22h, 23h, 24h, 25h, 26h, 27h-IRQ2-IRQ7
idt_struc 6 dup (<OFFSET dummy_iret0,CS_DESCR,0,INT_ACC,0>)
; int 28h, 29h, 2ah, 2bh, 2ch, 2dh, 2eh, 2fh-IRQ8-IRQ15
idt_struc 8 dup (<OFFSET dummy_iret1,CS_DESCR,0,INT_ACC,0>)
; -------------------- Вентиль прерывания --------------------
; int 30h
idt_struc <OFFSET Int_30h_Entry,CS_DESCR,0,INT_ACC,0>
IDT_SIZE = ($ - IDT_BEG)
CODESEG
PROC start
mov ax,DGROUP
mov ds,ax
call set_crt_base
mov bh, 77h
call clrscr
; Устанавливаем защищённый режим
call set_pmode
call write_hello_msg
; Размаскируем прерывания от таймера и клавиатуры
in al,INT_MASK_PORT
and al,0fch
out INT_MASK_PORT,al
; Ожидаем нажатия на клавишу <ESC>
charin:
int 30h ; ожидаем нажатия на клавишу
; AX - скан-код клавиши,
; BX - состояние переключающих клавиш
cmp al, 1 ; если <ESC> - выход из цикла
jz continue
push bx ; выводим скан-код на экран
mov bx, 0301h ; координаты вывода
call Print_Word
pop bx
mov ax, bx ; выводим состояние
push bx ; переключающих клавиш
mov bx, 0306h
call Print_Word
pop bx
jmp charin
; Следующий байт находится в сегменте кода.
; Он используется нами для демонстрации возникновения
; исключения при попытке записи в сегмент кода.
wrong1 db ?
continue:
; После нажатия на клавишу <ESC> выходим в это место
; программы. Следующие несколько строк демонстрируют
; команды, которые вызывают исключение. Вы можете
; попробовать их, если уберёте символ комментария
; из соответствующей строки.
; Попытка записи за конец сегмента данных. Метка wrong
; находится в самом конце программы.
mov [wrong], al
; Попытка записи в сегмент кода.
; mov [wrong1], al
; Попытка извлечения из пустого стека.
; pop ax
; Загрузка в сегментный регистр неправильного селектора.
; mov ax, 1280h
; mov ds, ax
; Прямой вызов исключения при помощи команды прерывания.
; int 1
call set_rmode ; установка реального режима
mov bh, 07h ; стираем экран и
call clrscr ; выходим в DOS
mov ah,4c
int 21h
ENDP start
MACRO setgdtentry
mov [(desc_struc bx).base_l],ax
mov [(desc_struc bx).base_h],dl
ENDM
; -------------------------------------------
; Установка защищённого режима
; -------------------------------------------
PROC set_pmode NEAR
mov ax,DGROUP
mov dl,ah
shr dl,4
shl ax,4
mov si,ax
mov di,dx
add ax,OFFSET gdtr
adc dl,0
mov bx,OFFSET gdt_gdt
setgdtentry
; Заполняем дескриптор в GDT, указывающий на
; дескрипторную таблицу прерываний
mov ax,si ; загружаем 24-битовый адрес сегмента
mov dx,di ; данных
add ax,OFFSET idt ; адрес дескриптора для IDT
adc dl,0
mov bx,OFFSET gdt_idt
setgdtentry
; Заполняем структуру для загрузки регистра IDTR
mov bx,OFFSET idtr
mov [(idtr_struc bx).idt_l],ax
mov [(idtr_struc bx).idt_h],dl
mov bx,OFFSET gdt_ds
mov ax,si
mov dx,di
setgdtentry
mov bx,OFFSET gdt_cs
mov ax,cs
mov dl,ah
shr dl,4
shl ax,4
setgdtentry
mov bx,OFFSET gdt_ss
mov ax,ss
mov dl,ah
shr dl,4
shl ax,4
setgdtentry
; готовим возврат в реальный режим
push ds
mov ax,40
mov ds,ax
mov [WORD 67],OFFSET shutdown_return
mov [WORD 69],cs
pop ds
cli
mov al,8f
out CMOS_PORT,al
jmp del1
del1:
mov al,5
out CMOS_PORT+1,al
mov ax,[rl_crt] ; сегмент видеопамяти
mov es,ax
call enable_a20 ; открываем линию A20
mov [real_ss],ss ; сохраняем сегментные
mov [real_es],es ; регистры
; -------- Перепрограммируем контроллер прерываний --------
; Устанавливаем для IRQ0-IRQ7 номера прерываний 20h-27h
mov dx,MASTER8259A
mov ah,20h
call set_int_ctrlr
; Устанавливаем для IRQ8-IRQ15 номера прерываний 28h-2Fh
mov dx,SLAVE8259A
mov ah,28h
call set_int_ctrlr
; Загружаем регистры IDTR и GDTR
lidt [FWORD idtr]
lgdt [QWORD gdt_gdt]
; Переключаемся в защищённый режим
mov ax,VIRTUAL_MODE
lmsw ax
; jmp far flush
db 0ea
dw OFFSET flush
dw CS_DESCR
LABEL flush FAR
; Загружаем селекторы в сегментные регистры
mov ax,SS_DESCR
mov ss,ax
mov ax,DS_DESCR
mov ds,ax
; Разрешаем прерывания
sti
ret
ENDP set_pmode
; --------------------------------
; Возврат в реальный режим
; --------------------------------
DATASEG
; Пустой дескриптор для выполнения возврата
; процессора в реальный режим через перевод
; его в состояние отключения.
null_idt idt_struc <>
CODESEG
PROC set_rmode NEAR
mov [real_sp],sp
; Переводим процессор в состояние отключения,
; это эквивалентно аппаратному сбросу, но
; выполняется быстрее.
; Сначала мы загружаем IDTR нулями, затем
; выдаём команду прерывания.
lidt [FWORD null_idt]
int 3
; Это старый способ сброса процессора через
; контроллер клавиатуры.
; mov al,SHUT_DOWN
; out STATUS_PORT,al
rwait:
hlt
jmp rwait
LABEL shutdown_return FAR
in al,INT_MASK_PORT
and al,0
out INT_MASK_PORT,al
mov ax,DGROUP
mov ds,ax
assume ds:DGROUP
cli
mov ss,[real_ss]
mov sp,[real_sp]
mov ax,000dh
out CMOS_PORT,al
sti
mov es,[real_es]
call disable_a20
ret
ENDP set_rmode
; -------------------------------------------------
; Обработка исключений
; -------------------------------------------------
; Обработчики исключений. Записываем в AX номер
; исключения и передаём управление процедуре
; shutdown
LABEL exc_00 WORD
mov ax,0
jmp shutdown
LABEL exc_01 WORD
mov ax,1
jmp shutdown
LABEL exc_02 WORD
mov ax,2
jmp shutdown
LABEL exc_03 WORD
mov ax,3
jmp shutdown
LABEL exc_04 WORD
mov ax,4
jmp shutdown
LABEL exc_05 WORD
mov ax,5
jmp shutdown
LABEL exc_06 WORD
mov ax,6
jmp shutdown
LABEL exc_07 WORD
mov ax,7
jmp shutdown
LABEL exc_08 WORD
mov ax,8
jmp shutdown
LABEL exc_09 WORD
mov ax,9
jmp shutdown
LABEL exc_0A WORD
mov ax,0ah
jmp shutdown
LABEL exc_0B WORD
mov ax,0bh
jmp shutdown
LABEL exc_0C WORD
mov ax,0ch
jmp shutdown
LABEL exc_0D WORD
mov ax,0dh
jmp shutdown
LABEL exc_0E WORD
mov ax,0eh
jmp shutdown
LABEL exc_0F WORD
mov ax,0fh
jmp shutdown
LABEL exc_10 WORD
mov ax,10h
jmp shutdown
LABEL exc_11 WORD
mov ax,11h
jmp shutdown
LABEL exc_12 WORD
mov ax,12h
jmp shutdown
LABEL exc_13 WORD
mov ax,13h
jmp shutdown
LABEL exc_14 WORD
mov ax,14h
jmp shutdown
LABEL exc_15 WORD
mov ax,15h
jmp shutdown
LABEL exc_16 WORD
mov ax,16h
jmp shutdown
LABEL exc_17 WORD
mov ax,17h
jmp shutdown
LABEL exc_18 WORD
mov ax,18h
jmp shutdown
LABEL exc_19 WORD
mov ax,19h
jmp shutdown
LABEL exc_1A WORD
mov ax,1ah
jmp shutdown
LABEL exc_1B WORD
mov ax,1bh
jmp shutdown
LABEL exc_1C WORD
mov ax,1ch
jmp shutdown
LABEL exc_1D WORD
mov ax,1dh
jmp shutdown
LABEL exc_1E WORD
mov ax,1eh
jmp shutdown
LABEL exc_1F WORD
mov ax,1fh
jmp shutdown
DATASEG
exc_msg db "Exception ...., ....:.... code ..... Press any key... "
CODESEG
; -----------------------------------------------
; Вывод на экран номера исключения, кода ошибки,
; дампа регистров и возврат в реальный режим.
; -----------------------------------------------
PROC shutdown NEAR
call rdump ; дамп регистров процессора
push ax
call beep ; звуковой сигнал
; Выводим сообщение об исключении
mov ax,[vir_crt]
mov es,ax
mov bx,1d
mov ax,4
mov si,OFFSET exc_msg
mov dh,74h
mov cx, SIZE exc_msg
call writexy
pop ax
mov bx, 040bh ; номер исключения
call Print_Word
pop ax
mov bx, 0420h ; код ошибки
call Print_Word
pop ax
mov bx, 0416h ; смещение
call Print_Word
pop ax
mov bx, 0411h ; селектор
call Print_Word
call set_rmode ; возвращаемся в реальный режим
mov ax, 0 ; ожидаем нажатия на клавишу
int 16h
mov bh, 07h
call clrscr
mov ah,4Ch
int 21h
ENDP shutdown
; -------------------------------------------------
; Перепрограммирование контроллера прерываний
; На входе: DX - порт контроллера прерывания
; AH - начальный номер прерывания
; -------------------------------------------------
PROC set_int_ctrlr NEAR
mov al,11
out dx,al
jmp SHORT $+2
mov al,ah
inc dx
out dx,al
jmp SHORT $+2
mov al,4
out dx,al
jmp SHORT $+2
mov al,1
out dx,al
jmp SHORT $+2
mov al,0ff
out dx,al
dec dx
ret
ENDP set_int_ctrlr
; -------------------------------
; Разрешение линии A20
; -------------------------------
PROC enable_a20 NEAR
mov al,A20_PORT
out STATUS_PORT,al
mov al,A20_ON
out KBD_PORT_A,al
ret
ENDP enable_a20
; -------------------------------
; Запрещение линии A20
; -------------------------------
PROC disable_a20 NEAR
mov al,A20_PORT
out STATUS_PORT,al
mov al,A20_OFF
out KBD_PORT_A,al
ret
ENDP disable_a20
; ---------- Обработчик аппаратных прерываний IRQ2-IRQ7
PROC dummy_iret0 NEAR
push ax
; Посылаем сигнал конца прерывания в первый контроллер 8259A
mov al,EOI
out MASTER8259A,al
pop ax
iret
ENDP dummy_iret0
; ---------- Обработчик аппаратных прерываний IRQ8-IRQ15
PROC dummy_iret1 NEAR
push ax
; Посылаем сигнал конца прерывания в первый
; и второй контроллеры 8259A
mov al,EOI
out MASTER8259A,al
out SLAVE8259A,al
pop ax
iret
ENDP dummy_iret1
; ------------------------------------------
; Процедура выдаёт короткий звуковой сигнал
; ------------------------------------------
PROC beep NEAR
push ax bx cx
in al,KBD_PORT_B
push ax
mov cx,80
beep0:
push cx
and al,11111100b
out KBD_PORT_B,al
mov cx,60
idle1:
loop idle1
or al,00000010b
out KBD_PORT_B,al
mov cx,60
idle2:
loop idle2
pop cx
loop beep0
pop ax
out KBD_PORT_B,al
pop cx bx ax
ret
ENDP beep
; ------------------------------------------------
; Процедура задерживает выполнение программы
; на некоторое время, зависящее от быстродействия
; процессора.
; ------------------------------------------------
PROC pause NEAR
push cx
mov cx,10
ploop0:
push cx
xor cx,cx
ploop1:
loop ploop1
pop cx
loop ploop0
pop cx
ret
ENDP pause
; ------------------------------------------
; Процедуры для работы с клавиатурой
; ------------------------------------------
DATASEG
key_flag db 0
key_code dw 0
ext_scan db 0
keyb_status dw 0
CODESEG
; ----------------------------------------------
; Обработчик аппаратного прерывания клавиатуры
; ----------------------------------------------
PROC Keyb_int NEAR
call beep ; выдаём звуковой сигнал
push ax
mov al, [ext_scan] ; расширенный скан-код
cmp al, 0 ; или обычный ?
jz normal_scan1
; --------- обработка расширенного скан-кода -------------
cmp al, 0e1h ; это клавиша <Pause>?
jz pause_key
in al, 60h ; вводим скан-код
cmp al, 2ah ; игнорируем префикс 2Ah
jz intkeyb_exit_1
cmp al, 0aah ; игнорируем отпускание
jz intkeyb_exit_1 ; клавиш
mov ah, [ext_scan] ; записываем скан-код и
call Keyb_PutQ ; расширенный скан-код
; в "очередь", состоящую
; из одного слова
mov al, 0 ; сбрасываем признак
mov [ext_scan], al ; получения расширенного
jmp intkeyb_exit ; скан-кода
pause_key: ; обработка клавиши <Pause>
in al, 60h ; вводим скан-код
cmp al, 0c5h ; если это код <Pause>,
jz pause_key1 ; записываем его в очередь,
cmp al, 45h ; иначе игнорируем
jz pause_key1
jmp intkeyb_exit
pause_key1:
mov ah, [ext_scan] ; запись в очередь
call Keyb_PutQ ; кода клавиши <Pause>
mov al, 0 ; сбрасываем признак
mov [ext_scan], al ; получения расширенного
jmp intkeyb_exit ; скан-кода
; --------- обработка обычного скан-кода -------------
normal_scan1:
in al, 60h ; вводим скан-код
cmp al, 0feh ; игнорируем FEh
jz intkeyb_exit
cmp al, 0e1h ; расширенный скан-код?
jz ext_key ; если да, то на обработку
; расширенного скан-кода
cmp al, 0e0h
jnz normal_scan
ext_key:
mov [ext_scan], al ; устанавливаем признак
jmp intkeyb_exit ; расширенного скан-кода
; Сброс признака расширенного скан-кода и выход
intkeyb_exit_1:
mov al, 0
mov [ext_scan], al
jmp intkeyb_exit
; Запись нормального скан-кода в очередь и выход
normal_scan:
mov ah, 0
call Keyb_PutQ
intkeyb_exit:
in al, 61h ; разблокируем клавиатуру
mov ah, al
or al, 80h
out 61h, al
xchg ah, al
out 61h, al
mov al,EOI ; посылаем сигнал конца
out MASTER8259A,al ; прерывания
pop ax
sti
iret
ENDP Keyb_int
; ---------------------------------------------------
; Запись скан-кода и расширенного скан-кода в
; "буфер", состоящий из одного слова.
; ---------------------------------------------------
PROC Keyb_PutQ NEAR
push ax
mov [key_code], ax ; записываемый код
; ------- Обрабатываем переключающие клавиши ---------
cmp ax, 002ah ; L_SHIFT down
jnz @@kb1
mov ax, [keyb_status]
or ax, L_SHIFT
mov [keyb_status], ax
jmp keyb_putq_exit
@@kb1:
cmp ax, 00aah ; L_SHIFT up
jnz @@kb2
mov ax, [keyb_status]
and ax, NL_SHIFT
mov [keyb_status], ax
jmp keyb_putq_exit
@@kb2:
cmp ax, 0036h ; R_SHIFT down
jnz @@kb3
mov ax, [keyb_status]
or ax, R_SHIFT
mov [keyb_status], ax
jmp keyb_putq_exit
@@kb3:
cmp ax, 00b6h ; R_SHIFT up
jnz @@kb4
mov ax, [keyb_status]
and ax, NR_SHIFT
mov [keyb_status], ax
jmp keyb_putq_exit
@@kb4:
cmp ax, 001dh ; L_CTRL down
jnz @@kb5
mov ax, [keyb_status]
or ax, L_CTRL
mov [keyb_status], ax
jmp keyb_putq_exit
@@kb5:
cmp ax, 009dh ; L_CTRL up
jnz @@kb6
mov ax, [keyb_status]
and ax, NL_CTRL
mov [keyb_status], ax
jmp keyb_putq_exit
@@kb6:
cmp ax, 0e01dh ; R_CTRL down
jnz @@kb7
mov ax, [keyb_status]
or ax, R_CTRL
mov [keyb_status], ax
jmp keyb_putq_exit
@@kb7:
cmp ax, 0e09dh ; R_CTRL up
jnz @@kb8
mov ax, [keyb_status]
and ax, NR_CTRL
mov [keyb_status], ax
jmp keyb_putq_exit
@@kb8:
cmp ax, 0038h ; L_ALT down
jnz @@kb9
mov ax, [keyb_status]
or ax, L_ALT
mov [keyb_status], ax
jmp keyb_putq_exit
@@kb9:
cmp ax, 00b8h ; L_ALT up
jnz @@kb10
mov ax, [keyb_status]
and ax, NL_ALT
mov [keyb_status], ax
jmp keyb_putq_exit
@@kb10:
cmp ax, 0e038h ; R_ALT down
jnz @@kb11
mov ax, [keyb_status]
or ax, R_ALT
mov [keyb_status], ax
jmp keyb_putq_exit
@@kb11:
cmp ax, 0e0b8h ; R_ALT up
jnz @@kb12
mov ax, [keyb_status]
and ax, NR_ALT
mov [keyb_status], ax
jmp keyb_putq_exit
@@kb12:
cmp ax, 003ah ; CAPS_LOCK up
jnz @@kb13
mov ax, [keyb_status]
xor ax, CAPS_LOCK
mov [keyb_status], ax
jmp keyb_putq_exit
@@kb13:
cmp ax, 00bah ; CAPS_LOCK down
jnz @@kb14
jmp keyb_putq_exit
@@kb14:
cmp ax, 0046h ; SCR_LOCK up
jnz @@kb15
mov ax, [keyb_status]
xor ax, SCR_LOCK
mov [keyb_status], ax
jmp keyb_putq_exit
@@kb15:
cmp ax, 00c6h ; SCR_LOCK down
jnz @@kb16
jmp keyb_putq_exit
@@kb16:
cmp ax, 0045h ; NUM_LOCK up
jnz @@kb17
mov ax, [keyb_status]
xor ax, NUM_LOCK
mov [keyb_status], ax
jmp keyb_putq_exit
@@kb17:
cmp ax, 00c5h ; NUM_LOCK down
jnz @@kb18
jmp keyb_putq_exit
@@kb18:
cmp ax, 0e052h ; INSERT up
jnz @@kb19
mov ax, [keyb_status]
xor ax, INSERT
mov [keyb_status], ax
jmp keyb_putq_exit
@@kb19:
cmp ax, 0e0d2h ; INSERT down
jnz @@kb20
jmp keyb_putq_exit
@@kb20:
test ax, 0080h ; фильтруем отжатия клавиш
jnz keyb_putq_exit
mov al, 0ffh ; устанавиваем признак
mov [key_flag], al ; готовности для чтения
; символа из "буфера"
keyb_putq_exit:
pop ax
ret
ENDP Keyb_PutQ
; -----------------------------------------------------
; Программное прерывание, предназначенное для чтения
; символа из буфера клавиатуры. По своим функциям
; напоминает прерывание INT 16h реального режима.
; В AX возвращается скан-код нажатой клавиши,
; в BX - состояние переключающих клавиш.
; -----------------------------------------------------
PROC Int_30h_Entry NEAR
push dx ; запрещаем прерывания и
cli ; сбрасываем признак
mov al, 0 ; готовности скан-кода
mov [key_flag], al ; в буфере клавиатуры
; Ожидаем прихода прерывания от клавиатуры.
; Процедура клавиатурного прерывания установит
; признак в переменной key_flag.
keyb_int_wait:
sti ; разрешаем прерывания
nop ; ждём прерывание
nop
cli ; запрещаем прерывания
mov al, [key_flag] ; и опрашиваем флаг
cmp al, 0 ; готовности скан-кода
jz keyb_int_wait
mov al, 0 ; сбрасываем флаг
mov [key_flag], al ; готовности
mov ax, [key_code] ; записываем скан-код
mov bx, [keyb_status] ; и состояние переключающих
; клавиш
sti ; разрешаем прерывания
pop dx
iret
ENDP Int_30h_Entry
; -------------------------------------------
; TIMER section
; -------------------------------------------
DATASEG
timer_cnt dw 0
CODESEG
PROC Timer_int NEAR
cli
push ax
; Увеличиваем содержимое счётчика времени
mov ax, [timer_cnt]
inc ax
mov [timer_cnt], ax
; Примерно раз в секунду выдаём звуковой сигнал
test ax, 0fh
jnz timer_exit
call beep
timer_exit:
; Посылаем команду конца прерывания
mov al,EOI
out MASTER8259A,al
pop ax
sti
iret
ENDP Timer_int
; --------------------------------------------------
; Процедуры обслуживания видеоконтроллера
; --------------------------------------------------
DATASEG
columns db 80d
rows db 25d
rl_crt dw COLOR_SEG
vir_crt dw CRT_DESCR
curr_line dw 0d
text_buf db " "
CODESEG
; -----------------------------------------
; Определение адреса видеопамяти
; -----------------------------------------
PROC set_crt_base NEAR
mov ax,40
mov es,ax
mov bx,[WORD es:4a]
mov [columns],bl
mov bl,[BYTE es:84]
inc bl
mov [rows],bl
mov bx,[WORD es:PORT_6845]
cmp bx,COLOR_PORT
je color_crt
mov [rl_crt],MONO_SEG
mov [vir_crt],MDA_DESCR
color_crt:
ret
ENDP set_crt_base
; -------------------------------------
; Запись строки в видеопамять
; -------------------------------------
PROC writexy NEAR
push si
push di
mov dl,[columns]
mul dl
add ax,bx
shl ax,1
mov di,ax
mov ah,dh
write_loop:
lodsb
stosw
loop write_loop
pop di
pop si
ret
ENDP writexy
; ---------------------------------------
; Стирание экрана (в реальном режиме)
; ---------------------------------------
PROC clrscr NEAR
xor cx,cx
mov dl,[columns]
mov dh,[rows]
mov ax,0600
int 10
ret
ENDP clrscr
DATASEG
hello_msg db " Protected mode monitor *TINY/OS*,
v.1.1 for CPU 80286 ¦ © Frolov A.V., 1992 "
CODESEG
; ------------------------------------
; Вывод начального сообщения
; в защищённом режиме
; ------------------------------------
PROC write_hello_msg NEAR
mov ax,[vir_crt]
mov es,ax
mov si,OFFSET hello_msg
mov bx,0
mov ax,[curr_line]
inc [curr_line]
mov cx,SIZE hello_msg
mov dh,30h
call writexy
call beep
ret
ENDP write_hello_msg
; ----------------------------------------------
; Процедура выводит на экран содержимое AX
; (x,y) = (bh, bl)
; ----------------------------------------------
PROC Print_Word near
push ax
push bx
push dx
push ax
mov cl,8
rol ax,cl
call Byte_to_hex
mov [text_buf], dh
mov [text_buf+1], dl
pop ax
call Byte_to_hex
mov [text_buf+2], dh
mov [text_buf+3], dl
mov si, OFFSET text_buf
mov dh, 70h
mov cx, 4
mov al, bh
mov ah, 0
mov bh, 0
call writexy
pop dx
pop bx
pop ax
ret
ENDP Print_Word
DATASEG
tabl db '0123456789ABCDEF'
CODESEG
; -----------------------------------------
; Преобразование байта в шестнадцатеричный
; символьный формат
; al - входной байт
; dx - выходное слово
; -----------------------------------------
PROC Byte_to_hex near
push cx
push bx
mov bx, OFFSET tabl
push ax
and al,0fh
xlat
mov dl,al
pop ax
mov cl,4
shr al,cl
xlat
mov dh,al
pop bx
pop cx
ret
ENDP Byte_to_hex
DATASEG
reg_title db " CS IP AX BX CX DX SP BP SI DI "
; .... .... .... .... .... .... .... ....
sreg_title db " DS ES SS FLAGS "
; .... .... .... .... .... .... .... ....
CODESEG
; ------------------------------------------------
; Вывод на экран содержимого регистров процессора
; ------------------------------------------------
PROC rdump NEAR
pushf
pusha
mov di, es
mov ax,[vir_crt]
mov es,ax
mov si,OFFSET reg_title
mov bx,1 ; (X,Y) = (AX,BX)
mov ax,6
mov cx,SIZE reg_title
mov dh,1fh ; чёрный на голубом фоне
call writexy
; Выводим содержимое всех регистров
mov ax,cs ; cs
mov bx, 0702h
call Print_Word
mov bp, sp
mov ax, [bp+18d] ; ip
mov bx, 0708h
call Print_Word
mov bx, 070eh
mov ax,[bp+14d] ; ax
call Print_Word
mov bx, 0714h
mov ax,[bp+8d] ; bx
call Print_Word
mov bx, 071ah
mov ax,[bp+12d] ; cx
call Print_Word
mov bx, 0720h
mov ax,[bp+10d] ; dx
call Print_Word
mov ax,bp
add ax,20d ; sp
mov bx, 0726h
call Print_Word
mov ax,[bp+4d] ; bp
mov bx, 072ch
call Print_Word
mov bx, 0732h
mov ax,[bp+2] ; si
call Print_Word
mov bx, 0738h
mov ax, [bp] ; di
call Print_Word
mov si,OFFSET sreg_title
mov bx,1
mov ax,8
mov cx,SIZE sreg_title
mov dh,1fh
call writexy
mov bx, 0902h
mov ax, ds ; ds
call Print_Word
mov bx, 0908h
mov ax, di ; es
call Print_Word
mov bx, 090eh
mov ax,ss ; ss
call Print_Word
mov bx, 0914h
mov ax, [bp+16d] ; flags
call Print_Word
; Восстанавливаем содержимое регистров
popa
popf
ret
ENDP rdump
CSEG_SIZE = ($ - start)
DATASEG
DSEG_SIZE = ($ - DSEG_BEG)
wrong db ?
END start
|