✍️ Статья Погружение в ассемблер. Учимся работать с памятью

The 146X Project Dublikat Web Studio Avram Lincoln AL Service Navigator Knyaz

BlackPope

Команда форума
Модератор DeepWeb ✔️
PR-Group DeepWeb 🔷
Регистрация
27.04.2020
Сообщения
230
В этой статье я познакомлю тебя с сегментной адресацией и сегментными регистрами, расскажу, как распределяется первый мегабайт оперативной памяти компьютера, затем мы обсудим получение прямого доступа к видеопамяти в текстовом режиме, а самое главное — поностальгируем по фильму «Хакер»! Под конец напишем психоделическую программу, которой позавидовали бы его герои.

Другие статьи курса
  • Погружение в assembler
  • Делаем первые шаги в освоении асма
  • Осваиваем арифметические инструкции
  • Как работают переменные, режимы адресации, инструкции условного перехода

Знакомимся с сегментными регистрами

Для начала разберемся, что такое сегментные регистры. Процессор 8088 умеет адресовать один мегабайт оперативной памяти, несмотря на то что регистры у него 16-битные. В норме 16 битами можно адресовать только 64 Кбайт. И как же тогда выкручивается 8088? Как ему удается адресовать целый мегабайт? Для этого в 8088 есть сегментные регистры! Четыре сегментных регистра: CS, ES, DS и SS, по 16 бит каждый. У каждого из этих регистров есть свое назначение.

Регистр CS указывает на сегмент кода. Процессор обращается к CS всякий раз, когда надо считать из памяти очередную инструкцию для выполнения.

Регистры DS и ES указывают на сегмент данных. К этим регистрам процессор обращается, когда выполняемая инструкция считывает или сохраняет данные. Имей в виду: DS используется чаще, чем ES. ES обычно вступает в игру, когда ты обрабатываешь массивы данных, индексируя их регистром DI.

Регистр SS указывает на сегмент стека. К этому регистру процессор обращается, когда выполняет инструкции, взаимодействующие со стеком: push, pop, call и ret.

Значения, хранимые в сегментных регистрах, — это базовый адрес, поделенный на 16. Если в CS записано значение 0x0000, процессор будет считывать инструкции из области памяти 0x00000 — 0x0FFFF. Если в регистре CS записано значение 0x1000, то процессор будет считывать инструкции из области памяти 0x10000 — 0x1FFFF.

Если тебе надо адресоваться в какой-то другой сегмент, а не тот, который будет задействован по умолчанию, напиши этот сегмент в префиксе к инструкции, которую используешь.


Поместить какое-то число в сегментные регистры напрямую нельзя. Для этого надо:
  • либо сначала записать число в какой-нибудь регистр и уже этот регистр присвоить сегментному регистру;
  • либо воспользоваться парой инструкций push/pop;
  • либо воспользоваться инструкциями lds/les.
Процессор 8088 настолько лоялен, что позволит тебе даже вот такую инструкцию: pop cs. Но только имей в виду, что это нарушит поток выполнения инструкций. В более новых процессорах, начиная с 80286, такую диверсию сделать уже не получится. И слава богу!

Ну вот вроде бы и все, что тебе надо знать о сегментных регистрах. Теперь ты с их помощью (в основном с помощью регистров DS и ES) можешь получать доступ ко всему первому мегабайту памяти ПК.

Как ПК распределяет память

Понимание того, как распределяется память в ПК, поможет тебе делать разные интересные вещи.

Начало загрузки у всех компьютеров одинаковое: они сначала переходят в текстовый цветной режим 80x25. К видеопамяти экрана, который работает в таком режиме, можно обращаться напрямую, через вот этот диапазон адресов: 0xB8000 — 0xB8FFF.

Первый байт диапазона — это первый символ в верхнем левом углу экрана. Второй байт — это цвет фона под символом и цвет самого символа. Затем (третьим байтом) идет второй символ. И так для всех 25 строк по 80 символов каждая.

INFO

