MASM Macros
Chapter 13
MASM is actually two languages rolled into a single program. The runtime language is the standard x86-64/MASM assembly language you’ve been reading about in all the previous chapters. This is called the runtime language because the programs you write execute when you run the executable file. MASM contains an interpreter for a second language, the MASM compile-time language (CTL). MASM executes the CTL program during assembly (compilation). Once MASM completes assembly, the CTL program terminates.

The CTL application is not a part of the runtime executable that MASM emits, although the CTL application can write part of the runtime program for you. By learning how to use the MASM CTL and applying it properly, you can develop assembly language applications as rapidly as high-level language applications.
Compile-Time Expressions and Operators
Constants and variables
You declare compile-time constants by using the textequ or equ directives. You declare compile-time variables by using the "=" directive.
inc_by equ 1
ctlVar = 0
ctlVar = ctlVar + inc_by
The MASM Escape (!) Operator
The first operator is the ! operator. When placed in front of another symbol, this operator tells MASM to treat that character as text rather than as a special symbol.
The MASM Evaluation (%) Operator
The second useful CTL operator is %. The percent operator causes MASM to evaluate the expression following it and replace that expression with its value.
num10 = 10
text10 textequ <10>
tn11 textequ %num10 + 1
The catstr Directive
The catstr function has the following syntax:
identifier catstr string1, string2, ...
; example
helloWorld catstr <Hello>, <, >, <World!!>
The identifier is an (up to this point) undefined symbol. The string1 and string2 operands are textual data surrounded by < and > symbols. This statement stores the concatenation of the two strings into identifier.
The echo and .err Directives
; CTL "Hello World" program
echo Listing 13-1: Hello, World!
end
The echo statement displays the textual representation of its argument list during the assembly of a MASM program. The above program will show -
Listing 13-1: Hello, world!
%out is a synonym for echo (just in case you see %out in any MASM source files)
The .err directive, like echo, will display a string to the console during assembly, though this must be a text string (delimited by < and >). The .err statement displays the text as part of a MASM error diagnostic.
Furthermore, the .err statement increments the error count, and this will cause MASM to stop the assembly (without assembling or linking) after processing the current source file.
.err <Error message here>
The instr Directive
The instr directive searches for the presence of one string within another.
identifier instr start, source, search
; example
WorldPosn instr 1, <Hello World>, <World>
where identifier is a symbol into which MASM will put the offset of the search string within the source string. The search begins at position start within source. Unconventionally, the first character in source has the position 1 (not 0).
The sizestr Directive
The sizestr directive computes the length of a string
identifier sizestr string
; example
hwLen sizestr <Hello World>
where identifier is the symbol into which MASM will store the string’s length, and string is the string literal whose length this directive computes.
The substr Directive
The substr directive extracts a substring from a larger string
identifier substr source, start, len
; example
hString substr <Hello World>, 1, 5
where identifier is the symbol that MASM will create (type TEXT, initialized with the substring characters), source is the source string from which MASM will extract the substring, start is the starting position in the string to begin the extraction, and len is the length of the substring to extract.
The len operand is optional; if it is absent, MASM will assume you want to use the remainder of the string.
Conditional And Repetitive Assembly
Conditional Assembly
The simplest form of the MASM compile-time if statement uses the following syntax:
if constant_boolean_expression
Text
endif
The expression must be a constant expression that evaluates to an integer value.
The identifiers in a compile-time expression must all be constant identifiers or a MASM compile-time function call (with appropriate parameters). Because MASM evaluates these expressions at assembly time, they cannot contain runtime variables.
The MASM if statement supports optional elseif and else clauses that behave in an intuitive fashion. The complete syntax for the if statement looks like the following:
if constant_boolean_expression1
Text
elseif constant_boolean_expression2
Text
else
Text
endif
Repetitive Assembly
MASM’s while..endm, for..endm, and forc..endm statements provide compiletime loop constructs. The while statement tells MASM to process the same sequence of statements repetitively during assembly. This is handy for constructing data tables as well as providing a traditional looping structure for compile-time programs.
while constant_boolean_expression
Text
endm
To understand how it works, look at the program below
; CTL while loop demonstration program
option casemap:none
nl = 10
.data
ary dword 2, 3, 5, 8, 13
include print.inc
.code
; Here is the "asmMain" function.
public asmMain
asmMain proc
push rbx
push rbp
mov rbp, rsp
sub rsp, 56 ;Shadow storage
i = 0
while i LT 5
mov edx, i ;This is a constant!
mov r8d, ary[ i*4 ] ;Index is a constant
call print
byte "array[ %d ] = %d", nl, 0
i = i + 1
endm
allDone: leave
pop rbx
ret ;Returns to caller
asmMain endp
end
MASM provides two forms of the for..endm loop. These two loops take the following general form:
for identifier, <arg1, arg2, ..., argn>
.
.
.
endm
forc identifier, <string>
.
.
.
endm
The first form of the for loop (plain for) repeats the code once for each of the arguments specified between the < and > brackets. On each repetition of the loop, it sets identifier to the text of the current argument: on the first iteration of the loop, identifier is set to arg1, and on the second iteration it is set to arg2, and so on, until the last iteration, when it is set to argn.
The forc compile-time loop repeats the body of its loop for each character appearing in the string specified by the second argument.
Macros
Standard Macros
MASM supports a straightforward macro facility that lets you define macros in a manner that is similar to declaring a procedure. A typical, simple macro declaration takes the following form:
macro_name macro arguments
Macro body
endm
To execute the code associated with a macro, you specify the macro’s name at the point you want to execute these instructions.
mov rax, qword ptr i128
mov rdx, qword ptr i128[8]
; calling the macro
macro_name
The difference between the macro invocation versus the procedure call is that macros expand their text inline, whereas a procedure call emits a call to the corresponding procedure elsewhere in the text.
You should choose macro versus procedure call based on efficiency. Macros are slightly faster than procedure calls because you don’t execute the call and corresponding ret instructions, but they can make your program larger because a macro invocation expands to the text of the macro’s body on each invocation.
Macros have many other disadvantages over procedures. Macros cannot have local (automatic) variables, macro parameters work differently than procedure parameters, macros don’t support (runtime) recursion, and macros are a little more difficult to debug than procedures (just to name a few disadvantages). Therefore, you shouldn’t really use macros as a substitute for procedures except when performance is absolutely critical.
Macro Parameters
Macro parameter declaration syntax is straightforward. You supply a list of parameter names as the operands in a macro declaration:
neg128 macro reg64HO, reg64LO
neg reg64HO
neg reg64LO
sbb reg64HO, 0
end
When you invoke a macro, you supply the actual parameters as arguments to the macro invocation:
neg128 rdx, rax
A macro invocation of the form neg128 rdx, rax is equivalent to the following:
reg64HO textequ <rdx>
reg64LO textequ <rax>
neg reg64HO
neg reg64LO
sbb reg64HO, 0
We use the < and > brackets to treat anything inside them as a single string.
chkError macro instr, jump, target
instr
jump target
endm
chkError <cmp eax, 0>, jnl, RangeError ; Example 1
.
.
.
When passing numeric parameters that may contain compile-time expressions, surround them in the macro with parentheses.
Echo2nTimes macro n, theStr
echoCnt = 0
while echoCnt LT (n) * 2
If you don’t have control over the macro definition (perhaps it’s part of a library module you use, and you can’t change the macro definition because doing so could break existing code), there is another solution to this problem: use the MASM % operator before the argument in the macro invocation so that the CTL interpreter evaluates the expression before expanding the parameters. For example:
Echo2nTimes %3 + 1, "Hello"
Optional and Required Macro Parameters
As a general rule, MASM treats macro arguments as optional arguments. If you define a macro that specifies two arguments and invoke that argument with only one argument, MASM will not (normally) complain about the invocation. Instead, it will simply substitute the empty string for the expansion of the second argument.
However, suppose you left off the second parameter in the neg128 macro given earlier. That would compile to a neg instruction with a missing operand and MASM would report an error
neg128 macro arg1, arg2 ; Line 6
neg arg1 ; Line 7
neg arg2 ; Line 8
sbb arg1, 0 ; Line 9
endm ; Line 10
; Line 11
neg128 rdx ; Line
MASM solves this issue by introducing several additional conditional if statements intended for use with text operands and macro arguments.

