0% found this document useful (0 votes)
34 views52 pages

Controlling Program Flow: Jumps

The document discusses various ways to control program flow in assembly language, including jumps, loops, procedures, and interrupts. It describes unconditional jumps and how MASM can optimize them. Conditional jumps test conditions. Loops repeat code using directives like .WHILE and .REPEAT. Procedures allow code organization and passing parameters with INVOKE. Interrupts transfer control to interrupt routines.

Uploaded by

darwinvargas2011
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
34 views52 pages

Controlling Program Flow: Jumps

The document discusses various ways to control program flow in assembly language, including jumps, loops, procedures, and interrupts. It describes unconditional jumps and how MASM can optimize them. Conditional jumps test conditions. Loops repeat code using directives like .WHILE and .REPEAT. Procedures allow code organization and passing parameters with INVOKE. Interrupts transfer control to interrupt routines.

Uploaded by

darwinvargas2011
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 52

161

C H A P T E R 7

Controlling Program Flow

Very few programs execute all lines sequentially from .STARTUP to .EXIT.
Rather, complex program logic and efficiency dictate that you control the flow
of your program — jumping from one point to another, repeating an action until
a condition is reached, and passing control to and from procedures. This chapter
describes various ways for controlling program flow and several features that
simplify coding program-control constructs.
The first section covers jumps from one point in the program to another. It
explains how MASM 6.1 optimizes both unconditional and conditional jumps
under certain circumstances, so that you do not have to specify every attribute.
The section also describes instructions you can use to test conditional jumps.
The next section describes loop structures that repeat actions or evaluate
conditions. It discusses MASM directives, such as .WHILE and .REPEAT, that
generate appropriate compare, loop, and jump instructions for you, and the .IF,
.ELSE, and .ELSEIF directives that generate jump instructions.
The “Procedures” section in this chapter explains how to write an assembly-
language procedure. It covers the extended functionality for PROC, a PROTO
directive that lets you write procedure prototypes similar to those used in C, an
INVOKE directive that automates parameter passing, and options for the stack-
frame setup inside procedures.
The last section explains how to pass program control to an interrupt routine.

Jumps
Jumps are the most direct way to change program control from one location to
another. At the processor level, jumps work by changing the value of the IP
(Instruction Pointer) register to a target offset and, for far jumps, by changing
the CS register to a new segment address. Jump instructions fall into only two
categories: conditional and unconditional.

Filename: LMAPGC07.DOC Project:


Template: MSGRIDA1.DOT Author: Ruth L Silverio Last Saved By: Ruth L Silverio
Revision #: 4 Page: 161 of 1 Printed: 10/02/00 04:23 PM
162 Programmer’s Guide

Unconditional Jumps
The JMP instruction transfers control unconditionally to another instruction.
JMP’s single operand contains the address of the target instruction.
Unconditional jumps skip over code that should not be executed, as shown here:
; Handle one case
label1: .
.
.
jmp continue

; Handle second case


label2: .
.
.
jmp continue
.
.
.
continue:

The distance of the target from the jump instruction and the size of the operand
determine the assembler’s encoding of the instruction. The longer the distance,
the more bytes the assembler uses to code the instruction. In versions of MASM
prior to 6.0, unconditional NEAR jumps sometimes generated inefficient code,
but MASM can now optimize unconditional jumps.

Jump Optimizing
The assembler determines the smallest encoding possible for the direct
unconditional jump. MASM does not require a distance operator, so you do not
have to determine the correct distance of the jump. If you specify a distance, it
overrides any assembler optimization. If the specified distance falls short of the
target address, the assembler generates an error. If the specified distance is
longer than the jump requires, the assembler encodes the given distance and
does not optimize it.
The assembler optimizes jumps when the following conditions are met:
u You do not specify SHORT, NEAR, FAR, NEAR16, NEAR32, FAR16,
FAR32, or PROC as the distance of the target.
u The target of the jump is not external and is in the same segment as the jump
instruction. If the target is in a different segment (but in the same group), it is
treated as though it were external.

Filename: LMAPGC07.DOC Project:


Template: MSGRIDA1.DOT Author: Ruth L Silverio Last Saved By: Ruth L Silverio
Revision #: 4 Page: 162 of 2 Printed: 10/02/00 04:23 PM
Chapter 7 Controlling Program Flow 163

If these two conditions are met, MASM uses the instruction, distance, and size
of the operand to determine how to optimize the encoding for the jump. No
syntax changes are necessary.

Note This information about jump optimizing also applies to conditional jumps
on the 80386/486.

Indirect Operands
An indirect operand provides a pointer to the target address, rather than the
address itself. A pointer is a variable that contains an address. The processor
distinguishes indirect (pointer) operands from direct (address) operands by the
instruction’s context.
You can specify the pointer’s size with the WORD, DWORD, or FWORD
attributes. Default sizes are based on .MODEL and the default segment size.
jmp [bx] ; Uses .MODEL and segment size defaults
jmp WORD PTR [bx] ; A NEAR16 indirect call

If the indirect operand is a register, the jump is always a NEAR16 jump for a
16-bit register, and NEAR32 for a 32-bit register:
jmp bx ; NEAR16 jump
jmp ebx ; NEAR32 jump

A DWORD indirect operand, however, is ambiguous to the assembler.


jmp DWORD PTR [var] ; A NEAR32 jump in a 32-bit segment;
; a FAR16 jump in a 16-bit segment

In this case, your code must clear the ambiguity with the NEAR32 or FAR16
keywords. The following example shows how to use TYPEDEF to define
NEAR32 and FAR16 pointer types.
NFP TYPEDEF PTR NEAR32
FFP TYPEDEF PTR FAR16
jmp NFP PTR [var] ; NEAR32 indirect jump
jmp FFP PTR [var] ; FAR16 indirect jump

You can use an unconditional jump as a form of conditional jump by specifying


the address in a register or indirect memory operand. Also, you can use indirect
memory operands to construct jump tables that work like C switch statements,
Pascal CASE statements, or Basic ON GOTO, ON GOSUB, or SELECT
CASE statements, as shown in the following example.

Filename: LMAPGC07.DOC Project:


Template: MSGRIDA1.DOT Author: Ruth L Silverio Last Saved By: Ruth L Silverio
Revision #: 4 Page: 163 of 3 Printed: 10/02/00 04:23 PM
164 Programmer’s Guide

NPVOID TYPEDEF NEAR PTR


.DATA
ctl_tbl NPVOID extended, ; Null key (extended code)
ctrla, ; Address of CONTROL-A key routine
ctrlb ; Address of CONTROL-B key routine
.CODE
.
.
.
mov ah, 8h ; Get a key
int 21h
cbw ; Stretch AL into AX
mov bx, ax ; Copy
shl bx, 1 ; Convert to address
jmp ctl_tbl[bx] ; Jump to key routine

extended:
mov ah, 8h ; Get second key of extended key
int 21h
. ; Use another jump table
. ; for extended keys
.
jmp next
ctrla: . ; CONTROL-A code here
.
.
jmp next
ctrlb: . ; CONTROL-B code here
.
.
jmp next
.
.
next: . ; Continue

In this instance, the indirect memory operands point to addresses of routines for
handling different keystrokes.

Conditional Jumps
The most common way to transfer control in assembly language is to use a
conditional jump. This is a two-step process:
1. First test the condition.
2. Then jump if the condition is true or continue if it is false.

Filename: LMAPGC07.DOC Project:


Template: MSGRIDA1.DOT Author: Ruth L Silverio Last Saved By: Ruth L Silverio
Revision #: 4 Page: 164 of 4 Printed: 10/02/00 04:23 PM
Chapter 7 Controlling Program Flow 165

All conditional jumps except two (JCXZ and JECXZ) use the processor flags
for their criteria. Thus, any statement that sets or clears a flag can serve as a test
basis for a conditional jump. The jump statement can be any one of 30
conditional-jump instructions. A conditional-jump instruction takes a single
operand containing the target address. You cannot use a pointer value as a target
as you can with unconditional jumps.

Jumping Based on the CX Register


JCXZ and JECXZ are special conditional jumps that do not consult the
processor flags. Instead, as their names imply, these instructions cause a jump
only if the CX or ECX register is zero. The use of JCXZ and JECXZ with
program loops is covered in the next section, “Loops.”

Jumping Based on the Processor Flags


The remaining conditional jumps in the processor’s repertoire all depend on the
status of the flags register. As the following list shows, several conditional jumps
have two or three names — JE (Jump if Equal) and JZ (Jump if Zero), for
example. Shared names assemble to exactly the same machine instruction, so
you may choose whichever mnemonic seems more appropriate. Jumps that
depend on the status of the flags register include:
Instruction Jumps if
JC/JB/JNAE Carry flag is set
JNC/JNB/JAE Carry flag is clear
JBE/JNA Either carry or zero flag is set
JA/JNBE Carry and zero flag are clear
JE/JZ Zero flag is set
JNE/JNZ Zero flag is clear
JL/JNGE Sign flag ≠ overflow flag
JGE/JNL Sign flag = overflow flag
JLE/JNG Zero flag is set or sign ≠ overflow
JG/JNLE Zero flag is clear and sign = overflow
JS Sign flag is set
JNS Sign flag is clear
JO Overflow flag is set
JNO Overflow flag is clear
JP/JPE Parity flag is set (even parity)
JNP/JPO Parity flag is clear (odd parity)

The last two jumps in the list, JPE (Jump if Parity Even) and JPO (Jump if
Parity Odd), are useful only for communications programs. The processor sets

Filename: LMAPGC07.DOC Project:


Template: MSGRIDA1.DOT Author: Ruth L Silverio Last Saved By: Ruth L Silverio
Revision #: 4 Page: 165 of 5 Printed: 10/02/00 04:23 PM
166 Programmer’s Guide

the parity flag if an operation produces a result with an even number of set bits.
A communications program can compare the flag against the parity bit received
through the serial port to test for transmission errors.
The conditional jumps in the preceding list can follow any instruction that
changes the processor flags, as these examples show:
; Uses JO to handle overflow condition
add ax, bx ; Add two values
jo overflow ; If value too large, adjust

; Uses JNZ to check for zero as the result of subtraction


sub ax, bx ; Subtract
mov cx, Count ; First, initialize CX
jnz skip ; If the result is not zero, continue
call zhandler ; Else do special case

As the second example shows, the jump does not have to immediately follow
the instruction that alters the flags. Since MOV does not change the flags, it can
appear between the SUB instruction and the dependent jump.
There are three categories of conditional jumps:
u Comparison of two values
u Individual bit settings in a value
u Whether a value is zero or nonzero

