Низкоуровневое программирование для Дzenствующих

Вид материалаДокументы

Содержание


III.7. Символ макроподстановки
III.8. Макроблоки
MASM предусмотрены несколько специальных макроопределений, которые можно назвать макроблоками. Первый из них FOR
С конструкцию FOR
MASM поддерживал бы такой тип макропеременных как массив. Но хотя MASM
REPT выполняется столько раз, сколько указано в nparams
FORC выполняется столько раз, сколько символов в строке string
Endif endm
III.9. Отладка макроопределений и заключение
III.10. Абстрактный алгоритм анализа строки MASM (Дополнение)
Подобный материал:
1   ...   34   35   36   37   38   39   40   41   42

III.7. Символ макроподстановки


Ещё раз вернёмся к формальным параметрам макро. Как было сказано, при раскрытии макроопределения препроцессор заменяет в теле макро формальные названия на их величины. В MASM32 предусмотрено ещё одно средство подстановки макропараметров – внутри строкового литерала.

Предположим нам нужно, чтобы макро генерировал строку: «label_xx». Где xx – это формальный параметр макро. Это можно сделать двумя способами:

@CatStr(label_,xx) ;;Вызов макрофункции конкантенации строк

или
label_&xx& ;;Использование символа макроподстановки

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

Следует отметить, что если макропараметр начинает или заканчивает литерал, то можно использовать только один символ «&»:

label_&xx
;;или ещё пример
label_&xx&&xx2 ;; Замена для двух макропараметров xx и xx2

 

III.8. Макроблоки


И, наконец, у читателя должен остаться единственный вопрос: «А как обрабатывать переменные типа VARARG»? Например, рассмотрим возможный макро для вызова функций – STDCALL:

stdcall macro funname,params:VARARG


endm

Этот макро должен генерировать код вызова функции согласно конвенции STDCALL:
  1. Поместить параметры в стек в обратном порядке их определению.
  2. Вызвать функцию funname, предварительно видоизменив её имя по правилам STDCALL.

Получить видоизмененное имя функции по значению параметра funname можно было бы при помощи символа макроподстановки.

call _&funname@(количество параметров * 4)

Но непонятно, как распознать параметры функции, которые представляют собой строку, где значения разделены символом «,». Более того, не понятно, как вообще можно получить эти параметры, и посчитать их число, ведь макропараметр params – это одна строка. То есть при вызове макро:

stdcall win32fun,1,2,3

Мы должны как-то определить количество параметров, а потом их значения.

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

Первый из них FOR позволяет получить значения элементов, разделённых в строке символом «,».

FOR parameter[:REQ | :=default], string
statements
ENDM

Вспоминая С конструкцию FOR, вы сразу поймёте что это цикл, где значение parameter последовательно принимает значения элементов списка string.

Вот вам wonderful пример:

FOR parameter,
echo parameter
ENDM
ВЫВОД:
-=-=-=-=-=-=
It's
wonderful
wonderful
asm
-=-=-=--=-=-

А вот пример макрофункции, который подсчитывает число аргументов VARARG:

@ArgCount MACRO parmlist:VARARG
count = 0
FOR param,

count = count + 1
ENDM
EXITM count
ENDM

Вот в принципе, уже на основе этих знаний можно было бы организовать макрос stdcall:


stdcall macro funname,params:VARARG
count = 0
FOR param,

count = count + 1 ;; Считаем число параметров
push param ;; Помещаем их в стек
ENDM
;;Вызываем функцию
call ??? ;;А вот как это сделать?
endm

Ещё несколько минут необходимо для того, чтобы понять, что этот макро работает неправильно. Хотя бы потому, что параметры помещаются в стек не так. Нужно было бы помещать их от последнего к первому, а не от первого к последнему. А, кроме того, ведь символ макроподстановки нельзя употреблять к макропеременной count, потому что это не макропараметр, это макропеременная.

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

Вторую проблему можно легко решить, воспользовавшись макрофункцией конкатенации строк:

call @CatStr(_,funname,@,%(count*4))

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

count = 0
FOR param,

count = count + 1 ;; Считаем число параметров
@CatStr(var,%count) TEXTEQU

ENDM

Как вы можете догадаться, в этом примере создаются макропеременные varXX, которым присваиваются значения параметров. Теперь с той же лёгкостью можно работать с этими переменными. Можно снова использовать цикл FOR, однако в данном случае, было бы грамотней воспользоваться значением count, и выполнить цикл столько раз, сколько записано в нашем счётчике параметров. Для этого мы воспользуемся ещё одним макроблоком rept, о котором скажем позже:

nparams = count


REPT nparams ;; Начало блока

push @CatStr(var,%count)

count = count - 1

ENDM

Блок REPT выполняется столько раз, сколько указано в nparams. Я ввёл эту дополнительную макропеременную, для того, чтобы значение, указанное в REPT осталось неизменным. Однако этого не нужно. Можно было бы написать и так:

REPT count ;; Начало блока
push @CatStr(var,%count)
count = count - 1
ENDM

Значение макропеременной count инициализирует цикл только один раз вначале, после чего, она может, как угодно менять значение.

И ещё один макроблок, без которого нам невозможно будет реализовать макрос для определения строк уникода, или макрос, который позволяет писать строки OEM в редакторе использующий кодировку win cp-1251 (например, при создании консольных приложений).

Этот макроблок FORC:

FORC char, string
;;блок
ENDM

