Программирование процессоров Intel x86 в защищённом режиме
Теоретическое введение в защиту

Автор: sergh
The RSDN Group

Версия текста: 1.0

Зачем
Лирическое наступление
Виды защит
Насущные проблемы
Снова дескрипторы
Уровни привилегий
DPL
CPL
RPL
IOPL
VIP only
Итого
Дескриптор сегмента данных, регистр не SS
Дескриптор сегмента данных, регистр SS
Пример
Задания

Jah love, Jah love - protect us

Vincent Ford (performed by Bob Marley)
 
Ты должен быть сильным, 
Ты должен уметь сказать 
«Руки прочь, прочь от меня».
Ты должен быть сильным, 
Иначе зачем тебе быть.

Виктор Цой

Нет защиты, кроме любви.

Борис Гребенщиков

Если вы успешно дочитали курс до этой главы, то, во-первых, вас можно и нужно поздравить! Поздравляю! Во-вторых, скорее всего, вы уже понимаете, зачем нужна подсистема защиты – в каждой из предыдущих глав были ссылки приблизительно следующего вида: «чтобы всё было хорошо, пользователь не должен иметь возможность …, подробнее этот вопрос рассмотрен в главах, посвящённых защите». Что ж, пришло время выполнять обещания. Но сначала, как обычно, ещё немного о «зачем».

Зачем

- Ты хочешь поговорить об этом?

Собирательный образ психолога

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

Лирическое наступление

Не случайно режим называется «Protected» – защищённый. Не Advanced, не Extended, не Improved, и даже не ++ или #, а именно Protected. Защита – его соль и суть. Ради неё всё и затевалось, именно она склеивает воедино всё остальное, без неё не было бы ни современных ОС, ни современных приложений. И это притом, что защита не даёт каких-то невероятных новых возможностей, наоборот, она отнимает многие старые. Нелогично? Ну, например, можно провести аналогию с переходом от «макаронного» кода, изобилующего goto, к структурному программированию. Или с ассемблера на язык высокого уровня. Только наличие правильно заданных ограничений позволяет системе оставаться управляемой и стабильной, и, как следствие, развиваться и расти дальше.

СОВЕТ

А теперь подумайте о своей жизни, как о разрабатываемой вами сложной системе. А о морали, нравственности, порядочности – как о тех самых правильных ограничениях, без которых невозможно развитие. Подумали? Действуйте!

Извиняюсь за отступление от темы, мысль слишком важна.

«Старый» подход упёрся в свой потолок: при появлении многозадачности, графических интерфейсов и т.п., система «компьютер + ОС + запущенное ПО» становится слишком сложной, утверждение «программисты умные, всё будет в порядке» перестаёт работать, и, если других гарантий надёжности нет, всё начинает падать с удручающим постоянством.

ПРИМЕЧАНИЕ

Хотя бы потому, что в понятие «программисты» теперь входит слишком много людей: несколько сотен на ОС, по двое-трое-десятеро на каждое приложение. Вы правда думаете, что они все умные? Ну так я вас огорчу :)

Для примера, допустим, что вы всё-таки написали многозадачную ОС реального режима:

ПРИМЕЧАНИЕ

Те, кто писал резиденты под DOS, поняли, что я имею в виду. Остальным объясняю:

1. Есть стандартный обработчик прерывания

2. Запускается Программа1, которой это прерывание нужно. Она залезает в таблицу векторов прерываний и меняет стандартный обработчик на свой. Как правильная, вежливая программа, она запоминает адрес старого обработчика. Во-первых, этот адрес надо будет восстановить, когда Программа1 будет выгружаться, во-вторых, можно вызывать этот обработчик из своего, для того чтобы не мешать работе стандартных механизмов.

3. Запускается Программа2, которой тоже нужно это же прерывание. Она проделывает ту же операцию, но поскольку стандартный обработчик уже подменён, она запоминает адрес обработчика Программы1.

