News:

MASM32 SDK Description, downloads and other helpful links
MASM32.com New Forum Link
masmforum WebSite

VARARG and Procs

Started by Jimg, July 06, 2005, 04:39:29 PM

Previous topic - Next topic

Jimg

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.

Jeff

#1
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

hutch--

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.
Download site for MASM32      New MASM Forum
https://masm32.com          https://masm32.com/board/index.php

rea

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 ;).

Jimg

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?



hutch--

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
Download site for MASM32      New MASM Forum
https://masm32.com          https://masm32.com/board/index.php

rea

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).

MichaelW

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

eschew obfuscation

Jimg

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.


MichaelW

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.
eschew obfuscation

hutch--

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.
Download site for MASM32      New MASM Forum
https://masm32.com          https://masm32.com/board/index.php

Jimg

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

Vortex

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]

Jimg

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!

hutch--

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.
Download site for MASM32      New MASM Forum
https://masm32.com          https://masm32.com/board/index.php