Пишем напрямую в видеопамять

; полностью завершенная COM-программа
codeseg segment
assume cs:codeseg, ds:codeseg, es:codeseg
org 100h
Code: jmp Start
x dw 50 ;координата x выводимого текста
y dw 20 ;координата y выводимого текста
Text db ‘string to be printed’,0 ;не забываем 0
Start:
mov AX,80 ;вычисляем адрес
mul y
add AX,x
shl AX,1 ;адрес в AX=160*y+2*x
mov DI,AX
mov SI,offset Text
push ES ;сохраняем ES
mov AX,0B800h ;сегмент экранной памяти в текстовом режиме
mov ES,AX
Print:
lodsb ;загружаем AL из DS:[SI] or AL,AL ;конец строки?
jz Ready ;да, AL=0
mov ES:[DI],AL ;символ для отображения
add DI,2 ;пропускаем байт атрибутов
jmp Print
Ready: pop ES ;восстанавливаем ES
;—————————————
int 20H
codeseg ends
end Code

Реклама

Рисование в SVGA

Пикселы расположены линейно в памяти видеоадаптера. В 256-цветных режимах пиксел представляется одним байтом. Поэтому смещение точки с координатами (x,y) можно вычислить как 640*y+x в режиме с 640 пикселами по горизонтали. Единственное ограничение, связанное с такими вычисленими, — это то, что последний доступный пиксел, к которому может быть получен доступ, имеет координаты x=255, y=102, его смещение 65535. Это известное ограничение 64Kбайтных сегментов.

Чтобы обойти это ограничение, применяется переключение банков памяти. При этом переопределяется расположение физического адреса, которое соответствует логическому адресу. Так, логический адрес 0 соответствует физическому адресу 65536 если активен первый банк в видеодаптером с размером «окна» (granularity) 64 KB.

Логический адрес точки с координатами (x,y) определяется как 640*y+x-B*WG где B — номер банка и WG — размер «окна». Банк памяти может быть переключен с помощью функции AX=4F05h прерывания 10h в видеоадаптерах, поддерживающих стандарт VESA.

Следующая процедура рисует пиксел на экране. Координаты пиксела находятся в регистрах AX и BX, а в регистре CX передается цвет пиксела. В процедуре предполагается, что размер «окна» равен 64 KB, что справедливо, например, для чипов S3.

SVGA_bank dw 0 ;номер активного банка памяти
S_rivi dw 640 ;длина строки в байтах
VGA_seg dw 0A000h ;сегмент памяти экрана VGA
CBpxl$ proc ;рисует пиксел с координатами x=AX, y=BX, цвет=CX
xchg AX,BX ;теперь x=BX, y=AX
mul S_rivi
add AX,BX
adc DX,0 ;в DX помещается требуемый номер банка
mov DI,AX ;логический адрес
cmp DX,SVGA_bank ;банк корректен?
je Cxl256_OK
mov SVGA_bank,DX ;новый банк
mov AX,4F05h
xor BX,BX ;функция: устанавливаем банк DX, окно A
int 10h
Cxl256_OK:
mov BX,ES ;сохраняем сегмент
mov AL,CL ;цвет
mov ES,VGA_seg
stosb ;рисуем пиксел
mov ES,BX
ret
CBpxl$ endp

TSR: Завершаемся и остаемся в памяти

Инсталляция TSR-программы выполняется в три этапа:
Загрузка резидентной части в память. Проверка, не находится ли наша программа уже в памяти. Сохранение необходимой информации для дальнейшего удаления резидента из памяти. Освобождение памяти, занятой копией переменных окружения для экономии.
Установка параметров для работы резидентной части. Обычно на этом этапе перехватываются прерывания.
Завершение установочной программы, при этом резидентная часть остается в памяти.
;Структура программы TSR
Begin: ;Здесь начинается .COM-программа
jmp Install
;Сюда нужно поместить резидентную часть

Install:
;сюда поместите код установки
mov AH,31h ;завершиться и остаться резидентом
mov AL,0 ;возвращает результат =OK
mov DX,offset Install
mov CL,4
shr DX,CL ;делим на 16
add DX,1 ;объем резидентной части программы
int 21h

Чтение параметров командной строки

Параметры командной строки (сразу после имени файла) могут быть прочитаны с помощью следующей процедуры ReadCL.

