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 Multiple Parameters

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.

opattr Return Values
  • 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.

8-Bit Values for opattr Results

Last updated