News:

Masm32 SDK description, downloads and other helpful links
Message to All Guests

Main Menu

a vararg Win32 __stdcall function

Started by bulk88, November 13, 2014, 05:16:38 PM

Previous topic - Next topic

bulk88

There is a general thought that a vararg stdcall function can not be created. One reason is "ret" instruction only takes imm16, not a register. The other is, that supposedly (I can't find the actual line in C89 to support this) C doesn't require that all args passed into the function to be va_arg()ed. The callee can ignore excess args according to C. But dont we want to use __stdcall to avoid all the extra "add esp, *" instructions, or the fat "mov eax, dword ptr [esi+*]; mov dword ptr [esp+*], eax" instructions used in non-moving esp style functions instead of push? Well, we can. Here is a simple cdecl vararg function converted into a stdcall vararg function. To use it from C, you will need to cast it every time obviously and write C macros ADD1, ADD2, ADD3, ADD4, etc.

Before

unsigned int adder(unsigned int paramCount, ...) {
    va_list va;
    unsigned int ret = 0;
    unsigned int i;
    va_start(va,paramCount);
    for(i=0;i<paramCount;i++){
        ret += va_arg(va,unsigned int);
    }
    va_end(va);
    return ret;
}

after

_paramCount$ = 8 ; size = 4
_addersc PROC NEAR ; COMDAT

; 273  :     unsigned int i;
; 274  :     va_start(va,paramCount);
; 275  :     for(i=0;i<paramCount;i++){
; call DWORD PTR __imp__DebugBreak@0
mov edx, DWORD PTR _paramCount$[esp-4]
xor eax, eax
test edx, edx
jbe SHORT _end

; 271  :     va_list va;
; 272  :     unsigned int ret = 0;

lea ecx, DWORD PTR _paramCount$[esp-4]
redo:

; 276  :         ret += va_arg(va,unsigned int);

add ecx, 4
add eax, DWORD PTR [ecx]
dec edx
jne SHORT redo
_end:

; 277  :     }
; 278  :     va_end(va);
; 279  :     return ret;
; 280  : }
pop edx ; put ret addr in reg
add ecx, 4 ; wipe arg paramCount's space
mov esp, ecx ; put esp at end of incoming args
push edx ; put ret addr back in stack in new location
ret ; use ret instruction instead of jmp edx to avoid
        ;the .2 us delay (by my measurement) of a missed CPU return stack buffer
        ;prediction
_addersc ENDP

dedndave

C calling convention allows this because the RET instruction does not "remove" args from the stack
instead, the caller is responsible for balancing the stack after the call

jj2007

Hi bulk,
Welcome to the Forum :icon14:
You might like this thread.

Gunther

Hi bulk88,

welcome to the forum and have a lot of fun.

Gunther
You have to know the facts before you can distort them.

Vortex

Not the best solution but the code below is working :

include     \masm32\include\masm32rt.inc
include     invoke.inc

.data

string      db 'Sum = %u',13,10,0

.data?

.code

start:

    _invoke CalcSum,1,3,5,7,9

    invoke  crt_printf,ADDR string,eax

    _invoke CalcSum,2,4,6

    invoke  crt_printf,ADDR string,eax

    invoke  ExitProcess,0


CalcSum PROC

    LOCAL   argcnt:DWORD

    mov     ecx,DWORD PTR [ebp+8]       ; get the number of parameters
                                        ; obtained by the _invoke macro
    mov     argcnt,ecx
                   
    xor     eax,eax
    lea     edx,[ebp+12]                ; get the address of the first
                                        ; argument
@@:
    add     eax,DWORD PTR [edx]
    add     edx,4
    dec     ecx
    jnz     @b

    mov     ecx,argcnt
    inc     ecx
    shl     ecx,2
    leave
    mov     edx,DWORD PTR [esp]         ; get the return address
    add     esp,ecx                     ; balance the stack
    mov     DWORD PTR [esp],edx
    retn

CalcSum ENDP

END start