Простейший блок повторений REPT (не поддерживается WASM) выполняет ассемблирование участка программы заданное число раз. Например, если требуется создать массив байтов, проинициализированный значениями от 0 до 0FFh, это можно сделать путем повтора псевдокоманды DB следующим образом:
hexnumber = 0 hextable label byte ; Имя массива rept 256 ; Начало блока db hexnumber ; Эти две строки ассемблируются hexnumber = hexnumber+1 ; 256 раз. endm
Блоки повторений, так же как макроопределения, могут вызываться с параметрами. Для этого используются директивы IRP и IRPC:
irp параметр,<значение1,значение2...> ... endm
irpc параметр,строка ... endm
Блок, описанный директивой IRP, будет вызываться столько раз, сколько значений указано в списке (в угловых скобках), и при каждом повторении будет определена метка с именем параметр, равная очередному значению из списка. Например, следующий блок повторений сохранит в стек регистры AX, BX, CX и DX:
irp reg,<ax,bx,cx,dx> push reg endm
Директива IRPC (FORC в WASM) описывает блок, который выполняется столько раз, сколько символов содержит указанная строка, и при каждом повторении будет определена метка с именем параметр, равная очередному символу из строки. Если строка содержит пробелы или другие символы, отличные от разрешенных для меток, она должна быть заключена в угловые скобки. Например, следующий блок задает строку в памяти, располагая после каждого символа строки атрибут 0Fh (белый символ на черном фоне), так что эту строку впоследствии можно будет скопировать прямо в видеопамять.
irpc character,<строка символов> db ’&character&’,0Fh endm
В этом примере используются амперсанды, чтобы вместо параметра character было подставлено его значение даже внутри кавычек. Амперсанд — это один из макрооператоров — специальных операторов, которые действуют только внутри макроопределений и блоков повторений.
Программный счетчик — внутренняя переменная ассемблера, равная смещению текущей команды или данных относительно начала сегмента. Для преобразования меток в адреса используется именно значение этого счетчика. Значением счетчика можно управлять с помощью следующих директив.
org выражение
Устанавливает значение программного счетчика. Директива ORG с операндом 100h обязательно используется при написании файлов типа COM, которые загружаются в память после блока параметров размером 100h.
even
Директива EVEN делает текущее значение счетчика кратным двум, вставляя команду NOP, если оно было нечетным. Это увеличивает скорость работы программы, так как для доступа к слову, начинающемуся с нечетного адреса, процессор должен считать два слова из памяти. Если при описании сегмента не использовалось выравнивание типа BYTE, счетчик в начале сегмента всегда четный.
align значение
Округляет значение программного счетчика до кратного указанному значению. Оно может быть любым четным числом. Если счетчик некратен указанному числу, эта директива вставляет необходимое количество команд NOP.
По умолчанию ассемблеры используют набор команд процессора 8086 и выдают сообщения об ошибках, если выбирается команда, которую этот процессор не поддерживал. Для того чтобы ассемблер разрешил использование команд, появившихся в более новых процессорах, и команд расширений, предлагаются следующие директивы:
.8086 — используется по умолчанию. Разрешены только команды 8086;
.186 — разрешены команды 80186;
.286 и .286c — разрешены непривилегированные команды 80286;
.286p — разрешены все команды 80286;
.386 и .386c — разрешены непривилегированные команды 80386;
.386p — разрешены все команды 80386;
.486 и .486c — разрешены непривилегированные команды 80486;
.486p — разрешены все команды 80486;
.586 и .586c — разрешены непривилегированные команды P5 (Pentium);
.586p — разрешены все команды P5 (Pentium);
.686 — разрешены непривилегированные команды P6 (Pentium Pro, Pentium II);
.686p — разрешены все команды P6 (Pentium Pro, Pentium II);
.8087 — разрешены команды NPX 8087;
.287 — разрешены команды NPX 80287;
.387 — разрешены команды NPX 80387;
.487 — разрешены команды FPU 80486;
.587 — разрешены команды FPU 80586;
.MMX — разрешены команды IA MMX;
.K3D — разрешены команды AMD 3D.
Не все ассемблеры поддерживают каждую директиву, например MASM и WASM не поддерживают .487 и .587, так как их действие не отличается от .387. Естественно, ассемблеры, вышедшие до появления последних процессоров и расширений, не в состоянии выполнять соответствующие им команды.
Если присутствует директива .386 или выше, ассемблер WASM всегда определяет все сегменты как 32-битные при условии, что не указан явно операнд USE16. MASM и TASM действуют так же, только если директива задания набора команд указана перед директивой .model.
Директива EXITM (не поддерживается WASM) выполняет преждевременный выход из макроопределения или блока повторений. Например, следующее макроопределение не выполнит никаких действий, то есть не будет расширено в команды процессора, если параметр не указан:
pushreg macro reg ifb <reg> exitm endif push reg endm
LOCAL метка... — перечисляет метки, которые будут применяться внутри макроопределения, чтобы не возникало ошибки «метка уже определена» при использовании макроса более одного раза или если та же метка присутствует в основном тексте программы (в WASM директива LOCAL позволяет использовать макрос с метками несколько раз, но не разрешает применять метку с тем же именем в программе). Операнд для LOCAL — метка или список меток, которые будут использоваться в макросе.
PURGE имя_макроса — отменяет определенный ранее макрос (не поддерживается WASM). Эта директива часто применяется сразу после INCLUDE, включившей в текст программы файл с большим количеством готовых макроопределений.
Каждая программа на языке ассемблера помимо команд процессора содержит еще и специальные инструкции, указывающие самому ассемблеру, как организовывать различные секции программы, где располагаются данные, а где команды, позволяющие создавать макроопределения, выбирать тип используемого процессора, организовывать связи между процедурами и т.д. К сожалению, пока нет единого стандарта на эти команды (он существует для UNIX, о чем рассказано в главе 11). Разные ассемблеры используют различные наборы директив, но TASM и MASM (два самых популярных ассемблера для DOS и Windows) поддерживают общий набор, или, точнее, TASM поддерживает набор директив MASM наряду с несовместимым собственным, известным как Ideal Mode. Все примеры программ в книге написаны так, чтобы для их компиляции можно было воспользоваться TASM, MASM или WASM — еще одним популярным ассемблером, поэтому в данной главе рассмотрены те предопределенные идентификаторы, операторы и директивы, которые поддерживаются этими тремя ассемблерами одновременно.
public язык метка... ; Для TASM и MASM
или
public метка язык... ; для WASM
Метка, объявленная директивой PUBLIC, становится доступной для других модулей программы. Так, можно объявлять имена процедур, переменные и константы, определенные директивой EQU. Необязательный операнд языка (C, PASCAL, BASIC, FORTRAN, SYSCALL или STDCALL) указывает, что метка будет вызываться из модуля, написанного на соответствующем языке, и при необходимости изменяет ее (например, добавляет _ перед первым символом метки).
comm расст язык метка:тип... ; для TASM comm язык расст метка:тип... ; для TASM comm расст метка:тип язык... ; для WASM
Директива COMM описывает общую переменную. Такие переменные доступны из всех модулей, и их размещение в программе определяется на этапе компоновки. Обязательные аргументы директивы COMM — метка (собственно имя общей переменной) и тип (BYTE, WORD, DWORD, FWORD, QWORD, TBYTE или имя структуры). Необязательный операнд «расстояние» (NEAR или FAR) указывает, находится ли переменная в группе сегментов DGROUP (ближняя переменная, для доступа достаточно смещения) или вне этих сегментов (дальняя переменная, для доступа потребуется сегментный адрес). Для моделей памяти TINY, SMALL и COMPACT по умолчанию значение этого операнда принимается за NEAR. И наконец, операнд «язык» действует аналогично такому же операнду для PUBLIC.
extrn язык метка:тип... ; для MASM и TASM extrn метка:тип язык... ; для WASM
Описывает метку, определенную в другом модуле (с помощью PUBLIC). Тип (BYTE, WORD, DWORD, FWORD, QWORD, TBYTE, имя структуры, FAR, NEAR, ABS) должен соответствовать типу метки в том модуле, где она была установлена (тип ABS используется для констант из других модулей, определенных директивой EQU). Необязательный операнд языка действует так же, как и для директивы PUBLIC.
global язык метка:тип... ; для MASM и TASM global метка:тип язык... ; для WASM
Директива GLOBAL действует, как PUBLIC и EXTRN одновременно. Когда указанная метка находится в этом же модуле, она становится доступной для других модулей, как если бы выполнилась директива PUBLIC. Если метка не описана — она считается внешней и выполняется действие, аналогичное действию директивы EXTRN.
end start_label
Этой директивой завершается любая программа на ассемблере. В роли необязательного операнда здесь выступает метка (или выражение), определяющая адрес, с которого начинается выполнение программы. Если программа состоит из нескольких модулей, только один файл может содержать начальный адрес, так же как в C только один файл может содержать функцию main().
Макрооператор & (амперсанд) нужен для того, чтобы параметр, переданный в качестве операнда макроопределению или блоку повторений, заменялся значением до обработки строки ассемблером. Так, например, следующий макрос выполнит команду PUSH EAX, если его вызвать как PUSHREG A:
pushreg macro letter push e&letter&x endm
Иногда можно использовать только один амперсанд — в начале параметра, если не возникает неоднозначностей. Например, если передается номер, а требуется создать набор переменных с именами, оканчивающимися этим номером:
irp number,<1,2,3,4> msg&number db ? endm
Макрооператор <> (угловые скобки) действует так, что весь текст, заключенный в эти скобки, рассматривается как текстовая строка, даже если он содержит пробелы или другие разделители. Как мы уже видели, этот макрооператор используется при передаче текстовых строк в качестве параметров для макросов. Другое частое применение угловых скобок — передача списка параметров вложенному макроопределению или блоку повторений.
Макрооператор ! (восклицательный знак) используется аналогично угловым скобкам, но действует только на один следующий символ, так что, если этот символ — запятая или угловая скобка, он все равно будет передан макросу как часть параметра.
Макрооператор % (процент) указывает, что находящийся за ним текст является выражением и должен быть вычислен. Обычно это требуется для того, чтобы передавать в качестве параметра в макрос не само выражение, а его результат.
Макрооператор ;; (две точки с запятой) — начало макрокомментария. В отличие от обычных комментариев текст макрокомментария не попадает в листинг и в текст программы при подстановке макроса. Это сэкономит память при ассемблировании программы с большим количеством макроопределений.
Одно из самых мощных языковых средств ассемблера — макроопределения. Макроопределением (или макросом) называется участок программы, которому присвоено имя и который ассемблируется всякий раз, когда ассемблер встречает это имя в тексте программы. Макрос начинается директивой MACRO и заканчивается ENDM. Например: пусть описано макроопределение hex2ascii, переводящее шестнадцатеричное число, находящееся в регистре AL, в ASCII-код соответствующей шестнадцатеричной цифры:
hex2ascii macro cmp al,10 sbb al,69h das endm
Теперь в программе можно использовать слово hex2ascii, как если бы это было имя команды, и ассемблер заменит каждое такое слово на три команды, содержащиеся в макроопределении. Разумеется, можно оформить этот же участок кода в виде процедуры и вызывать его командой CALL — если процедура вызывается больше одного раза, этот вариант программы займет меньше места, но вариант с макроопределением станет выполняться быстрее, так как в нем не будет лишних команд CALL и RET. Однако скорость выполнения — не главное преимущество макросов. В отличие от процедур макроопределения могут вызываться с параметрами, следовательно, в зависимости от ситуации, включаемый код будет немного различаться, например:
s_mov macro register1,register2 push register1 pop register2 endm
Теперь можно использовать S_MOV вместо команды MOV для того, чтобы скопировать значение из одного сегментного регистра в другой.
Следующее важное средство, использующееся в макроопределениях, — директивы условного ассемблирования. Например: напишем макрос, выполняющий умножение регистра AX на число, причем, если множитель — степень двойки, то умножение будет выполняться более быстрой командой сдвига влево.
fast_mul macro number if number eq 2 shl ax,1 ; Умножение на 2 elseif number eq 4 shl ax,2 ; Умножение на 4 elseif number eq 8 shl ax,3 ; Умножение на 8 ... ; Аналогично вплоть до: elseif number eq 32768 shl ax,15 ; Умножение на 32768 else mov dx,number ; Умножение на число, не являющееся mul dx ; степенью двойки. endif endm
Можно, конечно, усложнить этот макрос, применяя особые свойства команды LEA и ее комбинации, сдвиги и сложения, однако в нынешнем виде он чрезмерно громоздкий. Проблема решается с помощью третьего средства, постоянно использующегося в макросах, — блоков повторений.
Модели памяти задаются директивой .MODEL
.model модель,язык,модификатор
где модель — одно из следующих слов:
TINY — код, данные и стек размещаются в одном и том же сегменте размером до 64 Кб. Эта модель памяти чаще всего используется при написании на ассемблере небольших программ;
SMALL — код размещается в одном сегменте, а данные и стек — в другом (для их описания могут применяться разные сегменты, но объединенные в одну группу). Эту модель памяти также удобно использовать для создания программ на ассемблере;
COMPACT — код размещается в одном сегменте, а для хранения данных могут использоваться несколько сегментов, так что для обращения к данным требуется указывать сегмент и смещение (данные дальнего типа);
MEDIUM — код размещается в нескольких сегментах, а все данные — в одном, поэтому для доступа к данным используется только смещение, а вызовы подпрограмм применяют команды дальнего вызова процедуры;
LARGE и HUGE — и код, и данные могут занимать несколько сегментов;
FLAT — то же, что и TINY, но используются 32-битные сегменты, так что максимальный размер сегмента, содержащего и данные, и код, и стек, — 4 Мб.
Язык — необязательный операнд, принимающий значения C, PASCAL, BASIC, FORTRAN, SYSCALL и STDCALL. Если он указан, подразумевается, что процедуры рассчитаны на вызов из программ на соответствующем языке высокого уровня, следовательно, если указан язык C, все имена ассемблерных процедур, объявленных как PUBLIC, будут изменены так, чтобы начинаться с символа подчеркивания, как это принято в C.
Модификатор — необязательный операнд, принимающий значения NEARSTACK (по умолчанию) или FARSTACK. Во втором случае сегмент стека не будет объединяться в одну группу с сегментами данных.
После того как модель памяти установлена, вступают в силу упрощенные директивы определения сегментов, объединяющие действия директив SEGMENT и ASSUME. Кроме того, сегменты, объявленные упрощенными директивами, не требуется закрывать директивой ENDS — они закрываются автоматически, как только ассемблер обнаруживает новую директиву определения сегмента или конец программы.
Директива .CODE описывает основной сегмент кода
.code имя_сегмента
Обычно сегменты загружаются в память в том порядке, в котором они описываются в тексте программы, причем, если несколько сегментов объединяются в один, порядок определяется по началу первого из объединяемых сегментов. Этот порядок можно изменить с помощью одной из специальных директив.
.alpha
Эта директива устанавливает алфавитный порядок загрузки сегментов.
.dosseg ; для MASM и WASM
или
dosseg ; для MASM и TASM
Устанавливает порядок загрузки сегментов, существующий в MS DOS и часто требуемый для взаимодействия программ на ассемблере с программами на языках высокого уровня. DOSSEG устанавливает следующий порядок загрузки сегментов:
1. Все сегменты класса 'CODE'.
2. Все сегменты, не принадлежащие группе DGROUP и классу 'CODE'.
3. Группа сегментов DGROUP:
3.1. Все сегменты класса 'BEGDATA'.
3.2. Все сегменты, кроме классов 'BEGDATA', 'BSS' и 'STACK'.
3.3. Все сегменты класса 'BSS'.
3.4. Все сегменты класса 'STACK'.
.seq
Устанавливает загрузку сегментов в том порядке, в котором они описаны в тексте программы. Этот режим устанавливается по умолчанию, так что директива .SEQ просто отменяет действие .ALPHA или .DOSSEG.
Знание порядка загрузки сегментов необходимо, например, для вычисления длины программы или адреса ее конца. Для этого надо знать, какой сегмент будет загружен последним, и смещение последнего байта в нем.
Процедурой в ассемблере является все то, что в других языках называют подпрограммами, функциями, процедурами и т.д. Ассемблер не накладывает на процедуры никаких ограничений — на любой адрес программы можно передать управление командой CALL, и оно вернется к вызвавшей процедуре, как только встретится команда RET. Такая свобода выражения легко может приводить к трудночитаемым программам, и в язык ассемблера были включены директивы логического оформления процедур.
метка proc язык тип USES регистры ; TASM
или
метка proc тип язык USES регистры ; MASM/WASM ... ret метка endp
Все операнды PROC необязательны.
Тип может принимать значения NEAR и FAR, и если он указан, все команды RET в теле процедуры будут заменены соответственно на RETN и RETF. По умолчанию подразумевается, что процедура имеет тип NEAR в моделях памяти TINY, SMALL и COMPACT.
Операнд язык действует аналогично такому же операнду директивы .MODEL, определяя взаимодействие процедуры с языками высокого уровня. В некоторых ассемблерах директива PROC позволяет также считать параметры, передаваемые вызывающей программой. В этом случае указание языка необходимо, так как различные языки высокого уровня используют разные способы передачи параметров.
USES — список регистров, значения которых изменяет процедура. Ассемблер помещает в начало процедуры набор команд PUSH, а перед командой RET — набор команд POP, так что значения перечисленных регистров будут восстановлены.
Псевдокоманда — это директива ассемблера, которая приводит к включению данных или кода в программу, хотя сама она никакой команде процессора не соответствует. Псевдокоманды определения переменных указывают ассемблеру, что в соответствующем месте программы располагается переменная, определяют тип переменной (байт, слово, вещественное число и т.д.), задают ее начальное значение и ставят в соответствие переменной метку, которая будет использоваться для обращения к этим данным. Псевдокоманды определения данных записываются в общем виде следующим образом:
имя_переменной d* значение
где D* — одна из нижеприведенных псевдокоманд:
DB — определить байт;
DW — определить слово (2 байта);
DD — определить двойное слово (4 байта);
DF — определить 6 байт (адрес в формате 16-битный селектор: 32-битное смещение);
DQ — определить учетверенное слово (8 байт);
DT — определить 10 байт (80-битные типы данных, используемые FPU).
Поле значения может содержать одно или несколько чисел, строк символов (взятых в одиночные или двойные кавычки), операторов ? и DUP, разделенных запятыми. Все установленные таким образом данные окажутся в выходном файле, а имя переменной будет соответствовать адресу первого из указанных значений. Например, набор директив
text_string db 'Hello world!' number dw 7 table db 1,2,3,4,5,6,7,8,9,0Ah,0Bh,0Ch,0Dh,0Eh,0Fh float_number dd 3.5e7
заполняет данными 33 байта. Первые 12 байт содержат ASCII-коды символов строки «Hello world!», и переменная text_string указывает на первую букву в этой строке, так что команда
mov al,text_string
считает в регистр AL число 48h (код латинской буквы H). Если вместо точного значения указан знак ?, переменная считается неинициализированной и ее значение на момент запуска программы может оказаться любым. Если нужно заполнить участок памяти повторяющимися данными, используется специальный оператор DUP, имеющий формат счетчик DUP (значение). Например, вот такое определение:
table_512w dw 512 dup(?)
создает массив из 512 неинициализированных слов, на первое из которых указывает переменная table_512w. В качестве аргумента в операторе DUP могут выступать несколько значений, разделенных запятыми, и даже дополнительные вложенные операторы DUP.
Каждая программа, написанная на любом языке программирования, состоит из одного или нескольких сегментов. Обычно область памяти, в которой находятся команды, называют сегментом кода, область памяти с данными — сегментом данных и область памяти, отведенную под стек, — сегментом стека. Разумеется, ассемблер позволяет изменять устройство программы как угодно — помещать данные в сегмент кода, разносить код на множество сегментов, помещать стек в один сегмент с данными или вообще использовать один сегмент для всего.
Сегмент программы описывается директивами SEGMENT и ENDS.
имя_сегмента segment readonly выравн. тип разряд 'класс' ... имя_сегмента ends
Имя сегмента — метка, которая будет использоваться для получения сегментного адреса, а также для комбинирования сегментов в группы.
Все пять операндов директивы SEGMENT необязательны.
READONLY. Если этот операнд присутствует, MASM выдаст сообщение об ошибке на все команды, выполняющие запись в данный сегмент. Другие ассемблеры этот операнд игнорируют.
Выравнивание. Указывает ассемблеру и компоновщику, с какого адреса может начинаться сегмент. Значения этого операнда:
BYTE — с любого адреса;
WORD — с четного адреса;
DWORD — с адреса, кратного 4;
PARA — с адреса, кратного 16 (граница параграфа);
PAGE — с адреса, кратного 256.
По умолчанию используется выравнивание по границе параграфа.
Тип. Выбирает один из возможных типов комбинирования сегментов:
тип PUBLIC (иногда используется синоним MEMORY) означает, что все такие сегменты с одинаковым именем, но разными классами будут объединены в один;
тип STACK — то же самое, что и PUBLIC, но должен использоваться для сегментов стека, потому что при загрузке программы сегмент, полученный объединением всех сегментов типа STACK, будет использоваться как стек;
сегменты типа COMMON с одинаковым именем также объединяются в один, но не последовательно, а по одному и тому же адресу, следовательно, длина суммарного сегмента будет равна не сумме длин объединяемых сегментов, как в случае PUBLIC и STACK, а длине максимального. Таким способом иногда можно формировать оверлейные программы;
Программа на языке ассемблера состоит из строк, имеющих следующий вид:
метка команда/директива операнды ; комментарий
Причем все эти поля необязательны. Метка может быть любой комбинацией букв английского алфавита, цифр и символов _, $, @, ?, но цифра не может быть первым символом метки, а символы $ и ? иногда имеют специальные значения и обычно не рекомендуются к использованию. Большие и маленькие буквы по умолчанию не различаются, но различие можно включить, задав ту или иную опцию в командной строке ассемблера. Во втором поле, поле команды, может располагаться команда процессора, которая транслируется в исполняемый код, или директива, которая не приводит к появлению нового кода, а управляет работой самого ассемблера. В поле операндов располагаются требуемые командой или директивой операнды (то есть нельзя указать операнды и не указать команду или директиву). И наконец, в поле комментариев, начало которого отмечается символом ; (точка с запятой), можно написать все что угодно — текст от символа «;» до конца строки не анализируется ассемблером.
Для облегчения читаемости ассемблерных текстов принято, что метка начинается на первой позиции в строке, команда — на 17-й (две табуляции), операнды — на 25-й (три табуляции) и комментарии — на 41-й или 49-й. Если строка состоит только из комментария, его начинают с первой позиции.
Если метка располагается перед командой процессора, сразу после нее всегда ставится символ «:» (двоеточие), который указывает ассемблеру, что надо создать переменную с этим именем, содержащую адрес текущей команды:
some_loop: lodsw ; cчитать слово из строки, cmp ax,7 ; если это 7 - выйти из цикла loopne some_loop
Когда метка стоит перед директивой ассемблера, она обычно оказывается одним из операндов этой директивы и двоеточие не ставится. Рассмотрим директивы, работающие напрямую с метками и их значениями, — LABEL, EQU и =.
метка label тип
Директива LABEL определяет метку и задает ее тип. Тип может быть одним из: BYTE (байт), WORD (слово), DWORD (двойное слово), FWORD (6 байт), QWORD (учетверенное слово), TBYTE (10 байт), NEAR (ближняя метка), FAR (дальняя метка). Метка получает значение, равное адресу следующей команды или следующих данных, и тип, указанный явно. В зависимости от типа команда
mov метка,0
Директива STRUC позволяет определить структуру данных аналогично структурам в языках высокого уровня. Последовательность директив
имя struc поля
имя ends
где поля — любой набор псевдокоманд определения переменных или структур, устанавливает, но не инициализирует структуру данных. В дальнейшем для ее создания в памяти используют имя структуры как псевдокоманду:
метка имя <значения>
И наконец, для чтения или записи в элемент структуры используется оператор «.» (точка). Например:
point struc ; Определение структуры x dw 0 ; Три слова со значениями y dw 0 ; по умолчанию 0,0,0 z dw 0 color db 3 dup(?) ; и три байта point ends
cur_point point <1,1,1,255,255,255> ; Инициализация mov ax,cur_point.x ; Обращение к слову "x"
Если была определена вложенная структура, доступ к ее элементам осуществляется через еще один оператор «.» (точка).
color struc ; Определить структуру color. red db ? green db ? blue db ? color ends
point struc x dw 0 y dw 0 z dw 0 clr color <> point ends
cur_point point <> mov cur_point.clr.red,al ; Обращение к красной компоненте ; цвета точки cur_point.
INCLUDE имя_файла — директива, вставляющая в текст программы текст файла аналогично команде препроцессора C #include. Обычно используется для включения файлов, содержащих определения констант, структур и макросов.
INCLUDELIB имя_файла — директива, указывающая компоновщику имя дополнительной библиотеки или объектного файла, который потребуется при составлении данной программы. Например, если используются вызовы процедур или обращение к данным, определенным в других модулях. Использование этой директивы позволяет не указывать имена дополнительных библиотек при вызове компоновщика.
Обычно ассемблеры, помимо создания объектного файла, предоставляют возможность создания листинга программы (TASM /L — для TASM, ml /Fl — для MASM). Листинг — это файл, содержащий текст ассемблерной программы, код каждой ассемблированной команды, список определенных меток, перекрестных ссылок, сегментов и групп. Формат файла листинга отличается для разных ассемблеров, и директивы управления форматом этого файла также сильно различаются, но несколько наиболее общих директив все-таки поддерживаются всеми тремя ассемблерами, рассмотренными в этой книге.
TITLE текст — определяет заголовок листинга. Заголовок появляется в начале каждой страницы;
SUBTTL текст — определяет подзаголовок листинга. Подзаголовок появляется на следующей строке после заголовка;
PAGE высота,ширина — устанавливает размеры страниц листинга (высота 10—255, ширина 59—255). Директива PAGE без аргументов начинает новую страницу, директива PAGE + начинает новую секцию, и нумерация страниц ведется с самого начала;
NAME текст — определяет имя модуля программы. Если NAME не указан, в качестве имени используются первые 6 символов из TITLE; если нет ни NAME, ни TITLE, за имя берется название файла;
.XLIST — отменить выдачу листинга;
.LIST — разрешить выдачу листинга;
.SALL — запретить листинг макроопределений;
.SFCOND — запретить листинг неассемблированных условных блоков;
.LFCOND — разрешить листинг неассемблированных условных блоков;
.TFCOND — изменить режим листинга условных блоков на противоположный;
.CREF — разрешить листинг перекрестных ссылок;
.XCREF — запретить листинг перекрестных ссылок.
В большинстве языков программирования присутствуют средства, позволяющие игнорировать тот или иной участок программы в зависимости от выполнения условий, например: в языке C это осуществляется командами препроцессора #if, #ifdef, #ifndef и т.д. Ассемблер тоже предоставляет такую возможность.
if выражение ... endif
Если значение выражения — ноль (ложь), весь участок программы между IF и ENDIF игнорируется. Директива IF может также сочетаться с ELSE и ELSEIF:
if выражение ... else ... endif
Если значение выражения — ноль, ассемблируется участок программы от ELSE до ENDIF, в противном случае — от IF до ELSE.
if выражение1 ... elseif выражение2 ... elseif выражение3 ... else ... endif
Так, если, например, выражение 2 не равно нулю, будет ассемблироваться участок программы между первой и второй директивой ELSEIF. Если все три выражения равны нулю, ассемблируется фрагмент от ELSE до ENDIF. Данная структура директив может использоваться в частном случае аналогично операторам switch/case языков высокого уровня, если выражения — проверки некоторой константы на равенство.
Кроме общих директив IF и ELSEIF ассемблеры поддерживают набор специальных команд, каждая из которых проверяет специальное условие:
IF1/ELSEIF1 — если ассемблер выполняет первый проход ассемблирования;
IF2/ELSEIF2 — если ассемблер выполняет второй проход ассемблирования (часто не работает на современных ассемблерах);
IFE выражение/ELSEIFE выражение — если выражение равно нулю (ложно);
IFDEF метка/ELSEIFDEF метка — если метка определена;
IFNDEF метка/ELSEIFNDEF метка — если метка не определена;
IFB <аргумент>/ELSEIFB <аргумент> — если значение аргумента — пробел (эти и все следующие директивы используются в макроопределениях для проверки параметров);
IFNB <аргумент>/ELSEIFNB <аргумент> — если значение аргумента — не пробел (используется в макроопределениях для проверки переданных параметров);
Мы уже упоминали выражения при описании многих директив ассемблера. Выражение — это набор чисел, меток или строк, связанных друг с другом операторами. Например: 2 + 2 — выражение, состоящее из двух чисел (2 и 2) и оператора +. Каждое выражение имеет значение, которое определяется как результат действия операторов. Так, значение выражения 2 + 2 — число 4. Все выражения вычисляются в ходе ассемблирования программы, следовательно, в полученном коде используются только значения.
Оператор <> (угловые скобки). Часть выражения, заключенная в угловые скобки, не вычисляется, а применяется как строка символов, например:
message1 equ <foobar>
Оператор () (круглые скобки). Часть выражения, заключенная в круглые скобки, вычисляется в первую очередь.
mov al, 2*(3+4) ; mov al,14
Арифметические операторы: + (плюс), – (минус), * (умножение), / (целочисленное деление), MOD (остаток от деления). Они выполняют соответствующие арифметические действия.
mov al,90 mod 7 ; mov al,6
Кроме того, к арифметическим операторам относится унарный минус — минус, который ставят перед отрицательным числом.
Логические операторы: AND (И), NOT (НЕ), OR (ИЛИ), XOR (исключающее ИЛИ), SHL (сдвиг влево), SHR (сдвиг вправо). Эти операторы выполняют соответствующие логические действия.
mov ax,1234h AND 4321h ; mov ax,0220h
Операторы сравнения: EQ (равно), GE (больше или равно), GT (больше), LE (меньше или равно), LT (меньше), NE (не равно). Результат действия каждого из этих операторов — единица, если условие выполняется, и ноль — если не выполняется.
.errnz $ gt 65535 ; Если адрес больше 64 Кб – ошибка
Операторы адресации:
SEG выражение — сегментный адрес;
OFFSET выражение — смещение;
THIS тип — текущий адрес (MASM и TASM);
Тип PTR выражение — переопределение типа;
LARGE выражение — 32-битное смещение (TASM и WASM);
SMALL выражение — 16-битное смещение (TASM и WASM);
Функция DOS 3Fh — Чтение из файла или устройства
Ввод: | АН = 3Fh ВХ = идентификатор СХ = число байт DS:DX = адрес буфера для приема данных |
Вывод: | CF = 0 и АХ = число считанных байт, если не произошла ошибка CF = 1 и АХ = 05h, если доступ запрещен, 06h, если неправильный идентификатор |
Если при чтении из файла число фактически считанных байт в АХ меньше, чем заказанное число в СХ, при чтении был достигнут конец файла. Каждая следующая операция чтения, так же как и записи, начинается не с начала файла, а с того байта, на котором остановилась предыдущая операция чтения/записи. Если требуется считать (или записать) произвольный участок файла, используют функцию 42h (функция lseek в С).
Функция DOS 42h — Переместить указатель чтения/записи
Ввод: | АН = 42h ВХ = идентификатор CX:DX = расстояние, на которое надо переместить указатель (со знаком) AL = перемещение от:0 — от начала файла 1 — от текущей позиции 2 — от конца файла |
Вывод: | CF = 0 и CX:DX = новое значение указателя (в байтах от начала файла), если не произошла ошибка CF = 1 и АХ = 06h, если неправильный идентификатор |
Указатель можно установить за реальными пределами файла: если указатель устанавливается в отрицательное число, следующая операция чтения/записи вызовет ошибку; если указатель устанавливается в положительное число, большее длины файла, следующая операция записи увеличит размер файла. Эта функция также часто используется для определения длины файла — достаточно вызвать ее с СХ = 0, DX = 0, AL = 2, и в CX:DX будет возвращена длина файла в байтах.
Функция DOS 40h — Запись в файл или устройство
Ввод: | АН = 40h ВХ = идентификатор СХ = число байт DS:DX = адрес буфера с данными |
Вывод: | CF = 0 и АХ = число записанных байт, если не произошла ошибка CF = 1 и АХ = 05h, если доступ запрещен, 06h, если неправильный идентификатор |
Если при записи в файл указать СХ = 0, файл будет обрезан по текущему значению указателя. При записи в файл на самом деле происходит запись в буфер DOS, данные из которого сбрасываются на диск при закрытии файла или если их количество превышает размер сектора диска. Для немедленного сброса буфера можно использовать функцию 68h (функция fflush в С).
Ввод: | АН = 68h ВХ = идентификатор |
Вывод: | CF = 0, если операция выполнена CF = 1, если произошла ошибка (АХ = код ошибки) |
Ввод: | АН = 0Dh |
Вывод: | никакого |
Дополнительная память (EMS) — способ для программ, запускающихся в реальном режиме (или в режиме V86), обращаться к памяти, находящейся за пределами первого мегабайта. EMS позволяет отобразить сегмент памяти, начинающийся обычно с 0D000h, на любые участки памяти, аналогично тому, как осуществляется доступ к видеопамяти в SVGA-режимах. Вызывать функции EMS (прерывание 67h) можно, только если в системе присутствует драйвер с именем ЕММХХХХ0. Для проверки его существования можно, например, вызвать функцию 3Dh (открыть файл или устройство), причем на тот случай, если драйвер EMS отсутствует, а в текущей директории есть файл с именем ЕММХХХХ0, следует дополнительно вызвать функцию IOCTL — INT 21h с АХ = 4400h и ВХ = идентификатор файла или устройства, полученный от функции 3Dh. Если значение бита 7 в DX после вызова этой функции 1, то драйвер EMS наверняка присутствует в системе.
Основные функции EMS:
INT 67h, АН = 46h — Получить номер версии
Ввод: | AH = 46h |
Вывод: | АН = 0 и AL = номер версии в упакованном BCD (40h для 4.0) |
Во всех случаях, если АН не ноль, произошла ошибка.
INT 67h, АН = 41h — Получить сегментный адрес окна
Ввод: | AH = 41h |
Вывод: | АН = 0 и ВХ = сегментный адрес окна |
INT 67h, АН = 42h — Получить объем памяти
Ввод: | AH = 42h |
Вывод: | АН = 0 DX = объем EMS-памяти в 16-килобайтных страницах ВХ = объем свободной EMS-памяти в 16-килобайтных страницах |
INT 67h, АН = 43h — Выделить идентификатор и EMS-память
Ввод: | АН = 43h ВХ = требуемое число 16-килобайтных страниц |
Вывод: | АН = 0, DX = идентификатор |
Теперь указанный в этой функции набор страниц в EMS-памяти описывается как занятый и другие программы не смогут его выделить для себя.
INT 67h, АН = 44h — Отобразить память
Ввод: | АН = 44h AL = номер 16-килобайтной страницы в 64-килобайтном окне EMS (0 – 3) ВХ = номер 16-килобайтной страницы в EMS-памяти DX = идентификатор |
Вывод: | АН = 0 |
Ввод: | АН = 45h DX = идентификатор |
Вывод: | АH = 00h |
Спецификация доступа к дополнительной памяти (XMS) — еще один метод, позволяющий программам, запускающимся под управлением DOS в реальном режиме (или в режиме V86), использовать память, расположенную выше границы первого мегабайта.
INT 2Fh, АН = 43 — XMS- и DPMS-сервисы
Ввод: | АХ = 4300h: проверить наличие XMS |
Вывод: | АН = 80h, если HIMEM.SYS или совместимый драйвер загружен |
Ввод: | АХ = 4310h: получить точку входа XMS |
Вывод: | ES:BX = дальний адрес точки входа XMS |
После определения точки входа все функции XMS вызываются с помощью команды CALL на указанный дальний адрес.
Функция XMS 00h — Определить номер версии
Ввод: | AH = 00h |
Вывод: | АХ = номер версии, не упакованный BCD (0300h для 3.0) ВХ = внутренний номер модификации DX = 1, если HMA существует, 0, если нет |
Функция XMS 08h — Определить объем памяти
Ввод: | АН = 08h BL = 00h |
Вывод: | АХ = размер максимального доступного блока в килобайтах DX = размер всей XMS-памяти в килобайтах BL = код ошибки (A0h, если вся XMS-память занята, 00, если нет ошибок) |
Так как возвращаемый размер памяти оказывается ограниченным размером слова (65 535 Кб), начиная с версии XMS 3.0, введена более точная функция 88h.
Функция XMS 88h — Определить объем памяти
Ввод: | AH = 88h |
Вывод: | ЕАХ = размер максимального доступного блока в килобайтах BL = код ошибки (A0h, если вся XMS-память занята, 00 — если нет ошибок) ЕСХ = физический адрес последнего байта памяти (верный для ошибки А0h) EDX = размер XMS-памяти всего в килобайтах (0 для ошибки А0h) |
Функция XMS 09h — Выделить память
Ввод: | АН = 09h DX = размер запрашиваемого блока (в килобайтах) |
Вывод: | АХ = 1, если функция выполнена ВХ = идентификатор блока АХ = 0: BL = код ошибки (A0h, если не хватило памяти) |
Функция XMS 0Ah — Освободить память
Ввод: | АН = 0Ah DX = идентификатор блока |
Вывод: | АХ = 1, если функция выполнена Иначе — АХ = 0 и BL = код ошибки (A2h — неправильный идентификатор, ABh — участок заблокирован) |
Ввод: | АН = 0Bh DS:SI = адрес структуры для пересылки памяти |
Вывод: | АХ = 1, если функция выполнена Иначе — АХ = 0 и BL = код ошибки |
Ввод: | АН = 0Fh ВХ = новый размер DX = идентификатор блока |
Вывод: | АХ = 1, если функция выполнена Иначе — АХ = 0 и BL = код ошибки |
В случае если команда не передавалась бы интерпретатору DOS, а выполнялась нами самостоятельно, то оказалось бы: чтобы запустить любую программу из-под shell.com, потребовалось бы предварительно переходить в каталог с этой программой или вводить ее с полным путем. Дело в том, что COMMAND.COM при запуске файла ищет его по очереди в каждом из каталогов, указанных в переменной среды PATH. DOS создает копию всех переменных среды (так называемое окружение DOS) для каждого запускаемого процесса. Сегментный адрес копии окружения для текущего процесса располагается в PSP по смещению 2Ch. В этом сегменте записаны все переменные подряд в форме ASCIZ-строк вида "COMSPEC=C:/WINDOWS/COMMAND.COM",0. По окончании последней строки стоит дополнительный нулевой байт, затем слово (обычно 1) — количество дополнительных строк окружения, а потом — дополнительные строки. Первая дополнительная строка — всегда полный путь и имя текущей программы — также в форме ASCIZ-строки. При запуске новой программы с помощью функции 4Bh можно создать полностью новое окружение и передать его сегментный адрес запускаемой программе в блоке ЕРВ или просто указать 0, позволив DOS скопировать окружение текущей программы.
Кроме того, в предыдущем примере мы передавали запускаемой программе (command.com) параметры (/с команда), но пока не объяснили, как программа может определить, что за параметры были переданы ей при старте. При запуске программы DOS помещает всю командную строку (включая последний символ 0Dh) в блок PSP запущенной программы по смещению 81h и ее длину в байт 80h (таким образом, длина командной строки не может быть больше 7Eh (126) символов). Под Windows 95 и 4DOS, если командная строка превышает эти размеры, байт PSP:0080h (длина) устанавливается в 7Fh, в последний байт PSP (PSP:00FFh) записывается 0Dh, первые 126 байт командной строки размещаются в PSP, а вся строка целиком — в переменной среды CMDLINE.
; cat.asm ; копирует объединенное содержимое всех файлов, указанных в командной строке, ; в стандартный вывод. Можно как указывать список файлов, так и использовать ; маски (символы "*" и "?") в одном или нескольких параметрах, ; например: ; cat header *.txt footer > all-texts помещает содержимое файла ; header, всех файлов с расширением .txt в текущем каталоге и файла ; footer - в файл all-texts ; длинные имена файлов не используются, ошибки игнорируются ; .model tiny .code org 80h ; по смещению 80h от начала PSP находятся: cmd_length db ? ; длина командной строки cmd_line db ? ; и сама командная строка org 100h ; начало СОМ-программы - 100h от начала PSP start: cld ; для команд строковой обработки mov bp,sp ; сохранить текущую вершину стека в ВР mov cl,cmd_length cmp cl,1 ; если командная строка пуста - jle show_usage ; вывести информацию о программе и выйти
Область памяти от FFFFh:0010h (конец первого мегабайта) до FFFFh:FFFFh (конец адресного пространства в реальном режиме), 65 520 байт, может использоваться на компьютерах, начиная с 80286. Доступ к этой области осуществляется с помощью спецификации XMS, причем вся она выделяется целиком одной программе. Обычно, если загружен драйвер HIMEM.SYS и если в файле CONFIG.SYS присутствует строка DOS = HIGH, DOS занимает эту область, освобождая почти 64 Кб в основной памяти. При этом DOS может оставить небольшой участок HMA (16 Кб или меньше) для пользовательских программ, которые обращаются к нему с помощью недокументированной функции мультиплексора 4Ah.
INT 2Fh, AX= 4A01h — Определить размер доступной части HMA (DOS 5.0+)
Ввод: | АХ = 4A01h |
Вывод: | ВХ = размер доступной части HMA в байтах, 0000h, если DOS не в HMA ES:DI = адрес начала доступной части НМА (FFFFh:FFFFh, если DOS не в НМА) |
INT 2Fh, АХ= 4A02h — Выделить часть НМА (DOS 5.0+)
Ввод: | АХ = 4А02h ВХ = размер в байтах |
Вывод: | ES:DI = адрес начала выделенного блока ВХ = размер выделенного блока в байтах |
В версиях DOS 5.0 и 6.0 нет функций освобождения выделенных таким образом блоков НМА. В DOS 7.0 (Windows 95) выделение памяти в НМА было организовано аналогично выделению памяти в обычной памяти и UMB, с функциями изменения размера и освобождения блока.
INT 2Fh, АХ = 4A03h — Управление распределением памяти в НМА (DOS 7.0+)
Ввод: | АХ = 4A03h DL = 0 — выделить блок (ВХ = размер в байтах) DL = 1 — изменить размер блока (ES:DI = адрес, ВХ = размер) DL = 2 — освободить блок (ES:DI = адрес) СХ = сегментный адрес владельца блока |
Вывод: | DI = FFFFh, если не хватило памяти, ES:DI = адрес блока (при выделении) |
Следует помнить, что область НМА доступна для программ только в том случае, если адресная линия процессора А20 разблокирована. Если DOS не занимает НМА, она почти всегда заблокирована для совместимости с программами, написанными для процессора 8086/8088, которые считают, что адреса FFFFh:0010h — FFFFh:FFFFh всегда совпадают с 0000h:0000h — 0000h:FFEFh. Функции XMS 01 – 07 предоставляют возможность управления состоянием этой адресной линии. | |||
Функция DOS 58h — Считать/изменить стратегию выделения памяти
Ввод: | АН = 58h AL = 00h — считать стратегию AL = 01h — изменить стратегию ВХ = новая стратегия биты 2 – 0: 00 — первый подходящий блок 01 — наиболее подходящий блок 11 — последний подходящий блок биты 4 – 3: 00 — обычная память 01 — UMB (DOS 5.0+) 10 — UMB, затем обычная память (DOS 5.0+) AL = 02h — считать состояние UMB |
Вывод: | CF = 0, АХ = текущая стратегия для AL = 0, состояние UMB для AL = 2 CF = 1, AX = 01h, если функция не поддерживается (если не запущен менеджер памяти (например, EMM386) или нет строки DOS = UMB в CONFIG.SYS |
Если программа изменяла стратегию выделения памяти или состояние UMB, она обязательно должна их восстановить перед окончанием работы.
До сих пор, если требовалось создать массив данных в памяти, мы просто обращались к памяти за концом программы, считая, что там есть еще хотя бы 64 килобайта свободной памяти. Разумеется, как и во всех операционных системах, в DOS есть средства управления распределением памяти — выделение блока (аналог стандартной функции языка С malloc), изменение его размеров (аналог realloc) и освобождение (free).
Функция DOS 48h — Выделить память
Ввод: | АН = 48h ВХ = размер блока в 16-байтных параграфах |
Вывод: | CF = 0, если блок выделен АХ = сегментный адрес выделенного блока CF = 1, если произошла ошибка:АХ = 7 — блоки управления памятью разрушены АХ = 8 — недостаточно памяти:ВХ = размер максимального доступного блока |
Эта функция с большим значением в ВХ (обычно FFFFh) используется для определения размера самого большого доступного блока памяти.
Функция DOS 49h — Освободить память
Ввод: | АН = 49h ES = сегментный адрес освобождаемого блока |
Вывод: | CF = 0, если блок освобожден CF = 1:АХ = 7, если блоки управления памятью разрушены, АХ = 9, если в ES содержится неверный адрес |
Эта функция не позволит освободить блок памяти, которым текущая программа не владеет, но с помощью функции DOS 50h (AX = 50h, ВХ = сегментный адрес PSP процесса) программа может «притвориться» любым другим процессом.
Функция DOS 4Ah — Изменить размер блока памяти
Ввод: | АН = 4Ah ВХ = новый размер в 16-байтных параграфах ES = сегментный адрес модифицируемого блока |
Вывод: | CF = 0, если размер блока изменен CF = 1: АХ = 7, если блоки управления памятью разрушены, ВХ = максимальный размер, доступный для этого блока |
Если для увеличения блока не хватило памяти, DOS увеличивает его до возможного предела.
При запуске СОМ-программы загрузчик DOS выделяет самый большой доступный блок памяти для этой программы, так что при работе с основной памятью эти функции требуются редко (в основном для того, чтобы сократить выделенный программе блок памяти до минимума перед загрузкой другой программы), но уже в MS-DOS 5.0 и далее с помощью этих же функций можно выделять память в областях UMB — неиспользуемых участках памяти выше 640 Кб и ниже 1 Мб, для этого требуется сначала подключить UMB к менеджеру памяти и изменить стратегию выделения памяти с помощью функции DOS 58h.
Программа, написанная на ассемблере, так же как и программа, написанная на любом другом языке программирования, выполняется не сама по себе, а при помощи операционной системы. Операционная система выделяет области памяти для программы, загружает ее, передает ей управление и обеспечивает взаимодействие программы с устройствами ввода-вывода, файловыми системами и другими программами (разумеется, кроме тех случаев, когда эта программа сама является операционной системой или ее частью). Способы взаимодействия программы с внешним миром различны для разных операционных систем, так что программа, написанная для Windows, не будет работать в DOS, а программа для Linux — в Solaris/x86, хотя все эти системы могут работать на одном и том же компьютере.
Самая простая и распространенная операционная система для компьютеров, основанных на процессорах Intel, — DOS (Дисковая Операционная Система). Она распространяется как сама по себе несколькими производителями — Microsoft (MS-DOS), IBM (PC-DOS), Novell (Novell DOS), Caldera (Open DOS) и др., так и в виде части систем Microsoft Windows 95 и старше. DOS предоставляет программам полную свободу действий, никак не ограничивая доступ к памяти и внешним устройствам, позволяя им самим управлять процессором и распределением памяти. По этой причине DOS лучше всего подходит для того, чтобы близко познакомиться с устройством компьютера и возможностями, которые может использовать программа на ассемблере, но которые часто скрываются компиляторами с языков высокого уровня и более совершенными операционными системами.
Итак, чтобы программа выполнилась любой ОС, она должна быть скомпилирована в исполнимый файл. Основные два формата исполнимых файлов в DOS — СОМ и ЕХЕ. Файлы типа СОМ содержат только скомпилированный код без какой-либо дополнительной информации о программе. Весь код, данные и стек такой программы располагаются в одном сегменте и не могут превышать 64 килобайта. Файлы типа ЕХЕ содержат заголовок, в котором описывается размер файла, требуемый объем памяти, список команд в программе, использующих абсолютные адреса, которые зависят от расположения программы в памяти, и т.д. ЕХЕ-файл может иметь любой размер. Формат ЕХЕ также используется для исполнимых файлов в различных версиях DOS-расширителей и Windows, но со значительными изменениями.
Несмотря на то что файлам типа СОМ принято давать расширение .com, а файлам типа EXE — .exe, DOS не использует расширения для определения типа файла. Первые два байта заголовка ЕХЕ-файла — символы «MZ» или «ZM», и если файл начинается с этих символов и длиннее некоторого порогового значения, разного для разных версий DOS, он загружается как ЕХЕ, если нет — как СОМ. | |||
Кроме обычных исполнимых программ DOS может загружать драйверы устройств — специальные программы, используемые для упрощения доступа к внешним устройствам. Например, драйвер устройства LPT, входящий в IO.SYS, позволяет посылать тексты на печать из DOS простым копированием файла в LPT, а драйвер RAMDISK.SYS позволяет выделить область памяти и обращаться к ней, как к диску. Написание драйверов значительно сложнее, чем написание обычных программ, и рассмотрено далее.
Параллельные порты используются в первую очередь для подключения принтеров, хотя встречаются и другие устройства, например переносные жесткие диски, которые могут подключаться к этим портам. Базовые средства DOS и BIOS для работы с параллельными портами аналогичны соответствующим средствам для работы с последовательными портами: DOS инициализирует стандартное устройство PRN, соответствующее первому порту LPT1, которое может быть переопределено командой MODE, и предоставляет прерывание для вывода в это устройство.
Функция DOS 05h — Вывод символа в стандартное устройство PRN
Ввод: | АН = 05h DL = символ |
Кроме того, можно пользоваться функцией записи в файл или устройство, поместив в ВХ число 4, соответствующее устройству PRN. BIOS, в свою очередь, предоставляет базовый набор из трех функций для работы с принтером.
INT 17h, АН = 00 — Вывести символ в принтер
Ввод: | АН = 00h AL = символ DX = номер параллельного порта (00 — LPT1, 01 — LPT2, 02 — LPT3) |
Вывод: | АН = состояние принтера:
бит 7: принтер не занят бит 6: подтверждение бит 5: нет бумаги бит 4: принтер в состоянии on-line бит 3: ошибка ввода-вывода бит 0: тайм-аут |
INT 17h, АН = 01 — Выполнить аппаратный сброс принтера
Ввод: | АН = 01h DX = номер порта (00h — 02h) |
Вывод: | АН = состояние принтера |
INT 17h, AH = 02 — Получить состояние принтера
Ввод: | АН = 02h DX = номер порта (00h – 02h) |
Вывод: | АН = состояние принтера |
Например, чтобы распечатать содержимое экрана на принтере, можно написать такую программу:
; prtscr.asm ; распечатать содержимое экрана на принтере ; .model tiny .code .186 ; для команды push 0B800h org 100h ; начало СОМ-файла start: mov ah,1 mov dx,0 ; порт LPT1 int 17h ; инициализировать принтер, cmp ah,90h ; если принтер не готов, jne printer_error ; выдать сообщение об ошибке, push 0B800h ; иначе: pop ds ; DS = сегмент видеопамяти в текстовом режиме xor si,si ; SI = 0 mov cx,80*40 ; CX = число символов на экране cld ; строковые операции вперед main_loop: lodsw ; AL - символ, АН - атрибут, SI = SI + 2 mov ah,0 ; АН - номер функции int 17h ; вывод символа из AL на принтер loop main_loop ret ; закончить программу
printer_error: mov dx,offset msg ; адрес сообщения об ошибке в DS:DX mov ah,9 int 21h ; вывод строки на экран ret
msg db "Принтер на LPT1 находится в состоянии offline или занят$" end start
Чтобы распечатать экран в текстовом режиме на LPT1, достаточно всего лишь одной команды INT 05h, что в точности эквивалентно нажатию клавиши PrtScr.
Найти нужный файл на диске намного сложнее, чем просто открыть его, — для этого требуются две функции при работе с короткими именами (найти первый файл и найти следующий файл) и три — при работе с длинными именами в DOS 7.0 (найти первый файл, найти следующий файл, прекратить поиск).
Функция DOS 4Eh — Найти первый файл
Ввод: | АН = 4Eh AL используется при обращении к функции APPEND СХ = атрибуты, которые должен иметь файл (биты 0 (только для чтения) и 5 (архивный бит) игнорируются, если бит 3 (метка тома) установлен, все остальные биты игнорируются) DS:DX = адрес ASCIZ-строки с именем файла, которое может включать путь и маски для поиска (символы * и ?) |
Вывод: | CF = 0 и область DTA заполняется данными, если файл найден CF = 1 и АХ = 02h, если файл не найден, 03h — если путь не найден, 12h — если неправильный режим доступа |
Вызов этой функции заполняет данными область памяти DTA (область передачи данных), которая начинается по умолчанию со смещения 0080h от начала блока данных PSP (при запуске СОМ- и ЕХЕ-программ сегменты DS и ES содержат сегментный адрес начала PSP), но ее можно переопределить с помощью функции 1Ah.
Функция DOS 1Ah — Установить область DTA
Ввод: | АН = 1Ah DS:DX = адрес начала DTA (128-байтный буфер) |
Функции поиска файлов заполняют DTA следующим образом:
+00h: байт — биты 0 – 6: ASCII-код буквы диска; бит 7: диск сетевой
+01h: 11 байт — маска поиска (без пути)
+0СН: байт — атрибуты для поиска
+0Dh: слово — порядковый номер файла в каталоге
+0Fh: слово — номер кластера начала внешнего каталога
+11h: 4 байта — зарезервировано
+15h: байт — атрибут найденного файла
+16h: слово — время создания файла в формате DOS:
биты 15 – 11: час (0 — 23)
биты 10 – 5: минута
биты 4 – 0: номер секунды, деленный на 2 (0 – 30)
Ввод: | АН = 4Fh DTA — содержит данные от предыдущего вызова функции 4Е или 4F |
Вывод: | CF = 0 и DTA содержит данные о следующем найденном файле, если не произошла ошибка CF = 1 и АХ = код ошибки, если произошла ошибка |
Ввод: | АХ = 714Eh CL = атрибуты, которые файл может иметь (биты 0 и 5 игнорируются) СН = атрибуты, которые файл должен иметь SI = 0: использовать Windows-формат даты/времени SI = 1: использовать DOS-формат даты/времени DS:DX = адрес ASCIZ-строки с маской для поиска (может включать * и ?. Для совместимости маска *.* ищет все файлы в каталоге, а не только файлы, содержащие точку в имени) ES:DI = адрес 318-байтного буфера для информации о файле |
Вывод: | CF = 0 АХ = поисковый идентификатор СХ = Unicode-флаг: бит 0: длинное имя содержит подчеркивания вместо непреобразуемых Unicode-символов бит 1: короткое имя содержит подчеркивания вместо непреобразуемых Unicode-символовCF = 1, АХ = код ошибки, если произошла ошибка (7100h — функция не поддерживается) |
Ввод: | АХ = 714Fh ВХ = поисковый идентификатор (от функции 4Eh) SI = формат даты/времени ES:DI = адрес буфера для информации о файле |
Вывод: | CF = 0 и СХ = Unicode-флаг, если следующий файл найден CF = 1, АХ = код ошибки, если произошла ошибка (7100h — функция не поддерживается) |
Ввод: | АХ = 71A1h ВХ = поисковый идентификатор |
Вывод: | CF = 0, если операция выполнена CF = 1 и АХ = код ошибки, если произошла ошибка (7100h — функция не поддерживается) |
Каждый компьютер обычно оборудован, по крайней мере, двумя последовательными портами, которые чаще всего используются для подключения мыши и модема, но также могут использоваться и для подключения других дополнительных устройств или соединения компьютеров между собой. Для работы с устройствами, подключенными к портам, такими как мышь, используются драйверы, которые общаются с последовательным портом непосредственно на уровне портов ввода-вывода и предоставляют программам некоторый набор функций более высокого уровня, так что прямая работа с последовательными портами оказывается необходимой только при написании таких драйверов, работе с нестандартными устройствами или с модемами.
DOS всегда инициализирует первый порт СОМ1 как 2400 бод, 8N1 (8 бит в слове, 1 стоп-бит, четность не проверяется) и связывает с ним устройство STDAUX. В это устройство можно записывать и считывать один байт функциями 3 и 4.
Функция DOS 03h — Считать байт из STDAUX
Ввод: | АН = 03h |
Вывод: | AL = считанный байт |
Функция DOS 04h — Записать байт в STDAUX
Ввод: | АН = 04h DL = байт |
Или же можно воспользоваться функциями записи в файл (40h) и чтения из файла (3Fh), поместив в ВХ число 3, как это было показано ранее для вывода на экран.
Хотя установленную DOS скорость работы порта (2400 бод) и можно изменить командой MODE, все равно отсутствие обработки ошибок, буферизации и гибкого управления состоянием порта делает эти функции DOS практически неприменимыми. BIOS позволяет управлять любым из портов, писать и читать один байт и считывать состояние порта с помощью функций прерывания 14h, но BIOS (так же как и DOS) не позволяет инициализировать порт на скорость выше, чем 9600 бод. Таким образом выясняется, что большинство программ вынуждено программировать порты напрямую, но, если в системе присутствует драйвер, предоставляющий набор сервисов FOSSIL (такие как Х00 или BNU), оказывается возможным пользоваться для полноценного буферированного обмена данными с последовательными портами только функциями прерывания 14h.
Ввод: | АН = 04h DX = номер порта (0 — для СОМ1, 1 — для COM2 и т.д.) |
Вывод: | АХ = 1954h BL = максимальный поддерживаемый номер функции ВН = версия спецификации FOSSIL |
Ввод: | АН = 05h DX = номер порта (00h – 03h) |
Ввод: | АН = 00h AL = параметры инициализации: биты 7 – 5: 000 — 19 200 бод (110 бод без FOSSIL) 001 — 38 400 бод (150 бод без FOSSIL) 010 — 300 бод 011 — 600 бод 100 — 1200 бод 101 — 2400 бод 110 — 4800 бод 111 — 9600 бод биты 4 – 3: четность (01 — четная, 11 — нечетная, 00 или 10 — нет) бит 2: число стоп-бит (0 — один, 1 — два) биты 1 – 0: длина слова (00 — 5, 01 — 6, 10 — 7, 11 — 8) DX = номер порта (00h – 03h) |
Вывод: | АН = состояние порта бит 7: тайм-аут бит 6: буфер вывода пуст ( без FOSSIL: регистр сдвига передатчика пуст) бит 5: в буфере вывода есть место (без FOSSIL: регистр хранения передатчика пуст) бит 4: обнаружено состояние BREAK бит 3: ошибка синхронизации бит 2: ошибка четности бит 1: ошибка переполнения — данные потеряны бит 0: в буфере ввода есть данные AL = состояние модема бит 7: обнаружена несущая (состояние линии DCD) бит 6: обнаружен звонок (состояние линии RI) бит 5: запрос для передачи (состояние линии DSR) бит 4: сброс для передачи (состояние линии CTS) бит 3: линия DCD изменила состояние бит 2: линия RI изменила состояние бит 1: линия DSR изменила состояние бит 0: линия CTS изменила состояние |
Ввод: | АН = 01h AL = символ DX = номер порта (00h – 03h) |
Вывод: | АН = состояние порта |
Ввод: | АН = 02h DX = номер порта |
Вывод: | АН = состояние порта AL = считанный символ, если бит 7 АН равен нулю (не было тайм-аута) |
Ввод: | АН = 03h DX = номер порта (00h – 03h) |
Вывод: | АН = состояние линии AL = состояние модема |
Все, что изображено на мониторе — и графика, и текст, одновременно присутствует в памяти, встроенной в видеоадаптер. Для того чтобы изображение появилось на мониторе, оно должно быть записано в память видеоадаптера. Для этого отводится специальная область памяти, начинающаяся с абсолютного адреса B800h:0000h (для текстовых режимов) и заканчивающаяся на B800h:FFFFh. Все, что программы пишут в эту область памяти, немедленно пересылается в память видеоадаптера. В текстовых режимах для хранения каждого изображенного символа используются два байта: байт с ASCII-кодом символа и байт с его атрибутом, так что по адресу B800h:0000h лежит байт с кодом символа, находящимся в верхнем левом углу экрана; по адресу B800h:0001h лежит атрибут этого символа; по адресу B800h:0002h лежит код второго символа в верхней строке экрана и т.д.
Таким образом, любая программа может вывести текст на экран простой командой пересылки данных, не прибегая ни к каким специальным функциям DOS или BIOS.
; dirout.asm ; Выводит на экран все ASCII-символы без исключения, ; используя прямой вывод на экран .model tiny .code .386 ; будет использоваться регистр ЕАХ ; и команда STOSD org 100h ; начало СОМ-файла start: mov ax,0003h int 10h ; видеорежим 3 (очистка экрана) cld ; обработка строк в прямом направлении ; подготовка данных для вывода на экран mov еах,1F201F00h ; первый символ 00 с атрибутом 1Fh, ; затем пробел (20h) с атрибутом 1Fh mov bx,0F20h ; пробел с атрибутом 0Fh mov cx,255 ; число символов минус 1 mov di,offset ctable ; ES:DI - начало таблицы cloop: stosd ; записать символ и пробел в таблицу ctable inc al ; AL содержит следующий символ test cx,0Fh ; если СХ не кратен 16, jnz continue_loop ; продолжить цикл, push cx ; иначе: сохранить значение счетчика mov cx,80-32 ; число оставшихся до конца строки символов xchg ax,bx rep stosw ; заполнить остаток строки пробелами ; с атрибутом 0F xchg bx.ax ; восстановить значение ЕАХ pop cx ; восстановить значение счетчика continue_loop: loop cloop
ЕХЕ-программы немного сложнее в исполнении, но для них отсутствует ограничение размера в 64 килобайта, так что все достаточно большие программы используют именно этот формат. Конечно, ассемблер позволяет уместить и в 64 килобайтах весьма сложные и большие алгоритмы, а все данные хранить в отдельных файлах, но ограничение размера все равно очень серьезно, и даже чисто ассемблерные программы могут с ним сталкиваться.
; hello-2.asm ; Выводит на экран сообщение "Hello World!" и завершается .model small ; модель памяти, используемая для ЕХЕ .stack 100h ; сегмент стека размером в 256 байт .code start: mov ax,DGROUP ; сегментный адрес строки message mov ds,ax ; помещается в DS mov dx,offset message mov ah,9 int 21h ; функция DOS "вывод строки" mov ax,4C00h int 21h ; функция DOS "завершить программу" .data message db "Hello World!",0Dh,0Ah,'$' end start
В этом примере определяются три сегмента — сегмент стека директивой .STACK размером в 256 байт, сегмент кода, начинающийся с директивы .CODE, и сегмент данных, начинающийся с .DATA и содержащий строку. При запуске ЕХE-программы регистр DS уже не содержит адреса сегмента со строкой message (он указывает на сегмент, содержащий блок данных PSP), а для вызова используемой функции DOS этот регистр должен иметь сегментный адрес строки. Команда MOV AX,DGROUP загружает в АХ сегментный адрес группы сегментов данных DGROUP, a MOV DS,AX копирует его в DS. Для ассемблеров MASM и TASM можно использовать вместо DGROUP предопределенную метку «@data», но единственная модель памяти, в которой группа сегментов данных называется иначе, — FLAT (ей мы пока пользоваться не будем). И наконец, программы типа ЕХЕ должны завершаться системным вызовом DOS 4Ch: в регистр АН помещается значение 4Ch, в регистр AL помещается код возврата (в данном примере код возврата 0 и регистры АН и AL загружаются одной командой MOV AX,4C00h), после чего вызывается прерывание 21h.
Компиляция hello-2.asm:
Для TASM:
tasm hello-2.asm tlink /x hello-2.obj
Размер получаемого файла hello-2.exe — 559 байт.
Для MASM:
ml /с hello-2.asm link hello-2.obj
Размер получаемого файла hello-2.exe — 545 байт.
Для WASM:
wasm hello-2.asm wlink file hello-2.obj form DOS
Размер получаемого файла hello-2.exe — 81 байт.
Расхождения в размерах файлов вызваны различными соглашениями о выравнивании сегментов программы по умолчанию. Все примеры программ для DOS в этой книге рассчитаны на компиляцию в СОМ-файлы, так как идеология работы с памятью в них во многом совпадает с идеологией, применяемой при программировании под расширители DOS, DPMI и Windows.
Традиционно первая программа для освоения нового языка программирования — программа, выводящая на экран текст «Hello world!». He будет исключением и эта книга, так как такая программа всегда была удобной отправной точкой для дальнейшего освоения языка.
Итак, наберите в любом текстовом редакторе, который может записывать файлы как обычный текст (например: EDIT.COM в DOS, встроенный редактор в Norton Commander или аналогичной программе, NOTEPAD в Windows), следующий текст:
; hello-l.asm ; Выводит на экран сообщение "Hello World!" и завершается .model tiny ; модель памяти, используемая для СОМ .code ; начало сегмента кода org 100h ; начальное значение счетчика - 100h start: mov ah,9 ; номер функции DOS - в АН mov dx,offset message ; адрес строки - в DX int 21h ; вызов системной функции DOS ret ; завершение СОМ-программы message db "Hello World!",0Dh,0Ah,'$' ; строка для вывода end start ; конец программы
и сохраните его как файл hello-l.asm. Можно также использовать готовый файл с этим именем. (Все программы, использующиеся в этой книге в качестве примеров, вы можете найти в Internet: .) Чтобы превратить программу в исполнимый файл, сначала надо вызвать ассемблер, для того чтобы скомпилировать ее в объектный файл с именем hello-1.obj, набрав в командной строке следующую команду:
Для TASM:
tasm hello-1.asm
Для MASM:
ml /c hello-1.asm
Для WASM:
wasm hello-1.asm
С ассемблерными программами также можно работать из интегрированных сред разработки, как обычно работают с языками высокого уровня, но в них обычно удобнее создавать процедуры на ассемблере, вызываемые из программ на языке, для которого предназначена среда, а создание полноценных программ на ассемблере требует некоторой перенастройки.
Формат объектных файлов, используемых всеми тремя рассматриваемыми ассемблерами по умолчанию (OMF-формат), совпадает, так что можно пользоваться ассемблером из одного пакета и компоновщиком из другого.
Для TLINK:
tasm /t /x hello-1.obj
DOS при вызове СОМ-файла помещает в стек сегментный адрес программы и ноль, так что RET передает управление на нулевой адрес текущего сегмента, то есть на первый байт PSP. Там находится код команды INT 20h, которая и используется для возвращения управления в DOS. Можно сразу заканчивать программу командой INT 20h, хотя это длиннее на 1 байт. | ||
Возможно, основная функция DOS как операционной системы — организация доступа к дискам как к набору файлов и каталогов. DOS поддерживает только один тип файловой системы — FAT и, начиная с версии 7.0 (Windows 95), его модификацию VFAT с длинными именами файлов. Первоначальный набор функций для работы с файлами, предложенный в MS-DOS 1.0, оказался очень неудобным: каждый открытый файл описывался 37-байтной структурой FCB (блок управления файлом), адрес которой требовался для всех файловых операций, а передача данных осуществлялась через структуру данных DTA (область передачи данных). Уже в MS-DOS 2.0, вместе с усовершенствованием FAT (например, появлением вложенных каталогов), появился набор UNIX-подобных функций работы с файлами, использующих для описания файла всего одно 16-битное число, идентификатор файла или устройства. Все остальные функции работы с файлами используют затем только это число. Первые пять идентификаторов инициализируются системой следующим образом:
0: STDIN — стандартное устройство ввода (обычно клавиатура),
1: STDOUT — стандартное устройство вывода (обычно экран),
2: STDERR — устройство вывода сообщений об ошибках (всегда экран),
3: AUX — последовательный порт (обычно СОМ1),
4: PRN — параллельный порт (обычно LPT1),
так что функции чтения/записи (а также сброс буферов на диск) файлов можно точно так же применять и к устройствам.
Все общение с мышью в DOS выполняется через прерывание 33h, обработчик которого устанавливает драйвер мыши, загружаемый обычно при запуске системы. Современные драйверы поддерживают около 60 функций, позволяющих настраивать разрешение мыши, профили ускорений, виртуальные координаты, настраивать дополнительные обработчики событий и т.п. Большинство этих функций требуются редко, сейчас рассмотрим основные:
INT 33h, AX = 0 — Инициализация мыши
Ввод: | AX = 0000h |
Вывод: | АХ = 0000h, если мышь или драйвер мыши не установлены АХ = FFFFh, если драйвер и мышь установлены ВХ = число кнопок:0002 или FFFF — две 0003 — три 0000 — другое количество |
Выполняется аппаратный и программный сброс мыши и драйвера.
INT 33h, AX = 1 — Показать курсор
Ввод: | AX = 0001h |
INT 33h, AX = 2 — Спрятать курсор
Ввод: | AX = 0002h |
Драйвер мыши поддерживает внутренний счетчик, управляющий видимостью курсора мыши. Функция 2 уменьшает значение счетчика на единицу, а функция 1 увеличивает его, но только до значения 0. Если значение счетчика — отрицательное число, он спрятан, если ноль — показан. Это позволяет процедурам, использующим прямой вывод в видеопамять, вызывать функцию 2 в самом начале и 1 — в самом конце, не заботясь о том, в каком состоянии был курсор мыши у вызвавшей эту процедуру программы.
INT 33h, AX = 3 — Определить состояние мыши
Ввод: | AX = 0003h |
Вывод: | ВХ = состояние кнопок:
бит 0 — нажата левая кнопка бит 1 — нажата правая кнопка бит 2 — нажата средняя кнопкаСХ = Х-координата |
Возвращаемые координаты совпадают с пиксельными координатами соответствующей точки на экране в большинстве графических режимов, кроме 04, 05, 0Dh, 13h, в которых Х-координату мыши нужно разделить на 2, чтобы получить номер столбца соответствующей точки на экране. В текстовых режимах обе координаты надо разделить на 8, чтобы получить номер строки и столбца соответственно.
Ввод: | AX = 000Ch ES:DX = адрес обработчика СХ = условие вызова бит 0 — любое перемещение мыши бит 1 — нажатие левой кнопки бит 2 — отпускание левой кнопки бит 3 — нажатие правой кнопки бит 4 — отпускание правой кнопки бит 5 — нажатие средней кнопки бит 6 — отпускание средней кнопки СХ = 0000h — отменить обработчик |
Приведенную реализацию этого алгоритма можно значительно ускорить, использовав самомодифицирующийся код, то есть после проверки на направление прямой в начале алгоритма вписать прямо в дальнейший текст программы команды INС СХ, DEC CX, INС DX и DEC DX вместо команд сложения этих регистров с переменными X_increment и Y_increment. Самомодифицирующийся код часто применяется при программировании для DOS, но во многих многозадачных системах текст программы загружается в область памяти, защищенную от записи, так что в последнее время область применения этого приема становится ограниченной. | ||
В режиме VGA 320x200 с 256 цветами для отображения видеопамяти на основное адресное пространство используется 64 000 байт, располагающихся с адреса A000h:0000h. Дальнейшее увеличение разрешения или числа цветов приводит к тому, что объем видеопамяти превышает максимальные границы сегмента в реальном режиме (65 535 байт), а затем и размер участка адресного пространства, отводимого для видеопамяти (160 Кб, от A000h:0000h до B800h:FFFFh. С адреса C800h:0000h начинается область ROM BIOS). Чтобы вывести изображение, используются два механизма — переключение банков видеопамяти для реального режима и LFB (линейный кадровый буфер) для защищенного.
Во втором случае вся видеопамять отображается на непрерывный кусок адресного пространства, но начинающегося не с адреса 0A0000h, а с какого-нибудь другого адреса, так чтобы весь образ видеопамяти, который может занимать несколько мегабайтов, отобразился в один непрерывный массив. В защищенном режиме максимальный размер сегмента составляет 4 гигабайта, поэтому никаких сложностей с адресацией этого буфера не возникает. Буфер LFB можно использовать, только если видеоадаптер поддерживает спецификацию VBE 2.0 (см. пример в главе 6.4).
В реальном режиме вывод на экран осуществляется по-прежнему копированием данных в 64-килобайтный сегмент, обычно начинающийся с адреса A000h:0000h, но эта область памяти соответствует только части экрана. Чтобы вывести изображение в другую часть экрана, требуется вызвать функцию перемещения окна (или, что то же самое, переключения банка видеопамяти), изменяющую область видеопамяти, которой соответствует сегмент A000h. Например, в режиме 640x480 с 256 цветами требуется 307 200 байт для хранения всего видеоизображения. Заполнение сегмента A000h:0000h – A000h:FFFFh приводит к закраске приблизительно 1/5 экрана, перемещение окна А на позицию 1 (или переключение на банк 1) и повторное заполнение этой же области приводит к закраске следующей 1/5 экрана, и т.д. Перемещение окна осуществляется подфункцией 05 видеофункции 4Fh или передачей управления прямо на процедуру, адрес которой можно получить, вызвав подфункцию 01, как будет показано ниже. Некоторые видеорежимы позволяют использовать сразу два таких 64-килобайтных окна, окно А и окно В, так что можно записать 128 Кб данных, не вызывая прерывания.
Ввод: | AX = 4F00h ES:DI = адрес буфера (512 байт) |
Вывод: | AL = 4Fh, если функция поддерживается АН = 01, если произошла ошибка АН = 00, если данные получены и записаны в буфер |
+00h: | 4 байта — будет содержать «VESA» после вызова прерывания, чтобы получить поля, начиная с 14h, здесь надо предварительно записать строку «VBE2» |
+04h: | слово — номер версии VBE в двоично-десятичном формате (0102h — для 1.2, 0200h — для 2.0) |
+06h: | 4 байта — адрес строки-идентификатора производителя |
+0Ah: | 4 байта — флаги: бит 0 — АЦП поддерживает 8-битные цветовые компоненты (см. подфункцию 08h) бит 1 — видеоадаптер несовместим с VGA бит 2 — АЦП можно программировать только при обратном ходе луча бит 3 — поддерживается спецификация аппаратного ускорения графики VBE/AF 1.0 бит 4 — требуется вызов EnableDirectAccess перед использованием LFB бит 5 — поддерживается аппаратный указатель мыши бит 6 — поддерживается аппаратное отсечение бит 7 — поддерживается аппаратное копирование блоков биты 8 – 31 зарезервированы |
+0Eh: | 4 байта — адрес списка номеров поддерживаемых видеорежимов (массив слов, последнее слово = FFFFh, после которого обычно следует список нестандартных режимов, также заканчивающийся словом FFFFh) |
+12h: | слово — объем видеопамяти в 64-килобайтных блоках |
+14h: | слово — внутренняя версия данной реализации VBE |
+16h: | 4 байта — адрес строки с названием производителя |
+1Ah: | 4 байта — адрес строки с названием видеоадаптера |
+1Eh: | 4 байта — адрес строки с версией видеоадаптера |
+22h: | слово — версия VBE/AF (BCD, то есть 0100h для 1.0) |
+24h: | 4 байта — адрес списка номеров режимов, поддерживающих аппаратное ускорение (если бит поддержки VBE/AF установлен в 1) |
+28h: | 216 байт — зарезервировано VESA |
+100h: | 256 байт — зарезервировано для внутренних данных VBE. Так, например, в эту область копируются строки с названиями производителя, видеоадаптера, версии и т.д. |
Ввод: | AX = 4F01h СХ = номер SVGA-режима ( бит 14 соответствует использованию LFB, бит 13 — аппаратному ускорению) ES:DI = адрес буфера для информации о режиме (256 байт) |
Вывод: | AL = 4Fh, если функция поддерживается АН = 01, если произошла ошибка АН = 00, если данные получены и записаны в буфер |
+00h: | слово — атрибуты режима: бит 0 — режим присутствует бит 1 — дополнительная информация (смещения 12h – 1Eh) присутствует (для VBE 2.0 эта информация обязательна и бит всегда установлен) бит 2 — поддерживается вывод текста на экран средствами BIOS бит 3 — режим цветной бит 4 — режим графический бит 5 — режим несовместим с VGA бит 6 — переключение банков не поддерживается бит 7 — LFB не поддерживается бит 8 — не определен бит 9 — (для VBE/AF) приложения должны вызвать EnableDirectAccess, прежде чем переключать банки |
+02h: | байт — атрибуты окна А: бит 1 — окно существует бит 2 — чтение из окна разрешено бит 3 — запись в окно разрешена |
+03h: | байт — атрибуты окна В |
+04h: | слово — гранулярность окна — число килобайтов, которому всегда кратен адрес начала окна в видеопамяти (обычно 64) |
+06h: | слово — размер окна в килобайтах (обычно 64) |
+08h: | слово — сегментный адрес окна А (обычно A000h) |
+0Ah: | слово — сегментный адрес окна В |
+0Ch: | 4 байта — адрес процедуры перемещения окна (аналог подфункции 05h, но выполняется быстрее) |
+10h: | слово — число целых байт в логической строке |
+12h: | слово — ширина в пикселях (для графики) или символах (для текста) |
+14h: | слово — высота в пикселях (для графики) или символах (для текста) |
+16h: | байт — высота символов в пикселях |
+17h: | байт — ширина символов в пикселях |
+18h: | байт — число плоскостей памяти (4 — для 16-цветных режимов, 1 — для обычных, число переключений банков, требуемое для доступа ко всем битам (4 или 8), — для модели памяти 5) |
+19h: | байт — число бит на пиксель |
+1Ah: | байт — число банков для режимов, в которых строки группируются в банки (2 — для CGA, 4 — для HGC) |
+1Bh: | байт — модель памяти: 00h — текст 01h — CGA-графика 02h — HGC-графика 03h — EGA-графика (16 цветов) 04h — VGA-графика (256 цветов в одной плоскости) 05h — Режим X (256 цветов в разных плоскостях) 06h — RGB (15-битные и выше) 07h — YUV 08h – 0Fh — зарезервированы VESA 10h – FFh — нестандартные модели |
+1Ch: | байт — размер банка в килобайтах (8 — для CGA и HGC, 0 — для остальных) |
+1Dh: | байт — число видеостраниц |
+1Eh: | байт — зарезервирован |
+1Fh: | байт — битовая маска красной компоненты |
+20h: | байт — первый бит красной компоненты |
+21h: | байт — битовая маска зеленой компоненты |
+22h: | байт — первый бит зеленой компоненты |
+23h: | байт — битовая маска синей компоненты |
+24h: | байт — первый бит синей компоненты |
+25h: | байт — битовая маска зарезервированной компоненты |
+26h: | байт — первый бит зарезервированной компоненты |
+27h: | байт — дополнительные флаги: бит 0: —поддерживается перепрограммирование цветов (подфункция 09h) бит 1 — приложение может использовать биты в зарезервированной компоненте |
+28h: | 4 байта — физический адрес начала LFB |
+2Ch: | 4 байта — смещение от начала LFB, указывающее на первый байт после конца участка памяти, отображающейся на экране |
+30h: | слово — размер памяти в LFB, не отображающейся на экране, в килобайтах |
+32h: | 206 байт — зарезервировано |
Ввод: | AX=4F02h ВХ = номер режима: биты 0 – 6 — собственно номер режима бит 7 — видеопамять не очищается при установке режима, если все следующие биты — нули бит 8 — стандартный VBE SVGA-режим бит 9 — нестандартный SVGA-режим биты 10 – 12 — зарезервированы бит 13 — режим использует аппаратное ускорение бит 14 — режим использует LFB бит 15 — видеопамять не очищается при установке режима Кроме того, специальный номер режима 81FFh соответствует доступу ко всей видеопамяти и может использоваться для сохранения ее содержимого. |
Вывод: | AL = 4Fh, если функция поддерживается АН = 00, если режим установлен АН = 01 или 02, если произошла ошибка |
Ввод: | АХ = 4F03h |
Вывод: | AL = 4Fh, если функция поддерживается ВХ = номер режима |
Ввод: | АХ = 4F03h ВН = 00 — установить окно ВН = 01 — считать окно BL = 00 — окно А BL = 01 — окно В DX = адрес окна в видеопамяти в единицах гранулярности (номер банка), если ВН = 0 |
Вывод: | AL = 4Fh, если функция поддерживается DX = адрес окна в единицах гранулярности (номер банка), если ВН = 1 АН = 03, если функция была вызвана в режиме, использующем LFB |
Ввод: | АХ = 4F07h ВН = 00 BL = 00 — считать начало изображения BL = 80h — установить начало изображения (в VBE 2.0 автоматически выполняется при следующем обратном ходе луча) СХ = первый изображаемый пиксель в строке (для BL = 80h) DX = первая изображаемая строка (для BL = 80h) |
Вывод: | AL = 4Fh, если функция поддерживается АН = 01, если произошла ошибка АН = 00, если функция выполнилась успешно ВН = 00 (для BL = 00) СХ = первый изображаемый пиксель в строке (для BL = 00) DX = первая изображаемая строка (для BL = 00) |
Функция 00 прерывания BIOS 10h позволяет переключаться не только в текстовые режимы, использовавшиеся в предыдущих главах, но и в некоторые графические. Эти видеорежимы стандартны и поддерживаются всеми видеоадаптерами (начиная с VGA), см. табл. 19.
Таблица 19. Основные графические режимы VGA
Номер режима | Разрешение | Число цветов |
11h | 640x480 | 2 |
12h | 640x480 | 16 |
13h | 320x200 | 256 |
Существуют еще несколько видеорежимов, использовавшихся более старыми видеоадаптерами CGA и EGA (с номерами от 4 до 10h); их список приведен в приложении 2.
BIOS также предоставляет видеофункции чтения и записи точки на экране в графических режимах, но эти функции настолько медленно исполняются, что никогда не используются в реальных программах.
INТ 10h АН = 0Ch — Вывести точку на экран
Ввод: | АН = 0Ch ВН = номер видеостраницы (игнорируется для режима 13h, поддерживающего только одну страницу) DX = номер строки СХ = номер столбца AL = номер цвета (для режимов 10h и llh, если старший бит 1, номер цвета точки на экране будет результатом операции «исключающее ИЛИ») |
Вывод: | Никакого |
INТ 10h AH = 0Dh — Считать точку с экрана
Ввод: | АН = 0Dh ВН = номер видеостраницы (игнорируется для режима 13h, поддерживающего только одну страницу) DX = номер строки СХ = номер столбца |
Вывод: | AL = номер цвета |
Попробуем тем не менее воспользоваться средствами BIOS для вывода на экран. Следующая программа переводит экран в графический режим 13h (320x200), заселяет его точками случайным образом, после чего эти точки эволюционируют согласно законам алгоритма «Жизнь»: если у точки меньше двух или больше трех соседей, она погибает, а если у пустой позиции есть три соседа, в ней появляется новая точка. Мы будем использовать очень простой, но неоптимальный способ реализации этого алгоритма: сначала для каждой точки вычисляется число соседей, затем каждая точка преобразуется в соответствии с полученным числом соседей, и затем каждая точка выводится на экран.
; lifebios.asm ; Игра "Жизнь" на поле 320x200, использующая вывод на экран средствами BIOS .model small .stack 100h ; явное задание стека - для ЕХЕ-программ .code .186 ; для команд shl al,4 и shr al,4 start: push FAR_BSS ; сегментный адрес буфера в DS pop ds
Начиная с IBM AT, персональные компьютеры содержат два устройства для управления процессами — часы реального времени (RTC) и собственно системный таймер. Часы реального времени получают питание от аккумулятора на материнской плате и работают даже тогда, когда компьютер выключен. Это устройство можно использовать для определения/установки текущих даты и времени, установки будильника с целью выполнения каких-либо действий и для вызова прерывания IRQ8 (INT 4Ah) каждую миллисекунду. Системный таймер используется одновременно для управления контроллером прямого доступа к памяти, для управления динамиком и как генератор импульсов, вызывающий прерывание IRQ0 (INT 8h) 18,2 раза в секунду. Таймер предоставляет богатые возможности для препрограммирования на уровне портов ввода-вывода, но на уровне DOS и BIOS и часы реального времени, и системный таймер используются только как средство определения/установки текущего времени и организации задержек.
Функция DOS 2Ah — Определить дату
Ввод: | AX = 2Ah |
Вывод: | СХ = год (1980 – 2099) DH = месяц DL = день AL = день недели (0 — воскресенье, 1 — понедельник...) |
Функция DOS 2Ch — Определить время
Ввод: | AX = 2Ch |
Вывод: | СН = час CL = минута DH = секунда DL = сотая доля секунды |
Эта функция использует системный таймер, так что время изменяется только 18,2 раза в секунду и число в DL увеличивается сразу на 5 или 6.
Функция DOS 2Bh — Установить дату
Ввод: | АН = 2Bh СХ = год (1980 – 2099) DH = месяц DL = день |
Вывод: | АН = FFh, если введена несуществующая дата, АН = 00h, если дата установлена |
Функция DOS 2Dh — Установить время
Ввод: | АН = 2Dh СН = час CL = минута DH = секунда DL = сотая доля секунды |
Вывод: | AL = FFh, если введено несуществующее время, AL = 00h, если время установлено |
Функции 2Bh и 2Dh устанавливают одновременно как внутренние часы DOS, которые управляются системным таймером и обновляются 18,2 раза в секунду, так и часы реального времени. BIOS позволяет управлять часами напрямую:
Ввод: | АН = 04h |
Вывод: | CF = 0, если дата прочитана СХ = год ( в формате BCD, то есть 1998h для 1998-го года) DH = месяц (в формате BCD) DL = день (в формате BCD) CF = 1, если часы не работают или попытка чтения пришлась на момент обновления |
Ввод: | АН = 02h |
Вывод: | CF = 0, если время прочитано СН = час (в формате BCD) CL = минута (в формате BCD) DH = секунда (в формате BCD) DL = 01h, если действует летнее время, 00h, если нет CF = 1, если часы не работают или попытка чтения пришлась на момент обновления |
Ввод: | АН = 05h СХ = год (в формате BCD) DH = месяц DL = день |
Ввод: | АН = 03h СН = час (в формате BCD) CL = минута (в формате BCD) DH = секунда (в формате BCD) DL = 01h, если используется летнее время, 0 — если нет |
Ввод: | АН = 06h СН = час (BCD) CL = минута (BCD) DH = секунда (BCD) |
Вывод: | CF = 1, если произошла ошибка (будильник уже установлен или прерывание вызвано в момент обновления часов) CF = 0, если будильник установлен |
Ввод: | АН = 07h |
Ввод: | АН = 00h |
Вывод: | CX:DX = значение счетчика AL = байт переполнения счетчика |
Ввод: | АН = 01h CX:DX = значение счетчика |
Ввод: | АН = 86h CX:DX = длительность задержки в микросекундах (миллионных долях секунды!) |
Вывод: | AL = маска, записанная обработчиком в регистр управления прерываниями CF = 0, если задержка выполнена CF = 1, если таймер был занят |
Ввод: | АН = 83h AL = 0 — запустить счетчик AL = 1 — прервать счетчик CX:DX = длительность задержки в микросекундах ES:BX = адрес байта, старший бит которого по окончании работы счетчика будет установлен в 1 |
Вывод: | AL = маска, записанная обработчиком в регистр управления прерываниями CF = 0, если задержка выполнена CF = 1, если таймер был занят |
Функция DOS 3Ch — Создать файл
Ввод: | AX = 3Ch СХ = атрибут файла бит 7: файл можно открывать разным процессам в Novell Netware бит 6: не используется бит 5: архивный бит (1, если файл не сохранялся) бит 4: каталог (должен быть 0 для функции 3Ch) бит 3: метка тома (игнорируется функцией 3Ch) бит 2: системный файл бит 1: скрытый файл бит 0: файл только для чтения DS:DX = адрес ASCIZ-строки с полным именем файла (ASCIZ-строка ASCII-символов, оканчивающаяся нулем) |
Вывод: | CF = 0 и АХ = идентификатор файла, если не произошла ошибка CF = 1 и АХ = 03h, если путь не найден CF = 1 и АХ = 04h, если слишком много открытых файлов CF = 1 и АХ = 05h, если доступ запрещен |
Если файл уже существует, функция 3Ch все равно открывает его, присваивая ему нулевую длину. Чтобы этого не произошло, следует пользоваться функцией 5Bh.
Функция DOS 3Dh — Открыть существующий файл
Ввод: | AX = 3Dh AL = режим доступа биты 0 – : права доступа 00: чтение 01: запись 10: чтение и запись бит 1: открыть для записи биты 2 – 3: зарезервированы (0) биты 6 – 4: режим доступа для других процессов 000: режим совместимости (остальные процессы также должны открывать этот файл в режиме совместимости) 001: все операции запрещены 010: запись запрещена 011: чтение запрещено 100: запрещений нет бит 7: файл не наследуется порождаемыми процессами DS:DX = адрес ASCIZ-строки с полным именем файла CL = маска атрибутов файлов |
Вывод: | CF = 0 и АХ = идентификатор файла, если не произошла ошибка CF = 1 и АХ = код ошибки (02h — файл не найден, 03h — путь не найден, 04h — слишком много открытых файлов, 05h — доступ запрещен, 0Ch — неправильный режим доступа) |
Функция DOS 5Bh — Создать и открыть новый файл
Ввод: | AX = 5Bh СХ = атрибут файла DS:DX = адрес ASCIZ-строки с полным именем файла |
Вывод: | CF = 0 и АХ = идентификатор файла, открытого для чтения/записи в режиме совместимости, если не произошла ошибка CF = 1 и АХ = код ошибки (03h — путь не найден, 04h — слишком много открытых файлов, 05h — доступ запрещен, 50h — файл уже существует) |
Ввод: | AX = 5Ah СХ = атрибут файла DS:DX = адрес ASCIZ-строки с путем, оканчивающимся символом «\», и тринадцатью нулевыми байтами в конце |
Вывод: | CF = 0 и АХ = идентификатор файла, открытого для чтения/записи в режиме совместимости, если не произошла ошибка (в строку по адресу DS:DX дописывается имя файла) CF = 1 и АХ = код ошибки (03h — путь не найден, 04h — слишком много открытых файлов, 05h — доступ запрещен) |
Ввод: | AX = 716Сh ВХ = режим доступа Windows 95 биты 2 – 0: доступ 000 — только для чтения 001 — только для записи 010 — для чтения и записи 100 — только для чтения, не изменять время последнего обращения к файлу биты 6 – 4: доступ для других процессов (см. функцию 3Dh) бит 7: файл не наследуется порождаемыми процессами бит 8: данные не буферизуются бит 9: не архивировать файл, если используется архивирование файловой системы (DoubleSpace) бит 10: использовать число в DI для записи в конец короткого имени файла бит 13: не вызывать прерывание 24h при критических ошибках бит 14: сбрасывать буфера на диск после каждой записи в файл СХ = атрибут файла DX = действие бит 0: открыть файл (ошибка, если файл не существует) бит 1: заменить файл (ошибка, если файл не существует) бит 4: создать файл (ошибка, если файл существует) DS:SI = адрес ASCIZ-строки с именем файла DI = число, которое будет записано в конце короткого варианта имени файла |
Вывод: | CF = 0 АХ = идентификатор файла СХ = 1, если файл открыт СХ = 2, если файл создан СХ = 3, если файл заменен CF = 1, если произошла ошибка АХ = код ошибки (7100h, если функция не поддерживается) |
Ввод: | AX = 67h ВХ = новое максимальное число идентификаторов (20 – 65 535) |
Вывод: | CF = 0, если не произошла ошибка CF = 1 и АХ = код ошибки, если произошла ошибка (например: 04h, если заданное число меньше, чем количество уже открытых файлов, или 08h, если DOS не хватает памяти для новой таблицы идентификаторов) |
Функции DOS вывода на экран позволяют перенаправлять вывод в файл, но не позволяют вывести текст в любую позицию экрана и не позволяют изменить цвет текста. DOS предполагает, что для более тонкой работы с экраном программы должны использоваться видеофункции BIOS. BIOS (базовая система ввода-вывода) — это набор программ, расположенных в постоянной памяти компьютера, которые выполняют его загрузку сразу после включения и обеспечивают доступ к некоторым устройствам, в частности к видеоадаптеру. Все функции видеосервиса BIOS вызываются через прерывание 10h. Рассмотрим функции, которые могут быть полезны для вывода текстов на экран (полностью видеофункции BIOS описаны в приложении 2).
Выбор видеорежима
BIOS предоставляет возможность переключения экрана в различные текстовые и графические режимы. Режимы отличаются друг от друга разрешением (для графических) и количеством строк и столбцов (для текстовых), а также количеством возможных цветов.
INT 10h, АН = 00 — Установить видеорежим
Ввод: | AL = номер режима в младших 7 битах |
Вывод: | Обычно никакого, но некоторые BIOS (Phoenix и AMI) помещают в AL 30Н для текстовых режимов и 20h для графических |
Вызов этой функции приводит к тому, что экран переводится в выбранный режим. Если старший бит AL не установлен в 1, экран очищается. Номера текстовых режимов — 0, 1, 2, 3 и 7. 0 и 1 — 16-цветные режимы 40x25 (с 25 строками по 40 символов в строке), 2 и 3 — 16-цветные режимы 80x25, 7 — монохромный режим 80x25. Мы не будем пока рассматривать графические режимы, хотя функции вывода текста на экран DOS и BIOS могут работать и в них. Существует еще много текстовых режимов с более высоким разрешением (80x43, 80x60, 132x50 и т.д.), но их номера для вызова через эту функцию различны для разных видеоадаптеров (например, режим 61h — 132x50 для Cirrus 5320 и 132x29 для Genoa 6400). Однако, если видеоадаптер поддерживает стандарт VESA BIOS Extention, в режимы с высоким разрешением можно переключаться, используя функцию 4Fh.
Ввод: | ВХ = номер режима в младших 13 битах |
Вывод: | AL = 4Fh, если эта функция поддерживается АН = 0, если переключение произошло успешно АН = 1, если произошла ошибка |
Ввод: | АН = 02 ВН = номер страницы DH = строка DL = столбец |
Ввод: | АН = 03 ВН = номер страницы |
Вывод: | DH, DL = строка и столбец текущей позиции курсора СН, CL = первая и последняя строки курсора |
Обычный цвет | Яркий цвет | |
000b | черный | темно-серый |
001b | синий | светло-синий |
010b | зеленый | светло-зеленый |
011b | голубой | светло-голубой |
100b | красный | светло-красный |
101b | пурпурный | светло-пурпурный |
110b | коричневый | желтый |
111b | светло-серый | белый |
Ввод: | АН = 08 ВН = номер страницы |
Вывод: | АН = атрибут символа AL = ASCII-код символа |
Ввод: | АН = 09 ВН = номер страницы AL = ASCII-код символа BL = атрибут символа СХ = число повторений символа |
Ввод: | АН = 0Ah ВН = номер страницы AL = ASCII-код символа СХ = число повторений символа |
Ввод: | АН = 0Eh ВН = номер страницы AL = ASCII-код символа |
Ввод: | АН = 13h AL = режим вывода:бит 0 — переместить курсор в конец строки после вывода бит 1 — строка содержит не только символы, но также и атрибуты, так что каждый символ описывается двумя байтами: ASCII-код и атрибут биты 2 – 7 зарезервированыСХ = длина строки (только число символов) BL = атрибут, если строка содержит только символы DH,DL = строка и столбец, начиная с которых будет выводиться строки ES:BP = адрес начала строки в памяти |
Так же как и для вывода на экран, BIOS предоставляет больше возможностей по сравнению с DOS для считывания данных и управления клавиатурой. Например, функциями DOS нельзя определить нажатие комбинаций клавиш типа Ctrl-Alt-Enter или нажатие двух клавиш Shift одновременно, DOS не может определить момент отпускания нажатой клавиши, и наконец, в DOS нет аналога функции С ungetch(), помещающей символ в буфер клавиатуры, как если бы его ввел пользователь. Все это можно осуществить, используя различные функции прерывания 16h и операции с байтами состояния клавиатуры.
INT 16h, АН = 0, 10h, 20h — Чтение символа с ожиданием
Ввод: | АН = 00h (83/84-key), 10h (101/102-key), 20h (122-key) |
Вывод: | AL = ASCII-код символа, 0 или префикс скан-кода АН = скан-код нажатой клавиши или расширенный ASCII-код |
Каждой клавише на клавиатуре соответствует так называемый скан-код (см. приложение 1), соответствующий только этой клавише. Этот код посылается клавиатурой при каждом нажатии и отпускании клавиши и обрабатывается BIOS (обработчиком прерывания INT 9). Прерывание 16h дает возможность получить код нажатия, не перехватывая этот обработчик. Если нажатой клавише соответствует ASCII-символ, то в АН возвращается код этого символа, а в AL — скан-код клавиши. Если нажатой клавише соответствует расширенный ASCII-код, в AL возвращается префикс скан-кода (например, Е0 для серых клавиш) или 0, если префикса нет, а в АН — расширенный ASCII-код. Функция 00Н обрабатывает только комбинации, использующие клавиши 84-клавишной клавиатуры, l0h обрабатывает все 101 – 105-клавишные комбинации, 20h — 122-клавишные. Тип клавиатуры можно определить с помощью функции 09h прерывания 16h, если она поддерживается BIOS (поддерживается ли эта функция, можно узнать с помощью функции C0h прерывания 15h).
INT 16h, АН = 1, 11h, 21h — Проверка символа
Ввод: | АН = 01h (83/84-key), 11h (101/102-key), 21h (122-key) |
Вывод: | ZF = 1, если буфер пуст ZF = 0, если в буфере присутствует символ, в этом случае AL = ASCII-код символа, 0 или префикс скан-кода АН = скан-код нажатой клавиши или расширенный ASCII-код |
Ввод: | АН = 05h СН = скан-код CL = ASCII-код |
Вывод: | AL = 00, если операция выполнена успешно AL = 01h, если буфер клавиатуры переполнен АН модифицируется многими BIOS |
Ввод: | АН = 02h (83/84-key), 12h (101/102-key), 22h (122-key) |
Вывод: | AL = байт состояния клавиатуры 1 АН = байт состояния клавиатуры 2 (только для функций 12h и 22h) |
На примере первой программы на ассемблере мы уже познакомились с одним из способов вывода текста на экран — вызовом функции 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 равны нулю, что и соответствует кратности шестнадцати.
Ввод: | АН = 06h DL = ASCII-код символа (кроме FFh) |
Вывод: | Никакого, согласно документации, но на самом деле: AL = код записанного символа (копия DL) |
Ввод: | АН = 09h DS:DX = адрес строки, заканчивающейся символом $ (24h) |
Вывод: | Никакого, согласно документации, но на самом деле: AL = 24h (код последнего символа) |
Ввод: | АН = 40h ВХ = 1 для STDOUT или 2 для STDERR DS:DX = адрес начала строки СХ = длина строки |
Вывод: | CF = 0, АХ = число записанных байт |
Ввод: | AL = ASCII-код символа |
Ввод: | АН = 08h |
Вывод: | AL = код символа |
Ввод: | АН = 07h |
Вывод: | AL = код символа |
Ввод: | АН = 07h DL = 0FFh |
Вывод: | ZF = 1, если не была нажата клавиша, и AL = 00 ZF = 0, если клавиша была нажата. В этом случае AL = код символа |
Ввод: | АН = 0Bh |
Вывод: | AL = 0, если не была нажата клавиша AL = 0FFh, если была нажата клавиша |
Ввод: | АН = 0Ch AL = Номер функции DOS (01, 06, 07, 08, 0Ah) |
Вывод: | Зависит от вызванной функции |
Как и в случае вывода на экран, 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)
Начиная с MS-DOS 2.0, файловая система организована в виде каталогов, которые могут содержать файлы и другие каталоги. Функции поиска файлов действуют только в пределах текущего каталога, а функции создания и удаления файлов не действуют на каталоги, несмотря на то, что на самом низком уровне каталог — тот же файл, в атрибуте которого бит 4 установлен в 1 и который содержит список имен вложенных файлов, их атрибутов и физических адресов на диске.
Функция DOS 39h — Создать каталог
Ввод: | АН = 39h DS:DX = адрес ASCIZ-строки с путем, в котором все каталоги, кроме последнего, существуют. Для версии DOS 3.3 и более ранних длина всей строки не должна превышать 64 байта |
Вывод: | CF = 0, если каталог создан CF = 1 и АХ = 3, если путь не найден, 5 — если доступ запрещен |
Функция LFN 39h — Создать каталог с длинным именем
Ввод: | АХ = 7139h DS:DX = адрес ASCIZ-строки с путем |
Вывод: | CF = 0, если каталог создан CF = 1 и АХ = код ошибки (7100h, если функция не поддерживается) |
Функция DOS 3Ah — Удалить каталог
Ввод: | АН = 3Ah DS:DX = адрес ASCIZ-строки с путем, последний каталог в котором будет удален (только если он пустой, не является текущим, не занят командой SUBST) |
Вывод: | CF = 0, если каталог удален CF = 1 и АХ = 3, если путь не найден, 5 — если доступ запрещен, 10h — если удаляемый каталог — текущий |
Функция LFN 3Ah — Удалить каталог с длинным именем
Ввод: | АХ = 713Ah DS:DX = адрес строки с путем |
Вывод: | CF = 0, если каталог удален, иначе CF = 1 и АХ = код ошибки |
Функция DOS 47h — Определить текущий каталог
Ввод: | АН = 47h DL = номер диска (00h — текущий, 01h — А: и т.д.) DS:SI = 64-байтный буфер для текущего пути (ASCIZ-строка без имени диска, первого и последнего символа «\») |
Вывод: | CF = 0 и АХ = 0100h, если операция выполнена CF = 1 и АХ = 0Fh, если указан несуществующий диск |
Ввод: | АХ = 7147h DL = номер диска DS:SI = буфер для пути (ASCIZ- строка без имени диска, первого и последнего символа «\». Необязательно содержит только длинные имена — возвращается тот путь, который использовался при последней смене текущего каталога) |
Вывод: | CF = 0, если операция выполнена, иначе CF = 1 и АХ = код ошибки |
Ввод: | АН = 3Bh DS:DX = адрес 64-байтного ASCIZ-буфера с путем, который станет текущим каталогом |
Вывод: | CF = 0, если произошла смена каталога, иначе CF = 1 и АХ = 3 (путь не найден) |
Ввод: | АХ = 713ВН DS:DX = адрес ASCIZ-буфера с путем |
Вывод: | CF = 0, если произошла смена каталога, иначе CF = 1 и АХ = код ошибки |
Ввод: | АХ = 71A0Н DS:DX = адрес ASCIZ-строки с именем раздела (например: db "С:\",0) ES:DI = адрес буфера для имени файловой системы (FAT, NTFS, CDFS) СХ = размер буфера в ES:DI (обычно достаточно 32 байта) |
Вывод: | СХ = 0, АХ = 0000h или 0200h ВХ = флаги файловой системы: бит 0: функции поиска учитывают регистр символов бит 1: регистр символов сохраняется для имен каталогов бит 2: используются символы Unicode бит 14: поддерживаются функции LFN бит 15: включено сжатие раздела (DoubleSpace) СХ = максимальная длина имени файла (обычно 255) DX = максимальная длина пути (обычно 260) в Windows 95 SP1 возвращает 0000h для CD-ROM CF = 1 и АХ = код ошибки, если произошла ошибка (7100h, если функция не поддерживается) |
Как и любая операционная система, DOS загружает и выполняет программы. При загрузке программы в начале отводимого для нее блока памяти (для СОМ-программ это вся свободная на данный момент память) создается структура данных PSP (префикс программного сегмента) размером 256 байт (100h). Затем DOS создает копию текущего окружения для загружаемой программы, помещает полный путь и имя программы в конец окружения, заполняет поля PSP следующим образом:
+00h: слово — CDh 20h — команда INT 20h. Если СОМ-программа завершается командой RETN, управление передается на эту команду. Введено для совместимости с командой СР/М CALL 0.
+02h: слово — сегментный адрес первого байта после области памяти, выделенной для программы
+04h: байт — не используется DOS
+05h: 5 байт — 9Ah F0h FEh 1Dh F0h — команда CALL FAR на абсолютный адрес 000C0h, записанная так, чтобы второй и третий байты составляли слово, равное размеру первого сегмента для СОМ-файлов (в этом примере FEF0h). Введено для совместимости с командой СР/М CALL 5.
+0Ah: 4 байта — адрес обработчика INT 22h (выход из программы)
+0Eh: 4 байта — адрес обработчика INT 23h (обработчик нажатия Ctrl-Break).
+12h: 4 байта — адрес обработчика INT 24h (обработчик критических ошибок)
+16h: слово — сегментный адрес PSP процесса, из которого был запущен текущий.
+18h: 20 байт — JFT — список открытых идентификаторов, один байт на идентификатор, FFh — конец списка.
+2Ch: слово — сегментный адрес копии окружения для процесса.
+2Eh: 2 слова — SS:SP процесса при последнем вызове INT 21h.
+32h: слово — число элементов JFT (по умолчанию 20).
+34h: 4 байта — дальний адрес JFT (по умолчанию PSP:0018).
+38h: 4 байта — дальний адрес предыдущего PSP.
Ввод: | АН = 4Bh AL = 00h — загрузить и выполнить AL = 01h — загрузить и не выполнять DS:DX — адрес ASCIZ-строки с полным именем программы ES:BX — адрес блока параметров ЕРВ: +00h: слово — сегментный адрес окружения, которое будет скопировано для нового процесса (или 0, если используется текущее окружение) +02h: 4 байта — адрес командной строки для нового процесса +06h: 4 байта — адрес первого FCB для нового процесса +0Ah: 4 байта — адрес второго FCB для нового процесса +0Eh: 4 байта — здесь будет записан SS:SP нового процесса после его завершения (только для AL = 01) +12h: 4 байта — здесь будет записан CS:IP (точка входа) нового процесса после его завершения (только для AL = 01) AL = 03h — загрузить как оверлей DS:DX — адрес ASCIZ-строки с полным именем программы ES:BX — адрес блока параметров: +00h: слово — сегментный адрес для загрузки оверлея +02h: слово — число, которое будет использовано в командах, использующих непосредственные сегментные адреса, — обычно то же самое число, что и в предыдущем поле. 0 для СОМ-файлов AL = 05h — подготовиться к выполнению (DOS 5.0+) DS:DX — адрес следующей структуры: +00h: слово — 00h +02h: слово:бит 0 — программа — ЕХЕ бит 1 — программа — оверлей +04h: 4 байта — адрес ASCIZ-строки с именем новой программы +08h: слово — сегментный адрес PSP новой программы +0Ah: 4 байта — точка входа новой программы +0Eh: 4 байта — размер программы, включая PSP |
Вывод: | CF = 0, если операция выполнена, ВХ и DX модифицируются, CF = 1, если произошла ошибка, АХ = код ошибки (2 — файл не найден, 5 — доступ к файлу запрещен, 8 — не хватает памяти, 0Ah — неправильное окружение, 0Bh — неправильный формат) |
Ввод: | АН = 4Ch AL = код возврата |
Ввод: | АН = 4Dh |
Вывод: | АН = способ завершения:00h — нормальный 01h — Ctrl-Break 02h — критическая ошибка 03h — программа осталась в памяти как резидентнаяAL = код возврата CF = 0 |
Функция DOS 3Eh — Закрыть файл
Ввод: | АН = 3Eh ВХ = идентификатор |
Вывод: | CF = 0, если не произошла ошибка CF = 1 и АХ = 6, если неправильный идентификатор |
Если файл был открыт для записи, все файловые буфера сбрасываются на диск, устанавливается время модификации файла и записывается его новая длина.
Функция DOS 41h — Удаление файла
Ввод: | АН = 41h DS:DX = адрес ASCIZ-строки с полным именем файла |
Вывод: | CF = 0, если файл удален CF = 1 и АН = 02h, если файл не найден, 03h — если путь не найден, 05h — если доступ запрещен |
Удалить файл можно только после того, как он будет закрыт, так как DOS будет продолжать выполнять запись в несуществующий файл, что может привести к разрушению файловой системы. Функция 41h не позволяет использовать маски (символы * и ? в имени файла) для удаления сразу нескольких файлов, хотя этого можно добиться, вызывая ее через недокументированную функцию 5D00h. Но, начиная с DOS 7.0 (Windows 95), официальная функция удаления файла может работать сразу с несколькими файлами.
Функция LFN 41h — Удаление файлов с длинным именем
Ввод: | АХ = 7141h DS:DX = адрес ASCIZ-строки с длинным именем файла SI = 0000h: маски не разрешены и атрибуты в СХ игнорируются SI = 0001h: маски в имени файла и атрибуты в СХ разрешены:CL = атрибуты, которые файлы могут иметь СН = атрибуты, которые файлы должны иметь |
Вывод: | CF = 0, если файл или файлы удалены CF = 1 и АХ = код ошибки, если произошла ошибка. Код 7100h означает, что функция не поддерживается |