Parameter Passing Techniques on the Intel 80x86 ( file)

advertisement
Parameter Passing Via Flags
Example: The GetDecDigit procedure below reads a character from the keyboard and returns it in the AL register.
The subroutine signals that a decimal digit was read by clearing the CF; otherwise the CF is set to indicate an "error
condition".
;-----------------------------------------------------GetDecDigit PROC NEAR
; Entry Point
mov ah, 01h
int 21h
If01:
cmp al, '0'
jb Else01
cmp al, '9'
ja Else01
Then01: clc
jmp EndIf01
Else01:
stc
EndIf01:
;
;
;
;
;
;
;
get character from keyboard
character in AL register
is it < '0'
yes - not a digit
is it > '9'
yes - not a digit
found digit - clear CF
; not a digit - set CF
ret
; and return
GetDecDigit ENDP
;------------------------------------------------The following calling sequence tests the Carry Flag, transferring control to a routine to handle the "error" if CF is
set.
; get digit
call GetDecDigit
jc Not_Digit
; Return digit in AL
; If not a digit goto Not_Digit
1
Parameter Passing using Registers
Example: The subroutine DisplayChar uses INT 21h Service 02h to display the character in the DL register.
DisplayChar PROC NEAR
push ax
mov ah, 02h
int 21h
pop ax
ret
DisplayChar ENDP
;
;
;
;
;
Entry Point
save AX
set INT 21h Service 02h
display DL
restore AX
To display any character, load it into the DL register and call DisplayChar.
push dx
mov dl, char
call DisplayChar
pop dx
;
;
;
;
save DX
move character from memory into DL
display it
restore DX
Example: The subroutine RotateLeft4 rotates the bits in the AX register 4 bits to the left. Its effect is to left rotate
the hexadecimal digits in AX.
RotateLeft4 PROC NEAR
push cx
mov cl, 04
rol ax, cl
pop cx
ret
RotateLeft4 ENDP
; Entry Point
; save CX
; initialize CL for rotation
; restore CX
The subroutine RotateLeft4 can be used to "rotate" the hexadecimal digits in any word X by first loading AX with
the contents of X, calling RotateLeft4, then moving the result back to X (see code segment below).
push ax
mov ax, x
call RotateLeft4
mov x, ax
pop ax
; save AX
; move word to "rotate" to AX
; move result to x
; restore AX
This code sequence is awkward since AX must first be saved on the stack, the word whose digits are to be rotated
moved into the AX register, RotateLeft4 called, the results moved back, and AX restored.
2
Parameter Passing using Global Variables
Example: The subroutine MaxOf2 (below) returns the larger of two integers. Declare three word-length variables in
the Data Segment, A, B, and C and adopt the convention that MaxOf2 returns the larger of A and B in C. Thus A
and B would function as "input" to the subroutine while C would function as "output". Note that there is only one
Data Segment declaration; Variables A, B, and C should not be declared in a separately defined data segment.
DATA SEGMENT
A
dw ?
B
dw ?
C
dw ?
.....
DATA ENDS
; input for MaxOf2
; input for MaxOf2
; output for MaxOf2
.....
;------------------------------------------------MaxOf2 PROC NEAR
push ax
; save AX
mov ax, A
If02:
cmp ax, B
; is A >= B
ja EndIf02
;
yes - so keep A in AX
Then02:
mov ax, B
;
no - so move B to AX
EndIf02:
mov C, ax
; move AX to C
pop ax
; restore AX
ret
MaxOf2 ENDP
;-------------------------------------------------The calling sequence for MaxOf2 would have to "load" the variables A and B with the values to find the maximum
of. Upon return the maximum value would be extracted from C.
3
Parameter Passing using the Stack - Pass by Value
Example: Maximum Procedure
;---------------------------------------------------------------------MaxOf2_A PROC NEAR
; Entry Point
push bp
; save the old BP register
mov bp, sp
; BP points to top of "argument frame"
mov ax, [bp+4]
; get A (top parameter)
If03: cmp ax, [bp+6]
; compare with B (2nd parameter)
ja EndIf03
; A is larger
Then03:
mov ax, [bp+6]
; B is larger
EndIf03:
pop bp
; restore BP
ret 4
; return and pop off parameters
MaxOf2_A ENDP
;-------------------------------------------------------------The calling sequence is
push b
push a
call MaxOf2_A
; push B on stack
; push A on stack
; A is on top, B is 2nd
; larger of A and B is in AX register
To understand parameter passing using the stack, let's examine the stack at various points during the execution of
the subroutine MaxOf2_A. Start just before MaxOf2_A is called but after "b" and "a" are pushed , the stack has the
following structure (note the stack pointer SP “points” to the top of the stack).
|
|
+--------+
|
a
| <- SP
+--------+
|
b
|
+--------+
|
|
4
Since "a" was pushed last, it's on top of the stack. When MaxOf2_A is called, the return address (old_ip) is pushed
on the stack.
|
|
+--------+
| old_ip | <- SP
+--------+
|
a
|
+--------+
|
b
|
+--------+
|
|
The first two lines of the MaxOf2_A subroutine saves the old value of the BP register then sets BP to point to the
top of the stack. The parameters on the stack plus the old_ip and old_bp values make up the "argument frame" for
that particular procedure call.
|
|
+--------+
| old_bp | <- SP = BP
+--------+
| old_ip |
+--------+
|
a
| <- [bp+4]
+--------+
|
b
| <- [bp+6]
+--------+
|
|
--------^
|
argument
frame
|
|
|
---------
5
Parameter Passing using the Stack - Pass by Reference
Example: Modify the above subroutine to return the largest value using indirection through the stack.
First the calling sequence is
mov ax,offset c
push ax
push b
push a
call MaxOf2_B
;
;
;
;
;
move address of C to AX
and push it on stack
push B on stack
push A on stack
A is on top, B is 2nd, address of C is 3rd
; larger of A and B returned to C
The address of c is pushed onto the stack followed by the values a and b.
Note: Since there is no push immediate instruction, technically the instruction push offset c is illegal (although the
assembler automatically substitutes an interesting work-around when it detects a push immediate instruction). This
is why we use the instruction sequence mov ax, offset c; push ax.
The code for the modified subroutine, MaxOf2_B is similar to MaxOf2_A in that is uses [bp+4] and [bp+6] to
access the two values on the stack, compares them and puts the larger in AX. Then using [bp+8] to move the
address of c to the BX register and executing the instruction mov [bx], ax it uses register indirection to return
the value in AX to c.
;---------------------------------------------------------------MaxOf2_B PROC NEAR
; Entry Point
push bp
; save the old BP register
mov bp, sp
; set BP to point to the top of the stack
push ax
; save registers
push bx
mov ax, [bp+4] ; get A (top parameter)
If04:
cmp ax, [bp+6] ; compare with B (2nd parameter)
ja EndIf04
; A is larger
Then04: mov ax, [bp+6] ; B is larger
EndIf04:
mov bx, [bp+8] ; put address of C in BX
mov [bx], ax
; use indirection to return larger value
pop bx
; restore registers
pop ax
pop bp
; restore BP
ret 6
; return and pop off parameters
MaxOf2_B ENDP
;---------------------------------------------------------------
6
To understand exactly how this method works, again let's examine the stack. Just before the subroutine MaxOf2_B
is called the stack contains the values of A and B and the address of C.
|
|
+--------+
|
a
| <- SP
+--------+
|
b
|
+--------+
| addr_c |
+--------+
|
|
After MaxOf2_B is called and after the first three instructions in the subroutine are executed we have
|
|
+--------+
|
bx
|
+--------+
|
ax
|
+--------+
| old_bp |
+--------+
| old_ip |
+--------+
|
a
|
+--------+
|
b
|
+--------+
| addr_c |
+--------+
|
|
<- SP
---------^
|
|
argument
frame
|
|
|
|
----------
<- BP
<- [bp+4]
<- {bp+6]
<- [bp+8]
As mentioned above the logic used by MaxOf2_B is similar to that of MaxOf2_A. The first difference is that both
AX and BX are saved on the stack. The code to determine the larger of "a" and "b" and move it into the AX register
is the same. However, to pass back the larger value to "c, BX is loaded with the contents of [BP+8] which is the
address of "c". The allows the register indirect addressing of the "mov [bx], ax" instruction to move the contents of
AX to address "c".
mov bx, [bp+8]
mov [bx], ax
; put address of C in BX
; use indirection to return larger value
7
Allocating Local Variables on the Stack
Example: Allocating "local variables" using the Stack.
MaxOf2_C PROC NEAR
push bp
mov bp, sp
sub sp, 6
push ax
; Entry Point
; save old BP register
; BP points to top of stack
; free up 3 words on stack
; save registers
At this point the stack looks like
|
|
+--------+
|
bx
|
+--------+
|
ax
|
+--------+
|
|
+--------+
|
|
+--------+
|
|
+--------+
| old_bp |
+--------+
| old_ip |
+--------+
|
a
|
+--------+
|
b
|
+--------+
| addr_c |
+--------+
|
|
<- SP
---------|
local
variables
|
|
---------^
|
|
argument
frame
|
|
|
|
----------
<- [bp-6]
<- [bp-4]
<- [bp-2]
<- BP
<- [bp+4]
<- {bp+6]
<- [bp+8]
Local variables are accessed in the same way parameters are accessed with base + offset indirect addressing using
the BP register and negative offsets. For example, the three local variables allocated above can be accessed using
[bp-2]. [bp-4], and [bp-6]. However before returning from the subroutine, restore the registers, de-allocate the local
variables, restore the BP register, and return
pop bx
pop ax
add sp, 6
pop bp
ret 6
MaxOf2_C ENDP
; restore registers
; de-allocate local variables
; restore BP
; return and clear parameters
8
Parameter Passing - Array Parameters (Pass by Reference)
Example: Using Pass by Reference for Array Variables : Consider the C++ function SumArray (below) which sums
the integers in an array called list returning the sum using a pass-by-reference parameter. We would write such a
function as follows
void SumArray(int list[], int size, int& sum);
{
int i;
sum = 0;
for (i = 0 ; i < size; i++)
sum = sum + list[i];
return;
}
Note that pass by reference is needed if a structured type variable like an array or a string buffer is a parameter.
The Intel 80x86 assembler version of this code would have three parameters, the first and third are pass by reference
and the second pass by value. Since we adopt the convention of pushing parameters right to left, the Intel assembler
calling sequence would be
lea ax, Sum
push ax
push Last
lea ax, List
push ax
call SumArray
; Get address Sum
;
and push on stack
; Push Last in stack
; Get address of List
;
and push on stack
The code for SumArray, where we implement a FOR loop in assembler would be
;----------------------------------------------------SumArray
PROC NEAR
; Entry Point
push bp
mov bp, sp
push ax
; Save Registers
push bx
push cx
xor ax, ax
; Clear AX for sum
mov bx, [bp+4]
; BX contains address of List
mov cx, 1
; Initialize loop counter CX to 1
For1: cmp cx, [bp+6]
; Is CX > Last ?
jg EndFor1
; Yes - Exit Loop
add ax, [bx]
; No - add List component to AX
inc bx
; Increment BX by 2
inc bx
inc cx
; Increment loop counter
jmp For1
EndFor1:
mov bx, [bp+8]
; BX contains address of Sum
mov [bx], ax
; Return results to Sum
pop cx
; Restore Registers
pop bx
pop ax
pop bp
ret 6
; Return and Pop Parameters off Stack
SumArray ENDP
9
8.4.1 An Example of a Recursive Procedure - Factorials
DATA SEGMENT
n
dw ?
f
dw ?
DATA ENDS
CODE SEGMENT
Main PROC FAR
...
3 ->
; Calling Sequence:
lea ax f
push ax
; Pass by Reference parameter
push n
; Pass by Value parameter
call Factorial
; Call Factorial Subroutine
...
Main ENDP
;-----------------------------------------; Subroutine Code
;------------------------------------------Factorial PROC NEAR
push bp
; Save Base Pointer
mov bp, sp
2 ->
push ax
; Save Registers
push bx
push dx
cmp WORD PTR [bp+4], 0
; is n = 0?
jnz Factorial01
;
mov bx, [bp+6]
; YES - so set f = 1
mov WORD PTR [bx], 1
;
and return
jmp FactorialEnd
Factorial01:
push [bp+6]
; Pass address of f
mov ax, [bp+4]
; subtract 1 from n
dec ax
push ax
; Pass n
call Factorial
; Recursive Call
1 ->
mov bx, [bp+6]
; Get f
mov ax, [bx]
mul WORD PTR [bp+4]
; Multiply by n
mov bx, [bp+6]
; Save product to f
mov [bx],ax
FactorialEnd:
; Restore Registers
pop dx
pop bx
pop ax
pop bp
ret 4
; return
Factorial ENDP
;------------------------------------------CODE ENDS
0 ->
END Main
10
Stack
|
|
+------+
|
|
+------+
|
|
+------+
| dx | <- SP
+------+
| bx |
+------+
| ax |
+------+
|old bp| <- BP
+------+
|old ip|
+------+
| 0
|
+------+
|addr f|
+------+
| dx |
+------+
| bx |
+------+
| ax |
+------+
|old bp|
+------+
|old ip|
+------+
|
1 |
+------+
|addr f|
+------+
| dx |
+------+
| bx |
+------+
| ax |
+------+
|old bp|
+------+
|old ip|
+------+
|
2 |
+------+
|addr f|
+------+
|
|
Modular Programming
Method #1
Put the subroutine code in same file as main program, usually after the main program code. Since
the subroutines are in the same code segment as the main program, all subroutines are type NEAR.
Advantages
1.
Easy to do
Disadvantages
1.
2.
Subroutines are not reusable
Long source code so assembly takes longer
Method #2
A.
Place all subroutines in a separate file and use an "include" (i.e. include filename) directive to
assemble the file along with the main program code. Specifically
Create a separate file (e.g. Subr.asm) containing only the subroutine code. Each subroutine would
be enclosed by subroutine_name PROC NEAR and subroutine_name ENDP directives. Do not
define Data, Stack or Code segments.
Subr PROC NEAR
...
Subr ENDP
Note : Since the subroutine file contains only subroutine code, it can not be assembled by itself.
B.
Place the assembler directive Include Subr.asm in the main program code at the point where you
want to insert and assemble subroutine code (usually after the main procedure but before the
CODE ENDS directive (i.e within the code segment).
Advantages
1.
2.
easy to do
subroutine code is reusable
Disadvantages
1.
2.
large .OBJ files
assembly time is long since "include" file is repeatedly assembled
The two approaches above while straight-forward don't really make use of the capabilities of the assembler. The
next two do as they allow separate assembling of the subroutines and insertion (linking) of the assembled subroutine
code into the main code by the linker.
Method #3
Create a file of pre-assembled subroutines (an .obj file) and use the linker to link the subroutines
with the main code at link time
First create a separate file containing the code for the subroutines. The file will consist of a single code
segment (no data segment; no stack segment!). An example of such a file is given below. Note the
following:
A.
There is an ASSUME directive for the CS register only. There is no stack segment and we will
deal with (optional) data segments later.
B.
The PUBLIC directive is used to make the subroutine names known to the linker. Any subroutine
name that must be "visible" outside the file must be appear with this directive.
C.
The last line is the END directive with no starting address.
11
Example
CODE SEGMENT
ASSUME CS:CODE
PUBLIC Subr1, Subr2 ; makes Subr1 & Subr2 visible
Subr1 PROC NEAR
....
ret
Subr1 ENDP
Subr2 PROC NEAR
...
ret
Subr2 ENDP
CODE ENDS
END
; no starting address
A file in this form can be separately assembled. However, it can't be executed (why?). Note that all procedures are
NEAR.
Next in the main program file, use the EXTRN directive to inform the Assembler which subroutines will be
provided at link time. In the example below, the EXTRN directive appears after the ASSUME directive but before
the Main procedure code.
CODE SEGMENT
ASSUME CS:CODE; DS:DATA; SS:MYSTACK
EXTRN Subr1:NEAR, Subr2:NEAR
Main PROC FAR
...
call Subr1
; address provided at link time
call Subr2
; address provided at link time
Main ENDP
CODE ENDS
END Main
If your forget the EXTRN directive, you will get an "Undefined Symbol" error. Note that the EXTRN explicitly
states that both subroutines are type NEAR. Assemble this file separately.
Finally at link time use the tlink command
.obj file of subroutines
9
tlink a:Main+a:SubrFile, a:Main
to link subroutines (object file) into the main program code. Note the plus sign (+) is used to include the subroutine
object files with the main object file; there are no spaces between the plus sign and the file name.
.
Advantages
1.
2.
faster assembly time as subroutines assembled only once.
object code is reusable
12
Procedures with Separate Data Segments
;------------------------------------------------------;
; Routine Name : ReadDecimal
; Description : Reads an unsigned decimal value; Uses INT 21h
;
Function 0Ah String Read
;
; At Entry :
; At Exit : AX has integer
;
;------------------------------------------------------DATA SEGMENT
BufSize = 5
Buffer db BufSize+1, 0, Bufsize+1 DUP (?)
DATA ENDS
;------------------------------------------------------CODE SEGMENT
ASSUME CS:CODE, DS:DATA
PUBLIC ReadDecimal
ReadDecimal PROC NEAR
push bp
mov bp, sp
push bx
; save registers
push cx
push dx
push si
push ds
; save old DS register
mov ax, data
; Load DS with current data segment address
mov ds, ax
;
Read String
mov ah, 0ah
lea dx, Buffer
int 21h
mov bx, 10d
xor ax, ax
lea si, Buffer+2
;
; Put multiplier in BX
; Clear AX for Number
; SI points to first byte in string
Get Digit
Read1:
cmp BYTE PTR [si], '0'
jb Done
cmp BYTE PTR [si], '9'
ja Done
;
; Is digit < '0'
; Is digit > '9'
Convert Digit to Integer and Store in CX
mov cx, [si]
and cx, 0Fh
13
;
Number := Number * 10 + Digit
mul
add
inc
jmp
bx
ax, cx
si
Read1
Done:
pop ds
pop si
pop dx
pop cx
pop bx
pop bp
ret
ReadDecimal ENDP
CODE ENDS
END
;
;
;
;
multiply by 10
add digit
advance to next character
and go again
; Restore DS to old data segment
; Restore registers
14
Download