Jumps Based on Comparison of Two Values


The CMP instruction is the most common way to test for conditional jumps. It
compares two values without changing either, then sets or clears the processor
flags according to the results of the comparison.
Internally, the CMP instruction is the same as the SUB instruction, except that
CMP does not change the destination operand. Both set flags according to the
result of the subtraction.

Filename: LMAPGC07.DOC Project:


Template: MSGRIDA1.DOT Author: Ruth L Silverio Last Saved By: Ruth L Silverio
Revision #: 4 Page: 166 of 6 Printed: 10/02/00 04:23 PM
Chapter 7 Controlling Program Flow 167

You can compare signed or unsigned values, but you must choose the
subsequent conditional jump to reflect the correct value type. For example, JL
(Jump if Less Than) and JB (Jump if Below) may seem conceptually similar,
but a failure to understand the difference between them can result in program
bugs. Table 7.1 shows the correct conditional jumps for comparisons of signed
and unsigned values. The table shows the zero, carry, sign, and overflow flags
as ZF, CF, SF, and OF, respectively.
Table 7.1 Conditional Jumps Based on Comparisons of Two Values
Signed Comparisons Unsigned Comparisons
Instruction Jump if True Instruction Jump if True
JE ZF = 1 JE ZF = 1
JNE ZF = 0 JNE ZF = 0
JG/JNLE ZF = 0 and SF = OF JA/JNBE CF = 0 and ZF = 0
JLE/JNG ZF = 1 or SF ≠ OF JBE/JNA CF = 1 or ZF = 1
JL/JNGE SF ≠ OF JB/JNAE CF = 1
JGE/JNL SF = OF JAE/JNB CF = 0

The mnemonic names of jumps always refer to the comparison of CMP’s first
operand (destination) with the second operand (source). For instance, in this
example, JG tests whether the first operand is greater than the second.
cmp ax, bx ; Compare AX and BX
jg next1 ; Equivalent to: If ( AX > BX ) goto next1
jl next2 ; Equivalent to: If ( AX < BX ) goto next2

Jumps Based on Bit Settings


The individual bit settings in a single value can also serve as the criteria for a
conditional jump. The TEST instruction tests whether specific bits in an operand
are on or off (set or clear), and sets the zero flag accordingly.

Filename: LMAPGC07.DOC Project:


Template: MSGRIDA1.DOT Author: Ruth L Silverio Last Saved By: Ruth L Silverio
Revision #: 4 Page: 167 of 7 Printed: 10/02/00 04:23 PM
168 Programmer’s Guide

The TEST instruction is the same as the AND instruction, except that TEST
changes neither operand. The following example shows an application of TEST.
.DATA
bits BYTE ?
.CODE
.
.
.
; If bit 2 or bit 4 is set, then call task_a
; Assume "bits" is 0D3h 11010011
test bits, 10100y ; If 2 or 4 is set AND 00010100
jz skip1 ; --------
call task_a ; Then call task_a 00010000
skip1: ; Jump taken
.
.
.
; If bits 2 and 4 are clear, then call task_b
; Assume "bits" is 0E9h 11101001
test bits, 10100y ; If 2 and 4 are clear AND 00010100
jnz skip2 ; --------
call task_b ; Then call task_b 00000000
skip2: ; Jump taken

The source operand for TEST is often a mask in which the test bits are the only
bits set. The destination operand contains the value to be tested. If all the bits
set in the mask are clear in the destination operand, TEST sets the zero flag. If
any of the flags set in the mask are also set in the destination operand, TEST
clears the zero flag.
The 80386/486 processors provide additional bit-testing instructions. The BT
(Bit Test) series of instructions copy a specified bit from the destination operand
to the carry flag. A JC or JNC can then route program flow depending on the
result. For variations on the BT instruction, see the Reference.

Jumps Based on a Value of Zero


A program often needs to jump based on whether a particular register contains a
value of zero. We’ve seen how the JCXZ instruction jumps depending on the
value in the CX register. You can test for zero in other data registers nearly as
efficiently with the OR instruction. A program can OR a register with itself
without changing the register’s contents, then act on the resulting flags status.
For example, the following example tests whether BX is zero:
or bx, bx ; Is BX = 0?
jz is_zero ; Jump if so

Filename: LMAPGC07.DOC Project:


Template: MSGRIDA1.DOT Author: Ruth L Silverio Last Saved By: Ruth L Silverio
Revision #: 4 Page: 168 of 8 Printed: 10/02/00 04:23 PM
Chapter 7 Controlling Program Flow 169

This code is functionally equivalent to:


cmp bx, 0 ; Is BX = 0?
je is_zero ; Jump if so

but produces smaller and faster code, since it does not use an immediate number
as an operand. The same technique also lets you test a register’s sign bit:
or dx, dx ; Is DX sign bit set?
js sign_set ; Jump if so

Jump Extending
Unlike an unconditional jump, a conditional jump cannot reference a label more
than 128 bytes away. For example, the following statement is valid as long as
target is within a distance of 128 bytes:
; Jump to target less than 128 bytes away
jz target ; If previous operation resulted
; in zero, jump to target

However, if target is too distant, the following sequence is necessary to


enable a longer jump. Note this sequence is logically equivalent to the preceding
example:
; Jumps to distant targets previously required two steps
jnz skip ; If previous operation result is
; NOT zero, jump to "skip"
jmp target ; Otherwise, jump to target
skip:

MASM can automate jump-extending for you. If you target a conditional jump
to a label farther than 128 bytes away, MASM rewrites the instruction with an
unconditional jump, which ensures that the jump can reach its target. If target
lies within a 128-byte range, the assembler encodes the instruction jz target
as is. Otherwise, MASM generates two substitute instructions:
jne $ + 2 + (length in bytes of the next instruction)
jmp NEAR PTR target

The assembler generates this same code sequence if you specify the distance
with NEAR PTR, FAR PTR, or SHORT. Therefore,
jz NEAR PTR target

becomes

Filename: LMAPGC07.DOC Project:


Template: MSGRIDA1.DOT Author: Ruth L Silverio Last Saved By: Ruth L Silverio
Revision #: 4 Page: 169 of 9 Printed: 10/02/00 04:23 PM
170 Programmer’s Guide

jne $ + 5
jmp NEAR PTR target

even if target is less than 128 bytes away.


MASM enables automatic jump expansion by default, but you can turn it off
with the NOLJMP form of the OPTION directive. For information about the
OPTION directive, see page 24.
If the assembler generates code to extend a conditional jump, it issues a level 3
warning saying that the conditional jump has been lengthened. You can set the
warning level to 1 for development and to level 3 for a final optimizing pass to
see if you can shorten jumps by reorganizing.
If you specify the distance for the jump and the target is out of range for that
distance, a “Jump out of Range” error results.
Since the JCXZ and JECXZ instructions do not have logical negations,
expansion of the jump instruction to handle targets with unspecified distances
cannot be performed for those instructions. Therefore, the distance must always
be short.
The size and distance of the target operand determines the encoding for
conditional or unconditional jumps to externals or targets in different segments.
The jump-extending and optimization features do not apply in this case.

Note Conditional jumps on the 80386 and 80486 processors can be to targets
up to 32K away, so jump extension occurs only for targets greater than that
distance.

Anonymous Labels
When you code jumps in assembly language, you must invent many label
names. One alternative to continually thinking up new label names is to use
anonymous labels, which you can use anywhere in your program. But because
anonymous labels do not provide meaningful names, they are best used for
jumping over only a few lines of code. You should mark major divisions of a
program with actual named labels.
Use two at signs (@@) followed by a colon (:) as an anonymous label. To jump
to the nearest preceding anonymous label, use @B (back) in the jump
instruction’s operand field; to jump to the nearest following anonymous label,
use @F (forward) in the operand field.
The jump in the following example targets an anonymous label:

Filename: LMAPGC07.DOC Project:


Template: MSGRIDA1.DOT Author: Ruth L Silverio Last Saved By: Ruth L Silverio
Revision #: 4 Page: 170 of 10 Printed: 10/02/00 04:23 PM
Chapter 7 Controlling Program Flow 171

jge @F
.
.
.
@@:

The items @B and @F always refer to the nearest occurrences of @@:, so there
is never any conflict between different anonymous labels.

Decision Directives
The high-level structures you can use for decision-making are the .IF, .ELSEIF,
and .ELSE statements. These directives generate conditional jumps. The
expression following the .IF directive is evaluated, and if true, the following
instructions are executed until the next .ENDIF, .ELSE, or .ELSEIF directive is
reached. The .ELSE statements execute if the expression is false. Using the
.ELSEIF directive puts a new expression inside the alternative part of the
original .IF statement to be evaluated. The syntax is:
.IF condition1
statements
[[.ELSEIF condition2
statements]]
[[.ELSE
statements]]
.ENDIF
The decision structure
.IF cx == 20
mov dx, 20
.ELSE
mov dx, 30
.ENDIF

generates this code:


.IF cx == 20
0017 83 F9 14 * cmp cx, 014h
001A 75 05 * jne @C0001
001C BA 0014 mov dx, 20
.ELSE
001F EB 03 * jmp @C0003
0021 *@C0001:
0021 BA 001E mov dx, 30
.ENDIF
0024 *@C0003:

Filename: LMAPGC07.DOC Project:


Template: MSGRIDA1.DOT Author: Ruth L Silverio Last Saved By: Ruth L Silverio
Revision #: 4 Page: 171 of 11 Printed: 10/02/00 04:23 PM
172 Programmer’s Guide

Loops
Loops repeat an action until a termination condition is reached. This condition
can be a counter or the result of an expression’s evaluation. MASM 6.1 offers
many ways to set up loops in your programs. The following list compares
MASM loop structures:
Instructions Action
LOOP Automatically decrements CX. When CX = 0, the loop ends. The top
of the loop cannot be greater than 128 bytes from the LOOP
instruction. (This is true for all LOOP instructions.)
LOOPE/LOOPZ, Loops while equal or not equal. Checks both CX and the state of the
LOOPNE/LOOPNZ zero flag. LOOPZ ends when either CX=0 or the zero flag is clear,
whichever occurs first. LOOPNZ ends when either CX=0 or the zero
flag is set, whichever occurs first. LOOPE and LOOPZ assemble to
the same machine instruction, as do LOOPNE and LOOPNZ. Use
whichever mnemonic best fits the context of your loop. Set CX to a
number out of range if you don’t want a count to control the loop.
JCXZ, JECXZ Branches to a label only if CX = 0 or ECX = 0. Unlike other
conditional-jump instructions, which can jump to either a near or a
short label under the 80386 or 80486, JCXZ and JECXZ always
jump to a short label.
Conditional jumps Acts only if certain conditions met. Necessary if several conditions
must be tested. See “Conditional Jumps,” page 164.

