Chapter 5

Procedures

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 main

C:\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 main

Enter X: 5
Enter Y: 7
Result: +12

Example - 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()


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 main
Output

Number 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

  1. ESP = ESP - 4
  2. [ 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

  1. eax = [ ESP ]
  2. 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
     main   PROC

               mov ecx, 6
               mov esi, 0
@do1:     push source[ esi ]
               add esi, 4
@while1: loop @do1

               mov ecx, 6
               mov esi, 0
@do2:     pop dest[ esi ]
               add esi, 4
@while2: loop @do2

               exit
main ENDP

END main

int source[] = { 111h, 222h, 333h, 444h, 555h, 666h}

int dest[6];

ecx = 6
esi = 0

do {
      push source[ esi ]
      esi = esi + 4
} while ( --ecx != 0)

ecx = 6
esi = 0

do {
      pop dest[ esi ]
      esi = esi + 4
} while ( --ecx != 0)

source
0 1 2 3 4 5
111 222 333 444 555 666

dest

0 1 2 3 4 5
666 555 444 333 222 111
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
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 66 06 00 00
0x0040401C 55 05 00 00 44 04 00 00 33 03 00 00 22 02
0x0040402A 00 00 11 01 00 00 00 00 00 00 00 00 00 00

 

5.5    Defining and Using Procedures

C++ compared to Assembly Functions
int x = 3;          // Global x
1. void main() {       
2.    f();
3.    x = 4;        
4. }
5. void f() {
6.    x = 5; 
7.    return;       
8. }
 
C++ and Assembler Execution Sequence
C++ 1, 2, 5, 6, 7, 8, 3, 4
Assembler 1, 2, 6, 7, 8, 3, 4
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):

  1. Save the return location  - When executing Call f, the eIp points to the next instruction after the 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

  2. Execute first instruction of function - By setting eIp to the location (offset) of the first instruction of the function, that instruction will be fetched and executed next.

    The first instruction of function f is at offset 0000002316, the effect for 32-bit  is:

    eIp = offset f = 0000002316

Execution of Call f using 32-bit
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:

  1. Restore return location  - When executing the 32-bit Ret, the eIp points to the next instruction after the Ret, eIp = 00000015.

    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.

  1. main - calls print procedures

  2. print - prints the values in a global array A

Print 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
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
        add eSi, 4                            ;       eSi++
  @@while: loop @@do                          ;  } while( --eCx != 0 )

        ret                                   ;  return
print ENDP                                    ; }
main	PROC							; void main() {
		call		print				;	print()
		exit						; }
main		ENDP

		END 		main

Output:

1
2
3
4
5
 

 

Example

Following has three procedures.

  1. main - calls Sum and print procedures

  2. Sum - totals the values in a global array A to global variable Total

  3. print - prints the values in a global array A and the value of global variable Total

Total values in array, print array and 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: "
.code

Sum	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: 15

For 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.

Factorial Function Example in C++ and Assembler using Globals
// 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, Sum
void 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

.code
main 	PROC  
         	call       f
         	mov     eAx, x
         	call       WriteDec
         	exit
main 	Endp
f 	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.

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.
 
Factorial Function Example in C++ and in Assembler using Registers
// 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 Sum
int 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

.code
main 	PROC  
	mov     eCx, 4
         	call       f
         	call       WriteDec
         	exit
main 	Endp
f 	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.

Left and right functions are identical other than USES to save/restore registers
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.

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

Factorial Function in Assembler Passing Parameters in 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
   Clrscr               
   PromptForIntegers
      WriteString      
      ReadInt
   ArraySum
   DisplaySum
      WriteString
      WriteInt
 

 

 

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