flat assembler
Message board for the users of flat assembler.

Index > Main > varargs with stdcall

Goto page 1, 2  Next
Author
Thread Post new topic Reply to topic
vid
Verbosity in development


Joined: 05 Sep 2003
Posts: 7105
Location: Slovakia
vid 17 Jan 2007, 14:34
i have come with interesting idea:
is it possible to implement varargs with stdcall? If we can find inside function how many arguments we have, can we write epiloque code that will return from function AND remove arguments from stack?

something like fictional:
Code:
;eax contains number of arguments of function
retn eax    


main problem is, that such procedure should preserve all registers. and registers are usually restored before returning and clearing args.

this has to be accomplished without self-modyfing code so changing argument of retn won't work.

my proposal of epiloque:
Code:
printf:
push ebx ecx edx esi edi ;save registers
push ebp
mov ebp, esp

;...
;ebx contains number of arguments
;eax contains return value

;restore other regs
mov esp, ebp
pop ebp
pop edi esi edx ecx

;save return value
push eax

;overwrite last argument in stack with return address
mov eax, [esp+4+4]         ;EAX = return address = esp + saved eax + saved ebx
mov [esp + 4 + 4 + 4*(ebx-1)], eax

;overwrite dword before return address with saved value of EBX
mov eax, [esp+4]     ;EAX = saved ebx = esp + saved eax
mov [esp + 4 + 4 + 4*(ebx-2)], eax

;restore EAX (return value)
pop eax

;now move esp to new saved ebx and return value
lea esp, [esp + 4 + 4*(ebx-2)]

;restore
pop ebx

;return
retn    
(code not tested)

this won't work if EBX=0, but then it's enough to just retn.

anyone has better idea how to do this?
Post 17 Jan 2007, 14:34
View user's profile Send private message Visit poster's website AIM Address MSN Messenger ICQ Number Reply with quote
LocoDelAssembly
Your code has a bug


Joined: 06 May 2005
Posts: 4624
Location: Argentina
LocoDelAssembly 17 Jan 2007, 15:45
Why not just
Code:
printf:
; Usual prolog

; Code

; Partial epiloge (just non-volatile registers restoring)

mov  ecx, [ebp+4] ; Return address
mov  edx, [ebp+varArgsSize] ; I think that a stdcall version of print should specify the size instead of guessing it with fmt

; Another possible code could be
;if defined varArgsSize
;  mov edx, [ebp+varArgsSize]
;else
;  ; We trust that the programmer has set EDX before entering in the epilog
;end if

leave
lea esp, [esp+edx+..fixedArgsNumber*4]
mov [esp], ecx
ret ; I hope it will be predicted correctly even with all the mess above
    


But note that I think it is not a very good idea since it can be done by using regular stdcall and passing a pointer to the varArgsArray and both of our solutions are suboptimal than simply using "add esp, argsPassed*4" on return. So a better addition to fasm could be able to pass arrays in the same way you can pass strings (stdcall something, "Hello world"), but an improvement is required to allow defining all this constants on a global memory space instead of the current implementation which defines the data on code and it is skipped and pushed to stack by a CALL.