The following examples illustrate these loop constructions.


; The LOOP instruction: For 200 to 0 do task
mov cx, 200 ; Set counter
next: . ; Do the task here
.
.
loop next ; Do again
; Continue after loop

; The LOOPNE instruction: While AX is not 'Y', do task


mov cx, 256 ; Set count too high to interfere
wend: . ; But don't do more than 256 times
. ; Some statements that change AX
.
cmp al, 'Y' ; Is it Y or too many times?
loopne wend ; No? Repeat
; Yes? Continue

The JCXZ and JECXZ instructions provide an efficient way to avoid executing
loops when the loop counter CX is empty. For example, consider the following
loops:

Filename: LMAPGC07.DOC Project:


Template: MSGRIDA1.DOT Author: Ruth L Silverio Last Saved By: Ruth L Silverio
Revision #: 4 Page: 172 of 12 Printed: 10/02/00 04:23 PM
Chapter 7 Controlling Program Flow 173

mov cx, LoopCount ; Load loop counter


next: . ; Iterate loop CX times
.
.
loop next ; Do again

If LoopCount is zero, CX decrements to -1 on the first pass. It then must


decrement 65,535 more times before reaching 0. Use a JCXZ to avoid this
problem:
mov cx, LoopCount ; Load loop counter
jcxz done ; Skip loop if count is 0
next: . ; Else iterate loop CX times
.
.
loop next ; Do again
done: ; Continue after loop

Loop-Generating Directives
The high-level control structures generate loop structures for you. These
directives are similar to the while and repeat loops of C or Pascal, and can
make your assembly programs easier to code and to read. The assembler
generates the appropriate assembly code. These directives are summarized as
follows:
Directives Action
.WHILE ... .ENDW The statements between .WHILE condition and .ENDW
execute while the condition is true.
.REPEAT ... .UNTIL The loop executes at least once and continues until the condition
given after .UNTIL is true. Generates conditional jumps.
.REPEAT ... .UNTILCXZ Compares label to an expression and generates appropriate
loop instructions.
.BREAK End a .REPEAT or a .WHILE loop unconditionally.
.CONTINUE Jump unconditionally past any remaining code to bottom of
loop.

Filename: LMAPGC07.DOC Project:


Template: MSGRIDA1.DOT Author: Ruth L Silverio Last Saved By: Ruth L Silverio
Revision #: 4 Page: 173 of 13 Printed: 10/02/00 04:23 PM
174 Programmer’s Guide

These constructs work much as they do in a high-level language such as C or


Pascal. Keep in mind the following points:
u These directives generate appropriate processor instructions. They are not
new instructions.
u They require proper use of signed and unsigned data declarations.

These directives cause a set of instructions to execute based on the evaluation of


some condition. This condition can be an expression that evaluates to a signed
or unsigned value, an expression using the binary operators in C (&&, ||, or !),
or the state of a flag. For more information about expression operators, see page
178.
The evaluation of the condition requires the assembler to know if the operands
in the condition are signed or unsigned. To state explicitly that a named memory
location contains a signed integer, use the signed data allocation directives
SBYTE, SWORD, and SDWORD.

.WHILE Loops
As with while loops in C or Pascal, the test condition for .WHILE is checked
before the statements inside the loop execute. If the test condition is false, the
loop does not execute. While the condition is true, the statements inside the loop
repeat.
Use the .ENDW directive to mark the end of the .WHILE loop. When the
condition becomes false, program execution begins at the first statement
following the .ENDW directive. The .WHILE directive generates appropriate
compare and jump statements. The syntax is:
.WHILE condition
statements
.ENDW
For example, this loop copies the contents of one buffer to another until a ‘$’
character (marking the end of the string) is found:
.DATA
buf1 BYTE "This is a string",'$'
buf2 BYTE 100 DUP (?)
.CODE
sub bx, bx ; Zero out bx
.WHILE (buf1[bx] != '$')
mov al, buf1[bx] ; Get a character
mov buf2[bx], al ; Move it to buffer 2
inc bx ; Count forward
.ENDW

Filename: LMAPGC07.DOC Project:


Template: MSGRIDA1.DOT Author: Ruth L Silverio Last Saved By: Ruth L Silverio
Revision #: 4 Page: 174 of 14 Printed: 10/02/00 04:23 PM
Chapter 7 Controlling Program Flow 175

.REPEAT Loops
MASM’s .REPEAT directive allows for loop constructions like the do loop of C
and the REPEAT loop of Pascal. The loop executes until the condition
following the .UNTIL (or .UNTILCXZ) directive becomes true. Since the
condition is checked at the end of the loop, the loop always executes at least
once. The .REPEAT directive generates conditional jumps. The syntax is:
.REPEAT
statements
.UNTIL condition
.REPEAT
statements
.UNTILCXZ [[condition]]
where condition can also be expr1 == expr2 or expr1 != expr2. When two
conditions are used, expr2 can be an immediate expression, a register, or (if
expr1 is a register) a memory location.
For example, the following code fills a buffer with characters typed at the
keyboard. The loop ends when the ENTER key (character 13) is pressed:
.DATA
buffer BYTE 100 DUP (0)
.CODE
sub bx, bx ; Zero out bx
.REPEAT
mov ah, 01h
int 21h ; Get a key
mov buffer[bx], al ; Put it in the buffer
inc bx ; Increment the count
.UNTIL (al == 13) ; Continue until al is 13

Filename: LMAPGC07.DOC Project:


Template: MSGRIDA1.DOT Author: Ruth L Silverio Last Saved By: Ruth L Silverio
Revision #: 4 Page: 175 of 15 Printed: 10/02/00 04:23 PM
176 Programmer’s Guide

The .UNTIL directive generates conditional jumps, but the .UNTILCXZ


directive generates a LOOP instruction, as shown by the listing file code for
these examples. In a listing file, assembler-generated code is preceded by an
asterisk.
ASSUME bx:PTR SomeStruct

.REPEAT
*@C0001:
inc ax
.UNTIL ax==6
* cmp ax, 006h
* jne @C0001
.REPEAT
*@C0003:
mov ax, 1
.UNTILCXZ
* loop @C0003

.REPEAT
*@C0004:
.UNTILCXZ [bx].field != 6
* cmp [bx].field, 006h
* loope @C0004

.BREAK and .CONTINUE Directives


The .BREAK and .CONTINUE directives terminate a .REPEAT or .WHILE
loop prematurely. These directives allow an optional .IF clause for conditional
breaks. The syntax is:
.BREAK [[.IF condition]]
.CONTINUE [[.IF condition]]
Note that .ENDIF is not used with the .IF forms of .BREAK and .CONTINUE
in this context. The .BREAK and .CONTINUE directives work the same way
as the break and continue instructions in C. Execution continues at the
instruction following the .UNTIL, .UNTILCXZ, or .ENDW of the nearest
enclosing loop.
Instead of ending the loop execution as .BREAK does, .CONTINUE causes
loop execution to jump directly to the code that evaluates the loop condition of
the nearest enclosing loop.
The following loop accepts only the keys in the range ‘0’ to ‘9’ and terminates
when you press ENTER.

Filename: LMAPGC07.DOC Project:


Template: MSGRIDA1.DOT Author: Ruth L Silverio Last Saved By: Ruth L Silverio
Revision #: 4 Page: 176 of 16 Printed: 10/02/00 04:23 PM
Chapter 7 Controlling Program Flow 177

.WHILE 1 ; Loop forever


mov ah, 08h ; Get key without echo
int 21h
.BREAK .IF al == 13 ; If ENTER, break out of the loop
.CONTINUE .IF (al < '0') || (al > '9')
; If not a digit, continue looping
mov dl, al ; Save the character for processing
mov ah, 02h ; Output the character
int 21h
.ENDW

If you assemble the preceding source code with the /Fl and /Sg command-line
options and then view the results in the listing file, you will see this code:
.WHILE 1
0017 *@C0001:
0017 B4 08 mov ah, 08h
0019 CD 21 int 21h
.BREAK .IF al == 13
001B 3C 0D * cmp al, 00Dh
001D 74 10 * je @C0002
.CONTINUE .IF (al '0') || (al '9')
001F 3C 30 * cmp al, '0'
0021 72 F4 * jb @C0001
0023 3C 39 * cmp al, '9'
0025 77 F0 * ja @C0001
0027 8A D0 mov dl, al
0029 B4 02 mov ah, 02h
002B CD 21 int 21h
.ENDW
002D EB E8 * jmp @C0001
002F *@C0002:

The high-level control structures can be nested. That is, .REPEAT or .WHILE
loops can contain .REPEAT or .WHILE loops as well as .IF statements.
If the code generated by a .WHILE loop, .REPEAT loop, or .IF statement
generates a conditional or unconditional jump, MASM encodes the jump using
the jump extension and jump optimization techniques described in
“Unconditional Jumps,” page 162, and “Conditional Jumps,” page 164.

Filename: LMAPGC07.DOC Project:


Template: MSGRIDA1.DOT Author: Ruth L Silverio Last Saved By: Ruth L Silverio
Revision #: 4 Page: 177 of 17 Printed: 10/02/00 04:23 PM
178 Programmer’s Guide

Writing Loop Conditions


You can express the conditions of the .IF, .REPEAT, and .WHILE directives
using relational operators, and you can express the attributes of the operand with
the PTR operator. To write loop conditions, you also need to know how the
assembler evaluates the operators and operands in the condition. This section
explains the operators, attributes, precedence level, and expression evaluation
order for the conditions used with loop-generating directives.

Expression Operators
The binary relational operators in MASM 6.1 are the same binary operators
used in C. These operators generate MASM compare, test, and conditional
jump instructions. High-level control instructions include:
Operator Meaning
== Equal
!= Not equal
> Greater than
>= Greater than or equal to
< Less than
<= Less than or equal to
& Bit test
! Logical NOT
&& Logical AND
|| Logical OR

A condition without operators (other than !) tests for nonzero as it does in C.


For example, .WHILE (x) is the same as .WHILE (x != 0), and .WHILE
(!x) is the same as .WHILE (x == 0).

You can also use the flag names (ZERO?, CARRY?, OVERFLOW?, SIGN?,
and PARITY?) as operands in conditions with the high-level control structures.
For example, in .WHILE (CARRY?), the value of the carry flag determines the
outcome of the condition.

Signed and Unsigned Operands


Expression operators generate unsigned jumps by default. However, if either
side of the operation is signed, the assembler considers the entire operation
signed.

