+ Reply to Thread
Results 1 to 8 of 8

Thread: C-style Function Calling With Zasm

  1. #1
    Wire Noob ULTRA's Avatar
    Join Date
    Apr 2007
    Posts
    5

    Default

    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&#39;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&#39;t affect the caller&#39;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&#39;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 (&#39;inc ebp;&#39. Unfortunately ZASM doesn&#39;t allow statements like &#39;#(ebp+3)&#39; 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&#39;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 &#39;ret&#39; command can be executed to jump back to the next command of the caller. When the program executes the command &#39;ret&#39; (normally at the end of the function) it should pop the address that you pushed by using &#39;call&#39; 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&#39;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&#39;s edx value &#39;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&#39;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 &#39;1.5&#39; is still on the stack so we have to remove it if we don&#39;t need it anymore. This can be done by popping it off the stack (&#39;pop 0;&#39 or by adding &#39;1&#39; to the ESP register which has the same effect.
    If you pushed 3 arguments to the stack before a function call you can execute &#39;add esp,3&#39; 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&#39;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&#39;s for reading! I hope it wasn&#39;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&#39;t support statements like #(ebp+3) it will make the code very unreadable.

  2. #2
    That furred thing Black Phoenix's Avatar
    Join Date
    Feb 2007
    Location
    Kyiv, Ukraine
    Posts
    3,565

    Default

    Woah, this is really nice.

    Also hint, I think you can use pop 0; instead of add esp,1; because CPU will see that you try to write to a constant value, and will do nothing (a feature)
    I'm a wire-crazy person with a tail.

    Take a daily journey into my brain

    D2K5

  3. #3
    Wire Noob ULTRA's Avatar
    Join Date
    Apr 2007
    Posts
    5

    Default

    Woah, this is really nice.

    Also hint, I think you can use pop 0; instead of add esp,1; because CPU will see that you try to write to a constant value, and will do nothing (a feature)[/b]
    Thanks! I added it to the text but I will keep the &#39;add esp,1&#39; in the example because it is useful if you have more than one argument and it will keep the code more consistent.

    Edit:
    By the way, thanks for creating the CPU for GMod, it is absolutely great!

    Edit2:
    There is one thing that made me wonder: The ESP register always points to the next free memory &#39;cell&#39; (they are not really bytes, are they?). On my real x86 CPU the ESP register always points to the address where the last pushed value was stored. Was this intended?

  4. #4
    That furred thing Black Phoenix's Avatar
    Join Date
    Feb 2007
    Location
    Kyiv, Ukraine
    Posts
    3,565

    Default

    They are 32-bit bytes (welcome to imaginative world).
    I selected that behaviour on purpose, I can change it if you want (Should not influence lot of people).
    The main reason it was changed is that once CPU had only 65535 bytes of memory, and ESP is 32-bit SIGNED register, so it wont "swap" from 0 to 65535, which was not good.
    I'm a wire-crazy person with a tail.

    Take a daily journey into my brain

    D2K5

  5. #5
    Wire Noob ApostropheST's Avatar
    Join Date
    Apr 2007
    Posts
    17

    Default

    Great post. Very informative and definitely gave me, as a C/C++ coder, some ideas on how to delve further into ZASM in the future.

    Thanks.

  6. #6
    Wire Noob ULTRA's Avatar
    Join Date
    Apr 2007
    Posts
    5

    Default

    They are 32-bit bytes (welcome to imaginative world).
    I selected that behaviour on purpose, I can change it if you want (Should not influence lot of people).
    The main reason it was changed is that once CPU had only 65535 bytes of memory, and ESP is 32-bit SIGNED register, so it wont "swap" from 0 to 65535, which was not good.[/b]
    I think it&#39;s great. Considering one byte as memory for one floating point number makes life much easier
    Also I think you should leave the ESP thing like it is now. It is no problem at all. It just made my program not work initially because I didn&#39;t know that ESP always points to the next free &#39;byte&#39; ( ). If you once note this you&#39;ll get along with it.

    Another question I have in this context is if you could make the compiler understand statements like i.e.
    Code:
    mov eax,#(ebp+4);
    I know this isn&#39;t really a low-level statement. When compiling this would result in something like

    Code:
    push ebp;
    add ebp,4;
    mov eax,#ebp;
    pop ebp;
    But it would make coding of more advanced and nested programs (like the one I&#39;m on at the moment) a lot easier in terms of readability and error-proneness.

  7. #7
    That furred thing Black Phoenix's Avatar
    Join Date
    Feb 2007
    Location
    Kyiv, Ukraine
    Posts
    3,565

    Default

    I might implement offsets in future, they will be understand by CPU, so cpu will execute mov abc,#(def+1), not those 4 lines of code

    The biggest problem is the compiler, its pretty hard to do the strings job in lua.
    I'm a wire-crazy person with a tail.

    Take a daily journey into my brain

    D2K5

  8. #8
    Wire Weeaboo Pyro-Fire's Avatar
    Join Date
    Aug 2007
    Location
    WA, Australia
    Posts
    1,804

    Default

    it can be done in masm32.

    mov dword ptr [100+50],90

    will write NOP to offset 150



+ Reply to Thread

Similar Threads

  1. ZASM Code Editor - Wire CPU
    By SuperLlama in forum Installation and Malfunctions Support
    Replies: 0
    Last Post: 01-16-2009, 12:43 PM
  2. The Hall-of Fame (Barnstar Style)
    By leach139 in forum Off-Topic
    Replies: 5
    Last Post: 12-31-2007, 05:09 AM
  3. Holosphere CPU Style!
    By Pyro-Fire in forum CPU, GPU, and Hi-speed Discussion & Help
    Replies: 2
    Last Post: 10-03-2007, 11:48 AM
  4. Requesting ZASM Assembler for Windows
    By dnifan in forum CPU, GPU, and Hi-speed Discussion & Help
    Replies: 7
    Last Post: 08-31-2007, 05:45 PM

Bookmarks

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
proceed-collector
proceed-collector
proceed-collector
proceed-collector
linguistic-parrots
linguistic-parrots
linguistic-parrots
linguistic-parrots