Assembler - язык неограниченных возможностей

         

Средства DOS


На примере первой программы на ассемблере мы уже познакомились с одним из способов вывода текста на экран — вызовом функции DOS 09h. Это далеко не единственный способ вывода текста — DOS предоставляет для этого несколько функций.

Функция DOS 02h — Записать символ в STDOUT с проверкой на Ctrl-Break

Ввод: АН = 02h
DL = ASCII-код символа
Вывод: Никакого, согласно документации, но на самом деле: AL = код последнего записанного символа (равен DL, кроме случая, когда DL = 09h (табуляция), тогда в AL возвращается 20h).

Эта функция при выводе на экран обрабатывает некоторые управляющие символы — вывод символа BEL (07h) приводит к звуковому сигналу, символ BS (08h) приводит к движению курсора влево на одну позицию, символ НТ (09h) заменяется на несколько пробелов, символ LF (0Ah) опускает курсор на одну позицию вниз, и CR (0Dh) приводит к переходу на начало текущей строки.



Если в ходе работы этой функции была нажата комбинация клавиш Ctrl-Break, вызывается прерывание 23h, которое по умолчанию осуществляет выход из программы.

Например, напишем программу, выводящую на экран все ASCII-символы, 16 строк по 16 символов в строке.

; dosoutl.asm ; Выводит на экран все ASCII-символы ; .model tiny .code org 100h ; начало СОМ-файла start: mov ex,256 ; вывести 256 символов mov dl,0 ; первый символ - с кодом 00 mov ah,2 ; номер функции DOS "вывод символа" cloop: int 21h ; вызов DOS inc dl ; увеличение DL на 1 - следующий символ test dl,0Fh ; если DL не кратен 16, jnz continue_loop ; продолжить цикл, push dx ; иначе: сохранить текущий символ mov dl,0Dh ; вывести CR int 21h mov dl,0Ah ; вывести LF int 21h pop dx ; восстановить текущий символ continue_loop: loop cloop ; продолжить цикл ret ; завершение СОМ-файла end start

Это программа типа СОМ, и компилироваться она должна точно так же, как hello-1.asm в разделе 4.1. Здесь с помощью команды LOOP оформляется цикл, выполняющийся 256 раз (значение регистра СХ в начале цикла). Регистр DL содержит код символа, который равен нулю в начале цикла и увеличивается каждый раз на 1 командой INC DL. Если значение DL сразу после увеличения на 1 кратно 16, оно временно сохраняется в стеке и на экран выводятся символы CR и LF, выполняющие переход на начало новой строки. Проверка выполняется командой TEST DL,0Fh — результат операции AND над DL и 0Fh будет нулем, только если младшие четыре бита DL равны нулю, что и соответствует кратности шестнадцати.


Все функции DOS вывода на экран используют устройство STDOUT, стандартный вывод. Это позволяет перенаправлять вывод программы в файл или на стандартный ввод другой программы. Например, если написать в командной строке

hello-1.com > hello-1.out

то на экран ничего выдано не будет, а в текущем каталоге появится файл hello-1.out, содержащий строку «Hello World!». Точно так же, если написать

dosout1.com > dosout1.out

то в файле dosout1.out окажутся все символы ASCII, причем символы BEL и BS не будут интерпретироваться и запишутся в файл как есть. Символы CR и LF тоже запишутся как есть, но так как они отмечают конец строки, редакторы и просмотрщики текстовых файлов будут разрывать первую строку символов.

Функция DOS 06h — Записать символ в STDOUT без проверки на Ctrl-Break

Ввод: АН = 06h
DL = ASCII-код символа (кроме FFh)
Вывод: Никакого, согласно документации, но на самом деле: AL = код записанного символа (копия DL)
Эта функция не обрабатывает управляющие символы (CR, LF, HT и BS выполняют свои функции при выводе на экран, но сохраняются при перенаправлении вывода в файл) и не проверяет нажатие Ctrl-Break. Можно заменить в программе dosoutl.asm команду MOV АН,2 на MOV АН,6 и перекомпилировать этот пример, чтобы получить более полную таблицу символов.

Функция DOS 09h — Записать строку в STDOUT с проверкой на Ctrl-Break

Ввод: АН = 09h
DS:DX = адрес строки, заканчивающейся символом $ (24h)
Вывод: Никакого, согласно документации, но на самом деле: AL = 24h (код последнего символа)
Действие этой функции полностью аналогично действию функции 02h, но выводится не один символ, а целая строка, как в программах hello-1.asm и hello-2.asm.

