Hello Again Professor Hyde,We are currently working on ways to publish this text in a form other than HTML (e.g., Postscript, PDF, Frameviewer, hard copy, etc.). This, however, is a low-priority project. Please do not contact Randall Hyde concerning this effort. When something happens, an announcement will appear on "Randall Hyde's Assembly Language Page." Please visit this WEB site at http://webster.ucr.edu for the latest scoop.
Dallas gave me permission to take orders for the Computer Science 13 Manuals. We would need to take charge card orders. The only cards we take are: Master Card, Visa, and Discover. They would need to send the name, numbers, expiration date, type of card, and authorization to charge $95.00 for the manual and shipping, also we should have their phone number in case the company has any trouble delivery. They can use my e-mail address for the orders and I will process them as soon as possible. I would assume that two weeks would be sufficient for printing, packages and delivery time.
I am open to suggestions if you can think of any to make this as easy as possible.
Thank You for your business,
Kathy Chapman, Assistant
Printing and Reprographics
University of California
Riverside
(909) 787-4443/4444
call instruction, the procedure
returns to the caller with the ret instruction. For example,
the following 80x86 instruction calls the UCR Standard Library sl_putcr
routine[1]:
call sl_putcr
sl_putcr prints a carriage return/line feed sequence to the
video display and returns control to the instruction immediately following
the call sl_putcr instruction. ret instruction. For example, the following "procedure"
zeros out the 256 bytes starting at the address in the bx register:
ZeroBytes: xor ax, ax
mov cx, 128
ZeroLoop: mov [bx], ax
add bx, 2
loop ZeroLoop
ret
By loading the bx register with the address of some block of
256 bytes and issuing a call ZeroBytes instruction, you can
zero out the specified block. proc and endp assembler
directives. The ZeroBytes routine, using the proc
and endp directives, is
ZeroBytes proc
xor ax, ax
mov cx, 128
ZeroLoop: mov [bx], ax
add bx, 2
loop ZeroLoop
ret
ZeroBytes endp
Keep in mind that proc and endp are assembler
directives. They do not generate any code. They're simply a mechanism to
help make your programs easier to read. To the 80x86, the last two examples
are identical; however, to a human being, latter is clearly a self-contained
procedure, the other could simply be an arbitrary set of instructions within
some other procedure. Consider now the following code:
ZeroBytes: xor ax, ax
jcxz DoFFs
ZeroLoop: mov [bx], ax
add bx, 2
loop ZeroLoop
ret
DoFFs: mov cx, 128
mov ax, 0ffffh
FFLoop: mov [bx], ax
sub bx, 2
loop FFLoop
ret
Are there two procedures here or just one? In other words, can a calling
program enter this code at labels ZeroBytes and DoFFs
or just at ZeroBytes? The use of the proc and
endp directives can help remove this ambiguity:
ZeroBytes proc
xor ax, ax
jcxz DoFFs
ZeroLoop: mov [bx], ax
add bx, 2
loop ZeroLoop
ret
DoFFs: mov cx, 128
mov ax, 0ffffh
FFLoop: mov [bx], ax
sub bx, 2
loop FFLoop
ret
ZeroBytes endp
Treated as two separate routines:
ZeroBytes proc
xor ax, ax
jcxz DoFFs
ZeroLoop: mov [bx], ax
add bx, 2
loop ZeroLoop
ret
ZeroBytes endp
DoFFs proc
mov cx, 128
mov ax, 0ffffh
FFLoop: mov [bx], ax
sub bx, 2
loop FFLoop
ret
DoFFs endp
Always keep in mind that the proc and endp directives are logical procedure
separators. The 80x86 microprocessor returns from a procedure by executing
a ret instruction, not by encountering an endp directive. The following
is not equivalent to the code above:
ZeroBytes proc
xor ax, ax
jcxz DoFFs
ZeroLoop: mov [bx], ax
add bx, 2
loop ZeroLoop
; Note missing RET instr.
ZeroBytes endp
DoFFs proc
mov cx, 128
mov ax, 0ffffh
FFLoop: mov [bx], ax
sub bx, 2
loop FFLoop
; Note missing RET instr.
DoFFs endp
Without the ret instruction at the end of each procedure, the 80x86 will
fall into the next subroutine rather than return to the caller. After executing
ZeroBytes above, the 80x86 will drop through to the DoFFs subroutine (beginning
with the mov cx, 128 instruction). Once DoFFs is through, the 80x86 will
continue execution with the next executable instruction following DoFFs'
endp directive.
An 80x86 procedure takes the form:
ProcName proc {near|far} ;Choose near, far, or neither.
<Procedure instructions>
ProcName endp
The near or far operand is optional, the next
section will discuss its purpose. The procedure name must be on the both
proc and endp lines. The procedure name must be
unique in the program. proc directive must have a matching endp
directive. Failure to match the proc and endp
directives will produce a block nesting error. call instruction to call a far procedure
or a far call instruction to call a near procedure. Given this
little rule, the next question is "how do you control the emission
of a near or far call or ret?"call instruction uses the following syntax:
call ProcName
and the ret instruction is either[2]:
ret
or ret disp
Unfortunately, these instructions do not tell MASM if you are calling a
near or far procedure or if you are returning from a near or far procedure.
The proc directive handles that chore. The proc
directive has an optional operand that is either near or far.
Near is the default if the operand field is empty[3].
The assembler assigns the procedure type (near or far) to the symbol. Whenever
MASM assembles a call instruction, it emits a near or far call
depending on operand. Therefore, declaring a symbol with proc
or proc near, forces a near call. Likewise, using proc
far, forces a far call. proc's
operand also controls ret code generation. If a procedure has
the near operand, then all return instructions inside that procedure will
be near. MASM emits far returns inside far procedures.near ptr and far ptr
operators to override the automatic assignment of a near or far call.
If NearLbl is a near label and FarLbl is a far
label, then the following call instructions generate a near
and far call, respectively:
call NearLbl ;Generates a NEAR call.
call FarLbl ;Generates a FAR call.
Suppose you need to make a far call to NearLbl or a near call
to FarLbl. You can accomplish this using the following instructions:
call far ptr NearLbl ;Generates a FAR call.
call near ptr FarLbl ;Generates a NEAR call.
Calling a near procedure using a far call, or calling a far procedure using
a near call isn't something you'll normally do. If you call a near procedure
using a far call instruction, the near return will leave the cs value on
the stack. Generally, rather than:
call far ptr NearProc
you should probably use the clearer code:
push cs
call NearProc
Calling a far procedure with a near call is a very dangerous
operation. If you attempt such a call, the current cs value
must be on the stack. Remember, a far ret pops a segmented
return address off the stack. A near call instruction only
pushes the offset, not the segment portion of the return address.ret. If ret appears within
a procedure declared via proc and end;, MASM will
automatically generate the appropriate near or far return instruction. To
accomplish this, use the retn and retf instructions.
These two instructions generate a near and far ret, respectively.
OutsideProc proc near
jmp EndofOutside
InsideProc proc near
mov ax, 0
ret
InsideProc endp
EndofOutside: call InsideProc
mov bx, 0
ret
OutsideProc endp
Unlike some high level languages, nesting procedures in 80x86 assembly language
doesn't serve any useful purpose. If you nest a procedure (as with InsideProc
above), you'll have to code an explicit jmp around the nested
procedure. Placing the nested procedure after all the code in the outside
procedure (but still between the outside proc/endp
directives) doesn't accomplish anything. Therefore, there isn't a good reason
to nest procedures in this manner. proc and endp
statements for the nested procedure must lie between the proc
and endp directives of the outside, nesting, procedure. The following is
not legal:
OutsideProc proc near
.
.
.
InsideProc proc near
.
.
.
OutsideProc endp
.
.
.
InsideProc endp
The OutsideProc and InsideProc procedures overlap,
they are not nested. If you attempt to create a set of procedures like this,
MASM would report a "block nesting error". The figure below demonstrates
this graphically:
The only form acceptable to MASMis

