Author Topic: a vararg Win32 __stdcall function  (Read 1712 times)

bulk88

  • Regular Member
  • *
  • Posts: 1
a vararg Win32 __stdcall function
« on: November 13, 2014, 05:16:38 PM »
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
Code: [Select]
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
Code: [Select]
_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

  • Member
  • *****
  • Posts: 8734
  • Still using Abacus 2.0
    • DednDave
Re: a vararg Win32 __stdcall function
« Reply #1 on: November 13, 2014, 11:05:28 PM »
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

  • Member
  • *****
  • Posts: 7557
  • Assembler is fun ;-)
    • MasmBasic
Re: a vararg Win32 __stdcall function
« Reply #2 on: November 14, 2014, 01:58:12 AM »
Hi bulk,
Welcome to the Forum :icon14:
You might like this thread.

Gunther

  • Member
  • *****
  • Posts: 3515
  • Forgive your enemies, but never forget their names
Re: a vararg Win32 __stdcall function
« Reply #3 on: November 14, 2014, 04:12:50 AM »
Hi bulk88,

welcome to the forum and have a lot of fun.

Gunther
Get your facts first, and then you can distort them.

Vortex

  • Member
  • *****
  • Posts: 1704
Re: a vararg Win32 __stdcall function
« Reply #4 on: November 14, 2014, 07:12:20 AM »
Not the best solution but the code below is working :

Code: [Select]
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