Функция DOS 40h — Записать в файл или устройство

Ввод: АН = 40h
ВХ = 1 для STDOUT или 2 для STDERR
DS:DX = адрес начала строки
СХ = длина строки
Вывод: CF = 0,
АХ = число записанных байт
Эта функция предназначена для записи в файл, но, если в регистр ВХ поместить число 1, функция 40h будет выводить данные на STDOUT, а если ВХ = 2 — на устройство STDERR. STDERR всегда выводит данные на экран и не перенаправляется в файлы. На этой функции основаны используемые в С функции стандартного вывода — фактически функция С fputs() просто вызывает это прерывание, помещая свой первый аргумент в ВХ, адрес строки (второй аргумент) — в DS:DX и длину — в СХ.

; dosout2.asm ; Выводит на экран строку "This function can print $", ; используя вывод в STDERR, так что ее нельзя перенаправить в файл. .model tiny .code org 100h ; начало СОМ-файла start: mov ah,40h ; номер функции DOS mov bx,2 ; устройство STDERR mov dx,offset message ; DS:DX - адрес строки mov cx, message_length ; CX - длина строки int 21h ret ; завершение СОМ-файла message db "Эта функция может выводить знак $" message_length = $-message ; длина строки = текущий адрес ; минус адрес начала строки end start

Если скомпилировать эту программу и запустить ее командой

dosout2.com > dosout2.out

то сообщение появится на экране, а файл dosout2.out окажется пустым.

И наконец, последняя функция DOS вывода на экран — недокументированное прерывание 29h.

INT 29h: Быстрый вывод символа на экран

Ввод: AL = ASCII-код символа
В большинстве случаев INT 29h просто немедленно вызывает функцию BIOS «вывод символа на экран в режиме телетайпа», так что никаких преимуществ, кроме экономии байт при написании как можно более коротких программ, она не имеет.



/p> При чтении с помощью этой функции введенный символ автоматически немедленно отображается на экране (посылается в устройство STDOUT — так что его можно перенаправить в файл). При нажатии Ctrl-C или Ctrl-Break выполняется команда INT 23h. Если нажата клавиша, не соответствующая какому-нибудь символу (стрелки, функциональные клавиши Ins, Del и т.д.), то в AL возвращается 0 и функцию надо вызвать еще один раз, чтобы получить расширенный ASCII-код (см. приложение 1).

В трех следующих вариантах этой функции код символа возвращается в AL по такому же принципу.

Функция DOS 08h — Считать символ из STDIN без эха, с ожиданием и проверкой на Ctrl-Break

Ввод: АН = 08h
Вывод: AL = код символа
Функция DOS 07h — Считать символ из STDIN без эха, с ожиданием и без проверки на Ctrl-Break

Ввод: АН = 07h
Вывод: AL = код символа
Функция DOS 06h — Считать символ из STDIN без эха, без ожидания и без проверки на Ctrl-Break

Ввод: АН = 07h
DL = 0FFh
Вывод: ZF = 1, если не была нажата клавиша, и AL = 00
ZF = 0, если клавиша была нажата. В этом случае AL = код символа
Кроме перечисленных функций могут потребоваться и некоторые служебные функции DOS для работы с клавиатурой.

Функция DOS 0Bh — Проверить состояние клавиатуры

Ввод: АН = 0Bh
Вывод: AL = 0, если не была нажата клавиша
AL = 0FFh, если была нажата клавиша
Эту функцию удобно использовать перед функциями 01, 07 и 08, чтобы не ждать нажатия клавиши. Кроме того, вызов этой функции позволяет проверить, не считывая символ с клавиатуры, была ли нажата комбинация клавиш Ctrl-Break; если это произошло, выполнится прерывание 23h.

Функция DOS 0Ch — Очистить буфер и считать символ

Ввод: АН = 0Ch
AL = Номер функции DOS (01, 06, 07, 08, 0Ah)
Вывод: Зависит от вызванной функции
Функция 0Ch очищает буфер клавиатуры, так что следующая функция чтения символа будет ждать ввода с клавиатуры, а не использовать нажатый ранее и еще не обработанный символ. Например, именно эта функция используется для считывания ответа на вопрос «Уверен ли пользователь в том, что он хочет отформатировать диск?».



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

