Cамоучитель по Assembler




Формат дескриптора памяти.





Как видно из рисунка, дескриптор занимает 8 байт. В байтах 2...4 и 7 записывается линейный базовый адрес сегмента. Полная длина базового адреса - 32 бит. В байтах 0-1 записываются младшие 16 бит границы сегмента, а в младшие четыре бита байта атрибутов 2 - оставшиеся биты 16...19. Границей сегмента называется номер его последнего байта. Мы видим, что граница описывается 20-ю битами, и ее численное значение не может превышать 1М. Однако, единицы, в которых задается граница, можно изменять, что осуществляется с помощью бита дробности G (бит 7 байта атрибутов 2). Если G=0, граница указывается в байтах; если 1 - в блоках по 4 Кбайт. Таким образом, размер сегмента можно задавать с точностью до байта, но тогда он не может быть больше 1 Мбайт; если же установить G=l, то сегмент может достигать 4 Гбайт, однако его размер будет кратен 4 Кбайт. База сегмента и в том, и в другом случае задастся с точностью до байта.
Рассмотрим теперь атрибуты сегмента, которые занимают два байта дескриптора.
Бит A (Accessed, было обращение) устанавливается процессором в тот момент, когда в какой-либо сегментный регистр загружается селектор данного сегмента. Далее процессор этот бит не сбрасывает, однако его может сбросить программа (разумеется, если она имеет доступ к содержимому дескриптора, что обычно является прерогативой операционной системы). Анализируя биты обращения различных сегментов, программа может судить о том, было ли обращение к данному сегменту' после того, как она сбросила бит А.
Тип сегмента занимает 3 бит (иногда бит А включают в поле типа, и тогда тип занимает 4 бит) и может иметь 8 значений. Тип определяет правила доступа к сегменту. Так, если сегмент имеет тип 1, для него разрешены чтение и запись, что характерно для сегментов данных. Назначив сегменту тип 0, мы разрешим только чтение этого сегмента, защитив его тем самым от любых модификаций. Тип 4 обозначает разрешение исполнения, что характерно для сегментов команд. Используются и другие типы сегментов.
Подчеркнем, что защита сегментов памяти от несанкционированных его типом действий выполняется не программой, и даже не операционной системой, а процессором на аппаратном уровне. Так, при попытке записи в сегмент типа 0 возникнет так называемое исключение общей защиты. Исключением называется внутреннее прерывание, возбуждаемое процессором при возникновении каких-либо неправильных с его точки зрения ситуаций. Попытка записи в сегмент, для которого запись запрещена, и относится к такого рода ситуациям. Исключению общей защиты соответствует вектор 13, в котором должен находиться адрес обработчика этого исключения.
Стоит еще обратить внимание на тип 4. Для сегмента команд разрешается только исполнение, но не запись и даже не чтение. Это значит, что в защищенном режиме программа не может случайно залезть в свой сегмент команд и затереть его; не может она также и сознательно модифицировать команды в процессе своего выполнения - методика, иногда используемая в программах реального режима для защиты от их расшифровки любознательными программистами
Бит 4 байта атрибутов 1 является идентификатором сегмента. Если он равен 1, как это показано на Рисунок 4.9, дескриптор описывает сегмент памяти. Значение этого бита 0 характеризует дескриптор системного сегмента.
Поле DPL (Descriptor Privilege Level, уровень привилегий дескриптора) служит для защиты программ друг от друга. Уровень привилегий может принимать значения от 0 (максимальные привилегии) до 3 (минимальные). Программам операционной системы обычно назначается уровень 0, прикладным программам - уровень 3, в результате чего исключается возможность некорректным программам разрушить операционную систему. С другой стороны, если прикладная программа сама выполняет функции операционной системы, переводя процессор в защищенный режим и работая далее в этом режиме, ее сегментам следует назначить наивысший (нулевой) уровень привилегий, что откроет ей доступ ко всем средствам защищенного режима.
Бит Р говорит о присутствии сегмента в памяти. В основном он используется для организации виртуальной памяти. С помощью этого бита система может определить, находится ли требуемый сегмент в памяти, и при необходимости загрузить его с диска. В процессе выгрузки ненужного пока сегмента на диск бит Р в его дескрипторе сбрасывается.
Младшая половина байта атрибутов 2 занята старшими битами границы сегмента. Бит AVL (от Available, доступный) не используется и не анализируется процессором и предназначен для использования прикладными программами.
Бит D (Default, умолчание) определяет действующий по умолчанию размер для операндов и адресов. Он изменяет характеристики сегментов двух типов: исполняемых и стека. Если бит D сегмента команд равен 0, в сегменте по умолчанию используются 16-битовые адреса и операнды, если 1 - 32-битовые.
Атрибут сегмента, действующий по умолчанию, можно изменить на противоположный с помощью префиксов замены размера операнда (66h) и замены размера адреса (67п). Таким образом, для сегмента с D=0 префикс 66h перед некоторой командой заставляет ее рассматривать свои операнды, как 32-битовые, а для сегмента с D=l тот же префикс 66h, наоборот, сделает операнды 16-битовыми. В некоторых случаях транслятор сам включает в объектный модуль необходимые префиксы, в других случаях их приходится вводить в программу "вручную".
Рассмотрим теперь для примера простую программу, которая, будучи запущена обычным образом под управлением MS-DOS, переключает процессор в защищенный режим, выводит на экран для контроля символ, переходит назад в реальный режим (чтобы не вывести компьютер из равновесия) и завершается стандартным для DOS образом.
Для того, чтобы наша программа могла бы хоть что-то сделать в защищенном режиме, для нее необходимо создать среду защищенного режима, в первую очередь, таблицу глобальных дескрипторов с описанием всех сегментов, с которыми программа будет работать. Кроме нас никто эту таблицу (при работе в DOS) не создаст. Таким образом, наша программа будет в какой-то мере выполнять функции операционной системы защищенного режима.
Для практического исследования защищенного режима придется выполнить некоторую работу по переконфигурированию компьютера. В наше время компьютеры обычно конфигурируются так, что при их включении сразу загружается система Windows. Работы, для которых требуется DOS, выполняются либо в режиме эмуляции DOS, либо в сеансе DOS, организуемом системой Windows. Для запуска прикладной программы защищенного режима такой способ не годится. Нам понадобится DOS в "чистом виде", без следов Windows. Более того, перед запуском программы необходимо выгрузить все драйверы обслуживания расширенной памяти (HIMEM.SYS и EMM386.EXE) и программы, использующие расширенную память, например, SMARTDRV.EXE. Лучше всего загружать DOS с системной дискеты, подготовив файлы CONFIG.SYS и AUTOEXEC.BAT в минимальном варианте.
Обсуждая в начале этого раздела основы защищенного режима, мы не затронули многие, в том числе принципиальные вопросы, с которыми придется столкнуться при написании работоспособной программы. Необходимые пояснения будут даны в конце этого раздела.