Filename: LMAPGC07.DOC Project:


Template: MSGRIDA1.DOT Author: Ruth L Silverio Last Saved By: Ruth L Silverio
Revision #: 4 Page: 178 of 18 Printed: 10/02/00 04:23 PM
Chapter 7 Controlling Program Flow 179

You can use the PTR operator to tell the assembler that a particular operand in
a register or constant is a signed number, as in these examples:
.WHILE SWORD PTR [bx] <= 0
.IF SWORD PTR mem1 > 0

Without the PTR operator, the assembler would treat the contents of BX as an
unsigned value.
You can also specify the size attributes of operands in memory locations with
SBYTE, SWORD, and SDWORD, for use with .IF, .WHILE, and .REPEAT.
.DATA
mem1 SBYTE ?
mem2 WORD ?
.IF mem1 > 0
.WHILE mem2 < bx
.WHILE SWORD PTR ax < count

Precedence Level
As with C, you can concatenate conditions with the && operator for AND, the
|| operator for OR, and the ! operator for negate. The precedence level is !, &&,
and ||, with ! having the highest priority. Like expressions in high-level languages,
precedence is evaluated left to right.

Expression Evaluation
The assembler evaluates conditions created with high-level control structures
according to short-circuit evaluation. If the evaluation of a particular condition
automatically determines the final result (such as a condition that evaluates to
false in a compound statement concatenated with AND), the evaluation does not
continue.
For example, in this .WHILE statement,
.WHILE (ax > 0) && (WORD PTR [bx] == 0)

the assembler evaluates the first condition. If this condition is false (that is, if
AX is less than or equal to 0), the evaluation is finished. The second condition is
not checked and the loop does not execute, because a compound condition
containing && requires both expressions to be true for the entire condition to be
true.

Filename: LMAPGC07.DOC Project:


Template: MSGRIDA1.DOT Author: Ruth L Silverio Last Saved By: Ruth L Silverio
Revision #: 4 Page: 179 of 19 Printed: 10/02/00 04:23 PM
180 Programmer’s Guide

Procedures
Organizing your code into procedures that execute specific tasks divides large
programs into manageable units, allows for separate testing, and makes code
more efficient for repetitive tasks.
Assembly-language procedures are similar to functions, subroutines, and
procedures in high-level languages such as C, FORTRAN, and Pascal. Two
instructions control the use of assembly-language procedures. CALL pushes the
return address onto the stack and transfers control to a procedure, and RET
pops the return address off the stack and returns control to that location.
The PROC and ENDP directives mark the beginning and end of a procedure.
Additionally, PROC can automatically:
u Preserve register values that should not change but that the procedure might
otherwise alter.
u Set up a local stack pointer, so that you can access parameters and local
variables placed on the stack.
u Adjust the stack when the procedure ends.

Defining Procedures
Procedures require a label at the start of the procedure and a RET instruction at
the end. Procedures are normally defined by using the PROC directive at the
start of the procedure and the ENDP directive at the end. The RET instruction
normally is placed immediately before the ENDP directive. The assembler
makes sure the distance of the RET instruction matches the distance defined by
the PROC directive. The basic syntax for PROC is:
label PROC [[NEAR | FAR]]
.
.
.
RET [[constant]]
label ENDP
The CALL instruction pushes the address of the next instruction in your code
onto the stack and passes control to a specified address. The syntax is:
CALL {label | register | memory}
The operand contains a value calculated at run time. Since that operand can be a
register, direct memory operand, or indirect memory operand, you can write call
tables similar to the example code on page 164.

Filename: LMAPGC07.DOC Project:


Template: MSGRIDA1.DOT Author: Ruth L Silverio Last Saved By: Ruth L Silverio
Revision #: 4 Page: 180 of 20 Printed: 10/02/00 04:23 PM
Chapter 7 Controlling Program Flow 181

Calls can be near or far. Near calls push only the offset portion of the calling
address and therefore must target a procedure within the same segment or
group. You can specify the type for the target operand. If you do not, MASM
uses the declared distance (NEAR or FAR) for operands that are labels and for
the size of register or memory operands. The assembler then encodes the call
appropriately, as it does with unconditional jumps. (See previous “Unconditional
Jumps” and “Conditional Jumps.”)
MASM optimizes a call to a far non-external label when the label is in the
current segment by generating the code for a near call, saving one byte.
You can define procedures without PROC and ENDP, but if you do, you must
make sure that the size of the CALL matches the size of the RET. You can
specify the RET instruction as RETN (Return Near) or RETF (Return Far) to
override the default size:
call NEAR PTR task ; Call is declared near
. ; Return comes to here
.
.
task: ; Procedure begins with near label
.
. ; Instructions go here
.
retn ; Return declared near

The syntax for RETN and RETF is:


label: | label LABEL NEAR
statements
RETN [[constant]]
label LABEL FAR
statements
RETF [[constant]]
The RET instruction (and its RETF and RETN variations) allows an optional
constant operand that specifies a number of bytes to be added to the value of
the SP register after the return. This operand adjusts for arguments passed to
the procedure before the call, as shown in the example in “Using Local
Variables,” following.
When you define procedures without PROC and ENDP, you must make sure
that calls have the same size as corresponding returns. For example, RETF pops
two words off the stack. If a NEAR call is made to a procedure with a far
return, the popped value is meaningless, and the stack status may cause the
execution to return to a random memory location, resulting in program failure.

Filename: LMAPGC07.DOC Project:


Template: MSGRIDA1.DOT Author: Ruth L Silverio Last Saved By: Ruth L Silverio
Revision #: 4 Page: 181 of 21 Printed: 10/02/00 04:23 PM
182 Programmer’s Guide

An extended PROC syntax automates many of the details of accessing


arguments and saving registers. See “Declaring Parameters with the PROC
Directive,” later in this chapter.

Passing Arguments on the Stack


Each time you call a procedure, you may want it to operate on different data.
This data, called “arguments,” can be passed to the procedure in various ways.
Although you can pass arguments to a procedure in registers or in variables, the
most common method is the stack. Microsoft languages have specific
conventions for passing arguments. These conventions for assembly-language
modules shared with modules from high-level languages are explained in Chapter
12, “Mixed-Language Programming.”
This section describes how a procedure accesses the arguments passed to it on
the stack. Each argument is accessed as an offset from BP. However, if you use
the PROC directive to declare parameters, the assembler calculates these offsets
for you and lets you refer to parameters by name. The next section, “Declaring
Parameters with the PROC Directive,” explains how to use PROC this way.
This example shows how to pass arguments to a procedure. The procedure
expects to find those arguments on the stack. As this example shows, arguments
must be accessed as offsets of BP.
; C-style procedure call and definition

mov ax, 10 ; Load and


push ax ; push constant as third argument
push arg2 ; Push memory as second argument
push cx ; Push register as first argument
call addup ; Call the procedure
add sp, 6 ; Destroy the pushed arguments
. ; (equivalent to three pops)
.
.
addup PROC NEAR ; Return address for near call
; takes two bytes
push bp ; Save base pointer - takes two bytes
; so arguments start at fourth byte
mov bp, sp ; Load stack into base pointer
mov ax, [bp+4] ; Get first argument from
; fourth byte above pointer
add ax, [bp+6] ; Add second argument from
; sixth byte above pointer
add ax, [bp+8] ; Add third argument from
; eighth byte above pointer
pop bp ; Restore BP
ret ; Return result in AX
addup ENDP

Filename: LMAPGC07.DOC Project:


Template: MSGRIDA1.DOT Author: Ruth L Silverio Last Saved By: Ruth L Silverio
Revision #: 4 Page: 182 of 22 Printed: 10/02/00 04:23 PM
Chapter 7 Controlling Program Flow 183

Figure 7.1 shows the stack condition at key points in the process.

Figure 7.1 Procedure Arguments on the Stack

Starting with the 80186 processor, the ENTER and LEAVE instructions
simplify the stack setup and restore instructions at the beginning and end of
procedures. However, ENTER uses a lot of time. It is necessary only with
nested, statically-scoped procedures. Thus, a Pascal compiler may sometimes
generate ENTER. The LEAVE instruction, on the other hand, is an efficient
way to do the stack cleanup. LEAVE reverses the effect of the last ENTER
instruction by restoring BP and SP to their values before the procedure call.

Filename: LMAPGC07.DOC Project:


Template: MSGRIDA1.DOT Author: Ruth L Silverio Last Saved By: Ruth L Silverio
Revision #: 4 Page: 183 of 23 Printed: 10/02/00 04:23 PM
184 Programmer’s Guide

Declaring Parameters with the PROC Directive


With the PROC directive, you can specify registers to be saved, define param-
eters to the procedure, and assign symbol names to parameters (rather than as
offsets from BP). This section describes how to use the PROC directive to
automate the parameter-accessing techniques described in the last section.
For example, the following diagram shows a valid PROC statement for a
procedure called from C. It takes two parameters, var1 and arg1, and uses
(and must save) the DI and SI registers:

The syntax for PROC is:


label PROC [[attributes]] [[USES reglist]] [[, ]] [[parameter[[:tag]]... ]]
The parts of the PROC directive include:
Argument Description
label The name of the procedure.
attributes Any of several attributes of the procedure, including the distance, langtype, and
visibility of the procedure. The syntax for attributes is given on the following
page.
reglist A list of registers following the USES keyword that the procedure uses, and that
should be saved on entry. Registers in the list must be separated by blanks or
tabs, not by commas. The assembler generates prologue code to push these
registers onto the stack. When you exit, the assembler generates epilogue code
to pop the saved register values off the stack.
parameter The list of parameters passed to the procedure on the stack. The list can have a
variable number of parameters. See the discussion following for the syntax of
parameter. This list can be longer than one line if the continued line ends with a
comma.

This diagram shows a valid PROC definition that uses several attributes:

Filename: LMAPGC07.DOC Project:


Template: MSGRIDA1.DOT Author: Ruth L Silverio Last Saved By: Ruth L Silverio
Revision #: 4 Page: 184 of 24 Printed: 10/02/00 04:23 PM
Chapter 7 Controlling Program Flow 185