4. Если обе программы вызывают «старые» обработчики, вся цепочка может корректно работать. Если нет, будет вызываться только наиболее новый.

5. При выгрузке, программы попытаются восстановить в таблице векторов прерываний запомненные адреса обработчиков. И вот тут всё зависит от очерёдности. Если первой будет выгружаться Программа1, она запишет в таблицу адрес стандартного обработчика. А Программа2 – адрес обработчика из уже выгруженной Программы1.

6. И в этот момент ваша ОС накроется медным тазом.

Конечно, такая ситуация могла возникнуть и в DOS, существует и классическое решение – если программа видит, что в таблице адрес не её обработчика, то есть после неё кто-то уже перехватил это же прерывание, она ничего не восстанавливает и не выгружается. Но вы же понимаете, что так будут поступать далеко не все…

Согласитесь, так жить нельзя. Очевидны две проблемы:

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

Так что защита в защищённом режиме играет одну из ключевых ролей. Пытаться абстрагироваться можно и без неё… Но только работать это не будет.

Виды защит

Описанный в предыдущих главах механизм управления памятью тоже относится к «необходимым ограничениям». Проверки существования дескриптора, типа дескриптора, размеров, разрешения чтения/записи, дают большую структурированность, в частности становится невозможно писать по случайному адресу. И в общем-то всё это тоже относится к защите, в том числе и с точки зрения Intel. Согласно терминологии Intel, система защиты в целом называется «protection» и все перечисленные выше проверки относятся к «просто protection» (это не термин). Но есть еще один раздел системы защиты, называемый privilege level protection (а это термин, он довольно неуклюже переводится на русский как «защита по привилегиям»), и именно ему посвящена эта и следующие главы.

Если проводить аналогии, то «просто protection» это стены, крыша и дверь дома, а privilege level protection – возможность запереть дверь на ключ. Если дверь не запирать, дом прекрасно защищён от ветра, снега и дождя, но практически не защищён от человека. С другой стороны, «возможность запереть дверь» сама по себе не существует и появляется только тогда, когда дом и дверь уже есть. Это некоторая дополнительная надстройка над существующими проверками, отделяющая тех, у кого есть ключ, от тех, у кого ключа нет. И, конечно, если у дома разваливаются стены, даже запертая дверь плохо поможет (в качестве примера: по непроверенным слухам, в ОС семейства Windows 9x IDT находится в области памяти, доступной на запись для обычных приложений).

Насущные проблемы

Но спустимся с небес к нашим баранам. Начнём со списка потребностей в защите, которые «всплыли» в предыдущих главах.

Обобщая, получаем следующее:

ПРИМЕЧАНИЕ

Вопросу защиты портов ввода-вывода посвящена отдельная глава «Защита: ввод-вывод», здесь эта тема только несколько раз упоминается.

Снова дескрипторы

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

Положение Название Описание
Биты 0 – 3 Type Уточняет тип дескриптора (флаги в дескрипторах сегмента кода/данных, номер в системных дескрипторах).
Бит 4 S Определяет тип дескриптора – системный или сегмента кода/данных.
Биты 5 – 6 DPL Descriptor Privilege Level. Это ОНО! Именно на это поле обратите своё внимание, во многом ему посвящена глава. Описано ниже.
Бит 7 P Для завершённости – флаг Segment Present. В рамках курса всегда установлен в 1, больше нигде не упоминается, подробно рассмотрен в приложении.
Таблица 1. Формат пятого байта дескриптора.

На картинке:


Рисунок 1. Формат пятого байта дескриптора.

С точки зрения защиты, ключевое поле – DPL, Descriptor Privilege Level. Название переводится как «уровень привилегий дескриптора», и очень точно отражает предназначение поля.

Уровни привилегий

Царь-царевич, 
Король-королевич, 
Сапожник, портной. 
Кто ты будешь такой?

Считалка

Процессоры Intel x86 поддерживают четыре уровня привилегий.

