Приложение №2.

 

Учебные примеры программ защищенного режима.

 

!!! Замечание !!!

 

Все примеры, приведенные в этом приложении, являются своего рода операционными системами защищенного режима. Следовательно, их исполнение должно начинаться в реальном режиме процессора. То есть в среде «чистого» MS-DOS (без менеджеров оперативной памяти, таких как Himem, EMM386, QEMM и т.п.) или в режиме «Safe mode command prompt only» операционных систем Windows 9x. В операционной системе Windows 2000 (и ей подобных – Win NT/XP) – работа этих примеров, по определению, не возможна.

 

Пример №1.

Пример №2.

Пример №3.

Пример №4.

Пример №5.

 

 

Пример №1.

 

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

 

  1. Подготовка в оперативной памяти таблицы глобальных дескрипторов GDT.
  2. Инициализация необходимых дескрипторов в таблице GDT.
  3. Загрузка в регистр gdtr адреса и размера таблицы GDT.
  4. Запрет обработки аппаратных прерываний.
  5. Переключение микропроцессор в защищенный режим.
  6. Организация работы в защищенном режиме:

·         настроить сегментные регистры;

·         выполнить собственно содержательную работу программы; в нашем слу­чае мы просто обозначим сам факт успешного перехода в защищенный режим;

·         подготовиться к возврату в реальный режим;

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

  1.  Переключение микропроцессора в реальный режим.
  2.  Настройка сегментных регистров для работы в реальном режиме.
  3.  Разрешение прерываний и стандартное для MS-DOS завершение работы программы.

 

Опишем каждый шаг подробнее (см. также исходный текст программы).

 

Подготовка таблиц глобальных дескрипторов 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

 

Разрешение прерываний

 

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

 

Пример №2

 

Этот пример поясняет работу системы прерываний. Так как при этом возникает достаточно много нюансов, то мы рассмотрим задачу, которая бы отражала суть большинства из них. В нашей программе мы будем обрабатывать одно аппаратное прерывание (от таймера), две разнотипные исключительные ситуации и обычное пользовательское прерывание. На примере этой задачи будут показаны все аспекты обработки прерываний в защищенном режиме, за исключением использования задач в качестве обработчика прерываний. Вначале, так же как и для программы перехода в защищенный режим, обсудим некоторые ключевые моменты. Наша программа по-прежнему является операционной мини-системой, так как она сама обеспечивает свое функционирование на «чистом» микропроцессоре. Поэтому за основу мы возьмем предыдущую программу и дополним ее функционально, научив обрабатывать некоторые прерывания.

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

 

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

 

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

 

Далее приводятся исходные тексты вышерассмотренных примеров:

 

  1. Исходный текст «Пример 1 – работа в защищенном режиме»
  2. Исходный текст «Пример 2 – прерывания в защищенном режиме»
  3. Исходный текст включаемых служебных модулей mac.inc и show.inc

 

Исходные коды программы расположены в этом каталоге.

 

Пример №3 – «Ядро крошечной операционной системы – Kernel»

 

          После подробного рассмотрения выше приведенных примеров – приведем еще несколько исходных текстов примеров «мини операционных систем».

Программа демонстрирует основы системного программирования в защищенном  режиме процессора 386+.

 

Перечень возможностей и свойств:

 

  - 32-х битный код

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

  - Обработка прерываний от таймера и клавиатуры

  - Мультизадачность (задачи 0-го и 3-го колец)

 

Исходные коды программы (для компиляции) расположены в этом каталоге.

Для перехода к просмотру описания исходного кода кликните здесь.

 

Как компилировать

 

Компилируйте программу используя TASM 3.1  (или лучший,  TASM 5.0, например)

и следующую  последовательность команд:

 

    tasm /m2 /mx /o /t kernel.asm

    tlink /3 /x kernel.obj

 

Пример №4 – «Пример реализации страничной адресации»

 

Программа демонстрирует основы системного программирования в защищенном  режиме процессора 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

 

Пример №5 – «Пример реализации монитора режима V86»

 

          Этот пример демонстрирует возможность работы программ 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

 

Исходный текст «Пример 1 – работа в защищенном режиме»

 

;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

 

 

Исходный текст «Пример 2 – прерывания в защищенном режиме»

 

;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

 

;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

 

;;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