ения - директива 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 (глобальный). Этот последний атрибут указывает, что данное имя доступно из вне подпрограммы, т.е. глобально. Карта компановки (в конце листинга) отражает организацию программы в памяти. Заметьте, что здесь имеются два кодовых сегмента (для каждого ассемблирования) с разными стартовыми адресами. Последовательность расположения кодовых сегментов соответствует последовательности указанных для компа новки объектных модулей (обы