Greetings,
I'd like to show you how to program a function in assembly that uses the c-style function calling convention (stdcall).
First of all, you should be familiar with the basics of asm. You should know how the stack and the ESP register works.
This is how you call a function in C and retrieve the return value from it:
Code:
result = foo(arg1, arg2, ... );
In assembly it looks like this:
Code:
...
push #arg2;
push #arg1;
call foo;
mov #result,eax;
As you can see, the arguments are pushed onto the stack in reverse order. Then the function ist called. The opcode 'call' pushes the address of the next command onto the stack and then jumps to the label/address you specified as argument. The called function must store the return value into the EAX register.
Now let's take a look inside the foo()-function that uses the stdcall convention:
Code:
foo:****// Label and entry point of the 'foo' function
push ebp;**
mov ebp,esp;**
add ebp,3;
<push any other registers you might need onto the stack>
<do stuff>
mov eax,<result>;
<pop back the registers you stored before in reverse order>
pop ebp;
ret;**** // Continue execution inside the caller
Now let's go through this line after line.
The first line pushes the content of the EBP register onto the stack so we can do stuff with it. It is popped back before we return, so we don't affect the caller's execution.
Then the address stored inside the stack pointer register is copied into the EBP register.
At this point the ESP register points to the next free cell on top of the stack. Let me show you a small schematic of the content of the stack:
Code:
ESP => | <FREE CELL>
****** | --------------
****** | caller's EBP
****** | --------------
****** | return address
****** | --------------
****** | arg1
****** | --------------
****** | arg2******
****** | --------------
****** | ... etc.
This means that now we have the current ESP copied to EBP, we can add the number of 3 to EBP so that it points to the first argument. This is what the 3rd line is good for. So now we can use "#ebp" to acces the first argument. If you want to do something with the second argument, increase ebp by one ('inc ebp;'
. Unfortunately ZASM doesn't allow statements like '#(ebp+3)' atm to access the first argument and so on without changing the value of EBP.
By the way, if you need any other registers except the EAX register i.e. for calculations inside your function, this is a good place to push them onto the stack so that we can restore their values before returning.
When you do your calculations inside your function you may push and pop to and off the stack as many times as you wish but keep the push and pop commands balanced!
In the fourth line we copy the return value of the function into the EAX register so that the caller can do stuff with it.
Afterwards, pop the registers that you have possibly stored before back into the appropriate registers.
In the sixth line you restore the caller's value of the EBP register. The ESP register must be restored to exactly the same value it had when the function was entered so make sure you pop from the stack as many times as you push onto the stack. Then the 'ret' command can be executed to jump back to the next command of the caller. When the program executes the command 'ret' (normally at the end of the function) it should pop the address that you pushed by using 'call' from the stack and jumps to this address.
Maybe this was a bit confusing. Let me show you what I meant by dumping a small example. It will be a small Sleep(x); function that will make the CPU "pause" for x seconds and return the value of the internal timer after waiting:
Code:
sleep:********** // Entry point.
push ebp;********// Save caller's ebp value.
mov ebp,esp;
add ebp,3;****** // Now ebp points to our passed time we want it to sleep.
push edx;********// Save the caller's edx value 'cause we need this register.
timer eax;****** // Get the current internal timer value.
sleeploop:****** // Loop through this until the timer value
timer edx;****** // stored in edx minus the one stored in eax is
sub edx,eax;**** // greater or equal the passed time we wanted it to
cmp edx,#ebp;****// wait.
jl sleeploop;
mov eax,edx;**** // Set the return value to the last timer value.
pop edx;******** // Restore the value of our caller's edx register.
pop ebp;******** // Restore ebp register.
ret;************ // Return to the caller.
And this is how the function is called:
Code:
push 1.5;********// Wait 1.5 seconds.
call sleep;******// Execute the sleep function.
add esp,1;****** // Remove the pushed argument from the stack.
Note the last line. After the function call our argument '1.5' is still on the stack so we have to remove it if we don't need it anymore. This can be done by popping it off the stack ('pop 0;'
or by adding '1' to the ESP register which has the same effect.
If you pushed 3 arguments to the stack before a function call you can execute 'add esp,3' to get rid of all of them at once! I like to use this notation because it keeps the code more consistent and simple.
That's all I wanted you to know, maybe you are now able to make your program more structured. By the way, this method of function calling allows recursing as deep as you have free stack memory.
Thank's for reading! I hope it wasn't too hard for you to decipher what I wrote but English is not my native language.
Maybe I will show you how to allocate space for local variables inside functions later but as long as ZASM doesn't support statements like #(ebp+3) it will make the code very unreadable.
Bookmarks