Protected Mode Tutorial
by : Peter Quiring
date : Aug/97
Цель этого руководства - показать разницу между
protected mode и real mode,и как начать программировать
DOS-расширители (примером DOS-extenders являются DOS/4GW или PMODE/W).
Real mode - родной 8086 режим с 16-bit регистрами и ограничением памяти
в 640k.
Защищенные режимы предлагают больше.
Это руководство к созданию программ , которые будут работать
под дос-расширением,а не о том ,как создать сам дос-расширитель.
Сокращения:
RMODE = real mode
PMODE = protected mode
V86 = virtual 8086 mode
|
Почему именно Protected Mode?
|
Вы получаете доступ к бОльшим ресурсам.
Барьер в 640k становится преодолимым.
Protected mode позволяет сделать более защищенные программы ,
позволяет реализовать операционную систему.
Функция DOS extenders заключается в загрузке ваших программ
в защищенный режим.
|
32-битный PMODE появился вместе с 80386 processor.
80286 был первым CPU , имевшим PMODE , но это был 16-битный PMODE.
80286 мог поддерживать 16 MB RAM , которая выросла уже до 4GB на 386.
В 32-bit PMODE у вас есть доступ ко всей памяти напрямую.
В 80386 CPU в основном все регистры 32-битные.
порядок бит :
31 16 15 8 7 0
+---------+----+----+
| | AH | AL |
+---------+----+----+
[-- AX --]
[------- EAX -------]
+---------+----+----+
| | BH | BL |
+---------+----+----+
[-- BX --]
[------- EBX -------]
+---------+----+----+
| | CH | CL |
+---------+----+----+
[-- CX --]
[------- ECX -------]
+---------+----+----+
| | DH | DL |
+---------+----+----+
[-- DX --]
[------- EDX -------]
+---------+----+----+
| | SI |
+---------+----+----+
[------- ESI -------]
+---------+----+----+
| | DI |
+---------+----+----+
[------- ESI -------]
+---------+----+----+
| | BP |
+---------+----+----+
[------- EBP -------]
+---------+----+----+
| | SP |
+---------+----+----+
[------- ESP -------]
+---------+----+----+
| | flags |
+---------+----+----+
[----- Eflags ------]
Сегментные регистры имеют размерность в 16 бит.
32-битные регистры позволяют получить доступ к 4GBs памяти.
80386 имеет новый адресный режим , называемый scaled index base mode (SIB).
Можно разместить 2 любых регистра внутри квадратных скобок
и один из них умножить на 1,2,4 или 8. Можно прибавить смещение.
Пример:
mov al,[eax*2]
mov bl,[ebx+ecx*4+my_offset]
mov [esp+eax*2],edi
Несколько новых инструкций :
- iretd - 32bit iret
- pusad/pushfd/popad/popfd - 32bit versions
-
В ассемблерный файл нужно добавлять директиву ".386" , чтобы использовать
эти инструкции.
|
Когда 386 находится в real mode , он ведет себя как 8086.
При обращении к памяти каждый раз значение из сегмента
умножается на 10h и прибавляется смещение -
так получается физический адрес.
В PMODE все меняется. Значение в сегментном регистре
теперь тоже смещение внутри системных таблиц
и называется selector , и к этому смещению
прикручивваются дополнительные атрибуты - descriptors.
В дескрипторах хранится информация о правах ,
дескриптор указывает на сегмент , в котором может храниться
код , данные либо прерывания.
Теперь в сегментных регистрах не найдешь значений типа 0a000h.
Поскольку регистры теперь 32-битные , можно использовать память в диапазоне 4 GB.
Пример загрузки видеопамяти :
mov edi,0b8000h ;Colour text video segment
mov [edi],al ;put a byte on the text screen
mov edi,0400h ;BIOS segment
mov eax,[edi+67h] ;get the system timer tick (IRQ#1)
|
Системные таблицы GDT, LDT, IDT
|
3 основных типа:
GDT - Global Descriptor Table
LDT - Local Descriptor Table
IDT - Interrupt Descriptor Table
Таблицу GDT использует каждая работающая программа.
Она может хранить 8,192 дескрипторов, первый всегда зарезервирован.
LDT выполняет аналогичную функцию что и GDT но для локальной задачи.
IDT хранит обработчики прерываний.
Она может хранить только 256 дескрипторов.
RMODE interrupt table располагается в самом начале памяти,
PMODE interrupt table может быть где угодно.
Селектор может выглядеть вот так :
:
Selector layout
+---------------------------+
bit | 15 ... 3 | 2 | 1 0 |
desc | Descriptor | TI | PL |
+---------------------------+
Биты 15-3 - это индекс дескриптора.
Это 13 бит : 2^13 = 8192.
Бит 2 называется TI (table indicator).
Если он = 0 , селектор указывает на GDT,
иначе (1) он указывает на LDT.
Биты 1 и 0 - уровень привилегий. Они обеспечивают механизм защиты CPU.
CPU имеет специальные регистры для загрузки таких таблиц:
gdtr - GDT Register
idtr - IDT Register
LDT не имеет такого регистра.
Это 48-битные регистры. 32 бита задают адрес памяти
и остальные 16 бит - размер сегмента.
Ограничение = чмсло байт - 1.
|
Существуют 18 различных типов дескрипторов.
Многие из них зарезервированы или устарели.
Каждый дескриптор имеет следующую информацию:
BASE : Стартовый адрес сегмента. Например 0a0000h для видео-памяти. Затем
LIMIT : это размер сегмента.
TYPE : тип сегмента - их 3 - код,данные(или стек) и гейты.
Гейт - это обработчик прерывания.
Дескриптор задает тип сегмента на чтение-запись
Например , стек - сегмент на запись.
|
Исключение (или fault) генерится CPU при возникновении ошибки.
Исключения имеют нумерацию от INT #0h до INT #01fh.
Например #13h. это исключение генерится в следующем случае:
- Загрузка CS (с помощью JMP или CALL) дескриптором , который ссылается не на кодовый сегмент.
- запись в кодовый сегмент
- загрузка SS дескриптором,который указывает не на записываемый сегмент
- попытка переключиться в PMODE напрямую из V86 mode
И т.д.
- INT # 0h - деление на ноль
- INT # 1h - Debug trap (для 386)
- INT # 3h - User Breakpoint (debugging)
- INT # 13h - exc Handler (перехватывает все исключения ,которые
еще ничем не перехвачены - catch-all handler).
|
Специальные регистры (CRx)
|
В 386 появились специальные регистры для системной конфигурации,
дебага,тестирования.
Вот некоторые из них:
CR0 - его нулевой бит указывает на то , в каком режиме находится CPU -
PMODE(1) или RMODE(0).
DR0 -> DR7 - debugging registers.
TR0 -> TR? - тестирование.
|
386 имеет специальный режим , названный V86 mode.
Это симбиоз PMODE + RMODE.
Этот режим используется тогда , когда бит 17 регистра EFLAGS установлен в 1.
Программы RMODE могут выполняться при этом в режимеr PMODE.
|
В начале PMODE CPU не совсем корректно обрабатывал исключения.
Приложения RMODE могли напрямую установить бит 0 регистра CR0 для попадания в PMODE.
Если другое приложение при этом пыталось выполнить доступ к памяти, вызывалась ошибка.
Был разработан VCPI для того чтобы в режие V86 позволялось
многим приложениям иметь корректный доступ к памяти.
Но со временем VCPI перестали использовать в многозадачных
ОС , поскольку с его помощью приложениям давалась слишком большая степень свободы,
вот тут-то и родился DPMI server.
DPMI (Dos Protected Mode Interface) позволяет многим приложениям работвть в
PMODE , сохраняя при этом системную безопасность.
DPMI также реализует многие сервисы по выделению-освобождению ресурсов памяти.
DPMI server работает во много аналогично DOS INT 21h прерыванию.
Для вызова DPMI server нужно использовать INT 31h только в режиме PMODE.
Нельзя использовать DPMI server из RMODE.
|
Находясь в PMODE , при необходимости можно вернуться в RMODE
для вызова специфичных RMODE INT или других RMODE-функций.
Если нужно вызвать RMODE INT без защищенного параметра,
можно просто вызвать INT прямо из PMODE.
Если вы попытаетесь загрузить сегментный регистр в PMODE ,
а это значение вдруг не имеет прав на загрузку,
программа может потерпеть крах.
DOS extender выполняет сам специалные extend расширения для некоторых
INT-вызовов. К ним относятся большинство INT 21h .
DPMI server обеспечивает вызов RMODE INT
с помощью специальной структуры:
callstruct struct ;50 bytes (32h)
_edi dd ? ;0
_esi dd ? ;4
_ebp dd ? ;8
_res dd ? ;0ch reserved
_ebx dd ? ;10h
_edx dd ? ;14h
_ecx dd ? ;18h
_eax dd ? ;1ch
_flg dw ? ;20h flags
_es dw ? ;22h segments (NOT selectors)
_ds dw ? ;24h "
_fs dw ? ;26h "
_gs dw ? ;28h "
_ip dw ? ;2ah ignored in some calls
_cs dw ? ;2ch "
_sp dw ? ;2eh must be 0 to use system stacks
_ss dw ? ;30h "
callstruct ends
Эту структуру нужно заполнить для передачи
в RMODE INT handler или функцию. Нужно передавать сегментные значения , не селекторы.
Поля _sp и _ss надо обнулить для вызова системного стека.
Если вы делаете CALL для RMODE-функции, _cs и _ip должны быть загружены
FAR-адресами.
Есть DPMI-функции для реализации RMODE calling:
ax = 300h - Simulate RMODE INT
bl = INT #
bh = 0 (must always be zero)
cx = ? (number of dwords to pass on stack)
es:edi = offset of 'callstruct'
ax = 301h - Call RMODE Procedure with FAR stack frame
bh = 0 (must always be zero)
cx = ? (number of dwords to pass on stack)
es:edi = offset of 'callstruct'
ax = 302h - Call RMODE Procedure with IRET stack frame
bh = 0 (must always be zero)
cx = ? (number of dwords to pass on stack)
es:edi = offset of 'callstruct'
Регистр cx указывает на то ,сколько параметров скопировано из
PMODE=стека в RMODE-стек.
Размер каждого параметра должен быть dwords в 32bit PMODE программе
или 16bit в PMODE программе.
Вызов ISR (interrupt service rountine).
Нужно создать структуру в стеке перед вызовом :
myISR proc
pushad
sub esp,sizeof callstruct ;create a callstruct buffer
mov edi,esp
... ;place your code here
add esp,sizeof callstruct ;remove callstruct buffer
popad
iretd
myISR endp
Внутри процедуры регистр EDI хранит буфер для этой структуры ,
создаваемой каждый раз перед вызовом ISR.
|
Передача контроля между RMODE и PMODE
|
Для этого нужно выделять память для RMODE callback из DPMI host.
Каждый раз необходимо создавать структуру,которая будет хранить
RMODE-регистры при переходе в PMODE. При возвращении оттуда вы получаете значение в CS:IP
от DPMI host .
ES:EDI = callstruct you provided
DS:ESI = top of RMODE stack (SS:SP)
interrupts disabled
При возврате ( IRETD) :
ES:EDI = callstruct to be used for returning to RMODE
Пример:
simulate_retf proc
;Assume ES:EDI -> callstruct
; DS:ESI -> RMODE stack
xor eax,eax
mov ax,es:[edi].callstruct._ss
shl eax,4
xor ebx,ebx
add bx,es:[edi].callstruct._sp
add eax,ebx
mov bx,[eax]
mov es:[edi].callstruct._ip,bx
mov bx,[eax+2]
mov es:[edi].callstruct._cs,bx
add es:[edi].callstruct._sp,4
iretd
simulate_retf endp
simulate_iret proc
;Assume ES:EDI -> callstruct
; DS:ESI -> RMODE stack
xor eax,eax
mov ax,es:[edi].callstruct._ss
shl eax,4
xor ebx,ebx
add bx,es:[edi].callstruct._sp
add eax,ebx
mov bx,[eax]
mov es:[edi].callstruct._ip,bx
mov bx,[eax+2]
mov es:[edi].callstruct._cs,bx
mov bx,[eax+4]
mov es:[edi].callstruct._flg,bx
add es:[edi].callstruct._sp,6
iretd
simulate_iret endp
|
В дескрипторе есть флаг,который указывает на природу сегмента -
16bit или 32bits.
Для 16bit режима (RMODE или PMODE) :
0100:0000 89h d8h mov ax,bx
Для 32bit :
0100:0000 89h d8h mov eax,ebx
Если вы захотите использовать 32-битный регистр для 16-битного сегмента,
нужно использовать специальный префикс.Этот префикс - 066h.
db 66h
retf
|
|
|