Parameter passing methods

Question about which tools to use, bugs, the best way to implement a function, etc should go here. Don't forget to see if your question is answered in the wiki first! When in doubt post here.
User avatar
~
Member
Member
Posts: 1228
Joined: Tue Mar 06, 2007 11:17 am
Libera.chat IRC: ArcheFire

Parameter passing methods

Post by ~ »

I have always wondered about the most effective methods of passing parameters.

To begin with, is it right to pass, say 2 parameters through the stack with:

Code: Select all

push dword param2
push dword param1
Then, once our function gets its result, is it right to get rid of these stack elements with the following?

Code: Select all

ret 2
What about for a variable number of parameters?


Another question is, wouldn't using the stack for parameter passing make some more vulnerable our kernel to stack overflows in case a serious nesting needs to take place?

Which other considerations to pass parameters are there? It's specially because having to handle each parameter with an inverse addressing into the stack seems to be very bloated, even more when one needs to push further elements, which of course will change the position of the passed parameters.
User avatar
AJ
Member
Member
Posts: 2646
Joined: Sun Oct 22, 2006 7:01 am
Location: Devon, UK
Contact:

Post by AJ »

Boring answer: The calling convention you use is up to you.

More helpful (I Hope) answer:

If you are using pure ASM, do what you like, but stick to a single convention.

Otherwise, it really depends on the language and compiler you are using. GCC for 32 bit apps currently passes paramaters from right-to-left on the stack as you describe. Note it is the caller who is responsible for maintaining the stack frame (popping arguments), to you don't use 'ret n', you just use 'ret' with no value argument.

Also, in your example, you would need 'ret 8', because the number applies to the number of bytes to pop.

Have a look at this link. It outlines the AMD ABI. Looking at this (and through my own experience), when compiling with GCC for 64 bit targets, the first few parameters are passed via registers, which is faster and more convenient than passing by the stack as long as you have enough registers to play with.

Some compilers (Turbo Pascal IIRC, along with some others...) pass via the stack and do get the callee to pop values, as per your example above.

I realise that it can seem a bit 'backwards', passing on the stack, but if you follow the 'right-to left' rule and the 'caller pops' rule, you should quickly get the hang of it. If you are interfacing with a HLL, you may just have to adapt your programming practices to interface code properly.

HTH
Adam
User avatar
Brynet-Inc
Member
Member
Posts: 2426
Joined: Tue Oct 17, 2006 9:29 pm
Libera.chat IRC: brynet
Location: Canada
Contact:

Post by Brynet-Inc »

Choosing your calling convention depends on personal preference really..

A member here, "XCHG" has several articles on his website that explain each calling convention in considerable detail:

Code: Select all

http://www.asmtrauma.com/Articles.htm
(Not sure if he would appreciate hotlinking..)

Another handy resource is Wikipedia, http://en.wikipedia.org/wiki/X86_calling_conventions

Have fun 8)
Image
Twitter: @canadianbryan. Award by smcerm, I stole it. Original was larger.
User avatar
XCHG
Member
Member
Posts: 416
Joined: Sat Nov 25, 2006 3:55 am
Location: Wisconsin
Contact:

Post by XCHG »

Brynet-Inc wrote: A member here, "XCHG" has several articles on his website that explain each calling convention in considerable detail:

Code: Select all

http://www.asmtrauma.com/Articles.htm
(Not sure if he would appreciate hotlinking..)
I'm actually flattered :oops:
~ wrote:I have always wondered about the most effective methods of passing parameters.
I think that depends on the future of the program. You could use the register calling convention in your Operating System. Although that makes it faster than say, using the stack for passing parameters but at the same time, it makes it more difficult for compilers to be written that use that calling convention.
~ wrote:Then, once our function gets its result, is it right to get rid of these stack elements with the following?

Code: Select all

ret   2
Yes that's absolutely fine but since you are pushing DWORDs, you have to use

Code: Select all

RET     (0x04 * 2)
Since each DWORD in IA-32 is 4 bytes long and you have 2 of them pushed onto the stack.
~ wrote:What about for a variable number of parameters?
I think the StdCall calling convention is the most effective at the end of the day considering all the factors. You could use the StdCall calling convention also for variable number of parameters. You push the parameters from right to left and then as a DWORD, push the number of parameters onto the stack.

For example, if you have DWORD1 and DWORD2 to push onto the stack by value, then do this:

Code: Select all

  PUSH    DWORD PTR [DWORD1]
  PUSH    DWORD PTR [DWORD2]
  PUSH    DWORD 0x00000002
