Процессор Intel в защищенном режиме #8

Дата публикации 15 окт 2002

Процессор Intel в защищенном режиме #8 — Архив WASM.RU

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

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

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

Поехали!

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

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

Код (Text):
  1.  
  2. Глобальная таблица дескрипторов:
  3. GDT:
  4.  
  5. ; нулевой дескриптор (обязательно должен присутствовать в GDT!)
  6. NULL_descr      db  8 dup (0)
  7.  
  8. ; дескриптор 32-разрядного сегмента кода:   база = 00000000h, размер = FFFFFFFFh
  9. CODE_descr      db  0FFh, 0FFh, 00h, 00h, 00h, 10011010b, 11001111b , 00h  
  10.  
  11. ; дескриптор 32-разрядного сегмента данных: база = 00000000h, размер = FFFFFFFFh
  12. DATA_descr      db  0FFh, 0FFh, 00h, 00h, 00h, 10010010b, 11001111b , 00h
  13.  
  14. ; дескриптор сегмента видеопамяти:          база = 000B8000h, размер = 0000FFFFh
  15. VIDEO_descr     db  0FFh, 0FFh, 00h, 80h, 0Bh, 10010010b, 01000000b , 00h
  16.  
  17. ; размер таблицы GDT:
  18. GDT_size        db      $-GDT
  19.  
  20. ; а следующие три слова (размер GDT и линейный адрес начала таблицы) мы должны
  21. будем попозже загрузить в GDTR:
  22. GDTR            dw      GDT_size-1
  23.             dd      ?

Вычислим линейный адрес таблицы GDT. Зачем? Чтоб загрузить регистр GDTR. Линейный адрес GDT = линейный адрес базы сегмента RM_CODE (потому что GDT расположена именно в этом сегменте у нас в программе) + смещение метки GDT в нем.

Код (Text):
  1.  
  2. xor     EAX,EAX     ; обнуление регистра EAX
  3. mov     AX,RM_CODE      ; теперь AX = номер сегмента RM_CODE
  4. shl     EAX,4           ; сдвигаем значение в EAX на 4 влево
  5. ; вот теперь EAX = линейный адрес базы сегмента RM_CODE
  6.  
  7. add     AX,offset GDT   ; теперь EAX = линейный адрес GDT
  8.  
  9. ; линейный адрес GDT кладем в заранее подготовленную переменную:
  10. mov     dword ptr GDTR+2,EAX
  11.  
  12. ; собственно, загрузка регистра GDTR:
  13. lgdt        fword ptr GDTR

(вообще желательно просмотреть весь исходник целиком, потому что RM_CODE сбивает с толку)

Теперь нам еще нужно вычислить точно таким же макаром линейный адрес т.н. ТОЧКИ ВХОДА В ЗАЩИЩЕННЫЙ РЕЖИМ. Дело в том, что после переключения в защ. режим мы окажемся в неком подвешенном состоянии – когда регистр CS еще содержит номер сегмента из реального режима, а не селектор. Мы не можем просто сделать mov CS, селектор, CS можно изменить только дальним jmp-ом либо iret-ом, но щас не про то. Поэтому нам все же придется делать дальний jmp для изменения CS. НО КУДА? Вот именно на ТОЧКУ ВХОДА В ЗАЩИЩЕННЫЙ РЕЖИМ, см. исходник.

Мы уже почти готовы переключится в защ. режим, единственное, что мы обязаны еще сделать – ЗАПРЕТИТЬ ВСЕ ПРЕРЫВАНИЯ (причем, как маскируемые, так и немаскируемые), потому что пока мы не умеем с ними работать, и у нас нет ни одного обработчика, первый же тик таймера после переключения в PM завесит нам всю систему...

Код (Text):
  1.  
  2. ; запрещаем сначала маскируемые прерывания:
  3.     cli
  4.  
  5. ; а затем и немаскируемые:
  6.     in      AL,70h  ; читаем 70h порт
  7.     or      AL,80h  ; ставим восьмой бит
  8.     out     70h,AL  ; запихиваем на место
  9. ; теперь все прерывания запрещены

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

Код (Text):
  1.  
  2.     mov     EAX,CR0 ; EAX = CR0
  3.     or      AL,1        ; ставим нулевой бит
  4.     mov     CR0,EAX ; пихаем назад...

Кто играл в Half Life, помните, в первой части, когда взрыв на заводе в самом начале, Гордон падает в обморок, а потом на несколько секунд приходит в себя и оказывается в другом мире, вокруг какие то существа едят траву, вот что то типа этого произошло сейчас... :smile3:

Теперь давайте осмотримся кругом, что мы видим? Ага, вон таблица GDT во тьме. Память оперативная память на месте. Регистры, где вы? Мы все тута! Ну слава Богу... мы здесь не одни. Где таблица векторов? Но нет ответа... Ладно, и без нее обойдемся...

