Бери свою флейту; Я уже упаковал свой Станок с неизвестным количеством струн Борис Гребенщиков
И ещё одна глава-преамбула, простая, короткая, конкретная как дважды два. И опять без заданий.
Задача главы – познакомить вас с ещё одним полезным дескриптором. Дескриптор шлюза вызова (call gate descriptor) описывает функцию, которая реализована на относительно высоком уровне привилегий, но может быть доступна менее привилегированному коду. Как именно она будет доступна менее привилегированному коду – тема главы «Защита: передача управления», а пока – собственно дескриптор шлюза вызова.
ПРИМЕЧАНИЕ На самом деле, можно было построить курс и без него – программных прерываний и шлюзов ловушки достаточно для построения работоспособной ОС. Но мне не хотелось, чтобы у вас осталось впечатление, будто переход из пользовательского режима в режим ядра – это всегда прерывание. Есть и другие варианты, например дескриптор шлюза вызова. |
Формат дескриптора описан ниже, в таблице и на картинке.
Положение | Название | Краткое описание |
---|---|---|
Нулевой и первый байты | Offset (part 1) | Младшие два байта 32-х битного поля Offset. Поле Offset содержит смещение обработчика прерывания. |
Второй и третий байты | Segment Selector | Селектор сегмента, содержащего обработчик прерывания. |
0-й – 4-й биты четвёртого байта | Parameters Count | Количество передаваемых в стеке параметров, каждый параметр – двойное слово (4 байта). |
Остатки четвёртого байта | 0 | Эта часть дескриптора не используется. |
0-й – 3-й биты пятого байта | Type | Дескриптору шлюза вызова соответствует значение 1100b. |
4-й бит пятого байта | S | Это системный дескриптор, поэтому 0. |
5-й – 6-й биты пятого байта | DPL | С точки зрения классификации по «смыслу DPL» (см. главу «Теоретическое введение в защиту») дескриптор шлюза вызова относится к дескрипторам ресурсов, соответственно, DPL определяет минимальный уровень (численно – максимальный) привилегий, необходимый для доступа к шлюзу. |
7-й бит пятого байта | P | Устанавливайте в 1, подробнее в приложении. |
Шестой и седьмой байты | Offset (part 2) | Старшие два байта поля Offset. |
Пример:
db 04h ; Offset – два младших байта db 03h db 8 ; Segment selector db 0 db 1 ; 1 параметр, т.е. 4 байта db 11101100b ; DPL = 11 db 02h ; Offset – два старших байта db 01h |
Это дескриптор шлюза вызова, селектор сегмента указывает на первый дескриптор GDT, смещение – 01020304h, DPL равен 11b, в стеке передаётся один четырёхбайтовый параметр.
С учётом поля «Parameters Count», обобщённый дескриптор шлюза в виде структуры выглядит так:
gate_descriptor struct offset_low dw 0 ; Два младших байта поля Offset selector dw 0 ; Поле Segment Selector params db 0 ; Количество параметров в стеке type_and_permit db 0 ; Флаги offset_high dw 0 ; Старшие байты поля Offset gate_descriptor ends |
ПРИМЕЧАНИЕ Поле «params» имеет смысл только для шлюза вызова. Для дескрипторов шлюза ловушки и исключения значением этого поля всегда должен быть 0. |
Дескрипторы шлюза вызова могут находиться в GDT или в LDT (локальная таблица дескрипторов; local descriptor table), но, поскольку LDT мы не изучали и не планируем, мы будем пользоваться исключительно GDT. Естественно, при попытке записать в какой-либо сегментный регистр селектор дескриптора шлюза вызова, процессор сгенерирует исключение #GP.
Для обращения к функции через шлюз вызова, используется стандартная инструкция call, точнее, её межсегментная версия. Но есть особенности:
В итоге, вызов выглядит примерно так:
; параметры в стек push eax ; Самодельный call. К сожалению, masm не позволяет записать ; инструкцию call с такой адресной частью нормально. db 9Ah ; код инструкции call с адресом в явном виде dw 0 ; смещение – игнорируется – пусть будет 0 dw <селектор шлюза вызова> ; сегмент |
А сама функция – так:
some_function: ... ... ; какой-то код ... db 66h ; префикс, преврящающий простой retf в 32-х разрядный retf 4 |
То есть, в общем, почти всё как обычно.
ПРИМЕЧАНИЕ Практически аналогичным образом можно использовать дескрипторы шлюза вызова в инструкции jmp, но с очень серьёзным ограничением: вызов не должен приводить к изменению уровня привилегий, то есть, DPL целевого сегмента должен совпадать с CPL. Это означает, что если вы можете прыгнуть через шлюз, можно прыгать и напрямую, а значит, с точки зрения защиты, ситуация интереса не представляет. Ниже такое применение шлюзов вызова не рассматривается. |
Демонстрация использования шлюза вызова с одним параметром. Функция, доступная через шлюз вызова, проходит по экрану и прибавляет значение параметра к атрибуту каждого символа.
; call_gate.asm ; Программа демонстрирует изспользование шлюза вызова .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 params 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 ; Сегмент кода mov eax, offset cs0_dsc mov ebx, 0 call init_descriptor_base ; Запрещаем прерывания call disable_interrupts ; Инициализируем GDT call initialize_gdt ; Переключаем режим call set_PE ; 16-разрядный дальний переход. Перключает содержимое cs из нормального ; для реального режима (адрес) в нормальное для защищённого (селектор). ; Базовый адрес целевого сегмента совпадает с cs, ; поэтому смещение можно прописать сразу db 0EAh ; код команды дальнего перехода dw $ + 4 ; смещение dw cs0_sel ; селектор ; параметр в стек mov eax, 255 push eax ; вызов функции через шлюз db 9Ah dw 0 dw call_sel ; Обратно в RM call clear_PE ; Мы в реальном режиме, осталось разобраться значением регистра cs ; 16-разрядный дальний переход. Перключает содержимое cs из нормального ; для защищённог режима (селектор) в нормальное для реальног (адрес). ; Адрес сегмента вычисляется и прописывается во время выполнения. db 0EAh ; код команды дальнего перехода dw $ + 4 ; смещение rm_cs dw 0 ; сегмент call enable_interrupts ret ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; ;; Системная функция ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; Приниммает один четырёхбайтный параметр. ; проходит по экрану, добавляет значение параметра к атрибутам всех символов call_handler: ; Состояние стека в данный момент: ; [esp] – eip ; [esp + 4] – cs ; [esp + 8] – параметр ; Помещаем параметр в ebx mov ebx, [esp + 8] push eax push ecx mov eax, 0 ; Текущий символ mov ecx, 80 * 25 ; Колическтво символов на экране ; проходит по экрану, увеличивает атрибуты всех символов screen_loop1: inc eax add byte ptr es:[eax], bl ; Меняем атрибут inc eax loop screen_loop1 pop ecx pop eax ; 32-х разрядный выход с очисткой стека db 66h retf 4 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; ;; Данные ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; Глобальная таблица дескрипторов GDT label byte ; Нулевой дескриптор segment_descriptor <> ; Дескриптор шлюза вызова gate_descriptor <call_handler, cs0_sel, 1, 11101100b, 0> ; Дескриптор сегмента кода с системными привелегиями cs0_dsc segment_descriptor <0ffffh, 0, 0, 10011010b, 0, 0> ; Данные для загрузки в GDTR gdtr table_register <$ - GDT - 1, 0> ; Селекторы call_sel equ 00001000b ; селектор шлюза вызова cs0_sel equ 00010000b ; сегмент системного кода ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; ;; Служебные функции ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; Устанавливает базу дескриптора сегмента ; в eax передаётся адрес структуры segment_descriptor ; в ebx передаётся смещение базы относительно cs init_descriptor_base: push ecx ; Получаем базу mov ecx, 0 mov cx, cs shl ecx, 4 add ecx, ebx ; Прописываем её в дескриптор mov (segment_descriptor ptr [eax]).base_low, cx shr ecx, 16 mov (segment_descriptor ptr [eax]).base_high0, cl pop ecx ret ; Инициализирует GDT initialize_gdt: ; Вычисляем линейный адрес начала массива дескрипторов call cs_to_eax add eax, offset GDT ; Записываем его в структуру mov dword ptr gdtr.base, eax ; Загружаем GDTR lgdt fword ptr gdtr 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 end start |