Память в компьютере можно представить в виде очень длинной, почти бесконечной, прямой. Эта прямая поделена на участки, каждый из которых имеет свое предназначение. В 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.
И на закуску предлагаю вам самим написать программу которая выводит окно как на скриншоте внизу.
#ассемблер #программирование