Re: 64-bit: Why Can't I get "CreateFileA" to Access a File or Device?

Started by nidud, March 04, 2021, 06:02:33 AM

Previous topic - Next topic

nidud


jj2007

Quote from: nidud on March 04, 2021, 06:02:33 AMMicrosoft and GCC do this in reverse, in push-order which is right-to-left

Normally, the order is not an issue - except if you use those registers as arguments which are used for the 4 fastcall arguments:
include \Masm32\MasmBasic\Res\JBasic.inc ; ## builds in 32- or 64-bit mode with UAsm, ML, AsmC ##
BytesWritten dq ?
Init ; OPT_64 1 ; put 0 for 32 bit, 1 for 64 bit assembly
  PrintLine Chr$("This program was assembled with ", @AsmUsed$(1), " in ", jbit$, "-bit format.")
  jinvoke DeleteFile, Chr$("CreateFile.opened")
  if 1
mov eax, GENERIC_WRITE
mov ecx, FILE_SHARE_WRITE
mov edx, CREATE_ALWAYS
mov r8, FILE_ATTRIBUTE_NORMAL

jinvoke CreateFile, Chr$("CreateFile.opened"), eax, ecx, NULL, edx, r8, 0
  else
jinvoke CreateFile, Chr$("CreateFile.opened"), GENERIC_WRITE, FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0
  endif
  xchg rax, rbx
  PrintLine Err$()
  jinvoke WriteFile, rbx, Chr$("Hello World, how are you?"), c$Len, addr BytesWritten, 0
  jinvoke CloseHandle, rbx
EndOfCode


Under the hood:
int3                                                      |
mov eax,40000000                                          |
mov ecx,2                                                 |
mov edx,2                                                 |
mov r8,80                                                 |

and qword ptr ss:[rsp+30],0                               |
mov r10,r8                                                |
mov qword ptr ss:[rsp+28],r10                             |
mov r10d,edx                                              |
mov qword ptr ss:[rsp+20],r10                             |
xor r9d,r9d                                               |
mov r8d,ecx                                               |
mov edx,eax                                               |
lea rcx,qword ptr ds:[1400013A9]                          | 1400013A9:"CreateFile.opened"
call qword ptr ds:[<sub_140001740>]                       |


Obviously, left-to-right order would fail when passing arguments in rcx and rdx.

hutch--

Write order is not specific, only argument location.

        mov     dword ptr [rsp+28H], edx
        mov     dword ptr [rsp+30H], eax
        mov     dword ptr [rsp+20H], ecx
        mov     r9d, 4                 
        mov     r8d, 3                 
        mov     edx, 2                 
        mov     ecx, 1                 
        call    foo


Microsoft and GCC do this in reverse, in push-order which is right-to-left.

x64 does not have a PUSH order.

nidud


hutch--

> Of course it has. This is the main part of the convention which determine the position of each argument on the stack for a function call.

You may have a deviant terminology but I suggest that it has more to do with you supporting 32 bit STDCALL than 64 bit FASTCALL. With the x64 ABI you can have left to right, up to down, inside out and upside down or even a random distribution, as long as all the arguments end up in the right sequence, it will work but PUSH order, forget it.

jj2007

Quote from: hutch-- on March 04, 2021, 10:54:06 AMWith the x64 ABI you can have left to right, up to down, inside out and upside down or even a random distribution, as long as all the arguments end up in the right sequence, it will work

See Reply #1. Remember the "register gets overwritten" error?

hutch--

Yes, it was a bad example but I understood what he was aiming at. It seems that many paddling around trying to understand x64 FASTCALL have yet to fully understand how it works.

This is among the reasons why shadow space is required in some situations but the general drift is don't use any of the first four registers in the first four arguments.

The layout of x64 FASTCALL is primarily designed for a 64 bit C compiler but MASM can be configured to properly use x64 FASTCALL.

nidud

deleted

hutch--

The problem as I see it is that you are shifting across a large variety of calling conventions and effectively blurring the distinctions. We all know how 32 bit Windows STDCALL worked, push/call notation and the specs are publically available for Microsoft x64 Windows and there is no leakage across the two.

The Microsoft x64 ABI FASTCALL does not use PUSH at all and the notion of a PUSH order may be convenient when you are dealing with a multitude of different calling conventions but it misrepresents the 64 bit Windows FASTCALL. Now as I am sure that you understand the M$ FASTCALL, the obvious is that when you write the 1st 4 args to registers,

mov rcx, 1
mov rdx, 2
mov r8, 3
mov r9, 4

is the same as

mov r8, 3
mov rcx, 1
mov r9, 4
mov rdx, 2

While the identical data is written to the 1st 4 registers, they are not written in the same order as it simply does not matter, the 4 arguments are written to the correct registers. The idea of PUSH order is incorrect here, its the WRITE order that matters. With the 5th and higher arguments written to the stack after the 4 shadow space positions, they don't have to be written in PUSH order either. For each 64 bit location on the stack, if you write the correct data to each 64 bit location, you can write it in any order you like.

nidud

deleted

hutch--

 :biggrin:
Quote
It actually changes everything and it's done so by design. RCX is the most important register as it is used as a counter (hence the C) and operand for various shift operations. RDX is also a better choice than R8..Rn as it has two byte registers. Compilers will therefor use (spill) RAX, RCX, and RDX for loading arguments from the right and assign value to RCX at the end.
This is MS-DOS level thinking and it is not x64 Windows ABI compliant.

ax = accumulator
bx = base address
cx = counter
dx = data
si = source index
di = destination address
sp = stack pointer
bp = base pointer

With Win64 ABI you are free from this ancient thinking, in the 1st 4 registers in FASTCALL you can put anything you like as long as you don't over write registers with other registers.

Now when you make a call in 64 bit Windows FASTCALL, you have written values to the first 4 registers but in the procedure that you are calling you can directly use them, assign them to locals, copy them to other registers and even save some of the system registers and save them there.

There is no reason why a compiler cannot be fully ABI compliant, slopping data around with unsound assumptions produces junk code, something a compiler should not do.

jj2007

Quote from: hutch-- on March 05, 2021, 10:52:05 PMas long as you don't over write registers with other registers.

Yep, and that's the only reason why the "push order" matters a little bit.

nidud

deleted

TimoVJL

Windows x64 ABI and C/C++ rules, so be calm.

BTW:
Nice feature of programming, when we old people go to pension, our software still runs, even after we die or vanish :smiley:
Hardware workers are in better case, their work are usable a lot of longer.
May the source be with you

HSE

Equations in Assembly: SmplMath