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

Выпуск №9

 

В выпуске:

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


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

 

После длительного затишья мы продолжаем изучать защищенный режим. В предыдущем выпуске был рассмотрен пример переключения процессора в защищенный режим БЕЗ использования страничной адресации. В данном выпуске будет рассмотрен код перевода проца в защищенный режим С использованием страничной адресации. По сути – код остался прежним, я лишь опишу изменения, которые необходимо внести.

Приступим. Следует иметь ввиду, что все изменения для включения страничной адресации производятся уже ПОСЛЕ ПЕРЕХОДА В ЗАЩИЩЕННЫЙ РЕЖИМ !!! И еще - страницы у нас будут 4-килобайтными!!! И вот они, эти изменения:

1. Подготовить каталог страниц
2. Заполнить таблицу страниц
3. Адрес каталога страниц поместить в регистр CR3
4. Включить страничную адресацию (установка бита 31 в регистре CR0)

THAT’S ALL!

Видишь, как все просто? А ты боялся!
Вот картинка (она уже у нас фигурировала), из которой видно для чего нужны эти 4 пункта:



Прежде, чем приступить, хочу вкратце рассказать, чего же мы будем творить. Вопрос в том, КАК НАГЛЯДНО ПОКАЗАТЬ РАЗНИЦУ МЕЖДУ СЕГМЕНТНОЙ И СЕГМЕНТНО-СТРАНИЧНОЙ адресацией? Оказывается, это не так-то уж и просто... Но вот как я попытаюсь этого добиться:
В предыдущем выпуске рассылки мы рассмотрели код, который выводит строку на экран, т.е. все свелось в конце концов к тому, что по адресу ES:0 вывелась надпись, где ES – селектор на предварительно подготовленный дескриптор, база которого равна 0B8000h (т.е. начало видеопамяти в текстовом режиме). Все однозначно и понятно. Теперь, как ты уже знаешь, при страничной адресации адрес 0B8000h препарируется на три части: старшие 10 бит – номер элемента в каталоге страниц, средние 10 бит – номер элемента в таблице страниц, и, наконец, младшие 12 бит – это СМЕЩЕНИЕ В СТРАНИЦЕ.
Недолго думая, можно умозаключить, что, попытавшись что либо записать по адресу 0B8000h при СТРАНИЧНОЙ адресации, мы не обнаружим там никакого начала видеопамяти, разумеется, если ПРЕДВАРИТЕЛЬНО (!) мы корректно не настроим каталог и таблицы страниц. Вот она, виртуальность! Вроде, вот же он: адрес начала видеопамяти! 0B8000h! У нас перед носом! Ан, нет... Это всего лишь иллюзия, т.к. этот адрес – не линейный, а виртуальный. Вот разбить его на три части, пройтись по каталогу и таблице страниц – вот тогда сформируется ОКОНЧАТЕЛЬНЫЙ АДРЕС (ВЫСТАВЛЯЕМЫЙ ПРОЦЕМ НА ШИНУ), который (при страницах в 4Кб) В ДАННОМ, КОНКРЕТНОМ СЛУЧАЕ (0B8000h) может принять значение от 0 до 1Мб!!! Надеюсь, понятно почему именно эти значения?

Поэтому вот оно, золотое правило: ПРИ СТРАНИЧНОЙ АДРЕСАЦИИ ЛИНЕЙНЫЙ АДРЕС ФОРМИРУЮТ ЭЛЕМЕНТЫ КАТАЛОГОВ И ТАБЛИЦ СТРАНИЦ, ну и конечно «проводником» по этим таблицам служит сам ВИРТУАЛЬНЫЙ адрес...

Значит, это все к чему? А к тому, что если представить все это дело НАОБОРОТ, то придем точно к такому же выводу! Т.е. я хочу сказать, что если 0B8000h – это на самом деле нечто совершенно иное, то с точно таким же успехом, ЛЮБОЙ ВИРТУАЛЬНЫЙ АДРЕС ПОСЛЕ ПРЕОБРАЗОВАНИЙ МОЖЕТ ПРЕВРАТИТЬСЯ В 0B8000!!! НАМ СТОИТ ТОЛЬКО ЗАХОТЕТЬ ЭТОГО! А что значит захотеть? А это значит, соответствующим образом заполнить элементы каталога и таблицы страниц, только и всего... На примере все станет совершенно ясно.

Вот что мы еще сделаем: вообще уберем дескриптор сегмента видеопамяти из таблицы GDT (итого у нас останется два дескриптора – кода и данных). Для чего – станет ясно ниже.