; dosin2.asm ; Изображает пентамино F, которое можно перемещать по экрану клавишами ; управления курсором и вращать клавишами X и Z. Выход из программы - Esc. ; line_length = 3 ; число символов в строке изображения number_of_lines = 3 ; число строк

.model tiny .code org 100h ; начало СОМ-файла start: cld ; будут использоваться команды ; строковой обработки mov ax,0B800h ; адрес начала текстовой видеопамяти mov es,ax ; в ES mov ax,0003h int 10h ; текстовый режим 03 (80x25) mov ah,02h ; установить курсор mov bh,0 mov dh,26 ; на строку 26, то есть за пределы экрана mov dl,1 int 10h ; теперь курсора на экране нет call update_screen ; вывести изображение

; основной цикл опроса клавиатуры main_loop: mov ah,08h ; считать символ с клавиатуры int 21h ; без эха, с ожиданием, с проверкой на Ctrl-Break test al,al ; если AL = 0 jz eASCII_entered ; введен символ расширенного ASCII cmp al,1Bh ; иначе: если введен символ 1Bh (Esc), je key_ESC ; выйти из программы, cmp al,'Z' ; если введен символ Z, je key_Z ; перейти на его обработчик cmp al,'z' ; то же для z je key_Z cmp al,'X' ; если введен символ X, je key_X ; перейти на его обработчик cmp al,'х' ; то же для х je key_X jmp short main_loop ; считать следующую клавишу

eASCII_entered: ; был введен расширенный ASCII-символ int 21h ; получить его код (повторный вызов функции) cmp al,48h ; стрелка вверх je key_UP cmp al,50h ; стрелка вниз je key_DOWN cmp al,4Bh ; стрелка влево je key_LEFT cmp al,4Dh ; стрелка вправо je key_RIGHT jmp short main_loop ; считать следующую клавишу ; ; обработчики нажатий клавиш ; key_ESC: ; Esc ret ; завершить СОМ-программу

key_UP: ; стрелка вверх cmp byte ptr start_row,0 ; если изображение на верхнем ; краю экрана, jna main_loop ; считать следующую клавишу, dec byte ptr start_row ; иначе - уменьшить номер строки, call update_screen ; вывести новое изображение jmp short main_loop ; и считать следующую клавишу



key_DOWN: ; стрелка вниз cmp byte ptr start_row,25-number_of_lines ; если ; изображение на нижнем краю экрана, jnb main_loop ; считать следующую клавишу, inc byte ptr start_row ; иначе - увеличить номер строки, call update_screen ; вывести новое изображение jmp short main_loop ; и считать следующую клавишу

key_LEFT: ; стрелка влево cmp byte ptr start_col,0 ; если изображение на левом краю ; экрана, jna main_loop ; считать следующую клавишу, dec byte ptr start_col ; иначе - уменьшить номер столбца, call update_screen ; вывести новое изображение jmp short main_loop ; и считать следующую клавишу

key_RIGHT: ; стрелка вправо cmp byte ptr start_col,80-line_length ; если ; изображение на правом краю экрана, jnb main_loop ; считать следующую клавишу, inc byte ptr start_col ; иначе - увеличить номер столбца, call update_screen ; вывести новое изображение jmp short main_loop ; и считать следующую клавишу

key_Z: ; клавиша Z (вращение влево) mov ax,current_screen ; считать номер текущего изображения ; (значения 0, 1, 2, 3), dec ax ; уменьшить его на 1, jns key_Z_ok ; если получился -1 (поменялся знак), mov ах,3 ; АХ = 3 key_Z_ok: mov current_screen,ax ; записать номер обратно, call update_screen ; вывести новое изображение jmp main_loop ; и считать следующую клавишу

key_X: ; клавиша X (вращение вправо) mov ax,current_screen ; считать номер текущего изображения ; (значения 0, 1, 2, 3), inc ax ; увеличить его на 1, cmp ax,4 ; если номер стал равен 4, jne key_X_ok xor ах,ах ; АХ = 0 key_X_ok: mov current_screen,ax ; записать номер обратно, call update_screen ; вывести новое изображение jmp main_loop ; и считать следующую клавишу

