News:

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

VARARG and procedures

Started by rags, January 20, 2009, 12:36:47 AM

Previous topic - Next topic

rags

I was just playing with  VARARG and procedures, and I am getting errors when trying to compile this as a console app.The code does nothing, nor is it meant to:

    include \masm32\include\masm32rt.inc

tproc proto dItemCnt:dword, dItems:vararg

    .code
start:
    call main
    inkey
    exit

main proc
    invoke tproc, 2, 1, 2
     ret
main endp

tproc proc dItemCnt:dword, dItems:vararg
    mov ecx, dItemCnt
    xor edx, edx
@@1:
    mov eax, dItems[edx]
    dec ecx
    inc edx
    or ecx, ecx
    jnz @@1
tproc endp
end start


The errors I am getting are:

Microsoft (R) Macro Assembler Version 6.14.8444
Copyright (C) Microsoft Corp 1981-1997.  All rights reserved.

Assembling: calltesting.asm
calltesting.asm(10) : error A2131: VARARG parameter requires C calling convention
calltesting.asm(31) : error A2131: VARARG parameter requires C calling convention
calltesting.asm(36) : error A2006: undefined symbol : dItems
_
Assembly Error
Press any key to continue . . .


What has me confused at the moment is that the masm32 help file has this to say about vararg:
Quote
The VARARG attribute allows you to pass a variable number of arguments to a procedure. The VARARG parameter receives a near pointer to an array of WORD elements (DWORD if a far address is passed). You can apply the VARARG attribute only to the last parameter in a procedure definition. You can only use VARARG in procedures when the C, SYSCALL, or STDCALL language types are in effect.

If according to the help file I can use stdcall. then why the errors saying I must use the c calling convention?
God made Man, but the monkey applied the glue -DEVO

Jimg

Gee, this seems familiar..  Ah yes, I asked the very same question here.. http://www.masm32.com/board/index.php?topic=2175.0

Ultimately, I just made something work, using syscall, and didn't really get an answer to why the documentation says it should work, but masm won't accept it.

rags

Thanks Jim.
edit:
I stuck with the c calling,  and had to modify the line:

mov eax, dItems[edx]

to:

mov eax, dItems[edx * 4]

to get the correct argument from the stack.
God made Man, but the monkey applied the glue -DEVO

BogdanOntanu

Manuals and documentations are sometimes wrong. You should always look at them as a gift but also with a critical rational eye because many times thye do contain errors.

Let us think about it a little:
=====================
If using VARARG then your procedure does have an unknown number of parameters, it could be 2 parameters or 1001 parameters. When you invoke it in your code then (and only then) you do establish the exact number of parameters that you send as input this time.

In the STDCALL calling convention the PROC has to clean-up the stack by removing the arguments before returning. This can not be done (or it is very hard to be done by manipulating the stack and the return address). Even if you send the number of arguments as the first argument of the PROC this cleanup is hard to do because the arguments are after the return address in stack layout and  the epilogue code would have to manipulate the stack in "strange" nonstandard ways.

Also the STDCALL calling convention does not specify that in case of a VARARG you should use the first parameter as a parameter count. It could be the second or the 3rd parameter... How could the PROLOGUE/EPILOGUE code generator know what ad-hoc convention you have established yourself?

However in the "C" or CDECL calling convention the PROC does not have to do this arguments clean-up. Instead the assembler does this stack cleanup right after the calling of the procedure. Now this is very easy to be done because the INVOKE macro or the assembler does know the number of the parameters pushed on the stack from reading your source and can balance the stack with ease. It does have the disadvantage of adding stack cleanup code after each and every invocation of a procedure but under this circumstances (VARARG) it is an acceptable trade off.

Lastly if you are experienced you would have noticed that WIN32API does use the C calling convention for wsprintf API function because this one is using VARARG parameters and this fact alone should have given you a hint for the brain.

Of course that you can invent your own way to solve this problem BUT in this case it will no longer be the "standard" STDCALL or CDECL calling convention anymore and the whole idea behind standard calling conventions is ... well... to be "standard" and not "special" or custom.

In other words uniformity for ease of understanding at the expense of speed/size and flexibility

Ambition is a lame excuse for the ones not brave enough to be lazy.
http://www.oby.ro

rags

God made Man, but the monkey applied the glue -DEVO

jj2007

#5
A problem is that Masm doesn't accept changes of the calling convention - and most of the time you may want to use standard, not Ccall. Macros don't have that limitation. Here is an example:

varprint CrLf$, addr AppName, "Hello", CrLf$, "This seems to work", CrLf$, "just fine", offset txCrLf, "don't you think so?", CrLf$
varprint CrLf$, 'Another test', 13, 10, 'using "simple" quotes', 9, 'and a tab', CrLf$

Full code below.

EDIT(1+2): Changed a few bits to increase flexibility. Note that varprint preserves all registers (yes I mean all). However,

varprint "Myvar=", str$(esi)

would trash eax, ecx, edx because str$ expands before passing eax to varprint.

EDIT(3): Minor change to make it JWasm compatible:

if OPATTR(arg) eq 36 will not be accepted by JWasm - see here for explanations.

if (OPATTR arg) eq 36 is ok for Masm (6.14, 9.x) and JWasm.



include \masm32\include\masm32rt.inc

varprint MACRO FirstString, strings:VARARG
LOCAL argct
  ifb <FirstString>
  ifndef CrLf$
.data
txCrLf db 13, 10, 0
CrLf$ dd txCrLf
.code
endif
  PushString CrLf$
  else
