I'm trying to write a proc that allows a variable number of arguments without the user having to count up the number of arguments and pass that value. After searching for several days and many hours, I couldn't find a decent example, so I struck out on my own. The first stumbling block:
The documentation clearly states-
C SYSCALL STDCALL BASIC FORTRAN PASCAL
+-------+-------+-------+-------+-------+-------+
Leading Underscore | X | | X | | | |
|-------+-------+-------+-------+-------+-------|
Capitalize All | | | | X | X | X |
|-------+-------+-------+-------+-------+-------|
Arguments Left to Right | | | | X | X | X |
|-------+-------+-------+-------+-------+-------|
Arguments Right to Left | X | X | X | | | |
|-------+-------+-------+-------+-------+-------|
Caller Stack Cleanup | X | | * | | | |
|-------+-------+-------+-------+-------+-------|
BP Saved | | | | X | X | X |
|-------+-------+-------+-------+-------+-------|
:VARARG Allowed | X | X | X | | | |
+-------+-------+-------+-------+-------+-------+
This implies that VARARG is allowed with C, SYSCALL, and STDCALL calling conventions. When I try STDCALL using this code-
.686p
.model flat, stdcall
option casemap :none ; case sensitive
AddNumbers PROTO STDCALL:VARARG
.
AddNumbers Proc STDCALL args:VARARG
ret
AddNumbers EndP
I get the following errors-
VarArg.asm(45) : error A2131: VARARG parameter requires C calling convention
VarArg.asm(77) : error A2112: PROC and prototype calling conventions conflict
When I use syscall-
AddNumbers PROTO SYSCALL:VARARG
.
.
AddNumbers Proc SYSCALL args:VARARG
There are no complaints.
I've found this same documentation in many places, clearly stating that STDCALL is allowed, so I must be misinterpreting what it is saying.
Yes, I perversely refused to use "C" just to learn something. Can anyone shed some light on this please?
Also, any words of wisdom on how to determine how many parameters were passed without the caller specifying the number would be appreciated.
this is how i understand it, i may be wrong somewhere. unless someone could correct me.
now i dont know much about the SYSCALL calling convention but the STDCALL calling convention in fact, does not take variable arguments. that's why the assembler is having problems. i think this makes sense (with C and STDCALL at least) that VARARG does or does not work because if you recall, in the C calling convention, the
CALLER cleans up the stack (because the caller knows how many arguments it passed) while the STDCALL calling convention has no way of determining how many arguments were given. with that in mind, i suppose the SYSCALL calling convention gets the argument count via one of the registers but again, i dont know much about that convention so dont take that as the final word.
hope that helped a little
[edit]
i just noticed something in your example
Quote from: Jimg on July 06, 2005, 04:39:29 PM.686p
.model flat, stdcall
option casemap :none ; case sensitive
AddNumbers PROTO STDCALL:VARARG
.
AddNumbers Proc STDCALL args:VARARG
ret
AddNumbers EndP
either you typed it up (rushed) or you copied and pasted but that code should cause problems, the calling convention and argument list need to be seperated by a comma. same thing goes for the other. :U
Jim,
All a VARARG argument list does for you is let you point as much stuff to the stack as arguments as you like, your code in the proc being called must have some way to determine how many arguments are passd to it. This is where using the MASM pre-processor really shines, it can count the number of arguments and add that count into the argument list and if you want, it can calculate the stack correction after the C call has been made.
Here something that can help? http://www.masmforum.com/simple/index.php?topic=1345.0
By the way.. If you know the type of the arguments, a little diferent (a little) from the anterior "reference" would be some like
The thing is to adjust the stack when necesary....
caller:
push ebp
mov ebp, esp
sub esp, 4 ; space for store the esp instead of a register... at ebp-4 in this case
push eax
push ebx
mov [ebp-4], esp
mov eax, esp ; save the actual esp
invoke AddNumbers, 1,2,3,4,4,5,5,6,6,7,7,78,8,89,9,0,8,7,6,5,5
mov eax, esp ; same
invoke AddNumbers, 3,4,5,6, 4,6,34
mov eax, esp ; same
invoke AddNumbers,32,5,7,54
mov esp, [ebp-4] ;and that is .... adjusting the stack in the caller without much adds and when you need it....
....
ret
; The function will be of the same "type" described in the "reference"
AddNumbers:
sub eax, esp ; calculating the "distance"
; for calculate the number of arguments, being them a dword, then divide by 4 eax that is:
shr eax, 2 ;you have in eax the count of arguments....
; Do things here...
ret ; a single ret will do the job, no need for ret 16 or something like that....
Being this asm, and being this not a standar convention of calling, then is for you the choice to select if use ecx instead of eax... and things like that..... and is your choice see if it work, because like always, I havent tested it ;).
Thanks Hutch. By pre-processor, I assume you are talking about using a macro, something I was trying to avoid.
Thanks Rea. A little overwhelming, but I think I got it. Seems like a lot of extra work for the caller.
I've played around and gotten something working, but I'm not sure it safe to use. It seems like when I invoke the routine, the instruction following the invoke is
add esp, (number of bytes used for arguments, plus 4 for the return address)
so...
In my code, I pick up this number and use it in a loop thusly:
AddNumbers Proc SYSCALL args:VARARG
mov eax,[esp+4] ; return address
movzx ecx,byte ptr [eax+2] ; get the number of bytes out of the add esp instruction
mov eax,0
addloop:
sub ecx,4 ; get rid of the extra for the return address
jb done ; process the zero offset also
mov edx,args[ecx]
add eax,args[ecx]
jmp addloop
done:
ret
AddNumbers EndP
This way, its a simple invoke to use with no extra code...
invoke AddNumbers,4,ecx,15 ,eax,abc
but it seems a little risky. Any comments?
Jim,
I know they are a bit of a pain to get the swing of but the preprocessor allows you to do stuff like this very efficiently with no additional code. I will publish this macro with a whole pile of others shortly but this will do the job for you. Its one of the original 1990 MASM 6.0 macros that came with masm back then.
It lets you write code like,
add esp, argcount(args) * 4
If you function gets 4 args pointed at it and you use this type of macro to preprocess it, you end up with something like,
add esp, 16
; -----------------------------------------------
; count the number of arguments passed to a macro
; -----------------------------------------------
argcount MACRO args:VARARG
LOCAL cnt
cnt = 0
FOR item, <args>
cnt = cnt + 1
ENDM
EXITM %cnt ;; return as a number
ENDM
Quote
It seems like when I invoke the routine, the instruction following the invoke is
add esp, (number of bytes used for arguments, plus 4 for the return address)
A call will "push" the ret address.
Interesting the way you have get it :) :eek, tough, I havent thinked of this (If Im not wrong how you do it).
You take the value that is added with add esp, N after a call to a function that dosent clean the stack...
Tought you will see, sometimes, the value in esp isn't aligned after the return, for example, printf is a function that dosent clean the stack...., then a optimization instead of do the following:
A)
invoke printf, formatSTR1, 1,2,3
add esp, 3*4
invoke printf, formatSTR2, "hola", 4
add esp, 2*4
A compiller or you can do the following...
B)
invoke printf, formatSTR1, 1,2,3
invoke printf, formatSTR2, "hola", 4
add esp, 3*4+ 2*4
I guess with this example, you will see where the problem of your choice come ;).
What I refer is something like the following one of two options.... (the A,B are related to a similarity with the anterior examples).
A)
caller:
mov eax, esp
invoke funct1, 1,2,3,4,5
add esp, eax
mov eax, esp
invoke funct1, 3,6,4
add esp, eax
ret
funct1:
sub eax, esp
push eax ; save the number of bytes passed.
shr eax, 2 ; eax contain in this moment the number of args.
; do here what you whant
pop eax ; get again the number of bytes passed
ret
B)
caller:
push ebp
mov ebp, esp
sub esp, 4 ; a local variable for save the deep in this function...
mov [esp], esp ; this is fun isn't it?
mov eax, esp
invoke funct1, 1,2,3,4,5
mov eax, esp
invoke funct1, 3,6,4
add esp, [ebp-4]
ret
funct1:
sub eax, esp
shr eax, 2 ; eax contain in this moment the number of args.
; do here what you whant
ret
I guess the two examples are relative to the other 2 examples (I think that they work... ;)), also dont have the problem, one do align each time the function is called, the other each time the caller need it.
Dont know how secure this are compared with the way you propose :).... also if Im not wrong, you are not aligning the stack after you return from the function... (then it will still unaligned... if this matter in some place... you will need take care). Except for the part after about add esp, N, if you are coding it, and you have complete control (mean you always put the add esp, N) after a function of this type, then it should be suficient secure I guess.
Also see the diference between A and B, in B there is a necesity of know where the local variable is, that is why you need a stack frame, not like A there no exist completely at all the necesity for a stack frame being a single push in the funciton called at this time...
[added]
And remember you will be the one that will choise the registers... (instead of eax used normally for return an argument another one).
This is a procedure that determines the number of arguments it was passed. It is modified from the one I previously posted so it can handle an argument count of zero, and with some additional comments. I think it would generally be preferable to use the preprocessor to count arguments in the calling code, but a technique like this might be useful in library code where you have no control over the caller.
; ««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««
.486 ; create 32 bit code
.model flat, stdcall ; 32 bit memory model
option casemap :none ; case sensitive
include \masm32\include\windows.inc
include \masm32\include\masm32.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\masm32.lib
includelib \masm32\lib\kernel32.lib
include \masm32\macros\macros.asm
testproc PROTO C :VARARG
; ««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««
.data
.code
; ««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««
start:
; ««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««
invoke testproc
print ustr$(eax)
print chr$(13,10)
invoke testproc,1
print ustr$(eax)
print chr$(13,10)
invoke testproc,1,2
print ustr$(eax)
print chr$(13,10)
invoke testproc,1,2,3
print ustr$(eax)
print chr$(13,10)
invoke testproc,1,2,3,4
print ustr$(eax)
print chr$(13,10)
mov eax,input(13,10,"Press enter to exit...")
exit
; The return address placed on the stack will be the
; address of the instruction following the invoke
; statement. If any arguments were passed to the called
; procedure then the instruction will be an ADD ESP,n
; where n is the number of bytes to add to the stack
; pointer to "remove" the arguments from the stack.
; Assuming n < 256 the number of arguments can be
; calculated as the value of the third byte of the
; instruction divided by 4.
testproc proc C args:VARARG
xor eax, eax ; assume zero args
mov edx, [ebp+4] ; get return address
cmp WORD PTR[edx], 0C483h ; opcode for add esp,n
je @F
ret
@@:
movzx eax, BYTE PTR[edx+2] ; get value added to esp
shr eax, 2 ; divide by 4 for arg count
ret
testproc endp
; ««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««
end start
A disassembly of the fifth invoke:
004010A5 6A04 push 4
004010A7 6A03 push 3
004010A9 6A02 push 2
004010AB 6A01 push 1
004010AD E853000000 call fn_00401105
004010B2 83C410 add esp,10h
Thanks Michael, that looks like exactly the code I posted!
With the nice addition of
cmp WORD PTR[edx], 0C483h
to be sure the right instruction is coded where you think it is.
And, yes, I was thinking of a library routine, so macro's wouldn't really help here.
I searched both old and new forums for every occurance of vararg and didn't find this. What was the topic where you posted it?
As for the number of arguments, I couldn't get invoke to accept over 48 arguments no matter what I did, so there shouldn't be a problem with exceeding 255.
See rea's first post in this thread.
I just realized that the Calling Convention chart in the QE help, as well as the one in the Microsoft MASM Programmer's Guide, contains an error. I was assuming SYSCALL would not work because the charts do not indicate caller stack cleanup. But it does work, and in the Glossary of the MASM Programmer's Guide I found:
Quote
SYSCALL
A language type for a procedure. Its conventions are identical to C's, except no underscore is prefixed to the name.
I know you can manually code a version of FASTCALL by using 3 registers and doing any further argumets on the stack using either STDCALL or C call. I can see the advantage in very short procedures by using registers but with any normal length procedure it probably does not matter much. Its the same story as stack frame removal, if the procedure is small enough to matter, its faster again to inline it.
Quote from: MichaelW on July 07, 2005, 02:33:37 PM
See rea's first post in this thread.
Duh.... Sometimes there are just too many trees in my forest :red
Hi rea,
You wrote:
invoke printf, formatSTR1, 1,2,3
invoke printf, formatSTR2, "hola", 4
add esp, 3*4+ 2*4
Here is an example for this kind of optimization :
.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
include cinvoke.inc
includelib \masm32\lib\kernel32.lib
includelib msvcrt.lib
_printf PROTO SYSCALL
.data
msg1 db "Hello from Masm :)",13,10,0
msg2 db "This is an optimization example",0
fmt db "%s",0
.code
start:
cinvoke printf,ADDR fmt,ADDR msg1
cinvoke printf,ADDR fmt,ADDR msg2
add esp,4*4
invoke ExitProcess,0
END start
[attachment deleted by admin]
I found something interesting playing with this code. If you call with a 16bit value, the stack pointer is not reset properly.
Normal call results in this-
invoke AddNumbers,eax,4,edx,5
000000A5 6A 05 * push +000000005h ; 4 bytes
000000A7 52 * push edx ; 4 bytes
000000A8 6A 04 * push +000000004h ; 4 bytes
000000AA 50 * push eax ; 4 bytes
000000AB E8 00000065 * call AddNumbers
000000B0 83 C4 10 * add esp, 000000010h ; correct amount, 16 bytes
But if the call is with dx
invoke AddNumbers,eax,4,dx,5
000000A5 6A 05 * push +000000005h ; 4 bytes
000000A7 6A 00 * push 000h ; 4 bytes
000000A9 66| 52 * push dx ; 2 bytes
000000AB 6A 04 * push +000000004h ; 4 bytes
000000AD 50 * push eax ; 4 bytes
000000AE E8 00000065 * call AddNumbers
000000B3 83 C4 10 * add esp, 000000010h ; still 16 bytes, should have been 18 bytes
So afterwards, the stack pointer is off by 2 bytes. If you had perviously save on the stack
000000A5 51 push ecx ; save value for later
invoke AddNumbers,eax,4,dx,5
000000A6 6A 05 * push +000000005h
000000A8 6A 00 * push 000h
000000AA 66| 52 * push dx
000000AC 6A 04 * push +000000004h
000000AE 50 * push eax
000000AF E8 00000066 * call AddNumbers
000000B4 83 C4 10 * add esp, 000000010h
000000B7 59 pop ecx
then ecx now has the wrong value!
Jim,
Trying to call a 16 bit address will cause you other problem before you nitice a stack imbalance. The highest number in 16 bit does not even come close the 32 bit addresses so it should go bang when you call it.
Yes, the AddNumbers routine would need to know that the value pushed on the stack was a 16-bit number. Assuming it had some way to know that, it would work properly, but the stack would still be wrong.
And now that I think about it a little more....
000000A5 51 push ecx ; save value for later
invoke AddNumbers,eax,4,dx,5
000000A6 6A 05 * push +000000005h
000000A8 6A 00 * push 000h ; what is this???
000000AA 66| 52 * push dx
000000AC 6A 04 * push +000000004h
000000AE 50 * push eax
000000AF E8 00000066 * call AddNumbers
000000B4 83 C4 10 * add esp, 000000010h
000000B7 59 pop ecx
Why is there an extra zero dword pushed, and why would a routine know to expect this?
that extra push was inserted because the procedure was expecting 32bit arguments when you passed in a 16bit argument. then MASM tried to compensate for this so it (intends to) push 0 as a word. tho it usually does a bad job at this.
the best thing to do is pass ALL dwords or make the necessary changes if you need smaller (zero extend the word into a 32bit register and pass the register for an argument).
What I know about push is that the value substracted to (e)sp depend on the operation mode of the CPU, 16 or 32 bits, 16 bits = 2 bytes, 32 bits = 4 bytes.
Then I don't think that extra 0 is for compensate a 16 bit value pushed in the stack, also you can see here http://nasm.sourceforge.net/doc/html/nasmdocb.html#section-B.4.263 specific the first description line. tough like I see you say that is a bad work, and that is because is trying to do what already push do (compensate clearly if you push a 16 bit or 8 bit value in the stack) the necesary bytes for fill the correct dword starting at where esp was...
If you say... how the CPU inmediately try any value like a dword or word depending on the operation mode (for push/pop)????, see the encoding compared to the anterior reference
QuotePUSH imm8 ; 6A ib [186]
The codifications 6A followed by a inmediate byte, but if you execute this, it will be pushed a 32 bit value (expanded with zeros).
I guess or argee is a error of invoque... tought perhpas can be corrected.
Gentleman,
This is just my two cents. I am not sure why
invoke AddNumbers,eax,4,dx,5
is used instead of
invoke AddNumbers,eax,4,edx,5
Because even if you prefer to operate on a 16bit value, what harm does it do to push it as 32bit so as to ensure that the stack is properly aligned. I will reread the posts to make sure I am not missing something but IMO that seems to be the thing to do. If AddNumbers expects a value in DX, it will just ignore the upper 16bits.
PBrennick
Gentleman,
In rereading the posts I see that Hutch has already posted a warning about this type of thing. When it comes to macros used as preprocessors, Hutch is a real genius. Remember that invoke is a macro and listen to Hutch.
Shucks, now I am up to four cents.
PBrennick
:red
I wish you were right. Variations in characteristics depending on which internal macro operator is used has nearly driven me nutz. Cute things like a FOR loop reinitialises an equae with txt in it if a mcro using the equate is called from within thr FOR loop. :(
Hi Hutch,
I don't want to get too far off topic but genius is a relative term and you do this stuff better than most of us. Anyway, I think it is best to push EDX in this case. I hope I got that right, at least.
PBrennick
Yes, I agree about best to use edx. I was trying to account for every possibility in the routine I was working on, so am trying anything that can happen. I was just pointing out a possible problem. I was getting pretty trusting of invoke, even with it's shortcomings, but it's important that everyone know about this "feature". Does anyone know if this is fixed in versions later than 6.15? That's the latest one I have.
I may be talked into using a helper macro to call the routine just to avoid invoke :(
Jimq,
I doubt that they will fix what is not broke in their opinion (and mine). Intentionally causing a stack imbalance will probably lead them to believe that the problem lies with who is causing the imbalance. There is no offense implied here, when it comes to stretching the limits of the assembler, I am probably the worst offender because it has been so much a part of my life. Having said that, though, when something blows up, I point the 'fickle finger of fate' at myself and repair the code. :bg
PBrennick
Hi Paul-
Well, I went over all the documentation I had, and it all seemed to indicate it should work. So I went to msdn for the latest info-
QuoteINVOKE expression [[, arguments]]
Calls the procedure at the address given by expression, passing the arguments on the stack or in registers according to the standard calling conventions of the language type. Each argument passed to the procedure may be an expression, a register pair, or an address expression (an expression preceded by ADDR).
I'm still not sure what a register pair is in this context. I always thought that was like eax:edx
so searching on register pair I found at
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/vcmasm/html/vcerrA2033.asp
Quoteinvalid INVOKE argument : argument number
The INVOKE directive was passed a special 386 register, or a register pair containing a byte register or special 386 register. These registers are illegal with INVOKE.
It would have been nice to get this error comment. There is no indication what version of masm this would occur with.
Quote from: Jimg on July 09, 2005, 04:35:15 PM
Hi Paul-
Well, I went over all the documentation I had, and it all seemed to indicate it should work. So I went to msdn for the latest info-
QuoteINVOKE expression [[, arguments]]
Calls the procedure at the address given by expression, passing the arguments on the stack or in registers according to the standard calling conventions of the language type. Each argument passed to the procedure may be an expression, a register pair, or an address expression (an expression preceded by ADDR).
I'm still not sure what a register pair is in this context. I always thought that was like eax:edx
so searching on register pair I found at
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/vcmasm/html/vcerrA2033.asp
Quoteinvalid INVOKE argument : argument number
The INVOKE directive was passed a special 386 register, or a register pair containing a byte register or special 386 register. These registers are illegal with INVOKE.
It would have been nice to get this error comment. There is no indication what version of masm this would occur with.
i checked the MASM programmers guide and whenever they mention register pair, they were referring to a segment and GP register. (see "Invoking Far Addresses") to indicate a register pair, they used a double semicolon. i played around with that idea for a bit and it looks like we can do 2 GP registers as well (eax::edx).
someproc PROTO STDCALL,:QWORD,:QWORD,:DWORD
.
.
.
MOV eax,01234567h
MOV edx,89ABCDEFh
INVOKE someproc,eax::edx,edx::eax,ax::dx
.
.
.
someproc PROC STDCALL,a:QWORD,b:QWORD,c:DWORD
;a = 0123456789ABCDEFh
;b = 89ABCDEF01234567h
;c = 4567CDEFh
RET
someproc ENDP
you may find that useful. :)
[edit]
added stuff to clear things up.
Hi Jeff,
Yes, that is certainly useful. I am certain I never tried that.
Paul
Jeff,
:U That is useful, I didn't know you could do that.
Jim,
Here is a demo on how to handle a C calling convention procedure using the preprocessor to automatically handle pushing the variable number of arguments and correct the stack after. You need to get the latest macro file from the MASM2 subforum for the macros used but it gives you a clean and tidy way to handle a C calling convention procedure.
; «««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««
include \masm32\include\masm32rt.inc
; «««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««
comment * -----------------------------------------------------
Build this template with
"CONSOLE ASSEMBLE AND LINK"
----------------------------------------------------- *
tprint MACRO args:VARARG
LOCAL acnt,lcnt ;; a couple of macro locals
push esi ;; preserve ESI
mov esi, alloc(16384) ;; allocate 16k of buffer
acnt = argcount(args) ;; get the VARARG argument count
lcnt = acnt ;; assign to a local var
REPEAT acnt
push repargof(getarg(lcnt,args)) ;; push variable number of args
lcnt = lcnt - 1
ENDM
push esi ;; push the buffer address
push argcount(args) ;; push the argument count
call szMultiCat ;; call the C calling procedure
add esp, argcount(args)*4+8 ;; correct the stack
invoke StdOut, esi ;; display the text
fn StdOut,chr$(13,10) ;; append a CRLF to display
free esi ;; free the memory buffer
pop esi ;; restore ESI
ENDM
.code
start:
; «««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««
call main
inkey
exit
; «««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««
main proc
nops 3
tprint "This ","is ","a test"
ret
main endp
; «««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««
end start
DISASSEMBLY
push esi
push 4000h
push 40h
call fn_00401146
mov esi, eax
push 403021h
push 403028h
push 40302Ch
push esi
push 3
call fn_00401070
add esp, 14h
push esi
call fn_004010A0
push 403032h
call fn_004010A0
push esi
call fn_0040114C
pop esi
Hutch,
Looks nice.
Paul