News:

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

Main Menu

VARARG

Started by Biterider, November 20, 2017, 07:10:26 AM

Previous topic - Next topic

Biterider


Hello
Today I needed to use the VARARG proc feature. I noticed that the VARARG arguments default to DWORD instead of QWORD.
Type casting the arguments solves the problem, but in my opinion, the default should be QWORD.


The dissassembly shows what I mean:
start:
sub         rsp,38h 
mov         dword ptr [rsp+20h],89ABCDEFh 
mov         r9d,89ABCDEFh 
mov         r8d,89ABCDEFh 
mov         rdx,0B23456789ABCDEFh 
mov         rcx,0A23456789ABCDEFh 
call        TestProc (0331000h) 
mov         qword ptr [rsp+20h],rdx 
mov         r9,rcx 
mov         r8,rbx 
mov         rdx,rax 
mov         rcx,0A23456789ABCDEFh 
call        TestProc (0331000h) 
add         rsp,38h 
ret 

Source
.xmm
option casemap:none
option dotname
option frame:auto
option win64:8
option stackbase:rsp


.code


TestProc proc Arg1:QWORD, Arg2:VARARG
    mov r8, Arg1
    mov r9, Arg2
    ret
TestProc endp




start proc
    invoke TestProc, 0123456789ABCDEFh, QWORD ptr 0123456789ABCDEFh, 0123456789ABCDEFh, 0123456789ABCDEFh, 0123456789ABCDEFh
    invoke TestProc, 0A23456789ABCDEFh, rax, rbx, rcx, rdx
    ret
start endp


end start



Regards, Biterider

jj2007

Quote from: Biterider on November 20, 2017, 07:10:26 AMin my opinion, the default should be QWORD.

That is also my opinion, but the stupid x64 ABI says something else, as usual obfuscated in one cryptic phrase: The first four integer arguments are passed in registers.

My highlighting 8)

aw27

Quote from: jj2007 on November 20, 2017, 08:41:32 AM
That is also my opinion, but the stupid x64 ABI says something else, as usual obfuscated in one cryptic phrase: The first four integer arguments are passed in registers.

Ah, I see, by integer argument appears that they are saying a 32-bit value. Really misleading.  :icon_rolleyes:

johnsa

I'm not sure that the x64 ABI specifically dictates this, although the general convention is to default to dwords even in 64bit mode for many things.
If using the type override is troublesome it should be possible to automatically promote the type, as the immediate is clearly too large to be a DWORD.

aw27

Quote from: johnsa on November 21, 2017, 02:05:08 AM
I'm not sure that the x64 ABI specifically dictates this, although the general convention is to default to dwords even in 64bit mode for many things.
If using the type override is troublesome it should be possible to automatically promote the type, as the immediate is clearly too large to be a DWORD.

The default is 64-bit.
Have a look at how it is done in C/C++:

int testProc(long long arg1, ...)
{
   return 1;
}

int main()
{
   testProc(0x0123456789ABCDEF, 0x0123456789ABCDEF, 0x0123456789ABCDEF, 0x0123456789ABCDEF, 0x0123456789ABCDEF);

    return 0;
   
}

testProc(0x0123456789ABCDEF, 0x0123456789ABCDEF, 0x0123456789ABCDEF, 0x0123456789ABCDEF, 0x0123456789ABCDEF);
000000013F2B177E  mov         rax,123456789ABCDEFh 
000000013F2B1788  mov         qword ptr [rsp+20h],rax 
000000013F2B178D  mov         r9,123456789ABCDEFh 
000000013F2B1797  mov         r8,123456789ABCDEFh 
000000013F2B17A1  mov         rdx,123456789ABCDEFh 
000000013F2B17AB  mov         rcx,123456789ABCDEFh 
000000013F2B17B5  call        testProc (013F2B10F0h)

johnsa

Yep, that's exactly what I meant, in C if you passed in an immediate say 0x12345670 it should use a dword as it fits, but given the immediate is > we can make it a qword. The question is, should the limit be based on signed or unsigned, i would think signed range.

johnsa

Ok, done.. this will be in 2.46 soon :)

qWord

Quote from: johnsa on November 21, 2017, 02:47:28 AMThe question is, should the limit be based on signed or unsigned, i would think signed range.
The type of none-decimal literals in C or C++ is the first that match in the following list (MASM types in brackets; MS-tool-chain specific):
Quoteint (SDWORD)
unsigned int (DWORD)
long int (SDWORD)
unsigned long int (DWORD)
long long int (SQWORD)
unsigned long long int (QWORD)
For decimal literals the list is:
Quoteint (SDWORD)
long int (SDWORD)
long long int (SQWORD)
MREAL macros - when you need floating point arithmetic while assembling!

johnsa

Thanks for the info!  :t

jj2007

I made a quick test with The Behemoth aka The Blue Whale aka Visual Studio 2015:#include <stdio.h>
int testProc(long arg1, ...)
{
printf("Test: %i #################################\n", arg1);
return 1;
}

