diff options
Diffstat (limited to 'src/vm/amd64/remotingamd64.cpp')
-rw-r--r-- | src/vm/amd64/remotingamd64.cpp | 672 |
1 files changed, 672 insertions, 0 deletions
diff --git a/src/vm/amd64/remotingamd64.cpp b/src/vm/amd64/remotingamd64.cpp new file mode 100644 index 0000000000..587afae124 --- /dev/null +++ b/src/vm/amd64/remotingamd64.cpp @@ -0,0 +1,672 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. +/*=========================================================================== +** +** File: RemotingCpu.cpp +** +** +** +** Purpose: Defines various remoting related functions for the AMD64 architecture +** +** +** See code:EEStartup#TableOfContents for EE overview +** +=============================================================================*/ + +#include "common.h" + +#ifdef FEATURE_REMOTING + +#include "excep.h" +#include "comdelegate.h" +#include "remoting.h" +#include "field.h" +#include "siginfo.hpp" +#include "stackbuildersink.h" +#include "threads.h" +#include "method.hpp" + +#include "asmconstants.h" + +// External variables +extern DWORD g_dwNonVirtualThunkRemotingLabelOffset; +extern DWORD g_dwNonVirtualThunkReCheckLabelOffset; + +//+---------------------------------------------------------------------------- +// +// Method: CRemotingServices::CheckForContextMatch public +// +// Synopsis: This code generates a check to see if the current context and +// the context of the proxy match. +// +//+---------------------------------------------------------------------------- +// +// returns zero if contexts match +// returns non-zero if contexts don't match +// +extern "C" UINT_PTR __stdcall CRemotingServices__CheckForContextMatch(Object* pStubData) +{ + // This method cannot have a contract because CreateStubForNonVirtualMethod assumes + // it won't trash XMM registers. The code generated for contracts by recent compilers + // is trashing XMM registers. + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_GC_NOTRIGGER; + STATIC_CONTRACT_MODE_COOPERATIVE; // due to the Object parameter + STATIC_CONTRACT_SO_TOLERANT; + + UINT_PTR contextID = *(UINT_PTR*)pStubData->UnBox(); + UINT_PTR contextCur = (UINT_PTR)GetThread()->m_Context; + return (contextCur != contextID); // chosen to match x86 convention +} + + +//+---------------------------------------------------------------------------- +// +// Method: CTPMethodTable::CreateThunkForVirtualMethod private +// +// Synopsis: Creates the thunk that pushes the supplied slot number and jumps +// to TP Stub +// +//+---------------------------------------------------------------------------- +PCODE CTPMethodTable::CreateThunkForVirtualMethod(DWORD dwSlot, BYTE* pbCode) +{ + LIMITED_METHOD_CONTRACT; + + BYTE *pbCodeStart = pbCode; + + // NOTE: if you change the code generated here, update + // CVirtualThunkMgr::IsThunkByASM, CVirtualThunkMgr::GetMethodDescByASM + + // + // mov r10, <dwSlot> + // mov rax, TransparentProxyStub + // jmp rax + // + *pbCode++ = 0x49; + *pbCode++ = 0xc7; + *pbCode++ = 0xc2; + *((DWORD*)pbCode) = dwSlot; + pbCode += sizeof(DWORD); + *pbCode++ = 0x48; + *pbCode++ = 0xB8; + *((UINT64*)pbCode) = (UINT64)(TransparentProxyStub); + pbCode += sizeof(UINT64); + *pbCode++ = 0xFF; + *pbCode++ = 0xE0; + + _ASSERTE(pbCode - pbCodeStart == ConstVirtualThunkSize); + _ASSERTE(CVirtualThunkMgr::IsThunkByASM((PCODE)pbCodeStart)); + + return (PCODE)pbCodeStart; +} + + +#ifdef HAS_REMOTING_PRECODE + +//+---------------------------------------------------------------------------- +// +// Method: CTPMethodTable::ActivatePrecodeRemotingThunk private +// +// Synopsis: Patch the precode remoting thunk to begin interception +// +//+---------------------------------------------------------------------------- +void CTPMethodTable::ActivatePrecodeRemotingThunk() +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_ANY; + } + CONTRACTL_END; + + PORTABILITY_WARNING("CTPMethodTable::ActivatePrecodeRemotingThunk"); +} + +#else // HAS_REMOTING_PRECODE + +//+---------------------------------------------------------------------------- +// +// Method: CTPMethodTable::CreateStubForNonVirtualMethod public +// +// Synopsis: Create a stub for a non virtual method +// +//+---------------------------------------------------------------------------- +Stub* CTPMethodTable::CreateStubForNonVirtualMethod(MethodDesc* pMD, CPUSTUBLINKER* psl, + LPVOID pvAddrOfCode, Stub* pInnerStub) +{ + STANDARD_VM_CONTRACT; + + // Sanity check + + Stub *pStub = NULL; + + // we need a hash table only for virtual methods + _ASSERTE(!pMD->IsVirtual()); + + // Ensure the TP MethodTable's fields have been initialized. + EnsureFieldsInitialized(); + + /* + NonVirtualMethodStub<thisReg, pvAddrOfCode, pTPMethodTable, pvTPStub> + { + ;; thisReg: this + + sub rsp, 0x28 + + test thisReg, thisReg + je JmpAddrLabel + + mov rax, [thisReg] + mov r10, <pTPMethodTable> + cmp rax, r10 + jne JmpAddrLabel + + mov [rsp+0x30], rcx ;| + mov [rsp+0x38], rdx ;| + mov [rsp+0x40], r8 ;| + mov [rsp+0x48], r9 ;| + ;| + mov rax, [thisReg + TransparentProxyObject___stubData] ;| + call [thisReg + TransparentProxyObject___stub] ;| EmitCallToStub<pCtxMismatch> + ;| + mov rcx, [rsp+0x30] ;| + mov rdx, [rsp+0x38] ;| + mov r8, [rsp+0x40] ;| + mov r9, [rsp+0x48] ;| + ;| + test rax, rax ;| + jnz RemotingLabel ;| + + JmpAddrLabel: + mov rax, <pvAddrOfCode> + add rsp, 0x28 + jmp rax + + RemotingLabel: + mov r10, <pMD> + mov rax, <pvTPStub> + add rsp, 0x20 + jmp rax + } + */ + + X86Reg thisReg = kRCX; + void* pvTPStub = TransparentProxyStub_CrossContext; + + // Generate label where a null reference exception will be thrown + CodeLabel *pJmpAddrLabel = psl->NewCodeLabel(); + // Generate label where remoting code will execute + CodeLabel *pRemotingLabel = psl->NewCodeLabel(); + + // NOTE: if you change any of this code, you must update + // CNonVirtualThunkMgr::IsThunkByASM. + + // Allocate callee scratch area + // sub rsp, 0x28 + psl->X86EmitSubEsp(0x28); + + // test thisReg, thisReg + psl->X86EmitR2ROp(0x85, thisReg, thisReg); + // je JmpAddrLabel + psl->X86EmitCondJump(pJmpAddrLabel, X86CondCode::kJE); + + // Emit a label here for the debugger. A breakpoint will + // be set at the next instruction and the debugger will + // call CNonVirtualThunkMgr::TraceManager when the + // breakpoint is hit with the thread's context. + CodeLabel *pRecheckLabel = psl->NewCodeLabel(); + psl->EmitLabel(pRecheckLabel); + + // mov rax, [thisReg] + psl->X86EmitIndexRegLoad(kRAX, thisReg, 0); + + // mov r10, CTPMethodTable::GetMethodTable() + psl->X86EmitRegLoad(kR10, (UINT_PTR)CTPMethodTable::GetMethodTable()); + // cmp rax, r10 + psl->X86EmitR2ROp(0x3B, kRAX, kR10); + + // jne JmpAddrLabel + psl->X86EmitCondJump(pJmpAddrLabel, X86CondCode::kJNE); + + // CONSIDER: write all possible stubs in asm to ensure param registers are not trashed + + // mov [rsp+0x30], rcx + // mov [rsp+0x38], rdx + // mov [rsp+0x40], r8 + // mov [rsp+0x48], r9 + psl->X86EmitRegSave(kRCX, 0x30); + psl->X86EmitRegSave(kRDX, 0x38); + psl->X86EmitRegSave(kR8, 0x40); + psl->X86EmitRegSave(kR9, 0x48); + + // mov rax, [thisReg + TransparentProxyObject___stub] + psl->X86EmitIndexRegLoad(kRAX, thisReg, TransparentProxyObject___stub); + + // mov rcx, [thisReg + TransparentProxyObject___stubData] + psl->X86EmitIndexRegLoad(kRCX, thisReg, TransparentProxyObject___stubData); + + // call rax + psl->Emit16(0xd0ff); + + // mov rcx, [rsp+0x30] + // mov rdx, [rsp+0x38] + // mov r8, [rsp+0x40] + // mov r9, [rsp+0x48] + psl->X86EmitEspOffset(0x8b, kRCX, 0x30); + psl->X86EmitEspOffset(0x8b, kRDX, 0x38); + psl->X86EmitEspOffset(0x8b, kR8, 0x40); + psl->X86EmitEspOffset(0x8b, kR9, 0x48); + + // test rax, rax + psl->X86EmitR2ROp(0x85, kRAX, kRAX); + // jnz RemotingLabel + psl->X86EmitCondJump(pRemotingLabel, X86CondCode::kJNZ); + +// pJmpAddrLabel: + psl->EmitLabel(pJmpAddrLabel); + + // Make sure that the actual code does not require MethodDesc in r10 + _ASSERTE(!pMD->RequiresMethodDescCallingConvention()); + + // mov rax, <pvAddrOfCode> + // add rsp, 0x28 + // REX.W jmp rax + psl->X86EmitTailcallWithESPAdjust(psl->NewExternalCodeLabel(pvAddrOfCode), 0x28); + +// pRemotingLabel: + psl->EmitLabel(pRemotingLabel); + + // mov r10, <pMD> + psl->X86EmitRegLoad(kR10, (UINT_PTR)pMD); + + // mov rax, <pvTPStub> + // add rsp, 0x28 + // REX.W jmp rax + psl->X86EmitTailcallWithESPAdjust(psl->NewExternalCodeLabel(pvTPStub), 0x28); + + // Link and produce the stub + pStub = psl->LinkInterceptor(pMD->GetLoaderAllocator()->GetStubHeap(), + pInnerStub, pvAddrOfCode); + + return pStub; +} + + +//+---------------------------------------------------------------------------- +// +// Synopsis: Find an existing thunk or create a new one for the given +// method descriptor. NOTE: This is used for the methods that do +// not go through the vtable such as constructors, private and +// final methods. +// +//+---------------------------------------------------------------------------- +PCODE CTPMethodTable::CreateNonVirtualThunkForVirtualMethod(MethodDesc* pMD) +{ + CONTRACTL + { + STANDARD_VM_CHECK; + PRECONDITION(CheckPointer(pMD)); + } + CONTRACTL_END; + + CPUSTUBLINKER sl; + CPUSTUBLINKER* psl = &sl; + + Stub *pStub = NULL; + + // The thunk has not been created yet. Go ahead and create it. + // Compute the address of the slot + LPVOID pvEntryPoint = (LPVOID)pMD->GetMethodEntryPoint(); + + X86Reg thisReg = kRCX; + void* pvStub = CRemotingServices__DispatchInterfaceCall; + + // Generate label where a null reference exception will be thrown + CodeLabel *pExceptionLabel = psl->NewCodeLabel(); + + // !!! WARNING WARNING WARNING WARNING WARNING !!! + // + // DO NOT CHANGE this code without changing the thunk recognition + // code in CNonVirtualThunkMgr::IsThunkByASM + // & CNonVirtualThunkMgr::GetMethodDescByASM + // + // !!! WARNING WARNING WARNING WARNING WARNING !!! + + // NOTE: constant mov's should use an extended register to force a REX + // prefix and the full 64-bit immediate value, so that + // g_dwNonVirtualThunkRemotingLabelOffset and + // g_dwNonVirtualThunkReCheckLabelOffset are the same for all + // generated code. + + // if this == NULL throw NullReferenceException + // test rcx, rcx + psl->X86EmitR2ROp(0x85, thisReg, thisReg); + + // je ExceptionLabel + psl->X86EmitCondJump(pExceptionLabel, X86CondCode::kJE); + + // Generate label where remoting code will execute + CodeLabel *pRemotingLabel = psl->NewCodeLabel(); + + // Emit a label here for the debugger. A breakpoint will + // be set at the next instruction and the debugger will + // call CNonVirtualThunkMgr::TraceManager when the + // breakpoint is hit with the thread's context. + CodeLabel *pRecheckLabel = psl->NewCodeLabel(); + psl->EmitLabel(pRecheckLabel); + + // If this.MethodTable == TPMethodTable then do RemotingCall + // mov rax, [thisReg] + psl->X86EmitIndexRegLoad(kRAX, thisReg, 0); + // mov r10, CTPMethodTable::GetMethodTable() + psl->X86EmitRegLoad(kR10, (UINT_PTR)CTPMethodTable::GetMethodTable()); + // cmp rax, r10 + psl->X86EmitR2ROp(0x3B, kRAX, kR10); + // je RemotingLabel + psl->X86EmitCondJump(pRemotingLabel, X86CondCode::kJE); + + // Exception handling and non-remoting share the + // same codepath + psl->EmitLabel(pExceptionLabel); + + // Non-RemotingCode + // Jump to the vtable slot of the method + // mov rax, pvEntryPoint + // Encoded the mov manually so that it always uses the 64-bit form. + //psl->X86EmitRegLoad(kRAX, (UINT_PTR)pvEntryPoint); + psl->Emit8(REX_PREFIX_BASE | REX_OPERAND_SIZE_64BIT); + psl->Emit8(0xb8); + psl->EmitBytes((BYTE*)&pvEntryPoint, 8); + // jmp rax + psl->Emit8(0xff); + psl->Emit8(0xe0); + + // Remoting code. Note: CNonVirtualThunkMgr::TraceManager + // relies on this label being right after the jmp pvEntryPoint + // instruction above. If you move this label, update + // CNonVirtualThunkMgr::DoTraceStub. + psl->EmitLabel(pRemotingLabel); + + // Save the MethodDesc and goto TPStub + // push MethodDesc + psl->X86EmitRegLoad(kR10, (UINT_PTR)pMD); + + // jmp TPStub + psl->X86EmitNearJump(psl->NewExternalCodeLabel(pvStub)); + + // Link and produce the stub + // FUTURE: Do we have to provide the loader heap ? + pStub = psl->Link(SystemDomain::GetGlobalLoaderAllocator()->GetExecutableHeap()); + + // Grab the offset of the RemotingLabel and RecheckLabel + // for use in CNonVirtualThunkMgr::DoTraceStub and + // TraceManager. + DWORD dwOffset; + + dwOffset = psl->GetLabelOffset(pRemotingLabel); + ASSERT(!g_dwNonVirtualThunkRemotingLabelOffset || g_dwNonVirtualThunkRemotingLabelOffset == dwOffset); + g_dwNonVirtualThunkRemotingLabelOffset = dwOffset; + + dwOffset = psl->GetLabelOffset(pRecheckLabel); + ASSERT(!g_dwNonVirtualThunkReCheckLabelOffset || g_dwNonVirtualThunkReCheckLabelOffset == dwOffset); + g_dwNonVirtualThunkReCheckLabelOffset = dwOffset; + + return (pStub->GetEntryPoint()); +} + +#endif // HAS_REMOTING_PRECODE + +//+---------------------------------------------------------------------------- +// +// Method: CVirtualThunkMgr::DoTraceStub public +// +// Synopsis: Traces the stub given the starting address +// +//+---------------------------------------------------------------------------- +BOOL CVirtualThunkMgr::DoTraceStub(PCODE stubStartAddress, TraceDestination *trace) +{ + LIMITED_METHOD_CONTRACT; + + // <TODO> implement this </TODO> + return FALSE; +} + +//+---------------------------------------------------------------------------- +// +// Method: CVirtualThunkMgr::IsThunkByASM public +// +// Synopsis: Check assembly to see if this one of our thunks +// +//+---------------------------------------------------------------------------- +BOOL CVirtualThunkMgr::IsThunkByASM(PCODE startaddr) +{ + LIMITED_METHOD_CONTRACT; + + PTR_BYTE pbCode = PTR_BYTE(startaddr); + + // NOTE: this depends on the code generated by + // CTPMethodTable::CreateThunkForVirtualMethod. + + // mov r10, <dwSlot> + return 0x49 == pbCode[0] + && 0xc7 == pbCode[1] + && 0xc2 == pbCode[2] + // mov rax, TransparentProxyStub + && 0x48 == pbCode[7] + && 0xb8 == pbCode[8] + && (TADDR)TransparentProxyStub == *PTR_TADDR(pbCode+9) + // jmp rax + && 0xff == pbCode[17] + && 0xe0 == pbCode[18]; +} + +//+---------------------------------------------------------------------------- +// +// Method: CVirtualThunkMgr::GetMethodDescByASM public +// +// Synopsis: Parses MethodDesc out of assembly code +// +//+---------------------------------------------------------------------------- +MethodDesc *CVirtualThunkMgr::GetMethodDescByASM(PCODE pbThunkCode, MethodTable *pMT) +{ + LIMITED_METHOD_CONTRACT; + + // NOTE: this depends on the code generated by + // CTPMethodTable::CreateThunkForVirtualMethod. + + return pMT->GetMethodDescForSlot(*((DWORD *) (pbThunkCode + 3))); +} + + +#ifndef HAS_REMOTING_PRECODE + +//+---------------------------------------------------------------------------- +// +// Method: CNonVirtualThunkMgr::TraceManager public +// +// Synopsis: Traces the stub given the current context +// +//+---------------------------------------------------------------------------- +BOOL CNonVirtualThunkMgr::TraceManager(Thread* thread, + TraceDestination* trace, + CONTEXT* pContext, + BYTE** pRetAddr) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_ANY; + PRECONDITION(CheckPointer(thread, NULL_OK)); + PRECONDITION(CheckPointer(trace)); + PRECONDITION(CheckPointer(pContext)); + PRECONDITION(CheckPointer(pRetAddr)); + } + CONTRACTL_END; + + BOOL bRet = FALSE; + + MethodDesc * pMD = GetMethodDescByASM(GetIP(pContext) - g_dwNonVirtualThunkReCheckLabelOffset); + + LPBYTE pThis = (LPBYTE)pContext->Rcx; + + if ((pThis != NULL) && + (*(LPBYTE*)(SIZE_T)pThis == (LPBYTE)(SIZE_T)CTPMethodTable::GetMethodTable())) + { + // <TODO>We know that we've got a proxy + // in the way. If the proxy is to a remote call, with no + // managed code in between, then the debugger doesn't care and + // we should just be able to return FALSE. + // + // </TODO> + bRet = FALSE; + } + else + { + // No proxy in the way, so figure out where we're really going + // to and let the stub manager try to pickup the trace from + // there. + LPBYTE stubStartAddress = (LPBYTE)GetIP(pContext) - + g_dwNonVirtualThunkReCheckLabelOffset; + + // Extract the address of the destination + BYTE* pbAddr = (BYTE *)(SIZE_T)(stubStartAddress + + g_dwNonVirtualThunkRemotingLabelOffset - 2 - sizeof(void *)); + + SIZE_T destAddress = *(SIZE_T *)pbAddr; + + // Ask the stub manager to trace the destination address + bRet = StubManager::TraceStub((PCODE)(BYTE *)(size_t)destAddress, trace); + } + + // While we may have made it this far, further tracing may reveal + // that the debugger can't continue on. Therefore, since there is + // no frame currently pushed, we need to tell the debugger where + // we're returning to just in case it hits such a situtation. We + // know that the return address is on the top of the thread's + // stack. + (*pRetAddr) = *((BYTE**)(size_t)(GetSP(pContext))); + + return bRet; +} + +//+---------------------------------------------------------------------------- +// +// Method: CNonVirtualThunkMgr::DoTraceStub public +// +// Synopsis: Traces the stub given the starting address +// +//+---------------------------------------------------------------------------- +BOOL CNonVirtualThunkMgr::DoTraceStub(PCODE stubStartAddress, + TraceDestination* trace) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_ANY; + PRECONDITION(stubStartAddress != NULL); + PRECONDITION(CheckPointer(trace)); + } + CONTRACTL_END; + + BOOL bRet = FALSE; + + if (!IsThunkByASM(stubStartAddress)) + return FALSE; + + CNonVirtualThunk* pThunk = FindThunk((const BYTE *)stubStartAddress); + + if(NULL != pThunk) + { + // We can either jump to + // (1) a slot in the transparent proxy table (UNMANAGED) + // (2) a slot in the non virtual part of the vtable + // ... so, we need to return TRACE_MGR_PUSH with the address + // at which we want to be called back with the thread's context + // so we can figure out which way we're gonna go. + if((const BYTE *)stubStartAddress == pThunk->GetThunkCode()) + { + trace->InitForManagerPush( + (PCODE) (stubStartAddress + g_dwNonVirtualThunkReCheckLabelOffset), + this); + bRet = TRUE; + } + } + + return bRet; +} + +//+---------------------------------------------------------------------------- +// +// Method: CNonVirtualThunkMgr::IsThunkByASM public +// +// Synopsis: Check assembly to see if this one of our thunks +// +//+---------------------------------------------------------------------------- +BOOL CNonVirtualThunkMgr::IsThunkByASM(PCODE startaddr) +{ + LIMITED_METHOD_CONTRACT; + + PTR_BYTE pbCode = PTR_BYTE(startaddr); + + // test rcx, rcx ; 3 bytes + return 0x48 == pbCode[0] + && 0x85 == pbCode[1] + && 0xc9 == pbCode[2] + // je ... ; 2 bytes + && 0x74 == pbCode[3] + // mov rax, [rcx] ; 3 bytes + // mov r10, CTPMethodTable::GetMethodTable() ; 2 bytes + MethodTable* + && (TADDR)CTPMethodTable::GetMethodTable() == *PTR_TADDR(pbCode + 10); +} + +//+---------------------------------------------------------------------------- +// +// Method: CNonVirtualThunkMgr::GetMethodDescByASM public +// +// Synopsis: Parses MethodDesc out of assembly code +// +//+---------------------------------------------------------------------------- +MethodDesc* CNonVirtualThunkMgr::GetMethodDescByASM(PCODE pbThunkCode) +{ + LIMITED_METHOD_CONTRACT; + + return *((MethodDesc **) (pbThunkCode + g_dwNonVirtualThunkRemotingLabelOffset + 2)); +} + +#endif // HAS_REMOTING_PRECODE + + +//+---------------------------------------------------------------------------- +// +// Method: CTPMethodTable::GenericCheckForContextMatch private +// +// Synopsis: Calls the stub in the TP & returns TRUE if the contexts +// match, FALSE otherwise. +// +// Note: 1. Called during FieldSet/Get, used for proxy extensibility +// +//+---------------------------------------------------------------------------- +BOOL __stdcall CTPMethodTable__GenericCheckForContextMatch(Object* orTP) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_COOPERATIVE; // due to the Object parameter + SO_TOLERANT; + } + CONTRACTL_END; + + Object *StubData = OBJECTREFToObject(((TransparentProxyObject*)orTP)->GetStubData()); + CTPMethodTable::CheckContextCrossingProc *pfnCheckContextCrossing = + (CTPMethodTable::CheckContextCrossingProc*)(((TransparentProxyObject*)orTP)->GetStub()); + return pfnCheckContextCrossing(StubData) == 0; +} + +#endif // FEATURE_REMOTING + + |