[edit] Fixed problem of modification of EFLAGS and reduced the amount of instructions at the same time (so it's better now Razz) [/edit]

[edit2] Even better
Code:
printf:
; Usual prologue  

; Code   

; ECX is set with varArgsSize 

; Partial epilogue (just non-volatile registers restoring)  

  mov     esp, ebp 

  mov     ebp, [ebp+4] ; Return Address
  mov     [esp+ecx+4+..fixedArgsNumber*4], ebp

  pop     ebp
  lea     esp, [esp+ecx+..fixedArgsNumber*4]
  retn    

since it doesn't affect EDX which sometimes is used to return 64-bit values. ECX is OK because it is pure volatile since it is never used as return value (I mean according to standards)[/edit2]


Last edited by LocoDelAssembly on 18 Jan 2007, 03:12; edited 2 times in total
Post 17 Jan 2007, 15:45
View user's profile Send private message Reply with quote
vid
Verbosity in development


Joined: 05 Sep 2003
Posts: 7105
Location: Slovakia
vid 17 Jan 2007, 16:22
because ALL registers have to be preserved, including ECX and EDX.

I am planning to implement formatted output (printf-like) to FASMLIB, and i would like to keep current calling convention. Few extra cycles per such call is not a problem, having to count arguments every time you call it, and writing "lea esp,[esp+XXX]" is bigger problem.

Also many people would probably use "add esp," and destroy CF holding error status Wink
Post 17 Jan 2007, 16:22
View user's profile Send private message Visit poster's website AIM Address MSN Messenger ICQ Number Reply with quote
MazeGen



Joined: 06 Oct 2003
Posts: 977
Location: Czechoslovakia
MazeGen 17 Jan 2007, 16:48
First, can you use unallocated stack entries in the fasmlib? I mean something like
Code:
push 1
add esp, 4
mov eax, [esp-4] ; eax always contains the value of 1
    
Post 17 Jan 2007, 16:48
View user's profile Send private message Visit poster's website Reply with quote
vid
Verbosity in development


Joined: 05 Sep 2003
Posts: 7105
Location: Slovakia
vid 17 Jan 2007, 16:53
as long as "unallocated stack entries" doesn't get overwritten by interrupt, yes.

EDIT: and unless MS says somewhere we can do this on win32, and linus says we can do this on linux, and all future supported-OS authors say i can do it on their OSes, i won't do it Razz Wink

so no go....
Post 17 Jan 2007, 16:53
View user's profile Send private message Visit poster's website AIM Address MSN Messenger ICQ Number Reply with quote
LocoDelAssembly
Your code has a bug


Joined: 06 May 2005
Posts: 4624
Location: Argentina
LocoDelAssembly 17 Jan 2007, 18:22
Quote:

because ALL registers have to be preserved, including ECX and EDX.

Then you are not using stdcall.

If you want to keep stdcall again I think it's better to pass a pointer to array instead.

remember this things
Code:
  fmt db "%d %d", 0

  ccall printf fmt, 3, 4, 6, 8, 9
  ; No problem since ccall will add to ESP the correct value (which can be done with LEA if EFLAGS needs to be preserved)

  vidcall printf fmt, 3, 4, 6, 8, 9
  ; FATAL, since printf will wrongly think that there is just 2 varArgs only.
    

Note that the last fictitious call nor the ccall are incorrect since I can later change fmt to show those args but now are invisible due to "user settings".

shoudn't FASMLIB procedures be called throught libcall? Then better modify the macros for procedure declaration a bit to allow varArgs and then libcall will do a "lea esp, [esp+varArgsSize]" when it detects you are calling a procedure with varArgs (or just modify libcall and when you pass more args than the amount of formal parameters then on return it will do a "lea esp, [esp+extraArguments*4]").

Not all this is more easy to implement but it is safer also.

If you want to continue in your way anyway then sorry, I have no ideas of how to do it better Sad
Post 17 Jan 2007, 18:22
View user's profile Send private message Reply with quote
vid
Verbosity in development


Joined: 05 Sep 2003
Posts: 7105
Location: Slovakia
vid 17 Jan 2007, 18:34
Quote:
Then you are not using stdcall.

allright. varargs isn't stdcall too. i mean, arguments pushed right to left, procedure cleans arguments from stack.

[qutoe]If you want to keep stdcall again I think it's better to pass a pointer to array instead. [/quote]Is it better to do from pure assembly? It's this code:
Code:
push 2
push 1
push _format
call printf    
against:
Code:
push 2
push 1
mov eax, esp
push eax
push _format
call printf
lea esp, [esp+8] ;remove structure from stack without destroying CF    

Second is much uglier, harder to comprehend for newbies, harder to use, easier to make mistake (manual removing of structure from stack), etc. etc.

Quote:
shoudn't FASMLIB procedures be called throught libcall
Not at all, i propagate pure assembly usage, libcall is just helper i use.

Quote:
vidcall printf fmt, 3, 4, 6, 8, 9
I am aware about bad number of arguments, but that is not really the case so important for me. exactly same as bad number as arguments when calling procedure - you can screw things up, price for power. And there is no non-errorous reason to do that i know.

Quote:
If you want to continue in your way anyway then sorry, I have no ideas of how to do it better
No problem, thanks for discussion. I must stay solid behind my ideas
Post 17 Jan 2007, 18:34
View user's profile Send private message Visit poster's website AIM Address MSN Messenger ICQ Number Reply with quote
LocoDelAssembly
Your code has a bug


Joined: 06 May 2005
Posts: 4624
Location: Argentina
LocoDelAssembly 17 Jan 2007, 19:38
Quote:

against:
Code:
push 2 
push 1 
mov eax, esp 
push eax 
push _format 
call printf 
lea esp, [esp+8] ;remove structure from stack without destroying CF
    


In fact it is againts:
Code:
call endOfArray
dd 1 
dd 2
endOfArray:
push _format 
call printf 
    

I mean using an array placed in compile-time or run-rime allocated memory. But the code above uses the same trick that Tomasz does to push strings but for me the better way is the same that Fresh does/did which consists in defining all constant data in one place.

[edit] Though, my code does not accept arguments like "[ebx+something]" nor "addr ebx+something"... [/edit]
Post 17 Jan 2007, 19:38
View user's profile Send private message Reply with quote
vid
Verbosity in development


Joined: 05 Sep 2003
Posts: 7105
Location: Slovakia
vid 17 Jan 2007, 19:49
consider usage in pure assembly. in FASMLIB, it would be in simplest case:
Code:
idata{
array:
dd 1
dd 2
}
push array
push _format
call print    

but this is pretty inefficient... that data should be declared on stack, not statically.

Still, in pure assembly usage (the one which matters in assembly library) is ugly and my "vararg stdcall" is worth of it. Purpose of library is to make things easier for caller, and my idea is imho most easy usage without any major drawbacks. It's just unstandard, but that isn't problem for me, as long as it's effective
Post 17 Jan 2007, 19:49
View user's profile Send private message Visit poster's website AIM Address MSN Messenger ICQ Number Reply with quote
LocoDelAssembly
Your code has a bug


Joined: 06 May 2005
Posts: 4624
Location: Argentina
LocoDelAssembly 17 Jan 2007, 21:06
Code:
printf:
; Usual prologue *

; Code 

; EBP is set with varArgsSize

; Partial epilogue (just non-volatile registers restoring)


push    dword [esp+..localVarsSize] ; oldEBP
pop     dword [esp+ebp+..localVarsSize+..fixedArgsSize+4]
 
push    dword [esp+..localVarsSize+4] ; returnAddress
pop     dword [esp+ebp+..localVarsSize+4+..fixedArgsSize+4]

lea     esp, [esp+ebp+..localVarsSize+..fixedArgsSize]
pop     ebp
ret ; I hope it will be predicted correctly even with all the mess above

; * push  ebp
; * mov   ebp, esp
; * add   esp, ..localVarsSize
; * push  non-volatile registers    


What about implementing like above? If I remember right PUSH/POP pairs are optimized to behalf like "mov [mem], [mem]" instead of two reads and two writes to memory, but if not well, some testings will be needed to check if using MOV EAX, [...] (after preservation of EAX) is faster. I think that the way above is easily macrosable. I'm not apporting to much to your first code, but this one is more compatible with the prolog generated by PROC32.INC

If for some reason EBP must hold the number of varArgs instead of the size then replace all the occurrencies of "+ebp+" with "+ebp*4+". Decide this carefully, it seems to be more confortable for the programmer to set EBP to the number of args but since inside the code you probably did "mov eax, [ebp+lastFormalArg+edi]"/"add edi, 4" (fragment of code that iterates throught the varArgs), then before the epilog a simple "mov ebp, edi" will be enough instead of "shr edi, 2"/"mov ebp, edi". Most of the time the used pointers will help a lot to calculate the size but if EBP must be set with the number of args then you will need to store a counter somewhere or do a little more calculations.

Cheers


Last edited by LocoDelAssembly on 18 Jan 2007, 00:48; edited 1 time in total
Post 17 Jan 2007, 21:06
View user's profile Send private message Reply with quote
vid
Verbosity in development


Joined: 05 Sep 2003
Posts: 7105
Location: Slovakia
vid 17 Jan 2007, 23:05
looks like nice idea. From my point it doesn't matter if size is in EBX or EBP, and it suits better to classical epiloque. nice idea.
Post 17 Jan 2007, 23:05
View user's profile Send private message Visit poster's website AIM Address MSN Messenger ICQ Number Reply with quote
LocoDelAssembly
Your code has a bug


Joined: 06 May 2005
Posts: 4624
Location: Argentina
LocoDelAssembly 18 Jan 2007, 00:46
I see a stupid coding, LEAVE is incorrect and must be changed to a simple POP EBP. I'll edit it now.
Post 18 Jan 2007, 00:46
View user's profile Send private message Reply with quote
vid
Verbosity in development


Joined: 05 Sep 2003
Posts: 7105
Location: Slovakia
vid 18 Jan 2007, 01:07
no idea, i never ever used "leave" Smile
Post 18 Jan 2007, 01:07
View user's profile Send private message Visit poster's website AIM Address MSN Messenger ICQ Number Reply with quote
LocoDelAssembly
Your code has a bug


Joined: 06 May 2005
Posts: 4624
Location: Argentina
LocoDelAssembly 18 Jan 2007, 02:39
Never???? Well ret of PROC32.inc use it all the time and it is the functional equivalent of mov esp, ebp/pop ebp but in a single CPU instruction.

The problem of course is that in this case ESP already has the appropiate value to do POP EBP but thanks to LEAVE I'm destroying the good work done by LEA and setting ESP with the argsSize which is obviously fatal.

BTW, my epilogue introduces a new requirement not found in current PROC32.INC, the need to have ESP at the end of local variables, if the user by some reson decided to save registers in localVariables then he can set ESP as much bytes as he wants away of the localVariables since LEAVE supports it perfectly but not the new epilogue. (Yes, if the programmer has used "uses ebx esi edi" then ESP can't be with arbitrary values but since he could not use "uses" but instead save the registers by himself then on a stdcall with varArgs he must have special care - though, this situation is so, so, so improbable...-)
Post 18 Jan 2007, 02:39
View user's profile Send private message Reply with quote
f0dder



Joined: 19 Feb 2004
Posts: 3175
Location: Denmark
f0dder 18 Jan 2007, 03:40
bad bad fucking bad idea - SMC is bad for performance, and what happens if a wrong format string is used? *boom*.
Post 18 Jan 2007, 03:40
View user's profile Send private message Visit poster's website Reply with quote
vid
Verbosity in development


Joined: 05 Sep 2003
Posts: 7105
Location: Slovakia
vid 18 Jan 2007, 08:32
f0dder: no SMC anywhere. It was one of my conditions in first post - no SMC. I can't afford it in portable code.
Post 18 Jan 2007, 08:32
View user's profile Send private message Visit poster's website AIM Address MSN Messenger ICQ Number Reply with quote
Octavio



Joined: 21 Jun 2003
Posts: 366
Location: Spain
Octavio 18 Jan 2007, 15:25
Is there some reason for not using the C stdcall here?
if parameters are incorrect how will the called procedure return?
Post 18 Jan 2007, 15:25
View user's profile Send private message Visit poster's website Reply with quote
LocoDelAssembly
Your code has a bug


Joined: 06 May 2005
Posts: 4624
Location: Argentina
LocoDelAssembly 18 Jan 2007, 15:36
Returning is always possible, the problem is that the stack will be unbalanced, and if stack is unbalanced then the caller will not be able to return nor restore the non-volatile registers (unless he used local variables to save them), but the callee can mess the stack and still be able to return.
Post 18 Jan 2007, 15:36
View user's profile Send private message Reply with quote
vid
Verbosity in development


Joined: 05 Sep 2003
Posts: 7105
Location: Slovakia
vid 18 Jan 2007, 16:25
hehe, everyone is taking the wrong number of arguments into account just because ccall allowed it. It's same as when you call classical stdcall proc with bad number of arguments - you just fucked it up, it's BUG!

Quote:
Is there some reason for not using the C stdcall here?
I think you mean ccall. yes, i want to make usage nicer from pure assembly, so you don't need to count arguments sized and write "lea esp, [esp+XYZ]" every time yourself. And I CAN do it, so question is: Is there some reason NOT to do it?
Post 18 Jan 2007, 16:25
View user's profile Send private message Visit poster's website AIM Address MSN Messenger ICQ Number Reply with quote
Octavio



Joined: 21 Jun 2003
Posts: 366
Location: Spain
Octavio 18 Jan 2007, 23:55
>don't need to count arguments sized and write "lea esp, [esp+XYZ]" every
have you seen the ccall macro in fasm sources?
or perhaps is to reduce code size?

>time yourself. And I CAN do it, so question is: Is there some reason NOT to >do it?
of course no, that's assembly Smile

what about this:

Code:
mov ebp,esp 
push params
call f1
.........


f1:
pusha
........
popa
pop [ebp-4]
lea esp,[ebp-4]
ret
    
Post 18 Jan 2007, 23:55
View user's profile Send private message Visit poster's website Reply with quote
Display posts from previous:
Post new topic Reply to topic

Jump to:  
Goto page 1, 2  Next

< 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-2024, Tomasz Grysztar. Also on GitHub, YouTube.

Website powered by rwasa.