Процессор Intel в защищенном режиме #8 — Архив WASM.RU
В этом выпуске мы наконец-то применим часть полученных знаний на практике. Сейчас мы попробуем переключиться в защищенный режим. И все. Возвращаться назад (в реальный) мы не будем... Ты сам убедишься, что для переключения в защищенный режим нужно выполнить ряд простых и незамысловатых по сути действий. Конечно, тебе наверняка хотелось бы побыть ТАМ подольше , выполнить какие-нибудь невероятные, головокружительные трюки, чего-нибудь такое, чего в реальном сделать просто нереально. К сожалению, пока тех знаний, которыми мы обладаем, недостаточно, поэтому ограничимся малым - выведем на экран надпись.
Итак, для начала определимся с моделью памяти. Пусть это будет flat-модель (все сегменты имеют базу ноль и лимит 4 Гб). Рассмотрим сегментную адресацию. Страничную – в следующем выпуске.
Главное – это начать; начать можно с чего угодно, но я предлагаю с заполнения таблиц дескрипторов (надеюсь, еще помнишь что это такое? Дескриптор – описывает сегмент, селектор – указатель на дескриптор… Ну сейчас по ходу дела все вспомнится).
Поехали!
Таблица, которую нам нужно заполнить – таблица глобальных дескрипторов (GDT). У нас не будет таблицы локальных дескрипторов. Пусть в дескрипторах GDT будут описаны следующие сегменты:
- сегмента кода (код, который будет исполняться в защ. режиме)
- сегмент данных (данные, которые мы будем выводить на экран)
- сегмент видеопамяти (фактически это сегмент, в который мы будем выводить наш текст)
Код (Text):
Глобальная таблица дескрипторов: GDT: ; нулевой дескриптор (обязательно должен присутствовать в GDT!) NULL_descr db 8 dup (0) ; дескриптор 32-разрядного сегмента кода: база = 00000000h, размер = FFFFFFFFh CODE_descr db 0FFh, 0FFh, 00h, 00h, 00h, 10011010b, 11001111b , 00h ; дескриптор 32-разрядного сегмента данных: база = 00000000h, размер = FFFFFFFFh DATA_descr db 0FFh, 0FFh, 00h, 00h, 00h, 10010010b, 11001111b , 00h ; дескриптор сегмента видеопамяти: база = 000B8000h, размер = 0000FFFFh VIDEO_descr db 0FFh, 0FFh, 00h, 80h, 0Bh, 10010010b, 01000000b , 00h ; размер таблицы GDT: GDT_size db $-GDT ; а следующие три слова (размер GDT и линейный адрес начала таблицы) мы должны будем попозже загрузить в GDTR: GDTR dw GDT_size-1 dd ?Вычислим линейный адрес таблицы GDT. Зачем? Чтоб загрузить регистр GDTR. Линейный адрес GDT = линейный адрес базы сегмента RM_CODE (потому что GDT расположена именно в этом сегменте у нас в программе) + смещение метки GDT в нем.
Код (Text):
xor EAX,EAX ; обнуление регистра EAX mov AX,RM_CODE ; теперь AX = номер сегмента RM_CODE shl EAX,4 ; сдвигаем значение в EAX на 4 влево ; вот теперь EAX = линейный адрес базы сегмента RM_CODE add AX,offset GDT ; теперь EAX = линейный адрес GDT ; линейный адрес GDT кладем в заранее подготовленную переменную: mov dword ptr GDTR+2,EAX ; собственно, загрузка регистра GDTR: lgdt fword ptr GDTR(вообще желательно просмотреть весь исходник целиком, потому что RM_CODE сбивает с толку)
Теперь нам еще нужно вычислить точно таким же макаром линейный адрес т.н. ТОЧКИ ВХОДА В ЗАЩИЩЕННЫЙ РЕЖИМ. Дело в том, что после переключения в защ. режим мы окажемся в неком подвешенном состоянии – когда регистр CS еще содержит номер сегмента из реального режима, а не селектор. Мы не можем просто сделать mov CS, селектор, CS можно изменить только дальним jmp-ом либо iret-ом, но щас не про то. Поэтому нам все же придется делать дальний jmp для изменения CS. НО КУДА? Вот именно на ТОЧКУ ВХОДА В ЗАЩИЩЕННЫЙ РЕЖИМ, см. исходник.
Мы уже почти готовы переключится в защ. режим, единственное, что мы обязаны еще сделать – ЗАПРЕТИТЬ ВСЕ ПРЕРЫВАНИЯ (причем, как маскируемые, так и немаскируемые), потому что пока мы не умеем с ними работать, и у нас нет ни одного обработчика, первый же тик таймера после переключения в PM завесит нам всю систему...
Код (Text):
; запрещаем сначала маскируемые прерывания: cli ; а затем и немаскируемые: in AL,70h ; читаем 70h порт or AL,80h ; ставим восьмой бит out 70h,AL ; запихиваем на место ; теперь все прерывания запрещенынада посидеть на дорожку ... все. Теперь можно. Для переключения в PM нужно установить нулевой бит регистра CR0:
Код (Text):
mov EAX,CR0 ; EAX = CR0 or AL,1 ; ставим нулевой бит mov CR0,EAX ; пихаем назад...Кто играл в Half Life, помните, в первой части, когда взрыв на заводе в самом начале, Гордон падает в обморок, а потом на несколько секунд приходит в себя и оказывается в другом мире, вокруг какие то существа едят траву, вот что то типа этого произошло сейчас...
Теперь давайте осмотримся кругом, что мы видим? Ага, вон таблица GDT во тьме. Память оперативная память на месте. Регистры, где вы? Мы все тута! Ну слава Богу... мы здесь не одни. Где таблица векторов? Но нет ответа... Ладно, и без нее обойдемся...
Код (Text):
; загрузим в CS селектор на подготовленный сегмент кода. Мы не можем просто взять и написать mov CS,селектор: dd 66h ; префикс изменения разрядности операнда db 0EAh ; опкод команды jmp far dd ? ; смещение в сегменте, на которое мы jmp-аем dw 00001000b ; селектор сегмента, в который мы jmp-аемИтак, давай разберемся, куда же мы все-таки прыгнули. Селектор равен 00001000b. Младшие два бита – нули, это уровень привилегий, на него не смотрим. Второй бит (TI) – нолик. Вспоминаем. Значит этот селектор указывает на дескриптор из таблицы GDT... На какой же? Смотрим левее... 001! На первый! А что у нас описывает первый дескриптор в GDT? Правильно, сегмент кода.
; значит, мы перелетели на некую «точку входа в защищенный режим», смещение которой мы уже высчитали (см. исходник):
Код (Text):
ENTRY_POINT: ; загрузим сегментные регистры селекторами на соответствующие дескрипторы: mov AX,00010000b ; AX = селектор дескриптора данных (№2) mov DS,AX ; кладем его в DS mov AX,00011000b ; AX = селектор дескриптора видеопамяти (№3) mov ES,AX ; кладем его в ES xor SI,SI ; обнуляем SI mov SI,PM_DATA ; SI = номер сегмента PM_DATA shl ESI,4 ; ESI = линейный адрес сегмента PM_DATA add ESI,offset message ; ESI = линейный адрес строки message xor EDI,EDI ; EDI = позиция на экране (относительно 0B8000h) mov ECX,mes_len ; длина текста в ECX ; вывод на экран: rep movsb ; DS:ESI (наше сообщение) -> ES:EDI (видеопамять) jmp $ ; погружаемся в вечный циклТеперь единственное, что мы можем сделать – это перезагрузить компьютер (причем, только кнопкой RESET). Я умышленно убрал часть кода, которая отвечает за переключение обратно в реальный режим, потому что она сильно все затуманит (надо в таблице GDT еще подготовить 16-битные дескрипторы), но если кому интересно – пишите.
Да, еще что. В полном исходнике (см. ниже) сразу может напугать «Линия А20». Что это такое? Дело в том, что после запуска компа для совместимости с 8086 используются 20-разрядные адреса (адресные линии А0-А19), т.ч. попытка записать что-то по линейному адресу 100000h приведет к записи по адресу 0h. Вот этот режим нам и нужно отменить (установив бит 2 в 92h порту) для использования 32-х разрядной адресации.
Самое главное – СПРАШИВАЙТЕ, СПРАШИВАЙТЕ И ЕЩЕ РАЗ СПРАШИВАЙТЕ! Приветствуются любые вопросы, начиная с «Что такое привилегированные инструкции?» и заканчивая «Почему так много пробелов в конце выводимой фразы?». На второй вопрос отвечу сразу. Дело в том, что при организации блоков повторений через irpc TASM почему то неправильно вычисляет его длину, если мы делаем это через equ. Посмотрите сами и напишите, если вы знаете в чем тут причина.
Скачать исходник целиком можно здесь: http://brokensword.hotbox.ru/PM.asm.
Код (Text):
; ------------------------CUT HERE---------------------- ; TASM: ; TASM /m PM.asm ; TLINK /x /3 PM.obj ; PM.exe ; MASM: ; ML /c PM.asm ; LINK PM.obj,,NUL,,, ; PM.exe .386p ; разрешить привилегированные инструкции i386 ; СЕГМЕНТ КОДА (для Real Mode) ; --------------------------------------------------------------------------------------------------------- RM_CODE segment para public 'CODE' use16 assume CS:RM_CODE,SS:RM_STACK @@start: ; очистка экрана: mov AX,3 int 10h ; открываем линию А20 (для 32-х битной адресации): in AL,92h or AL,2 out 92h,AL ; вычисляем линейный адрес метки ENTRY_POINT (точка входа в защищенный режим): xor EAX,EAX ; обнуляем регистра EAX mov AX,PM_CODE ; AX = номер сегмента PM_CODE shl EAX,4 ; EAX = линейный адрес PM_CODE add EAX,offset ENTRY_POINT ; EAX = линейный адрес ENTRY_POINT mov dword ptr ENTRY_OFF,EAX ; сохраняем его в переменной ; (кстати, подобный "трюк" называется SMC или Self Modyfing Code - самомодифицирующийся код) ; теперь надо вычислить линейный адрес GDT (для загрузки регистра GDTR): xor EAX,EAX mov AX,RM_CODE ; AX = номер сегмента RM_CODE shl EAX,4 ; EAX = линейный адрес RM_CODE add AX,offset GDT ; теперь EAX = линейный адрес GDT ; линейный адрес GDT кладем в заранее подготовленную переменную: mov dword ptr GDTR+2,EAX ; а подобный трюк назвать SMC уже нельзя, потому как по сути мы модифицируем данные <img src="styles/smiles_s/smile3.gif" class="mceSmilie" alt=":smile3:" title="Smile3 :smile3:"> ; собственно, загрузка регистра GDTR: lgdt fword ptr GDTR ; запрет маскируемых прерываний: cli ; запрет немаскируемых прерываний: in AL,70h or AL,80h out 70h,AL ; переключение в защищенный режим: mov EAX,CR0 or AL,1 mov CR0,EAX ; загрузить новый селектор в регистр CS db 66h ; префикс изменения разрядности операнда db 0EAh ; опкод команды JMP FAR ENTRY_OFF dd ? ; 32-битное смещение dw 00001000b ; селектор первого дескриптора (CODE_descr) ; ТАБЛИЦА ГЛОБАЛЬНЫХ ДЕСКРИПТОРОВ: GDT: ; нулевой дескриптор (обязательно должен присутствовать в GDT!): NULL_descr db 8 dup(0) CODE_descr db 0FFh,0FFh,00h,00h,00h,10011010b,11001111b,00h DATA_descr db 0FFh,0FFh,00h,00h,00h,10010010b,11001111b,00h VIDEO_descr db 0FFh,0FFh,00h,80h,0Bh,10010010b,01000000b,00h GDT_size equ $-GDT ; размер GDT GDTR dw GDT_size-1 ; 16-битный лимит GDT dd ? ; здесь будет 32-битный линейный адрес GDT RM_CODE ends ; --------------------------------------------------------------------------------------------------------- ; СЕГМЕНТ СТЕКА (для Real Mode) ; --------------------------------------------------------------------------------------------------------- RM_STACK segment para stack 'STACK' use16 db 100h dup(?) ; 256 байт под стек - это даже много RM_STACK ends ; --------------------------------------------------------------------------------------------------------- ; СЕГМЕНТ КОДА (для Protected Mode) ; --------------------------------------------------------------------------------------------------------- PM_CODE segment para public 'CODE' use32 assume CS:PM_CODE,DS:PM_DATA ENTRY_POINT: ; загрузим сегментные регистры селекторами на соответствующие дескрипторы: mov AX,00010000b ; селектор на второй дескриптор (DATA_descr) mov DS,AX ; в DS его mov AX,00011000b ; селектор на третий дескриптор (VIDEO_descr) mov ES,AX ; а этого в ES xor SI,SI ; обнуляем SI mov SI,PM_DATA ; SI = номер сегмента PM_DATA shl ESI,4 ; ESI = линейный адрес сегмента PM_DATA add ESI,offset message ; ESI = линейный адрес строки message xor EDI,EDI ; EDI = позиция на экране (относительно 0B8000h) mov ECX,mes_len ; длина текста в ECX ; вывод на экран: rep movsb ; DS:ESI (наше сообщение) -> ES:EDI (видеопамять) jmp $ ; погружаемся в вечный цикл PM_CODE ends ; --------------------------------------------------------------------------------------------------------- ; СЕГМЕНТ ДАННЫХ (для Protected Mode) ; --------------------------------------------------------------------------------------------------------- PM_DATA segment para public 'DATA' use32 assume CS:PM_DATA ; сообщение, которое мы будем выводить на экран (оформим его в виде блока повторений irpc): message: irpc mes, <Congratulations! We are in PM! Now you MUST press RESET key... > db '&mes&',0Dh endm mes_len equ $-message ; длина в байтах PM_DATA ends ; --------------------------------------------------------------------------------------------------------- end @@start ; ------------------------CUT HERE----------------------Находчивый подписчик сразу спросит: «А не могли бы мы, допустим, сделать базу видеосегмента в нуле, а при выводе на экран значение 0B8000h поместить в EDI?». Тот, у кого возникнет данный вопрос – уже видит защищенный режим насквозь. Конечно могли бы, это то же самое по сути!
Ну и напоследок еще одна деталька (на всякий случай): ВЫ МОЖЕТЕ ПЕРЕКЛЮЧИТСЯ В ЗАЩИЩЕННЫЙ РЕЖИМ НАХОДЯСЬ ТОЛЬКО В РЕАЛЬНОМ РЕЖИМЕ !!! © Broken Sword
Процессор Intel в защищенном режиме #8
Дата публикации 15 окт 2002