The last parameter will be the number of parameters that are pushed onto the stack. The good thing about this method is that the procedure that wants to receive these parameters will always be able to find the number of parameters since the number of parameters is the last parameter pushed onto the stack:

Code: Select all

  PUSH    DWORD PTR [DWORD1]
  PUSH    DWORD PTR [DWORD2]
  PUSH    DWORD 0x00000002
  CALL    __Procedure1
  
  
  
  __Procedure1:
    ; void __Procedure1 (DWORD Param1, DWORD Param2, ...); VarArg;
    
    PUSH    EAX
    PUSH    EBP
    MOV     EBP , ESP
    
    ; [EBP + (3*4)] = Number of parameters
    ; [EBP + (4*4)] = Param1
    ; [EBP + (5*4)] = Param2
    ; [EBP + (6*4)] = ...
    
    .EP:
      POP     EBP
      POP     EAX
    RET     (0x04 * 2)
See? [EBP + (3*4)] is always equal to the number of parameters even when you change the prototype of your procedure/function. (3*4 is because we have 3 registers pushed onto the stack for the stack frame, EBP, EAX and an EIP that is pushed onto the stack by the CALL instruction).

I hope that helps.
On the field with sword and shield amidst the din of dying of men's wails. War is waged and the battle will rage until only the righteous prevails.
Craze Frog
Member
Member
Posts: 368
Joined: Sun Sep 23, 2007 4:52 am

Post by Craze Frog »

http://www.asmtrauma.com/Articles.htm
I suggest you read this and decide which one you like and has good compiler support (cdecl/stdcall/register, not pascal).

Note that while the articles linked to seems to explain well, they contain a good deal of errors that you need to be aware of if you program in assembly:
Cdecl:
- Bytes and words are returned in eax, not al and ax, however, the number is truncated to fit in al/ax. This sounds like the same, but it means that if you want to assign the result of a function returning a byte to a dword variable, you can do so without conversion. If the result was returned in only al/ax you'd end up with partial garbage if you didn't convert from byte/word to dword.
- Even if the result size is 4 or 8 bytes, it is returned in st0 if it is a floating point value.
Any parameter with any size is passed to the procedure using a 4-byte stack space whether it is 8-bits or 80-bits long.
If it's 80 bits it's passed in 8 bytes, not 4.
Keep the original state of the CPU registers in your procedure/function unless you need to return a value in one or two or some of them.
The registers eax, ecx, edx and st0-st7 are called scratch registers, and can be modified freely by any procedure. If you want them preserved them across a procedure call, you must do it yourself. All other registers must be saved by the procedure.
Create a stack frame before trying to access local parameters.
Do not use the stack pointer explicitly neither to access local parameters nor to create local variables.
Stack frames are not necessary, nor fast nor particularly easy.

Register/Fastcall:
- Register and fastcall are different/not standardised. Which register to use for parameter is different from compiler to compiler.
- Gcc/VC++ passes the TWO first parameters in ecx then edx, the rest on the stack.
- Delphi passes the THREE first parameters in EAX, EDX then ECX, the rest on the stack.
The result of the function should be put inside one of these registers: AL, AX, EAX, EDX:EAX depending on the size of the result.
Same error as on cdecl: eax is used for byte and word returns.

Stdcall:
Same error about al/ax.
Ready4Dis
Member
Member
Posts: 571
Joined: Sat Nov 18, 2006 9:11 am

Post by Ready4Dis »

Most people use the standard stack approach, if you use it with gcc the easiest is to let the caller handle the fixing of the stack, and it allows for dynamic # of arguments passed (ala, printf).

Test:
push ebp
mov ebp, esp
pusha ;Push the rest of the registers if we have to!
mov eax, [ebp+8] ;First argument
mov ebx, [ebp+12] ;Second argument
popa
pop ebp
mov eax, 0 ;Return value if we need it!
ret ;Return to caller

To call this function...

push dword 10
push dword 5
call Test
add esp, 8 ;Restore the stack

Another way, so it doesn't matter how many you pass it:
push ebp
mov ebp, esp
push dword 10
push dword 5
call Test
mov esp, ebp
pop ebp

This allows you to pass any # of arguments without modifying the code and figuring out how many to add back to restore. The first way is a bit faster, but if you know you aren't using EBP for anything else you can skip the push/pop for it, which makes it very close in speed (one extra instruction). There are plenty of methods, some standard, some dependant on compiler, some faster than others, some slower... some harder to work depending on supported languages.
User avatar
bewing
Member
Member
Posts: 1401
Joined: Wed Feb 07, 2007 1:45 pm
Location: Eugene, OR, US

