Chapter 5Procedures |
Modified: |
5.2 Linking to an External Library
Most programs use library procedures for input/output, mathematics functions, etc.
Visual Studio projects link library files with your program code to form a complete program.
Example
The WriteString procedure is part of a library supplied by the text.
INCLUDE Irvine32.inc .data
HelloWorld db "Hello World", 0.code
main PROC
mov edx, OFFSET HelloWorld
call WriteString
exit
main ENDP
END mainC:\Irvine\Irvine32.inc defines the prototype for the procedure. The assembler includes the prototypes.
The prototype in the Irvine32.inc file is:
WriteString PROTO
C:\Irvine\Irvine32.lib contains the code that is linked with the program code above.
The linker links the code into one complete program.
5.2 The Book's Link Library (pp. 113-124)
Important for now.
ReadInt Reads a 32-bit signed integer into EAX register. WriteInt Writes a 32-bit signed integer from EAX register. WriteString Writes a 0 byte terminated string referenced by EDX register. Example
INCLUDE Irvine32.inc .data
EnterX db "Enter X: ", 0
EnterY db "Enter Y: ", 0
Result db "Result: ", 0.code
main PROC
mov edx, OFFSET EnterX
call WriteString
call ReadInt
mov eBx, eAx
mov edx, OFFSET EnterY
call WriteString
call ReadInt
add eAx, eBx
mov edx, OFFSET Result
call WriteString
call WriteInt
exit
main ENDP
END mainEnter X: 5
Enter Y: 7
Result: +12Example - Dice game.
Psuedocode print("Number of tosses")
ecx = read( )
do {
print("Toss?)
al = read( )
eax = random( 6 )
print( eax )
print(" ")
eax = random( 6 )
print( eax )
while( --ecx != 0)
WaitMsg()
Output
INCLUDE Irvine32.inc
.data
strToss db "Toss?", 0
strDone db "Done.", 0
strN db "Number of tosses? ", 0
strSpace db " ", 0
strCRLF db 10, 13, 0
.code
main PROC
mov edx, OFFSET strN ; print("Number of tosses");
call WriteString
call ReadInt ; ecx = read()
mov eCx, eAx
@do: ; do {
mov edx, OFFSET strToss ; print("Toss?")
call WriteString
call ReadChar ; al = read()
mov eax, 6
call RandomRange ; eax = random(6)
inc eax
call WriteDec ; print( eax )
mov edx, OFFSET strSpace ; print(" ")
call WriteString
mov eax, 6
call RandomRange ; eax = random(6)
inc eax
call WriteDec
mov edx, OFFSET strCRLF
call WriteString ; print( eax )
@while: loop @do ; while( --ecx != 0 )
call WaitMsg ; WaitMsg()
mov edx, OFFSET strDone
call WriteString ; print("Done")
exit
main ENDP
END mainNumber of tosses? 5
Toss?1 1
Toss?6 6
Toss?6 1
Toss?5 1
Toss?4 3
Press any key to continue...
5.4 Stack Operations
Stack is a FILO data structure (First In - Last Out).
Can only access top element (most recently added).
top = 10.
Add another, top = 11.
5.4.1 Runtime Stack
ESP register points to the stack top.
ESP = 00001000
[ ESP ] = 00000006
Push
Push 000000A5h
- ESP = ESP - 4
- [ ESP ] = 000000A5h
Push 000000A5h
Push 00000001h
Push 00000002h
Push
16 or 32-bit
register, memory or constant
Question 1 - Show results of executing:
mov eAx, 44444444h
Push eAx
Push 66666666h
Offset Contents 00000018 55555555 <- eSp eAx = ?
Pop
Pop eax
- eax = [ ESP ]
- ESP = ESP + 4
eax = 00000002
ESP = 00000FF8
Pop
16 or 32 bit
register or memory
Question 2 - Show results of executing:
mov eAx, 44444444h
Pop eAx
Pop eBx
Offset Contents 00000018 33333333 00000014 22222222 00000010 11111111 <- eSp eAx = ?
eBx = ?
Example - Reversing an array
Question 3 - Show stack results of executing the grayed box below.
| INCLUDE Irvine32.inc .data source dword 111h,222h dword 333h,444h dword 555h,666h dest dword 6 dup( ? ) .code
mov ecx, 6 |
int source[] = { 111h, 222h, 333h, 444h, 555h, 666h} int dest[6]; ecx = 6 do { ecx = 6 do { |
||||||||||||||||||||||||
source
dest
|
source 0x00404000 11 01 00 00 22 02 00 00 33 03 00 00 44 04 0x0040400E 00 00 55 05 00 00 66 06 00 00 00 00 00 00 0x0040401C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x0040402A 00 00 00 00 00 00 00 00 00 00 00 00 00 00 dest |
5.5 Defining and Using Procedures
int x = 3; // Global x 1. void main() {
2. f();
3. x = 4;
4. }
5. void f() {
6. x = 5;
7. return;
8. }
|
INCLUDE Irvine32.inc .data x Dword 3 ;; Global x .code 1. main PROC 2. call f 3. mov x, 4 4. exit 5. main Endp 6. f PROC 7. mov x, 5 8. ret 9. f Endp End main |
Call/Ret - Function Execution
An execution trace of the C++ and Assembler programs above reveals the following execution sequence.Notice that the Call f transfers control to the function f, the Ret returns control back to the caller, the main function.
C++ and Assembler Execution Sequence C++ 1, 2, 5, 6, 7, 3, 4 Assembler 1, 2, 6, 7, 8, 3, 4
Calling a Function - Calling a function generally requires the following steps (specifically for Call f):
For 32-bit execution, eIp = 0000001516 must be saved on the stack before the function begins execution. The effect is:
eSp = eSp - 4
[eSp] = eIp = 0000001516
The first instruction of function f is at offset 0000002316, the effect for 32-bit is:
eIp = offset f = 0000002316
| Assembly Listing | Stack, eSp, and eIp |
Address
00000010 E8 00000023 call f
00000015 C7 06 0000r 0004 mov X, 4
:
:
f Proc Near
00000023 C7 06 0000r 0005 mov X, 5
00000029 C3 ret
0000002A f Endp
|
After call f
________
|________|
eSp -> |00000015|
eIp = 0000000023
After ret
________
eSp -> |________|
|00000015|
eIp = 0000000015 |
Function Return - Returning from a function is the reverse of the Call requiring the following step:
The return location must be restored from the stack to eIp.
Since eIp points to the next instruction to fetch, the effect of restoring eIp=0000001516 is to resume execution at offset 0000001516:
eIp = [eSp] = 0000001516
eSp = eSp + 4
Question 4
What is eIp after call f?
What is eIp after ret?
What is X after ret?
Address
00000003 E8 00000052 call f
00000008 C7 06 0000r 0004 mov X, 4
:
:
f Proc Near
00000052 C7 06 0000r 0005 mov X, 5
00000058 C3 ret
00000059 f Endp
|
Parameters
Parameters allow the same code of a function to produce a range of results dependent upon the parameter value.For example, the sqrt(4) is 2 and the sqrt(25) is 5.
This supports reuse of generic code rather than implementing unique code for each possible case (e.g. sqrt4() and sqrt25()). The following examines two simple methods of implementing parameters, global variables and registers. Both methods have problems that will be identified in the discussion.
Global Variables
It should be remembered that all variables declared in the data segment and all CPU registers are accessible globally, from within any function in the algorithm.
While this may seem wonderfully useful, reflect upon writing a large C++ or Java program with no local variables or function parameters, a globally defined variable can be accessed in any function allowing accidental change to occur undetected. When globals are used to exchange data, the same variable is accessed in one or more functions. The effects of function execution are not localized to that function, since global variables can be changed in any function as a hidden side-effect of the function's execution. When global variables are used to exchange data between functions, the likelihood of creating unwanted side-effects increases directly with the number of assignment statements and the number of global variables. The potential for side-effects increases exponentially as the number of global variables increase, creating a daunting challenge for a programmer to produce reliable programs.
Example
Following has two procedures.
main - calls print procedures
print - prints the values in a global array A
INCLUDE Irvine32.inc
.data
A dword 1, 2, 3, 4, 5 ; int A[] = {1, 2, 3, 4, 5}
n dword 5 ; int n = 5
.code
main PROC ; void main() {
call print ; print()
exit ; }
main ENDP
END main |
Output:
1
2
3
4
5
Example
Following has three procedures.
main - calls Sum and print procedures
Sum - totals the values in a global array A to global variable Total
print - prints the values in a global array A and the value of global variable Total
INCLUDE Irvine32.inc
.data
A dword 1, 2, 3, 4, 5 ; int A[] = {1, 2, 3, 4, 5}
n dword 5 ; int n = 5
Total dword ? ; int Total;
TotalString byte "Total: ", 0 ; String TotalString="Total: "
.codeSum PROC NEAR ; void Sum() {
mov eSi, 0 ; eSi = 0
mov eAx, 0 ; total = 0
mov eCx, n ; eCx = n
@do: add eAx, A[eSi*4] ; do{
inc eSi ; total = total + A[eSi]
@while: loop @do ; eSi++
mov Total, eAx ; } while( --eCx != 0 )
ret ; return
Sum ENDP ;print PROC NEAR ; void print() {
mov eSi, 0 ; eSi = 0
mov eCx, n ; eCx = n
@@do: ; do {
mov eAx, A[ eSi ] ; write( A[ eSi ] )
Call WriteDec ; write( CRLF )
Call CrLf ; } while( --eCx != 0 )
add eSi, 4
@@while: loop @@do
mov eDx, OFFSET TotalString ; write("Total: ")
call WriteString
mov eAx, Total ; write( Total )
Call WriteDec
ret ; return
print ENDP ; }main PROC ; void main() {
call Sum ; Sum()
call print ; print()
exit ; }
main ENDP
END main
|
Output:
1
2
3
4
5
Total: 15For a simple example of global variables as parameters see the program above, variable A in the Assembler are globals, accessible from all functions.
While global variables and the globally accessible CPU registers share many of the same problems when used to exchange data between assembly procedures, there are two strong reasons for using registers over global variables: 1) As part of the CPU, the same registers are known by the same name everywhere and are shared by all functions/programs/etc. making it possible to pass data between two separately written functions/programs. Global variable names are normally limited to the source program and data segment where defined. 2) The Push/Pop mechanism can be used to preserve register values from unwanted side-effects.
To illustrate the difference between the use of global variables in the data segment and register parameters, programs in C++ and Assembler using global variables and another using register parameters are listed below. Each of the included examples compute N!. All of the factorial functions return a value in the eAx register, eAx is used by convention for returning a 32-bit result from a function.
// Determine sum of 4! and 5! // using global variables 1. #include <iostream.h>
2. int Sum, N; // Global variable N
// Factorial parameter
3. int Factorial(void) {
4. int F = 1;
5. do {
6. F = F * N;
7. N = N - 1;
8. } while (N != 0);
9. return F;
10. }
11. void main(void) {
12. N = 4;
13. Sum = Factorial();
14. N = 5; // Side-effect N
15. Sum =+ Factorial();
16. cout << N;
17. }
|
INCLUDE Irvine32.inc
.data
N dword ? ;; Global N parameter to
Sum dword ? ;; Factorial function
.code
;; int Factorial()
;; Pre N > 0
;; Parameters N positive integer (global)
;; Returns eAx factorial value
;; Registers Altered eAx, eCx, flags
1. Factorial Proc ;; int Factorial()
2. Mov eCx, N
3. Mov eAx, 1 ;; F = 1;
4. @do:
5. Mul eCx ;; F = F * N;
6. @while: Loop @do ;; N = N - 1;
7. Ret ;; eAx has result
8. Factorial Endp
9. Main Proc Near
10. Mov N, 4
11. Call Factorial
12. Mov Sum, eAx ;; Sum = Factorial
13. Mov N, 5
14. Call Factorial
15. Add Sum, eAx ;; Sum =+ Factorial;
16. Mov eAx, Sum
17. Call WriteDec
18. Exit
19. Main Endp
20. End Main
|
Question 5
Translate the following pseudocode into Assembler using global variables.
An example Assembler program is at right:
int N, Sumvoid Total() { do { Sum = Sum + N } while ( --N != 0 ) }void main() { N = 6 Total() WriteDec( Sum ) N = 4000000000 Total() WriteDec( Sum ) } INCLUDE Irvine32.inc .data x Dword 3 ;; Global x .codemain PROC call f mov eAx, x call WriteDec exit main Endpf PROC mov x, 5 ret f Endp End main
Register Parameters
When registers are used to pass parameters to a procedure or function, it again creates the opportunity for unwanted side effects. However, using Push and Pop to save and restore registers used within the procedure one can eliminate at least side-effect to registers. The C++ algorithms reflects the use of parameters although, as we will see in later chapters, C++ passes parameters on the stack rather than in registers (with only a few registers the number of possible parameters would be severely limited). The Assembler example instead uses registers to pass data to the function. The key weaknesses of register parameters are:
In the following the C++ and Assembly programs both compute 4! + 5!. In the C++ example, 4 and 5 are passed as parameters, in the Assembler example, eCx register is used to pass the parameters 4 and 5 to the Factorial function, the result is returned in eAx register.
- Registers are global.
- Limited number of registers.
In the Assembler program below at line:
8. The value 4 is moved to parameter eCx.
10. The returned result eAx = 4! is used.
11. The value 5 is moved to the parameter eCx.
13. The returned result eAx = 5! is used.
4. The parameter eCx is side-effected (changed) in function.
// Determine sum of 4! and 5!
// using parameters
1. #include <iostream.h>
2. int Sum, N;
3. void main(void) {
4. N = 4;
5. Sum = Factorial(N);
6. N = 5;
7. Sum =+ Factorial(N);
8. cout << N;
9. }
10. int Factorial( int N ) {
11. int F=1;
12. do {
13. F = F * N;
14. N = N - 1;
15. } while (N != 0) ;
16. return F;
17.} //Factorial
|
Include Irvine32.inc
.data
Sum dd ?
.code
;; int Factorial()
;; Pre eCx > 0
;; Parameters eCx positive integer (register)
;; Returns eAx factorial value
;; Registers Altered eCx, eAx, flags
1. Factorial Proc ;; int Factorial()
2. Mov eAx, 1 ;; F = 1;
3. @do: Mul eCx ;; eAx = eCx * eAx
4. @while: Loop @do ;; F = F * eBx;
5. Ret ;; eAx has result
6. Factorial Endp
7. Main Proc Near
8. Mov eCx, 4
9. Call Factorial
10. Mov Sum, eAx ;; Sum = Factorial
11. Mov eCx, 5
12. Call Factorial
13. Add Sum, eAx ;; Sum =+ Factorial
14. Mov eAx, Sum
15. Call WriteDec ;; cout << Sum
16. exit
17. Main Endp
18. End Main
|
Question 6
Translate the following pseudocode into Assembler, passing N in register eCx and returning the result in eAx.
An example Assembler program is at right:
int Sumint Total(int N) { int result = 0 do { result = result + N } while ( --N != 0 ) }void main() { Sum = Total( 4 ) WriteDec( Sum ) Sum = Total(4000000000) WriteDec( Sum ) } INCLUDE Irvine32.inc .codemain PROC mov eCx, 4 call f call WriteDec exit main Endpf PROC mov eAx, eCx ret f Endp End main
Saving/Restoring Registers
Register values do not change after calling WriteString, WriteDec, or most other of the text supplied functions.One convention to prevent register side-effects is to push any register values that will be altered at the beginning of the function and pop the values back into the same registers at the end of the function.
For example, the following function returns 5*10 in the eAx register.
Execution alters eAx, eBx, eDx and flag registers but restores all but eAx, which holds the result of the function execution.
Mult Proc Push eBx ; Save registers Push eDx PushFD Mov eAx, 5 Mov eBx, 10 ; eBx altered Mul eBx ; eDx, eAx, flags altered PopFD ; Pop registers in reverse Pop eDx Pop eBx Ret Mult Endp |
Mult Proc USES eBx, eDx PushFD Mov eAx, 5 Mov eBx, 10 ; eBx altered Mul eBx ; eDx, eAx, flags altered PopFD ; Flags saved separately Ret Mult Endp |
Conventions for Returning Function Results
Functions generally return a single result for each execution. The standard method followed in this course and for most high-level languages such as C++ is the following:It is of course possible to use any other registers to return results but that is considered nonstandard use. The factorial functions examined above use eAx to return a 32-bit result.
- byte - Al for 8-bit
- word - Ax for 16-bit
- double word - eAx for 32-bit
Common errors
One common error is to Push/Pop eAx on entry and exit to the function.
eAx should change during the function execution but is restored to its original value.
Example - Saving and Restoring registers
Include Irvine32.inc .code 1. Factorial Proc
2. Push eCx
3. Mov eAx, 1
4. @do: Mul eCx
5. @while: Loop @do
6. Pop eCx
7. Ret
8. Factorial Endp
9. Main Proc Near
10. Mov eCx, 4
11. Call Factorial
12. Mov eBx, eAx
13. Mov eCx, 5
14. Call Factorial
15. Add eBx, eAx
16. Mov eAx, eBx
17. Call WriteDec
18. exit
19. Main Endp
20. End Main
|
Include Irvine32.inc
.code
1. Factorial Proc Uses eCx ;; int Factorial()
2. Mov eAx, 1 ;; F = 1;
3. @do: Mul eCx ;; eAx = eCx * eAx
4. @while: Loop @do ;; F = F * eBx;
5. Ret ;; eAx has result
6. Factorial Endp
7. Main Proc Near
8. Mov eCx, 4
9. Call Factorial
10. Mov eBx, eAx ;; eBx = Factorial
11. Mov eCx, 5
12. Call Factorial
13. Add eBx, eAx ;; eBx =+ Factorial
14. Mov eAx, eBx
15. Call WriteDec ;; cout << Sum
16. exit
17. Main Endp
18. End Main
|
Flowcharts
Flowcharts and pseudocode define algorithm execution order without details of a language syntax.
Flowcharts are pictorial, the algorithm flow can be charted graphical, with less interpretation required.
Flowchart symbols
Algorithm to sum an array
Flowchart that expresses the following pseudocode:
input exam grade from the user
if( grade > 70 )
display "Pass"
else
display "Fail"
endif
5.6 Program Design Using Procedures
Flowcharts and pseudocode define execution order without details of a language syntax.
| Modularization using Procedures Main
|
![]() |
| INCLUDE Irvine32.inc IntegerCount = 3 ; array size .data prompt1 BYTE "Enter a signed integer: ",0 prompt2 BYTE "The sum of the integers is: ",0 array DWORD IntegerCount DUP(?) .code main PROC call Clrscr mov esi,OFFSET array mov ecx,IntegerCount call PromptForIntegers call ArraySum call DisplaySum exit main ENDP PromptForIntegers PROC pushad mov edx,OFFSET prompt1 L1: call WriteString call ReadInt call Crlf mov [esi],eax ; store in array add esi,4 ; next integer loop L1 L2: popad ret PromptForIntegers ENDP |
ArraySum PROC push esi push ecx mov eax,0 L1: add eax,[esi] ; add each integer to sum add esi,4 ; point to next integer loop L1 ; repeat for array size L2: pop ecx pop esi ret ; sum is in EAX ArraySum ENDP DisplaySum PROC push edx mov edx,OFFSET prompt2 call WriteString call WriteInt ; display EAX call Crlf pop edx ret DisplaySum ENDP END main |