ПРЕДУПРЕЖДЕНИЕ

Обратите внимание: меньшее численное значение соответствует большему уровню привилегий. Во избежание двусмысленности, в скобках будет уточняться, что имеется в виду. Например: «… минимальный (численно максимальный) уровень ..».

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

[Лирическое отступление] Картинка-с-кольцами

Читатель ждёт уж рифму «розы»;
На вот, возьми её скорей!

Александр Сергеевич Пушкин

Самое известное изображение уровней привилегий.


Рисунок 2. Она. Скопирована из [Intel 2004]

Все просмотренные мною источники по защищённому режиму содержат эту картинку (кроме [Зубков 1999], но у него вообще ни одной картинки нет). Я не знаю зачем, видимо так сложилось по каким-то историческим причинам. Скорее всего, где-нибудь в 1982 году, при создании 80286 неизвестный автор рекламной брошюры Intel попытался таким образом донести свою мысль до покупателей. Видимо, брошюра пользовалась успехом, и с тех пор это изображение так и кочует из документа в документ.

Помимо картинки обычно вводится соответствующая терминология:

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

DPL

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

Дескриптор сегмента кода

Значение DPL это уровень привилегий, которыми обладает находящийся в сегменте код. Именно этим отличается ОС и пользовательские приложения: код ОС находится в сегментах с нулевым DPL и обладает максимальным (нулевым) уровнем привилегий, код приложений – минимальным (третьим).

ПРИМЕЧАНИЕ

Передаче управления между сегментами кода и куче связанных с этим проблем посвящена глава «Защита: передача управления».

Дескриптор «подчинённого» сегмента кода

Кроме дескрипторов «обычного» сегмента кода, существуют дескрипторы подчинённого (conforming) сегмента кода. Их описание вынесено в приложение.

Дескриптор «ресурса»

В поле DPL хранится минимальный (численно максимальный) уровень привилегий, необходимый для доступа к ресурсу. Если к ресурсу обращается код с недостаточными привилегиями, процессор генерирует исключение #GP.

Из описанных в предыдущих главах, к этому классу относятся дескрипторы сегментов данных и обработчиков прерываний (оба варианта). Сюда же можно отнести и дескрипторы сегментов кода (только «обычные», подчинённые будут работать иначе), при использовании с сегментными регистрами DS, ES, FS, GS, то есть не для передачи управления, а как «данные-только-для-чтения». Все неизученные типы дескрипторов также относятся к этому классу.

Например, такое прерывание может вызвать только код с нулевым уровнем привилегий:

        gate_descriptor <int26_handler, 8, 0, 10001111b, 0>

А такое – любой:

        gate_descriptor <int26_handler, 8, 0, 11101111b, 0>

Аналогично с любыми другими дескрипторами ресурсов.

ПРИМЕЧАНИЕ

В данном случае проверка производится только при явном вызове обработчика инструкцией int 26. При возникновении внешнего прерывания или исключения проверки нет, обработчик будет вызван независимо от уровня привилегий текущего кода (в [Григорьев 1993] утверждается обратное, это ошибка автора).

Поскольку никакие другие ресурсы (на данный момент из всех «других» описаны только сегменты кода/данных) не могут быть использованы подобным «самопроизвольным» образом, к ним это замечание не относится.

Исключение: дескриптор сегмента данных с регистром SS

Сначала рассмотрим ситуацию (ситуация предполагает периодическое изменение привилегий, сейчас нам не важно как это происходит). Пусть имеется:

Два варианта развития событий:

Для предотвращения таких ситуаций системный код должен использовать свой сегмент стека. А, видимо, для повышения мотивации использования разных стеков, разработчики Intel просто запретили использовать один и тот же: при загрузке селектора в регистр SS, текущий уровень привилегий должен точно совпадать с DPL дескриптора сегмента.

Подробнее о переключении стеков при смене уровней привилегий написано в главе «Защита: передача управления».

CPL

