Найти тему
Junior Coder

Ассемблер. Прямой доступ к текстовой видеопамяти.

Память в компьютере можно представить в виде очень длинной, почти бесконечной, прямой. Эта прямая поделена на участки, каждый из которых имеет свое предназначение. В Windows графический режим начинается с адреса 0А0000h, текстовый монохромный режим с адреса 0B0000h, текстовый цветной с 0B8000h. В DOS, с которым мы будем работать, графический режим начинается с 0A000h, текстовый монохромный с 0B000h, а текстовый цветной с адреса 0B800h. В программировании есть такое понятие - линейный адрес, оно, как раз, и представляет память в виде прямой.

Для образца возьмем цветной текстовый режим номер три, в нем работает стандартная консоль Windows (cmd.exe), разрешение этого режима 80х25, то есть 25 строк по 80 символов в строке. 80х25=2000 символов на экране. На каждый символ используется два байта текстовой памяти, в первом байте записывается код символа по ASCII таблице, во втором атрибуты этого символа - цвет самого символа и цвет фона. То есть, как то так:

символ, атрибут, символ, атрибут и так далее.

если экран заполнен маленькими латинскими буквами "a", ярко-белыми на черном фоне, то память страницы экрана будет выглядеть так:

61h, 0Fh, 61h, 0Fh и т.д.

в бинарном виде:

01100001,00001111,01100001,00001111 и т.д.

размер одной страницы можно вычислить так 80*2*25=4000 байт, при этом надо учитывать, что и строка и столбец начинаются с нулевого адреса, то есть если мы хотим что-то записать на 25-ю строку, то нам нужно обращаться к 24 строке.

С байтом символа все понятно, надеюсь, разберемся с байтом атрибута. Старший бит (седьмой - биты считаются справа налево), самый левый, отвечает за мерцание, если он включен "1", то символ мерцает. Следующие три бита - 6, 5, 4 отвечают за цвета фона: красный, зеленый, синий соответсвенно.

10000000 - символ мерцает

01000000 - фон красный

00100000 - фон зеленый

01100000 - фон желтый

Третий бит отвечает за яркость символа, если он включен - "1", то символ яркий, если выключен - "0", то символ тусклый. Биты 2, 1, 0 отвечают за цвета: красный, зеленый, синий.

01111000 - символ ярко-белый на черном фоне(стандарт).

00011100 - ярко-красный на синем.

Теперь, когда все стало понятно, напишем программу. Сначала я выложу код целиком, а затем разложим его по полочкам. Чтобы не было осечек воспользуйтесь компилятором FASM. 

Да, чуть про страницы не забыл написать, их восемь. Им соответствуют определенные адреса: 0 - 0B800h, 1 - 0B900h, 2 - 0BA00h и т. д. Для примера будем писать на страницу номер два.

код:

use16       ;пишется слитно

org 100h

jmp main

blue db 00011110b

poka db "P",01001001b,"o",10101110b,"k",00001111b,"a",10011000b

priv db "Privet!!!$"

main:

mov ax,0003

int 10h

mov ax,0502h

int 10h

push 0ba00h

pop es

xor di,di

mov cx,2000

mov ah,[blue]

mov al,20h

rep stosw

mov ah,9

mov dx,priv

int 21h

mov cx,8

mov di, 80*2*24+76*2

lea si,[poka]

rep movsb

mov ah,0

int 16h

int 20h

Строка use16 сообщает компилятору, что код написан в 16-ти битном формате.

Далее идет org 100h, дело в том, что мы пишем программу с расширением .com, она размещается в одном сегменте и для собственных нужд DOS нам необходимо оставить пространство под так называемое PSP, то есть код размещается в сегменте начиная с сотого адреса.

jmp main - "скачок" на метку main.

Далее идут данные необходимые нам в ходе выполнения программы, думаю, что вам теперь не составит труда разобраться с ними. 