PushString <FirstString> ;; first arg on the stack
  endif
  argct = 1
  FOR item, <strings>
argct = argct + 1
PushString item ;; push all the others
  ENDM
  push argct ;; push the count
  call varprintP
  add esp, 4*argct+4 ;; correct the stack incl. counter
ENDM

PushString MACRO arg:REQ
LOCAL quot, hasq, nustr, needslea, sc, src, tmp$
  src equ <arg>
  needslea equ <no> ;; default: no lea needed
  sc SIZESTR <arg>
  if sc gt 5
needslea SUBSTR src, 1, 4 ;; may contain "addr"
  endif
  quot SUBSTR src, 1, 1
  hasq INSTR src, <">
  if hasq eq 0
hasq INSTR src, <'>
if hasq eq 0
ifidn needslea, <no>
if (OPATTR arg) eq 36 ;; for the print "test", 13, 10, "me" syntax
hasq = 1
endif
endif
endif
  endif
  if hasq
.data
nustr db src, 0
.code
push offset nustr ;; literal string source
  else
ifidni <addr>, needslea
src SUBSTR src, 5 ;; get rid of "addr "
sc = OPATTR src
if sc eq 98
push 0
push eax
lea eax, src
mov [esp+4], eax ;; and push the address
pop eax
else
tmp$ CATSTR <push offset >, <src>
tmp$
endif
else
push src ;; else pass src
endif
  endif
ENDM

.code
AppName db "Testing the varprint macro:", 0
txCrLf db 13, 10, 0
CrLf$ dd txCrLf

start:
print chr$("------ TEST START --------------", 13, 10)

mov eax, offset AppName

mov esi, esp ; for the test, let's check the stack
add esi, eax  ; and the preserved registers (eax, ecx, edx, ...)
add esi, ecx
add esi, edx
add esi, ebx
add esi, edi
add esi, ebp

varprint addr txCrLf, eax, CrLf$, 13, 10,\
"Hello", CrLf$,\
"This seems to work", CrLf$, "just fine",\
offset txCrLf, "don't you think so?", CrLf$

varprint ; no argument=CrLf$

varprint 'Another test', 13, 10, 'using "simple" quotes', 9, 'and a tab', CrLf$

; varprint "esi=", str$(esi) ; will trash registers because str$ expands before passing eax to varprint

; putting literal strings in <brackets> saves a few bytes in the .data section
varprint <'Another test', 13, 10, 'using "simple" quotes', 9, 'and a tab and brackets'>, CrLf$

call TestLocalStrings

sub esi, esp ; subtract current stack from original stack
sub esi, eax  ; do the same for the registers we added above
sub esi, ecx
sub esi, edx
sub esi, ebx
sub esi, edi
sub esi, ebp

print chr$(13, 10, "------ TEST END ----------------------", 13, 10)

print "Register check (must be zero): "
print str$(esi), 13, 10
inkey
exit

TestLocalStrings proc
LOCAL LocBuf[60]:BYTE ; we'll copy a string to a local buffer
  pushad ; lstrcpy does not preserve the registers,

  invoke lstrcpy, addr LocBuf, chr$(13, 10, "only a local variable needs ADDR, i.e. lea eax, LocBuf")

  popad ; so we better save them for this test

  varprint CrLf$, "Reducing code size: ", addr LocBuf, CrLf$

  ret
TestLocalStrings endp

OPTION PROLOGUE:none
OPTION EPILOGUE:none
varprintP proc
  push ebx ; will not trash any registers
  push eax
  push ecx
  push edx
  mov ebx, [esp+4+16] ; the counter was passed in ecx

@@: push [esp+4*ebx+4+16] ; get pointers from the stack
call StdOut ; send them to the console
dec ebx
jg @B
  pop edx
  pop ecx
  pop eax
  pop ebx
  ret
varprintP endp
OPTION PROLOGUE:PrologueDef
OPTION EPILOGUE:EpilogueDef

end start

Vortex

Hi rags,

Here is a simple wsprintf simulator to concatenate strings. It uses the symbol % instead of %s

; simple wsprintf emulator for NULL terminated strings
; Return value : eax holds the length of the string in the buffer

.386
.model flat,stdcall
option casemap:none

.code

OPTION PROLOGUE:NONE
OPTION EPILOGUE:NONE

wsprintfX PROC C buffer:DWORD,format:DWORD,args:VARARG

    push    esi
    push    edi
    push    ebx
    mov     edi,DWORD PTR [esp+16]  ; buffer
    mov     esi,DWORD PTR [esp+20]  ; format
    lea     ebx,DWORD PTR [esp+24]  ; args
    sub     edi,1
    sub     esi,1
@@:
    add     edi,1

_wsp1:

    add     esi,1
    movzx   eax,BYTE PTR [esi]
    cmp     al,'%'
    je      @f
    mov     BYTE PTR [edi],al
    test    al,al
    jnz     @b
    mov     eax,edi
    sub     eax,DWORD PTR [esp+16]
    pop     ebx
    pop     edi
    pop     esi
    ret
@@:
    mov     edx,DWORD PTR [ebx]
    xor     eax,eax
@@:
    movzx   ecx,BYTE PTR [edx+eax]
    mov     BYTE PTR [edi+eax],cl
    add     eax,1
    test    cl,cl
    jnz     @b
    lea     edi,[edi+eax-1]
    add     ebx,4
    jmp     _wsp1

wsprintfX ENDP

OPTION PROLOGUE:PROLOGUEDEF
OPTION EPILOGUE:EPILOGUEDEF

END