Текущий уровень привилегий (Current Privilege Level, CPL) – уровень привилегий исполняемого в данный момент кода, или, другими словами, уровень привилегий, на котором в данный момент находится процессор. Обычно CPL совпадает со значением двух младших битов регистра CS.


Рисунок 3. Регистр CS и CPL.

Именно CPL определяет, что разрешено, а что запрещено, именно он проверяется при попытке выполнения привилегированных команд (см. ниже), сравнивается с DPL ресурса (см. выше) и с IOPL (см. ниже). В общем, несколько упрощая, при нулевом CPL можно сделать с системой вообще всё, в любом другом случае придётся спрашивать разрешения.

ПРИМЕЧАНИЕ

Защита многослойна, нулевой уровень привилегий разрешает «вообще всё» только с точки зрения доступа к любым ресурсам независимо от их DPL (а со стеками и это не получится, см. выше), к любым портам ввода-вывода и к любым инструкциям. А, например, обращаться за пределы сегмента, менять read-only сегменты и передавать исполнение сегментам данных запрещёно независимо от текущего уровня привилегий.

Но, если очень нужно, имея нулевой CPL, можно определить свою собственную GDT и описать там те дескрипторы, которые хочется, обходя таким образом практически любые механизмы защиты.

Значение CPL определяется в соответствии с двумя простыми правилами (подчинённые сегменты кода добавят третье, но пока – два):

Изменение CPL тоже рассмотрено в главе «Защита: передача управления».

RPL

Два младших бита «нормального» селектора (не загруженного в CS) называются уровень привилегий запроса (Request Privilege Level, RPL), и примерно в 99% ситуаций, единственно разумным значением этого поля будет 0. Тем не менее, поле придумано разработчиками не совсем напрасно, и ситуация, когда ненулевой RPL имеет смысл, всё-таки существует.


Рисунок 4. Селектор и RPL

Ситуация

Рассмотрим следующий сценарий:

Теперь добавим уровни привилегий:

Поскольку «F» выполняется с нулевым уровнем привилегий, она может читать/записывать данные независимо от значения DPL сегмента SEG. Может быть, это как раз то поведение, которое требуется, но может быть, нужно ограничить возможности работы только теми сегментами, которые доступны «A». И вот тут-то на помощь приходит RPL! С ним сценарий выглядит так:

ПРИМЕЧАНИЕ

Это близко к понятию имперсонализации (impersonation), отличие в том, что с помощью RPL невозможно увеличить свои права, только уменьшить.

Строгое описание

Описанное выше применение RPL сводится к следующему правилу: при загрузке селектора сегмента в сегментный дескриптор, система защиты сравнивает c DPL сегмента не только CPL, но и RPL селектора.

Это единственный разумный случай использования RPL. К сожалению, есть ещё несколько не очень разумных, в которых значение RPL всё-таки играет роль:

IOPL

Уровень привилегий ввода-вывода (Input-Output Privilege Level, IOPL), находится в 13-м и 12-м битах регистра EFLAGS. IOPL подробно описано в главе «Защита: ввод-вывод», а здесь упомянуто просто для комплекта.

VIP only

Следующие инструкции в защищённом режиме можно выполнять только при нулевом CPL (в реальном режиме таких проблем нет, можно всем, всё и всегда):

Эти инструкции называются привилегированными (privileged instructions), попытка выполнить их при ненулевом CPL приведёт к генерации исключения #GP.

ПРИМЕЧАНИЕ

Обратите внимание: cli, sti, in, out – не привилегированные инструкции, контроль за их исполнением осуществляется иначе, более гибко. Это тема главы «Защита: ввод-вывод».

Итого

Если вернуться к началу главы, к разделу «Насущные проблемы», и вспомнить, чего требовалось достичь, то окажется, что практически всё уже успешно достигнуто. Список результатов приведён в Таблице 2.

