ПРОЦЕССОР INTEL В
ЗАЩИЩЕННОМ РЕЖИМЕ
Выпуск
№5
__________________________________________________
Prelude:
Мда... Чето не везет мне с рассылкой. Вот новый дизайн придумали
(спасибо Maverick-у), а он почему то ни перед кем не хочет
во всей красе предстать... Самое интересное то, что ко мне, как к подписчику,
пришел полноценный, рабочий вариант, и даже в архив специально лазил – все
прекрасно открывается. Но вот уже у трех человек траблы,
поэтому возвращаю рассылке старый, убогий и огромный прикид...
(кстати, огромен он оттого, что клепаю я ее в ворде, а он почти к каждой букве лепит шрифт, размер и
цвет, поэтому и весит под мамонта). Может кто
подскажет что с этми всем беспределом делать? Интересно,
есть ли хоть один читатель у которого не было с пятым
выпуском никаких проблем?
Кстати, здесь есть 3 важные картинки, поэтому дождитесь их полной
загрузки...
FAQ:
- Чето ты с этими лимитами нездорового напорол в спецвыпуске...
Какие то «реальные лимиты», «лимиты в натуре» и т.п. Нельзя ли обойтись без
подобных медитаций, просто, как есть на самом деле?
Вообще все это напоминает недавний бум с миллениумом: в 1999 году
все СМИ как один гудели про то, что новое тысячелетие начнется в 2000 году.
Потом посчитали и оказалось, что в 2000 начнется
только ПОСЛЕДНИЙ ГОД УХОДЯЩЕГО ТЫСЯЧЕЛЕТИЯ, а новое начнется только в 2001...
Вот и я туда же - начал выдумывать какие
то "реальные лимиты", к-рые соотвествуют "концу последнего байта в сегменте"
и т.п. медитации. Хотел как лучше, получилось как всегда... :(
Вот КАК В ШЕСТИ СТРОЧКАХ ВСЕ ВЫГЛЯДИТ НА САМОМ ДЕЛЕ, без вольных импровизаций и
медитаций:
При G=0:
Адрес последнего
байта сегмента = Значение поля «База сегмента» + Значение поля «Лимит сегмента»
Размер сегмента = Значение поля
«Лимит сегмента» + 1
При
G=1:
Адрес последнего
байта сегмента = Значение поля «База сегмента» + Значение поля «Лимит сегмента» *
1000h + 0FFFh
Размер сегмента = Значение поля
«Лимит сегмента» * 1000h + 1000h
Вообще, ВОТ С ЭТОГО и стоило начинать, и на этом и закончить, ато расписал на 2 выпуска + спецвыпуск
какой то мути, всех запутал и напугал новичков... Вообще на этой проблеме не
стоит сосредотачиваться, нас ждут вещи поважнее.
РЕЗУЛЬТАТЫ КОНКУРСА:
Правильные
ответы:
1. Descr_code db
34h,12h,00h,00h,00h,XXh,0X000000b,00h
; сегмент с базой = 0 и размером
= 1235h
2. Descr_data db
0C8h,0Dh,36h,12h,00h,XXh,0X100000b,00h
; сегмент с базой = 1236h и размером = 0DC9h
3. Descr_stack
db 0FFh,00h,00h,20h,00h,XXh,1X000000b,00h
; сегмент с базой = 2000h и размером = 100000h
4. Descr_code2 db
0DEh,0BCh,01h,20h,10h,XXh,0X001010b,00h
; сегмент с базой = 102001h и размером = 0ABCDFh
5. Descr_data2 db
00h,00h,00h,00h,00h,XXh,0X000000b,10h
; сегмент с базой = 10000000h и размером = 1
6. Descr_stack2 db
01h,00h,10h,00h,00h,XXh,0X000001b,10h
; сегмент с базой = 10000010h и размером = 10002h
7. Descr_LDT
db 04h,00h,00h,00h,00h,XXh,1X000000b,20h
; сегмент с базой = 20000000h и размером = 5000h
Дополнительные
задания:
Ошибка во втором дескрипторе: бит 21 во втором двойном слове дескриптора
ДОЛЖЕН ВСЕГДА равняться нулю.
Те, кто знакомы с компиляторами типа TASM без труда смогут
загрузить GDTR так:
mov eax,offset GDT
mov dword ptr GDTR+2,eax
lgdt fword ptr GDTR
(но вообще это забегая вперед)
Краткое
содержание предыдущих серий…
Итак, сначала ты узнал, что
программа состоит из сегментов и все они расположены в
памяти. Каждый сегмент описывает специальная структура – дескриптор. Дескриптор
хранится в специальной таблице. Найти в океане памяти таблицу можно по
специальному регистру (GDTR, LDTR). Это как в той сказке: на острове – дуб, на
дубе ларец, в ларце – яйцо, в яйце – игла и т.д. Ну и с битом гранулярности вроде разобрались (слава Богу!), теперь пора
двигать дальше.
Селектор
«Все это хорошо и понятно» - скажешь ты,- «но вот что-то
я ничего пока не слыхал про сегментные регистры (ну те
самые – CS, DS, SS…). Что-то они себя пока никак не проявили, а мне казалось,
что именно ОНИ, как никто другие, должны служить нам при обращении к памяти и
все такое…»
Если ты заметил, то мы все это время спускаемся вниз по ступенькам:
сегмент в памяти <---- дескриптор <---- таблица дескрипторов …
Следующей ступенью будет СЕЛЕКТОР. Не правда ли, где-то это слово уже встречалось?
Так вот: селектор – это 16-битная структура данных (что??!!
ОПЯТЬ??!!!...), которая является идентификатором сегмента.
- Боже ж ты мой! Сколько это будет продолжаться??!! У каждого сегмента есть
свой дескриптор, мы уже прекрасно знаем где этот
чертов дескриптор расположен, а ты нам подсовываешь еще какой-то «селектор»!!!
… Селектор указывает не на САМ сегмент в памяти, а на его дескриптор, в таблице
дескрипторов… СЕЛЕКТОР ЖИВЕТ В СЕГМЕНТНОМ РЕГИСТРЕ (CS, DS…).
-Неужели…?! Ну и хрена с того ?
Спокойно!!!! Вот он:
Поле ИНДЕКС (биты 3-15):
указывает на один из 8192 дескрипторов в таблице дескрипторов (GDT или LDT).
Почему 8192? А какое максимальное число по-твоему
влезет в «биты 3-15»? Во-во…
-Подожжи, подожжи… не
так быстро… Ведь дескриптор же занимает 8 байт так? А
если индекс равен двойке? Так что это, значит, селектор указывает на второй
байт дескриптора в таблице или что?
ИЛИ ЧТО! ПРОЦЕССОР УМНОЖАЕТ значение поля ИНДЕКС НА 8 И
ДОБАВЛЯЕТ к полученному значению АДРЕС БАЗЫ ТАБЛИЦЫ. Т.е. процессор умножит
«двойку» на 8, а потом прибавит значение регистра таблицы – и мы благополучно
указываем НА НАЧАЛО ДЕСКРИПТОРА, как не крути!!!
-Как же узнать, из КАКОЙ ИМЕННО ТАБЛИЦЫ дескриптор?
Вот для этого нужен флажок TI (table indicator) (второй бит). Если он = 0, то прибавится
значение регистра GDTR (т.е. дескриптор расположен в таблице GDT), если же
установлен – LDTR.
-А шо за RPL?
(Requested Privilege Level) Запрашиваемый уровень привилегий… пока его лучше не
трогать. Но видишь, пока что, все что нам не знакомо
относится к каким то загадочным уровням привилегий…
Теперь внимательно следи: допустим мы ложим в DS число 0000000000110 0 00b. Что это значит? А
смотри: сразу разбиваем DS на кусочки (15-3 биты – индекс, 2 – TI, 1-0 – пока
лучше не смотрим на них). Индекс равен 6. Значит, шестой по счету дескриптор. А
где? В GDT конечно! (TI=0).
Ложим (кладем :) в ES число
0000000001000 1 00b. Восьмой дескриптор! На этот раз в LDT! Разобрались!
-А учитывается ли нулевой дескриптор (null descriptor) при «счете»?
ОБЯЗАТЕЛЬНО! БОЛЕЕ ТОГО! МЫ ДАЖЕ МОЖЕМ ПОЛОЖИТЬ В сегментный регистр (DS, SS…)
СЕЛЕКТОР С ПОЛЕМ ИНДЕКС и TI РАВНЫМИ 0!!! Т.е. фактически мы выбираем НУЛЕВОЙ
ДЕСКРИПТОР В ТАБЛИЦЕ GDT!!
-Но это же невероятно и невозможно!!!! :)
Возможно… Ничего страшного не
произойдет до тех пор, пока мы не ОБРАТИМСЯ К ПАМЯТИ, используя ТАКОЙ
сегментный регистр… А так он может хоть сто лет там
пролежать, но как только мы обратимся к памяти используя такой регистр (с
индексом = 0) – ВСЕ! ХАНА! #GP!!!!
Преобразование
логического адреса в линейный
И вот мы
подобрались к самому важному моменту: как же все таки
процессор формирует адрес и знает куда обращаться? Для этого нужно собрать все
полученные нами знания в кучу. Что для этого нужно сделать? Уважаемый читатель,
представь, что ты процессор...
Допустим, сейчас
ты выполняешь какой то код в оперативной памяти. И вот как ты размышляешь: о
местоположении в памяти инструкции тебе ничего не известно, кроме того, что на
нее указывает CS:EIP. По сути - это логический (т.е. некий абстрактный адрес).
Как же найти линейный адрес в памяти, руководствуясь только этими двумя
значениями: CS и EIP? Теперь мы можем это сделать! Сразу смотрим в CS и ищем в
нем поле "Индекс" (см. на селектор выше). Смотрим в поле индекс и тут
же узнаем о местоположении нужного дескриптора в таблице дескрипторов. Далее
нам нужно узнать АДРЕС БАЗЫ сегмента. Узнали. Что теперь? Теперь осталось
только одно: сложить этот адрес базы с EIP - и мы получим линейный адрес
инструкции в памяти (к-рый при сегментной организации
совпадает с физическим, мы об этом говорили ранее). Еще
раз: селектор-->дескриптор-->база... +EIP = ЛИНЕЙНЫЙ адрес.
Сегментный
регистр
В архитектуре
процессоров Intel существуют ШЕСТЬ сегментных
регистров:
CS, DS, SS, ES, GS и FS. Каждый из этих регистров отвечает за свой сегмент в
памяти (кода, данных или стека). Итак, даже если программа состоит из ТЫСЯЧИ
сегментов, ТОЛЬКО 6 из них могут быть доступны В
ДАННЫЙ МОМЕНТ ВРЕМЕНИ. Другие сегменты станут доступны
ТОЛЬКО ПОСЛЕ ЗАГРУЗКИ СООТВ. селекторов в сегментные регистры.
Помнишь, как в
реальном режиме формировались линейные адреса? Значение сегментного регистра
умножалось на 10h и прибавлялось смещение. Т.е. никакого селектора явно не
существовало, никаких дескрипторов, ничего! Только сегментный регистр и
смещение! Один шаг до формирования линейного адреса! В защищеном
режиме нужно пройти сквозь огонь, воду и медные трубы
(селектор-дескриптор-база...) чтобы докопаться до линейного адреса... Итак, в
защищенном режиме сегментный регистр - это 16 битный регистр, содержащий
информацию о дескрипторе (а именно - местоположении) и запрашиваемом уровне
привилегий. Стоп! Здесь сразу же возникает вопрос: неужели же процессор при
каждом обращении к памяти все время повторяет одни и те же действия (ищет
дескриптор, потом ищет базу, затем прибавляет к базе смещение...), и так при
выполнении фактически каждой команды... КОНЕЧНО ЖЕ
НЕТ! НА САМОМ ДЕЛЕ сегментный регистр - это 80-битный регистр (!!! да да !!!), нам же доступны ТОЛЬКО
младшие 16 бит, которые и называются СЕЛЕКТОРОМ!!! Остальные 64 бита называются
"Теневым регистром" (Shadow register) или "Дескрипторным кэшем"
(Descriptor cache), ОНИ И
СОДЕРЖИТ ТУ САМУЮ БАЗУ, которую проц по идее должен
был бы высчитывать на каждом шаге. Кроме базы этот самый "теневой
регистр" содержит еще и лимит, и права доступа. Еще раз: как только мы
загружаем в ВИДИМУЮ ЧАСТЬ (в селектор) соответствующее значение, процессор
СРАЗУ же по селектору (а конкретно - по полю индекс) выпасает базу, лимит и
права доступа из дескриптора и заносит их в "теневую часть" сегмнетного регистра, тем самым
облегчая себе жизнь в дальнейшем.
А ТЕПЕРЬ
ВНИМАНИЕ!!!
Если
мы вдруг решим неожиданно поменять значение базы в дескрипторе для какого-либо
сегмента, селектор которого в данный момент УЖЕ находится в сегментном
регистре, то мы также должны позаботиться и о ПЕРЕЗАГРУЗКЕ сегментного
регистра, т.к. в теневой части остануться СТАРЫЕ
ЗНАЧЕНИЯ базы и лимита, и фактически процу абсолютно
наплевать на то, что твориться в таблице дескрипторов, он руководствуется
только ТЕКУЩИМ ЗНАЧЕНИЕМ БАЗЫ И ЛИМИТА В ТЕНЕВОЙ ЧАСТИ. Короче запомни золотое
правило: поменял базу (или лимит) в дескрипторе - перегрузи соотв. сегментный
регистр!!!
Загрузить сегментные регистры (явно или неявно) позволяют 16 команд:
ЯВНО:
MOV - ну это и так ясно
POP - значение из стека
LDS - загрузить DS
LES - загрузить ES
LSS - загрузить SS
LGS - загурзить GS
LFS - загрузить FS
НЕЯВНО:
CALL, JMP, RET, SYSENTER, SYSEXIT, IRET, INTn, INTO,
INT3. Чаще
всего "неявные" команды изменяют значение именно CS-регистра, но в некторых случаях и других.
Кстати, как ты понимешь, в реальном режиме архитектура проца
никуда не девается, при загрузке селектора в сегм.
регистр процессор сам создает соответствующий
дескриптор в его сркытой части. Он описывает
16-битный сегмент, начинающийся по указанному адресу с границей 64 Кб.
p.s. если инструкции LCS не
существует в документации Интела, это не значит что ее не существует вообще. Я где
то читал про это, просто интел в таблицах опкодов оставила для нее рядом с соотв. инструкциями lds, lss... пустой квадратик.
Сейчас найти не могу. Кто об этом что то знает - мыльните пожалста.
На сегодня все. Пишите письма... brokensword@mail.ru