Intel x86 Assembly Language & Microarchitecture
Реальные против защищенных режимов
Поиск…
Реальный режим
Когда Intel разработала исходный x86, 8086 (и 8088 производных), они включили Сегментацию, чтобы позволить 16-разрядному процессору получить доступ к адресу, превышающему 16 бит. Они сделали это, сделав 16-разрядные адреса относительно заданного 16-битного регистра сегментов, из которых они определили четыре: сегмент кода ( CS ), сегмент данных ( DS ), дополнительный сегмент ( ES ) и сегмент стека ( SS ) ,
Большинство инструкций подразумевают, какой сегментный регистр использовать: инструкции были отклонены от сегмента кода, PUSH и POP подразумевал сегмент стека, а простые ссылки на данные подразумевали сегмент данных - хотя это можно было бы переопределить для доступа к памяти в любом из других сегментов.
Реализация была простой: для каждого доступа к памяти CPU принимает подразумеваемый (или явный) регистр сегментов, сдвигает его на четыре места влево, а затем добавляет указанный адрес:
+-------------------+---------+
Segment | 16-bit value | 0 0 0 0 |
+-------------------+---------+
PLUS
+---------+-------------------+
Address | 0 0 0 0 | 16-bit value |
+---------+-------------------+
EQUALS
+-----------------------------+
Result | 20-bit memory address |
+-----------------------------+
Это позволило использовать различные методы:
- Разрешение кодов, данных и стека для всех быть взаимно доступным (
CS,DSиSSимеют одинаковое значение); - Сохранение кода, данных и стека полностью отделено друг от друга (
CS,DSиSSвсе 4K (или более), отделенные друг от друга - помните, что он умножается на 16, так что это 64K).
Это также позволило причудливые совпадения и всевозможные странные вещи!
Когда 80286 был изобретен, он поддерживал этот устаревший режим (теперь он называется «Реальный режим»), но добавил новый режим «Защищенный режим» (qv).
Важно отметить, что в реальном режиме:
- Любой адрес памяти был доступен, просто поместив правильное значение в регистр сегментов и получив доступ к 16-битовому адресу;
- Степень «защиты» заключалась в том, чтобы позволить программисту разделить разные области памяти для разных целей и затруднить случайную запись неверных данных - при этом все же можно сделать это.
Другими словами ... не очень защищен вообще!
Защищенный режим
Вступление
Когда 80286 был изобретен, он поддерживал наследственную сегментацию 8086 (теперь называемую «Реальный режим») и добавил новый режим «Защищенный режим». Этот режим был в каждом процессоре x86 с тех пор, хотя и расширен с различными улучшениями, такими как 32- и 64-разрядная адресация.
дизайн
В защищенном режиме простой «Добавить адрес в значение регистра сдвинутого сегмента» был полностью удален. Они хранили регистры сегментов, но вместо того, чтобы использовать их для вычисления адреса, они использовали их для индексации в таблицу (фактически, одну из двух ...), которая определяла сегмент, к которому нужно получить доступ. Это определение не только описывало, где в памяти сегмент был (с использованием Base и Limit), но и каким типом сегмента он был (код, данные, стек или даже система) и какие программы могли получить к нему доступ (ядро ОС, обычная программа , Драйвер устройства и т. Д.).
Регистр сегментов
Каждый 16-битный регистр сегментов принял следующий вид:
+------------+-----+------+
| Desc Index | G/L | Priv |
+------------+-----+------+
Desc Index = 13-bit index into a Descriptor Table (described below)
G/L = 1-bit flag for which Descriptor Table to Index: Global or Local
Priv = 2-bit field defining the Privilege level for access
Глобальный / местный
Глобальный / локальный бит определял, был ли доступ включен в глобальную таблицу дескрипторов (называемую неудивительно глобальной таблицей дескриптора или GDT) или локальной таблицей дескриптора (LDT). Идея для LDT заключалась в том, что каждая программа может иметь свою собственную таблицу дескрипторов - ОС woud определяет глобальный набор сегментов, и каждая программа будет иметь свой собственный набор локальных кодов, данных и сегментов стека. ОС будет управлять памятью между различными таблицами дескрипторов.
Таблица дескрипторов
Каждая таблица дескрипторов (глобальная или локальная) представляла собой массив из 64 тыс. Дескрипторов из 8 192 дескрипторов: каждая из 8-байтных записей определяла несколько аспектов сегмента, который он описывал. Поля индекса дескриптора регистров сегментов разрешены для 8 192 дескрипторов: не совпадение!
дескриптор
Дескриптор содержал следующую информацию: обратите внимание, что формат дескриптора изменился по мере выпуска новых процессоров, но в каждом из них хранилась одна и та же информация:
- База
Это определило начальный адрес сегмента памяти. - предел
Это определило размер сегмента памяти - своего рода. Они должны были принять решение: будет ли размер0x0000иметь размер0, поэтому недоступен? Или максимальный размер?
Вместо этого они выбрали третий вариант: поле «Лимит» было последним адресным местом в сегменте. Это означало, что можно было бы определить одноразовый сегмент; или максимальный размер для размера адреса. - Тип
Было несколько типов сегментов: традиционный код, данные и стеки (см. Ниже), но также были определены другие сегменты системы:- Сегменты локальных дескрипторов таблицы определили, сколько локальных дескрипторов можно было получить;
- Сегменты состояния задачи могут использоваться для коммутации аппаратного управления;
- Контролируемые «Call Gate», которые могут позволить программам звонить в Операционную систему, - но только через тщательно управляемые точки входа.
- Атрибуты
Определенные атрибуты Сегмента также поддерживались, если это необходимо:- Только чтение и чтение-запись;
- Был ли данный сегмент представлен или нет, - позволяет управлять памятью по требованию;
- Какой уровень кода (OS vs Driver vs program) может получить доступ к этому сегменту.
Истинная защита наконец!
Если ОС хранила таблицы дескрипторов в сегментах, к которым не могли быть доступны простые программы, тогда она могла бы жестко управлять тем, какие сегменты были определены, и какая память была назначена и доступна для каждого. Программа могла бы изготовить любое значение регистра Сегмента, которое ему понравилось, но если бы у него была смелость фактически загрузить его в Регистр сегментов ! ... аппаратное обеспечение ЦП распознало бы, что предлагаемое значение дескриптора нарушило любое из большого числа правил и вместо того, чтобы завершать запрос, он поднимет Исключение к операционной системе, чтобы позволить ему обрабатывать ошибочную программу.
Это исключение обычно было # 13, исключение общей защиты - сделало мир знаменитым Microsoft Windows ... (Кто-нибудь думает, что инженер Intel был суеверным?)
ошибки
Ошибки, которые могут произойти, включали:
Если предлагаемый индекс дескриптора был больше, чем размер таблицы;
Если предлагаемый дескриптор был системным дескриптором, а не кодом, данными или стеком;
Если предлагаемый дескриптор был более привилегированным, чем запрашивающая программа;
Если предложенный дескриптор был помечен как нечитаемый (например, сегмент кода), но он был попытален читать, а не выполняться;
Если предложенный дескриптор отмечен Not Present.
Обратите внимание, что последнее не может быть фатальной проблемой для программы: ОС может отметить флаг, восстановить Segment, пометить его как сейчас Present, а затем позволить инструкции сбоя продолжить успешно.
Или, возможно, дескриптор был успешно загружен в регистр сегментов, но затем доступ к нему в будущем нарушил один из нескольких правил:
- Регистр сегментов был загружен индексом дескриптора
0x0000для GDT. Это было зарезервировано аппаратным обеспечением какNULL; - Если загруженный дескриптор был помечен как «Только для чтения», но попытка записи была предпринята.
- Если какая-либо часть доступа (1, 2, 4 или более байтов) находилась вне пределов сегмента.
Включение в защищенный режим
Переключение в защищенный режим прост: вам просто нужно установить один бит в регистре управления. Но, находясь в защищенном режиме, процессор не поднимает руки и не перезагружается из-за того, что не знает, что делать дальше, требует большой подготовки.
Короче говоря, требуются следующие шаги:
Область памяти для Глобальной таблицы дескрипторов должна быть настроена для определения как минимум трех дескрипторов:
- Нулевой,
NULLдескриптор; - Другой дескриптор для сегмента кода;
- Другой дескриптор для сегмента данных.
Это может использоваться как для данных, так и для стека.
- Нулевой,
Регистр глобальной дескрипторной таблицы (
GDTR) необходимо инициализировать, чтобы указать на эту определенную область памяти;GDT_Ptr dw SIZE GDT dd OFFSET GDT ... lgdt [GDT_Ptr]Бит
PMвCR0должен быть установлен:mov eax, cr0 ; Get CR0 into register or eax, 0x01 ; Set the Protected Mode bit mov cr0, eax ; We're now in Protected Mode!Регистры сегментов необходимо загрузить из GDT, чтобы удалить текущие значения Real Mode:
jmp 0x0008:NowInPM ; This is a FAR Jump. 0x0008 is the Code Descriptor NowInPM: mov ax, 0x0010 ; This is the Data Descriptor mov ds, ax mov es, ax mov ss, ax mov sp, 0x0000 ; Top of stack!
Обратите внимание , что это абсолютный минимум, только чтобы получить процессор в защищенный режим. Чтобы получить всю готовую систему, может потребоваться еще много шагов. Например:
- Возможно, необходимо включить верхние области памяти - выключение затвора
A20; - Прерывания должны быть обязательно отключены - но, возможно, различные обработчики ошибок могут быть настроены до входа в защищенный режим, чтобы допускать ошибки на ранней стадии обработки.
Оригинальный автор этого раздела написал полное руководство по входу защищенного режима и работе с ним.
Нереальный режим
В нереальном режиме используются два факта о том, как и процессоры Intel, и AMD загружают и сохраняют информацию для описания сегмента.
Процессор кэширует информацию дескриптора, извлеченную во время перемещения в регистре селектора в защищенном режиме.
Эти данные хранятся в архитектурной невидимой части регистра селектора.В реальном режиме регистры селектора называются сегментными регистрами, но, кроме этого, они обозначают один и тот же набор регистров, и поэтому они также имеют невидимую часть. Эти части заполнены фиксированными значениями, но для базы, которая получена из только что загруженного значения.
В таком представлении реальный режим является лишь особым случаем защищенного режима: где информация сегмента, такая как база и предел, извлекается без GDT / LDT, но все еще считывается из скрытой части регистра сегмента.
При переключении в защищенном режиме и при создании GDT можно создать сегмент с желаемыми атрибутами, например базой 0 и лимитом 4GiB.
При последовательной загрузке регистра селектора такие атрибуты кэшируются, тогда можно вернуться в реальном режиме и иметь сегментный регистр, через который можно получить доступ ко всему 32-битовому адресному пространству.
BITS 16
jmp 7c0h:__START__
__START__:
push cs
pop ds
push ds
pop ss
xor sp, sp
lgdt [GDT] ;Set the GDTR register
cli ;We don't have an IDT set, we can't handle interrupts
;Entering protected mode
mov eax, cr0
or ax, 01h ;Set bit PE (bit 0) of CR0
mov cr0, eax ;Apply
;We are now in Protected mode
mov bx, 08h ;Selector to use, RPL = 0, Table = 0 (GDT), Index = 1
mov fs, bx ;Load FS with descriptor 1 info
mov gs, bx ;Load GS with descriptor 1 info
;Exit protected mode
and ax, 0fffeh ;Clear bit PE (bit0) of CR0
mov cr0, eax ;Apply
sti
;Back to real mode
;Do nothing
cli
hlt
GDT:
;First entry, number 0
;Null descriptor
;Used to store a m16&32 object that tells the GDT start and size
dw 0fh ;Size in byte -1 of the GDT (2 descriptors = 16 bytes)
dd GDT + 7c00h ;Linear address of GDT start (24 bits)
dw 00h ;Pad
dd 0000ffffh ;Base[15:00] = 0, Limit[15:00] = 0ffffh
dd 00cf9200h ;Base[31:24] = 0, G = 1, B = 1, Limit[19:16] = 0fh,
;P = 1, DPL = 0, E = 0, W = 1, A = 0, Base[23:16] = 00h
TIMES 510-($-$$) db 00h
dw 0aa55h
Соображения
- Как только перезагружается сегментный регистр, даже с тем же значением, процессор перезагружает скрытые атрибуты в соответствии с текущим режимом. Вот почему приведенный выше код использует
fsиgsдля хранения «расширенных» сегментов: такие регистры с меньшей вероятностью будут использоваться / сохранены / восстановлены различными 16-битными службами. - Команда
lgdtне загружает дальний указатель на GDT, вместо этого загружает 24-разрядный (может быть переопределен до 32-разрядного) линейный адрес . Это не самый близкий адрес , это физический адрес (поскольку пейджинг должен быть отключен). Вот почемуGDT+7c00h. - Программа выше - это загрузчик (для MBR, у него нет BPB), который устанавливает
cs/ds/sstp 7c00h и запускает счетчик местоположения от 0. Таким образом, байт со смещением X в файле находится со смещением X в сегменте 7c00h и на линейном адресе 7c00h + X. - Прерывания должны быть отключены, так как IDT не установлен для короткого раунда в защищенном режиме.
- В коде используется хак для сохранения 6 байтов кода. Структура, загруженная
lgdt, сохраняется в ... самом GDT, в нулевом дескрипторе (первый дескриптор).
Описание дескрипторов GDT см. В главе 3.4.3 руководства Intel Volume 3A .