; процедура update_screen ; очищает экран и выводит текущее изображение ; модифицирует значения регистров АХ, ВХ, СХ, DX, SI, DI update_screen: mov cx,25*80 ; число символов на экране mov ax,0F20h; ; символ 20h (пробел) с атрибутом 0Fh ; (белый на черном) xor di,di ; ES:DI = начало видеопамяти rep stosw ; очистить экран mov bx,current_screen ; номер текущего изображения в ВХ shl bx,1 ; умножить на 2, так как screens - массив слов mov si,screens[bx] ; поместить в ВХ смещение начала ; текущего изображения из массива screens, mov ax,start_row ; вычислить адрес начала mul row_length ; изображения в видеопамяти add ax,start_col ; (строка * 80 + столбец) * 2 shl ax,1 mov di,ax ; ES:DI - начало изображения в видеопамяти mov ah,0Fh ; используемый атрибут - белый на черном mov dx,number_of_lines ; число строк в изображении сору_lines: mov cx,line_length ; число символов в строке copy_1: lodsb ; считать ASCII-код в AL, stosw ; записать его в видеопамять ; (AL - ASCII, АН - атрибут), loop copy_1 ; вывести так все символы в строке, add di,(80-line_length)*2 ; перевести DI на начало ; следующей строки экрана, dec dx ; если строки не закончились - jnz copy_lines ; вывести следующую ret ; конец процедуры update_screen



; изображение пентамино F screen1 db " XX" ; выводимое изображение db "XX " db " X "

screen2 db " X " ; поворот на 90 градусов вправо db "XXX" db " X"

screen3 db " X " ; поворот на 180 градусов db " XX" db "XX "

screen4 db "X " ; поворот на 90 градусов влево db "XXX" db " X " ; массив, содержащий адреса всех вариантов изображения screens dw screen1,screen2,screen3,screen4 current_screen dw 0 ; текущий вариант изображения start_row dw 10 ; текущая верхняя строка изображения start_col dw 37 ; текущий левый столбец row_length db 80 ; длина строки экрана для команды MUL

end start

В этом примере для вывода на экран используется прямое копирование в видеопамять, так как вызов функции BIOS вывода строки (INT 10h, АН = 13h) прокручивает экран вверх на одну строку при выводе символа в нижнем правом углу экрана.


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

Функция DOS 0Ah — Считать строку символов из STDIN в буфер

Ввод: АН = 0Ah
DS:DX = адрес буфера
Вывод: Буфер содержит введенную строку

Для вызова этой функции надо подготовить буфер, первый байт которого содержит максимальное число символов для ввода (1 – 254), а содержимое, если оно задано, может использоваться как подсказка для ввода. При наборе строки обрабатываются клавиши Esc, F3, F5, BS, Ctrl-C/Ctrl-Break и т.д., как при наборе команд DOS (то есть Esc начинает ввод сначала, F3 восстанавливает подсказку для ввода, F5 запоминает текущую строку как подсказку, Backspace стирает предыдущий символ). После нажатия клавиши Enter строка (включая последний символ CR (0Dh)) записывается в буфер, начиная с третьего байта. Во второй байт записывается длина реально введенной строки без учета последнего CR.

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

; dosinl.asm ; Переводит десятичное число в шестнадцатеричное ; .model tiny .code .286 ; для команды shr al,4 org 100h ; начало СОМ-файла start: mov dx,offset message1 mov ah,9 int 21h ; вывести приглашение ко вводу message1 mov dx,offset buffer mov ah,0Ah int 21h ; считать строку символов в буфер mov dx,offset crlf mov ah,9 int 21h ; перевод строки

; перевод числа в ASCII-формате из буфера в бинарное число в АХ xor di,di ; DI = 0 - номер байта в буфере xor ах,ах ; АХ = 0 - текущее значение результата mov cl,blength xor ch,ch xor bx,bx mov si,cx ; SI - длина буфера mov cl,10 ; CL = 10, множитель для MUL asc2hex: mov bl,byte ptr bcontents[di] sub bl,'0' ; цифра = код цифры - код символа "0", jb asc_error ; если код символа был меньше, чем код "0", cmp bl,9 ; или больше, чем "9", ja asc_error ; выйти из программы с сообщением об ошибке, mul cx ; иначе: умножить текущий результат на 10, add ax,bx ; добавить к нему новую цифру, inc di ; увеличить счетчик cmp di,si ; если счетчик+1 меньше числа символов - jb asc2hex ; продолжить (счетчик считается от 0)

Содержание раздела