Статья ASM для х86. (2,0,) Реальный и Защищённый режимы работы процессора

Часть 2. Реальный и Защищённый режимы работы процессора
В глазах процессора, мир существует в виде 1-байтных ячеек памяти. Будь-то физическое устройство (типа клавиатура), или наша логическая программа – для процессора это группа из нескольких байт. Считаю, что изучение ассемблера нужно начинать именно с организации памяти, поскольку 90% времени работать придётся именно с нею. Совсем не обязательно зубрить наизусть названия регистров – это придёт как озорение само-собой. Лучше направить энергию на изучение почвы, по которой собираемся ходить.

На данный момент х86 процессоры могут работать в четырёх режимах. Мы рассмотрим только первые два из них, как более распространённые:
  1. Real-Mode - реальный, системная поддержка в виде BIOS;
  2. Protected-Mode - защищённый, поддержка Win и никсы;
  3. Virtual-Mode - для эмуляции реального режима, из защищённого;
  4. System-Management-Mode – реальный из защищённого, без эмуляции.

2.0. Память в реальном режиме

В Real-моде, программист полноправный хозяин системы и имеет дело только с физической памятью. Ему доступны буквально все инструкции процессора, прямой доступ к портам оборудования, и многое другое. Здесь не задают вопросов типа “Вы уверены? Да/Нет”, и за необдуманные действия ответственность несёте только вы. Бонусом идёт программирование вообще без системы, на одних только функциях биос (благо его и сейчас эмулируют все EFI). Основной задачей ОС является как-раз абстрагирование пользователя от реальных устройств, которые он может по неосторожности вывести из строя. Одним словом, зевать от скуки тут не приходится, и нужно быть всегда на чеку.

У первых процессоров 8086 шина-адреса(A) была 20-битная. Такая ширина позволяет адресовать всего FFFFF байт памяти. Запускаем виндовый калькулятор, переводим его и BIN, и введём 20 единиц. Именно такой вид имела шина(А) на физическом уровне.

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


bus.png


Как видим, увеличение разрядности шины увеличивает кол-во адресуемых ячеек памяти. Это хорошо.. Но регистры ЦП были в то время 16-битными (без приставки ‘E’), и ими можно было дотянуться максимум до 64 Кб физической памяти (см.рис). Как вместить в 16-битный регистр 20-битный адрес? Было решено разбить всю память на логические сегменты, и адресовать её через регистровую пару в формате сегмент+смещение. Формула и вид получился такой:
Код:
Линейный адрес = (Seg * 16) + Offset
---------------------------------------
     1111 1111 1111 1111  ---------> CS
     |    1111 1111 1111 1111  ----> IP
     |                      |
     |<------ 20-бит ------>|
     1111 1111 1111 1111 1111  ----> адрес = DS:SI, ES:DI
                                             CS:IP, SS:SP
Здесь видно, что три средние тетрады перекрываются. Для сегментных регистров типа СS остаются только смещённые влево 4-бита, которыми можно выбрать один из 16-ти сегментов. Под смещение выделяется как и прежде 16-бит, а это 64 Кб внутри выбранного сегмента памяти. Таким образом, размер сегмента в реальном режиме равен 64К, а всего сегментов =16. В сумме они дают: 64К х16=1М памяти.

Однако это не табу, и ничто не мешает нам перевернуть ‘пластинку’ наоборот, ведь мы имеем дело с логическим адресом. Тогда получим 65.536 сегментов, по 16-байт в каждом. Эти 16-байт назвали ‘параграф’ и это мин.размер одного сегмента в R-моде. Такая картина позволяет операционной системе (например DOS) эффективно распределять память, которой и так с гулькин-нос. Сегменты по 64К бывают только на бумажке, а на практике - это просто макс предел, который выделяет ОС под один сегмент.

Во-первых, среди одного мегабайта, 64К непрерывной памяти у системы может и не быть, а во-вторых - если у программы в сегменте-данных всего три строчки текста, зачем выделять для них 64К памяти? Система просто выравнит эти данные до 16-байтного параграфа, и вручит их пользователю. Если (в процессе работы) программе понадобится ещё доп.памяти, то программист должен запросить её у системы, и она без проблем выделит из своего резерва кусок (если таковой остался).

На рисунке выше, я выделил красным одну из линий шины-адреса и вставил туда ключ. Эту линию назвали А20, поскольку она идёт следующей от 20-битного адреса 0-19. Программист обязательно должен включить её при переводе процессора из реального в защищённый режим работы, иначе не видать нам четырёх гигов памяти. Если линия А20 отключена (а она отключена по-умолчанию), то после 20-битного адреса 0xfffff счётчик адресов опять сбросится в нуль, не давая нам вылезти из первого мегабайта. На аппаратном уровне линией А20 управляет чипсет, через порт 92h системной логики.

Асматикам старой школы издавна не давала покоя мысль, поиметь 4 гига в реальном режиме. Это открывало большие перспективы, поскольку в R-моде нет многозадачности, и все ресурсы процессора полностью принадлежат только одному приложению. На современных процессорах, оно будет летать как истребитель. И нужно сказать, что энтузиастам это удалось! 4Gb памяти в реальном режиме это не сказка, а недокументированный режим, который назвали UnReal-Mode (можете погуглить).

Если коротко, то выделяется один сегментный регистр (как правило ES), который будет играть роль торпеды в 4Gb пространство. Для него подготавливается соответствующий дескриптор с базой(0) и лимитом в 4Gb. Запихать этот дескриптор в ES находясь в реальном режиме процессор не позволит, поэтому нужно перевести его в защищённый режим, обновить ES и вернуться обратно в R-моду. Теперь через ES можно гулять по 4G пространству, как у себя дома. Позже, когда приём вылез из хакерских нор и получил огласку, его взяли на вооружение чуть-ли не все досовские игры. В практической части мы ещё вспомним про UnReal, а пока топаем дальше..



2.1. Память защищённого режима

Процессор переводится в Рrotected-моду всего одним\нулевым битом в регистре CR0. Сразу-же о сегментной памяти реального режима можно забыть. Хотя сегменты по-прежнему и присутствуют, они несут в себе уже другую нагрузку. Адрес теперь не состоит из двух составляющих Seg:Offs, а вся память приобретает плоский FLAT вид. В игру вступают защитные механизмы, которыми природа щедро одарила этот режим.

Процессоры изначально заточены под сегментную организацию памяти, это наследие докатилось и до наших дней. Более того, они не имеют механизмов запрещения сегментации, тогда-как страничную трансляцию можно включать\выключать битом(31) регистра управления процессором CR0. У процессора всегда было и будет шесть сегментных регистров, которые он использует в качестве указателей на определённый блок памяти в Win32 программах:

  • CS – Code Segment – сегмент кода;
  • DS – Data Segment – сегмент данных;
  • SS – Stack Segment – сегмент стека;
  • ES – Extended Segment – дополнительный для данных;
  • GS – General Segment – добавлен для РМ-моды
  • FS – Безымянный (сл.по алфавиту) для РМ-моды.
Сегментные регистры нельзя недооценивать. Они имеют 2-байтную видимую для прикладного программиста, и 8-байтную скрытую часть. Итого получаем 10-байтный регистр. Видимую часть назвали ‘Селектор’, а скрытую – ‘Дескриптор’. Во-втором томе доках Интела есть описание дескриптора, фрагмент которого представлен ниже.

desc.png


По большому счёту, возиться с дескрипторами приходится только тем, кто пишет свою ОС. В остальных случаях они уже настроены системой и трогать их не имеет смысла. Поэтому обратим внимание только на основные моменты.

  1. Каждый сегмент памяти описывает его собственный дескриптор. Размер дескриптора 8-байт (64-бита). В нём хранится линейный адрес начала сегмента в памяти (Base=32-бит), размер сегмента (Limit=20-бит), и атрибуты защиты (Access=12-бит).
  2. При обращении к сегменту, системные механизмы защиты сначала проверяют значение смещения, которое не должно превышать лимит. То есть адрес не должен выходить за пределы сегмента, в противном случае ЦП сгенерит исключение ‘AccessViolation’ - ошибка доступа.
  3. Как выделенными для лимита 20-ю битами определить размер сегмента в 4Gb? Ответ - лимит указывается в единицах измерения! Среди атрибутов, в дескрипторе имеется т.н. бит гранулярности(G). Если он сброшен, то единицей измерения считается байт, если взведён - то 4К-байтная страница. Отсюда следует, что при G=0 размер\лимит сегмента ограничен значением 1Мб, а при G=1 включается множитель 4Кб, и получаем 4Gb.

Это так.. к сведению. Лучше посмотрим на схему ниже..
В аппаратной части подсистемы памяти фигурируют 3 типа адресов: логический, линейный и физический. Из этой троицы, ЦП оперирует только логическим адресом, который использует в своих программах программист. Для трансляции остальных адресов, процессор имеет блок управления памятью MMU - Memory Management Unit.

Логический адрес состоит из сегмента+смещения, например CS:EIP, DS:ESI, ES:EDI и т.д.. MMU собирает эти составляющие, преобразуя лог.адрес в линейный, в диапазоне 0..N. Если в регистре CR0 включён страничный режим, то в дело вступает транслятор страниц в MMU. Если-же страничное преобразование выключено, то линейный адрес совпадает с физическим (здесь он выключен). На рисунке ниже, в сегментных регистрах лежит 2-байтный селектор сегмента, адрес процессора – это смещение, сегментацией и трансляцией занимается блок MMU процессора, РА – это PhysicalAddress:


1011.png


Таким образом, память в защищённом режиме работы процессора – вся линейна. Сегментные регистры служат только для защиты доступа к памяти. В дескрипторах всех сегментных регистров, всегда устанавливается база(0) с максимальным лимитом в 4Gb, и никак иначе. Транслятор страниц постоянно включён, и разбивает большие сегменты на мелкие страницы памяти, размером по 4К-байт каждая. Любая из страниц имеет свои атрибуты защиты, не считая защиты верхнего уровня родительских сегментов. Это огромная корпорация, которая следит за всеми, и жёстко пресекает любое нарушение установленных прав. Одним словом – защищённый режим это руль! ..который позже согнём в баранку.
----------------------------------------

PS\\: Кстати в отладчике OllyDbg можно наглядно увидеть значения дескрипторов в шести сегментных регистрах. Как-видим, особняком стоит тут только FS, который система всегда держит при себе, и программисту трогать его не советует. Это указатель на системную таблицу РЕВ - Process Environment Block, куда система сбрасывает информацию о текущем процессе – т.н. окружение процесса.


olly.png
 

CKAP

Green Team
10.06.2019
68
39
BIT
0
Сам есчё не читал, но очень советуют. Тег в поисковик "Что каждый программист должен знать о памяти"


-----------------------------------------------
Зы. ТС не останавливайся.. Ты лучший !!!
 
Последнее редактирование:
  • Нравится
Реакции: Marylin
Мы в соцсетях:

Обучение наступательной кибербезопасности в игровой форме. Начать игру!