flat assembler
Message board for the users of flat assembler.

Index > Main > Parameters -> Stack Or Register?

Author
Thread Post new topic Reply to topic
IHateRegistering



Joined: 24 Oct 2013
Posts: 2
IHateRegistering
Hi all,

I really hope that this question hasn't been answered already, but I wasn't able to find anything related to this using the search.

Well, I am a beginner in writing assembler and after I came to function-calls using the instruction CALL in connection with RET I wondered what the best way would be to pass parameters to such a function. I can imagine two ways: either pushing all needed parameters onto the stack or use registers.

Each one has its advantages and disadvantages I guess. Some of my thoughts are:


    * When using the stack, I first have to POP the return address in order to get access to the actual parameters. [-]
    * When using the stack, I can call my function recursively [+]
    * When using registers, I can leave the stack as it is.


I noticed also that on linux the system-call-parameters are passed through registers.
I also noticed, that when using multiple functions I have to take care of my registers' contents. Saving them on stack would be one solution, but is this efficient? I can imagine some functions which call each other some times and the resulting pushing and popping would be more operations than the actual code the functions are doing. Smile

What can you recommend? Am I on a wrong way? Or can you point me to a tutorial / explanation / ... which addresses this problems?

Thanks in advance,
IHateRegistering
Post 25 Oct 2013, 00:02
View user's profile Send private message Reply with quote
ejamesr



Joined: 04 Feb 2011
Posts: 52
Location: Provo, Utah, USA
ejamesr
The best way to learn is to look at lots of code examples. Try googling "learning assembly language" and pick sites that peak your interest. You'll finds many tutorials that can get you started. Check the examples that come with FASM, learn to compile them and make changes to them.

Understanding how the stack works is one of the first things to help open up the assembly world to you. A good debugger (such as Ollydbg) will help you explore and see how what you write is actually interpreted by the CPU. You can either push parameters on the stack or put them into registers (which can be faster if done correctly). You do not have to pop anything off the stack until the called function returns; you can easily access any data pushed on the stack.

FASM will work whether you go the Windows or the Linux route, whether you go 32 bits or 64 bits. I've done Windows assembly programming exclusively, so I can't help with any Linux-specific issues). The key thing is to keep pushing forward; there is ultimately no restriction on the type of program you can create with FASM.

Good luck and happy programming!

Eric
Post 25 Oct 2013, 01:03
View user's profile Send private message Send e-mail Reply with quote
DimonSoft



Joined: 03 Mar 2010
Posts: 821
Location: Belarus
DimonSoft
IHateRegistering wrote:
* When using the stack, I first have to POP the return address in order to get access to the actual parameters. [-]

Like ejamesr said, you don't really have to. It depends on the platform you're programming for (32/64 bit, Intel/Non-Intel, even Windows/Linux/Smth else), but let's look at how the general idea can be implemented in 32-bit Windows (simplified as far as possible).

If you're just writing your application, you can think of a stack as just a piece of memory plus 3 registers: SS, ESP, EBP. SS is already set up for you and you usually don't have to deal with it at all. ESP is a pointer to the top of the stack: when you push a dword onto the stack, its value decreases by 4, when you pop a dword, its value increases by 4. If you look at the memory at SS:ESP, you'll see the last value pushed onto the stack. Now you should know that whenever you access memory through ESP or EBP without explicitly specifying any segment register, SS (Stack Segment) is used by default. Now note that you can use [ESP+4] to access the value pushed before the return address, then [ESP+8], [ESP+12], etc. Thus, there's no need to pop the return address anywhere.

Now, what can the EBP register be used for? Consider the following code:
Code:
MyProc:
push ebp
mov ebp, esp
sub esp, 4

; Procedure body

mov esp, ebp
pop ebp
ret    

Let's have a look at the first part. It first saves the value of EBP onto the stack, then replaces it with the value of ESP. The last action before actually executing procedure body is to subtract 4 from ESP (it can also be 8, 12, feel free to continue). What shall we have now?

