flat assembler
Message board for the users of flat assembler.
Index
> Main > Parameters -> Stack Or Register? |
Author |
|
ejamesr 25 Oct 2013, 01:03
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 |
|||
25 Oct 2013, 01:03 |
|
DimonSoft 25 Oct 2013, 07:28
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. |
|||
25 Oct 2013, 07:28 |
|
revolution 25 Oct 2013, 07:44
IHateRegistering wrote: Or can you point me to a tutorial / explanation / ... which addresses this problems? 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. |
|||
25 Oct 2013, 07:44 |
|
AsmGuru62 25 Oct 2013, 14:48
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 |
|||
25 Oct 2013, 14:48 |
|
DimonSoft 25 Oct 2013, 15:44
AsmGuru62 wrote: In my opinion -- if parameters passed on stack -- our code is no better than C/C++. 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? |
|||
25 Oct 2013, 15:44 |
|
AsmGuru62 25 Oct 2013, 17:26
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. |
|||
25 Oct 2013, 17:26 |
|
cod3b453 25 Oct 2013, 18:04
IHateRegistering wrote: ... 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. |
|||
25 Oct 2013, 18:04 |
|
uart777 25 Oct 2013, 19:57
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 Guru: What?! Writing standard procedures (using FASM's proc) is equivalent to using C?! |
|||
25 Oct 2013, 19:57 |
|
IHateRegistering 26 Oct 2013, 01:33
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! |
|||
26 Oct 2013, 01:33 |
|
< Last Thread | Next Thread > |
Forum Rules:
|
Copyright © 1999-2025, Tomasz Grysztar. Also on GitHub, YouTube.
Website powered by rwasa.