Example use of ifb:
neg128 macro reg64HO, reg64LO
ifb <reg64LO>
.err <neg128 requires 2 operands>
endif
neg reg64HO
neg reg64LO
sbb reg64HO, 0
endm
The neg128 macro complete code:
; neg128 macro
option casemap:none
nl = 10
.const
neg128 macro reg64HO, reg64LO
ifb <reg64LO>
.err "neg128 requires 2 operands"
endif
neg reg64HO
neg reg64LO
sbb reg64HO, 0
endm
include print.inc
.code
; Here is the "asmMain" function.
public asmMain
asmMain proc
push rbx
push rbp
mov rbp, rsp
sub rsp, 56 ;Shadow storage
mov rdx, 12345678h
mov rax, 90123456h
neg128 rdx, rax
allDone: leave
pop rbx
ret ;Returns to caller
asmMain endp
end
A different way to handle missing macro arguments is to explicitly tell MASM that an argument is required with the :req suffix on the macro definition line.
neg128 macro reg64HO:req, reg64LO:req
neg reg64HO
neg reg64LO
sbb reg64HO, 0
endm
Default Macro Parameter Values
One way to handle missing macro arguments is to define default values for those arguments.
neg128 macro reg64HO:=<rdx>, reg64LO:=<rax>
neg reg64HO
neg reg64LO
sbb reg64HO, 0
endm
The ":=" operator tells MASM to substitute the text constant to the right of the operator for the associated macro argument if an actual value is not present on the macro invocation.
Macros with a Variable Number of Parameters
It is possible to tell MASM to allow a variable number of arguments in a macro invocation:
varParms macro varying:vararg
Macro body
endm
.
.
.
varParms 1
varParms 1, 2
varParms 1, 2, 3
varParms
A macro can have, at most, one vararg parameter. If a macro has more than one parameter and also has a vararg parameter, the vararg parameter must be the last argument.
The Macro Expansion (&) Operator
Inside a macro, you can use the & operator to replace a macro name (or other text symbol) with its actual value. This operator is active anywhere, even with string literals.
expand macro parm
byte '&parm', 0
endm
.data
expand a
If, for some reason, you need the string '&parm' to be emitted within a macro (that has parm as one of its parameters), you will have to work around the expansion operator.
expand macro parm
byte '&', 'parm', 0
endm
Local Symbols in a macro
Local macro symbols are unique to a specific invocation of a macro. You must explicitly tell MASM which symbols must be local by using the local directive:
macro_name macro optional_parameters
local list_of_local_names
Macro body
endm
; example
jzc macro target
local NotTarget
jnz NotTarget
jc target
NotTarget:
endm
The exitm Directive
The MASM exitm directive tells MASM to immediately terminate the processing of the macro. MASM will ignore any additional lines of text within the macro.
The exitm directive is useful in a conditional assembly sequence. Perhaps after checking for the presence (or absence) of certain macro arguments, you might want to stop processing the macro to avoid additional errors from MASM
neg128 macro reg64HO, reg64LO
ifb <reg64LO>
.err <neg128 requires 2 operands>
exitm
endif
neg reg64HO
neg reg64LO
sbb reg64HO, 0
endm
MASM Macro Function Syntax
Today, MASM supports additional syntax that allows you to create macro functions. A MASM macro function definition looks exactly like a normal macro definition with one addition: you use an exitm directive with a textual argument to return a function result from the macro.
; CTL while loop demonstration program
option casemap:none
nl = 10
; upperCase macro function.
;
; Converts text argument to a string, converting
; all lower-case characters to uppercase.
upperCase macro theString
local resultString, thisChar, sep
resultStr equ <> ;Initialize function result with ""
sep textequ <> ;Initialize separator char with ""
forc curChar, theString
; Check to see if the character is lower case.
; Convert it to upper case if it is, otherwise
; output it to resultStr as-is. Concatenate the
; current character to the end of the result string
; (with a ", " separator, if this isn�t the first
; character appended to resultStr).
if ('&curChar' GE 'a') and ('&curChar' LE 'z')
resultStr catstr resultStr, sep, %'&curChar'-32
else
resultStr catstr resultStr, sep, %'&curChar'
endif
; First time through, sep is the empty string, for all
; other iterations, sep is the comma separator between
; values.
sep textequ <, >
endm ;End for
exitm <resultStr>
endm ;End macro
; Demonstratoin of the upperCase macro function
.data
chars byte "Demonstration of upperCase "
byte "macro function:"
byte upperCase( <abcdEFG123> ), nl, 0
.code
externdef printf:proc
; Here is the "asmMain" function.
public asmMain
asmMain proc
push rbx
push rbp
mov rbp, rsp
sub rsp, 56 ;Shadow storage
lea rcx, chars
call printf
allDone: leave
pop rbx
ret ;Returns to caller
asmMain endp
end
Whenever you invoke a MASM macro function, you must always follow the macro name with a pair of parentheses enclosing the macro’s arguments. Even if the macro has no arguments, an empty pair of parentheses must be present. This is how MASM differentiates standard macros and macro functions.
Writing Compile-Time “Programs”
The MASM compile-time language allows you to write short programs that write other programs—in particular, to automate the creation of large or complex assembly language sequences.
Constructing Data Tables at Compile Time
One common use for the compile-time language is to build ASCII character lookup tables for alphabetic case manipulation with the xlat instruction at runtime.
Note the use of a macro as a compiletime procedure to reduce the complexity of the table-generating code.
; Creating lookup tables with macros
option casemap:none
nl = 10
.const
fmtStr1 byte "testString converted to UC:", nl
byte "%s", nl, 0
fmtStr2 byte "testString converted to LC:", nl
byte "%s", nl, 0
testString byte "This is a test string ", nl
byte "Containing UPPER CASE ", nl
byte "and lower case chars", nl, 0
emitChRange macro start, last
local index, resultStr
index = start
while index lt last
byte index
index = index + 1
endm
endm
; Lookup table that will convert lowercase
; characters to uppercase. The byte at each
; index contains the value of that index,
; except for the bytes at indexes 'a'..'z'.
; Those bytes contain the values 'A'..'Z'.
; Therefore, if a program uses an ASCII
; character's numeric value as an index
; into this table and retrieves that byte,
; it will convert the character to upper
; case.
lcToUC equ this byte
emitChRange 0, 'a'
emitChRange 'A', %'Z'+1
emitChRange %'z'+1, 0ffh
; As above, but this table converts upper
; case to lower case characters.
UCTolc equ this byte
emitChRange 0, 'A'
emitChRange 'a', %'z'+1
emitChRange %'Z'+1, 0ffh
.data
; Store the destination strings here:
toUC byte 256 dup (0)
TOlc byte 256 dup (0)
.code
externdef printf:proc
; Here is the "asmMain" function.
public asmMain
asmMain proc
push rbx
push rdi
push rsi
push rbp
mov rbp, rsp
sub rsp, 56 ;Shadow storage
; Convert the characters in testString to Uppercase
lea rbx, lcToUC
lea rsi, testString
lea rdi, toUC
jmp getUC
toUCLp: xlat
mov [rdi], al
inc rsi
inc rdi
getUC: mov al, [rsi]
cmp al, 0
jne toUCLp
; Display the converted string
lea rcx, fmtStr1
lea rdx, toUC
call printf
; Convert the characters in testString to lowercase
lea rbx, UCTolc
lea rsi, testString
lea rdi, TOlc
jmp getLC
toLCLp: xlat
mov [rdi], al
inc rsi
inc rdi
getLC: mov al, [rsi]
cmp al, 0
jne toLCLp
; Display the converted string
lea rcx, fmtStr2
lea rdx, TOlc
call printf
allDone: leave
pop rsi
pop rdi
pop rbx
ret ;Returns to caller
asmMain endp
end
Unrolling Loops
With a small amount of extra typing plus one copy of the loop body, you can unroll a loop as many times as you please. If you simply want to repeat the same code sequence a certain number of times, unrolling the code is especially trivial. All you have to do is wrap a MASM while..endm loop around the sequence and count off the specified number of iterations.
For example, if you wanted to print Hello World 10 times, you could encode this as follows:
count = 0
while count LT 10
call print
byte "Hello World", nl, 0
count = count + 1
endm
Using Macros to Write Macros
One advanced use of macros is to have a macro invocation create one or more new macros. If you nest a macro declaration inside another macro, invoking that (enclosing) macro will expand the enclosed macro definition and define that macro at that point.
Below is an example of a macro writing a macro to initialize a compile-time array
option casemap:none
genArray macro arrayName, elements
local index, eleName, getName
; Loop over each element of the array:
index = 0
while index lt &elements
; Generate a textequ statement to define a single
; element of the array, e.g.,
;
; aryXX = 0
;
; where "XX" is the index (0..(elements-1))
eleName catstr <&arrayName>,%index,< = 0>
; Expand the text just created with the catstr directive
eleName
; Move on to next array index:
index = index + 1
endm ;while
; Create a macro function to retrieve a value from
; the array:
getName catstr <&arrayName>,<_get>
getName macro theIndex
local element
element catstr <&arrayName>,%theIndex
exitm <element>
endm
; Create a macro to assign a value to
; an array element
setName catstr <&arrayName>,<_set>
setName macro theIndex, theValue
local assign
assign catstr <&arrayName>, %theIndex, < = >, %theValue
assign
endm
endm ;genArray
; mout-
;
; Replacement for echo. Allows "%" operator
; in operand field to expand text symbols.
mout macro valToPrint
local cmd
cmd catstr <echo >, <valToPrint>
cmd
endm
; Create an array ("ary") with ten elements:
genArray ary, 10
; Initialize each element of the array to
; its index value:
index = 0
while index lt 10
ary_set index, index
index = index + 1
endm
; Print out the array values:
index = 0
while index lt 10
value = ary_get(index)
mout ary[%index] = %value
index = index + 1
endm
end
Simulating HLL Procedure Calls
Macros provide a good mechanism to call procedures and functions in a high-level-like manner.
HLL-Like Calls with No Parameters
Of course, the most trivial example is a call to an assembly language procedure that has no arguments at all:
someProc macro
call _someProc
endm
_someProc proc
.
.
.
_someProc endp
.
.
.
someProc ; Call the procedure
This simple example demonstrates a couple of conventions used for calling procedures via macro invocation:
If the procedure and all calls to the procedure occur within the same source file, place the macro definition immediately before the procedure to make it easy to find.
If you would normally name the procedure someProc, change the procedure’s name to _someProc and then use someProc as the macro name.
HLL-Like Calls with One or More Parameter
Assuming you’re using the Microsoft ABI and passing the parameter in RCX, the simplest solution is something like the following:
someProc macro parm1
mov rcx, parm1
call _someProc
endm
.
.
.
someProc Parm1Value
This macro works well if you’re passing a 64-bit integer by value. If the parameter is an 8-, 16-, or 32-bit value, you would swap CL, CX, or ECX for RCX in the mov instruction.
If you’re passing the first argument by reference, you would swap an lea instruction for the mov instruction in this example. As reference parameters are always 64-bit values, the lea instruction would usually take this form:
lea rcx, parm1
Finally, if you’re passing a real4 or real8 value as the parameter, you’d swap one of the following instructions for the mov instruction in the previous macro:
movss xmm0, parm1 ; Use this for real4 parameters
movsd xmm0, parm1 ; Use this for real8 parameters
Expanding the macro-calling mechanism from one parameter to two or more (assuming a fixed number of parameters) is fairly easy. All you need to do is add more formal parameters and handle those arguments in your macro definition.
Below code uses macro invocations with multiple for calls to r10ToStr, e10ToStr, and some fixed calls to printf.
HLL-Like Calls with a Varying Parameter List
It can be done as shown below-
; HLL-like procedure calls with
; a varying parameter list.
option casemap:none
nl = 10
.const
ttlStr byte "Listing 14-9", 0
.code
externdef printf:proc
; Note: don't include print.inc here
; because this code uses a macro for
; print.
; print macro-
;
; HLL-like calling sequence for the _print
; function (which is, itself, a shell for
; the printf function).
;
; If print appears on a line by itself (no
; arguments), then emit a string consisting
; of a single newline character (and zero
; terminating byte). If there are one or
; more arguments, emit each argument; append
; a single zero byte after all the arguments.
;
; Examples:
;
; print
; print "Hello, World!"
; print "Hello, World!", nl
print macro arg1, optArgs:vararg
call _print
ifb <arg1>
; If print is used byte itself, print a
; newline character:
byte nl, 0
else
; If we have one or more arguments, then
; emit each of them:
byte arg1
for oa, <optArgs>
byte oa
endm
; Zero-terminate the string.
byte 0
endif
endm
_print proc
push rax
push rbx
push rcx
push rdx
push r8
push r9
push r10
push r11
push rbp
mov rbp, rsp
sub rsp, 40
and rsp, -16
mov rcx, [rbp+72] ;Return address
call printf
mov rcx, [rbp+72]
dec rcx
skipTo0: inc rcx
cmp byte ptr [rcx], 0
jne skipTo0
inc rcx
mov [rbp+72], rcx
leave
pop r11
pop r10
pop r9
pop r8
pop rdx
pop rcx
pop rbx
pop rax
ret
_print endp
; Here is the "asmMain" function.
public asmMain
asmMain proc
push rbx
push rdi
push rsi
push rbp
mov rbp, rsp
sub rsp, 56 ;Shadow storage
print "Hello world"
print
print "Hello, World!", nl
allDone: leave
pop rsi
pop rdi
pop rbx
ret ;Returns to caller
asmMain endp
end
Advanced Macro Parameter Parsing
Using opattr to Determine Argument Types
Opattr compile-time operator returns an integer value with certain bits set based on the type of expression that follows. In particular, bit 2 will be set if the expression following is relocatable or otherwise references memory. Therefore, this bit will be set if a variable such as fmtStr appears as the argument, and it will be clear if you pass a string literal as the argument.
It follows the following syntax-
opattr expression
The opattr operator returns an integer value that is a bit mask specifying the opattr attributes of the associated expression. If the expression following opattr contains forward-referenced symbols or is an illegal expression, opattr returns 0.