Note that EBP now holds the value of ESP exactly after old EBP value was pushed, Thus [EBP] (4 bytes of memory pointed to by EBP) is the old value of EBP. [EBP+4] is something that was pushed a moment before—the return address. [EBP+8] is one more step ahead. What is it? It is the last thing pushed onto the stack before CALLing the procedure, which is one of the parameters. [EBP+12], [EBP+16], etc. will be the other parameters pushed onto the stack. How many of them? Depends on how many parameters your procedure has.

So, why do we need the magic with subtracting something from ESP? Generally, we don't need. But if we do, this is the way to reserve some stack space for local variables. In the example [EBP-4] will actually be the 4 bytes we've reserved. You need more space? Feel free to subtract more from ESP!

Now, why do we use EBP, not ESP? That's easy: ESP may change during the execution of the procedure (just push something), while EBP will generally change only if you do it explicitly.

The first part (3 lines) is called prologue. The other 3 lines are called epilogue. Let's look at it closer. First, we restore the value of ESP to the one it had after saving old EBP in the stack. Now we pop the old value of EBP to… well… to EBP and that's it! We have the ESP and EBP values the same as after entering the procedure. Particularly, ESP points to the return address. Now you can RETurn from the procedure.

The prologue and epilogue may vary greatly depending on your task, on your calling convention (another useful thing to google or wiki), etc. But you get the idea (I hope so). And if you look at some of the existing calling conventions (like fastcall), you'll see that both registers and stack can be used at the same time.

Hope this helps.
Post 25 Oct 2013, 07:28
View user's profile Send private message Visit poster's website Reply with quote
revolution
When all else fails, read the source


Joined: 24 Aug 2004
Posts: 17671
Location: In your JS exploiting you and your system
revolution
IHateRegistering wrote:
Or can you point me to a tutorial / explanation / ... which addresses this problems?
I'm not sure what the problem is you are trying to solve. What you describe is the classic trade-off between various factors. There is not one solution that is best in all cases. Using registers can be "faster" (whatever that means) and more "efficient" (depending upon how one measures it). But also using the stack can be faster and more efficient if one is using a different ways to measure the parameters. In other cases a mixed approach might be more useful. In summary "it depends".

Some language compilers try to be "smart" and choose different calling conventions on the fly based upon various program conditions. Some of the simpler methods used are just to use registers (where possible) for all leaf functions and the stack for everything else.
Post 25 Oct 2013, 07:44
View user's profile Send private message Visit poster's website Reply with quote
AsmGuru62



Joined: 28 Jan 2004
Posts: 1419
Location: Toronto, Canada
AsmGuru62
In my opinion -- if parameters passed on stack -- our code is no better than C/C++.
I usually pass in registers.
I also keep all registers saved/restored with PUSHA/POPA instructions.
Of course, if you need to optimize for speed (which is rare) - some other solution may be used.

To return a register from a function with POPA I use a simple structure:
Code:
virtual at 0
RegsAtPopa:
    .reg_EDI dd ?
    .reg_ESI dd ?
    .reg_EBP dd ?
    .Skip    dd ?
    .reg_EBX dd ?
    .reg_EDX dd ?
    .reg_ECX dd ?
    .reg_EAX dd ?
    .size = $
end virtual
    

Then when returning from a function - I just store a return value into an output register like this:
Code:
;
; Return with EAX <= EDI
;
mov [esp + RegsAtPopa.reg_EAX], edi
popa
ret
    
Post 25 Oct 2013, 14:48
View user's profile Send private message Send e-mail Reply with quote
DimonSoft



Joined: 03 Mar 2010
Posts: 821
Location: Belarus
DimonSoft
AsmGuru62 wrote:
In my opinion -- if parameters passed on stack -- our code is no better than C/C++.
I usually pass in registers.
I also keep all registers saved/restored with PUSHA/POPA instructions.
Of course, if you need to optimize for speed (which is rare) - some other solution may be used.

To return a register from a function with POPA I use a simple structure:
Code:
virtual at 0
RegsAtPopa:
    .reg_EDI dd ?
    .reg_ESI dd ?
    .reg_EBP dd ?
    .Skip    dd ?
    .reg_EBX dd ?
    .reg_EDX dd ?
    .reg_ECX dd ?
    .reg_EAX dd ?
    .size = $