Проблема Решение
Отличать пользовательское приложение от ОС Динамически – CPL, статически – DPL дескриптора сегмента кода.
Запретить для пользовательских приложений потенциально опасные инструкции. Привилегированные инструкции
Разделять ресурсы, к которым пользователю обращаться можно, и ресурсы, к которым пользователю обращаться нельзя DPL дескриптора ресурса, CPL
Порты ввода-вывода. CPL, IOPL, подробнее – в главе «Защита: ввод вывод».
Таблица 2. Проблемы и решения

Для сегментов кода предложенные решения порождают проблему следующего уровня: отделять мы уже умеем, теперь надо как-то наладить связь между кодом с разными уровнями привилегий. Этому вопросу посвящена глава «Защита: передача управления».

А с сегментами данных новых проблем нет, получена логичная законченная система проверок, даже две – для обычного сегментного регистра и для SS. В качестве некоторого подведения итогов, ниже они приведены полностью.

Дескриптор сегмента данных, регистр не SS

При загрузке селектора в регистр:

  1. Если селектор нулевой, он просто загружается, никаких проверок не проводится.
  2. Если находящийся в селекторе индекс дескриптора выходит за границы GDT, генерируется исключение #GP.
  3. Анализируется тип дескриптора. Флаг S должен быть установлен в 1, дескриптор должен описывать либо сегмент данных, либо доступный для чтения сегмент кода, иначе генерируется исключение #GP.
  4. Анализируется поле DPL дескриптора. Должны выполняться неравенства DPL >= CPL и DPL >= RPL селектора (для подчинённых сегментов кода этот пункт будет отличаться), иначе генерируется исключение #GP.
  5. Анализируется флаг P дескриптора, он должен быть установлен в 1, подробнее – см. приложение.

При выполнении операции:

  1. Если в регистре нулевой селектор, исключение #GP.
  2. Если попытка записи в read-only сегмент, исключение #GP.
  3. Если адрес выходит за границы сегмента, исключение #GP.

Дескриптор сегмента данных, регистр SS

При загрузке селектора в регистр:

  1. Если селектор нулевой, генерируется исключение #GP.
  2. Если находящийся в селекторе индекс дескриптора выходит за границы GDT, генерируется исключение #GP.
  3. Анализируется тип дескриптора. Дескриптор должен описывать доступный для записи сегмент данных, иначе генерируется исключение #GP.
  4. Анализируется поле DPL дескриптора. Должны выполняться равенства DPL == CPL и DPL == RPL селектора, иначе генерируется исключение #GP.
  5. Анализируется флаг P дескриптора, он должен быть установлен в 1, подробнее – см. приложение.

При выполнении операции – то же, что и в случае обычного сегментного регистра, отличие только в том, что две первые проверки никогда не сработают: в SS не может находиться нулевой селектор или селектор read-only сегмента.

Пример

Программа выполняет следующие действия:

В итоге на экране будет три восклицательных знака.

ПРЕДУПРЕЖДЕНИЕ

Как выяснилось, VMWare Workstation 4.0.5 build-6030 ведёт себя не совсем корректно. Сценарий:

* Тот же пример, но значение RPL селектора установлено в 1

* После последней перезагрузки виртуальной машины не запускались версии примера с RPL равным 2 или 3

Результат:

* На экране нет ни одного восклицательного знака, т.е. установка fs отработала без ошибок

Версии с RPL равным 2 и 3 работают нормально, после них версия с RPL равным 1 тоже работает нормально. В VMWare Workstation 5.0.0 build-12124 ошибка исправлена.

; rpl.asm
; Программа, проверяющая действие поля RPL

        .model tiny
        .code
        .386p
        org     100h

;;;;;;;;;;;;;;;;;;;;;;;;;;;        
;
; Структуры
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;

; Сегментный дескриптор
segment_descriptor struct
    limit_low   dw      0    ; Младшие два байта поля Segment limit
    base_low    dw      0    ; Младшие два байта поля Base Address
    base_high0  db      0    ; Второй байт поля Base Address
    type_and_permit db  0    ; Флаги
    flags       db      0    ; Ещё одни флаги
    base_high1  db      0    ; Старший байт поля Base Address
