Наши проекты:
Журнал · Discuz!ML · Wiki · DRKB · Помощь проекту |
||
ПРАВИЛА | FAQ | Помощь | Поиск | Участники | Календарь | Избранное | RSS |
[44.213.80.174] |
|
Сообщ.
#1
,
|
|
|
Некоторые возможности макроязыка TASM. Введение. Данная статья не является учебником по макроязыку Turbo Assembler, скорее это забавный пример его практического использования, предполагающий знания основ макроязыка. Когда-то мне понадобилось защитить часть кода загрузчика CD-ROM. Вариант шифрования кода и данных отбросил сразу, поскольку аналогичная защита на диске «Reanimator 2K» была снята мной менее чем за день. И тогда вспомнил про старый добрый Форт, с его прямым шитым кодом, который и без шифрования создает трудный для понимания код. Вот его мы и создадим, используя макроязык Turbo Assembler. Нам понадобится TASM последней версии, то есть 5.3. Есть еще версия 5.4, но она отличается от предыдущей только копирайтом. Имена. Основная проблема, которую нужно решить, заключается в том, что в Форте для имён допустимы любые символы, включая те, что TASM запрещает использовать в именах меток и идентификаторов. Вдобавок, имена в Форте могут начинаться с цифры. Поэтому, строку вида: >R R@ R> ; _ >R R@ R> EXIT Первым делом необходимо изъять из строки некоторые спецсимволы, которые, которые макроязык ассемблера интерпретирует особым образом. Это символы ‘>’, ‘<’ и ‘!’. Для начала определим макрос подмены одного символа в строке: 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 Теперь можно написать макрос предварительной обработки строки: macro $PRENAME name: rest ??NAME equ &name& $SWAP less, << $SWAP more, > $SWAP save, ! endm macro $POSTNAME name ??NAME equ &name& $SWAP plus, + $SWAP minus, - $SWAP mul, * $SWAP eq, = $SWAP div, / $SWAP dot, . $SWAP tld, ~ endm 54 -2 * ‘*’ ! 54 minus2 mul ‘mul’ save 54 -2 * ‘*’ save mul save Поскольку числа-константы, символы и подпрограммы компилируются разными способами, нужен макрос для их распознавания. В макроязыке нет предусмотренных средств для определения типа идентификатора, а что есть – не работает. Придется сделать макрос идентификации чисел и символов самостоятельно: 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 Компиляция кода Теперь можно приступать к макросу компиляции Форт-строки. Цель макроса – разбить строку на отдельные слова, идентифицировать их и скомпилировать в шитый код. Сам макрос выглядит проще некуда: macro _ listcmd: rest $PRENAME listcmd % $parsewords ??NAME endm Макрос разбивки строки на слова вполне стандартен: macro $parsewords name: vararg irp @i, <name> $COMPILE @i endm endm macro $COMPILE name $ISNUM <&name> if ??ISNUM dw _LIT dw &name else $POSTNAME name % $COMPILENAME ??NAME endif endm macro $COMPILENAME name ifdef _&name&_F _&name ;; немедленно выполняем слово else dw _&name ;; просто компилируем слово в код endif endm Подпрограммы Настало время определиться со стеками и регистрами. Стеков будет два, арифметический и возвратов. На верхушку первого будет указывать регистр SP, а второй реализуем через регистр BP. Регистр SI задействуем в качестве некоего аналога IP, он будет указывать на адрес следующей исполняемой подпрограммы. Регистры CS, DS и SS должны быть настроены на один сегмент. Остальные регистры свободны. Подпрограммы бывают двух видов – высокого и низкого уровня. Начнем с простейших. Низкоуровневые подпрограммы – это почти обычные ассемблерные вставки. Для их объявления достаточно преобразовать имя в допустимый вид и объявить метку: macro $CODE cname: rest $PRENAME cname % $POSTNAME ??NAME $LABEL <%??NAME> endm macro $LABEL name _&name&: endm macro $NEXT lodsw jmp ax endm $CODE >R ; ( x -- )(R: -- x ) переносит x с ариф. стека на стек возвратов pop ax xchg bp, sp push ax xchg bp, sp $NEXT Подпрограммы высокого уровня реализуются прямым шитым кодом. Это значит, что для их выполнения нужна специальная функция. Назовем её DOLST. В качестве аргумента ей передается адрес подпрограммы высокого уровня, которую она и запустит на выполнение: $CODE DOLST ; ( addr -- ) xchg bp, sp push si xchg bp, sp pop si $NEXT $CODE EXIT mov si, [bp] add bp, 2 $NEXT Теперь можно написать макрос для объявления подпрограмм высокого уровня: macro $COLN cname: rest $CODE cname call _DOLST endm $COLN NEG ; ( n -- -n ) n = -n _ NOT 1+ EXIT 03C5 _NEG: 03C5 E8 FD93 call _DOLST 03C8 0539r dw _NOT 03CA 03F2r dw _1plus 03CC 011Dr dw _EXIT Подпрограммы немедленного исполнения Как уже упоминалось ранее, для реализации ветвления кода и циклов необходимы подпрограммы немедленного исполнения, так называемые immediate. Все дело в том, что еще на этапе генерации кода необходимо расставить команды переходов на код, который еще даже не объявлен. Возьмем для примера простейшую конструкцию: IF – ELSE - THEN (так в Форте называется ENDIF). IF if (param == FALSE) then GOTO (ELSE or THEN) <code> jmp THEN [ELSE] <code> THEN Список меток организуем по принципу стека, последним пришел – первым ушел. Всего нам понадобится три макроса, $PUSH – для генерации новой метки и помещению ее в список, $POP – соответственно для извлечения метки из списка, и $TOP – для получения метки со списка, без ее извлечения из этого списка. Примерно так: 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 Теперь непосредственно сами макрокоманды условия. Первым делом объявляем их IMMEDIATE: IMMEDIATE = 1 ; флаг слов немедленного исполнения _IF_F = IMMEDIATE _THEN_F = IMMEDIATE _ELSE_F = IMMEDIATE macro _IF ; ( bool -- ) $PUSH <??LABEL> dw _?BRANCH % dw ??LABEL endm Макрос завершения тела условия: macro _THEN $POP <??LABEL> % ??LABEL: endm Макрос альтернативного выбора: macro _ELSE $POP <??LABEL> $PUSH <??NEWLAB> dw _BRANCH % dw ??NEWLAB % ??LABEL: endm Похожим образом строятся конструкции CASE – OF – ENDCASE, а также конструкции циклов BEGIN – UNTIL, BEGIN – WHILE – REPEAT, DO – LOOP. Приводить их реализацию не буду, все есть во вложении. Данные В Форте нет переменных и констант, в том виде, что есть в других языках программирования. Любая переменная или константа - это подпрограмма. При вызове переменной, она оставляет на стеке адрес связанной с ней ячейки памяти, куда можно что-то записать, или считать из нее. В отличие от переменной, константа оставляет на стеке не адрес ячейки, а её значение. При желании, значение константы можно менять по ходу выполнения программы, хотя вряд ли в этом есть смысл. В большинстве реализаций Форта для создания переменных и констант предусмотрены слова VARIABLE и CONSTANT. Не будем и мы отбиваться от большинства. Начнем с констант: macro CONSTANT name, imm:req $CODE <&name> push &imm $NEXT endm При объявлении переменной нужно учесть возможность зарезервировать место под массив, а так же возможность объявления инициированных данных: 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 Что касается строк, то в Форте предусмотрены средства для работы с паскаль-строками, но я не стал реализовывать определяющие строки слова. Вместо этого создал отдельный макрос для объявления строк: 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 STRING sAnyKey, "Press any key",13,10,"to continue",'$' Практика Макросы готовы, ядро написано и заняло всего 2 Кб. Осталось проверить. Я не сторонник искусственных тестов, поэтому поискал нечто более комплексное. Не помню, откуда, но завалялись у меня старые исходники тетриса на Форте. Добавил цвет и получил годный тетрис под DosBox. Полностью приводить код не буду, он есть в аттаче, разве что небольшой кусочек для наглядности: $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”. Просто из примера: 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) |
Сообщ.
#2
,
|
|
|
Дополню тему такой статьёй в применении ассемблера. (Fasm)
Разместить FORTH в 512 байтах P.S. Проект MiniForth на Github |