// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. // FCALL.CPP // // #include "common.h" #include "vars.hpp" #include "fcall.h" #include "excep.h" #include "frames.h" #include "gms.h" #include "ecall.h" #include "eeconfig.h" NOINLINE LPVOID __FCThrow(LPVOID __me, RuntimeExceptionKind reKind, UINT resID, LPCWSTR arg1, LPCWSTR arg2, LPCWSTR arg3) { STATIC_CONTRACT_THROWS; // This isn't strictly true... But the guarentee that we make here is // that we won't trigger without having setup a frame. // STATIC_CONTRACT_TRIGGER STATIC_CONTRACT_GC_NOTRIGGER; STATIC_CONTRACT_SO_TOLERANT; // function probes before it does any work // side effect the compiler can't remove if (FC_NO_TAILCALL != 1) return (LPVOID)(SIZE_T)(FC_NO_TAILCALL + 1); FC_CAN_TRIGGER_GC(); INCONTRACT(FCallCheck __fCallCheck(__FILE__, __LINE__)); FC_GC_POLL_NOT_NEEDED(); HELPER_METHOD_FRAME_BEGIN_RET_ATTRIB_NOPOLL(Frame::FRAME_ATTR_CAPTURE_DEPTH_2); // Now, we can construct & throw. // In V1, throwing an ExecutionEngineException actually never really threw anything... its was the same as a // fatal error in the runtime, and we will most probably would have ripped the process down. Starting in // Whidbey, this behavior has changed a lot. Its not really legal to try to throw an // ExecutionEngineExcpetion with this function. _ASSERTE((reKind != kExecutionEngineException) || !"Don't throw kExecutionEngineException from here. Go to EEPolicy directly, or throw something better."); if (resID == 0) { // If we have an string to add use NonLocalized otherwise just throw the exception. if (arg1) COMPlusThrowNonLocalized(reKind, arg1); //COMPlusThrow(reKind,arg1); else COMPlusThrow(reKind); } else COMPlusThrow(reKind, resID, arg1, arg2, arg3); HELPER_METHOD_FRAME_END(); FC_CAN_TRIGGER_GC_END(); _ASSERTE(!"Throw returned"); return NULL; } NOINLINE LPVOID __FCThrowArgument(LPVOID __me, RuntimeExceptionKind reKind, LPCWSTR argName, LPCWSTR resourceName) { STATIC_CONTRACT_THROWS; // This isn't strictly true... But the guarentee that we make here is // that we won't trigger without having setup a frame. // STATIC_CONTRACT_TRIGGER STATIC_CONTRACT_GC_NOTRIGGER; STATIC_CONTRACT_SO_TOLERANT; // function probes before it does any work // side effect the compiler can't remove if (FC_NO_TAILCALL != 1) return (LPVOID)(SIZE_T)(FC_NO_TAILCALL + 1); FC_CAN_TRIGGER_GC(); INCONTRACT(FCallCheck __fCallCheck(__FILE__, __LINE__)); FC_GC_POLL_NOT_NEEDED(); // throws always open up for GC HELPER_METHOD_FRAME_BEGIN_RET_ATTRIB_NOPOLL(Frame::FRAME_ATTR_CAPTURE_DEPTH_2); switch (reKind) { case kArgumentNullException: if (resourceName) { COMPlusThrowArgumentNull(argName, resourceName); } else { COMPlusThrowArgumentNull(argName); } break; case kArgumentOutOfRangeException: COMPlusThrowArgumentOutOfRange(argName, resourceName); break; case kArgumentException: COMPlusThrowArgumentException(argName, resourceName); break; default: // If you see this assert, add a case for your exception kind above. _ASSERTE(argName == NULL); COMPlusThrow(reKind, resourceName); } HELPER_METHOD_FRAME_END(); FC_CAN_TRIGGER_GC_END(); _ASSERTE(!"Throw returned"); return NULL; } /**************************************************************************************/ /* erect a frame in the FCALL and then poll the GC, objToProtect will be protected during the poll and the updated object returned. */ NOINLINE Object* FC_GCPoll(void* __me, Object* objToProtect) { CONTRACTL { THROWS; // This isn't strictly true... But the guarentee that we make here is // that we won't trigger without having setup a frame. UNCHECKED(GC_NOTRIGGER); SO_TOLERANT; // function probes before it does any work } CONTRACTL_END; FC_CAN_TRIGGER_GC(); INCONTRACT(FCallCheck __fCallCheck(__FILE__, __LINE__)); Thread *thread = GetThread(); if (thread->CatchAtSafePointOpportunistic()) // Does someone want this thread stopped? { HELPER_METHOD_FRAME_BEGIN_RET_ATTRIB_1(Frame::FRAME_ATTR_CAPTURE_DEPTH_2, objToProtect); #ifdef _DEBUG BOOL GCOnTransition = FALSE; if (g_pConfig->FastGCStressLevel()) { GCOnTransition = GC_ON_TRANSITIONS (FALSE); } #endif CommonTripThread(); #ifdef _DEBUG if (g_pConfig->FastGCStressLevel()) { GC_ON_TRANSITIONS (GCOnTransition); } #endif HELPER_METHOD_FRAME_END(); } FC_CAN_TRIGGER_GC_END(); return objToProtect; } #ifdef _DEBUG unsigned FcallTimeHist[11]; #endif #ifdef ENABLE_CONTRACTS /**************************************************************************************/ #if defined(_TARGET_X86_) && defined(ENABLE_PERF_COUNTERS) static __int64 getCycleCount() { LIMITED_METHOD_CONTRACT; return GET_CYCLE_COUNT(); } #else static __int64 getCycleCount() { LIMITED_METHOD_CONTRACT; return(0); } #endif /**************************************************************************************/ // No contract here: The contract destructor restores the thread contract state to what it was // soon after constructing the contract. This would have the effect of reverting the contract // state change made by the call to BeginForbidGC. DEBUG_NOINLINE ForbidGC::ForbidGC(const char *szFile, int lineNum) { SCAN_SCOPE_BEGIN; STATIC_CONTRACT_GC_NOTRIGGER; STATIC_CONTRACT_MODE_COOPERATIVE; m_pThread = GetThread(); m_pThread->BeginForbidGC(szFile, lineNum); } /**************************************************************************************/ // No contract here: The contract destructor restores the thread contract state to what it was // soon after constructing the contract. This would have the effect of reverting the contract // state change made by the call to BeginForbidGC. DEBUG_NOINLINE ForbidGC::~ForbidGC() { SCAN_SCOPE_END; // IF EH happens, this is still called, in which case // we should not bother if (m_pThread->RawGCNoTrigger()) m_pThread->EndNoTriggerGC(); } /**************************************************************************************/ DEBUG_NOINLINE FCallCheck::FCallCheck(const char *szFile, int lineNum) : ForbidGC(szFile, lineNum) { SCAN_SCOPE_BEGIN; STATIC_CONTRACT_GC_NOTRIGGER; STATIC_CONTRACT_MODE_COOPERATIVE; #ifdef _DEBUG unbreakableLockCount = m_pThread->GetUnbreakableLockCount(); #endif didGCPoll = false; notNeeded = false; startTicks = getCycleCount(); } /**************************************************************************************/ DEBUG_NOINLINE FCallCheck::~FCallCheck() { SCAN_SCOPE_END; // Confirm that we don't starve the GC or thread-abort. // Basically every control flow path through an FCALL must // to a poll. If you hit the assert below, you can fix it by // // If you erect a HELPER_METHOD_FRAME, you can // // Call HELPER_METHOD_POLL() // or use HELPER_METHOD_FRAME_END_POLL // // If you don't have a helper frame you can used // // FC_GC_POLL_AND_RETURN_OBJREF or // FC_GC_POLL or // FC_GC_POLL_RET // // Note that these must be at GC safe points. In particular // all object references that are NOT protected will be trashed. // There is a special poll called FC_GC_POLL_NOT_NEEDED // which says the code path is short enough that a GC poll is not need // you should not use this in most cases. _ASSERTE(unbreakableLockCount == m_pThread->GetUnbreakableLockCount() || (!m_pThread->HasUnbreakableLock() && !m_pThread->HasThreadStateNC(Thread::TSNC_OwnsSpinLock))); if (notNeeded) { /* TODO, we want to actually measure the time to make certain we are not too far off unsigned delta = unsigned(getCycleCount() - startTicks); */ } else if (!didGCPoll) { // TODO turn this on!!! _ASSERTE(!"FCALL without a GC poll in it somewhere!"); } } #if defined(_TARGET_AMD64_) FCallTransitionState::FCallTransitionState () { WRAPPER_NO_CONTRACT; m_pThread = GetThread(); _ASSERTE(m_pThread); m_pPreviousHelperMethodFrameCallerList = m_pThread->m_pHelperMethodFrameCallerList; m_pThread->m_pHelperMethodFrameCallerList = NULL; } FCallTransitionState::~FCallTransitionState () { WRAPPER_NO_CONTRACT; m_pThread->m_pHelperMethodFrameCallerList = m_pPreviousHelperMethodFrameCallerList; } PermitHelperMethodFrameState::PermitHelperMethodFrameState () { WRAPPER_NO_CONTRACT; m_pThread = GetThread(); _ASSERTE(m_pThread); CONSISTENCY_CHECK_MSG((HelperMethodFrameCallerList*)-1 != m_pThread->m_pHelperMethodFrameCallerList, "fcall entry point is missing a FCALL_TRANSITION_BEGIN or a FCIMPL\n"); m_ListEntry.pCaller = m_pThread->m_pHelperMethodFrameCallerList; m_pThread->m_pHelperMethodFrameCallerList = &m_ListEntry; } PermitHelperMethodFrameState::~PermitHelperMethodFrameState () { WRAPPER_NO_CONTRACT; m_pThread->m_pHelperMethodFrameCallerList = m_ListEntry.pCaller; } VOID PermitHelperMethodFrameState::CheckHelperMethodFramePermitted () { CONTRACTL { NOTHROW; GC_NOTRIGGER; DEBUG_ONLY; } CONTRACTL_END; // // Get current context and unwind to caller // CONTEXT ctx; ClrCaptureContext(&ctx); Thread::VirtualUnwindCallFrame(&ctx); // // Make sure each unmanaged frame used PERMIT_HELPER_METHOD_FRAME_BEGIN. // If we hit NULL before we reach managed code, then the caller of the // fcall was not managed. // Thread *pThread = GetThread(); _ASSERTE(pThread); HelperMethodFrameCallerList *pList = pThread->m_pHelperMethodFrameCallerList; PCODE CurrentIP; TADDR CurrentSP; do { CurrentSP = GetSP(&ctx); CurrentIP = GetIP(&ctx); Thread::VirtualUnwindCallFrame(&ctx); TADDR CallerSP = GetSP(&ctx); unsigned nAssociatedListEntries = 0; while ( (SIZE_T)pList >= (SIZE_T)CurrentSP && (SIZE_T)pList < (SIZE_T)CallerSP) { nAssociatedListEntries++; pList = pList->pCaller; } if (!nAssociatedListEntries) { char szFunction[cchMaxAssertStackLevelStringLen]; GetStringFromAddr((DWORD_PTR)CurrentIP, szFunction); CONSISTENCY_CHECK_MSGF(false, ("Unmanaged caller %s at sp %p/ip %p is missing a " "PERMIT_HELPER_METHOD_FRAME_BEGIN, or this function " "is calling an fcall entry point that is missing a " "FCALL_TRANSITION_BEGIN or a FCIMPL\n", szFunction, CurrentSP, CurrentIP)); } } while (pList && !ExecutionManager::IsManagedCode(GetIP(&ctx))); // // We should have exhausted the list. If not, the list was not reset at // the transition from managed code. // if (pList) { char szFunction[cchMaxAssertStackLevelStringLen]; GetStringFromAddr((DWORD_PTR)CurrentIP, szFunction); CONSISTENCY_CHECK_MSGF(false, ("fcall entry point %s at sp %p/ip %p is missing a " "FCALL_TRANSITION_BEGIN or a FCIMPL\n", szFunction, CurrentSP, CurrentIP)); } } CompletedFCallTransitionState::CompletedFCallTransitionState () { WRAPPER_NO_CONTRACT; Thread *pThread = GetThread(); _ASSERTE(pThread); m_pLastHelperMethodFrameCallerList = pThread->m_pHelperMethodFrameCallerList; pThread->m_pHelperMethodFrameCallerList = (HelperMethodFrameCallerList*)-1; } CompletedFCallTransitionState::~CompletedFCallTransitionState () { WRAPPER_NO_CONTRACT; Thread *pThread = GetThread(); _ASSERTE(pThread); pThread->m_pHelperMethodFrameCallerList = m_pLastHelperMethodFrameCallerList; } #endif // _TARGET_AMD64_ #endif // ENABLE_CONTRACTS