call DS:subadr;Код FF 16 dddd
main endp
subr proc near;Подпрограмма
ret endp ends segment |
;Код СЗ
subr code data
subadr dw subr |
;Ячейка с адресом подпрограммы
data |
ends
Процедура-программа с атрибутом near находится в том же сегменте, что и вызывающая программа, а ее относительный адрес в ячейке subadr в сегменте данных. В коде команды dddd обозначает относительный адрес слова subadr в сегменте данных. Второй байт кода команды (I6h в данном примере) зависит от способа адресации. Косвенный вызов позволяет использовать разнообразные способы адресации подпрограммы:
call ВХ;В ВХ адрес подпрограммы
call [ВХ);В ВХ адрес ячейки с адресом подпрограммы
call [BX][SI];B ВХ адрес таблицы адресов подпрограмм,
;в SI индекс в этой таблице,
call tbl[SI];tbl — адрес таблицы адресов подпрограмм,
;в SI индекс в этой таблице
Косвенный дальний вызов. Отличается от косвенного ближнего вызова лишь тем, что подпрограмма находится в другом сегменте, а в ячейке
Глава 2
Основы программирования
памяти содержится полный адрес подпрограммы, включающий сегмент и смещение.
;Основная программа;Код FF IE dddd |
codel segment main proc
call dword ptr subadr
main endp
Подпрограмма;Код СВ |
codcl ends
code2 segment
subr proc far
ret
subadr dd |
subr endp code2 ends data segment
subr |
;Двухсловная ячейка с;адресом подпрограммы
data |
ends
Процедура-подпрограмма с атрибутом far находится в другом сегменте команд той же программы, а ее полный двухсловный адрес — в ячейке subadr в сегменте данных. Второй байт кода команды (IE в данном примере) зависит от способа адресации. Косвенный дальний вызов, как и косвенный ближний, позволяет использовать различные способы адресации.
2.8. Макросредства ассемблера
Современные ассемблеры содержат в себе так называемые макросредства и по этой причине называются иногда макроассемблерами. Общая идея макросредств заключается в том, что включением в исходный текст программы предложений специального языка макросредств (макроязыка) мы в какой-то степени упраачясм процессом трансляции программы. Макроязык позволяет выполнять или не выполнять трансляцию отдельных участков программы в зависимости от некоторого нами же определяемого условия (условная трансляция); осуществлять размножение участка исходного текста программы, в том числе, с модификацией каждого повторения (блоки повторения); включать в программу написанные отдельно фрагменты с настройкой их текста в соответствии с заданными параметрами (макрокоманды). Объекты, создаваемые с помощью директив макроязыка, обычно называют макросами. Иногда, правда, термин макрос относят только к одному конкретному виду макросрсдств, именно, к макрокоманде. Использование макросов упрощает составление исходного текста программы и иногда делает этот текст более наглядным, хотя в отдельных случаях, как, например, в случае директив условной трансляции, наоборот, может привести к существе иному усложнению исходного текста.
Как и во всяком языке программирования, в языке макросредств имеется много разного рода тонкостей, но в прикладном программирова-
нии зачастую используются лишь базовые возможности этого языка. Поэтому мы ограничимся здесь рассмотрением основных макросредств ассемблера.
Блоки повторения
Блоки повторения заставляют транслятор повторить заданный блок исходного текста указанное число раз. Повторяемый блок может состоять из директив описания данных (и тогда он включается в состав сегмента данных) или из команд процессора (и тогда он описывается в программном сегменте). Например, следующий фрагмент сегмента данных позволяет образовать массив, состоящий из кодов ASCII прописных русских букв:
sym=*A*;Начальное значение временной переменной
symbols:;Имя массива для ссылок на него
rept 32; Повторять столько раз
db sym;Повторяемая директива
sym=sym+l;Изменение переменной
endm;Консц блока повторения
Как видно из приведенного фрагмента, блок повторения начинается с директивы ассемблера rept (от repetition, повторение), а заканчивается директивой endm (end macro, конец макроса). Реально в сегменте данных выделяется 32 байт, заполненных числами от 8Hi до 9Fh, которые предполагается рассматривать, как последовательность русских букв. Того же результата можно было достигнуть с помощью следующего предложения:
symbols db "А", "Б", "В", "Г", и т.д. до буквы Я или проще, хотя и менее наглядно:
symbols db 128,129,130,131, и т.д. до числа 159.
Макрос повторения несколько сокращает время, требуемое для описания в тексте программы требуемого массива, хотя, возможно, снижает наглядность этого описания.
При подключении к компьютеру измерительного или управляющего оборудования иногда возникает необходимость замедлить работу процессора при обращении к портам этого оборудования. Замедление осуществляется включением в текст программы одной или, если требуется, нескольких команд безусловного перехода на следующее предложение:
in AL,300h;Первое обращение к оборудованию
jmp а;3адержка на время
a: jmp b;вьшолнения
b: jmp с;трех команд jmp
с: in AL,301h;Следующее обращение к оборудованию
Для того, чтобы не создавать много ненужных, в сущности, меток, такого рода предложения часто записывают следующим образом:
in AL,300h;Первое обращение к оборудованию
jmp S+2;3адержка на время
Глава 2
Основы программирования
jmp S+2;выполнения
jrap S+2;трех команд jmp
in AL,301h;Следующее обращение к оборудованию
Здесь используется обозначение счетчика текущего адреса S- При трансляции любой команды в счетчике текущего адреса содержится адрес этой команды (смещение ее первого байта). Команда короткого перехода занимает 2 байт, поэтому команда jmp S+2 осуществляет переход на команду, идущую следом.
Часто в подобных случаях ограничиваются одной командой jmp, которая создает необходимую задержку в доли микросекунды. В тех случаях, однако, когда устройство сопряжения с оборудованием работает заметно медленнее процессора, приходится включать между командами обращения к портам 5-6 команд jmp. Такой фрагмент можно оформить в виде блока повторения:
rept 6 jmp S+2 end m
Это, пожалуй, проще, чем писать 6 команд jmp. Макросы повторения имеют несколько разновидностей, которые мы не будем здесь рассматривать.
Макрокоманды
Программы, написанные на языке ассемблера, часто содержат повторяющиеся участки текста с одинаковой структурой. Такой участок текста можно оформить в виде макроопределения, характеризующегося произвольным именем и необязательным списком формальных аргументов. После того, как такое определение сделано, появление в программе строки, содержащей имя макроопределения и список фактических аргументов (все это вместе называют макрокомандой), приводит к генерации всего требуемого текста, называемого макрорасширением. Варьируя фактические аргументы, можно, сохраняя неизменной структуру макрорасширения, изменить отдельные его элементы.
Макроопределение должно начинаться строкой с именем макроопределения и директивой macro, в поле аргументов которой указывается список формальных аргументов. Заканчивается макроопределение директивой endm.
Пусть в программе требуется неоднократно сохранять в стеке содержимое трех регистров, но в каждом конкретном случае номера регистров и их порядок отличаются. Оформим эти действия в виде макроопределения:
psh
macroa,b,c push a push b push с endm
Появление в исходном тексте программы строки
psh АХ, ВХ, СХ
приведет к генерации следующего фрагмента текста:
АХ ВХ СХ |
push push push
Если же в исходном тексте имеется строка psh DX, ES, ВР то соответствующее макрорасширение будет иметь вид:
DX ES BP |
push push push
В качестве фактических аргументов могут выступать любые обозначения ассемблера, допустимые для данной команды. В частности, макровызов
push push push |
psh mem,[BX],ES:[17h] приведет к следующему макрорасширению:
mem [ВХ] ES:[17h]
Если какие-то строки макроопределения должны быть помечены (например, с целью организации циклов), то обозначения меток следует объявить локальными с помощью оператора local. В этом случае ассемблер, генерируя макрорасширения, будет создавать собственные обозначения меток, не повторяющиеся при повторных вызовах одной и той же макрокоманды:
delay macro
local point
mov CX,200 point: loop point
endm
Макрос delay создает задержку фиксированной длительности. Если в текст программы включить две макрокоманды delay
delay
delay
to их макрорасширения, подставленные в текст программы, будут выглядеть следующим образом:
'Основы программирования
raov loop mov loop |
??0000: |
CX.20000??0000
CX.20000??0001
??0001:
При повторных подстановках макроопределения транслятор заменяет
обозначение метки point на различающиеся обозначения "0000, "0001 и т.д., обеспечивая тем самым правильное выполнение команд циклов и переходов.
Макрокоманды схожи с подпрограммами в том отношении, что в обоих случаях мы описываем некоторый программный фрагмент один раз, а обращаемся к нему многократно, возможно, с передачей различных параметров. Однако эти вычислительные средства различаются как по способу использования, так и по своим возможностям.
Подпрограммы позволяют сократить объем выполнимого файла за счет описания повторяющихся участков программы лишь однажды. При каждом вызове подпрограммы командой call происходит переход на один и тот же фрагмент программы, содержащий подпрограмму, а после выполнения подпрограммы — возврат назад в точку вызова. Текст подпрограммы полностью определяется на этапе ее написания, и изменения в ходе выполнения подпрограммы возможны только за счет передачи ей тех или иных конкретных значений.
Механизм использования макроса иной. Каждая макрокоманда, встретившаяся транслятору в тексте программы, заменяется им на полный текст макроопределения. Если макрокоманда содержит параметры, то в процессе этой замены происходит подстановка параметров в текст макроопределения. Образованное таким образом макрорасширение составляет часть текста программы, неотличимо от остальных предложений программы и не нуждается в каких-либо вызовах. В силу этих обстоятельств макрокоманды оказываются несколько эффективнее подпрограмм по скорости выполнения, особенно, если учесть время, требуемое для подготовки параметров перед вызовом подпрограммы (например, проталкивание их в стек). Вряд ли стоит, однако, проводить такое сравнение. Подпрограммы и макрокоманды имеют различные области применения.
Подпрограммы служат для сокращения объема программы, повышения ее наглядности и упрощения перестройки алгоритма выполнения всего программного комплекса путем изменения состава и порядка вызываемых подпрограмм. При этом активное использование подпрограмм может уменьшить размер всей программы в десятки раз.
Смысл использования макрокоманд совсем иной. Макрокоманды позволяют упростить процесс написания программы и, можно сказать, являются средством автоматизации программирования. При этом язык макрокоманд предоставляет большие возможности по изменению текста макрорасширения в зависимости от указываемых в макрокоманде параметров. Проиллюстрируем эти возможности на простом примере макрокоманды
вывода на экран символа. Такой макрокомандой можно пользоваться в процессе отладки сложных программ, чтобы получать информацию о содержимом любых ячеек памяти. Пример оформлен в виде законченной программы, которая носит чисто демонстрационный характер.
;Пример 2-1. Использование макрокоманды
sym | macro с; |
push | AX; |
push | DX; |
mov | AH,02h;< |
mov | DL,c;: |
int | 21h; |
pop | DX; |
pop | AX;i |
cndm | ?• |
code | segment |
assume cs:code | |
main | proc |
sym | V;< |
sym | ES:0;: |
sym | CS:msg;: |
lea | BX,msg+l;, |
sym | [BX];] |
mov | AX,40h;; |
mov | DS,AX;i |
sym | DS:49h;] |
mov | AX,4COOh;: |
int | 21h |
main | cndp |
msg | db 'OK' |
code | ends |
;Имя и формальный аргумент
;Сохраним используемые
;в макроопределении регистры
;функция DOS вывода символа
;3аберем символ
; Вызов DOS
восстановим
;регистры
;Конец макроопределения
;Символ указан непосредственно
; Вывод первого байта PSP
; Вывод первой буквы из nisg
;Адрес второй буквы из nisg
;Вывод второй буквы
; Настроим DS
;на начало памяти
;Вывод номера видеорежима
;3авершение программы
Тексты макроопределений обычно размещаются в самом начале программы, что дает возможность вызывать макрокоманды из любых точек программы. Содержательная часть макроса syni состоит в вызове функции 021i DOS, которая выводит на экран символ из регистра DL. Поскольку макрос использует регистры АХ и DX, они в начале макроса сохраняются в стеке, а перед его завершением восстанавливаются. В качестве параметра макрокоманды можно использовать любое обозначение ассемблера, которое может интерпретироваться, как адрес символа.
Сама программа умышленно построена несколько нестандартным образом. В ней имеется единственный сегмент с текстом программы, в конце которого помещена строка данных (слово 'ОК'). Такое расположение данных допустимо, однако для обращения к ним необходимо использовать замену сегмента (как это сделано в третьей строке программы), так как программный сегмент адресуется через регистр CS. Сегмент стека в Программе отсутствует, что не очень хорошо, но для небольших программ Допустимо. Фактически под стек будет использован самый низ сегмента
Глава 2
Г
Основы программирования
команд, начиная с адреса FFFEh, Поскольку наша программа имеет размер, существенно меньше 64К, такое расположение стека не приведет ни к каким неприятностям (при большом размере программы стек мог бы начать затирать нижние строки программы).
В программе проиллюстрировано использование в качестве фактического аргумента макрокоманды различных конструкций языка: непосредственного обозначения символа (что, наверное, лишено смысла), прямого обращения к различным участкам памяти по абсолютным адресам через регистры ES и DS, адресации с использованием символического обозначения поля данных. На рис. 2.18 приведен вывод программы.
FACURREMT>p.exe u=OK*
Рис. 2.18. Вывод программы 2.1.
Как уже отмечалось, при загрузке программы в память в регистры DS и ES заносится сегментный адрес префикса программы, поэтому адресация через ES позволяет прочитать содержимое PSP. Префикс содержит, главным образом, данные, необходимые системе для обслуживания текущей программы, но, кроме того, и несколько команд. В частности, префикс начинается с команды CD 20h, которая уже давно не используется, но в префиксе присутствует ради обеспечения' совместимости со старыми версиями DOS. Первый байт этой команды, если его рассматривать, как код символа, соответствует элементу двойной горизонтальной рамки (длинный знак равенства).
Занеся в регистр DS число 40U, мы настроили его на начало области данных BIOS, которая начинается с абсолютного адреса 400h, занимает 256 байт и содержит разнообразные данные, используемые BIOS в процессе обслуживания аппаратуры компьютера. Так, например, по адресу О от начала этой области хранится базовый адрес первого последовательного порта; по адресу 8 — адрес первого параллельного порта, а по адресу 49h — код текущего видеорежима. При работе в DOS видеоадаптер обычно настраивается на режим 3 (80x25 символов, 16 цветов). Будучи выведен на экран, код 3 образует изображение червонного туза.
В тех случаях, когда макрокоманды состашгяются для конкретной программы, они включаются в текст программы так, как это было сделано в примере 2.1. Однако часто программист оформляет в виде макрокоманд стандартные процедуры общего назначения, например, программную задержку или вывод на экран строки текста. В этом случае тексты макроопределений целесообразно поместить в макробиблиотеку.
Макробиблиотека представляет собой файл с текстами макроопределений. Макроопределения записываются в этот файл точно в таком же виде, как и в текст программы. Ниже приведен текст файла макробиблиотеки с произвольным именем MYMACRO.MAC, содержащей две макрокоманды.
;Макрокоманда endpr завершения программы
endpr macro Макрокоманда без параметров
mov AX,4COOh
hit 21h
endm;Конец макрокоманды
Макрокоманда delay настраиваемой программной задержки
delay macro time;Параметр — число шагов
local Iabell,label2;Локальньге метки
push CX,;Сохраним внешний счетчик
mov CX,time;Получим фактический параметр
Iabel2: push CX;Сохраним его в стеке
mov СХ,0;Пустъ будет 64К шагов
labell: loop lanell;Внутренний цикл
pop CX;Извлечем внешний счетчик
loop Iabel2;Внешний цикл
pop CX восстановим CX программы
endm;Конец макрокоманды
. Для того чтобы транслятору были доступны макрокоманды из файла MYMACRO.MAC, его следует на этапе трансляции подсоединить к исходному тексту программы директивой ассемблера include:
include mymacro.mac
Все макрокоманды, включенные в этот файл, можно использовать в любом месте программы.
Директивы условной трансляции
Директивы условной трансляции (условного ассемблирования) позволяют иметь в исходном тексте программы различные варианты отдельных фрагментов программы, и путем задания определенных условий управлять процессом трансляции. Таким образом можно, например, включать или исключать из текста программы служебные, отладочные фрагменты или настраивать программ)' для выполнения на заданном процессоре.
Пусть, например, в процессе отладки сложной программы мы используем подпрограмму regs вывода на экран содержимого всех регистров процессора. Включая в разные места программы вызов этой подпрограммы, мы имеем возможность контролировать ход ее выполнения, в том числе и такие тонкие моменты, как, например, расположение программы в памяти или интенсивность использование стека. Для управления процессом трансляции предусмотрим константу debug (отладка), ненулевое значение которой будет требовать отладочного варианта трансляции, а нулевое — рабочего. Начало программы, а также участки с вызовом отладочной подпрограммы будут выглядеть следующим образом:
;debug=l;Удалите символ ';'для отладочной трансляции
;debug=0;Удалите ';' для рабочей трансляции
;Текст программы
Глава 2
Основы программирования
if debug
call endif
if debug
call endif
regs
regs
Транслировать только если debug=l;Вызов отладочной подпрограммы;Конец блока условной трансляции Продолжение профаммы;Следующее включение отладочного блока
push push push push push push endif
CX
DX
BX
BP
SI
DI
if 1386 | |
рора | |
else | |
pop | DI |
pop | SI |
pop | BP |
pop | BX |
pop | DX |
pop | CX |
pop | AX |
endif |
Если в начале программы имеется объявление 1386=1, то, во-первых, в программу будет включена директива,.386, позволяющая использовать в программе дополнительные команды, а во-вторых, в последующих условных блоках будут транслироваться те их участки, которые содержат команды процессора 80386. Если же объявление i386=l изъять, то в условных блоках будут транслироваться эквивалентные по существу, но менее эффективные последовательности команд МП 86. |
Продолжение программы
Разумеется, можно отлаживать программу в отладочном варианте, а затем удалить все вызовы вспомогательной подпрограммы regs вручную и получить рабочий вариант, однако на практике обычно (или даже всегда) оказывается, что после эксплуатации программы в течение некоторого времени в ней обнаруживаются незамеченные ранее ошибки, что приводит к необходимости снова вставлять в нее отладочные строки. Часто эту процедуру приходится повторять многократно. Использование в программе директив условной трансляции сокращают процедуру преобразования программы из отладочного варианта в рабочий или наоборот до операции стирания одного символа «;» в начале программы И устраняют вероятность случайного внесения в программу новых ошибок в процессе удаления или вставки отладочных строк.
Рассмотрим еще один пример применения директив условной трансляции. Как уже отмечалось, современные процессоры предоставляют программисту значительное количество дополнительных команд, которые можно использовать в программах реального режима, но только, разумеется, если компьютер оснащен соответствующим процессором. Нетрудно составить универсальную программу, которую можно выполнять как на современных процессорах (в более эффективном режиме), так и на более старых (с некоторой потерей эффективности), если включить в нее директивы условной трансляции этих дополнительных команд. К таким командам, в частности, относятся команды сохранения в стеке всех регистров общего назначения pusha и восстановления всех регистров рора. Приведем пример условной трансляции этих команд, в котором используется конструкция макроязыка if... else... endif:
i386=l
if i386
.386
endif
code segment useI6
assume CS:code
main proc
if i386 pusha else push AX |
;Сохранение всех регистров одной командой
Использование регистров после;сохранения их исходных значений
;Восстановление всех регистров одной командой
Команды и алгоритмы
Глава 3. КОМАНДЫИ АЛГОРИТМЫ
3.1. Организация приложений MS-DOS
Как уже отмечаюсь выше, язык ассемблера является отражением архитектуры процессора, и изучение языка в сущности означает изучение системы команд и способов адресации, реализуемых процессором. Однако любой язык программирования полезен лишь постольку, поскольку на нем можно написать какие-то работоспособные программы. В то же время трудно представить себе реальную программу, которая выполняет чисто логические или вычислительные действия, ничего не вводя и не выводя и не взаимодействуя с другими программами. Однако такие вопросы, как организация выполнимой программы, ее запуск, взаимодействие с разнообразными аппаратными и программными объектами вычислительной системы (клавиатурой, дисками, таймером, памятью, системными драйверами и проч.) и, наконец, завершение являются прерогативой операционной системы. Поэтому в программах на языке ассемблера всегда широко используются системные средства, например, для вывода на экран или ввода с клавиатуры, чтения или записи файлов, управления памятью и проч. Более того, сама внутренняя организация программы, ее структура и, в определенной степени, алгоритмы поведения в сильной степени определяются правилами организации вычислительного процесса, заложенными в DOS. Изучение языка ассемблера в отрыве от конкретной операционной системы вырождается в схоластическое занятие, результатом которого будет знание формальных правил написания программных предложений без возможности применить эти правила для создания работоспособных программ.
В то же время возможности даже такой относительно простой операционной системы, как MS-DOS, весьма велики и многообразны, и их изучение составляет самостоятельный раздел программирования. В настоящей книге средства DOS рассматриваются лишь в том минимальном объеме, который необходим для создания простых, но работоспособных программ на языке ассемблера, а также для демонстрации основных алгоритмов и приемов программирования.
Желающие получить более глубокое представление о возможностях MS-DOS и использовании функций DOS в прикладном программировании, могут обратиться к книге: К.Г.Финогенов «Самоучитель по системным функциям MS-DOS», M., Радио и связь, Энтроп, 1995.
К числу важнейших вопросов, требующих хотя бы минимального рассмотрения, следует отнести требования, предъявляемые MS-DOS к структуре прикладных программ, а также к особенностям их взаимодействия с самой DOS и с другими программами.
Программы, предназначенные для выполнения под управлением MS-DOS, можно классифицировать по разным признакам. По внутренней
организации все программы принадлежат к одному из двух типов, которым соответствуют расширения имен программных файлов.ЕХЕ и.СОМ. По взаимодействию с самой DOS программы подразделяются на транзитные и резидентные. Наконец, следует выделить важнейших класс программ, служащих для обработки аппаратных или программных прерываний, и называемых обычно обработчиками прерываний. Мы не касаемся здесь таких специфических программ, как устанашшваемые драйверы устройств, командные процессоры (к их числу принадлежит COMMAND.COM) или оболочки DOS (например, широко распространенная программа Norton Commander), которые можно выделить в самостоятельные классы.
Первый пример законченной программы, рассмотренный нами в гл. 2, относился к наиболее распространенному типу.ЕХЕ-приложений. Для такой программы характерно наличие отдельных сегментов команд, данных и стека; для адресации к полям каждого сегмента используется свой сегментный регистр. Удобство.ЕХЕ-программы заключается в том, что ее можно почти неограниченно расширять за счет увеличения числа сегментов. В случае большого объема вычислений в программу можно включить несколько сегментов команд, обеспечив, разумеется, переходы из сегмента в сегмент с помощью команд дальних переходов или дальних вызовов подпрограмм. Если же программа должна обрабатывать большие объемы данных, в ней можно предусмотреть несколько сегментов данных. Каждый сегмент не может иметь размер более 64 Кбайт, однако в сумме их объем ограничивается только наличной оперативной памятью. Правда, в реальном режиме затруднительно обратиться к памяти за пределами 1 Мбайт адресного пространства, так что максимальный размер программы, если не предусматривать в ней какие-то специальные средства поочередной загрузки сегментов, ограничен величиной 550... 600 Кбайт. Наличие в МП 86 лишь двух сегментных регистров данных (DS и ES) несколько усложняет алгоритмы обработки больших объемов данных, так как приходится постоянно переключать эти регистры с одного сегмента на другой. Однако реально в современных процессорах имеются не два, а четыре сегментных регистра данных (DS, ES, FS и GS), которые вполне можно использовать в приложениях DOS, упростив тем самым процедуры обращения к данным и ускорив выполнение программ. Позже вес эти возможности будут рассмотрены более подробно.
Во многих случаях объем программы оказывается невелик — меньше, а часто и много меньше, чем 64 Кбайт. Такую программу нет никакой необходимости составлять из нескольких сегментов: и команды, и данные, и стек можно разместить в единственном сегменте, настроив на его начало все 4 сегментных регистра. Для односегментных программ в MS-DOS существует специальный формат и специальные правила их составления. Программные файлы с программами, составленными по этим правилам, имеют расширение -СОМ. В формате.СОМ обычно пишутся резидентные программы и драйверы, хотя любую прикладную программу небольшого объема можно оформить в виде,СОМ-приложения. Если посмотреть список системных программ, входящих в DOS, и реализующих, в частности, внешние команды DOS, то можно заметить, что приблизи-