Post by bewing »

If you want to program your kernel in ASM and take the register method to its logical extreme, read up on Brendan's method on this thread:
http://www.osdev.org/phpBB2/viewtopic.php?t=14662
Ready4Dis
Member
Member
Posts: 571
Joined: Sat Nov 18, 2006 9:11 am

Post by Ready4Dis »

bewing wrote:If you want to program your kernel in ASM and take the register method to its logical extreme, read up on Brendan's method on this thread:
http://www.osdev.org/phpBB2/viewtopic.php?t=14662
That's how I wrote my original kernel, it was 100% asm, passed all parameters by registers... then I wanted to write my GUI in C, lol... so I re-wrote all my kernel functions :). Just warning you if you wanted to support other stuff, use a standard way of doing it. Although, my 100% asm kernel was smaller and faster, and used less resources, it just wasn't easy to develop for. I am currently re-writing it again almost entirely in C this time to be even easier to develop for, and my ASM code that I do need uses the standard stack based parameter passing, which works fine when I compile under gcc, or tiny c, where to get it workign under both using my original required an interface to convert from stack -> register (which wasn't to efficient), not to mention it was difficult to get things like printf to work with any # of arguments.
User avatar
XCHG
Member
Member
Posts: 416
Joined: Sat Nov 25, 2006 3:55 am
Location: Wisconsin
Contact:

Post by XCHG »

Craze Frog wrote: - Bytes and words are returned in eax, not al and ax
Wrong. Debug some 16-bit Pascal programs and see what I mean. A function that returns a bye value should NOT modify AH or the leftmost 16-bits of the EAX register in the aforementioned programming language.
Craze Frog wrote:If it's 80 bits it's passed in 8 bytes, not 4.
Again, wrong. A parameter that is bigger than the size of the biggest GPR of the CPU is passed to the function by its pointer. The pointer if of course as big as a GPR which in the IA-32 architecture, is 4 bytes. So if you have an array of 10 DWORDs, the compiler will pass the pointer to that array into the stack.
Craze Frog wrote:The registers eax, ecx, edx and st0-st7 are called scratch registers, and can be modified freely by any procedure. If you want them preserved them across a procedure call, you must do it yourself. All other registers must be saved by the procedure.
Eh, wrong! That is for High Level Languages where the compiler keeps track of modifications in GPRs. When you are coding in Assembly, you can't simply change the value of GPR in your function without saving its original state. If you are using a function inside another, you will have to keep track of what function changes what and etc. And by the way, aren't these registers called General Purpose Registers or GPRs?

Craze Frog wrote:Delphi passes the THREE first parameters in EAX, EDX then ECX, the rest on the stack.
I actually have this line in my article:
My Website wrote:If there are more than three parameters, the first three should be placed inside EAX, ECX and EDX registers and the rest should be pushed onto the stack from left to right.
Do you see the word "three"? I don't see what you refer to as incorrect because I clearly stated the same thing.

About the FastCall calling convention, I am simply giving hints about how it works. Of course you could pick your own style but the bottom line is that you might pass as many parameters as you have GPRs available :roll:
On the field with sword and shield amidst the din of dying of men's wails. War is waged and the battle will rage until only the righteous prevails.
User avatar
XCHG
Member
Member
Posts: 416
Joined: Sat Nov 25, 2006 3:55 am
Location: Wisconsin
Contact:

Post by XCHG »

Ready4Dis wrote: That's how I wrote my original kernel, it was 100% asm, passed all parameters by registers... then I wanted to write my GUI in C, lol... so I re-wrote all my kernel functions :).
Have you tried StdCall? It's pretty efficient!
On the field with sword and shield amidst the din of dying of men's wails. War is waged and the battle will rage until only the righteous prevails.
Ready4Dis
Member
Member
Posts: 571
Joined: Sat Nov 18, 2006 9:11 am

Post by Ready4Dis »

XCHG wrote:
Ready4Dis wrote: That's how I wrote my original kernel, it was 100% asm, passed all parameters by registers... then I wanted to write my GUI in C, lol... so I re-wrote all my kernel functions :).
Have you tried StdCall? It's pretty efficient!
No, I haven't looked into it, I just use the stack based method because it's the default for most compilers. It's easy to use, allows any # of arguments, and very simple to implement :). I don't see it being to big of a bottle-neck, but I may look into other methods to get a little more speed out of it, but it's not a huge deal right now.
User avatar
JamesM
Member
Member
Posts: 2935
Joined: Tue Jul 10, 2007 5:27 am
Location: York, United Kingdom
Contact:

Post by JamesM »

Stack frames are not necessary, nor fast nor particularly easy.
But their absence makes debugging recursive functions a pain in the tits!
Craze Frog
Member
Member
Posts: 368
Joined: Sun Sep 23, 2007 4:52 am

Post by Craze Frog »

XCHG wrote:
Craze Frog wrote: - Bytes and words are returned in eax, not al and ax
Wrong. Debug some 16-bit Pascal programs and see what I mean. A function that returns a bye value should NOT modify AH or the leftmost 16-bits of the EAX register in the aforementioned programming language.
I'm talking about 32-bit here. Also, I was talking about cdecl, stdcall and register, neither of which I believe were used by your 16-bit Pascal compiler (which probably used the pascal calling convention). In 32-bit mode bytes and words are returned in eax. Debug some 32-bit C/Basic/C++ programs to see what I mean.
XCHG wrote:
Craze Frog wrote:If it's 80 bits it's passed in 8 bytes, not 4.
Again, wrong. A parameter that is bigger than the size of the biggest GPR of the CPU is passed to the function by its pointer. The pointer if of course as big as a GPR which in the IA-32 architecture, is 4 bytes. So if you have an array of 10 DWORDs, the compiler will pass the pointer to that array into the stack.
Sorry, but have obviously no clue what you are talking about. Instead of arguing, why don't you go an look at some actual compiler output? I wouldn't have said these things if I didn't have tested it, I suggest you follow the same policy.
XCHG wrote:
Craze Frog wrote:The registers eax, ecx, edx and st0-st7 are called scratch registers, and can be modified freely by any procedure. If you want them preserved them across a procedure call, you must do it yourself. All other registers must be saved by the procedure.
Eh, wrong! That is for High Level Languages where the compiler keeps track of modifications in GPRs. When you are coding in Assembly, you can't simply change the value of GPR in your function without saving its original state. If you are using a function inside another, you will have to keep track of what function changes what and etc. And by the way, aren't these registers called General Purpose Registers or GPRs?
When coding in assembly you can do whatever you want to, but if you want to follow the cdecl or stdcall standard, you can't. The GPR registers are eax, ebx, ecx, edx, edi, esi and ebp. Three of these are scratch registers.

XCHG wrote:
Craze Frog wrote:Delphi passes the THREE first parameters in EAX, EDX then ECX, the rest on the stack.
I actually have this line in my article:
My Website wrote:If there are more than three parameters, the first three should be placed inside EAX, ECX and EDX registers and the rest should be pushed onto the stack from left to right.
Do you see the word "three"? I don't see what you refer to as incorrect because I clearly stated the same thing.
That line is WRONG for gcc and vcc. Even for Delphi it's wrong order...
XCHG wrote:About the FastCall calling convention, I am simply giving hints about how it works. Of course you could pick your own style but the bottom line is that you might pass as many parameters as you have GPRs available :roll:
You can't pick your own style if you want to interface with compiled code. :roll:

Look, I've written a compiler (without lex/yacc/other tools) and I know how calling conventions works.
JamesM wrote:
Stack frames are not necessary, nor fast nor particularly easy.
But their absence makes debugging recursive functions a pain in the tits!
It is my opinion that debugging code should not be embedded in a program in a way that makes it impossible to strip out, which is the case with stack frames added in asm in the way described. In any case, it was my point that the stack frames was described as a part of the calling convention, when it doesn't have much to do with it.
Ready4Dis
Member
Member
Posts: 571
Joined: Sat Nov 18, 2006 9:11 am

Post by Ready4Dis »

Craze Frog wrote:
JamesM wrote:
Stack frames are not necessary, nor fast nor particularly easy.
But their absence makes debugging recursive functions a pain in the tits!
It is my opinion that debugging code should not be embedded in a program in a way that makes it impossible to strip out, which is the case with stack frames added in asm in the way described. In any case, it was my point that the stack frames was described as a part of the calling convention, when it doesn't have much to do with it.
I dunno, I am going to stick with the stack approach for now... it works for me, it's very easy to use, and the variable is already in memory so I have plenty of registers to play with. I'm sure it's slower, and I may switch some of my functions over (ones with few params, and/or called very often to benefit from the speed boost). It's nice to know there are so many options though, and most of which are standard. What kind of outputs does your compiler support? Does it also do assembling and/or linking?
Craze Frog
Member
Member
Posts: 368
Joined: Sun Sep 23, 2007 4:52 am

Post by Craze Frog »

You can pass parameters on the stack and access them easily without creating a stack frame with ebp. (My compiler supports only stdcall, and uses fasm for assembling.)
Post Reply