--------------------------------------------- ПЕЧАТЬ Цель: Описать возможности программ на языке ассемблера для вывода информации на печатающее устройство (принтер). ВВЕДЕНИЕ ------------------------------------------------------------ Вывод на принтер несколько проще операций с экраном и диском. Для печати существует несколько операций, выполняющихся через DOS INT 21H и BIOS INT 17H. Команды, посылаемые на принтер, включают коды "конец страницы", "конец строки" и "возврат каретки". Принтеры классифицируются по качеству печати. Матричный принтер формирует символы в виде матрицы точек и обеспечи вает нормальный, узкий и широкий форматы символов. Более совершенные матричные принтеры обеспечивают точечную графи ку, наклонный шрифт, жирную печать и двойную плотность, а также могут печатать, например, символы игральных карт и другие алфавитно-цифровые символы. Высококачественные печа тающие устройства ограничены набором символов на сменной "ромашке" или барабане, но обеспечивают отличное качество печати и большое разнообразие принтеров. Многие высокока чественные принтеры обеспечивают печать 10,12 или 15 симво лов на дюйм, а также пропорциональное расположение пробелов, подчеркивание, теневую и полужирную печать. Лазерные принте ры обладают преимуществами как для матричной графики, так и для качественной печати текстов. Другая классификация печатающих устройств связана с интер фейсами. Компьютеры IBM PC имеют параллельный интерфейс, позволяющий передавать одновременно восемь битов из процес сора на принтер. Кроме того, существует последовательный интерфейс, который выполняет побитовую передачу данных. Многие принтеры имеют буфер памяти, объемом в несколько тысяч байтов. Принтеры также могут принимать биты контроля на четность (нечетность). Принтеры должны "понимать" специ альные сигналы из процессора, например, для прогона листа, перевода строки или горизонтальной табуляции. В свою очередь, процессор должен "понимать" сигналы от принтера, указывающие на конец бумаги или состояние "занято". К сожалению многие типы принтеров по разному реагируют на сигналы процессора и одной из наиболее сложных проблем для программистов - обеспечить соответствие собственных программ имеющимся печатающим устройством. СИМВОЛЫ УПРАВЛЕНИЯ ПЕЧАТЬЮ ------------------------------------------------------------ Стандартными символами управления печатью являются сле дующие: Десятичн. Шест. Назначение Ассемблер для IBM PC. Глава 19 2 08 08 Возврат на шаг 09 09 Горизонтальная табуляция 10 0A Перевод строки 11 0B Вертикальная табуляция 12 0C Прогон страницы 13 0D Возврат каретки Горизонтальная табуляция. Горизонтальная табуляция (шест. 09) возможна только на принтерах, имеющих соответствующее обеспечение, иначе символы табуляции игнорируются. В последнем случае можно имитировать табуляцию выводом соответствующего числа пробелов. Перевод строки. Символ перевода строки (шест.OA) исполь зуется для прогона листа на один интервал. Соответственно для печати через два интервала используется два символа перевода строки. Прогон страницы. Установка бумаги после включения принте ра определяет начальную позицию печати страницы. Длина страницы по умолчанию составляет 11 дюймов. Ни процессор, ни принтер автоматически не определяют конец страницы. Если ваша программа продолжает печатать после конца страницы, то произойдет переход через межстраничную перфорацию на на чало следующей страницы. Для управления страницами необходи мо подсчитывать число напечатанных строк и при достижении максимального значения (например, 55 строк) выдать код прого на страницы (шест.OC) и, затем, сбросить счетчик строк в 0 или 1. В конце печати необходимо выдать символ "перевода строки" или "прогона страницы" для вывода на печать данные последней строки, находящиеся в буфере печатающего устройства. Использование последнего символа "прогон страницы" позволяет установить напечатанный последний лист в положение для отрыва. ФУНКЦИИ ПЕЧАТИ В РАСШИРЕННОЙ ВЕРСИИ DOS ------------------------------------------------------------ В операционной системе DOS 2.0 имеются файловые указатели, которые были показаны в главах по управлению экраном дисплея и дисковой печати. Для вывода на печатающее устройство используется функция DOS шест.40 и стандартный файловый номер 04. Следующий пример демонстрирует печать 25 символов из области HEADG: HEADG DB 'Industrial Bicycle Mfrs', 0DH, 0AH ... MOV AH,40H ; Запрос печати MOV BX,04 ; Файловый номер принтера MOV CX,25 ; 25 символов LEA DX,HEADG ; Область вывода INT 21H ; Вызов DOS Ассемблер для IBM PC. Глава 19 3 В случае ошибки операция устанавливает флаг CF и возвраща ет код ошибки в регистре AX. ПРОГРАММА: ПОСТРАНИЧНАЯ ПЕЧАТЬ С ЗАГОЛОВКАМИ ------------------------------------------------------------ Программа, приведенная на рис.19.1, аналогична программе на рис.9.1, за исключением того, что после ввода имен с клавиатуры выводит их не на экран, а на печатающее устрой ство. Каждая напечатанная страница содержит заголовок и через двойной интервал список введенных имен в следующем виде: List of Employee Names Page 01 Clancy Alderson Ianet Brown David Christie ... Программа подсчитывает число напечатанных строк и при дос тижении конца страницы выполняет прогон до начала следующей страницы. В программе имеются процедуры: D10INPT Выдает на экран запрос и затем вводит имя с кла виатуры. E10PRNT Выводит имя на печатающее устройство (длина име ни берется из вводного списка параметров); в конце страницы вызывает процедуру M10PAGE. M10PAGE Выполняет прогон на новую страницу, печатает за головок, сбрасывает счетчик строк и увели чивает счетчик страниц на единицу. P100UT Общая подпрограмма для непосредственного вывода на печатающее устройство. В начале выполнения необходимо напечатать заголовок, но не делать перед этим перевод страницы. Поэтому процедура M10PAGE обходит перевод страницы, если счетчик PAGECTR содержит 01 (начальное значение). Поле PAGECTR определено как PAGECTR DB '01' В начале выполнения необходимо напечатать заголовок, но не делать перед этим перевод страницы. Поэтому процедура M10PAGE обходит перевод страницы, если счетчик PAGECTR содержит 01 (начальное значение). Поле PAGECTR определено как PAGECTR DB '01' В результате будет сгенерировано число в ASCII коде - шест. 3031. Процедура M10PAGE увеличивает счетчик PAGECTR на 1 так, что значение становится последовательно 3032, 3033 и т.д. Эти значения корректны до 3039, далее следует 303A, что будет распечатано, как двоеточие (:). Поэтому, если в правом байте поля PAGECTR появляется шест.3A, то это значение Ассемблер для IBM PC. Глава 19 4 заменяется на шест.30, а к левому байту прибавляется единица. Таким образом шест.303A перекодируется в шест. 3130, т.е. в 10 в символьном представлении. ------------------------------------------------------------ ------------------------------------------------------------ Рис.19.1. Постраничная печать с заголовком. Проверка на конец страницы до (но не после) печати имени гарантирует, что на последней странице будет напечатано по крайней мере одно имя под заголовком. ПЕЧАТЬ ASCII-ФАЙЛОВ И ТАБУЛЯЦИЯ ------------------------------------------------------------ Табуляция, обеспечиваемая, например, видеоадаптерами, заключается в замене одного символа табуляции (код 09) несколькими пробелами при выводе так, чтобы следующая позиция была кратна 8. Таким образом, стандартные позиции табуляции являются 8, 16, 24 и т.д. Многие принтеры, однако, игнорируют символы табуляции. Поэтому, такая программа, как DOS PRINT, предназначенная для печати ASCII файлов (например ассемблерных исходных текстов) проверяет каждый символ, посылаемый на принтер. И, если обнаруживается символ табуляции, то программа выдает несколько пробелов до позиции кратной 8. Программа, приведенная на рис.19.2, выводит на экран запрос на ввод имени файла и, затем, печатает содержимое указанного файла. Эта программа в отличие от приведенной на рис.17.3 (вывод файлов на экран) осуществляет замену выводимых символов табуляции на соответствующее число пробелов. В результате символ табуляции в позициях от 0 до 7 приводит к переходу на позицию 8, от 8 до 15 - на 16 и т.д. Команды, реализующие данную логику, находятся в процедуре G10XFER после метки G60. Рассмотрим три примера обработки символа табуляции: Текущая позиция печати: 1 9 21 Двоичное значение: 00000001 00001001 00010101 Очистка трех правых битов: 00000000 00001000 00010000 Прибавление 8: 00001000 00010000 00011000 Новая позиция: 8 16 24 В программе организованы следующие процедуры: С10PRMP Запрашивает ввод имени файла. Нажатие только клавиши Return приводит к завершению работы программы. E10OPEN Открывает дисковый файл по указанному имени. G10XFER Контролирует конец сектора, конец файла, конец области вывода, символы "перевод строки" и табуля ции. Пересылает обычные символы в область вывода. Ассемблер для IBM PC. Глава 19 5 P10PRNT Распечатывает выводную строку и очищает область вывода. R10READ Считывает сектор из дискового файла. Коды "возврат каретки", "перевод строки" и "прогон страницы" действительны для любых принтеров. Можно модифици ровать программу для подсчета распечатываемых строк и выполнения прогона страницы (шест.OC) при достижении, например, строки 62. ------------------------------------------------------------ ------------------------------------------------------------ Рис.19.2. Печать ASCII файла. Некоторые пользователи предпочитают устанавливать символы "прогон страницы" в ASCII файлах с помощью текстового редактора в конкретных местах текста, например, в конце ассемблерных процедур. Кроме того, можно изменить программу для функции 05 базовой версии DOS. Эта функция выполняет вывод каждого символа непосредственно на принтер. Таким образом можно исключить определение и использование области вывода. ПЕЧАТЬ ПОД УПРАВЛЕНИЕМ БАЗОВОЙ DOS ------------------------------------------------------------ Для печати в базовой версии DOS необходимо установить в регистре AH код функции 05, а в регистр DL поместить распечатываемый символ и, затем, выполнить команду INT 21H следующим образом: MOV AH,05 ;Запрос функции печати MOV DL,char ;Распечатываемый символ INT 21H ;Вызов DOS С помощью этих команд можно передавать на принтер управляющие символы. Однако, печать, обычно, предполагает вывод полной или частичной строки текста и пошаговую обработку области данных, отформатированной по строкам. Ниже показазна программа печати полной строки. Сначала в регистр SI загружается начальный адрес области HEADG, а в регистр CX - длина этой области. Цикл, начинающийся по метке P20, выделяет очередной символ из области HEADG и посылает его на принтер. Так как первый символ области HEADG - "прогон страницы", а последние два - "перевод строки", то заголовок печатается в начале новой страницы и после него следует двойной интервал. HEADG DB 0CH,'Industrial Bicycle Mfrs',0DH,0AH,0AH LEA SI,HEADG ;Установка адреса и MOV CX,27 ; длины заголовка P20: MOV AH,05 ;Запрос функции печати Ассемблер для IBM PC. Глава 19 6 MOV DL,[SI] ;Символ из заголовка INT 21H ;Вызов DOS INC SI ;Следующий символ LOOP P20 Пока принтер не включен, DOS выдает сообщения "Out of pap er". После включения питания программа начинает работать нормально. Для прекращения печати можно нажать клавиши Ctrl/Break. СПЕЦИАЛЬНЫЕ КОМАНДЫ ПРИНТЕРА ------------------------------------------------------------ Выше уже был показан ряд команд, которые являются основными для большинства печатающих устройств. Кроме того существуют следующие команды: Десятичн. Шест. 15 0F Включить узкий формат 14 0E Включить широкий формат 18 12 Выключить узкий формат 20 14 Выключить широкий формат Есть команды, которые распознаются по предшествующему символу Esc (шест.IB). Некоторые из них в зависимости от печатающего устройства представлены ниже: 1B 30 Установить плотность 8 строк на дюйм 1B 32 Установить плотность 6 строк на дюйм 1B 45 Включить жирный формат 1B 46 Выключить жирный формат Коды команд можно посылать на принтер двумя разными способами: 1. Определить команды в области данных. Следующий пример устанавливает узкий формат, 8 строк на дюйм, затем печатает заголовок с завершающими командами "возврат каретки" и " перевод строки": HEADG DB 0FH, 1BH, 30H, 'Title...', 0DH, 0AH 2. Использовать команды с непосредственными данными: MOV AH,05 ;Запрос функции печати MOV DL,0FH ;Включить узкий формат INT 21H Все последующие символы будут печататься в узком формате до тех пор, пока программа не выдаст на принтер команду, выключающую этот формат. Ассемблер для IBM PC. Глава 19 7 Приведенные команды не обязательно работают на принтерах любых моделей. Для проверки возможных команд управления следует ознакомиться с руководством конкретного печатающего устройства. ПЕЧАТЬ С ПОМОЩЬЮ BIOS INT 17H ------------------------------------------------------------ Прерывание BIOS INT 17H обеспечивает три различные операции, специфицированные содержимым регистра AH: AH=0: Данная операция выполняет печать одного символа на три принтера по номерам 0,1 и 2 (стандартное значение - 0). MOV AH,00 ;Запрос функции печати MOV AL,char ;Символ, выводимый на печать MOV DX,00 ;Выбор принтера Э 0 INT 17H ;Вызов BIOS Если операция не может распечатать символ, то в регистре AH устанавливается значение 01. AH=1: Инициализация порта печатающего устройства: MOV AH,01 ;Запрос на инициализацию порта MOV DX,00 ;Выбор порта Э 0 INT 17H ;Вызов BIOS Данная операция посылает на принтер символ "прогон страницы", поэтому ее можно использовать для установки положения "верх страницы". Большинство принтеров выполняют данную установку автоматически при включении. AH=2: Чтение состояние порта принтера: MOV AH,02 ;Функция чтения состояния порта MOV DX,00 ;Выбор порта Э 0 INT 17H ;Вызов BIOS TEST AH,00101001B; Принтер готов? JNZ errormsg ;Нет - выдать сообщение об ошибке Назначение функций AH=1 и AH=2 состоит в определении состояния принтера. В результате выполнения этих функций биты регистра AH могут устанавливаться в 1: Бит Причина 7 Не занято 6 Подтверждение от принтера 5 Конец бумаги 4 Выбран 3 Ошибка ввода/вывода 0 Таймаут Ассемблер для IBM PC. Глава 19 8 Если прринтер включен, то операция возвращает шест.90 или двоичное 10010000 - принтер "не занят" и "выбран" - это нормальное состояние готовности. В случае неготовности принтера устанавливаются бит 5 (конец бумаги или бит 3 (ошибка вывода). Если принтер выключен, то операция возвращает шест.B0 или двоичное 10110000, указывая на "конец бумаги". Выполняя программу при выключенном принтере, BIOS не выдает сообщение автоматически, поэтому предполагается, что программа должна сама проверить и отреагировать на состояние принтера. Если программа не делает этого, то единственной индикацией будет мигающий курсор на экране дисплея. Если в этот момент включить принтер, то некоторые выходные данные могут быть потеряны. Следовательно, прежде чем использовать функции BIOS для печати, следует проверить состояние порта принтера и, если будет обнаружена ошибка, то выдать соответствующее сообщение. (Функции DOS выполняют эту проверку автоматически, хотя их сообщение "Out of paper" относится к различным состояниям). После включения принтера, вывод сообщений об ошибке прекращается и принтер начинает нормально работать без потери данных. В процессе работы принтер может выйти за страницу или быть нечаянно выключен. Поэтому в программах печати следует предусмотреть проверку состояния принтера перед каждой попыткой печати. ОСНОВНЫЕ ПОЛОЖЕНИЯ НА ПАМЯТЬ ------------------------------------------------------------ ъ Прежде чем выводить данные на печатающее устройство, включите принтер и вставьте в него бумагу. ъ Для завершении печати используйте символы "перевод строки" и "прогон страницы" для очистки буфера принтера. ъ Функции DOS для печати предусматривают вывод сообщений при возникновении ошибки принтера. Функции BIOS возвращают только код состояния. При использовании BIOS INT 17H проверяйте состояние принтера перед печатью. ВОПРОСЫ ДЛЯ САМОПРОВЕРКИ ------------------------------------------------------------ 19.1. Напишите программу в расширенной версии DOS для а) прогона страницы; б) печати вашего имени; в) перевода строки и печати вашего адреса; г) перевода строки и печати названия вашего города/штата (республики); д) прогона страницы. 19.2. Переделайте программу из предыдущего вопроса для базовой версии DOS. Ассемблер для IBM PC. Глава 19 9 19.3. Закодируйте строку, в которой имеется следующая информация: возврат каретки, прогон страницы, включе ние узких букв, заголовок (любое имя) и выключение узких букв. 19.4. Измените программу из вопроса 19.1 для использования BIOS INT 17H. Обеспечьте проверку состояния принтера. 19.5. Измените программу из вопроса 19.1 так, чтобы пункты б), в), г) выполнялись по 5 раз. 19.6. Измените программу на рис.19.1 для выполнения в базовой версии DOS. 19.7. Измените программу на рис.19.2 так, чтобы распечатывае мые строки также выводились на экран. Ассемблер для IBM PC. Глава 20 18 ГЛАВА 20. Макросредства ------------------------------------------------------------ Макросредства Цель: Объяснить определение и использование ассемблерных макрокоманд. ВВЕДЕНИЕ ------------------------------------------------------------ Для каждой закодированной команды ассемблер генерирует одну команду на машинном языке. Но для каждого закодированного оператора компиляторного языка Pascal или C генерируется один или более (чаще много) команд машинного языка. В этом отношении можно считать, что компиляторный язык состоит из макро операторов. Ассемблер MASM также имеет макросредства, но макросы здесь определяются программистом. Для этого задается имя макроса, директива MACRO, различные ассемблерные команды, которые должен генерировать данный макрос и для завершения макропределения - директива MEND. Затем в любом месте программы, где необходимо выполнение определенных в макроко манде команд, достаточно закодировать имя макроса. В резуль тате ассемблер сгенерирует необходимые команды. Использование макрокоманд позволяет: - упростить и сократить исходный текст программы; - сделать программу более понятной; - уменьшить число возможных ошибок кодирования. Примерами макрокоманд могут быть операции ввода-вывода, связанные с инициализацией регистров и выполнения прерываний преобразования ASCII и двоичного форматов данных, арифмети ческие операции над длинными полями, обработка строковых данных, деление с помощью вычитания. В данной главе рассмотрены особенности макросредств, включая те, которые не достаточно ясно даны в руководстве по ассемблеру. Тем не менее пояснения для некоторых малоисполь зуемых операций следует искать в руководстве по ассеблеру. ПРОСТОЕ МАКРООПРЕДЕЛЕНИЕ ------------------------------------------------------------ Макроопределение должно находиться до определения сегмента. Рассмотрим пример простого макроопределения по имени INIT1, которое инициализирует сегментные регистры для EXE-программы: INIT1 MACRO ;Начало ASSUME CS:CSEG,DS:DSEG,SS:STACK;ES:DSEG ; \ PUSH DS ; \ Ассемблер для IBM PC. Глава 20 19 SUB AX,AX ; \ PUSH AX ;Тело \ MOV AX,DSEG ;макро/ MOV DS,AX ; / MOV ES,AX ; / ENDM ;Конец Директива MACRO указывает ассемблеру, что следующие команды до директивы ENDM являются частью макроопределения. Имя маккрокоманды - INIT1, хотя здесь возможны другие правильные уникальные ассемблерные имена. Директива ENDM завершает макроопределение. Семь команд между директивами MACRO и ENDM составляют тело макроопределения. Имена, на которые имеются ссылки в макроопределении, CSEG, DSEG и STACK должны быть определены где-нибудь в другом месте программы. Макрокоманда INIT1 может использо ваться в кодовом сегменте там, где необходимо инициализиро вать регистры. Когда ассемблер анализирует команду INIT1, он сначала просматривает таблицу мнемокодов и, не обнаружив там соответствующего элемента, проверяет макрокоманды. Так как программа содержит определение макрокоманды INIT1 ассем блер подставляет тело макроопределения, генерируя необходи мые команды - макрорасширение. Программа использует рассматриваемую макрокоманду только один раз, хотя имеются другие макрокоманды, предназначенные на любое число применений и для таких макрокоманд ассемблер генерирует одинаковые макрорасширения. На рис.20.1 показана ассемблированная программа. В листин ге макрорасширения каждая команда, помеченная слева знаком плюс (+), является результатом генерации макрокоманды. Кроме того, в макрорасширении отсутствует директива ASSUME, так как она не генерирует объектный код. В последующем разделе "Включение из библиотеки макро определений показана возможность каталогизации макрокоманд в библиотеке и автоматическое включение их в любые программы. ------------------------------------------------------------ ------------------------------------------------------------ Рис.20.1. Пример ассемблирования макрокоманды. ИСПОЛЬЗОВАНИЕ ПАРАМЕТРОВ В МАКРОКОМАНДАХ ------------------------------------------------------------ В предыдущем макроопределении требовались фиксированные имена сегментов: CSEG, DSEG и STACK. Для того, чтобы макро команда была более гибкой и могла принимать любые имена сегментов, определим эти имена, как формальные параметры: INIT2 MACRO CSNAME,DSNAME,SSNAME ;Формальные параметры ASSUME CS:CSNAME,DS:DSNAME,SC:SSNAME,ES:DSNAME PUSH DS SUB AX,AX PUSH AX Ассемблер для IBM PC. Глава 20 20 MOV AX,DSNAME MOV DS,AX MOV ES,AX ENDM ;Конец макроопределения Формальные параметры в макроопределении указывают ассемблеру на соответствие их имен любым аналогичным именам в теле макроопределения. Все три формальных параметра CSNAME, DSNAME и SSNAME встречаются в директиве ASSUME, а параметр DSNAME еще и в последующей команде MOV. Формальные параметры могут иметь любые правильные ассемблерные имена, не обязательно совпадающими именами в сегменте данных. Теперь при использовании макрокоманды INIT2 необходимо указать в качестве параметров действительные имена трех сегментов в соответствующей последовательности. Например, следующая макрокоманда содержит три параметра, которые соответствуют формальным параметрам в исходном макроопреде лении: Макроопределение: INIT2 MACRO CSNAME,DSNAME,SSNAME (формальные параметры) Макрокоманда: | | | INIT2 CSEG,DSEG,STACK (параметры) Так как ассемблер уже определил соответствие между формальны ми параметрами и операторами в макроопределении, то теперь ему остается подставить параметры макрокоманды в макрорасши рении: - Параметр 1: CSEG ставится в соответствие с CSNAME в макроопределении. Ассемблер подставляет CSEG вместо CSNAME в директиве ASSUME. - Параметр 2: DSEG ставится в соответствие с DSNAME в макроопределении. Ассемблер подставляет DSEG вместо двух DSNAME: в директиве ASSUME и в команде MOV. - Параметр 3: STACK ставится в соответствие с SSNAME в макроопределении. Ассемблер подставляет STACK вместо SSNAME в директиве ASSUME. Макроопределение с формальными параметрами и соответствую щее макрорасширение приведены на рис.20.2. ------------------------------------------------------------ ------------------------------------------------------------ Рис.20.2. Использование параметров в макрокомандах. Формальный параметр может иметь любое правильное ассемблерное имя (включая имя регистра, например, CX), которое в процессе ассемблирования будет заменено на параметр макрокоманды. Отсюда следует, что ассемблер не распознает регистровые имена и имена, определенные в области Ассемблер для IBM PC. Глава 20 21 данных, как таковые. В одной макрокоманде может быть определено любое число формальных парамтеров, разделенных запятыми, вплоть до 120 колонки в строке. КОММЕНТАРИИ ------------------------------------------------------------ Для пояснений назначения макроопределения в нем могут находиться комментарии. Директива COMMENT или символ точка с запятой указывают на строку комментария, как это показано в следующем макроопределении PROMPT: PROMPT MACRO MESSGE ; Эта макрокоманда выводит сообщения на экран MOV AH,09H LEA DX,MESSGE INT 21H ENDM Так как по умолчанию в листинг попадают только команды генерирующие объектный код, то ассемблер не будет автомати чески выдавать и комментарии, имеющиеся в макроопределении. Если необходимо, чтобы в расширении появлялись комментарии, следует использовать перед макрокомандой директиву .LALL ("list all" - выводить все), которая кодируется вместе с лидирующей точкой: .LALL PROMPT MESSAG1 Макроопределение может содержать несколько комментариев, причем некоторые из них могут выдаваться в листинге, а другие - нет. В первом случае необходимо использовать директиву .LALL. Во втором - кодировать перед комментарием два символа точка с запятой (;;) - признак подавления вывода комментария в листинг. По умолчанию в ассемблере действует директива .XALL, которая выводит в листинг только команды, генерирующие объектный код. И, наконец, можно запретить появление в листинге ассемблерного кода в макрорасширениях, особенно при использовании макрокоманды в одной программе несколько раз. Для этого служит директива .SALL ("suppress all" - подавить весь вывод), которая уменьшает размер выводимого листинга, но не оказывает никакого влияния на размер объектного модуля. Директивы управления листинком .LALL, .XALL, .SALL сохраняют свое действие по всему тексту программы, пока другая директива листинга не изменит его. Эти директивы можно размещать в программе так, чтобы в одних макрокомандах распечатывались комментарии, в других - макрорасширения, а в третьих подавлялся вывод в листинг. Программа на рис.20.3 демонстрирует описанное выше свойство директив листинга. В программе опредлелено два макроопределения INIT2 и PROMPT, расмотренные ранее. Кодовый сегмент содержит директиву .SALL для подавления распечатки Ассемблер для IBM PC. Глава 20 22 INIT2 и первого расширения PROMPT. Для второго расширения PROMPT директива .LALL указывает ассемблеру на вывод в листинг комментария и макрорасширения. Заметим, однако, что комментарий, отмеченный двумя символами точка с запятой (;;) в макроопределении PROMPT, не распечатывается в макрорасшире ниях независимо от действия директив управления листингом. ------------------------------------------------------------ ------------------------------------------------------------ Рис.20.3. Распечатка и подавление макрорасширений в листинге. ИСПОЛЬЗОВАНИЕ МАКРОКОМАНД В МАКРООПРЕДЕЛЕНИЯХ ------------------------------------------------------------ Макроопределение может содержать ссылку на другое макроопределение. Рассмотрим простое макроопределение DOS21, которое заносит в регистр AH номер функции DOS и выполняет INT 21H: DOS21 MACRO DOSFUNC MOV AH,DOSFUNC INT 21H ENDM Для использования данной макрокоманды при вводе с клавиатуры необходимо закодировать: LEA DX,NAMEPAR DOS21 0AH Предположим, что имеется другое макроопределение, использую щее функцию 02 в регистре AH для вывода символа: DISP MACRO CHAR MOV AH,02 MOV DL,CHAR INT 21H ENDM Для вывода на экран, например, звездочки достаточно закодиро вать макрокоманду DISP '*'. Можно изменить макроопределение DISP, воспользовшись макрокомандой DOC21: DISP MACRO CHAR MOV DL,CHAR DOS21 02 ENDM Теперь, если закодировать макрокоманду DISP в виде DISP '*', то ассемблер сгенерирует следующие команды: MOV DL,'*' Ассемблер для IBM PC. Глава 20 23 MOV AH,02 INT 21H ДИРЕКТИВА LOCAL ------------------------------------------------------------ В некоторых макрокомандах требуется определять элементы данных или метки команд. При использовании такой макрокоманды в программе более одного раза происходит также неоднократное определение одинаковых полей данных или меток. В результате ассемблер выдаст сообщения об ошибке из-за дублирования имен. Для обеспечения уникальности генерируемых в каждом макрорасширении имен используется директива LOCAL, которая кодируется непосредственно после директивы MACRO, даже перед комментариями. Общий формат имеет следующий вид: LOCAL dummy-1,dummy-2,... ;Формальные параметры Рис.20.4. иллюстрирует использование директивы LOCAL. В приведенной на этом рисунке программе выполняется деление вычитанием; делитель вычитается из делимого и частное увеличивается на 1 до тех пор, пока делимое больше делителя. Для данного алгоритма необходимы две метки: COMP - адрес цикла, OUT - адрес выхода из цикла по завершению. Обе метки COMP и OUT определены как LOCAL и могут иметь любые правильные ассемблерные имена. В макрорасширении для COMP генерируется метка ??0000, а для OUT - ??0001. Если макрокоманда DIVIDE будет использова на в этой программе еще один раз, то в следующем макрорасши рении будут сгенерированы метки ??0002 и ??0003 соответствен но. Таким образом, с помощью директивы LOCAL обеспечивается уникальность меток в макрорасширениях в одной программе. ------------------------------------------------------------ ------------------------------------------------------------ Рис.20.4. Использование директивы LOCAL. ИСПОЛЬЗОВАНИЕ БИБЛИОТЕК МАКРООПРЕДЕЛЕНИЙ ------------------------------------------------------------ Определение таких макрокоманд, как INIT1 и INIT2 и одноразовое их использование в программе кажется бессмысленным. Лучшим подходом здесь является каталогизация собственных макрокоманд в библиотеке на магнитном диске, используя любое описательное имя, например, MACRO.LIB: INIT MACRO CSNAME,DSNAME,SSNAME . . ENDM PROMPT MACRO MESSGE . . Ассемблер для IBM PC. Глава 20 24 ENDM Теперь для использования любой из каталогизированных макрокоманд вместо MACRO определения в начале программы следует применять директиву INCLUDE: INCLUDE C:MACRO.LIB . . INIT CSEG,DATA,STACK В этом случае ассемблер обращается к файлу MACRO.LIB (в нашем примере) на дисководе C и включает в программу оба макроопределения INIT и PROMPT. Хотя в нашем примере требуется только INIT. Ассемблерный листинг будет содержать копию макроопределения, отмеченного символом C в 30 колонке LST-файла. Следом за макрокомандой идет ее расширение с объектным кодом и с символом плюс (+) в 31 колонке. Так как транслятор с ассемблера является двухпроходовым, то для обеспечения обработки директивы INCLUDE только в первом проходе (а не в обоих) можно использовать следующую конструкцию: IF1 INCLUDE C:MACRO.LIB ENDIF IF1 и ENDIF являются условными директивами. Директива IF1 указывает ассемблеру на необходимость доступа к библиотеке только в первом проходе трансляции. Директива ENDIF заверша ет IF-логику. Таким образом, копия макроопределений не появится в листинге - будет сэкономлено и время и память. Программа на рис.20.5 содержит рассмотренные выше директи вы IF1, INCLUDE и ENDIF, хотя в LST-файл ассемблер выводит только директиву ENDIF. Обе макрокоманды в кодовом сегменте INIT и PROMPT закаталогизированы в файле MACRO.LIB, т.е. просто записаны друг за другом на дисковый файл по имени MACRO.LIB с помощью текстового редактора. Расположение директивы INCLUDE не критично, но она должна появиться ранее любой макрокоманды из включаемой библиотеки. ------------------------------------------------------------ ------------------------------------------------------------ Рис.20.5. Использование библиотеки макроопределений. Директива очистки Директива INCLUDE указывает ассемблеру на включение всех макроопределений из специфицированной библиотеки. Например, библиотека содержит макросы INIT, PROMPT и DIVIDE, хотя Ассемблер для IBM PC. Глава 20 25 программе требуется только INIT. Директива PURGE позволяет "удалить" нежелательные макросы PROMPT и DIVIDE в текущем ассемблировании: IF1 INCLUDE MACRO.LIB ;Включить всю библиотеку ENDIF PURGE PROMRT,DIYIDE ;Удалить ненужные макросы ... INIT CSEG,DATA,STACK ;Использование оставшейся ; макрокоманды Директива PURGE действует только в процессе ассемблирова ния и не оказывает никакого влияния на макрокоманды, находящиеся в библиотеке. КОНКАТЕНАЦИЯ (&) ------------------------------------------------------------ Символ амперсанд (&) указывает ассемблеру на сцепление (конкатенацию) текста или символов. Следующая макрокоманда MOVE генерирует команду MOVSB или MOVSW: MOVE MACRO TAG REP MOVS&TAG ENDM Теперь можно кодировать макрокоманду в виде MOVE B или MOVE W. В результате макрорасширения ассемблер сцепит параметр с командой MOVS и получит REP MOVSB или REP MOVSW. Данный пример весьма тривиален и служит лишь для иллюстрации. ДИРЕКТИВЫ ПОВТОРЕНИЯ: REPT, IRP, IRPC ------------------------------------------------------------ Директивы повторения заставляют ассемблер повторить блок операторов, завершаемых директивой ENDM. Эти директивы не обязательно должны находится в макроопределении, но если они там находятся, то одна директива ENDM требуется для завершения повторяющегося блока, а вторая ENDM - для завершения макроопределения. REPT: Повторение Операция REPT приводит к повторению блока операторов до директивы ENDM в соответствии с числом повторений, указанным в выражении: REPT выражение В следующем примере происходит начальная инициализация значения N=0 и затем повторяется генерация DB N пять раз: N = 0 Ассемблер для IBM PC. Глава 20 26 REPT 5 N = N + 1 DB N ENDM В результате будут сгенерированы пять операторов DB от DB 1 до DB 5. Директива REPT может использоваться таким образом для определения таблицы или части таблицы. Другим примером может служить генерация пяти команд MOVSB, что эквивалентно REP MOVSB при содержимом CX равном 05: REPT 5 MOVSB ENDM IRP: Неопределенное повторение Операция IRP приводит к повторению блока команд до директивы ENDM. Основной формат: IRP dummy,<arguments> Аргументы, содержащиеся в угловых скобках, представляют собой любое число правильных символов, строк, числовых или арифметических констант. Ассемблер генерирует блок кода для каждого аргумента. В следующем примере ассемблер генерирует DB 3, DB 9, DB 17, DB 25 и DB 28: IRP N,<3, 9, 17, 25, 28> DB N ENDM IRPC: Неопределенное повторение символа Операция IRPC приводит к повторению блока операторов до директивы ENDM. Основной формат: IRPC dummy,string Ассемблер генерирует блок кода для каждого символа в строке "string". В следующем примере ассемблер генерирует DW 3, DW 4 ... DW 8: IRPC N,345678 DW N ENDM УСЛОВНЫЕ ДИРЕКТИВЫ ------------------------------------------------------------ Ассемблер поддерживает ряд условных директив. Ранее нам уже приходилось использовать директиву IF1 для включения библиотеки только в первом проходе ассемблирования. Условные Ассемблер для IBM PC. Глава 20 27 директивы наиболее полезны внутри макроопределений, но не ограничены только этим применением. Каждая директива IF должна иметь спаренную с ней директиву ENDIF для завершения IF-логики и возможную директиву ELSE для альтернативного действия: IFxx (условие) . } . } Условный ELSE (не обязательное действие) } . } блок . } ENDIF (конец IF-логики) Отсутствие директивы ENDIF вызывает сообщение обошибке: "Undeterminated conditional" (незавершенный условный блок). Если проверяемое условие истино, то ассемблер выполняет условный блок до директивы ELSE или при отсутствии ELSE - до директивы ENDIF. Если условие ложно, то ассемблер выполняет условный блок после директивы ELSE, а при отсутствии ELSE вообще обходит условный блок. Ниже перечислены различные условные директивы: IF выражение Если выражение не равно нулю, ассемблер обрабатывает операторы в условном блоке. IFE выражение Если выражение равно нулю, ассемблер обрабатывает операторы в условном блоке. IF1 (нет выражения) Если осуществляется первый проход ассемблирования, то обрабатываются операторы в условном блоке. IF2 (нет выражения) Если осуществляется второй проход ассемблирования, то обрабатываются операторы в условном блоке. IFDEF идентификатор Если идентификатор определен в программе или объявлен как EXTRN, то ассемблер обрабатывает операторы в условном блоке. IFNDEF идентификатор Если идентификатор не определен в программе или не объявлен как EXTRN, то ассемблер обрабатывает операторы в условном блоке. IFB <аргумент> Если аргументом является пробел, ассемблер обрабатывает операторы в условном блоке. Аргумент должен быть в угловых скобках. IFNB <аргумент> Если аргументом является не пробел, то ассемблер обрабатывает операторы в условном блоке. Аргумент должен быть в угловых скобках. Ассемблер для IBM PC. Глава 20 28 IFIDN <арг-1>,<арг-2> Если строка первого аргумента идентична строке второго аргумента, то ассемблер обрабатывает операторы в условном блоке. Аргументы должны быть в угловых скобках. IFDIF<арг-1>,<арг-2> Если строка первого аргумента отличается от строки второго аргумента, то ассемблер обрабатывает операторы в условном блоке. Аргументы должны быть в угловых скобках. Ниже приведен простой пример директивы IFNB (если не пробел). Для DOS INT 21H все запросы требуют занесения номера функции в регистр AH, в то время как лишь некоторые из них используют значение в регистре DX. Следующее макроопределение учитывает эту особенность: DOS21 MACRO DOSFUNC,DXADDRES MOV AN,DOSFUNC IFNB <DXADDRES> MOV DX,OFFSET DXADDRES ENDIF INT 21H ENDM Использование DOS21 для простого ввода с клавиатуры требует установки значения 01 в регистр AH: DOS21 01 Ассемблер генерирует в результате команды MOV AH,01 и INT 21H. Для ввода символьной строки требуется занести в регистр AH значение 0AH, а в регистр DX - адрес области ввода: DOS21 0AH,IPFIELD Ассемблер генерирует в результате обе команды MOV и INT 21H. ДИРЕКТИВА ВЫХОДА ИЗ МАКРОСА EXITM. ------------------------------------------------------------ Макроопределение может содержать условные дерективы, которые проверяют важные условия. Если условие истинно, то ассемблер должен прекратить дальнейшее макрорасширение. Для этой цели служит директива EXITM: IFxx [условие] . . (неправильное условие) . EXITM . . Ассемблер для IBM PC. Глава 20 29 ENDIF Как только ассемблер попадает в процессе генерации макро расширения на директиву EXITM, дальнейшое расширение прекращается и обработка продолжается после директивы ENDM. Можно использовать EXITM для прекращения повторений по директивам REPT, IRP и IRPC даже если они находятся внутри макроопределения. МАКРОКОМАНДЫ, ИСПОЛЬЗУЮЩИЕ IF И IFNDEF УСЛОВИЯ ------------------------------------------------------------ Программа на рис.20.6 содержит макроопределение DIVIDE, которая генерирует подпрограмму для выполнения деления вычитанием. Макрокоманда должна кодироваться с параметрами в следующей последовательности: делимое, делитель, частное. Макрокоманда содержит директиву IFNDEF для проверки наличия параметров. Для любого неопределенного элемента макрокоманда увеличивает счетчик CNTR. Этот счетчик может иметь любое корректное имя и предназначен для временного использования в макроопределении. После проверки всех трех параметров, макрокоманда проверяет CNTR: IF CNTR ; Макрорасширение прекращено EXITM Если счетчик CNTR содержит ненулевое значение, то ассемблер генерирует комментарий и прекращает по директиве EXITM дальнейшее макрорасширение. Заметим, что начальная команда устанавливает в счетчике CNTR нулевое значение и, кроме того, блоки IFNDEF могут устанавливать в CNTR единичное значение, а не увеличивать его на 1. Если ассемблер успешно проходит все проверки, то он генерирует макрорасширение. В кодовом сегменте первая макрокоманда DIVIDE содержит правильные делимое и частное и, поэтому генерирует только комментарии. Один из способов улучшения рассматриваемой макрокоманды - обеспечить проверку на ненулевой делитель и на одинаковый знак делимого и делителя; для этих целей лучше использовать коды ассемблера, чем условные директивы. ------------------------------------------------------------ ------------------------------------------------------------ Рис.20.6. Использование директив IF и IFNDEF. МАКРОС, ИСПОЛЬЗУЮЩИЙ IFIDN-УСЛОВИЕ ------------------------------------------------------------ Ассемблер для IBM PC. Глава 20 30 Программа на рис.20.7 содержит макроопределение по имени MOVIF, которая генерирует команды MOVSB или MOVSW в зависимости от указанного параметра. Макрокоманду можно кодировать с параметром B (для байта) или W (для слова) для генерации команд MOVSB или MOVSW из MOVS. Обратите внимание на первые два оператора в макроопределе нии: MOVIF MACRO TAG IFIDN <&TAG>,<B> Условная директива IFIDN сравнивает заданный параметр (предположительно B или W) со строкой B. Если значения идентичны, то ассемблер генерирует REP MOVSB. Обычное использование амперсанда (&) - для конкатенации, но в данном примере операнд <TAG> без амперсанда не будет работать. Если в макрокоманде не будет указан параметр B или W, то ассемблер сгенерирует предупреждающий комментарий и команду MOVSB (по умолчанию). Примеры в кодовом сегменте трижды проверяют макрокоманду MOVIF: для параметра B, для параметра W и для неправильного параметра. Не следует делать попыток выполнения данной программы в том виде, как она приведена на рисунке, так как регистры CX и DX не обеспечены правильными значениями. Предполагается, что рассматриваемая макрокоманда не является очень полезной и ее назначение здесь - проиллюстри ровать условные директивы в простой форме. К данному моменту, однако, вы имеете достаточно информации для составления больших полезных макроопределений. ------------------------------------------------------------ ------------------------------------------------------------ Рис.20.7. Использование директивы IFIDN ОСНОВНЫЕ ПОЛОЖЕНИЯ НА ПАМЯТЬ ------------------------------------------------------------ ъ Макросредства возможны только для полной версии ассемблера (MASM). ъ Использование макрокоманд в программах на ассемблере дает в результате более удобочитаемые программы и более произ водительный код. ъ Макроопределение состоит из директивы MACRO, блока из одного или нескольких операторов, которые генерируются при макрорасширениях и директивы ENDM для завершения определения. ъ Код, который генерируется в программе по макрокоманде, представляет собой макрорасширение. ъ Директивы .SALL,.LALL и .XALL позволяют управлять распечаткой комментариев и генерируемого объектного кода в макрорасширении. ъ Директива LOCAL позволяет использовать имена внутри макроопределений. Директива LOCAL кодируется непосредственно после директивы MACRO. Ассемблер для IBM PC. Глава 20 31 ъ Использование формальных параметров в макроопределении позволяет кодировать параметры, обеспечивающие большую гибкость макросредств. ъ Библиотека макроопределений дает возможность использо вать макрокоманды для различных ассемблерных программ. ъ Условные директивы позволяют контролировать параметры макрокоманд. ВОПРОСЫ ДЛЯ САМОПРОВЕРКИ ------------------------------------------------------------ 20.1. Напишите необходимые директивы: а) для подавления всех команд, которые генерирует макрокоманда и б) для распечатки только команд, генерирующих объектный код. 20.2. Закодируйте два макроопределения для умножения: а) MULTBY должна генерировать код для умножения байта на байт; б) MULTWD должна генерировать код для умножения слова на слово. Для множителя и множимого используйте в макро определении формальные параметры. Проверьте выполнение макрокоманд на небольшой программе, в которой также определены необходимые области данных. 20.3. Запишите макроопределения из вопроса 20.2 в "макро библиотеку". Исправьте программу для включения элементов библиотеки по директиве INCLUDE в первом проходе ассемблирования. 20.4. Напишите макроопределение BIPRINT, использующей BIOS INT 17H для печати. Макроопределение должно включать проверку состояния принтера и обеспечивать печать любых строк любой длины. 20.5. Измените макроопределение на рис.20.6 для проверки делителя на ноль (для обхода деления). Ассемблер для IBM PC. Глава 21 1 ГЛАВА 21. Компановка программ ------------------------------------------------------------ Компановка программ Цель: Раскрыть технологию программирования, включающую компа новку и выполнение ассемблерных программ. ВВЕДЕНИЕ ------------------------------------------------------------ Примеры программ в предыдущих главах состояли из одного шага ассемблирования. Возможно, однако, выполнение програм много модуля, состоящего из нескольких ассемблированных программ. В этом случае программу можно рассматривать, как состоящую из основной программы и одной или более подпрограмм. Причины такой организации программ состоят в следующем: ъ бывает необходимо скомпановать программы, написанные на разных языках, например, для объединения мощности языка высокого уровня и эффективности ассемблера; ъ программа, написанная в виде одного модуля, может оказаться слишком большой для ассемблирования; ъ отдельные части программы могут быть написаны разными группами программистов, ассемблирующих свои модули раздельно; ъ ввиду возможно большого размера выполняемого модуля, может появиться необходимость перекрытия частей программы в процессе выполнения. Каждая программа ассемблируется отдельно и генерирует собственный уникальный объектный (OBJ) модуль. Программа компановщик (LINK) затем компанует объектные модули в один объединенный выполняемый (EXE) модуль. Обычно выполнение начинается с основной программы, которая вызывает одну или более подпрограмм. Подпрограммы, в свою очередь, могут вызывать другие подпрограммы. На рис.21.1 показаны два примера иерархической структуры основной подпрограммы и трех подпрограмм. На рис. 21.1 (а) основная программы вызывает подпрограммы 1, 2 и 3. На рис. 21.1 (б) основная программа вызывает подпрограммы 1 и 2, а подпрограмма 1 вызывает подпрограмму 3. ------------------------------------------------------------ ------------------------------------------------------------ Рис.21.1. Иерархия программ. Существует много разновидностей организации подпрограмм, но любая организация должна быть "понятна" и ассемблеру, и компановщику, и этапу выполнения. Следует быть внимательным к ситуациям, когда, например, под программа 1 вызывает Ассемблер для IBM PC. Глава 21 2 подпрограмму 2, которая вызывает подпрограмму 3 и, которая в свою очередь вызывает подпрограмму 1. Такой процесс, известный как рекурсия, может использоваться на практике, но при неаккуратном обращении может вызвать любопытные ошибки при выполнении. МЕЖСЕГМЕНТНЫЕ ВЫЗОВЫ ------------------------------------------------------------ Команды CALL в предыдущих главах использовались для внутрисегментных вызовов, т.е. для вызовов внутри одного сегмента. Внутрисегментный CALL может быть короткий (в пределах от +127 до -128 байт) или длинный ( превышающий указанные границы). В результате такой операции "старое" значение в регистре IP запоминается в стеке, а "новый" адрес перехода загружается в этот регистр. Например, внутрисегментный CALL может иметь следующий объектный код: E82000. Шест.E8 представляет собой код операции, которая заносит 2000 в виде относительного адреса 0020 в регистр IP. Затем процессор объединяет текущий адрес в регистре CS и относительный адрес в регистре IP для получения адреса следующей выполняемой команды. При возврате из процедуры команда RET восстанавливает из стека старое значение в регистре IP и передает управление таким образом на следующую после CALL команду. Вызов в другой кодовый сегмент представляет собой межсег ментный (длинный) вызов. Данная операция сначала записывает в стек содержимое регистра CS и заносит в этот регистр адрес другого сегмента, затем записывает в стек значение регистра IP и заносит новый относительный адрес в этот регистр. Таким образом в стеке запоминаются и адрес кодового сег мента и смещение для последующего возврата из подпрограммы. Например, межсегментный CALL может состоять из следующего объектного кода: 9A 0002 AF04 Шест.9A представляет собой код команды межсегментного вызова которая записывает значение 0002 в виде 0200 в регистр IP, а значение AF04 в виде 04AF в регистр CS. Комбинация этих адресов указывает на первую выполняемую команду в вызываемой подпрограмме: Кодовый сегмент 04AF0 Смещение в IP 0200 Действительный адрес 04CF0 При выходе из вызванной процедуры межсегментная команда возврата REP восстанавливает оба адреса в регистрах CS и IP и таким образом передает управление на следующую после CALL команду. АТРИБУТЫ EXTRN и PUBLIC Ассемблер для IBM PC. Глава 21 3 ------------------------------------------------------------ Рассмотрим основную программу (MAINPROG), которая вызывает подпрограмму (SUBPROG) с помощью межсегментного CALL, как показано на рис.21.2. Команда CALL в MAINPROG должна "знать", что SUBPROG существует вне данного сегмента (иначе ассемблер выдаст сообщение о том, что идентификатор SUBPROG не определен). С помощью директивы EXTRN можно указать ассемблеру, что ссылка на SUBPROG имеет атрибут FAR, т.е.определена в другом ассемблерном модуле. Так как сам ассемблер не имеет возможности точно определить такие ссылки, он генерирует "пустой" объектный код для последующего заполнения его при компановке: 9A 0000 ---- E Подпрограмма SUBPROG содержит директиву PUBLIC, которая указывает ассемблеру и компановщику, что другой модуль должен "знать" адрес SUBPROG. В последнем шаге, когда оба модуля MAINPROG и SUBPROG будут успешно ассемблированы в объектные модули, они могут быть скомпанованы следующим образом: Запрос компановщика LINK: Ответ: Object Modules [.OBJ]: B:MAINPROG+B:SUBPROG Run File [filespec.EXE]: B:COMBPROG (или другое имя) List File [NUL.MAP]: CON Libraries [.LIB]: [return] ------------------------------------------------------------ ------------------------------------------------------------ Рис.21.2. Межсегментный вызов. Компановщик устанавливает соответствия между адресами EXTRN в одном объектном модуле с адресами PUBLIC в другом и заносит необходимые относительные адреса. Затем он объединя ет два объектных модуля в один выполняемый. При невозможнос ти разрешить ссылки компановщик выдает сообщения об ошибках. Следите за этими сообщениями прежде чем пытаться выполнить программу. Директива EXTRN Директива EXTRN имеет следующий формат: EXTRN имя:тип [, ... ] Можно определить более одного имени (до конца строки) или закодировать дополнительные директивы EXTRN. В другом ассемблерном модуле соответствующее имя должно быть определено и идентифицировано как PUBLIC. Тип элемента может Ассемблер для IBM PC. Глава 21 4 быть ABS, BYTE, DWORD, FAR, NEAR, WORD. Имя может быть определено через EQU и должно удовлетворять реальному определению имени. Директива PUBLIC Директива PUBLIC указывает ассемблеру и компановщику, что адрес указанного иддентификатора доступен из других программ Директива имеет следующий формат: PUBLIC идентификатор [, ... ] Можно определить более одного идентификатора (до конца строки) или закодировать дополнительные директивы PUBLIC. Идентификаторы могут быть метками (включая PROC-метки), переменными или числами. Неправильными идентификаторами являются имена регистров и EQU-идентификаторы, определяющие значения более двух байт. Рассмотрим три различных способа компановки программ. ПРОГРАММА: ИСПОЛЬЗОВАНИЕ ДИРЕКТИВ EXTRN и PUBLIC ДЛЯ МЕТОК ------------------------------------------------------------ Программа на рис.21.3 состоит из основной программы CALLMUL1 и подпрограммы SUBMUL1. В основной программе определены сегменты для стека, данных и кода. В сегменте данных определены поля QTY и PRICE. В кодовом сегменте регистр AX загружается значением PRICE, а регистр BX - значением QTY, после чего происходит вызов подпрограммы. Директива EXTRN в основной программе определяет SUBMUL как точку входа в подпрограмму. Подпрограмма содержит директиву PUBLIC (после ASSUME), которая указывает компановщику, что точкой входа для выполне ния является метка SUBMUL. Подпрограмма выполняет умножение содержимого регистра AX (цена) на содержимое регистра BX (количество). Результат умножения вырабатывается в регистро вой паре DX:AX в виде шест. 002E 4000. Так как подпрограмма не определяет каких-либо данных, то ей не требуется сегмент данных. Если бы подпрограмма имела сегмент данных, то только она одна использовала бы свои данные. Также в подпрограмме не определен стековый сегмент, так как она использует те же стековые адреса, что и основная программа. Таким образом, стек определенный в основной программе является доступным и в подпрогрпмме. Для компанов щика необходимо обнаружить по крайней мере один стек и определение стека в основной программе является достаточным. Рассмотрим теперь таблицы иднтификаторов, вырабатываемые после каждого ассемблирования. Обратите внимание, что SUBMUL в таблице идентификаторов для основной программы имеет атрибуты FAR и External (внешний), а для подпрограммы - F Ассемблер для IBM PC. Глава 21 5 (для FAR) и Global (глобальный). Этот последний атрибут указывает, что данное имя доступно из вне подпрограммы, т.е. глобально. Карта компановки (в конце листинга) отражает организацию программы в памяти. Заметьте, что здесь имеются два кодовых сегмента (для каждого ассемблирования) с разными стартовыми адресами. Последовательность расположения кодовых сегментов соответствует последовательности указанных для компа новки объектных модулей (обычно основная программа указывается первой). Таким образом, относительный адрес начала основной программы - шест.00000, а подпрограммы - шест. 00020. ------------------------------------------------------------ ------------------------------------------------------------ Рис. 21.3. Использование директив EXTRN и PUBLIC. При трассировке выполнения программы можно обнаружить, что команда CALL SUBMUL имеет объектный код 9A 0000 D413 Машинный код для межсегментного CALL - шест.9A. Эта команда сохраняет в стеке регистр IP и загружает в него значение 0000, сохраняет в стеке значение шест.13D2 из регистра CS и загружает в него шест.D413. Следующая выполняемая команда находится по адресу в регистровой паре CS:IP т.е. 13D40 плюс 0000. Обратите внимание, что основная программа начинается по адресу в регистре CS, содержащему шест.13D2, т.е. адрес 13D20. Из карты компановки видно, что подпрограмма начинает ся по относительному адресу шест.0020. Складывая эти два значения, получим действительный адрес кодового сегмента для подпрограммы: Адрес в CS 13D20 Смещение в IP 0020 Действительный адрес 13D40 Компановщик определяет это значение точно таким же образом, и подставляет его в операнд команды CALL. ПРОГРАММА: ИСПОЛЬЗОВАНИЕ ДИРЕКТИВЫ PUBLIC В КОДОВОМ СЕГМЕНТЕ ------------------------------------------------------------ Следующий пример на рис.21.4 представляет собой вариант программы на рис.21.3. Имеется одно изменение в основной программе и одно - в подпрограмме. В обоих случаях в директиве SEGMENT используется атрибут PUBLIC: CODESG SEGMENT PARA PUBLIC 'CODE' ------------------------------------------------------------ ------------------------------------------------------------ Рис.21.4. Кодовый сегмент, определенный как PUBLIC. Ассемблер для IBM PC. Глава 21 6 Рассмотрим результирующую карту компановки и ообъектный код команды CALL. Из таблицы идентификаторов (в конце каждого листинга ассемблирования) следует: обобщенный тип кодового сегмента CODESG - PUBLIC (на рис.21.3 было NONE). Но более интересным является то, что карта компановки в конце листинга показыва ет теперь только один кодовый сегмент! Тот факт, что оба сегмента имеют одни и те же имя (CODESG), класс ('CODE') и атрибут PUBLIC, заставил компановщика объединить два логичес ких кодовых сегмента в один физический кодовый сегмент. Кроме того, при трассировке выполнения программы можно обнаружить, что теперь команда вызова подпрограммы имеет следующий объектный код: 9A 2000 D213 Эта команда заносит шест.2000 в регистр IP и шест. D213 в регистр CS. Так как подпрограмма находится в общем с основной программой кодовом сегменте, то в регистре CS устанавливается тот же стартовый адрес - шест.D213. Но теперь смещение равно шест.0020: Адрес в CS: 13D20 Смещение в IP: 0020 Действительный адрес: 13D40 Таким образом, кодовый сегмент подпрограммы начинается, очевидно, по адресу шест.13D40. Правильно ли это? Карта компановки не дает ответа на этот вопрос, но можно определить адрес по листингу основной программы, которая заканчивается на смещении шест.0016. Так как кодовый сегмент для подпрограммы определен как SEGMENT, то он должен начинаться на границе параграфа, т.е. его адрес должен нацело делиться на шест.10 или правая цифра адреса должна быть равна 0. Компановщик размещает подпрограмму на ближайшей границе параграфа непосредственно после основной программы - этот относительный адрес равен шест.00020. Поэтому кодовый сегмент подпрограммы начинается по адресу 13D20 плюс 0020 или 13D40. +----------------------------------------+--------------+ | Основная программа... (не используемый | Подпрограмма | | участок) | | +----------------------------------------+--------------+ | | | 13D20 13D30 13D40 Рассмотрим, каким образом компановщик согласует данные, определенные в основной программе и имеющие ссылки из подпрограммы. ПРОГРАММА: ОБЩИЕ ДАННЫЕ В ПОДПРОГРАММЕ Ассемблер для IBM PC. Глава 21 7 ------------------------------------------------------------ Наличие общих данных предполагает возможность обработки в одном ассемблерном модуле данных, которые определены в другом ассемблерном модуле. Изменим предыдущий пример так, чтобы области QTY и PRICE по-прежнему определялись в основной программе, но загрузка значений из этих областей в регистры BX и AX выполнялась в подпрограмме. Такая программа приведена на рис.21.5. В ней сделаны следующие изменения: ъ В основной программе имена QTY и PRICE определены как PUBLIC. Сегмент данных также определен с атрибутом PUBLIC. Обратите внимание на атрибут Global (глобаль ный) для QTY и PRICE в таблице идентификаторов. ъ В подпрограмме имена QTY и PRICE определены как EXTRN и WORD. Такое определение указывает ассемблеру на длину этих полей в 2 байта. Теперь ассемблер сгенерирует правильный код операции для команд MOV, а компановщик установит значения операндов. Заметьте, что имена QTY и PRICE в таблице идентификаторов имеют атрибут External (внешний). ------------------------------------------------------------ ------------------------------------------------------------ Рис.21.5. Общие данные в подпрограмме. Команды MOV в листинге подпрограммы имеют следующий вид: A1 0000 E MOV AX,PRICE 8B 1E 0000 E MOV BX,QTY В объектном коде шест.A1 обозначает пересылку слова из памяти в регистр AX, а шест.8B - пересылку слова из памяти в регистр BX (объектный код для операций с регистром AX чаще требует меньшее число байтов, чем с другими регистрами). Трассировка выполнения программы показывает, что компановщик установил в объектном коде следующие операнды: A1 0200 8B 1E 0000 Объектный код теперь идентичен коду сгенерированному в преды дущем примере, где команды MOV находились в вызывающей программе. Это логичный результат, так как операнды во всех трех программах базировались по регистру DS и имели одинаковые относительные адреса. Основная программа и подпрограмма могут определять любые другие элементы данных, но общими являются лишь имеющие атрибуты PUBLIC и EXTRN. Следуя основным правилам, рассмотренным в данной главе, можно теперь компановать программы, состоящие более чем из двух ассемблерных модулей и обеспечивать доступ к общим Ассемблер для IBM PC. Глава 21 8 данным из всех модулей. При этом следует предусматривать стек достаточных размеров - в разумных пределах, для больших программ определение 64 слов для стека бывает достаточным. В главе 23 будет рассмотрены дополнительные свойства сегментов, включая определение более одного сегмента данных и кодового сегмента в одном ассемблерном модуле и использова ние директивы GROUP для объединения сегментов в один общий сегмент. ПЕРЕДАЧА ПАРАМЕТРОВ ------------------------------------------------------------ Другим способом обеспечения доступа к данным из вызывае мой подпрограммы является передача параметров. В этом случае вызывающая программа физически передает данные через стек. Каждая команда PUSH должна записывать в стек данные размером в одно слово из памяти или из регистра. Программа, приведенная на рис.21.6, прежде чем вызвать подпрограмму SUBMUL заносит в стек значения из полей PRICE и QTY. После команды CALL стек выглядит следующим образом: ... | 1600 | D213 | 4001 | 0025 | 0000 | C213 | 6 5 4 3 2 1 1. Инициализирующая команда PUSH DS заносит адрес сегмента в стек. Этот адрес может отличаться в разных версиях DOS. 2. Команда PUSH AX заносит в стек нулевой адрес. 3. Команда PUSH PRICE заносит в стек слово (2500). 4. Команда PUSH QTY заносит в стек слово (0140). 5. Команда CALL заносит в стек содержимое регистра CS (D213) 6. Так как команда CALL представляет здесь межсегментный вызов, то в стек заносится также содержимое регистра IP (1600). Вызываемая программа использует регистр BP для доступа к параметрам в стеке, но прежде она запоминает содержимое регистра BP, записывая его в стек. В данном случае, предположим, что регистр BP содержит нуль, тогда нулевое слово будет записано в вершине стека (слева). Затем программа помещает в регистр BP содержимое из регистра SP, так как в качестве индексного регистра может использоваться регистр BP, но не SP. Команда загружает в регистр BP значение 0072. Первоначально регистр SP содержал размер пустого стека, т.е. шест.80. Запись каждого слова в стек уменьшает содержимое SP на 2: | 0000 | 1600 | D213 | 4001 | 0025 | 0000 |C213 | | | | | | | | SP: 72 74 76 78 7A 7C 7E Ассемблер для IBM PC. Глава 21 9 Так как BP теперь также содержит 0072, то параметр цены (PRICE) будет по адресу BP+8, а параметр количества (QTY) - по адресу BP+6. Программа пересылает эти величины из стека в регистры AX и BX соответственно и выполняет умножение. ------------------------------------------------------------ ------------------------------------------------------------ Рис.21.6. Передача параметров. Перед возвратом в вызывающую программу в регистре BP восстанавливается первоначальное значение, а содержимое в регистре SP увеличивается на 2, с 72 до 74. Последняя команда RET представляет собой "длинный" возврат в вызывающую программу. По этой команде выполняются следующие действия: ъ Из вершины стека восстанавливается значение регистра IP (1600). ъ Содержимое регистра SP увеличивается на 2, от 74 до 76. ъ Из новой вершины стека восстанавливается значение регистра CS (D213). ъ Содержимое регистра SP увеличивается на 2 от 76 до 78. Таким образом осуществляется корректный возврат в вызываю щую программу. Осталось одно небольшое пояснение. Команда RET закодирована как RET 4 Параметр 4 представляет собой число байт в стеке использо ванных при передаче параметров (два слова в данном случае). Команда RET прибавит этот параметр к содержимому регистра SP, получив значение 7C. Таким образом, из стека исключаются ненужные больше параметры. Будьте особенно внимательны при восстановлении регистра SP - ошибки могут привести к непред сказуемым результатам. КОМПАНОВКА ПРОГРАММ НА BASIC-ИНТЕРПРЕТАТОРЕ И АССЕМБЛЕРЕ ------------------------------------------------------------ В руководстве по языку BASIC для IBM PC приводятся различ ные методы связи BASIC-интерпретатора и программ на ассемблере. Для этого имеются две причины: сделать возможным использование BIOS-прерываний через ассемблерные модули и создать более эффективные программы. Цель данного раздела - дать общий обзор по данному вопросу; повторять здесь технические подробности из руководства по языку BASIC нет необходимости. Для связи с BASIC ассемблерные программы кодируются, транслируются и компануются отдельно. Выделение памяти для подпрограмм на машинном языке может быть либо внутри, либо вне 64 Кбайтовой области памяти, которой ограничен BASIC. Выбор лежит на программисте. Ассемблер для IBM PC. Глава 21 10 Существует два способа загрузки машинного кода в память: использование оператора языка BASIC - POKE или объединение скомпанованного модуля с BASIC-программой. Использование BASIC-оператора POKE. Хотя это и самый простой способ, но он удобен только для очень коротких подпрограмм. Способ заключается в том, что сначала определяется объектный код ассемблерной программы по LST-файлу или с помощью отладчика DEBUG. Затем шестнадцати ричные значения кодируются непосредственно в BASIC-программе в операторах DATA. После этого с помощью BASIC-оператора READ считывается каждый байт и оператором POKE заносится в память для выполнения. Компановка ассемблерных модулей. С большими ассемблерными подпрограммами обычно проще иметь дело, ели они оттранслированы и скомпанованые как выполнимые (EXE) модули. Необходимо организовать BASIC-программу и выполнимый модуль в рабочую программу. При работе с BASIC-программой не забывайте пользоваться командой BSAVE (BASIC save) для сохранения программы и BLOAD - для загрузки ее перед выполнением. Прежде чем кодировать BASIC- и ассемблерную программы, необходимо решить, каким из двух способов они будут связаны. В языке BASIC возможны два способа: функция USR и оператор CALL. В обоих способах регистры DS, ES и SS на входе содержат указатель на адресное пространство среды BASIC. Регистр CS содержит текущее значение, определенное последним оператором DEF SEG (если он имеется). Стековый указатель SP указывает на стек, состоящий только из восьми слов, так что может потребоваться установка другого стеке в подпрограмме. В последнем случае необходимо на входе сохранить значение указателя текущего стека, а при выходе восстановить его. В обоих случаях при выходе необходимо восстановить значение сегментных регистров и SP и обеспечить возврат в BASIC с помощью межсегментного возврата RET. Скомпануйте ваш ассемблированный объектный файл так, что бы он находился в старших адресах памяти. Для этого используется параметр HIGH при ответе на второй запрос компа новщика, например, B:имя/HIGH. Затем с помощью отладчика DEBUG необходимо загрузить EXE-подпрограмму и по команде R определить значения в регистрах CS и IP: они показывают на стартовый адрес подпрограммы. Находясь в отладчике укажите имя (команда N) BASIC и загрузите его командой L. Два способа связи BASIC-программы и EXE-подпрограммы - использование операторов USR или CALL. Работая в отладчике, необходимо определить стартовый адрес EXE-подпрограммы и, затем, указать этот адрес или в операторе USRn или в CALL. В руководстве по языку BASIC для IBM PC детально представлено описание функции USRn и оператора CALL с различными примерами. Ассемблер для IBM PC. Глава 21 11 Программа: Компановка BASIC и ассемблера. Рассмотрим теперь простой пример компановки программы для BASIC-интерпретатора и подпрограммы на ассемблере. В этом примере BASIC-программа запрашивает ввод значений времени и расценки и выводит на экран их произведение - размер зарплаты. Цикл FOR-NEXT обеспечивает пятикратное выполнение ввода и затем программа завершается. Пусть BASIC- программа вызывает ассемблерный модуль, который очищает экран. На рис. 21.7 приведена исходная BASIC-программа и ассемб лерная подпрограмма. Обратите внимание на следующие особен ности BASIC-программы: оператор 10 очищает 32К байт памяти; операторы 20, 30, 40 и 50 временно содержат комментарии. Позже мы вставим BASIC-операторы для связи с ассемблерным модулем. BASIC-программу можно сразу проверить. Введите команду BASIC и затем наберите все пронумерованные операторы так, как они показаны в примере. Для выполнения программы нажмите F2. Не забудте сохранить текст программы с помощью команды SAVE "B:BASTEST.BAS" Обратите внимание на следующие особенности ассемблерной подпрограммы: - отсутствует определение стека, так как его обеспечивает BASIC; программа не предусмотрена для отдельного выполнения и не может быть выполнена; - подпрограмма сохраняет в стеке содержимое регистра BP и записывает значение регистра SP в BP; - подпрограмма выполняет очистку экрана, хотя она может быть изменена для выполнения других операций, таких как прокрутка экрана вверх или вниз или установка курсора. ------------------------------------------------------------ ------------------------------------------------------------ Рис.21.7. Основная программа на языке BASIC и подпрограмма на ассемблере. Все что осталось - это связать эти программы вместе. Следующие действия предполагают, что системная дискета (DOS) находится на дисководе A, а рабочие программы - на дисководе B: 1. Наберите ассемблерную подпрограмму, сохраните ее под именем B:LINKBAS.ASM и оттранслируйте ее. 2. Используя компановщик LINK, сгенерируйте объектный модуль, который будет загружаться в старшие адреса памяти: LINK B:LINKBAS,B:LINKBAS/HIGH,CON; 3. С помощью отладчика DEBUG загрузите BASIC - компилятор: DEBUG BASIC.COM. Ассемблер для IBM PC. Глава 21 12 4. По команде отладчика R выведите на экран содержимое регистров. Запишите значения в регистрах SS, CS и IP. 5. Теперь установите имя и загрузите скомпанованный ассемблерный модуль следующими командами: N B:LINKBAS.EXE L 6. По команде R выведите на экран содержимое регистров и запишите значения в CX, CS и IP. 7. Замените содержимое регистров SS, CS и IP значениями из шага 4. (Для этого служат команды R SS, R CS и R IP). 8. Введите команду отладчика G (go) для передачи управле ния в BASIC. На экране должен появиться запрос из BASIC-программы. 9. Для того, чтобы сохранить ассемблерный модуль, введите следующие команды (без номеров операторов): DEF SEG = &Hxxxx (значение в CS из шага 6) BSAVE "B:CLRSCRN.MOD",0,&Hxx (значение в CX из шага 6) Первая команда обеспечивает адрес загрузки модуля в память для выполнения. Вторая команда идентифицирует имя модуля, относительную точку входа и размер модуля. По второй команде система запишет модуль на дисковод B. 10. Теперь необходимо модифицировать BASIC-программу для компановки. Можно загрузить ее сразу, находясь в отладчике, но вместо этого наберите команду SYSTEM для выхода из BASIC и, затем, введите Q для выхода из отладчика DEBUG. На экране должно появиться приглашение DOS. 11. Введите команду BASIC, загрузите BASIC-программу и выведите ее на экран: BASIC LOAD "B:BASTEST.BAS" LIST 12. Измените операторы 20, 30, 40 и 50 следующим образом: 20 BLOAD "B:CLRSCRN.MOD" 30 DEF SEG = &Hxxxx (значение в CS из шага 6) 40 CLRSCRN = 0 (точка входа в подпрограмму) 50 CALL CLRSCRN (вызов подпрограммы) 13. Просмотрите, выполните и сохраните измененную BASIC- программу. Если BASIC-программа и ассемблерные команды были введены правильно, а также правильно установлены шестнадцатеричные значения из регистров, то связанная программа должна сразу очистить экран и выдать запрос на ввод времени и расценки. Ассемблер для IBM PC. Глава 21 13 На рис.21.8 приведен протокол всех шагов - но некоторые значения могут отличаться в зависимости от версии операционной системы и размера памяти. Приведенный пример выбран намеренно простым только для демонстрации компановки. Можно использовать более сложную технологию, используя передачу параметров из BASIC-программы в ассемблерную подпрограмму с помощью оператора CALL подпрограмма (параметр-1,параметр-2,...) ------------------------------------------------------------ ------------------------------------------------------------ Рис.21.8. Этапы связи BASIC и ассемблера. Ассемблерная подпрограмма может получить доступ к этим параметрам, используя регистр BP в виде [BP], как это делалось ранее на рис.21.3. В этом случае необходимо определить операнд в команде RET, соответствующий длине адресов параметров в стеке. Например, если оператор CALL передает три параметра то возврат должен быть закодирован в виде RET 6. КОМПАНОВКА ПРОГРАММ НА ЯЗЫКЕ PASCAL И АССЕМБЛЕРЕ ------------------------------------------------------------ В данном разделе показано, как можно установить связь между программами на языке PASCAL фирм IBM и MicroSoft с программами на ассемблере. На рис.21.9 приведен пример связи простой PASCAL-программы с ассемблерной подпрограммой. PASCAL-программа скомпилирована для получения OBJ-модуля, а ассемблерная программа оттранслирована также для получения OBJ-модуля. Программа LINK затем компанует вместе эти два OBJ-модуля в один выполнимый EXE-модуль. В PASCAL-программе определены две переменные: temp_row и temp_col, которые содержат введенные с клавиатуры значения строки и колонки соответственно. Программа передает адреса переменных temp_row и temp_col в виде парамтеров в ассемблерную подпрограмму для установки курсора по этим координатам. PASCAL-программа определяет также имя ассемблерной подпрограммы в операторе procedure как move_cursor и определяет два параметра, как extern (внешние). Оператор в PASCAL-программе, который вызывает ассемблерную программу по имени и передает парметры, имеет следующий вид: move_cursor (temp_row, temp_col); Через стек передаются следующие величины: указатель блока вызывающей программы, указатель на сегмент возврата, смещение возврата и адреса двух передаваемых параметров. Ниже показаны смещения для каждого элемента в стеке: 00 Указатель блока вызывающей программы 02 Указатель сегмента возврата Ассемблер для IBM PC. Глава 21 14 04 Указатель смещения возврата 06 Адрес второго параметра 08 Адрес первого параметра Так как ассемблерная подпрограмма будет использовать регистр BP, то его необходимо сохранить в стеке для последующего восстановления при возврате в вызывающую PASCAL-программу. Заметьте, что этот шаг в вызываемой подпрограмме аналогичен предыдущему примеру на рис.21.6. ------------------------------------------------------------ ------------------------------------------------------------ Рис.21.9. Компановка PASCAL-ассемблер. Регистр SP обычно адресует элементы стека. Но так как этот регистр нельзя использовать в качестве индексного регистра, то после сохранения старого значения регистра BP необходимо переслать адрес из регистра SP в BP. Этот шаг дает возможность использовать регистр BP в качестве индексного регистра для доступа к элементам в стеке. Следующий шаг - получить доступ к адресам двух параметров в стеке. Первый переданный параметр (адрес строки) находится в стеке по смещению 08, и может быть адресован по BP+08. Второй переданный параметр (адрес столбца) находится в стеке по смещению 06 и может быть адресован по BP+06. Два адреса из стека должны быть переданы в один из индексных регистров BX, DI или SI. В данном примере адрес строки пересылается из [BP+08] в регистр SI, а затем содержимое из [SI] (значение строки) пересылается в регистр DH. Значение столбца пересылается аналогичным способом в регистр DL. Затем подпрограмма использует значения строки и столбца в регистре DX при вызове BIOS для установки курсора. При выходе подпрограмма восстанавливает регистр BP. Команда RET имеет операнд, значение которого в два раза больше числа параметров, в данном случае 2х2, или 4. Параметры автома тически выводятся из стека и управление переходит в вызываю щую программу. Если в подпрограмме предстоит изменить сегментный регистр то необходимо сохранить его значение командой PUSH на входе и восстановить командой POP на выходе. Можно также использо вать стек для передачи величин из подпрограммы в вызывающую программу. Хотя рассмотренная подпрограмма не возвращает каких-либо значений, в языке PASCAL предполагается, что подпрограмма возращает одно слово в регистре AX или двойное слово в регистровой паре DX:AX. В результате компановки двух программ будет построена карта компановки, в которой первый элемент PASCALL представляет PASCALL-программу, второй элемент CODESEG (имя сегмента кода) представляет ассемблерную подпрограмму. Далее следует несколько подпрограмм для PASCALL-программы. Эта довольно тривиальная программа занимает в результате Ассемблер для IBM PC. Глава 21 15 шест.5720 байт памяти - более 20К. Компилирующие языки обычно генерируют объектные коды значительно превышающие по объему размеры компилируемой программы. КОМПАНОВКА ПРОГРАММ НА ЯЗЫКЕ C И АССЕМБЛЕРЕ ------------------------------------------------------------ Трудность описания связи программ на языке C и ассемблерных программ состоит в том, что различные версии языка C имеют разные соглашения о связях и для более точной информации следует пользоваться руководством по имеющейся версии языка C. Здесь приведем лишь некоторые соображения, представляющие интерес: ъ Большинство версий языка C обеспечивают передачу параметров через стек в обратной (по сравнению с другими языками) последовательности. Обычно доступ, например, к двум параметрам, передаваемым через стек, осуществляется следующим образом: MOV ES,BP MOV BP,SP MOV DH,[BP+4] MOV DL,[BP+6] ... POP BP RET ъ Некоторые версии языка C различают прописные и строчные буквы, поэтому имя ассемблерного модуля должно быть представленно в том же символьном регистре, какой используют для ссылки C-программы. ъ В некоторых версиях языка C требуется, чтобы ассемб лерные программы, изменяющие регистры DI и SI, записы вали их содержимое в стек при входе и восстанавливали эти значения из стека при выходе. ъ Ассемблерные программы должны возвращать значения, если это необходимо, в регистре AX (одно слово) или в регистровой паре DX:AX (два слова). ъ Для некоторых версий языка C, если ассемблерная программа устанавливает флаг DF, то она должна сбросить его командой CLD перед возвратом. ОСНОВНЫЕ ПОЛОЖЕНИЯ НА ПАМЯТЬ ------------------------------------------------------------ ъ В основной программе, вызывающей подпрограмму, необходимо определять точку входа как EXTRN, а в подпрограмме - как PUBLIC. Ассемблер для IBM PC. Глава 21 16 ъ Будьте внимательны при использовании рекурсий, когда подпрограмма 1 вызывает подпрограмму 2, которая в свою очередь вызывает подпрограмму 1. ъ Если кодовые сегменты необходимо скомпановать в один сегмент, то необходимо определить их с одинаковыми именами, одинаковыми классами и атрибутом PUBLIC. ъ Для простоты программирования начинайте выполнение с основной программы. ъ Определение общих данных в основной программе обычно проще (но не обязательно). Основная программа определя ет общие данные как PUBLIC, а подпрограмма (или подпрограммы) - как EXTRN. ВОПРОСЫ ДЛЯ САМОПРОВЕРКИ ------------------------------------------------------------ 21.1. Предположим, что программа MAINPRO должна вызвать под программу SUBPRO. а) Какая директива в программе MAINPRO указывает ассемблеру, что имя SUBPRO определе но вне ее собственного кода? б) Какая директива в подпрограмме SUBPRO необходима для того, чтобы имя точки входа было доступно в основной программе MAINPRO? 21.2. Предположим, что в программе MAINPRO определены переменные QTY как DB, VALUE как DW и PRICE как DW. Подпрограмма SUBPRO должна разделить VALUE на QTY и записать частное в PRICE. а) Каким образом программа MAINPRO указывает ассемблеру, что три переменные должны быть доступный извне основной программы? б) Каким образом подпрограмма SUBPRO указывает ассемблеру, что три переменные определены в другом модуле? 21.3. На основании вопросов 21.2 и 21.3 постройте работающую программу и проверьте ее. 21.4. Измените программу из предыдущего вопроса так, чтобы программа MAINPRO передавала все три переменные, как параметры. Подпрограмма SUBPRO должна возвращать результат через параметр. 21.5. Теперь предлагаем упражнение, на которое потребуется больше времени. Требуется расширить программу из вопроса 21.4 так, чтобы программа MAINPRO позволяла вводить количество (QTY) и общую стоимость (VALVE) с клавиатуры, подпрограмма SUBCONV преобразовывала ASCII-величины в двоичное представление; подпрограмма Ассемблер для IBM PC. Глава 21 17 SUBCALC вычисляла цену (PRICE); и подпрограмма SUBDISP преобразовывала двоичную цену в ASCII-представление и выводила результат на экран. Ассемблер для IBM PC. Глава 22 33 ГЛАВА 22. Программный загрузчик ------------------------------------------------------------ Программный загрузчик Цель: Раскрыть особенности загрузки выполнимых модулей в память для выполнения. ВВЕДЕНИЕ ------------------------------------------------------------ В данной главе описана организация базовой версии DOS и операции, которые выполняет DOS для загрузки выполнимых модулей в память для выполнения. DOS состоит из четырех основных программ, которые обеспечивают конкретные функции: 1. Блок начальной загрузки находится на первом секторе нулевой дорожки дискеты DOS, а также на любом диске, форматированном командой FORMAT /S. Когда вы иницииру ете систему (предполагается, что DOS расположен на дисководе A или C) происходит автоматическая загрузка с диска в память блока начальной загрузки. Этот блок представляет собой программу, которая затем загружает с диска в память три программы, описанные ниже. 2. Программа IBMBIO.COM обеспечивает интерфейс низкого уровня с программами BIOS в ROM; она загружается в память, начиная с адреса шест.00600. При инициализации программа IBMBIO.COM определяет состояние всех устройств и оборудования, а затем загружает программу COMMAND.COM. Программа IBMBIO.COM управляет операциями ввода-вывода между памятью и внешними устройствами, такими как видеомонитор и диск. 3. Программа IBMDOS.COM обеспечивает интерфейс высокого уровня с программами и загружается в память, начиная с адреса шест.00B00. Эта программа управляет оглавлениями и файлами на диске, блокированием и деблокированием дисковых записей, функциями INT 21H, а также содержит ряд других сервисных функций. 4. Программа COMMAND.COM выполняет различные команды DOS, такие как DIR или CHKDSK, а также выполняет COM, EXE и BAT-программы. Она состоит из трех частей: небольшая резидентная часть, часть инициализации и транзитная часть. Программа COMMAND.COM, подробно расмотренная в следующем разделе, отвечает за загрузку выполняемых программ с диска в память. На рис.22.1 показана карта распределения памяти. Некото рые элементы могут отличаться в зависимости от модели компьютера. Ассемблер для IBM PC. Глава 22 34 ------------------------------------------------------------ Начальный Программа адрес 00000 Векторная таблица прерываний (см.гл.23) 00400 Область связи с ROM (ПЗУ) 00500 Область связи с DOS 00600 IBMBIO.COM XXXX0 IBMDOS.COM Буфер каталога Дисковый буфер Таблица параметров дисковода или таблица распределения файлов (FAT, по одной для каждого дисковода) XXXX0 Резидентная часть COMMAND.COM XXXX0 Внешние команды или утилиты (COM или EXE-файлы) XXXX0 Пользовательский стек для COM-файлов (256 байтов) XXXX0 Транзитная часть COMMAND.COM, записывается в самые старшие адреса памяти. ------------------------------------------------------------ Рис.22.1. Карта распределения DOS в памяти. КОМАНДНЫЙ ПРОЦЕССОР COMMAND.COM ------------------------------------------------------------ Система загружает три части программы COMMAND.COM в память во время сеанса работы постоянно или временно. Ниже описано назначение каждой из трех чатей COMMAND.COM: 1. Резидентная часть непосредственно следует за программой IBMDOS.COM (и ее области данных), где она находится на протяжении всего сеанса работы. Резидентная часть обрабатывает все ошибки дисковых операций ввода-вывода и управляет следующими прерываниями: INT 22H Адрес программы обработки завершения задачи. INT 23H Адрес программы реакции на Ctrl/Break. INT 24H Адрес программы реакции на ошибоки дисковых операций чтения/записи или сбойный участок памяти в таблице распределения файлов (FAT). INT 27H Завершение работы, после которого программа остается резидентной. 2. Часть инициализации непосредственно следует за резидент ной чатью и содержит средства поддержки AUTOEXEC- файлов. В начале работы системы данная часть первой получает управление. Она выдает запрос на ввод даты и определяет сегментный адрес, куда система должна загружать программы для выполнения. Ни одна из этих программ инициализации не потребуются больше во время сеанса работы. Поэтому первая же команда вводимая с клавиатуры и вызывающая загрузку некоторой программы с диска перекрывают часть инициализации в памяти. Ассемблер для IBM PC. Глава 22 35 3. Транзитная часть загружается в самые старшие адреса памяти. "Транзит" обозначает, что DOS может перекрыть данную область другими программами, если потребуется. Транзитная часть программы COMMAND.COM выводит на экран приглашение DOS A> или C>, вводит и выполняет запросы. Она содержит настраивающий загрузчик и предназначена для загрузки COM- или EXE-файлов с диска в память для выполнения. Если поступил запрос на выполнение какой-либо программы, то транзитная часть строит префикс программного сегмента (PSP) непосредственно вслед за резидентной частью COMMAND.COM. Затем она загружает запрошенную программу с диска в память по смещению шест.100 от начала программного сегмента, устанавливает адреса выхода и передает управление в загруженную программу. Ниже приведена данная последовательность: IBMBIO.COM IBMDOS.COM COMMAND.COM (резидент) Префикс программного сегмента Выполняемая программа ... COMMAND.COM (транзитная часть, может быть перекрыта). Выполнение команды RET или INT 20H в конце программы приводит к возврату в резидентную часть COMMAND.COM. Если транзитная часть была перекрыта, то резидентная часть перезагружает транзитную часть с диска в память. ПРЕФИКС ПРОГРАММНОГО СЕГМЕНТА ------------------------------------------------------------ Префикс программного сегмента (PSP) занимает 256 (шест. 100) байт и всегда предшествует в памяти каждой COM- или EXE-программе, которая должна быть выполнена. PSP содержит следующие поля: 00 Команда INT 20H (шест.CD20). 02 Общий размер доступной памяти в формате хххх0. Напри мер, 512K указывается как шест.8000 вместо шест.80000. 04 Зарезервировано. 05 Длинный вызов диспетчера функций DOS. OA Адрес подпрограммы завершения. OE Адрес подпрограммы реакции на Ctrl/Break. 12 Адрес подпрограммы реакции на фатальную ошибку. 16 Зарезервировано. 2C Сегментный адрес среды для хранения ASCIIZ строк. 50 Вызов функций DOS (INT 21H и RETF). 5C Параметрическая область 1, форматированная как стандарт ный неоткрытый блок управления файлов (FCBЭ1). Ассемблер для IBM PC. Глава 22 36 6C Параметрическая область 2, форматированная как стандарт ный неоткрытый блок управления файлом (FCBЭ2); перекры вается, если блок FCBЭ1 открыт. 80-FF Буфер передачи данных (DTA). Буфер передачи данных DTA Данная часть PSP начинается по адресу шест.80 и представляет собой буферную область ввода-вывода для текущего дисковода. Она содержит в первом байте число, указывающее сколько раз были нажаты клавиши на клавиатуре непосредственно после ввода имени программы. Начиная со второго байта, находятся введенные символы (если таковые имеются). Далее следует всевозможный "мусор", оставшийся в памяти после работы предыдущей программы. Следующие примеры демонстрируют назначение буфера DTA: Пример 1. Команда без операндов. Предположим, что вы выз вали программу CALCIT.EXE для выполнения с помощью команды CALCIT [return]. После того, как DOS построит PSP для этой программы, он установит в буфере по адресу шест.80 значение шест.000D. Первый байт содержит число символов, введенных с клавиатуры после имени CALCIT, исключая символ "возврат каретки". Так как кроме клавиши Return не было нажато ни одной, то число символов равно нулю. Второй байт содержит символ возврата каретки, шест.0D. Таким образом, по адресам шест.80 и 81 на ходятся 000D. Пример 2. Команда с текстовым операндом. Предположим, что после команды был указан текст (но не имя файла), например, COLOR BY, обозначающий вызов программы COLOR и передачу этой программе параметра "BY" для установки голубого цвета на желтом фоне. В этом случае, начиная с адреса шест.80, DOS установит следующие значения байт: 80: 03 20 42 59 0D Эти байты обозначают длину 3, пробел, "BY" и возврат каретки. Пример 3. Команда с именем файла в операнде. Программы типа DEL (удаление файла) предполагают после имени программы ввод имени файла в качестве параметра. Если будет введено, например, DEL B:CALCIT.OBJ [return], то PSP, начиная с адресов шест.5C и шест.80, будет содержать: 5C: 02 43 41 4C 43 49 54 20 20 4F 42 4A C A L C I T O B J 80: 0D 20 42 3A 43 41 4C 43 49 54 2E 4F 42 4A 0D B : C A L C I T . 0 B J Ассемблер для IBM PC. Глава 22 37 Начиная с адреса шест.5C, находится неоткрытый блок FCB, содержащий имя файла, который был указан в параметре, CALCIT.OBJ, но не имя выполняемой программы. Первый символ указывает номер дисковода (02=B в данном случае). Следом за CALCIT находятся два пробела, которые дополняют имя файла до восьми символов, и тип файла, OBJ. Если ввести два параметра, например: progname A:FILEA,B:FILEB тогда DOS построит FCB для FILEA по смещению шест.5C и FCB для FILEB по смещению шест.6C. Начиная с адреса шест.80 в этом случае содержится число введенных символов (длина параметров) - 16, пробел (шест.20) A:FILEA,B:FILEB и символ возврат каретки (OD). Так как PSP непосредственно предшествует вашей программе, то возможен доступ к области PSP для обработки указанных файлов или для предпринятия определенных действий. Для локализации буфера DTA COM-программа может просто поместить шест.80 в регистр SI и получить доступ следующим образом: MOV SI,80H ;Адрес DTA CMP BYTE PTR [SI],0 ;В буфере нуль? JE EXIT Для EXE-программы нельзя с уверенностью утверждать, что кодовый сегмент непосредственно располагается после PSP. Однако, здесь при инициализации регистры DS и ES содержат адрес PSP, так что можно сохранить содержимое регистра ES после загрузки регистра DS: MOV AX,DSEG MOV DS,AX MOV SAVEPSP,ES Позже можно использовать сохраненный адрес для доступа к буферу PSP: MOV SI,SAVEPSP CMP BYTE PTR [SI+ 80H],0 ;В буфере нуль? JE EX