Макросы

Тема в разделе "WASM.ARTICLES", создана пользователем Mikl___, 25 янв 2017.

  1. Mikl___

    Mikl___ Супермодератор Команда форума

    Публикаций:
    14
    Регистрация:
    25 июн 2008
    Сообщения:
    3.709

    В.Н.Пильщиков "Программирование на языке ассемблера IBM PC".
    М.: "ДИАЛОГ-МИФИ", 1994. ― 288 с.
    Фрагменты из "Главы 11. Макросредства"

    Макроязык

    Нередко бывает полезным предварительное (до начала трансляции) преобразование текста программы. Например, может потребоваться, чтобы какой-то фрагмент программы был продублирован несколько раз или чтобы в зависимости от некоторых условий в тексте программы были сохранены одни фрагменты и удалены другие. Подобную возможность предоставляют так называемые макросредства. Расширение языка Ассемблера за счет этих средств называют макроязыком.
    Программа, написанная на макроязыке, транслируется в два этапа.
    1. Сначала она переводится на «чистый» ассемблер, то есть преобразуется к такому виду, где нет никаких макросредств. Этот этап называется этапом макрогенерации, его осуществляет специальный транслятор ― макрогенератор.
    2. На втором этапе полученная программа переводится в машинный код. Это этап ассемблирования, его осуществляет ассемблер.
    Трансляция программы, написанная на макроязыке, занимает больше времени, чем трансляция программы на чистом языке ассемблера. Но макроязык предоставляет больше возможностей, поэтому писать большие программы проще на макроязыке.
    Макрогенератор и ассемблер могут взаимодействовать двояко.
    1. Они могут действовать независимо друг от друга: сначала текст программы обрабатывает макрогенератор и только затем начинает работать ассемблер. Такой способ достаточно прост для понимания и для реализации используется достаточно часто,однако у него есть серьезный недостаток: макрогенератор, работая до ассемблера, не может воспользоваться информацией, извлекаемой из текста программы ассемблером. Например, в макросредствах нельзя использовать то,что после директивы
      N equ 10
      имя N обозначает число 10, так как эта директива относится к чистому ассемблеру и будет обработана только им. Из-за подобного рода ограничений приходится усложнять макроязык.
    2. Макрогенератор и ассемблер могут действовать совместно, чередуя свою работу: первым каждое предложение программы просматривает макрогенератор; если эта конструкция макроязыка, то макрогенератор соответствующим образом преобразует ее в группу предложений ассемблера и передает их на обработку ассемблеру, а если это предложение ассемблера, то макрогенератор сразу передает его ассемблеру. В таких условиях в конструкциях макроязыка уже можно ссылаться на объекты (например, константы) "чистого ассемблера".
    Стандартные функции могут быть определены в библиотеке макрокоманд и доступны при дальнейшем программировании. Число операций повторного кодирования уменьшается, уменьшается и количество ошибок.

    Блоки повторения

    Иногда в программе приходится выписывать несколько раз подряд один и тот же (или почти один и тот же) фрагмент, и хотелось бы, чтобы мы сами выписали этот фрагмент только раз, а макрогенератор размножил его нужное количество раз. Такая возможность предусмотрена в языке ассемблер, и реализуется она с помощью блоков повторения (repeat blocks).

    Структура блока повторения


    <заголовок>
    <тело блока>
    ENDM
    • <тело блока> ― любое число любых предложений (в частности, ими могут быть снова блоки повторения),
    • ENDM ― директива, указывающая на конец тела и всего блока повторений.
    Встречая в исходном тексте программы такой блок, макрогенератор подставляет вместо него в окончательную программу несколько копий тела.
    При дублировании тело может выписываться без всяких изменений, а может копироваться с модификациями. Как именно будет происходить дублирование, сколько копий будет создано зависит от заголовка блока. Имеются три разновидности заголовка, поэтому различают три варианта блока повторения:
    • REPT–блоки
    • IRP–блоки
    • IRPC–блоки

    REPT–блоки

    Этот тип блоков повторения записывается следующим образом:
    REPT k
    <тело блока>
    ENDM
    • k ― константное выражение с неотрицательным значением. Это выражение должно быть таким, чтобы можно было вычислить его сразу (например, в нем не должно быть ссылок вперед). Вычислив значение k, макрогенератор создаст k точных копий тела блока и подставляет их в окончательный текст программы:
    До макрогенерацииПосле макрогенерации
    REPT 3
    SHR AX,1
    ENDM
    SHR AX,1
    SHR AX,1
    SHR AX,1
    До макрогенерацииПосле макрогенерации
    N EQU 6
    REPT N-4
    DB 0,1
    DW ?
    ENDM
    N EQU 6
    DB 0,1
    DW ?
    DB 0,1
    DW ?
    В блоках повторения довольно часто используется директива присваивания (=). Для описания 100-байтового массива X, элементы которого имеют начальные значения от 0 до 99, можно так
    X DB 0,1,...,99
    До
    макрогенерации
    После
    макрогенерации
    X DB 0
    K=0
    REPT 99
    K=K+1
    DB K
    ENDM
    X DB 0
    K=0
    K=K+1
    DB K
    K=K+1
    DB K
    ...
    }99 таких пар

    WHILE-блоки

    WHILE-блоки, также как и REPT-блоки, применяются для повторения определенного количества раз некоторой последовательности строк. В отличие от директивы REPT, которая автоматически уменьшает на единицу значение константного выражения и повторяет тело блока до тех пор пока значение константного выражения не станет равным нулю, директива WHILE требует чтобы уменьшение константного выражения происходило внутри тела блока.

    структура WHILE-блока


    WHILE k
    <тело блока>
    ENDM
    макрос демонстрирует резервирование памяти длиной len байтов с использованием директивы WHILE:
    WHILE len
    DB 0
    len=len-1
    ENDM

    IRP–блоки

    Блоки повторения этого типа имеют следующий вид
    IRP p,<v1,...,vk>
    <тело блока>
    ENDM
    Запись в уголках <v1,...,vk> ― это явно указываемые символы, а не метасимволы. Здесь p ― некоторое имя, оно играет роль формального (фиктивного) параметра и используется в предложениях тела макроса. vi ― это фактические параметры; это любые тексты (возможно, и пустые), но, чтобы не было путаницы, они должны быть сбалансированы по кавычкам и не должны содержать запятые, точки с запятой и уголки вне кавычек (если это ограничение надо нарушить, то следует воспользоваться микрооператорами). Параметры vi перечисляются через запятую, а вся их совокупность обязательно заключается в угловые скобки.
    Встречая такой блок макрогенератор заменяет его на k копий тела (по одной на фактический параметр), причем в i-той копии все вхождения имени p заменяются на vi:
    До макрогенерацииПосле макрогенерации
    IRP REG,<RAX,RBX,RCX]
    PUSH REG
    ENDM
    PUSH RAX
    PUSH RBX
    PUSH RCX
    Формальный параметр локализуется в теле блока (им нельзя пользоваться вне блока) и может быть любым именем. Если оно совпадает с именем другого объекта программы, то в теле блока оно обозначает именно параметр, а не объект. Имя BX обозначает параметр, а не регистр, поэтому по данному блоку будет построен следующий фрагмент окончательной программы:
    До макрогенерацииПосле макрогенерации
    IRP BX,<1,5>
    ADD AX,BX
    ENDM
    ADD AX,1
    ADD AX,5
    Замены формального параметра на фактические ― это чисто текстуальные подстановки, без учета смысла: просто один участок p заменяется на другой vi. При этом параметром p можно обозначить любую часть предложения (в частности, участки комментария) или даже целиком все предложение (однако два и более предложения он не может обозначать), лишь бы после замены p на vi получались правильные предложения:
    До макрогенерацииПосле макрогенерации
    IRP Q,<DEC WORD PTR,L: INC>
    Q W
    JMP M2
    ENDM
    DEC WORD PTR W
    JMP M2
    L: INC W
    JMP M2
    В теле блока повторения заменяется только формальный параметр, другие имена (например, имена констант переносятся в копии тела без изменений:
    До макрогенерацииПосле макрогенерации
    N EQU 1
    IRP P,<A,B>
    P EQU N
    ENDM
    N EQU 1
    A EQU N;но не A EQU 1
    B EQU N

    IRPC–блоки

    Блоки этого типа записываются следующим образом:
    IRPC p,s1...sk
    <тело блока>
    ENDM
    • p ― формальный параметр
    • si ― символы. Это могут быть любые символы, кроме пробелов и точек с запятой (считается, что с точки с запятой начинается комментарий, а пробел заканчивает операнд; если надо указать точку с запятой или пробел, то всю последовательность символов следует заключить в угловые скобки).
    Встречая IRPC–блок, макрогенератор заменяет его на k копий тела блока (по одной на каждый символ), причем в i-ой копии все вхождения p будут заменены на символ si.
    До макрогенерацииПосле макрогенерации
    IRPC D,17W
    ADD AX,D
    ENDM
    ADD AX,1
    ADD AX,7
    ADD AX,W

    Макрооператоры

    При использовании блоков повторения (и макросов) возникает ряд проблем с записью формальных и фактических параметров. Эти проблемы решаются с помощью макрооператоров ― операторов, разрешенных к применению только в конструкциях макроязыка.
     
  2. Mikl___

    Mikl___ Супермодератор Команда форума

    Публикаций:
    14
    Регистрация:
    25 июн 2008
    Сообщения:
    3.709
    Макрооператор &
    Рассмотрим следующий блок повторения и построенные по нему копии:
    До макрогенерацииПосле макрогенерации
    IRP W,<VAR1, VAR6>
    W DW ?
    ENDM
    VAR1 DW ?
    VAR6 DW ?
    Параметр W обозначает имя переменной целиком. Но фактические имена (VAR1 и VAR6) различаются лишь последним символом, поэтому было бы разумно объявить параметром только этот символ, а не все имя. Но если так сделать, то получится неоднозначность ― непонятно когда W обозначает формальный параметр, а когда просто букву W (почему в VARW надо W заменять на 1 и 6, а в DW не надо?)
    IRPC W,16
    VARW DW ?
    ENDM
    В предыдущих примерах формальные параметры легко выделялись из окружающего текста благодаря ограничителям (пробелам, запятым), стоящим слева и справа от параметра. Но если рядом с параметром стоит имя или число, то границы параметра становятся неопределяемыми. В подобной ситуации следует между параметром и соседним с ним числом или именем поставить символ & (A&W, 1&W&B и тому подобное). Первый блок повторения должен быть записан так:
    IRPC W,16
    VAR&W DW ?
    ENDM
    Знак & указывает границу формального параметра, выделяет его из окружающего текста, но в окончательный текст программы не попадает. Если & поставить не около параметра, то он будет пропущен.
    Макрооператор & используется не только тогда, когда формальный параметр сливается с соседними именами и числами, но и когда его надо указать внутри строк. Макрогенератор игнорирует вхождение формального параметра в строки. Чтобы обратить внимание макрогенератора на эти вхождения, перед параметром в строках ставят знак &, а если не ясна правая граница параметра, то & надо указывать и после параметра:
    До макрогенерацииПосле макрогенерации
    IRPC A,”<
    DB ‘A,&A,&A&B’
    ENDM
    DB ‘A,”,”B’
    DB ‘A,<,<B’
    Если рядом поставить несколько знаков &, то макрогенератор удалит только первый из них ― это дает возможность создавать вложенные блоки повторений. Встретив в тексте исходной программы блок повторения, макрогенератор сначала создаст первую копию тела внешнего блока, в котором все вхождения формального параметра P1 будут заменены на символ A. При этом из двух стоящих подряд в команде INC знаков & будет удален только один, оставшийся знак & будет отделять формальный параметр внутреннего блока от стоящей слева буквы A. В полученной копии остались конструкции макроязыка, поэтому макрогенератор продолжит свою работу. После обработки внутреннего блока получится уже окончательный текст. Далее будет создана вторая копия внешнего блока, которая будет обработана аналогично:
    До макрогенерацииПосле первой макрогенерацииПосле второй макрогенерации
    IRPC P1,AB
    IRPC P2,HL
    INC P1&&P2
    ENDM
    ENDM
    IRPC P2,HL
    INC A&P2
    ENDM
    IRPC P2,HL
    INC B&P2
    ENDM
    INC AH
    INC AL
    INC BH
    INC BL

    Макрооператор <>

    Фактические параметры IRP-блока не должны содержать запятые, точки с запятой и уголки, второй операнд IRPC-блока не должен содержать пробелы и точки с запятой. Эти ограничения введены чтобы не возникало неоднозначности ― если внутри фактического параметра IRP-блока указать запятую (например: 1,2), тогда будет непонятно, что означает эта запись ― два параметра разделенных запятой или один параметр, содержащий внутри себя запятую. Если необходимо нарушить указанные ограничения, тогда весь фактический параметр IRP-блока или всю последовательность символов в IRPC-блоке надо заключить в угловые скобки (например: <1,2>), причем весь текст внутри этих скобок должен быть сбалансирован по уголкам:
    До макрогенерацииПосле макрогенерации
    IRP VAL,<<1,2>,3>
    DB VAL
    ENDM
    DB 1,2
    DB 3
    Считается, что внешние угловые скобки не относятся к параметру или последовательности, а лишь указывают их границы:
    До макрогенерацииПосле макрогенерации
    IRPC S,<A;B>
    DB ‘&S’
    ENDM
    DB 'A'
    DB ';'
    DB 'B'

    Макрооператор !

    Для задания внутри фактического параметра непарного уголка или непарной кавычки, а также для задания других спецсимволов (вне и внутри угловых скобок) предусмотрен макрооператор:
    !<символ>
    Символ "!" не переносится в окончательный текст, но следующий за ним символ трактуется как обычный символ, а не как символ, играющий какую-то специальную роль:
    До макрогенерацииПосле макрогенерации
    IRP X,<A!>B, Привет!, компьютер!!>
    DB ‘&X’
    ENDM
    DB ‘A>B’
    DB ‘Привет, компьютер!’
    Макрооператор "!" можно использовать только при записи фактических параметров IRP-блоков и макросов. В последовательности символов IRPC-блока знак ! рассматривается как обычный символ, а не как макрооператор. Указывать в этой последовательности уголки можно сколько угодно раз, если эта последовательность не начинается с открывающей угловой скобки, а если начинается ― пока нет баланса угловых скобок.

    Макрооператор %

    Макрооператор используемый при записи фактических параметров IRP-блоков и макросов:
    %<константное выражение>
    Встретив такую конструкцию в фактическом параметре, макрогенератор вычисляет указанное выражение и подставляет его значение вместо всей этой конструкции. Вложенности макрооператоров % не допускается. В конструкции %5-%K будет зафиксирована ошибка «неописанное имя %K».
    До макрогенерацииПосле макрогенерации
    K EQU 4
    . . .
    IRP A,<K+1,%K+1,W%K+1>
    DW A
    ENDM
    DW K+1
    DW 5
    DW W5
    Концом константного выражения считается первый символ (запятая, угловая скобка или знак равенства), который не может по синтаксису входить в константные выражения (например, если константа K=4 параметр %K-1+K=K будет преобразован в 7=K). В последовательности символов IRPC-блока знак % рассматривается как обычный символ, а не как макрооператор.

    Макрооператор ;;

    Если в теле блока повторения или макроса имеются комментарии (в конце каких-то предложений или как самостоятельные предложения), то они переносятся во все копии блока. Если комментарий полезен при описании самого блока повторения, но совершенно не нужен в его копиях ― тогда комментарий начинают с двух точек с запятой ― такие комментарии не копируются:
    До макрогенерацииПосле макрогенерации
    IRP R,<RAX,RBX>
    ;;восстановление регистров
    ;восстановить R

    POP R;;из стека в R
    ENDM
    ;восстановить RAX
    POP RAX
    ;восстановить RBX
    POP RBX
     
    Последнее редактирование: 27 янв 2017
  3. Mikl___

    Mikl___ Супермодератор Команда форума

    Публикаций:
    14
    Регистрация:
    25 июн 2008
    Сообщения:
    3.709

    Макросы

    С помощью блока повторения один раз описывается некоторый фрагмент программы, который затем многократно копируется макрогенератором. Но блоки повторения можно использовать, только если эти копии расположены рядом друг с другом.
    Если фрагмент программы должен повторяться в разных местах программы используются макросы ― специальным образом описанный фрагмент программы, которому дали имя, а затем в нужных местах программы выписывается ссылка на этот макрос с указанием его имени. Когда макрогенератор просматривает текст программы и встречает такую ссылку, то он вместо нее подставляет в окончательный текст программы сам макрос ― соответствующий фрагмент программы.
    Так делается для каждой ссылки на макрос, в каком бы месте программы она не встретилась.
    Терминология:
    • макроопределение ― описание макроса,
    • ссылка на макрос ― макрокоманда,
    • процесс замены макрокоманды на макрос ― макроподстановка,
    • результат макроподстановки ― макрорасширение.

    Макроопределения

    Описание макроса имеет следующий вид:
    <имя макроса> MACRO <формальные параметры через запятую>
    <тело макроса>
    ENDM
    Первая строка макроопределения ― это директива MACRO, которую принято называть заголовком макроса.
    В ней, во-первых, указывается имя, которое дается макросу, во-вторых, через запятую перечисляются формальные параметры макроса. Необходимость в параметрах вызвана тем, что в общем случае макрос должен копироваться не в неизменном виде, а с некоторыми модификациями; параметры и обозначают те величины, которые влияют на модификации.
    Формальным параметрам можно давать любые имена, эти имена локализуются в теле макроса; если имя параметра совпало с именем другого объекта программы, то внутри макроопределения под этим именем понимается параметр, а не объект.
    Тело макроса ― это тот фрагмент программы, который и будет затем многократно копироваться. Тело может состоять из любого числа любых предложений, в которых можно использовать формальные параметры макроса. Как и в блоках повторения, формальные параметры могут обозначать любые части предложений тела. Если рядом с параметром надо указать имя или число, или если параметр надо указать внутри строки, то следует использовать макрооператор &. В теле макроса можно использовать комментарии, начинающиеся с двух точек с запятой.
    Завершает макроопределение директива ENDM (end of macro). В этой директиве, в отличие от блока повторения, не надо повторять имя макроса. Если перед директивой ENDM указать имя макроса, то это предложение будет рассматриваться как рекурсивный вызов макроса.
    Макроопределения могут быть размещены в любом месте текста программы, по ним в машинную программу ничего не записывается. Но макроопределение должно быть записано до первой ссылки на этот макрос ― действует правило ассемблера: “Сначала опиши макрос и только затем обращайся к нему”.

    Макрокоманды

    В тех местах программы, где макрогенератор должен подставить макрос, выписывается обращение к макросу в виде макрокоманды, которая записывается следующим образом:
    <имя макроса> <фактические параметры через запятую или пробел>

    Конкретные примеры:


    1-ый вариант2-ой вариант
    SUM A,ES:BSUM A ES:B
    VAR Z,W,?VAR Z W,?
    Макрокоманды очень похожи на обычные команды и директивы. Но вместо названия команды или директивы, являющегося служебным словом, в макрокоманде указывается имя макроса, которое придумал автор программы. В макрокоманде параметры могут отделяться как запятыми, так и пробелами.
    В качестве фактического параметра может быть указан любой текст (в том числе и пустой), но он должен быть сбалансирован по кавычкам и угловым скобкам и в нем не должно быть запятых, пробелов и точек с запятой вне этих кавычек и скобок. Поскольку запятыми и пробелами отделяется один параметр от другого, а с точек с запятой начинается комментарий, то их использование внутри фактического параметра приведет к неоднозначности.
    При записи параметров макрокоманд можно использовать те же макрооператоры <>, ! и %, что и при записи фактических параметров блоков повторения. Если в фактическом параметре надо указать запятую, пробел или точку с запятой, то параметр следует заключить в уголки:
    SUM <WORD PTR [SI]>,A
    VAR C W <1,2>
    Уголки не считаются относящимися к параметру, а лишь указывают его границы. Число фактических параметров, указываемых в макрокоманде, должно равняться числу формальных параметров макроса, причем i-ый фактический параметр соответствует i-му формальному параметру. Но, если фактических параметров указано больше, чем надо, то лишние фактические параметры игнорируются, а если меньше, то считается, что в качестве недостающих фактических параметров заданы пустые тексты.

    Макроподстановки и макрорасширения

    Когда макрогенератор, просматривая исходный текст программы, встречает макрокоманду, то он выполняет макроподстановку: находит описание макроса с указанным именем, берет его тело, заменяет в этом теле все формальные параметры на соответствующие фактические параметры и полученный таким образом текст (он называется макрорасширением) подставляет в программу вместо макрокоманды:
    Макроопределениемакрокомандамакрорасширение
    SUM MACRO X,Y
    MOV AX,Y
    ADD X,AX
    ENDM
    SUM A,ES:BMOV AX,ES:B
    ADD A,AX
    VAR MACRO NM,TP,VL
    NM D&TP VL
    ENDM
    VAR,W,<1,2>DW 1,2

    Примеры использования макросов

    Одним из недостатков программирования на языке ассемблера ― его чрезвычайная низкоуровневость операций.
    Например, язык ассемблера может сложить два числа, а вот для сложения трех чисел необходимо писать специальную программу. Такое сведение к операциям низкого уровня приходится делать для любого алгоритма каким бы сложным он не был.
    В определенной мере этот недостаток устраняется с помощью макросов. Для этого надо в виде макросов описать более крупные операции, а затем составлять программу с использованием этих макросов.
    Пусть в нашей программе многократно встречается условный переход «if x < y then goto L». Эта операция реализуется тремя командами:
    IF_LESS MACRO X,Y,L
    MOV AX,X
    CMP AX,Y
    JL L
    ENDM
    Чтобы каждый раз не выписывать эту группу команд заново, имеет смысл описать ее как макрос, а затем пользоваться этим макросом. Имея макрос IF_LESS, можно следующим образом описать вычисление минимума трех чисел:
    До макрогенерацииПосле макрогенерации
    MOV DX,A
    IF_LESS A,B,M1
    MOV DX,B
    M1: IF_LESS DX,C,M2
    MOV DX,C
    M2:
    . . .
    MOV DX,A
    MOV AX,A
    CMP AX,B
    JL M1
    MOV DX,B
    M1: MOV AX,DX
    CMP AX,C
    JL M2
    MOV DX,C
    M2: . . .
    Использование макросов сокращает размеры исходного текста программы и позволяет составлять программу в терминах более крупных операций. Если в виде макросов описать все часто используемые операции, то можно построить новый язык программирования, создавать программы на котором существенно легче, чем на чистом языке ассемблера. История гласит, что язык Си был создан потому, что его авторам Бриану Керниган (Brian Kernighan) и Дэннису Ритчи (Dennis Ritchie) надоело писать программы на языке ассемблера. История умалчивает о том, что они отнюдь не презирали язык ассемблера как таковой. Наверняка они отдавали должное возможности программировать на самой грани между программными и аппаратными средствами; но есть основания подозревать, что кому-то из них больше по душе были макросы :).
    Предположим, что имеется процедура вычисляющая наибольший общий делитель (NOD). Параметр X передается через регистр RAX, параметр Y ― через регистр RBX, результат Z возвращается через RAX. Вычисляем RAX=NOD(A,B)+NOD(C,D).
    Соответствующий фрагмент программы:
    MOV RAX,A
    MOV RBX,B
    CALL NOD
    MOV temp,RAX
    MOV RAX,C
    MOV RBX,D
    CALL NOD
    ADD RAX,temp
    При каждом обращении к процедуре NOD выписывается почти одна и та же группа команд.
    Опишем эту группу как макрос, а затем его используем. Описание этого макроса:
    CALL_NOD MACRO X,Y
    MOV RAX,X
    MOV RBX,Y
    CALL NOD
    ENDM
    Нужный нам фрагмент программы выглядит более наглядно и короче:
    CALL_NOD A,B
    MOV temp,RAX
    CALL_NOD C,D
    ADD RAX,temp
    После макроподстановок получится тот же текст программы, что и прежде, но построением такого текста будет заниматься макрогенератор, а не программист.
    При входе в процедуру приходится сохранять в стеке содержимое регистров, для чего многократно записывается несколько команд PUSH, а при выходе из процедуры несколько команд POP. Если в программе много процедур ― стоит облегчить себе жизнь, записав эти команды в виде макросов.
    У этих макросов может быть любое количество фактических параметров (разное количество названий регистров), так как в языке ассемблера можно определять макросы с фиксированным числом формальных параметров, поэтому макрос описывается с одним формальным параметром, но при обращении к нему в макрокоманде указывают через запятую нужное количество фактических параметров и заключают весь список в угловые скобки, в результате получается синтаксически один параметр. В теле макроса от этого списка отделяют по одному настоящему параметру и что-то с ним делают.
    Макрос должен поставлять в текст программы (в макрорасширение) несколько однотипных команд PUSH R. Воспользуемся IRP-блоком повторения:
    SAVE MACRO REGS; запись в стек регистров из списка
    IRP R,<REGS>
    PUSH R
    ENDM
    ENDM
     
    Последнее редактирование: 15 дек 2022
  4. Mikl___

    Mikl___ Супермодератор Команда форума

    Публикаций:
    14
    Регистрация:
    25 июн 2008
    Сообщения:
    3.709
    По макрокоманде SAVE <RAX,RSI,RBP> в теле макроса формальный параметр будет заменен на фактический. Уголки, в которые был заключен фактический параметр, считаются не относящимися к параметру и при макроподстановке удаляются. Но в директиве IRP были свои уголки, именно они и сохранились в тексте, поэтому директива сохранила правильный синтаксис. Получившийся текст содержит конструкцию макроязыка, поэтому макрогенератор продолжает обрабатывать IRP-блок, пока не получится текст без макроподстановок:
    До макрогенерацииПосле макрогенерации
    IRP R,<RAX,RSI,RBP>
    PUSH R
    ENDM
    PUSH RAX
    PUSH RSI
    PUSH RBP
    Аналогично создается макрос для подстановок команд POP.

    Определение макроса через макрос

    Из процедуры можно обращаться к другой процедуре. Аналогично, при описании одного макроса можно ссылаться на другой макрос. Допускается также обращение макроса к самому себе, то есть разрешены рекурсивные макросы. Но на практике рекурсивные макросы встречаются крайне редко. Рассмотрим нерекурсивное обращение одного макроса к другому.
    Пусть макрос ARR предназначен для описания массива X из N байтов:
    ARR MACRO X,N
    X DB N DUP (?)
    ENDM
    Используя его, можно определить макрос ARR2,предназначенный для описания сразу двух массивов одного и того же размера:
    ARR2 MACRO X1,X2,K
    ARR X1,<K>
    ARR X2,<K>
    ENDM
    При таком макроопределении макроподстановка для макроса ARR2 происходит в два этапа. В теле макроса ARR2 при обращении к макросу ARR необходимо второй фактический параметр записать в уголках:
    До макрогенерацииПосле первой макрогенерацииПосле второй макрогенерации
    ARR2 A,B,20ARR A,<20>
    ARR B,<20>
    A DB 20 DUP (?)
    B DB 20 DUP (?)
    Если по смыслу первым и вторым параметрами макроса ARR2 могут быть только имена, то как третий параметр может быть указана достаточно сложная конструкция, например: ARR2 A,B,<25 MOD 10>.
    Если бы вместо записи <K> использовалась просто запись K, тогда на первом этапе макроподстановки получилась бы макрокоманда ARR A,25 MOD 10 с четырьмя операндами, а не с двумя (при макроподстановке уголки фактического параметра отбрасываются и в макрокомандах параметры могут отделяться как запятыми, так и пробелами). При записи <K> уголки заставляют рассматривать эту конструкцию как один параметр: ARR A,<25 MOD 10>.
    В языке ассемблера допускается вложенность макроопределений. Но при этом макрос ARR, хотя и описан внутри макроса ARR2, не локализуется в ARR2, и к нему можно обращаться вне макроса ARR2. Но язык ассемблера заметит описание внутреннего макроса только при первом обращении к внешнему макросу.
    ARR2 MACRO X1,X2,K
    ARR MACRO X,N
    X DB N DUP (?)
    ENDM
    ARR X1,<K>
    ARR X2,<K>
    ENDM
    Поэтому обращаться макросу ARR до обращения к макросу ARR2 нельзя:
    ARR A,50 ;ошибка (имя ARR еще не описано)
    ARR2 B,C,100
    ARR D,60 ;можно (имя ARR уже описано)

    Директива LOCAL

    При использовании макросов возникает проблема с метками, которые могут быть помечены предложения тела макроса. Если есть макрос M с внутренней меткой L и в программе имеется N обращений к этому макросу.
    M MACRO
    ...
    L: ...
    ...
    ENDM
    Тогда после макроподстановок в окончательном тексте программы появится N команд, помеченные одной и той же меткой L, а это ошибка.
    МакрокомандаМакрорасширение
    M
    . . .
    M
    . . .
    L: . . .
    . . .
    L: . . .
    . . .
    Имя L не является формальным параметром макроса и поэтому при макроподстановке ни на что не заменяется, а переносится в макрорасширение без всяких изменений. Для избежания этой ошибки можно включить метку L в число параметров макроса и при обращении к макросу указывать различные фактические метки. Но метки, которыми метятся команды макроса, ― это только внутреннее дело макроса, и для того, кто будет пользоваться таким макросом, придумывать имена для внутренних меток только лишняя головная боль. Поэтому в языке ассемблера принято более удобное решение данной проблемы. В подобных случаях после директивы MACRO указывают директиву LOCAL v1,...,vk, где vi ― имена, используемые в макроопределении (метки). При макроподстановке макрогенератор заменит эти имена на имена вида: ??xxxx, где ― xxxx четырехзначное шестнадцатеричное число от ??0000 до ??FFFF.
    MD MACRO R1,R2
    LOCAL M1,M2
    M1: CMP R1,R2
    JB M2
    SUB R1,R2
    JMP M1
    M2:
    ENDM
    Директиву LOCAL можно указывать только после директивы MACRO любое число раз и нигде более. Директива LOCAL не переносится в макрорасширение.

    Директивы EXITM и GOTO

    У директивы EXITM нет операндов. Ее можно использовать только внутри макроопределений и блоков повторения (внутри конструкций макроязыка заканчивающихся директивой ENDM). Встретив директиву EXITM макрогенератор завершает обработку ближайшего объемлющего макроопределения или блока повторения.
    Макрогенератор, создавая первую копию тела блока повторения, перенесет предложение DB 0 в макрорасширение, а затем, встретив EXITM, полностью завершит обработку этого блока, но не покинет тело макроса, а перескочит за ближайшую директиву ENDM. Директива EXITM используется тогда, когда при выполнении некоторого условия надо досрочно, не доходя до ENDM, прекратить макроподстановку или раскрутку блока повторения. Используется в основном в отладочных целях.
    МакроопределениеМакрокомандаМакрорасширение
    A MACRO K
    REPT K
    DB 0
    EXITM
    ENDM
    DW ?
    ENDM
    A 5DB 0
    DW ?
    Макрогенератор, создав первую копию тела блока повторения, перенесет предложение DB 0 в макрорасширение, а затем, встретив EXITM, полностью завершил обработку блока, но не покинул тело макроса, а перескочил за ближайшую директиву ENDM на предложение DW ?
    Директива GOTO <имя метки> переводит процесс генерации макроопределения в другое место, прекращая тем самым последовательное разворачивание строк макроопределения. Метка, на которую передается управление, имеет специальный формат
    : :<имя метки>

    Переопределение и отмена макросов

    Если в тексте программы описать макрос с именем, которым ранее был обозначен другой макрос, то с этого момента прежний макрос считается уничтоженным, а новый макрос действующим:
    МакроопределениеМакрокомандаМакрорасширение
    A MACRO X
    INC X
    ENDM
    A CXINC CX
    A MACRO Y,Z
    CMP Y,0
    JE Z
    ENDM
    A BH,EQCMP BH,0
    JE EQ
    Макрос можно уничтожить, не определяя новый макрос с тем же именем, используя директиву PURGE с именем макроса (именами макросов через запятую). После этой директивы все макросы, имена которых в ней перечислены, считаются недействующими.
    После директивы PURGE A,INIT к макросам A и INIT уже нельзя обращаться.

    Условное ассемблирование

    Условное ассемблирование ― это средство для указания программе ассеблеру о трансляции или нетрансляции в объектный код конкретных блоков исходной программы. Если макросы и блоки повторений позволяют избежать многократного выписывания в исходном тексте программы повторяющихся фрагментов, то такое средство макроязыка, как условное ассемблирование, удобно при многократных прогонах программы. Условное ассемблирование позволяет в исходном тексте держать несколько вариантов одного и того же участка программы, а при каждом ее прогоне оставлять в окончательном тексте только один из фрагментов. Какой из вариантов будет оставлен, зависит от тех или иных условий, которые автор программы задает перед прогоном. Автор программы перед каждым прогоном не должен в ручную редактировать текст программы, а возлагает эту работу на макрогенератор. Вы можете в одном тексте программы создать и рабочую программу, и ее демонстрационную версию с ограниченным использованием функций рабочей программы. Возможно также создание в программе интерфейса, поддерживающего несколько языков для общения с пользователем и так далее и тому подобное.
    Программист, изменяя несколько предложений в начале программы, указывает программе-ассемблеру, какие именно куски программы нужно включать в объектную программу для данной системы.
    Участок программы, затрагиваемый условным ассемблированием, записывается в виде IF-блока:
    <IF-директива>
    <фрагмент1>
    ELSE
    <фрагмент2>
    ENDIF
    или
    <IF-директива>
    <фрагмент1>
    ENDIF
    Директивы ELSE и ENDIF обязательно должны записываться в отдельных строчках. В каждом фрагменте может быть любое количество любых предложений, в них допускаются вложенные IF-блоки.
     
  5. Mikl___

    Mikl___ Супермодератор Команда форума

    Публикаций:
    14
    Регистрация:
    25 июн 2008
    Сообщения:
    3.709
    В IF-директиве указывается некоторое условие, которое проверяется макрогенератором. Если условие выполнено, то макрогенератор оставляет в окончательном тексте программы только фрагмент1, а фрагмент2 исключает, не переносит в окончательный текст. Если же условие не выполнено, тогда фрагмент1 игнорируется, а в окончательную программу вставляется только фрагмент2. Если части ELSE нет, то считается, что пуст фрагмент2, поэтому при невыполнении условия такой IF-блок ничего не вставляет в окончательный текст программы.
    Так как условие в IF-директиве проверяется на этапе макрогенерации, то в нем не должно быть ссылок на величины, которые станут известными только при выполнении программы (в условии нельзя ссылаться на содержимое регистров или ячеек памяти). Условие должно быть таким, чтобы макрогенератор мог вычислить его сразу, как только встретит его (не должно быть ссылок вперед).

    Директивы IF и IFE

    Встречая директиву IF <константное условие> или IFE <константное условие>, макрогенератор вычисляет указанное в ней константное условие. В директиве IF условие считается выполненным, если значение выражения не равно нулю, а в директиве IFE (if equal― если равно) если значение выражения равно нулю.
    При отладке программы в определенные места текста программы вставляют печать промежуточных значений каких-то переменных, заканчивая отладку, отладочную печать убирают из текста программы. Но если в программе опять появляются ошибки, чтобы их найти, придется снова вставить отладочную печать, а после исправления опять удалить. И этот процесс вставки и удаления отладочной печати может продолжаться долго.
    Вставлять и удалять отладочную печать можно самим, но если отладочной печати много и если она разбросана по всей программе, то такое изменение текста займет много времени, и здесь легко ошибиться. Понять это может только тот, кто сам прошел через отладочную распечатку своей программы. В подобной ситуации удобно использовать возможности условного ассемблирования: в тексте программы сохраняем отладочную печать, но перед ней указываем условие, что команды отладочной печати должны появляться в окончательном тексте программы, если установлена константа DEBUG EQU 1. Участок исходной программы с отладочной печатью должен выглядеть так:
    IF DEBUG
    Print X
    ENDIF
    Пусть при отладке на экран выводится значение переменной X. Достаточно перед прогоном программы изменить константу DEBUG и макрогенератор будет формировать окончательный текст программы как с отладочной печатью, так и без нее. Сделать так, чтобы отладочная печать выполнялась при отладке и не выполнялась при счете, можно и с помощью команд условного перехода: получается то же самое решение проблемы.
    MOV A,AX
    CMP отладка?
    JNE L
    Print X
    L: ...
    Но в данном случае команды отладочной печати остаются в программе всегда, нужны они или нет, и поэтому будут всегда занимать место в памяти. Проверка, выполнять отладочную печать или нет, делается в процессе выполнения программы и на это тратится время. И последнее, избыточный код ― это всегда лазейка для взломщика. При условном ассемблировании команды отладочной печати, если они не нужны, будут удалены из окончательного варианта программы. Им просто не откуда взяться в памяти. Нет проверки, запускать отладочную печать или нет. При условном ассемблировании затрачивается больше времени на трансляцию программы, зато сама программа получается более экономичной и по памяти, и по времени.
    Допустим, Вы описываете в виде макроса SHIFT X,N сдвиг значения в ячейке X на N разрядов вправо при условии, что параметр X ― это имя какой-то переменной, чье значение находится в ячейке [110h], а N ― явно заданное положительное число, при условии, что макрорасширение этого макроса должно содержать минимально возможное число байт.
    SHIFT MACRO X,N
    IFE N-1
    SHR X,1
    ELSE
    SHR X,N
    ENDIF
    ENDM
    По макрокоманде SHIFT X,N при N=1 выработается код D02E1001 (SHR B/[0110],1), а при N>1 код C02E10010N (SHR B/[0110],0N), что на один байт больше. Поскольку в зависимости от величины N в окончательный текст программы должны попадать разные фрагменты, при описании макроса воспользуемся средствами условного ассемблирования.

    Операторы отношения. Логические операторы


    Константное выражение, указываемое в директивах IF и IFE, может быть любым, но по смыслу оно должно быть логическим выражением. В языке ассемблера существуют операторы, которые позволяют сформировать «чисто логический» результат. Это шесть операторов отношений результатом работы которых может быть одно из двух значений:
    • истина ― число, которое содержит единицы во всех разрядах 0FFFFh;
    • ложь ― число, которое содержит единицы во всех разрядах 0000h.
    Синтаксис
    Значение
    <выражение>EQ<выражение>Equal = равно
    <выражение>NE<выражение>Not Equal = не равно
    <выражение>LT<выражение>Less Then = меньше чем
    <выражение>LE<выражение>Less or Equal = меньше или равно
    <выражение>GT<выражение>Greater Then = больше чем
    <выражение>GE<выражение>Greater or Equal = больше или равно
    Четыре логических оператора результатом работы которых может быть как логический, так и числовой результат:
    Синтаксис
    Значение
    NOT<константное выражение>Logical NOT = инверсия
    <выражение>AND<выражение>Logical AND = конъюнкция
    <выражение>OR<выражение>Logical OR = дизъюнкция
    <выражение>XOR<выражение>logical eXclusive OR=исключающее ИЛИ
    В качестве примера макрос RSH B,N сдвиг байтовой переменной B на N разрядов вправо при условии, что N явно заданное неотрицательное число. При N=0 сдвиг не нужен (макрорасширение должно быть пустым), а при N>7 результат сдвига известен заранее (это 0), поэтом сдвиг заменен на запись нуля
    RSH MACRO B,N
    IF (N GT 0) AND (N LT 8) ;; 0<N<8 ?
    SHR B,N
    ELSE
    IF N GE 8
    MOV B,0
    ENDIF
    ENDIF
    ENDM
    Логические значения и отношения могут объединяться в более сложные логические выражения.
    Логические операторы используются для записи операндов команд и директив и вычисляются еще на этапе трансляции программы. Операндами логических операторов могут быть любые константные (но не адресные), значения которых трактуются как 16-битные слова. Значением логических операторов является 16-битное слово, которое получается в результате поразрядного выполнения соответствующей операции.

    Директивы IFB и IFNB, IFIDN, IFDIF

    Директивы IFB и IFNB используются для проверки формальных параметров, передаваемых в макрос. При вызове макрокоманды они анализируют значение аргумента, и в зависимости от того, равно оно пробелу или нет, транслируется либо <фрагмент1>, либо <фрагмент2>. В директиве IFB <аргумент> (if blank, если пустой) условие считается выполненным, если значение аргумента равно пробелу, то есть фактический аргумент при вызове макрокоманды не был задан, а в директиве IFNB <аргумент> (if not blank ― если не пустой) ― если значение аргумента не равно пробелу.
    SAVE MACRO REGS
    IFB <REGS>
    display “не задан регистр”
    EXITM
    ENDIF
    IRP R,<REGS>
    PUSH R
    ENDM
    ENDM
    Перед вами уже знакомый, но слегка модифицированный макрос SAVE. Если вызвать его без аргументов, то будет выведено сообщение о том, что не задан регистр и генерация макрорасширения будет прекращена директивой EXITM. Сообщение выводится при вызове макроса display с параметром “не задан регистр”.
    Директивы IFIDN и IFDIF позволяют не просто проверить наличие или значение аргументов макрокоманды, но и выполнить идентификацию аргументов как строк символов. Директива IFIDN сравнивает аргумент1 и аргумент2 как строки символов. Если строки совпадают (identifity ― тождество), то транслируется <фрагмент1> иначе <фрагмент2>. Директива IFIDNI ― вариант, директивы IFIDN которая игнорирует различие между строчными и прописными буквами. Синтаксис этих директив:
    IFIDN аргумент1, аргумент2
    <фрагмент1>
    ELSE
    <фрагмент2>
    ENDIF
    Директива IFDIF сравнивает аргумент1 и аргумент2 как строки символов. Если строки не совпадают (differnt ― другой), то транслируется <фрагмент1> иначе <фрагмент2>. Директива IFDIFI ― вариант директивы IFDIF, которая игнорирует различие между строчными и прописными буквами. Синтаксис директив:
    IFDIF аргумент1, аргумент2
    <фрагмент1>
    ELSE
    <фрагмент2>
    ENDIF
     
    Последнее редактирование: 26 янв 2017
    Мановар нравится это.
  6. Mikl___

    Mikl___ Супермодератор Команда форума

    Публикаций:
    14
    Регистрация:
    25 июн 2008
    Сообщения:
    3.709
    IFIDN <t1>,<t2>
    IFDIF <t1>,<t2>
    t1 и t2 ― любые тексты (последовательности символов), причем они обязательно должны быть заключены в уголки. Эти тексты по-символьно сравниваются. В директиве IFIDN условие считается выполненным, если эти тексты равны (identical ― идентичны), а в директиве IFDIF ― если они не равны (different ― различны). При сравнении текстов строчные и прописные буквы не отождествляются.
    Примеры:
    IFIDN <а+b>,<а+b> ― условие выполнено
    IFIDN <а+b>,<а> ― условие не выполнено
    IFIDN <a+b>,<a+B> ― условие не выполнено
    Обе эти директивы имеет смысл использовать лишь внутри тела макроса (или блока повторения), указывая в сравниваемых текстах формальные параметры макроса. При макроподстановке эти параметры будут заменяться на фактические параметры, и это позволяет проверить, заданы ли при обращении к макросу фактические параметры определенного вида или нет.
    Опишем в виде макроса MM R1,R2,T (где R1 и R2 ― имена байтовых регистров, содержимое которых трактуется как знаковые числа, а Т ― слово МАХ или MIN) операцию R1:=T(R1,R2), то есть запись в R1 либо максимума чисел R1 и R2 (при Т=МАХ), либо минимума.
    Прежде всего отметим, что этот макрос должен генерировать непустое макрорасширение, только если R1 и R2 ― разные регистры (при обращении же, скажем, MM AL,AL,MAX менять значение AL не надо). Как проверить несовпадение двух первых фактических параметров макроса? Отметим, что директива IF R1 NE R2 здесь не проходит, так как оператор NE предназначен для сравнения чисел, а нам надо сравнить имена регистров. Зато здесь проходит директива IFDIF <R1>,<R2>, в которой при макроподстановке формальные параметры R1 и R2 будут заменены на имена регистров (для указанной выше макрокоманды эта директива примет вид IFIDN <AL>,<AL>), что и позволит сравнить эти имена.
    Далее. Чтобы определить, что именно надо вычислять ― максимум или минимум, надо проверить третий фактический параметр: МАХ это или MIN? Сделать это можно, например, с помощью директивы IFIDN <Т>,<МАХ>: при
    макроподстановке формальный параметр Т будет заменен на третий фактический параметр, который тем самым и будет сравниваться со словом МАХ.
    И последнее. Вычисления максимума и минимума различаются лишь одной командой ― командой условного перехода:
    ;Rl:=max(Rl,R2)
    CMP R1,R2
    JGE L
    MOV R1,R2
    L:
    ;R1:=min(Rl,R2)
    CMP R1,R2
    JLE L
    MOV R1,R2
    L:
    поэтому не имеет смысла в макроопределении дважды выписывать эту практически одинаковую группу команд, а достаточно выписать ее лишь раз, поместив вместо команды условного перехода IF-блок, который в зависимости от параметра Т выберет нужную команду условного перехода.
    С учетом всего сказанного получаем такое описание макроса ММ:
    MM MACRO R1,R2,T
    LOCAL L
    IFDIF <R1>,<R2> ;;R1 и R2 ― разные регистры?
    CMP R1,R2
    IFIDN <T>, <MAX> ;; T=MAX?
    JGE L ;;да ― поместить JGE L в макрорасширение
    ELSE
    JLE L ;;нет ― поместить JLE L
    ENDIF
    MOV R1,R2
    L:
    ENDIF
    ENDM
    Приведем этапы макроподстановки для макрокоманды MM AL,BH,MIN после того, как в теле макроса формальные параметры были заменены на фактические параметры, а локальная метка L ― на специмя (скажем, ??0105):
    123
    IFDIF <AL>,<BH>
    CMP AL<BH
    IFIDN <MIN>,<MAX>
    JGE ??0105
    ELSE
    JLE ??0105
    ENDIF
    MOV AL,BH
    ??0105:
    ENDIF
    CMP AL,BH
    IFIDN <MIN>,<MAX>
    JGE ??0105
    ELSE
    JLE ??0105
    ENDIF
    MOV AL,BH
    ??0105:
    CMP AL,BH
    JLE ??0105
    MOV AL,BH
    ??0105:
    Как уже отмечалось, при сравнении текстов в директивах IFIDN и IFDIF большие и малые буквы не отождествляются. Это неприятная особенность данных директив. Обычно с их помощью в макросах сравниваются имена переменных и регистров, а эти имена в текстах программ могут записываться как малыми, так и большими буквами или вперемежку, и если при обращении к макросу имена записаны не теми буквами, как предусмотрел автор макроса, то этот макрос может сформировать неправильное макрорасширение. Например, по макрокоманде MM AL,AH,Max будет построена группа команд для вычисления минимума чисел из регистров AL и АН, так как в теле макроса ММ директива IFIDN будет сравнивать тексты Мах и МАХ, которые не совпадают.
    Как преодолеть этот недостаток? Можно предложить следующий метод: берем все возможные варианты записи имени (например, АН, Ah, аН и ah) и с помощью IRP-блока и директивы IFIDN проверяем, не совпадает ли заданное имя с одним из этих вариантов; если совпадает, тогда некоторому вспомогательному имени с помощью директивы присваивания (=) даем одно значение, не совпадает ― другое; затем это значение нужно проверить директивой IF.
    Эти действия (кроме последней проверки) можно описать в виде следующего макроса SAME N,LN,F (где N ― то, что проверяем; LN ― список (в уголках, через запятую) всех возможных вариантов записи имени, для которого делаем проверку; F ― имя, которому присваивается результат проверки: -1 (истина) ― есть совпадение, 0 ― нет):
    SAME MACRO N,LN,F
    F=0 ;;присвоить 0 имени F (на случай несовпадения)
    IRP V,<LN>;;для каждого варианта V из LN выполнить:
    IFIDN <V>,<N> ;;если V совпал с N, то
    F=-1;; переприсвоить F значение -1
    EXITM;;прекратить макроподстановку
    END IF
    ENDM
    ENDM
    Хотя макроподстановка для этого макроса может выполняться достаточно долго, в конце концов генерируется либо одна, либо две директивы присваивания, например:
    SAME Ah,<AH,Ah,aH,ah>,AX?AX?=0
    AX?=-1
    SAME bh,<AH,Ah,aH,ah>,AX?AX?=0
    Используем SAME для описания в виде макроса CALL_P X команд обращения к процедуре Р, параметр (X) для которой передается через регистр АХ. В общем случае этот макрос должен обозначать следующие команды:
    MOV АХ,Х
    CALL Р
    Однако, если в качестве X указан регистр АХ, то первая команда не нужна. Таким образом, здесь надо сравнивать X с именем АХ, причем с любым из возможных вариантов его записи. Для этого мы и воспользуемся макросом SAME, причем имя, которому присваивается результат проверки, локализуем в макросе CALL_P:
    CALL_P MACRO X
    LOCAL F
    SAME X,<AX,Ax,aX,ax>,F ;;F:-"истина", если X ― это AX
    IFE F;;если F ― "ложь", то
    MOV AX,X;;сформировать команду AX:-X
    ENDIF
    CALL P
    ENDM
    Теперь рассмотрим еще две IF-директивы:
    IFB <t>
    IFNB <t>
    Они фактически являются вариантами директив IFIDN и IFDIF, когда второй текст пуст. В директиве IFB (if blank ― если пусто) условие считается выполненным, если текст t пуст, а в директиве IFNB (if not blank ― если текст не пуст).
    Эти директивы используются в макросах для проверки, задан ли фактический параметр или нет (не задать параметр ― значит указать в соответствующей позиции макрокоманды пустой текст). Например, макроопределение
    DEF MACRO X,V
    IFB <V>;;параметр V не задан (пустой)?
    X DB ?
    ELSE
    X DB V
    ENDIF
    ENDM
    описывает макрос DEF X,N, по которому определяется байтовая переменная X с начальным значением V, если оно указано, или без него:
    DEF A,6A DB 6
    DEF BB DB ?
    Помимо рассмотренных здесь IF-директив имеются и другие IF-директивы, но на практике они используются не очень часто
     
    Последнее редактирование: 26 янв 2017
  7. Mikl___

    Mikl___ Супермодератор Команда форума

    Публикаций:
    14
    Регистрация:
    25 июн 2008
    Сообщения:
    3.709
    в качестве сборника упражнений к теории описанной в "Главе 11. Макросредства" можно использовать

    Пильщиков В.Н. «Упражнения по языку ассемблера MASM (Учебное пособие)» — М.: Изд-во факультета ВМиК МГУ, 1997. — 40 с.


    Контрольные вопросы и упражнения


    1. Описать с помощью подходящего блока повторения решение следующей задачи:
      • записать в регистр AH сумму чисел из регистров AL, BL, CL и DH;
      • обнулить переменные A, B и C типа DWORD;
      • используя стандрартную функцию вывода на экран вывести (с кавычками) текст "A+B=B+A";
      • зарезервировать (с помощью директивы DB) место в памяти для 40 байтов, присвоив им в качестве начальных значений первые 40 нечетных чисел (1, 3, 5, ..., 79).
    2. Выписать текст окончательной программы, который построит макрогенератор по следующему фрагменту исходной программы:
      • IRP C,<INC A,JE L>
        C
        ENDM
      • IRP W,<SW,< A,2>>
        MOV&W
        ENDM
      • N EQU 5
        IRP OP,<N,%N,N%N>
        ADD AX,OP
        ENDM
      • IRP C,<K,LL,M>
        C EQU C&C&CC
        DB 'C&C&CC'
        ENDM
      • IRP T,<AB,C>
        IRPC U,T
        DW U,T&U,T&&U
        ENDM
        DW U,T&U
        ENDM
      • IRPC CH,<! %">
        ADD AL,'&CH' ;код CH
        ENDM
    3. Описать в виде указанных (слева) макросов указанные операции над двойными
      словами (X, Y и Z ― переменные типа DWORD, L ― метка):
      DMOV X,Y : X:=Y
      DADD X,Y,Z : X:=Y+Z
      DSUB X,Y,Z : X:=Y-Z
      DJNE X,Y,L : при X ≠ Y перейти на L
    4. Описать в виде макроса DEF X, T, N, V определение массива X из N величин V, тип которых задается параметром T: при T = B это тип BYTE, при T = W ― тип WORD, при T = D ― тип DWORD.
      Выписать текст, который построит макрогенератор по следующему фрагменту исходной программы:
      K EQU 4
      DEF C,B,10,'*'
      DEF W,W,K+1,<TYPE C]
      DEF ,D,%K+1,%(TYPE W)
      DEF A,B,1,<1,2,3>
    5. Описать полную программу, которая вводит три числа H, M и S и проверяет, удовлетворяют ли они следующим условиям: 0 ≤ H ≤ 23, 0 ≤ M, S ≤ 59. Если нет, программа должна выдать сообщение об ошибке, а иначе, трактуя эти числа как час (H), минута (M) и секунда (S) некоторого момента суток, должна напечатать время суток, на 1 секунду большее (с учетом смены суток).
      Определить и использовать в этой программе два макроса, один из которых проверяет условие aXb, а другой ― увеличивает X на 1 и, если X > b, обнуляет X.
    6. Описать в виде указанного (слева) макроса указанное действие над знаковыми числами размером в байт:
      • ABS R,X : R := abs(X), где R ― регистр, X ― переменная
      • SUM X,N : AX := сумма элементов массива X из N байтов (N > 0)
      • MAX X,N : AL := максимум элементов массива X из N байтов (N > 0)
    7. Предположим, что следующих команд не существует. Опишите их в виде макроса
      (в качестве вспомогательных можно использовать регистры AX и BX):
      • PUSH X (X ― переменная размером в слово);
      • POP X (X ― переменная размером в слово);
      • CALL P (P ― имя процедуры);
      • RET N (возврат из процедуры);
      • LOOP L (L ― метка);
    8. Дано описание:
      X DW 1234h
      A MACRO R,U
      MOV R,U
      ENDM
      B MACRO R,V
      A R,<V>
      ENDM
      C MACRO R,V
      A R,V
      ENDM
      Определить значения регистров AH и AL после выполнения следующего фрагмента программы:
      B AH,<BYTE PTR X>
      C AL,<BYTE PTR X>
    9. Описать в виде макроса MAX2 M, X, Y вычисление M = max(X, Y) и на его основе описать макрос MAX3 M, X, Y, Z для вычисления M = max(X, Y, Z), где M, X, Y и Z ― знаковые байтовые переменные.
      Выписать макрорасширение для макрокоманды
      MAX3 A,<BYTE PTR B>,C+1,D
    10. Пусть K ― числовая константа с положительным значением, а α (альфа), β (бета) и γ (гамма)― некоторые группы предложений. Используя средства условного ассемблирования, выписать фрагмент исходной программы, в котором α, β и γ указаны лишь по разу и по которому в зависимости от значения K формируются следующие варианты окончательной программы:
      • при K = 1:
        α
        β
      • при K = 2:
        α
        γ
        γ
      • при K > 2:
        β
        γ
        ... (K раз)
        γ
    11. Пусть RUN ― константа, которая своим значением 1 или 0 указывает режим текущего прогона программы: счет или отладка. Используя средства условного ассемблирования, выписать фрагмент программы, в котором находится наибольший общий делитель двух положительных чисел с записью его в регистр AX, при условии, что в режиме счета эти два числа должны вводиться, а в режиме отладки эти числа равны 45 и 30.
    12. Имеются следующие описания макроса M X, который должен уменьшить значение X на 5, если X ― это переменная-байт, или на 12, если X ― это переменная-слово:
      • M MACRO X
        LOCAL L2,L
        CMP TYPE X,BYTE
        JNE L2
        SUB X,5
        JMP L
        L2: SUB X,12
        L:
        ENDM
      • M MACRO X
        LOCAL L2,L
        MOV AL,TYPE X
        CMP AL,BYTE
        JNE L2
        SUB X,5
        JMP L
        L2: SUB X,12
        L:
        ENDM
      • M MACRO X
        IF TYPE X EQ BYTE
        SUB X,5
        ELSE
        SUB X,12
        ENDIF
        ENDM
      Какое из этих описаний неправильно (и почему) и какое из двух правильных описаний лучше другого (и почему)?
    13. Описать в виде макроса SHIFT X, K (X ― имя байтовой переменной, K ― явно заданное число) сдвиг значения X на | K | разрядов вправо (при K > 0) или влево (при K < 0).
      Выписать макрорасширения для макрокоманд SHIFT A, -1 и SHIFT B, 5.
    14. Описать в виде макроса IF0 X, L (X ― переменная размером в байт, слово или двойное слово, L ― метка) переход на метку L в том случае, когда значение переменной X равно 0.
      Выписать макрорасширения для макрокоманд IF0 B, L1 и IF0 D, L2 при условии, что B ― переменная типа BYTE, а D ― типа DWORD.
    15. Описать в виде макроса SIGN X (X ― знаковая переменная размером в байт, слово или двойное слово) операцию засылки в регистр AL числа 1 при X > 0, числа 0 при X = 0 и числа -1 при X < 0.
      Выписать макрорасширения для макрокоманд SIGN W и SIGN D при условии, что W ― переменная размером в слово, а D ― размером в двойное слово.
    16. Описать в виде макроса NULL X, N, T (X ― имя массива из N байтов, N ― положительное целое число, T ― это FIRST или LAST) обнуление либо начального (при T = FIRST), либо последнего (при T = LAST) элемента массива X.
      Выписать макрорасширение для макрокоманды NULL A, 100, LAST.
    17. Описать в виде макроса VAR X, EQ, V определение байтовой переменной с именем X и начальным значением V, а если последний параметр не задан ― без начального значения (параметр EQ фиктивный).
      Выписать макрорасширения для макрокоманд:
      • VAR A = '*'
      • VAR B : <10 DUP(?)>
      • VAR C
      • VAR
    18. Описать в виде макроса NULL RS (RS ― это <R1, R2, ..., Rk>, где Ri ― имена регистров общего назначения, k &ge; 0) обнуление регистров Ri.
      Выписать макрорасширения для макрокоманд NULL <AL, BX, SI> и NULL <>.
    19. Описать макрос SUM R, где R ― имя (большими буквами) одного из 16-разрядных регистров общего назначения, для записи в R суммы значений всех остальных таких регистров.
    20. Описать в виде макроса OUTF без параметров вывод текущих значений флагов CF, OF, SF и ZF в виде четверки из 0 и 1, причем значения всех флагов и используемых макросом регистров должны быть сохранены.
      Воспользоваться командами PUSHF и POPF. В регистре флагов Flags указанным флагам соответствуют биты со следующими номерами (нумерация битов справа налево от 0): CF ― 0, OF ― 11, SF ― 7, ZF ― 6.
    21. Описать в виде макроса SL RS, OP (RS ― это <R1, R2, ..., Rk>, где Ri ― 16-разрядные регистры общего назначения, k > 0; OP ― это SAVE или LOAD) запись в стек (при OP = SAVE) или восстановление из стека (при OP = LOAD) регистров Ri.
      Выписать макрорасширение для макрокоманды SL <AX, CX, DI>, LOAD.
    22. Описать в виде макроса SUM X (X ― это <X1, X2, ..., Xk>, где Xi ― имена знаковых байтовых переменных, k > 0) вычисление суммы значений Xi и записи ее в регистр BX.
      Выписать макрорасширение для макрокоманды SUM <A, B, C>.
     
  8. Mikl___

    Mikl___ Супермодератор Команда форума

    Публикаций:
    14
    Регистрация:
    25 июн 2008
    Сообщения:
    3.709
    23. Описать в виде макроса MAX X (X ― это <X1, X2, ..., Xk>, где Xi ― имена знаковых байтовых переменных, k > 0) вычисление максимума Xi и записи его в регистр AL. (Обратить особое внимание на метки, которые будут появляться в макрорасширениях.)
    Выписать макрорасширение для макрокоманды MAX <A, B, A>.
    24. Предположим, что имеется процедура P от двух параметров, которые по значению передаются через регистры AX и BX. Описать в виде макроса CALL_P X,Y команды обращения к этой процедуре, которые должны сохранять регистры AX и BX и которые должны корректно работать, когда в качестве X и Y указаны AX и/или BX. (Считать, что названия регистров записываются только большими буквами.)
    Выписать макрорасширения для макрокоманд:
    • CALL_P Q,2
    • CALL_P AX,BX
    • CALL_P BX,5
    • CALL_P BX,AX
    25. Выписать при указанном макроопределении макрорасширение для указанной макрокоманды:
    макроопределениемакрокомандамакрорасширение
    MA MACRO K
    JMP M1
    MOV AX,K
    M&K: ADD AX,K
    ENDM
    MA 1
    MB MACRO K
    MOV AH,K
    IFIDN <AH>,<2>
    ADD AH,K
    ENDIF
    ENDM
    MB 2
    MV MACRO K
    A EQU K
    IFDIF <A>,<K>
    ADD DX,A&K
    ENDIF
    ENDM
    MV 3
    MG MACRO K
    IFDIF <K+1>,<5>
    MOV AX,K
    ENDIF
    MOV BX,K&K
    ENDM
    MG 4
    MD MACRO K
    IF K+1 EQ 6
    X DB '&K+1'
    ENDIF
    Y&K DW K+1
    ENDM
    MD 5
    ME MACRO K
    MOV AH,0
    IRPC C,K-1
    ADD AH,'&C'
    ENDM
    ENDM
    ME 6
     
    Последнее редактирование: 27 янв 2017
  9. Mikl___

    Mikl___ Супермодератор Команда форума

    Публикаций:
    14
    Регистрация:
    25 июн 2008
    Сообщения:
    3.709

    Специфические особенности языка ассемблер


    Павел Соколенко статья с сайта http://asm.shadrinsk.net/

    Нельзя сказать, что ассемблер относится к числу незаслуженно забытых языков программирования, тем не менее, негативное мнение о его возможностях и целесообразности применения получило весьма широкое распространение. Если такой точки зрения придерживаются начинающие программисты или "продвинутые пользователи", то это можно отнести к недостатку практических навыков и ограниченности профессиональных знаний. Но отрицательные высказывания, которые можно встретить в некоторых книгах и статьях, лично у меня вызывают недоумение. Более того, я считаю, что подобная неконструктивная критика любого языка просто вредна. Это обстоятельство и послужило поводом для написания данных заметок.

    Сравнивать язык команд с алгоритмическими языками имеет смысл только в отношении сложности и трудоемкости процесса программирования. Безусловно, при таком сравнении ассемблер уступает алгоритмическим языкам, но это не означает, что его не следует знать и уметь применять в случае необходимости.

    Как известно, универсального языка программирования пока не придумали и потребность в нем совсем не очевидна. Поэтому, для расширения сферы деятельности и повышения профессионального уровня, программист должен владеть несколькими языками (быть полиглотом) и желательно, чтобы одним из них был ассемблер. Ниже я попытаюсь обосновать эту точку зрения.

    Немного истории

    Ассемблер (assembly language, assembler) является машинно-ориентированным языком программирования. В переводе с английского слово assembly означает собрание, сборка, монтаж и т. п. Выбор такого названия объясняется тем, что первые трансляторы с Алгола, Фортрана, Кобола и PL/I использовали ассемблер в качестве промежуточного языка при сборке программы из отдельных модулей.

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

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

    С появлением персональных компьютеров (ПК) маятник качнулся в обратную сторону и, на некоторое время, ассемблер вновь оказался в числе лидеров. Это произошло потому, что ограниченные возможности первых моделей ПК не позволяли применять компиляторы с языков высокого уровня. Но технический прогресс постепенно все расставил по своим местам, и программисты вновь стали отдавать предпочтение алгоритмическим языкам.

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

    Специфические особенности языка

    Ассемблер является языком программирования низкого уровня, составленная на нем программа представляет собой последовательность команд конкретной ЭВМ, чаще конкретного семейства ЭВМ, записанных в некой условной мнемонической форме. Именно машинная ориентация определяет достоинства и недостатки этого языка.

    Неоспоримым достоинством ассемблера является возможность составления программ, рационально использующих все особенности системы команд конкретной ЭВМ. Он предоставляет неограниченные возможности для различного рода трюков (в хорошем смысле этого слова), тут все зависит от профессиональных навыков программиста и его изобретательности.

    Другим положительным свойством является универсальность языка, ― он позволяет составить программу для любой задачи, которая имеет решение и может быть решена на машинах данного семейства. Это утверждение основано на том очевидном факте, что любая программа, составленная на языке высокого уровня, при компиляции преобразуется в последовательность машинных команд.

    Очевидным недостатком является низкий уровень абстрагирования от особенностей конкретной ЭВМ, необходимость знать и учитывать эти особенности. В то время как при работе с языками высокого уровня программист может полностью сосредоточить свое внимание на особенностях реализуемого алгоритма.

    Другим недостатком является большое, а иногда и очень большое, количество команд, выполняемых конкретной машиной. Например, у микропроцессора Pentium 4 их около 500! Это вынуждает пользоваться при работе специальными справочниками, ― не держать же в голове все разнообразие имен команд и особенностей их выполнения.

    Ассемблер и макроассемблер

    С точки зрения программиста язык ассемблера является подмножеством языка макроассемблера. Последний обязательно компилирует полный набор инструкций конкретного семейства микропроцессоров и, кроме того, исполняет большой набор операторов и директив (далее макросредств) различного назначения. У программистов, работающих на компьютерах IBM PC, наибольшей популярностью пользуются макроассемблеры MASM (компании Microsoft) и TASM (компании Borland), имеющие примерно одинаковые возможности и во многом (но не полностью) совместимые. В данном разделе мы будем говорить об особенностях MASM.
     
    Последнее редактирование: 26 янв 2017
  10. Mikl___

    Mikl___ Супермодератор Команда форума

    Публикаций:
    14
    Регистрация:
    25 июн 2008
    Сообщения:
    3.709
    Прежде всего, следует отметить, что в чистом виде язык ассемблера применяется только при оформлении вставок в тексты программ, составляемых на языках высокого уровня. Без использования макросредств невозможно составить даже простую подпрограмму, не говоря о завершенных программах, а вот без использования команд ассемблера это возможно.

    При определенных условиях исходный текст программы, выполняющей достаточно сложные действия, может содержать только макросредства. В нем не будет ни одной команды ассемблера! О какой сложности и трудоемкости программирования можно говорить в подобных случаях?

    В руководствах по MASM, в том числе и в HELP, обычно описываются 8 типов операторов и 12 типов директив, начиная с версии MASM 6.10, их количество не изменялось. В рамках данной статьи не имеет смысла приводить полное описание макросредств, поэтому мы ограничимся их общей характеристикой.

    Директивы макроассемблера предназначены для выполнения трех основных категорий действий:
    1. управление распределением оперативной памяти выполняют директивы, предназначенные для описания простых переменных, структур данных, записей и сегментирования программ;
    2. управление работой с внешними и внутренними подпрограммами, фрагментами программ и макроопределениями выполняют директивы их явного или предварительного описания и вызова;
    3. управление процессом компиляции программы выполняют директивы условной компиляции, среди них встречаются такие знакомые каждому программисту имена, как IF, ELSE, FOR, WHILE, REPEAT, GOTO.
    Независимо от конкретного назначения все директивы и операторы исполняются на стадии компиляции, при этом сами они не преобразуются в команды. В результате их исполнения в объектный модуль включаются или не включаются группы команд, находящиеся в исходном тексте программы или во внешних библиотеках. Кроме того, в объектный модуль включаются данные, необходимые компоновщику (LINK) для сборки и построения задачи.

    Таким образом, при обсуждении достоинств и недостатков программирования на ассемблере не следует забывать, что на практике мы, как правило, работаем с языком макроассемблера, а это далеко не одно и тоже.

    Техника программирования

    Ахиллесовой пятой ассемблера является трудоемкость программирования на этом языке. Что может сделать программист для упрощения собственного труда?

    Единственным возможным решением является накопление и использование собственного и чужого опыта, оформленного в виде библиотек или отдельных модулей, желательно с критической оценкой этого опыта. Макроассемблер позволяет применять при разработке задачи готовые исходные и объектные модули. Первые включаются в исходный текст программы с помощью директивы Include, а вторые подключаются к списку объектных модулей на стадии построения задачи компоновщиком. Есть разные источники объектных и исходных модулей, одни из них более доступны, а другие менее. Выбор зависит от ваших возможностей.
    1. В состав полной версии системы программирования на макроассемблере (дистрибутивного пакета) входит набор макроопределений и подпрограмм различного назначения и примеры их использования. Внимательно просмотрите все подкаталоги вашей версии макроассемблера, наверняка в них найдутся полезные и интересные решения, которые можно применять в вашей работе.
    2. Не забывайте о том, что при составлении программ можно использовать модули библиотек, входящих в состав систем программирования на языках высокого уровня, например, на Си. Правда, для этого надо иметь описание этих библиотек, но без него невозможно их использование и в рамках той системы программирования, для которой они созданы.
    3. Если у вас есть доступ к сети Internet, то на разных ее сайтах можно найти множество готовых решений буквально на все случаи жизни. Только обращайте внимание на даты разработки программ и подпрограмм. Многие из них морально устарели и при использовании новых инструкций микропроцессоров могут быть существенно упрощены.
    4. В процессе работы с ассемблером у вас постепенно будет накапливаться набор собственных решений, которые пригодятся в новых разработках. Для удобства их последующего использования старайтесь при написании программ составлять как можно больше подпрограмм, оформленных в виде отдельных модулей, лучше исходных, а не объектных ― первые проще модифицировать в случае необходимости. Применение подпрограмм несколько увеличивает размеры задачи и замедляет процесс ее выполнения. Но взамен вы получаете возможность отлаживать большую программу по частям и использовать отлаженные подпрограммы в других своих разработках.
    5. Не забывайте о существовании такого эффективного средства, как макросы ― макроопределения и макровызовы. Они позволяют сокращать не только исходный текст программы, но и количество возможных ошибок при его наборе. Кроме того, макровызовы удобно применять при запросе системных функций, которые выполняют много полезных действий таких, как работа с окнами, с файловой системой, ввод и вывод данных и пр.
    Таким образом, при наличии опыта и практических навыков, затраты труда при программирования на ассемблере можно свести к такому уровню, при котором окажется возможной разработка достаточно больших проектов. Безусловно, прежде чем приступать к их реализации надо взвесить все доводы за и против и учесть свои реальные возможности.

    Когда ассемблеру нет альтернативы

    Традиционно ассемблер применяется для написания подпрограмм и вставок в программы, составленные на алгоритмических языках. За последние несколько лет целесообразность такого его применения не только увеличилась, но и приобрела новый смысл. Это объясняется тем, что задачи, получаемые при программировании на алгоритмических языках, крайне неэффективно используют ресурсы современных микропроцессоров. Разумеется, в этом виноваты не языки, а компиляторы, которые не поддерживают работу с групповыми операциями и не учитывают особенности конвейерной обработки. Все старания инженеров и проектировщиков, направленные на повышение производительности микропроцессоров оказываются бесполезными, как и ваши финансовые затраты на приобретение нового оборудования.
     
    Последнее редактирование: 26 янв 2017
  11. Mikl___

    Mikl___ Супермодератор Команда форума

    Публикаций:
    14
    Регистрация:
    25 июн 2008
    Сообщения:
    3.709
    Обвинять в этом разработчиков компиляторов не имеет смысла, ― проблема не столь тривиальна, и для ее решения нужно не только улучшать технику компиляции, но и разрабатывать принципиально новые языковые средства. Мы привыкли к тому, что арифметические и логические операции выполняются над одной парой операндов, иногда над одним операндом (смена знака или отрицание). А групповые операции современных микропроцессоров (не только семейства Pentium) могут оперировать сразу с несколькими парами операндов, количество и разрядность которых не фиксированы.

    В этой ситуации альтернативы ассемблеру просто не существует. Недаром в технической документации к компилятору "Intel C++ Compiler" рекомендуются три варианта работы с новыми командами:
    1. прямые вставки ассемблерного текста в тело программы, написанной на Си;
    2. составление процедур (подпрограмм) на языке ассемблера;
    3. использование Intrinsic (встроенные функции).
    Последние являются альтернативной формой записи групповых операций и, по существу, не упрощают (скорее усложняют) задачу программиста.

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

    Использовать преимущества конвейерной обработки не так просто, как это может показаться. При составлении программы, неважно на каком языке, мы исходим из предположения о том, что машина сначала выполняет одну команду, и только потом приступает к выполнению другой. На самом деле современные процессоры уже давно так не работают, ― они пытаются полностью или частично совместить выполнение нескольких команд. Очевидно, что обычные задачи не приспособлены для этого и при их выполнении производительность процессора не соответствует его реальным возможностям.

    В связи с этим у старой проблемы оптимизации появились новые аспекты: надо выявлять и изменять те места задачи, в которых производительность процессора занижена, желательно исключить из программы или свести к минимуму условные ветвления (например, по результату сравнения операндов) и так далее. Решать подобные проблемы в процессе компиляции программы, составленной на алгоритмическом языке пока невозможно, и мы снова обращаемся к ассемблеру.

    Компания Intel разработала и продает специальный анализатор производительности микропроцессора в процессе выполнения конкретной задачи (VTuneTM Performance Analyzer 6.0). Он позволяет выявить в ней слабые места (критические участки кода задачи), и затем исправить их с помощью ассемблерных вставок.

    Подробнее о новых возможностях микропроцессоров вы можете прочитать в моей статье "Pentium глазами программиста"[1]. В Internet она опубликована на странице http://www.macro.aaanet.ru/apnd_4.html

    Резюме

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

    Если вы собираетесь расширять свой кругозор, то вспомните высказывание Э. Дейкстры о влиянии языка на способ мышления. Осваивая только алгоритмические языки, вы привыкаете думать при программировании формальными категориями, основанными на синтаксисе и семантике. Я не считаю что это плохо, скорее наоборот хорошо, но несколько однобоко. В конце концов, мы имеем дело не с виртуальной, а с реальной техникой и желательно представлять, как она работает. В этом случае вам будет проще понять что можно, а чего и почему нельзя сделать с помощью средств того или иного алгоритмического языка.

    Чтобы оставаться на острие технического прогресса и уметь создавать задачи, рационально использующие возможности самых современных компьютеров, надо в совершенстве владеть искусством программирования ЭВМ. Овладеть этим искусством без знания языка ассемблера, на мой взгляд, невозможно.
     
    Последнее редактирование: 26 янв 2017
  12. Mikl___

    Mikl___ Супермодератор Команда форума

    Публикаций:
    14
    Регистрация:
    25 июн 2008
    Сообщения:
    3.709

    Такие разные макросы

    Павел Соколенко статья с сайта http://asm.shadrinsk.net/


    Введение

    Makros (макрос) ― греческое слово, обозначающее "большой" или "длинный". В Кратком оксфордском словаре английского языка сказано, что от него происходит префикс macro. В литературе по программированию macro обычно употребляется как синоним слова "макрокоманда".

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

    Макросы являются полезным и универсальным средством широкого назначения, упрощающим программирование. Тем не менее, в практике программирования они применяются не так часто, как того заслуживают. В основном их разрабатывают, распространяют и используют в своих программах специалисты высокой квалификации. Возможно, это объясняется отсутствием специальной литературы по программированию макросов, или ограниченным доступом к такой литературе.

    Цель данной статьи обратить внимание программистов, работающих с ассемблером, на существование специальных средств, предназначенных для составления макросов и показать примеры их применения. Для проверки приведенных примеров версия MASM (Microsoft Macro Assembler) должна быть не ниже чем 6.0.

    Общие положения

    Макросы делятся на макроопределения (macro definition или просто macro) и макровызовы (macro instruction или macrocode).

    Признаком макроопределения является директива MACRO, записанная в его первой строке. Перед ней обязательно указывается имя макроса, а после нее ― список параметров, если они используются. В последующих строках располагается тело макроопределения, структура которого зависит от назначения макроса. В общем случае тело содержит директивы MASM и ассемблерные команды или их заготовки. В заготовках команд вместо имен операндов или операций указываются имена параметров макроопределения. Тело макроса обязательно заканчивается директивой ENDM.

    Признаком макровызова является имя макроопределения и список параметров, если они используются. В процессе компиляции, обнаружив любое имя, MASM ищет его в таблицах имен, описанных в компилируемой программе и в своих таблицах, зарезервированных имен. Если имя соответствует макроопределению, то выполняется макроподстановка (macro substitution). При этом фрагменты ассемблерного кода, содержащиеся в теле макроопределения, включаются в текст программы, сразу компилируются и при отсутствии ошибок передаются в формируемый объектный модуль. В таких случаях макроподстановку иногда называют макрорасширением (macro expansion). Если же тело макроопределения не содержит ассемблерного кода, то в тексте программы и в объектном коде не остается никаких следов от его выполнения (пример 6 данной статьи).

    По аналогии с подпрограммами можно считать, что в макроопределении описаны формальные параметры, а в макровызове ― фактические. Как обычно, должно соблюдаться строгое соответствие количества, типов и последовательности перечисления формальных и фактических параметров. Параметры, отсутствующие в макровызове, заменяются двумя подряд расположенными запятыми. Фактический параметр может отсутствовать, только если это предусмотрено при разработке макроопределения, т. е. перед использованием параметра проверяется, определен он или нет (указан или отсутствует).

    По функциональному назначению макроопределения можно разделить на две основные категории:
    1. средства для упрощения программирования на ассемблере;
    2. средства для расширения возможностей компилятора.
    Принципиальное различие между ними заключается в следующем. Вызов макросов первой категории можно заменить группой команд, получаемых в результате подстановки фактических параметров. Макросы второй группы ничем заменить нельзя, поскольку они формируют объектные коды таких команд, который не обрабатывает конкретная версия компилятора.

    Макроопределения первой категории чаше всего применяются для упрощения программирования запросов функций, выполняемых операционными системами и базовой системой ввода-вывода (BIOS). Разработчики MASM включают в состав дистрибутивных пакетов файлы, содержащие такие макроопределения, их можно (и нужно) использовать в прикладных программах различного назначения.

    В дистрибутивном пакете MASM 6.0 имеется каталог INCLUDE, в котором собраны файлы с макроопределениями различного назначения. Например, файлы bios.inc и dos.inc содержат макроопределения запросов функций BIOS и DOS, соответственно, а файл macros.inc содержит макросы, выполняющие различные вспомогательные действия, в том числе и обработку строк текста. В том же каталоге находятся файлы с другими макроопределениями.

    В дистрибутивном пакете MASM32 имеется каталог MACROS, в котором находятся файлы usemacro.inc и macros.asm, содержащие макроопределениями запросов функций, выполняемых 32-x разрядными операционными системами семейства WINDOWS. Если запросы функций BIOS и DOS программируются сравнительно просто, то программировать запросы системных функций WIDOWS, без использования готовых макроопределений просто нецелесообразно.

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

    Макросредства, предназначенные для расширения возможностей компилятора, стали использоваться сравнительно недавно. Необходимость в них возникла потому, что система команд микропроцессоров семейства Pentium развивалась слишком быстро. Появлялись десятки и сотни принципиально новых команд, которые не могли компилировать существующие версии MASM. В этой ситуации сами разработчики стали публиковать макроопределения, позволяющие использовать новые команды при работе с устаревшими версиями MASM.

    В заключение данного раздела следует отметить, что разработка макроопределений имеет мало общего с программированием на языке ассемблера. В этом случае программист решает совершенно другие задачи и использует другие средства. В двух следующих разделах данной статьи описаны некоторые приемы, применяемые при разработке макросов различного назначения.

    Макросы для работы со стеком

    Во многих руководствах встречаются примеры макроопределений сохраняющих в стеке и восстанавливающих из стека содержимое регистров общего назначения. Они позволяют заменить в тексте программы группы команд push и pop списками имен операндов. Нельзя сказать, что эти макросы представляют большую практическую ценность, но на их примере удобно показать некоторые приемы разработки макросов. В частности, в данном разделе описаны разные варианты организации циклов, описания формальных параметров и нестандартное использование стека.

    Исходный вариант макроопределений, выполняющих сохранение в стеке и восстановление из стека параметров команд общего назначения, приведен в примере 1. Тексты макросов различаются только одной строкой. У PushArg в ней записана заготовка команды push par, а у PopArg ― заготовка команды pop par.

    Пример 1. Макроопределения PushArg и PopArg
    Код (ASM):
    1. ;    Сохранение аргументов в стеке
    2. PushArg    macro    arg    ; заголовок макроопределения
    3. local    par    ; описание локальной переменной par
    4. ;    цикл формирования последовательности команд push arg[I]
    5. irp par,; par = arg[I] - имя очередного аргумента
    6. push    par    ; push arg[I] - заготовка формируемой команды push
    7. endm    ; конец цикла (действия директивы irp)
    8. endm    ; конец макроопределения
    9. ;    Выборка аргументов из стека
    10. PopArg    macro    arg    ; заголовок макроопределения
    11. local    par    ; описание локальной переменной par
    12. ;    цикл формирования последовательности команд pop arg[I]
    13. irp    par,; par = arg[I] - имя очередного аргумента
    14. pop    par    ; pop arg[I] - заготовка формируемой команды pop
    15. endm    ; конец цикла (действия директивы irp)
    16. endm    ; конец макроопределения
    Описание макроопределения открывает директива MACRO, в данном случае после нее указан один параметр arg, но он является списком имен операндов команд push или pop. Завершает макроопределение директива ENDM, кроме того, она используется для указания конца директив IRP.

    Описание local par вводит локальную символьную переменную par. Оно не является обязательным и применяется только для указания локального характера переменных, используемых в данном макросе. Если такое указание не требуется, то описание переменных можно опустить.

    Директива IRP является заголовком тела цикла, а завершает его директива ENDM. При каждом повторе цикла символьной переменной par присваивается имя очередного операнда из списка arg. При отсутствии ошибок цикл повторяется до окончания списка. Запись означает, что arg является параметром макроопределения.

    Тело цикла может быть достаточно сложным, но в данных примерах оно состоит только из одной команды. Макроассемблер подставляет в эту команду конкретное имя из переменной par и компилирует полученный результат. Если при компиляции будет обнаружено несуществующее имя операнда команды, то выполнение всего макроопределения прерывается с выдачей аварийного сообщения. В случае нормального завершения цикла формируется ровно столько команд push или pop, сколько имен было указано в списке фактических параметров макровызова.

    Макровызовы и параметры. При обработке макроопределения MASM ограничивается общей проверкой структуры его текста, а процесс компиляции (исполнения макроса) производится только при его вызове. Для использования макроопределений в нужные места исходного текста программы включаются их имена и заключенные в угловые скобки списки имен операндов, содержимое которых сохраняется в стеке или восстанавливается из стека. Например:
     
    Последнее редактирование: 26 янв 2017
  13. Mikl___

    Mikl___ Супермодератор Команда форума

    Публикаций:
    14
    Регистрация:
    25 июн 2008
    Сообщения:
    3.709
    Пример 2. Варианты макровызовов определений примера 1
    Код (ASM):
    1. PushArg    ; сохранение содержимого регистров
    2. PopArg    ; восстановление содержимого регистров

    Несколько замечаний относительно списка параметров:


    • работа со стеком организована по принципу "первым пришел, последним ушел", поэтому для восстановления сохраненных значений параметров их имена должны располагаться в списках PushArg и PopArg в противоположном порядке;
    • это не мешает вам при выталкивании из стека сознательно изменить последовательность параметров или указать другие имена;
    • в списках можно указывать имена любых операндов, использование которых допускают команды push и pop, а не только регистров общего назначения, чтобы подчеркнуть это обстоятельство, макросы имеют имена PushArg и PopArg;
    • для сохранения содержимого сразу всех регистров общего назначения, лучше использовать не макросы, а команды pusha и popa, это сократит не только исходный текст, но и объектный модуль и несколько ускорит выполнение задачи.

    Изменения макроопределений

    Текст примера 1 достаточно прост и вполне корректен, тем не менее, в него можно внести некоторые полезные изменения.

    1. Директива irp морально устарела, начиная с MASM 6.0, появилась ее более привычная запись FOR <тело цикла> ENDM. Директива for обладает некоторыми преимуществами перед irp, но в данном случае их действия одинаковы.

      Для использования новой директивы в обоих вариантах примера 1 замените строку irp par, на строку for par, . Логика выполнения макроопределений при такой замене не изменяется.
      Замечание Существуют директивы irpc и forc. Они отличаются от irp и for тем, что при каждом повторе цикла из строки выбирается только один символ.
    2. В макровызовах примера 2 список имен операндов заключается в угловые скобки. Если вы хотите избавиться от них, то надо внести небольшое изменение в текст примера 1.

      Параметры макроопределений могут иметь атрибуты. В данном случае нас интересует только один из них, а именно VARARG. Он указывает на то, что параметр является списком имен, отделенных друг от друга запятыми. Для использования этого атрибута первые строки обоих вариантов примера 1 надо записать так:
      Код (ASM):
      1. PushArg    macro    arg:vararg    ; заголовок макроопределения PushArg
      2. PopArg    macro    arg:vararg    ; заголовок макроопределения PopArg
      Теперь при записи макровызовов угловые скобки не нужны, но если вы их укажите, то ошибки не будет.
      Замечание Признаком конца списка, специфицированного как VARARG, является конец строки или точка с запятой. Поэтому только один, причем последний, параметр макроопределения может быть описан как VARARG.

    3. Можно так изменить текст макроопределения PushArg, что при вызове PopArg не потребуется указание списка операндов. Для этого надо запомнить имена сохраненных в стеке операндов, а затем использовать их при восстановлении из стека.

    Полный текст соответствующего примера находится в файле macros.inc в каталоге Include дистрибутивного пакета MASM версии 6.0. Он состоит из трех макроопределений @SaveRegs, @RestoreRegs, @ArgRev и описания глобальной текстовой переменной pregs (имена взяты из файла macros.inc).

    Макроопределение @SaveRegs отличается от PushArg только тем, что перед формированием команд push список фактических параметров, макровызова копируется в переменную pregs с помощью строковой директивы CATSTR (см ниже).

    Макроопределение @RestoreRegs вызывается без параметров. При его выполнении используется вспомогательное макроопределение @ArgRev. Оно инвертирует список параметров, сохраненный в переменной pregs, и передает его макросу @RestoreRegs, для формирования группы команд pop. Способ инверсии списка параметров показан в примере 6.

    Работа с новыми регистрами. У микропроцессоров семейства Pentium появилось сначала восемь 64-х разрядных регистров, имеющих имена от MM0 до MM7, а затем еще восемь 128-ми разрядных регистров с именами от XMM0 до XMM7. Они доступны только новым инструкциям групп MMX, SSE1 и SSE2. Инструкциям общего назначения, к которым относятся push и pop, новые регистры недоступны. Поэтому при работе со стеком пересылка данных выполняется с помощью новых инструкций.

    В примере 3 приведены макроопределения для сохранения и восстановления содержимого регистров mmx. Вместо push и pop в текст программы вставляются две команды. Одна из них корректирует указатель стека на размер операнда (8 байтов), а другая выполняет пересылку из регистра в стек или в обратном направлении. Для пересылки 64-х разрядных операндов, вместо обычной команды mov используется MOVQ из группы mmx.

    Пример 3. Макроопределения PushMmx и PopMmx
    Код (ASM):
    1. ;    Сохранение в стеке содержимого регистров mmx
    2. PushMmx    macro    regs64:vararg    ; заголовок макроопределения
    3. ;    цикл формирования последовательности команд
    4. for    par, ; par=regs64[i] очередное имя регистра
    5. sub    esp, 8    ; команда коррекции указателя стека movq    [esp], par    ; заготовка команды пересылки в стек
    6. endm    ; конец действия директивы for
    7. endm    ; конец макроопределения
    8. ;    Восстановление из стека содержимого регистров mmx
    9. PopMmx    macro    regs64:vararg    ; заголовок макроопределения
    10. ;цикл формирования последовательности команд
    11. for    par, ; par=regs64[i] очередное имя регистра
    12. movq    par, [esp]    ; заготовка команды пересылки из стека
    13. add    esp, 8    ; команда коррекции указателя стека
    14. endm    ; конец действия директивы for
    15. endm    ; конец макроопределения
    В примере 3 циклы организованы с помощью директивы for, а список параметров специфицирован как vararg, для исключения угловых скобок, ограничивающих список операндов макровызова. Вариант макровызовов показан в примере 4.

    Пример 4. Вызовы макроопределений примера 3
    Код (ASM):
    1. PushMmx    mm0, mm1, mm2 ; сохранение содержимого трех регистров
    2. PopMmx    mm2, mm1, mm0 ; восстановление содержимого трех регистров
    Обращаем ваше внимание на то, что в данном случае список макровызова может содержать только имена 64-х разрядных регистров, но не переменных.

    128-ми разрядные регистры. При сохранении или восстановлении содержимого 128-ми разрядных регистров xmm указатель стека должен изменяться на 16 байтов, а для пересылки данных используется команда MOVUPS. Она входит в группу инструкций SSE1 и пересылает не выровненные данные, расположенные в памяти начиная с любого адреса. Для получения новых макроопределений надо переименовать макросы примера 3, а тела циклов для сохранения и выборки записать так:
    Код (ASM):
    1. ;при сохранении в стеке содержимого регистров xmm
    2. sub    esp, 16    ; команда коррекции указателя стека
    3. movups    [esp], par    ; заготовка команды пересылки в стек
    4. ; при восстановлении из стека содержимого регистров xmm
    5. movups    par, [esp]    ; заготовка команды пересылки из стека
    6. add    esp, 16    ; команда коррекции указателя стека
    MASM 6.0 не компилирует новые инструкции микропроцессоров Pentium. Поэтому при работе с макросами, приведенными в примерах 3 и 4, надо либо расширять возможности MASM 6.0 с помощью макроопределений, выполняющих компиляцию новых инструкций, либо использовать версию MASM32 6.14.

    Стек числовых регистров. В состав всех микропроцессоров семейства Pentium входит специализированный процессор, выполняющий вычисления с вещественными числами (FPU). Исполняемые им инструкции работают со специальными регистрами, сгруппированными в стек и имеющими имена от st(0) до st(7). Регистр st(0) или просто st находится в верхушке стека и обязательно участвует во всех операциях. Числовые регистры недоступны командам push и pop, поэтому для обмена данными со стеком общего назначения используются инструкции FPU.

    Копирование в память содержимого верхушки числового стека, и освобождение числового регистра (перемещение верхушки) выполняет инструкция fstp. Обратное действие, т. е. выделение нового регистра (перемещение верхушки числового стека) и запись в него содержимого памяти выполняет инструкция fld. В зависимости от размера операнда обе инструкции пересылают 32-х или 64-х разрядные вещественные числа (обычная или двойная точность).

    В примере 5 приведены макроопределения для сохранения и восстановления заданного количества числовых регистров (от 1 до 8).

    Пример 5. Макроопределения PushFlt и PopFtt
    Код (ASM):
    1. ; Сохранение в стеке содержимого числовых регистров
    2. PushFltr macro num    ; заголовок макроопределения
    3. repeat num    ; начало цикла формирования команд
    4. sub esp, 8    ; команда коррекции указателя стека
    5. fstp qword ptr [esp]    ; команда копирования содержимого st в стек
    6. endm    ; конец директивы repeat
    7. endm    ; конец макроопределения
    8. ; Восстановление из стека содержимого числовых регистров
    9. PopFltr    macro num    ; заголовок макроопределения
    10. repeat num    ; начало цикла формирования команд
    11. fld qword ptr [esp]    ; команда копирования из стека в регистр st
    12. add esp, 8    ; команда коррекции указателя стека
    13. endm    ; конец директивы repeat
    14. endm    ; конец макроопределения
     
    Последнее редактирование: 26 янв 2017
  14. Mikl___

    Mikl___ Супермодератор Команда форума

    Публикаций:
    14
    Регистрация:
    25 июн 2008
    Сообщения:
    3.709
    В данном случае невозможно указать список имен числовых регистров, поэтому в примере 5 для организации циклов формирования команд использована директива REPEAT. При каждом выполнении цикла исходное значение num уменьшается на 1, если результат отличен от 0, то цикл повторяется.
    Тело цикла состоит из двух команд и заканчивается директивой ENDM. В командах fstp и fld явно указывается размер операнда (qword ptr). В отличие от предыдущих примеров, в данном случае используются готовые команды, не требующие настройки.

    Количество освобождаемых при сохранении или занимаемых при восстановлении числовых регистров указывается в макровызове, например PushFltr 4, или PopFltr 4.

    Инверсия списка параметров. В некоторых случаях может возникнуть необходимость перед записью в стек изменить порядок параметров на противоположный тому, в котором они перечислены в списке макровызова. Например, при компиляции директивы INVOKE, MASM анализирует язык, на котором составлена основная программа (Си, Бейсик, Фортран или Паскаль) и, в зависимости от этого, выбирает прямой или обратный способ записи в стек параметров подпрограммы.

    Список параметров является строкой текста, а для манипуляций со строками в MASM 6.0 были введены следующие четыре директивы:
    • CatStr ― объединение строк;
    • InStr ― определение позиции символа в строке;
    • SizeStr ― определение количества символов в строке;
    • SubStr ― выделение подстроки.
    Их можно использовать и как операторы в символьных выражениях, в таком случае перед именем указывается символ @ ― @CatStr, @InStr, @SizeStr, @SubStr.

    Рассмотрим, способ преобразования списка параметров с помощью текстовых директив. Текст соответствующего макроопределения приведен в примере 6. В этом же примере показан способ возвращения результата вызывающему макросу.

    Пример 6. Инверсия списка параметров
    Код (ASM):
    1. InvList    MACRO arglist    ; заголовок макроопределения
    2. LOCAL txt, arg    ; описание локальных переменных
    3. txt TEXTEQU <>    ; очистка текстовой переменной
    4. %    FOR arg, arglist; начало цикла перестановки параметров
    5. txt CATSTR , , txt; запись в txt очередного параметра и запятой
    6. ENDM    ; конец цикла перестановки параметров
    7. txt SUBSTR txt, 1, @SizeStr(%txt) - 1; удаление последней запятой в txt
    8. txt CATSTR , txt, > ; окружение содержимого txt угловыми скобками
    9. EXITM txt    ; завершение выполнения с передачей имени txt
    10. ENDM    ; конец макроопределения
    Обратите внимание на то, что в тексте примера 6 нет ни одной ассемблерной команды, следовательно, при его вызове текст программы не изменяется. На основе указанного при вызове списка параметров, макроопределение примера 6 формирует в переменной txt инвертированный список. Предположим, что в качестве параметра arglist передается следующий текст: , после выполнения макроса переменная txt будет содержать инвертированный список, а именно: .

    В исходе строка txt пустая. При каждом повторе цикла перестановки строковая директива CATSTR добавляет в ее начало имя очередного элемента списка arglist и запятую после него. После окончания цикла перестановок в конце строки txt окажется лишняя запятая, которую надо исключить.

    После выхода из цикла директива SUBSTR укорачивает размер строки txt на один символ и таким способом исключает последнюю запятую. В списке этой директивы использован оператор @SizeStr, который возвращает исходный размер строки (количество символов в строке).

    Последняя директива CATSTR добавляет угловые скобки в начало и в конец строки txt. Директива EXITM вызывает принудительное прекращение компиляции макроса, независимо от того закончился его текст или нет. После нее (в той же строке) можно указать имя выходного параметра. В данном случае это переменная txt, содержащая результат выполнения макроса.

    Покажем, применение описанного макроса для записи параметров в стек в обратном порядке. Текст соответствующего макроопределения приведен в примере 7. Его основное отличие от примера 1 заключается в том, что в заголовке цикла вместо исходного списка сохраняемых параметров записан макровызов определения примера 6. В процессе компиляции MASM сначала исполняет макрос InvList, преобразующий исходный список параметров, а затем формирует последовательность команд push.

    Пример 7. Запись параметров в стек в обратном порядке
    Код (ASM):
    1. PushArg    MACRO argum:VARARG    ; заголовок макроопределения
    2. local prm    ; описание локальной переменной
    3. %    FOR prm, InvList (<>); заголовок цикла с вызовом InvList
    4. push prm    ; заготовка формируемой команды
    5. ENDM    ; конец действия директивы for
    6. ENDM    ; конец макроопределения
    Пусть вас не смущает обилие скобок при вызове InvList. Круглые скобки ограничивают список параметров, в данном случае он состоит из одного элемента. Две угловые скобки нужны потому, что argum является параметром внешнего макроопределения (PushArg), который специфицирован как vararg.

    Замечание

    В примерах 6 и 7 перед директивой for указан знак процента (%). Обычно он означает, что текст интерпретируется как выражение.


    Таким образом, начав с простого макроса примера 1, мы пришли к достаточно сложному и элегантному примеру 7. Он, является наглядной иллюстрацией того, что макросы могут выполнять не только простую подстановку имен параметров в заготовку команд, но и намного более сложные действия.

    Макросы для компиляции команд

    За последние несколько лет сменилось три поколения микропроцессоров семейства Pentium. У процессоров каждого поколения и у разных моделей одного поколения появлялось достаточно много новых инструкций. Например, по сравнению с Pentium III, у Pentium 4 прибавилось сразу 144 инструкции!

    Компания Intel заблаговременно информирует разработчиков программного обеспечения о своих планах, поэтому Microsoft достаточно оперативно реагирует на технические нововведения и выпускает новые версии компиляторов. Если по каким то причинам последняя версия MASM вам недоступна, то возможности устаревших версий можно расширить с помощью макроопределений новых команд.

    Такие макроопределения преобразуют исходную мнемоническую запись команды на языке ассемблера в объектный код. При этом они выполняют синтаксический разбор записи команды, сложность которого зависит от используемых способов адресации операндов. Мы начнем с рассмотрения простых случаев, а затем перейдем к сложным.

    Команда без операндов. Во многих руководствах встречается макроопределение для компиляции инструкции CPUID, которую выполняют все модели микропроцессоров семейства Pentium. Ее могут компилировать только версии MASM, исполняющие директиву .586, в противном случае надо использовать макрос, приведенный в примере 8.

    Пример 8. Компиляция инструкции cpuid
    Код (ASM):
    1. CPUID    MACRO    ; заголовок макроопределения
    2. db 0Fh, 0A2h    ; объектный код инструкции cpuid
    3. ENDM    ; конец макроопределения
    Макровызовом этого определения является запись в исходном тексте программы команды cpuid, вместо нее в текст программы будет включена директива db 0Fh, 0A2h.

    Команда имеет операнды. Подходящих для наших целей одноадресных команд нет, поэтому выберем такую группу двухадресных команд, операнды которых адресуются наиболее просто.

    У микропроцессора Pentium Pro появились две группы команд условной пересылки CMOVcc и FCMOVcc. Первая группа относится к категории команд общего назначения. Инструкции второй группы исполняет процессор FPU, они применяются при программировании вычислений с плавающей точкой. В записи конкретной команды вместо маленьких букв cc подставляется мнемоническое обозначение условия, например, CMOVNE или FCMOVB.

    Компилировать инструкции этих групп могут только версии MASM, начиная с 6.12 (исполняющие директиву .686), в противном случае надо применять специальные макроопределения. Мы опишем макроопределения для компиляции инструкций группы FCMOVcc. Операнды инструкций этой группы могут находиться только в числовых регистрах, что упрощает анализ и преобразование их имен.

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

    В примере 9 приведены текст мета-макро определения FPIDEF и список его вызовов, содержащий имена восьми инструкций группы FCMOVcc с соответствующими им кодами операций. При вызове FPIDEF формируется и исполняется одно из 8-ми обычных макроопределений, имена которых совпадают с именем компилируемой инструкции, а параметрами являются ее (инструкции) операнды.
     
    Последнее редактирование: 26 янв 2017
  15. Mikl___

    Mikl___ Супермодератор Команда форума

    Публикаций:
    14
    Регистрация:
    25 июн 2008
    Сообщения:
    3.709
    В данном случае невозможно указать список имен числовых регистров, поэтому в примере 5 для организации циклов формирования команд использована директива REPEAT. При каждом выполнении цикла исходное значение num уменьшается на 1, если результат отличен от 0, то цикл повторяется. В примере 9 приведены текст мета-макро определения FPIDEF и список его вызовов, содержащий имена восьми инструкций группы FCMOVcc с соответствующими им кодами операций. При вызове FPIDEF формируется и исполняется одно из 8-ми обычных макроопределений, имена которых совпадают с именем компилируемой инструкции, а параметрами являются ее (инструкции) операнды.

    Пример 9. Мета-макро определения инструкций FCMOVcc
    Код (ASM):
    1. FPIDEF    MACRO OPNAME, OPCODE1, OPCODE2 ; мета-макроопределение
    2. OPNAME MACRO OP1, OP2 ; заготовка основного определения
    3. ;    Проверка имени первого операнда
    4. LOCAL fvsym, reg ; описание локальных переменных
    5. IFDIFI    , IFDIFI , ; сравнение операндов
    6. .ERR1 %OUT 'Error: First operand must be st'
    7. ENDIF ; конец тела второй директивы IFDIFI
    8. ENDIF    ; конец тела первой директивы IFDIFI
    9. ; Проверка имени второго операнда
    10. reg SUBSTR , 4, 1 ; выделение номера регистра
    11. fvsym    SUBSTR ,1+reg*5,5 ; выбор эталона
    12. IFDIFI fvsym, ; сравнение OP2 с эталоном имени
    13. .ERR2
    14. %OUT 'Error: Second operand must be st(i)'
    15. ENDIF    ; конец тела директивы IFDIFI
    16. ;    Запись кода команды в объектный модуль
    17. db    opcode1, opcode2+reg
    18. ENDM    ; конец макроопределения opname
    19. ENDM ; конец макроопределения fpidef
    20. ; Список вызовов макроопределения FPIDEF
    21. FPIDEF FCMOVB,    0DAh, 0C0h    ; пересылка если меньше
    22. FPIDEF FCMOVE,    0DAh, 0C8h    ; пересылка если равно
    23. FPIDEF FCMOVBE,    0DAh, 0D0h    ; пересылка если меньше или равно
    24. FPIDEF FCMOVU,    0DAh, 0D8h    ; пересылка если неупорядочены
    25. FPIDEF FCMOVNB,    0DBh, 0C0h    ; пересылка если больше или равно
    26. FPIDEF FCMOVNE,    0DBh, 0C8h    ; пересылка если не равно
    27. FPIDEF FCMOVNBE,0DBh, 0D0h    ; пересылка если больше
    28. FPIDEF FCMOVNU,    0DBh, 0D8h    ; пересылка если упорядочены
    Предположим, что в тексте программы встретилась команда "fcmove st, st(4)". MASM ищет любые имена в своих списках зарезервированных символов и в таблицах имен, описанных в компилируемой программе. Обнаружив имя fcmove в списке, приведенном в конце текста примера 9, он формирует макровызов "fpidef fcmove, 0DAh, 0C8h". Макрос fpidef, в свою очередь, вместо OPNAME формирует и исполняет макроопределение "fcmove st, st(4)", которое компилирует команду и вставляет в текст программы директиву "db 0DAh, 0CCh".

    Макроопределение OPNAME проверяет имена операндов, формирует код второго операнда в переменной reg и включает в текст программы указанную выше директиву db, описывающую код команды.

    Имя первого операнда проверяется сравнением с его допустимыми образцами st или st(0). Для того чтобы результат не зависел от регистра, на котором набраны буквы имени (st, ST, sT, St), сравнение выполняет директива IFDIFI (если различаются). Буква I в конце ее имени указывает на то, что перед сравнением операндов коды всех букв имен приводятся к одному регистру.

    Для проверки второго операнда в переменную reg с помощью директивы SUBSTR помещается четвертый символ его имени. Если имя указано правильно, то это будет цифра от 0 до 7. Затем вторая директива SUBSTR копирует в переменную fvsym образец правильной записи имени второго операнда из списка допустимых имен. Наконец, директива IFDIFI сравнивает содержимое fvsym с именем второго операнда. При несовпадении выдается сообщение об ошибке и компиляция прекращается. В случае совпадения в текст программы вставляется код команды.

    Дополнение к примеру 9. При выполнении инструкций группы FCMOVcc проверяется состояние разрядов регистра флагов EFLSGS. Обычным инструкциям FPU регистр флагов недоступен. Поэтому разработчики Pentium Pro ввели 4 новые операции, которые помещают результат сравнения операндов в разряды EFLAGS. Эти четыре инструкции имеют тот же формат, что и инструкции группы FCMOVcc. Поэтому для их компиляции можно использовать макроопределение примера 9, просто добавьте в конец его текста следующие пять строчек:
    Код (ASM):
    1. ; Дополнение списка вызовов макроопределения FPIDEF
    2. FPIDEF FCOMI,    0DBh, 0F0h    ; простое сравнение операндов
    3. FPIDEF FCOMIP,   0DFh, 0F0h    ; тоже, но с освобождением st
    4. FPIDEF FUCOMI,   0DBh, 0E8h    ; сравнение неупорядоченных операндов
    5. FPIDEF FUCOMIP,  0DFh, 0E8h    ; тоже, но с освобождением st
    Напомним, что операнды неупорядочены, если значения одного или обоих лежат за пределами допустимого диапазона чисел. Обычно при выполнении операций над неупорядоченными операндами возникает аварийная ситуация, при сравнении этого не происходит, а вырабатывается соответствующий признак.

    Операнд в оперативной памяти. У новых двухадресных инструкций групп MMX, SSE1 и SSE2 один операнд находится в регистре mmx или xmm, а другой либо в регистре, либо в оперативной памяти. В большинстве случаев в оперативной памяти находится второй операнд, исключением являются инструкции пересылки. Они позволяют перемещать данные как из памяти в регистры, так и в обратном направлении.

    Составить макроопределения для анализа и преобразования имен новых регистров mmx и xmm несложно. Намного сложнее выполнять анализ и преобразование адресов операндов, находящихся в оперативной памяти. При работе с 32-х разрядными адресами код операнда содержит переменное количество байтов, а в его мнемонической записи допускается использование арифметических и логических выражений. В качестве примера приведем три вполне корректные формы записи адресов операндов:
    dword ptr [ebx + 8];
    [eax + 4*ecx + 32];
    16[ebx][eax*4];
    Очевидно, что для анализа и преобразования подобных выражений нужен разбор всех допустимых случаев, при этом текст макроопределения становится слишком большим и трудно обозримым. Пример макроса, обрабатывающего ограниченный набор способов записи адресов операндов, приведен в Интернет на моем сайте http://www.macro.aaanet.ru/codes.html Но и при разумных ограничениях текст макроса остается достаточно длинным, поэтому некоторые авторы выбирают другой путь. К их числу относится Агнер Фог, разработанные им макросы для компиляции новых инструкций находятся в Интернет, на сайте http://www.agner.org/assem

    Подмена компилируемой инструкции. В этом случае в макроопределении выполняется только анализ и преобразование имен новых регистров, а адреса операндов, находящихся в оперативной памяти компилирует MASM. Для того чтобы он мог это сделать, в тексте макроопределения формируется псевдокоманда. Она состоит из имени подходящей инструкции общего назначения и операндов, указанных в реальной команде. После компиляции псевдокоманды в полученном объектном коде изменяется код операции. В качестве примера мы рассмотрим такой трюк (иначе его никак не назовешь) в чистом виде, когда в командах не используются имена новых регистров.

    Как уже говорилось, начиная с Pentium Pro, микропроцессоры семейства Pentium поддерживают группу CMOVcc, состоящую из 16-ти инструкций условной пересылки. При работе с версиями MASM 6.0 ― 6.11 для компиляции инструкций этой группы нужно макроопределение, текст которого приведен в примере 10.

    Пример 10. Макроопределение для компиляции инструкций CMOVcc
    Код (ASM):
    1. CMOVDEF MACRO OPNAME, CCODE ; мета-макро определение
    2. OPNAME MACRO DST, SRC:VARARG ; заготовка макроопределения
    3. LOCAL X, Y    ; описание локальных переменных (меток)
    4. X:    ; сохранение адреса начала команды
    5. BSF DST, SRC    ; компиляция псевдокоманды
    6. Y:    ; сохранение адреса конца команды
    7. ORG X+1    ; возврат ко второму байту кода операции
    8. DB CCODE    ; изменение кода второго байта команды
    9. ORG Y    ; восстановление текущего адреса
    10. ENDM    ; конец макроопределения OPNAME
    11. ENDM    ; конец макроопределения CMOVDEF
    12. ; Начало списка инструкций группы вызовов CMPVDEF
    13. CMOVDEF CMOVO,    40h    ; пересылка если переполнение
    14. CMOVDEF CMOVNO,   41h    ; пересылка если нет переполнения
    15. CMOVDEF CMOVB,    42h    ; пересылка если меньше
    16. CMOVDEF CMOVNB,   43h    ; пересылка если больше или равно
    17. ; и так далее вплоть до инструкции CMOVG с кодом 4Fh
    Текст примера 6.10 состоит из мета-макро определения CMOVDEF и неполного списка его макровызовов с указанием имен 4-х первых инструкций группы CMOVcc и соответствующих им кодов операций.

    Предположим, что в тексте программы встретилась команда "cmovb cx, [bx+2]". MASM находит имя инструкции в списке CMOVDEF и вызывает одноименное мета-макро определение с параметрами "CMOVB, 42h". Оно, в свою очередь формирует и выполняет макроопределение "cmovb cx, [bx+2]", в котором производится подмена имени компилируемой инструкции.

    Для подмены взята инструкция BSF, ее исполняют микропроцессоры семейства Intel, начиная с модели 386, и могут компилировать все версии MASM, начиная с 5.1. Таким образом, реально компилируется команда "bsf cx, [bx+2]", в результате MASM получается объектный код "0F BC 4F 02". Остается заменить в этом коде содержимое второго байта (BC) на 42, то есть сформировать код "0F 42 4F 02", соответствующий исходной команде "cmovb cx, [bx+2]".

    Особенности работы с макроопределением. Как уже говорилось, подмена компилируемой инструкции это трюк, а применение любых трюков обычно связано с определенными ограничениями. Обсудим два основных ограничения.
     
    Последнее редактирование: 27 янв 2017
  16. Mikl___

    Mikl___ Супермодератор Команда форума

    Публикаций:
    14
    Регистрация:
    25 июн 2008
    Сообщения:
    3.709
    1. При корректировке кода инструкции BSF в примере 10 изменяется содержимое второго байта, но в некоторых случаях, нужный байт может быть не вторым, а третьим или четвертым. Давайте разберемся, в каких случаях это происходит.

      Начиная с Intel 386, микропроцессоры могут работать в 16-ти разрядном (реальном) и в 32-х разрядном (защищенном) режимах. Независимо от установленного режима у команд могут быть как 16-ти, так и 32-х разрядные операнды и адреса. Если разрядность операнда или адреса не совпадает с разрядностью установленного режима работы процессора, то у команды появляются один или два префикса. Префикс размера операнда имеет код 66h, а префикс размера адреса имеет код 67h.

      При наличии префиксов текущий адрес, сохраненный в примере 10 в переменной X, не соответствует адресу начала кода операции, а содержимое адреса X+1 не является вторым байтом кода операции и его изменять нельзя. Как поступать в таких случаях?

      MASM определяет разрядность режима работы микропроцессора исходя из модели памяти, указанной в директиве .MODEL. Допустимы следующие имена моделей: TINY, SMALL, COMPACT, MEDIUM, LARGE, HUGE, FLAT. Из них только модель FLAT соответствует 32-х разрядному режиму работы микропроцессора.

      Если компилируемые инструкции работают только с 16-ти разрядными операндами и адресами, то вы можете выбрать любую модель, кроме FLAT. И наоборот, если инструкции работают только с 32-х разрядными операндами и адресами, то надо выбрать модель FLAT. Только при выполнении этих условий у команд отсутствуют префиксы, и макроопределение примера 10 является корректным.

      Если по каким-то причинам указанное требование невыполнимо, то надо произвести предварительную компиляцию и получить листинг программы. По листингу уточняется наличие и количество префиксов перед командой BSF и вносится изменение в текст макроопределения примера 10. В зависимости от количества префиксов адрес в директиве ORG X+1 изменяется на X+2 или X+3. Ничего лучшего предложить нельзя.
    2. Запись адреса операнда, находящегося в оперативной памяти, может состоять из нескольких слов или выражений, разделенных пробелами. При компиляции таких записей с применением макроопределения примера 10 будет выдано аварийное сообщение "слишком много операндов". Для того чтобы в подобных случаях не возникало аварийной ситуации запись адреса в команде надо заключить в угловые скобки, например, . Сказанное не распространяется на пробелы, используемые при записи индексных выражений, заключенных в квадратные скобки.
    Следует подчеркнуть, что указанные особенности или недостатки относятся не только к примеру 10, но и ко всем макроопределениям, в которых применяется подмена компилируемой инструкции.
    Условное ассемблирование. Одним из полезных приемов программирования является включение в исходный текст программы условных блоков. Так называют последовательность команд, директив или макроопределений, которые компилируются или не компилируются в зависимости от выполнения заданного условия. Мы опишем пример условного блока, но предварительно поясним его назначение.
    Макроопределения, выполняющие компиляцию любых инструкций можно применять только в тех случаях, когда используемая версия MASM не делает это самостоятельно. В противном случае MASM выдаст аварийное сообщение о повторном определении имени известной ему инструкции.
    Программист обычно знает номер версии MASM, с которой он работает и самостоятельно решает вопрос о необходимости применения соответствующих макроопределений. Но если создается общедоступное макроопределение, то заранее не известно кто им воспользуется, и какая версия компилятора окажется в его распоряжении. В таком случае текст макроопределения целесообразно оформить в виде условного блока, перед компиляцией которого проверяется поддержка MASM конкретной группы директив. Текст начала такого блока приведен в примере 11.

    Пример 11. Управление компиляцией макроопределения
    Код (ASM):
    1. IFDEF @version ; если переменная @version определена, то
    2. IF @version GE 612 ; если версия MASM 6.12 или выше, то
    3. ; MASM поддерживает инструкции группы P6
    4. .686 ; разрешение компиляции инструкций группы P6
    5. SUPPORT EQU 1 ; определение переменной SUPPORT
    6. ENDIF ; конец действия директивы IF
    7. ENDIF ; конец действия директивы IFDEF
    8. IFNDEF SUPPORT ; если переменная SUPPORT не определена, то
    9. ; здесь располагается текст макроопределения для компиляции новых
    10. ; инструкции из группы P6, например, полный текст примера 9 или 10.
    11. ENDIF ; конец действия директивы IFNDEF SUPPORT
    В примере 6.11 проверяется поддержка MASM инструкций общего назначения, входящих в группу P6. Если вы собираетесь компилировать новые инструкции MMX, то, не изменяя версию, замените в тексте примера 6.11 директиву .686 на .mmx. При компиляции инструкций групп SSE надо изменить номер версии на 614, и вместо директивы .686 указать .xmm.

    Замечание

    Директивы .686, .mmx и .xmm разрешают компиляцию разных категорий инструкций микропроцессоров, поэтому их можно использовать совместно, но директива .686 должна быть указана первой. По крайней мере, так надо поступать при работе с версией MASM 6.15.

    Заключение

    В пределах статьи невозможно описать все разнообразие макросов и все возможные случаи их применения. Поэтому я стремился показать некоторые общие приемы, которые могут быть полезны при составлении макросов различного назначения. Насколько удачной оказалась эта попытка судить вам, уважаемые читатели и программисты.
     
  17. Mikl___

    Mikl___ Супермодератор Команда форума

    Публикаций:
    14
    Регистрация:
    25 июн 2008
    Сообщения:
    3.709

    Зубков "Ассемблер для DOS, Windows и Unix"

    3.3.9. Условное ассемблирование

    В большинстве языков программирования присутствуют средства, позволяющие игнорировать тот или иной участок программы в зависимости от выполнения условий, например: в языке C это осуществляется командами препроцессора #if, #ifdef, #ifndef и так далее. Ассемблер тоже предоставляет такую возможность.
    if выражение
    ...
    endif
    Если значение выражения — ноль (ложь), весь участок программы между IF и ENDIF игнорируется. Директива IF может также сочетаться с ELSE и ELSEIF:
    if выражение
    ...
    else
    ...
    endif
    Если значение выражения — ноль, ассемблируется участок программы от ELSE до ENDIF, в противном случае — от IF до ELSE.
    if выражение1
    ...
    elseif выражение2
    ...
    elseif выражение3
    ...
    else
    ...
    endif
    Так, если, например, выражение2 не равно нулю, будет ассемблироваться участок программы между первой и второй директивой ELSEIF. Если все три выражения равны нулю, ассемблируется фрагмент от ELSE до ENDIF. Данная структура директив может использоваться в частном случае аналогично операторам switch/case языков высокого уровня, если выражения — проверки некоторой константы на равенство.
    Кроме общих директив IF и ELSEIF ассемблеры поддерживают набор специальных команд, каждая из которых проверяет специальное условие:
    • IF1/ELSEIF1 — если ассемблер выполняет первый проход ассемблирования;
    • IF2/ELSEIF2 — если ассемблер выполняет второй проход ассемблирования (часто не работает на современных ассемблерах);
    • IFE выражение/ELSEIFE выражение — если выражение равно нулю (ложно);
    • IFDEF метка/ELSEIFDEF метка — если метка определена;
    • IFNDEF метка/ELSEIFNDEF метка — если метка не определена;
    • IFB <аргумент>/ELSEIFB <аргумент> — если значение аргумента — пробел (эти и все следующие директивы используются в макроопределениях для проверки параметров);
    • IFNB <аргумент>/ELSEIFNB <аргумент> — если значение аргумента — не пробел (используется в макроопределениях для проверки переданных параметров);
    • IFDIF <аргумент1>,<аргумент2>/ELSEIFDIF <аргумент1>,<аргумент2> — если аргументы отличаются (с различием больших и маленьких букв);
    • IFDIFI <аргумент1>,<аргумент2>/ELSEIFDIFI <аргумент1>,<аргумент2> — если аргументы отличаются (без различия больших и маленьких букв);
    • IFIDN <аргумент1>,<аргумент2>/ELSEIFIDN <аргумент1>,<аргумент2> — если аргументы одинаковы (с различием больших и маленьких букв);
    • IFIDNI <аргумент1>,<аргумент2>/ELSEIFIDNI <аргумент1>,<аргумент2> — если аргументы одинаковы (без различия больших и маленьких букв).
    Иногда директивы условного ассемблирования используются для того, чтобы прервать ассемблирование программы, если обнаружилась какая-нибудь ошибка. Для таких случаев предназначены директивы условной генерации ошибок.
    if $ gt 65535 ; Если адрес вышел за пределы сегмента.
    .err
    endif
    Встретив директиву .ERR, ассемблер прекратит работу с сообщением об ошибке. Аналогично командам условного ассемблирования существуют модификации команды .ERR:
    • .ERR1 — ошибка при первом проходе ассемблирования;
    • .ERR2 — ошибка при втором проходе ассемблирования;
    • .ERRE выражение — ошибка, если выражение равно нулю (ложно);
    • .ERRNZ выражение — ошибка, если выражение не равно нулю (истинно);
    • .ERRDEF метка — ошибка, если метка определена;
    • .ERRNDEF метка — ошибка, если метка не определена;
    • .ERRB <аргумент> — ошибка, если аргумент пуст (эта и все следующие директивы используются в макроопределениях для проверки параметров);
    • .ERRNB <аргумент> — ошибка, если аргумент не пуст;
    • .ERRDIF <аргумент1>,<аргумент2> — ошибка, если аргументы различны;
    • .ERRDIFI <аргумент1>,<аргумент2> — ошибка, если аргументы отличаются (сравнение не различает большие и маленькие буквы);
    • .ERRIDN <аргумент1>,<аргумент2> — ошибка, если аргументы совпадают;
    • .ERRIDNI <аргумент1>,<аргумент2> — ошибка, если аргументы совпадают (сравнение не различает большие и маленькие буквы).

    3.5. Макроопределения

    Одно из самых мощных языковых средств ассемблера — макроопределения. Макроопределением (или макросом) называется участок программы, которому присвоено имя и который ассемблируется всякий раз, когда ассемблер встречает это имя в тексте программы. Макрос начинается директивой MACRO и заканчивается ENDM. Например: пусть описано макроопределение hex2ascii, переводящее шестнадцатеричное число, находящееся в регистре AL, в ASCII-код соответствующей шестнадцатеричной цифры:
    Код (ASM):
    1. hex2ascii macro
    2.             cmp      al,10
    3.             sbb      al,69h
    4.             das
    5.             endm
    Теперь в программе можно использовать слово hex2ascii, как если бы это было имя команды, и ассемблер заменит каждое такое слово на три команды, содержащиеся в макроопределении. Разумеется, можно оформить этот же участок кода в виде процедуры и вызывать его командой CALL — если процедура вызывается больше одного раза, этот вариант программы займет меньше места, но вариант с макроопределением станет выполняться быстрее, так как в нем не будет лишних команд CALL и RET. Однако скорость выполнения — не главное преимущество макросов. В отличие от процедур макроопределения могут вызываться с параметрами, следовательно, в зависимости от ситуации, включаемый код будет немного различаться, например:
    Код (ASM):
    1. s_mov      macro      register1,register2
    2.            push       register1
    3.            pop        register2
    4.            endm
    Теперь можно использовать S_MOV вместо команды MOV для того, чтобы скопировать значение из одного сегментного регистра в другой.
    Следующее важное средство, использующееся в макроопределениях, — директивы условного ассемблирования. Например: напишем макрос, выполняющий умножение регистра AX на число, причем, если множитель — степень двойки, то умножение будет выполняться более быстрой командой сдвига влево.
    Код (ASM):
    1. fast_mul macro number
    2.            if         number eq 2
    3.                       shl      ax,1      ; Умножение на 2
    4.            elseif     number eq 4
    5.                       shl      ax,2      ; Умножение на 4
    6.            elseif     number eq 8
    7.                       shl      ax,3      ; Умножение на 8
    8.                       ...                ; Аналогично вплоть до:
    9.            elseif     number eq 32768
    10.                       shl      ax,15     ; Умножение на 32768
    11.            else
    12.                       mov      dx,number ; Умножение на число, не являющееся
    13.                       mul      dx        ; степенью двойки.
    14.            endif
    15.            endm
    Можно, конечно, усложнить этот макрос, применяя особые свойства команды LEA и ее комбинации, сдвиги и сложения, однако в нынешнем виде он чрезмерно громоздкий. Проблема решается с помощью третьего средства, постоянно использующегося в макросах, — блоков повторений.

    3.5.1. Блоки повторений

    Простейший блок повторений REPT выполняет ассемблирование участка программы заданное число раз. Например, если требуется создать массив байтов, проинициализированный значениями от 0 до 0FFh, это можно сделать путем повтора псевдокоманды DB следующим образом:
    Код (ASM):
    1. hexnumber = 0
    2. hextable       label   byte           ; Имя массива
    3.                rept    256            ; Начало блока
    4.                db      hexnumber      ; Эти две строки ассемблируются
    5. hexnumber = hexnumber+1               ; 256 раз.
    6.                endm
    Блоки повторений, так же как макроопределения, могут вызываться с параметрами. Для этого используются директивы IRP и IRPC:
    Код (ASM):
    1.               irp       параметр,<значение1,значение2...>
    2.                  ...
    3.                endm
    4.  
    5.                irpc      параметр,строка
    6.                  ...
    7.                endm
    Блок, описанный директивой IRP, будет вызываться столько раз, сколько значений указано в списке (в угловых скобках), и при каждом повторении будет определена метка с именем параметр, равная очередному значению из списка. Например, следующий блок повторений сохранит в стек регистры AX, BX, CX и DX:
    Код (ASM):
    1.                irp       reg,<ax,bx,cx,dx>
    2.                push      reg
    3.                endm
    Директива IRPC (FORC в WASM) описывает блок, который выполняется столько раз, сколько символов содержит указанная строка, и при каждом повторении будет определена метка с именем параметр, равная очередному символу из строки. Если строка содержит пробелы или другие символы, отличные от разрешенных для меток, она должна быть заключена в угловые скобки.
    Например, следующий блок задает строку в памяти, располагая после каждого символа строки атрибут 0Fh (белый символ на черном фоне), так что эту строку впоследствии можно будет скопировать прямо в видеопамять.
    Код (ASM):
    1.                irpc      character,<строка символов>
    2.                db        ’&character&’,0Fh
    3.                endm
    В этом примере используются & (амперсанды), чтобы вместо параметра character было подставлено его значение даже внутри кавычек.
    & (амперсанд) — это один из макрооператоров — специальных операторов, которые действуют только внутри макроопределений и блоков повторений.
     
    Последнее редактирование: 27 янв 2017
  18. Mikl___

    Mikl___ Супермодератор Команда форума

    Публикаций:
    14
    Регистрация:
    25 июн 2008
    Сообщения:
    3.709

    3.5.2. Макрооператоры


    • Макрооператор & (амперсанд) нужен для того, чтобы параметр, переданный в качестве операнда макроопределению или блоку повторений, заменялся значением до обработки строки ассемблером. Так, например, следующий макрос выполнит команду PUSH RAX, если его вызвать как PUSHREG A:
      pushreg macro letter
      push e&letter&x
      endm
      Иногда можно использовать только один амперсанд — в начале параметра, если не возникает неоднозначностей. Например, если передается номер, а требуется создать набор переменных с именами, оканчивающимися этим номером:
      irp number,<1,2,3,4>
      msg&number db ?
      endm
    • Макрооператор <> (угловые скобки) действует так, что весь текст, заключенный в эти скобки, рассматривается как текстовая строка, даже если он содержит пробелы или другие разделители. Как мы уже видели, этот макрооператор используется при передаче текстовых строк в качестве параметров для макросов. Другое частое применение угловых скобок — передача списка параметров вложенному макроопределению или блоку повторений.
    • Макрооператор ! (восклицательный знак) используется аналогично угловым скобкам, но действует только на один следующий символ, так что, если этот символ — запятая или угловая скобка, он все равно будет передан макросу как часть параметра.
    • Макрооператор % (процент) указывает, что находящийся за ним текст является выражением и должен быть вычислен. Обычно это требуется для того, чтобы передавать в качестве параметра в макрос не само выражение, а его результат.
    • Макрооператор ;; (две точки с запятой) — начало макрокомментария. В отличие от обычных комментариев текст макрокомментария не попадает в листинг и в текст программы при подстановке макроса. Это сэкономит память при ассемблировании программы с большим количеством макроопределений.

    3.5.3. Другие директивы, используемые в макроопределениях


    • Директива EXITM выполняет преждевременный выход из макроопределения или блока повторений. Например, следующее макроопределение не выполнит никаких действий, то есть не будет расширено в команды процессора, если параметр не указан:
      pushreg macro reg
      ifb <reg>
      exitm
      endif
      push reg
      endm
    • LOCAL метка... — перечисляет метки, которые будут применяться внутри макроопределения, чтобы не возникало ошибки «метка уже определена» при использовании макроса более одного раза или если та же метка присутствует в основном тексте программы. Операнд для LOCAL метка или список меток, которые будут использоваться в макросе.
    • PURGE имя_макроса — отменяет определенный ранее макрос. Эта директива часто применяется сразу после INCLUDE, включившей в текст программы файл с большим количеством готовых макроопределений.
     
    Последнее редактирование: 31 янв 2017
  19. Edmond

    Edmond узник замка IF THEN ELSE

    Публикаций:
    0
    Регистрация:
    2 сен 2002
    Сообщения:
    203
    Адрес:
    WASM.RU

    Тайны макросов в MASM32 — Архив WASM.RU


    Edmond/HI-TECH

    Руководство по проектированию макросов в MASM32


    (часть 1.1)
    Пойми в Хаосе Разное, и стань человеком.
    Осознай Единое в Различном — и будь Богом.

    Автор​

    I. От автора


    В этом руководстве раскрывается тема создания, использования (а главное — проектирования) макросов и макрофункций в проектах на MASM32.
    Что не так важно в ЯВУ, то очень важно в программировании на ассемблере. Если выстроить по приоритетам недостатки программирования на ассемблере, то первым недостатком будет не объём строк написанного кода (как не странно), а отсутствие средств, обеспечивающих хороший стиль написания кода.
    Что значит стиль? А что значит плохой или хороший? Это можно быстро понять на простом примере.
    Допустим, у вас есть процедура объёмом на несколько экранов. Вы её написали месяц назад, а теперь вам нужно несколько изменить её поведение. Для того, чтобы сделать это, вам необходимо:
    1. Вспомнить её алгоритм (если забыли)
    2. Вспомнить особенности реализации (у вас должны быть комментарии)
    3. Вспомнить какой участок кода, чем занимается.
    Если код процедуры был прооптимизирован, вероятней всего вам захочется, чтобы после модификации он остался настолько же оптимальным, а поэтому вы должны вспомнить все тонкости кода, или только того участка, который подлежит модификации.
    А это не так то просто, даже если исходник написан вами, в вашем неповторимом стиле.
    Если этот стиль будет хорошим, вы потратите меньшее время, если бы стиль был бы плохим.
    Хороший стиль программирования — это сэкономленное время, которое можно потратить на понимание, модификацию или, как это называют, сопровождение кода.
    Стиль программирования — это архитектура исходного кода — не только его внешнее оформление, но и использование констант, разбиения кода на функции или процедуры, способы вызова функций и процедур, согласованность структур, их потенциал к расширению, гибкость алгоритмов и многое другое. Стиль программирования сложно отделить от архитектуры самой программы, так как хорошо спроектированная программа не может иметь плохого стиля программирования.
    Конечно же, на ЯВУ легче писать качественно оформленные программы, хотя бы, потому что ЯВУ уже имеет готовые средства выражения, и шаблоны мышления.
    Что такое шаблоны мышления? Всё чем вы так активно пользуетесь:
    • типы
    • функции
    • классы
    • массивы
    • указатели на типы
    • пространства имён
    • шаблоны (С++)
    Всё это направляет ваше понимание программирования как пространства сотканного из таких абстракций.
    Недавно я прочёл следующую мысль на форуме WASM.RU:
    Да, зачем вы пишите программы на asm под Win32, лучше уже писать под DOS, там хоть нет этого бесконечно однообразного кода создания окон и обработки сообщений.
    Такое заявление говорит, что программист не желает писать проекты более чем на 6 000 строк (или 3 000 :)). Вместо того чтобы извлечь великую выгоду из единообразия кода, мы ругаем его. А ведь это первый звонок к автоматизации программирования.
    Неужели программирование asm может быть похоже на Delphi (ох как его не любят некоторые)? Снова интегрированная среда? Конечно!!! (Жаль, её всё-таки нет!) Но это не значит, что она играет отрицательную роль. Хотя о средствах автоматизации и их создании мы поговорим в другой работе.
    Ассемблер не определяет шаблонов мышления, и практически не имеет средств выражения каких либо шаблонов (из-за чего автор пользуется им).
    Очень сложно назвать директиву proc средством выражение процедурной модели программирования.
    Однако я могу ручаться, если вы научитесь писать качественно стилизированные программы на ассемблере, то на ЯВУ… ?.
    Об искусстве стилизации или проектировании архитектуры написано слишком мало, а рассказать хотелось бы слишком много. Только нельзя объять необъятное, и потому цель этого руководства рассказать об использовании макросов в MASM32, а также о том, как их можно либо нужно использовать, чтобы более качественно стилизировать код.

    I.1 Для тех, кто впервые...

    Если вы ещё не работали с макросами, или работали, но очень мало, я спешу признаться, что это руководство не предназначалось для начинающих. Но благодаря рекомендациям и советам The Svin/HI-TECH я решился добавить в него вырезки и упражнения, которые позволят вам быстро войти во вкус макромира MASM32. Если же вы уже имеете дело с макросами, тогда это руководство укрепит ваши знания и представления по данной теме.
    Для исследования макромира MASM мы воспользуемся директивой echo, которая позволит вывести нам на экран то, что творится в препроцессоре MASM. Очень удобно, а главное наглядно. Я уверен, что вы быстро усвоите этот материал.

    I.2. Примечания (обо всём понемногу)

    В данной работе я часто пишу: «Препроцессор ML». Кто-то из умников (или просто жаждущих подловить «на горячем») воскликнет: «Да какой же такой ML.EXE — препроцессор? Наглая ложь». На всякий случай оговорю, что здесь имеется ввиду не утверждение «ML — препроцессор», а именование его подсистемы — препроцессор.
    Всё, что есть в этом руководстве не взято с потолка, и не является вымышленным. Весь код проверен, и работает именно так как описано, если только автор случайно не ошибся, что так же случается.
    Многое из того, что написано в этом руководстве недокументированно (или плохо документировано) в официальном. Поэтому вы всегда должны помнить, что если в следующих версиях ML (например, 8.0) что-то не будет работать, никто не виноват.
    Если вы думаете, что я дизассемблировал ML.EXE — то ошибаетесь. Алгоритмы работы, приведённые здесь, получены логическим путём на основе знаний работы компиляторов, а поэтому их не следует воспринимать как истинные. Важна сама логика работы, понимание которой, поможет вам безболезненно использовать макро, допуская меньшее количество ошибок.
    На самом деле MASM очень плохо документирован, и видно MS совсем не относится к нему как к продукту (что вполне очевидно). Хотя уже в MSDN 2002 был внесён раздел MASM Reference, и всё равно — вы не найдёте лучше описания чем в MASM32 by Hutch.
    Когда вы прочтете, то воскликните: «Да, зачем мне такой ML?». Есть NASM и FASM — главная надежда мира ассемблерщиков. Однако и теперь ML всё ещё выигрывает у них по удобству эксплуатации, большей частью видимо благодаря Хатчу, и многим замечательным людям, поддерживающим MASM32. Кто знает, может после этой статьи кто-то воскликнет: «Я знаю, какой должен быть компилятор мечты асмовцев!». И напишет новый компилятор. (Автор шутит ?)
    Уверен, что программисты из MS вряд ли прочтут эту статью (они плохо знакомы с русским), и оно к лучшему. Возможно, такая статья могла бы их огорчить, а я не люблю портить настроение людям, трудами которых пользуюсь. (Снова шутит, только про что?)
    И наконец-то мне в свою очередь хочется порадоваться, что многие вопросы по макросам в MASM закрыты на долгое время, во всяком случае, для русскоязычной аудитории. (Шутит, или нет? Гм…)

    I.3. Особенности терминологии

    Терминология этой статьи различается от терминологии принятой в MASM.
    В частности автором было предложено называть:
    MacroConstant EQU 123 ;; Числовая макроконстанта
    MacroVar = 123 ;; Числовая макропеременная
    MacroText EQU <string> ;; строковая макропеременная
    MacroText TEXTEQU <string> ;; строковая макропеременная
    В MASM:
    MacroConstant EQU 123 ;; numeric equates
    MacroVar = 123 ;; numeric equates
    MacroText EQU <string> ;; text macro
    MacroText TEXTEQU <string> ;; text macro
    Можно было бы попросту выбрать терминологию MASM, однако последняя не позволяет объяснять материал систематически. То есть все четыре вида выражений — по сути, являются переменными или константами. Однако в терминологии MASM два последних определения называются текстовыми макро, подчёркивая их связь с макросами.
    Если пойти этим путём, то тогда и первые два определения — являются упрощёнными определениями макро. Если разработчики желали подчеркнуть, что сама суть внутренней реализации ML представляет текстовые макросы как макро, то тогда не ясны те все эффекты функциональности, обсуждаемые в этой статье.
    Что имеет ввиду автор?
    Посмотрите что такое макроопределение — это некий текст, который как бы «вставляется» препроцессором в исходный текст программы в месте вызова макро.
    А что такое в терминологии MASM numeric equates, или text macro — это некоторые переменные, значения которых «подставляются» в исходный текст программы во время компиляции вместо имён этих переменных.
    Таким образом, можно сказать, что определения представленные выше — макро, но в упрощённом их виде.
    Этот спор не решаем, что не так и важно. Поэтому автор отдаёт предпочтение двум терминам для «text macro»: «текстовой макро» и «строковая макропеременная».
    Понятие: «numeric equates» является общим для первых двух случаев, и разрывает смысловую связь с двумя последними определениями. Поэтому я пользуюсь своим вариантом терминологии, который подчёркивает, что определения:
    MacroConstant EQU 123 ;; Числовая макроконстанта
    MacroVar = 123 ;; Числовая макропеременная
    являются подобными макро. А, кроме того, первое из низ — константа, а второе — переменная.
     
    Последнее редактирование модератором: 2 фев 2017
    rococo795 нравится это.
  20. Edmond

    Edmond узник замка IF THEN ELSE

    Публикаций:
    0
    Регистрация:
    2 сен 2002
    Сообщения:
    203
    Адрес:
    WASM.RU

    I.4. Благодарности

    Не могу не написать этот пункт, ибо не только автору обязана эта статья.
    Она обязана замечательной версии Win98 с инсталляцией от 2000, которая отформатировала весь мой винчестер, и унесла в небытие первый вариант настоящей статьи. :)
    Не малая заслуга в вопросе терминологии MASM, и его разрешении принадлежит Four-F, который как он сам мне признался, съел на макросах собаку, при чём без соли :).
    Когда я думаю, чтобы было бы без самого Маниакального редактора в Inet, CyberManiac'а, то понимаю: без его правок мои статьи приводили бы в ужас, и лишали разума всех морально неустойчивых читателей. CyberManiac: «Только такой замечательный безумец как ты может выдержать ЭТО!!!» :).
    FatMoon, rustam, The Svin – вы дали понять мне то, что такая статья действительно нужна, и это, наверное, самое главное. Вряд ли я бы так долго работал над ней, если бы меня никто не подталкивал.
    Всех кого я забыл поблагодарить здесь, и кого не забыл, жду в условном месте в условное время для раздачи благодарностей.
    С уважением, Edmond/HI-TECH

    II. Лень – двигатель Макро

    Когда говорят, что лень – это двигатель прогресса, видимо лицемерят или преувеличивают. Скорее это нежелание выполнять одну и ту же работу очень часто. Первая парадигма к созданию макро звучит так:
    Если есть что-то похожее, что нужно делать очень часто, я могу оформить это как макроопределение.
    Ассемблер, дающий программисту полную свободу в использовании методик программирования, совершенно лишает его средств для выражения этих методик. Например, ООП. В MASM32 нет классов, конструкторов и других механизмов, поддерживающих эту абстракцию. Зато вместо ООП Вы можете придумать множество других методик и абстракций (как, например модель серверов).
    Та или иная методика программирования обязательно состоит из каких-либо компонентов, которые являются подобными друг другу. Например, следующие макро очень любимы в примерах пакета MASM32:
    m2m MACRO M1, M2
    push M2
    pop M1
    ENDM
    return MACRO arg
    mov eax, arg
    ret
    ENDM
    Предположим, что кому-то так надоело писать:
    Код (ASM):
    1.             push переменная2
    2.             pop  переменная1
    И он решил придумать макро для этого. Эта пара команд осуществляет пересылку данных из одной ячейки памяти в другую. То есть теперь в программе, когда вы захотите написать push/pop, вы можете заменить это некой m2m операнд1, операнд2. Посмотрите на эти два участка кода:
    Код (ASM):
    1.     mov wc.cbWndExtra,     NULL
    2.     m2m wc.hInstance,      hInst
    3.     mov wc.hbrBackground,  COLOR_BTNFACE+1
    4.     . . . .
    5.     mov wc.cbWndExtra,     NULL
    6.     push hInst
    7.     pop wc.hInstance,
    8.     mov wc.hbrBackground,  COLOR_BTNFACE+1
    Первый вариант не только занимает меньше строк (что тоже важно), но и намного понятнее, чем push/pop (если вы, знаете что такое m2m). Конечно, если говорить о макро m2m, то он имеет и очень важный недостаток.
    Мощь макро была бы сказочной, если бы MASM умел следить за кодом, или ему можно было бы указать, что, например, сейчас регистр ebx == 0, или eax никем не используется. Хотя мы попробуем достичь подобного эффекта самостоятельно.
    Этот недостаток потеря контроля над оптимальностью кода. Например, более быстрыми, по сравнению с парой команд push/pop, являются mov eax,… Употребляя макро m2m, вы получаете худший код, если стремитесь оптимизировать по скорости. И здесь есть две стороны проектирования кода:
    1. Эффективность кода
    2. Совершенство стилистики
    Используя макро m2m, вы повышаете уровень стилистики, так как сокращаете время на понимание исходного кода (вами же или другим программистом). Однако с другой стороны вы теряете эффективность.
    Это одна из вечных задач архитектора – найти баланс между эффективностью в коде и совершенством стилистики.
    Другая парадигма использования макро звучит так:
    Если, объединяя что-то в одно целое, я улучшаю стиль кода – это можно сделать в виде макроопределения.
    Эта парадигма отличается от предыдущей тем, что создание макроопределения обуславливается только улучшением стилизации кода, и не имеет особой практической ценности. Например, я определил такие макро для определения кода начала и конца в главном модуле программы:
    Код (ASM):
    1. $$$WIN32START  macro
    2. PUBLIC l$_ExitProgram
    3. _start:          
    4. xor ebx,ebx
    5. endm
    6.                                            
    7. $$$WIN32END  macro
    8. l$_ExitProgram:  
    9. push $$$__null
    10. call ExitProcess
    11. end _start
    12.  
    13. endm
    В этих макро нет по сути никакой пользы, кроме эстетической. Зато, глядя на код, можно сразу понять, что это не что иное, как начало программы нечто вроде main() в C++.
    И последняя парадигма использования макро:
    Если ты используешь технологию программирования – попытайся заключить её в комплекс макроопределений.
    Например, для модульного программирования нужно создать макросы для определения модуля, его частей, кода и данных.
    Наиболее важная часть использования макро. Посмотрите, например, файл Objects.INC из пакета MASM32 в папке oop (NaN & Thomas).
    Мы начнём создание первых макро со следующей задачи.
    Наверное, вы знаете, что EXE приложения всегда могут загружаться по адресу равному:
    PROGRAM_IMAGE_BASE EQU 400000h
    Во-первых, это даёт нам право убрать из приложения всю Relock секцию, тем самым, уменьшив объём образа (если эта секция нужна для систем плагинов, её можно держать отдельно).
    Во-вторых мы можем более не вызывать функцию GetModuleHandle, что так же полезно для нас. Использование константы PROGRAM_IMAGE_BASE очень удобно. Однако, что будет значить это удобство, если всё-таки PROGRAM_IMAGE_BASE не определено? Это будет означать, что мы обязаны переписать весь код. А если этого кода много?
    Определённо об этом нужно позаботится заранее. Давайте же будем решать эту проблему при помощи макро! Для этого нам станут необходимыми некоторые знания о том, как обрабатывается макро, и что это такое.

    III. Макромир MASM

    Макрос представляет собой именованный участок исходного текста программы, который обрабатывается компилятором каждый раз в том месте, где вызывается макрос.
    Пример:
    Создайте небольшой модуль с именем macro.asm.
    И напишите в нём несколько строчек
    .386
    .data
    .code
    echo Hello!!!
    echo Ты должен увидеть во время компиляции
    end
    Так действует директива echo. С помощью неё можно подсмотреть значения переменных.
    Mycount = 1
    %echo @CatStr(%Mycount)
    Если вы не знаете, как это работает, не волнуйтесь, обо всём будет рассказано. А пока несколько экспериментов:
    Напишите:
    MyMacro macro reg
    dec reg
    endm
    .code
    mov eax,5
    MyMacro reg
    MyMacro reg
    Взгляните на код программы под отладчиком. Что у вас получилось? Что будет, если вы измените текст внутри макроопределения?
    Теперь напишите:
    MyVar = 1
    MyMacro macro
    MyVar = MyVar+1
    %echo MyVar = @CatStr(%MyVar)
    endm
    MyMacro
    MyMacro
    MyMacro
    MyMacro
    Каким будет вывод на экран во время компиляции?
    С этого момента вам придётся различать в ассемблере ML две подсистемы: препроцессор и компилятор. Если компилятор переводит код мнемоник в машинный код, вычисляет значения меток и смещений, то препроцессор занимается вычислением выражений этапа компиляции, и что самое важное – процессом раскрытия макросов.
    Подобно многим объектам мира программирования макро имеет два состояния в исходном тексте: определение, и использование.
    Таким образом, мы будем иметь дело с определением макроса (макроопределением), и его вызовом (использованием макроса).
    Макроопределением называется любой текст, заключённый между ключевыми словами:
    MacroName macro paramlist
    макроопределение
    endm
    При каждом вызове макро, а именно:

    MacroName
    или
    mov eax, MacroName()
    Будет анализироваться и исполнятся текст, заключённый в макро. Именно так это и реализовано в ML. Поскольку текст в макроопределении не компилируется, то естественно, вы не увидите сообщений об ошибке, даже если с точки зрения ассемблера эта ошибка будет в теле макроопределения. Однако ошибка появится при попытке вызова макроопределения, её могут выдать вам, либо сам препроцессор, либо компилятор, если текст, сгенерированный препроцессором является неверным с точки зрения компилятора.
    Каждый раз, когда препроцессор встречает макроопределение, он помещает его имя в специальную таблицу, и копирует его тело к себе в память (это не обязательно именно так, но вам должна быть понятна суть). Встретив макроопределение, препроцессор не проверяет, а есть ли макро с таким же именем. Это значит, что макро можно переопределять.
    MyMacro macro
    echo Это макро 1
    endm
    MyMacro macro
    echo Это макро 2
    endm
    MyMacro
    Вы можете самостоятельно удалять макроопределения, из памяти препроцессора используя директиву PURGE:
    PURGE macroname
    После этой директивы макро с именем macroname перестаёт существовать. И вы освобождаете память для компилятора.
     
    Последнее редактирование модератором: 31 янв 2017
    rococo795 нравится это.