Какая информация может содержаться в таблице дескрипторов прерываний
Лекция №13
Система
прерываний 32-разрядных микропроцессоров i80x86.
Работа системы
прерываний в защищенном режиме
Вопросы
1.
Отличия работы системы прерываний в защищенном режиме
2.
Классификация прерываний в защищенном режиме
3. Таблица
дескрипторов прерываний IDT
4. Обобщенная
схема обработки прерывания в защищенном режиме
5. Шлюз
ловушки
6. Шлюз
прерывания
7. Шлюз
задачи
8.
Резюме
Обработка прерываний в защищенном
режиме отличается от обработки в реальном режиме так же сильно, как и
защищенный режим отличается от реального.
Потому что:
1.
В защищенном режиме немного изменено распределение
номеров векторов прерываний. (Можно самостоятельно посмотреть по таблицам,
например, в книге Аssembler Юрова,
Хорошенко на стр. 439-441)
2.
Принципиально иным является механизм обработки
прерываний.
Классификация прерываний в защищенном режиме
Прерывания и исключения можно разделить
на несколько групп:
1.
сбой;
2.
ловушка;
3.
аварийное завершение.
Это деление производится в
соответствии со следующими признаками:
· какая
информация сохраняется о месте возникновения прерывания (исключения)?
· возможно
ли возобновление прерванной программы?
Исходя из этих признаков, можно
дать следующие характеристики вышеперечисленным группам:
Сбой (ошибка) — прерывание или исключение, при
возникновении которого в стек записываются значения регистров cs: ip, указывающие на команду,
вызвавшую данное прерывание. Это позволяет, получив доступ к сегменту кода,
исправить ошибочную команду в обработчике прерывания и, вернув управление
программе, фактически осуществить ее рестарт (вспомните, что в реальном режиме
при возникновении прерывания в стеке всегда запоминается адрес команды,
следующей за той, которая вызвала это прерывание).
Ловушка — прерывание или исключение, при
возникновении которого в стек записываются значения регистров cs: ip,
указывающие на команду, следующую за командой, вызвавшей данное
прерывание.
Так же, как и в случае
ошибок возможен рестарт программы. Для этого необходимо лишь исправить в
обработчике прерывания соответствующие код или данные, послужившие источником
ошибки. После этого перед возвратом управления нужно скорректировать значение ip в
стеке на длину команды, вызвавшей данное прерывание.
Механизм
ловушек похож на механизм прерываний в реальном режиме, хотя не во всем. Здесь есть один тонкий
момент. Если прерывание типа ловушки возникло в команде передачи управления jmp, то
содержимое пары cs: ip в стеке будет отражать результат этого перехода,
то есть соответствовать команде назначения.
Аварийное
завершение — прерывание, при
котором информация о месте его возникновения недоступна или неполна и поэтому
рестарт практически невозможен, если только данная ситуация не была
запланирована заранее.
Þ
соответствующие программы-обработчики ошибок, ловушек и аварий будут отличаться
алгоритмами работы.
Микропроцессор
жестко определяет, какие прерывания являются ошибками, ловушками и авариями.
Замечания.
Для некоторых
номеров прерываний в реальном режиме возможны 2 источника прерывания
(развитие микропроцессоров).
Некоторые
прерывания при своем возникновении дополнительно генерируют и записывают в
стек так называемый код ошибки. Этот код
может впоследствии использоваться для установления источника прерывания. Код
ошибки записывается в стек вслед за содержимым регистров eflags, cs и eip.
Þ
можно распознать истинный источник прерывания (если есть неоднозначность).
Таблица дескрипторов прерываний IDT
Вместо таблицы векторов
прерываний система прерываний имеет дело с таблицей дескрипторов прерываний (IDT, interrupt descriptor table)
Таблица IDT
связывает каждый вектор прерывания с дескриптором процедуры или задачи, которая
будет обрабатывать это прерывание.
Элементами
таблицы IDT являются
дескрипторы – Размер дескриптора = 8 байтам.
(Т.е. формат
таблицы IDT подобен формату GDT и LDT.)
Дескрипторы
в таблице прерываний обычно называются шлюзами
(иногда коммутаторами).
Шлюзы
предназначены для указания точки входа в программу обработки прерывания.
В дескрипторной
таблице прерываний IDT могут содержаться шлюзы трех типов:
·
шлюз ловушки;
·
шлюз прерывания;
· шлюз
задачи.
Физически
микропроцессор отличает шлюзы по содержимому полей.
Особенности
таблицы IDT:
· нулевой
дескриптор используется для описания шлюза для программы обработки
исключительной ситуации 0 (ошибка деления);
(в
таблице GDT отсутствует)
· дескрипторы
в таблице IDT строго упорядочены в соответствии с номерами прерываний.
В
таблицах GDT и LDT порядок описания дескрипторов роли не играет, хотя и
допускается наличие некоторых соглашений по их упорядоченности;
· размерность
таблицы IDT — не более
256 элементов размером по восемь байт, по числу возможных источников
прерываний.
В отдельных случаях есть
смысл описывать все 256 дескрипторов этой таблицы, формируя для неиспользуемых
номеров прерываний шлюзы-заглушки. Это позволит корректно обрабатывать все
прерывания, даже если они и не планируются к использованию в данной задаче.
Если этого не сделать, то при незапланированном прерывании с номером,
превышающим пределы IDT для данной задачи, будет возникать исключительная
ситуация общей защиты (с номером 13 (ODh)).
Адрес и длина
таблицы IDT содержатся в регистре idtr.
Заметим, что в реальном режиме
регистр idtr также содержит адрес
таблицы прерываний, но при этом каждый вектор занимает 4 байта и содержит
32-разрядный адрес в формате CS:IP. Начальное значение этого
регистра равно нулю, но внего можно занести и другое значение.
Þ Возможно произвольное
размещение в памяти этой таблицы не только в защищенном режиме, но и реальном.
В защищенном режиме произвести загрузку регистра idtr может только код с максимальным
уровнем привилегий.
Доступ к таблице IDT со стороны
пользовательских (прикладных) программ невозможен.
Обобщенная схема обработки прерывания в защищенном режиме
При возникновении прерывания от
источника с номером п микропроцессор,
находясь в защищенном режиме, выполняет следующие действия:
1.
Определяет местонахождение таблицы IDT, адрес и размер которой содержится в
регистре idtr.
2.
Складывает значение адреса, по которому размещена IDT, и значение n*8. По данному смещению в
таблице IDT должен находиться 8-байтовый дескриптор,
определяющий местоположение процедуры обработки прерывания.
3.
Переключается на процедуру обработки прерывания.
Шлюз
ловушки
Формат шлюза ловушки —
offset_2 | p | dpl | 1 1 1 1 | 000 | не используется |
63 47
39 36 32
indicator | offset_1 |
31 15
Offset_1, offset_2 – первая и
вторая половины смещения в сегменте
indicator – Селектор,
указывающий на дескриптор в LDT
или GDT
000 – Постоянное
значение
type – 1111 – Тип шлюза —
ловушка
dpl – Определение
минимального уровня привилегий задачи, которая может передать управление
обработчику прерываний через данный шлюз (обычно dpl =
112)
p – Бит присутствия (0 или 1)
Когда возникает
прерывание и его вектор выбирает в таблице IDT дескриптор шлюза с типом ловушки, микропроцессор сохраняет в
стеке информацию о месте, где он прервал работу текущей программы. После этого
он передает управление в соответствии с полями indicator и offset.
Поле indicator представляет
селектор одной из таблиц, GDT или LDT, в зависимости от состояния бита TI в нем.
Поле offset определяет смещение в
сегменте кода. Этот сегмент кода описывается дескриптором, на который указывает
селектор в поле indicator.
После того как
управление было передано обработчику прерывания, он выполняет свою работу до
тех пор, пока не встретит команду iret. Эта команда восстанавливает из стека состояние регистров eflags, cs и eip на момент возникновения
прерывания, и работа приостановленной программы продолжается.
При подготовке выхода из программы обработки прерывания
имейте в виду, что команда iret ничего не знает о возможности наличия в стеке
кода ошибки, поэтому для корректного возврата управления не забудьте при
необходимости предварительно удалить командой pop код ошибки из стека.
Шлюз
прерывания
Шлюз
прерывания отличается
только полем типа = 1110.
При
возникновении прерывания, которому соответствует шлюз прерывания,
микропроцессор выполняет те же действия, что и для шлюза ловушки, но с одним важным
отличием:
при
передаче управления обработчику прерывания через шлюз прерывания,
микропроцессор сбрасывает флаг прерывания в регистре eflags в 0, запрещая тем самым обработку аппаратных прерываний.
Этот факт имеет важное значение для программирования обработчиков аппаратных и
программных прерываний.
Совет. Если у вас есть сомнение в том, какой из шлюзов –
прерывания или ловушки – использовать — применяйте шлюз прерывания.
Шлюз задачи
Шлюз
задачи отличается
1) поле типа = 1110
2) поле indicator вместо селектора сегмента кода указывает на дескриптор сегмента состояния задачи (TSS).
Осуществляется
переход на новую задачу, которая будет осуществлять обработку прерывания. Задача является частью механизма
многозадачности. Т.е. производится переключение задач. (Текущее состояние
прерываемой задачи аппаратно сохраняется в ее собственном TSS.)
Для каждой задачи
определяется сегмент состояния задачи TSS (Task Segment Status) со строго определенной структурой. В этом сегменте
есть поля для сохранения всех регистров общего назначения, некоторых системных
регистров и другой информации. Всю совокупность этой информации называют контекстом задачи. Этот сегмент
описывается, подобно другим сегментам, дескриптором в таблице GDT или LDT.
Если с помощью некоторого
селектора обратиться к такому дескриптору, то микропроцессор осуществит
переключение на соответствующую задачу. Подобные переключения могут, в частности, осуществляться
операционной системой, поддерживающей многозадачность, в соответствии с
некоторой дисциплиной разделения времени между задачами. Переключение задач
может производиться обычными командами межсегментной передачи управления
либо по возникновению прерывания при переходе к обработчику прерывания
через шлюз задачи.
РЕЗЮМЕ
Шлюзы ловушки и
прерывания с помощью полей
indicator и offset
определяют адрес, по которому находится точка входа в программу обработки
прерывания.
Шлюз задачи
предназначен для реализации принципиально иного перехода к обработчику
прерываний — с использованием механизма переключения задач.
Обратим внимание, разработчики современных ОС
редко используют шлюз задачи, т.к. переключение на другую задачу требует
существенно больших затрат времени, а полное сохранение всех рабочих регистров
часто не требуется. Таким образом, используются чаще всего шлюзы прерываний.
Источник
Таблицы дескрипторов прерываний ( IDT ) представляет собой структуру данных , используемой архитектуры x86 реализовать вектор прерывания таблицу. IDT используется процессором для определения правильного ответа на прерывания и исключения .
Подробности в описании ниже относятся конкретно к архитектуре x86 и архитектуре AMD64 . Другие архитектуры имеют аналогичные структуры данных, но могут вести себя по-другому.
Использование IDT запускается тремя типами событий: аппаратными прерываниями, программными прерываниями и исключениями процессора, которые вместе называются прерываниями . IDT состоит из 256 векторов прерываний, первые 32 из которых (0–31 или 0x00–0x1F) зарезервированы для исключений процессора.
Реальный режим
В процессоре 8086 таблица прерываний называется IVT (таблица векторов прерываний). IVT всегда находится в одном и том же месте в памяти, в диапазоне от 0x0000до 0x03ff, и состоит из 256 четырехбайтовых дальних указателей реального режима (256 × 4 = 1024 байта памяти).
Указатель реального режима определяется как 16-битный сегмент и 16-битное смещение в этом сегменте. Сегмент внутренне расширяется процессором до 20 бит, сдвигая его на 4 бита влево, тем самым ограничивая обработчики прерываний реального режима первым 1 мегабайтом памяти. Первые 32 вектора зарезервированы для внутренних исключений процессора, а аппаратные прерывания могут быть отображены на любой из векторов с помощью программируемого контроллера прерываний.
На 80286 и более поздних версиях размер и расположение IVT могут быть изменены таким же образом, как это делается с IDT в защищенном режиме (т. Е. С помощью инструкции LIDT), хотя это не меняет его формат. 80286 также представил область верхней памяти , которая увеличивает ограничение адреса в реальном режиме на 65 520 байт.
Обычно используется x86 прерываний реального режима является INT 10, то Video BIOS код для обработки примитивный экран функции рисования , такие как рисование пикселей и изменение разрешения экрана.
Защищенный режим
В защищенном режиме IDT представляет собой массив дескрипторов, последовательно сохраняемых в памяти и индексированных вектором прерывания. Длина полностью заполненного IDT составляет 2 КБ в 32-битном защищенном режиме (256 записей по 8 байтов каждая) и 4 КБ в 64-битном защищенном режиме (256 записей по 16 байтов каждая). Нет необходимости использовать все возможные записи: достаточно заполнить IDT до самого высокого используемого вектора прерывания и соответственно установить часть длины IDT IDTR.
IDT защищенного режима может находиться где угодно в физической памяти. В процессоре есть специальный регистр ( IDTR) для хранения как физического базового адреса, так и длины IDT в байтах. Когда происходит прерывание, процессор умножает вектор прерывания на размер дескриптора и добавляет результат к базовому адресу IDT. Затем с помощью длины IDT проверяется, находится ли результирующий адрес памяти в таблице; если он слишком большой, создается исключение. Если все в порядке, загружается дескриптор, хранящийся в вычисленной ячейке памяти, и предпринимаются действия в соответствии с типом и содержимым дескриптора.
Дескрипторы могут быть либо шлюзами прерывания, либо шлюзами-ловушками, либо, только для 32-битного защищенного режима, шлюзами задач. Шлюзы прерывания и ловушки указывают на ячейку памяти, содержащую код для выполнения, указывая как сегмент (присутствующий в GDT или LDT ), так и смещение внутри этого сегмента. Единственное различие между этими двумя параметрами заключается в том, что шлюз прерывания отключает дальнейшую обработку процессором аппаратных прерываний, что делает его особенно подходящим для обслуживания аппаратных прерываний, в то время как вентиль прерывания оставляет аппаратные прерывания включенными и, таким образом, в основном используется для обработки программных прерываний и исключений. Наконец, шлюз задачи вызовет переключение текущего активного сегмента состояния задачи, используя аппаратный механизм переключения задач, чтобы эффективно передать использование процессора другой программе, потоку или процессу.
Векторы 0-31 зарезервированы Intel для процессоров генерируются исключений ( общая ошибки защиты , неисправность страницы и т.д.). В настоящее время процессоры Intel используют только векторы 0–20, а процессоры AMD используют векторы 0–19 и 29–30. Однако будущие процессоры могут создать несовместимость для сломанного программного обеспечения, которое использует эти векторы для других целей.
Аппаратные исключения
Все INT_NUM от 0x0 до 0x1F включительно зарезервированы для исключений; INT_NUM больше 0x1F используются для обработки прерываний. (Обратите внимание, что IBM PC не всегда подчинялся этому правилу, например, используя прерывание 5, чтобы указать, что была нажата клавиша Print Screen .)
INT_NUM | Краткое описание PM |
---|---|
0x00 | Деление на ноль |
0x01 | Одношаговое прерывание (см. Флаг прерывания ) |
0x02 | НМИ |
0x03 | Точка останова (вызывается специальной 1-байтовой инструкцией 0xCC, используемой отладчиками) |
0x04 | Переполнение |
0x05 | Границы |
0x06 | Неверный код операции |
0x07 | Сопроцессор недоступен |
0x08 | Двойная ошибка |
0x09 | Переполнение сегмента сопроцессора (только 386 или более ранняя версия) |
0x0A | Недействительный сегмент состояния задачи |
0x0B | Сегмент отсутствует |
0x0C | Ошибка стека |
0x0D | Ошибка общей защиты |
0x0E | Ошибка страницы |
0x0F | зарезервированный |
0x10 | Математическая ошибка |
0x11 | Проверка выравнивания |
0x12 | Проверка машины |
0x13 | Исключение SIMD с плавающей запятой |
0x14 | Исключение виртуализации |
0x15 | Исключение защиты управления |
Крючок
Некоторые программы Windows перехватывают вызовы IDT. Это включает в себя написание режима ядра драйвер , который перехватывает вызовы к IDT и добавляет в ее собственной обработке. Это никогда официально не поддерживалось Microsoft , но не было программно предотвращено в ее операционных системах до 64-битных версий Windows, где драйвер, который пытается использовать ловушку режима ядра , заставит машину выполнить проверку ошибок .
Ссылки
Общее
- Руководство разработчика программного обеспечения для архитектур Intel 64 и IA-32, Том 3: Руководство по системному программированию
внешние ссылки
- Руководство разработчика программного обеспечения для архитектур Intel 64 и IA-32, том 3A: Руководство по системному программированию, часть 1 (см. ГЛАВУ 5, ОБРАБОТКА ПРЕРЫВАНИЙ И ИСКЛЮЧЕНИЙ и ГЛАВУ 10, РАСШИРЕННЫЙ ПРОГРАММИРУЕМЫЙ КОНТРОЛЛЕР ПРЕРЫВАНИЯ)]
- Таблица дескрипторов прерываний на OSDev.org
Источник
Автор: sergh
The RSDN Group
Опубликовано: 02.04.2006
Версия текста: 1.0
Any time at all, any time at all, any time at all,
All you’ve gotta do is call and I’ll be there.
John Lennon
Конечно же, вы знаете, что такое прерывание (interrupt), слышали о таблице векторов прерываний (interrupt vector table) и вообще довольно неплохо представляете, как прерывания обрабатываются в реальном режиме 🙂
Если это случайно не так, пожалуйста, обратитесь к литературе по программированию на ассемблере или спросите у более опытных в этом вопросе товарищей. Несмотря на то, что глава содержит всю необходимую информацию, вам будет значительно проще, если перед прочтением вы более-менее разберётесь с обработкой прерываний в реальном режиме. Причём желательно не ограничиваться теорией, а написать хотя бы одну работающую программу.
Но, тем не менее, пару общих слов сказать нужно, так как тема важная и непростая.
Общие слова
Программные прерывания (software generated interrupts) вызываются инструкциями int x, где x – любое число от 0 до 255 (int 3 несколько отличается от прочих, подробнее эта тема освещена в следующей главе). Это, в общем-то, почти и не прерывания вовсе, так как ничего непонятного в них нет 🙂 Фактически это просьба к процессору вызвать определённую подпрограмму, которая должна быть специальным образом зарегистрирована в системе, она называется обработчик прерывания (interrupt handler)
В защищённом режиме это не обязательно будет подпрограмма, есть ещё один вариант, он будет обсуждаться отдельно, в главе «Многозадачность».
Естественно, защищённый режим, так же как и реальный, предоставляет возможность обработки прерываний, не менее естественно, что, по сравнению с реальным режимом, в нём многое изменилось. Но, что приятно, сохранились основные положения:
- Прерывания нумеруются, диапазон номеров от 0 до 255.
- Обработчик прерывания это некоторая подпрограмма.
- Где-то в памяти находится таблица, устанавливающая соотношение между номерами прерываний и их обработчиками.
- В таблице в некотором формате хранится адрес обработчика прерывания.
Таблица дескрипторов прерываний
Аналог таблицы векторов прерываний, существующий в защищённом режиме, называется таблицей дескрипторов прерываний (Interrupt Descriptor Table, IDT).
Это термин из официального руководства Intel. В книге Дэвида Соломона и Марка Руссиновича «Внутреннее устройство Windows 2000» (Питер, Русская Редакция, 2001) упоминается IDT, но, почему-то, там она названа таблица диспетчеризации прерываний (interrupt dispatch table), причём это не ошибка русских переводчиков, в английском оригинале используется такое же название. На мой взгляд, следует придерживаться терминологии Intel.
По формату и способу инициализации IDT практически идентична GDT. Основные отличия:
- Содержимое – в IDT находятся дескрипторы других типов. Первый будет рассмотрен ниже в этой главе, второй – в главе «Внешние прерывания», третий и последний – в главе «Многозадачность».
- Регистр, определяющий размер и положение таблицы называется IDTR, команда для его загрузки – lidt.
- В отличие от GDT, нулевой дескриптор IDT это обычный, полноценный дескриптор, к которому вполне возможно обращение.
- Размер IDT не должен превышать 256 дескрипторов.
Таким образом, слегка модифицируя код из предыдущей главы, получаем пример инициализации IDT:
…
mov eax, 0
mov ax, ds
shl eax, 4
add eax, offset IDT
mov dword ptr offset idtr + 2, eax
lidt fword ptr idtr
…
IDT label byte
db …
…
db …
idt_len equ $ – IDT
idtr dw idt_len – 1
dd ?
Точно так же, как и в случае с lgdt, нормальные пользовательские приложения не имеют доступа к инструкции lidt. Этот факт вместе с возможностью поместить IDT в недоступную приложениям область памяти позволяет ОС контролировать обработку прерываний. Подробнее, как обычно, в главе про защиту.
Несложно заметить, что та же самая структура table_register подходит и для загрузки в IDTR.
Дескриптор шлюза ловушки
Надпись маркером в лифте.
Как уже говорилось в предыдущей главе, дескриптор это структура, описывающая некую системную сущность. Очередным типом дескрипторов, с которым вам придется познакомиться, будет дескриптор шлюза ловушки (trap gate descriptor; в [Гук 1999] слово «gate» переводится как «вентиль», это стандартный перевод для схемотехники, программисты обычно говорят «шлюз»), он предназначен для IDT и имеет следующий формат:
Положение | Название | Краткое описание |
---|---|---|
Нулевой и первый байты | Offset (part 1) | Младшие два байта 32-х битного поля Offset. Поле Offset содержит смещение обработчика прерывания. |
Второй и третий байты | Segment Selector | Селектор сегмента, содержащего обработчик прерывания. |
Четвёртый байт | 0, просто 0. Эта часть дескриптора не используется. | |
0-й – 3-й биты пятого байта | ?? | Для дескриптора шлюза ловушки значение должно быть равно 1111b. Подробности про это и следующее поле в разделе «[Лирическое отступление] Классификация дескрипторов». |
4-й бит пятого байта | ?? | Устанавливайте в 0. |
5-й – 7-й биты пятого байта | ?? | Пока неважно, устанавливайте в 100b. |
Шестой и седьмой байты | Offset (part 2) | Старшие два байта поля Offset. |
Таблица 1. Формат дескриптора шлюза ловушки.
То же самое на картинке:
Рисунок 1. Формат дескриптора шлюза ловушки.
Основная информация, которую несёт в себе дескриптор шлюза ловушки, это:
- Указание на то, что данный дескриптор действительно является дескриптором шлюза ловушки.
- Адрес обработчика прерывания в формате <селектор>:<смещение>.
Поскольку формат практически прозрачен, остаётся только привести пример:
db 04h
db 03h
db 8
db 0
db 0 db 10001111b
db 02h
db 01h
Это дескриптор шлюза ловушки, селектор сегмента указывает на первый дескриптор GDT, смещение – 01020304h. В виде структуры дескриптор шлюза выглядит так:
gate_descriptor struct
offset_low dw 0
selector dw 0
zero db 0
type_and_permit db 0
offset_high dw 0
gate_descriptor ends
[Лирическое отступление] Классификация дескрипторов
Сегмента-кода-или-данных/системный
Первый признак классификации дескрипторов – значение 4-го бита пятого байта дескриптора. Полное название этого бита – флаг «descriptor type», краткое – флаг «S» (довольно странное сокращение, но везде применяется именно оно). В соответствии с этим признаком, дескрипторы делятся на два класса:
- Если флаг установлен, то это дескриптор сегмента кода/данных. Они были более-менее рассмотрены в предыдущей главе
- Если флаг сброшен – системный дескриптор (system descriptor). В том числе, к этому классу относится и дескриптор шлюза ловушки.
Принципиальное отличие системных дескрипторов от дескрипторов сегмента кода/данных в том, что селекторы первых не могут быть загружены в сегментные регистры и участвовать в формировании логического адреса <сегмент>:<смещение>. Даже если системный дескриптор описывает сегмент (вы познакомитесь с таким дескриптором в главе «Первая преамбула: одна задача»), для доступа к данным придётся использовать сегмент кода/данных (с соответствующим дескриптором и селектором), проецирующийся на нужную область памяти.
Системные дескрипторы имеют различный формат и единственное общее отличие их формата от формата дескрипторов сегмента кода/данных – младшие четыре бита пятого байта. Для обоих видов дескрипторов это поле называется «Type» и уточняет тип дескриптора, но при этом используются разные подходы:
- Для дескрипторов сегмента кода/данных поле состоит из четырёх однобитовых флагов (два из них – флаги C/D и W/R – были рассмотрены в предыдущей главе).
- Для системных дескрипторов это просто номер, задающий тип дескриптора. Как уже было сказано, для дескриптора шлюза ловушки значение должно быть равно 1111b.
Сегмента/шлюза
Второй признак классификации – характер сущности, описываемой дескриптором. Это может быть:
- Сегмент. О том, что такое сегмент было подробно написано в предыдущей главе, соответствующий дескриптор называется дескриптором сегмента. К ним относятся дескрипторы сегмента кода/данных и несколько типов системных дескрипторов.
- Шлюз. В данном случае это слово следует трактовать как «точка входа», то есть (несколько упрощая) адрес начала некоторой подпрограммы. Дескриптор называется дескриптором шлюза (gate descriptor), к ним относятся остальные системные дескрипторы, в частности дескриптор шлюза ловушки.
Дескрипторы, описывающие похожие сущности, имеют близкие форматы. Так, любой дескриптор сегмента по формату похож на дескриптор сегмента кода/данных, а любой дескриптор шлюза – на дескриптор шлюза ловушки.
Всё вместе
– Ура!!!
– Каждому шлюзу по обработчику!
– Ура!!!
– Каждому обработчику по прерыванию!
[вопрос с места]
– А правда, что прерываний только 255 и их не хватит всем пятистам обработчикам?
– Нет!
– Ура!!!
…
Популистское выступление продолжается.
Докладчику пока удаётся не врать…
Объединим всё, уже известное про прерывания и добавим недостающие детали.
Вызов обработчика прерывания
Целью всего механизма обработки прерываний является своевременный вызов обработчика. В нашем случае (остальные случаи – в остальных главах, описывать всё сразу совершенно ни к чему) вызов происходит примерно так:
- По номеру прерывания в IDT отыскивается нужный дескриптор, из него извлекается селектор сегмента и смещение начала обработчика.
- Проверяется корректность селектора (указывает на дескриптор в пределах GDT, это дескриптор сегмента кода, и т.п.) и смещения (попадает в сегмент).
- В стеке сохраняются регистр EFLAGS и дальний указатель на команду, следующую за int x, то есть регистр CS и значение регистра EIP, увеличенное на размер команды int x. На каждый из регистров в стеке выделяется по 4 байта, рядом с CS находятся два «лишних» байта.
- CS и EIP загружаются новыми значениями.
- Начинается выполнение обработчика. Дескриптор шлюза ловушки не предназначен для обработки внешних (аппаратных) прерываний, поэтому аппаратные прерывания не маскируются автоматически при входе в обработчик.
- После своего завершения обработчик возвращает управление командой iretd. В отличии от iret, она предназначена для случая, когда в стеке сохранены 32-х разрядные EIP, CS и EFLAGS, а не 16-ти разрядные IP, CS и FLAGS (подробнее про 16-ти и 32-х разрядность см. в приложении).
- Возврат к исходной программе. В регистры EFLAGS, CS и EIP загружается значение из стека, при этом происходит обращение к GDT и проверка корректности устанавливаемого селектора и смещения.
Ещё раз обращаю ваше внимание: независимо от префиксов команд, разрядности сегмента стека, разрядности целевого и исходного сегмента кода и т.п. (всё это будет обсуждаться в приложении в разделе про разрядность), под EFLAGS, CS, EIP в стеке выделяется 12 байт, по 4 байта на каждый регистр.
Это связано с тем, что тип 1111b соответствует 32-х разрядному дескриптору шлюза ловушки. Существует полностью аналогичный 16-и разрядный, но он в курсе не рассматривается.
Упрощённая версия алгоритма в виде комикса:
Рисунок 2. Немного упрощенный алгоритм вызова обработчика прерывания.
Ключевые признаки, отделяющие «наш» случай от «не нашего»:
- Происходит программное прерывание.
- Соответствующая ячейка IDT содержит именно дескриптор шлюза ловушки.
- Несколько условий, связанных с защитой. Если дескрипторы в GDT и IDT такие, как рекомендовано (то есть, в описанные части дескрипторов записано что-то разумное, для «пока неважных» частей взяты значения, приведённые в таблицах), все условия выполняются. Подробнее эта тема рассмотрена в главах, посвящённых защите.
Переключение режимов: инициализация IDT
Добавим к алгоритмам переключения ещё несколько шагов. Из реального режима в защищённый:
- Запретить маскируемые и немаскируемые прерывания.
- Инициализировать GDT и загрузить её адрес в GDTR.
- Инициализировать IDT и загрузить её адрес в IDTR.
- Установить флаг PE (младший бит регистра CR0).
- Выполнить дальний переход (jmp или call). Это нужно для перезагрузки регистра CS.
- Перезагрузить все сегментные регистры.
В данном случае нельзя использовать CS, оставшийся «в наследство» от реального режима, так как при возврате из обработчика прерывания сохранённое в стеке значение CS будет толковаться как селектор сегмента.
Поскольку обрабатывать аппаратные прерывания мы пока не планируем, разрешать прерывания будет преждевременно.
Обратное переключение:
- Сделать текущим сегментом кода какой-либо доступный для чтения сегмент с пределом FFFFh байт.
- Загрузить во все сегментные регистры селекторы дескрипторов доступных для записи сегментов данных с пределом FFFFh.
- Сбросить флаг PE
- Выполнить дальний переход (jmp или call).
- Перезагрузить сегментные регистры.
- Восстановить в IDTR значение для реального режима.
- Разрешить прерывания.
Сохранение значения IDTR
Для того, чтобы при переключении в реальный режим восстановить значение IDTR, при старте программы его необходимо сохранить. Для этого предназначена команда sidt.
Пример использования:
…
sidt fword ptr old_idtr
…
old_idtr table_register <>
Пример
Обработчик int 0
Практически минимальная программа, устанавливающая обработчик нулевого прерывания и вызывающая его командой int 0.
Программа написана на основе примера cs64kb.asm из предыдущей главы.
.model tiny
.code
.386p
org 100h
segment_descriptor struct
limit_low dw 0
base_low dw 0
base_high0 db 0
type_and_permit db 0
flags db 0
base_high1 db 0
segment_descriptor ends
gate_descriptor struct
offset_low dw 0
selector dw 0
zero db 0
type_and_permit db 0
offset_high dw 0
gate_descriptor ends
table_register struct
limit dw 0
base dd 0
table_register ends
start:
push cs
pop ds
push 0b800h
pop es
mov ax, cs
mov cs:rm_cs, ax
call cs_to_eax
mov cs_dsc.base_low, ax
shr eax, 16
mov cs_dsc.base_high0, al
sidt fword ptr old_idtr
call disable_interrupts
call initialize_gdt
call initialize_idt
call set_PE
db 0EAh
dw $ + 4
dw 8
int 0
call clear_PE
db 0EAh
dw $ + 4
rm_cs dw 0
lidt fword ptr old_idtr
call enable_interrupts
ret
int0_handler:
push eax
push ecx
mov eax, 0
mov ecx, 80 * 25
screen_loop:
inc byte ptr es:[eax]
inc eax
inc byte ptr es:[eax]
inc eax
loop screen_loop
pop ecx
pop eax
iretd
GDT label byte
segment_descriptor <>
cs_dsc segment_descriptor <0ffffh, 0, 0, 10011010b, 0, 0>
gdtr table_register <$ – GDT – 1, 0>
IDT label byte
gate_descriptor <int0_handler, 8, 0, 8Fh, 0>
idtr table_register <$ – IDT – 1, 0>
old_idtr table_register <>
initialize_idt:
call cs_to_eax
add eax, offset IDT
mov idtr.base, eax
lidt fword ptr idtr
ret
initialize_gdt:
call cs_to_eax
add eax, offset GDT
mov gdtr.base, eax
lgdt fword ptr gdtr
ret
disable_interrupts:
cli
in al, 70h
or al, 80h
out 70h, al
ret
enable_interrupts:
in al, 70h
and al, 7Fh
out 70h, al
sti
ret
set_PE:
mov eax, cr0
or al, 1
mov cr0, eax
ret
clear_PE:
mov eax, cr0
and al, 0FEh
mov cr0, eax
ret
cs_to_eax:
mov eax, 0
mov ax, cs
shl eax, 4
ret
end start
Задания
- Перепишите программу так, чтобы вызывалось 29-е прерывание, но она продолжала бы работать.
- Вызовите прерывание, номер которого выходит за границы IDT
- Объявите IDT, содержащую более 256 дескрипторов. Есть ли в этом смысл?
- Запишите в IDT нулевой дескриптор, дескриптор сегмента данных, сегмента кода, вызовите соответствующие прерывания.
- Запишите в IDT дескриптор шлюза ловушки такой, что содержащийся в нём селектор: нулевой, ссылается за пределы GDT, ссылается на сегмент данных. Вызовите прерывание.
- Запишите в IDT дескриптор шлюза ловушки такой, что: содержащееся в нём смещение выходит за пределы сегмента, не соответствует началу обработчика. Вызовите прерывание.
- Замените в обработчике прерывания iretd на iret.
- Посмотрите, что находится в IDTR в реальном режиме. Запишите туда что-нибудь другое.
- Реализуйте в обработчике аналог 9-й функции стандартного DOS-прерывания int 21h (вывод на экран строки, завершённой символом «$»). Для простоты не заботьтесь об управляющих символах (табуляции, перевода строки и т.п.).
- В обработчике выведите на экран значения EIP, CS и EFLAGS из стека. Проверьте, что это именно они.
Любой из материалов, опубликованных на этом сервере, не может быть воспроизведен в какой бы
то ни было форме и какими бы то ни было средствами без письменного разрешения владельцев авторских
прав.
Источник