<- previous    index    next ->

Lecture 7 Subroutines

Here is a basic subroutine (function, procedure, etc)
Note the use of the stack pointer for passing parameters.
Note saving and restoring the callers registers.
(Yes, this is needed for CMSC 313 project 2)

"call1" below, is called by the "C" program test_call1.c

/* test_call1.c   test  call1.asm */
 #include <stdio.h>
int main()
{
  int L[2];
  L[0]=1;
  L[1]=2;
  call1(L);
  printf("L[0]=%d, L[1]=%d \n", L[0], L[1]);
  return 0;
}

The result is L{0]=0, L[1]=0, from the following:

; call1.asm  a basic structure for a subroutine to be called from "C"
;
; This saves more registers than used here
; Parameters: int L[] or  int *L
; Result: L[0]=0  L[1]=0

        global call1		; linker must know name of subroutine
call1:				; name must appear as a nasm label
        push	ebp		; save ebp
        mov	ebp, esp	; ebp is callers stack
        push	ebx		; save registers
        push	edi
	push	esi

        mov	edi,[ebp+8]	; get address of L into edi
        mov	eax,0		; get a 32-bit zero 
        mov	[edi],eax	; L[0]=0
        add	edi,4		; add one dword=32-bit int
        mov	[edi],eax	; L[1]=0

        pop	esi		; restore registers
	pop	edi		; in reverse order
        pop	ebx
        mov	esp,ebp		; restore callers stack frame
        pop	ebp
        ret			; return
;
; Notes about the callers stack, ebp in our code:
; ebp+8  is the last argument passed to us by the caller,
;        this is our first argument
; ebp+12 would be our second argument, etc. +4 each
;        the arguments can be values or addresses,
;        as defined by the "C" function prototypes
; ebp+4  is the return address in the caller, used by 'ret'
; ebp    which is our starting esp, is the next available stack space

Study call1.asm 

Now, to pass more arguments, call2.c
can be implemented as call2.asm
Note passing arrays including strings is via address,
     passing scalar values is via passing values.

; call2.asm  code loopint.c as subroutine (void function)
; /* call2.c a very simple loop that will be coded for nasm */
; #include <stdio.h>
; void call2(int *A, int start, int end, int value);
; int main()
; {
;   int dd1[100];
;   int i;
;   dd1[0]=5; /* be sure loop stays 1..98 */
;   dd1[1]=6;
;   dd1[98]=8;
;   dd1[99]=9;
;   call2(dd1,1,98,7); /* fill dd1[1] thru dd1[98] with 7 */
;   printf("dd1[0]=%d, dd1[1]=%d, dd1[98]=%d, dd1[99]=%d\n",
;           dd1[0], dd1[1], dd1[98],dd1[99]);
;   return 0;
; }
; void call2(int *A, int start, int end, int value)
; {
;   int i;
;
;   for(i=start; i<=end; i++) A[i]=value;
; }

; execution output is dd1[0]=5, dd1[1]=7, dd1[98]=7, dd1[99]=9
 
	section	.bss
i:	resd	1		; actually unused, kept in register

	section .text
	global	call2		; linker must know name of subroutine
call2:				; name must appear as a nasm label
        push	ebp		; save ebp
        mov	ebp, esp	; ebp is callers stack
        push	ebx		; save registers
        push	edi

        mov	edi,[ebp+8]	; get address of A into edi
        mov	eax,[ebp+12]	; get value of start
        mov	ebx,[ebp+16]	; get value of end
        mov	edx,[ebp+20]	; get value of value
	
loop1:	mov	[4*eax+edi],edx	; A[i]=value;
	add	eax,1		; i++;
	cmp	eax,ebx		; i<=end
	jle	loop1		; loop i<=end is false

	pop	edi		; in reverse order
        pop	ebx
        mov	esp,ebp		; restore callers stack frame
        pop	ebp
        ret			; return
;
; Notes about the callers stack, ebp in our code:
; ebp+8  is the last argument passed to us by the caller,
;        this is our first argument, the address of A.
; ebp+12 is our second argument, 'start' a value.

A simple function, called and written in the same .asm file
intfunc.asm

; intfunc.asm  call integer function  int sum(int x, int y)
; 
; compile:	nasm -f elf intfunc.asm 
; link:		gcc -o intfunc.o
; run:		intfunc
; result:	5 = sum(2,3)

	extern	printf
	section .data
x:	dd	2
y:	dd	3
z:	dd	1
fmt:	db	"%d = sum(%d,%d)",10,0
	global	main
main:	push	ebp
	mov	ebp,esp
	push	ebx

	push	dword [y]	; push arguments for sum
	push	dword [x]
	call	sum		; coded below
	add	esp,8
	mov	[z],eax		; save result from sum

	push	dword [y]	; print
	push	dword [x]
	push	dword [z]
	push	dword fmt
	call	printf
	add	esp,16

	pop	ebx
	mov	esp,ebp
	pop	ebp
	mov	eax,0
	ret
; end main

sum:	push	ebp		; function int sum(int x, int y)
	mov	ebp,esp
	push	ebx

	mov	eax,[ebp+8]	; get argument x
	mov	ebx,[ebp+12]	; get argument y
	add	eax,ebx		; x+y with result in eax

	pop	ebx
	mov	esp,ebp
	pop	ebp
	ret			; return value in eax
; end of function  int sum(int x, int y)

A simple demonstration of using a double sin(double x) function
from the "C" math.h  fltfunc.asm

; fltfunc.asm  call math routine  double sin(double x)
; 
; compile:	nasm -f elf fltfunc.asm 
; link:		gcc -o fltfunc.o -lm    # needs math library
; run:		fltfunc
; 
	extern	sin             ; be sure to 'extern' functions
	extern	printf
	section .data
x:	dq	0.7853975	; Pi/4 = 45 degrees
y:	dq	1.0		; should be about 7.07E-1
fmt:	db	"%e = sin(%e)",10,0
	global	main
main:	push	ebp
	mov	ebp,esp
	push	ebx

	push	dword [x+4]     ; push quad word (double) for sin
	push	dword [x]
	call	sin             ; call the library sin function
	add	esp,8
	fstp	qword [y]       ; save the return value

	push	dword [x+4]     ; print
	push	dword [x]
	push	dword [y+4]
	push	dword [y]
	push	dword fmt
	call	printf
	add	esp,20

	pop	ebx
	mov	esp,ebp
	pop	ebp
	mov	eax,0
	ret

; result:	7.071063e-01 = sin(7.853975e-01)
; end fltfunc.asm

And a final example of a simple recursive function, factorial,
written in unoptimized assembly language following the "C" code.
test_factorial.asm
test_factorial.c

; test_factorial.asm   based on test_factorial.c
; /* test_factorial.c  the simplest example of a recursive function         */
; /*                   a recursive function is a function that calls itself */
; static int factorial(int n)  /* n! is n factorial = 1*2*3*...*(n-1)*n  */
; {
;   if( n <= 1 ) return 1;     /* must have a way to stop recursion      */
;   return n * factorial(n-1); /* factorial calls factorial with n-1     */
; }                            /* n * (n-1) * (n-2) * ... * (1)          */
; #include <stdio.h>
; int main()
; {
;   printf(" 0!=%d \n", factorial(0)); /* Yes, 0! is one */
;   printf(" 1!=%d \n", factorial(1));
;   ...
;   printf("18!=%d \n", factorial(18)); /* wrong, uncaught in C */
;   return 0;
; }
; /* output of execution is:
;   0!=1
;   1!=1
;   ...
;  12!=479001600
;  13!=1932053504    wrong! 13! = 12! * 13, must end in two zeros
;  14!=1278945280    wrong! and no indication!
;  15!=2004310016    wrong!
;  16!=2004189184    wrong!
;  17!=-288522240    wrong and obvious if you check your results
;  18!=-898433024    Only sometimes does integer overflow go negative
; */
; 
; compile:	nasm -f elf test_factorial.asm 
; link:		gcc -o test_factorial.o
; run:		test_factorial

	section	.bss
tmp:	resd	1		; over written each call
	section	.text
factorial:                      ; not global is 'static' in "C"
	push	ebp		; function int factorial(int n)
	mov	ebp,esp
	push	ebx

	mov	eax,[ebp+8]	; get argument n
	cmp	eax,1		; compare for exit
	jle	exitf		; go return a 1
	sub	eax,1		; n-1
	push	dword eax	; compute factorial(n-1)
	call	factorial
	pop	edx		; get back our "n-1"
	add	edx,1		; have our "n"
	mov	[tmp],edx
	imul	eax,[tmp]	; n * factorial(n-1) in eax
	jmp	returnf
exitf:	mov	eax,1
returnf:	
	pop	ebx
	mov	esp,ebp
	pop	ebp
	ret			; return value in eax
; end of function  static int factorial(int n)

	extern	printf
	section .data
n:	dd	0		; initial, will count to 18
fmt:	db	"%d! = %d",10,0 ; simple format
	section	.bss
nfact:	resd	1		; just a place to hold result
	section	.text
	global	main
main:	push	ebp
	mov	ebp,esp
	push	ebx

loop1:	
	push	dword [n]	; push arguments for factorial
	call	factorial	; coded above
	add	esp,4
	mov	[nfact],eax	; save result from factorial

	push	dword [nfact]	; print
	push	dword [n]
	push	dword fmt
	call	printf
	add	esp,12
	mov	eax,[n]
	inc	eax
	mov	[n],eax
	cmp	eax,18
	jle	loop1		; print factorial 0..18
	
	pop	ebx
	mov	esp,ebp
	pop	ebp
	mov	eax,0
	ret
; end main



    <- previous    index    next ->

Other links

Go to top