Author Topic: Passing a structure  (Read 5069 times)

Biterider

  • Member
  • ****
  • Posts: 542
  • ObjAsm Developer
    • ObjAsm
Passing a structure
« on: March 22, 2019, 05:36:44 AM »
Hi
Today I found a difference in the behavior of UASM in 32 bit and 64 bit. Using the invoke directive and a structure as argument, in 32 bit (stdcall) all members of the structure are passed in sequence. Using exactly the same syntax, but in 64 bit (fastcall), only a pointer to the structure is passed as argument. I checked all settings I know and I can’t find the reason why UASM acts that way. Maybe has it something to do with the calling convention, but I have not found any clues on this particular topic.

Maybe the UASM team can shed some light on this matter.  :biggrin:

Code: [Select]
.code
TestProc proc Arg1:RECT
  ret
TestProc endp


start proc
  local Rct:RECT

  invoke TestProc, Rct
  invoke ExitProcess, 0
start endp


In 32 bit mode (stdcall)
Code: [Select]
  invoke TestProc, Rct
01361059 FF 75 FC             push        dword ptr [ebp-4]
0136105C FF 75 F8             push        dword ptr [ebp-8]
0136105F FF 75 F4             push        dword ptr [ebp-0Ch]
01361062 FF 75 F0             push        dword ptr [Rct]
01361065 E8 96 FF FF FF       call        TestProc (01361000h)

In 64 bit mode (fastcall)
Code: [Select]
  invoke TestProc, Rct
000000013F3A1075 48 8D 4D F0          lea         rcx,[Rct]
000000013F3A1079 E8 82 FF FF FF       call        TestProc (013F3A1000h)

Biterider

habran

  • Member
  • *****
  • Posts: 1225
    • uasm
Re: Passing a structure
« Reply #1 on: March 22, 2019, 03:58:38 PM »
Hi Biterider :biggrin:
IMHO it actually behaves the same, it sends  a reference.
The difference is in allocating the structure. Here is one example how 64 bit allocates the local data:
Code: [Select]
WinMain proc FRAME hInst : HINSTANCE, hPrevInst : HINSTANCE, CmdLine : LPSTR, CmdShow : UINT

local wc : WNDCLASSEXA
local msg : MSG
local hwnd : HWND
local Rct : RECT
push  rbp
.pushreg rbp
mov   rbp, rsp
.setframe rbp, 0
sub   rsp, sizeof WNDCLASSEXA + sizeof MSG + sizeof HWND + 13 * 8; make sure rsp is 16 - byte aligned
.allocstack sizeof WNDCLASSEXA + sizeof MSG + sizeof RECT + sizeof HWND + 13 * 8
.endprolog
.....
.....
Cod-Father

AW

  • Member
  • *****
  • Posts: 2583
  • Let's Make ASM Great Again!
Re: Passing a structure
« Reply #2 on: March 22, 2019, 04:27:30 PM »
STDCALL always pushes everything unless you expressly tell to pass by reference - the default is to pass by value. Arrays are an exception but in ASM there is no direct concept of array. In 64-bit Windows ABI (why call it fastcall?)  everything over 8 bytes is passed by reference.

Biterider

  • Member
  • ****
  • Posts: 542
  • ObjAsm Developer
    • ObjAsm
Re: Passing a structure
« Reply #3 on: March 23, 2019, 02:12:13 AM »
Hi
Thanks for the answers. I have found the corresponding passage in the Microsoft x64 ABI.
https://docs.microsoft.com/en-us/cpp/build/x64-calling-convention?view=vs-2017
"Any argument that does not fit in 8 bytes, or is not 1, 2, 4, or 8 bytes, must be passed by reference."

This is interesting. I tested what happens with a 3 byte structure

Code: [Select]
MyStruct struct
  a1  BYTE  ?
  a2  BYTE  ?
  a3  BYTE  ?
MyStruct ends

.code
TestProc proc Arg1:MyStruct
  ret
TestProc endp

start proc
  local Argument:MyStruct

  invoke TestProc, Argument
  invoke ExitProcess, 0
start endp

  invoke TestProc, Argument
000000013F9E1015 48 8B 4D FD          mov         rcx,qword ptr [Argument]
 

This does not seem to fulfill the ABI. OTOH, following the link "Parameter passing" from the previous paper
https://docs.microsoft.com/en-us/cpp/build/x64-calling-convention?view=vs-2017#parameter-passing
it says that:
"All arguments are right-justified in registers, so the callee can ignore the upper bits of the register and access only the portion of the register necessary."