; OPATTR demonstration
option casemap:none
nl = 10
.const
fmtStr byte nl, "Hello, World! #2", nl, 0
.code
externdef printf:proc
; cprintf macro-
;
; cprintf fmtStr
; cprintf "Format String"
cprintf macro fmtStrArg
local fmtStr, attr, isConst
attr = OPATTR fmtStrArg
isConst = (attr and 4) eq 4
if (attr eq 0) or isConst
.data
fmtStr byte fmtStrArg, 0
.code
lea rcx, fmtStr
else
lea rcx, fmtStrArg
endif
call printf
endm
atw = opattr "Hello Word"
bin = opattr "abcdefghijklmnopqrstuvwxyz"
; Here is the "asmMain" function.
public asmMain
asmMain proc
push rbx
push rdi
push rsi
push rbp
mov rbp, rsp
sub rsp, 56 ;Shadow storage
cprintf "Hello World!"
cprintf fmtStr
allDone: leave
pop rsi
pop rdi
pop rbx
ret ;Returns to caller
asmMain endp
end
If, in a macro, you were to test only bit 2 to determine if the operand is a constant, you could get into trouble when bit 2 is set and you assume that it is a constant. Probably the wisest thing to do is to mask off bits 0 to 7 (or maybe just bits 0 to 6) and compare the result against an 8-bit value rather than a simple mask.


