| [Назад] [Далее] | ![]() |
Теперь воспользуемся интерфейсом DPMI для переключения в защищенный режим и вывода строки на экран. Этот пример будет работать только в системах, предоставляющих DPMI для запускаемых в них DOS-программ, например в Windows 95.
; dpmiex.asm
; Выполняет переключение в защищенный режим средствами DPMI
.386 ; 32-битный защищенный режим появился в 80386
; 16-битный сегмент - в нем выполняется подготовка и переход в защищенный режим
RM_seg segment byte public use16
assume cs:RM_seg, ds:PM_seg, ss:RM_stack
; точка входа программы
RM_entry:
; проверить наличие DPMI
mov ax,1687h ; номер 1678h
int 2Fh ; прерывание мультиплексора,
test ax,ax ; если АХ не ноль,
jnz DPMI_error ; произошла ошибка (DPMI отсутствует),
test bl,1 ; если не поддерживается 32-битный режим,
jz DPMI_error ; нам тоже нечего делать
; подготовить базовые адреса для будущих дескрипторов
mov eax,PM_seg
mov ds,ax ; DS - сегментный адрес PM_seg
shl eax,4 ; EAX - линейный адрес начала сегмента PM_seg
mov dword ptr PM_seg_addr,eax
or dword ptr GDT_flatCS+2,eax ; дескриптор для CS
or dword ptr GDT_flatDS+2,eax ; дескриптор для DS
; сохранить адрес процедуры переключения DPMI
mov word ptr DPMI_ModeSwitch,di
mov word ptr DPMI_ModeSwitch+2,es
; ES должен указывать на область данных для переключения режимов,
; у нас она будет совпадать с началом будущего 32-битного стека
add eax,offset DPMI_data ; добавить к EAX смещение
shr eax,4 ; и превратить в сегментный адрес
inc ax
mov es,ax
; перейти в защищенный режим
mov ах, 1 ; АХ = 1 - мы будем 32 приложением
ifdef _WASM_
db 67h ; поправка для wasm
endif
call dword ptr DPMI_ModeSwitch
jc DPMI_error ; если переключения не произошло - выйти
; теперь мы находимся в защищенном режиме, но лимиты всех сегментов
; установлены на 64 Кб, а разрядности сегментов - на 16 бит.
; Нам надо подготовить два
; 32-битных селектора с лимитом 4 Гб - один для кода и один для данных
push ds
pop es ; ES вообще был сегментом PSP с лимитом 100h
mov edi,offset GDT ; EDI - адрес таблицы GDT
; цикл по всем дескрипторам в нашей GDT,
mov edx,1 ; которых всего два (0, 1) sel_loop:
xor ах,ах ; функция DPMI 00
mov cx,1
int 31h ; создать локальный дескриптор
mov word ptr selectors[edx*2],ax ; сохранить селектор
mov bx,ax ; в таблицу selectors
mov ax,000Ch ; функция DPMI OCh
int 31h ; установить селектор
add di,8 ; EDI - следующий дескриптор
dec dx
jns sel_loop
; загрузить селектор сегмента кода в CS при помощи команды RETF
push dword ptr Sel_flatCS ; селектор для CS
ifdef _WASM_
db 066h
endif
push offset PM_entry ; EIP
db 066h ; префикс размера операнда
retf ; выполнить переход в 32-битный сегмент
; сюда передается управление, если произошла ошибка при инициализации DPMI
; (обычно, если DPMI просто нет)
DPMI_error:
push cs
pop ds
mov dx,offset nodpmi_msg
mov ah,9h ; вывод строки на экран
int 21h
mov ah,4Ch ; конец ЕХЕ-программы
int 21h
nodpmi_msg db "Ошибка DPMI$"
RM_seg ends
; сегмент PM_seg содержит код, данные и стек для защищенного режима
PM_seg segment byte public use32
assume cs:PM_seg,ds:PM_seg,ss:PM_seg
; таблица дескрипторов
GDT label byte
; дескриптор для CS
GDT_flatCS db 0FFh,0FFh,0h,0h,0h,0FAh,0CFh,0h
; дескриптор для DS
GDT_flatDS db 0FFh,0FFh,0h,0h,0h,0F2h,0CFh,0h
; точка входа в 32-битный режим - загружен только CS
PM_entry:
mov ax,word ptr Sel_flatDS ; селектор для данных
mov ds,ax ; в DS
mov es,ax ; в ES
mov ss,ax ; и в SS
mov esp,offset PM_stack_bottom ; и установить стек
; отсюда начинается текст собственно программы,
; программа работает в модели памяти flat с ненулевой базой,
; база CS, DS, ES и SS совпадает и равна линейному адресу начала PM_seg
; все лимиты - 4 Гб
mov ах,0300h ; функция DPMI 0300h
mov bx,0021h ; прерывание DOS 21h
xor есх,есх ; стек не копировать
mov edi,offset v86_regs ; ES:EDI - адрес v86_regs
int 31h ; вызвать прерывание
mov ah,4Ch ; Это единственный способ
int 21h ; правильно завершить DPMI-программу
hello_msg db "Hello world из 32-битного защищенного режима!$"
v86_regs: ; значения регистров для функции DPMI ОЗООп
dd 0,0,0,0,0 ; EDI, ESI, EBP, 0, ЕВХ
v_86_edx dd offset hello_msg ; EDX
dd 0 ; ЕСХ
v86_eax dd 0900h ; EAX (AH = 09h, вывод строки на экран)
dw 0,0 ; FLAGS, ES
v86_ds dw PM_seg ; DS
dw 0,0,0,0,0,0 ; FS, GS, 0, 0, SP, SS
; различные временные переменные, нужные для переключения режимов
DPMI_ModeSwitch dd ? ; точка входа DPMI
PM_seg_addr dd ? ; линейный адрес сегмента PM_seg
; значения селекторов
selectors:
Sel_flatDS dw ?
Sel_flatCS dw ?
; стек для нашей 32-битной программы
DPMI_data: ; и временная область данных DPMI одновременно
db 16384 dup (?)
PM_stack_bottom:
PM_seg ends
; стек 16-битной программы, который использует DPMI-сервер при переключении режимов
; Windows 95 требует 16 байт
; CWSDPMI требует 32 байта
; QDPMI требует 96 байт
; мы выберем по максимуму
RM_stack segment byte stack "stack" use16
db 96 dup (?)
RM_stack ends
end RM_entry ; точка входа для DOS - RM_entry
Несмотря на то что DPMI разрешает пользоваться многими прерываниями напрямую и всеми через функцию 0300h, он все равно требует некоторой подготовки для переключения режимов. Кроме того, программа, использующая DPMI для переключения режимов, должна сочетать в себе 16-битный и 32-битный сегменты, что неудобно с точки зрения практического программирования. На самом деле для написания приложений, идущих в защищенном режиме под DOS, никто не применяет переключение режимов вообще — это делают специальные программы, называющиеся расширителями DOS.