Код (Text):
  1.  
  2. ; загрузим в CS селектор на подготовленный сегмент кода. Мы не можем просто
  3. взять и написать mov CS,селектор:
  4.     dd      66h         ; префикс изменения разрядности операнда
  5.     db      0EAh            ; опкод команды jmp far
  6.     dd      ?           ; смещение в сегменте, на которое мы jmp-аем
  7.     dw      00001000b       ; селектор сегмента, в который мы jmp-аем

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

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

Код (Text):
  1.  
  2. ENTRY_POINT:
  3. ; загрузим сегментные регистры селекторами на соответствующие дескрипторы:
  4.     mov     AX,00010000b    ; AX = селектор дескриптора данных (№2) 
  5.     mov     DS,AX           ; кладем его в DS
  6.     mov     AX,00011000b    ; AX = селектор дескриптора видеопамяти (№3)
  7.     mov     ES,AX           ; кладем его в ES
  8.  
  9. xor         SI,SI               ; обнуляем SI
  10.     mov         SI,PM_DATA          ; SI = номер сегмента PM_DATA
  11.     shl         ESI,4               ; ESI = линейный адрес сегмента PM_DATA
  12.     add       ESI,offset message  ; ESI = линейный адрес строки message
  13.       xor         EDI,EDI       ; EDI = позиция на экране (относительно 0B8000h)
  14.     mov       ECX,mes_len         ; длина текста в ECX
  15.  
  16. ; вывод на экран:
  17.     rep         movsb    ; DS:ESI (наше сообщение) -> ES:EDI (видеопамять)
  18.     jmp         $        ; погружаемся в вечный цикл

Теперь единственное, что мы можем сделать – это перезагрузить компьютер (причем, только кнопкой RESET). Я умышленно убрал часть кода, которая отвечает за переключение обратно в реальный режим, потому что она сильно все затуманит (надо в таблице GDT еще подготовить 16-битные дескрипторы), но если кому интересно – пишите.

Да, еще что. В полном исходнике (см. ниже) сразу может напугать «Линия А20». Что это такое? Дело в том, что после запуска компа для совместимости с 8086 используются 20-разрядные адреса (адресные линии А0-А19), т.ч. попытка записать что-то по линейному адресу 100000h приведет к записи по адресу 0h. Вот этот режим нам и нужно отменить (установив бит 2 в 92h порту) для использования 32-х разрядной адресации.

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

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