Итак, где-нибудь сразу после перехода в защ. режим и загрузки сегментных регистров соотв. селекторами нужно создать КАТАЛОГ СТРАНИЦ.

ENTRY_POINT:
; загрузим сегментные регистры селекторами на соответствующие дескрипторы:

          	mov	 AX,00010000b   	; селектор на второй дескриптор (DATA_descr)
	    	mov	 DS,AX         		; в DS его
	   	mov	 ES,AX           	; и в ES его же

вот теперь создадим каталог страниц. Что это такое? Каталог страниц – это набор 32- разрядных записей (элементов); вообще, структура записей подробно представлена в седьмом выпуске рассылки, поэтому кто забыл – посмотрите.
Начало каталога страниц в оперативной памяти будет располагаться по адресу 1Мб (100000h).

Теперь секундочку внимания! В нашем примере единственное, что мы делаем полезного – выводим на экран некоторую надпись. По сути, при этом используется один единственный адрес – ES:ESI, или 0B8000h. Следовательно, в данном примере для демонстрации возможностей страничной адресации достаточно заполнить лишь ОДИН ЕДИНСТВЕННЫЙ элемент каталога страниц. Так и сделаем:

	    	mov	 EDI,100000h		; начало каталога страниц (1Мб)
	    	mov	 EAX,101007h		; это наш один единственный значащий элемент
	    	stosd				; ES:[EDI] <- EAX
	    	mov	 ECX,1023		; остальные 1023 элементов
	    	xor	 EAX,EAX		
	    	rep	 stosd			; забьем нулями все остальные элементы каталога

кстати, почему такой загадочный элемент – 101007h? Открываем формат элемента каталога страниц и все становится ясно. (не ленимся, не ленимся! Открываем 7 выпуск и смотрим на картинку). 7 (установлены младшие 3 бита адреса) – означает, что страница присутствует в оперативной памяти (бит P), доступна для чтения записи (бит R/W) и доступна с любого уровня привилегий (бит U/S). А что такое 1010? Не забыл, что младшие 12 бит для АДРЕСА таблицы страниц ВСЕГДА РАВНЫ нулю? Значит, это 1010000h, что соответствует 1Мб + 4Кб, а 4 Кб – потому что САМ каталог занимает столько.

все. С каталогом покончено. Теперь примемся за ТАБЛИЦУ СТРАНИЦ



	    	mov	 EAX,00000007h		; первая запись – адрес нулевой страницы равен 0
	   	mov	 ECX,1024		; кол-во страниц в таблице

fill_page_table:
	    	stosd				; запишем первый элемент
	    	add	 EAX,1000h		; добавим 4 Кб
	    	loop	 fill_page_table	; и повторим для всех элементов таблицы страниц
	
	    	mov	 EAX,00100000h		; базовый адрес = 1 Мб
	   	mov	 CR3,EAX		; в CR3 его! (база каталога страниц ВСЕГДА должна лежать в CR3)

; включить страничную адресацию
          	mov	 EAX,CR0
	    	or	 EAX,80000000h
	    	mov	 CR0,EAX
; а теперь изменить физический адрес страницы 12000h на 0B8000h
          	mov	 EAX,000B8007h
	    	mov	 ES:00101000h+012h*4,EAX

вот здесь надо проникнуться всем существом. Чего это мы такого сотворили? А вот чего: мы, в таблице страниц, заменили адрес страницы, начало которой равно 12000h НА АДРЕС НАЧАЛА ВИДЕОПАМЯТИ (0B8000h)!!! Как это у нас получилось? Очень просто. АДРЕС НАЧАЛА ТАБЛИЦЫ СТРАНИЦ = 101000h, так? А сколько занимает один элемент? 4 байта!!! (32 бита). Поэтому 12*4 – это и есть ЗАПИСЬ ТАБЛИЦЫ СТРАНИЦ, которая указывает на страницу, начинающуюся по адресу 12000h!!!

А вот теперь демонстрация силы и мощи страничной адресации:

; вывод mes1 по стандартному адресу (начало видеопамяти 0B8000h)
	    	mov	 EDI,0B8000h   		; для команды movsw, EDI = начало видепамяти
	    	mov      ESI,PM_DATA    
	    	shl      ESI,4
	    	add      ESI,offset mes1  	; ESI = адрес начала mes1
	    	mov	 ECX,mes_len      	; длина текста в ECX
	    	rep      movsw           	; DS:ESI (наше сообщение) -> ES:EDI (видеопамять)