Например, если ваша программа называется KOE.COM и вы запускаете ее, набрав команду

KOE 4abcs

в командной строке DOS, то процедура ReadCL вернет строку 4abcs в формате ASCIIZ.

ReadCL proc ;чтение параметров командной строки в буфер по адресу ES:[DI] ;DS должен остаться неизменным после запуска программы (=PSP)
mov SI,80h ;адрес парамтеров
xor CX,CX
mov CL,[SI] ;длина в байтах
inc SI ;игнорируем байт длины
rep movsb ;перемещаем строку в буфер
mov AL,0
stosb ;завершаем строку ASCIIZ нулем
ret
ReadCL endp

Таблица размещения файлов FAT

Первый сектор (с номером 0) диска — это загрузочный сектор. Его первые байты содержат следующую информацию:
byte
0-2 Переход на загрузочную программу
3-10 Имя в ASCII формате или что-нибудь еще
11-12 Байт на сектор
13 Секторов на кластер
14-15 Секторов в загрузочной записи =B
16 Количество копий FAT
17-18 Количество каталогов в корне диска
19-20 Секторов на диск
21 Тип диска =xx
22-23 Секторов на FAT =F
и т.д.
Первая таблица FAT начинается с B. Ее копия располагается в секторе B+F etc.
Можно детально рассмотреть FAT используя утилиту DEBUG. Не вносите изменений в таблицу FAT на жестком диске, если вы не уверены, что вы делаете.
Первая запись таблицы FAT выглядит так:

12 bit FAT: xx 0FFh 0FFh
16 bit FAT: xx 0FFh 0FFh 0FFh
xx — тип диска.
Затем, с кластера 2 начинаются элементы таблицы. Возможные значения перечислены в следующей таблице:
12-бит.FAT 16-бит. FAT
000h 0000h пусто
002h-0FEFh 0002h-0FFEFh использовано кластеров.
Значение-указатель на следующую запись в цепочке.
0FF0h-0FF6h 0FFF0h-0FFF6h зарезервировано
0FF7h 0FFF7h bad
0FF8h-0FFFh 0FFF8-0FFFFh последний кластер в цепочке
Вы можете читать сектора, используя прерывание 25h. Отметим, что это прерывание сохраняет флаги в стеке, так что после выполнения прерывания они должны быть восстановлены

Запуск дочерней программы
DOS выделяет всю доступную память текущей программе, независимо от того, какой объем реально необходим. Поэтому вы должны освободить часть памяти для того, чтобы загрузить и выполнить дочернюю программу. Это выполняется процедурой Setmem. Каждый параграф занимает 16 байт. Пространство, необходимое текущей программе вычисляется как размер в параграфах = Lseg — Psp + 1
где Lseg — сегмент, расположенный после последнего байта программы, а Psp — сегмент, в котором расположен psp программы.

Setmem proc
;Выделяет AX параграфов памяти текущей программе
:и очищает всю остальную память.
;Входные данные: количество выделяемых параграфов в AX
;Выходные данные: число реально выделенных параграфов в AX
mov BX,AX ;объем выделяемой памяти в 16-битных параграфах
mov AH,4Ah
int 21h ;ES должен указыват на сегмент PSP программы
mov AX,BX ;число выделенных параграфов
ret
Setmem endp
Следующий фрагмент кода запускает программу CHILD.COM с параметром /HELP.

;сегмент данныхt:
ChildName db ‘CHILD.COM’,0 ;имя файла в виде строки ASCIIZ

; сегмент кода:
mov AX,CS
mov SegCmdLine,AX
mov SegFCB1,AX
mov SegFCB2,AX
push DS ;сохраняем регистры
push ES
mov CS:Shell_SS,SS ;сохраняем только регистр CS
mov CS:Shell_SP,SP
;exec-function
mov DX,offset ChildName ;DS:DX — указатель на строку, содержащую имя файла
mov AX,CS
mov ES,AX
mov BX,offset CS:Parm_Table ;таблица параметров ES:BX
mov AX,4B00h ;загрузить и выполнить программу
int 21h
cli ;запрещаем прерывания
mov SS,CS:Shell_SS ;восстанавливаем регистры
mov SP,CS:Shell_SP
sti ;разрешаем прерывания
pop ES
pop DS
cld ;флаг направления (direction flag) = 0
jc ThereWasError ;ошибка