Attributes
The syntax for the attributes field is:
[[distance]] [[langtype]] [[visibility]] [[<prologuearg>]]
The explanations for these options include:
Argument Description
distance Controls the form of the RET instruction generated. Can be NEAR or FAR. If
distance is not specified, it is determined from the model declared with the
.MODEL directive. NEAR distance is assumed for TINY, SMALL,
COMPACT, and FLAT. The assembler assumes FAR distance for MEDIUM ,
LARGE , and HUGE . For 80386/486 programming with 16- and 32-bit
segments, you can specify NEAR16, NEAR32, FAR16, or FAR32.
langtype Determines the calling convention used to access parameters and restore the stack.
The BASIC, FORTRAN, and PASCAL langtypes convert procedure names to
uppercase, place the last parameter in the parameter list lowest on the stack, and
generate a RET num instruction to end the procedure. The RET adjusts the stack
upward by num, which represents the number of bytes in the argument list. This
step, called “cleaning the stack,” returns the stack pointer SP to the value it had
before the caller pushed any arguments.
The C and STDCALL langtype prefixes an underscore to the procedure name
when the procedure’s scope is PUBLIC or EXPORT and places the first
parameter lowest on the stack. SYSCALL is equivalent to the C calling convention
with no underscore prefixed to the procedure’s name. STDCALL uses caller
stack cleanup when :VARARG is specified; otherwise the called routine must
clean up the stack (see Chapter 12).
visibility Indicates whether the procedure is available to other modules. The visibility can
be PRIVATE, PUBLIC, or EXPORT. A procedure name is PUBLIC unless it
is explicitly declared as PRIVATE. If the visibility is EXPORT, the linker places
the procedure’s name in the export table for segmented executables. EXPORT
also enables PUBLIC visibility.
You can explicitly set the default visibility with the OPTION directive. OPTION
PROC:PUBLIC sets the default to public. For more information, see Chapter 1,
“Using the Option Directive.”
prologuearg Specifies the arguments that affect the generation of prologue and epilogue code
(the code MASM generates when it encounters a PROC directive or the end of a
procedure). For an explanation of prologue and epilogue code, see “Generating
Prologue and Epilogue Code,” later in this chapter.

Filename: LMAPGC07.DOC Project:


Template: MSGRIDA1.DOT Author: Ruth L Silverio Last Saved By: Ruth L Silverio
Revision #: 4 Page: 185 of 25 Printed: 10/02/00 04:23 PM
186 Programmer’s Guide