segment_descriptor ends

; Дескриптор шлюза
gate_descriptor struct
    offset_low  dw      0    ; Два младших байта поля Offset
    selector    dw      0    ; Поле Segment Selector
    zero        db      0
    type_and_permit db  0    ; Флаги
    offset_high dw      0    ; Старшие байты поля Offset
gate_descriptor ends

; Регистр, описывающий таблицу
table_register struct
    limit       dw      0    ; Table Limit
    base        dd      0    ; Linear Base Address
table_register ends

;;;;;;;;;;;;;;;;;;;;;;;;;;
;
; Код
;
;;;;;;;;;;;;;;;;;;;;;;;;;;

start:
        ; Подготавливаем DS
        push    cs
        pop     ds

        ; В es - начало видеобуфера. Можно было сделать то же
        ; самое средствами защищённого режима, но так проще
        push    0b800h
        pop     es

        ; Устанавливаем правильный сегмент в long-jmp-to-RM
        mov     ax, cs
        mov     cs:rm_cs, ax

        ; Прописываем адрес начала cs в качестве базового адреса сегментов
        call    cs_to_eax
        mov     cs_dsc.base_low, ax
        shr     eax, 16
        mov     cs_dsc.base_high1, al

        ; Сохраняем IDTR реального режима
        sidt    fword ptr old_idtr

        ; Запрешаем прерывания
        call disable_interrupts
        ; Инициализируем IDT
        call initialize_idt
        ; Инициализируем GDT
        call initialize_gdt
        ; Переключаем режим
        call set_PE

        ; 16-разрядный дальний переход. Перключает содержимое cs из нормального
        ; для реального режима (адрес) в нормальное для защищённого (селектор).
        ; Базовый адрес целевого сегмента совпадает с cs,
        ; поэтому смещение можно прописать сразу
        db      0EAh   ; код команды дальнего перехода
        dw      $ + 4  ; смещение
        dw      8      ; селектор

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

        ; Селектор второго дескриптора GDT, RPL равен 3
        mov ax, 10011b
        ; Попытка поместить селектор в сегментный регистр
        mov fs, ax

        call clear_PE

        ; Мы в реальном режиме, осталось разобраться с
        ; значением регистра cs

        ; 16-разрядный дальний переход. Перключает содержимое cs из нормального
        ; для защищённог режима (селектор) в нормальное для реальног (адрес).
        ; Адрес сегмента вычисляется и прописывается во время выполнения.
        db      0EAh   ; код команды дальнего перехода
        dw      $ + 4  ; смещение
rm_cs   dw      0      ; сегмент

        ; восстанавливаем IDTR реального режима
        lidt    fword ptr old_idtr

        call enable_interrupts
        ret

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; Обработчики
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

intGpf_handler:
        push   eax
        push   ebx

        mov     bh, 07h       ; белый текст на чёрном фоне
        mov     bl, '!'

        mov     eax, 80 * 24 * 2
        mov     word ptr es:[eax], bx

        call upscroll_screen  ; прокручивает экран на строчку вверх

        ; Запоминаем пятый байт дескриптора в al и ah
        mov     al, res_dsc.type_and_permit
        mov     ah, al

        ; Оставляем в al только DPL (биты 5-6), инкрементируем это поле
        and     al, 01100000b
        add     al, 00100000b
        and     al, 01100000b

        ; Обнуляем в ah поле DPL
        and     ah, 10011111b

        ; Совмещаем
        or      ah, al
        
        ; И кладём в дескриптор
        mov     res_dsc.type_and_permit, ah

        pop     ebx
        pop     eax
        add     esp, 4

        iretd

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; Данные
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

