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 retBy 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 endpKeep 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 retAre 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 endpTreated 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 endpAlways 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 endpWithout 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 endpThe
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 ProcNameand the
ret
instruction is either[2]:
ret or ret dispUnfortunately, 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 NearProcyou should probably use the clearer code:
push cs call NearProcCalling 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 endpUnlike 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 endpThe
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 endpThe
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 endThe 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 endpThis 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 endpNote 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 endpThere 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 endpThis 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.