blue db 00011110b

blue - имя переменной, db - размер значения которое надо достать из памяти, байт в нашем случае. Далее идут атрибуты.

С метки main начинается код исполняемой программы.

mov ax,0003 - здесь мы вызываем тот самый графический режим номер три, более наглядно можно записать так:

mov ah,0 - код функции 0

mov al,3 - номер режима 3

и вызвать прерывание биос - int 10h.

mov ax,0502h - здесь мы выбираем страницу с которой будем работать.

mov ah, 05 - код функции 5

mov al, 02 - номер страницы 2

int 10h - прерывание биос

В сегментные регистры нельзя загрузить значения напрямую типа:

mov es,0ba00h

поэтому мы выталкиваем значение в стек:

push 0ba00h

затем возвращаем его в нужный регистр:

pop es

можно воспользовать и другим вариантом - загрузить значение через один из пользовательских регистров:

mov ax,0ba00h

mov es,ax

теперь в es у нас хранится адрес второй страницы экрана.

Обнуляем регистр-указатель DI, он "призван сотрудничать" с сегментными регистрами типа ES, FS ..

xor di,di

теперь di указывает на нулевой символ второй страницы.

xor - логическое вычитание. Об этом нужно отдельно разговаривать.

Загружаем в регистр-счетчик CX количество символов на экране - 2000. По мере выполнения цикла это значение будет уменьшаться пока не достигнет нуля, после чего цикл прекратится.

Компьютер переворачивает цифровые значения когда записывает их в память, если вы изучая структуру своего жесткого диска например MBR или MFT наткнетесь на длинну раздела в секторах которая будет равна скажем 0D E5, то реальное значение надо читать как E5 0D. Поэтому, записывая в регистр-аккумулятор AX код символа и его фон, мы пишем в старшую часть регистра атрибуты:

mov ah,[blue]

а в младшую код символа - 20h это пробел по таблице ASCII.

mov al,20h

хотя можно записать просто:

mov al,' '

компьютер поменяет местами эти значения и все будет нормально.

Оператор REP вызывает цикл повторов пока в регистре CX не обнулится значение, а оператор STOSW записывает два байта из регистра AX по адресу ES:DI, увеличивая значение в DI на 2 с каждым шагом цикла. Есть еще формат этой команды - STOSB, при использовании этого формата берется один байт из AL, а значение DI увеличивается на 1.

С помощью стандартной функции DOS выведем на экран строку "Privet!!!", символ "$" указывает компилятору, что это конец строки.

mov ah,9 - код функции "вывод строки"

mov dx,priv - имя переменной

int 21h - прерывание DOS

Строка будет выведена в то место на экране, куда указывает курсор. Кстати, у каждого экрана(страницы) свой курсор. Курсор мы не трогали, поэтому он сохранился на нулевом символе, то есть в верхнем левом углу, туда-то строка и будет выведена.

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

mov cx,8 - в CX грузим значение равное числу символов и атрибутов.

mov di, 80*2*24+76*2 - указатель DI "нацелим" на самую нижнюю строку 80*2*24 и переместим к концу этой строки +76*2.

lea - "загрузчик эффективного адреса", на самом деле он просто указывает регистру SI на первый байт переменной Poka.

lea si,[poka]

Формат команды MOVSB очень похож на формат команды STOSB, только значения в данном случае берутся из регистра SI, а не AX и так же записываются по адресу ES:DI.

MOVSB - пересылка одного байта

MOVSW - двух байт (WORD - машинное слово).

С помощью функции биос вызываем задержку до нажатия любой клавиши:

mov ah,0 - код функции

int 16h - прерывание биос

int 20h - это прерывание заканчивает работу программы.

Для запуска программы под Виндоус воспользуйтесь эмулятором MSDOS DOSBox.

И на закуску предлагаю вам самим написать программу которая выводит окно как на скриншоте внизу.

-2

#ассемблер #программирование