The MASM Forum

64 bit assembler => UASM Assembler Development => Topic started by: Biterider on November 20, 2017, 07:10:26 AM

Title: VARARG
Post by: Biterider on November 20, 2017, 07:10:26 AM

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
Title: Re: VARARG
Post by: jj2007 on November 20, 2017, 08:41:32 AM
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. (https://docs.microsoft.com/en-us/cpp/build/parameter-passing)

My highlighting 8)
Title: Re: VARARG
Post by: aw27 on November 20, 2017, 07:15:24 PM
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. (https://docs.microsoft.com/en-us/cpp/build/parameter-passing)

Ah, I see, by integer argument appears that they are saying a 32-bit value. Really misleading.  :icon_rolleyes:
Title: Re: VARARG
Post by: 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.
Title: Re: VARARG
Post by: aw27 on November 21, 2017, 02:13:22 AM
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)
Title: Re: VARARG
Post by: johnsa on November 21, 2017, 02:47:28 AM
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.
Title: Re: VARARG
Post by: johnsa on November 21, 2017, 03:07:53 AM
Ok, done.. this will be in 2.46 soon :)
Title: Re: VARARG
Post by: qWord on November 21, 2017, 03:18:04 AM
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)
Title: Re: VARARG
Post by: johnsa on November 21, 2017, 03:21:22 AM
Thanks for the info!  :t
Title: Re: VARARG
Post by: jj2007 on November 21, 2017, 10:59:39 AM
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.
Title: Re: VARARG
Post by: qWord on November 21, 2017, 11:32:00 AM
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.
Title: Re: VARARG
Post by: 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.
Title: Re: VARARG
Post by: hutch-- on November 21, 2017, 11:47:18 AM
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
Title: Re: VARARG
Post by: qWord on November 21, 2017, 12:06:14 PM
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.

Title: Re: VARARG
Post by: jj2007 on November 21, 2017, 06:28:56 PM
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.
Title: Re: VARARG
Post by: aw27 on November 21, 2017, 08:05:46 PM
It produces the warnings when you BUILD.
IT does not produce warnings when you press F5 to debug-run, because it is assumed that you already know what you are doing (which is not case  :badgrin:).
Title: Re: VARARG
Post by: jj2007 on November 21, 2017, 08:13:11 PM
Quote from: aw27 on November 21, 2017, 08:05:46 PM
It produces the warnings when you BUILD.
Title: Re: VARARG
Post by: aw27 on November 21, 2017, 08:22:33 PM
We already know that you always use all sorts of magic tricks to defend your unrealistic  theories.  :lol:


1>------ Rebuild All started: Project: ConsoleApplication1, Configuration: Release x64 ------
1>  ConsoleApplication1.cpp
1>ConsoleApplication1.cpp(16): warning C4305: 'initializing': truncation from '__int64' to 'long'
1>ConsoleApplication1.cpp(16): warning C4309: 'initializing': truncation of constant value
1>ConsoleApplication1.cpp(27): warning C4305: 'argument': truncation from '__int64' to 'long'
1>ConsoleApplication1.cpp(27): warning C4309: 'argument': truncation of constant value
1>  stdafx.cpp
1>  Generating code
1>  All 6 functions were compiled because no usable IPDB/IOBJ from previous compilation was found.
1>  Finished generating code
1>  ConsoleApplication1.vcxproj -> C:\Users\Jose\Documents\Visual Studio 2015\Projects\ConsoleApplication1\x64\Release\ConsoleApplication1.exe
1>  ConsoleApplication1.vcxproj -> C:\Users\Jose\Documents\Visual Studio 2015\Projects\ConsoleApplication1\x64\Release\ConsoleApplication1.pdb (Full PDB)
========== Rebuild All: 1 succeeded, 0 failed, 0 skipped ==========
Title: Re: VARARG
Post by: jj2007 on November 21, 2017, 08:35:13 PM
Quote from: aw27 on November 21, 2017, 08:22:33 PM
We already know that you always use all sorts of magic tricks to defend your unrealistic  theories.  :lol:

