С помощью параметров макросы могут получать извне некоторые данные. Параметры указываются в объявлении макроса через запятую после слова macro
:
имя_макроса macro параметр1, параметр2, ... параметрN код макроса endm
При вызове макроса после его имени указываются через запятую значения для параметров:
имя_макроса значение_параметра1, значение_параметра2, ... значение_параметраN
Например, определим макроса, который складывает 128-разрядное число с 64-разрядным и оба числа получает через параметры:
.data num qword 0ffffffffffffffffh, ; младшие 64 бит 4 ; старшие 64 бит .code add128 macro l64, h64, number64 add l64, number64 adc h64, 0 endm main proc mov rax, num ; младшие 64 бит - в RAX mov rdx, num[8] ; старшие 64 бит - в RDX mov rbx, 3 add128 rax, rdx, rbx ; вставляем макрос ret main endp end
В данном случае макрос называется add128
и принимает три параметра: l64 (младшие 64 бита 129-разрядного числа), h64 (старшие 64 бита 129-разрядного числа), number64
(прибавляемое число)
add128 macro l64, h64, number64 add l64, number64 adc h64, 0 endm
Внутри макроса проводим все арифметические операции с числами. При этом на момент написания макроса мы можем не знать, как конкретно будут передаваться эти числа - через какие регистры и т.д.
При вызове макроса в процедуре main передаем его параметрам значения по позиции:
add128 rax, rdx, rbx
То есть параметру l64 передается значение регистра RAX, параметру h64 - значение регистра RDX, а параметру number64 - регистр RBX.
В качестве значений для параметров мы можем передавать в макросы регистры (как в случае выше), переменные, константы, главное, чтобы они удовлетворяли требованиям инструкций, с которыми они используются.
Вполне может быть ситуация, когда по какой-то причине макросу будет передано меньшее количество значений, чем у него есть параметров. К примеру, если мы используем макрос, который написан не нами и к исходному коду которого у нас нет доступа. Например:
add128 rax, rdx
В этом случае при компиляции ассемблер может нам отобразить какую-то общую ошибку, что что-то не так с вызовом макроса. Мы можем проверить подобную ситуацию и настроить сообщение об ошибке,
которое даст понимание, что именно не так. Для проверки параметров MASM предоставляет ряд специальных конструкций, которые аналогичны условной конструкции if
:
ifnb arg
: условие истинно, если значение для параметра не указано
ifdif arg1, arg2
: условие истинно, если оба параметра различаются (учитывается регистр символов)
ifdifi arg1, arg2
: условие истинно, если оба параметра различаются (регистр символов не учитывается)
ifidn arg1, arg2
: условие истинно, если оба параметра идентичны (учитывается регистр символов)
ifidni arg2, arg2
: условие истинно, если оба параметра идентичны (регистр символов не учитывается)
ifb arg
: условие истинно, если значение для параметра не указано. Фактически является сокращением выражения ifidn <arg>, <>
В данном случае arg
, arg1
, arg2
- значения, которые передаются операторам if
Проверим наличие значения для параметра:
.data num qword 0ffffffffffffffffh, ; младшие 64 бит 4 ; старшие 64 бит .code add128 macro l64, h64, number64 ; проверяем наличие параметра number64 ifb <number64> .err <add128 requires 3 operands> endif add l64, number64 adc h64, 0 endm main proc mov rax, num ; младшие 64 бит - в RAX mov rdx, num[8] ; старшие 64 бит - в RDX mov rbx, 3 add128 rax, rdx ; передаем только два значения ret main endp end
В коде макроса с помощью выражения
ifb <number64>
проверяем отсутствие значения для параметра number64. И если это значение отсутствует, выполняем выражение .err
.err <add128 requires 3 operands>
Оператору .err
в угловых скобках передается сообщение об ошибке, которое будет отображаться при компиляции при отстуствии значения для number64.
В качестве альтернативы проверки значения параметра мы можем использовать суффикс :req
, который указывает, что параметр обязательный (required):
add128 macro l64:req, h64:req, number64:req add l64, number64 adc h64, 0 endm
В этом случае, если параметру не будет передано значение, в процессе компиляции мы получим ошибку типа следующей:
hello.asm(14) : error A2125:missing macro argument
Другое решение проблемы отсутствующих параметров - установка для параметров значений по умолчанию:
.data num qword 0ffffffffffffffffh, ; младшие 64 бит 4 ; старшие 64 бит .code add128 macro l64:=<rax>, h64:=<rdx>, number64:=<0> add l64, number64 adc h64, 0 endm main proc mov rax, num ; младшие 64 бит - в RAX mov rdx, num[8] ; старшие 64 бит - в RDX mov rbx, 3 add128 ; без аргументов ret main endp end
Для установки значения по умолчанию с помощью оператора :=
параметру присваивается в угловых скобках значение по умолчанию. Например:
l64:=<rax>
В данном случае, если параметру l64 не передано значения, то он берет значение из регистра RAX. Аналогичным образом можно установить конкретные значения. Например, для параметра number64 значение по умолчанию - число 0:
number64:=<0>
Чтобы проверить, что именно представляет аргумент макроса, применяется оператор opattr:
opattr выражение
В качестве операнда он принимает некоторое выражение - это может быть и аргумент макроса, и какое-то более сложное по структуре выражение.
opattr
возвращает целочисленное значение, которые представляет битовую маску и которое позволяет определить тип выражения:
Бит | Значение | |
0 | Выражение представляет метку | |
1 | Выражение представляет перемещаемое значение | |
2 | Выражение представляет константу | |
3 | Выражение представляет прямую адресацию | |
4 | Выражение представляет регистр | |
5 | устравшее (выражение содержит неопределенный идентификатор) | |
6 | Выражение представляет обращение к памяти стека | |
7 | Выражение ссылается на внешний идентификатор |
Применим оператор opattr
:
.data num qword 0ffffffffffffffffh, ; младшие 64 бит 4 ; старшие 64 бит .code add128 macro l64, h64, number64 ; проверяем, что l64 и h64 - регистры if (((opattr l64) and 10h) AND ((opattr h64) and 10h)) add l64, number64 adc h64, 0 else .err <the first two operands of add128 should be registers> endif endm main proc mov rax, num ; младшие 64 бит - в RAX mov rdx, num[8] ; старшие 64 бит - в RDX mov rbx, 3 add128 rax, 4, 3 ; второй параметр должен быть регистром, а не константой ret main endp end
Здесь мы проверяем, что первые два параметра макроса - l64 и h64 являются регистрами. Если параметр - регистр, то выражение opattr l64
(как и opattr h64
)
возвратит байт, у которого установлен 4 бит. Чтобы проверить его установку, мы можем применить логическое умножение на 10h и ли число 16 в лесятичной системе
(opattr l64) and 10h
И если 4-й бит установлен, то выражение возвратит 1. Подобным образом проверяем и второй параметр макроса.
Если первые два параметра представляют регистры, то выполняем сложение, если нет - то генерируем ошибку с определенным сообщением.
Для проверки параметра можно использовать следующие маски:
0
: неопределенный символ или некорректное выражение
34 / 22h
: доступ к памяти в виде [reg + const]
36 / 24h
: константа
37 / 25h
: имя процедуры или метка с символом :
38 / 26h
: выражение в виде offset var
, где var
- переменная из секции .data
42 / 2Ah
: глобальный идентификатор (например, из секции .data
)
43 / 2Bh
: выражение доступа к памяти в виде [reg + label]
, где label
представляет имя процедуры или метка
48 / 30h
: регистр общего назначения, XMM, YMM, ZMM, ST, либо другой регистр специального назначения
98 / 62h
: обращение к стеку в виде [rsp + xxx]
и [rbp + xxx]
Подобным образом мы можем проверить и конктерный регистр:
add128 macro l64, h64, number64 ; проверяем, представляет ли l64 регистр RAX ifidn <l64>, <rax> ; действия, если регистр - RAX else ; действия, если регистр - не RAX endif endm