dotnet / runtime

.NET is a cross-platform runtime for cloud, mobile, desktop, and IoT apps.
https://docs.microsoft.com/dotnet/core/
MIT License
14.98k stars 4.66k forks source link

native-managed HResult incompatibility #70099

Closed t-mustafin closed 2 years ago

t-mustafin commented 2 years ago

Runtime related code has different HResult definitions: managed part defines HResult as struct {int Value} while native part defines HResult as int. Generally it is different types and native<->managed conversion should be done explicitly. But in many cases this conversion is processed implicitly relying on expectation of same types representation on specific platform. It could produce bugs on some platforms in some cases.

So happened on Linux x86 while dotnet dump analyze execution. In particular example native SOSInitializeByHost function

extern "C" HRESULT STDMETHODCALLTYPE SOSInitializeByHost(IUnknown* punk, IDebuggerServices* debuggerServices)
{
    IHost* host = nullptr;
    HRESULT hr;

    if (punk != nullptr)
    {
        hr = punk->QueryInterface(__uuidof(IHost), (void**)&host);
        if (FAILED(hr)) {
            return hr;
        }
    }

calls managed QueryInterface method

        private HResult QueryInterfaceImpl(IntPtr _, in Guid guid, out IntPtr ptr)
        {
            if (_interfaces.TryGetValue(guid, out IntPtr value))
            {
                Interlocked.Increment(ref _refCount);
                ptr = value;
                return HResult.S_OK;
            }

            ptr = IntPtr.Zero;
            return HResult.E_NOINTERFACE;
        }

Native SOSInitializeByHost expects QueryInterface would return int:

typedef LONG HRESULT;

while managed ReversePInvoke for QueryInterface returns struct {int Value}:

namespace Microsoft.Diagnostics.Runtime.Utilities
{
    public struct HResult
    {
        public const int S_OK = 0;
        public const int S_FALSE = 1;
        public const int E_FAIL = unchecked((int)0x80004005);
        public const int E_INVALIDARG = unchecked((int)0x80070057);
        public const int E_NOTIMPL = unchecked((int)0x80004001);
        public const int E_NOINTERFACE = unchecked((int)0x80004002);

        public bool IsOK => Value == S_OK;

        public int Value { get; set; }

        public HResult(int hr) => Value = hr;

Linux x86 platform processes structure returning in common way, no matter how large the structure is. So native code expects ret value in eax, while QueryInterface expects additional implicit arg with reference to ret structure provided by caller to fill the structure with values and mov to eax reference to the structure.

Linux x86 asm for SOSInitializeByHost and ReversePInvoke ```asm Dump of assembler code for function SOSInitializeByHost: 0xac0335f0 <+0>: push ebp 0xac0335f1 <+1>: mov ebp,esp 0xac0335f3 <+3>: push ebx 0xac0335f4 <+4>: push edi 0xac0335f5 <+5>: push esi 0xac0335f6 <+6>: and esp,0xfffffff0 0xac0335f9 <+9>: sub esp,0x20 0xac0335fc <+12>: mov eax,DWORD PTR [ebp+0x8] 0xac0335ff <+15>: mov ecx,DWORD PTR gs:0x14 0xac033606 <+22>: call 0xac03360b 0xac03360b <+27>: pop ebx 0xac03360c <+28>: add ebx,0xbf2e5 0xac033612 <+34>: test eax,eax 0xac033614 <+36>: mov DWORD PTR [esp+0x18],ecx 0xac033618 <+40>: mov DWORD PTR [esp+0x14],0x0 0xac033620 <+48>: je 0xac033663 0xac033622 <+50>: mov ecx,DWORD PTR [eax] 0xac033624 <+52>: mov esi,DWORD PTR [ebx+0x6dc] 0xac03362a <+58>: lea edx,[esp+0x14] 0xac03362e <+62>: mov DWORD PTR [esp],eax // implicit this arg 0xac033631 <+65>: mov DWORD PTR [esp+0x8],edx // host arg 0xac033635 <+69>: mov DWORD PTR [esp+0x4],esi // __uuidof(IHost) arg 0xac033639 <+73>: call DWORD PTR [ecx] // ReversePInvoke for QueryInterface call. esp = 0xbf9c7940 0xac03363b <+75>: test eax,eax // Read return value from eax as int. esp = 0xbf9c7944 0xac03363d <+77>: js 0xac033708 0xac033643 <+83>: mov eax,DWORD PTR [esp+0x14] 0xac033647 <+87>: mov edi,DWORD PTR [ebx+0x37c] 0xac03364d <+93>: mov esi,DWORD PTR [edi] 0xac03364f <+95>: test esi,esi 0xac033651 <+97>: je 0xac033671 0xac033653 <+99>: xor edi,edi 0xac033655 <+101>: cmp BYTE PTR [ebx+0x3924],0x0 0xac03365c <+108>: je 0xac0336d2 0xac03365e <+110>: jmp 0xac03370a 0xac033663 <+115>: xor eax,eax 0xac033665 <+117>: mov edi,DWORD PTR [ebx+0x37c] 0xac03366b <+123>: mov esi,DWORD PTR [edi] 0xac03366d <+125>: test esi,esi 0xac03366f <+127>: jne 0xac033653 0xac033671 <+129>: mov DWORD PTR [esp+0x10],eax 0xac033675 <+133>: mov DWORD PTR [esp],0x18 0xac03367c <+140>: call 0xac01a750 <_Znwj@plt> 0xac033681 <+145>: mov esi,eax 0xac033683 <+147>: mov eax,DWORD PTR [ebp+0xc] 0xac033686 <+150>: mov DWORD PTR [esp],esi 0xac033689 <+153>: mov DWORD PTR [esp+0x4],eax 0xac03368d <+157>: call 0xac087d20 0xac033692 <+162>: mov eax,DWORD PTR [ebx+0x594] 0xac033698 <+168>: add eax,0x8 0xac03369b <+171>: mov DWORD PTR [esi],eax 0xac03369d <+173>: mov eax,DWORD PTR [esp+0x10] 0xac0336a1 <+177>: mov DWORD PTR [esi+0x4],eax 0xac0336a4 <+180>: mov DWORD PTR [esp],0x8 0xac0336ab <+187>: call 0xac01a750 <_Znwj@plt> 0xac0336b0 <+192>: mov ecx,DWORD PTR [ebx+0x578] 0xac0336b6 <+198>: mov DWORD PTR [edi],esi 0xac0336b8 <+200>: mov DWORD PTR [eax],ecx 0xac0336ba <+202>: mov ecx,DWORD PTR [ebx+0x5b4] 0xac0336c0 <+208>: mov edx,DWORD PTR [ecx] 0xac0336c2 <+210>: mov DWORD PTR [ecx],eax 0xac0336c4 <+212>: mov DWORD PTR [eax+0x4],edx 0xac0336c7 <+215>: xor edi,edi 0xac0336c9 <+217>: cmp BYTE PTR [ebx+0x3924],0x0 0xac0336d0 <+224>: jne 0xac03370a 0xac0336d2 <+226>: mov DWORD PTR [esp],esi 0xac0336d5 <+229>: call 0xac087f20 0xac0336da <+234>: test eax,eax 0xac0336dc <+236>: je 0xac03370a 0xac0336de <+238>: mov BYTE PTR [ebx+0x3924],0x1 0xac0336e5 <+245>: mov DWORD PTR [esp],0x8 0xac0336ec <+252>: call 0xac01a750 <_Znwj@plt> 0xac0336f1 <+257>: mov esi,DWORD PTR [ebx+0x5b4] 0xac0336f7 <+263>: lea ecx,[ebx-0xbe2c0] 0xac0336fd <+269>: mov DWORD PTR [eax],ecx 0xac0336ff <+271>: mov edx,DWORD PTR [esi] 0xac033701 <+273>: mov DWORD PTR [esi],eax 0xac033703 <+275>: mov DWORD PTR [eax+0x4],edx 0xac033706 <+278>: jmp 0xac03370a 0xac033708 <+280>: mov edi,eax 0xac03370a <+282>: mov eax,gs:0x14 0xac033710 <+288>: cmp eax,DWORD PTR [esp+0x18] 0xac033714 <+292>: jne 0xac033720 0xac033716 <+294>: mov eax,edi 0xac033718 <+296>: lea esp,[ebp-0xc] 0xac03371b <+299>: pop esi 0xac03371c <+300>: pop edi 0xac03371d <+301>: pop ebx 0xac03371e <+302>: pop ebp 0xac03371f <+303>: ret 0xac033720 <+304>: call 0xac01ae30 <__stack_chk_fail@plt> 0xac033725 <+309>: mov edi,eax 0xac033727 <+311>: mov DWORD PTR [esp],esi 0xac03372a <+314>: call 0xac087d80 0xac03372f <+319>: jmp 0xac033733 0xac033731 <+321>: mov edi,eax 0xac033733 <+323>: mov DWORD PTR [esp],esi 0xac033736 <+326>: call 0xac01ad10 <_ZdlPv@plt> 0xac03373b <+331>: mov DWORD PTR [esp],edi 0xac03373e <+334>: call 0xac01b210 <_Unwind_Resume@plt> End of assembler dump. ``` ```asm ; Assembly listing for method ILStubClass:IL_STUB_ReversePInvoke(int,int,int):Microsoft.Diagnostics.Runtime.Utilities.HResult ; Emitting BLENDED_CODE for generic X86 CPU - Unix ; optimized code ; ebp based frame ; fully interruptible ; No PGO data ; Final local variable assignments ; ; V00 RetBuf [V00,T04] ( 2, 2 ) byref -> esi single-def ; V01 arg0 [V01,T11] ( 1, 1 ) int -> [ebp+0CH] single-def ; V02 arg1 [V02,T12] ( 1, 1 ) int -> [ebp+10H] single-def ; V03 arg2 [V03,T13] ( 1, 1 ) int -> [ebp+14H] single-def ;* V04 loc0 [V04,T07] ( 0, 0 ) int -> zero-ref do-not-enreg[Z] EH-live ;* V05 loc1 [V05 ] ( 0, 0 ) int -> zero-ref ; V06 loc2 [V06,T05] ( 2, 2 ) byref -> edi single-def ; V07 loc3 [V07,T06] ( 2, 2 ) byref -> ebx single-def ;* V08 loc4 [V08 ] ( 0, 0 ) struct ( 4) zero-ref ;* V09 loc5 [V09 ] ( 0, 0 ) struct ( 4) zero-ref ld-addr-op ;* V10 loc6 [V10 ] ( 0, 0 ) struct ( 4) zero-ref ; V11 loc7 [V11 ] ( 3, 3 ) int -> [ebp-24H] do-not-enreg[X] addr-exposed "stub argument" ; V12 tmp1 [V12,T02] ( 2, 4 ) int -> eax "impImportIndirectCall" ; V13 tmp2 [V13,T00] ( 2, 4 ) ref -> edx class-hnd single-def "impAppendStmt" ; V14 tmp3 [V14,T01] ( 2, 4 ) byref -> esi single-def "Single return block return value" ; V15 tmp4 [V15 ] ( 3, 3 ) blk (20) [ebp-20H] do-not-enreg[XB] addr-exposed "Reverse Pinvoke FrameVar" ; V16 tmp5 [V16,T08] ( 2, 2 ) int -> eax V08.k__BackingField(offs=0x00) P-INDEP "field V08.k__BackingField (fldOffset=0x0)" ; V17 tmp6 [V17,T09] ( 2, 2 ) int -> eax V09.k__BackingField(offs=0x00) P-INDEP "field V09.k__BackingField (fldOffset=0x0)" ; V18 tmp7 [V18,T10] ( 2, 2 ) int -> eax V10.k__BackingField(offs=0x00) P-INDEP "field V10.k__BackingField (fldOffset=0x0)" ; V19 cse0 [V19,T03] ( 3, 3 ) ref -> ecx "CSE - aggressive" ; ; Lcl frame size = 28 G_M40989_IG01: ;; offset=0000H 55 push ebp 8BEC mov ebp, esp 57 push edi 56 push esi 53 push ebx 83EC1C sub esp, 28 8945DC mov dword ptr [ebp-24H], eax 8B7508 mov esi, bword ptr [ebp+08H] // Read first implicit arg, reference for ret structure expected, but it is `this` ptr ;; bbWeight=1 PerfScore 6.50 G_M40989_IG02: ;; offset=000FH 8B4DDC mov ecx, dword ptr [ebp-24H] 83EC0C sub esp, 12 51 push ecx 8D4DE0 lea ecx, [ebp-20H] BAFCDDAEAA mov edx, 0xAAAEDDFC E8F524160B call CORINFO_HELP_JIT_REVERSE_PINVOKE_ENTER_TRACK_TRANSITIONS 83C410 add esp, 16 ;; bbWeight=1 PerfScore 4.25 G_M40989_IG03: ;; offset=0026H 8B7D10 mov edi, bword ptr [ebp+10H] // Read third arg, guid expected but it is &host reference 8B5D14 mov ebx, bword ptr [ebp+14H] // Read fourth arg, &host expected but it is some value from previous stack frame 8B4DDC mov ecx, dword ptr [ebp-24H] 8B4908 mov ecx, dword ptr [ecx+8] 8B09 mov ecx, gword ptr [ecx] 8B5104 mov edx, gword ptr [ecx+4] 8B410C mov eax, dword ptr [ecx+12] 83EC08 sub esp, 8 57 push edi 53 push ebx 8BCA mov ecx, edx 8B550C mov edx, dword ptr [ebp+0CH] // Read second arg, `this` ptr _ expected but it is __uuidof(IHost) FFD0 call eax 83C410 add esp, 16 ;; bbWeight=1 PerfScore 17.75 G_M40989_IG04: ;; offset=0049H 8906 mov dword ptr [esi], eax // Store return value into structure provided by caller as reference first implicit arg 8D4DE0 lea ecx, [ebp-20H] E8052C160B call CORINFO_HELP_JIT_REVERSE_PINVOKE_EXIT_TRACK_TRANSITIONS 8BC6 mov eax, esi // Place to eax reference to ret struct provided by caller ;; bbWeight=1 PerfScore 2.75 G_M40989_IG05: ;; offset=0055H 8D65F4 lea esp, [ebp-0CH] 5B pop ebx 5E pop esi 5F pop edi 5D pop ebp C20400 ret 4 // Finally break down stack alignment. Caller would not compensate it. ;; bbWeight=1 PerfScore 4.50 G_M40989_IG06: ;; offset=005FH 83EC0C sub esp, 12 ;; bbWeight=0 PerfScore 0.00 G_M40989_IG07: ;; offset=0062H 83C40C add esp, 12 C3 ret ;; bbWeight=0 PerfScore 0.00 ; Total bytes of code 102, prolog size 15, PerfScore 45.95, instruction count 42, allocated bytes for code 102 (MethodHash=b7fe5fe2) for method ILStubClass:IL_STUB_ReversePInvoke(int,int,int):Microsoft.Diagnostics.Runtime.Utilities.HResult ; ============================================================ ```

On other architectures such little structures are returned via standard return value registers (eax, r0). This non-standard platform specific feature allows implicitly convert managed HResult struct{int} to native HResult int anywhere except Linux x86. Feature demonstration on godbolt.

I can propose fix for this particular case by https://github.com/t-mustafin/clrmd/commit/98016eb854860b3b4910d720a7dbe243d28eb5ba, but it changes clrmd Public API. Maybe exists another better solution?

cc @jkotas @jakobbotsch @BruceForstall @alpencolt

ghost commented 2 years ago

Tagging subscribers to this area: @JulieLeeMSFT See info in area-owners.md if you want to be subscribed.

Issue Details
Runtime related code has different HResult definitions: managed part defines HResult as `struct {int Value}` while native part defines HResult as `int`. Generally it is different types and native<->managed conversion should be done explicitly. But in many cases this conversion is processed implicitly relying on expectation of same types representation on specific platform. It could produce bugs on some platforms in some cases. So happened on Linux x86 while `dotnet dump analyze` execution. In particular example native [SOSInitializeByHost](https://github.com/dotnet/diagnostics/blob/a74196805d9aea2d1ee54a2952b3c11dfee008b2/src/SOS/Strike/symbols.cpp#L52) function ```C++ extern "C" HRESULT STDMETHODCALLTYPE SOSInitializeByHost(IUnknown* punk, IDebuggerServices* debuggerServices) { IHost* host = nullptr; HRESULT hr; if (punk != nullptr) { hr = punk->QueryInterface(__uuidof(IHost), (void**)&host); if (FAILED(hr)) { return hr; } } ``` calls managed [QueryInterface](https://github.com/microsoft/clrmd/blob/be891ed2cc2c8b98d9e0b531b513b75a4d4bfd88/src/Microsoft.Diagnostics.Runtime/src/Utilities/COMInterop/ComCallableIUnknown.cs#L97) method ```C# private HResult QueryInterfaceImpl(IntPtr _, in Guid guid, out IntPtr ptr) { if (_interfaces.TryGetValue(guid, out IntPtr value)) { Interlocked.Increment(ref _refCount); ptr = value; return HResult.S_OK; } ptr = IntPtr.Zero; return HResult.E_NOINTERFACE; } ``` Native SOSInitializeByHost expects QueryInterface would [return `int`](https://github.com/dotnet/diagnostics/blob/a74196805d9aea2d1ee54a2952b3c11dfee008b2/src/shared/pal/inc/pal_mstypes.h#L662): ```C++ typedef LONG HRESULT; ``` while managed ReversePInvoke for QueryInterface [returns `struct {int Value}`](https://github.com/microsoft/clrmd/blob/554f7c1e3c93736b6d24694b7f253f260692b4f3/src/Microsoft.Diagnostics.Runtime/src/Utilities/COMInterop/HResult.cs#L18): ```C# namespace Microsoft.Diagnostics.Runtime.Utilities { public struct HResult { public const int S_OK = 0; public const int S_FALSE = 1; public const int E_FAIL = unchecked((int)0x80004005); public const int E_INVALIDARG = unchecked((int)0x80070057); public const int E_NOTIMPL = unchecked((int)0x80004001); public const int E_NOINTERFACE = unchecked((int)0x80004002); public bool IsOK => Value == S_OK; public int Value { get; set; } public HResult(int hr) => Value = hr; ``` Linux x86 platform processes structure returning in common way, no matter how large the structure is. So native code expects ret value in eax, while QueryInterface expects additional implicit arg with reference to ret structure provided by caller to fill the structure with values and mov to eax reference to the structure.
Linux x86 asm for SOSInitializeByHost and ReversePInvoke ```asm Dump of assembler code for function SOSInitializeByHost: 0xac0335f0 <+0>: push ebp 0xac0335f1 <+1>: mov ebp,esp 0xac0335f3 <+3>: push ebx 0xac0335f4 <+4>: push edi 0xac0335f5 <+5>: push esi 0xac0335f6 <+6>: and esp,0xfffffff0 0xac0335f9 <+9>: sub esp,0x20 0xac0335fc <+12>: mov eax,DWORD PTR [ebp+0x8] 0xac0335ff <+15>: mov ecx,DWORD PTR gs:0x14 0xac033606 <+22>: call 0xac03360b 0xac03360b <+27>: pop ebx 0xac03360c <+28>: add ebx,0xbf2e5 0xac033612 <+34>: test eax,eax 0xac033614 <+36>: mov DWORD PTR [esp+0x18],ecx 0xac033618 <+40>: mov DWORD PTR [esp+0x14],0x0 0xac033620 <+48>: je 0xac033663 0xac033622 <+50>: mov ecx,DWORD PTR [eax] 0xac033624 <+52>: mov esi,DWORD PTR [ebx+0x6dc] 0xac03362a <+58>: lea edx,[esp+0x14] 0xac03362e <+62>: mov DWORD PTR [esp],eax // implicit this arg 0xac033631 <+65>: mov DWORD PTR [esp+0x8],edx // host arg 0xac033635 <+69>: mov DWORD PTR [esp+0x4],esi // __uuidof(IHost) arg 0xac033639 <+73>: call DWORD PTR [ecx] // ReversePInvoke for QueryInterface call. esp = 0xbf9c7940 0xac03363b <+75>: test eax,eax // Read return value from eax as int. esp = 0xbf9c7944 0xac03363d <+77>: js 0xac033708 0xac033643 <+83>: mov eax,DWORD PTR [esp+0x14] 0xac033647 <+87>: mov edi,DWORD PTR [ebx+0x37c] 0xac03364d <+93>: mov esi,DWORD PTR [edi] 0xac03364f <+95>: test esi,esi 0xac033651 <+97>: je 0xac033671 0xac033653 <+99>: xor edi,edi 0xac033655 <+101>: cmp BYTE PTR [ebx+0x3924],0x0 0xac03365c <+108>: je 0xac0336d2 0xac03365e <+110>: jmp 0xac03370a 0xac033663 <+115>: xor eax,eax 0xac033665 <+117>: mov edi,DWORD PTR [ebx+0x37c] 0xac03366b <+123>: mov esi,DWORD PTR [edi] 0xac03366d <+125>: test esi,esi 0xac03366f <+127>: jne 0xac033653 0xac033671 <+129>: mov DWORD PTR [esp+0x10],eax 0xac033675 <+133>: mov DWORD PTR [esp],0x18 0xac03367c <+140>: call 0xac01a750 <_Znwj@plt> 0xac033681 <+145>: mov esi,eax 0xac033683 <+147>: mov eax,DWORD PTR [ebp+0xc] 0xac033686 <+150>: mov DWORD PTR [esp],esi 0xac033689 <+153>: mov DWORD PTR [esp+0x4],eax 0xac03368d <+157>: call 0xac087d20 0xac033692 <+162>: mov eax,DWORD PTR [ebx+0x594] 0xac033698 <+168>: add eax,0x8 0xac03369b <+171>: mov DWORD PTR [esi],eax 0xac03369d <+173>: mov eax,DWORD PTR [esp+0x10] 0xac0336a1 <+177>: mov DWORD PTR [esi+0x4],eax 0xac0336a4 <+180>: mov DWORD PTR [esp],0x8 0xac0336ab <+187>: call 0xac01a750 <_Znwj@plt> 0xac0336b0 <+192>: mov ecx,DWORD PTR [ebx+0x578] 0xac0336b6 <+198>: mov DWORD PTR [edi],esi 0xac0336b8 <+200>: mov DWORD PTR [eax],ecx 0xac0336ba <+202>: mov ecx,DWORD PTR [ebx+0x5b4] 0xac0336c0 <+208>: mov edx,DWORD PTR [ecx] 0xac0336c2 <+210>: mov DWORD PTR [ecx],eax 0xac0336c4 <+212>: mov DWORD PTR [eax+0x4],edx 0xac0336c7 <+215>: xor edi,edi 0xac0336c9 <+217>: cmp BYTE PTR [ebx+0x3924],0x0 0xac0336d0 <+224>: jne 0xac03370a 0xac0336d2 <+226>: mov DWORD PTR [esp],esi 0xac0336d5 <+229>: call 0xac087f20 0xac0336da <+234>: test eax,eax 0xac0336dc <+236>: je 0xac03370a 0xac0336de <+238>: mov BYTE PTR [ebx+0x3924],0x1 0xac0336e5 <+245>: mov DWORD PTR [esp],0x8 0xac0336ec <+252>: call 0xac01a750 <_Znwj@plt> 0xac0336f1 <+257>: mov esi,DWORD PTR [ebx+0x5b4] 0xac0336f7 <+263>: lea ecx,[ebx-0xbe2c0] 0xac0336fd <+269>: mov DWORD PTR [eax],ecx 0xac0336ff <+271>: mov edx,DWORD PTR [esi] 0xac033701 <+273>: mov DWORD PTR [esi],eax 0xac033703 <+275>: mov DWORD PTR [eax+0x4],edx 0xac033706 <+278>: jmp 0xac03370a 0xac033708 <+280>: mov edi,eax 0xac03370a <+282>: mov eax,gs:0x14 0xac033710 <+288>: cmp eax,DWORD PTR [esp+0x18] 0xac033714 <+292>: jne 0xac033720 0xac033716 <+294>: mov eax,edi 0xac033718 <+296>: lea esp,[ebp-0xc] 0xac03371b <+299>: pop esi 0xac03371c <+300>: pop edi 0xac03371d <+301>: pop ebx 0xac03371e <+302>: pop ebp 0xac03371f <+303>: ret 0xac033720 <+304>: call 0xac01ae30 <__stack_chk_fail@plt> 0xac033725 <+309>: mov edi,eax 0xac033727 <+311>: mov DWORD PTR [esp],esi 0xac03372a <+314>: call 0xac087d80 0xac03372f <+319>: jmp 0xac033733 0xac033731 <+321>: mov edi,eax 0xac033733 <+323>: mov DWORD PTR [esp],esi 0xac033736 <+326>: call 0xac01ad10 <_ZdlPv@plt> 0xac03373b <+331>: mov DWORD PTR [esp],edi 0xac03373e <+334>: call 0xac01b210 <_Unwind_Resume@plt> End of assembler dump. ``` ```asm ; Assembly listing for method ILStubClass:IL_STUB_ReversePInvoke(int,int,int):Microsoft.Diagnostics.Runtime.Utilities.HResult ; Emitting BLENDED_CODE for generic X86 CPU - Unix ; optimized code ; ebp based frame ; fully interruptible ; No PGO data ; Final local variable assignments ; ; V00 RetBuf [V00,T04] ( 2, 2 ) byref -> esi single-def ; V01 arg0 [V01,T11] ( 1, 1 ) int -> [ebp+0CH] single-def ; V02 arg1 [V02,T12] ( 1, 1 ) int -> [ebp+10H] single-def ; V03 arg2 [V03,T13] ( 1, 1 ) int -> [ebp+14H] single-def ;* V04 loc0 [V04,T07] ( 0, 0 ) int -> zero-ref do-not-enreg[Z] EH-live ;* V05 loc1 [V05 ] ( 0, 0 ) int -> zero-ref ; V06 loc2 [V06,T05] ( 2, 2 ) byref -> edi single-def ; V07 loc3 [V07,T06] ( 2, 2 ) byref -> ebx single-def ;* V08 loc4 [V08 ] ( 0, 0 ) struct ( 4) zero-ref ;* V09 loc5 [V09 ] ( 0, 0 ) struct ( 4) zero-ref ld-addr-op ;* V10 loc6 [V10 ] ( 0, 0 ) struct ( 4) zero-ref ; V11 loc7 [V11 ] ( 3, 3 ) int -> [ebp-24H] do-not-enreg[X] addr-exposed "stub argument" ; V12 tmp1 [V12,T02] ( 2, 4 ) int -> eax "impImportIndirectCall" ; V13 tmp2 [V13,T00] ( 2, 4 ) ref -> edx class-hnd single-def "impAppendStmt" ; V14 tmp3 [V14,T01] ( 2, 4 ) byref -> esi single-def "Single return block return value" ; V15 tmp4 [V15 ] ( 3, 3 ) blk (20) [ebp-20H] do-not-enreg[XB] addr-exposed "Reverse Pinvoke FrameVar" ; V16 tmp5 [V16,T08] ( 2, 2 ) int -> eax V08.k__BackingField(offs=0x00) P-INDEP "field V08.k__BackingField (fldOffset=0x0)" ; V17 tmp6 [V17,T09] ( 2, 2 ) int -> eax V09.k__BackingField(offs=0x00) P-INDEP "field V09.k__BackingField (fldOffset=0x0)" ; V18 tmp7 [V18,T10] ( 2, 2 ) int -> eax V10.k__BackingField(offs=0x00) P-INDEP "field V10.k__BackingField (fldOffset=0x0)" ; V19 cse0 [V19,T03] ( 3, 3 ) ref -> ecx "CSE - aggressive" ; ; Lcl frame size = 28 G_M40989_IG01: ;; offset=0000H 55 push ebp 8BEC mov ebp, esp 57 push edi 56 push esi 53 push ebx 83EC1C sub esp, 28 8945DC mov dword ptr [ebp-24H], eax 8B7508 mov esi, bword ptr [ebp+08H] // Read first implicit arg, reference for ret structure expected, but it is `this` ptr ;; bbWeight=1 PerfScore 6.50 G_M40989_IG02: ;; offset=000FH 8B4DDC mov ecx, dword ptr [ebp-24H] 83EC0C sub esp, 12 51 push ecx 8D4DE0 lea ecx, [ebp-20H] BAFCDDAEAA mov edx, 0xAAAEDDFC E8F524160B call CORINFO_HELP_JIT_REVERSE_PINVOKE_ENTER_TRACK_TRANSITIONS 83C410 add esp, 16 ;; bbWeight=1 PerfScore 4.25 G_M40989_IG03: ;; offset=0026H 8B7D10 mov edi, bword ptr [ebp+10H] // Read third arg, guid expected but it is &host reference 8B5D14 mov ebx, bword ptr [ebp+14H] // Read fourth arg, &host expected but it is some value from previous stack frame 8B4DDC mov ecx, dword ptr [ebp-24H] 8B4908 mov ecx, dword ptr [ecx+8] 8B09 mov ecx, gword ptr [ecx] 8B5104 mov edx, gword ptr [ecx+4] 8B410C mov eax, dword ptr [ecx+12] 83EC08 sub esp, 8 57 push edi 53 push ebx 8BCA mov ecx, edx 8B550C mov edx, dword ptr [ebp+0CH] // Read second arg, `this` ptr _ expected but it is __uuidof(IHost) FFD0 call eax 83C410 add esp, 16 ;; bbWeight=1 PerfScore 17.75 G_M40989_IG04: ;; offset=0049H 8906 mov dword ptr [esi], eax // Store return value into structure provided by caller as reference first implicit arg 8D4DE0 lea ecx, [ebp-20H] E8052C160B call CORINFO_HELP_JIT_REVERSE_PINVOKE_EXIT_TRACK_TRANSITIONS 8BC6 mov eax, esi // Place to eax reference to ret struct provided by caller ;; bbWeight=1 PerfScore 2.75 G_M40989_IG05: ;; offset=0055H 8D65F4 lea esp, [ebp-0CH] 5B pop ebx 5E pop esi 5F pop edi 5D pop ebp C20400 ret 4 // Finally break down stack alignment. Caller would not compensate it. ;; bbWeight=1 PerfScore 4.50 G_M40989_IG06: ;; offset=005FH 83EC0C sub esp, 12 ;; bbWeight=0 PerfScore 0.00 G_M40989_IG07: ;; offset=0062H 83C40C add esp, 12 C3 ret ;; bbWeight=0 PerfScore 0.00 ; Total bytes of code 102, prolog size 15, PerfScore 45.95, instruction count 42, allocated bytes for code 102 (MethodHash=b7fe5fe2) for method ILStubClass:IL_STUB_ReversePInvoke(int,int,int):Microsoft.Diagnostics.Runtime.Utilities.HResult ; ============================================================ ```
On other architectures such little structures are returned via standard return value registers (`eax`, `r0`). This non-standard platform specific feature allows implicitly convert managed HResult `struct{int}` to native HResult `int` anywhere except Linux x86. [Feature demonstration on godbolt](https://godbolt.org/z/fWfd4jYGd). I can propose fix for this particular case by https://github.com/t-mustafin/clrmd/commit/98016eb854860b3b4910d720a7dbe243d28eb5ba, but it changes clrmd Public API. Maybe exists another better solution? cc @jkotas @jakobbotsch @BruceForstall @alpencolt
Author: t-mustafin
Assignees: -
Labels: `area-CodeGen-coreclr`, `untriaged`
Milestone: -
jkotas commented 2 years ago

This is bug in the clrmd interop signature definition. There is not much runtime can do about it. It needs to be fixed in clrmd.

AaronRobinsonMSFT commented 2 years ago

@t-mustafin This is annoying but alas a well-known issue with the clever HRESULT wrapped in a struct pattern that is all too common. It works as an artifact of bad specifications and being clever. Unfortunately there is little that can be done from the runtime side as @jkotas mentions.

@leculver might be able to help here from the clrmd side of things.

leculver commented 2 years ago

Feel free to close this issue and open a pull request in ClrMD. It's no big deal to submit a breaking change to ClrMD, I just released 2.1 for example. If there's reason for a breaking change we can just release 2.2.

Do we even support x86 Linux?

While using an HResult struct might be overly clever, it did eliminate a ton of coding issues in ClrMD. I'm not sure I'm super eager to replace it back with an int, but I'm happy to take a closer look if it's blocking.

AaronRobinsonMSFT commented 2 years ago

@leculver Another approach that does help with the typing issues that people like to mitigate is to use an enum instead. This permits typing but alas you need to create standalone functions instead of adding them to the type.

Do we even support x86 Linux?

I honestly don't think we do, but there are some vendors that rely upon it I believe. @jkotas or @richlander would recall the specific details.

jkotas commented 2 years ago

Do we even support x86 Linux?

Linux x86 is supported by Samsung for Tizen OS.

MichalStrehovsky commented 2 years ago

@leculver Another approach that does help with the typing issues that people like to mitigate is to use an enum instead. This permits typing but alas you need to create standalone functions instead of adding them to the type.

Extension methods work fine with enum for this scenario. Unfortunately there's no extension properties, so one would have to convert the properties to methods, but that would be the extent of a source breaking change to make it an enum.