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.
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.
The catstr Directive
The catstr function has the following syntax:
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
The echo statement displays the textual representation of its argument list during the assembly of a MASM program. The above program will show -
%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.
The instr Directive
The instr directive searches for the presence of one string within another.
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
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
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:
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:
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.
To understand how it works, look at the program below
MASM provides two forms of the for..endm loop. These two loops take the following general form:
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:
To execute the code associated with a macro, you specify the macroβs name at the point you want to execute these instructions.
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:
When you invoke a macro, you supply the actual parameters as arguments to the macro invocation:
A macro invocation of the form neg128 rdx, rax is equivalent to the following:
We use the < and > brackets to treat anything inside them as a single string.
When passing numeric parameters that may contain compile-time expressions, surround them in the macro with parentheses.
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:
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
MASM solves this issue by introducing several additional conditional if statements intended for use with text operands and macro arguments.

Example use of ifb:
The neg128 macro complete code:
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.
Default Macro Parameter Values
One way to handle missing macro arguments is to define default values for those arguments.
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:
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.
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.
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:
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
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.
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.
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:
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
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:
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:
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:
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:
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-
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-
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.


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.


Last updated