!!! Замечание !!!
Все примеры,
приведенные в этом приложении, являются своего рода операционными системами
защищенного режима. Следовательно, их исполнение должно начинаться в реальном
режиме процессора. То есть в среде «чистого» MS-DOS (без менеджеров оперативной памяти, таких как Himem, EMM386, QEMM и т.п.) или в режиме «Safe mode command prompt only» операционных систем Windows 9x. В операционной
системе Windows 2000 (и ей подобных – Win NT/XP) –
работа этих примеров, по определению, не возможна.
Рассмотрим фрагменты операционной
мини-системы, которые подготавливают структуры данных защищенного режима,
переводят микропроцессор в этот режим, имитируют работу путем вывода некоторого
сообщения, после чего переводят микропроцессор обратно в реальный режим и
заканчивают выполнение. Работа с системой прерываний в данном примере не
затрагивается (см. пример 2). Перечислим и обсудим действия,
необходимые для обеспечения функционирования этой программы:
·
настроить
сегментные регистры;
·
выполнить
собственно содержательную работу программы; в нашем случае мы просто обозначим
сам факт успешного перехода в защищенный режим;
·
подготовиться
к возврату в реальный режим;
·
запретить
аппаратные прерывания;
Опишем каждый шаг подробнее (см. также
исходный текст программы).
Подготовка таблиц глобальных дескрипторов GDT
Так как наша программа претендует лишь на роль фрагмента
операционной системы, то достаточно
определить пока только одну дескрипторную таблицу — глобальную дескрипторную
таблицу GDT. Прерывания
мы пока обрабатывать не будем. Таблицу LDT есть смысл применять, когда в
системе работают несколько задач и необходимо изолировать их друг от друга.
Пока от нас этого не требуется.
Определимся теперь с набором дескрипторов в таблице GDT, которые
понадобятся для нашей программы:
Форматы дескриптора сегмента
описаны в главе 2. В программе его удобнее описать в виде структуры:
Descr struc
Limit dw 0
Base_1 dw 0
Base_2 db 0
Atr db 0
Lim_atr db 0
Base_3 db 0
Ends
Используя этот шаблон структуры, опишем таблицу GDT как массив
структур:
gdt_seg segment para
gdt_0
descr <0,0,0,0,0,0>
gdt_gdt_8 descr
<0,0,0,0,0,0>
gdt_ldt_10 descr
<0,0,0,0,0,0>
gdt_ds_18 descr
<0,0,0,0,0,0>
gdt_es_vbf_20 descr
<0,0,0,0,0,0>
gdt_ss_28 descr
<0,0,0,0,0, 0>
gdt_cs_30 descr
<0,0,0,0,0,0>
gdt_size=$-gdt_0-1 ;определение
размера таблицы GDT
gdt_seg ends
Инициализация дескрипторов в таблице GDT
Поле limit – размер сегмента.
Поля base_1, base_2, base_3 – поля 32-х разрядного адреса.
Фрагмент программы заполнения поля базового адреса для дескриптора gdt_gdt_8 может
выглядеть так:
хоr еах,еах
mov ax,gdt_seg
;адрес сегмента в ах
;сдвигом на 4 разряда получим
физический
;20-разрядный адрес
сегмента gdt_seg:
shl eax,4
mov base_1,ах
rol eax,16
; меняем для получения оставшейся части адреса
mov base_2,al
Так как эту операцию нам придется проделывать несколько раз, для
каждого из сегментов программы, то для удобства работы и повышения наглядности
оформим этот фрагмент в виде макроса load_addr:
load_addr macro descr,seg_addr,seg_size
mov descr.limit,seg_size
xor eax,eax
mov ax,seg_adr
shl eax,4
mov descr.base_1,ax
rol eax,16
mov descr.base_2,al
endm
Также здесь производится и инициализация поля размера.
Поле atr - байт атрибутов. Его структура определяется типом сегмента.
Понятно, что для конкретного сегмента его значение будет всегда константой.
Значение этого байта в целом удобнее формировать как логическую сумму нужных
значений его полей для определения типа конкретного сегмента.
;биты 0, 4, 5, 6, 7 - постоянная часть байта ATR
;для всех типов сегментов (в нашем примере)
const equ 10010000b
;бит 1 - доступность сегментов по чтению/записи
code_r_n equ 00000000b ;сегмент кода: чтение запрещено
code_r_y equ 00000010b ;сегмент кода: чтение разрешено
data_wm_n equ 00000000b ;сегмент данных: модификация запрещена data_wm_y equ
00000010b ;сегмент данных:
модификация разрешена
;бит 2 - тип сегмента
code_n equ 00000000b ;обычный сегмент кода
code_p equ 00000100b ;подчиненный сегмент кода
_data equ 00000000b ;сегмент данных
_stack equ 00000100b ;сегмент стека
;бит 3 – предназначение
_code equ 00001000b ;сегмент кода
data_stk equ 00000000b ;сегмент данных или стека
Теперь для получения значения атрибута для нужного сегмента
достаточно подобрать нужные константы по битам и выполнить подсчет суммы, как,
например, для дескриптора gdt_gdt_8:
atr = const or data_wm_y or _data or data_stk
Значение atr для дескриптора сегмента gdt_gdt_8 будет равно 10010010 = 92h. Для
удобства использования можно создать макрос, который будет создавать константу
с именем, состоящим из двух частей: приставки atr и имени дескриптора, для которого
формируется байт атрибута (к примеру, для дескриптора gdt_gdt_8 это будет atr_gdt_gdt_8):
atr macro descr,bit1,bit2, bit3
atr&descr=const or bit1 or bit2 or bit3
endm
Имена формируемых констант нужно указывать при инициализации структур
для каждого дескриптора, к примеру для gdt_gdt_8:
gdt_seg segment para
gdt_0 descr <0,0,0,0,0,0>
atr gdt_gdt_8,data_wm_y,_data,data_stk
gdt_gdt_8 descr
<0,0,0,atr_gdt_gdt_8,0,0>
gdt_seg ends
Поле lim_atr - байт, состоящий из четырех
старших битов размера сегмента и четырех атрибутов. В нашем случае размер
сегмента небольшой, то есть четыре старших бита размера равны 0. И оставшиеся
биты атрибутов для нашего случая также нулевые;
Поле base_3 содержит старший байт 4-байтового физического адреса сегмента.
Так как мы начинаем работу в реальном режиме, где размер максимального
физического адреса не превышает 20 бит, то этот байт также будет нулевым.
Таким образом, в нашей программе инициализации будут подлежать три
поля: limit, три первых
байта адреса base_1 и base_2 и байт
атрибута atr.
Загрузка регистра gdtr
Для загрузки регистра gdtr существует команда lgdt:
lgdt адрес_48-битного_поля (Load GDT register) -
загрузить регистр gdtr.
Команда lgdt загружает системный регистр gdtr содержимым 6-байтового поля,
адрес которого указан в качестве операнда. Вначале необходимо сформировать поле
из шести байт со структурой, аналогичной формату регистра gdtr, а затем
указать адрес этого поля в качестве операнда команды lgdt.
Для резервирования поля из шести байт (48 бит) TASM поддерживает специальные
директивы резервирования и инициализации данных — dp и df. После выделения — с помощью
одной из этих директив — области памяти в сегменте данных необходимо
сформировать в этой области указатель на начало таблицы GDT и ее
размер. Но удобнее использовать структуру. Пример ее использования показан в
следующем фрагменте программы:
point struc
lim dw 0
adr dd 0
ends
data segment
point_gdt point <gdt_size,0>
;...
code segment
;...
xor eax,eax
mov ax,gdt_seg
shl eax,4
mov dword ptr point_gdt.adr,eax
lgdt pword point_gdt
Запрет обработки аппаратных прерываний
Обработке прерываний посвящен следующий пример, здесь же мы
запретим прерывания, так как без соответствующей настройки первое же прерывание
от таймера, которое происходит 18,2 раза в секунду, «подвесит» компьютер. Для
этого есть несколько способов: прямым программированием контроллера прерываний
и командой микропроцессора cli. Можно использовать любой, только не нужно их сочетать.
Переключение микропроцессора в защищенный режим
Теперь у нас все готово для того, чтобы корректно перейти в
защищенный режим. Специальных команд микропроцессора для выполнения такого
перехода нет. О том, что микропроцессор находится в защищенном режиме, говорит
лишь состояние бита PE в регистре сr0. Установить этот бит можно двумя способами:
· Непосредственной установкой бита PE в регистре
сr0. Состояние
этого бита управляет режимами работы микропроцессора: если PE = 0, то микропроцессор работает
в реальном режиме, если PE = 1, то микропроцессор работает в защищенном
режиме;
· Использованием функции 89h прерывания
15h BIOS. Мы опишем
оба способа, но использовать будем первый.
Регистр сr0 программно доступен (в реальном режиме и на нулевом кольце в защищенном),
поэтому установить бит PE можно, используя обычные команды ассемблера:
mov eax,cr0
or
eax,0001h
mov cr0,eax
Последняя команда mov переводит микропроцессор в защищенный режим.
Функция 89h прерывания 15h выполняет это и некоторые другие действия неявно. Прежде чем
вызывать это прерывание, необходимо посредством регистров сообщить ему
следующее:
·
ah = 89h;
·
bl = новый номер для аппаратного
прерывания уровня irq0. Уровни irql...7 будут иметь следующие по порядку номера;
·
bh = новый номер для аппаратного
прерывания уровня irq8. Уровни irq9...f будут иметь следующие по порядку номера;
·
ds:si = адрес GDT для
защищенного режима;
·
сх = адрес
первой выполняемой команды в защищенном режиме,
Эта функция предполагает, что дескрипторы в таблице GDT расположены в определенной
последовательности:
·
0h — пустой
дескриптор;
·
8h —
дескриптор таблицы GDT;
·
10h —
дескриптор таблицы LDT;
·
18h — дескриптор сегмента данных, на
него указывает селектор в регистре ds;
·
20h — дескриптор дополнительного
сегмента данных, на него указывает селектор в регистре es;
·
28h — дескриптор сегмента стека, на
него указывает селектор в регистре ss;
·
30h — дескриптор сегмента кода, на
него указывает селектор в регистре cs;
·
остальные
дескрипторы.
В нашей таблице GDT этот порядок следования соблюден, поэтому фрагмент программы
перевода микропроцессора в защищенный режим может быть следующим:
code segment
mov ah,89h
mov bl,20h
mov bh,28h
mov ax,gdt_seg
mov ds,ax
mov si,0
lea cx,protect
int 15h
protect:
;работа в защищенном режиме
Работа в защищенном режиме
Настройка сегментных регистров
Как только микропроцессор оказывается в защищенном режиме, первую
же команду он пытается выполнить традиционно: по содержимому пары cs:ip определить
ее адрес, выбрать ее и т. д. Но содержимое cs должно быть индексом, указывающим
на дескриптор сегмента кода в таблице GDT. Но пока это не так, так как в
данный момент cs все еще
содержит физический адрес параграфа сегмента кода, как этого требуют правила
формирования физического адреса в реальном режиме. То же самое происходит и с
другими регистрами. Но если содержимое других сегментные
регистров можно подкорректировать в программе, то в случае с регистром cs этого
сделать нельзя, так как он в защищенном режиме программно недоступен. Нужно
«помочь» микропроцессору сориентироваться в этой затруднительной ситуации.
Вспомним, что действие команд перехода основано как раз на изменении
содержимого регистров cs и iр. Команды ближнего перехода изменяют только содержимое eip\ip, а команды
дальнего перехода — оба регистра cs и eip\ip. Воспользуемся этим
обстоятельством, вдобавок существует и еще одно свойство команд передачи
управления — они сбрасывают конвейер микропроцессора, подготавливая его тем
самым к приему команд, которые сформированы уже по правилам защищенного режима.
Это же обстоятельство заставляет нас впрямую моделировать команду
межсегментного перехода, чтобы поместить правильное значение селектора в
сегментный регистр cs. Это можно сделать так:
сode segment
db 0eah ; машинный код команды jmp
dw offset protect ; смещение метки перехода в сегменте команд
dw 30h ; селектор сегмента кода в таблице GDT
protect:
; загрузить селекторы для остальных дескрипторов
mov ax,18h
mov ds,ax ; сегментный регистр данных
mov ax,28h
mov ss,ax ; сегментный регистр стека
mov ax,20h
mov es,ax ; дополнительный сегмент данных:
; для указания на видеобуфер
Но за кадром остался один момент, на который нужно обязательно
обратить внимание. В микропроцессоре каждому сегментному регистру соответствует
свой теневой регистр дескриптора. Этот
регистр имеет размер 64 бит и формат дескриптора сегмента. Смена содержимого теневых
регистров производится автоматически всякий раз при смене содержимого
соответствующего сегментного регистра. Последние наши действия по изменению
содержимого сегментных регистров привели к тому, что мы неявно записали в
теневые регистры микропроцессора содержимое соответствующих дескрипторов из GDT.
Программисту теневые регистры недоступны, с ними работает только микропроцессор.
Если есть необходимость изменить их содержимое, то для этого нужно сначала
изменить сам дескриптор, а затем загрузить соответствующий ему селектор в
нужный сегментный регистр.
Выполнение собственно программы защищенного режима
Наша программа должна будет выполнить несколько действий. При
этом, есть существенные ограничения на ее работу. Прежде всего, это связано с
тем, что пока мы договорились не использовать прерывания. Так что придется
использовать прямой доступ к аппаратуре, используя пространство портов
ввода-вывода (см. исходный код).
Подготовка к возврату в реальный режим
Здесь возникает примерно та же проблема с сегментными регистрами,
что была при входе в защищенный режим. Мы упоминали уже о теневых регистрах
микропроцессора, но не сказали того, что микропроцессор использует их, даже
работая в реальном режиме. При этом поля этих регистров заполнены, конечно, в соответствии
с требованиями реального режима:
·
предел
должен быть равен 64 К = 0ffffh;
·
бит G = 0
(значение размера в поле предела) — это значение в байтах;
·
байт
атрибута равен 10010010 = 92h;
·
базовый
адрес значения не имеет.
Следовательно, перед переходом в реальный режим нужно сформировать
эти значения в соответствующих дескрипторах и сделать актуальными эти изменения
в теневых регистрах, для чего нужно перезагрузить сегментные регистры. После
этого можно переходить в реальный режим. В программе все эти действия могут
выглядеть так:
code segment
;...
; мы в защищенном режиме и начинаем переход в реальный режим
;загрузим значение 0ffffh в поле предела
mov gdt_ds_18.limit,0ffffh
;для регистров cs, ss, es, fs и gs аналогично
;селектор - в регистр данных, чтобы обновить теневой регистр для ds
mov ax,18
mov ds,ax
;для регистров ss, es, fs и gs аналогично
;для регистра cs загрузку селектора проведем
;особым способом - командой jmp far
db 0eah
dw
offset jump
dw
30h
jump:
;можно переходить в реальный режим
Запрет аппаратных прерываний
Для нашей программы этого делать и не нужно, так как мы их и не
разрешали. Но в целом надо иметь в виду, что перед переходом в реальный режим кроме
настройки сегментных регистров нужно будет перестраивать и систему прерываний.
Для этого на участке программы, производящем соответствующие действия,
необходимо запретить аппаратные прерывания.
Переключение микропроцессора в реальный режим
Для этого необходимо сбросить нулевой бит регистра сr0 (только
для 386+). Это можно сделать несколькими способами. К примеру:
mov eax,cr0
and al,0feh
mov cr0,eax
После сброса бита PE микропроцессор снова оказался в реальном
режиме.
Настройка сегментных регистров
После перехода в реальный режим опять возникает проблема с
содержимым сегментных регистров. Решается она ранее описанным способом:
;...
;моделирование команды дальнего перехода для загрузки cs
db
0eah
dw
real_mode
dw
code real_mode:
mov
ax,data
mov ds,ax
mov ax,stk
mov ss,ax
Разрешение прерываний
Теперь можно разрешить прерывания. Так как в системе прерываний мы
ничего не меняли, то действия наши тоже были простейшими: а) запрещение аппаратных
прерываний для того, чтобы на время смены режима работы микропроцессора нас не
беспокоило прерывание от таймера, б) последующее разрешение прерываний после
возврата в реальный режим. На практике, конечно, без прерываний не обойтись –
рассмотрим этот вопрос на следующем примере.
Этот пример поясняет работу
системы прерываний. Так как при этом возникает достаточно много нюансов, то мы
рассмотрим задачу, которая бы отражала суть большинства из них. В нашей
программе мы будем обрабатывать одно аппаратное прерывание (от таймера), две
разнотипные исключительные ситуации и обычное пользовательское прерывание. На
примере этой задачи будут показаны все аспекты обработки прерываний в
защищенном режиме, за исключением использования задач в качестве обработчика
прерываний. Вначале, так же как и для программы перехода в защищенный режим,
обсудим некоторые ключевые моменты. Наша программа по-прежнему является
операционной мини-системой, так как она сама обеспечивает свое функционирование
на «чистом» микропроцессоре. Поэтому за основу мы возьмем предыдущую программу
и дополним ее функционально, научив обрабатывать некоторые прерывания.
В общем случае для того, чтобы
сделать возможным обработку прерываний в защищенном режиме, необходимо выполнить
следующие действия:
1.
Инициализировать
таблицу IDT.
2.
Составить
процедуры обработчиков прерываний.
3.
Запретить
аппаратные прерывания.
4.
Перепрограммировать
контроллер прерываний i8259A.
5.
Загрузить
регистр IDTR адресом и размером таблицы IDT.
6.
Перейти в
защищенный режим.
7.
Разрешить
обработку прерываний.
Далее
микропроцессор, находясь в защищенном режиме, может производить обработку
необходимых прерываний. По окончании работы для возврата в реальный режим нужно
будет все «поставить на свои места», выполнив для этого последовательность
следующих действий:
1.
Запретить
аппаратные прерывания.
2.
Выполнить
обратное перепрограммирование контроллера прерываний.
3.
Перейти в
реальный режим работы микропроцессора.
4.
Разрешить
обработку аппаратных прерываний.
5.
Обсудим эти
действия подробнее.
Инициализация таблицы IDT
Выше мы уже определились с тем,
что так же, как и в реальном режиме, все прерывания защищенного режима имеют
свои номера от 0 до 255. Отличие их от прерываний реального режима в том, что
под цели микропроцессора (исключительные ситуации или просто исключения) отдано
гораздо большее количество номеров — первые 32 вектора с номерами 0...31. Из
этих 32 векторов реально используются только 0...17, остальные вектора 18...31
пока зарезервированы для будущих разработок. Для того чтобы сформировать
таблицу IDT в целом,
необходимо определиться с описанием отдельных ее дескрипторов - шлюзов. Можно
предложить следующий, оформленный в виде структуры, шаблон для описания
дескрипторов в таблице IDT:
descr_idt struc
offs_1 dw 0 ;младшая часть адреса смещения обработчика прерывания
sel dw 30 ;селектор на сегмент команд в таблице GDT
no_use db 0
type_attr db 8eh ;по умолчанию шлюз прерывания,
;для ловушки - 8fh
offs_2 dw 0;старшая часть адреса смещения обработчика прерывания
ends
Теперь можно приступить к
формированию таблицы IDТ. Это можно делать как в отдельном сегменте, так и в сегменте
данных. Чтобы не загромождать сегмент данных, оформим таблицу IDT в виде
отдельного сегмента.
idt_seg segment
rept 5
descr_idt <dummy,,,,>
endm
int05h descr_idt
<int_05h,,,,>
rept 7
descr_idt <dummy_err,,,,>
endm
int0dh descr_idt
<int_0dh,,,,>
rept 3
descr_idt <dummy,,,,>
endm
int11h descr_idt
<dummy_err,,,,>
rept 14
descr_idt <dummy,,,,>
endm
int20h descr_idt
<new_08h,,,,>
int21h descr_idt
<sirena,38,,,>
rept 221
descr_idt <dummy,,,,>
endm
idt_seg ends
Как видите, мы полностью описали
всю таблицу прерываний. При этом мы инициализировали все дескрипторы именем
процедур обработки прерываний. Основная часть дескрипторов имеет в качестве
обработчиков процедуру dummy, то есть они будут обрабатываться одной программой. Для некоторых
прерываний мы ввели уникальные имена; эти прерывания будут иметь свои процедуры
обработки в нашей программе.
Обработчики прерываний
Расположение и порядок следования
процедур обработки прерываний в памяти может быть произвольным. Главное, не
забывать поместить их адреса в соответствующие дескрипторы. При написании самих
программ обработки прерываний мы должны помнить о том, что при возникновении
некоторых прерываний в стек вслед за содержимым регистров eip, cs и eflags может
записываться еще и код ошибки. Поэтому в общем случае процедура обработки
прерывания должна происходить по следующей схеме действий:
1.
Снять со
стека и проанализировать код ошибки (если он есть).
2.
Сохранить в
стеке используемые в обработчике регистры микропроцессора.
3.
Выполнить необходимые
действия, в том числе подготовить возможный рестарт команды, вызвавшей
прерывание. Подобный рестарт подразумевает возобновление выполнения прерванной
программы, начиная с команды, инициировавшей процесс прерывания.
4.
Восстановить
сохраненные на шаге 2 регистры;
5.
Выполнить
команду iret.
Прерываний, для которых
микропроцессор формирует код ошибки, немного — всего 8. В программе этого
примера приведены все возможные типы прерываний защищенного режима. Так, для
исследования исключений типа ошибок взяты исключения 5 и 13 (0Dh), а для
ловушек — 3 и обработка прерывания пользователя.
Программирование контроллера прерываний i8259А
При загрузке BIOS производит инициализацию
контроллеров прерываний (ведущего и ведомого). При этом BIOS назначает им базовые номера:
ведущему — 08h, ведомому —
70h. Из этого
следует, что если мы разрешим обработку аппаратных прерываний в защищенном
режиме, то первое же прерывание от таймера заставит микропроцессор через
таблицу IDT обратиться
к процедуре dummy. Что же
делать? Ничего не остается, как перепрограммировать ведущий контроллер на
другое значение базового вектора. Ведомый контроллер перепрограммировать не
обязательно — значение его базового вектора ничем нам не мешает, так как оно
находится в области, отведенной под прерывания пользователя.
Загрузка регистра IDTR
Аналогично таблице GDT нужно
сообщить микропроцессору, где находится таблица IDТ. Это делается с помощью
специально выделенного для этой цели регистра idtr. Таким образом, в защищенном
режиме любая задача, имея достаточный уровень привилегий, может создать свою
среду для обработки прерываний. Для загрузки регистра idtr можно использовать
ту же структуру point, что и для регистра gdtr. Загрузка регистра idtr
производится специальной командой lidt, которая имеет следующий формат:
lidt адрес_48-битного_поля (Load IDT register) —
загрузить регистр idtr.
Команда lidt загружает системный регистр idtr
содержимым 6-байтового поля, адрес которого указан в качестве операнда.
point struc
lim
dw 0
adr
dd 0
ends
data segment
point_idt point <idt_size,0>
…
data ends
code segment
…
xor eax,eax
mov ax,idt_seg
shl eax,4
mov dword
ptr point_idt.adr,eax
lidt pword point_idt
code ends
После этого можно перейти в
защищенный режим и разрешить аппаратные прерывания. Выполнив работу в
защищенном режиме, мы готовимся к обратному переходу в реальный режим. Для
этого, кроме восстановления вычислительной среды этого режима, необходимо
восстановить и соответствующий механизм прерываний.
Далее приводятся исходные тексты
вышерассмотренных примеров:
Исходные коды программы
расположены в этом каталоге.
После подробного рассмотрения
выше приведенных примеров – приведем еще несколько исходных текстов примеров
«мини операционных систем».
Программа демонстрирует основы
системного программирования в защищенном
режиме процессора 386+.
Перечень
возможностей и свойств:
- 32-х битный код
- Обработка исключений
- Обработка прерываний от таймера и
клавиатуры
- Мультизадачность (задачи 0-го и 3-го колец)
Исходные
коды программы (для компиляции) расположены в этом каталоге.
Для перехода
к просмотру описания исходного кода кликните здесь.
Компилируйте
программу используя TASM 3.1 (или лучший, TASM 5.0,
например)
и
следующую последовательность команд:
tasm /m2
/mx /o /t kernel.asm
tlink /3 /x kernel.obj
Программа демонстрирует основы
системного программирования в защищенном
режиме процессора 386+.
Две версии программы (PAGING и
NOPAGING) различаются лишь тем, устанавливается ли бит PG регистра CR0 в
"1" или нет. В первом случае устанавливается (режим
страничной адресации включен). Результат конечно же разный: программы рисуют
цветные горизонтальные полосы на экране
в разном порядке. Это происходит из-за того, что в программе таблица страниц настраивается не на полное
соответствие линейных адресов физическим. Точнее, в области графического буфера порядок
странц изменен на противоположный.
Исходные
коды программы (для компиляции) расположены в этом каталоге.
Для перехода к просмотру описания исходного кода кликните здесь.
Компилируйте программу используя TASM 3.1 (или лучший, TASM 5.0, например)
и следующую
последовательность команд:
tasm /m2
/mx /o /t paging.asm
tlink /3 /x
paging.obj
tasm /m2 /mx /o /t
nopaging.asm
tlink /3 /x nopaging.obj
Этот пример демонстрирует возможность работы программ DOS в режиме «эмуляции виртуальной
машины x86» - V86 Mode. После
переключения в защищенный режим создается задача виртуального V86 режима, в
которой запускается командный процессор Command.com, под управлением которого могут
выполняться «несложные» DOS-программы.
Перечень
возможностей и свойств:
- 32-х битный код
- Обработка исключений
- Обработка прерываний от таймера и
клавиатуры
- Переключение задач с использованием TSS
- Эмуляция режима V86
- Поддержка до 8-ми вложенных прерываний в
V86 режиме
- Поддержка ф-ции 87H прерывания 15H BIOS
- Запуск командного процессора COMMAND.COM
- С частотой около раза в секунду в правом
верхнем углу экрана мигает звездочка, показывая, что работает V86 монитор
Исходные
коды программы (для компиляции) расположены в этом каталоге.
Для перехода к просмотру описания исходного кода кликните здесь.
Компилируйте
программу используя TASM 3.1 (или лучший, TASM 5.0,
например)
и
следующую последовательность команд:
tasm /m2 /mx /o /t v86.asm
tlink /3 /x v86.obj
;prog1.asm
.586P ;разрешение инструкций Pentium
.MODEL large
include mac.inc
;структура для описания дескрипторов сегментов
descr
STRUC
limit
dw 0
base_1 dw 0
base_2 db 0
attr
db 0
lim_atr db 0
base_3 db 0
ENDS
;макрос инициализации дескрипторов
load_descr
MACRO des,seg_addr,seg_size
mov des.limit,seg_size
xor eax,eax
mov ax,seg_addr
shl eax,4
mov des.base_1,ax
rol eax,16
mov des.base_2,al
ENDM
atr
MACRO descr,bit1,bit2,bit3
;можно использовать условные директивы
;для проверки наличия параметров
atr_&descr=constp or bit1 or bit2 or bit3
ENDM
;структура для описания псевдодескриптора gdtr
point
STRUC
lim
dw 0
adr
dd 0
ENDS
;атрибуты для описания дескрипторов сегментов
;постоянная часть байта AR для всех
;сегментов - биты 0, 4, 5, 6, 7
constp equ 10010000b
;бит 1
code_r_n equ 00010000b ;кодовый сегмент:
;чтение запрещено;
code_r_y equ 00000010b ;кодовый сегмент:
;чтение разрешено
data_wm_n equ 00000000b ;сегмент данных:
;модификация запрещена
data_wm_y equ 00000010b ;сегмент данных:
;модификация разрешена;
;бит 2
code_n equ 00000000b ;обычный сегмент кода
code_p equ 00000100b ;подчиненный сегмент кода
data_ equ 00000000b ;для сегмента данных
stack_ equ 00000000b ;для сегмента стека
;бит 3
code_ equ 00001000b ;сегмент кода
data_stk equ 00000000b ;сегмент данных или стека
stk
segment stack 'stack' use16
db 256 dup (0)
stk
ends
;таблица глобальных дескрипторов
gdt_seg segment para public 'data' use16
gdt_0
descr <0,0,0,0,0,0> ;никогда не используется
atr gdt_gdt_8,data_wm_y,data_,data_stk
;ниже описываем саму gdt
gdt_gdt_8
descr <0,0,0,atr_gdt_gdt_8,0,0>
;не используем
gdt_ldt_10 descr <0,0,0,0,0,0>
atr
gdt_ds_18,data_wm_y,data_,data_stk
;дескриптор сегмента данных
gdt_ds_18 descr
<0,0,0,atr_gdt_ds_18,0,0>
atr gdt_vbf_20,data_wm_y,data_,data_stk
gdt_es_vbf_20
descr
<0,0,0,atr_gdt_vbf_20,0,0>
;видеобуфер
atr gdt_ss_28,data_wm_y,stack_,data_stk
gdt_ss_28 descr
<0,0,0,atr_gdt_ss_28,0,0>
;сегмент стека
atr gdt_cs_30,code_r_y,code_n,code_
gdt_cs_30 descr
<0,0,0,atr_gdt_cs_30,0,0>
;сегмент кода
gdt_size=$-gdt_0-1 ;размер GDT
минус 1
GDT_SEG ENDS
;данные программы
data
segment para public 'data' use16
;сегмент данных
point_gdt point
<gdt_size,0>
hello db "Вывод сообщения в защищенном режиме"
hel_size=$-hello
tonelow dw 2651 ;нижняя граница звучания 450 Гц
cnt db 0 ;счётчик для выхода из программы
temp dw ? ;верхняя граница звучания
data_size=$-point_gdt-1
data
ends
code
segment byte public 'code' use16
;сегмент кода с 16-разрядным режимом адресации
assume cs:code,ss:stk
main
proc
mov ax,stk
mov ss,ax
;заполняем таблицу глобальных дескрипторов
assume ds:gdt_seg
mov
ax,gdt_seg
mov ds,ax
load_descr gdt_gdt_8,GDT_SEG,gdt_size
load_descr gdt_ds_18,DATA,data_size
load_descr gdt_es_vbf_20,0b800h,3999
load_descr gdt_ss_28,STK,255
load_descr gdt_cs_30,CODE,0ffffh ;code_size
assume ds:data
mov ax,data
mov ds,ax
;загружаем gdtr
xor eax,eax
mov ax,gdt_seg
shl eax,4
mov point_gdt.adr,eax
lgdt point_gdt
;запрещаем прерывания
cli
mov al,80h
out 70h,al
;переключаемся в защищенный режим
mov
eax,cr0
or al,1
mov cr0,eax
;настраиваем регистры
db 0eah ;машинный код команды jmp
dw offset protect ;смещение метки перехода
;в сегменте команд
dw 30h ;селектор сегмента кода
;в таблице GDT
protect:
;загрузить селекторы для остальных дескрипторов
mov
ax,18h
mov ds,ax
mov ax,20h
mov es,ax
mov ax,28h
mov ss,ax
;работаем в защищенном режиме:
;выводим строку в видеобуфер
mov cx,hel_size ;длина сообщения
mov si,offset hello ;адрес строки сообщения
mov di,1640 ;начальный адрес видеобуфера для вывода
mov ah,07h ;атрибут выводимых символов
outstr1:
mov al,[si]
mov es:[di],ax
inc si
inc di
inc di
loop outstr1
;запускаем сирену
go:
;заносим слово состояния 110110110b (0В6h):
;выбираем второй канал порта 43h - динамик
mov
ax,0B06h
out 43h,ax
;в порт 43h
in al,61h ;получим значение порта 61h в al
or al,3 ;инициализируем динамик - подаем ток
out 61h,al ;в порт 61h
mov cx,2083 ;количество шагов
musicup:
;в ax значение нижней границы частоты 1193000/2651 = 450 Гц,
;где 1193000- частота динамика
mov ax,tonelow
out 42h,al ;al в порт 42h
mov al,ah ;обмен между al и ah
out 42h,al ;старший байтax (ah) в порт 42h
add tonelow,1 ;увеличиваем частоту
delay 1 ;задержка на 1 мкс
mov dx,tonelow ; текущее значение частоты в dx
mov temp,dx ;temp - верхнее значение частоты
loop musicup ;повторить цикл повышения
mov cx,2083
musicdown:
mov ax,temp ;верхнее значение частоты в ax
out 42h,al ;младший байтax (al)в порт 42h
mov al,ah ;обмен между al и ah
out 42h,al ;в порт 42h уже старший байт ax (ah)
sub temp,1 ;уменьшаем значение частоты
delay 1 ;задержка на 1 мкс
loop musicdown ;повторить цикл понижения
nosound:
in al,61h ;получим значение порта 61h в al
;слово состояния - выключение динамика и таймера
and
al,0FCh
out 61h,al
;в порт 61h
mov dx,2651 ;для последующих циклов
mov tonelow,dx
;увеличиваем счётчик проходов - количество звучаний сирены
inc cnt
cmp cnt,5 ;5 раз
jne go ;если нет - идти на метку go
;формирование дескрипторов для реального режима
assume ds:gdt_seg
mov
ax,8h
mov ds,ax
mov gdt_ds_18.limit,0ffffh
mov gdt_es_vbf_20.limit,0ffffh
mov gdt_ss_28.limit,0ffffh
mov gdt_cs_30.limit,0ffffh
assume ds:data
;загрузка теневых дескрипторов
mov
ax,18h
mov ds,ax
mov ax,20h
mov es,ax
mov ax,28h
mov ss,ax
db 0eah
dw offset jump
dw 30h
jump:
mov eax,cr0
and al,0feh
mov cr0,eax
db 0eah
dw offset r_mode
dw code
r_mode:
mov ax,data
mov ds,ax
mov ax,stk
mov ss,ax
;разрешаем прерывания
sti
xor al,al
out 70h,al
;окончание работы программы (стандартно)
mov ax,4c00h
int 21h
main
endp
code
ends
end main
;prog2.asm
.386P ;разрешение инструкций i386
.MODEL large
;макрос визуализации регистров al, ah, ax или eax
include show.inc
include mac.inc
;структура для описания дескрипторов сегментов
descr STRUC
limit
dw 0
base_1 dw 0
base_2 db 0
attr
db 0
lim_atr db 0
base_3 db 0
ENDS
;макрос инициализации дескрипторов
load_descr
MACRO des,seg_addr,seg_size
mov des.limit,seg_size
xor eax,eax
mov ax,seg_addr
shl eax,4
mov des.base_1,ax
rol eax,16
mov des.base_2,al
ENDM
atr
MACRO descr,bit1,bit2,bit3
atr_&descr=constp or bit1 or bit2
or bit3
ENDM
;структура для описания псевдодескриптора gdtr и idtr
point
STRUC
lim
dw 0
adr
dd 0
ENDS
;структура для описания дескрипторов таблицы idt
descr_idt STRUC
offs_1 dw 0
sel dw 30h ;селектор сегмента команд в таблице GDT
no_use db 0
type_attr db
8eh ;шлюз прерывания
offs_2 dw 0
ENDS
;атрибуты для описания дескрипторов сегментов
constp equ
10010000b
code_r_n equ
00010000b
code_r_y equ
00000010b
data_wm_n equ
00000000b
data_wm_y equ
00000010b
code_n equ 00000000b
code_p equ 00000100b
data_
equ 00000000b
stack_ equ 00000000b
code_
equ 00001000b
data_stk equ
00000000b
stk
segment stack 'stack'
db 256 dup (0)
stk ends
;сегмент с таблицей глобальных дескрипторов
gdt_seg segment para public 'data' use16
gdt_0
descr <0,0,0,0,0,0>
atr
gdt_gdt_8,data_wm_y,data_,data_stk
;описывает саму GDT
gdt_gdt_8 descr
<0,0,0,atr_gdt_gdt_8,0,0>
gdt_ldt_10
descr <0,0,0,0,0,0> ;не используем
atr
gdt_ds_18,data_wm_y,data_,data_stk
;дескриптор сегмента данных
gdt_ds_18 descr
<0,0,0,atr_gdt_ds_18,0,0>
atr
gdt_vbf_20,data_wm_y,data_,data_stk
gdt_es_vbf_20
descr <0,0,0,atr_gdt_vbf_20,0,0> ;видеобуфер
atr
gdt_ss_28,data_wm_y,stack_,data_stk
gdt_ss_28 descr
<0,0,0, atr_gdt_ss_28,0,0>
;сегмент стека
atr
gdt_cs_30,code_r_y,code_n,code_
gdt_cs_30 descr
<0,0,0,atr_gdt_cs_30,0,0>
;сегмент кода
atr
gdt_sirena_38,code_r_y,code_n,code_
gdt_sirena_38
descr
<0,0,0,atr_gdt_sirena_38,0,0>
gdt_size=$-gdt_0-1 ;размер GDT минус 1
gdt_seg ends
idt_seg segment para public 'data' use16
int00h descr_idt
<dummy,,,,>
REPT 2
descr_idt <dummy,,,,>
ENDM
int03h descr_idt
<int_03h,,,,>
descr_idt <dummy,,,,>
int05h descr_idt
<int_05h,,,,>
REPT
7
descr_idt <dummy_err,,,,>
ENDM
int0dh descr_idt <int_0dh,,,,>
REPT
3
descr_idt <dummy,,,,>
ENDM
int11h descr_idt
<dummy_err,,,,>
REPT
14
descr_idt <dummy,,,,>
ENDM
int20h descr_idt
<new_08h,,,,>
int21h descr_idt
<sirena,38h,,,>
REPT
222
descr_idt <dummy,,,,>
ENDM
idt_size=$-int00h-1
idt_seg ends
;данные программы
data
segment para public 'data' use16
;сегмент данных
point_gdt point
<gdt_size,0>
point_idt point
<idt_size,0>
char
db '0'
maskf
db 07h
position dw
2000
tonelow dw 2651
;нижняя граница звучания 450 Гц
cnt db 0 ;счётчик для выхода из программы
temp dw ? ;верхняя граница звучания
min_index dw 0
max_index dw
99
data_size=$-point_gdt-1
data
ends
SOUND SEGMENT byte private use16
assume cs:SOUND,ds:DATA,ss:STK
sirena PROC ;пользовательское прерывание
push ds
push ax
push cx
push dx
go:
;заносим слово состояния 10110110b(0В6h) в командный регистр (порт 43h)
mov
al,0B6h
out 43h,al
in al,61h ;получим значение порта 61h в al
or al,3 ;инициализируем динамик и подаем ток в порт 61h
out 61h,al
mov cx,2083 ;количество шагов ступенчатого изменения тона
musicup:
;в ax значение нижней границы частоты
mov ax,tonelow
out 42h,al ;в порт 42h младшее слово ax :al
xchg al,ah ;обмен между al и ah
out 42h,al ;в порт 42h старшее слово ax:ah
add tonelow,1 ;повышаем тон
delay 1 ;задержка на 1 мкс
mov dx,tonelow ;в dx текущее значение высоты
mov temp,dx ;temp - верхнее значение высоты
loop musicup ;повторить цикл повышения
mov cx,2083 ; восстановить счетчик цикла
musicdown:
mov ax,temp ;в ax верхнее значение высоты
out 42h,al ;в порт 42h младшее слово ax :al
mov al,ah ;обмен между al и ah
out 42h,al;в порт 42h старшее слово ax :ah
sub temp,1 ;понижаем высоту
delay 1 ;задержка на 1 мкс
loop musicdown ;повторить цикл понижения
nosound:
in al,61h ;получим значение порта 61h в AL
and al,0FCh ;выключить динамик
out 61h,al ;в порт 61h
mov dx,2651 ;для последующих циклов
mov tonelow,dx
inc cnt ;увеличиваем счётчик проходов, то есть
;количество звучаний сирены
cmp cnt,5 ;5 раз ?
jne go ;если нет - идти на метку go
pop dx
pop cx
pop ax
pop ds
mov bp,sp
mov eax,[bp]
show eax,0
mov eax,[bp+4]
show eax,160
mov eax,[bp+8]
show eax,320
db 66h
iret
endp
sound_size=$-sirena-1
sound
ends
code
segment byte public 'code' use16
;сегмент кода с 16-разрядным режимом адресации
assume cs:code,ss:stk
dummy proc ;исключения без кода ошибки
mov
ax,0ffffh
db 66h
iret
endp
dummy_err proc ;исключения с кодом ошибки
pop eax ;снимаем со стека код ошибки и на экран
db 66h
iret
endp
int_03h proc
;для этого исключения не формируется кода ошибки,
;поэтому анализируем содержимое eip в стеке и возвращаемся в программу
pop eax
show eax,0
push eax
db 66h
iret
endp
int_05h proc ;обработчик для 5-го исключения - команда bound
mov al,5
mov ah,al
mov si,2
db 66h
iret
endp
int_0dh proc
pop eax ;снимаем со стека код ошибки
sub bx,4 ;исправляем причину возникновения исключения
db 66h ;возвращаемся обратно и рестарт виноватой команды
iret
endp
new_08h proc ;новое прерывание от таймера
assume ds:data
push ds
push es
push ax
push bx
mov ax,20h
mov es,ax
scr:
mov al,char
mov ah,maskf
mov bx,position
mov es:[bx],ax
add bx,2
mov position,bx
inc char
pop bx
pop ax
pop es
pop ds
mov al,20h
out 20h,al
db 66h
iret
endp
new_8259a proc
;в al - значение нового базового
;вектора для ведущего контроллера
push ax
mov al,00010001b
out 20h,al
;ICW1 в порт 20h
jmp $+2
jmp $+2 ;эадержка, чтобы успела отработать аппаратура
pop ax
out 21h,al ;ICW2 в порт 20h - новый базовый номер
jmp $+2
jmp $+2 ;эадержка, чтобы успела
;отработать аппаратура
mov al,00000100b
out 21h,al ;ICW3 - ведомый подключается
;к уровню 2 (см. рис. 15.1)
jmp $+2
jmp $+2 ;эадержка, чтобы успела
;отработать аппаратура
mov al,00000001b
out 21h,al ;ICW4 - EOI выдает программа пользователя
ret
endp
main
proc
mov ax,stk
mov ss,ax
;заполняем таблицу глобальных дескрипторов
assume ds:GDT_SEG
mov ax,GDT_SEG
mov ds,ax
load_descr gdt_gdt_8,GDT_SEG,gdt_size
load_descr gdt_ds_18,DATA,data_size
load_descr gdt_es_vbf_20,0b800h,3999
load_descr gdt_ss_28,STK,255
load_descr gdt_cs_30,CODE,code_size
load_descr gdt_sirena_38,SOUND,sound_size
assume ds:data
mov ax,data
mov ds,ax
;загружаем gdtr
xor eax,eax
mov ax,GDT_SEG
shl eax,4
mov point_gdt.adr,eax
lgdt point_gdt
;запрещаем прерывания
cli
mov al,80h
out 70h,al
mov al,20h ;новое значение базового вектора
call
new_8259A
;загружаем idtr
xor eax,eax
mov ax,IDT_SEG
shl eax,4
mov point_idt.adr,eax
lidt point_idt
;переключаемся в защищенный режим
mov eax,cr0
or al,1
mov cr0,eax
;настраиваем регистры
db 0eah ;машинный код команды jmp
dw offset protect ;смещение метки перехода
;в сегменте команд
dw 30h ;селектор сегмента кода в GDT
protect:
;загрузить селекторы для остальных дескрипторов
mov ax,18h
mov ds,ax
mov ax,20h
mov es,ax
mov ax,28h
mov ss,ax
;работаем в защищенном режиме:
;разрешаем прерывания от таймера, наблюдаем
sti
delay 3500
cli
;далее имитируем возникновение двух
;исключительных ситуаций (типа ошибки): 5 и 13
mov
si,130
bound si,dword ptr min_index
mov bx,data_size
mov ax,[bx]
;а теперь имитируем возникновение исключительной ситуации типа ловушки - 3:
int 3
;обрабатываем их и в знак успеха запускаем
;из другого сегмента команд сирену
int 21h
;готовимся к переходу в реальный режим
;прерывания запрещены
;перепрограммируем контроллер
mov al,08h
call new_8259A
;формирование дескрипторов для реального режима
assume ds:GDT_SEG
mov ax,8h
mov ds,ax
mov gdt_ds_18.limit,0ffffh
mov gdt_es_vbf_20.limit,0ffffh
mov gdt_ss_28.limit,0ffffh
mov gdt_cs_30.limit,0ffffh
assume ds:DATA
;загрузка теневых дескрипторов
mov
ax,18h
mov ds,ax
mov ax,20h
mov es,ax
mov ax,28h
mov ss,ax
db 0eah
dw offset jump
dw 30h
jump:
mov eax,cr0
and al,0feh
mov cr0,eax
db 0eah
dw offset r_mode
dw CODE
r_mode:
mov ax,DATA
mov ds,ax
mov ax,STK
mov ss,ax
mov ax,3ffh
mov point_idt.lim,ax
xor eax,eax
mov point_idt.adr,eax
lidt point_idt
;разрешаем прерывания
sti
xor al,al
out 70h,al
;окончание работы программы (стандартно)
mov
ax,4C00h
int 21h
main
ENDP
code_size=$-dummy
code
ends
end
main
;mac.inc
GetStr macro
Buf,MaxLen
local m,TmpBuf
;ввод строки произвольной длины (функция 0ah int 21h)
;на входе:
;Buf - адрес строки куда будет помещен ввод
;MaxLen - максимальная длина вводимой строки
;на выходе - введенная строка по адресу Buf
;al - длина введенной строки
jmp m
TmpBuf label byte ;временный буфер форматом для функции 0ah (int 21h)
rept MaxLen+3 ;доп. три байта - служебная информация
db ' '
endm
m:
SaveReg <ds,es,dx,cx>
xor cx,cx
mov cs:TmpBuf,MaxLen+1
mov ah,0ah
push ds
pop es
push cs
pop ds
lea dx,cs:TmpBuf
int 21h
mov al,cs:TmpBuf+1
;пересылка TmpBuf в Buf со сдвигом на два байта влево (для удаления служебных символов):
mov cl,al ;длина введенной строки в al
lea
si,cs:TmpBuf+2 ;откуда - ds:si
lea di,buf
;куда - es:di
rep
movsb
LoadReg <cx,dx,es,ds>
endm
mov_string
macro dest,src,len
;Пересылка строк
;На входе идентификаторы: строки-источника - src, строки-приемника - dest
;сегментные регистры ds (для источника) и es (для приемника) должны быть загружены
;правильными значениями до вызова макрокоманды
mov cx,len
lea
si,src
lea di,dest
rep
movsb
endm
null_string
macro dest,len
local m,Z_String
;очистка строки произвольной длины пробелами
;на входе:
;dest - адрес строки
;len - длина очищаемой строки
jmp m
Z_String label
byte ;пустая
строка
rept len
db ' '
endm
m:
SaveReg <ds,es,cx,si,di>
mov cx,len
push ds
pop es
;адрес dest (приемник) - es:di
push cs
pop ds
;адрес Z_String (источник) - ds:si
lea si,cs:Z_String
lea di,dest
rep
movsb
LoadReg <di,si,cx,es,ds>
endm
OutStr macro str
;Вывод строки на экран.
;На входе - идентификатор начала выводимой строки.
;Строка должна заканчиваться символом '$'.
;На выходе- сообщение на экране.
SaveReg <ax,dx>
mov ah,09h
lea dx,str
int 21h
LoadReg <dx,ax>
endm
GetChar macro
;Ввод символа с клавиатуры.
;На выходе - в al введённый символ.
mov
ah,01h
int 21h
endm
OutChar macro
;Вывод символа на экран.
;На входе - в dl выводимый символ.
push ax
mov ah,02h
int 21h
pop ax
endm
SaveReg macro RegList
;Сохранение указанных в списке RegList регистров в стеке
;список регистров должен быть заключен в угловые скобки,
;например - <ax,bx,cx>
irp reg,<RegList>
push reg
endm
endm
LoadReg macro RegList
;Восстановление указанных в списке RegList регистров из стека
;список регистров должен быть заключен в угловые скобки,
;например - <ax,bx,cx>
irp reg,<RegList>
pop reg
endm
endm
clear_r macro rg
;очистка регистра rg
xor rg,rg
endm
conv_16_2 macro
;макрос преобразования символа шестнадцатеричной цифры
;в ее двоичный эквивалент в al
sub
dl,30h
cmp dl,9h
jle $+5
sub dl,7h
endm
init_ds macro
;макрос настройки ds на сегмент данных
mov
ax,data
mov ds,ax
xor ax,ax
endm
delay
macro time
local ext,iter
;макрос задержки. На входе - значение переменной задержки (в мкс).
push cx
mov cx,time
ext:
push cx
mov cx,5000 ;это значение можно поменять, исходя из производительности процессора.
iter:
loop iter
pop cx
loop ext
pop cx
endm
_Exit macro
;Выход из программы.
mov
ax,4c00h
int 21h
endm
;;show.inc
;;макроопределение для визуализации регистров al, ah, ax, eax
;;на входе:
;;arg_n - имя одного из регистров al,ah,ax,eax
;;n_poz - номер позиции на экране, по умолчанию - 1000
Show MACRO
arg_n,n_poz:=<1000>
LOCAL main_part,disp,pause,template,VideoBuffer,p_mode,m1,m2
;;переход на начало блока команд,
;;чтобы избежать выполнения данных
jmp main_part
;;некоторые константы и переменные
FALSE equ 0 ;;ложь
TRUE equ 0ffffh ;;истина
?reg8bit=false ;;флаг того, что передан регистр ah или al
?reg16bit=false ;;флаг того, что передан регистр ax
?reg32bit=false ;;флаг того, что передан регистр eax
;;таблица-шаблон для xlat
template db '0123456789ABCDEF'
;;адрес видеобуфера - для прямого вывода на экран
VideoBuffer dw 0b800h
main_part: ;;начало
блока команд
;;сохранение в стеке используемых регистров:
;;eax, ebx, ecx, edx, edi, ds, es
push eax
push ebx
push ecx
push edx
push edi
push ds
push es
push cs
pop ds
;;в bx - адрес таблицы-шаблона (для xlat)
lea
bx,cs:template
xor cx,cx
;очистка cx
;;начало блока определения того,
;;какой регистр был передан макросу
IFIDNI <al>,<&arg_n> ;;если аргумент=al или AL,
?reg8bit=TRUE ;;установка флага 8-битового регистра
mov ah,al
ENDIF
;;передан не al или AL
IFIDNI <ah>,<&arg_n> ;;если аргумент=ah или AH,
?reg8bit=TRUE ;;установка флага 8-битового регистра
ENDIF
;;передан не AH или ah
IFIDNI <ax>,<&arg_n> ;;если аргумент равен ax или AX,
?reg16bit=TRUE ;;установка флага 16-битового регистра
ENDIF
;;передан не ah, AH ,ax или AX
IFIDNI <eax>,<&arg_n> ;;если аргумент равен eax или EAX,
?reg32bit=TRUE ;;установка флага 32-битового регистра
ENDIF
;;обработка содержимого регистров al, ah, ax, eax
IF (?reg8bit) ;;если передан al или ah
push eax
and ah,0f0h ;;обращение к старшей четвёрке битов ah
shr ax,12 ;;сдвиг битов в начало (16-4=12)
xlat ;;трансляция таблицы-шаблона
;;помещение символа из al в edi
mov
di,ax
shl di,8
inc cx
pop eax
and ax,0f00h ;;обращение к младшей тетраде ah
shr ax,8 ;;сдвиг битов в начало (16-(4+4)=8)
xlat ;;трансляция таблицы-шаблона
or di,ax ;;помещение очередного символа в di
shl
edi,16
inc cx
ENDIF
IF (?reg16bit) ;;если передан ax
или ax
;;начало обработки значения регистра ax
push eax
;;обращение к старшей четвёрке битов ax
and ax,0f000h
shr ax,12 ;;сдвиг битов в начало (16-4=12)
xlat ;;трансляция таблицы-шаблона
;;помещение символа из al в старшую
;;тетраду старшей половины edi
mov di,ax
shl
edi,8
inc cx
pop eax
push eax
;;обращение ко второй четвёрке битов ax
and ax,0f00h
shr ax,8 ;;сдвиг битов в начало (16-(4+4)=8)
xlat ;;трансляция таблицы-шаблона
;;помещение очередного символа в младшую
;;тетраду старшей половины edi
or di,ax
shl
edi,8
inc cx
pop eax
push eax
and ax,0f0h ;;обращение к третьей четвёрке битов в ax
shr ax,4 ;;сдвиг битов в начало (16-(4+4+4)=4)
xlat ;;трансляция таблицы-шаблона
or di,ax ;;помещение очередного символа в edi
shl
edi,8
inc cx
pop eax
and ax,0fh ;;обращение к младшей четвёрке битов ax
xlat ;;трансляция таблицы-шаблона
or di,ax ;;помещение очередного символа в edi
inc cx
ENDIF
IF (?reg32bit) ;;если передан eax или EAX
;;начало обработки значения регистра eax
push eax
;;обращение к старшей четвёрке битов eax
and eax,0f0000000h
shr eax,28 ;;сдвиг битов в начало (32-4=28)
xlat ;;трансляция таблицы-шаблона
;;помещение символа из al в старшую
;;тетраду старшей половины edx
mov dh,al
shl
edx,8
pop eax
push eax
inc cx
pop eax
push eax
;;обращение к следующей четвёрке битов eax
and eax,0f000000h
shr eax,24 ;;сдвиг битов в начало (32-(4+4)=24)
xlat ;;трансляция таблицы-шаблона
;;помещение очередного символа из al в младшую
;;тетраду старшей половины edx
mov dh,al
shl edx,8
inc cx
pop eax
push eax
and eax,0f00000h
shr eax,20
xlat
mov dh,al
inc cx
pop eax
push eax
and eax,0f0000h
shr eax,16
xlat
mov dl,al
inc cx
pop eax
push eax
and eax,0f000h
shr eax,12
xlat
or di,ax
shl edi,8
inc cx
pop eax
push eax
and eax,0f00h
shr eax,8
xlat
or di,ax
shl edi,8
pop eax
push eax
inc cx
and eax,0f0h
shr eax,4
xlat
or di,ax
shl edi,8
inc cx
pop eax
push eax
and eax,0fh
xlat
or di,ax
inc cx
ENDIF
;;вывод на экран результата
;;результат в паре edx:ebx, количество цифр в cx
;;проверим режим работы микропроцессора
mov eax,cr0
test
eax,00000001h
jnz p_mode
;;для реального режима
;;загружаем в es адрес видеопамяти
mov
ax,cs:VideoBuffer
mov es,ax
p_mode:
;;для реального и защищенного режимов
;;количество циклов в cx
cld ;;просмотр вперед - для stosw
xchg edi,ebx
mov di,n_poz ;;начальная позиция для
;;вывода на экран
disp:
cmp ecx,4
jle m1 ;переход, если ecx<=4
shld
eax,edx,8
shl edx,8
jmp m2
m1:
shld eax,ebx,8
shl ebx,8
m2:
mov ah,71h
;;байт-атрибут
stosw ;;копирование значения ax
;;в es:di (видеобуфер)
loop disp ;;повтор цикла cx раз
mov
cx,65535 ;;задержка
pause:
loop pause
;;переопределение/восстановление из стека
;;используемых регистров
pop es
pop ds
pop edi
pop edx
pop ecx
pop ebx
pop eax
ENDM