We can check for specific parameter types using opattr operator as shown in the code below.
; Several useful macro functions:
;
; mout- Like echo, but allows "%" operators.
;
; testStr- Tests an operand to see if it
; is a string literal constant
;
; @sizestr- Handles missing MASM function
;
; isDigit- Tests first character of its
; argument to see if it's a decimal
; digit.
;
; isSign- Tests first character of its
; argument to see if it's a '+'
; or a '-' character.
;
; extract1st- Removes the first character
; from its argument (side effect)
; and returns that character as
; the function result.
;
; getReal- Parses the argument and returns
; true if it is a reasonable-
; looking real constant.
option casemap:none
; mout-
;
; Replacement for echo. Allows "%" operator
; in operand field to expand text symbols.
mout macro valToPrint
local cmd
cmd catstr <echo >, <valToPrint>
cmd
endm
; testStr is a macro function that tests its
; operand to see if it is a string literal.
testStr macro strParm
local firstChar
ifnb <strParm>
firstChar substr <strParm>, 1, 1
ifidn firstChar,<!">
; First character was '"', so assume it's
; a string.
exitm <1>
endif ;ifidn
endif ;ifnb
; If we get to this point in the macro
; we definitely do not have a string.
exitm <0>
endm
; @sizestr-
;
; Replacement for the MASM function
; which doesn't seem to work
@sizestr macro theStr
local theLen
theLen sizestr <theStr>
exitm <&theLen>
endm ;@sizestr
; isDigit-
;
; Macro function that returns true if the
; first character of its parameter is a
; decimal digit.
isDigit macro parm
local FirstChar
if @sizestr(%parm) eq 0
exitm <0>
endif
FirstChar substr parm, 1, 1
ifidn FirstChar,<0>
exitm <1>
endif
ifidn FirstChar,<1>
exitm <1>
endif
ifidn FirstChar,<2>
exitm <1>
endif
ifidn FirstChar,<3>
exitm <1>
endif
ifidn FirstChar,<4>
exitm <1>
endif
ifidn FirstChar,<5>
exitm <1>
endif
ifidn FirstChar,<6>
exitm <1>
endif
ifidn FirstChar,<7>
exitm <1>
endif
ifidn FirstChar,<8>
exitm <1>
endif
ifidn FirstChar,<9>
exitm <1>
endif
exitm <0>
endm
; isSign-
;
; Macro function that returns true if the
; first character of its parameter is a
; + or -
isSign macro parm
local FirstChar
ifb <parm>
exitm <0>
endif
FirstChar substr parm, 1, 1
ifidn FirstChar, <+>
exitm <1>
endif
ifidn FirstChar, <->
exitm <1>
endif
exitm <0>
endm
; isE-
;
; Macro function that returns true if the
; first character of its parameter is a
; e or E
isE macro parm
local FirstChar
if @sizestr(%parm) eq 0
exitm <0>
endif
FirstChar substr parm, 1, 1
ifidni FirstChar, <e>
exitm <1>
endif
exitm <0>
endm
; extract1st
;
; Macro that gets passed a text variable.
; Extracts the first character (if any)
; and removes that character from the
; argument.
extract1st macro parm
local FirstChar
ifb <%parm>
exitm <>
endif
FirstChar substr parm, 1, 1
if @sizestr(%parm) GE 2
parm substr parm, 2
else
parm textequ <>
endif
exitm <FirstChar>
endm
; getExp-
;
; Given an input string that (presumably)
; holds an exponent value, this function
; returns the following:
;
; <0>: Invalid text (error)
; <1>: Either a valid exponent
; is present, or no exponent
; at all is present.
;
; Note that a blank input string is legal.
; Exponents are optional.
;
; Sign :=(+|-)
; Digit ::= [0-9]
; Exp ::= (e|E) Sign? Digit Digit? Digit? Digit?
getExp macro parm
local curChar
; Return success if no exponent present
if @sizestr(%parm) eq 0
exitm <1>
endif
; Extract the next character, return failure
; if it is not an 'e' or 'E' character:
curChar textequ extract1st(parm)
if isE(curChar) eq 0
exitm <0>
endif
; Extract the next character:
curChar textequ extract1st(parm)
; If an optional sign character appears,
; remove it from the string:
if isSign( curChar )
curChar textequ extract1st(parm) ;Skip sign char
endif ;isSign
; Must have at least one digit
if isDigit( curChar ) eq 0
exitm <0>
endif
; Optionally, we can have up to three additional digits
if @sizestr(%parm) gt 0
curChar textequ extract1st(parm) ;Skip 1st digit
if isDigit( curChar ) eq 0
exitm <0>
endif
endif
if @sizestr(%parm) gt 0
curChar textequ extract1st(parm) ;Skip 2nd digit
if isDigit( curChar ) eq 0
exitm <0>
endif
endif
if @sizestr(%parm) gt 0
curChar textequ extract1st(parm) ;Skip 3rd digit
if isDigit( curChar ) eq 0
exitm <0>
endif
endif
; If we get to this point, we have a valid exponent.
exitm <1>
endm ;getExp
; getMant
;
;
; Given an input string that (presumably)
; holds a mantissa value, this function
; returns the following:
;
; <0>: Invalid text (error)
; <1>: A valid mantissa
;
; Note that processing stops on the first
; illegal character (or the second decimal
; point, if it finds 2 of them).
; As long as the string contains at least 1 digit,
; this function returns true.
;
; Digit ::= [0-9]
; Mantissa ::= (Digit)+ | '.' Digit)+ | (Digit)+ '.' Digit*
getMant macro parm
local curChar, sawDecPt, rpt
sawDecPt = 0
curChar textequ extract1st(parm) ;get 1st char
ifidn curChar, <.> ;Check for dec pt
sawDecPt = 1
curChar textequ extract1st(parm) ;get 2nd char
endif
; Must have at least one digit:
if isDigit( curChar ) eq 0
exitm <0> ;Bad mantissa
endif
; Process zero or more digits. If we haven't already
; seen a decimal point, allow exactly one of those.
;
; Do loop at least once if there is at least one
; character left in parm:
rpt = @sizestr(%parm)
while rpt
; Get the 1st char from parm and see if
; it is a decimal point or a digit:
curChar substr parm, 1, 1
ifidn curChar, <.>
rpt = sawDecPt eq 0
sawDecPt = 1
else
rpt = isDigit(curChar)
endif
; If char was legal, then extract it from parm:
if rpt
curChar textequ extract1st(parm) ;get next char
endif
; Repeat as long as we have more chars and the
; current character is legal:
rpt = rpt and (@sizestr(%parm) gt 0)
endm ;while
; If we've seen at least one digit, we've got a valid
; mantissa. We've stopped processing on the first
; character that is not a digit or the 2nd '.' char.
exitm <1>
endm ;getMant
; getReal-
;
; Parses a real constant.
;
; Returns:
; true: If the parameter contains a syntactically
; correct real number (and no extra characters).
; false: If there are any illegal characters or
; other syntax errors in the numeric string.
;
; Sign :=(+|-)
; Digit ::= [0-9]
; Mantissa ::= (Digit)+ | '.' Digit)+ | (Digit)+ '.' Digit*
; Exp ::= (e|E) Sign? Digit Digit? Digit? Digit?
; Real ::= Sign? Mantissa Exp?
getReal macro origParm
local parm, curChar, result
; Make a copy of the parameter so we don't
; delete the characters in the original string
parm textequ &origParm
; Must have at least one character:
ifb parm
exitm <0>
endif
; Extract the optional sign:
if isSign(parm)
curChar textequ extract1st(parm) ;skip sign char
endif
; Get the required mantissa:
if getMant(parm) eq 0
exitm <0> ;Bad mantissa
endif
; Extract the optional exponent:
result textequ getExp(parm)
exitm <&result>
endm ;getReal
; Test strings and invocations for the
; getReal macro;
mant1 textequ <1>
mant2 textequ <.2>
mant3 textequ <3.4>
rv4 textequ <1e1>
rv5 textequ <1.e1>
rv6 textequ <1.0e1>
rv7 textequ <1.0e+1>
rv8 textequ <1.0e-1>
rv9 textequ <1.0e12>
rva textequ <1.0e1234>
rvb textequ <1.0E123>
rvc textequ <1.0E+1234>
rvd textequ <1.0E-1234>
rve textequ <-1.0E-1234>
rvf textequ <+1.0E-1234>
badr1 textequ <>
badr2 textequ <a>
badr3 textequ <1.1.0>
badr4 textequ <e1>
badr5 textequ <1ea1>
badr6 textequ <1e1a>
% echo get_Real(mant1) = getReal(mant1)
% echo get_Real(mant2) = getReal(mant2)
% echo get_Real(mant3) = getReal(mant3)
% echo get_Real(rv4) = getReal(rv4)
% echo get_Real(rv5) = getReal(rv5)
% echo get_Real(rv6) = getReal(rv6)
% echo get_Real(rv7) = getReal(rv7)
% echo get_Real(rv8) = getReal(rv8)
% echo get_Real(rv9) = getReal(rv9)
% echo get_Real(rva) = getReal(rva)
% echo get_Real(rvb) = getReal(rvb)
% echo get_Real(rvc) = getReal(rvc)
% echo get_Real(rvd) = getReal(rvd)
% echo get_Real(rve) = getReal(rve)
% echo get_Real(rvf) = getReal(rvf)
% echo get_Real(badr1) = getReal(badr1)
% echo get_Real(badr2) = getReal(badr2)
% echo get_Real(badr3) = getReal(badr3)
% echo get_Real(badr4) = getReal(badr4)
% echo get_Real(badr5) = getReal(badr5)
% echo get_Real(badr5) = getReal(badr5)
; Here is the "asmMain" function.
end
Checking For Registers
Although the opattr operator provides a bit to tell you that its operand is an x86-64 register, that’s the only information opattr provides. In particular, opattr’s return value won’t tell you which register it has seen; whether it’s a general-purpose, XMM, YMM, ZMM, MM, ST, or other register; or the size of that register.
The below code contains a set of equates that map register names to numeric values. These equates use symbols of the form regXXX, where XXX is the register name (all uppercase).
Below is an example of implementation of the opattr register check
Last updated