; вывод mes2 по НЕСТАНДАРТНОМУ АДРЕСУ 12000h:
       		mov      EDI,0120A0h 		; 12000h (уже можешь считать, что это 0B8000h) + A0h
       		mov      ESI,PM_DATA
       		shl      ESI,4
       		add      ESI,offset mes2     	; ESI = адрес начала mes2
       		mov      ECX,mes_len         	; длина текста в ECX
       		rep      movsw               	; DS:ESI (наше сообщение) -> ES:EDI (видеопамять)

А теперь небольшое партийное задание: переделать прогу так, чтобы начало видеопамяти совпадало с адресом 66000h.

Ответы присылать на brokensword@mail.ru

Кто разберется и пришлет правильный ответ – тот проникся страничной адресацией ).

Напоследок – код всей проги (запускать ТОЛЬКО в реальном режиме :) Качать здесь: PM2.asm



; 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,03h
               	int            	10h                             ; текстовый режим 80x25 + очистка экрана
                
; открываем линию А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 уже нельзя, потому как по сути мы модифицируем данные :)

; собственно, загрузка регистра 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
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	       ES,AX                      	; его же - в ES

; * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
; создать каталог страниц
                mov	       EDI,00100000h	           	; его физический адрес - 1 Мб
		mov	       EAX,00101007h	           	; адрес таблицы 0 = 1 Мб + 4 Кб
		stosd			                   	; записать первый элемент каталога
		mov	       ECX,1023	                   	; остальные элементы каталога -
		xor	       EAX,EAX	                   	; нули
		rep            stosd
; заполнить таблицу страниц 0
                mov	       EAX,00000007h	           	; 0 - адрес страницы 0
		mov	       ECX,1024	                   	; число страниц в таблице
fill_page_table:
		stosd			                   	; записать элемент таблицы
		add	       EAX,00001000h	           	; добавить к адресу 4096 байтов
		loop           fill_page_table		   	; и повторить для всех элементов
; поместить адрес каталога страниц в CR3
                mov	       EAX,00100000h	           	; базовый адрес = 1 Мб
		mov	       CR3,EAX
; включить страничную адресацию,
                mov	       EAX,CR0
 		or	       EAX,80000000h
	 	mov	       CR0,EAX
; а теперь изменить физический адрес страницы 12000h на 0B8000h
                mov	       EAX,000B8007h
		mov	       ES:00101000h+012h*4,EAX
; * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
; вывод mes1 по стандартному адресу (начало видеопамяти 0B8000h)
		mov	       EDI,0B8000h                 	; для команды movsw, EDI = начало видепамяти
		mov            ESI,PM_DATA                 	 
		shl            ESI,4
		add            ESI,offset mes1             	; ESI = адрес начала mes1
		mov	       ECX,mes_len                 	; длина текста в ECX
		rep            movsw                       	; DS:ESI (наше сообщение) -> ES:EDI (видеопамять)

; вывод mes2 по НЕСТАНДАРТНОМУ АДРЕСУ 12000h:
                mov            EDI,0120A0h                 	; 12000h (уже можешь считать, что это 0B8000h) + A0h
                mov            ESI,PM_DATA
                shl            ESI,4
                add            ESI,offset mes2             	; ESI = адрес начала mes2
                mov            ECX,mes_len                 	; длина текста в ECX
                rep            movsw                       	; DS:ESI (наше сообщение) -> ES:12000h (типа видеопамять)

		jmp            $                           	; погружаемся в вечный цикл
PM_CODE	        ends
; ---------------------------------------------------------------------------------------------------------


; СЕГМЕНТ ДАННЫХ (для Protected Mode)
; ---------------------------------------------------------------------------------------------------------
PM_DATA	        segment        para public 'DATA' use32
		assume	       CS:PM_DATA

; сообщение, которое мы будем выводить на экран (оформим его в виде блока повторений irpc):
mes1:
irpc            mes1,          This string was outputted to standart adress 0B8000h...          
               	db             '&mes1&',0Dh
endm
mes2:
irpc            mes2,          And this one - to dummy adress 0120A0h. Cool? Now press RESET... 
                db             '&mes2&',0Bh
endm
mes_len         equ            66		           ; длина в байтах
PM_DATA         ends
; ---------------------------------------------------------------------------------------------------------  
                end	        @@start

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

© Igoryk, 2002 - Дизайн




Hosted by uCoz