// // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. // // // Methods to support the implementation of Constrained Execution Regions (CERs). This includes logic to walk the IL of methods to // determine the statically determinable call graph and prepare each submethod (jit, prepopulate generic dictionaries etc., // everything needed to ensure that the runtime won't generate implicit failure points during the execution of said call graph). // // #include "common.h" #include #include #include #include #include #include #ifdef FEATURE_PREJIT #include #endif // Internal debugging support. Would be nice to use the common logging code but we've run out of unique facility codes and the debug // info we spew out is of interest to a limited audience anyhow. #ifdef _DEBUG #define CER_NOISY_PREPARE 0x00000001 #define CER_NOISY_RESTORE 0x00000002 #define CER_NOISY_CONTRACTS 0x00000004 #define CER_NOISY_WARNINGS 0x00000008 #define CER_NOISY_NGEN_STATS 0x00000010 DWORD g_dwCerLogActions = 0xffffffff; DWORD GetCerLoggingOptions() { WRAPPER_NO_CONTRACT; if (g_dwCerLogActions != 0xffffffff) return g_dwCerLogActions; return g_dwCerLogActions = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_CerLogging); } #define CER_LOG(_reason, _msg) do { if (GetCerLoggingOptions() & CER_NOISY_##_reason) printf _msg; } while (false) #else #define CER_LOG(_reason, _msg) #endif // Enumeration used to determine the number of inline data bytes included inside a given IL instruction (except for the case of a // SWITCH instruction, where a dynamic calculation is required). enum { ArgBytes_InlineNone = 0, // no inline args ArgBytes_InlineVar = 2, // local variable (U2 (U1 if Short on)) ArgBytes_InlineI = 4, // an signed integer (I4 (I1 if Short on)) ArgBytes_InlineR = 8, // a real number (R8 (R4 if Short on)) ArgBytes_InlineBrTarget = 4, // branch target (I4 (I1 if Short on)) ArgBytes_InlineI8 = 8, ArgBytes_InlineMethod = 4, // method token (U4) ArgBytes_InlineField = 4, // field token (U4) ArgBytes_InlineType = 4, // type token (U4) ArgBytes_InlineString = 4, // string TOKEN (U4) ArgBytes_InlineSig = 4, // signature tok (U4) ArgBytes_InlineRVA = 4, // ldptr token (U4) ArgBytes_InlineTok = 4, // a meta-data token of unknown type (U4) ArgBytes_InlineSwitch = 4, // count (U4), pcrel1 (U4) .... pcrelN (U4) ArgBytes_ShortInlineVar = 1, ArgBytes_ShortInlineI = 1, ArgBytes_ShortInlineR = 4, ArgBytes_ShortInlineBrTarget = 1 }; // Build an array of argument byte counts as described above by extracting the 'args' field of each entry in opcode.def. #define OPDEF(c, s, pop, push, args, type, l, s1, s2, ctrl) ArgBytes_##args, const BYTE g_rOpArgs[] = { #include }; #undef OPDEF // Global cache of methods and their reliability contract state. PtrHashCache *g_pMethodContractCache = NULL; // Private method forward references. bool IsPcrReference(Module *pModule, mdToken tkMethod); MethodContext *TokenToMethodDesc(Module *pModule, mdToken tokMethod, SigTypeContext *pTypeContext); TypeHandle GetTypeFromMemberDefOrRefOrSpecThrowing(Module *pModule, mdToken tokMethod, SigTypeContext *pTypeContext); bool MethodCallGraphPreparer::ShouldGatherExplicitCERCallInfo() { LIMITED_METHOD_CONTRACT; // If we're partially processing a method body (at the top of the call graph), we need to fetch exception handling // information to determine possible ranges of interesting IL (potentially each finally and catch clause). // // And if we are probing for stack overflow, we need to know if the explicit CER region contains any calls out, in // which case we want to probe in the call to PrepareConstrainedExecutionRegions. This will ensure that we don't // take an SO in boundary code and not be able to call the CER. When stack probing is disabled, we rip the process // if we take an SO anywhere but managed, or if we take an SO with a CER on the stack. For NGEN images, we need // to always probe because stack probing may be enabled in the runtime, but if we haven't probed in the NGEN image // then we could take an SO in boundary code and not be able to crawl the stack to know that we've skipped a CER and // need to tear the process. // // Additionally, if the MDA for illegal PrepareConstrainedRegions call positioning is enabled we gather this information for // all methods in the graph. return !m_fEntireMethod #ifdef MDA_SUPPORTED || MDA_GET_ASSISTANT(IllegalPrepareConstrainedRegion) #endif #ifdef FEATURE_NATIVE_IMAGE_GENERATION || m_fNgen #endif || g_pConfig->ProbeForStackOverflow(); } MethodCallGraphPreparer::MethodCallGraphPreparer(MethodDesc *pRootMD, SigTypeContext *pRootTypeContext, bool fEntireMethod, bool fExactTypeContext, bool fIgnoreVirtualCERCallMDA) { CONTRACTL { STANDARD_VM_CHECK; PRECONDITION(CheckPointer(pRootMD)); PRECONDITION(CheckPointer(pRootTypeContext)); } CONTRACTL_END; // Canonicalize value type unboxing stubs into their underlying method desc. if (pRootMD->IsUnboxingStub()) pRootMD = pRootMD->GetWrappedMethodDesc(); m_pRootMD = pRootMD; m_pRootTypeContext = pRootTypeContext; m_fEntireMethod = fEntireMethod; m_fExactTypeContext = fExactTypeContext; m_fIgnoreVirtualCERCallMDA = fIgnoreVirtualCERCallMDA; m_pEHClauses = NULL; m_cEHClauses = 0; m_pCerPrepInfo = NULL; m_pMethodDecoder = NULL; #ifdef FEATURE_NATIVE_IMAGE_GENERATION m_fNgen = false; #endif m_pThread = GetThread(); m_fPartialPreparation = false; m_fMethodHasCallsWithinExplicitCer = false; } // Walk the call graph of the method given by pRootMD (and type context in pRootTypeContext which provides instantiation information // for generic methods/classes). // // If fEntireMethod is true then the entire body of pRootMD is scanned for callsites, otherwise we assume that one or more CER // exception handlers exist in the method and only the finally and catch blocks of such handlers are scanned for graph roots. // // Each method we come across in the call graph (excluding late bound invocation destinations precipitated by virtual or interface // calls) is jitted and has any generic dictionary information we can determine at jit time prepopulated. This includes implicit // cctor invocations. If this method is called at ngen time we will attach extra fixup information to the affected method to ensure // that fixing up the root method of the graph will cause all methods in the graph to be fixed up at that point also. // // Some generic dictionary entries may not be prepopulated if unbound type variables exist at the root of the call tree. Such cases // will be ignored (as for the virtual/interface dispatch case we assume the caller will use an out-of-band mechanism to pre-prepare // these entries explicitly). bool MethodCallGraphPreparer::Run() { STANDARD_VM_CONTRACT; // Avoid recursion while jitting methods for another preparation. if (!m_pThread->GetCerPreparationState()->CanPreparationProceed(m_pRootMD, m_pRootTypeContext)) return TRUE; // Assume the worst #ifdef FEATURE_NATIVE_IMAGE_GENERATION // Determine if we're being called in order to provide an ngen image. This impacts whether we actually prepare methods and the // type of tracking data we produce. Ideally we'd call GetAppDomain()->IsCompilationDomain() here, but we have to deal with the // problem of ngen'ing mscorlib. Mscorlib code is always shared and some of it is run before the compilation domain is fully // created (so we'd end up with some methods being prepared without saving any ngen metadata to that effect). So instead we // check to see whether this is an ngen process. This will catch those first few mscorlib methods. m_fNgen = IsCompilationProcess() != FALSE; // We keep a hash table of CERs we've processed on the module object of the root method. See if any work has been done on this // CER before. We store different data for ngen and non-ngen cases. if (m_fNgen) { // Pretty simple in ngen case -- if we've stored a context record for this method at all then we've already processed it. if (m_pRootMD->GetModule()->IsNgenCerRootMethod(m_pRootMD)) { m_pCerPrepInfo = m_pRootMD->GetModule()->GetCerPrepInfo(m_pRootMD); // We always store CerPrepInfo if the method has calls, so if we haven't stored // anything then we know it doesn't have any calls return (m_pCerPrepInfo && m_pCerPrepInfo->m_fMethodHasCallsWithinExplicitCer); } } else #endif { // The non-ngen case (normal jit, call to PrepareMethod etc). m_pCerPrepInfo = m_pRootMD->GetModule()->GetCerPrepInfo(m_pRootMD); if (m_pCerPrepInfo) { // Check for the "everything's done" case. if (m_pCerPrepInfo->m_fFullyPrepared) return m_pCerPrepInfo->m_fMethodHasCallsWithinExplicitCer; // Check for the "we can't do anything" case (see below for descriptions of that). if (m_pCerPrepInfo->m_fRequiresInstantiation && !m_fExactTypeContext) return m_pCerPrepInfo->m_fMethodHasCallsWithinExplicitCer; // Check for the "need to prepare per-instantiation, but we've already done this one" case. if (m_pCerPrepInfo->m_fRequiresInstantiation) { HashDatum sDatum; if (m_pCerPrepInfo->m_sIsInitAtInstHash.GetValue(m_pRootTypeContext, &sDatum)) return m_pCerPrepInfo->m_fMethodHasCallsWithinExplicitCer; } } } // We can't deal with generic methods or methods on generic types that may have some representative type parameters in their // instantiation (i.e. some reference types indicated by Object rather than the exact type). The jit will tend to pass us these // since it shares code between instantiations over reference types. We can't prepare methods like these completely -- even // though we can jit all the method bodies the code might require generic dictionary information at the class or method level // that is populated at runtime and can introduce failure points. So we reject such methods immediately (they will need to be // prepared at non-jit time by an explicit call to PrepareMethod with a fully instantiated method). // // In the case where the type context is marked as suspect (m_fExactTypeContext == false) there are a number of possibilites for // bad methods the jit will pass us: // 1) We're passed a MethodDesc that shared between instantiations (bogus because exact method descs are never shared). // 2) We're passed a MethodDesc that's an instantiating stub (bogus because non-shared methods don't need this). // 3) We're passed a MethodDesc that has generic variables in its instantiations (I've seen this during ngen). // // Technically we could do a little better than this -- we could determine whether any of the representative type parameters are // actually used within the CER call graph itself. But this would require us to understand the IL at a much deeper level (i.e. // parse every instruction that could take a type or member spec and pull apart those specs to see if a type var is used). Plus // we couldn't make this determination until we've prepared the entire region and the result is rather brittle from the code // author's point of view (i.e. we might prepare a CER automatically one day but stop doing after some relatively subtle changes // in the source code). m_fPartialPreparation = m_pRootMD->IsSharedByGenericInstantiations() || m_pRootMD->IsInstantiatingStub() || m_pRootMD->ContainsGenericVariables(); if (!m_fExactTypeContext && m_fPartialPreparation) { #ifdef MDA_SUPPORTED MDA_TRIGGER_ASSISTANT(OpenGenericCERCall, ReportViolation(m_pRootMD)); #endif CER_LOG(WARNINGS, ("CER: %s has open type parameters and can't be pre-prepared\n", m_pRootMD->m_pszDebugMethodName)); #ifdef FEATURE_NATIVE_IMAGE_GENERATION if (!m_fNgen) #endif { // Set up a prep info structure for this method if it's not there already (the create method takes care of races). if (m_pCerPrepInfo == NULL) m_pCerPrepInfo = m_pRootMD->GetModule()->CreateCerPrepInfo(m_pRootMD); // We may be racing to update the structure at this point but that's OK since the flag we're setting is never cleared once // it's set and is always guaranteed to be set before we rely on its value (setting it here is just a performance thing, // letting us early-out on multiple attempts to prepare this CER from the jit). m_pCerPrepInfo->m_fRequiresInstantiation = true; } if (! g_pConfig->ProbeForStackOverflow()) { return FALSE; } m_pCerPrepInfo = m_pRootMD->GetModule()->GetCerPrepInfo(m_pRootMD); // We always store CerPrepInfo if the method has calls, so if we haven't stored // anything then we know it doesn't have any calls return (m_pCerPrepInfo && m_pCerPrepInfo->m_fMethodHasCallsWithinExplicitCer); } #ifdef FEATURE_NATIVE_IMAGE_GENERATION // If we've been called for a shared generic root method and the exact instantiation (this can happen because ngen lets code // execute under some circumstances) we don't currently support saving this information in the ngen image (we don't have a // format for the instantiation info). We just ignore the preparation in this case (it will be prepared at runtime). if (m_fNgen && m_fPartialPreparation) return TRUE; #endif // Prevent inlining of the root method (otherwise it's hard to tell where ThreadAbort exceptions should be delayed). Note that // MethodDesc::SetNotInline is thread safe. m_pRootMD->SetNotInline(true); // Remember the checkpoint for all of our allocations. Keep it in a holder so they'll be unwound if we throw an exception past // here. CheckPointHolder sCheckpoint(m_pThread->m_MarshalAlloc.GetCheckpoint()); // Push the current method as the one and only method to process so far. m_sLeftToProcess.Push(MethodContext::PerThreadAllocate(m_pRootMD, m_pRootTypeContext)); MethodContext *pContext = NULL; // The current MethodContext we're processing // Iterate until we run out of methods to process. while ((pContext = m_sLeftToProcess.Pop()) != NULL) { // Restore the MD if necessary. In particular, if this is an instantiating stub and the wrapped MethodDesc could // not be hard bound, then we'll need to restore that pointer before getting it. pContext->m_pMethodDesc->CheckRestore(); // Transfer the method to the already seen stack immediately (we don't want to loop infinitely in the case of method // recursion). m_sAlreadySeen.Push(pContext); // Check if the enclosing class requires a static class constructor to be run. If so, we need to prepare that method as // though it were any other call. if (pContext->m_pMethodDesc->GetMethodTable()->HasClassConstructor()) { // Decode target method into MethodDesc and new SigTypeContext. // The type context is easy to derive here : .cctors never have any method type parameters and the class instantiations // are those of the method we're currently parsing, so can be simply copied down. MethodDesc *pCctor = pContext->m_pMethodDesc->GetCanonicalMethodTable()->GetClassConstructor(); SigTypeContext sCctorTypeContext(pCctor, pContext->m_sTypeContext.m_classInst, Instantiation()); MethodContext *pCctorContext = MethodContext::PerThreadAllocate(pCctor, &sCctorTypeContext); // Only process this cctor the first time we find it in this call graph. if (!m_sAlreadySeen.IsInStack(pCctorContext) && !m_sLeftToProcess.IsInStack(pCctorContext)) m_sLeftToProcess.Push(pCctorContext); } // Skip further processing if this method doesn't have an IL body (note that we assume the method we entered with was IL, so // we don't need to bother with partial method processing). if (!pContext->m_pMethodDesc->IsIL()) { _ASSERTE(m_fEntireMethod); continue; } // Locate the IL body of the current method. May have to account for the fact that the current method desc is an // instantiating stub and burrow down for the real method desc. MethodDesc *pRealMethod = pContext->m_pMethodDesc; if (pRealMethod->IsInstantiatingStub()) { _ASSERTE(!pRealMethod->ContainsGenericVariables()); pRealMethod = pRealMethod->GetWrappedMethodDesc(); } COR_ILMETHOD* pILHeader = pRealMethod->GetILHeader(); // Skip malformed methods. (We should always have method with IL for well-formed images here.) if (pILHeader == NULL) { continue; } COR_ILMETHOD_DECODER method(pILHeader); m_pMethodDecoder = &method; // We want to reget the EH clauses for the current method so that we can scan its handlers GetEHClauses(); LookForInterestingCallsites(pContext); // Whatever we've done, we're definitely not processing the top-level method at this point (so we'll be processing full // method bodies from now on). m_fEntireMethod = true; } #ifdef FEATURE_NATIVE_IMAGE_GENERATION if (!m_fNgen) #endif { // Set up a prep info structure for this method if it's not there already (the create method takes care of races). // This needs to happen before we start JITing the methods as part of preparation. The JIT needs to know // about the CER root in CEEInfo::canTailCall. if (m_pCerPrepInfo == NULL) m_pCerPrepInfo = m_pRootMD->GetModule()->CreateCerPrepInfo(m_pRootMD); } // Prevent infinite recursion by recording on the thread which roots we're currently preparing. ThreadPreparingCerHolder sCerHolder(this); // Once we get here we've run out of methods to process and have recorded each method we visited in the m_sAlreadySeen stack. Now // it's time to prepare each of these methods (jit, prepopulate generic dictionaries etc.). PrepareMethods(); return RecordResults(); } // Determine whether a CER preparation for the given root method (with type context for generic instantiation // if necessary) can go ahead given any current preparation already being performed on the current thread. BOOL MethodCallGraphPreparer::CanPreparationProceed(MethodDesc * pMD, SigTypeContext * pTypeContext) { WRAPPER_NO_CONTRACT; MethodCallGraphPreparer * pCurrPrep = this; while (pCurrPrep) { // Is the prepartion request for the root method of the current preparer? if (pMD == pCurrPrep->m_pRootMD && SigTypeContext::Equal(pTypeContext, pCurrPrep->m_pRootTypeContext)) { // We're already preparing this root, return FALSE to turn the request into a no-op and avoid // infinite recursion. return FALSE; } pCurrPrep = pCurrPrep->m_pNext; } // We found no previous preparation for the same root, so the request can proceed. return TRUE; } // Methods that push and pop thread local state used to determine if a re-entrant preparation request should // complete immediately as a no-op (because it would lead to an infinite recursion) or should proceed // recursively. //static void MethodCallGraphPreparer::BeginPrepareCerForHolder(MethodCallGraphPreparer * pPrepState) { LIMITED_METHOD_CONTRACT; Thread * pThread = pPrepState->m_pThread; pPrepState->m_pNext = pThread->GetCerPreparationState(); pThread->SetCerPreparationState(pPrepState); } //static void MethodCallGraphPreparer::EndPrepareCerForHolder(MethodCallGraphPreparer * pPrepState) { LIMITED_METHOD_CONTRACT; Thread * pThread = pPrepState->m_pThread; _ASSERTE(pThread && pThread->GetCerPreparationState() == pPrepState); pThread->SetCerPreparationState(pPrepState->m_pNext); } void MethodCallGraphPreparer::GetEHClauses() { STANDARD_VM_CONTRACT; if (! ShouldGatherExplicitCERCallInfo()) { return; } m_cEHClauses = 0; m_pEHClauses = NULL; // we use the StackingAllocator, so don't have to delete the previous storage COR_ILMETHOD_SECT_EH const * pEH = m_pMethodDecoder->EH; if (pEH == NULL ||pEH->EHCount() == 0) { return; } m_cEHClauses = pEH->EHCount(); m_pEHClauses = new (&m_pThread->m_MarshalAlloc) EHClauseRange[m_cEHClauses]; for (DWORD i = 0; i < m_cEHClauses; i++) { COR_ILMETHOD_SECT_EH_CLAUSE_FAT sEHClauseBuffer; const COR_ILMETHOD_SECT_EH_CLAUSE_FAT *pEHClause; pEHClause = (COR_ILMETHOD_SECT_EH_CLAUSE_FAT*)pEH->EHClause(i, &sEHClauseBuffer); // The algorithm below assumes handlers are located after their associated try blocks. If this turns out to be a // false assumption we need to move to a two pass technique (or defer callsite handling in some other fashion until // we've scanned the IL for all calls to our preparation marker method). if (!(pEHClause->GetTryOffset() < pEHClause->GetHandlerOffset())) { COMPlusThrowHR(COR_E_NOTSUPPORTED, IDS_EE_NOTSUPPORTED_CATCHBEFORETRY); } m_pEHClauses[i].m_dwTryOffset = pEHClause->GetTryOffset(); m_pEHClauses[i].m_dwHandlerOffset = pEHClause->GetHandlerOffset(); m_pEHClauses[i].m_dwHandlerLength = pEHClause->GetHandlerLength(); m_pEHClauses[i].m_fActive = false; //printf("Try: %u Handler: %u -> %u\n", pEHClause->GetTryOffset(), pEHClause->GetHandlerOffset(), pEHClause->GetHandlerOffset() + pEHClause->GetHandlerLength() - 1); } } void MethodCallGraphPreparer::MarkEHClauseActivatedByCERCall(MethodContext *pContext, BYTE *pbIL, DWORD cbIL) { STANDARD_VM_CONTRACT; DWORD dwOffset = (DWORD)(SIZE_T)((pbIL + ArgBytes_InlineTok) - (BYTE*)m_pMethodDecoder->Code); // Additionally we need to cope with the fact that VB and C# (for debug builds) can generate NOP instructions // between the PCR call and the beginning of the try block. So we're potentially looking for the // intersection of the try with a range of instructions. Count the number of consecutive NOP instructions // which follow the call. DWORD dwLength = 0; BYTE *pbTmpIL = pbIL + ArgBytes_InlineTok; while (pbTmpIL < (pbIL + cbIL) && *pbTmpIL++ == CEE_NOP) { dwLength++; } bool fMatched = false; for (DWORD i = 0; i < m_cEHClauses; i++) { if (m_pEHClauses[i].m_dwTryOffset >= dwOffset && m_pEHClauses[i].m_dwTryOffset <= (dwOffset + dwLength)) { fMatched = true; m_pEHClauses[i].m_fActive = true; } } if (!fMatched) { #if defined(_DEBUG) || defined(MDA_SUPPORTED) DWORD dwPCROffset = (DWORD)(SIZE_T)((pbIL - 1) - (BYTE*)m_pMethodDecoder->Code); #endif // defined(_DEBUG) || defined(MDA_SUPPORTED) #ifdef MDA_SUPPORTED MDA_TRIGGER_ASSISTANT(IllegalPrepareConstrainedRegion, ReportViolation(pContext->m_pMethodDesc, dwPCROffset)); #endif CER_LOG(WARNINGS, ("CER: %s: Invalid call to PrepareConstrainedRegions() at IL +%04X\n", pContext->m_pMethodDesc->m_pszDebugMethodName, dwPCROffset)); } } bool MethodCallGraphPreparer::CheckIfCallsiteWithinCER(DWORD dwOffset) { STANDARD_VM_CONTRACT; //printf("Found: %s at %u\n", pCallTarget->m_pMethodDesc->m_pszDebugMethodName, dwOffset); // Search all the EH regions we know about. for (DWORD i = 0; i < m_cEHClauses; i++) { bool fCallsiteWithinCER = false; if (! m_pEHClauses[i].m_fActive) { // clause not CER-active so skip it continue; } if (dwOffset >= (m_pEHClauses[i].m_dwHandlerOffset + m_pEHClauses[i].m_dwHandlerLength)) { // offset beyond clause, so skip it continue; } if (dwOffset >= m_pEHClauses[i].m_dwTryOffset) { // For stack probing optimization, we care if either the try or the handler has a call. If neither // does, then we can optimize the probe out. m_fMethodHasCallsWithinExplicitCer = true; if (dwOffset >= m_pEHClauses[i].m_dwHandlerOffset) { fCallsiteWithinCER = true; } } // Only terminate if we got a positive result (i.e. the calliste is within a hardened clause). // We can't terminate early in the negative case because the callsite could be nested // in another EH region which may be hardened. if (fCallsiteWithinCER == true) { return true; } } return false; } // Iterate through the body of the method looking for interesting call sites. void MethodCallGraphPreparer::LookForInterestingCallsites(MethodContext *pContext) { STANDARD_VM_CONTRACT; BYTE *pbIL = (BYTE*)m_pMethodDecoder->Code; DWORD cbIL = m_pMethodDecoder->GetCodeSize(); while (cbIL) { // Read the IL op. DWORD dwOp = *pbIL++; cbIL--; // Handle prefix codes (only CEE_PREFIX1 is legal so far). if (dwOp == CEE_PREFIX1) { if (!cbIL) COMPlusThrowHR(COR_E_BADIMAGEFORMAT); dwOp = 256 + *pbIL++; cbIL--; } else if (dwOp >= CEE_PREFIX7) COMPlusThrowHR(COR_E_BADIMAGEFORMAT); // We're interested in NEWOBJ, JMP, CALL and CALLVIRT (can't trace through CALLI). We include CALLVIRT becase C# // routinely calls non-virtual instance methods this way in order to get this pointer null checking. We prepare NEWOBJ // because that covers the corner case of value types which can be constructed with no failure path. if (dwOp == CEE_CALL || dwOp == CEE_CALLVIRT || dwOp == CEE_NEWOBJ || dwOp == CEE_JMP) { if (cbIL < sizeof(DWORD)) COMPlusThrowHR(COR_E_BADIMAGEFORMAT); // Decode target method into MethodDesc and new SigTypeContext. mdToken tkCallTarget = (mdToken)GET_UNALIGNED_VAL32(pbIL); MethodContext *pCallTarget = TokenToMethodDesc(pContext->m_pMethodDesc->GetModule(), tkCallTarget, &pContext->m_sTypeContext); // Check whether we've found a call to our own preparation marker method. if (pCallTarget->m_pMethodDesc == g_pPrepareConstrainedRegionsMethod) { if (ShouldGatherExplicitCERCallInfo()) { // If we're preparing a partial method these callsites are significant (we mark which EH clauses are now // 'activated' by proximity to this marker method call). Look for EH regions that are 'activated' by the call to // PrepareConstrainedRegions by comparing the IL offset of the start of the try to the offset immediately after // the callsite (remember to account for the rest of the CALLVIRT instruction we haven't skipped yet). MarkEHClauseActivatedByCERCall(pContext, pbIL, cbIL); } // Record the fact that we found a method in the CER which is the root of a sub-CER. This is important since the // rude thread abort protection algorithm relies on root CER methods being marked as such. pContext->m_fRoot = true; } // Determine if this was really a virtual call (we discard those since we can't reliably determine the call target). bool fNonVirtualCall = dwOp == CEE_CALL || !pCallTarget->m_pMethodDesc->IsVirtual() || pCallTarget->m_pMethodDesc->IsFinal(); // When we're only processing qualified catch / finally handlers then we need to compute whether this call site // lands in one of them. The callsite is always within a cer if we are processing the full method. // If we have stackoverflow probing on, also call to determine if the CER try or finally makes any calls bool fCallsiteWithinCerInThisFunction = false; if (!m_fEntireMethod || g_pConfig->ProbeForStackOverflow()) { DWORD dwOffset = (DWORD)(SIZE_T)((pbIL - 1) - (BYTE*)m_pMethodDecoder->Code); fCallsiteWithinCerInThisFunction = CheckIfCallsiteWithinCER(dwOffset); } bool fCallsiteWithinCer = m_fEntireMethod || fCallsiteWithinCerInThisFunction; // Check for the presence of some sort of reliability contract (on the method, class or assembly). This will // determine whether we log an error, ignore the method or treat it as part of the prepared call graph. ReliabilityContractLevel eLevel = RCL_UNKNOWN; if (fNonVirtualCall && // Ignore virtual calls fCallsiteWithinCer && // And calls outside CERs !m_sAlreadySeen.IsInStack(pCallTarget) && // And methods we've seen before !m_sLeftToProcess.IsInStack(pCallTarget) && // And methods we've already queued for processing (eLevel = CheckForReliabilityContract(pCallTarget->m_pMethodDesc)) >= RCL_PREPARE_CONTRACT) // And unreliable methods m_sLeftToProcess.Push(pCallTarget); // Otherwise add this method to the list to process else if (fCallsiteWithinCer) { #if defined(_DEBUG) || defined(MDA_SUPPORTED) DWORD dwOffset = (DWORD)(SIZE_T)((pbIL - 1) - (BYTE*)m_pMethodDecoder->Code); #endif // defined(_DEBUG) || defined(MDA_SUPPORTED) if (eLevel == RCL_NO_CONTRACT) { // Method was sufficiently unreliable for us to warn interested users that something may be amiss. Do this // through MDA logging. #ifdef MDA_SUPPORTED MDA_TRIGGER_ASSISTANT(InvalidCERCall, ReportViolation(pContext->m_pMethodDesc, pCallTarget->m_pMethodDesc, dwOffset)); #endif CER_LOG(WARNINGS, ("CER: %s +%04X -> %s: weak contract\n", pContext->ToString(), dwOffset, pCallTarget->ToString())); } else if (!fNonVirtualCall && !m_fIgnoreVirtualCERCallMDA) { // Warn users about virtual calls in CERs (so they can go back and consider which target methods need to be // prepared ahead of time). #ifdef MDA_SUPPORTED MDA_TRIGGER_ASSISTANT(VirtualCERCall, ReportViolation(pContext->m_pMethodDesc, pCallTarget->m_pMethodDesc, dwOffset)); #endif CER_LOG(WARNINGS, ("CER: %s +%04X -> %s: virtual call\n", pContext->ToString(), dwOffset, pCallTarget->ToString())); } } } // Skip the rest of the current IL instruction. Look up the table built statically at the top of this module for most // instructions, but CEE_SWITCH requires special processing (the length of that instruction depends on a count DWORD // embedded right after the opcode). if (dwOp == CEE_SWITCH) { DWORD dwTargets = GET_UNALIGNED_VAL32(pbIL); if (dwTargets >= (MAXDWORD / sizeof(DWORD))) COMPlusThrowHR(COR_E_BADIMAGEFORMAT); // multiplication below would overflow DWORD cbSwitch = (1 + dwTargets) * sizeof(DWORD); if (cbIL < cbSwitch) COMPlusThrowHR(COR_E_BADIMAGEFORMAT); pbIL += cbSwitch; cbIL -= cbSwitch; } else { if (dwOp >= _countof(g_rOpArgs)) COMPlusThrowHR(COR_E_BADIMAGEFORMAT); DWORD cbOp = g_rOpArgs[dwOp]; if (cbIL < cbOp) COMPlusThrowHR(COR_E_BADIMAGEFORMAT); pbIL += cbOp; cbIL -= cbOp; } } // End of IL parsing loop } void MethodCallGraphPreparer::PrepareMethods() { STANDARD_VM_CONTRACT; #ifdef _DEBUG DWORD dwCount = 0; if (GetCerLoggingOptions()) { CER_LOG(PREPARE, ("---------------------------------------------------------------\n")); SString ssMethod; TypeString::AppendMethodInternal(ssMethod, m_pRootMD, TypeString::FormatNamespace | TypeString::FormatStubInfo); CER_LOG(PREPARE, ("Preparing from %S\n", ssMethod.GetUnicode())); } #endif MethodContext *pContext; // The current MethodContext we're processing while ((pContext = m_sAlreadySeen.Pop()) != NULL) { MethodDesc *pMD = pContext->m_pMethodDesc; #ifndef CROSSGEN_COMPILE // Jitting. Don't need to do this for the ngen case. #ifdef FEATURE_NATIVE_IMAGE_GENERATION if (!m_fNgen) #endif { // Also skip the jit for the root method in the activated from jit case (where this would result in a recursive // jit). We'd cope with this just fine, the main reason for this logic is to avoid unbalancing some profiler event // counts that upset some of our test cases. This is safe in the face of multiple instantiations of the same method // because in the jit activated case (where we're told the root type context is not exact) we early exit if the root // method desc isn't 'unique' (i.e. independent of the type context). if (m_fExactTypeContext || pMD != m_pRootMD) { // Jit the method we traced. if (pMD->IsPointingToPrestub()) { pMD->EnsureActive(); pMD->DoPrestub(NULL); } // If we traced an instantiating stub we need to jit the wrapped (real) method as well. if (pMD->IsInstantiatingStub()) { _ASSERTE(!pMD->ContainsGenericVariables()); MethodDesc *pRealMD = pMD->GetWrappedMethodDesc(); if (pRealMD->IsPointingToPrestub()) { pMD->EnsureActive(); pRealMD->DoPrestub(NULL); } } } // Remember sub-CER root methods for further processing in RecordResults. We need to build CerPrepInfo structures for // these just the same as top-level CERs since we may wander in to them by a route that doesn't include the top-level CER // and the thread abort deflection algorithm relies on each CER root method being marked by a CerPrepInfo. Defer this // processing to RecordResults since we aren't guaranteed to have prepared all the methods of the sub-graph at this // point. if (pContext->m_fRoot && pMD != m_pRootMD) m_sPersist.Push(pContext); } #endif // CROSSGEN_COMPILE // Prepare generic dictionaries (both class and method as needed). We do this even in the ngen scenario, trying to get // as many slots filled as possible. By the looks of it, it's possible that not all of these entries will make it across // to runtime (the fixup code seems to give up on some of the more complex entries, not sure of the details). But we'll // do as best we can here to hopefully minimize any real work on the other side. // Don't use the direct PrepopulateDictionary method on MethodTable here, it takes binding considerations into account // (which we don't care about). DictionaryLayout *pClassDictLayout = pMD->GetClass()->GetDictionaryLayout(); if (pClassDictLayout) { // Translate the representative method table we can find from our method desc into an exact instantiation using the // type context we have. MethodTable *pMT = TypeHandle(pMD->GetMethodTable()).Instantiate(pContext->m_sTypeContext.m_classInst).AsMethodTable(); pMT->GetDictionary()->PrepopulateDictionary(NULL, pMT, false); // The dictionary may have overflowed in which case we need to prepopulate the jit's lookup cache as well. PrepopulateGenericHandleCache(pClassDictLayout, NULL, pMT); } // Don't use the direct PrepopulateDictionary method on MethodDesc here, it appears to use a representative class // instantiation (and we have the exact one handy). DictionaryLayout *pMethDictLayout = pMD->GetDictionaryLayout(); if (pMethDictLayout) { pMD->GetMethodDictionary()->PrepopulateDictionary(pMD, NULL, false); // The dictionary may have overflowed in which case we need to prepopulate the jit's lookup cache as well. PrepopulateGenericHandleCache(pMethDictLayout, pMD, NULL); } #ifdef FEATURE_NATIVE_IMAGE_GENERATION // Keep some of the method contexts around for the ngen case (the ones that might still need fixup at runtime). We'll // write them into a persisted data structure in the next step. // @todo: We use a horrible workaround here to get round the fact that while ngen'ing mscorlib we may prepare some of its // methods before we've had a chance to start up the compilation domain (mscorlib code is shared and used by the ngen // process itself). So we can't blindly call NeedsRestore() on an mscorlib method since that code asserts we're in the // compilation domain. Instead, if we're in the ngen process and we're outside the compilation domain we're going to // assume that the method doesn't need restoration. This only affects a handful of methods (six at last count, all to do // with security safe handles or some CERs in remoting). if (m_fNgen) { if (GetAppDomain() == NULL || !GetAppDomain()->IsCompilationDomain() || !(GetAppDomain()->ToCompilationDomain()->canCallNeedsRestore()) || !(GetAppDomain()->ToCompilationDomain()->GetTargetImage()->CanPrerestoreEagerBindToMethodDesc(pMD, NULL))|| pMD->HasClassOrMethodInstantiation() || pMD->IsNDirect() || pMD->IsComPlusCall() || pMD->IsFCall() || pContext->m_fRoot) m_sPersist.Push(pContext); } #endif #ifdef _DEBUG CER_LOG(PREPARE, (" %s\n", pContext->ToString())); dwCount++; #endif } #ifdef _DEBUG CER_LOG(PREPARE, ("Prepared a total of %u methods\n", dwCount)); #ifdef FEATURE_NATIVE_IMAGE_GENERATION if (m_fNgen) CER_LOG(PREPARE, ("Saved data for %u of them in the ngen image\n", m_sPersist.GetCount())); #endif CER_LOG(PREPARE, ("---------------------------------------------------------------\n")); #endif } // Common code used in creating/looking up a CerPrepInfo and initializing/updating it. void InitPrepInfo(MethodDesc *pMD, SigTypeContext *pTypeContext, bool fMethodHasCallsWithinExplicitCer) { CONTRACTL { STANDARD_VM_CHECK; PRECONDITION(CheckPointer(pMD)); } CONTRACTL_END; // Lookup or allocate the CerPrepInfo. CerPrepInfo *pInfo = pMD->GetModule()->CreateCerPrepInfo(pMD); pInfo->m_fMethodHasCallsWithinExplicitCer = fMethodHasCallsWithinExplicitCer; // Work out if this was a partial preparation. bool fPartialPreparation = pMD->IsSharedByGenericInstantiations() || pMD->IsInstantiatingStub() || pMD->ContainsGenericVariables(); // Simple case first: if this isn't a partial preparation (no pesky unbound type vars to worry about), then the method is // now fully prepared. if (!fPartialPreparation) { pInfo->m_fFullyPrepared = true; return; } // Else we know we require per-instantiation initialization. We need to update a hash table to record the preparation we did // in this case, and that requires taking a mutex. We could check that nobody beat us to it first, but that will hardly ever // happen, so it's not really worth it. So just acquire the mutex right away. CrstHolder sHolder(pMD->GetModule()->GetCerCrst()); pInfo->m_fRequiresInstantiation = true; // Add an entry to a hash that records which instantiations we've prep'd for (again, only if someone hasn't beaten us). HashDatum sDatum; if (!pInfo->m_sIsInitAtInstHash.GetValue(pTypeContext, &sDatum)) { pInfo->m_sIsInitAtInstHash.InsertKeyAsValue(pTypeContext); } } bool MethodCallGraphPreparer::RecordResults() { STANDARD_VM_CONTRACT; // Preparation has been successful, record what we've done in a manner consistent with whether we're ngen'ing or running for // real. #ifdef FEATURE_NATIVE_IMAGE_GENERATION // If we're ngen'ing an image we save our progess as a list of method contexts that might need restoration at runtime (since // even with prejitting there are some things that need to be prepared at runtime). This list goes into a per module table (the // module in question being that of the root method in the CER). if (m_fNgen) { // We have the list of MethodContexts ready, but they're in cheap storage that will go away once we exit this method. // Not only do we have to copy them to heap memory, but we also know exactly how many there are. So we can allocate a // single array with a more compact form of MethodContext for each element. We allocate an extra sentinel value for the end // of the list. This means we can store just a pointer to the list without a count (the code that accesses this list cares // about keeping the list heads compact and densely packed and doesn't care about counting the elements in the list). DWORD cContexts = m_sPersist.GetCount(); LoaderHeap *pHeap = m_pRootMD->GetAssembly()->GetLowFrequencyHeap(); MethodContextElement *pContexts = (MethodContextElement*)(void*)pHeap->AllocMem(S_SIZE_T(sizeof(MethodContextElement)) * (S_SIZE_T(cContexts) + S_SIZE_T(1))); DWORD i = 0; MethodContext *pContext; // The current MethodContext we're processing while ((pContext = m_sPersist.Pop()) != NULL) { pContexts[i].m_pMethodDesc.SetValue(pContext->m_pMethodDesc); MethodTable * pExactMT = NULL; if (!pContext->m_sTypeContext.m_classInst.IsEmpty()) { pExactMT = TypeHandle(pContext->m_pMethodDesc->GetMethodTable()).Instantiate(pContext->m_sTypeContext.m_classInst).AsMethodTable(); _ASSERTE(pExactMT->HasInstantiation()); } else { _ASSERTE(!pContext->m_pMethodDesc->GetMethodTable()->HasInstantiation()); } pContexts[i].m_pExactMT.SetValue(pExactMT); i++; // Keep the context round for a little longer if the method in question was the root of a sub-CER. if (pContext->m_fRoot) m_sRootMethods.Push(pContext); } // Write sentinel entry terminating list. _ASSERTE(i == cContexts); // Add list representing this CER to the per-module table (keyed by root method desc). m_pRootMD->GetModule()->AddCerListToRootTable(m_pRootMD, pContexts); // If this did have an call from an explicit PCER range, create a PrepInfo for it so that we can // quickly grab that information later when we jit that method. This allows us to optimize the probe // away if there are no calls from the PCER range. This is an issue when we've prepared a method // as part of a CER call from another method, but haven't ngened that method yet. When we get // around to finally ngening that method, we want to be able to optimize the CER probe out if // we can, but don't want to reprepare the method. if (g_pConfig->ProbeForStackOverflow() && m_fMethodHasCallsWithinExplicitCer) { if (m_pCerPrepInfo == NULL) m_pCerPrepInfo = m_pRootMD->GetModule()->CreateCerPrepInfo(m_pRootMD); m_pCerPrepInfo->m_fMethodHasCallsWithinExplicitCer = TRUE; } // We need to be careful with sub-CERs in the ngen case. In the jit case they're dealt with automatically (preparing a // super-CER always completely prepares a sub-CER). But in the ngen case we potentially need to run further preparation // steps at the point that a CER root is executed for the first time. If the sub-root is encountered before the super-root // then the sub-CER won't have been prepared correctly. // We solve this simply by recursively running this routine over the methods we noted were sub-roots earlier (this list // doesn't include the main root). We could potentially do a little better than this given that we've calculated the // super-graph, but this is complicated somewhat by the fact that we don't retain the graph structure (i.e. we can't extract // sub-graphs easily) and the effort seems wasted just to avoid a little CPU time and stack space just for the ngen creation // scenario. while ((pContext = m_sRootMethods.Pop()) != NULL) { MethodCallGraphPreparer mgcp(pContext->m_pMethodDesc, &pContext->m_sTypeContext, false, false); mgcp.Run(); } return m_fMethodHasCallsWithinExplicitCer; } #endif // FEATURE_NATIVE_IMAGE_GENERATION // This is the runtime (non-ngen case). Record our progress in an info structure placed in a hash table hung off the module // which owns the root method desc in the CER. The methods which create this info structure handle race conditions (to // ensure we don't leak memory or data), but the updates to the info structure itself might not require any serialization // (the values are 'latched' -- recomputation should yield the same result). The exception is any update to a more complex // data fields (lists and hash tables) that require serialization to prevent corruption of the basic data structure. // Process sub-CER roots first. We need to build CerPrepInfo structures for these just as same as top-level CERs since we may // wander in to them by a route that doesn't include the top-level CER and the thread abort deflection algorithm relies on each // CER root method being marked by a CerPrepInfo. MethodContext *pContext; while ((pContext = m_sPersist.Pop()) != NULL) { _ASSERTE(pContext->m_fRoot); // @todo: need to flow fMethodHasCallsWithinExplicitCer information through method contexts. For now just make a // conservative, safe choice. InitPrepInfo(pContext->m_pMethodDesc, &pContext->m_sTypeContext, true); } // Now process the top-level CER. InitPrepInfo(m_pRootMD, m_pRootTypeContext, m_fMethodHasCallsWithinExplicitCer); return m_fMethodHasCallsWithinExplicitCer; } // Determines whether the given method contains a CER root that can be pre-prepared (i.e. prepared at jit time). bool ContainsPrePreparableCerRoot(MethodDesc *pMD) { CONTRACTL { STANDARD_VM_CHECK; PRECONDITION(CheckPointer(pMD)); } CONTRACTL_END; // Deal with exotic cases (non-IL methods and the like). if (!pMD->IsIL() || pMD->IsAbstract()) return false; // And cases where we can't jit prepare (because the code is shared between instantiations). if (pMD->IsSharedByGenericInstantiations() || pMD->IsInstantiatingStub() || pMD->ContainsGenericVariables()) return false; // Otherwise we have a trickier calculation. We don't want to force the jit of the method at this point (may cause infinite // recursion problems when we're called from the jit in the presence of call cycles). Instead we walk the top-level of the // method IL using the same algorithm as PrepareMethodCallGraph. // Locate the IL body of the current method. May have to account for the fact that the current method desc is an // instantiating stub and burrow down for the real method desc. MethodDesc *pRealMethod = pMD; if (pRealMethod->IsInstantiatingStub()) { _ASSERTE(!pRealMethod->ContainsGenericVariables()); pRealMethod = pRealMethod->GetWrappedMethodDesc(); } COR_ILMETHOD_DECODER method(pRealMethod->GetILHeader()); BYTE *pbIL = (BYTE*)method.Code; DWORD cbIL = method.GetCodeSize(); // Look for exception handling information for the method. If there isn't any then we know there can't be a CER rooted here. COR_ILMETHOD_SECT_EH const * pEH = method.EH; if (pEH == NULL || pEH->EHCount() == 0) return false; // Iterate through the body of the method looking for interesting call sites. while (cbIL) { // Read the IL op. DWORD dwOp = *pbIL++; cbIL--; // Handle prefix codes (only CEE_PREFIX1 is legal so far). if (dwOp == CEE_PREFIX1) { if (!cbIL) COMPlusThrowHR(COR_E_BADIMAGEFORMAT); dwOp = 256 + *pbIL++; cbIL--; if (dwOp >= CEE_ILLEGAL) COMPlusThrowHR(COR_E_BADIMAGEFORMAT); } else if (dwOp >= CEE_PREFIX7) COMPlusThrowHR(COR_E_BADIMAGEFORMAT); // We'll only ever see CALL instructions targeting PrepareConstrainedRegions (well those are the ones we're interested in // anyway). if (dwOp == CEE_CALL) { if (cbIL < sizeof(DWORD)) COMPlusThrowHR(COR_E_BADIMAGEFORMAT); if (IsPcrReference(pMD->GetModule(), (mdToken)GET_UNALIGNED_VAL32(pbIL))) return true; } // Skip the rest of the current IL instruction. Look up the table built statically at the top of this module for most // instructions, but CEE_SWITCH requires special processing (the length of that instruction depends on a count DWORD // embedded right after the opcode). if (dwOp == CEE_SWITCH) { if (cbIL < sizeof(DWORD)) COMPlusThrowHR(COR_E_BADIMAGEFORMAT); DWORD dwTargets = GET_UNALIGNED_VAL32(pbIL); if (dwTargets >= (MAXDWORD / sizeof(DWORD))) COMPlusThrowHR(COR_E_BADIMAGEFORMAT); // multiplication below would overflow DWORD cbSwitch = (1 + dwTargets) * sizeof(DWORD); if (cbIL < cbSwitch) COMPlusThrowHR(COR_E_BADIMAGEFORMAT); pbIL += cbSwitch; cbIL -= cbSwitch; } else { if (dwOp >= _countof(g_rOpArgs)) COMPlusThrowHR(COR_E_BADIMAGEFORMAT); DWORD cbOp = g_rOpArgs[dwOp]; if (cbIL < cbOp) COMPlusThrowHR(COR_E_BADIMAGEFORMAT); pbIL += cbOp; cbIL -= cbOp; } } // End of IL parsing loop // If we get here then there was no CER-root. return false; } // The name of the PrepareConstrainedRegions method, broken down into its components (the code below scans for these directly in the // metadata). #define PCR_METHOD "PrepareConstrainedRegions" #define PCR_TYPE "RuntimeHelpers" #define PCR_NAMESPACE "System.Runtime.CompilerServices" // Given a token and a module scoping it, determine if that token is a reference to PrepareConstrainedRegions. We want to do this // without loading any random types since we're called in a context where type loading is prohibited. bool IsPcrReference(Module *pModule, mdToken tkMethod) { CONTRACTL { STANDARD_VM_CHECK; PRECONDITION(CheckPointer(pModule)); } CONTRACTL_END; IMDInternalImport *pImport = pModule->GetMDImport(); // Validate that the token is one that we can handle. if (!pImport->IsValidToken(tkMethod) || (TypeFromToken(tkMethod) != mdtMethodDef && TypeFromToken(tkMethod) != mdtMethodSpec && TypeFromToken(tkMethod) != mdtMemberRef)) COMPlusThrowHR(COR_E_BADIMAGEFORMAT, BFA_INVALID_METHOD_TOKEN); // No reason to see a method spec for a call to something as simple as PrepareConstrainedRegions. if (TypeFromToken(tkMethod) == mdtMethodSpec) return false; // If it's a method def then the module had better be mscorlib. if (TypeFromToken(tkMethod) == mdtMethodDef) { if (pModule->GetAssembly()->GetManifestModule() == SystemDomain::SystemAssembly()->GetManifestModule()) return tkMethod == g_pPrepareConstrainedRegionsMethod->GetMemberDef(); else return false; } // That leaves the cross module reference case. _ASSERTE(TypeFromToken(tkMethod) == mdtMemberRef); // First get the method name and signature and validate it. PCCOR_SIGNATURE pSig; DWORD cbSig; LPCSTR szMethod; IfFailThrow(pImport->GetNameAndSigOfMemberRef(tkMethod, &pSig, &cbSig, &szMethod)); { SigParser sig(pSig, cbSig); ULONG nCallingConvention; ULONG nArgumentsCount; BYTE bReturnType; // Signature is easy: void PCR(). // Must be a static method signature. if (FAILED(sig.GetCallingConvInfo(&nCallingConvention))) return false; if (nCallingConvention != IMAGE_CEE_CS_CALLCONV_DEFAULT) return false; // With no arguments. if (FAILED(sig.GetData(&nArgumentsCount))) return false; if (nArgumentsCount != 0) return false; // And a void return type. if (FAILED(sig.GetByte(&bReturnType))) return false; if (bReturnType != (BYTE)ELEMENT_TYPE_VOID) return false; } // Validate the name. if (strcmp(szMethod, PCR_METHOD) != 0) return false; // The method looks OK, move up to the type and validate that. mdToken tkType; IfFailThrow(pImport->GetParentOfMemberRef(tkMethod, &tkType)); if (!pImport->IsValidToken(tkType)) COMPlusThrowHR(COR_E_BADIMAGEFORMAT, BFA_INVALID_TOKEN); // If the parent is not a type ref then this isn't our target (we assume that mscorlib never uses a member ref to target // PrepareConstrainedRegions, check that with the assert below, if it ever fails we need to add some additional logic below). _ASSERTE(TypeFromToken(tkType) != mdtTypeDef || pModule->GetAssembly()->GetManifestModule() != SystemDomain::SystemAssembly()->GetManifestModule()); if (TypeFromToken(tkType) != mdtTypeRef) return false; // Get the type name and validate it. LPCSTR szNamespace; LPCSTR szType; IfFailThrow(pImport->GetNameOfTypeRef(tkType, &szNamespace, &szType)); if (strcmp(szType, PCR_TYPE) != 0) return false; if (strcmp(szNamespace, PCR_NAMESPACE) != 0) return false; // Type is OK as well. Check the assembly reference. mdToken tkScope; IfFailThrow(pImport->GetResolutionScopeOfTypeRef(tkType, &tkScope)); if (TypeFromToken(tkScope) != mdtAssemblyRef) return false; if (!pImport->IsValidToken(tkScope)) { COMPlusThrowHR(COR_E_BADIMAGEFORMAT, BFA_INVALID_TOKEN); } // Fetch the name and public key or public key token. BYTE *pbPublicKeyOrToken; DWORD cbPublicKeyOrToken; LPCSTR szAssembly; DWORD dwAssemblyFlags; IfFailThrow(pImport->GetAssemblyRefProps( tkScope, (const void**)&pbPublicKeyOrToken, &cbPublicKeyOrToken, &szAssembly, NULL, // AssemblyMetaDataInternal: we don't care about version, culture etc. NULL, // Hash value pointer, obsolete information NULL, // Byte count for above &dwAssemblyFlags)); // Validate the name. if (stricmpUTF8(szAssembly, g_psBaseLibraryName) != 0) return false; // And the public key or token, which ever was burned into the reference by the compiler. For mscorlib this is the ECMA key or // token. if (IsAfPublicKeyToken(dwAssemblyFlags)) { if (cbPublicKeyOrToken != sizeof(g_rbNeutralPublicKeyToken) || memcmp(pbPublicKeyOrToken, g_rbNeutralPublicKeyToken, cbPublicKeyOrToken) != 0) return false; } else { if (cbPublicKeyOrToken != sizeof(g_rbNeutralPublicKey) || memcmp(pbPublicKeyOrToken, g_rbNeutralPublicKey, cbPublicKeyOrToken) != 0) return false; } // If we get here we've finally proved the call target was indeed PrepareConstrainedRegions. Whew. return true; } // Prepares a method as a CER root. In some scenarios we set // fIgnoreVirtualCERCallMDA=TRUE, this happens when we want to ignore firing a // VirtualCERCall MDA because we know for sure that the virtual methods are // already prepared. A good example of this case is preparing // g_pExecuteBackoutCodeHelperMethod method. void PrepareMethodDesc(MethodDesc* pMD, Instantiation classInst, Instantiation methodInst, BOOL onlyContractedMethod, BOOL fIgnoreVirtualCERCallMDA) { CONTRACTL { THROWS; DISABLED(GC_TRIGGERS); MODE_ANY; } CONTRACTL_END; GCX_PREEMP(); #ifdef FEATURE_PREJIT // This method may have some ngen fixup information provided, in which case we just check that it's been restored and can // dispense with the preparation altogether. Module *pModule = pMD->GetModule(); if (pModule->IsNgenCerRootMethod(pMD)) { pMD->CheckRestore(); pModule->RestoreCer(pMD); return; } #endif // If we are only going to prepare contracted methods and this method does // not have a contract then we just return. if (onlyContractedMethod && CheckForReliabilityContract(pMD) < RCL_BASIC_CONTRACT) { return; } SigTypeContext sTypeContext(pMD, classInst, methodInst); MethodCallGraphPreparer mcgp(pMD, &sTypeContext, true, true, fIgnoreVirtualCERCallMDA == TRUE); mcgp.Run(); } // Prepares the critical finalizer call graph for the given object type (which // must derive from CriticalFinalizerObject). This involves preparing at least // the finalizer method and possibly some others (for SafeHandle and // CriticalHandle derivations). If a module pointer is supplied then only the // critical methods introduced in that module are prepared (this is used at // ngen time to ensure that we're only generating ngen preparation info for the // targetted module). void PrepareCriticalFinalizerObject(MethodTable *pMT, Module *pModule) { CONTRACTL { THROWS; GC_TRIGGERS; MODE_ANY; PRECONDITION(CheckPointer(pMT)); } CONTRACTL_END; // Have we prepared this type before? if (pMT->CriticalTypeHasBeenPrepared()) return; GCX_PREEMP(); // Restore the method table if necessary. pMT->CheckRestore(); // Determine the interesting parent class (either SafeHandle, CriticalHandle or CriticalFinalizerObject). MethodTable *pSafeHandleClass = MscorlibBinder::GetClass(CLASS__SAFE_HANDLE); MethodTable *pCriticalHandleClass = MscorlibBinder::GetClass(CLASS__CRITICAL_HANDLE); MethodTable *pParent = pMT; while (pParent) { if (pParent == g_pCriticalFinalizerObjectClass || pParent == pSafeHandleClass || pParent == pCriticalHandleClass) { break; } pParent = pParent->GetParentMethodTable(); } _ASSERTE(pParent != NULL); BinderMethodID rgMethods[5]; int nMethods; // Prepare the method or methods based on the parent class. if (pParent == pSafeHandleClass) { rgMethods[0] = METHOD__CRITICAL_FINALIZER_OBJECT__FINALIZE; rgMethods[1] = METHOD__SAFE_HANDLE__RELEASE_HANDLE; rgMethods[2] = METHOD__SAFE_HANDLE__GET_IS_INVALID; rgMethods[3] = METHOD__SAFE_HANDLE__DISPOSE; rgMethods[4] = METHOD__SAFE_HANDLE__DISPOSE_BOOL; nMethods = 5; } else if (pParent == pCriticalHandleClass) { rgMethods[0] = METHOD__CRITICAL_FINALIZER_OBJECT__FINALIZE; rgMethods[1] = METHOD__CRITICAL_HANDLE__RELEASE_HANDLE; rgMethods[2] = METHOD__CRITICAL_HANDLE__GET_IS_INVALID; rgMethods[3] = METHOD__CRITICAL_HANDLE__DISPOSE; rgMethods[4] = METHOD__CRITICAL_HANDLE__DISPOSE_BOOL; nMethods = 5; } else { _ASSERTE(pParent == g_pCriticalFinalizerObjectClass); rgMethods[0] = METHOD__CRITICAL_FINALIZER_OBJECT__FINALIZE; nMethods = 1; } for (int iMethod = 0; iMethod < nMethods; iMethod++) { // Prepare a (possibly virtual) method on an instance. The method is identified via a binder ID, so the initial // declaration of the method must reside within mscorlib. We might have ngen fixup information for the method and can avoid direct // preparation as well. MethodDesc *pPrepMethod = pMT->GetMethodDescForSlot(MscorlibBinder::GetMethod(rgMethods[iMethod])->GetSlot()); #ifdef FEATURE_PREJIT if (pPrepMethod->GetModule()->IsNgenCerRootMethod(pPrepMethod)) { pPrepMethod->GetModule()->RestoreCer(pPrepMethod); } else if (IsCompilationProcess() && pPrepMethod->IsAbstract()) { // Skip abstract methods during NGen (we should not ever get abstract methods here at runtime) } else #endif { if (pModule == NULL || pPrepMethod->GetModule() == pModule) { SigTypeContext _sTypeContext(pPrepMethod, TypeHandle(pMT)); MethodCallGraphPreparer mcgp(pPrepMethod, &_sTypeContext, true, true); mcgp.Run(); } } } // Note the fact that we've prepared this type before to prevent repetition of the work above. (Though repetition is harmless in // all other respects, so there's no need to worry about the race setting this flag). pMT->SetCriticalTypeHasBeenPrepared(); } #ifdef _DEBUG static const char * const g_rszContractNames[] = { "RCL_NO_CONTRACT", "RCL_BASIC_CONTRACT", "RCL_PREPARE_CONTRACT" }; static DWORD g_dwContractChecks = 0; #define ReturnContractLevel(_level) do { \ g_dwContractChecks++; \ if ((g_dwContractChecks % 100) == 0 && g_pMethodContractCache) \ g_pMethodContractCache->DbgDumpStats(); \ ReliabilityContractLevel __level = (_level); \ CER_LOG(CONTRACTS, ("%s -- %s\n", pMD->m_pszDebugMethodName, g_rszContractNames[__level])); \ return __level; \ } while (false) #else #define ReturnContractLevel(_level) return (_level) #endif // Look for reliability contracts at the method, class and assembly level and parse them to extract the information we're interested // in from a runtime preparation viewpoint. This information is abstracted in the form of the ReliabilityContractLevel enumeration. ReliabilityContractLevel CheckForReliabilityContract(MethodDesc *pMD) { CONTRACTL { NOTHROW; GC_NOTRIGGER; MODE_ANY; PRECONDITION(CheckPointer(pMD)); } CONTRACTL_END; // We are attempting to abstract reliability contracts for the given method into three different buckets: those methods that // will cause an error (or a MDA report at least) during preparation (RCL_NO_CONTRACT), those we allow but don't prepare // (RCL_BASIC_CONTRACT) and those we allow and prepare (RCL_PREPARE_CONTRACT). // // We place methods into the first bucket below that matches: // RCL_NO_CONTRACT -- Methods with no consistency or whose consistency states they may corrupt the appdomain or process. // RCL_BASIC_CONTRACT -- Methods that state CER.None (or don't specify a CER attribute) // RCL_PREPARE_CONTRACT -- Methods that state CER.MayFail or CER.Success // // We look for reliability contracts at three levels: method, class and assembly. Definitions found at the method level override // those at the class and assembly level and those at the class level override assembly settings. // // In the interests of efficiency we cache contract information in a number of ways. Firstly we look at a hash of recently // queried MethodDescs. This contains authoritative answers (assembly/class/method information has already been composed so on a // hit we don't need to look anywhere else). This cache is allocated lazily, never grows (newer items eventually displace older // ones), is global, requires no locks and is never freed. The idea is to limit the amount of working set we ever occupy while // keeping the CPU usage as low as possible. Typical usages of this method involve querying a small number of methods in a stack // walk, possibly multiple times, so a small hash cache should work reasonably well here. // // On a miss we're going to have to bite the bullet and look at the assembly, class and method. The assembly and class cache // this information at load (ngen) time though, so they're not so expensive (class level data is cached on the EEClass, so it's // cold data, but the most performance sensitive scenario in which we're called here, ThreadAbort, isn't all that hot). // Check the cache first, it contains a raw contract level. ReliabilityContractLevel eLevel; if (g_pMethodContractCache && g_pMethodContractCache->Lookup(pMD, (DWORD*)&eLevel)) ReturnContractLevel(eLevel); // Start at the method level and work up until we've found enough information to make a decision. The contract level is composed // in an encoded DWORD form that lets us track both parts of the state (consistency and cer) and whether each has been supplied // yet. See the RC_* macros for encoding details. DWORD dwMethodContractInfo = GetReliabilityContract(pMD->GetMDImport(), pMD->GetMemberDef()); if (RC_INCOMPLETE(dwMethodContractInfo)) { DWORD dwClassContractInfo = pMD->GetClass()->GetReliabilityContract(); dwMethodContractInfo = RC_MERGE(dwMethodContractInfo, dwClassContractInfo); if (RC_INCOMPLETE(dwMethodContractInfo)) { DWORD dwAssemblyContractInfo = pMD->GetModule()->GetReliabilityContract(); dwMethodContractInfo = RC_MERGE(dwMethodContractInfo, dwAssemblyContractInfo); } } // We've got an answer, so attempt to cache it for the next time. // First check we have a cache (we allocate it lazily). if (g_pMethodContractCache == NULL) { PtrHashCache *pCache = new (nothrow) PtrHashCache(); if (pCache) if (FastInterlockCompareExchangePointer(&g_pMethodContractCache, pCache, NULL) != NULL) delete pCache; } // We still might not have a cache in low memory situations. That's OK. if (g_pMethodContractCache) g_pMethodContractCache->Add(pMD, RC_ENCODED_TO_LEVEL(dwMethodContractInfo)); ReturnContractLevel(RC_ENCODED_TO_LEVEL(dwMethodContractInfo)); } // Macro used to handle failures in the routine below. #define IfFailRetRcNull(_hr) do { if (FAILED(_hr)) return RC_NULL; } while (false) // Look for a reliability contract attached to the given metadata token in the given scope. Return the result as an encoded DWORD // (see the RC_ENCODE macro). DWORD GetReliabilityContract(IMDInternalImport *pImport, mdToken tkParent) { CONTRACTL { NOTHROW; GC_NOTRIGGER; MODE_ANY; PRECONDITION(CheckPointer(pImport)); } CONTRACTL_END; HRESULT hr; DWORD dwResult = RC_NULL; // Sadly we only have two unmanaged APIs available to us for looking at custom attributes. One looks up attributes by name but // only returns the byte blob, not the attribute ctor information (which we need to parse the blob) while the other returns // everything but requires us to enumerate all attributes on a given token looking for the one we're interested in. To keep the // cost down we probe for the existence of the attribute using the first API and then use the enumeration method if we get a // hit. hr = pImport->GetCustomAttributeByName(tkParent, RELIABILITY_CONTRACT_NAME, NULL, NULL); if (hr == S_FALSE) return RC_NULL; IfFailRetRcNull(hr); // Got at least one contract against this parent. Enumerate them all (filtering by name). MDEnumHolder hEnum(pImport); hr = pImport->SafeAndSlowEnumCustomAttributeByNameInit(tkParent, RELIABILITY_CONTRACT_NAME, &hEnum); _ASSERTE(hr != S_FALSE); IfFailRetRcNull(hr); // Enumerate over all the contracts. mdToken tkContract; while (S_OK == pImport->SafeAndSlowEnumCustomAttributeByNameNext(tkParent, RELIABILITY_CONTRACT_NAME, &hEnum, &tkContract)) { // Get the attribute type (token of the ctor used) since we need this information in order to parse the blob we'll find // next. mdToken tkAttrType; IfFailRetRcNull(pImport->GetCustomAttributeProps(tkContract, &tkAttrType)); if (!pImport->IsValidToken(tkAttrType)) continue; // The token should be a member ref or method def. // Get the signature of the ctor so we know which version has been called. PCCOR_SIGNATURE pSig; DWORD cbSig; LPCSTR szName_Ignore; if (TypeFromToken(tkAttrType) == mdtMemberRef) { IfFailRetRcNull(pImport->GetNameAndSigOfMemberRef(tkAttrType, &pSig, &cbSig, &szName_Ignore)); } else { if (TypeFromToken(tkAttrType) != mdtMethodDef) continue; IfFailRetRcNull(pImport->GetNameAndSigOfMethodDef(tkAttrType, &pSig, &cbSig, &szName_Ignore)); } // Only two signatures are supported: the null sig '()' and the full sig '(Consistency, CER)'. // Set a boolean based on which one was provided. bool fNullCtor; ULONG eCallConv; SigPointer sig(pSig, cbSig); // Check the calling convention is what we expect (default convention on an instance method). IfFailRetRcNull(sig.GetCallingConvInfo(&eCallConv)); _ASSERTE(eCallConv == (IMAGE_CEE_CS_CALLCONV_DEFAULT | IMAGE_CEE_CS_CALLCONV_HASTHIS)); if (eCallConv != (IMAGE_CEE_CS_CALLCONV_DEFAULT | IMAGE_CEE_CS_CALLCONV_HASTHIS)) IfFailRetRcNull(COR_E_BADIMAGEFORMAT); // If so, the next datum is the count of arguments, and this is all we need to determine which ctor has been used. ULONG dwArgs; IfFailRetRcNull(sig.GetData(&dwArgs)); _ASSERTE(dwArgs == 0 || dwArgs == 2); if (dwArgs != 0 && dwArgs != 2) IfFailRetRcNull(COR_E_BADIMAGEFORMAT); fNullCtor = dwArgs == 0; // Now we know how to parse the blob, let's fetch a pointer to it. BYTE const *pbData; DWORD cbData; IfFailRetRcNull(pImport->GetCustomAttributeAsBlob(tkContract, (const void **)&pbData, &cbData)); // Check serialization format (we support version 1 only). if (cbData < sizeof(WORD) || GET_UNALIGNED_VAL16(pbData) != 1) IfFailRetRcNull(COR_E_BADIMAGEFORMAT); pbData += sizeof(WORD); cbData -= sizeof(WORD); // Parse ctor arguments if we have any. if (!fNullCtor) { // We assume the enums are based on DWORDS. if (cbData < (sizeof(DWORD) * 2)) IfFailRetRcNull(COR_E_BADIMAGEFORMAT); // Consistency first. DWORD dwConsistency = GET_UNALIGNED_VAL32(pbData); pbData += sizeof(DWORD); cbData -= sizeof(DWORD); if (dwConsistency > RC_CONSISTENCY_CORRUPT_NOTHING) IfFailRetRcNull(COR_E_BADIMAGEFORMAT); // Followed by Cer. DWORD dwCer = GET_UNALIGNED_VAL32(pbData); pbData += sizeof(DWORD); cbData -= sizeof(DWORD); if (dwCer > RC_CER_SUCCESS) IfFailRetRcNull(COR_E_BADIMAGEFORMAT); dwResult = RC_MERGE(dwResult, RC_ENCODE(dwConsistency, dwCer)); } // Get the count of field/property, value pairs. if (cbData < sizeof(WORD)) IfFailRetRcNull(COR_E_BADIMAGEFORMAT); WORD cPairs = GET_UNALIGNED_VAL16(pbData); pbData += sizeof(WORD); cbData -= sizeof(WORD); // Iterate over any such pairs, looking for values we haven't picked up yet. for (DWORD i = 0 ; i < cPairs; i++) { // First is a field vs property selector. We expect only properties. if (cbData < sizeof(BYTE) || *(BYTE*)pbData != SERIALIZATION_TYPE_PROPERTY) IfFailRetRcNull(COR_E_BADIMAGEFORMAT); pbData += sizeof(BYTE); cbData -= sizeof(BYTE); // Next is the type of the property. It had better be an enum. if (cbData < sizeof(BYTE) || *(BYTE*)pbData != SERIALIZATION_TYPE_ENUM) IfFailRetRcNull(COR_E_BADIMAGEFORMAT); pbData += sizeof(BYTE); cbData -= sizeof(BYTE); // Next we have the assembly qualified enum type name. This is preceded by a metadata style packed byte length (the // string itself is 8-bit and not null terminated). Ignore it (just skip across) and we'll key off the property name // (coming up) instead. DWORD cbName; BYTE const * pbPostEncodedLength; IfFailRetRcNull(CPackedLen::SafeGetData(pbData, cbData, &cbName, &pbPostEncodedLength)); DWORD cbEncodedLength = static_cast(pbPostEncodedLength - pbData); pbData += cbEncodedLength + cbName; cbData -= cbEncodedLength + cbName; // Now we have the name of the property (in a similar format to above). IfFailRetRcNull(CPackedLen::SafeGetData(pbData, cbData, &cbName, &pbPostEncodedLength)); cbEncodedLength = static_cast(pbPostEncodedLength - pbData); pbData += cbEncodedLength; cbData -= cbEncodedLength; bool fConsistencyProp = false; if (cbName == strlen(RC_CONSISTENCY_PROP_NAME) && strncmp((const char*)pbData, RC_CONSISTENCY_PROP_NAME, cbName) == 0) fConsistencyProp = true; else if (cbName == strlen(RC_CER_PROP_NAME) && strncmp((const char*)pbData, RC_CER_PROP_NAME, cbName) == 0) fConsistencyProp = false; else IfFailRetRcNull(COR_E_BADIMAGEFORMAT); pbData += cbName; cbData -= cbName; // And finally the actual enum value (again, we assume the underlying type is a DWORD). if (cbData < sizeof(DWORD)) IfFailRetRcNull(COR_E_BADIMAGEFORMAT); DWORD dwValue = GET_UNALIGNED_VAL32(pbData); pbData += sizeof(DWORD); cbData -= sizeof(DWORD); if (fConsistencyProp) { if (dwValue > RC_CONSISTENCY_CORRUPT_NOTHING) IfFailRetRcNull(COR_E_BADIMAGEFORMAT); dwResult = RC_MERGE(dwResult, RC_ENCODE(dwValue, RC_CER_UNDEFINED)); } else { if (dwValue > RC_CER_SUCCESS) IfFailRetRcNull(COR_E_BADIMAGEFORMAT); dwResult = RC_MERGE(dwResult, RC_ENCODE(RC_CONSISTENCY_UNDEFINED, dwValue)); } } // Shouldn't have any bytes left in the blob at this stage. _ASSERTE(cbData == 0); } // Return the result encoded and packed into the 2 low order bits of a DWORD. return dwResult; } // Given a metadata token, a scoping module and a type context, look up the appropriate MethodDesc (and recomputed accompanying type // context). MethodContext *TokenToMethodDesc(Module *pModule, mdToken tokMethod, SigTypeContext *pTypeContext) { STANDARD_VM_CONTRACT; // Validate that the token is one that we can handle. if (!pModule->GetMDImport()->IsValidToken(tokMethod) || (TypeFromToken(tokMethod) != mdtMethodDef && TypeFromToken(tokMethod) != mdtMethodSpec && TypeFromToken(tokMethod) != mdtMemberRef)) { COMPlusThrowHR(COR_E_BADIMAGEFORMAT, BFA_INVALID_METHOD_TOKEN); } // Look up the MethodDesc based on the token and type context. MethodDesc *pMD = MemberLoader::GetMethodDescFromMemberDefOrRefOrSpec(pModule, tokMethod, pTypeContext, TRUE, FALSE); // The MethodDesc we get might be shared between several types. If so we'll need to do extra work to locate the exact // class instantiation instead of the default representative one. SigTypeContext sNewTypeContext; if (pMD->IsSharedByGenericInstantiations()) { TypeHandle th = GetTypeFromMemberDefOrRefOrSpecThrowing(pModule, tokMethod, pTypeContext); SigTypeContext::InitTypeContext(pMD, th,&sNewTypeContext); } else SigTypeContext::InitTypeContext(pMD, pMD->GetClassInstantiation(), pMD->GetMethodInstantiation(),&sNewTypeContext); return MethodContext::PerThreadAllocate(pMD, &sNewTypeContext); } // Locate an exact type definition given a method token and the type context in which it can be resolved. TypeHandle GetTypeFromMemberDefOrRefOrSpecThrowing(Module *pModule, mdToken tokMethod, SigTypeContext *pTypeContext) { STANDARD_VM_CONTRACT; IMDInternalImport *pImport = pModule->GetMDImport(); // Convert method specs into the underlying member ref. if (TypeFromToken(tokMethod) == mdtMethodSpec) { PCCOR_SIGNATURE pSig; ULONG cSig; mdMemberRef tkGenericMemberRef; IfFailThrow(pImport->GetMethodSpecProps(tokMethod, &tkGenericMemberRef, &pSig, &cSig)); if (!pImport->IsValidToken(tkGenericMemberRef) || (TypeFromToken(tkGenericMemberRef) != mdtMethodDef && TypeFromToken(tkGenericMemberRef) != mdtMemberRef)) { COMPlusThrowHR(COR_E_BADIMAGEFORMAT, BFA_INVALID_TOKEN_TYPE); } tokMethod = tkGenericMemberRef; } // Follow the member ref/def back up to the type def/ref/spec or module (for global methods). if (TypeFromToken(tokMethod) == mdtMemberRef) { IfFailThrow(pImport->GetParentOfMemberRef(tokMethod, &tokMethod)); // For varargs, a memberref can point to a methodDef if (TypeFromToken(tokMethod) == mdtMethodDef) { if (!pImport->IsValidToken(tokMethod)) { COMPlusThrowHR(COR_E_BADIMAGEFORMAT, BFA_INVALID_TOKEN); } IfFailThrow(pImport->GetParentToken(tokMethod, &tokMethod)); } } else if (TypeFromToken(tokMethod) == mdtMethodDef) { IfFailThrow(pImport->GetParentToken(tokMethod, &tokMethod)); } if (!pImport->IsValidToken(tokMethod) || (TypeFromToken(tokMethod) != mdtTypeDef && TypeFromToken(tokMethod) != mdtTypeRef && TypeFromToken(tokMethod) != mdtTypeSpec && TypeFromToken(tokMethod) != mdtModuleRef)) { COMPlusThrowHR(COR_E_BADIMAGEFORMAT, BFA_INVALID_TOKEN); } // Load the type in question, using a type context if necessary to get an exact representation. TypeHandle th; if (TypeFromToken(tokMethod) == mdtModuleRef) { DomainFile *pNewModule = pModule->LoadModule(GetAppDomain(), tokMethod, FALSE); if (pNewModule != NULL) th = TypeHandle(pNewModule->GetModule()->GetGlobalMethodTable()); } else { th = ClassLoader::LoadTypeDefOrRefOrSpecThrowing(pModule, tokMethod, pTypeContext); } if (th.IsNull()) COMPlusThrowHR(COR_E_BADIMAGEFORMAT); return th; } // Determine whether the method given as a parameter is the root of a CER. // @todo: Need an x86 offset as well and logic to determine whether we're actually in a root-CER portion of the method (if the whole // thing isn't the root). bool IsCerRootMethod(MethodDesc *pMD) { CONTRACTL { NOTHROW; GC_NOTRIGGER; MODE_ANY; PRECONDITION(CheckPointer(pMD)); SO_TOLERANT; } CONTRACTL_END; // Treat IL stubs as CER roots (marshaling code needs to string together operations without being interruped by thread aborts). if (pMD->IsILStub()) return true; // There are some well defined root methods defined by the system. if (pMD == g_pExecuteBackoutCodeHelperMethod) return true; // For now we just look to see whether there is some prep or fixup info stored for this method. Module *pModule = pMD->GetModule(); if (pModule->GetCerPrepInfo(pMD) != NULL) return true; #ifdef FEATURE_PREJIT if (pModule->IsNgenCerRootMethod(pMD)) return true; #endif return false; } // Fill the cache of overflowed generic dictionary entries that the jit maintains with all the overflow slots stored so far in the // dictionary layout. void PrepopulateGenericHandleCache(DictionaryLayout *pDictionaryLayout, MethodDesc *pMD, MethodTable *pMT) { CONTRACTL { THROWS; GC_TRIGGERS; MODE_ANY; } CONTRACTL_END; // Dictionary overflow entries are recorded starting in the second bucket of the dictionary layout. DictionaryLayout *pOverflows = pDictionaryLayout->GetNextLayout(); while (pOverflows) { for (DWORD i = 0; i < pOverflows->GetMaxSlots(); i++) { DictionaryEntryLayout *pEntry = pOverflows->GetEntryLayout(i); // We've finished as soon as we find the first unused slot. if (!pEntry->m_signature) return; // We have a valid overflow entry. Determine the handle value given the type context we have and push it into the JIT's // cache. JIT_GenericHandleWorker(pMD, pMT, pEntry->m_signature); } pOverflows = pOverflows->GetNextLayout(); } } #ifdef FEATURE_PREJIT // Prepare the CER rooted at the given method (it's OK to pass a MethodDesc* that doesn't root a CER, in which case the method // is a no-op). void CerNgenRootTable::Restore(MethodDesc *pRootMD) { #ifndef CROSSGEN_COMPILE STANDARD_VM_CONTRACT; // We don't have a restoration bitmap at ngen time. No matter, we just always claim everything is restored. if (m_pRestoreBitmap == NULL) return; // Locate the root index from the table. Failure indicates there's no work to do. DWORD dwIndex = FindIndex(pRootMD); if (dwIndex == NoSuchRoot) return; _ASSERTE(m_pRoots[dwIndex].m_pRootMD == pRootMD); // Check then mark the fact that we're preparing (to prevent potential recursion). SigTypeContext typeContext; if (!GetThread()->GetCerPreparationState()->CanPreparationProceed(pRootMD, &typeContext)) return; MethodCallGraphPreparer sPrep(pRootMD, &typeContext, true, true); MethodCallGraphPreparer::ThreadPreparingCerHolder sCerHolder(&sPrep); #ifdef _DEBUG if (GetCerLoggingOptions()) { CER_LOG(RESTORE, ("---------------------------------------------------------------\n")); SString ssRootMethod; TypeString::AppendMethodInternal(ssRootMethod, pRootMD, TypeString::FormatNamespace | TypeString::FormatStubInfo); CER_LOG(RESTORE, ("Restoring CER graph from %S\n", ssRootMethod.GetUnicode())); } #endif g_IBCLogger.LogCerMethodListReadAccess(pRootMD); // Retrieve the CerRoot structure. CerRoot *pRoot = &m_pRoots[dwIndex]; _ASSERTE(pRoot->m_pRootMD == pRootMD); // Scan the list of methods in the CER (the last one is a sentinel with a NULL MethodDesc*). Restore each method as we go. MethodContextElement *pEntry = pRoot->m_pList; while (pEntry->GetMethodDesc()) { // Method desc and type handle pointers may still be tokenized at this point. Module::RestoreMethodDescPointer(&pEntry->m_pMethodDesc); Module::RestoreMethodTablePointer(&pEntry->m_pExactMT); g_IBCLogger.LogCerMethodListReadAccess(pEntry->GetMethodDesc()); MethodDesc * pMD = pEntry->GetMethodDesc(); // Check whether there are generic dictionaries that need to be pre-populated. // Don't use the direct PrepopulateDictionary method here for MethodTable/MethodDesc // - MethodTable: Takes binding considerations into account (which we don't care about) // - MethodDesc: Appears to use a representative class instantiation (and we have the exact one handy) // // Additionally, avoid touching EE Class if we don't need to MethodTable * pMT = pEntry->GetExactMT(); if (pMT != NULL) { // MethodTable DictionaryLayout *pClassDictLayout = pMT->GetClass()->GetDictionaryLayout(); if (pClassDictLayout) { pMT->GetDictionary()->PrepopulateDictionary(NULL, pMT, false); // The dictionary may have overflowed in which case we need to prepopulate the jit's lookup cache as well. PrepopulateGenericHandleCache(pClassDictLayout, NULL, pMT); } // MethodDesc DictionaryLayout *pMethDictLayout = pMD->GetDictionaryLayout(); if (pMethDictLayout) { pMD->GetMethodDictionary()->PrepopulateDictionary(pMD, NULL, false); // The dictionary may have overflowed in which case we need to prepopulate the jit's lookup cache as well. PrepopulateGenericHandleCache(pMethDictLayout, pMD, NULL); } } // Recreate stubs used by P/Invoke, COM calls, or FCalls by exercising the prestub. if (pMD->IsPointingToPrestub() && (pMD->IsNDirect() || pMD->IsComPlusCall() || pMD->IsFCall())) { pMD->EnsureActive(); pMD->DoPrestub(NULL); } #ifdef _DEBUG if (GetCerLoggingOptions()) { SString ssMethod; TypeString::AppendMethodInternal(ssMethod, pMD, TypeString::FormatNamespace | TypeString::FormatStubInfo); CER_LOG(RESTORE, (" %S\n", ssMethod.GetUnicode())); } #endif // Move to next entry. pEntry++; } CER_LOG(RESTORE, ("---------------------------------------------------------------\n")); // Mark this whole CER region as fixed up by setting a flag in the restore bitmap (kept separate so we can cluster all our page // writes). // Compute the DWORD offset into the flag array and then the mask for the specific bit in that DWORD. DWORD dwOffset = dwIndex / (sizeof(DWORD) * 8); DWORD dwMask = 1 << (dwIndex % (sizeof(DWORD) * 8)); EnsureWritablePages(m_pRestoreBitmap, sizeof(DWORD) * SizeOfRestoreBitmap()); FastInterlockOr(&m_pRestoreBitmap[dwOffset], dwMask); // If we fixed up any methods with their own CERs then we will have implicitly fixed up those too. Mark their fixup records as // completed as well to avoid further unecessary work. pEntry = pRoot->m_pList; while (pEntry->GetMethodDesc()) { dwIndex = FindIndex(pEntry->GetMethodDesc()); if (dwIndex != NoSuchRoot) { dwOffset = dwIndex / (sizeof(DWORD) * 8); dwMask = 1 << (dwIndex % (sizeof(DWORD) * 8)); FastInterlockOr(&m_pRestoreBitmap[dwOffset], dwMask); } pEntry++; } #endif // CROSSGEN_COMPILE } #ifdef FEATURE_NATIVE_IMAGE_GENERATION // Add a new root to the table, expanding it as necessary. Note that this method must be called with the CerCrst already held. void CerNgenRootTable::AddRoot(MethodDesc *pRootMD, MethodContextElement *pList) { CONTRACTL { STANDARD_VM_CHECK; PRECONDITION(IsOwnerOfCrst(pRootMD->GetModule()->GetCerCrst())); } CONTRACTL_END; // Ensure we have enough space first. if (m_cRoots == m_cSlots) { DWORD cNewSize = m_cSlots + 16; CerRoot *pNewArray = new CerRoot[cNewSize]; memcpyNoGCRefs(pNewArray, m_pRoots, m_cRoots * sizeof(CerRoot)); MethodContextElement **pNewRootsInCompilationOrder = new MethodContextElement*[cNewSize]; memcpyNoGCRefs(pNewRootsInCompilationOrder, m_pRootsInCompilationOrder, m_cRoots * sizeof(MethodContextElement*) ); m_cSlots = cNewSize; delete m_pRoots; m_pRoots = pNewArray; delete m_pRootsInCompilationOrder; m_pRootsInCompilationOrder = pNewRootsInCompilationOrder; } // Fill in the new entry in sorted order. DWORD i; for (i = 0; i < m_cRoots; i++) if ((UPTR) m_pRoots[i].m_pRootMD > (UPTR) pRootMD) break; if (i < m_cRoots) memmove(&m_pRoots[i + 1], &m_pRoots[i], (m_cRoots - i) * sizeof(CerRoot)); m_pRoots[i].m_pRootMD = pRootMD; m_pRoots[i].m_pList = pList; m_pRootsInCompilationOrder[m_cRoots] = pList; m_cRoots++; } // Ngen callouts to help serialize this structure and its children to storage. void CerNgenRootTable::Save(DataImage *image, CorProfileData *profileData) { STANDARD_VM_CONTRACT; #ifdef _DEBUG DWORD dwMaxEntries = 0; DWORD dwTotalEntries = 0; #endif image->StoreStructure(this, sizeof(CerNgenRootTable), DataImage::ITEM_CER_ROOT_TABLE); image->StoreStructure(m_pRoots, m_cRoots * sizeof(CerRoot), DataImage::ITEM_CER_ROOT_TABLE); // Create a bitmap of boolean flags (1 bit per flag) indicating whether the CER at a given index in the array has been restored. // This is initially all zero and only filled in at runtime (keep all the flags together this way because they're the only // things we have to write at runtime and we want to keep them as dense as possible). _ASSERTE((SizeOfRestoreBitmap() % sizeof(DWORD)) == 0); m_pRestoreBitmap = new DWORD[SizeOfRestoreBitmap() / sizeof(DWORD)]; memset(m_pRestoreBitmap, 0xff, SizeOfRestoreBitmap()); image->StoreStructure(m_pRestoreBitmap, SizeOfRestoreBitmap(), DataImage::ITEM_CER_RESTORE_FLAGS); // Next save off the list of MethodContextElements associated with each root. for (DWORD i = 0; i < m_cRoots; i++) { MethodContextElement *pEntry = m_pRootsInCompilationOrder[i]; // Count entries in list. DWORD cEntries = 0; while (pEntry->GetMethodDesc()) { cEntries++; pEntry++; } // Plus one for the sentinel value. cEntries++; #ifdef _DEBUG dwTotalEntries += cEntries; if (cEntries > dwMaxEntries) dwMaxEntries = cEntries; #endif // Store this list. image->StoreStructure(m_pRootsInCompilationOrder[i], cEntries * sizeof(MethodContextElement), DataImage::ITEM_CER_METHOD_LIST); } #ifdef _DEBUG if (m_cRoots > 0) { CER_LOG(NGEN_STATS, ("Saving %u CER roots in ngen image\n", m_cRoots)); CER_LOG(NGEN_STATS, (" Max methods in CER: %u\n", dwMaxEntries)); CER_LOG(NGEN_STATS, (" Avg methods in CER: %.1f\n", (float)((float)dwTotalEntries / (float)m_cRoots))); } else CER_LOG(NGEN_STATS, ("No CER roots in ngen image\n")); #endif } void CerNgenRootTable::Fixup(DataImage *image) { STANDARD_VM_CONTRACT; DWORD i; // We still use the point to the root array even though at runtime the two structures will be adjacent. image->FixupPointerField(this, offsetof(CerNgenRootTable, m_pRoots)); // Restoration flags are used only at runtime and must start off zeroed. image->FixupPointerField(this, offsetof(CerNgenRootTable, m_pRestoreBitmap)); image->ZeroField(m_pRestoreBitmap, 0, SizeOfRestoreBitmap()); // The root list in compilation order is only used at ngen time, and is not written into native image. image->ZeroPointerField(this, offsetof(CerNgenRootTable, m_pRootsInCompilationOrder)); // Fixup all the pointers in the individual CERs. for (i = 0; i < m_cRoots; i++) { // For each MethodContextElement in the list we need to fixup a pointer to a MethodDesc and two array pointers (one for any // class instantiation and one for any method instantiation). The actual MethodDescs and TypeHandles themselves are already // fixed up as are the instantiation arrays we point to (they're the ones inside the generic dictionaries of the class/method // concerned). MethodContextElement *pList = m_pRootsInCompilationOrder[i]; MethodContextElement *pEntry = pList; while (pEntry->GetMethodDesc()) { image->FixupMethodDescPointer(pList, &pEntry->m_pMethodDesc); image->FixupMethodTablePointer(pList, &pEntry->m_pExactMT); pEntry++; } } } void CerNgenRootTable::FixupRVAs(DataImage *image) { STANDARD_VM_CONTRACT; DWORD i, j; // Now we go back through the root table and sort the entries based on the locations of the root method descs in the new image // (they may be rearranged due to IBC profiling). CerRoot *pNewRoots = (CerRoot*)image->GetImagePointer(m_pRoots); PREFIX_ASSUME(pNewRoots != NULL); // Simple insertion sort. Starting at the second element insert a candidate into its correct location in the sub-list // preceding it (which by definition will already be sorted). for (i = 1; i < m_cRoots; i++) { // Look at all of the preceding elements for the first that is larger than the candidate (i.e. should succeed the // candidate in sorted order). If we don't find one then the candidate is already in place and we can proceed to the // next candidate. for (j = 0; j < i; j++) if (image->GetRVA(pNewRoots[j].m_pRootMD) > image->GetRVA(pNewRoots[i].m_pRootMD)) { // Need to move candidate element up. Cache its value because we're about to overwrite it. MethodDesc *pTmpRootMD = pNewRoots[i].m_pRootMD; MethodContextElement *pTmpList = pNewRoots[i].m_pList; // Shuffle the sorted list one up to make room for the candidate. memmove(&pNewRoots[j + 1], &pNewRoots[j], (i - j) * sizeof(CerRoot)); // Insert the candidate into position. pNewRoots[j].m_pRootMD = pTmpRootMD; pNewRoots[j].m_pList = pTmpList; // Sorted the candidate, move onto the next. break; } } // Fixup all the pointers in the individual CERs. for (i = 0; i < m_cRoots; i++) { // Fix up the pointer to the root method and the list of methods in the CER. image->FixupField(m_pRoots, sizeof(CerRoot) * i + offsetof(CerRoot, m_pRootMD), pNewRoots[i].m_pRootMD); image->FixupField(m_pRoots, sizeof(CerRoot) * i + offsetof(CerRoot, m_pList), pNewRoots[i].m_pList); } } #endif // FEATURE_NATIVE_IMAGE_GENERATION // Locate the index of a given CerRoot record in the array given the root method. This is used to access the array and to locate the // restored flag for the entry in the restored bitmap. NoSuchRoot is returned if the root cannot be found. DWORD CerNgenRootTable::FindIndex(MethodDesc *pRootMD) { CONTRACTL { NOTHROW; MODE_ANY; GC_NOTRIGGER; PRECONDITION(CheckPointer(pRootMD)); SO_TOLERANT; } CONTRACTL_END; // The table is guaranteed to be sorted, so we can lookup our target with a binary search. DWORD dwLow = 0; DWORD dwHigh = m_cRoots - 1; while (true) { // Take out the simple cases first. // The range has only one entry. if (dwLow == dwHigh) { if (m_pRoots[dwLow].m_pRootMD == pRootMD) return dwLow; #ifdef _DEBUG for (DWORD i = 0; i < m_cRoots; i++) _ASSERTE(m_pRoots[i].m_pRootMD != pRootMD); #endif return NoSuchRoot; } // The range has only two entries. if (dwLow == dwHigh - 1) { if (m_pRoots[dwLow].m_pRootMD == pRootMD) return dwLow; if (m_pRoots[dwHigh].m_pRootMD == pRootMD) return dwHigh; #ifdef _DEBUG for (DWORD i = 0; i < m_cRoots; i++) _ASSERTE(m_pRoots[i].m_pRootMD != pRootMD); #endif return NoSuchRoot; } // Now we can compute a midpoint that is definitely distinct and in-between the endpoints. DWORD dwMid = dwLow + ((dwHigh - dwLow) / 2); // Did we nail it? if (m_pRoots[dwMid].m_pRootMD == pRootMD) return dwMid; // Otherwise adjust our range to be the bit we haven't looked at and iterate. if ((UPTR)m_pRoots[dwMid].m_pRootMD < (UPTR)pRootMD) dwLow = dwMid + 1; else dwHigh = dwMid - 1; } } // Prepare the class if it is derived from CriticalFinalizerObject. This is used at ngen time since such classes are normally // prepared at runtime (at instantiation) and would therefore miss the ngen image. void PrepareCriticalType(MethodTable * pMT) { STANDARD_VM_CONTRACT; // Prepare any class that satisfies the criteria. Pass a pointer to this module so that we'll only prepare any overrides of // the critical methods that were actually introduced here. if (pMT->HasCriticalFinalizer()) PrepareCriticalFinalizerObject(pMT, pMT->GetLoaderModule()); } // Prepare a method and its statically determinable call graph if a hint attribute has been applied. This is only called at ngen // time to save additional preparation information into the ngen image that wouldn't normally be there (and thus lower runtime // overheads). void PrePrepareMethodIfNecessary(CORINFO_METHOD_HANDLE hMethod) { STANDARD_VM_CONTRACT; EX_TRY { // Translate jit-style method handle into method desc. MethodDesc *pMD = GetMethod(hMethod); // Check for the existance of the attribute. IMDInternalImport *pImport = pMD->GetMDImport(); mdToken tkMethod = pMD->GetMemberDef(); HRESULT hr = pImport->GetCustomAttributeByName(tkMethod, "System.Runtime.ConstrainedExecution.PrePrepareMethodAttribute", NULL, NULL); // TODO: We should add IBC probes which indicate that methods need to be preprepared // which can then be reflected in the IBC data, we can add an additional check // here to cover that case, then we can get around this problem with profiling // instead of manual programmer effort. // Only prepare if we definitely saw the attribute. if (hr == S_OK) { // Prepare the method and its graph. There should never be any open type parameters (we can't do much at ngen time with these), // so we can pass a null type context. SigTypeContext sTypeContext; MethodCallGraphPreparer mcgp(pMD, &sTypeContext, true, true); mcgp.Run(); } } EX_CATCH { } EX_END_CATCH(SwallowAllExceptions); } #endif // FEATURE_PREJIT PtrHashCache::PtrHashCache() { LIMITED_METHOD_CONTRACT; ZeroMemory(this, sizeof(*this)); // First entry in each bucket is a chain index used to evenly distribute inserts within a bucket. _ASSERTE(PHC_CHAIN > 1); } bool PtrHashCache::Lookup(void *pKey, DWORD *pdwValue) { LIMITED_METHOD_CONTRACT; _ASSERTE(((UINT_PTR)pKey & PHC_DATA_MASK) == 0); DWORD dwBucket = GetHash(pKey); // Skip first entry in bucket, it's a sequence number used for insertions. for (DWORD i = 1; i < PHC_CHAIN; i++) { UINT_PTR uipEntry = VolatileLoad(&m_rEntries[(dwBucket * PHC_CHAIN) + i]); if ((uipEntry & ~PHC_DATA_MASK) == (UINT_PTR)pKey) { #ifdef _DEBUG FastInterlockIncrement((LONG*)&m_dwHits); #endif *pdwValue = uipEntry & PHC_DATA_MASK; return true; } } #ifdef _DEBUG FastInterlockIncrement((LONG*)&m_dwMisses); #endif return false; } void PtrHashCache::Add(void *pKey, DWORD dwValue) { LIMITED_METHOD_CONTRACT; _ASSERTE(((UINT_PTR)pKey & PHC_DATA_MASK) == 0); _ASSERTE((dwValue & ~PHC_DATA_MASK) == 0); DWORD dwBucket = GetHash(pKey); // We keep a sequence number in the first entry of the bucket so that we distribute insertions within the bucket evenly. We're // racing when we update this value, but it doesn't matter if we lose an update (we're a cache after all). We don't bother being // careful to avoid overflowing the value here (we just keep incrementing); we'll do the modulo logic when we insert our value // instead. DWORD dwIndex = static_cast(m_rEntries[dwBucket * PHC_CHAIN]++); dwIndex = (dwIndex % (PHC_CHAIN - 1)) + 1; m_rEntries[(dwBucket * PHC_CHAIN) + dwIndex] = ((UINT_PTR)pKey & ~PHC_DATA_MASK) | dwValue; } DWORD PtrHashCache::GetHash(void *pKey) { LIMITED_METHOD_CONTRACT; return (DWORD)(((UINT_PTR)pKey >> 4) % PHC_BUCKETS); } #ifdef _DEBUG void PtrHashCache::DbgDumpStats() { #if 0 if ((m_dwHits + m_dwMisses) == 0) return; printf("Dumping stats for PtrHashCache %08X\n", this); printf(" %u hits, %u misses (%u%% hit rate)\n", m_dwHits, m_dwMisses, (m_dwHits * 100) / (m_dwHits + m_dwMisses)); for (DWORD i = 0; i < PHC_BUCKETS; i++) printf(" [%2u] : %u insertions\n", i, m_rEntries[i * PHC_CHAIN]); printf("\n"); #endif } #endif