end virtual
    

Then when returning from a function - I just store a return value into an output register like this:
Code:
;
; Return with EAX <= EDI
;
mov [esp + RegsAtPopa.reg_EAX], edi
popa
ret
    

Interesting solution. There seems to be some reason for it. What are the tasks you solve and what do you gain by using this approach?
Post 25 Oct 2013, 15:44
View user's profile Send private message Visit poster's website Reply with quote
AsmGuru62



Joined: 28 Jan 2004
Posts: 1419
Location: Toronto, Canada
AsmGuru62
If I just return one register, I do not need to do 6 PUSH-es on entry and 6 POP-es on RET, saving some bytes for every procedure.
There may be opinions that this approach is not optimized.
It is, however, unclear. Probably for a decoder to just deal with a single instruction is better, I do not know or care -- if the code is not on critical path, in most cases PUSHA/POPA is very nice to use -- shorter procedures, easier to read.

Also, I know, reading my own code that most of the registers are preserved from CALL to CALL, so it is also opens some opportunities for not reloading variables into registers between CALLs.

In most cases, I optimize for code size first, then if some bottleneck is found - then for speed.
Post 25 Oct 2013, 17:26
View user's profile Send private message Send e-mail Reply with quote
cod3b453



Joined: 25 Aug 2004
Posts: 619
cod3b453
IHateRegistering wrote:
...
    * When using the stack, I first have to POP the return address in order to get access to the actual parameters. [-]
    * When using the stack, I can call my function recursively [+]
    * When using registers, I can leave the stack as it is.
...
The return address stack push/pop are automatically handled by the call/ret instructions, so you only need to concern yourself with additional stack allocations for local data (if any). You can also write recursive functions to use only use registers - some can reduce to a "tight" loop and hence remove the call/ret as well.

There are quite a number of defined calling conventions; the main two groups are stdcall/cdecl, which pass parameters on the stack and return values in registers and fastcall/SystemV, which uses registers for parameters (though when it runs out of registers it uses the stack again) and return values. Both are compromises on the points you raised.
Post 25 Oct 2013, 18:04
View user's profile Send private message Reply with quote
uart777



Joined: 17 Jan 2012
Posts: 369
uart777
You can learn by studying the disassembly ("listing") of optimized HL compilers (example: VC++) and there are settings to customize code generation. I prefer fast-call and/or my function macro. Advantages: Portable to X86+ARM, easy, no "stdcall" prefix, no ret before end, endf can return a value, "try" error checking, no [] for locals and parameters:
Code:
function foo, a, b
 locals x, y, z
 ; ...
endf    
DimonSoft showed a typical X86 way to setup a stack frame that supports locals. Another approach is esp-only, but it's unsafe and in most cases, execution time far outweighs the call overhead except in the smallest examples - rgb(r,g,b), strlen(s) - that are being called repetitively in a tight loop.

Guru: What?! Writing standard procedures (using FASM's proc) is equivalent to using C?!
Post 25 Oct 2013, 19:57
View user's profile Send private message Reply with quote
IHateRegistering



Joined: 24 Oct 2013
Posts: 2
IHateRegistering
Thank you very much! I didn't know that my question's topic has the name calling convention. I searched the internet and found massive examples. That's quite a big topic and I guess as I am using assembler I will more understand what kind of convention to use in which situation. Due to lack of experience in the moment, I simply will try to use stdcall and fastcall as they're easy to understand.

Thanks again for all your answers!
Post 26 Oct 2013, 01:33
View user's profile Send private message Reply with quote
Display posts from previous:
Post new topic Reply to topic

Jump to:  


< Last Thread | Next Thread >
Forum Rules:
You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot vote in polls in this forum
You cannot attach files in this forum
You can download files in this forum


Copyright © 1999-2020, Tomasz Grysztar. Also on GitHub, YouTube, Twitter.

Website powered by rwasa.