Поиск…


Реальный режим

Когда 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 или более байтов) находилась вне пределов сегмента.

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

Переключение в защищенный режим прост: вам просто нужно установить один бит в регистре управления. Но, находясь в защищенном режиме, процессор не поднимает руки и не перезагружается из-за того, что не знает, что делать дальше, требует большой подготовки.

Короче говоря, требуются следующие шаги:

  • Область памяти для Глобальной таблицы дескрипторов должна быть настроена для определения как минимум трех дескрипторов:

    1. Нулевой, NULL дескриптор;
    2. Другой дескриптор для сегмента кода;
    3. Другой дескриптор для сегмента данных.

      Это может использоваться как для данных, так и для стека.

  • Регистр глобальной дескрипторной таблицы ( 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 загружают и сохраняют информацию для описания сегмента.

  1. Процессор кэширует информацию дескриптора, извлеченную во время перемещения в регистре селектора в защищенном режиме.
    Эти данные хранятся в архитектурной невидимой части регистра селектора.

  2. В реальном режиме регистры селектора называются сегментными регистрами, но, кроме этого, они обозначают один и тот же набор регистров, и поэтому они также имеют невидимую часть. Эти части заполнены фиксированными значениями, но для базы, которая получена из только что загруженного значения.

В таком представлении реальный режим является лишь особым случаем защищенного режима: где информация сегмента, такая как база и предел, извлекается без 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 / ss tp 7c00h и запускает счетчик местоположения от 0. Таким образом, байт со смещением X в файле находится со смещением X в сегменте 7c00h и на линейном адресе 7c00h + X.
  • Прерывания должны быть отключены, так как IDT не установлен для короткого раунда в защищенном режиме.
  • В коде используется хак для сохранения 6 байтов кода. Структура, загруженная lgdt , сохраняется в ... самом GDT, в нулевом дескрипторе (первый дескриптор).

Описание дескрипторов GDT см. В главе 3.4.3 руководства Intel Volume 3A .



Modified text is an extract of the original Stack Overflow Documentation
Лицензировано согласно CC BY-SA 3.0
Не связан с Stack Overflow