Пример Программирование защищенного режима


.586Р ;Разрешение трансляции всех команд МП 586
;Структура для описания дескрипторов сегментов
dcr struc ;Имя структуры
limit dw 0 ;Граница (биты 0...15)
base_l dw 0 ;База, биты 0...15
base_m db 0 ;База, биты 16...23
attr_l db 0 ;Байт атрибутов 1
attr_2 db ;Граница (биты 16...19) и атрибуты 2
base_h db 0 ;База, биты 24...31
dcr ends ;
data segment use16 ;
;Таблица глобальных дескрипторов GDT
gdt_null dcr <0,0,0,0,0,0> ;Селектор 0-обязательный
;нулевой дескриптор
gdt_data dcr <data_size-l,0,0,92h,0,0> ;Селектор 8,
;сегмент данных
gdt_code dcr <code_size-l,0,0,98h,0,0>;Селектор 16,
;сегмент команд
gdt_stack dcr <511,0,0,92h,0,0> ;Селектор 24 -
;сегмент стека
gdt_screen dcr <4095,B000h,OBh,92h,0,0> ;Селектор 32,
;видеобуфер
pdescr df 0 ;Псевдодескриптор для команды Igdt
data_size=$-gdt_null ;Размер сегмента данных
data ends ;Конец сегмента данных
text segment use16 ;Сегмент команд, 16-разрядный режим
assume CS:text,DS:data;
main proc ;
xor EAX,EAX ;Очистим ЕАХ
mov AX,data ;Загрузим в DS сегментный
mov DS,AX ;адрес сегмента данных
;Вычислим 32-битовый линейный адрес сегмента данных
;и загрузим его в дескриптор сегмента данных в GDT.
;В регистре АХ уже находится сегментный адрес.
;Умножим его на 16 сдвигом влево на 4 бита
shl ЕАХ,4 ;В ЕАХ линейный базовый адрес
mov EBP, ЕАХ ;Сохраним его в ЕВР для будущего
mov BX,offset gdt_data ;В ВХ адрес дескриптора
mov [BX].base_l,AX ;Загрузим младшую часть базы
rol ЕАХ,16 ;Обмен старшей и младшей половин ЕАХ
mov [BX].base_m,AL ;Загрузим среднюю часть базы
;Вычислим 32-битовый линейный адрес -сегмента команд
;и загрузим его в дескриптор сегмента команд в GDT
хог ЕАХ, ЕАХ ;Очистим ЕАХ
mov AX,CS ;Сегментный адрес сегмента команд
shl ЕАХ,4 ;В ЕАХ линейный базовый адрес
mov BX,offset gdt_code ;В ВХ адрес дескриптора
mov [BX] .base_l,AX ;Загрузим младшую часть базы
rol ЕАХ,16 ;Обмен старшей и младшей половин ЕАХ
mov [BX].base_m,AL ;Загрузим среднюю часть базы
;Вычислим 32-битовый линейный адрес сегмента стека
хог ЕАХ, ЕАХ ;Все, как и для других
mov AX,SS ;дескрипторов
shl ЕАХ,4
mov BX,offset gdt_stack
mov [BX].base_l,AX
rol EAX,16
mov [BX].base_m,AL
;Подготовим псевдодескриптор pdescr для загрузки регистра GDTR
mov dword ptr pdescr+2,EBP ;База GDT
mov word ptr pdescr, 39 ;Граница GDT
Igdt pdescr ;Загрузим регистр GDTR
cli ;Запрет прерываний
;Переходим в защищенный режим
mov EAX,CR0 ;Получим содержимое CR0
or EAX,1 ;Установим бит защищенного режима
mov CRO,ЕАХ ;Запишем назад в CR0
;---------------------------------------------------------;
;Теперь процессор работает в защищенном режиме ;
;---------------------------------------------------------;
;Загружаем в CS:IP селектор:смещение точки continue
db OEAh ;Код команды far jmp
dw offset continue ;Смещение
dw 16 ;Селектор сегмента команд
continue:
;Делаем адресуемыми данные
mov AX, 8 ;Селектор сегмента данных
mov DS,AX ;Загрузим в DS
;Делаем адресуемым стек
mov AX,24 ;Селектор сегмента стека
mov SS,AX ;Загрузим в SS
;Инициализируем ES и выводим символ
mov AX,32 ;Селектор сегмента видеобуфера
mov ES,AX ;Загрузим в ES
mov BX,2000 ;Начальное смещение на экране
mov AX,09FOFh ;Символ с атрибутом
mov ES : [BX] , АХ;Вывод в видеобуфер
;Вернемся в реальный режим
mov gdt_data.limit,0FFFFh ;Установим
mov gdt_code.limit,0FFFFh ;значение границы
mov gdt_stack.limit,0FFFFh;для реального
mov gdt_screen.limit,0FFFFh ;режима
mov AX,8 ;Загрузим теневой регистр
mov DS,AX ;сегмента данных
mov AX,24 ;To же для
mov SS,AX ;стека
mov AX,32 ;To же
mov ES, AX ;для регистра ES
;Выполним дальний переход, чтобы заново загрузить
;селектор в CS и модифицировать его теневой регистр
db0Eah ;Код команды jmp far
dwoffset go ;Смещение точки перехода
dw!6 ;Селектор сегмента команд
;Переключим режим процессора
go: mov EAX,CR0 ;Получим содержимое CR0
and EAX,0FFFFFFFEh;Сбросим бит РЕ
mov CR0,EAX ;Запишем назад в CR0
db0Eah ; Код команды far jmp
dwoffset return ;Смещение точки перехода
dwtext ;Сегментный адрес
;---------------------------------------------;
;Теперь процессор снова работает в реальном режиме ;
;---------------------------------------------;
;Восстановим операционную среду реального режима
return: mov AX,data ;Загрузим сегментный
mov DS,AX ;регистр DS
mov AX,stk ;Загрузим сегментный
mov SS,AX ;регистр SS
mov SP,512 ;Восстановим SP
sti ;Разрешим прерывания
mov AX,4C00h ;Завершим программу
;обычным образом
int 2 In main endp
code_size=$-main ;Размер сегмента команд
text ends /Конец сегмента команд
stk segment stack ;Сегмент
db 512 dup (') ;стека stk ends
end main ;Конец программы и точка входа


Для того, чтобы разрешить использование всех, в том числе привилегированных команд 32-разрядных процессоров, в программу включена директива .586Р.
Программа начинается с объявления структуры dcr, с помощью которой будут описываться дескрипторы сегментов. Сравнивая описание структуры dcr в программе с Рисунок 4.9, нетрудно проследить их соответствие друг другу. Для удобства программного обращения в структуре dcr база описывается тремя полями: младшим словом (base_l) и двумя байтами: средним (base_m) и старшим (base_h).
В байте атрибутов 1 задается ряд характеристик сегмента. В примере 4.4 используются сегменты двух типов: сегмент команд, для которого байт attr_l должен иметь значение 98h (присутствующий, только исполнение, DPL=0), и сегмент данных (или стека) с кодом 92h (присутствующий, чтение и запись, DPL=0).
Некоторые дополнительные характеристики сегмента указываются в старшем полубайте байта attr_2. Для всех наших сегментов значение этого полубайта равно 0 (бит G=0, так как граница указывается в байтах, а D=0, так как программа 16-разрядная).
Сегмент данных data начинается с описания важнейшей системной структуры - таблицы глобальных дескрипторов. Как уже отмечалось выше, обращение к сегментам в защищенном режиме возможно исключительно через дескрипторы этих сегментов. Таким образом, в таблице дескрипторов должно быть описано столько дескрипторов, сколько сегментов использует программа. В нашем случае в таблицу включены, помимо обязательного нулевого дескриптора, всегда занимающего первое место в таблице, четыре дескриптора для сегментов данных, команд, стека и дополнительного сегмента данных, который мы наложим на видеобуфер, чтобы обеспечить возможность вывода в него символов. Порядок дескрипторов в таблице (кроме нулевого) не имеет значения.
Поля дескрипторов для наглядности заполнены конкретными данными явным образом, хотя объявление структуры dcr с нулями во всех полях позволяет описать дескрипторы несколько короче, например:

gdt_null dcr <> ;Селектор 0 - обязательный
;нулевой дескриптор

gdt_data dcr <data_size - l, , , 92h> ;Селектор 8 - сегмент данных

В дескрипторе gdt_data, описывающем сегмент данных программы, заполняется поле границы сегмента (фактическое значение размера сегмента data_size будет вычислено транслятором, см. последнее предложение сегмента данных), а также байт атрибутов 1. База сегмента, т.е. линейный адрес его начата, в явной форме в программе отсутствует, поэтому ее придется программно вычислить и занести в дескриптор уже на этапе выполнения.
Дескриптор gdt_codc сегмента команд заполняется схожим образом.
Дескриптор gdt_stack сегмента стека имеет, как и любой сегмент данных, код атрибута 92h, что разрешает его чтение и запись, и явным образом заданную границу - 255 байт, что соответствует размеру стека. Базовый адрес сегмента стека так же придется вычислить на этапе выполнения программы.
Последний дескриптор gdt_scrcen описывает страницу 0 видеобуфера. Размер видеостраницы, как известно, составляет 4096 байт, поэтому в поле границы указано число 4095. Базовый физический адрес страницы известен, он равен BS000h. Младшие 16 бит базы (число 8000И) заполняют слово base_l дескриптора, биты 16...19 (число OBU) - байт base_m. Биты 20...31 базового адреса равны 0, поскольку видеобуфер размещается в первом мегабайте адресного пространства.
Первая половина программы посвящена подготовке перехода в защищенный режим. Прежде всего надо завершить формирование дескрипторов сегментов программы, в которых остались незаполненными базовые адреса сегментов.
Базовые (32-битовые) адреса определяются путем умножения значений сегментных адресов на 16. После обнуления регистра ЕАХ и инициализации сегментного регистра DS, которая позволит нам обращаться к полям данных программы в реальном режиме, содержимое ЕАХ командой sill сдвигается влево на 4 бита, образуя линейный 32-битовый адрес. Поскольку этот адрес будет использоваться и в последующих фрагментах программы, он запоминается в регистре ЕВР (или любом другом свободном регистре общего назначения). В ВХ загружается адрес дескриптора данных, после чего в дескриптор заносится младшая половина линейного адреса из регистра АХ. Поскольку к старшей половине регистра ЕАХ (где нас интересуют биты 17...24) обратиться невозможно, над всем содержимым ЕАХ с помощью команды rol выполняется циклический сдвиг на 16 бит, в результате которого младшая и старшая половины ЕАХ меняются местами.
После сдвига содержимое AL (где теперь находятся биты 17...24 линейного адреса) заносится в поле base_m дескриптора. Аналогично Вычисляются линейные адреса сегмента команд и сегмента стека.
Следующий этап подготовки к переходу в защищенный режим - загрузка в регистр процессора GDTR (Global Descriptor Table Register, регистр таблицы глобальных дескрипторов) информации о таблице глобальных дескрипторов. Эта информация включает в себя линейный базовый адрес таблицы и ее границу и размещается в 6 байтах поля данных, называемого иногда псевдодескриптором. Для загрузки GDTR предусмотрена специальная привилегированная команда Igdt (load global descriptor table, загрузка таблицы глобальных дескрипторов), которая требует указания в качестве операнда имени псевдодескриптора. Формат псевдодескриптора приведен на Рисунок 4.10.









Начало  Назад  Вперед