Отправляет email-рассылки с помощью сервиса Sendsay
  Все выпуски  

Процессор INTEL в защищенном режиме


Информационный Канал Subscribe.Ru

 

Процессор INTEL в защищенном режиме

Выпуск №8

 

В выпуске:

 
- Переключение в защищенный режим. Практика.


Переключение в защищенный режим

 

В этом выпуске мы наконец-то применим часть полученных знаний на практике. Сейчас мы попробуем переключиться в защищенный режим. И все. Возвращаться назад (в реальный) мы не будем...

Ты сам убедишься, что для переключения в защищенный режим нужно выполнить ряд простых и незамысловатых по сути действий.

Конечно, тебе наверняка хотелось бы побыть ТАМ подольше :), выполнить какие-нибудь невероятные, головокружительные трюки, чего-нибудь такое, чего в реальном сделать просто нереально. К сожалению, пока тех знаний, которыми мы обладаем, недостаточно, поэтому ограничимся малым - выведем на экран надпись.



Итак, для начала определимся с моделью памяти. Пусть это будет flat-модель (все сегменты имеют базу ноль и лимит 4 Гб). Рассмотрим сегментную адресацию. Страничную – в следующем выпуске.

Главное – это начать; начать можно с чего угодно, но я предлагаю с заполнения таблиц дескрипторов (надеюсь, еще помнишь что это такое? Дескриптор – описывает сегмент, селектор – указатель на дескриптор… Ну сейчас по ходу дела все вспомнится).

И еще, если у кого то в данном выпуске разъехалось форматирование - пишите, вышлю нормальный вариант (это уже к subscribe.ru все претензии, они не любят тег pre).

Поехали!

Таблица, которую нам нужно заполнить – таблица глобальных дескрипторов (GDT). У нас не будет таблицы локальных дескрипторов. Пусть в дескрипторах GDT будут описаны следующие сегменты:

- сегмента кода (код, который будет исполняться в защ. режиме)

- сегмент данных (данные, которые мы будем выводить на экран)

- сегмент видеопамяти (фактически это сегмент, в который мы будем выводить наш текст)

 ТАБЛИЦА ГЛОБАЛЬНЫХ ДЕСКРИПТОРОВ:
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 в нем.

    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 завесит нам всю систему...

 ; запрещаем сначала маскируемые прерывания:
    cli

 ; а затем и немаскируемые:
    in AL,70h  ; читаем 70h порт
    or AL,80h  ; ставим восьмой бит
    out 70h,AL  ; запихиваем на место
 ; теперь все прерывания запрещены

Теперь нада обязательно посидеть на дорожку :)... все. Теперь можно. Для переключения в PM нужно установить нулевой бит регистра CR0:


    mov EAX,CR0  ; EAX = CR0
    or AL,1  ; ставим нулевой бит
    mov CR0,EAX  ; пихаем назад...


Кто играл в Half Life, помните, в первой части, когда взрыв на заводе в самом начале, Гордон падает в обморок, а потом на несколько секунд приходит в себя и оказывается в другом мире, вокруг какие то существа едят траву, вот что то типа этого произошло сейчас... :) Теперь давайте осмотримся кругом, что мы видим? Ага, вон таблица GDT во тьме. Память? Оперативная память на месте. Регистры, где вы? Мы все тута! Ну слава Богу... мы здесь не одни. Где таблица векторов? Но нет ответа... Ладно, и без нее обойдемся...

 ; загрузим в CS селектор на подготовленный сегмент кода. Мы не можем просто взять

и написать mov CS,селектор:
    dd 66h  ; префикс изменения разрядности

операнда
    db 0EAh  ; опкод команды jmp far
    dd ?   ; смещение в сегменте, на которое

мы jmp-аем
    dw 00001000b ; селектор сегмента, в который мы

jmp-аем

Итак, давай разберемся, куда же мы все-таки прыгнули. Селектор равен 00001000b. Младшие два бита – нули, это уровень привилегий, на него не смотрим. Второй бит (TI) – нолик. Вспоминаем. Значит этот селектор указывает на дескриптор из таблицы GDT... На какой же? Смотрим левее... 001! На первый! А что у нас описывает первый дескриптор в GDT? Правильно, сегмент кода.

 ; значит, мы перелетели на некую «точку входа в защищенный режим», смещение

которой мы уже высчитали (см. исходник):

 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-х разрядной адресации. Линия А20 не стоит того, чтобы о ней думать.

Самое главное – СПРАШИВАЙТЕ, СПРАШИВАЙТЕ И ЕЩЕ РАЗ СПРАШИВАЙТЕ! Приветствуются любые вопросы, начиная с «Что такое привилегированные инструкции?» и заканчивая «Почему так много пробелов в конце выводимой фразы?». На второй вопрос отвечу сразу. Дело в том, что при организации блоков повторений через irpc TASM почему то неправильно вычисляет его длину, если мы делаем это через equ. Посмотрите сами и напишите, если вы знаете в чем тут причина.

Скачать исходник целиком можно здесь: PM.asm




; ------------------------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 (точка входа в защищенный режим,
; находится в PM_CODE сегменте, поэтому от него и пляшем):
  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  ; сохраняем его в

переменной ENTRY_OFF
; (кстати, подобный "трюк" называется 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 уже нельзя, потому как по сути мы модифицируем данные :)

; собственно, загрузка регистра 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,           
                db             '&mes&',0Dh
endm
mes_len         equ            $-message     ; длина в байтах
PM_DATA         ends
;

------------------------------------------------------------------------------------------

---------------

                end         @@start

; ------------------------CUT HERE----------------------

Находчивый подписчик сразу спросит: «А не могли бы мы, допустим, сделать базу видеосегмента в нуле, а при выводе на экран значение 0B8000h поместить в EDI?». Тот, у кого возникнет данный вопрос – уже видит защищенный режим насквозь. Конечно могли бы, это то же самое по сути!

Ну и напоследок еще одна деталька (на всякий случай): ВЫ МОЖЕТЕ ПЕРЕКЛЮЧИТСЯ В ЗАЩИЩЕННЫЙ РЕЖИМ НАХОДЯСЬ ТОЛЬКО В РЕАЛЬНОМ РЕЖИМЕ !!!

В следующем выпуске нас ждет страничная адресация, поэтому настоятельно рекомендую до конца разобраться с сегментной.


© Broken Sword, 2002 - Рассылка

© Igoryk, 2002 - Дизайн




http://subscribe.ru/
E-mail: ask@subscribe.ru
Отписаться
Убрать рекламу

В избранное