Код (Text):
  1.  
  2. ; ------------------------CUT HERE----------------------
  3. ; TASM:
  4. ; TASM /m PM.asm
  5. ; TLINK /x /3 PM.obj
  6. ; PM.exe
  7.  
  8. ; MASM:
  9. ; ML /c PM.asm
  10. ; LINK PM.obj,,NUL,,,
  11. ; PM.exe
  12.  
  13.         .386p                                           ; разрешить привилегированные инструкции i386
  14.        
  15. ; СЕГМЕНТ КОДА (для Real Mode)
  16. ; ---------------------------------------------------------------------------------------------------------
  17. RM_CODE     segment     para public 'CODE' use16
  18.         assume      CS:RM_CODE,SS:RM_STACK
  19. @@start:
  20. ; очистка экрана:
  21.                 mov             AX,3
  22.                 int             10h
  23.                
  24. ; открываем линию А20 (для 32-х битной адресации):
  25.         in      AL,92h
  26.         or      AL,2
  27.         out     92h,AL
  28.  
  29. ; вычисляем линейный адрес метки ENTRY_POINT (точка входа в защищенный режим):
  30.         xor     EAX,EAX             ; обнуляем регистра EAX
  31.         mov     AX,PM_CODE          ; AX = номер сегмента PM_CODE
  32.         shl     EAX,4               ; EAX = линейный адрес PM_CODE
  33.         add     EAX,offset ENTRY_POINT      ; EAX = линейный адрес ENTRY_POINT
  34.         mov     dword ptr ENTRY_OFF,EAX     ; сохраняем его в переменной    
  35. ; (кстати, подобный "трюк" называется SMC или Self Modyfing Code - самомодифицирующийся код)
  36.  
  37. ; теперь надо вычислить линейный адрес GDT (для загрузки регистра GDTR):
  38.         xor     EAX,EAX
  39.         mov     AX,RM_CODE          ; AX = номер сегмента RM_CODE
  40.         shl     EAX,4               ; EAX = линейный адрес RM_CODE
  41.         add     AX,offset GDT           ; теперь EAX = линейный адрес GDT
  42.  
  43. ; линейный адрес GDT кладем в заранее подготовленную переменную:
  44.         mov     dword ptr GDTR+2,EAX
  45. ; а подобный трюк назвать SMC уже нельзя, потому как по сути мы модифицируем данные <img src="styles/smiles_s/smile3.gif" class="mceSmilie" alt=":smile3:" title="Smile3    :smile3:">
  46.  
  47. ; собственно, загрузка регистра GDTR:
  48.         lgdt        fword ptr GDTR
  49.  
  50. ; запрет маскируемых прерываний:
  51.         cli
  52.  
  53. ; запрет немаскируемых прерываний:
  54.         in      AL,70h
  55.         or      AL,80h
  56.         out     70h,AL
  57.  
  58. ; переключение в защищенный режим:
  59.         mov     EAX,CR0
  60.         or      AL,1
  61.         mov     CR0,EAX
  62.  
  63. ; загрузить новый селектор в регистр CS
  64.         db      66h             ; префикс изменения разрядности операнда
  65.         db      0EAh                ; опкод команды JMP FAR
  66. ENTRY_OFF   dd      ?               ; 32-битное смещение
  67.         dw      00001000b           ; селектор первого дескриптора (CODE_descr)
  68.  
  69. ; ТАБЛИЦА ГЛОБАЛЬНЫХ ДЕСКРИПТОРОВ:
  70. GDT:  
  71. ; нулевой дескриптор (обязательно должен присутствовать в GDT!):
  72. NULL_descr  db      8 dup(0)
  73. CODE_descr  db      0FFh,0FFh,00h,00h,00h,10011010b,11001111b,00h
  74. DATA_descr  db      0FFh,0FFh,00h,00h,00h,10010010b,11001111b,00h
  75. VIDEO_descr      db              0FFh,0FFh,00h,80h,0Bh,10010010b,01000000b,00h
  76. GDT_size    equ                 $-GDT               ; размер GDT
  77.  
  78. GDTR        dw      GDT_size-1          ; 16-битный лимит GDT
  79.         dd      ?               ; здесь будет 32-битный линейный адрес GDT
  80. RM_CODE         ends
  81. ; ---------------------------------------------------------------------------------------------------------
  82.  
  83.  
  84.  
  85. ; СЕГМЕНТ СТЕКА (для Real Mode)
  86. ; ---------------------------------------------------------------------------------------------------------
  87. RM_STACK       segment          para stack 'STACK' use16
  88.            db          100h dup(?)                     ; 256 байт под стек - это даже много
  89. RM_STACK       ends
  90. ; ---------------------------------------------------------------------------------------------------------
  91.  
  92.  
  93.  
  94. ; СЕГМЕНТ КОДА (для Protected Mode)
  95. ; ---------------------------------------------------------------------------------------------------------
  96. PM_CODE     segment     para public 'CODE' use32
  97.         assume      CS:PM_CODE,DS:PM_DATA
  98. ENTRY_POINT:
  99. ; загрузим сегментные регистры селекторами на соответствующие дескрипторы:
  100.                 mov        AX,00010000b                ; селектор на второй дескриптор (DATA_descr)
  101.             mov        DS,AX                       ; в DS его
  102.             mov        AX,00011000b                ; селектор на третий дескриптор (VIDEO_descr)
  103.             mov        ES,AX                       ; а этого в ES
  104.  
  105.             xor            SI,SI                       ; обнуляем SI
  106.             mov            SI,PM_DATA                  ; SI = номер сегмента PM_DATA
  107.             shl            ESI,4                       ; ESI = линейный адрес сегмента PM_DATA
  108.             add        ESI,offset message          ; ESI = линейный адрес строки message
  109.                 xor            EDI,EDI                     ; EDI = позиция на экране (относительно 0B8000h)
  110.             mov        ECX,mes_len                 ; длина текста в ECX
  111.  
  112. ; вывод на экран:
  113.             rep            movsb                       ; DS:ESI (наше сообщение) -> ES:EDI (видеопамять)
  114.             jmp            $                           ; погружаемся в вечный цикл
  115. PM_CODE         ends
  116. ; ---------------------------------------------------------------------------------------------------------
  117.  
  118. ; СЕГМЕНТ ДАННЫХ (для Protected Mode)
  119. ; ---------------------------------------------------------------------------------------------------------
  120. PM_DATA         segment        para public 'DATA' use32
  121.         assume         CS:PM_DATA                
  122.        
  123. ; сообщение, которое мы будем выводить на экран (оформим его в виде блока повторений irpc):
  124. message:
  125. irpc            mes,           <Congratulations! We are in PM! Now you MUST press RESET key...              >
  126.                 db             '&mes&',0Dh
  127. endm
  128. mes_len         equ            $-message           ; длина в байтах
  129. PM_DATA         ends
  130. ; ---------------------------------------------------------------------------------------------------------  
  131.  
  132.                 end         @@start
  133.  
  134. ; ------------------------CUT HERE----------------------

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

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


0 1.267
archive

archive
New Member

Регистрация:
27 фев 2017
Публикаций:
532