Calling functions written in assembly from C is not so difficult, but it is important you understand the C function calling convention. According to this convention parameters are to be pushed onto the stack and return values stored in the eax register.
This means that when we call our function from C, the C compiler will push the parameters onto the stack, and our asm program must look there to fetch them.
But where exactly on the stack are they stored? To answer this we must first know what the stack looks like on entering our asm function.
When we invoke our function the C compiler does the following:
(1) Pushes all the parameters for the function onto the stack, in reverse order to their order in the function prototype. Why reverse order? So that the first parameter popped off the stack matches the first (left-most) parameter declared in the C function prototype.
(2) Pushes the return address for the function onto the stack. The return address is the address of the next instruction in the calling function, it is where execution takes off once the asm function has completed.
Inside our asm function the following should also be performed on entry: (the function prologue)
(1) The ebp (base pointer/frame pointer) register is pushed onto the stack.
(2) The esp (stack pointer) register is saved to the ebp register.
The stack-related operations therefore are the pushing of parameters, the pushing of the return address and the pushing of the base pointer.
After these operations the stack will look like this:
------top of stack------ *old ebp *return_address *parameter 1 *parameter 2 ..... *parameter N ..... -------------------------
We can now see where on the stack the parameters are stored and can access them easily enough. Since every element on the stack in our example is 4 bytes long we can find the first parameter by adding 8 to the address of ebp and then using indirection to get the value stored there. Why do we add 8 ? Because we need to skip over the old ebp (4 bytes) and the return address (also 4 bytes).
Expressed in at&t assembly notation we get the first parameter by doing the following:
(and saving it into the eax register):
movl 8(%ebp), %eax
The address of the second parameter is also found by adding 12 to the base pointer address (we are assuming that each parameter is 4 bytes).
In my example code that follows the C function is passing two parameters to the asm function; the first being a pointer to the start of an integer array; the second parameter being the size of that array.
Here is the code:
#include <stdio.h> /* prototype for asm function */ int * asm_mod_array(int *ptr,int size); int main() { int fren[5]={ 1, 2, 3, 4, 5 }; /* call the asm function */ asm_mod_array(fren, 5); return 0; }
Note prototyping and calling an asm function is the same as with a regular C function.
Now here is the code for the asm function:
#VARIABLES: The registers have the following uses: # description: this function takes an int array and multiplies # every element by 2 and adds 5. # %edi - Holds the index of the data item being examined # %ecx - size of the array # %eax - pointer to first item in array # %edx - used for scratch space # .section .text .globl asm_mod_array .type asm_mod_array, @function asm_mod_array: pushl %ebp movl %esp, %ebp movl 8(%ebp),%eax # get pointer to start of array passed from C movl 12(%ebp),%ecx # get size of array xorl %edi, %edi # zero out our array index start_loop: # start loop cmpl %edi, %ecx # check to see if we’ve hit the end je loop_exit movl (%eax,%edi,4), %edx # store the element in %edx for calculations leal 5(,%edx,2), %edx # multiply array element by 2 and add 5 movl %edx, (%eax,%edi,4) # overwrite old element with new value incl %edi # increment the index, moving through the array. jmp start_loop # jump to loop beginning loop_exit: # function epilogue movl %ebp, %esp popl %ebp ret # pop the return address and jmp to it
We do not modify the eax register after we copy the pointer into it, so the return value of the asm function (in accordance with the C function calling convention) will be the pointer.
These programs both compile on a 32 bit linux system using ‘as’ and ‘gcc’. To use simply compile each program to a .o (object file) and then link them together using gcc -o.