Блок FORC выполняется столько раз, сколько символов в строке string, при этом макропараметр char равен текущему символу из строки.
Например, посчитать количество символов в строке можно было бы так:

count = 0
FORC char, <Сколько тут символов?>
count = count + 1
ENDM
%echo @CatStr(%count)

А вот так, можно было бы посчитать количество пробельных символов.

count = 0
FORC char, <Сколько тут символов?>
IFB
count = count + 1
ENDIF
ENDM
%echo @CatStr(%count)

Упражнение:

TheSvin'у, как и любому программисту, который часто имеет дело с битами, было бы удобно записывать значения бит по группам, через пробел.

;;Вот так неудобно и ненаглядно

mov eax,011110111011b

;;Вот так удобно и наглядно, но компилятор выдаст ошибку

;;Вариант1

mov eax, 0111 101 1101 1b

;;А вот так вообще замечательно, только ML неправильно поймёт

;;Вариант2

mov eax, [0111] [101] [1101] [1]b

Хорошо бы было написать некую макрофункцию, которая смогла бы позволить записывать эти выражения:

mov eax,nf(0111 101 1101 1b)

Напишите такую макрофункцию, которая позволила бы это делать. Напишите её для первого и второго вариантов исполнения.

 

III.9. Отладка макроопределений и заключение


А напоследок… остаётся маленькая деталь.

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

.\start.asm(84) : error A2008: syntax error : in directive
MacroLoop(3): iteration 8: Macro Called From
.\start.asm(84): Main Line Code

То есть он выдаёт относительную строку в макро MacroLoop(3), где эта ошибка появилась. А если ещё макровызовы будут вложенными, то вам лучше не видеть этой замечательной картины.

Единственной возможностью качественно и относительно легко отлаживать макро – это употребление директивы echo.

На протяжении статьи вы не раз наблюдали примеры её использования. Но я снова повторюсь:

;; Для макропараметров
echo macroparam
;; Для макропеременных типа строка или текстовых макро
%echo macrovar_string
;; Для целочисленных макропеременных, или макроконстант
%echo @CatStr(%macro_num)

Заметьте, чтобы вывести значение целочисленной макропеременной необходимо воспользоваться макрофункцией @CatStr(), и перед аргументом указать оператор %. Почему именно так обсуждалась в пункте III.2. Определение макро переменных и строк.

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

 

III.10. Абстрактный алгоритм анализа строки MASM (Дополнение)


1. Определены таблицы элементов:

Таблица переменных

Хранит сведения о всех переменных модуля

Таблица меток

Хранит список меток в коде.

Таблица процедур

Хранит таблицу и прототип процедур

Список ключевых слов KEYLIST

Хранит список ключевых слов, на которые реагирует ML

Таблица макрофункций

Хранит тело всех макро, их имена и тип: макрофункция, или макро. Список макропараметров

Таблица макросов

-=-

Таблица макропеременных, или переменных времени компиляции

Хранит тип макропеременной и её значение.

Всё остальное, что не включено

 

2. Начальное состояние анализа строки.

3. Читать поток символов, пока не встретится символ возврата каретки без предыдущего символа «/». Игнорировать часть строки после «;»


a. Определить наличие лексем первого уровня в строке:

i. Выделить все строковые литералы в кавычках, если только это не выражение с TEXTEQU и символ комментария «;»
ii. Строковые литералы: <текст>
iii. Численные литералы: 1234, 1234h, 01011b
iv. Правильные литералы: строка из символов «A-Z,a-z,_0-9», но не начинающаяся на цифру
v. Литералы разделители: «,.»
vi. Управляющие Литералы: «+-*» Правильные литералы: строка из символов «A-Z,a-z,_0-9», но не начинающаяся на цифру


b. Проверить правильные литералы на совпадение в списке ключевых слов, и определить схему выражения. В зависимости от схемы выражения, выполнить или пропустить:

i. Проверить правильные литералы на совпадение в списке макро (в зависимости от способа вызова в списке макрофункций, или макросов)
ii. Проверить на наличие имени правильного литерала в таблице макропеременных.
iii. Осуществить вызов и замену макро и макропеременных, в соответствии с выражением строки.
iv. Вычислить все выражения допустимые в ML (+-*).


c. Осуществить разбор схемы.


i. Если это определение процедуры, записать в таблице процедур имя и прототип новой процедуры
ii. Если это макроопределение: анализировать его тело. Если найден возвращаемый параметр, записать макроопределение в таблицу макрофункций, иначе в таблицу макросов.
iii. Если это определение EQU вычислить правую часть.


1. Если эта макропеременная уже есть в таблице макропеременных, и её тип – числовой, выдать ошибку. Если эта макропеременная имеет строковый тип, изменить строку, на которую указывает свойство value этой макропеременной.
2. Если правая часть числовой литерал – записать EQU определение в таблицу, и пометить его тип как числовой константы. Записать в свойство макропеременной value значение указателя на строку. Записать свойство value равным числу.
3. иначе EQU – переменная, имеющая указатель на строку. Записать в значения свойства value указатель на строку.


iv. Если это выражение с «=» или подобное, выполнить замену всех литералов на макроконстанты, переменные, вызов всех макрофункций, и только потом выполнять выражение.

4. Перейти к анализу следующей строки.


# Эпилог


 

Когда большая стрелка часов перекотила через 12-ти часовой рубеж, появился свет...

 


###########################################################################