Besides fitting inside an enclosing procedure, proc/endp
groups must fit entirely within a segment. Therefore the following code
is illegal:
cseg segment
MyProc proc near
ret
cseg ends
MyProc endp
The endp directive must appear before the cseg ends statement
since MyProc begins inside cseg. Therefore, procedures
within segments must always take the form shown below:
Not only can you nest procedures inside other procedures and segments, but you can nest segments inside other procedures and segments as well. If you're the type who likes to simulate Pascal or C procedures in assembly language, you can create variable declaration sections at the beginning of each procedure you create, just like Pascal:
cgroup group cseg1, cseg2
cseg1 segment para public 'code'
cseg1 ends
cseg2 segment para public 'code'
cseg2 ends
dseg segment para public 'data'
dseg ends
cseg1 segment para public 'code'
assume cs:cgroup, ds:dseg
MainPgm proc near
; Data declarations for main program:
dseg segment para public 'data'
I word ?
J word ?
dseg ends
; Procedures that are local to the main program:
cseg2 segment para public 'code'
ZeroWords proc near
; Variables local to ZeroBytes:
dseg segment para public 'data'
AXSave word ?
BXSave word ?
CXSave word ?
dseg ends
; Code for the ZeroBytes procedure:
mov AXSave, ax
mov CXSave, cx
mov BXSave, bx
xor ax, ax
ZeroLoop: mov [bx], ax
inc bx
inc bx
loop ZeroLoop
mov ax, AXSave
mov bx, BXSave
mov cx, CXSave
ret
ZeroWords endp
Cseg2 ends
; The actual main program begins here:
mov bx, offset Array
mov cx, 128
call ZeroWords
ret
MainPgm endp
cseg1 ends
end
The system will load this code into memory as shown below:
ZeroWords follows the main program because it belongs to
a different segment (cseg2) than MainPgm (cseg1).
Remember, the assembler and linker combine segments with the same class
name into a single segment before loading them into memory (see Chapter
Eight for more details). You can use this feature of the assembler to "pseudo-Pascalize"
your code in the fashion shown above. However, you'll probably not find
your programs to be any more readable than using the straight forward non-nesting
approach.
proc/endp
directives. All the rules and techniques that apply to procedures apply
to functions. This text will take another look at functions later in this
chapter in the section on function results. From here on, procedure will
mean procedure or function.
mov cx, 10
Loop0: call PrintSpaces
putcr
loop Loop0
.
.
.
PrintSpaces proc near
mov al, ' '
mov cx, 40
PSLoop: putc
loop PSLoop
ret
PrintSpaces endp
This section of code attempts to print ten lines of 40 spaces each. Unfortunately,
there is a subtle bug that causes it to print 40 spaces per line in an infinite
loop. The main program uses the loop instruction to call PrintSpaces
10 times. PrintSpaces uses cx to count off the
40 spaces it prints. PrintSpaces returns with cx
containing zero. The main program then prints a carriage return/line feed,
decrements cx, and then repeats because cx isn't
zero (it will always contain 0FFFFh at this point). PrintSpaces subroutine doesn't
preserve the cx register. Preserving a register means you save
it upon entry into the subroutine and restore it before leaving. Had the
PrintSpaces subroutine preserved the contents of the cx
register, the program above would have functioned properly. push and pop instructions to preserve
register values while you need to use them for something else. Consider
the following code for PrintSpaces:
PrintSpaces proc near
push ax
push cx
mov al, ' '
mov cx, 40
PSLoop: putc
loop PSLoop
pop cx
pop ax
ret
PrintSpaces endp
Note that PrintSpaces saves and restores ax and
cx (since this procedure modifies these registers). Also, note
that this code pops the registers off the stack in the reverse order that
it pushed them. The operation of the stack imposes this ordering. call instruction)
or the callee (the subroutine) can take responsibility for preserving the
registers. In the example above, the callee preserved the registers. The
following example shows what this code might look like if the caller preserves
the registers:
mov cx, 10
Loop0: push ax
push cx
call PrintSpaces
pop cx
pop ax
putcr
loop Loop0
.
.
.
PrintSpaces proc near
mov al, ' '
mov cx, 40
PSLoop: putc
loop PSLoop
ret
PrintSpaces endp
There are two advantages to callee preservation: space and maintainability.
If the callee preserves all affected registers, then there is only one copy
of the push and pop instructions, those the procedure contains. If the caller
saves the values in the registers, the program needs a set of push and pop
instructions around every call. Not only does this make your programs longer,
it also makes them harder to maintain. Remembering which registers to push
and pop on each procedure call is not something easily done. ax. Although PrintSpaces changes
the al, this won't affect the program's operation. If the caller
is preserving the registers, it doesn't have to save registers it doesn't
care about:
mov cx, 10
Loop0: push cx
call PrintSpaces
pop cx
putcr
loop Loop0
putcr
putcr
call PrintSpaces
mov al, '*'
mov cx, 100
Loop1: putc
push ax
push cx
call PrintSpaces
pop cx
pop ax
putc
putcr
loop Loop1
.
.
.
PrintSpaces proc near
mov al, ' '
mov cx, 40
PSLoop: putc
loop PSLoop
ret
PrintSpaces endp
This example provides three different cases. The first loop (Loop0)
only preserves the cx register. Modifying the al
register won't affect the operation of this program. Immediately after the
first loop, this code calls PrintSpaces again. However, this
code doesn't save ax or cx because it doesn't
care if PrintSpaces changes them. Since the final loop (Loop1)
uses ax and cx, it saves them both. putcr
macro to accomplish this, but this call instruction will accomplish
the same thing.