; эти данные должны быть определены в сегменте кода
CmdLineTail db 6,’ /HELP’,13 ;6 — число символов
even ;faster this way
Shell_SS dw 0 ;указатель стека
Shell_SP dw 0
Parm_Table dw 0 ;наследуем переменные окружения родительской программы
dw offset CmdLineTail
SegCmdLine dw 0 ;сюда будет записан CS
dw 5Ch ;блок управления файлом (FCB) #1
SegFCB1 dw 0 ;сюда будет записан CS
dw 6Ch ;блок управления файлом (FCB) #2
SegFCB2 dw 0 ;сюда будет записан CS

Линейные преобразования в системах с фиксированной точкой

Линейные преобразования в системах с фиксированной точкой
Следующая подпрограмма переводит дюймы в миллиметры, но также она может быть использована для любых линейных преобразований, для чего достаточно изменить коэффициент преобразования.

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

Код использует 32-битные инструкции, но может быть откомпилирован и 16-битным компилятором.

; данные
ConvFactor dw 26214 ;младший байт коэффициента преобразования 25.4
dw 25 ;старший байт
Inches dw 32768 ;младший байт представления 12.5 дюймов
dw 12 ;старший байт
mMeters dw ? ;младший байт результата в мм
dw ? ;старший байт
; код
db 66h
mov AX,Inches ;mov EAX,dword ptr ConvFactor
db 66h
mul ConvFactor ;результат в EDX:EAX
mov CL,16
db 66h
shr AX,CL ;shr EAX,16
mov mMeters,AX ;младший байт результата
mov mMeters+2,DX ;старший байт

Установка видеорежимов VGA

Видеорежимы, поддерживаемые BIOS’ом адаптеров VGA BIOS:
Экран
Режим Текст Графика Цвета Размер Адрес

0 CGA 25*40 only text 16 B&W 2000 0B800h
1 CGA 25*40 only text 16 2000 0B800h
2 CGA 25*80 only text 16 B&W 4000 0B800h
3 CGA 25*80 only text 16 4000 0B800h
4 CGA 25*40 320*200 4 16000 0B800h
5 CGA 25*40 320*200 2 B&W 8000 0B800h
6 CGA 25*80 640*200 2 16000 0B800h
7 MDA 25*80 only text 2 4000
0Dh EGA 25*40 320*200 16 32000 0A000h
0Eh EGA 25*80 640*200 16 64000 0A000h
0Fh EGA 25*80 640*350 2 28000 0A000h
10h EGA 25*80 640*350 16 112000 0A000h
11h VGA 30*80 640*480 2 38400 0A000h
12h VGA 30*80 640*480 16 153600 0A000h
13h VGA 25*40 320*200 256 64000 0A000h
Требуемый видеорежим устанавливается вызовом функции BIOS

mov AH,0 ;POW39
mov AL,ScreenModeNumber
int 10h

Этот фрагмент также очищает экран. Содержимое AX не сохраняется. Стандартный BIOS не возвращает никакой информации, сигнализирующей об ошибке. В подерживаемых режимах можно читать и писать в видеопамять путем вызовов соответствующих функций (функции 8,9,0Ch,0Dh). Нормальный текстовый режим DOS — это режим 3.

Следующий фрагмент загружает набор символов из ROM в RAM и соответственно корректирует высоту отображения символов.

mov AH,11h ;изменить используемый набор символов и корректировать высоту их отображения
;mov AL,11h ;выбрать набор символов 8*14, 28 строк в режиме VGA
;mov AL,12h ;выбрать набор символов 8*8, 50 строк
mov AL,14h ;выбрать набор символов 8*16, 25 строк
mov BX,0 ;банк памяти генератора символов
int 10h

Asm: Определяем тип процессора

Следующая процедура WhatCPU определяет тип процессора, установленного в системе. Результат возвращается в регистре AX. Процедура может быть откомпилирована и 16-битным компилятором, несмотря на то, что в ней используются 32-битные инструкции для определения различия между 386, 486 и Pentium.

