|
In the last issue you have learned how to write a very basic procedure which could be called by a Delphi Application. This time I will introduce how to pass parameters to functions and returning a values to the caller. Perhaps you want to write the following function with NASM: function GetMaxDword: Dword;
Returning a value is quiet easy. You only have to store your result in "eax". Our Assemblercode looks like that:
GetMaxDword:
mov edx, 0 not edx mov eax, edx ret Note that I've only used "edx" to illustrate that you finally have to move your value into "eax". If you want to return a floating point value you can store it on the FPU Stack ("st0"). Where your parameters are stored depends on the calling convention you've chosen. Delphi's standard calling convention is "register", fast and complicated. But there are a few other cc you might want to use. To make it a bit clearer I will explain it on the Delphi function:
function Foobar(a,b,c,d,e: Integer): Integer; register; external;
a = [esp+4]
b = [esp+8] c = [esp+12] d = [esp+16] e = [esp+20] result = eax exit with "ret 20"
a = [esp+4]
b = [esp+8] c = [esp+12] d = [esp+16] e = [esp+20] result = eax exit with "ret"
a = eax
b = edx c = ecx d = [esp+8] e = [esp+4] result = eax exit with "ret 8"
a = [esp+20]
b = [esp+16] c = [esp+12] d = [esp+8] e = [esp+4] result = eax exit with "ret 20" The modifiers const and var do not pass the data of the value but the address. So if your function looks like procedure Decrease(var Val: Integer); register; external;
you can access Val with the following code:
Decrease:
dec dword [eax] ret Note that the following declaration works with the identical sourcecode:
procedure Decrease(Val: PInteger); register; external;
Floating Point Values are always stored on the stack. The caller passes two values, the address of the array and the highest index. E. g.
function Sum(ints: array of Integer): Integer; register; external;
would look like that in your assembler code:
Sum:
; eax <- Address ; edx <- highest Index mov ecx, 0 .addloop: add ecx, dword [eax + 4*edx] dec edx jns .addloop mov eax, ecx ret To explain why stack frames are such useful I will first try to investigate the "ret" Instruction. "Ret" should be called in the end of every function. It takes one parameter specifying the number of bytes stored on the stack by the caller. "Ret" remembers the value the stack pointer (esp) points at. Then it adds the value passed to ret and 4 to the stack pointer (esp := esp + val + 4). Finally "ret" jumps to the address the stack pointer has pointed at. You will hopefully see that it is very important that the stack pointer remains unchanged during your function. Well, you have to change the stack pointer if you want to use Local Variables because they are located on the stack. To reserve the memory for two Dwords you would type
sub esp, 8
Now you should use Stack Frames. Stack Frames save the stack pointer in ebp and finally restore it, but thats not the end of the story. ebp belongs to the registers whose values must remain unchanged or Delphi will run into problems (only eax, edx and ecx can be changed whithout being saved). A complete stackframe could look like this:
push ebp
mov ebp, esp sub esp, 4 mov esp, ebp pop ebp ret Alternatively you could code the following which does the same but is a little bit slower (well, you probably won't notice it):
enter 4
leave ret You should be able to write your own programs now. Sit down, play with what you've got and look how smooth and fast your code gets. Well, I've only touched some subjects here so let me know if you have some additions or if you found a mistake. |
| « Previous Issue | Back to assembly section |