I use default settings, inter alia -W3. Glad to see that it works for you :icon_mrgreen:
Title: Re: VARARG
Post by: aw27 on November 21, 2017, 10:51:42 PM
Quote from: jj2007 on November 21, 2017, 08:35:13 PM
IGlad to see that it works for you :icon_mrgreen:
Quote
000000013FF71100 | 49 B9 88 88 88 88 88 88 88 88 | movabs r9, 8888888888888888 | correct!
I have the strange feeling that you are cheating all the time. Since when does Visual Studio use GAS instructions, I mean movabs?  ::)
Title: Re: VARARG
Post by: jj2007 on November 21, 2017, 10:57:43 PM
Of course, I love cheating. And feeding trolls :icon_mrgreen:
000000013F4610AC | BA 66 06 00 00                    | mov edx, 666                       |
000000013F4610B1 | 49 B9 88 88 88 88 88 88 88 88     | movabs r9, 8888888888888888        |
000000013F4610BB | C7 44 24 20 11 01 00 00           | mov dword ptr ss:[rsp+20], 111     |
Title: Re: VARARG
Post by: aw27 on November 21, 2017, 11:10:40 PM
Quote from: jj2007 on November 21, 2017, 10:57:43 PM
Of course, I love cheating. And feeding trolls :icon_mrgreen:
000000013F4610AC | BA 66 06 00 00                    | mov edx, 666                       |
000000013F4610B1 | 49 B9 88 88 88 88 88 88 88 88     | movabs r9, 8888888888888888        |
000000013F4610BB | C7 44 24 20 11 01 00 00           | mov dword ptr ss:[rsp+20], 111     |


I got it now, you are using one of those crappy disassemblers from github.   :lol:
mov dword ptr ss:[rsp+20], 111 is also lovely.  :badgrin:
Title: Re: VARARG
Post by: jj2007 on November 21, 2017, 11:24:35 PM
Quote from: aw27 on November 21, 2017, 11:10:40 PMI got it now, you are using one of those crappy disassemblers from github.   :lol:
mov dword ptr ss:[rsp+20], 111 is also lovely.  :badgrin:

You shouldn't underestimate these tools. Actually, these crappy debuggers (not disassemblers) emulate a whole fake CPU: When they reach that address, and you step through the code, r9 fills magically with 8888888888888888, and memory at [rsp+20] fills with 111. Imagine what a giant programming effort is behind such an emulator 8)
Title: Re: VARARG
Post by: aw27 on November 22, 2017, 12:25:42 AM
Quote from: jj2007 on November 21, 2017, 11:24:35 PM
You shouldn't underestimate these tools. Actually, these crappy debuggers (not disassemblers) emulate a whole fake CPU: When they reach that address, and you step through the code, r9 fills magically with 8888888888888888, and memory at [rsp+20] fills with 111. Imagine what a giant programming effort is behind such an emulator 8)
I am not an aficionado of magic debugger/emulators for fake CPUs.  :dazzled:
Title: Re: VARARG
Post by: hutch-- on November 22, 2017, 01:23:10 AM
Ha ha, the last disassembler I really liked was 1990 CodeView and used to write code formatted the same way. Now ALA 1998 - 2000 NuMega SoftIce was the genuine boys toy but later versions of Windows were pigs to get any form of debugger working because of their protected mode architecture. What I really need is a post mortem debugger which tells you where an app crashes which is far more use to me. The old DrWatson worked really well at this task but the later Win7 and 10 versions want to dump a pile of high level chyte and send if back to Microsoft.

These days I rarely ever use one, they are easy enough to defeat in security terms and are a lot slower than console output or even message boxes. The real use I have is for a decent disassembler that outputs plain text, ArkDasm barely does the job but it was really useful when I was designing the stack layout and call automation (invoke) macros for 64 but MASM.
Title: Re: VARARG
Post by: jj2007 on November 22, 2017, 01:53:21 AM
Quote from: hutch-- on November 22, 2017, 01:23:10 AMWhat I really need is a post mortem debugger which tells you where an app crashes

