1 // Licensed to the .NET Foundation under one or more agreements.
2 // The .NET Foundation licenses this file to you under the MIT license.
3 // See the LICENSE file in the project root for more information.
6 // Handles our private native calling interface.
15 #include "comdelegate.h"
17 #ifndef DACCESS_COMPILE
19 #ifdef CROSSGEN_COMPILE
20 namespace CrossGenMscorlib
22 extern const ECClass c_rgECClasses[];
23 extern const int c_nECClasses;
25 using namespace CrossGenMscorlib;
26 #else // CROSSGEN_COMPILE
27 extern const ECClass c_rgECClasses[];
28 extern const int c_nECClasses;
29 #endif // CROSSGEN_COMPILE
32 // METHOD__STRING__CTORF_XXX has to be in same order as ECall::CtorCharXxx
33 #define METHOD__STRING__CTORF_FIRST METHOD__STRING__CTORF_CHARARRAY
34 static_assert_no_msg(METHOD__STRING__CTORF_FIRST + 0 == METHOD__STRING__CTORF_CHARARRAY);
35 static_assert_no_msg(METHOD__STRING__CTORF_FIRST + 1 == METHOD__STRING__CTORF_CHARARRAY_START_LEN);
36 static_assert_no_msg(METHOD__STRING__CTORF_FIRST + 2 == METHOD__STRING__CTORF_CHAR_COUNT);
37 static_assert_no_msg(METHOD__STRING__CTORF_FIRST + 3 == METHOD__STRING__CTORF_CHARPTR);
38 static_assert_no_msg(METHOD__STRING__CTORF_FIRST + 4 == METHOD__STRING__CTORF_CHARPTR_START_LEN);
39 static_assert_no_msg(METHOD__STRING__CTORF_FIRST + 5 == METHOD__STRING__CTORF_READONLYSPANOFCHAR);
41 // ECall::CtorCharXxx has to be in same order as METHOD__STRING__CTORF_XXX
42 #define ECallCtor_First ECall::CtorCharArrayManaged
43 static_assert_no_msg(ECallCtor_First + 0 == ECall::CtorCharArrayManaged);
44 static_assert_no_msg(ECallCtor_First + 1 == ECall::CtorCharArrayStartLengthManaged);
45 static_assert_no_msg(ECallCtor_First + 2 == ECall::CtorCharCountManaged);
46 static_assert_no_msg(ECallCtor_First + 3 == ECall::CtorCharPtrManaged);
47 static_assert_no_msg(ECallCtor_First + 4 == ECall::CtorCharPtrStartLengthManaged);
48 static_assert_no_msg(ECallCtor_First + 5 == ECall::CtorReadOnlySpanOfCharManaged);
50 #define NumberOfStringConstructors 6
52 void ECall::PopulateManagedStringConstructors()
56 INDEBUG(static bool fInitialized = false);
57 _ASSERTE(!fInitialized); // assume this method is only called once
58 _ASSERTE(g_pStringClass != NULL);
60 for (int i = 0; i < NumberOfStringConstructors; i++)
62 MethodDesc* pMD = MscorlibBinder::GetMethod((BinderMethodID)(METHOD__STRING__CTORF_FIRST + i));
63 _ASSERTE(pMD != NULL);
65 PCODE pDest = pMD->GetMultiCallableAddrOfCode();
67 ECall::DynamicallyAssignFCallImpl(pDest, ECallCtor_First + i);
69 INDEBUG(fInitialized = true);
72 static CrstStatic gFCallLock;
74 // This variable is used to force the compiler not to tailcall a function.
77 #endif // !DACCESS_COMPILE
79 // To provide a quick check, this is the lowest and highest
80 // addresses of any FCALL starting address
81 GVAL_IMPL_INIT(TADDR, gLowestFCall, (TADDR)-1);
82 GVAL_IMPL(TADDR, gHighestFCall);
84 GARY_IMPL(PTR_ECHash, gFCallMethods, FCALL_HASH_SIZE);
86 inline unsigned FCallHash(PCODE pTarg) {
87 LIMITED_METHOD_DAC_CONTRACT;
88 return pTarg % FCALL_HASH_SIZE;
91 #ifdef DACCESS_COMPILE
93 GARY_IMPL(PCODE, g_FCDynamicallyAssignedImplementations,
94 ECall::NUM_DYNAMICALLY_ASSIGNED_FCALL_IMPLEMENTATIONS);
96 #else // !DACCESS_COMPILE
98 PCODE g_FCDynamicallyAssignedImplementations[ECall::NUM_DYNAMICALLY_ASSIGNED_FCALL_IMPLEMENTATIONS] = {
99 #undef DYNAMICALLY_ASSIGNED_FCALL_IMPL
100 #define DYNAMICALLY_ASSIGNED_FCALL_IMPL(id,defaultimpl) GetEEFuncEntryPoint(defaultimpl),
101 DYNAMICALLY_ASSIGNED_FCALLS()
104 void ECall::DynamicallyAssignFCallImpl(PCODE impl, DWORD index)
114 _ASSERTE(index < NUM_DYNAMICALLY_ASSIGNED_FCALL_IMPLEMENTATIONS);
115 g_FCDynamicallyAssignedImplementations[index] = impl;
118 /*******************************************************************************/
119 static INT FindImplsIndexForClass(MethodTable* pMT)
129 LPCUTF8 pszNamespace = 0;
130 LPCUTF8 pszName = pMT->GetFullyQualifiedNameInfo(&pszNamespace);
132 // Array classes get null from the above routine, but they have no ecalls.
137 unsigned high = c_nECClasses;
140 static bool checkedSort = false;
143 for (unsigned i = 1; i < high; i++) {
144 // Make certain list is sorted!
145 int cmp = strcmp(c_rgECClasses[i].m_szClassName, c_rgECClasses[i-1].m_szClassName);
147 cmp = strcmp(c_rgECClasses[i].m_szNameSpace, c_rgECClasses[i-1].m_szNameSpace);
148 _ASSERTE(cmp > 0 && W("You forgot to keep ECall class names sorted")); // Hey, you forgot to sort the new class
153 unsigned mid = (high + low) / 2;
154 int cmp = strcmp(pszName, c_rgECClasses[mid].m_szClassName);
156 cmp = strcmp(pszNamespace, c_rgECClasses[mid].m_szNameSpace);
170 /*******************************************************************************/
171 /* Finds the implementation for the given method desc. */
173 static INT FindECIndexForMethod(MethodDesc *pMD, const LPVOID* impls)
183 LPCUTF8 szMethodName = pMD->GetName();
184 PCCOR_SIGNATURE pMethodSig;
185 ULONG cbMethodSigLen;
187 pMD->GetSig(&pMethodSig, &cbMethodSigLen);
188 Module* pModule = pMD->GetModule();
190 for (ECFunc* cur = (ECFunc*)impls; !cur->IsEndOfArray(); cur = cur->NextInArray())
192 if (strcmp(cur->m_szMethodName, szMethodName) != 0)
195 if (cur->HasSignature())
197 Signature sig = MscorlibBinder::GetTargetSignature(cur->m_pMethodSig);
199 //@GENERICS: none of these methods belong to generic classes so there is no instantiation info to pass in
200 if (!MetaSig::CompareMethodSigs(pMethodSig, cbMethodSigLen, pModule, NULL,
201 sig.GetRawSig(), sig.GetRawSigLen(), MscorlibBinder::GetModule(), NULL))
207 // We have found a match!
208 return static_cast<INT>((LPVOID*)cur - impls);
214 /*******************************************************************************/
215 /* ID is formed of 2 USHORTs - class index in high word, method index in low word. */
216 /* class index starts at 1. id == 0 means no implementation. */
218 DWORD ECall::GetIDForMethod(MethodDesc *pMD)
228 // We should not go here for NGened methods
229 _ASSERTE(!pMD->IsZapped());
231 INT ImplsIndex = FindImplsIndexForClass(pMD->GetMethodTable());
234 INT ECIndex = FindECIndexForMethod(pMD, c_rgECClasses[ImplsIndex].m_pECFunc);
238 return (ImplsIndex<<16) | (ECIndex + 1);
241 static ECFunc *FindECFuncForID(DWORD id)
243 LIMITED_METHOD_CONTRACT;
248 INT ImplsIndex = (id >> 16);
249 INT ECIndex = (id & 0xffff) - 1;
251 return (ECFunc*)(c_rgECClasses[ImplsIndex].m_pECFunc + ECIndex);
254 static ECFunc* FindECFuncForMethod(MethodDesc* pMD)
261 PRECONDITION(pMD->IsFCall());
265 DWORD id = ((FCallMethodDesc *)pMD)->GetECallID();
268 id = ECall::GetIDForMethod(pMD);
270 CONSISTENCY_CHECK_MSGF(0 != id,
271 ("No method entry found for %s::%s.\n",
272 pMD->m_pszDebugClassName, pMD->m_pszDebugMethodName));
275 ((FCallMethodDesc *)pMD)->SetECallID(id);
278 return FindECFuncForID(id);
281 /*******************************************************************************
282 * Returns 0 if it is an ECALL,
283 * Otherwise returns the native entry point (FCALL)
285 PCODE ECall::GetFCallImpl(MethodDesc * pMD, BOOL * pfSharedOrDynamicFCallImpl /*=NULL*/)
292 PRECONDITION(pMD->IsFCall());
296 MethodTable * pMT = pMD->GetMethodTable();
299 // Delegate constructors are FCalls for which the entrypoint points to the target of the delegate
300 // We have to intercept these and set the call target to the helper COMDelegate::DelegateConstruct
302 if (pMT->IsDelegate())
304 if (pfSharedOrDynamicFCallImpl)
305 *pfSharedOrDynamicFCallImpl = TRUE;
307 // COMDelegate::DelegateConstruct is the only fcall used by user delegates.
308 // All the other gDelegateFuncs are only used by System.Delegate
309 _ASSERTE(pMD->IsCtor());
311 // We need to set up the ECFunc properly. We don't want to use the pMD passed in,
312 // since it may disappear. Instead, use the stable one on Delegate. Remember
313 // that this is 1:M between the FCall and the pMDs.
314 return GetFCallImpl(MscorlibBinder::GetMethod(METHOD__DELEGATE__CONSTRUCT_DELEGATE));
317 #ifdef FEATURE_COMINTEROP
318 // COM imported classes have special constructors
319 if (pMT->IsComObjectType() && pMT != g_pBaseCOMObject && pMT != g_pBaseRuntimeClass)
321 if (pfSharedOrDynamicFCallImpl)
322 *pfSharedOrDynamicFCallImpl = TRUE;
324 // This has to be tlbimp constructor
325 _ASSERTE(pMD->IsCtor());
326 _ASSERTE(!pMT->IsProjectedFromWinRT());
328 // FCComCtor does not need to be in the fcall hashtable since it does not erect frame.
329 return GetEEFuncEntryPoint(FCComCtor);
331 #endif // FEATURE_COMINTEROP
333 if (!pMD->GetModule()->IsSystem())
334 COMPlusThrow(kSecurityException, BFA_ECALLS_MUST_BE_IN_SYS_MOD);
336 ECFunc* ret = FindECFuncForMethod(pMD);
338 // ECall is a set of tables to call functions within the EE from the classlibs.
339 // First we use the class name & namespace to find an array of function pointers for
340 // a class, then use the function name (& sometimes signature) to find the correct
341 // function pointer for your method. Methods in the BCL will be marked as
342 // [MethodImplAttribute(MethodImplOptions.InternalCall)] and extern.
344 // You'll see this assert in several situations, almost all being the fault of whomever
345 // last touched a particular ecall or fcall method, either here or in the classlibs.
346 // However, you must also ensure you don't have stray copies of mscorlib.dll on your machine.
347 // 1) You forgot to add your class to c_rgECClasses, the list of classes w/ ecall & fcall methods.
348 // 2) You forgot to add your particular method to the ECFunc array for your class.
349 // 3) You misspelled the name of your function and/or classname.
350 // 4) The signature of the managed function doesn't match the hardcoded metadata signature
351 // listed in your ECFunc array. The hardcoded metadata sig is only necessary to disambiguate
352 // overloaded ecall functions - usually you can leave it set to NULL.
353 // 5) Your copy of mscorlib.dll & mscoree.dll are out of sync - rebuild both.
354 // 6) You've loaded the wrong copy of mscorlib.dll. In msdev's debug menu,
355 // select the "Modules..." dialog. Verify the path for mscorlib is right.
356 // 7) Someone mucked around with how the signatures in metasig.h are parsed, changing the
357 // interpretation of a part of the signature (this is very rare & extremely unlikely,
358 // but has happened at least once).
360 CONSISTENCY_CHECK_MSGF(ret != NULL,
361 ("Could not find an ECALL entry for %s::%s.\n"
362 "Read comment above this assert in vm/ecall.cpp\n",
363 pMD->m_pszDebugClassName, pMD->m_pszDebugMethodName));
365 CONSISTENCY_CHECK_MSGF(!ret->IsQCall(),
366 ("%s::%s is not registered using FCFuncElement macro in ecall.cpp",
367 pMD->m_pszDebugClassName, pMD->m_pszDebugMethodName));
369 #ifdef CROSSGEN_COMPILE
371 // Use the ECFunc address as a unique fake entrypoint to make the entrypoint<->MethodDesc mapping work
372 PCODE pImplementation = (PCODE)ret;
374 pImplementation |= THUMB_CODE;
377 #else // CROSSGEN_COMPILE
379 PCODE pImplementation = (PCODE)ret->m_pImplementation;
381 int iDynamicID = ret->DynamicID();
382 if (iDynamicID != InvalidDynamicFCallId)
384 if (pfSharedOrDynamicFCallImpl)
385 *pfSharedOrDynamicFCallImpl = TRUE;
387 pImplementation = g_FCDynamicallyAssignedImplementations[iDynamicID];
388 _ASSERTE(pImplementation != NULL);
389 return pImplementation;
392 #endif // CROSSGEN_COMPILE
394 // Insert the implementation into hash table if it is not there already.
396 CrstHolder holder(&gFCallLock);
398 MethodDesc * pMDinTable = ECall::MapTargetBackToMethod(pImplementation, &pImplementation);
400 if (pMDinTable != NULL)
402 if (pMDinTable != pMD)
404 // The fcall entrypoints has to be at unique addresses. If you get failure here, use the following steps
406 // 1. Consider merging the offending fcalls into one fcall. Do they really do different things?
407 // 2. If it does not make sense to merge the offending fcalls into one,
408 // add FCUnique(<a random unique number here>); to one of the offending fcalls.
410 _ASSERTE(!"Duplicate pImplementation entries found in reverse fcall table");
416 ECHash * pEntry = (ECHash *)(PVOID)SystemDomain::GetGlobalLoaderAllocator()->GetHighFrequencyHeap()->AllocMem(S_SIZE_T(sizeof(ECHash)));
418 pEntry->m_pImplementation = pImplementation;
421 if(gLowestFCall > pImplementation)
422 gLowestFCall = pImplementation;
423 if(gHighestFCall < pImplementation)
424 gHighestFCall = pImplementation;
427 ECHash** spot = &gFCallMethods[FCallHash(pImplementation)];
429 if (*spot == 0) { // found end of list
433 spot = &(*spot)->m_pNext;
437 if (pfSharedOrDynamicFCallImpl)
438 *pfSharedOrDynamicFCallImpl = FALSE;
440 _ASSERTE(pImplementation != NULL);
441 return pImplementation;
444 BOOL ECall::IsSharedFCallImpl(PCODE pImpl)
446 LIMITED_METHOD_CONTRACT;
448 PCODE pNativeCode = pImpl;
451 #ifdef FEATURE_COMINTEROP
452 (pNativeCode == GetEEFuncEntryPoint(FCComCtor)) ||
454 (pNativeCode == GetEEFuncEntryPoint(COMDelegate::DelegateConstruct));
457 BOOL ECall::CheckUnusedECalls(SetSHash<DWORD>& usedIDs)
459 STANDARD_VM_CONTRACT;
461 BOOL fUnusedFCallsFound = FALSE;
463 INT num = c_nECClasses;
464 for (INT ImplsIndex=0; ImplsIndex < num; ImplsIndex++)
466 const ECClass * pECClass = c_rgECClasses + ImplsIndex;
468 BOOL fUnreferencedType = TRUE;
469 for (ECFunc* ptr = (ECFunc*)pECClass->m_pECFunc; !ptr->IsEndOfArray(); ptr = ptr->NextInArray())
471 if (ptr->DynamicID() == InvalidDynamicFCallId && !ptr->IsUnreferenced())
473 INT ECIndex = static_cast<INT>((LPVOID*)ptr - pECClass->m_pECFunc);
475 DWORD id = (ImplsIndex<<16) | (ECIndex + 1);
477 if (!usedIDs.Contains(id))
479 printf("CheckMscorlibExtended: Unused ecall found: %s.%s::%s\n", pECClass->m_szNameSpace, c_rgECClasses[ImplsIndex].m_szClassName, ptr->m_szMethodName);
480 fUnusedFCallsFound = TRUE;
484 fUnreferencedType = FALSE;
487 if (fUnreferencedType)
489 printf("CheckMscorlibExtended: Unused type found: %s.%s\n", c_rgECClasses[ImplsIndex].m_szNameSpace, c_rgECClasses[ImplsIndex].m_szClassName);
490 fUnusedFCallsFound = TRUE;
495 return !fUnusedFCallsFound;
499 #if defined(FEATURE_COMINTEROP) && !defined(CROSSGEN_COMPILE)
500 FCIMPL1(VOID, FCComCtor, LPVOID pV)
507 #endif // FEATURE_COMINTEROP && !CROSSGEN_COMPILE
522 gFCallLock.Init(CrstFCall);
524 // It is important to do an explicit increment here instead of just in-place initialization
525 // so that the global optimizer cannot figure out the value and remove the side-effect that
526 // we depend on in FC_INNER_RETURN macros and other places
530 LPVOID ECall::GetQCallImpl(MethodDesc * pMD)
537 PRECONDITION(pMD->IsNDirect());
541 DWORD id = ((NDirectMethodDesc *)pMD)->GetECallID();
544 id = ECall::GetIDForMethod(pMD);
548 ((NDirectMethodDesc *)pMD)->SetECallID(id);
551 ECFunc * cur = FindECFuncForID(id);
554 CONSISTENCY_CHECK_MSGF(cur != NULL,
555 ("%s::%s is not registered in ecall.cpp",
556 pMD->m_pszDebugClassName, pMD->m_pszDebugMethodName));
558 CONSISTENCY_CHECK_MSGF(cur->IsQCall(),
559 ("%s::%s is not registered using QCFuncElement macro in ecall.cpp",
560 pMD->m_pszDebugClassName, pMD->m_pszDebugMethodName));
562 DWORD dwAttrs = pMD->GetAttrs();
563 BOOL fPublicOrProtected = IsMdPublic(dwAttrs) || IsMdFamily(dwAttrs) || IsMdFamORAssem(dwAttrs);
565 // SuppressUnmanagedCodeSecurityAttribute on QCalls suppresses a full demand, but there's still a link demand
566 // for unmanaged code permission. All QCalls should be private or internal and wrapped in a managed method
567 // to suppress this link demand.
568 CONSISTENCY_CHECK_MSGF(!fPublicOrProtected,
569 ("%s::%s has to be private or internal.",
570 pMD->m_pszDebugClassName, pMD->m_pszDebugMethodName));
573 return cur->m_pImplementation;
576 #endif // !DACCESS_COMPILE
578 MethodDesc* ECall::MapTargetBackToMethod(PCODE pTarg, PCODE * ppAdjustedEntryPoint /*=NULL*/)
591 // Searching all of the entries is expensive
592 // and we are often called with pTarg == NULL so
593 // check for this value and early exit.
598 // Could this possibily be an FCall?
599 if ((pTarg < gLowestFCall) || (pTarg > gHighestFCall))
602 ECHash * pECHash = gFCallMethods[FCallHash(pTarg)];
603 while (pECHash != NULL)
605 if (pECHash->m_pImplementation == pTarg)
607 return pECHash->m_pMD;
609 pECHash = pECHash->m_pNext;
614 #ifndef DACCESS_COMPILE
617 CorInfoIntrinsics ECall::GetIntrinsicID(MethodDesc* pMD)
625 PRECONDITION(pMD->IsFCall());
629 MethodTable * pMT = pMD->GetMethodTable();
631 #ifdef FEATURE_COMINTEROP
632 // COM imported classes have special constructors
633 if (pMT->IsComObjectType())
635 // This has to be tlbimp constructor
636 return(CORINFO_INTRINSIC_Illegal);
638 #endif // FEATURE_COMINTEROP
641 // Delegate constructors are FCalls for which the entrypoint points to the target of the delegate
642 // We have to intercept these and set the call target to the helper COMDelegate::DelegateConstruct
644 if (pMT->IsDelegate())
646 // COMDelegate::DelegateConstruct is the only fcall used by user delegates.
647 // All the other gDelegateFuncs are only used by System.Delegate
648 _ASSERTE(pMD->IsCtor());
650 return(CORINFO_INTRINSIC_Illegal);
653 // All intrinsic live in mscorlib.dll (FindECFuncForMethod does not work for non-mscorlib intrinsics)
654 if (!pMD->GetModule()->IsSystem())
656 return(CORINFO_INTRINSIC_Illegal);
659 ECFunc* info = FindECFuncForMethod(pMD);
662 return(CORINFO_INTRINSIC_Illegal);
664 return info->IntrinsicID();
669 void FCallAssert(void*& cache, void* target)
671 STATIC_CONTRACT_NOTHROW;
672 STATIC_CONTRACT_GC_NOTRIGGER;
673 STATIC_CONTRACT_DEBUG_ONLY;
681 // Special case fcalls with 1:N mapping between implementation and methoddesc
683 if (ECall::IsSharedFCallImpl((PCODE)target))
689 MethodDesc* pMD = ECall::MapTargetBackToMethod((PCODE)target);
695 // Slow but only for debugging. This is needed because in some places
696 // we call FCALLs directly from EE code.
698 unsigned num = c_nECClasses;
699 for (unsigned i=0; i < num; i++)
701 for (ECFunc* ptr = (ECFunc*)c_rgECClasses[i].m_pECFunc; !ptr->IsEndOfArray(); ptr = ptr->NextInArray())
703 if (ptr->m_pImplementation == target)
711 // Now check the dynamically assigned table too.
712 for (unsigned i=0; i<ECall::NUM_DYNAMICALLY_ASSIGNED_FCALL_IMPLEMENTATIONS; i++)
714 if (g_FCDynamicallyAssignedImplementations[i] == (PCODE)target)
721 _ASSERTE(!"Could not find FCall implemenation in ECall.cpp");
724 void HCallAssert(void*& cache, void* target)
728 SO_TOLERANT; // STATIC_CONTRACT_DEBUG_ONLY
737 cache = ECall::MapTargetBackToMethod((PCODE)target);
738 _ASSERTE(cache == 0 || "Use FCIMPL for fcalls");
743 #endif // !DACCESS_COMPILE
745 #ifdef DACCESS_COMPILE
747 void ECall::EnumFCallMethods()
750 gLowestFCall.EnumMem();
751 gHighestFCall.EnumMem();
752 gFCallMethods.EnumMem();
754 // save all ECFunc for stackwalks.
755 // TODO: we could be smarter and only save buckets referenced during stackwalks. But we
756 // need that entire bucket so that traversals such as MethodDesc* ECall::MapTargetBackToMethod will work.
757 for (UINT i=0;i<FCALL_HASH_SIZE;i++)
759 ECHash *ecHash = gFCallMethods[i];
762 // If we can't read the target memory, stop immediately so we don't work
764 if (!DacEnumMemoryRegion(dac_cast<TADDR>(ecHash), sizeof(ECHash)))
766 ecHash = ecHash->m_pNext;
769 // Test hook: when testing on debug builds, we want an easy way to test that the while
770 // correctly terminates in the face of ridiculous stuff from the target.
771 if (CLRConfig::GetConfigValue(CLRConfig::INTERNAL_DumpGeneration_IntentionallyCorruptDataFromTarget) == 1)
773 // Force us to struggle on with something bad.
776 ecHash = (ECHash *)(((unsigned char *)&gFCallMethods[i])+1);
779 #endif // defined (_DEBUG)
785 #endif // DACCESS_COMPILE