-
Notifications
You must be signed in to change notification settings - Fork 5.3k
Description
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?