WhatCPU proc ;POW38
;Результат в AX
;0: i88,i86, 1: i186, 2: i286, 3: i386, 4: i486, 5: Pentium
pushf ;сохраняем флаги
mov DX,0F000h
sub AX,AX
push AX ;записываем 0 в верхушку стека
popf ;восстанавливаем регистр флагов из стека
pushf ;записываем флаги в стек
pop AX
popf ;восстанавливаем флаги
and AX,DX ;выделяем четыре старших байта
cmp AX,DX ;они равны 1 ?
jne CPU_ei8088
mov AX,0 ;результат 0 (8088 или 8086)
ret
CPU_ei8088:
push SP
pop BX
cmp BX,SP ;изменяется ли указатель стека перед записыванием в него?
je CPU_ei186
mov AX,1 ;результат 1 (80186)
ret
CPU_ei186:
pushf ;сохраняем флаги
mov AX,DX ;0F000h
push AX
popf
pushf
pop AX
popf ;оригинальные флаги
and AX,DX
jne CPU_ei286
mov AX,2 ;результат 2 (80286)
ret
CPU_ei286:
db 66h
pushf ;pushfd
db 66h
pushf ;pushfd
db 66h
pop AX ;pop EAX
db 66h
or AX,0000h
db 04h,00h ;или EAX,00040000h
db 66h
push AX ;push EAX
db 66h
popf ;popfd
db 66h
pushf ;pushfd
db 66h
pop AX ;pop EAX
db 66h
popf ;popfd
db 66h
test AX,0000h
db 04h,00h ;test EAX,00040000h
jnz CPU_ei386
db 66h
mov AX,3 ;результат AX=00000003h (80386)
db 0h,0h
ret
CPU_ei386:
db 66h
pushf ;pushfd
db 66h
pushf ;pushfd
db 66h
pop AX ;pop EAX
db 66h
mov BX,AX ;mov EBX,EAX
db 66h
xor AX,0000h
db 20h,00h ;xor EAX,00200000h
db 66h
push AX ;push EAX
db 66h
popf ;popfd
db 66h
pushf ;pushfd
db 66h
pop AX ;pop EAX
db 66h
popf ;popfd
db 66h
and AX,0000h
db 20h,00h ;and EAX,00200000h
db 66h
and BX,0000h
db 20h,00h ;and EBX,00200000h
db 66h
cmp AX,BX ;cmp EAX,EBX
jne CPU_ei486
db 66h
mov AX,4 ;результат EAX=00000004h (80486)
db 0h,0h
db 66h ;обнуление 32 битных регистров
xor BX,BX ;xor EBX,EBX
ret
CPU_ei486: ;Pentium
db 66h
mov AX,5 ;результат EAX=00000005h (Pentium)
db 0h,0h
db 66h
xor BX,BX ;xor EBX,EBX
ret
WhatCPU endp

Asm: Чтение значения счетчика времени

В памяти по адресу 40:6C расположено двойное слово, которое увеличивается на единицу приблизительно 18.2 раза в секунду. Системное время можно получить, считывая это слово. Младший байт может быть использован для многих «временных» задач, в т.ч. в качестве исходного значения для генератора псевдослучайных чисел (а в некторых случаях и заменить его).

GetTicks proc ;POW37
; Входные данные: нет
; Выходные данные: Младший байт счетчика времени в AX
; Регистры не сохраняются.
mov BX,ES ;Сохраняем адрес дополнительного сегмента
mov AX,40h ;сегмент данных BIOS
mov ES,AX
mov AX,ES:[6Ch] ;читаем счетчик
mov ES,BX ;восстанавливаем регистр ES
ret
GetTicks endp

Перевод чисел в двоичную форму (в виде строки)

Данная процедура конвертирует 16-битное слово в строку ASCIIZ, т.е. число 7 преобразовывается в строку 0000000000000111. Лидирующие нули включаются в строку. Строка ASCIIZ — это набор символов, завершающихся 0.

NmbrToBi$ proc ;POW36
;Входные данные: AX — смещение строки, BX — число, которое необходимо преобразовать
;Выходные данные: Строка ASCIIZ. Регистры не сохраняются.
mov DI,AX ;смещение строки
mov DX,8000h ;проверочное слово, 1 в позиции 15
mov CX,16 ;обрабатываем 16 бит
NumberTo_B0:
mov AL,48 ;символ ‘0’
test BX,DX ;бит равен 1?
jz NumberTo_B
inc AL ;символ ‘1’
NumberTo_B:
stosb ;записываем в строку ‘1’ или ‘0’
shr DX,1 ;сдвигаем тестовый бит вправо
loop NumberTo_B0
mov [DI],DL ;завершаем строку 0
ret
NmbrToBi$ endp