That is actually possible with x64dbg: Options/Preferences/Misc/Set x64dbg as Just In Time Debugger (as usual, they have ruthlessly stolen that feature from OllyDbg).

When the code crashes e.g. because of an idiv 0, see screenshot below, the grey box to the right appears; click on debug and hit F8 a couple of times, and voilĂ , here it is:
0000000140001089   | 33 C9              | xor ecx,ecx                             |
000000014000108B   | F7 F9              | idiv ecx                                |
000000014000108D   | 48 8D 15 A5 1F 00  | lea rdx,qword ptr ds:[140003039]        | 140003039:"Wow, it works!!"
0000000140001094   | 49 C7 C1 00 00 01  | mov r9,10000                            |
000000014000109B   | 4C 8D 05 A7 1F 00  | lea r8,qword ptr ds:[140003049]         | 140003049:"Hi"
00000001400010A2   | 33 C9              | xor ecx,ecx                             |
00000001400010A4   | FF 15 B6 20 00 00  | call qword ptr ds:[<&MessageBoxA>]      |
Title: Re: VARARG
Post by: aw27 on November 22, 2017, 02:36:52 AM
Quote from: jj2007 on November 22, 2017, 01:53:21 AM
That is actually possible with x64dbg: Options/Preferences/Misc/Set x64dbg as Just In Time Debugger (as usual, they have ruthlessly stolen that feature from OllyDbg).

Ahaha  :lol:
But this is not Post-Mortem, the program is still alive. What these github debugger/emulators do is go through the stack trace till where the exception happened.
Windbg does not even let the program crash, it quietly breaks where the exception happened (so they are called 2nd chance exceptions, the debugger gets the second chance to see it live).
This is the difference between a professional tool and a colorful toy downloaded from github.
  :biggrin:
Post-Mortem is working through crash dumps. Here again, only Windbg can do it.
Title: Re: VARARG
Post by: jj2007 on November 22, 2017, 02:46:04 AM
Quote from: aw27 on November 22, 2017, 02:36:52 AM.... till where the exception happened.

Which is usually the only aspect that I am interested in :bgrin:
Title: Re: VARARG
Post by: Biterider on December 20, 2017, 12:30:09 AM
Hi
Coming back to VARARG, I noticed that floating point arguments passed as VARARG are stored in GP-Registers and not, as I would expect, in XMM regs. Is that the intended way it should work?



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




.code


Test1 proc Arg1:QWORD, Args:VARARG
    mov rax, Arg1
    ret
Test1 endp




start proc
    invoke Test1, 102.54, REAL4 ptr 1.234, REAL8 ptr 1.234
    ret
start endp


end start




Test1 proc Arg1:QWORD, Args:VARARG
mov         qword ptr [Arg1],rcx 
mov         qword ptr [Args],rdx 
mov         qword ptr [rsp+18h],r8 
mov         qword ptr [rsp+20h],r9 
    mov rax, Arg1
mov         rax,qword ptr [Arg1] 
    ret
ret 
Test1 endp

The disassembly looks like

start proc
sub         rsp,28h 
    invoke Test1, 102.54, REAL4 ptr 1.234, REAL8 ptr 1.234
mov         r8,3FF3BE76C8B43958h 
mov         edx,3F9DF3B6h 
mov         rcx,42CD147Bh 
call        Test1 (0811000h) 
    ret
add         rsp,28h 
ret 



I'm not sure why the callee is saving all regs into the homing area. Has it something to do with the VARARG feature?


Biterider
Title: Re: VARARG
Post by: johnsa on December 20, 2017, 03:26:53 AM
Yes it is. the items in vararg are effectively untyped, so they're passed in register and then stored in home-spaces and further stack spaces for consistency.
So it would be the first argument that in some way would be required to determine the count and type of the variable arguments, which could then be found and processed from stack locations.