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
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)
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:
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.
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)
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.
Ok, done.. this will be in 2.46 soon :)
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)
Thanks for the info! :t
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.
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.
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.
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
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.
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.
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:).
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 ==========
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:
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? ::)
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 |
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:
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)
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:
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.
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>] |
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.
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:
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
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.