Parameters
The comma that separates parameters from reglist is optional, if both fields
appear on the same line. If parameters appears on a separate line, you must end
the reglist field with a comma. In the syntax:
parmname [[:tag]
parmname is the name of the parameter. The tag can be the qualifiedtype or
the keyword VARARG. However, only the last parameter in a list of param-
eters can use the VARARG keyword. The qualifiedtype is discussed in “Data
Types,” Chapter 1. An example showing how to reference VARARG param-
eters appears later in this section. You can nest procedures if they do not have
parameters or USES register lists. This diagram shows a procedure definition
with one parameter definition.

The procedure presented in “Passing Arguments on the Stack,” page 182, is


here rewritten using the extended PROC functionality. Prior to the procedure
call, you must push the arguments onto the stack unless you use INVOKE.
(See “Calling Procedures with INVOKE,” later in this chapter.)
addup PROC NEAR C,
arg1:WORD, arg2:WORD, count:WORD
mov ax, arg1
add ax, count
add ax, arg2
ret
addup ENDP

If the arguments for a procedure are pointers, the assembler does not generate
any code to get the value or values that the pointers reference; your program
must still explicitly treat the argument as a pointer. (For more information about
using pointers, see Chapter 3, “Using Addresses and Pointers.”)

Filename: LMAPGC07.DOC Project:


Template: MSGRIDA1.DOT Author: Ruth L Silverio Last Saved By: Ruth L Silverio
Revision #: 4 Page: 186 of 26 Printed: 10/02/00 04:23 PM
Chapter 7 Controlling Program Flow 187

In the following example, even though the procedure declares the parameters as
near pointers, you must code two MOV instructions to get the values of the
param-
eters. The first MOV gets the address of the parameters, and the second MOV
gets the parameter.
; Call from C as a FUNCTION returning an integer

.MODEL medium, c
.CODE
myadd PROC arg1:NEAR PTR WORD, arg2:NEAR PTR WORD

mov bx, arg1 ; Load first argument


mov ax, [bx]
mov bx, arg2 ; Add second argument
add ax, [bx]

ret

myadd ENDP

You can use conditional-assembly directives to make sure your pointer


parameters are loaded correctly for the memory model. For example, the
following version of myadd treats the parameters as FAR parameters, if
necessary.
.MODEL medium, c ; Could be any model
.CODE
myadd PROC arg1:PTR WORD, arg2:PTR WORD

IF @DataSize
les bx, arg1 ; Far parameters
mov ax, es:[bx]
les bx, arg2
add ax, es:[bx]
ELSE
mov bx, arg1 ; Near parameters
mov ax, [bx]
mov bx, arg2
add ax, [bx]
ENDIF

ret
myadd ENDP

Filename: LMAPGC07.DOC Project:


Template: MSGRIDA1.DOT Author: Ruth L Silverio Last Saved By: Ruth L Silverio
Revision #: 4 Page: 187 of 27 Printed: 10/02/00 04:23 PM
188 Programmer’s Guide

Using VARARG
In the PROC statement, you can append the :VARARG keyword to the last
parameter to indicate that the procedure accepts a variable number of
arguments. However, :VARARG applies only to the C, SYSCALL, or
STDCALL calling conventions (see Chapter 12). A symbol must precede
:VARARG so the procedure can access arguments as offsets from the given
variable name, as this example illustrates:
addup3 PROTO NEAR C, argcount:WORD, arg1:VARARG

invoke addup3, 3, 5, 2, 4

addup3 PROC NEAR C, argcount:WORD, arg1:VARARG


sub ax, ax ; Clear work register
sub si, si

.WHILE argcount > 0 ; Argcount has number of arguments


add ax, arg1[si] ; Arg1 has the first argument
dec argcount ; Point to next argument
inc si
inc si
.ENDW

ret ; Total is in AX
addup3 ENDP

You can pass non-default-sized pointers in the VARARG portion of the


parameter list by separately passing the segment portion and the offset portion
of the address.

Note When you use the extended PROC features and the assembler encounters
a RET instruction, it automatically generates instructions to pop saved registers,
remove local variables from the stack, and, if necessary, remove parameters. It
generates this code for each RET instruction it encounters. You can reduce code
size by having only one return and jumping to it from various locations.

Using Local Variables


In high-level languages, local variables are visible only within a procedure. In
Microsoft languages, these variables are usually stored on the stack. In
assembly-language programs, you can also have local variables. These variables
should not be confused with labels or variable names that are local to a module,
as described in Chapter 8, “Sharing Data and Procedures Among Modules and
Libraries.”

Filename: LMAPGC07.DOC Project:


Template: MSGRIDA1.DOT Author: Ruth L Silverio Last Saved By: Ruth L Silverio
Revision #: 4 Page: 188 of 28 Printed: 10/02/00 04:23 PM
Chapter 7 Controlling Program Flow 189

This section outlines the standard methods for creating local variables. The next
section shows how to use the LOCAL directive to make the assembler

Filename: LMAPGC07.DOC Project:


Template: MSGRIDA1.DOT Author: Ruth L Silverio Last Saved By: Ruth L Silverio
Revision #: 4 Page: 189 of 29 Printed: 10/02/00 04:23 PM
190 Programmer’s Guide

automatically generate local variables. When you use this directive, the
assembler generates the same instructions as those demonstrated in this section
but handles some of the details for you.
If your procedure has relatively few variables, you can usually write the most
efficient code by placing these values in registers. Use local (stack) data when
you have a large amount of temporary data for the procedure.
To use a local variable, you must save stack space for it at the start of the
procedure. A procedure can then reference the variable by its position in the
stack. At the end of the procedure, you must clean the stack by restoring the
stack pointer. This effectively throws away all local variables and regains the
stack space they occupied.
This example subtracts 2 bytes from the SP register to make room for a local
word variable, then accesses the variable as [bp-2].
push ax ; Push one argument
call task ; Call
.
.
.

task PROC NEAR


push bp ; Save base pointer
mov bp, sp ; Load stack into base pointer
sub sp, 2 ; Save two bytes for local variable
.
.
.
mov WORD PTR [bp-2], 3 ; Initialize local variable
add ax, [bp-2] ; Add local variable to AX
sub [bp+4], ax ; Subtract local from argument
. ; Use [bp-2] and [bp+4] in
. ; other operations
.
mov sp, bp ; Clear local variables
pop bp ; Restore base
ret 2 ; Return result in AX and pop
task ENDP ; two bytes to clear parameter

Notice the instruction mov sp,bp at the end of the procedure restores the
original value of SP. The statement is required only if the value of SP changes
inside the procedure (usually by allocating local variables). The argument passed
to the procedure is removed with the RET instruction. Contrast this to the
example in “Passing Arguments on the Stack,” page 182, in which the calling
code adjusts the stack for the argument.

Filename: LMAPGC07.DOC Project:


Template: MSGRIDA1.DOT Author: Ruth L Silverio Last Saved By: Ruth L Silverio
Revision #: 4 Page: 190 of 30 Printed: 10/02/00 04:23 PM
Chapter 7 Controlling Program Flow 191

Figure 7.2 shows the stack at key points in the process.

Figure 7.2 Local Variables on the Stack

Creating Local Variables Automatically


MASM’s LOCAL directive automates the process for creating local variables
on the stack. LOCAL frees you from having to count stack words, and it
makes your code easier to write and maintain. This section illustrates the
advantages of creating temporary data with the LOCAL directive.
To use the LOCAL directive, list the variables you want to create, giving a type
for each one. The assembler calculates how much space is required on the
stack. It also generates instructions to properly decrement SP (as described in
the previous section) and to reset SP when you return from the procedure.
When you create local variables this way, your source code can refer to each
local variable by name rather than as an offset of the stack pointer. Moreover,

Filename: LMAPGC07.DOC Project:


Template: MSGRIDA1.DOT Author: Ruth L Silverio Last Saved By: Ruth L Silverio
Revision #: 4 Page: 191 of 31 Printed: 10/02/00 04:23 PM
192 Programmer’s Guide

the assembler generates debugging information for each local variable. If you
have programmed before in a high-level language that allows scoping, local
variables will seem familiar. For example, a C compiler sets up variables with
automatic storage class in the same way as the LOCAL directive.
We can simplify the procedure in the previous section with the following code:
task PROC NEAR arg:WORD
LOCAL loc:WORD
.
.
.
mov loc, 3 ; Initialize local variable
add ax, loc ; Add local variable to AX
sub arg, ax ; Subtract local from argument
. ; Use "loc" and "arg" in other operations
.
.
ret
task ENDP

The LOCAL directive must be on the line immediately following the PROC
statement with the following syntax:
LOCAL vardef [[, vardef]]...
Each vardef defines a local variable. A local variable definition has this form:
label[[[count]]][[:qualifiedtype]]
These are the parameters in local variable definitions:
Argument Description
label The name given to the local variable. You can use this name to access the
variable.
count The number of elements of this name and type to allocate on the stack. You
can allocate a simple array on the stack with count. The brackets around
count are required. If this field is omitted, one data object is assumed.
qualifiedtype A simple MASM type or a type defined with other types and attributes. For
more information, see “Data Types” in Chapter 1.

If the number of local variables exceeds one line, you can place a comma at the
end of the first line and continue the list on the next line. Alternatively, you can
use several consecutive LOCAL directives.

Filename: LMAPGC07.DOC Project:


Template: MSGRIDA1.DOT Author: Ruth L Silverio Last Saved By: Ruth L Silverio
Revision #: 4 Page: 192 of 32 Printed: 10/02/00 04:23 PM
Chapter 7 Controlling Program Flow 193

The assembler does not initialize local variables. Your program must include
code to perform any necessary initializations. For example, the following code
fragment sets up a local array and initializes it to zero:
arraysz EQU 20

aproc PROC USES di


LOCAL var1[arraysz]:WORD, var2:WORD
.
.
.
; Initialize local array to zero
push ss
pop es ; Set ES=SS
lea di, var1 ; ES:DI now points to array
mov cx, arraysz ; Load count
sub ax, ax
rep stosw ; Store zeros
; Use the array...
.
.
.
ret
aproc ENDP

Even though you can reference stack variables by name, the assembler treats
them as offsets of BP, and they are not visible outside the procedure. In the
following procedure, array is a local variable.
index EQU 10
test PROC NEAR
LOCAL array[index]:WORD
.
.
.
mov bx, index
; mov array[bx], 5 ; Not legal!

The second MOV statement may appear to be legal, but since array is an
offset of BP, this statement is the same as
; mov [bp + bx + arrayoffset], 5 ; Not legal!

BP and BX can be added only to SI and DI. This example would be legal,
however, if the index value were moved to SI or DI. This type of error in your
program can be difficult to find unless you keep in mind that local variables in
procedures are offsets of BP.

Filename: LMAPGC07.DOC Project:


Template: MSGRIDA1.DOT Author: Ruth L Silverio Last Saved By: Ruth L Silverio
Revision #: 4 Page: 193 of 33 Printed: 10/02/00 04:23 PM
194 Programmer’s Guide

Declaring Procedure Prototypes


MASM provides the INVOKE directive to handle many of the details important
to procedure calls, such as pushing parameters according to the correct calling
conventions. To use INVOKE, the procedure called must have been declared
previously with a PROC statement, an EXTERNDEF (or EXTERN) statement,
or a TYPEDEF. You can also place a prototype defined with PROTO before
the INVOKE if the procedure type does not appear before the INVOKE.
Procedure prototypes defined with PROTO inform the assembler of types and
numbers of arguments so the assembler can check for errors and provide
automatic conversions when INVOKE calls the procedure.
Declaring procedure prototypes is good programming practice, but is optional.
Prototypes in MASM perform the same function as prototypes in C and other
high-level languages. A procedure prototype includes the procedure name, the
types, and (optionally) the names of all parameters the procedure expects.
Prototypes usually are placed at the beginning of an assembly program or in a
separate include file so the assembler encounters the prototype before the actual
procedure.
Prototypes enable the assembler to check for unmatched parameters and are
especially useful for procedures called from other modules and other languages.
If you write routines for a library, you may want to put prototypes into an
include file for all the procedures used in that library. For more information
about using include files, see Chapter 8, “Sharing Data and Procedures among
Modules and Libraries.”
The PROTO directive provides one way to define a procedure prototype. The
syntax for a prototype definition is the same as for a procedure declaration (see
“Declaring Parameters with the PROC Directive,” earlier in this chapter), except
that you do not include the list of registers, prologuearg list, or the scope of the
procedure.
Also, the PROTO keyword precedes the langtype and distance attributes. The
attributes (like C and FAR) are optional. However, if they are not specified, the
defaults are based on any .MODEL or OPTION LANGUAGE statement. The
names of the parameters are also optional, but you must list parameter types. A
label preceding :VARARG is also optional in the prototype but not in the
PROC statement.
If a PROTO and a PROC for the same function appear in the same module,
they must match in attribute, number of parameters, and parameter types. The
easiest way to create prototypes with PROTO is to write your procedure and
then copy the first line (the line that contains the PROC keyword) to a location
in your program that follows the data declarations. Change PROC to PROTO
and remove the USES reglist, the prologuearg field, and the visibility field. It

Filename: LMAPGC07.DOC Project:


Template: MSGRIDA1.DOT Author: Ruth L Silverio Last Saved By: Ruth L Silverio
Revision #: 4 Page: 194 of 34 Printed: 10/02/00 04:23 PM
Chapter 7 Controlling Program Flow 195

is important that the prototype follow the declarations for any types used in it to
avoid any forward references used by the parameters in the prototype.
The following example illustrates how to define and then declare two typical
procedures. In both prototype and declaration, the comma before the argument
list is optional only when the list does not appear on a separate line:
; Procedure prototypes.

addup PROTO NEAR C argcount:WORD, arg2:WORD, arg3:WORD


myproc PROTO FAR C, argcount:WORD, arg2:VARARG

; Procedure declarations

addup PROC NEAR C, argcount:WORD, arg2:WORD, arg3:WORD


.
.
.
myproc PROC FAR C PUBLIC <callcount> USES di si,
argcount:WORD,
arg2:VARARG

When you call a procedure with INVOKE, the assembler checks the arguments
given by INVOKE against the parameters expected by the procedure. If the
data types of the arguments do not match, MASM reports an error or converts
the type to the expected type. These conversions are explained in the next
section.

Calling Procedures with INVOKE


INVOKE generates a sequence of instructions that push arguments and call a
procedure. This helps maintain code if arguments or langtype for a procedure
are changed. INVOKE generates procedure calls and automatically:
u Converts arguments to the expected types.
u Pushes arguments on the stack in the correct order.
u Cleans the stack when the procedure returns.

If arguments do not match in number or if the type is not one the assembler can
convert, an error results.
If the procedure uses VARARG, INVOKE can pass a number of arguments
different from the number in the parameter list without generating an error or
warning. Any additional arguments must be at the end of the INVOKE
argument list. All other arguments must match those in the prototype parameter
list.

Filename: LMAPGC07.DOC Project:


Template: MSGRIDA1.DOT Author: Ruth L Silverio Last Saved By: Ruth L Silverio
Revision #: 4 Page: 195 of 35 Printed: 10/02/00 04:23 PM
196 Programmer’s Guide

The syntax for INVOKE is:


INVOKE expression [[, arguments]]
where expression can be the procedure’s label or an indirect reference to a
procedure, and arguments can be an expression, a register pair, or an expression
preceded with ADDR. (The ADDR operator is discussed later in this chapter.)
Procedures with these prototypes
addup PROTO NEAR C argcount:WORD, arg2:WORD, arg3:WORD
myproc PROTO FAR C, argcount:WORD, arg2:VARARG

and these procedure declarations


addup PROC NEAR C, argcount:WORD, arg2:WORD, arg3:WORD
.
.
.
myproc PROC FAR C PUBLIC <callcount> USES di si,
argcount:WORD,
arg2:VARARG

can be called with INVOKE statements like this:


INVOKE addup, ax, x, y
INVOKE myproc, bx, cx, 100, 10

The assembler can convert some arguments and parameter type combinations
so that the correct type can be passed. The signed or unsigned qualities of the
arguments in the INVOKE statements determine how the assembler converts
them to the types expected by the procedure.
The addup procedure, for example, expects parameters of type WORD, but
the arguments passed by INVOKE to the addup procedure can be any of these
types:
u BYTE, SBYTE, WORD, or SWORD
u An expression whose type is specified with the PTR operator to be one of
those types
u An 8-bit or 16-bit register
u An immediate expression in the range –32K to +64K
u A NEAR PTR

If the type is smaller than that expected by the procedure, MASM widens the
argument to match.

Filename: LMAPGC07.DOC Project:


Template: MSGRIDA1.DOT Author: Ruth L Silverio Last Saved By: Ruth L Silverio
Revision #: 4 Page: 196 of 36 Printed: 10/02/00 04:23 PM
Chapter 7 Controlling Program Flow 197

Widening Arguments
For INVOKE to correctly handle type conversions, you must use the signed
data types for any signed assignments. MASM widens an argument to match the
type expected by a procedure’s parameters in these cases:
Type Passed Type Expected
BYTE, SBYTE WORD, SWORD, DWORD, SDWORD
WORD, SWORD DWORD, SDWORD

The assembler can extend a segment if far data is expected, and it can convert
the type given in the list to the types expected. If the assembler cannot convert
the type, however, it generates an error.

Detecting Errors
If the assembler needs to widen an argument, it first copies the value to AL or
AX. It widens an unsigned value by placing a zero in the higher register area,
and widens a signed value with a CBW, CWD, or CWDE instruction as
required. Similarly, the assembler copies a constant argument value into AL or
AX when the .8086 directive is in effect. You can see these generated
instructions in the listing file when you include the /Sg command-line option.
Using the accumulator register to widen or copy an argument may lead to an
error if you attempt to pass AX as another argument. For example, consider the
following INVOKE statement for a procedure with the C calling convention
INVOKE myprocA, ax, cx, 100, arg

where arg is a BYTE variable and myproc expects four arguments of type
WORD. The assembler widens and then pushes arg like this:
mov al, DGROUP:arg
xor ah, ah
push ax

The generated code thus overwrites the last argument (AX) passed to the
procedure. The assembler generates an error in this case, requiring you to
rewrite the INVOKE statement.
To summarize, the INVOKE directive overwrites AX and perhaps DX when
widening arguments. It also uses AX to push constants on the 8088 and 8086. If
you use these registers (or EAX and EDX on an 80386/486) to pass arguments,
they may be overwritten. The assembler’s error detection prevents this from
ever becoming a run-time bug, but AX and DX should remain your last choice
for holding arguments.

Filename: LMAPGC07.DOC Project:


Template: MSGRIDA1.DOT Author: Ruth L Silverio Last Saved By: Ruth L Silverio
Revision #: 4 Page: 197 of 37 Printed: 10/02/00 04:23 PM
198 Programmer’s Guide

Invoking Far Addresses


You can pass a FAR pointer in a segment::offset pair, as shown in the following.
Note the use of double colons to separate the register pair. The registers could
be any other register pair, including a pair that an MS-DOS call uses to return
values.
FPWORD TYPEDEF FAR PTR WORD
SomeProc PROTO var1:DWORD, var2:WORD, var3:WORD

pfaritem FPWORD faritem


.
.
.
les bx, pfaritem
INVOKE SomeProc, ES::BX, arg1, arg2

However, INVOKE cannot combine into a single address one argument for the
segment and one for the offset.

Passing an Address
You can use the ADDR operator to pass the address of an expression to a
procedure that expects a NEAR or FAR pointer. This example generates code
to pass a far pointer (to arg1) to the procedure proc1.
PBYTE TYPEDEF FAR PTR BYTE
arg1 BYTE "This is a string"
proc1 PROTO NEAR C fparg:PBYTE
.
.
.
INVOKE proc1, ADDR arg1

For information on defining pointers with TYPEDEF, see “Defining Pointer


Types with TYPEDEF” in Chapter 3.

Filename: LMAPGC07.DOC Project:


Template: MSGRIDA1.DOT Author: Ruth L Silverio Last Saved By: Ruth L Silverio
Revision #: 4 Page: 198 of 38 Printed: 10/02/00 04:23 PM
Chapter 7 Controlling Program Flow 199

Invoking Procedures Indirectly


You can make an indirect procedure call such as call [bx + si] by using a
pointer to a function prototype with TYPEDEF, as shown in this example:
FUNCPROTO TYPEDEF PROTO NEAR ARG1:WORD
FUNCPTR TYPEDEF PTR FUNCPROTO

.DATA
pfunc FUNCPTR OFFSET proc1, OFFSET proc2

.CODE
.
.
.
mov bx, OFFSET pfunc ; BX points to table
mov si, Num ; Num contains 0 or 2
INVOKE FUNCPTR PTR [bx+si], arg1 ; Call proc1 if Num=0
; or proc2 if Num=2

You can also use ASSUME to accomplish the same task. The following
ASSUME statement associates the type FUNCPTR with the BX register.
ASSUME BX:FUNCPTR
mov bx, OFFSET pfunc
mov si, Num
INVOKE [bx+si], arg1

Checking the Code Generated


Code generated by the INVOKE directive may vary depending on the processor
mode and calling conventions in effect. You can check your listing files to see
the code generated by the INVOKE directive if you use the /Sg command-line
option.

Generating Prologue and Epilogue Code


When you use the PROC directive with its extended syntax and argument list,
the assembler automatically generates the prologue and epilogue code in your
procedure. “Prologue code” is generated at the start of the procedure. It sets up
a stack pointer so you can access parameters from within the procedure. It also
saves space on the stack for local variables, initializes registers such as DS, and
pushes registers that the procedure uses. Similarly, “epilogue code” is the code
at the end of the procedure that pops registers and returns from the procedure.

Filename: LMAPGC07.DOC Project:


Template: MSGRIDA1.DOT Author: Ruth L Silverio Last Saved By: Ruth L Silverio
Revision #: 4 Page: 199 of 39 Printed: 10/02/00 04:23 PM
200 Programmer’s Guide

The assembler automatically generates the prologue code when it encounters the
first instruction or label after the PROC directive. This means you cannot label
the prologue for the purpose of jumping to it. The assembler generates the
epilogue code when it encounters a RET or IRET instruction. Using the
assembler-generated prologue and epilogue code saves time and decreases the
number of repetitive lines of code in your procedures.
The generated prologue or epilogue code depends on the:
u Local variables defined.
u Arguments passed to the procedure.
u Current processor selected (affects epilogue code only).
u Current calling convention.
u Options passed in the prologuearg of the PROC directive.
u Registers being saved.

The prologuearg list contains options specifying how to generate the prologue
or epilogue code. The next section explains how to use these options, gives the
standard prologue and epilogue code, and explains the techniques for defining
your own prologue and epilogue code.

Using Automatic Prologue and Epilogue Code


The standard prologue and epilogue code handles parameters and local
variables. If a procedure does not have any parameters or local variables, the
prologue and epilogue code that sets up and restores a stack pointer is omitted,
unless
FORCEFRAME is included in the prologuearg list. (FORCEFRAME is
discussed later in this section.) Prologue and epilogue code also generates a push
and pop for each register in the register list.
The prologue code consists of three steps:
1. Point BP to top of stack.
2. Make space on stack for local variables.
3. Save registers the procedure must preserve.

Filename: LMAPGC07.DOC Project:


Template: MSGRIDA1.DOT Author: Ruth L Silverio Last Saved By: Ruth L Silverio
Revision #: 4 Page: 200 of 40 Printed: 10/02/00 04:23 PM
Chapter 7 Controlling Program Flow 201

The epilogue cancels these three steps in reverse order, then cleans the stack, if
necessary, with a RET num instruction. For example, the procedure declaration
myproc PROC NEAR PASCAL USES di si,
arg1:WORD, arg2:WORD, arg3:WORD
LOCAL local1:WORD, local2:WORD

generates the following prologue code:


push bp ; Step 1:
mov bp, sp ; point BP to stack top
sub sp, 4 ; Step 2: space for 2 local words
push di ; Step 3:
push si ; save registers listed in USES

The corresponding epilogue code looks like this:


pop si ; Undo Step 3
pop di
mov sp, bp ; Undo Step 2
pop bp ; Undo Step 1
ret 6 ; Clean stack of pushed arguments

Notice the RET 6 instruction cleans the stack of the three word-sized
arguments. The instruction appears in the epilogue because the procedure does
not use the C calling convention. If myproc used C conventions, the epilogue
would end with a RET instruction without an operand.
The assembler generates standard epilogue code when it encounters a RET
instruction without an operand. It does not generate an epilogue if RET has a
nonzero operand. To suppress generation of a standard epilogue, use RETN or
RETF with or without an operand, or use RET 0.
The standard prologue and epilogue code recognizes two operands passed in the
prologuearg list, LOADDS and FORCEFRAME. These operands modify the
prologue code. Specifying LOADDS saves and initializes DS. Specifying
FORCEFRAME as an argument generates a stack frame even if no arguments
are sent to the procedure and no local variables are declared. If your procedure
has any parameters or locals, you do not need to specify FORCEFRAME.

Filename: LMAPGC07.DOC Project:


Template: MSGRIDA1.DOT Author: Ruth L Silverio Last Saved By: Ruth L Silverio
Revision #: 4 Page: 201 of 41 Printed: 10/02/00 04:23 PM
202 Programmer’s Guide

For example, adding LOADDS to the argument list for myproc creates this
prologue:
push bp ; Step 1:
mov bp, sp ; point BP to stack top
sub sp, 4 ; Step 2: space for 2 locals
push ds ; Save DS and point it
mov ax, DGROUP ; to DGROUP, as
mov ds, ax ; instructed by LOADDS
push di ; Step 3:
push si ; save registers listed in USES

The epilogue code restores DS:


pop si ; Undo Step 3
pop di
pop ds ; Restore DS
mov sp, bp ; Undo Step 2
pop bp ; Undo Step 1
ret 6 ; Clean stack of pushed arguments

User-Defined Prologue and Epilogue Code


If you want a different set of instructions for prologue and epilogue code in your
procedures, you can write macros that run in place of the standard prologue and
epilogue code. For example, while you are debugging your procedures, you may
want to include a stack check or track the number of times a procedure is called.
You can write your own prologue code to do these things whenever a procedure
executes. Different prologue code may also be necessary if you are writing
applications for Windows. User-defined prologue macros will respond correctly
if you specify FORCEFRAME in the prologuearg of a procedure.
To write your own prologue or epilogue code, the OPTION directive must
appear in your program. It disables automatic prologue and epilogue code
generation. When you specify
OPTION PROLOGUE : macroname
OPTION EPILOGUE : macroname
the assembler calls the macro specified in the OPTION directive instead of
generating the standard prologue and epilogue code. The prologue macro must
be a macro function, and the epilogue macro must be a macro procedure.

Filename: LMAPGC07.DOC Project:


Template: MSGRIDA1.DOT Author: Ruth L Silverio Last Saved By: Ruth L Silverio
Revision #: 4 Page: 202 of 42 Printed: 10/02/00 04:23 PM
Chapter 7 Controlling Program Flow 203

The assembler expects your prologue or epilogue macro to have this form:
macroname MACRO procname, \
flag, \
parmbytes, \
localbytes, \
<reglist>, \
userparms
Your macro must have formal parameters to match all the actual arguments
passed. The arguments passed to your macro include:
Argument Description
procname The name of the procedure.
flag A 16-bit flag containing the following information:
Bit = Value Description
Bit 0, 1, 2 For calling conventions (000=unspecified language type,
001=C, 010=SYSCALL, 011=STDCALL,
100=PASCAL, 101=FORTRAN, 110=BASIC).
Bit 3 Undefined (not necessarily zero).
Bit 4 Set if the caller restores the stack
(use RET, not RETn).
Bit 5 Set if procedure is FAR.
Bit 6 Set if procedure is PRIVATE.
Bit 7 Set if procedure is EXPORT.
Bit 8 Set if the epilogue is generated as a result of an IRET
instruction and cleared if the epilogue is generated as a
result of a RET instruction.
Bits 9–15 Undefined (not necessarily zero).
parmbytes The accumulated count in bytes of all parameters given in the PROC
statement.
localbytes The count in bytes of all locals defined with the LOCAL directive.
reglist A list of the registers following the USES operator in the procedure
declaration. Enclose this list with angle brackets (< >) and separate each item
with commas. Reverse the list for epilogues.
userparms Any argument you want to pass to the macro. The prologuearg (if there is
one) specified in the PROC directive is passed to this argument.

Your macro function must return the parmbytes parameter. However, if the
prologue places other values on the stack after pushing BP and these values are
not referenced by any of the local variables, the exit value must be the number
of bytes for procedure locals plus any space between BP and the locals.
Therefore, parmbytes is not always equal to the bytes occupied by the locals.

Filename: LMAPGC07.DOC Project:


Template: MSGRIDA1.DOT Author: Ruth L Silverio Last Saved By: Ruth L Silverio
Revision #: 4 Page: 203 of 43 Printed: 10/02/00 04:23 PM
204 Programmer’s Guide

The following macro is an example of a user-defined prologue that counts the


number of times a procedure is called.
ProfilePro MACRO procname, \
flag, \
bytecount, \
numlocals, \
regs, \
macroargs

.DATA
procname&count WORD 0
.CODE
inc procname&count ; Accumulates count of times the
; procedure is called
push bp
mov bp, sp
; Other BP operations
IFNB <regs>
FOR r, regs
push r
ENDM
ENDIF
EXITM %bytecount
ENDM

Your program must also include this statement before calling any procedures
that use the prologue:
OPTION PROLOGUE:ProfilePro

If you define either a prologue or an epilogue macro, the assembler uses the
standard prologue or epilogue code for the one you do not define. The form of
the code generated depends on the .MODEL and PROC options used.
If you want to revert to the standard prologue or epilogue code, use
PROLOGUEDEF or EPILOGUEDEF as the macroname in the OPTION
statement.
OPTION EPILOGUE:EPILOGUEDEF

You can completely suppress prologue or epilogue generation with


OPTION PROLOGUE:None
OPTION EPILOGUE:None

In this case, no user-defined macro is called, and the assembler does not
generate a default code sequence. This state remains in effect until the next
OPTION
PROLOGUE or OPTION EPILOGUE is encountered.

Filename: LMAPGC07.DOC Project:


Template: MSGRIDA1.DOT Author: Ruth L Silverio Last Saved By: Ruth L Silverio
Revision #: 4 Page: 204 of 44 Printed: 10/02/00 04:23 PM
Chapter 7 Controlling Program Flow 205

For additional information about writing macros, see Chapter 9, “Using


Macros.” The PROLOGUE.INC file provided in the MASM 6.1 distribution
disks can create the prologue and epilogue sequences for the Microsoft C
professional development system.

MS-DOS Interrupts
In addition to jumps, loops, and procedures that alter program execution,
interrupt routines transfer execution to a different location. In this case, control
goes to an interrupt routine.
You can write your own interrupt routines, either to replace an existing routine
or to use an undefined interrupt number. For example, you may want to replace
an MS-DOS interrupt handler, such as the Critical Error (Interrup 24h) and
CONTROL+C (Interrupt 23h) handlers. The BOUND instruction checks array
bounds and calls Interrupt 5 when an error occurs. If you use this instruction,
you need to write an interrupt handler for it.
This section summarizes the following:
u How to call interrupts
u How the processor handles interrupts
u How to redefine an existing interrupt routine

The example routine in this section handles addition or multiplication overflow


and illustrates the steps necessary for writing an interrupt routine. For additional
information about MS-DOS and BIOS interrupts, see Chapter 11, “Writing
Memory-Resident Software.”

Calling MS-DOS and ROM-BIOS Interrupts


Interrupts provide a way to access MS-DOS and ROM-BIOS from assembly
language. They are called with the INT instruction, which takes an immediate
value between 0 and 255 as its only operand.
MS-DOS and ROM-BIOS interrupt routines accept data through registers. For
instance, most MS-DOS routines (and many BIOS routines) require a function
number in the AH register. Many handler routines also return values in registers.
To use an interrupt, you must know what data the handler routine expects and
what data, if any, it returns. For information, consult Help or one of the other
references mentioned in the Introduction.

Filename: LMAPGC07.DOC Project:


Template: MSGRIDA1.DOT Author: Ruth L Silverio Last Saved By: Ruth L Silverio
Revision #: 4 Page: 205 of 45 Printed: 10/02/00 04:23 PM
206 Programmer’s Guide

The following fragment illustrates a simple call to MS-DOS Function 9, which


displays the string msg on the screen:
.DATA
msg BYTE "This writes to the screen$"
.CODE
mov ax, SEG msg ; Necessary only if DS does not
mov ds, ax ; already point to data segment
mov dx, offset msg ; DS:DX points to msg
mov ah, 09h ; Request Function 9
int 21h

When the INT instruction executes, the processor:


1. Looks up the address of the interrupt routine in the Interrupt Vector Table.
This table starts at the lowest point in memory (segment 0, offset 0) and
consists of a series of far pointers called vectors. Each vector comprises a 4-
byte address (segment:offset) pointing to an interrupt handler routine. The
table sequence implies the number of the interrupt the vector references: the
first vector points to the Interrupt 0 handler, the second vector to the
Interrupt 1 handler, and so forth. Thus, the vector at 0000:i*4 holds the
address of the handler routine for Interrupt i.
2. Clears the trap flag (TF) and interrupt enable flag (IF).
3. Pushes the flags register, the current code segment (CS), and the current
instruction pointer (IP), in that order. (The current instruction is the one
following the INT statement.) As with a CALL, this ensures control returns
to the next logical position in the program.
4. Jumps to the address of the interrupt routine, as specified in the Interrupt
Vector Table.
5. Executes the code of the interrupt routine until it encounters an IRET
instruction.
6. Pops the instruction pointer, code segment, and flags.

Filename: LMAPGC07.DOC Project:


Template: MSGRIDA1.DOT Author: Ruth L Silverio Last Saved By: Ruth L Silverio
Revision #: 4 Page: 206 of 46 Printed: 10/02/00 04:23 PM
Chapter 7 Controlling Program Flow 207

Figure 7.3 illustrates how interrupts work.

Figure 7.3 Operation of Interrupts

Replacing an Interrupt Routine


To replace an existing interrupt routine, your program must:
u Provide a new routine to handle the interrupt.
u Replace the old routine’s address in the Interrupt Vector Table with the
address of your new routine.
u Replace the old address back into the vector table before your program ends.

You can write an interrupt routine as a procedure by using the PROC and
ENDP directives. The routine should always be defined as FAR and should end
with an IRET instruction instead of a RET instruction.

Filename: LMAPGC07.DOC Project:


Template: MSGRIDA1.DOT Author: Ruth L Silverio Last Saved By: Ruth L Silverio
Revision #: 4 Page: 207 of 47 Printed: 10/02/00 04:23 PM
208 Programmer’s Guide

Note You can use the full extended PROC syntax (described in “Declaring
Parameters with the PROC Directive,” earlier in this chapter) to write interrupt
procedures. However, you should not make interrupt procedures NEAR or
specify arguments for them. You can use the USES keyword, however, to
correctly generate code to save and restore a register list in interrupt procedures.

The IRET instruction in MASM 6.1 has two forms that suppress epilogue code.
This allows an interrupt to have local variables or use a user-defined prologue.
IRETF pops a FAR16 return address, and IRETFD pops a FAR32 return
address.
The following example shows how to replace the handler for Interrupt 4. Once
registered in the Interrupt Vector Table, the new routine takes control when the
processor encounters either an INT 4 instruction or its special variation INTO
(Interrupt on Overflow). INTO is a conditional instruction that acts only when
the overflow flag is set. With INTO after a numerical calculation, your code can
automatically route control to a handler routine if the calculation results in a
numerical overflow. By default, the routine for Interrupt 4 simply consists of an
IRET, so it returns without doing anything. Using INTO is an alternative to
using JO (Jump on Overflow) to jump to another set of instructions.
The following example program first executes INT 21h to invoke MS-DOS
Function 35h (Get Interrupt Vector). This function returns the existing vector
for Interrupt 4. The program stores the vector, then invokes MS-DOS Function
25h (Set Interrupt Vector) to place the address of the ovrflow procedure in the
Interrupt Vector Table. From this point on, ovrflow gains control whenever
the processor executes INTO while the overflow flag is set. The new routine
displays a message and returns with AX and DX set to 0.
.MODEL LARGE, C
FPFUNC TYPEDEF FAR PTR
.DATA
msg BYTE "Overflow - result set to 0",13,10,'$'
vector FPFUNC ?
.CODE
.STARTUP

mov ax, 3504h ; Load Interrupt 4 and call DOS


int 21h ; Get Interrupt Vector
mov WORD PTR vector[2],es ; Save segment
mov WORD PTR vector[0],bx ; and offset

Filename: LMAPGC07.DOC Project:


Template: MSGRIDA1.DOT Author: Ruth L Silverio Last Saved By: Ruth L Silverio
Revision #: 4 Page: 208 of 48 Printed: 10/02/00 04:23 PM
Chapter 7 Controlling Program Flow 209

push ds ; Save DS
mov ax, cs ; Load segment of new routine
mov ds, ax
mov dx, OFFSET ovrflow ; Load offset of new routine
mov ax, 2504h ; Load Interrupt 4 and call DOS
int 21h ; Set Interrupt Vector
pop ds ; Restore
.
.
.
add ax, bx ; Do arithmetic
into ; Call Interrupt 4 if overflow
.
.
.
lds dx, vector ; Load original address
mov ax, 2504h ; Restore it to vector table
int 21h ; with DOS set vector function
mov ax, 4C00h ; Terminate function
int 21h

ovrflow PROC FAR


sti ; Enable interrupts
; (turned off by INT)
mov ah, 09h ; Display string function
mov dx, OFFSET msg ; Load address
int 21h ; Call DOS
sub ax, ax ; Set AX to 0
cwd ; Set DX to 0
iret ; Return
ovrflow ENDP
END

Before the program ends, it again uses MS-DOS Function 25h to reset the
original Interrupt 4 vector back into the Interrupt Vector Table. This
reestablishes the original routine as the handler for Interrupt 4.
The first instruction of the ovrflow routine warrants further discussion. When
the processor encounters an INT instruction, it clears the interrupt flag before
branching to the specified interrupt handler routine. The interrupt flag serves a
crucial role in smoothing the processor’s tasks, but must not be abused. When
clear, the flag inhibits hardware interrupts such as the keyboard or system timer.
It should be left clear only briefly and only when absolutely necessary. Unless
you have a

Filename: LMAPGC07.DOC Project:


Template: MSGRIDA1.DOT Author: Ruth L Silverio Last Saved By: Ruth L Silverio
Revision #: 4 Page: 209 of 49 Printed: 10/02/00 04:23 PM
210 Programmer’s Guide

compelling reason to leave the flag clear, always include an STI (Set Interrupt
Flag) instruction at the beginning of your interrupt handler routine to reenable
hardware interrupts.
CLI (Clear Interrupt Flag) and its corollary STI are designed to protect small
sections of time-dependent code from interruptions by the hardware. If you use
CLI in your program, be sure to include a matching STI instruction as well. The
sample interrupt handlers in Chapter 11, “Writing Memory-Resident Software,”
illustrate how to use these important instructions.

Filename: LMAPGC07.DOC Project:


Template: MSGRIDA1.DOT Author: Ruth L Silverio Last Saved By: Ruth L Silverio
Revision #: 4 Page: 210 of 50 Printed: 10/02/00 04:23 PM
210 Programmer’s Guide

Filename: LMAPGC07.DOC Project:


Template: MSGRIDA1.DOT Author: Ruth L Silverio Last Saved By: Ruth L Silverio
Revision #: 4 Page: 210 of 52 Printed: 10/02/00 04:23 PM

You might also like