Исторический факт. IBM PC образца 1981 года поставлялся в двух модификациях: с монохромным режимом и с цветным режимом. Тогдашним разработчикам игрушек приходилось придумывать разные эвристики, чтобы понять, какая у компьютера графика: монохромная или цветная. Несколько старых добрых игрушек для этого писали какую-нибудь цифру по адресу 0xB8000 и считывали ее обратно — чтобы узнать, в каком режиме сейчас идет работа: в цветном или в монохромном.

Прямой доступ к видеопамяти в текстовом режиме

Перед тем как мы сможем напрямую обращаться к видеопамяти экрана, то есть без использования сервисов BIOS, надо задать нужный нам видеорежим.

В этом режиме видеопамять экрана доступна по адресу 0xB8000. Теперь ты можешь легко получить доступ к ней. Примерно так, как в коде ниже.

Обрати внимание, что после того, как ты выполнил этот кусок кода, ты не сможешь обращаться к переменным, которые сохраняешь в сегменте кода, как мы это делали в примерах из прошлых уроков. Потому что DS и ES теперь нацелены не на сегмент кода, а на видеопамять.

Как же быть? Как вариант, можно перед тем, как нацеливать DS и ES на видеопамять, сохранить их значение на стеке. Потом нацелиться на видеопамять и вывести нужные данные на экран. А после этого снять со стека сохраненный указатель и снова поместить его в DS и ES.

Видеопамять, которая отображается на экран, организована вот таким вот образом.

Здесь на каждый символ отведено по два байта. Первый байт — это ASCII-код буквы. А второй — цвет фона и символа.

Кодировка цветов следующая.

А теперь давай посмотрим все это на живом примере. Напиши и выполни вот такой код.

Мы здесь задействовали парочку новых инструкций: cld и stosw. Инструкция cld очищает флаг направления. Зачем нужен этот флаг? Некоторые продвинутые инструкции (многошаговые) используют его, чтобы понять, что нужно делать с регистрами SI и DI после очередного шага выполнения: увеличивать их или уменьшать.

Инструкция stosw записывает значение регистра AX по адресу ES:DI и увеличивает регистр DI на два (размер слова — сдвоенного байта). Обрати внимание: если флаг направления установлен в единицу (это можно сделать инструкцией sed), то та же самая инструкция будет не увеличивать DI, а уменьшать. Тоже на два. Поэтому мы сначала используем cld, поскольку не знаем, какое на данный момент значение у флага направления.

Всякий раз, когда мы загружаем AX, у нас AL содержит саму букву, которую надо нарисовать на экране, а AH — цвет символа и цвет его фона (в данном случае синий фон и разные цвета для каждой буквы).

Когда мы пишем слово (сдвоенный байт) в память, младший байт всегда пишется первым (в данном случае значение регистра AL), а старший байт идет следом (в данном случае значение регистра AH).

Таким образом, после того как весь наш текст записан в видеопамять, память будет заполнена вот так.

Теперь ты умеешь рисовать на экране цветные буквы. При желании можешь поэкспериментировать с символами псевдографики, которые доступны в BIOS.

Кстати, на всякий случай имей в виду, что в 8088 еще есть инструкция stosb. Она работает так же, как stosw, но только не со словами (сдвоенными байтами), а с байтами. stosb пишет значение регистра AL по адресу ES:DI и увеличивает DI на единицу (или уменьшает, если флаг направления установлен в единицу).

Рисуем психоделические круги

А теперь переходим к самому интересному! Сейчас напишем программу, которая рисует психоделические круги, как в фильме «Хакер». Помнишь там эпизод, где наши с тобой коллеги восхищаются «миллионом психоделических оттенков» на ноуте Кислотного Ожога?

Мы нарисуем то же самое, но в классическом текстовом режиме, в разрешении 80x25 с 16 цветами для фона и текста. Итак, приступим.

Сначала инициализируем видеорежим и настраиваем сегменты на видеопамять экрана.

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

Что тут происходит? Сервис 0x00 прерывания 0x1A считывает, сколько тиков прошло с того момента, как была загружена система. Результат возвращается в 32-битном виде, помещается в пару регистров: CX:DX.

