Skip to content

native-managed HResult incompatibility #70099

@t-mustafin

Description

@t-mustafin

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
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 <SOSInitializeByHost+27>
   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 <SOSInitializeByHost+115>
   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 <SOSInitializeByHost+280>
   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 <SOSInitializeByHost+129>
   0xac033653 <+99>:    xor    edi,edi
   0xac033655 <+101>:   cmp    BYTE PTR [ebx+0x3924],0x0
   0xac03365c <+108>:   je     0xac0336d2 <SOSInitializeByHost+226>
   0xac03365e <+110>:   jmp    0xac03370a <SOSInitializeByHost+282>
   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 <SOSInitializeByHost+99>
   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 <SOSInitializeByHost+282>
   0xac0336d2 <+226>:   mov    DWORD PTR [esp],esi
   0xac0336d5 <+229>:   call   0xac087f20
   0xac0336da <+234>:   test   eax,eax
   0xac0336dc <+236>:   je     0xac03370a <SOSInitializeByHost+282>
   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 <SOSInitializeByHost+282>
   0xac033708 <+280>:   mov    edi,eax
   0xac03370a <+282>:   mov    eax,gs:0x14
   0xac033710 <+288>:   cmp    eax,DWORD PTR [esp+0x18]
   0xac033714 <+292>:   jne    0xac033720 <SOSInitializeByHost+304>
   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 <SOSInitializeByHost+323>
   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.
; 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.<Value>k__BackingField(offs=0x00) P-INDEP "field V08.<Value>k__BackingField (fldOffset=0x0)"
;  V17 tmp6         [V17,T09] (  2,  2   )     int  ->  eax         V09.<Value>k__BackingField(offs=0x00) P-INDEP "field V09.<Value>k__BackingField (fldOffset=0x0)"
;  V18 tmp7         [V18,T10] (  2,  2   )     int  ->  eax         V10.<Value>k__BackingField(offs=0x00) P-INDEP "field V10.<Value>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 t-mustafin/clrmd@98016eb, but it changes clrmd Public API. Maybe exists another better solution?

cc @jkotas @jakobbotsch @BruceForstall @alpencolt

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions