Let's take a look at how a function call works in a real program. The function we are going to write is the power function. We will give the power function two parameters - the number and the power we want to raise it to. For example, if we gave it the parameters 2 and 3, it would raise 2 to the power of 3, or 2*2*2, giving 8. In order to make this program simple, we will only allow numbers 1 and greater.
The following is the code for the complete program. As usual, an explanation follows. Name the file power.s.
#PURPOSE: Program to illustrate how functions work # This program will compute the value of # 2^3 + 5^2 # #Everything in the main program is stored in registers, #so the data section doesn't have anything. .section .data .section .text .globl _start _start: pushl $3 #push second argument pushl $2 #push first argument call power #call the function addl $8, %esp #move the stack pointer back pushl %eax #save the first answer before #calling the next function pushl $2 #push second argument pushl $5 #push first argument call power #call the function addl $8, %esp #move the stack pointer back popl %ebx #The second answer is already #in %eax. We saved the #first answer onto the stack, #so now we can just pop it #out into %ebx addl %eax, %ebx #add them together #the result is in %ebx movl $1, %eax #exit (%ebx is returned) int $0x80 #PURPOSE: This function is used to compute # the value of a number raised to # a power. # #INPUT: First argument - the base number # Second argument - the power to # raise it to # #OUTPUT: Will give the result as a return value # #NOTES: The power must be 1 or greater # #VARIABLES: # %ebx - holds the base number # %ecx - holds the power # # -4(%ebp) - holds the current result # # %eax is used for temporary storage # .type power, @function power: pushl %ebp #save old base pointer movl %esp, %ebp #make stack pointer the base pointer subl $4, %esp #get room for our local storage movl 8(%ebp), %ebx #put first argument in %ebx movl 12(%ebp), %ecx #put second argument in %ecx movl %ebx, -4(%ebp) #store current result power_loop_start: cmpl $1, %ecx #if the power is 1, we are done je end_power movl -4(%ebp), %eax #move the current result into %eax imull %ebx, %eax #multiply the current result by #the base number movl %eax, -4(%ebp) #store the current result decl %ecx #decrease the power jmp power_loop_start #run for the next power end_power: movl -4 (%ebp), %eax #return value goes in %eax movl %ebp, %esp #restore the stack pointer popl %ebp #restore the base pointer ret
Type in the program, assemble it, and run it. Try calling power for different values, but remember that the result has to be less than 256 when it is passed back to the operating system. Also try subtracting the results of the two computations. Try adding a third call to the power function, and add its result back in.
The main program code is pretty simple. You push the arguments onto the stack, call the function, and then move the stack pointer back. The result is stored in %eax. Note that between the two calls to power, we save the first value onto the stack. This is because the only register that is guaranteed to be saved is %ebp. Therefore we push the value onto the stack, and pop the value back off after the second function call is complete.
Let's look at how the function itself is written. Notice that before the function, there is documentation as to what the function does, what its arguments are, and what it gives as a return value. This is useful for programmers who use this function. This is the function's interface. This lets the programmer know what values are needed on the stack, and what will be in %eax at the end.
We then have the following line:
.type power,@function
This tells the linker that the symbol power should be treated as a function. Since this program is only in one file, it would work just the same with this left out. However, it is good practice.
After that, we define the value of the power label:
power:
As mentioned previously, this defines the symbol power to be the address where the instructions following the label begin. This is how call power works. It transfers control to this spot of the program. The difference between call and jmp is that call also pushes the return address onto the stack so that the function can return, while the jmp does not.
Next, we have our instructions to set up our function:
pushl %ebp movl %esp, %ebp subl $4, %esp
At this point, our stack looks like this:
Base Number <--- 12(%ebp) Power <--- 8(%ebp) Return Address <--- 4(%ebp) Old %ebp <--- (%ebp) Current result <--- -4 (%ebp) and (%esp)
Although we could use a register for temporary storage, this program uses a local variable in order to show how to set it up. Often times there just aren't enough registers to store everything, so you have to offload them into local variables. Other times, your function will need to call another function and send it a pointer to some of your data. You can't have a pointer to a register, so you have to store it in a local variable in order to send a pointer to it.
Basically, what the program does is start with the base number, and store it both as the multiplier (stored in %ebx) and the current value (stored in -4(%ebp)). It also has the power stored in %ecx It then continually multiplies the current value by the multiplier, decreases the power, and leaves the loop if the power (in %ecx) gets down to 1.
By now, you should be able to go through the program without help. The only things you should need to know is that imull does integer multiplication and stores the result in the second operand, and decl decreases the given register by 1. For more information on these and other instructions, see Appendix B
A good project to try now is to extend the program so it will return the value of a number if the power is 0 (hint, anything raised to the zero power is 1). Keep trying. If it doesn't work at first, try going through your program by hand with a scrap of paper, keeping track of where %ebp and %esp are pointing, what is on the stack, and what the values are in each register.