That can also be applied to the 3 byte structure, but this is not what the calling convention specifies.
BTW, the same happens to a 5, 6, 7 byte structure.

Thanks, Biterider

AW

  • Member
  • *****
  • Posts: 2583
  • Let's Make ASM Great Again!
Re: Passing a structure
« Reply #4 on: March 23, 2019, 03:06:01 AM »
rcx will be filled with 5 random bytes that don't belong to the structure. It is probably a bug.

johnsa

  • Member
  • ****
  • Posts: 807
    • Uasm
Re: Passing a structure
« Reply #5 on: March 25, 2019, 08:36:55 PM »
That definitely seems like a bug to me. Will add it to the log for 2.49

Biterider

  • Member
  • ****
  • Posts: 542
  • ObjAsm Developer
    • ObjAsm
Re: Passing a structure
« Reply #6 on: March 25, 2019, 08:39:18 PM »
Thank you johnsa!


Biterider

johnsa

  • Member
  • ****
  • Posts: 807
    • Uasm
Re: Passing a structure
« Reply #7 on: March 26, 2019, 08:05:47 PM »
I think the best option, rather than anything automated would be to perform a check

when you use invoke myProc, myStruct

if myStruct is not 1/2/4/8 an error will be emitted ("Structure parameter not 1/2/4/8 bytes, please pass by reference") so that it's clear what the situation is and force the programmer to then use ADDR.

Sound ok ?

Biterider

  • Member
  • ****
  • Posts: 542
  • ObjAsm Developer
    • ObjAsm
Re: Passing a structure
« Reply #8 on: March 26, 2019, 08:51:45 PM »
Hi
It's OK for me.  :t
Please add to the error conditions sizes bigger than 8 bytes.

Thanks!!

Biterider

nidud

  • Member
  • *****
  • Posts: 1980
    • https://github.com/nidud/asmc
Re: Passing a structure
« Reply #9 on: March 26, 2019, 10:23:52 PM »
For the RECT struct:

msvc x86 will pass rc as a reference in eax push rc
msvc x64 will pass rc as a reference in rcx
masm x86 will push rc
masm x64 will return error (no invoke)
uasm x86 will push rc
uasm x64 will pass rc as a reference in rcx

For the 3 byte struct:

msvc x86 will push ms (8 bytes)
msvc x64 will pass ms as a reference in rcx
masm x86 will push ms - push ax + push word ptr ms
masm x64 will return error (no invoke)
uasm x86 will return error A2070: invalid instruction operands - push ms
uasm x64 will pass ms in rcx - mov rcx, qword ptr [rbp-5H]
asmc x86 will return error A2070: invalid instruction operands - push ms
asmc x64 will pass ms in cx - mov cx, word ptr [rbp-5H]

EDIT:
The x64 error (asmc) in fastcall.c: (using ecx)

   switch ( psize ) {
   case 1: base =   0*4; break;
   case 2: base =   1*4; break;
   case 3:
   case 4: base =   2*4; break;
   default:
       base = 3*4; break;
   }


EDIT2:
The 3 byte thing seems to be a special case.

  local ms:MS3

    mov al,ms._0
    mov al,ms._1
    mov al,ms._2
    invoke foo, ms

So masm produce this code:

        add     esp, -4
        mov     al, byte ptr [ebp-4H]
        mov     al, byte ptr [ebp-3H]
        mov     al, byte ptr [ebp-2H]
        mov     al, byte ptr [ebp-2H]
        push    ax                           
        push    word ptr [ebp-2H] <-- [ebp-4] ?
        call    _foo                         

Tempting to just push dword ptr ms here but given it could be the last 3 bytes within a page.

The safe way:

    mov al,byte ptr ms[2]
    shl eax,16
    mov ax,word ptr ms
    push eax

And for 16-bit

    mov al,byte ptr ms[2]
    push ax
    push word ptr ms

« Last Edit: March 27, 2019, 03:25:18 AM by nidud »

AW

  • Member
  • *****
  • Posts: 2583
  • Let's Make ASM Great Again!
Re: Passing a structure
« Reply #10 on: March 27, 2019, 12:23:37 AM »
For the RECT struct:

msvc x86 will pass rc as a reference in eax
msvc x64 will pass rc as a reference in rcx
masm x86 will push rc
masm x64 will return error (no invoke)
uasm x86 will push rc
uasm x64 will pass rc as a reference in rcx

For the 3 byte struct:

msvc x86 will push ms (8 bytes)
msvc x64 will pass ms as a reference in rcx
masm x86 will push ms - push ax + push word ptr ms
masm x64 will return error (no invoke)
usmc x86 will return error A2070: invalid instruction operands - push ms
uasm x64 will pass ms in rcx - mov rcx, qword ptr [rbp-5H]
asmc x64 will pass ms in cx - mov cx, word ptr [rbp-5H]
I may be wrong but I don't think so on this:
msvc x86 will push ms (8 bytes):
   mov   BYTE PTR _somethree$[ebp], 11      ; 0000000bH
   mov   BYTE PTR _somethree$[ebp+1], 22      ; 00000016H
   mov   BYTE PTR _somethree$[ebp+2], 33      ; 00000021H

   mov   eax, esp
   mov   cx, WORD PTR _somethree$[ebp]
   mov   WORD PTR [eax], cx
   mov   dl, BYTE PTR _somethree$[ebp+2]
   mov   BYTE PTR [eax+2], dl
   call   _threeByteFunction@4



nidud

  • Member
  • *****
  • Posts: 1980
    • https://github.com/nidud/asmc
Re: Passing a structure
« Reply #11 on: March 27, 2019, 01:15:35 AM »
Quote
msvc x86 will pass rc as a reference in eax

It does actually push the arguments there...
Note that the default calling convention for VC x86 is C, not STDCALL. The result will be the same thought.

https://gcc.godbolt.org:


#include <windows.h>

void foo(RECT);

void bar()
{
    RECT rc;

    foo(rc);
}

void bar(void) PROC
        sub     esp, 16
        movups  xmm0, XMMWORD PTR _rc$[esp+16]
        sub     esp, 16
        mov     eax, esp
        movups  XMMWORD PTR [eax], xmm0
        call    void foo(tagRECT)
        add     esp, 32         
        ret     0
void bar(void) ENDP             


The 3 byte struct:

#include <windows.h>

typedef struct {
    char a;
    char b;
    char c;
} S1;

void foo(S1);

void bar()
{
    S1 rc;

    foo(rc);
}

void bar(void) PROC
        push    ecx
        mov     ax, WORD PTR _rc$[esp+4]
        push    ecx
        mov     ecx, esp
        mov     WORD PTR [ecx], ax
        mov     al, BYTE PTR _rc$[esp+10]
        mov     BYTE PTR [ecx+2], al
        call    void foo(S1)
        add     esp, 8
        ret     0
void bar(void) ENDP

AW

  • Member
  • *****
  • Posts: 2583
  • Let's Make ASM Great Again!
Re: Passing a structure
« Reply #12 on: March 27, 2019, 01:25:11 AM »
Quote
msvc x86 will pass rc as a reference in eax
It does actually push the arguments there...
Cool, we are talking about stdcall.

Quote
Note that the default calling convention for VC x86 is C, not STDCALL.
We are talking about stdcall since the beginning.

Quote
The result will be the same thought.
:biggrin:

nidud

  • Member
  • *****
  • Posts: 1980
    • https://github.com/nidud/asmc
Re: Passing a structure
« Reply #13 on: March 27, 2019, 03:50:21 AM »
Seems to be a MASM bug there in the 3 byte thing but I added this to invoke.c:

        default:
            if ( asize == 3 ) {
                if ( pushsize == 4 ) {
                    AddLineQueueX( " push dword ptr %s", fullparam );
                } else {
                    AddLineQueueX( " push word ptr %s[2]", fullparam );
                    AddLineQueueX( " push word ptr %s", fullparam );
                }
            } else
                AddLineQueueX( " push %s", fullparam );

AW

  • Member
  • *****
  • Posts: 2583
  • Let's Make ASM Great Again!
Re: Passing a structure
« Reply #14 on: March 28, 2019, 07:05:10 AM »
I have checked MASM and MSVC for the 32-bit case and 3 byte strutures.
There is indeed a bug in MASM when using invoke with the 3 byte structure.
On the other hand MSVC handles well the situation, it does not makes 2 pushes totaling 8 bytes, it reserves 4 bytes in the stack through "sub esp" and puts the structure there by moving 1 byte more 1 word. The receiving end knows how to pick it correctly.