; Глобальная таблица дескрипторов
GDT     label byte
        ; Нулевой дескриптор
        segment_descriptor <>
        ; Дескриптор сегмента кода, размер 64 Kb
cs_dsc  segment_descriptor <0ffffh, 0, 0, 10011010b, 0, 0>
        		; 10011010b - 1001, C/D - 1, 0, R/W - 1, 0
        		; 0         - G - 0, 000, Limit - 0
        ; Дескриптор ресурса, в данном случае – сегмента данных
res_dsc  segment_descriptor <0ffffh, 0, 0, 10010010b, 10001111b, 0>
        		; 10011010b - 1001, C/D - 0, 0, R/W - 1, 0
        		; 10001111b - G - 1, 000, Limit - 1111
       		
; Данные для загрузки в GDTR
gdtr    table_register <$ - GDT - 1, 0>


; Таблица дескрипторов прерываний
IDT     label byte
        db    13 dup (  8 dup (0)) ; 0 – 12
        ; Дескриптор шлюза ловушки, обработчик #GP
        gate_descriptor <intGpf_handler, 8, 0, 8Fh, 0>

; Данные для загрузки в IDTR
idtr    table_register <$ - IDT - 1, 0>

; Место для хранения IDTR реального режима
old_idtr table_register <>  

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; Служебные функции
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

; Инициализирует GDT
initialize_gdt:
        ; Вычисляем линейный адрес начала массива дескрипторов
        call    cs_to_eax
        add     eax, offset GDT
        ; Записываем его в структуру
        mov     dword ptr gdtr.base, eax

        ; Загружаем GDTR
        lgdt    fword ptr gdtr
        ret

; Инициализирует IDT
initialize_idt:
        ; Вычисляем линейный адрес начала массива дескрипторов
        call    cs_to_eax
        add     eax, offset IDT
        ; Записываем его в структуру
        mov     dword ptr idtr.base, eax

        ; Загружаем GDTR
        lidt    fword ptr idtr
        ret

; Запрещает маскируемые и немаскируемые прерывания
disable_interrupts:
        cli              ; запретить прерывания
        in      al, 70h  ; индексный порт CMOS
        or      al, 80h  ; установка бита 7 в нем запрещает NMI
        out     70h, al
        ret

; Разрешает маскируемые и немаскируемые прерывания
enable_interrupts:
        in      al, 70h  ; индексный порт CMOS
        and     al, 7Fh  ; сброс бита 7 отменяет блокирование NMI
        out     70h, al
        sti              ; разрешить прерывания
        ret

; Устанавливает флаг PE
set_PE:
        mov     eax, cr0 ; прочитать регистр CR0
        or      al, 1    ; установить бит PE,
        mov     cr0, eax ; с этого момента мы в защищенном режиме
        ret

; Сбрасывает флаг PE
clear_PE:
        mov     eax, cr0 ; прочитать CR0
        and     al, 0FEh ; сбросить бит PE
        mov     cr0, eax ; с этого момента мы в реальном режиме
        ret

; Вычисляет линейный адрес начала сегмента кода
cs_to_eax:
        mov     eax, 0
        mov     ax, cs
        shl     eax, 4
        ret

; Прокручивает экран на строчку вверх
upscroll_screen:
        push   eax
        push   ecx
        push   edx

        mov    eax, 0         ; Текущий символ
        mov    ecx, 80 * 24   ; Колическтво символов на экране
                              ; (без последней строки, она отдельно)
screen_loop:
        mov    dx, word ptr es:[eax * 2 + 80*2]
        mov    word ptr es:[eax * 2], dx      ; Меняем символ
        inc    eax
        loop   screen_loop

        mov    ecx, 80        ; Длинна последней строки
        mov    dx,  0720h     ; символ, которым заполняется строка

last_line_loop:
        mov    word ptr es:[eax * 2], dx      ; Меняем символ
        inc    eax
        loop   last_line_loop

        pop    edx
        pop    ecx
        pop    eax
        ret

        end     start

Задания


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