На главную Наши проекты:
Журнал   ·   Discuz!ML   ·   Wiki   ·   DRKB   ·   Помощь проекту
ПРАВИЛА FAQ Помощь Участники Календарь Избранное RSS
msm.ru
! Перед отправкой сообщения внимательно прочтите правила раздела!!!
1. Все статьи должны быть оформлены согласно Правил оформления статей.
2. Любые обсуждения должны происходить в специальной теме, обсуждение в любых других темах раздела запрещены.
3. Запрещается писать статьи о создании и распространении вирусов, троянов и других вредоносных программ!
4. За грамотно написанные и правильно оформленные статьи авторы награждаются DigiMoney.

Дополнительные ссылки:
Желаю творческих успехов! ;)
Модераторы: Jin X
  
    > Некоторые возможности макроязыка TASM , x86, TASM, Общие вопросы
      Некоторые возможности макроязыка TASM.


      Введение.

      Данная статья не является учебником по макроязыку Turbo Assembler, скорее это забавный пример его практического использования, предполагающий знания основ макроязыка.
      Когда-то мне понадобилось защитить часть кода загрузчика CD-ROM. Вариант шифрования кода и данных отбросил сразу, поскольку аналогичная защита на диске «Reanimator 2K» была снята мной менее чем за день. И тогда вспомнил про старый добрый Форт, с его прямым шитым кодом, который и без шифрования создает трудный для понимания код. Вот его мы и создадим, используя макроязык Turbo Assembler.
      Нам понадобится TASM последней версии, то есть 5.3. Есть еще версия 5.4, но она отличается от предыдущей только копирайтом.

      Имена.

      Основная проблема, которую нужно решить, заключается в том, что в Форте для имён допустимы любые символы, включая те, что TASM запрещает использовать в именах меток и идентификаторов. Вдобавок, имена в Форте могут начинаться с цифры. Поэтому, строку вида:
      ExpandedWrap disabled
        >R R@ R> ;
      ассемблер не воспримет. Единственным выходом является создание макроса, которому будет передаваться строка с командами Форта, а он разберет её на составляющие и скомпилирует в код, примерно так:
      ExpandedWrap disabled
        _  >R R@ R> EXIT

      Первым делом необходимо изъять из строки некоторые спецсимволы, которые, которые макроязык ассемблера интерпретирует особым образом. Это символы ‘>’, ‘<’ и ‘!’. Для начала определим макрос подмены одного символа в строке:
      ExpandedWrap disabled
        macro $SWAP new, old: rest
            ??ch equ &old&
            ??pos instr ??NAME, ??ch
            :while
            if ??pos
              ??prev substr ??NAME, 1, ??pos-1
              ??next substr ??NAME, ??pos+1
              ??NAME catstr ??prev, <new>, ??next
              ??pos  instr  ??NAME, ??ch
              goto while
            endif
        endm
      Поиск производится в строке ??NAME, штатной функцией INSTR, а строка, в случае успешного поиска символа old, разбивается функцией SUBSTR на две части – до символа (??prev) и после (??next). Затем строки склеиваются функцией CATSTR, а между ними, вместо символа old, вставляется строка new.
      Теперь можно написать макрос предварительной обработки строки:
      ExpandedWrap disabled
        macro $PRENAME name: rest
            ??NAME   equ &name&
            $SWAP    less, <<
            $SWAP    more, >
            $SWAP    save, !
        endm
      Макрос подменяет символы ‘>’, ‘<’ и ‘!’ на строки ‘more’, ‘less’ и ‘save’ соответственно. Это поможет корректно разбить строку на отдельные слова, но не избавит от проблем с именами подпрограмм, поскольку остаются другие недопустимые символы. Поэтому нам понадобится еще один макрос, завершающий подмену остальных символов:
      ExpandedWrap disabled
        macro $POSTNAME name
            ??NAME   equ &name&
            $SWAP    plus, +
            $SWAP    minus, -
            $SWAP    mul, *
            $SWAP    eq, =
            $SWAP    div, /
            $SWAP    dot, .
            $SWAP    tld, ~
        endm
      Он будет использоваться на завершающем этапе формирования имен, когда уже будут отсеяны числа и символы. Почему не сразу? Проще это понять на примере. Допустим, имеется строка:
      ExpandedWrap disabled
        54 -2 * ‘*’ !
      Если бы мы подменяли все символы сразу, то получили бы строку:
      ExpandedWrap disabled
        54 minus2 mul ‘mul’ save
      То есть, вместо числа (-2) идентификатор (minus2), а вместо символа (‘*’) целую строку (‘mul’), что очевидно неправильно. Если же использовать сначала $PRENAME, то получим для разбора строку:
      ExpandedWrap disabled
        54 -2 * ‘*’ save
      В этой строке мы легко выберем числа и символы, а оставшиеся слова пропустим через $POSTNAME и получим допустимые идентификаторы:
      ExpandedWrap disabled
        mul save

      Поскольку числа-константы, символы и подпрограммы компилируются разными способами, нужен макрос для их распознавания. В макроязыке нет предусмотренных средств для определения типа идентификатора, а что есть – не работает. Придется сделать макрос идентификации чисел и символов самостоятельно:
      ExpandedWrap disabled
        macro $ISNUM n
            ??ISNUM = 0
            ??NUM equ &n&
            @len sizestr ??NUM
            ;; проверяем первый символ
            @cfirst SubStr ??NUM, 1, 1
            @IsFirst InStr <''>, @cfirst
            if (@IsFirst ne 0)
                ;; это символ, обрабатывается как число
                ??ISNUM = 1
                exitm
            endif
            ;; проверяем первый символ на наличие знака числа
            @IsFirst InStr <-+>, @cfirst
            if (@IsFirst ne 0)
                ;; первым идет знак числа, убираем его
                ??NUM SubStr ??NUM, 2, @len-1
                @cfirst SubStr ??NUM, 1, 1
                @len sizestr ??NUM
            endif
            if (@len eq 0)
                ;; это не число
                exitm
            endif
            ;; проверяем основание числа по последнему символу
            @numstr CatStr <0123456789>
            @clast SubStr ??NUM, @len, 1
            @IsLast InStr <bBhH>, @clast
            if (@IsLast ne 0)
                ;; это основание числа, убираем его
                if (@IsLast gt 2)
                    ;; это шестнадцатеричное число
                    @numstr CatStr @numstr,<ABCDEFabcdef>
                else
                    ;; это двоичное число, допустимы только 0 и 1
                    @numstr CatStr <01>
                endif
                ??NUM SubStr ??NUM, 1, @len-1
                @len sizestr ??NUM
            endif
            ;; первым символом, после знака, должна быть цифра
            @IsFirst InStr <0123456789>, @cfirst
            if (@len eq 0) or (@IsFirst eq 0)
                exitm
            endif
            ;; сканируем число, без учета символов знака и основания
            rept @len
                @nchr SubStr ??NUM, 1, 1
                ??NUM SubStr ??NUM, 2, @len
                @isDigit InStr @numstr, @nchr
                if (@isDigit eq 0)
                    exitm
                endif
            endm
            if (@isDigit ne 0)
                ??ISNUM = 1
            endif
        endm
      Вначале пропускаем возможные символы знака числа. Затем, убираем символ основания числа в конце строки и задаем набор допустимых цифр для данного числа в идентификатор @numstr. И в заключение, проверяем оставшуюся строку на предмет соответствия цифрам из строки @numstr. Результат возвращаем в глобальном идентификаторе ??ISNUM. Все просто.

      Компиляция кода

      Теперь можно приступать к макросу компиляции Форт-строки. Цель макроса – разбить строку на отдельные слова, идентифицировать их и скомпилировать в шитый код. Сам макрос выглядит проще некуда:
      ExpandedWrap disabled
        macro _ listcmd: rest
            $PRENAME listcmd
        %    $parsewords ??NAME
        endm
      То есть, подменяем в строке недопустимые символы и результат передаем в макрос $parsewords, который и произведет разбивку строки на слова. Обращаем внимание, что параметр макроса определен как REST, чтобы строка передавалась в него в неизменном виде, без предварительной обработки транслятором ассемблера. Однако есть нюанс. Если первым символом идет ‘<’, то его нужно продублировать ‘<<’, иначе ассемблер воспримет его, как начало текстовой строки и естественно отбросит. В других местах стоки прокатит и одиночный символ ‘<’.
      Макрос разбивки строки на слова вполне стандартен:
      ExpandedWrap disabled
        macro $parsewords name: vararg
            irp @i, <name>
                $COMPILE @i
            endm
        endm
      Параметр задан через VARARG, поэтому строку на слова разобьет уже сам транслятор ассемблера и макрос получит готовый массив слов, который он поочередно, с помощью IRP, скормит макросу $COMPILE:
      ExpandedWrap disabled
         macro $COMPILE name
            $ISNUM <&name>
            if ??ISNUM
                dw      _LIT
                dw      &name
            else
              $POSTNAME name
        %     $COMPILENAME ??NAME
            endif
        endm
      А вот это и есть компилятор слов Форта. Если слово распознано как число или символ, то оно компилируется в связке с подпрограммой LIT, которая, во время выполнения, извлекает его из кода и помещает на арифметический стек. В противном случае считаем слово идентификатором и, завершив подмену спецсимволов макросом $POSTNAME, компилируем его в код:
      ExpandedWrap disabled
        macro $COMPILENAME name
            ifdef _&name&_F
              _&name                  ;; немедленно выполняем слово
            else
              dw      _&name          ;; просто компилируем слово в код
            endif
        endm
      Тут две тонкости. Во первых, к имени добавляется префикс ‘_’, для решения проблем с именами, начинающимися с цифры. Во вторых, есть два типа подпрограмм – немедленного исполнения (immediate) и простые. С простыми все ясно, их адрес просто добавляется в код, т.е. компилируется. А вот с immediate сложнее. Они не компилируются, а сразу же выполняются, то есть это некий аналог макросов. Такие подпрограммы используются, в основном, в командах ветвления, или циклах, где необходимо сразу же расставить метки для переходов. Поэтому, реализуем их в виде макросов, а для отличия от простых слов, параллельно им будем определять идентификатор, с тем же именем, но с постпрефиксом ‘_F’. Подробнее о подпрограммах немедленного исполнения будет ниже.


      Подпрограммы

      Настало время определиться со стеками и регистрами. Стеков будет два, арифметический и возвратов. На верхушку первого будет указывать регистр SP, а второй реализуем через регистр BP. Регистр SI задействуем в качестве некоего аналога IP, он будет указывать на адрес следующей исполняемой подпрограммы. Регистры CS, DS и SS должны быть настроены на один сегмент. Остальные регистры свободны.
      Подпрограммы бывают двух видов – высокого и низкого уровня. Начнем с простейших. Низкоуровневые подпрограммы – это почти обычные ассемблерные вставки. Для их объявления достаточно преобразовать имя в допустимый вид и объявить метку:
      ExpandedWrap disabled
        macro $CODE cname: rest
            $PRENAME cname
        %   $POSTNAME ??NAME
            $LABEL <%??NAME>
        endm
         
        macro $LABEL name
            _&name&:
        endm
      Для завершения ассемблерных вставок нельзя использовать команду RET, вместо этого нужно передать управление на следующую подпрограмму. Адрес следующей подпрограммы хранится в слове, на которое указывает регистр SI. Для удобства оформим макрос:
      ExpandedWrap disabled
        macro $NEXT
                 lodsw
                 jmp     ax
        endm
      Таким образом, низкоуровневая подпрограмма будет выглядеть следующим образом:
      ExpandedWrap disabled
        $CODE >R      ; ( x -- )(R: -- x )    переносит x с ариф. стека на стек возвратов
                pop     ax
                xchg    bp, sp
                push    ax
                xchg    bp, sp
                $NEXT

      Подпрограммы высокого уровня реализуются прямым шитым кодом. Это значит, что для их выполнения нужна специальная функция. Назовем её DOLST. В качестве аргумента ей передается адрес подпрограммы высокого уровня, которую она и запустит на выполнение:
      ExpandedWrap disabled
        $CODE DOLST   ; ( addr -- )
                xchg    bp, sp
                push    si
                xchg    bp, sp
                pop     si
                $NEXT
      Функция сохраняет в стек возвратов текущее значение регистра SI и загружает в регистр новое, с арифметического стека. Этим значением будет указатель на массив адресов подпрограмм, что составляют тело исполняемой подпрограммы. С помощью макроса $NEXT запускаем выполнение первой подпрограммы из массива, а она передаст управление следующей и т.д., пока не будет достигнут конец массива, то есть особая функция завершения подпрограммы. В Форте имя этой подпрограммы – точка с запятой. Ассемблер не даст нам использовать такое имя, поэтому выберем другое:
      ExpandedWrap disabled
        $CODE EXIT
                mov     si, [bp]
                add     bp, 2
                $NEXT
      Подпрограмма восстанавливает значение регистра SI из стека возвратов и таким образом возвращает управление туда, откуда и была вызвана подпрограмма, точнее на следующий адрес.
      Теперь можно написать макрос для объявления подпрограмм высокого уровня:
      ExpandedWrap disabled
        macro $COLN cname: rest
            $CODE cname
                call    _DOLST
        endm
      Для примера:
      ExpandedWrap disabled
        $COLN NEG     ; ( n -- -n )           n = -n
                _       NOT 1+ EXIT
      сгенерирует код:
      ExpandedWrap disabled
        03C5                _NEG:
        03C5  E8 FD93         call    _DOLST
        03C8  0539r           dw      _NOT
        03CA  03F2r           dw      _1plus
        03CC  011Dr           dw      _EXIT
      Инструкция CALL кладет на арифметический стек адрес первого элемента массива подпрограмм, что составляют тело текущей подпрограммы (dw _NOT, _1plus и т.д.), а DOLST запускает выполнение.

      Подпрограммы немедленного исполнения

      Как уже упоминалось ранее, для реализации ветвления кода и циклов необходимы подпрограммы немедленного исполнения, так называемые immediate. Все дело в том, что еще на этапе генерации кода необходимо расставить команды переходов на код, который еще даже не объявлен. Возьмем для примера простейшую конструкцию: IF – ELSE - THEN (так в Форте называется ENDIF).
      ExpandedWrap disabled
        IF
          if (param == FALSE) then GOTO (ELSE or THEN)
         
              <code>
         
              jmp THEN
        [ELSE]
         
              <code>
         
        THEN
      При выполнении макроса IF придется сгенерировать код проверки условия и возможный переход либо на конец условия (THEN), либо на возможную ветку ELSE, на данном этапе просто невозможно определить, куда именно будет переход. Поэтому, мы сгенерируем переход на некую метку и занесем ее имя в список. Последующий макрос, THEN или ELSE, извлечет это имя из списка, и сгенерируют метку в коде.
      Список меток организуем по принципу стека, последним пришел – первым ушел. Всего нам понадобится три макроса, $PUSH – для генерации новой метки и помещению ее в список, $POP – соответственно для извлечения метки из списка, и $TOP – для получения метки со списка, без ее извлечения из этого списка. Примерно так:
      ExpandedWrap disabled
        macro $PUSH variable
          local @@lab
                ??STACK CatStr <@@lab>, <,>, ??STACK
                ifnb <variable>
                  variable CatStr <@@lab>
                endif
        endm
         
        macro $POP variable: req
                variable equ <>
                @pos InStr ??STACK, <,>
                if @pos
                        @pos     = @pos-1
                        variable SubStr ??STACK, 1, @pos
                        @pos     = @pos+2
                        ??STACK  SubStr ??STACK, @pos
                endif
        endm
         
        macro $TOP variable: req
                variable equ <>
                @pos InStr ??STACK, <,>
                if @pos
                        @pos     = @pos-1
                        variable SubStr ??STACK, 1, @pos
                endif
        endm
      В параметре макросам передается имя идентификатора, в который будет сохранено имя метки. Имена меток нам генерирует сам ассемблер, посредством своего макроса LOCAL. Это гарантирует уникальность имени. В остальном, думаю все понятно, новое значение добавляется в начало строки, чтобы при извлечении снова быть первым.
      Теперь непосредственно сами макрокоманды условия. Первым делом объявляем их IMMEDIATE:
      ExpandedWrap disabled
        IMMEDIATE       = 1             ; флаг слов немедленного исполнения
         
          _IF_F         = IMMEDIATE
          _THEN_F       = IMMEDIATE
          _ELSE_F       = IMMEDIATE
      Сам макрос условия:
      ExpandedWrap disabled
        macro _IF                       ; ( bool -- )
                $PUSH   <??LABEL>
                dw      _?BRANCH
        %       dw      ??LABEL
        endm
      Генерирует метку и условный переход на нее, если на верхушке арифметического стека находится значение FALSE (подпрограмма ?BRANCH).
      Макрос завершения тела условия:
      ExpandedWrap disabled
        macro _THEN
                $POP    <??LABEL>
        %  ??LABEL:
        endm
      Просто извлекает из списка метку, что сгенерировал макрос IF или ELSE, и ставит в конец условия.
      Макрос альтернативного выбора:
      ExpandedWrap disabled
        macro _ELSE
                $POP    <??LABEL>
                $PUSH   <??NEWLAB>
                dw      _BRANCH
        %       dw      ??NEWLAB
        %  ??LABEL:
        endm
      Вначале генерирует код безусловного перехода на конец условия (BRANCH), то есть на метку, что сгенерирует THEN. Этим самым завершается выполнение ветки IF. После этого объявляем метку, что сгенерировал макрос IF. Теперь именно сюда и перейдет управление, в случае если не сработает условие оператора IF.
      Похожим образом строятся конструкции CASE – OF – ENDCASE, а также конструкции циклов BEGIN – UNTIL, BEGIN – WHILE – REPEAT, DO – LOOP. Приводить их реализацию не буду, все есть во вложении.


      Данные

      В Форте нет переменных и констант, в том виде, что есть в других языках программирования. Любая переменная или константа - это подпрограмма. При вызове переменной, она оставляет на стеке адрес связанной с ней ячейки памяти, куда можно что-то записать, или считать из нее. В отличие от переменной, константа оставляет на стеке не адрес ячейки, а её значение. При желании, значение константы можно менять по ходу выполнения программы, хотя вряд ли в этом есть смысл. В большинстве реализаций Форта для создания переменных и констант предусмотрены слова VARIABLE и CONSTANT. Не будем и мы отбиваться от большинства. Начнем с констант:
      ExpandedWrap disabled
        macro CONSTANT name, imm:req
            $CODE <&name>
                push    &imm
                $NEXT
        endm
      Макрос создает подпрограмму, которая кладет на арифметический стек значение константы.
      При объявлении переменной нужно учесть возможность зарезервировать место под массив, а так же возможность объявления инициированных данных:
      ExpandedWrap disabled
        macro VARIABLE name: req, len, imm
            $CODE <&name>
            @name CatStr <$$_>,??NAME
        %        push offset @name
                 $NEXT
          ifnb <imm>
            align 2
            ifnb <len>
        %        @name dw len dup(imm)
            else
        %        @name dw imm
            endif
          else
            udataseg
            align 2
            ifnb <len>
        %        @name dw len dup(?)
            else
        %        @name dw ?
            endif
            codeseg
          endif
        endm
      Макрос создает подпрограмму, которая кладет на арифметический стек адрес зарезервированной памяти, из LEN ячеек. Если LEN не задана, то резервируется одна ячейка. Если задано значение параметра IMM, то создаются инициализированные данные.
      Что касается строк, то в Форте предусмотрены средства для работы с паскаль-строками, но я не стал реализовывать определяющие строки слова. Вместо этого создал отдельный макрос для объявления строк:
      ExpandedWrap disabled
        macro STRING name, imm:rest
          local @endstr
            $CODE <&name>
            @name CatStr <$$_>,??NAME
        %        push offset @name
                $NEXT
                align 2
        %    label @name byte
                db @endstr - @name - 1          ;; байт длины строки
                db &imm&
             label @endstr byte
        endm
      Это удобнее чем в Форте, так можно задавать строки с любым набором символов. Например:
      ExpandedWrap disabled
        STRING sAnyKey, "Press any key",13,10,"to continue",'$'
      В стандартном Форте пришлось бы выводить это сообщение в три приема.

      Практика

      Макросы готовы, ядро написано и заняло всего 2 Кб. Осталось проверить. Я не сторонник искусственных тестов, поэтому поискал нечто более комплексное. Не помню, откуда, но завалялись у меня старые исходники тетриса на Форте. Добавил цвет и получил годный тетрис под DosBox. Полностью приводить код не буду, он есть в аттаче, разве что небольшой кусочек для наглядности:
      ExpandedWrap disabled
        $COLN PLAY-GAME ; ( -- )
              _         BEGIN
              _             NEW-BRICK
              _             -1 3 INSERT-BRICK
              _             NEXT-DRAW
              _         WHILE
              _             BEGIN
              _                 4 0 DO
              _                     HELP.X HELP.Y GOTO-XY
              _                     KEY?
              _                     IF
              _                         BEGIN
              _                             KEY KEY?
              _                         WHILE
              _                             DROP
              _                         REPEAT
              _                         ON-KEY 0=         ; обрабатываем нажатия клавиш
              _                         IF
              _                             UNLOOP EXIT   ; прерываем игру
              _                         THEN
              _                     THEN
              _                     GAME.SPEED @ DELAY
              _                 LOOP
                                ; перемещаем фигуру вниз
              _                 1 0 MOVE-BRICK  0=
              _             UNTIL
                            ; фигура достигла "дна", проверяем и удаляем зап. линии
              _             PIT-CHECKFULL
              _             SHOW-SCORES
              _             CALCK-SPEED
              _         REPEAT
                        ; запрашиваем новую игру, или выходим
              _         sNew GET-INPUT NOT GAME.CONTINUE !
              _         EXIT
      Если бы не символ подчеркивания, ничем бы не отличалось от кода на «натуральном» Форте. Что собственно и требовалось.

      Заключение

      Понятно, что я далеко не первый и не последний, кто занимался «исследованиями» на тему возможностей макроязыка. Уже на заре увлечения ассемблером имел удовольствие ознакомиться с макробиблиотекой “Macro Language Version 2.0”. Просто из примера:
      ExpandedWrap disabled
         Include "system.h"
         
        Program ArrayDemo;
         
        Var
          array ARR1[10*20] of shortint;
          array ARR2 of integer = <10,9,8,7,6,5,4,3,2,1>;
          words i, j;
         
        Begin
          lea si,[ARR2];
          for i = 1 to 10 do
              asm lodsw  \  j = ax;
              WriteLn<'ARR2['>,i,<'] = '>,j;
          next i;
        End_
      Если кто-нибудь вам скажет, что это не ассемблер, плюньте ему в глаза © «Эл Стоу».
      Пожалуй, всё. К статье, помимо «Форта», прикладываю вышеупомянутую макробиблиотеку «Macro Language Version 2.0». Заодно выложу инклюды к Turbo Assembler. Начинал их еще AndNot, а я доработал и опробовал в нескольких небольших проектах, пока окончательно не перешел на Си. В них тоже есть немножко макросов, облегчающих жизнь при работе со строками – text, invoke и move. В качестве примера их использования приложу вывод фрактала папоротника, написанный по мотивам книги Никулина «Компьютерная геометрия и алгоритмы машинной графики». Пример в папке EXAMPLE, для компиляции нужно в батнике настроить пути к TASM’у. В папке TOOLS конвертеры инклюдов, детали в файле ‘READ.ME’.

      Прикреплённый файлПрикреплённый файлFORTH.ZIP (20,05 Кбайт, скачиваний: 219)
      Прикреплённый файлПрикреплённый файлINCLUDE.rar (192,22 Кбайт, скачиваний: 215)
      Прикреплённый файлПрикреплённый файлLIB.rar (57,77 Кбайт, скачиваний: 216)
      Прикреплённый файлПрикреплённый файлTASM_Macro_Language.part1.rar (195,31 Кбайт, скачиваний: 220)
      Прикреплённый файлПрикреплённый файлTASM_Macro_Language.part2.rar (195,31 Кбайт, скачиваний: 223)
      Прикреплённый файлПрикреплённый файлTASM_Macro_Language.part3.rar (152,77 Кбайт, скачиваний: 217)
        Дополню тему такой статьёй в применении ассемблера. (Fasm)
        Разместить FORTH в 512 байтах

        P.S. Проект MiniForth на Github
        0 пользователей читают эту тему (0 гостей и 0 скрытых пользователей)
        0 пользователей:


        Рейтинг@Mail.ru
        [ Script execution time: 0,0610 ]   [ 18 queries used ]   [ Generated: 19.03.24, 10:20 GMT ]