int main()
{
int MyInt = 0x666;
long MyLong = 0x7777777777777777;
long long MyLongLong = 0x8888888888888888;
#if 0
000000013FF710F3 | BA 66 06 00 00 | mov edx, 666 | arg2, int
000000013FF710F8 | C7 44 24 28 22 02 00 00 | mov dword ptr ss : [rsp + 28], 222 | last arg passed
000000013FF71100 | 49 B9 88 88 88 88 88 88 88 88 | movabs r9, 8888888888888888 | correct!
000000013FF7110A | C7 44 24 20 11 01 00 00 | mov dword ptr ss : [rsp + 20], 111 | 2nd last
000000013FF71112 | 41 B8 77 77 77 77 | mov r8d, 77777777 | wrongly initialised long
000000013FF71118 | B9 EF CD AB 89 | mov ecx, 89ABCDEF | wrongly assigned long
000000013FF7111D | E8 7E FF FF FF | call <project1.testProc> |
#endif
testProc(0x0123456789ABCDEF, MyInt, MyLong, MyLongLong, 0x111, 0x222);
    return 0;
   
}


It compiles fine. It doesn't even issue a little warning - no wonder that M$ software is often buggy.

qWord

Quote from: jj2007 on November 21, 2017, 10:59:39 AM
It compiles fine. It doesn't even issue a little warning - no wonder that M$ software is often buggy.
It does warn and the produce code is of course correct: long alias long int and their unsigned counterparts are 32 bit wide in MS environment.
MREAL macros - when you need floating point arithmetic while assembling!

jj2007

int testProc(long arg1, ...)
...
testProc(0x0123456789ABCDEF, MyInt, MyLong, MyLongLong, 0x111, 0x222);


Obviously, 0x0123456789ABCDEF can't fit into a long, therefore a decent compiler would throw an error. VC doesn't care:
mov ecx, 89ABCDEF | wrongly assigned long

And thank you for calling me a liar, qword.

hutch--

The approach to VARARG is simple if you choose to see how the Win 64 bit ABI works. If you leave the stack alone, you can write as many extra arguments as you like up to the limit of stack memory "after the first 4 registers" and like most old C functions make the first arg the arg count. I did this in 64 bit MASM so I could join multiple strings with a simple macro and all it has to do after the first 4 registers is keep writing the additional 64 bit args to the next stack location.

The macro that "invoke" calls will handle 24 arguments which starts to run into the arg length limit but it has been super useful in joining complex strings and numeric conversion data. Now the trick is for smaller arguments, DWORD, WORD, BYTE is in the design of the stack, write each smaller data size to a 64 bit location and everything fits correctly. The difference with 64 bit is you have far more memory and many of these older views about saving space belong back in the DOS COM era. With arguments passed on the stack, you are using memory that is ALREADY ALLOCATED so you are usually not saving anything. If you run a highly recursive proc, simply set your stack size in the linker to a size big enough to handle the recursion.

Trying to hang onto the architecture of Win32 is harking back to a ghost from the past. Win64 /LARGEADDRESSAWARE is geared to handle gigabytes, do a few megabytes matter in terms of memory allocation ?

Here is an example of VARARG in 64 bit MASM using a macro.

  ; -------------------------------------
  ; ffmpeg and ffplay must be in the path
  ; -------------------------------------
    mcat pout,"ffmpeg -i ",psrc," -stats -loglevel 0 -b:v ",pbit," -s ", \
         pvid,sharp,"-hide_banner -codec:v h264 ",psnd,pdst

qWord

Quote from: jj2007 on November 21, 2017, 11:46:01 AM
int testProc(long arg1, ...)
...
testProc(0x0123456789ABCDEF, MyInt, MyLong, MyLongLong, 0x111, 0x222);


Obviously, 0x0123456789ABCDEF can't fit into a long, therefore a decent compiler would throw an error. VC doesn't care:
mov ecx, 89ABCDEF | wrongly assigned long

And thank you for calling me a liar, qword.
I didn't do so - but it is yet another case where you are not able to use the tool correct and instead claim that the tools or the language are buggy, shitty, crappy and so on.
The default warning level for projects is W3, which does show warnings (C4305). Unless you did not disable them or change the warning level, they are displayed exactly above the lines in your evidence image.

MREAL macros - when you need floating point arithmetic while assembling!

jj2007

Quote from: qWord on November 21, 2017, 12:06:14 PMThe default warning level for projects is W3, which does show warnings (C4305). Unless you did not disable them or change the warning level, they are displayed exactly above the lines in your evidence image.

The project was at level W3, the default, and there is NO warning regarding the wrong type. This is VS Community 2015. Attached is the output for W4, lots of crappy warnings about missing pdb files (who cares?) but no warning that the compiler chose to pick the low dword of the 64 bits passed to a long, instead of throwing an error, as every decent tool would do.