From 9e976d515099c7708cf26cef7520b628564f5acf Mon Sep 17 00:00:00 2001 From: Morgan Brown Date: Thu, 6 Sep 2018 19:37:30 -0700 Subject: [PATCH] Enable IJW Native calling managed (#19750) * Adds back code required to make IJW native->managed calls (if the runtime is already started) and includes a simple test. --- src/dlls/mscorrc/mscorrc.rc | 1 + src/dlls/mscorrc/resource.h | 1 + src/vm/ceeload.cpp | 381 ++++++++++++++++++++- src/vm/ceeload.h | 9 + src/vm/domainfile.cpp | 4 + src/vm/peimage.cpp | 160 +++++++++ src/vm/peimage.h | 44 +++ tests/src/Interop/CMakeLists.txt | 1 + tests/src/Interop/IJW/FakeMscoree/mscoree.cpp | 4 + .../IjwNativeDll/CMakeLists.txt | 10 +- .../IjwNativeDll/IjwNativeDll.cpp | 4 + .../IjwNativeCallingManagedDll/CMakeLists.txt | 42 +++ .../IjwNativeCallingManagedDll.cpp | 25 ++ .../NativeCallingManaged/NativeCallingManaged.cs | 58 ++++ .../NativeCallingManaged.csproj | 39 +++ 15 files changed, 773 insertions(+), 10 deletions(-) create mode 100644 tests/src/Interop/IJW/NativeCallingManaged/IjwNativeCallingManagedDll/CMakeLists.txt create mode 100644 tests/src/Interop/IJW/NativeCallingManaged/IjwNativeCallingManagedDll/IjwNativeCallingManagedDll.cpp create mode 100644 tests/src/Interop/IJW/NativeCallingManaged/NativeCallingManaged.cs create mode 100644 tests/src/Interop/IJW/NativeCallingManaged/NativeCallingManaged.csproj diff --git a/src/dlls/mscorrc/mscorrc.rc b/src/dlls/mscorrc/mscorrc.rc index a298563..713d7e0 100644 --- a/src/dlls/mscorrc/mscorrc.rc +++ b/src/dlls/mscorrc/mscorrc.rc @@ -1086,6 +1086,7 @@ BEGIN BFA_BAD_CA_HEADER "Malformed custom attribute header." BFA_BAD_STRING_TOKEN "Bad string token." BFA_BAD_STRING_TOKEN_RANGE "No string associated with token." + BFA_FIXUP_WRONG_PLATFORM "Image has a platform-specific fixup type that is not compatible with this platform." BFA_UNEXPECTED_GENERIC_TOKENTYPE "Token specifying generic type must be either a typeref or typedef." BFA_MDARRAY_BADRANK "Array rank may not be zero." BFA_SDARRAY_BADRANK "Single-dimensional array rank must be one." diff --git a/src/dlls/mscorrc/resource.h b/src/dlls/mscorrc/resource.h index d58780f..0a42dad 100644 --- a/src/dlls/mscorrc/resource.h +++ b/src/dlls/mscorrc/resource.h @@ -540,6 +540,7 @@ #define BFA_BAD_CA_HEADER 0x2050 #define BFA_BAD_STRING_TOKEN 0x2052 #define BFA_BAD_STRING_TOKEN_RANGE 0x2053 +#define BFA_FIXUP_WRONG_PLATFORM 0x2054 #define BFA_UNEXPECTED_GENERIC_TOKENTYPE 0x2055 #define BFA_MDARRAY_BADRANK 0x2056 #define BFA_SDARRAY_BADRANK 0x2057 diff --git a/src/vm/ceeload.cpp b/src/vm/ceeload.cpp index b3d2d59..19c5167 100644 --- a/src/vm/ceeload.cpp +++ b/src/vm/ceeload.cpp @@ -6390,8 +6390,7 @@ MethodDesc *Module::FindMethod(mdToken pMethod) CONTRACT_VIOLATION(ThrowsViolation); char szMethodName [MAX_CLASSNAME_LENGTH]; CEEInfo::findNameOfToken(this, pMethod, szMethodName, COUNTOF (szMethodName)); - // This used to be LF_IJW, but changed to LW_INTEROP to reclaim a bit in our log facilities - // IJW itself is not supported in coreclr so this code should never be run. + // This used to be IJW, but changed to LW_INTEROP to reclaim a bit in our log facilities LOG((LF_INTEROP, LL_INFO10, "Failed to find Method: %s for Vtable Fixup\n", szMethodName)); #endif // _DEBUG } @@ -6837,7 +6836,379 @@ void Module::NotifyDebuggerUnload(AppDomain *pDomain) g_pDebugInterface->UnloadModule(this, pDomain); } +#if !defined(CROSSGEN_COMPILE) +//================================================================================= +mdToken GetTokenForVTableEntry(HINSTANCE hInst, BYTE **ppVTEntry) +{ + CONTRACTL{ + NOTHROW; + } CONTRACTL_END; + + mdToken tok =(mdToken)(UINT_PTR)*ppVTEntry; + _ASSERTE(TypeFromToken(tok) == mdtMethodDef || TypeFromToken(tok) == mdtMemberRef); + return tok; +} + +//================================================================================= +void SetTargetForVTableEntry(HINSTANCE hInst, BYTE **ppVTEntry, BYTE *pTarget) +{ + CONTRACTL{ + THROWS; + } CONTRACTL_END; + + DWORD oldProtect; + if (!ClrVirtualProtect(ppVTEntry, sizeof(BYTE*), PAGE_READWRITE, &oldProtect)) + { + + // This is very bad. We are not going to be able to update header. + _ASSERTE(!"SetTargetForVTableEntry(): VirtualProtect() changing IJW thunk vtable to R/W failed.\n"); + ThrowLastError(); + } + + *ppVTEntry = pTarget; + + DWORD ignore; + if (!ClrVirtualProtect(ppVTEntry, sizeof(BYTE*), oldProtect, &ignore)) + { + // This is not so bad, we're already done the update, we just didn't return the thunk table to read only + _ASSERTE(!"SetTargetForVTableEntry(): VirtualProtect() changing IJW thunk vtable back to RO failed.\n"); + } +} + +//================================================================================= +BYTE * GetTargetForVTableEntry(HINSTANCE hInst, BYTE **ppVTEntry) +{ + CONTRACTL{ + NOTHROW; + } CONTRACTL_END; + + return *ppVTEntry; +} + +//====================================================================================== +// Fixup vtables stored in the header to contain pointers to method desc +// prestubs rather than metadata method tokens. +void Module::FixupVTables() +{ + CONTRACTL{ + INSTANCE_CHECK; + STANDARD_VM_CHECK; + } CONTRACTL_END; + + + // If we've already fixed up, or this is not an IJW module, just return. + // NOTE: This relies on ILOnly files not having fixups. If this changes, + // we need to change this conditional. + if (IsIJWFixedUp() || m_file->IsILOnly()) { + return; + } + + HINSTANCE hInstThis = GetFile()->GetIJWBase(); + + // @todo: workaround! + // If we are compiling in-process, we don't want to fixup the vtables - as it + // will have side effects on the other copy of the module! + if (SystemDomain::GetCurrentDomain()->IsPassiveDomain()) { + return; + } + +#ifdef FEATURE_PREJIT + // We delayed filling in this value until the LoadLibrary occurred + if (HasTls() && HasNativeImage()) { + CORCOMPILE_EE_INFO_TABLE *pEEInfo = GetNativeImage()->GetNativeEEInfoTable(); + pEEInfo->rvaStaticTlsIndex = GetTlsIndex(); + } +#endif + // Get vtable fixup data + COUNT_T cFixupRecords; + IMAGE_COR_VTABLEFIXUP *pFixupTable = m_file->GetVTableFixups(&cFixupRecords); + + // No records then return + if (cFixupRecords == 0) { + return; + } + + // Now, we need to take a lock to serialize fixup. + PEImage::IJWFixupData *pData = PEImage::GetIJWData(m_file->GetIJWBase()); + + // If it's already been fixed (in some other appdomain), record the fact and return + if (pData->IsFixedUp()) { + SetIsIJWFixedUp(); + return; + } + + ////////////////////////////////////////////////////// + // + // This is done in three stages: + // 1. We enumerate the types we'll need to load + // 2. We load the types + // 3. We create and install the thunks + // + + COUNT_T cVtableThunks = 0; + struct MethodLoadData + { + mdToken token; + MethodDesc *pMD; + }; + MethodLoadData *rgMethodsToLoad = NULL; + COUNT_T cMethodsToLoad = 0; + + // + // Stage 1 + // + + // Each fixup entry describes a vtable, so iterate the vtables and sum their counts + { + DWORD iFixup; + for (iFixup = 0; iFixup < cFixupRecords; iFixup++) + cVtableThunks += pFixupTable[iFixup].Count; + } + + Thread *pThread = GetThread(); + StackingAllocator *pAlloc = &pThread->m_MarshalAlloc; + CheckPointHolder cph(pAlloc->GetCheckpoint()); + + // Allocate the working array of tokens. + cMethodsToLoad = cVtableThunks; + + rgMethodsToLoad = new (pAlloc) MethodLoadData[cMethodsToLoad]; + memset(rgMethodsToLoad, 0, cMethodsToLoad * sizeof(MethodLoadData)); + + // Now take the IJW module lock and get all the tokens + { + // Take the lock + CrstHolder lockHolder(pData->GetLock()); + + // If someone has beaten us, just return + if (pData->IsFixedUp()) + { + SetIsIJWFixedUp(); + return; + } + + COUNT_T iCurMethod = 0; + + if (cFixupRecords != 0) + { + for (COUNT_T iFixup = 0; iFixup < cFixupRecords; iFixup++) + { + // Vtables can be 32 or 64 bit. + if ((pFixupTable[iFixup].Type == (COR_VTABLE_PTRSIZED)) || + (pFixupTable[iFixup].Type == (COR_VTABLE_PTRSIZED | COR_VTABLE_FROM_UNMANAGED)) || + (pFixupTable[iFixup].Type == (COR_VTABLE_PTRSIZED | COR_VTABLE_FROM_UNMANAGED_RETAIN_APPDOMAIN))) + { + const BYTE** pPointers = (const BYTE **)m_file->GetVTable(pFixupTable[iFixup].RVA); + for (int iMethod = 0; iMethod < pFixupTable[iFixup].Count; iMethod++) + { + if (pData->IsMethodFixedUp(iFixup, iMethod)) + continue; + mdToken mdTok = GetTokenForVTableEntry(hInstThis, (BYTE **)(pPointers + iMethod)); + CONSISTENCY_CHECK(mdTok != mdTokenNil); + rgMethodsToLoad[iCurMethod++].token = mdTok; + } + } + } + } + + } + + // + // Stage 2 - Load the types + // + + { + for (COUNT_T iCurMethod = 0; iCurMethod < cMethodsToLoad; iCurMethod++) + { + mdToken curTok = rgMethodsToLoad[iCurMethod].token; + if (!GetMDImport()->IsValidToken(curTok)) + { + _ASSERTE(!"Invalid token in v-table fix-up table"); + ThrowHR(COR_E_BADIMAGEFORMAT); + } + + + // Find the method desc + MethodDesc *pMD; + + { + CONTRACT_VIOLATION(LoadsTypeViolation); + pMD = FindMethodThrowing(curTok); + } + + CONSISTENCY_CHECK(CheckPointer(pMD)); + rgMethodsToLoad[iCurMethod].pMD = pMD; + } + } + + // + // Stage 3 - Create the thunk data + // + { + // Take the lock + CrstHolder lockHolder(pData->GetLock()); + + // If someone has beaten us, just return + if (pData->IsFixedUp()) + { + SetIsIJWFixedUp(); + return; + } + + // This phase assumes there is only one AppDomain and that thunks + // can all safely point directly to the method in the current AppDomain + + AppDomain *pAppDomain = GetAppDomain(); + + // Used to index into rgMethodsToLoad + COUNT_T iCurMethod = 0; + + + // Each fixup entry describes a vtable (each slot contains a metadata token + // at this stage). + DWORD iFixup; + for (iFixup = 0; iFixup < cFixupRecords; iFixup++) + cVtableThunks += pFixupTable[iFixup].Count; + + DWORD dwIndex = 0; + DWORD dwThunkIndex = 0; + + // Now to fill in the thunk table. + for (iFixup = 0; iFixup < cFixupRecords; iFixup++) + { + // Tables may contain zero fixups, in which case the RVA is null, which triggers an assert + if (pFixupTable[iFixup].Count == 0) + continue; + + const BYTE** pPointers = (const BYTE **) + m_file->GetVTable(pFixupTable[iFixup].RVA); + + // Vtables can be 32 or 64 bit. + if (pFixupTable[iFixup].Type == COR_VTABLE_PTRSIZED) + { + for (int iMethod = 0; iMethod < pFixupTable[iFixup].Count; iMethod++) + { + if (pData->IsMethodFixedUp(iFixup, iMethod)) + continue; + + mdToken mdTok = rgMethodsToLoad[iCurMethod].token; + MethodDesc *pMD = rgMethodsToLoad[iCurMethod].pMD; + iCurMethod++; + +#ifdef _DEBUG + if (pMD->IsNDirect()) + { + LOG((LF_INTEROP, LL_INFO10, "[0x%lx] <-- PINV thunk for \"%s\" (target = 0x%lx)\n", + (size_t)&(pPointers[iMethod]), pMD->m_pszDebugMethodName, + (size_t)(((NDirectMethodDesc*)pMD)->GetNDirectTarget()))); + } +#endif // _DEBUG + + CONSISTENCY_CHECK(dwThunkIndex < cVtableThunks); + + // Point the local vtable slot to the thunk we created + SetTargetForVTableEntry(hInstThis, (BYTE **)&pPointers[iMethod], (BYTE *)pMD->GetMultiCallableAddrOfCode()); + + pData->MarkMethodFixedUp(iFixup, iMethod); + + dwThunkIndex++; + } + + } + else if (pFixupTable[iFixup].Type == (COR_VTABLE_PTRSIZED | COR_VTABLE_FROM_UNMANAGED) || + (pFixupTable[iFixup].Type == (COR_VTABLE_PTRSIZED | COR_VTABLE_FROM_UNMANAGED_RETAIN_APPDOMAIN))) + { + for (int iMethod = 0; iMethod < pFixupTable[iFixup].Count; iMethod++) + { + if (pData->IsMethodFixedUp(iFixup, iMethod)) + continue; + + mdToken mdTok = rgMethodsToLoad[iCurMethod].token; + MethodDesc *pMD = rgMethodsToLoad[iCurMethod].pMD; + iCurMethod++; + LOG((LF_INTEROP, LL_INFO10, "[0x%p] <-- VTable thunk for \"%s\" (pMD = 0x%p)\n", + (UINT_PTR)&(pPointers[iMethod]), pMD->m_pszDebugMethodName, pMD)); + + UMEntryThunk *pUMEntryThunk = (UMEntryThunk*)(void*)(GetDllThunkHeap()->AllocAlignedMem(sizeof(UMEntryThunk), CODE_SIZE_ALIGN)); // UMEntryThunk contains code + FillMemory(pUMEntryThunk, sizeof(*pUMEntryThunk), 0); + + UMThunkMarshInfo *pUMThunkMarshInfo = (UMThunkMarshInfo*)(void*)(GetThunkHeap()->AllocAlignedMem(sizeof(UMThunkMarshInfo), CODE_SIZE_ALIGN)); + FillMemory(pUMThunkMarshInfo, sizeof(*pUMThunkMarshInfo), 0); + + pUMThunkMarshInfo->LoadTimeInit(pMD); + pUMEntryThunk->LoadTimeInit(NULL, NULL, pUMThunkMarshInfo, pMD, pAppDomain->GetId()); + SetTargetForVTableEntry(hInstThis, (BYTE **)&pPointers[iMethod], (BYTE *)pUMEntryThunk->GetCode()); + + pData->MarkMethodFixedUp(iFixup, iMethod); + } + } + else if ((pFixupTable[iFixup].Type & COR_VTABLE_NOT_PTRSIZED) == COR_VTABLE_NOT_PTRSIZED) + { + // fixup type doesn't match the platform + THROW_BAD_FORMAT(BFA_FIXUP_WRONG_PLATFORM, this); + } + else + { + _ASSERTE(!"Unknown vtable fixup type"); + } + } + + // Indicate that this module has been fixed before releasing the lock + pData->SetIsFixedUp(); // On the data + SetIsIJWFixedUp(); // On the module + } // End of Stage 3 +} + +// Self-initializing accessor for m_pThunkHeap +LoaderHeap *Module::GetDllThunkHeap() +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_ANY; + } + CONTRACTL_END; + return PEImage::GetDllThunkHeap(GetFile()->GetIJWBase()); + +} + +LoaderHeap *Module::GetThunkHeap() +{ + CONTRACT(LoaderHeap *) + { + INSTANCE_CHECK; + THROWS; + GC_NOTRIGGER; + MODE_ANY; + INJECT_FAULT(COMPlusThrowOM()); + POSTCONDITION(CheckPointer(RETVAL)); + } + CONTRACT_END + + if (!m_pThunkHeap) + { + size_t * pPrivatePCLBytes = NULL; + size_t * pGlobalPCLBytes = NULL; + + COUNTER_ONLY(pPrivatePCLBytes = &(GetPerfCounters().m_Loading.cbLoaderHeapSize)); + + LoaderHeap *pNewHeap = new LoaderHeap(VIRTUAL_ALLOC_RESERVE_GRANULARITY, // DWORD dwReserveBlockSize + 0, // DWORD dwCommitBlockSize + pPrivatePCLBytes, + ThunkHeapStubManager::g_pManager->GetRangeList(), + TRUE); // BOOL fMakeExecutable + + if (FastInterlockCompareExchangePointer(&m_pThunkHeap, pNewHeap, 0) != 0) + { + delete pNewHeap; + } + } + + RETURN m_pThunkHeap; +} +#endif // !CROSSGEN_COMPILE #ifdef FEATURE_NATIVE_IMAGE_GENERATION @@ -12492,7 +12863,11 @@ void Module::DeleteProfilingData() } #endif //FEATURE_PREJIT - +void Module::SetIsIJWFixedUp() +{ + LIMITED_METHOD_CONTRACT; + FastInterlockOr(&m_dwTransientFlags, IS_IJW_FIXED_UP); +} #ifdef FEATURE_PREJIT /* static */ diff --git a/src/vm/ceeload.h b/src/vm/ceeload.h index 73f1a8f..d8a278e 100644 --- a/src/vm/ceeload.h +++ b/src/vm/ceeload.h @@ -1740,6 +1740,7 @@ protected: void ApplyMetaData(); + void FixupVTables(); void FreeClassTables(); @@ -3156,6 +3157,14 @@ public: } #endif // FEATURE_PREJIT + // LoaderHeap for storing IJW thunks + PTR_LoaderHeap m_pThunkHeap; + + // Self-initializing accessor for IJW thunk heap + LoaderHeap *GetThunkHeap(); + // Self-initializing accessor for domain-independent IJW thunk heap + LoaderHeap *GetDllThunkHeap(); + void EnumRegularStaticGCRefs (AppDomain* pAppDomain, promote_func* fn, ScanContext* sc); protected: diff --git a/src/vm/domainfile.cpp b/src/vm/domainfile.cpp index 6b30ee7..12d3f3c 100644 --- a/src/vm/domainfile.cpp +++ b/src/vm/domainfile.cpp @@ -1075,6 +1075,10 @@ void DomainFile::VtableFixups() { WRAPPER_NO_CONTRACT; +#if !defined(CROSSGEN_COMPILE) + if (!GetCurrentModule()->IsResource()) + GetCurrentModule()->FixupVTables(); +#endif // !CROSSGEN_COMPILE } void DomainFile::FinishLoad() diff --git a/src/vm/peimage.cpp b/src/vm/peimage.cpp index 821dc3a..6b066ff 100644 --- a/src/vm/peimage.cpp +++ b/src/vm/peimage.cpp @@ -28,6 +28,8 @@ CrstStatic PEImage::s_hashLock; PtrHashMap *PEImage::s_Images = NULL; +CrstStatic PEImage::s_ijwHashLock; +PtrHashMap *PEImage::s_ijwFixupDataHash; extern LocaleID g_lcid; // fusion path comparison lcid @@ -54,6 +56,12 @@ void PEImage::Startup() LockOwner lock = { &s_hashLock, IsOwnerOfCrst }; s_Images = ::new PtrHashMap; s_Images->Init(CompareImage, FALSE, &lock); + + s_ijwHashLock.Init(CrstIJWHash, CRST_REENTRANCY); + LockOwner ijwLock = { &s_ijwHashLock, IsOwnerOfCrst }; + s_ijwFixupDataHash = ::new PtrHashMap; + s_ijwFixupDataHash->Init(CompareIJWDataBase, FALSE, &ijwLock); + PEImageLayout::Startup(); #ifdef FEATURE_USE_LCID g_lcid = MAKELCID(LOCALE_INVARIANT, SORT_DEFAULT); @@ -196,6 +204,20 @@ PEImage::~PEImage() } +/* static */ +BOOL PEImage::CompareIJWDataBase(UPTR base, UPTR mapping) +{ + CONTRACTL{ + PRECONDITION(CheckStartup()); + PRECONDITION(CheckPointer((BYTE *)(base << 1))); + PRECONDITION(CheckPointer((IJWFixupData *)mapping)); + NOTHROW; + GC_NOTRIGGER; + MODE_ANY; + } CONTRACTL_END; + + return ((BYTE *)(base << 1) == ((IJWFixupData*)mapping)->GetBase()); +} // Thread stress #if 0 @@ -686,7 +708,145 @@ void DECLSPEC_NORETURN PEImage::ThrowFormat(HRESULT hrError) EEFileLoadException::Throw(m_path, hrError); } +#if !defined(CROSSGEN_COMPILE) + +//may outlive PEImage +PEImage::IJWFixupData::IJWFixupData(void *pBase) + : m_lock(CrstIJWFixupData), + m_base(pBase), m_flags(0), m_DllThunkHeap(NULL), m_iNextFixup(0), m_iNextMethod(0) +{ + WRAPPER_NO_CONTRACT; +} + +PEImage::IJWFixupData::~IJWFixupData() +{ + WRAPPER_NO_CONTRACT; + if (m_DllThunkHeap) + delete m_DllThunkHeap; +} + + +// Self-initializing accessor for m_DllThunkHeap +LoaderHeap *PEImage::IJWFixupData::GetThunkHeap() +{ + CONTRACT(LoaderHeap *) + { + INSTANCE_CHECK; + THROWS; + GC_NOTRIGGER; + MODE_ANY; + INJECT_FAULT(COMPlusThrowOM()); + POSTCONDITION(CheckPointer(RETVAL)); + } + CONTRACT_END + + if (!m_DllThunkHeap) + { + size_t * pPrivatePCLBytes = NULL; + size_t * pGlobalPCLBytes = NULL; + + COUNTER_ONLY(pPrivatePCLBytes = &(GetPerfCounters().m_Loading.cbLoaderHeapSize)); + + LoaderHeap *pNewHeap = new LoaderHeap(VIRTUAL_ALLOC_RESERVE_GRANULARITY, // DWORD dwReserveBlockSize + 0, // DWORD dwCommitBlockSize + pPrivatePCLBytes, + ThunkHeapStubManager::g_pManager->GetRangeList(), + TRUE); // BOOL fMakeExecutable + + if (FastInterlockCompareExchangePointer((PVOID*)&m_DllThunkHeap, (VOID*)pNewHeap, (VOID*)0) != 0) + { + delete pNewHeap; + } + } + + RETURN m_DllThunkHeap; +} + +void PEImage::IJWFixupData::MarkMethodFixedUp(COUNT_T iFixup, COUNT_T iMethod) +{ + LIMITED_METHOD_CONTRACT; + // supports only sequential fixup/method + _ASSERTE((iFixup == m_iNextFixup + 1 && iMethod == 0) || //first method of the next fixup or + (iFixup == m_iNextFixup && iMethod == m_iNextMethod)); //the method that was next to fixup + + m_iNextFixup = iFixup; + m_iNextMethod = iMethod + 1; +} + +BOOL PEImage::IJWFixupData::IsMethodFixedUp(COUNT_T iFixup, COUNT_T iMethod) +{ + LIMITED_METHOD_CONTRACT; + if (iFixup < m_iNextFixup) + return TRUE; + if (iFixup > m_iNextFixup) + return FALSE; + if (iMethod < m_iNextMethod) + return TRUE; + + return FALSE; +} + +/*static */ +PTR_LoaderHeap PEImage::GetDllThunkHeap(void *pBase) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_ANY; + } + CONTRACTL_END; + return GetIJWData(pBase)->GetThunkHeap(); +} + +/* static */ +PEImage::IJWFixupData *PEImage::GetIJWData(void *pBase) +{ + CONTRACTL{ + THROWS; + GC_TRIGGERS; + MODE_ANY; + INJECT_FAULT(COMPlusThrowOM();); + } CONTRACTL_END + + // Take the IJW hash lock + CrstHolder hashLockHolder(&s_ijwHashLock); + + // Try to find the data + IJWFixupData *pData = (IJWFixupData *)s_ijwFixupDataHash->LookupValue((UPTR)pBase, pBase); + + // No data, must create + if ((UPTR)pData == (UPTR)INVALIDENTRY) + { + pData = new IJWFixupData(pBase); + s_ijwFixupDataHash->InsertValue((UPTR)pBase, pData); + } + + // Return the new data + return (pData); +} + +/* static */ +void PEImage::UnloadIJWModule(void *pBase) +{ + CONTRACTL{ + NOTHROW; + GC_TRIGGERS; + MODE_ANY; + } CONTRACTL_END + + // Take the IJW hash lock + CrstHolder hashLockHolder(&s_ijwHashLock); + + // Try to delete the hash entry + IJWFixupData *pData = (IJWFixupData *)s_ijwFixupDataHash->DeleteValue((UPTR)pBase, pBase); + + // Now delete the data + if ((UPTR)pData != (UPTR)INVALIDENTRY) + delete pData; +} +#endif // !CROSSGEN_COMPILE diff --git a/src/vm/peimage.h b/src/vm/peimage.h index 22aed04..f4e2924 100644 --- a/src/vm/peimage.h +++ b/src/vm/peimage.h @@ -271,6 +271,7 @@ private: }; static BOOL CompareImage(UPTR image1, UPTR image2); + static BOOL CompareIJWDataBase(UPTR base, UPTR mapping); void DECLSPEC_NORETURN ThrowFormat(HRESULT hr); @@ -341,6 +342,49 @@ private: BOOL m_bSignatureInfoCached; HRESULT m_hrSignatureInfoStatus; DWORD m_dwSignatureInfo; + + //@TODO:workaround: Remove this when we have one PEImage per mapped image, + //@TODO:workaround: and move the lock there + // This is for IJW thunk initialization, as it is no longer guaranteed + // that the initialization will occur under the loader lock. + static CrstStatic s_ijwHashLock; + static PtrHashMap *s_ijwFixupDataHash; + +public: + class IJWFixupData + { + private: + Crst m_lock; + void *m_base; + DWORD m_flags; + PTR_LoaderHeap m_DllThunkHeap; + + // the fixup for the next iteration in FixupVTables + // we use it to make sure that we do not try to fix up the same entry twice + // if there was a pass that was aborted in the middle + COUNT_T m_iNextFixup; + COUNT_T m_iNextMethod; + + enum { + e_FIXED_UP = 0x1 + }; + + public: + IJWFixupData(void *pBase); + ~IJWFixupData(); + void *GetBase() { LIMITED_METHOD_CONTRACT; return m_base; } + Crst *GetLock() { LIMITED_METHOD_CONTRACT; return &m_lock; } + BOOL IsFixedUp() { LIMITED_METHOD_CONTRACT; return m_flags & e_FIXED_UP; } + void SetIsFixedUp() { LIMITED_METHOD_CONTRACT; m_flags |= e_FIXED_UP; } + PTR_LoaderHeap GetThunkHeap(); + void MarkMethodFixedUp(COUNT_T iFixup, COUNT_T iMethod); + BOOL IsMethodFixedUp(COUNT_T iFixup, COUNT_T iMethod); + }; + + static IJWFixupData *GetIJWData(void *pBase); + static PTR_LoaderHeap GetDllThunkHeap(void *pBase); + static void UnloadIJWModule(void *pBase); + private: DWORD m_dwPEKind; DWORD m_dwMachine; diff --git a/tests/src/Interop/CMakeLists.txt b/tests/src/Interop/CMakeLists.txt index f2a480e..0107958 100644 --- a/tests/src/Interop/CMakeLists.txt +++ b/tests/src/Interop/CMakeLists.txt @@ -38,5 +38,6 @@ if(WIN32) # IJW isn't supported on ARM64 if(NOT CLR_CMAKE_PLATFORM_ARCH_ARM64) add_subdirectory(IJW/ManagedCallingNative/IjwNativeDll) + add_subdirectory(IJW/NativeCallingManaged/IjwNativeCallingManagedDll) endif() endif(WIN32) diff --git a/tests/src/Interop/IJW/FakeMscoree/mscoree.cpp b/tests/src/Interop/IJW/FakeMscoree/mscoree.cpp index 415c8d5..49470d3 100644 --- a/tests/src/Interop/IJW/FakeMscoree/mscoree.cpp +++ b/tests/src/Interop/IJW/FakeMscoree/mscoree.cpp @@ -1,3 +1,7 @@ +// 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. + #include // Entrypoint jumped to by IJW dlls when their dllmain is called diff --git a/tests/src/Interop/IJW/ManagedCallingNative/IjwNativeDll/CMakeLists.txt b/tests/src/Interop/IJW/ManagedCallingNative/IjwNativeDll/CMakeLists.txt index e0b9127..612e6aa 100644 --- a/tests/src/Interop/IJW/ManagedCallingNative/IjwNativeDll/CMakeLists.txt +++ b/tests/src/Interop/IJW/ManagedCallingNative/IjwNativeDll/CMakeLists.txt @@ -5,17 +5,13 @@ set(SOURCES IjwNativeDll.cpp) if (WIN32) # 4365 - signed/unsigned mismatch - add_compile_options(-wd4365) + add_compile_options(/wd4365) # IJW - add_compile_options(-clr) + add_compile_options(/clr) # IJW requires the CRT as a dll, not linked in - if(UPPERCASE_CMAKE_BUILD_TYPE STREQUAL DEBUG OR UPPERCASE_CMAKE_BUILD_TYPE STREQUAL CHECKED) - add_compile_options(-MDd) - else() - add_compile_options(-MD) - endif() + add_compile_options(/MD$<$,$>:d>) # CMake enables /RTC1 and /EHsc by default, but they're not compatible with /clr, so remove them if(CMAKE_CXX_FLAGS_DEBUG MATCHES "/RTC1") diff --git a/tests/src/Interop/IJW/ManagedCallingNative/IjwNativeDll/IjwNativeDll.cpp b/tests/src/Interop/IJW/ManagedCallingNative/IjwNativeDll/IjwNativeDll.cpp index 6ac5601..cb25b44 100644 --- a/tests/src/Interop/IJW/ManagedCallingNative/IjwNativeDll/IjwNativeDll.cpp +++ b/tests/src/Interop/IJW/ManagedCallingNative/IjwNativeDll/IjwNativeDll.cpp @@ -1,3 +1,7 @@ +// 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. + #pragma unmanaged int NativeFunction() { diff --git a/tests/src/Interop/IJW/NativeCallingManaged/IjwNativeCallingManagedDll/CMakeLists.txt b/tests/src/Interop/IJW/NativeCallingManaged/IjwNativeCallingManagedDll/CMakeLists.txt new file mode 100644 index 0000000..c8b0edc --- /dev/null +++ b/tests/src/Interop/IJW/NativeCallingManaged/IjwNativeCallingManagedDll/CMakeLists.txt @@ -0,0 +1,42 @@ +cmake_minimum_required (VERSION 2.6) +project (IjwNativeCallingManagedDll) +include_directories( ${INC_PLATFORM_DIR} ) +set(SOURCES IjwNativeCallingManagedDll.cpp) + +if (WIN32) + # 4365 - signed/unsigned mismatch + add_compile_options(/wd4365) + + # IJW + add_compile_options(/clr) + + # IJW requires the CRT as a dll, not linked in + add_compile_options(/MD$<$,$>:d>) + + # CMake enables /RTC1 and /EHsc by default, but they're not compatible with /clr, so remove them + if(CMAKE_CXX_FLAGS_DEBUG MATCHES "/RTC1") + string(REPLACE "/RTC1" " " CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG}") + endif() + + if(CMAKE_CXX_FLAGS MATCHES "/EHsc") + string(REPLACE "/EHsc" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") + endif() + + # IJW isn't compatible with CFG + if(CMAKE_CXX_FLAGS MATCHES "/guard:cf") + string(REPLACE "/guard:cf" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") + endif() + + # IJW isn't compatible with GR- + if(CMAKE_CXX_FLAGS MATCHES "/GR-") + string(REPLACE "/GR-" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") + endif() + +endif() + +# add the shared library +add_library (IjwNativeCallingManagedDll SHARED ${SOURCES}) +target_link_libraries(IjwNativeCallingManagedDll ${LINK_LIBRARIES_ADDITIONAL}) + +# add the install targets +install (TARGETS IjwNativeCallingManagedDll DESTINATION bin) diff --git a/tests/src/Interop/IJW/NativeCallingManaged/IjwNativeCallingManagedDll/IjwNativeCallingManagedDll.cpp b/tests/src/Interop/IJW/NativeCallingManaged/IjwNativeCallingManagedDll/IjwNativeCallingManagedDll.cpp new file mode 100644 index 0000000..9ba7e3a --- /dev/null +++ b/tests/src/Interop/IJW/NativeCallingManaged/IjwNativeCallingManagedDll/IjwNativeCallingManagedDll.cpp @@ -0,0 +1,25 @@ +// 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. + +#pragma managed +int ManagedCallee() +{ + return 100; +} + +#pragma unmanaged +int NativeFunction() +{ + return ManagedCallee(); +} + +#pragma managed +public ref class TestClass +{ +public: + int ManagedEntryPoint() + { + return NativeFunction(); + } +}; diff --git a/tests/src/Interop/IJW/NativeCallingManaged/NativeCallingManaged.cs b/tests/src/Interop/IJW/NativeCallingManaged/NativeCallingManaged.cs new file mode 100644 index 0000000..5f035d1 --- /dev/null +++ b/tests/src/Interop/IJW/NativeCallingManaged/NativeCallingManaged.cs @@ -0,0 +1,58 @@ +// 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. + +using System; +using System.IO; +using System.Reflection; +using System.Runtime.InteropServices; +using TestLibrary; + +namespace NativeCallingManaged +{ + class NativeCallingManaged + { + static int Main(string[] args) + { + bool success = true; + // Load a fake mscoree.dll to avoid starting desktop + LoadLibraryEx(Path.Combine(Environment.CurrentDirectory, "mscoree.dll"), IntPtr.Zero, 0); + + TestFramework.BeginScenario("Calling from managed to native IJW code"); + + // Building with a reference to the IJW dll is difficult, so load via reflection instead + TestFramework.BeginTestCase("Load IJW dll via reflection"); + Assembly ijwNativeDll = Assembly.Load("IjwNativeCallingManagedDll"); + TestFramework.EndTestCase(); + + TestFramework.BeginTestCase("Call native method returning int"); + Type testType = ijwNativeDll.GetType("TestClass"); + object testInstance = Activator.CreateInstance(testType); + MethodInfo testMethod = testType.GetMethod("ManagedEntryPoint"); + int result = (int)testMethod.Invoke(testInstance, null); + if(result != 100) + { + TestFramework.LogError("IJW", "Incorrect result returned: " + result); + success = false; + } + TestFramework.EndTestCase(); + + TestFramework.BeginTestCase("Ensure .NET Framework was not loaded"); + IntPtr clrHandle = GetModuleHandle("mscoreei.dll"); + if (clrHandle != IntPtr.Zero) + { + TestFramework.LogError("IJW", ".NET Framework loaded by IJw module load"); + success = false; + } + TestFramework.EndTestCase(); + + return success ? 100 : 99; + } + + [DllImport("kernel32.dll")] + static extern IntPtr LoadLibraryEx(string lpFileName, IntPtr hReservedNull, int dwFlags); + + [DllImport("kernel32.dll")] + static extern IntPtr GetModuleHandle(string lpModuleName); + } +} diff --git a/tests/src/Interop/IJW/NativeCallingManaged/NativeCallingManaged.csproj b/tests/src/Interop/IJW/NativeCallingManaged/NativeCallingManaged.csproj new file mode 100644 index 0000000..3b92603 --- /dev/null +++ b/tests/src/Interop/IJW/NativeCallingManaged/NativeCallingManaged.csproj @@ -0,0 +1,39 @@ + + + + + Debug + AnyCPU + NativeCallingManaged + 2.0 + {8B76A001-5654-4F11-A80B-EF12644EAD3D} + Exe + {786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + ..\..\ + + + true + + + true + + + + + + + + + False + + + + + + + + + + + + -- 2.7.4