Поскольку для наших нужд надо вести счет только от 0 до 63 и затем обратно, мы проверяем шестой бит (0x40), и если он установлен в единицу, то добавляем к текущему значению счетчика знак минус.

Затем инструкцией and al, 0x3F выделяем шесть младших битов и при помощи вычитания приводим знаковое 8-битное значение к диапазону -32..+31, которое затем расширяем до слова (до сдвоенного байта). Результат помещаем в CX.

А теперь самое интересное. Вычисляем параметры текущего шага анимации и отрисовываем ее.

Что мы тут делаем? В DI у нас будет храниться текущая позиция на экране, куда выводить символ. Сначала сбрасываем ее в 0, чтобы рисовать начиная с левого верхнего угла. В DH и DL будем хранить номер строки и столбца. А в BX у нас будет адрес таблицы синусов (ее покажу чуть позже).

Теперь, когда мы проинициализировали все нужные переменные, нам надо вычислить с помощью таблицы синусов, каким цветом отрисовывать символ (у нас это будет звездочка) в очередной позиции экрана. Чтобы в итоге получились круги.

Для этого делаем вот что. Берем номер текущей строки (из DH). Умножаем его на два, чтобы круги были кругами, а не овалами. По этому значению извлекаем синус из таблицы символов (смотри инструкцию xlat).

Что это за инструкция такая, xlat? Ее мнемоника — это сокращение от слова translate. Она считывает байт с адреса BX+AL и помещает его в регистр AL. То есть делает то же самое, что инструкция mov, al, [bx+al], но только более лаконично. Перед тем как обращаться к xlat, надо сначала загрузить в BX адрес таблицы, а в AL — индекс нужного нам элемента из этой таблицы.

Обрати внимание на префикс CS, который стоит перед xlat. Он говорит процессору, что сейчас при считывании данных надо обращаться к кодовому сегменту. Без префикса CS данные будут считаны из сегмента, на который указывает регистр DS. И получится белиберда, потому что DS сейчас указывает на экран, а не на код, где размещена таблица синусов. После того как мы извлекли значение синуса, сохраняем его на стеке (смотри инструкцию push ax).

На этом полдела сделано. Мы взяли номер текущей строки (DH) и вычислили по нему синус. Дальше делаем то же самое для номера текущего столбца (DL). В итоге у нас получается два значения синуса. Одно сейчас хранится в регистре AX, а другое лежит на стеке.

Поэтому мы снимаем со стека предыдущее значение синуса (смотри инструкцию pop DX) и складываем с текущим значением синуса, которое сейчас хранится в AX. И еще плюсуем к ним текущее время (в тиках, от момента включения компьютера). Младший байт полученного результата — это и есть искомое значение цвета (цвет фона + цвет текста).

Надеюсь, ты не забыл, что всю эту чехарду с синусами мы затеяли для того, чтобы вычислить, каким цветом отрисовывать звездочку в очередной позиции экрана и получить в итоге круги?

Итак, нужный цвет мы вычислили и теперь выводим звездочку на экран: mov [di], ax. Затем добавляем к DI двойку, чтобы перейти на следующую позицию экрана — где будем рисовать новую звездочку, но уже другим цветом. Ты ведь помнишь, что у нас на каждый символ отводится по два байта?

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

Что мы тут делаем? Сначала берем номера текущих строки и столбца (их мы сохранили на стеке). Помещаем их в DX. Затем прибавляем единицу к DL. Если результат получился меньше 80 (столько столбцов у нас на экране), то повторяем цикл. А если досчитали до 80, то переходим на следующую строку: увеличиваем DH на единицу, а DL обнуляем. Но делаем этот последний шаг, только если не досчитали до 25. А если в DH у нас уже 25, то переходим в самое начало программы (смотри метку @@main_loop) и начинаем все заново.

В итоге получается анимация с психоделическими кругами, которые то растут, то уменьшаются.

Выводы

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

📌 Золотая реклама

AnonPaste

Верх