// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. // // FILE: ProfToEEInterfaceImpl.cpp // // This module implements the ICorProfilerInfo* interfaces, which allow the // Profiler to communicate with the EE. This allows the Profiler DLL to get // access to private EE data structures and other things that should never be // exported outside of the EE. // // // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! // NOTE! NOTE! NOTE! NOTE! NOTE! NOTE! NOTE! NOTE! NOTE! NOTE! NOTE! NOTE! // // PLEASE READ! // // There are strict rules for how to implement ICorProfilerInfo* methods. Please read // https://github.com/dotnet/coreclr/blob/master/Documentation/botr/profilability.md // to understand the rules and why they exist. // // As a reminder, here is a short summary of your responsibilities. Every PUBLIC // ENTRYPOINT (from profiler to EE) must have: // // - An entrypoint macro at the top (see code:#P2CLRRestrictionsOverview). Your choices are: // PROFILER_TO_CLR_ENTRYPOINT_SYNC (typical choice): // Indicates the method may only be called by the profiler from within // a callback (from EE to profiler). // PROFILER_TO_CLR_ENTRYPOINT_CALLABLE_ON_INIT_ONLY // Even more restrictive, this indicates the method may only be called // from within the Initialize() callback // PROFILER_TO_CLR_ENTRYPOINT_ASYNC // Indicates this method may be called anytime. // THIS IS DANGEROUS. PLEASE READ ABOVE DOC FOR GUIDANCE ON HOW TO SAFELY // CODE AN ASYNCHRONOUS METHOD. // You may use variants of these macros ending in _EX that accept bit flags (see // code:ProfToClrEntrypointFlags) if you need to specify additional parameters to how // the entrypoint should behave, though typically you can omit the flags and the // default (kP2EENone) will be used. // // - A complete contract block with comments over every contract choice. Wherever // possible, use the preferred contracts (if not possible, you must comment why): // NOTHROW // GC_NOTRIGGER // MODE_ANY // CANNOT_TAKE_LOCK // SO_NOT_MAINLINE // (EE_THREAD_(NOT)_REQUIRED are unenforced and are thus optional. If you wish // to specify these, EE_THREAD_NOT_REQUIRED is preferred.) // Note that the preferred contracts in this file are DIFFERENT than the preferred // contracts for eetoprofinterfaceimpl.cpp. // // Private helper functions in this file do not have the same preferred contracts as // public entrypoints, and they should be contracted following the same guidelines // as per the rest of the EE. // // NOTE! NOTE! NOTE! NOTE! NOTE! NOTE! NOTE! NOTE! NOTE! NOTE! NOTE! NOTE! // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! // // // #P2CLRRestrictionsOverview // // The public ICorProfilerInfo(N) functions below have different restrictions on when // they're allowed to be called. Listed roughly in order from most to least restrictive: // * PROFILER_TO_CLR_ENTRYPOINT_CALLABLE_ON_INIT_ONLY: Functions that are only // allowed to be called while the profiler is initializing on startup, from // inside the profiler's ICorProfilerCallback::Initialize method // * PROFILER_TO_CLR_ENTRYPOINT_SYNC: Functions that may be called from within any of // the profiler's callbacks, or anytime from a thread created by the profiler. // These functions may only be called by profilers loaded on startup // * PROFILER_TO_CLR_ENTRYPOINT_SYNC_EX(kP2EEAllowableAfterAttach): Same as above, // except these may be called by startup AND attaching profilers. // * PROFILER_TO_CLR_ENTRYPOINT_ASYNC: Functions that may be called at any time and // from any thread by a profiler loaded on startup // * PROFILER_TO_CLR_ENTRYPOINT_ASYNC_EX(kP2EEAllowableAfterAttach): Same as above, // except these may be called by startup AND attaching profilers. // // The above restrictions are lifted for certain tests that run with these environment // variables set. (These are only available on DEBUG builds--including chk--not retail // builds.) // * COMPlus_TestOnlyEnableSlowELTHooks: // * If nonzero, then on startup the runtime will act as if a profiler was loaded // on startup and requested ELT slow-path (even if no profiler is loaded on // startup). This will also allow the SetEnterLeaveFunctionHooks(2) info // functions to be called outside of Initialize(). If a profiler later // attaches and calls these functions, then the slow-path wrapper will call // into the profiler's ELT hooks. // * COMPlus_TestOnlyEnableObjectAllocatedHook: // * If nonzero, then on startup the runtime will act as if a profiler was loaded // on startup and requested ObjectAllocated callback (even if no profiler is loaded // on startup). If a profiler later attaches and calls these functions, then the // ObjectAllocated notifications will call into the profiler's ObjectAllocated callback. // * COMPlus_TestOnlyEnableICorProfilerInfo: // * If nonzero, then attaching profilers allows to call ICorProfilerInfo inteface, // which would otherwise be disallowed for attaching profilers // * COMPlus_TestOnlyAllowedEventMask // * If a profiler needs to work around the restrictions of either // COR_PRF_ALLOWABLE_AFTER_ATTACH or COR_PRF_MONITOR_IMMUTABLE it may set // this environment variable. Its value should be a bitmask containing all // the flags that are: // * normally immutable or disallowed after attach, AND // * that the test plans to set after startup and / or by an attaching // profiler. // // // // ====================================================================================== #include "common.h" #include #include "proftoeeinterfaceimpl.h" #include "proftoeeinterfaceimpl.inl" #include "dllimport.h" #include "threads.h" #include "method.hpp" #include "vars.hpp" #include "dbginterface.h" #include "corprof.h" #include "class.h" #include "object.h" #include "ceegen.h" #include "eeconfig.h" #include "generics.h" #include "gcinfo.h" #include "safemath.h" #include "threadsuspend.h" #include "inlinetracking.h" #ifdef PROFILING_SUPPORTED #include "profilinghelper.h" #include "profilinghelper.inl" #include "eetoprofinterfaceimpl.inl" #include "profilingenumerators.h" #endif #include "profdetach.h" #include "metadataexports.h" //--------------------------------------------------------------------------------------- // Helpers // An OR'd combination of these flags may be specified in the _EX entrypoint macros to // customize the behavior. enum ProfToClrEntrypointFlags { // Just use the default behavior (this one is used if the non-_EX entrypoint macro is // specified without any flags). kP2EENone = 0x00000000, // By default, Info functions are not allowed to be used by an attaching profiler. // Specify this flag to override the default. kP2EEAllowableAfterAttach = 0x00000001, // This info method has a GC_TRIGGERS contract. Whereas contracts are debug-only, // this flag is used in retail builds as well. kP2EETriggers = 0x00000002, }; // Default versions of the entrypoint macros use kP2EENone if no // ProfToClrEntrypointFlags are specified #define PROFILER_TO_CLR_ENTRYPOINT_ASYNC(logParams) \ PROFILER_TO_CLR_ENTRYPOINT_ASYNC_EX(kP2EENone, logParams) #define PROFILER_TO_CLR_ENTRYPOINT_SYNC(logParams) \ PROFILER_TO_CLR_ENTRYPOINT_SYNC_EX(kP2EENone, logParams) // ASYNC entrypoints log and ensure an attaching profiler isn't making a call that's // only supported by startup profilers. #define CHECK_IF_ATTACHING_PROFILER_IS_ALLOWED_HELPER(p2eeFlags) \ do \ { \ if ((((p2eeFlags) & kP2EEAllowableAfterAttach) == 0) && \ (g_profControlBlock.pProfInterface->IsLoadedViaAttach())) \ { \ LOG((LF_CORPROF, \ LL_ERROR, \ "**PROF: ERROR: Returning CORPROF_E_UNSUPPORTED_FOR_ATTACHING_PROFILER " \ "due to a call illegally made by an attaching profiler \n")); \ return CORPROF_E_UNSUPPORTED_FOR_ATTACHING_PROFILER; \ } \ } while(0) #ifdef _DEBUG #define CHECK_IF_ATTACHING_PROFILER_IS_ALLOWED(p2eeFlags) \ do \ { \ if (!((&g_profControlBlock)->fTestOnlyEnableICorProfilerInfo)) \ { \ CHECK_IF_ATTACHING_PROFILER_IS_ALLOWED_HELPER(p2eeFlags); \ } \ } while(0) #else //_DEBUG #define CHECK_IF_ATTACHING_PROFILER_IS_ALLOWED(p2eeFlags) \ do \ { \ CHECK_IF_ATTACHING_PROFILER_IS_ALLOWED_HELPER(p2eeFlags); \ } while(0) #endif //_DEBUG #define PROFILER_TO_CLR_ENTRYPOINT_ASYNC_EX(p2eeFlags, logParams) \ do \ { \ INCONTRACT(AssertTriggersContract(((p2eeFlags) & kP2EETriggers))); \ _ASSERTE(g_profControlBlock.curProfStatus.Get() != kProfStatusNone); \ LOG(logParams); \ /* If profiler was neutered, disallow call */ \ if (g_profControlBlock.curProfStatus.Get() == kProfStatusDetaching) \ { \ LOG((LF_CORPROF, \ LL_ERROR, \ "**PROF: ERROR: Returning CORPROF_E_PROFILER_DETACHING " \ "due to a post-neutered profiler call\n")); \ return CORPROF_E_PROFILER_DETACHING; \ } \ CHECK_IF_ATTACHING_PROFILER_IS_ALLOWED(p2eeFlags); \ } while(0) // SYNC entrypoints must ensure the current EE Thread shows evidence that we're // inside a callback. If there's no EE Thread, then we automatically "pass" // the check, and the SYNC call is allowed. #define PROFILER_TO_CLR_ENTRYPOINT_SYNC_EX(p2eeFlags, logParams) \ do \ { \ PROFILER_TO_CLR_ENTRYPOINT_ASYNC_EX(p2eeFlags, logParams); \ DWORD __dwExpectedCallbackState = COR_PRF_CALLBACKSTATE_INCALLBACK; \ if (((p2eeFlags) & kP2EETriggers) != 0) \ { \ __dwExpectedCallbackState |= COR_PRF_CALLBACKSTATE_IN_TRIGGERS_SCOPE; \ } \ if (!AreCallbackStateFlagsSet(__dwExpectedCallbackState)) \ { \ LOG((LF_CORPROF, \ LL_ERROR, \ "**PROF: ERROR: Returning CORPROF_E_UNSUPPORTED_CALL_SEQUENCE " \ "due to illegal asynchronous profiler call\n")); \ return CORPROF_E_UNSUPPORTED_CALL_SEQUENCE; \ } \ } while(0) // INIT_ONLY entrypoints must ensure we're executing inside the profiler's // Initialize() implementation on startup (attach init doesn't count!). #define PROFILER_TO_CLR_ENTRYPOINT_CALLABLE_ON_INIT_ONLY(logParams) \ do \ { \ PROFILER_TO_CLR_ENTRYPOINT_ASYNC(logParams); \ if (g_profControlBlock.curProfStatus.Get() != kProfStatusInitializingForStartupLoad && \ g_profControlBlock.curProfStatus.Get() != kProfStatusInitializingForAttachLoad) \ { \ return CORPROF_E_CALL_ONLY_FROM_INIT; \ } \ } while(0) // This macro is used to ensure that the current thread is not in a forbid // suspend region. Some methods are allowed to be called asynchronously, // but some of them call JIT functions that take a reader lock. So we need to ensure // the current thread hasn't been hijacked by a profiler while it was holding the writer lock. // Checking the ForbidSuspendThread region is a sufficient test for this #define FAIL_IF_IN_FORBID_SUSPEND_REGION() \ do \ { \ Thread * __pThread = GetThreadNULLOk(); \ if ((__pThread != NULL) && (__pThread->IsInForbidSuspendRegion())) \ { \ return CORPROF_E_ASYNCHRONOUS_UNSAFE; \ } \ } while(0) // // This type is an overlay onto the exported type COR_PRF_FRAME_INFO. // The first four fields *must* line up with the same fields in the // exported type. After that, we can add to the end as we wish. // typedef struct _COR_PRF_FRAME_INFO_INTERNAL { USHORT size; USHORT version; FunctionID funcID; UINT_PTR IP; void *extraArg; LPVOID thisArg; } COR_PRF_FRAME_INFO_INTERNAL, *PCOR_PRF_FRAME_INFO_INTERNAL; // // After we ship a product with a certain struct type for COR_PRF_FRAME_INFO_INTERNAL // we have that as a version. If we change that in a later product, we can increment // the counter below and then we can properly do versioning. // #define COR_PRF_FRAME_INFO_INTERNAL_CURRENT_VERSION 1 //--------------------------------------------------------------------------------------- // // Converts TypeHandle to a ClassID // // Arguments: // th - TypeHandle to convert // // Return Value: // Requested ClassID. // ClassID TypeHandleToClassID(TypeHandle th) { WRAPPER_NO_CONTRACT; return reinterpret_cast (th.AsPtr()); } //--------------------------------------------------------------------------------------- // // Converts TypeHandle for a non-generic type to a ClassID // // Arguments: // th - TypeHandle to convert // // Return Value: // Requested ClassID. NULL if th represents a generic type // #ifdef PROFILING_SUPPORTED static ClassID NonGenericTypeHandleToClassID(TypeHandle th) { CONTRACTL { SO_NOT_MAINLINE; NOTHROW; GC_NOTRIGGER; MODE_ANY; } CONTRACTL_END; if ((!th.IsNull()) && (th.HasInstantiation())) { return NULL; } return TypeHandleToClassID(th); } //--------------------------------------------------------------------------------------- // // Converts MethodDesc * to FunctionID // // Arguments: // pMD - MethodDesc * to convert // // Return Value: // Requested FunctionID // static FunctionID MethodDescToFunctionID(MethodDesc * pMD) { LIMITED_METHOD_CONTRACT; return reinterpret_cast< FunctionID > (pMD); } #endif //--------------------------------------------------------------------------------------- // // Converts FunctionID to MethodDesc * // // Arguments: // functionID - FunctionID to convert // // Return Value: // MethodDesc * requested // MethodDesc *FunctionIdToMethodDesc(FunctionID functionID) { LIMITED_METHOD_CONTRACT; MethodDesc *pMethodDesc; pMethodDesc = reinterpret_cast< MethodDesc* >(functionID); _ASSERTE(pMethodDesc != NULL); return pMethodDesc; } // (See comments for ArrayKindFromTypeHandle below.) typedef enum { ARRAY_KIND_TYPEDESC, // Normal, garden-variety typedesc array ARRAY_KIND_METHODTABLE, // Weirdo array with its own unshared methodtable (e.g., System.Object[]) ARRAY_KIND_NOTARRAY, // Not an array } ARRAY_KIND; //--------------------------------------------------------------------------------------- // // A couple Info calls need to understand what constitutes an "array", and what // kinds of arrays there are. ArrayKindFromTypeHandle tries to put some of this // knowledge in a single place // // Arguments: // th - TypeHandle to inspect // // Return Value: // ARRAY_KIND describing th // inline ARRAY_KIND ArrayKindFromTypeHandle(TypeHandle th) { LIMITED_METHOD_CONTRACT; if (th.IsArray()) { return ARRAY_KIND_TYPEDESC; } if (!th.IsTypeDesc() && th.GetMethodTable()->IsArray()) { return ARRAY_KIND_METHODTABLE; } return ARRAY_KIND_NOTARRAY; } #ifdef PROFILING_SUPPORTED //--------------------------------------------------------------------------------------- // ModuleILHeap IUnknown implementation // // Function headers unnecessary, as MSDN adequately documents IUnknown // ULONG ModuleILHeap::AddRef() { // Lifetime of this object is controlled entirely by the CLR. This // is created on first request, and is automatically destroyed when // the profiler is detached. return 1; } ULONG ModuleILHeap::Release() { // Lifetime of this object is controlled entirely by the CLR. This // is created on first request, and is automatically destroyed when // the profiler is detached. return 1; } HRESULT ModuleILHeap::QueryInterface(REFIID riid, void ** pp) { HRESULT hr = S_OK; if (pp == NULL) { return E_POINTER; } *pp = 0; if (riid == IID_IUnknown) { *pp = static_cast(this); } else if (riid == IID_IMethodMalloc) { *pp = static_cast(this); } else { hr = E_NOINTERFACE; } if (hr == S_OK) { // CLR manages lifetime of this object, but in case that changes (or // this code gets copied/pasted elsewhere), we'll still AddRef here so // QI remains a good citizen either way. AddRef(); } return hr; } //--------------------------------------------------------------------------------------- // Profiler entrypoint to allocate space from this module's heap. // // Arguments // cb - size in bytes of allocation request // // Return value // pointer to allocated memory, or NULL if there was an error void * STDMETHODCALLTYPE ModuleILHeap::Alloc(ULONG cb) { CONTRACTL { // Yay! NOTHROW; // (see GC_TRIGGERS comment below) CAN_TAKE_LOCK; // Allocations using loader heaps below enter a critsec, which switches // to preemptive, which is effectively a GC trigger GC_TRIGGERS; // Yay! MODE_ANY; SO_NOT_MAINLINE; } CONTRACTL_END; LOG((LF_CORPROF, LL_INFO1000, "**PROF: ModuleILHeap::Alloc 0x%08xp.\n", cb)); if (cb == 0) { return NULL; } return new (nothrow) BYTE[cb]; } //--------------------------------------------------------------------------------------- // The one and only instance of the IL heap ModuleILHeap ModuleILHeap::s_Heap; //--------------------------------------------------------------------------------------- // Implementation of ProfToEEInterfaceImpl's IUnknown // // The VM controls the lifetime of ProfToEEInterfaceImpl, not the // profiler. We'll automatically take care of cleanup when profilers // unload and detach. // ULONG STDMETHODCALLTYPE ProfToEEInterfaceImpl::AddRef() { LIMITED_METHOD_CONTRACT; return 1; } ULONG STDMETHODCALLTYPE ProfToEEInterfaceImpl::Release() { LIMITED_METHOD_CONTRACT; return 1; } COM_METHOD ProfToEEInterfaceImpl::QueryInterface(REFIID id, void ** pInterface) { if (pInterface == NULL) { return E_POINTER; } if (id == IID_ICorProfilerInfo) { *pInterface = static_cast(this); } else if (id == IID_ICorProfilerInfo2) { *pInterface = static_cast(this); } else if (id == IID_ICorProfilerInfo3) { *pInterface = static_cast(this); } else if (id == IID_ICorProfilerInfo4) { *pInterface = static_cast(this); } else if (id == IID_ICorProfilerInfo5) { *pInterface = static_cast(this); } else if (id == IID_ICorProfilerInfo6) { *pInterface = static_cast(this); } else if (id == IID_ICorProfilerInfo7) { *pInterface = static_cast(this); } else if (id == IID_ICorProfilerInfo8) { *pInterface = static_cast(this); } else if (id == IID_ICorProfilerInfo9) { *pInterface = static_cast(this); } else if (id == IID_IUnknown) { *pInterface = static_cast(static_cast(this)); } else { *pInterface = NULL; return E_NOINTERFACE; } // CLR manages lifetime of this object, but in case that changes (or // this code gets copied/pasted elsewhere), we'll still AddRef here so // QI remains a good citizen either way. AddRef(); return S_OK; } #endif // PROFILING_SUPPORTED //--------------------------------------------------------------------------------------- // // GC-related helpers. These are called from elsewhere in the EE to determine profiler // state, and to update the profiling API with info from the GC. // //--------------------------------------------------------------------------------------- // // ProfilerObjectAllocatedCallback is called if a profiler is attached, requesting // ObjectAllocated callbacks. // // Arguments: // objref - Reference to newly-allocated object // classId - ClassID of newly-allocated object // void __stdcall ProfilerObjectAllocatedCallback(OBJECTREF objref, ClassID classId) { CONTRACTL { THROWS; GC_TRIGGERS; MODE_COOPERATIVE; } CONTRACTL_END; TypeHandle th = OBJECTREFToObject(objref)->GetTypeHandle(); // WARNING: objref can move as a result of the ObjectAllocated() call below if // the profiler causes a GC, so any operations on the objref should occur above // this comment (unless you're prepared to add a GCPROTECT around the objref). #ifdef PROFILING_SUPPORTED // Notify the profiler of the allocation { BEGIN_PIN_PROFILER(CORProfilerTrackAllocations()); // Note that for generic code we always return uninstantiated ClassIDs and FunctionIDs. // Thus we strip any instantiations of the ClassID (which is really a type handle) here. g_profControlBlock.pProfInterface->ObjectAllocated( (ObjectID) OBJECTREFToObject(objref), classId); END_PIN_PROFILER(); } #endif // PROFILING_SUPPORTED } //--------------------------------------------------------------------------------------- // // Wrapper around the GC Started callback // // Arguments: // generation - Generation being collected // induced - Was this GC induced by GC.Collect? // void __stdcall GarbageCollectionStartedCallback(int generation, BOOL induced) { CONTRACTL { NOTHROW; GC_NOTRIGGER; MODE_ANY; // can be called even on GC threads } CONTRACTL_END; #ifdef PROFILING_SUPPORTED // // Mark that we are starting a GC. This will allow profilers to do limited object inspection // during callbacks that occur while a GC is happening. // g_profControlBlock.fGCInProgress = TRUE; // Notify the profiler of start of the collection { BEGIN_PIN_PROFILER(CORProfilerTrackGC()); BOOL generationCollected[COR_PRF_GC_LARGE_OBJECT_HEAP+1]; if (generation == COR_PRF_GC_GEN_2) generation = COR_PRF_GC_LARGE_OBJECT_HEAP; for (int gen = 0; gen <= COR_PRF_GC_LARGE_OBJECT_HEAP; gen++) generationCollected[gen] = gen <= generation; g_profControlBlock.pProfInterface->GarbageCollectionStarted( COR_PRF_GC_LARGE_OBJECT_HEAP+1, generationCollected, induced ? COR_PRF_GC_INDUCED : COR_PRF_GC_OTHER); END_PIN_PROFILER(); } #endif // PROFILING_SUPPORTED } //--------------------------------------------------------------------------------------- // // Wrapper around the GC Finished callback // void __stdcall GarbageCollectionFinishedCallback() { CONTRACTL { NOTHROW; GC_NOTRIGGER; MODE_ANY; // can be called even on GC threads } CONTRACTL_END; #ifdef PROFILING_SUPPORTED // Notify the profiler of end of the collection { BEGIN_PIN_PROFILER(CORProfilerTrackGC()); g_profControlBlock.pProfInterface->GarbageCollectionFinished(); END_PIN_PROFILER(); } // Mark that GC is finished. g_profControlBlock.fGCInProgress = FALSE; #endif // PROFILING_SUPPORTED } #ifdef PROFILING_SUPPORTED //--------------------------------------------------------------------------------------- // // Describes a GC generation by number and address range // struct GenerationDesc { int generation; BYTE *rangeStart; BYTE *rangeEnd; BYTE *rangeEndReserved; }; struct GenerationTable { ULONG count; ULONG capacity; static const ULONG defaultCapacity = 4; // that's the minimum for 3 generation plus the large object heap GenerationTable *prev; GenerationDesc *genDescTable; #ifdef _DEBUG ULONG magic; #define GENERATION_TABLE_MAGIC 0x34781256 #define GENERATION_TABLE_BAD_MAGIC 0x55aa55aa #endif }; //--------------------------------------------------------------------------------------- // // This is a callback used by the GC when we call GCHeapUtilities::DiagDescrGenerations // (from UpdateGenerationBounds() below). The GC gives us generation information through // this callback, which we use to update the GenerationDesc in the corresponding // GenerationTable // // Arguments: // context - The containing GenerationTable // generation - Generation number // rangeStart - Address where generation starts // rangeEnd - Address where generation ends // rangeEndReserved - Address where generation reserved space ends // // static static void GenWalkFunc(void * context, int generation, BYTE * rangeStart, BYTE * rangeEnd, BYTE * rangeEndReserved) { CONTRACT_VOID { NOTHROW; GC_NOTRIGGER; MODE_ANY; // can be called even on GC threads PRECONDITION(CheckPointer(context)); PRECONDITION(0 <= generation && generation <= 3); PRECONDITION(CheckPointer(rangeStart)); PRECONDITION(CheckPointer(rangeEnd)); PRECONDITION(CheckPointer(rangeEndReserved)); } CONTRACT_END; GenerationTable *generationTable = (GenerationTable *)context; _ASSERTE(generationTable->magic == GENERATION_TABLE_MAGIC); ULONG count = generationTable->count; if (count >= generationTable->capacity) { ULONG newCapacity = generationTable->capacity == 0 ? GenerationTable::defaultCapacity : generationTable->capacity * 2; GenerationDesc *newGenDescTable = new (nothrow) GenerationDesc[newCapacity]; if (newGenDescTable == NULL) { // if we can't allocate a bigger table, we'll have to ignore this call RETURN; } memcpy(newGenDescTable, generationTable->genDescTable, sizeof(generationTable->genDescTable[0]) * generationTable->count); delete[] generationTable->genDescTable; generationTable->genDescTable = newGenDescTable; generationTable->capacity = newCapacity; } _ASSERTE(count < generationTable->capacity); GenerationDesc *genDescTable = generationTable->genDescTable; genDescTable[count].generation = generation; genDescTable[count].rangeStart = rangeStart; genDescTable[count].rangeEnd = rangeEnd; genDescTable[count].rangeEndReserved = rangeEndReserved; generationTable->count = count + 1; } // This is the table of generation bounds updated by the gc // and read by the profiler. So this is a single writer, // multiple readers scenario. static GenerationTable *s_currentGenerationTable; // The generation table is updated atomically by replacing the // pointer to it. The only tricky part is knowing when // the old table can be deleted. static Volatile s_generationTableLock; // This is just so we can assert there's a single writer #ifdef ENABLE_CONTRACTS static Volatile s_generationTableWriterCount; #endif #endif // PROFILING_SUPPORTED //--------------------------------------------------------------------------------------- // // This is called from the gc to push a new set of generation bounds // void __stdcall UpdateGenerationBounds() { CONTRACT_VOID { NOTHROW; GC_NOTRIGGER; MODE_ANY; // can be called even on GC threads #ifdef PROFILING_SUPPORTED PRECONDITION(FastInterlockIncrement(&s_generationTableWriterCount) == 1); POSTCONDITION(FastInterlockDecrement(&s_generationTableWriterCount) == 0); #endif // PROFILING_SUPPORTED } CONTRACT_END; #ifdef PROFILING_SUPPORTED // Notify the profiler of start of the collection if (CORProfilerTrackGC()) { // generate a new generation table GenerationTable *newGenerationTable = new (nothrow) GenerationTable(); if (newGenerationTable == NULL) RETURN; newGenerationTable->count = 0; newGenerationTable->capacity = GenerationTable::defaultCapacity; // if there is already a current table, use its count as a guess for the capacity if (s_currentGenerationTable != NULL) newGenerationTable->capacity = s_currentGenerationTable->count; newGenerationTable->prev = NULL; newGenerationTable->genDescTable = new (nothrow) GenerationDesc[newGenerationTable->capacity]; if (newGenerationTable->genDescTable == NULL) newGenerationTable->capacity = 0; #ifdef _DEBUG newGenerationTable->magic = GENERATION_TABLE_MAGIC; #endif // fill in the values by calling back into the gc, which will report // the ranges by calling GenWalkFunc for each one IGCHeap *hp = GCHeapUtilities::GetGCHeap(); hp->DiagDescrGenerations(GenWalkFunc, newGenerationTable); // remember the old table and plug in the new one GenerationTable *oldGenerationTable = s_currentGenerationTable; s_currentGenerationTable = newGenerationTable; // WARNING: tricky code! // // We sample the generation table lock *after* plugging in the new table // We do so using an interlocked operation so the cpu can't reorder // the write to the s_currentGenerationTable with the increment. // If the interlocked increment returns 1, we know nobody can be using // the old table (readers increment the lock before using the table, // and decrement it afterwards). Any new readers coming in // will use the new table. So it's safe to delete the old // table. // On the other hand, if the interlocked increment returns // something other than one, we put the old table on a list // dangling off of the new one. Next time around, we'll try again // deleting any old tables. if (FastInterlockIncrement(&s_generationTableLock) == 1) { // We know nobody can be using any of the old tables while (oldGenerationTable != NULL) { _ASSERTE(oldGenerationTable->magic == GENERATION_TABLE_MAGIC); #ifdef _DEBUG oldGenerationTable->magic = GENERATION_TABLE_BAD_MAGIC; #endif GenerationTable *temp = oldGenerationTable; oldGenerationTable = oldGenerationTable->prev; delete[] temp->genDescTable; delete temp; } } else { // put the old table on a list newGenerationTable->prev = oldGenerationTable; } FastInterlockDecrement(&s_generationTableLock); } #endif // PROFILING_SUPPORTED RETURN; } #ifdef PROFILING_SUPPORTED //--------------------------------------------------------------------------------------- // // Determines whether we are in a window to allow object inspection. // // Return Value: // Returns S_OK if we can determine that we are in a window to allow object // inspection. Otherwise a failure HRESULT is returned // HRESULT AllowObjectInspection() { CONTRACTL { NOTHROW; GC_NOTRIGGER; MODE_ANY; // tests for preemptive mode dynamically as its main function so contract enforcement is not appropriate } CONTRACTL_END; // // Check first to see if we are in the process of doing a GC and presume that the profiler // is making this object inspection from the same thread that notified of a valid ObjectID. // if (g_profControlBlock.fGCInProgress) { return S_OK; } // // Thus we must have a managed thread, and it must be in coop mode. // (That will also guarantee we're in a callback). // Thread * pThread = GetThreadNULLOk(); if (pThread == NULL) { return CORPROF_E_NOT_MANAGED_THREAD; } // Note this is why we don't enforce the contract of being in cooperative mode the whole point // is that clients of this fellow want to return a robust error if not cooperative // so technically they are mode_any although the only true preemptive support they offer // is graceful failure in that case if (!pThread->PreemptiveGCDisabled()) { return CORPROF_E_UNSUPPORTED_CALL_SEQUENCE; } return S_OK; } //--------------------------------------------------------------------------------------- // // helper functions for the GC events // #endif // PROFILING_SUPPORTED #if defined(PROFILING_SUPPORTED) || defined(FEATURE_EVENT_TRACE) //--------------------------------------------------------------------------------------- // // It's generally unsafe for profiling API code to call Get(GCSafe)TypeHandle() on // objects, since we can encounter objects on the heap whose types belong to unloading // AppDomains. In such cases, getting the type handle of the object could AV. Use this // function instead, which will return NULL for potentially unloaded types. // // Arguments: // pObj - Object * whose ClassID is desired // // Return Value: // ClassID of the object, if it's safe to look it up. Else NULL. // ClassID SafeGetClassIDFromObject(Object * pObj) { CONTRACTL { NOTHROW; GC_NOTRIGGER; } CONTRACTL_END; TypeHandle th = pObj->GetGCSafeTypeHandleIfPossible(); if(th == NULL) { return NULL; } return TypeHandleToClassID(th); } //--------------------------------------------------------------------------------------- // // Callback of type walk_fn used by IGCHeap::DiagWalkObject. Keeps a count of each // object reference found. // // Arguments: // pBO - Object reference encountered in walk // context - running count of object references encountered // // Return Value: // Always returns TRUE to object walker so it walks the entire object // bool CountContainedObjectRef(Object * pBO, void * context) { LIMITED_METHOD_CONTRACT; // Increase the count (*((size_t *)context))++; return TRUE; } //--------------------------------------------------------------------------------------- // // Callback of type walk_fn used by IGCHeap::DiagWalkObject. Stores each object reference // encountered into an array. // // Arguments: // pBO - Object reference encountered in walk // context - Array of locations within the walked object that point to other // objects. On entry, (*context) points to the next unfilled array // entry. On exit, that location is filled, and (*context) is incremented // to point to the next entry. // // Return Value: // Always returns TRUE to object walker so it walks the entire object // bool SaveContainedObjectRef(Object * pBO, void * context) { LIMITED_METHOD_CONTRACT; // Assign the value **((Object ***)context) = pBO; // Now increment the array pointer // // Note that HeapWalkHelper has already walked the references once to count them up, // and then allocated an array big enough to hold those references. First time this // callback is called for a given object, (*context) points to the first entry in the // array. So "blindly" incrementing (*context) here and using it next time around // for the next reference, over and over again, should be safe. (*((Object ***)context))++; return TRUE; } //--------------------------------------------------------------------------------------- // // Callback of type walk_fn used by the GC when walking the heap, to help profapi and ETW // track objects. This guy orchestrates the use of the above callbacks which dig // into object references contained each object encountered by this callback. // This method is defined when either GC_PROFILING is defined or FEATURE_EVENT_TRACING // is defined and can operate fully when only one of the two is defined. // // Arguments: // pBO - Object reference encountered on the heap // pvContext - Pointer to ProfilerWalkHeapContext, containing ETW context built up // during this GC, and which remembers if profapi-profiler is supposed to be called. // // Return Value: // BOOL indicating whether the heap walk should continue. // TRUE=continue // FALSE=stop // extern bool s_forcedGCInProgress; bool HeapWalkHelper(Object * pBO, void * pvContext) { CONTRACTL { NOTHROW; GC_NOTRIGGER; SO_INTOLERANT; MODE_ANY; } CONTRACTL_END; OBJECTREF * arrObjRef = NULL; size_t cNumRefs = 0; bool bOnStack = false; MethodTable * pMT = pBO->GetMethodTable(); ProfilerWalkHeapContext * pProfilerWalkHeapContext = (ProfilerWalkHeapContext *) pvContext; if (pMT->ContainsPointersOrCollectible()) { // First round through calculates the number of object refs for this class GCHeapUtilities::GetGCHeap()->DiagWalkObject(pBO, &CountContainedObjectRef, (void *)&cNumRefs); if (cNumRefs > 0) { // Create an array to contain all of the refs for this object bOnStack = cNumRefs <= 32 ? true : false; if (bOnStack) { // It's small enough, so just allocate on the stack arrObjRef = (OBJECTREF *)_alloca(cNumRefs * sizeof(OBJECTREF)); } else { // Otherwise, allocate from the heap arrObjRef = new (nothrow) OBJECTREF[cNumRefs]; if (!arrObjRef) { return FALSE; } } // Second round saves off all of the ref values OBJECTREF * pCurObjRef = arrObjRef; GCHeapUtilities::GetGCHeap()->DiagWalkObject(pBO, &SaveContainedObjectRef, (void *)&pCurObjRef); } } HRESULT hr = E_FAIL; #if defined(GC_PROFILING) if (pProfilerWalkHeapContext->fProfilerPinned) { // It is not safe and could be overflowed to downcast size_t to ULONG on WIN64. // However, we have to do this dangerous downcast here to comply with the existing Profiling COM interface. // We are currently evaluating ways to fix this potential overflow issue. hr = g_profControlBlock.pProfInterface->ObjectReference( (ObjectID) pBO, SafeGetClassIDFromObject(pBO), (ULONG) cNumRefs, (ObjectID *) arrObjRef); } #endif #ifdef FEATURE_EVENT_TRACE if (s_forcedGCInProgress && ETW_TRACING_CATEGORY_ENABLED(MICROSOFT_WINDOWS_DOTNETRUNTIME_PROVIDER_Context, TRACE_LEVEL_INFORMATION, CLR_GCHEAPDUMP_KEYWORD)) { ETW::GCLog::ObjectReference( pProfilerWalkHeapContext, pBO, (ULONGLONG) SafeGetClassIDFromObject(pBO), cNumRefs, (Object **) arrObjRef); } #endif // FEATURE_EVENT_TRACE // If the data was not allocated on the stack, need to clean it up. if ((arrObjRef != NULL) && !bOnStack) { delete [] arrObjRef; } // Return TRUE iff we want to the heap walk to continue. The only way we'd abort the // heap walk is if we're issuing profapi callbacks, and the profapi profiler // intentionally returned a failed HR (as its request that we stop the walk). There's // a potential conflict here. If a profapi profiler and an ETW profiler are both // monitoring the heap dump, and the profapi profiler requests to abort the walk (but // the ETW profiler may not want to abort the walk), then what do we do? The profapi // profiler gets precedence. We don't want to accidentally send more callbacks to a // profapi profiler that explicitly requested an abort. The ETW profiler will just // have to deal. In theory, I could make the code more complex by remembering that a // profapi profiler requested to abort the dump but an ETW profiler is still // attached, and then intentionally inhibit the remainder of the profapi callbacks // for this GC. But that's unnecessary complexity. In practice, it should be // extremely rare that a profapi profiler is monitoring heap dumps AND an ETW // profiler is also monitoring heap dumps. return (pProfilerWalkHeapContext->fProfilerPinned) ? SUCCEEDED(hr) : TRUE; } #endif // defined(GC_PROFILING) || defined(FEATURE_EVENT_TRACING) #ifdef PROFILING_SUPPORTED //--------------------------------------------------------------------------------------- // // Callback of type walk_fn used by the GC when walking the heap, to help profapi // track objects. This is really just a wrapper around // EEToProfInterfaceImpl::AllocByClass, which does the real work // // Arguments: // pBO - Object reference encountered on the heap // pv - Structure used by EEToProfInterfaceImpl::AllocByClass to do its work. // // Return Value: // BOOL indicating whether the heap walk should continue. // TRUE=continue // FALSE=stop // Currently always returns TRUE // bool AllocByClassHelper(Object * pBO, void * pv) { CONTRACTL { NOTHROW; GC_NOTRIGGER; MODE_ANY; } CONTRACTL_END; _ASSERTE(pv != NULL); { BEGIN_PIN_PROFILER(CORProfilerPresent()); // Pass along the call g_profControlBlock.pProfInterface->AllocByClass( (ObjectID) pBO, SafeGetClassIDFromObject(pBO), pv); END_PIN_PROFILER(); } return TRUE; } #endif // PROFILING_SUPPORTED #if defined(GC_PROFILING) || defined(FEATURE_EVENT_TRACE) //--------------------------------------------------------------------------------------- // // Callback of type promote_func called by GC while scanning roots (in GCProfileWalkHeap, // called after the collection). Wrapper around EEToProfInterfaceImpl::RootReference2, // which does the real work. // // Arguments: // pObj - Object reference encountered /// ppRoot - Address that references ppObject (can be interior pointer) // pSC - ProfilingScanContext * containing the root kind and GCReferencesData used // by RootReference2 // dwFlags - Properties of the root as GC_CALL* constants (this function converts // to COR_PRF_GC_ROOT_FLAGS. // void ScanRootsHelper(Object* pObj, Object ** ppRoot, ScanContext *pSC, uint32_t dwFlags) { CONTRACTL { NOTHROW; GC_NOTRIGGER; SO_INTOLERANT; MODE_ANY; } CONTRACTL_END; // RootReference2 can return E_OUTOFMEMORY, and we're swallowing that. // Furthermore, we can't really handle it because we're callable during GC promotion. // On the other hand, this only means profiling information will be incomplete, // so it's ok to swallow E_OUTOFMEMORY. // FAULT_NOT_FATAL(); ProfilingScanContext *pPSC = (ProfilingScanContext *)pSC; DWORD dwEtwRootFlags = 0; if (dwFlags & GC_CALL_INTERIOR) dwEtwRootFlags |= kEtwGCRootFlagsInterior; if (dwFlags & GC_CALL_PINNED) dwEtwRootFlags |= kEtwGCRootFlagsPinning; #if defined(GC_PROFILING) void *rootID = NULL; switch (pPSC->dwEtwRootKind) { case kEtwGCRootKindStack: rootID = pPSC->pMD; break; case kEtwGCRootKindHandle: _ASSERT(!"Shouldn't see handle here"); case kEtwGCRootKindFinalizer: default: break; } // Notify profiling API of the root if (pPSC->fProfilerPinned) { // Let the profiling code know about this root reference g_profControlBlock.pProfInterface-> RootReference2((BYTE *)pObj, pPSC->dwEtwRootKind, (EtwGCRootFlags)dwEtwRootFlags, (BYTE *)rootID, &((pPSC)->pHeapId)); } #endif #ifdef FEATURE_EVENT_TRACE // Notify ETW of the root if (s_forcedGCInProgress && ETW_TRACING_CATEGORY_ENABLED(MICROSOFT_WINDOWS_DOTNETRUNTIME_PROVIDER_Context, TRACE_LEVEL_INFORMATION, CLR_GCHEAPDUMP_KEYWORD)) { ETW::GCLog::RootReference( NULL, // handle is NULL, cuz this is a non-HANDLE root pObj, // object being rooted NULL, // pSecondaryNodeForDependentHandle is NULL, cuz this isn't a dependent handle FALSE, // is dependent handle pPSC, dwFlags, // dwGCFlags dwEtwRootFlags); } #endif // FEATURE_EVENT_TRACE } #endif // defined(GC_PROFILING) || defined(FEATURE_EVENT_TRACE) #ifdef PROFILING_SUPPORTED //--------------------------------------------------------------------------------------- // // Private ProfToEEInterfaceImpl maintenance functions // //--------------------------------------------------------------------------------------- // // Initialize ProfToEEInterfaceImpl (including ModuleILHeap statics) // // Return Value: // HRESULT indicating success // HRESULT ProfToEEInterfaceImpl::Init() { CONTRACTL { NOTHROW; CANNOT_TAKE_LOCK; GC_NOTRIGGER; MODE_ANY; } CONTRACTL_END; LOG((LF_CORPROF, LL_INFO1000, "**PROF: Init.\n")); #ifdef _DEBUG if (ProfilingAPIUtility::ShouldInjectProfAPIFault(kProfAPIFault_StartupInternal)) { return E_OUTOFMEMORY; } #endif //_DEBUG return S_OK; } //--------------------------------------------------------------------------------------- // // Destroy ProfToEEInterfaceImpl (including ModuleILHeap statics) // ProfToEEInterfaceImpl::~ProfToEEInterfaceImpl() { CONTRACTL { NOTHROW; GC_NOTRIGGER; MODE_ANY; } CONTRACTL_END; LOG((LF_CORPROF, LL_INFO1000, "**PROF: Terminate.\n")); } //--------------------------------------------------------------------------------------- // // Obsolete info functions // HRESULT ProfToEEInterfaceImpl::GetInprocInspectionInterface(IUnknown **) { LIMITED_METHOD_CONTRACT; return E_NOTIMPL; } HRESULT ProfToEEInterfaceImpl::GetInprocInspectionIThisThread(IUnknown **) { LIMITED_METHOD_CONTRACT; return E_NOTIMPL; } HRESULT ProfToEEInterfaceImpl::BeginInprocDebugging(BOOL, DWORD *) { LIMITED_METHOD_CONTRACT; return E_NOTIMPL; } HRESULT ProfToEEInterfaceImpl::EndInprocDebugging(DWORD) { LIMITED_METHOD_CONTRACT; return E_NOTIMPL; } HRESULT ProfToEEInterfaceImpl::SetFunctionReJIT(FunctionID) { LIMITED_METHOD_CONTRACT; return E_NOTIMPL; } //--------------------------------------------------------------------------------------- // // ******************************* // Public Profiler->EE entrypoints // ******************************* // // ProfToEEInterfaceImpl implementation of public ICorProfilerInfo* methods // // NOTE: All ICorProfilerInfo* method implementations must follow the rules stated // at the top of this file! // // See corprof.idl / MSDN for detailed comments about each of these public // functions, their parameters, return values, etc. HRESULT ProfToEEInterfaceImpl::SetEventMask(DWORD dwEventMask) { CONTRACTL { // Yay! NOTHROW; // Yay! GC_NOTRIGGER; // Yay! MODE_ANY; // Yay! EE_THREAD_NOT_REQUIRED; CANNOT_TAKE_LOCK; SO_NOT_MAINLINE; } CONTRACTL_END; PROFILER_TO_CLR_ENTRYPOINT_ASYNC_EX(kP2EEAllowableAfterAttach, (LF_CORPROF, LL_INFO1000, "**PROF: SetEventMask 0x%08x.\n", dwEventMask)); _ASSERTE(CORProfilerPresentOrInitializing()); return g_profControlBlock.pProfInterface->SetEventMask(dwEventMask, 0 /* No high bits */); } HRESULT ProfToEEInterfaceImpl::SetEventMask2(DWORD dwEventsLow, DWORD dwEventsHigh) { CONTRACTL { // Yay! NOTHROW; // Yay! GC_NOTRIGGER; // Yay! MODE_ANY; // Yay! EE_THREAD_NOT_REQUIRED; CANNOT_TAKE_LOCK; SO_NOT_MAINLINE; } CONTRACTL_END; PROFILER_TO_CLR_ENTRYPOINT_ASYNC_EX(kP2EEAllowableAfterAttach, (LF_CORPROF, LL_INFO1000, "**PROF: SetEventMask2 0x%08x, 0x%08x.\n", dwEventsLow, dwEventsHigh)); _ASSERTE(CORProfilerPresentOrInitializing()); return g_profControlBlock.pProfInterface->SetEventMask(dwEventsLow, dwEventsHigh); } HRESULT ProfToEEInterfaceImpl::GetHandleFromThread(ThreadID threadId, HANDLE *phThread) { CONTRACTL { // Yay! NOTHROW; // Yay! GC_NOTRIGGER; // Yay! MODE_ANY; // Yay! EE_THREAD_NOT_REQUIRED; // Yay! CANNOT_TAKE_LOCK; SO_NOT_MAINLINE; } CONTRACTL_END; PROFILER_TO_CLR_ENTRYPOINT_SYNC_EX(kP2EEAllowableAfterAttach, (LF_CORPROF, LL_INFO1000, "**PROF: GetHandleFromThread 0x%p.\n", threadId)); if (!IsManagedThread(threadId)) { return E_INVALIDARG; } HRESULT hr = S_OK; HANDLE hThread = ((Thread *)threadId)->GetThreadHandle(); if (hThread == INVALID_HANDLE_VALUE) hr = E_INVALIDARG; else if (phThread) *phThread = hThread; return (hr); } HRESULT ProfToEEInterfaceImpl::GetObjectSize(ObjectID objectId, ULONG *pcSize) { CONTRACTL { // Yay! NOTHROW; // Yay! GC_NOTRIGGER; // Yay! Fail at runtime if in preemptive mode via AllowObjectInspection() MODE_ANY; // Yay! EE_THREAD_NOT_REQUIRED; // Yay! CANNOT_TAKE_LOCK; SO_NOT_MAINLINE; } CONTRACTL_END; PROFILER_TO_CLR_ENTRYPOINT_SYNC_EX(kP2EEAllowableAfterAttach, (LF_CORPROF, LL_INFO1000, "**PROF: GetObjectSize 0x%p.\n", objectId)); if (objectId == NULL) { return E_INVALIDARG; } HRESULT hr = AllowObjectInspection(); if (FAILED(hr)) { return hr; } // Get the object pointer Object *pObj = reinterpret_cast(objectId); // Get the size if (pcSize) { SIZE_T size = pObj->GetSize(); if(size < MIN_OBJECT_SIZE) { size = PtrAlign(size); } if (size > ULONG_MAX) { *pcSize = ULONG_MAX; return COR_E_OVERFLOW; } *pcSize = (ULONG)size; } // Indicate success return (S_OK); } HRESULT ProfToEEInterfaceImpl::GetObjectSize2(ObjectID objectId, SIZE_T *pcSize) { CONTRACTL { // Yay! NOTHROW; // Yay! GC_NOTRIGGER; // Yay! Fail at runtime if in preemptive mode via AllowObjectInspection() MODE_ANY; // Yay! EE_THREAD_NOT_REQUIRED; // Yay! CANNOT_TAKE_LOCK; SO_NOT_MAINLINE; } CONTRACTL_END; PROFILER_TO_CLR_ENTRYPOINT_SYNC_EX(kP2EEAllowableAfterAttach, (LF_CORPROF, LL_INFO1000, "**PROF: GetObjectSize2 0x%p.\n", objectId)); if (objectId == NULL) { return E_INVALIDARG; } HRESULT hr = AllowObjectInspection(); if (FAILED(hr)) { return hr; } // Get the object pointer Object *pObj = reinterpret_cast(objectId); // Get the size if (pcSize) { SIZE_T size = pObj->GetSize(); if(size < MIN_OBJECT_SIZE) { size = PtrAlign(size); } *pcSize = size; } // Indicate success return (S_OK); } HRESULT ProfToEEInterfaceImpl::IsArrayClass( /* [in] */ ClassID classId, /* [out] */ CorElementType *pBaseElemType, /* [out] */ ClassID *pBaseClassId, /* [out] */ ULONG *pcRank) { CONTRACTL { NOTHROW; GC_NOTRIGGER; // Yay! MODE_ANY; // Yay! EE_THREAD_NOT_REQUIRED; // Yay! CANNOT_TAKE_LOCK; SO_NOT_MAINLINE; } CONTRACTL_END; PROFILER_TO_CLR_ENTRYPOINT_ASYNC_EX(kP2EEAllowableAfterAttach, (LF_CORPROF, LL_INFO1000, "**PROF: IsArrayClass 0x%p.\n", classId)); HRESULT hr; if (classId == NULL) { return E_INVALIDARG; } TypeHandle th = TypeHandle::FromPtr((void *)classId); ARRAY_KIND arrayKind = ArrayKindFromTypeHandle(th); // If this is indeed an array class, get some info about it switch (arrayKind) { default: { _ASSERTE(!"Unexpected return from ArrayKindFromTypeHandle()"); hr = E_UNEXPECTED; break; } case ARRAY_KIND_TYPEDESC: { // This is actually an array, so cast it up ArrayTypeDesc *pArr = th.AsArray(); // Fill in the type if they want it if (pBaseElemType != NULL) { *pBaseElemType = pArr->GetArrayElementTypeHandle().GetVerifierCorElementType(); } // If this is an array of classes and they wish to have the base type // If there is no associated class with this type, then there's no problem // because GetClass returns NULL which is the default we want to return in // this case. // Note that for generic code we always return uninstantiated ClassIDs and FunctionIDs if (pBaseClassId != NULL) { *pBaseClassId = TypeHandleToClassID(pArr->GetTypeParam()); } // If they want the number of dimensions of the array if (pcRank != NULL) { *pcRank = (ULONG) pArr->GetRank(); } // S_OK indicates that this was indeed an array hr = S_OK; break; } case ARRAY_KIND_METHODTABLE: { MethodTable *pArrMT = th.GetMethodTable(); // Fill in the type if they want it if (pBaseElemType != NULL) { *pBaseElemType = pArrMT->GetArrayElementType(); } // If this is an array of classes and they wish to have the base type. if (pBaseClassId != NULL) { *pBaseClassId = TypeHandleToClassID(pArrMT->GetApproxArrayElementTypeHandle()); } // If they want the number of dimensions of the array if (pcRank != NULL) { *pcRank = (ULONG) pArrMT->GetRank(); } // S_OK indicates that this was indeed an array hr = S_OK; break; } case ARRAY_KIND_NOTARRAY: { if (pBaseClassId != NULL) { *pBaseClassId = NULL; } // This is not an array, S_FALSE indicates so. hr = S_FALSE; break; } } return hr; } HRESULT ProfToEEInterfaceImpl::GetThreadInfo(ThreadID threadId, DWORD *pdwWin32ThreadId) { CONTRACTL { // Yay! NOTHROW; // Yay! GC_NOTRIGGER; // Yay! MODE_ANY; // Yay! EE_THREAD_NOT_REQUIRED; // Yay! CANNOT_TAKE_LOCK; SO_NOT_MAINLINE; } CONTRACTL_END; PROFILER_TO_CLR_ENTRYPOINT_SYNC_EX(kP2EEAllowableAfterAttach, (LF_CORPROF, LL_INFO1000, "**PROF: GetThreadInfo 0x%p.\n", threadId)); if (!IsManagedThread(threadId)) { return E_INVALIDARG; } if (pdwWin32ThreadId) { *pdwWin32ThreadId = ((Thread *)threadId)->GetOSThreadId(); } return S_OK; } HRESULT ProfToEEInterfaceImpl::GetCurrentThreadID(ThreadID *pThreadId) { CONTRACTL { // Yay! NOTHROW; // Yay! GC_NOTRIGGER; // Yay! MODE_ANY; // Yay! EE_THREAD_NOT_REQUIRED; // Yay! CANNOT_TAKE_LOCK; SO_NOT_MAINLINE; } CONTRACTL_END; PROFILER_TO_CLR_ENTRYPOINT_ASYNC_EX(kP2EEAllowableAfterAttach, (LF_CORPROF, LL_INFO1000, "**PROF: GetCurrentThreadID.\n")); HRESULT hr = S_OK; // No longer assert that GetThread doesn't return NULL, since callbacks // can now occur on non-managed threads (such as the GC helper threads) Thread * pThread = GetThreadNULLOk(); // If pThread is null, then the thread has never run managed code and // so has no ThreadID if (!IsManagedThread(pThread)) hr = CORPROF_E_NOT_MANAGED_THREAD; // Only provide value if they want it else if (pThreadId) *pThreadId = (ThreadID) pThread; return (hr); } //--------------------------------------------------------------------------------------- // // Internal helper function to wrap a call into the JIT manager to get information about // a managed function based on IP // // Arguments: // ip - IP address inside managed function of interest // ppCodeInfo - [out] information about the managed function based on IP // // Return Value: // HRESULT indicating success or failure. // // HRESULT GetFunctionInfoInternal(LPCBYTE ip, EECodeInfo * pCodeInfo) { CONTRACTL { NOTHROW; GC_NOTRIGGER; EE_THREAD_NOT_REQUIRED; CAN_TAKE_LOCK; CANNOT_RETAKE_LOCK; SO_NOT_MAINLINE; // If this is called asynchronously (from a hijacked thread, as with F1), it must not re-enter the // host (SQL). Corners will be cut to ensure this is the case if (ShouldAvoidHostCalls()) { HOST_NOCALLS; } else { HOST_CALLS; } } CONTRACTL_END; // Before calling into the code manager, ensure the GC heap has been // initialized--else the code manager will assert trying to get info from the heap. if (!IsGarbageCollectorFullyInitialized()) { return CORPROF_E_NOT_YET_AVAILABLE; } if (ShouldAvoidHostCalls()) { ExecutionManager::ReaderLockHolder rlh(NoHostCalls); if (!rlh.Acquired()) { // Couldn't get the info. Try again later return CORPROF_E_ASYNCHRONOUS_UNSAFE; } pCodeInfo->Init((PCODE)ip, ExecutionManager::ScanNoReaderLock); } else { pCodeInfo->Init((PCODE)ip); } if (!pCodeInfo->IsValid()) { return E_FAIL; } return S_OK; } HRESULT GetFunctionFromIPInternal(LPCBYTE ip, EECodeInfo * pCodeInfo, BOOL failOnNoMetadata) { CONTRACTL { NOTHROW; GC_NOTRIGGER; MODE_ANY; EE_THREAD_NOT_REQUIRED; CAN_TAKE_LOCK; SO_NOT_MAINLINE; } CONTRACTL_END; _ASSERTE (pCodeInfo != NULL); HRESULT hr = GetFunctionInfoInternal(ip, pCodeInfo); if (FAILED(hr)) { return hr; } if (failOnNoMetadata) { // never return a method that the user of the profiler API cannot use if (pCodeInfo->GetMethodDesc()->IsNoMetadata()) { return E_FAIL; } } return S_OK; } HRESULT ProfToEEInterfaceImpl::GetFunctionFromIP(LPCBYTE ip, FunctionID * pFunctionId) { CONTRACTL { // Yay! NOTHROW; // Yay! GC_NOTRIGGER; // Yay! MODE_ANY; // Yay! EE_THREAD_NOT_REQUIRED; // Querying the code manager requires a reader lock. However, see // code:#DisableLockOnAsyncCalls DISABLED(CAN_TAKE_LOCK); // Asynchronous functions can be called at arbitrary times when runtime // is holding locks that cannot be reentered without causing deadlock. // This contract detects any attempts to reenter locks held at the time // this function was called. CANNOT_RETAKE_LOCK; SO_NOT_MAINLINE; // If this is called asynchronously (from a hijacked thread, as with F1), it must not re-enter the // host (SQL). Corners will be cut to ensure this is the case if (ShouldAvoidHostCalls()) { HOST_NOCALLS; } else { HOST_CALLS; } } CONTRACTL_END; // See code:#DisableLockOnAsyncCalls PERMANENT_CONTRACT_VIOLATION(TakesLockViolation, ReasonProfilerAsyncCannotRetakeLock); PROFILER_TO_CLR_ENTRYPOINT_ASYNC_EX(kP2EEAllowableAfterAttach, (LF_CORPROF, LL_INFO1000, "**PROF: GetFunctionFromIP 0x%p.\n", ip)); // This call is allowed asynchronously, but the JIT functions take a reader lock. // So we need to ensure the current thread hasn't been hijacked by a profiler while // it was holding the writer lock. Checking the ForbidSuspendThread region is a // sufficient test for this FAIL_IF_IN_FORBID_SUSPEND_REGION(); HRESULT hr = S_OK; EECodeInfo codeInfo; hr = GetFunctionFromIPInternal(ip, &codeInfo, /* failOnNoMetadata */ TRUE); if (FAILED(hr)) { return hr; } if (pFunctionId) { *pFunctionId = MethodDescToFunctionID(codeInfo.GetMethodDesc()); } return S_OK; } HRESULT ProfToEEInterfaceImpl::GetFunctionFromIP2(LPCBYTE ip, FunctionID * pFunctionId, ReJITID * pReJitId) { CONTRACTL { // Yay! NOTHROW; // Grabbing the rejitid requires entering the rejit manager's hash table & lock, // which can switch us to preemptive mode and trigger GCs GC_TRIGGERS; // Yay! MODE_ANY; // Yay! EE_THREAD_NOT_REQUIRED; // Grabbing the rejitid requires entering the rejit manager's hash table & lock, CAN_TAKE_LOCK; SO_NOT_MAINLINE; } CONTRACTL_END; // See code:#DisableLockOnAsyncCalls PERMANENT_CONTRACT_VIOLATION(TakesLockViolation, ReasonProfilerAsyncCannotRetakeLock); PROFILER_TO_CLR_ENTRYPOINT_SYNC_EX( kP2EEAllowableAfterAttach | kP2EETriggers, (LF_CORPROF, LL_INFO1000, "**PROF: GetFunctionFromIP2 0x%p.\n", ip)); HRESULT hr = S_OK; EECodeInfo codeInfo; hr = GetFunctionFromIPInternal(ip, &codeInfo, /* failOnNoMetadata */ TRUE); if (FAILED(hr)) { return hr; } if (pFunctionId) { *pFunctionId = MethodDescToFunctionID(codeInfo.GetMethodDesc()); } if (pReJitId != NULL) { MethodDesc * pMD = codeInfo.GetMethodDesc(); *pReJitId = ReJitManager::GetReJitId(pMD, codeInfo.GetStartAddress()); } return S_OK; } //***************************************************************************** // Given a function id, retrieve the metadata token and a reader api that // can be used against the token. //***************************************************************************** HRESULT ProfToEEInterfaceImpl::GetTokenAndMetaDataFromFunction( FunctionID functionId, REFIID riid, IUnknown **ppOut, mdToken *pToken) { CONTRACTL { // Yay! NOTHROW; // Yay! GC_NOTRIGGER; // Yay! MODE_ANY; // Yay! EE_THREAD_NOT_REQUIRED; // PEFile::GetRWImporter and GetReadablePublicMetaDataInterface take locks CAN_TAKE_LOCK; SO_NOT_MAINLINE; } CONTRACTL_END; PROFILER_TO_CLR_ENTRYPOINT_SYNC_EX(kP2EEAllowableAfterAttach, (LF_CORPROF, LL_INFO1000, "**PROF: GetTokenAndMetaDataFromFunction 0x%p.\n", functionId)); if (functionId == NULL) { return E_INVALIDARG; } HRESULT hr = S_OK; MethodDesc *pMD = FunctionIdToMethodDesc(functionId); // it's not safe to examine a methoddesc that has not been restored so do not do so if (!pMD->IsRestored()) return CORPROF_E_DATAINCOMPLETE; if (pToken) { *pToken = pMD->GetMemberDef(); } // don't bother with any of this module fetching if the metadata access isn't requested if (ppOut) { Module * pMod = pMD->GetModule(); hr = pMod->GetReadablePublicMetaDataInterface(ofRead, riid, (LPVOID *) ppOut); } return hr; } //--------------------------------------------------------------------------------------- // What follows are the GetCodeInfo* APIs and their helpers. The two helpers factor out // some of the common code to validate parameters and then determine the code info from // the start of the code. Each individual GetCodeInfo* API differs in how it uses these // helpers, particuarly in how it determines the start of the code (GetCodeInfo3 needs // to use the rejit manager to determine the code start, whereas the others do not). // Factoring out like this allows us to have statically determined contracts that differ // based on whether we need to use the rejit manager, which requires locking and // may trigger GCs. //--------------------------------------------------------------------------------------- HRESULT ValidateParametersForGetCodeInfo( MethodDesc * pMethodDesc, ULONG32 cCodeInfos, COR_PRF_CODE_INFO codeInfos[]) { LIMITED_METHOD_CONTRACT; if (pMethodDesc == NULL) { return E_INVALIDARG; } if ((cCodeInfos != 0) && (codeInfos == NULL)) { return E_INVALIDARG; } // it's not safe to examine a methoddesc that has not been restored so do not do so if (!pMethodDesc->IsRestored()) return CORPROF_E_DATAINCOMPLETE; if (pMethodDesc->HasClassOrMethodInstantiation() && pMethodDesc->IsTypicalMethodDefinition()) { // In this case, we used to replace pMethodDesc with its canonical instantiation // (FindOrCreateTypicalSharedInstantiation). However, a profiler should never be able // to get to this point anyway, since any MethodDesc a profiler gets from us // cannot be typical (i.e., cannot be a generic with types still left uninstantiated). // We assert here just in case a test proves me wrong, but generally we will // disallow this code path. _ASSERTE(!"Profiler passed a typical method desc (a generic with types still left uninstantiated) to GetCodeInfo2"); return E_INVALIDARG; } return S_OK; } HRESULT GetCodeInfoFromCodeStart( PCODE start, ULONG32 cCodeInfos, ULONG32 * pcCodeInfos, COR_PRF_CODE_INFO codeInfos[]) { CONTRACTL { NOTHROW; GC_NOTRIGGER; MODE_ANY; // We need to take the ExecutionManager reader lock to find the // appropriate jit manager. CAN_TAKE_LOCK; SO_NOT_MAINLINE; // If this is called asynchronously (from a hijacked thread, as with F1), it must not re-enter the // host (SQL). Corners will be cut to ensure this is the case if (ShouldAvoidHostCalls()) { HOST_NOCALLS; } else { HOST_CALLS; } } CONTRACTL_END; ENABLE_FORBID_GC_LOADER_USE_IN_THIS_SCOPE(); /////////////////////////////////// // Get the code region info for this function. This is a multi step process. // // MethodDesc ==> Code Address ==> JitMananger ==> // MethodToken ==> MethodRegionInfo // // (Our caller handled the first step: MethodDesc ==> Code Address.) // // // // On WIN64 we have a choice of where to go to find out the function address range size: // GC info (which is what we're doing below on all architectures) or the OS unwind // info, stored in the RUNTIME_FUNCTION structure. The latter produces // a SMALLER size than the former, because the latter excludes some data from // the set we report to the OS for unwind info. For example, switch tables can be // separated out from the regular code and not be reported as OS unwind info, and thus // those addresses will not appear in the range reported by the RUNTIME_FUNCTION gotten via: // // IJitManager* pJitMan = ExecutionManager::FindJitMan((PBYTE)codeInfos[0].startAddress); // PRUNTIME_FUNCTION pfe = pJitMan->GetUnwindInfo((PBYTE)codeInfos[0].startAddress); // *pcCodeInfos = (ULONG) (pfe->EndAddress - pfe->BeginAddress); // // (Note that GCInfo & OS unwind info report the same start address--it's the size that's // different.) // // The advantage of using the GC info is that it's available on all architectures, // and it gives you a more complete picture of the addresses belonging to the function. // // A disadvantage of using GC info is we'll report those extra addresses (like switch // tables) that a profiler might turn back around and use in a call to // GetFunctionFromIP. A profiler may expect we'd be able to map back any address // in the function's GetCodeInfo ranges back to that function's FunctionID (methoddesc). But // querying these extra addresses will cause GetFunctionFromIP to fail, as they're not // actually valid instruction addresses that the IP register can be set to. // // The advantage wins out, so we're going with GC info everywhere. // // HRESULT hr; if (start == NULL) { return CORPROF_E_FUNCTION_NOT_COMPILED; } EECodeInfo codeInfo; hr = GetFunctionInfoInternal( (LPCBYTE) start, &codeInfo); if (hr == CORPROF_E_ASYNCHRONOUS_UNSAFE) { _ASSERTE(ShouldAvoidHostCalls()); return hr; } if (FAILED(hr)) { return CORPROF_E_FUNCTION_NOT_COMPILED; } IJitManager::MethodRegionInfo methodRegionInfo; codeInfo.GetMethodRegionInfo(&methodRegionInfo); // // Fill out the codeInfo structures with valuse from the // methodRegion // // Note that we're assuming that a method will never be split into // more than two regions ... this is unlikely to change any time in // the near future. // if (NULL != codeInfos) { if (cCodeInfos > 0) { // // We have to return the two regions in the order that they would appear // if straight-line compiled // if (PCODEToPINSTR(start) == methodRegionInfo.hotStartAddress) { codeInfos[0].startAddress = (UINT_PTR)methodRegionInfo.hotStartAddress; codeInfos[0].size = methodRegionInfo.hotSize; } else { _ASSERTE(methodRegionInfo.coldStartAddress != NULL); codeInfos[0].startAddress = (UINT_PTR)methodRegionInfo.coldStartAddress; codeInfos[0].size = methodRegionInfo.coldSize; } if (NULL != methodRegionInfo.coldStartAddress) { if (cCodeInfos > 1) { if (PCODEToPINSTR(start) == methodRegionInfo.hotStartAddress) { codeInfos[1].startAddress = (UINT_PTR)methodRegionInfo.coldStartAddress; codeInfos[1].size = methodRegionInfo.coldSize; } else { codeInfos[1].startAddress = (UINT_PTR)methodRegionInfo.hotStartAddress; codeInfos[1].size = methodRegionInfo.hotSize; } } } } } if (NULL != pcCodeInfos) { *pcCodeInfos = (NULL != methodRegionInfo.coldStartAddress) ? 2 : 1; } return S_OK; } //***************************************************************************** // Gets the location and size of a jitted function //***************************************************************************** HRESULT ProfToEEInterfaceImpl::GetCodeInfo(FunctionID functionId, LPCBYTE * pStart, ULONG * pcSize) { CONTRACTL { // Yay! NOTHROW; // Yay! GC_NOTRIGGER; // Yay! MODE_ANY; // Yay! EE_THREAD_NOT_REQUIRED; // (See locking contract comment in GetCodeInfoHelper.) DISABLED(CAN_TAKE_LOCK); // (See locking contract comment in GetCodeInfoHelper.) CANNOT_RETAKE_LOCK; SO_NOT_MAINLINE; // If this is called asynchronously (from a hijacked thread, as with F1), it must not re-enter the // host (SQL). Corners will be cut to ensure this is the case if (ShouldAvoidHostCalls()) { HOST_NOCALLS; } else { HOST_CALLS; } } CONTRACTL_END; // See code:#DisableLockOnAsyncCalls PERMANENT_CONTRACT_VIOLATION(TakesLockViolation, ReasonProfilerAsyncCannotRetakeLock); // This is called asynchronously, but GetCodeInfoHelper() will // ensure we're not called at a dangerous time PROFILER_TO_CLR_ENTRYPOINT_ASYNC_EX(kP2EEAllowableAfterAttach, (LF_CORPROF, LL_INFO1000, "**PROF: GetCodeInfo 0x%p.\n", functionId)); // GetCodeInfo may be called asynchronously, and the JIT functions take a reader // lock. So we need to ensure the current thread hasn't been hijacked by a profiler while // it was holding the writer lock. Checking the ForbidSuspendThread region is a sufficient test for this FAIL_IF_IN_FORBID_SUSPEND_REGION(); if (functionId == 0) { return E_INVALIDARG; } MethodDesc * pMethodDesc = FunctionIdToMethodDesc(functionId); COR_PRF_CODE_INFO codeInfos[2]; ULONG32 cCodeInfos; HRESULT hr = GetCodeInfoFromCodeStart( pMethodDesc->GetNativeCode(), _countof(codeInfos), &cCodeInfos, codeInfos); if ((FAILED(hr)) || (0 == cCodeInfos)) { return hr; } if (NULL != pStart) { *pStart = reinterpret_cast< LPCBYTE >(codeInfos[0].startAddress); } if (NULL != pcSize) { if (!FitsIn(codeInfos[0].size)) { return E_UNEXPECTED; } *pcSize = static_cast(codeInfos[0].size); } return hr; } HRESULT ProfToEEInterfaceImpl::GetCodeInfo2(FunctionID functionId, ULONG32 cCodeInfos, ULONG32 * pcCodeInfos, COR_PRF_CODE_INFO codeInfos[]) { CONTRACTL { // Yay! NOTHROW; // Yay! GC_NOTRIGGER; // Yay! MODE_ANY; // Yay! EE_THREAD_NOT_REQUIRED; // (See locking contract comment in GetCodeInfoHelper.) DISABLED(CAN_TAKE_LOCK); // (See locking contract comment in GetCodeInfoHelper.) CANNOT_RETAKE_LOCK; SO_NOT_MAINLINE; // If this is called asynchronously (from a hijacked thread, as with F1), it must not re-enter the // host (SQL). Corners will be cut to ensure this is the case if (ShouldAvoidHostCalls()) { HOST_NOCALLS; } else { HOST_CALLS; } PRECONDITION(CheckPointer(pcCodeInfos, NULL_OK)); PRECONDITION(CheckPointer(codeInfos, NULL_OK)); } CONTRACTL_END; // See code:#DisableLockOnAsyncCalls PERMANENT_CONTRACT_VIOLATION(TakesLockViolation, ReasonProfilerAsyncCannotRetakeLock); PROFILER_TO_CLR_ENTRYPOINT_ASYNC_EX(kP2EEAllowableAfterAttach, (LF_CORPROF, LL_INFO1000, "**PROF: GetCodeInfo2 0x%p.\n", functionId)); HRESULT hr = S_OK; EX_TRY { MethodDesc * pMethodDesc = FunctionIdToMethodDesc(functionId); hr = ValidateParametersForGetCodeInfo(pMethodDesc, cCodeInfos, codeInfos); if (SUCCEEDED(hr)) { hr = GetCodeInfoFromCodeStart( pMethodDesc->GetNativeCode(), cCodeInfos, pcCodeInfos, codeInfos); } } EX_CATCH_HRESULT(hr); return hr; } HRESULT ProfToEEInterfaceImpl::GetCodeInfo3(FunctionID functionId, ReJITID reJitId, ULONG32 cCodeInfos, ULONG32* pcCodeInfos, COR_PRF_CODE_INFO codeInfos[]) { CONTRACTL { // Yay! NOTHROW; // We need to access the rejitmanager, which means taking locks, which means we // may trigger a GC GC_TRIGGERS; // Yay! MODE_ANY; // Yay! EE_THREAD_NOT_REQUIRED; // We need to access the rejitmanager, which means taking locks CAN_TAKE_LOCK; SO_NOT_MAINLINE; PRECONDITION(CheckPointer(pcCodeInfos, NULL_OK)); PRECONDITION(CheckPointer(codeInfos, NULL_OK)); } CONTRACTL_END; // See code:#DisableLockOnAsyncCalls PERMANENT_CONTRACT_VIOLATION(TakesLockViolation, ReasonProfilerAsyncCannotRetakeLock); PROFILER_TO_CLR_ENTRYPOINT_SYNC_EX( kP2EEAllowableAfterAttach | kP2EETriggers, (LF_CORPROF, LL_INFO1000, "**PROF: GetCodeInfo3 0x%p 0x%p.\n", functionId, reJitId)); HRESULT hr = S_OK; EX_TRY { MethodDesc * pMethodDesc = FunctionIdToMethodDesc(functionId); hr = ValidateParametersForGetCodeInfo(pMethodDesc, cCodeInfos, codeInfos); if (SUCCEEDED(hr)) { PCODE pCodeStart = NULL; CodeVersionManager* pCodeVersionManager = pMethodDesc->GetCodeVersionManager(); { CodeVersionManager::TableLockHolder lockHolder(pCodeVersionManager); ILCodeVersion ilCodeVersion = pCodeVersionManager->GetILCodeVersion(pMethodDesc, reJitId); NativeCodeVersionCollection nativeCodeVersions = ilCodeVersion.GetNativeCodeVersions(pMethodDesc); for (NativeCodeVersionIterator iter = nativeCodeVersions.Begin(); iter != nativeCodeVersions.End(); iter++) { // Now that tiered compilation can create more than one jitted code version for the same rejit id // we are arbitrarily choosing the first one to return. To address a specific version of native code // use GetCodeInfo4. pCodeStart = iter->GetNativeCode(); break; } } hr = GetCodeInfoFromCodeStart(pCodeStart, cCodeInfos, pcCodeInfos, codeInfos); } } EX_CATCH_HRESULT(hr); return hr; } HRESULT ProfToEEInterfaceImpl::GetEventMask(DWORD * pdwEvents) { CONTRACTL { // Yay! NOTHROW; // Yay! GC_NOTRIGGER; // Yay! MODE_ANY; // Yay! EE_THREAD_NOT_REQUIRED; // Yay! CANNOT_TAKE_LOCK; SO_NOT_MAINLINE; } CONTRACTL_END; PROFILER_TO_CLR_ENTRYPOINT_ASYNC_EX(kP2EEAllowableAfterAttach, (LF_CORPROF, LL_INFO10, "**PROF: GetEventMask.\n")); if (pdwEvents == NULL) { return E_INVALIDARG; } *pdwEvents = g_profControlBlock.dwEventMask; return S_OK; } HRESULT ProfToEEInterfaceImpl::GetEventMask2(DWORD *pdwEventsLow, DWORD *pdwEventsHigh) { CONTRACTL { // Yay! NOTHROW; // Yay! GC_NOTRIGGER; // Yay! MODE_ANY; // Yay! EE_THREAD_NOT_REQUIRED; // Yay! CANNOT_TAKE_LOCK; SO_NOT_MAINLINE; } CONTRACTL_END; PROFILER_TO_CLR_ENTRYPOINT_ASYNC_EX(kP2EEAllowableAfterAttach, (LF_CORPROF, LL_INFO10, "**PROF: GetEventMask2.\n")); if ((pdwEventsLow == NULL) || (pdwEventsHigh == NULL)) { return E_INVALIDARG; } *pdwEventsLow = g_profControlBlock.dwEventMask; *pdwEventsHigh = g_profControlBlock.dwEventMaskHigh; return S_OK; } // static void ProfToEEInterfaceImpl::MethodTableCallback(void* context, void* objectUNSAFE) { CONTRACTL { NOTHROW; GC_NOTRIGGER; SO_NOT_MAINLINE; MODE_ANY; } CONTRACTL_END; // each callback identifies the address of a method table within the frozen object segment // that pointer is an object ID by definition -- object references point to the method table CDynArray< ObjectID >* objects = reinterpret_cast< CDynArray< ObjectID >* >(context); *objects->Append() = reinterpret_cast< ObjectID >(objectUNSAFE); } // static void ProfToEEInterfaceImpl::ObjectRefCallback(void* context, void* objectUNSAFE) { // we don't care about embedded object references, ignore them } HRESULT ProfToEEInterfaceImpl::EnumModuleFrozenObjects(ModuleID moduleID, ICorProfilerObjectEnum** ppEnum) { CONTRACTL { // Yay! NOTHROW; // Yay! GC_NOTRIGGER; // Yay! MODE_ANY; // Yay! EE_THREAD_NOT_REQUIRED; // Yay! CANNOT_TAKE_LOCK; SO_NOT_MAINLINE; } CONTRACTL_END; PROFILER_TO_CLR_ENTRYPOINT_SYNC_EX(kP2EEAllowableAfterAttach, (LF_CORPROF, LL_INFO1000, "**PROF: EnumModuleFrozenObjects 0x%p.\n", moduleID)); if (NULL == ppEnum) { return E_INVALIDARG; } Module* pModule = reinterpret_cast< Module* >(moduleID); if (pModule == NULL || pModule->IsBeingUnloaded()) { return CORPROF_E_DATAINCOMPLETE; } HRESULT hr = S_OK; EX_TRY { // If we don't support frozen objects at all, then just return empty // enumerator. *ppEnum = new ProfilerObjectEnum(); } EX_CATCH_HRESULT(hr); return hr; } /* * GetArrayObjectInfo * * This function returns informatin about array objects. In particular, the dimensions * and where the data buffer is stored. * */ HRESULT ProfToEEInterfaceImpl::GetArrayObjectInfo(ObjectID objectId, ULONG32 cDimensionSizes, ULONG32 pDimensionSizes[], int pDimensionLowerBounds[], BYTE **ppData) { CONTRACTL { // Yay! NOTHROW; // Yay! GC_NOTRIGGER; // Yay! Fail at runtime if in preemptive mode via AllowObjectInspection() MODE_ANY; // Yay! CANNOT_TAKE_LOCK; SO_NOT_MAINLINE; } CONTRACTL_END; PROFILER_TO_CLR_ENTRYPOINT_SYNC_EX(kP2EEAllowableAfterAttach, (LF_CORPROF, LL_INFO1000, "**PROF: GetArrayObjectInfo 0x%p.\n", objectId)); if (objectId == NULL) { return E_INVALIDARG; } if ((pDimensionSizes == NULL) || (pDimensionLowerBounds == NULL) || (ppData == NULL)) { return E_INVALIDARG; } HRESULT hr = AllowObjectInspection(); if (FAILED(hr)) { return hr; } Object * pObj = reinterpret_cast(objectId); // GC callbacks may come from a non-EE thread, which is considered permanently preemptive. // We are about calling some object inspection functions, which require to be in co-op mode. // Given that none managed objects can be changed by managed code until GC resumes the // runtime, it is safe to violate the mode contract and to inspect managed objects from a // non-EE thread when GetArrayObjectInfo is called within GC callbacks. if (NativeThreadInGC()) { CONTRACT_VIOLATION(ModeViolation); return GetArrayObjectInfoHelper(pObj, cDimensionSizes, pDimensionSizes, pDimensionLowerBounds, ppData); } return GetArrayObjectInfoHelper(pObj, cDimensionSizes, pDimensionSizes, pDimensionLowerBounds, ppData); } HRESULT ProfToEEInterfaceImpl::GetArrayObjectInfoHelper(Object * pObj, ULONG32 cDimensionSizes, __out_ecount(cDimensionSizes) ULONG32 pDimensionSizes[], __out_ecount(cDimensionSizes) int pDimensionLowerBounds[], BYTE **ppData) { CONTRACTL { // Yay! NOTHROW; // Yay! GC_NOTRIGGER; // Because of the object pointer parameter, we must be either in CO-OP mode, // or on a non-EE thread in the process of doing a GC . if (!NativeThreadInGC()) { MODE_COOPERATIVE; } // Yay! CANNOT_TAKE_LOCK; SO_NOT_MAINLINE; } CONTRACTL_END; // Must have an array. MethodTable * pMT = pObj->GetTrueMethodTable(); if (!pMT->IsArray()) { return E_INVALIDARG; } ArrayBase * pArray = static_cast (pObj); unsigned rank = pArray->GetRank(); if (cDimensionSizes < rank) { return HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER); } // Copy range for each dimension (rank) int * pBounds = pArray->GetBoundsPtr(); int * pLowerBounds = pArray->GetLowerBoundsPtr(); unsigned i; for(i = 0; i < rank; i++) { pDimensionSizes[i] = pBounds[i]; pDimensionLowerBounds[i] = pLowerBounds[i]; } // Pointer to data. *ppData = pArray->GetDataPtr(); return S_OK; } /* * GetBoxClassLayout * * Returns information about how a particular value type is laid out. * */ HRESULT ProfToEEInterfaceImpl::GetBoxClassLayout(ClassID classId, ULONG32 *pBufferOffset) { CONTRACTL { // Yay! NOTHROW; // Yay! GC_NOTRIGGER; // Yay! MODE_ANY; // Yay! EE_THREAD_NOT_REQUIRED; // Yay! CANNOT_TAKE_LOCK; SO_NOT_MAINLINE; } CONTRACTL_END; PROFILER_TO_CLR_ENTRYPOINT_SYNC_EX(kP2EEAllowableAfterAttach, (LF_CORPROF, LL_INFO1000, "**PROF: GetBoxClassLayout 0x%p.\n", classId)); if (pBufferOffset == NULL) { return E_INVALIDARG; } if (classId == NULL) { return E_INVALIDARG; } TypeHandle typeHandle = TypeHandle::FromPtr((void *)classId); // // This is the incorrect API for arrays. Use GetArrayInfo and GetArrayLayout. // if (!typeHandle.IsValueType()) { return E_INVALIDARG; } *pBufferOffset = Object::GetOffsetOfFirstField(); return S_OK; } /* * GetThreadAppDomain * * Returns the app domain currently associated with the given thread. * */ HRESULT ProfToEEInterfaceImpl::GetThreadAppDomain(ThreadID threadId, AppDomainID *pAppDomainId) { CONTRACTL { // Yay! NOTHROW; // Yay! GC_NOTRIGGER; // Yay! MODE_ANY; // Yay! EE_THREAD_NOT_REQUIRED; // Yay! CANNOT_TAKE_LOCK; SO_NOT_MAINLINE; } CONTRACTL_END; PROFILER_TO_CLR_ENTRYPOINT_ASYNC_EX(kP2EEAllowableAfterAttach, (LF_CORPROF, LL_INFO1000, "**PROF: GetThreadAppDomain 0x%p.\n", threadId)); if (pAppDomainId == NULL) { return E_INVALIDARG; } Thread *pThread; if (threadId == NULL) { pThread = GetThreadNULLOk(); } else { pThread = (Thread *)threadId; } // // If pThread is null, then the thread has never run managed code and // so has no ThreadID. // if (!IsManagedThread(pThread)) { return CORPROF_E_NOT_MANAGED_THREAD; } *pAppDomainId = (AppDomainID)pThread->GetDomain(); return S_OK; } /* * GetRVAStaticAddress * * This function returns the absolute address of the given field in the given * class. The field must be an RVA Static token. * * Parameters: * classId - the containing class. * fieldToken - the field we are querying. * pAddress - location for storing the resulting address location. * * Returns: * S_OK on success, * E_INVALIDARG if not an RVA static, * CORPROF_E_DATAINCOMPLETE if not yet initialized. * */ HRESULT ProfToEEInterfaceImpl::GetRVAStaticAddress(ClassID classId, mdFieldDef fieldToken, void **ppAddress) { CONTRACTL { // Yay! NOTHROW; // Yay! GC_NOTRIGGER; // Yay! MODE_ANY; // FieldDesc::GetStaticAddress takes a lock CAN_TAKE_LOCK; SO_NOT_MAINLINE; } CONTRACTL_END; PROFILER_TO_CLR_ENTRYPOINT_SYNC_EX(kP2EEAllowableAfterAttach, (LF_CORPROF, LL_INFO1000, "**PROF: GetRVAStaticAddress 0x%p, 0x%08x.\n", classId, fieldToken)); // // Check for NULL parameters // if ((classId == NULL) || (ppAddress == NULL)) { return E_INVALIDARG; } if (GetThread() == NULL) { return CORPROF_E_NOT_MANAGED_THREAD; } if (GetAppDomain() == NULL) { return E_FAIL; } TypeHandle typeHandle = TypeHandle::FromPtr((void *)classId); // // If this class is not fully restored, that is all the information we can get at this time. // if (!typeHandle.IsRestored()) { return CORPROF_E_DATAINCOMPLETE; } // // Get the field descriptor object // FieldDesc *pFieldDesc = typeHandle.GetModule()->LookupFieldDef(fieldToken); if (pFieldDesc == NULL) { return E_INVALIDARG; } // // Verify this field is of the right type // if(!pFieldDesc->IsStatic() || !pFieldDesc->IsRVA() || pFieldDesc->IsThreadStatic() || pFieldDesc->IsContextStatic()) { return E_INVALIDARG; } // It may seem redundant to try to retrieve the same method table from GetEnclosingMethodTable, but classId // leads to the instantiated method table while GetEnclosingMethodTable returns the uninstantiated one. MethodTable *pMethodTable = pFieldDesc->GetEnclosingMethodTable(); // // Check that the data is available // if (!IsClassOfMethodTableInited(pMethodTable, GetAppDomain())) { return CORPROF_E_DATAINCOMPLETE; } // // Store the result and return // PTR_VOID pAddress = pFieldDesc->GetStaticAddress(NULL); if (pAddress == NULL) { return CORPROF_E_DATAINCOMPLETE; } *ppAddress = pAddress; return S_OK; } /* * GetAppDomainStaticAddress * * This function returns the absolute address of the given field in the given * class in the given app domain. The field must be an App Domain Static token. * * Parameters: * classId - the containing class. * fieldToken - the field we are querying. * appDomainId - the app domain container. * pAddress - location for storing the resulting address location. * * Returns: * S_OK on success, * E_INVALIDARG if not an app domain static, * CORPROF_E_DATAINCOMPLETE if not yet initialized. * */ HRESULT ProfToEEInterfaceImpl::GetAppDomainStaticAddress(ClassID classId, mdFieldDef fieldToken, AppDomainID appDomainId, void **ppAddress) { CONTRACTL { // Yay! NOTHROW; // Yay! GC_NOTRIGGER; // Yay! MODE_ANY; // Yay! EE_THREAD_NOT_REQUIRED; // FieldDesc::GetStaticAddress & FieldDesc::GetBaseInDomain take locks CAN_TAKE_LOCK; SO_NOT_MAINLINE; } CONTRACTL_END; PROFILER_TO_CLR_ENTRYPOINT_SYNC_EX(kP2EEAllowableAfterAttach, (LF_CORPROF, LL_INFO1000, "**PROF: GetAppDomainStaticAddress 0x%p, 0x%08x, 0x%p.\n", classId, fieldToken, appDomainId)); // // Check for NULL parameters // if ((classId == NULL) || (appDomainId == NULL) || (ppAddress == NULL)) { return E_INVALIDARG; } // Some domains, like the system domain, aren't APP domains, and thus don't contain any // statics. See if the profiler is trying to be naughty. if (!((BaseDomain*) appDomainId)->IsAppDomain()) { return E_INVALIDARG; } TypeHandle typeHandle = TypeHandle::FromPtr((void *)classId); // // If this class is not fully restored, that is all the information we can get at this time. // if (!typeHandle.IsRestored()) { return CORPROF_E_DATAINCOMPLETE; } // // Get the field descriptor object // FieldDesc *pFieldDesc = typeHandle.GetModule()->LookupFieldDef(fieldToken); if (pFieldDesc == NULL) { // // Give specific error code for literals. // DWORD dwFieldAttrs; if (FAILED(typeHandle.GetModule()->GetMDImport()->GetFieldDefProps(fieldToken, &dwFieldAttrs))) { return E_INVALIDARG; } if (IsFdLiteral(dwFieldAttrs)) { return CORPROF_E_LITERALS_HAVE_NO_ADDRESS; } return E_INVALIDARG; } // // Verify this field is of the right type // if(!pFieldDesc->IsStatic() || pFieldDesc->IsRVA() || pFieldDesc->IsThreadStatic() || pFieldDesc->IsContextStatic()) { return E_INVALIDARG; } // It may seem redundant to try to retrieve the same method table from GetEnclosingMethodTable, but classId // leads to the instantiated method table while GetEnclosingMethodTable returns the uninstantiated one. MethodTable *pMethodTable = pFieldDesc->GetEnclosingMethodTable(); AppDomain * pAppDomain = (AppDomain *)appDomainId; // // Check that the data is available // if (!IsClassOfMethodTableInited(pMethodTable, pAppDomain)) { return CORPROF_E_DATAINCOMPLETE; } // // Get the address // void *base = (void*)pFieldDesc->GetBaseInDomain(pAppDomain); if (base == NULL) { return CORPROF_E_DATAINCOMPLETE; } // // Store the result and return // PTR_VOID pAddress = pFieldDesc->GetStaticAddress(base); if (pAddress == NULL) { return E_INVALIDARG; } *ppAddress = pAddress; return S_OK; } /* * GetThreadStaticAddress * * This function returns the absolute address of the given field in the given * class on the given thread. The field must be an Thread Static token. threadId * must be the current thread ID or NULL, which means using curernt thread ID. * * Parameters: * classId - the containing class. * fieldToken - the field we are querying. * threadId - the thread container, which has to be the current managed thread or * NULL, which means to use the current managed thread. * pAddress - location for storing the resulting address location. * * Returns: * S_OK on success, * E_INVALIDARG if not a thread static, * CORPROF_E_DATAINCOMPLETE if not yet initialized. * */ HRESULT ProfToEEInterfaceImpl::GetThreadStaticAddress(ClassID classId, mdFieldDef fieldToken, ThreadID threadId, void **ppAddress) { CONTRACTL { // Yay! NOTHROW; // Yay! GC_NOTRIGGER; // Yay! MODE_ANY; // Yay! CANNOT_TAKE_LOCK; SO_NOT_MAINLINE; } CONTRACTL_END; PROFILER_TO_CLR_ENTRYPOINT_SYNC_EX(kP2EEAllowableAfterAttach, (LF_CORPROF, LL_INFO1000, "**PROF: GetThreadStaticAddress 0x%p, 0x%08x, 0x%p.\n", classId, fieldToken, threadId)); // // Verify the value of threadId, which must be the current thread ID or NULL, which means using curernt thread ID. // if ((threadId != NULL) && (threadId != ((ThreadID)GetThread()))) { return E_INVALIDARG; } threadId = reinterpret_cast(GetThread()); AppDomainID appDomainId = reinterpret_cast(GetAppDomain()); // // Check for NULL parameters // if ((classId == NULL) || (ppAddress == NULL) || !IsManagedThread(threadId) || (appDomainId == NULL)) { return E_INVALIDARG; } return GetThreadStaticAddress2(classId, fieldToken, appDomainId, threadId, ppAddress); } /* * GetThreadStaticAddress2 * * This function returns the absolute address of the given field in the given * class on the given thread. The field must be an Thread Static token. * * Parameters: * classId - the containing class. * fieldToken - the field we are querying. * appDomainId - the AppDomain container. * threadId - the thread container. * pAddress - location for storing the resulting address location. * * Returns: * S_OK on success, * E_INVALIDARG if not a thread static, * CORPROF_E_DATAINCOMPLETE if not yet initialized. * */ HRESULT ProfToEEInterfaceImpl::GetThreadStaticAddress2(ClassID classId, mdFieldDef fieldToken, AppDomainID appDomainId, ThreadID threadId, void **ppAddress) { CONTRACTL { // Yay! NOTHROW; // Yay! GC_NOTRIGGER; // Yay! MODE_ANY; // Yay! CANNOT_TAKE_LOCK; SO_NOT_MAINLINE; } CONTRACTL_END; PROFILER_TO_CLR_ENTRYPOINT_SYNC_EX(kP2EEAllowableAfterAttach, (LF_CORPROF, LL_INFO1000, "**PROF: GetThreadStaticAddress2 0x%p, 0x%08x, 0x%p, 0x%p.\n", classId, fieldToken, appDomainId, threadId)); if (threadId == NULL) { if (GetThread() == NULL) { return CORPROF_E_NOT_MANAGED_THREAD; } threadId = reinterpret_cast(GetThread()); } // // Check for NULL parameters // if ((classId == NULL) || (ppAddress == NULL) || !IsManagedThread(threadId) || (appDomainId == NULL)) { return E_INVALIDARG; } // Some domains, like the system domain, aren't APP domains, and thus don't contain any // statics. See if the profiler is trying to be naughty. if (!((BaseDomain*) appDomainId)->IsAppDomain()) { return E_INVALIDARG; } TypeHandle typeHandle = TypeHandle::FromPtr((void *)classId); // // If this class is not fully restored, that is all the information we can get at this time. // if (!typeHandle.IsRestored()) { return CORPROF_E_DATAINCOMPLETE; } // // Get the field descriptor object // FieldDesc *pFieldDesc = typeHandle.GetModule()->LookupFieldDef(fieldToken); if (pFieldDesc == NULL) { return E_INVALIDARG; } // // Verify this field is of the right type // if(!pFieldDesc->IsStatic() || !pFieldDesc->IsThreadStatic() || pFieldDesc->IsRVA() || pFieldDesc->IsContextStatic()) { return E_INVALIDARG; } // It may seem redundant to try to retrieve the same method table from GetEnclosingMethodTable, but classId // leads to the instantiated method table while GetEnclosingMethodTable returns the uninstantiated one. MethodTable *pMethodTable = pFieldDesc->GetEnclosingMethodTable(); AppDomain * pAppDomain = (AppDomain *)appDomainId; // // Check that the data is available // if (!IsClassOfMethodTableInited(pMethodTable, pAppDomain)) { return CORPROF_E_DATAINCOMPLETE; } // // Store the result and return // PTR_VOID pAddress = (void *)(((Thread *)threadId)->GetStaticFieldAddrNoCreate(pFieldDesc, pAppDomain)); if (pAddress == NULL) { return E_INVALIDARG; } *ppAddress = pAddress; return S_OK; } /* * GetContextStaticAddress * * This function returns the absolute address of the given field in the given * class in the given context. The field must be an Context Static token. * * Parameters: * classId - the containing class. * fieldToken - the field we are querying. * contextId - the context container. * pAddress - location for storing the resulting address location. * * Returns: * S_OK on success, * E_INVALIDARG if not a context static, * CORPROF_E_DATAINCOMPLETE if not yet initialized. * */ HRESULT ProfToEEInterfaceImpl::GetContextStaticAddress(ClassID classId, mdFieldDef fieldToken, ContextID contextId, void **ppAddress) { CONTRACTL { // Yay! NOTHROW; // Yay! GC_NOTRIGGER; // Yay! MODE_ANY; // Yay! CANNOT_TAKE_LOCK; SO_NOT_MAINLINE; } CONTRACTL_END; PROFILER_TO_CLR_ENTRYPOINT_SYNC_EX(kP2EEAllowableAfterAttach, (LF_CORPROF, LL_INFO1000, "**PROF: GetContextStaticAddress 0x%p, 0x%08x, 0x%p.\n", classId, fieldToken, contextId)); return E_NOTIMPL; } /* * GetAppDomainsContainingModule * * This function returns the AppDomains in which the given module has been loaded * * Parameters: * moduleId - the module with static variables. * cAppDomainIds - the input size of appDomainIds array. * pcAppDomainIds - the output size of appDomainIds array. * appDomainIds - the array to be filled up with AppDomainIDs containing initialized * static variables from the moduleId's moudle. * * Returns: * S_OK on success, * E_INVALIDARG for invalid parameters, * CORPROF_E_DATAINCOMPLETE if moduleId's module is not yet initialized. * */ HRESULT ProfToEEInterfaceImpl::GetAppDomainsContainingModule(ModuleID moduleId, ULONG32 cAppDomainIds, ULONG32 * pcAppDomainIds, AppDomainID appDomainIds[]) { CONTRACTL { // Yay! NOTHROW; // This method iterates over AppDomains, which adds, then releases, a reference on // each AppDomain iterated. This causes locking, and can cause triggering if the // AppDomain gets destroyed as a result of the release. (See code:AppDomainIterator::Next // and its call to code:AppDomain::Release.) GC_TRIGGERS; // Yay! MODE_ANY; // (See comment above GC_TRIGGERS.) CAN_TAKE_LOCK; SO_NOT_MAINLINE; } CONTRACTL_END; PROFILER_TO_CLR_ENTRYPOINT_SYNC_EX( kP2EEAllowableAfterAttach | kP2EETriggers, (LF_CORPROF, LL_INFO1000, "**PROF: GetAppDomainsContainingModule 0x%p, 0x%08x, 0x%p, 0x%p.\n", moduleId, cAppDomainIds, pcAppDomainIds, appDomainIds)); // // Check for NULL parameters // if ((moduleId == NULL) || ((appDomainIds == NULL) && (cAppDomainIds != 0)) || (pcAppDomainIds == NULL)) { return E_INVALIDARG; } Module* pModule = reinterpret_cast< Module* >(moduleId); if (pModule->IsBeingUnloaded()) { return CORPROF_E_DATAINCOMPLETE; } // IterateAppDomainContainingModule uses AppDomainIterator, which cannot be called while the current thread // is holding the ThreadStore lock. if (ThreadStore::HoldingThreadStore()) { return CORPROF_E_UNSUPPORTED_CALL_SEQUENCE; } IterateAppDomainContainingModule iterateAppDomainContainingModule(pModule, cAppDomainIds, pcAppDomainIds, appDomainIds); return iterateAppDomainContainingModule.PopulateArray(); } /* * GetStaticFieldInfo * * This function returns a bit mask of the type of statics the * given field is. * * Parameters: * classId - the containing class. * fieldToken - the field we are querying. * pFieldInfo - location for storing the resulting bit mask. * * Returns: * S_OK on success, * E_INVALIDARG if pFieldInfo is NULL * */ HRESULT ProfToEEInterfaceImpl::GetStaticFieldInfo(ClassID classId, mdFieldDef fieldToken, COR_PRF_STATIC_TYPE *pFieldInfo) { CONTRACTL { // Yay! NOTHROW; // Yay! GC_NOTRIGGER; // Yay! MODE_ANY; // Yay! EE_THREAD_NOT_REQUIRED; // Yay! CANNOT_TAKE_LOCK; SO_NOT_MAINLINE; } CONTRACTL_END; PROFILER_TO_CLR_ENTRYPOINT_SYNC_EX(kP2EEAllowableAfterAttach, (LF_CORPROF, LL_INFO1000, "**PROF: GetStaticFieldInfo 0x%p, 0x%08x.\n", classId, fieldToken)); // // Check for NULL parameters // if ((classId == NULL) || (pFieldInfo == NULL)) { return E_INVALIDARG; } TypeHandle typeHandle = TypeHandle::FromPtr((void *)classId); // // If this class is not fully restored, that is all the information we can get at this time. // if (!typeHandle.IsRestored()) { return CORPROF_E_DATAINCOMPLETE; } // // Get the field descriptor object // FieldDesc *pFieldDesc = typeHandle.GetModule()->LookupFieldDef(fieldToken); if (pFieldDesc == NULL) { return E_INVALIDARG; } *pFieldInfo = COR_PRF_FIELD_NOT_A_STATIC; if (pFieldDesc->IsContextStatic()) { *pFieldInfo = (COR_PRF_STATIC_TYPE)(*pFieldInfo | COR_PRF_FIELD_CONTEXT_STATIC); } if (pFieldDesc->IsRVA()) { *pFieldInfo = (COR_PRF_STATIC_TYPE)(*pFieldInfo | COR_PRF_FIELD_RVA_STATIC); } if (pFieldDesc->IsThreadStatic()) { *pFieldInfo = (COR_PRF_STATIC_TYPE)(*pFieldInfo | COR_PRF_FIELD_THREAD_STATIC); } if ((*pFieldInfo == COR_PRF_FIELD_NOT_A_STATIC) && pFieldDesc->IsStatic()) { *pFieldInfo = (COR_PRF_STATIC_TYPE)(*pFieldInfo | COR_PRF_FIELD_APP_DOMAIN_STATIC); } return S_OK; } /* * GetClassIDInfo2 * * This function generalizes GetClassIDInfo for all types, both generic and non-generic. It returns * the module, type token, and an array of instantiation classIDs that were used to instantiate the * given classId. * * Parameters: * classId - The classId (TypeHandle) to query information about. * pParentClassId - The ClassID (TypeHandle) of the parent class. * pModuleId - An optional parameter for returning the module of the class. * pTypeDefToken - An optional parameter for returning the metadata token of the class. * cNumTypeArgs - The count of the size of the array typeArgs * pcNumTypeArgs - Returns the number of elements of typeArgs filled in, or if typeArgs is NULL * the number that would be needed. * typeArgs - An array to store generic type parameters for the class. * * Returns: * S_OK if successful. */ HRESULT ProfToEEInterfaceImpl::GetClassIDInfo2(ClassID classId, ModuleID *pModuleId, mdTypeDef *pTypeDefToken, ClassID *pParentClassId, ULONG32 cNumTypeArgs, ULONG32 *pcNumTypeArgs, ClassID typeArgs[]) { CONTRACTL { // Yay! NOTHROW; // Yay! GC_NOTRIGGER; // Yay! MODE_ANY; // Yay! EE_THREAD_NOT_REQUIRED; // Yay! CANNOT_TAKE_LOCK; SO_NOT_MAINLINE; PRECONDITION(CheckPointer(pParentClassId, NULL_OK)); PRECONDITION(CheckPointer(pModuleId, NULL_OK)); PRECONDITION(CheckPointer(pTypeDefToken, NULL_OK)); PRECONDITION(CheckPointer(pcNumTypeArgs, NULL_OK)); PRECONDITION(CheckPointer(typeArgs, NULL_OK)); } CONTRACTL_END; PROFILER_TO_CLR_ENTRYPOINT_ASYNC_EX(kP2EEAllowableAfterAttach, (LF_CORPROF, LL_INFO1000, "**PROF: GetClassIDInfo2 0x%p.\n", classId)); // // Verify parameters. // if (classId == NULL) { return E_INVALIDARG; } if ((cNumTypeArgs != 0) && (typeArgs == NULL)) { return E_INVALIDARG; } TypeHandle typeHandle = TypeHandle::FromPtr((void *)classId); // // If this class is not fully restored, that is all the information we can get at this time. // if (!typeHandle.IsRestored()) { return CORPROF_E_DATAINCOMPLETE; } // // Handle globals which don't have the instances. // if (classId == PROFILER_GLOBAL_CLASS) { if (pParentClassId != NULL) { *pParentClassId = NULL; } if (pModuleId != NULL) { *pModuleId = PROFILER_GLOBAL_MODULE; } if (pTypeDefToken != NULL) { *pTypeDefToken = mdTokenNil; } return S_OK; } // // Do not do arrays via this API // ARRAY_KIND arrayKind = ArrayKindFromTypeHandle(typeHandle); if (arrayKind == ARRAY_KIND_TYPEDESC || arrayKind == ARRAY_KIND_METHODTABLE) { return CORPROF_E_CLASSID_IS_ARRAY; } _ASSERTE (arrayKind == ARRAY_KIND_NOTARRAY); if (typeHandle.IsTypeDesc()) { // Not an array, but still a typedesc? We don't know how to // deal with those. return CORPROF_E_CLASSID_IS_COMPOSITE; } // // Fill in the basic information // if (pParentClassId != NULL) { TypeHandle parentTypeHandle = typeHandle.GetParent(); if (!parentTypeHandle.IsNull()) { *pParentClassId = TypeHandleToClassID(parentTypeHandle); } else { *pParentClassId = NULL; } } if (pModuleId != NULL) { *pModuleId = (ModuleID) typeHandle.GetModule(); _ASSERTE(*pModuleId != NULL); } if (pTypeDefToken != NULL) { *pTypeDefToken = typeHandle.GetCl(); _ASSERTE(*pTypeDefToken != NULL); } // // See if they are just looking to get the buffer size. // if (cNumTypeArgs == 0) { if (pcNumTypeArgs != NULL) { *pcNumTypeArgs = typeHandle.GetMethodTable()->GetNumGenericArgs(); } return S_OK; } // // Adjust the count for the size of the given array. // if (cNumTypeArgs > typeHandle.GetMethodTable()->GetNumGenericArgs()) { cNumTypeArgs = typeHandle.GetMethodTable()->GetNumGenericArgs(); } if (pcNumTypeArgs != NULL) { *pcNumTypeArgs = cNumTypeArgs; } // // Copy over the instantiating types. // ULONG32 count; Instantiation inst = typeHandle.GetMethodTable()->GetInstantiation(); for (count = 0; count < cNumTypeArgs; count ++) { typeArgs[count] = TypeHandleToClassID(inst[count]); } return S_OK; } HRESULT ProfToEEInterfaceImpl::GetModuleInfo(ModuleID moduleId, LPCBYTE * ppBaseLoadAddress, ULONG cchName, ULONG * pcchName, __out_ecount_part_opt(cchName, *pcchName) WCHAR wszName[], AssemblyID * pAssemblyId) { CONTRACTL { // Yay! NOTHROW; // Yay! GC_NOTRIGGER; // See comment in code:ProfToEEInterfaceImpl::GetModuleInfo2 CAN_TAKE_LOCK; // Yay! MODE_ANY; // Yay! EE_THREAD_NOT_REQUIRED; SO_NOT_MAINLINE; PRECONDITION(CheckPointer((Module *)moduleId, NULL_OK)); PRECONDITION(CheckPointer(ppBaseLoadAddress, NULL_OK)); PRECONDITION(CheckPointer(pcchName, NULL_OK)); PRECONDITION(CheckPointer(wszName, NULL_OK)); PRECONDITION(CheckPointer(pAssemblyId, NULL_OK)); } CONTRACTL_END; PROFILER_TO_CLR_ENTRYPOINT_ASYNC_EX(kP2EEAllowableAfterAttach, (LF_CORPROF, LL_INFO1000, "**PROF: GetModuleInfo 0x%p.\n", moduleId)); // Paramter validation is taken care of in GetModuleInfo2. return GetModuleInfo2( moduleId, ppBaseLoadAddress, cchName, pcchName, wszName, pAssemblyId, NULL); // Don't need module type } //--------------------------------------------------------------------------------------- // // Helper used by GetModuleInfo2 to determine the bitmask of COR_PRF_MODULE_FLAGS for // the specified module. // // Arguments: // pModule - Module to get the flags for // // Return Value: // Bitmask of COR_PRF_MODULE_FLAGS corresponding to pModule // DWORD ProfToEEInterfaceImpl::GetModuleFlags(Module * pModule) { CONTRACTL { NOTHROW; GC_NOTRIGGER; CAN_TAKE_LOCK; // IsWindowsRuntimeModule accesses metadata directly, which takes locks MODE_ANY; } CONTRACTL_END; PEFile * pPEFile = pModule->GetFile(); if (pPEFile == NULL) { // Hopefully this should never happen; but just in case, don't try to determine the // flags without a PEFile. return 0; } DWORD dwRet = 0; // First, set the flags that are dependent on which PEImage / layout we look at // inside the Module (disk/ngen/flat) if (pModule->HasNativeImage()) { // NGEN dwRet |= (COR_PRF_MODULE_DISK | COR_PRF_MODULE_NGEN); // Intentionally not checking for flat, since NGEN PEImages never have flat // layouts. } else { #ifdef FEATURE_READYTORUN // pModule->HasNativeImage() returns false for ReadyToRun images if (pModule->IsReadyToRun()) { // Ready To Run dwRet |= (COR_PRF_MODULE_DISK | COR_PRF_MODULE_NGEN); } #endif // Not NGEN or ReadyToRun. if (pPEFile->HasOpenedILimage()) { PEImage * pILImage = pPEFile->GetOpenedILimage(); if (pILImage->IsFile()) { dwRet |= COR_PRF_MODULE_DISK; } if (pPEFile->GetLoadedIL()->IsFlat()) { dwRet |= COR_PRF_MODULE_FLAT_LAYOUT; } } } if (pModule->IsReflection()) { dwRet |= COR_PRF_MODULE_DYNAMIC; } if (pModule->IsCollectible()) { dwRet |= COR_PRF_MODULE_COLLECTIBLE; } if (pModule->IsResource()) { dwRet |= COR_PRF_MODULE_RESOURCE; } if (pModule->IsWindowsRuntimeModule()) { dwRet |= COR_PRF_MODULE_WINDOWS_RUNTIME; } return dwRet; } HRESULT ProfToEEInterfaceImpl::GetModuleInfo2(ModuleID moduleId, LPCBYTE * ppBaseLoadAddress, ULONG cchName, ULONG * pcchName, __out_ecount_part_opt(cchName, *pcchName) WCHAR wszName[], AssemblyID * pAssemblyId, DWORD * pdwModuleFlags) { CONTRACTL { // Yay! NOTHROW; // Yay! GC_NOTRIGGER; // The pModule->GetScopeName() call below can result in locks getting taken to // access the metadata implementation. However, these locks do not do a mode // change. CAN_TAKE_LOCK; // Yay! MODE_ANY; // Yay! EE_THREAD_NOT_REQUIRED; SO_NOT_MAINLINE; PRECONDITION(CheckPointer((Module *)moduleId, NULL_OK)); PRECONDITION(CheckPointer(ppBaseLoadAddress, NULL_OK)); PRECONDITION(CheckPointer(pcchName, NULL_OK)); PRECONDITION(CheckPointer(wszName, NULL_OK)); PRECONDITION(CheckPointer(pAssemblyId, NULL_OK)); } CONTRACTL_END; PROFILER_TO_CLR_ENTRYPOINT_ASYNC_EX(kP2EEAllowableAfterAttach, (LF_CORPROF, LL_INFO1000, "**PROF: GetModuleInfo2 0x%p.\n", moduleId)); if (moduleId == NULL) { return E_INVALIDARG; } Module * pModule = (Module *) moduleId; if (pModule->IsBeingUnloaded()) { return CORPROF_E_DATAINCOMPLETE; } HRESULT hr = S_OK; EX_TRY { PEFile * pFile = pModule->GetFile(); // Pick some safe defaults to begin with. if (ppBaseLoadAddress != NULL) *ppBaseLoadAddress = 0; if (wszName != NULL) *wszName = 0; if (pcchName != NULL) *pcchName = 0; if (pAssemblyId != NULL) *pAssemblyId = PROFILER_PARENT_UNKNOWN; // Module flags can be determined first without fear of error if (pdwModuleFlags != NULL) *pdwModuleFlags = GetModuleFlags(pModule); // Get the module file name LPCWSTR wszFileName = pFile->GetPath(); _ASSERTE(wszFileName != NULL); PREFIX_ASSUME(wszFileName != NULL); // If there is no filename, which is the case for RefEmit modules and for SQL // modules, then rather than returning an empty string for the name, just use the // module name from metadata (a.k.a. Module.ScopeName). This is required to // support SQL F1 sampling profiling. StackSString strScopeName; LPCUTF8 szScopeName = NULL; if ((*wszFileName == W('\0')) && SUCCEEDED(pModule->GetScopeName(&szScopeName))) { strScopeName.SetUTF8(szScopeName); strScopeName.Normalize(); wszFileName = strScopeName.GetUnicode(); } ULONG trueLen = (ULONG)(wcslen(wszFileName) + 1); // Return name of module as required. if (wszName && cchName > 0) { if (cchName < trueLen) { hr = HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER); } else { wcsncpy_s(wszName, cchName, wszFileName, trueLen); } } // If they request the actual length of the name if (pcchName) *pcchName = trueLen; if (ppBaseLoadAddress != NULL && !pFile->IsDynamic()) { if (pModule->IsProfilerNotified()) { // Set the base load address -- this could be null in certain error conditions *ppBaseLoadAddress = pModule->GetProfilerBase(); } else { *ppBaseLoadAddress = NULL; } if (*ppBaseLoadAddress == NULL) { hr = CORPROF_E_DATAINCOMPLETE; } } // Return the parent assembly for this module if desired. if (pAssemblyId != NULL) { // Lie and say the assembly isn't avaialable until we are loaded (even though it is.) // This is for backward compatibilty - we may want to change it if (pModule->IsProfilerNotified()) { Assembly *pAssembly = pModule->GetAssembly(); _ASSERTE(pAssembly); *pAssemblyId = (AssemblyID) pAssembly; } else { hr = CORPROF_E_DATAINCOMPLETE; } } } EX_CATCH_HRESULT(hr); return (hr); } /* * Get a metadata interface instance which maps to the given module. * One may ask for the metadata to be opened in read+write mode, but * this will result in slower metadata execution of the program, because * changes made to the metadata cannot be optimized as they were from * the compiler. */ HRESULT ProfToEEInterfaceImpl::GetModuleMetaData(ModuleID moduleId, DWORD dwOpenFlags, REFIID riid, IUnknown **ppOut) { CONTRACTL { // Yay! NOTHROW; // Yay! GC_NOTRIGGER; // Yay! MODE_ANY; // Currently, this function is technically EE_THREAD_REQUIRED because // some functions in synch.cpp assert that there is a Thread object, // but we might be able to lift that restriction and make this be // EE_THREAD_NOT_REQUIRED. // PEFile::GetRWImporter & PEFile::GetEmitter & // GetReadablePublicMetaDataInterface take locks CAN_TAKE_LOCK; SO_NOT_MAINLINE; } CONTRACTL_END; PROFILER_TO_CLR_ENTRYPOINT_SYNC_EX(kP2EEAllowableAfterAttach, (LF_CORPROF, LL_INFO1000, "**PROF: GetModuleMetaData 0x%p, 0x%08x.\n", moduleId, dwOpenFlags)); if (moduleId == NULL) { return E_INVALIDARG; } // Check for unsupported bits, and return E_INVALIDARG if present if ((dwOpenFlags & ~(ofNoTransform | ofRead | ofWrite)) != 0) { return E_INVALIDARG; } Module * pModule; HRESULT hr = S_OK; pModule = (Module *) moduleId; _ASSERTE(pModule != NULL); if (pModule->IsBeingUnloaded()) { return CORPROF_E_DATAINCOMPLETE; } // Make sure we can get the importer first if (pModule->IsResource()) { if (ppOut) *ppOut = NULL; return S_FALSE; } // Decide which type of open mode we are in to see which you require. if ((dwOpenFlags & ofWrite) == 0) { // Readable interface return pModule->GetReadablePublicMetaDataInterface(dwOpenFlags, riid, (LPVOID *) ppOut); } // Writeable interface IUnknown *pObj = NULL; EX_TRY { pObj = pModule->GetValidatedEmitter(); } EX_CATCH_HRESULT_NO_ERRORINFO(hr); // Ask for the interface the caller wanted, only if they provide a out param if (SUCCEEDED(hr) && ppOut) hr = pObj->QueryInterface(riid, (void **) ppOut); return (hr); } /* * Retrieve a pointer to the body of a method starting at it's header. * A method is scoped by the module it lives in. Because this function * is designed to give a tool access to IL before it has been loaded * by the Runtime, it uses the metadata token of the method to find * the instance desired. Note that this function has no effect on * already compiled code. */ HRESULT ProfToEEInterfaceImpl::GetILFunctionBody(ModuleID moduleId, mdMethodDef methodId, LPCBYTE *ppMethodHeader, ULONG *pcbMethodSize) { CONTRACTL { // Yay! NOTHROW; // Yay! GC_NOTRIGGER; // Yay! MODE_ANY; // PEFile::CheckLoaded & Module::GetDynamicIL both take a lock CAN_TAKE_LOCK; SO_NOT_MAINLINE; } CONTRACTL_END; PROFILER_TO_CLR_ENTRYPOINT_SYNC((LF_CORPROF, LL_INFO1000, "**PROF: GetILFunctionBody 0x%p, 0x%08x.\n", moduleId, methodId)); Module * pModule; // Working pointer for real class. ULONG RVA; // Return RVA of the method body. DWORD dwImplFlags; // Flags for the item. if ((moduleId == NULL) || (methodId == mdMethodDefNil) || (methodId == 0) || (TypeFromToken(methodId) != mdtMethodDef)) { return E_INVALIDARG; } pModule = (Module *) moduleId; _ASSERTE(pModule != NULL && methodId != mdMethodDefNil); if (pModule->IsBeingUnloaded()) { return CORPROF_E_DATAINCOMPLETE; } // Find the method body based on metadata. IMDInternalImport *pImport = pModule->GetMDImport(); _ASSERTE(pImport); PEFile *pFile = pModule->GetFile(); if (!pFile->CheckLoaded()) return (CORPROF_E_DATAINCOMPLETE); LPCBYTE pbMethod = NULL; // Don't return rewritten IL, use the new API to get that. pbMethod = (LPCBYTE) pModule->GetDynamicIL(methodId, FALSE); // Method not overriden - get the original copy of the IL by going to metadata if (pbMethod == NULL) { HRESULT hr = S_OK; IfFailRet(pImport->GetMethodImplProps(methodId, &RVA, &dwImplFlags)); // Check to see if the method has associated IL if ((RVA == 0 && !pFile->IsDynamic()) || !(IsMiIL(dwImplFlags) || IsMiOPTIL(dwImplFlags) || IsMiInternalCall(dwImplFlags))) { return (CORPROF_E_FUNCTION_NOT_IL); } EX_TRY { // Get the location of the IL pbMethod = (LPCBYTE) (pModule->GetIL(RVA)); } EX_CATCH_HRESULT(hr); if (FAILED(hr)) { return hr; } } // Fill out param if provided if (ppMethodHeader) *ppMethodHeader = pbMethod; // Calculate the size of the method itself. if (pcbMethodSize) { if (!FitsIn(PEDecoder::ComputeILMethodSize((TADDR)pbMethod))) { return E_UNEXPECTED; } *pcbMethodSize = static_cast(PEDecoder::ComputeILMethodSize((TADDR)pbMethod)); } return (S_OK); } //--------------------------------------------------------------------------------------- // Retrieves an IMethodMalloc pointer around a ModuleILHeap instance that will own // allocating heap space for this module (for IL rewriting). // // Arguments: // moduleId - ModuleID this allocator shall allocate for // ppMalloc - [out] IMethodMalloc pointer the profiler will use for allocation requests // against this module // // Return value // HRESULT indicating success / failure // // Notes // IL method bodies used to have the requirement that they must be referenced as // RVA's to the loaded module, which means they come after the module within // METHOD_MAX_RVA. In order to make it easier for a tool to swap out the body of // a method, this allocator will ensure memory allocated after that point. // // Now that requirement is completely gone, so there's nothing terribly special // about this allocator, we just keep it around for legacy purposes. HRESULT ProfToEEInterfaceImpl::GetILFunctionBodyAllocator(ModuleID moduleId, IMethodMalloc ** ppMalloc) { CONTRACTL { // Yay! NOTHROW; // ModuleILHeap::FindOrCreateHeap may take a Crst if it // needs to create a new allocator and add it to the list. Taking a crst // switches to preemptive, which is effectively a GC trigger GC_TRIGGERS; // Yay! MODE_ANY; // Yay! EE_THREAD_NOT_REQUIRED; // (see GC_TRIGGERS comment) CAN_TAKE_LOCK; SO_NOT_MAINLINE; } CONTRACTL_END; PROFILER_TO_CLR_ENTRYPOINT_SYNC_EX( kP2EETriggers, (LF_CORPROF, LL_INFO1000, "**PROF: GetILFunctionBodyAllocator 0x%p.\n", moduleId)); if ((moduleId == NULL) || (ppMalloc == NULL)) { return E_INVALIDARG; } Module * pModule = (Module *) moduleId; if (pModule->IsBeingUnloaded() || !pModule->GetFile()->CheckLoaded()) { return (CORPROF_E_DATAINCOMPLETE); } *ppMalloc = &ModuleILHeap::s_Heap; return S_OK; } /* * Replaces the method body for a function in a module. This will replace * the RVA of the method in the metadata to point to this new method body, * and adjust any internal data structures as required. This function can * only be called on those methods which have never been compiled by a JITTER. * Please use the GetILFunctionBodyAllocator to allocate space for the new method to * ensure the buffer is compatible. */ HRESULT ProfToEEInterfaceImpl::SetILFunctionBody(ModuleID moduleId, mdMethodDef methodId, LPCBYTE pbNewILMethodHeader) { CONTRACTL { // PEFile::GetEmitter, Module::SetDynamicIL all throw THROWS; // Locks are taken (see CAN_TAKE_LOCK below), which may cause mode switch to // preemptive, which is triggers. GC_TRIGGERS; // Yay! MODE_ANY; // Module::SetDynamicIL & PEFile::CheckLoaded & PEFile::GetEmitter take locks CAN_TAKE_LOCK; SO_NOT_MAINLINE; } CONTRACTL_END; PROFILER_TO_CLR_ENTRYPOINT_SYNC_EX( kP2EETriggers, (LF_CORPROF, LL_INFO1000, "**PROF: SetILFunctionBody 0x%p, 0x%08x.\n", moduleId, methodId)); if ((moduleId == NULL) || (methodId == mdMethodDefNil) || (TypeFromToken(methodId) != mdtMethodDef) || (pbNewILMethodHeader == NULL)) { return E_INVALIDARG; } Module *pModule; // Working pointer for real class. HRESULT hr = S_OK; // Cannot set the body for anything other than a method def if (TypeFromToken(methodId) != mdtMethodDef) return (E_INVALIDARG); // Cast module to appropriate type pModule = (Module *) moduleId; _ASSERTE (pModule != NULL); // Enforced in CorProfInfo::SetILFunctionBody if (pModule->IsBeingUnloaded()) { return CORPROF_E_DATAINCOMPLETE; } // Remember the profiler is doing this, as that means we must never detach it! g_profControlBlock.pProfInterface->SetUnrevertiblyModifiedILFlag(); // This action is not temporary! // If the profiler want to be able to revert, they need to use // the new ReJIT APIs. pModule->SetDynamicIL(methodId, (TADDR)pbNewILMethodHeader, FALSE); return (hr); } /* * Sets the codemap for the replaced IL function body */ HRESULT ProfToEEInterfaceImpl::SetILInstrumentedCodeMap(FunctionID functionId, BOOL fStartJit, ULONG cILMapEntries, COR_IL_MAP rgILMapEntries[]) { CONTRACTL { // Debugger::SetILInstrumentedCodeMap throws THROWS; // Debugger::SetILInstrumentedCodeMap triggers GC_TRIGGERS; // Yay! MODE_ANY; // Yay! EE_THREAD_NOT_REQUIRED; // Debugger::SetILInstrumentedCodeMap takes a lock when it calls Debugger::GetOrCreateMethodInfo CAN_TAKE_LOCK; SO_NOT_MAINLINE; } CONTRACTL_END; PROFILER_TO_CLR_ENTRYPOINT_SYNC_EX( kP2EETriggers, (LF_CORPROF, LL_INFO1000, "**PROF: SetILInstrumentedCodeMap 0x%p, %d.\n", functionId, fStartJit)); if (functionId == NULL) { return E_INVALIDARG; } if (cILMapEntries >= (MAXULONG / sizeof(COR_IL_MAP))) { // Too big! The allocation below would overflow when calculating the size. return E_INVALIDARG; } #ifdef DEBUGGING_SUPPORTED MethodDesc *pMethodDesc = FunctionIdToMethodDesc(functionId); // it's not safe to examine a methoddesc that has not been restored so do not do so if (!pMethodDesc ->IsRestored()) return CORPROF_E_DATAINCOMPLETE; if (g_pDebugInterface == NULL) { return CORPROF_E_DEBUGGING_DISABLED; } COR_IL_MAP * rgNewILMapEntries = new (nothrow) COR_IL_MAP[cILMapEntries]; if (rgNewILMapEntries == NULL) return E_OUTOFMEMORY; memcpy_s(rgNewILMapEntries, sizeof(COR_IL_MAP) * cILMapEntries, rgILMapEntries, sizeof(COR_IL_MAP) * cILMapEntries); return g_pDebugInterface->SetILInstrumentedCodeMap(pMethodDesc, fStartJit, cILMapEntries, rgNewILMapEntries); #else //DEBUGGING_SUPPORTED return E_NOTIMPL; #endif //DEBUGGING_SUPPORTED } HRESULT ProfToEEInterfaceImpl::ForceGC() { CONTRACTL { // GC calls "new" which throws THROWS; // Uh duh, look at the name of the function, dude GC_TRIGGERS; // Yay! MODE_ANY; // Yay! EE_THREAD_NOT_REQUIRED; // Initiating a GC causes a runtime suspension which requires the // mother of all locks: the thread store lock. CAN_TAKE_LOCK; SO_NOT_MAINLINE; } CONTRACTL_END; ASSERT_NO_EE_LOCKS_HELD(); // We need to use IsGarbageCollectorFullyInitialized() instead of IsGCHeapInitialized() because // there are other GC initialization being done after IsGCHeapInitialized() becomes TRUE, // and before IsGarbageCollectorFullyInitialized() becomes TRUE. if (!IsGarbageCollectorFullyInitialized()) { return CORPROF_E_NOT_YET_AVAILABLE; } // Disallow the cases where a profiler calls this off a hijacked CLR thread // or inside a profiler callback. (Allow cases where this is a native thread, or a // thread which previously successfully called ForceGC.) Thread * pThread = GetThreadNULLOk(); if ((pThread != NULL) && (!AreCallbackStateFlagsSet(COR_PRF_CALLBACKSTATE_FORCEGC_WAS_CALLED)) && (pThread->GetFrame() != FRAME_TOP || AreCallbackStateFlagsSet(COR_PRF_CALLBACKSTATE_INCALLBACK))) { LOG((LF_CORPROF, LL_ERROR, "**PROF: ERROR: Returning CORPROF_E_UNSUPPORTED_CALL_SEQUENCE " "due to illegal hijacked profiler call or call from inside another callback\n")); return CORPROF_E_UNSUPPORTED_CALL_SEQUENCE; } // NOTE: We cannot use the standard macro PROFILER_TO_CLR_ENTRYPOINT_SYNC_EX // here because the macro ensures that either the current thread is not an // EE thread, or, if it is, that the CALLBACK flag is set. In classic apps // a profiler-owned native thread will not get an EE thread associated with // it, however, in AppX apps, during the first call into the GC on a // profiler-owned thread, the EE will associate an EE-thread with the profiler // thread. As a consequence the second call to ForceGC on the same thread // would fail, since this is now an EE thread and this API is not called from // a callback. // First part of the PROFILER_TO_CLR_ENTRYPOINT_SYNC_EX macro: PROFILER_TO_CLR_ENTRYPOINT_ASYNC_EX( kP2EEAllowableAfterAttach | kP2EETriggers, (LF_CORPROF, LL_INFO1000, "**PROF: ForceGC.\n")); #ifdef FEATURE_EVENT_TRACE // This helper, used by ETW and profAPI ensures a managed thread gets created for // this thread before forcing the GC (to work around Jupiter issues where it's // expected this thread is already managed before starting the GC). HRESULT hr = ETW::GCLog::ForceGCForDiagnostics(); #else // !FEATURE_EVENT_TRACE HRESULT hr = E_FAIL; #endif // FEATURE_EVENT_TRACE // If a Thread object was just created for this thread, remember the fact that it // was a ForceGC() thread, so we can be more lenient when doing // COR_PRF_CALLBACKSTATE_INCALLBACK later on from other APIs pThread = GetThreadNULLOk(); if (pThread != NULL) { pThread->SetProfilerCallbackStateFlags(COR_PRF_CALLBACKSTATE_FORCEGC_WAS_CALLED); } return hr; } /* * Returns the ContextID for the current thread. */ HRESULT ProfToEEInterfaceImpl::GetThreadContext(ThreadID threadId, ContextID *pContextId) { CONTRACTL { // Yay! NOTHROW; // Yay! GC_NOTRIGGER; // Yay! MODE_ANY; // Yay! EE_THREAD_NOT_REQUIRED; // Yay! CANNOT_TAKE_LOCK; SO_NOT_MAINLINE; } CONTRACTL_END; PROFILER_TO_CLR_ENTRYPOINT_ASYNC_EX(kP2EEAllowableAfterAttach, (LF_CORPROF, LL_INFO1000, "**PROF: GetThreadContext 0x%p.\n", threadId)); if (!IsManagedThread(threadId)) { return E_INVALIDARG; } // Cast to right type Thread *pThread = reinterpret_cast(threadId); // Get the context for the Thread* provided Context *pContext = pThread->GetContext(); _ASSERTE(pContext); // If there's no current context, return incomplete info if (!pContext) return (CORPROF_E_DATAINCOMPLETE); // Set the result and return if (pContextId) *pContextId = reinterpret_cast(pContext); return (S_OK); } HRESULT ProfToEEInterfaceImpl::GetClassIDInfo(ClassID classId, ModuleID *pModuleId, mdTypeDef *pTypeDefToken) { CONTRACTL { // Yay! NOTHROW; // Yay! GC_NOTRIGGER; // Yay! MODE_ANY; // Yay! EE_THREAD_NOT_REQUIRED; // Yay! CANNOT_TAKE_LOCK; SO_NOT_MAINLINE; } CONTRACTL_END; PROFILER_TO_CLR_ENTRYPOINT_ASYNC_EX(kP2EEAllowableAfterAttach, (LF_CORPROF, LL_INFO1000, "**PROF: GetClassIDInfo 0x%p.\n", classId)); if (classId == NULL) { return E_INVALIDARG; } if (pModuleId != NULL) { *pModuleId = NULL; } if (pTypeDefToken != NULL) { *pTypeDefToken = NULL; } // Handle globals which don't have the instances. if (classId == PROFILER_GLOBAL_CLASS) { if (pModuleId != NULL) { *pModuleId = PROFILER_GLOBAL_MODULE; } if (pTypeDefToken != NULL) { *pTypeDefToken = mdTokenNil; } } else if (classId == NULL) { return E_INVALIDARG; } // Get specific data. else { TypeHandle th = TypeHandle::FromPtr((void *)classId); if (!th.IsTypeDesc()) { if (!th.IsArray()) { // // If this class is not fully restored, that is all the information we can get at this time. // if (!th.IsRestored()) { return CORPROF_E_DATAINCOMPLETE; } if (pModuleId != NULL) { *pModuleId = (ModuleID) th.GetModule(); _ASSERTE(*pModuleId != NULL); } if (pTypeDefToken != NULL) { *pTypeDefToken = th.GetCl(); _ASSERTE(*pTypeDefToken != NULL); } } } } return (S_OK); } HRESULT ProfToEEInterfaceImpl::GetFunctionInfo(FunctionID functionId, ClassID *pClassId, ModuleID *pModuleId, mdToken *pToken) { CONTRACTL { // Yay! NOTHROW; // Yay! GC_NOTRIGGER; // Yay! MODE_ANY; // Yay! EE_THREAD_NOT_REQUIRED; // Yay! CANNOT_TAKE_LOCK; SO_NOT_MAINLINE; } CONTRACTL_END; PROFILER_TO_CLR_ENTRYPOINT_ASYNC_EX(kP2EEAllowableAfterAttach, (LF_CORPROF, LL_INFO1000, "**PROF: GetFunctionInfo 0x%p.\n", functionId)); if (functionId == NULL) { return E_INVALIDARG; } MethodDesc *pMDesc = (MethodDesc *) functionId; if (!pMDesc->IsRestored()) { return CORPROF_E_DATAINCOMPLETE; } MethodTable *pMT = pMDesc->GetMethodTable(); if (!pMT->IsRestored()) { return CORPROF_E_DATAINCOMPLETE; } ClassID classId = PROFILER_GLOBAL_CLASS; if (pMT != NULL) { classId = NonGenericTypeHandleToClassID(TypeHandle(pMT)); } if (pClassId != NULL) { *pClassId = classId; } if (pModuleId != NULL) { *pModuleId = (ModuleID) pMDesc->GetModule(); } if (pToken != NULL) { *pToken = pMDesc->GetMemberDef(); } return (S_OK); } /* * GetILToNativeMapping returns a map from IL offsets to native * offsets for this code. An array of COR_DEBUG_IL_TO_NATIVE_MAP * structs will be returned, and some of the ilOffsets in this array * may be the values specified in CorDebugIlToNativeMappingTypes. */ HRESULT ProfToEEInterfaceImpl::GetILToNativeMapping(FunctionID functionId, ULONG32 cMap, ULONG32 * pcMap, // [out] COR_DEBUG_IL_TO_NATIVE_MAP map[]) // [out] { CONTRACTL { // MethodDesc::FindOrCreateTypicalSharedInstantiation throws THROWS; // MethodDesc::FindOrCreateTypicalSharedInstantiation triggers, but shouldn't trigger when // called from here. Since the profiler has a valid functionId, the methoddesc for // this code will already have been created. We should be able to enforce this by // passing allowCreate=FALSE to FindOrCreateTypicalSharedInstantiation. DISABLED(GC_NOTRIGGER); // Yay! MODE_ANY; // The call to g_pDebugInterface->GetILToNativeMapping() below may call // Debugger::AcquireDebuggerLock CAN_TAKE_LOCK; SO_NOT_MAINLINE; } CONTRACTL_END; PROFILER_TO_CLR_ENTRYPOINT_SYNC_EX(kP2EEAllowableAfterAttach, (LF_CORPROF, LL_INFO1000, "**PROF: GetILToNativeMapping 0x%p.\n", functionId)); return GetILToNativeMapping2(functionId, 0, cMap, pcMap, map); } HRESULT ProfToEEInterfaceImpl::GetILToNativeMapping2(FunctionID functionId, ReJITID reJitId, ULONG32 cMap, ULONG32 * pcMap, // [out] COR_DEBUG_IL_TO_NATIVE_MAP map[]) // [out] { CONTRACTL { // MethodDesc::FindOrCreateTypicalSharedInstantiation throws THROWS; // MethodDesc::FindOrCreateTypicalSharedInstantiation triggers, but shouldn't trigger when // called from here. Since the profiler has a valid functionId, the methoddesc for // this code will already have been created. We should be able to enforce this by // passing allowCreate=FALSE to FindOrCreateTypicalSharedInstantiation. DISABLED(GC_NOTRIGGER); // Yay! MODE_ANY; // The call to g_pDebugInterface->GetILToNativeMapping() below may call // Debugger::AcquireDebuggerLock CAN_TAKE_LOCK; SO_NOT_MAINLINE; } CONTRACTL_END; PROFILER_TO_CLR_ENTRYPOINT_SYNC_EX(kP2EEAllowableAfterAttach, (LF_CORPROF, LL_INFO1000, "**PROF: GetILToNativeMapping2 0x%p 0x%p.\n", functionId, reJitId)); if (functionId == NULL) { return E_INVALIDARG; } if ((cMap > 0) && ((pcMap == NULL) || (map == NULL))) { return E_INVALIDARG; } HRESULT hr = S_OK; EX_TRY { // Cast to proper type MethodDesc * pMD = FunctionIdToMethodDesc(functionId); if (pMD->HasClassOrMethodInstantiation() && pMD->IsTypicalMethodDefinition()) { // In this case, we used to replace pMD with its canonical instantiation // (FindOrCreateTypicalSharedInstantiation). However, a profiler should never be able // to get to this point anyway, since any MethodDesc a profiler gets from us // cannot be typical (i.e., cannot be a generic with types still left uninstantiated). // We assert here just in case a test proves me wrong, but generally we will // disallow this code path. _ASSERTE(!"Profiler passed a typical method desc (a generic with types still left uninstantiated) to GetILToNativeMapping2"); hr = E_INVALIDARG; } else { PCODE pCodeStart = NULL; CodeVersionManager *pCodeVersionManager = pMD->GetCodeVersionManager(); ILCodeVersion ilCodeVersion = NULL; { CodeVersionManager::TableLockHolder lockHolder(pCodeVersionManager); pCodeVersionManager->GetILCodeVersion(pMD, reJitId); NativeCodeVersionCollection nativeCodeVersions = ilCodeVersion.GetNativeCodeVersions(pMD); for (NativeCodeVersionIterator iter = nativeCodeVersions.Begin(); iter != nativeCodeVersions.End(); iter++) { // Now that tiered compilation can create more than one jitted code version for the same rejit id // we are arbitrarily choosing the first one to return. To address a specific version of native code // use GetILToNativeMapping3. pCodeStart = iter->GetNativeCode(); break; } } hr = GetILToNativeMapping3(pCodeStart, cMap, pcMap, map); } } EX_CATCH_HRESULT(hr); return hr; } //***************************************************************************** // Given an ObjectID, go get the EE ClassID for it. //***************************************************************************** HRESULT ProfToEEInterfaceImpl::GetClassFromObject(ObjectID objectId, ClassID * pClassId) { CONTRACTL { // Yay! NOTHROW; // Yay! GC_NOTRIGGER; // Yay! Fail at runtime if in preemptive mode via AllowObjectInspection() MODE_ANY; // Object::GetTypeHandle takes a lock CAN_TAKE_LOCK; SO_NOT_MAINLINE; } CONTRACTL_END; PROFILER_TO_CLR_ENTRYPOINT_SYNC_EX(kP2EEAllowableAfterAttach, (LF_CORPROF, LL_INFO1000, "**PROF: GetClassFromObject 0x%p.\n", objectId)); if (objectId == NULL) { return E_INVALIDARG; } HRESULT hr = AllowObjectInspection(); if (FAILED(hr)) { return hr; } // Cast the ObjectID as a Object Object *pObj = reinterpret_cast(objectId); // Set the out param and indicate success // Note that for generic code we always return uninstantiated ClassIDs and FunctionIDs if (pClassId) { *pClassId = SafeGetClassIDFromObject(pObj); } return S_OK; } //***************************************************************************** // Given a module and a token for a class, go get the EE data structure for it. //***************************************************************************** HRESULT ProfToEEInterfaceImpl::GetClassFromToken(ModuleID moduleId, mdTypeDef typeDef, ClassID *pClassId) { CONTRACTL { // Yay! NOTHROW; // ClassLoader::LoadTypeDefOrRefThrowing triggers GC_TRIGGERS; // Yay! MODE_ANY; // ClassLoader::LoadTypeDefOrRefThrowing takes a lock CAN_TAKE_LOCK; SO_NOT_MAINLINE; } CONTRACTL_END; PROFILER_TO_CLR_ENTRYPOINT_SYNC_EX( kP2EEAllowableAfterAttach | kP2EETriggers, (LF_CORPROF, LL_INFO1000, "**PROF: GetClassFromToken 0x%p, 0x%08x.\n", moduleId, typeDef)); if ((moduleId == NULL) || (typeDef == mdTypeDefNil) || (typeDef == NULL)) { return E_INVALIDARG; } if (!g_profControlBlock.fBaseSystemClassesLoaded) { return CORPROF_E_RUNTIME_UNINITIALIZED; } // Get the module Module *pModule = (Module *) moduleId; // No module, or it's disassociated from metadata if ((pModule == NULL) || (pModule->IsBeingUnloaded())) { return CORPROF_E_DATAINCOMPLETE; } // First, check the RID map. This is important since it // works during teardown (and the below doesn't) TypeHandle th; th = pModule->LookupTypeDef(typeDef); if (th.IsNull()) { HRESULT hr = S_OK; EX_TRY { th = ClassLoader::LoadTypeDefOrRefThrowing(pModule, typeDef, ClassLoader::ThrowIfNotFound, ClassLoader::PermitUninstDefOrRef); } EX_CATCH_HRESULT(hr); if (FAILED(hr)) { return hr; } } if (!th.GetMethodTable()) { return CORPROF_E_DATAINCOMPLETE; } // // Check if it is generic // ClassID classId = NonGenericTypeHandleToClassID(th); if (classId == NULL) { return CORPROF_E_TYPE_IS_PARAMETERIZED; } // Return value if necessary if (pClassId) { *pClassId = classId; } return S_OK; } HRESULT ProfToEEInterfaceImpl::GetClassFromTokenAndTypeArgs(ModuleID moduleID, mdTypeDef typeDef, ULONG32 cTypeArgs, ClassID typeArgs[], ClassID* pClassID) { CONTRACTL { // Yay! NOTHROW; // LoadGenericInstantiationThrowing may load GC_TRIGGERS; // Yay! MODE_ANY; // ClassLoader::LoadGenericInstantiationThrowing takes a lock CAN_TAKE_LOCK; SO_NOT_MAINLINE; } CONTRACTL_END; PROFILER_TO_CLR_ENTRYPOINT_SYNC_EX( kP2EEAllowableAfterAttach | kP2EETriggers, (LF_CORPROF, LL_INFO1000, "**PROF: GetClassFromTokenAndTypeArgs 0x%p, 0x%08x.\n", moduleID, typeDef)); if (!g_profControlBlock.fBaseSystemClassesLoaded) { return CORPROF_E_RUNTIME_UNINITIALIZED; } Module* pModule = reinterpret_cast< Module* >(moduleID); if (pModule == NULL || pModule->IsBeingUnloaded()) { return CORPROF_E_DATAINCOMPLETE; } // This array needs to be accessible at least until the call to // ClassLoader::LoadGenericInstantiationThrowing TypeHandle* genericParameters = new (nothrow) TypeHandle[cTypeArgs]; NewArrayHolder< TypeHandle > holder(genericParameters); if (NULL == genericParameters) { return E_OUTOFMEMORY; } for (ULONG32 i = 0; i < cTypeArgs; ++i) { genericParameters[i] = TypeHandle(reinterpret_cast< MethodTable* >(typeArgs[i])); } // // nickbe 11/24/2003 10:12:56 // // In RTM/Everett we decided to load the class if it hadn't been loaded yet // (see ProfToEEInterfaceImpl::GetClassFromToken). For compatibility we're // going to make the same decision here. It's potentially confusing to tell // someone a type doesn't exist at one point in time, but does exist later, // and there is no good way for us to determing that a class may eventually // be loaded without going ahead and loading it // TypeHandle th; HRESULT hr = S_OK; EX_TRY { // Not sure if this is a valid override or not - making this a VIOLATION // until we're sure. CONTRACT_VIOLATION(LoadsTypeViolation); if (GetThreadNULLOk() == NULL) { // Type system will try to validate as part of its contract if the current // AppDomain returned by GetAppDomain can load types in specified module's // assembly. On a non-EE thread it results in an AV in a check build // since the type system tries to dereference NULL returned by GetAppDomain. // More importantly, loading a type on a non-EE thread is not allowed. // // ENABLE_FORBID_GC_LOADER_USE_IN_THIS_SCOPE() states that callers will not // try to load a type, so that type system will not try to test type // loadability in the current AppDomain. However, // ENABLE_FORBID_GC_LOADER_USE_IN_THIS_SCOPE does not prevent callers from // loading a type. It is profiler's responsibility not to attempt to load // a type in unsupported ways (e.g. from a non-EE thread). It doesn't // impact retail builds, in which contracts are not available. ENABLE_FORBID_GC_LOADER_USE_IN_THIS_SCOPE(); // ENABLE_FORBID_GC_LOADER_USE_IN_THIS_SCOPE also defines FAULT_FORBID, which // causes Scanruntime to flag a fault violation in AssemblySpec::InitializeSpec, // which is defined as FAULTS. It only happens in a type-loading path, which // is not supported on a non-EE thread. Suppressing a contract violation in an // unsupported execution path is more preferable than causing AV when calling // GetClassFromTokenAndTypeArgs on a non-EE thread in a check build. See Dev10 // 682526 for more details. FAULT_NOT_FATAL(); th = ClassLoader::LoadGenericInstantiationThrowing(pModule, typeDef, Instantiation(genericParameters, cTypeArgs), ClassLoader::LoadTypes); } else { th = ClassLoader::LoadGenericInstantiationThrowing(pModule, typeDef, Instantiation(genericParameters, cTypeArgs), ClassLoader::LoadTypes); } } EX_CATCH_HRESULT(hr); if (FAILED(hr)) { return hr; } if (th.IsNull()) { // Hmm, the type isn't loaded yet. return CORPROF_E_DATAINCOMPLETE; } *pClassID = TypeHandleToClassID(th); return S_OK; } //***************************************************************************** // Given the token for a method, return the fucntion id. //***************************************************************************** HRESULT ProfToEEInterfaceImpl::GetFunctionFromToken(ModuleID moduleId, mdToken typeDef, FunctionID *pFunctionId) { CONTRACTL { // Yay! NOTHROW; // Yay! GC_NOTRIGGER; // Yay! MODE_ANY; // Yay! EE_THREAD_NOT_REQUIRED; // Yay! CANNOT_TAKE_LOCK; SO_NOT_MAINLINE; } CONTRACTL_END; PROFILER_TO_CLR_ENTRYPOINT_SYNC_EX(kP2EEAllowableAfterAttach, (LF_CORPROF, LL_INFO1000, "**PROF: GetFunctionFromToken 0x%p, 0x%08x.\n", moduleId, typeDef)); if ((moduleId == NULL) || (typeDef == mdTokenNil)) { return E_INVALIDARG; } if (!g_profControlBlock.fBaseSystemClassesLoaded) { return CORPROF_E_RUNTIME_UNINITIALIZED; } // Default HRESULT HRESULT hr = S_OK; // Get the module Module *pModule = (Module *) moduleId; // No module, or disassociated from metadata if (pModule == NULL || pModule->IsBeingUnloaded()) { return CORPROF_E_DATAINCOMPLETE; } // Default return value of NULL MethodDesc *pDesc = NULL; // Different lookup depending on whether it's a Def or Ref if (TypeFromToken(typeDef) == mdtMethodDef) { pDesc = pModule->LookupMethodDef(typeDef); } else if (TypeFromToken(typeDef) == mdtMemberRef) { pDesc = pModule->LookupMemberRefAsMethod(typeDef); } else { return E_INVALIDARG; } if (NULL == pDesc) { return E_INVALIDARG; } // // Check that this is a non-generic method // if (pDesc->HasClassOrMethodInstantiation()) { return CORPROF_E_FUNCTION_IS_PARAMETERIZED; } if (pFunctionId && SUCCEEDED(hr)) { *pFunctionId = MethodDescToFunctionID(pDesc); } return (hr); } HRESULT ProfToEEInterfaceImpl::GetFunctionFromTokenAndTypeArgs(ModuleID moduleID, mdMethodDef funcDef, ClassID classId, ULONG32 cTypeArgs, ClassID typeArgs[], FunctionID* pFunctionID) { CONTRACTL { // Yay! NOTHROW; // It can trigger type loads GC_TRIGGERS; // Yay! MODE_ANY; // MethodDesc::FindOrCreateAssociatedMethodDesc enters a Crst CAN_TAKE_LOCK; SO_NOT_MAINLINE; } CONTRACTL_END; PROFILER_TO_CLR_ENTRYPOINT_SYNC_EX( kP2EEAllowableAfterAttach | kP2EETriggers, (LF_CORPROF, LL_INFO1000, "**PROF: GetFunctionFromTokenAndTypeArgs 0x%p, 0x%08x, 0x%p.\n", moduleID, funcDef, classId)); TypeHandle typeHandle = TypeHandle::FromPtr((void *)classId); Module* pModule = reinterpret_cast< Module* >(moduleID); if ((pModule == NULL) || typeHandle.IsNull()) { return E_INVALIDARG; } if (!g_profControlBlock.fBaseSystemClassesLoaded) { return CORPROF_E_RUNTIME_UNINITIALIZED; } if (pModule->IsBeingUnloaded()) { return CORPROF_E_DATAINCOMPLETE; } MethodDesc* pMethodDesc = NULL; if (mdtMethodDef == TypeFromToken(funcDef)) { pMethodDesc = pModule->LookupMethodDef(funcDef); } else if (mdtMemberRef == TypeFromToken(funcDef)) { pMethodDesc = pModule->LookupMemberRefAsMethod(funcDef); } else { return E_INVALIDARG; } MethodTable* pMethodTable = typeHandle.GetMethodTable(); if (pMethodTable == NULL || !pMethodTable->IsRestored() || pMethodDesc == NULL || !pMethodDesc->IsRestored()) { return CORPROF_E_DATAINCOMPLETE; } // This array needs to be accessible at least until the call to // MethodDesc::FindOrCreateAssociatedMethodDesc TypeHandle* genericParameters = new (nothrow) TypeHandle[cTypeArgs]; NewArrayHolder< TypeHandle > holder(genericParameters); if (NULL == genericParameters) { return E_OUTOFMEMORY; } for (ULONG32 i = 0; i < cTypeArgs; ++i) { genericParameters[i] = TypeHandle(reinterpret_cast< MethodTable* >(typeArgs[i])); } MethodDesc* result = NULL; HRESULT hr = S_OK; EX_TRY { result = MethodDesc::FindOrCreateAssociatedMethodDesc(pMethodDesc, pMethodTable, FALSE, Instantiation(genericParameters, cTypeArgs), TRUE); } EX_CATCH_HRESULT(hr); if (NULL != result) { *pFunctionID = MethodDescToFunctionID(result); } return hr; } //***************************************************************************** // Retrieve information about a given application domain, which is like a // sub-process. //***************************************************************************** HRESULT ProfToEEInterfaceImpl::GetAppDomainInfo(AppDomainID appDomainId, ULONG cchName, ULONG *pcchName, __out_ecount_part_opt(cchName, *pcchName) WCHAR szName[], ProcessID *pProcessId) { CONTRACTL { // Yay! NOTHROW; // AppDomain::GetFriendlyNameForDebugger triggers GC_TRIGGERS; // Yay! MODE_ANY; // AppDomain::GetFriendlyNameForDebugger takes a lock CAN_TAKE_LOCK; SO_NOT_MAINLINE; } CONTRACTL_END; PROFILER_TO_CLR_ENTRYPOINT_SYNC_EX( kP2EEAllowableAfterAttach | kP2EETriggers, (LF_CORPROF, LL_INFO1000, "**PROF: GetAppDomainInfo 0x%p.\n", appDomainId)); if (appDomainId == NULL) { return E_INVALIDARG; } BaseDomain *pDomain; // Internal data structure. HRESULT hr = S_OK; // @todo: // Right now, this ID is not a true AppDomain, since we use the old // AppDomain/SystemDomain model in the profiling API. This means that // the profiler exposes the SharedDomain and the SystemDomain to the // outside world. It's not clear whether this is actually the right thing // to do or not. - seantrow // // Postponed to V2. // pDomain = (BaseDomain *) appDomainId; // Make sure they've passed in a valid appDomainId if (pDomain == NULL) return (E_INVALIDARG); // Pick sensible defaults. if (pcchName) *pcchName = 0; if (szName) *szName = 0; if (pProcessId) *pProcessId = 0; LPCWSTR szFriendlyName; if (pDomain == SystemDomain::System()) szFriendlyName = g_pwBaseLibrary; else if (pDomain == SharedDomain::GetDomain()) szFriendlyName = W("EE Shared Assembly Repository"); else szFriendlyName = ((AppDomain*)pDomain)->GetFriendlyNameForDebugger(); if (szFriendlyName != NULL) { // Get the module file name ULONG trueLen = (ULONG)(wcslen(szFriendlyName) + 1); // Return name of module as required. if (szName && cchName > 0) { ULONG copyLen = trueLen; if (copyLen >= cchName) { copyLen = cchName - 1; } wcsncpy_s(szName, cchName, szFriendlyName, copyLen); } // If they request the actual length of the name if (pcchName) *pcchName = trueLen; } // If we don't have a friendly name but the call was requesting it, then return incomplete data HR else { if ((szName != NULL && cchName > 0) || pcchName) hr = CORPROF_E_DATAINCOMPLETE; } if (pProcessId) *pProcessId = (ProcessID) GetCurrentProcessId(); return (hr); } //***************************************************************************** // Retrieve information about an assembly, which is a collection of dll's. //***************************************************************************** HRESULT ProfToEEInterfaceImpl::GetAssemblyInfo(AssemblyID assemblyId, ULONG cchName, ULONG *pcchName, __out_ecount_part_opt(cchName, *pcchName) WCHAR szName[], AppDomainID *pAppDomainId, ModuleID *pModuleId) { CONTRACTL { // SString::SString throws THROWS; // Yay! GC_NOTRIGGER; // Yay! MODE_ANY; // Yay! EE_THREAD_NOT_REQUIRED; // PEAssembly::GetSimpleName() enters a lock via use of the metadata interface CAN_TAKE_LOCK; SO_NOT_MAINLINE; } CONTRACTL_END; PROFILER_TO_CLR_ENTRYPOINT_SYNC_EX(kP2EEAllowableAfterAttach, (LF_CORPROF, LL_INFO1000, "**PROF: GetAssemblyInfo 0x%p.\n", assemblyId)); if (assemblyId == NULL) { return E_INVALIDARG; } HRESULT hr = S_OK; Assembly *pAssembly; // Internal data structure for assembly. pAssembly = (Assembly *) assemblyId; _ASSERTE(pAssembly != NULL); if (pcchName || szName) { // Get the friendly name of the assembly SString name(SString::Utf8, pAssembly->GetSimpleName()); const COUNT_T nameLength = name.GetCount() + 1; if ((NULL != szName) && (cchName > 0)) { wcsncpy_s(szName, cchName, name.GetUnicode(), min(nameLength, cchName - 1)); } if (NULL != pcchName) { *pcchName = nameLength; } } // Get the parent application domain. if (pAppDomainId) { *pAppDomainId = (AppDomainID) pAssembly->GetDomain(); _ASSERTE(*pAppDomainId != NULL); } // Find the module the manifest lives in. if (pModuleId) { *pModuleId = (ModuleID) pAssembly->GetManifestModule(); // This is the case where the profiler has called GetAssemblyInfo // on an assembly that has been completely created yet. if (!*pModuleId) hr = CORPROF_E_DATAINCOMPLETE; } return (hr); } // Setting ELT hooks is only allowed from within Initialize(). However, test-only // profilers may need to set those hooks from an attaching profiling. See // code:ProfControlBlock#TestOnlyELT #ifdef PROF_TEST_ONLY_FORCE_ELT #define PROFILER_TO_CLR_ENTRYPOINT_SET_ELT(logParams) \ do \ { \ if (g_profControlBlock.fTestOnlyForceEnterLeave) \ { \ PROFILER_TO_CLR_ENTRYPOINT_ASYNC_EX(kP2EEAllowableAfterAttach, logParams); \ } \ else \ { \ PROFILER_TO_CLR_ENTRYPOINT_CALLABLE_ON_INIT_ONLY(logParams); \ } \ } while(0) #else // PROF_TEST_ONLY_FORCE_ELT #define PROFILER_TO_CLR_ENTRYPOINT_SET_ELT \ PROFILER_TO_CLR_ENTRYPOINT_CALLABLE_ON_INIT_ONLY #endif // PROF_TEST_ONLY_FORCE_ELT HRESULT ProfToEEInterfaceImpl::SetEnterLeaveFunctionHooks(FunctionEnter * pFuncEnter, FunctionLeave * pFuncLeave, FunctionTailcall * pFuncTailcall) { CONTRACTL { // Yay! NOTHROW; // Yay! GC_NOTRIGGER; // Yay! MODE_ANY; // Yay! EE_THREAD_NOT_REQUIRED; CANNOT_TAKE_LOCK; SO_NOT_MAINLINE; } CONTRACTL_END; // The profiler must call SetEnterLeaveFunctionHooks during initialization, since // the enter/leave events are immutable and must also be set during initialization. PROFILER_TO_CLR_ENTRYPOINT_SET_ELT((LF_CORPROF, LL_INFO10, "**PROF: SetEnterLeaveFunctionHooks 0x%p, 0x%p, 0x%p.\n", pFuncEnter, pFuncLeave, pFuncTailcall)); return g_profControlBlock.pProfInterface->SetEnterLeaveFunctionHooks(pFuncEnter, pFuncLeave, pFuncTailcall); } HRESULT ProfToEEInterfaceImpl::SetEnterLeaveFunctionHooks2(FunctionEnter2 * pFuncEnter, FunctionLeave2 * pFuncLeave, FunctionTailcall2 * pFuncTailcall) { CONTRACTL { // Yay! NOTHROW; // Yay! GC_NOTRIGGER; // Yay! MODE_ANY; // Yay! EE_THREAD_NOT_REQUIRED; CANNOT_TAKE_LOCK; SO_NOT_MAINLINE; } CONTRACTL_END; // The profiler must call SetEnterLeaveFunctionHooks2 during initialization, since // the enter/leave events are immutable and must also be set during initialization. PROFILER_TO_CLR_ENTRYPOINT_SET_ELT((LF_CORPROF, LL_INFO10, "**PROF: SetEnterLeaveFunctionHooks2 0x%p, 0x%p, 0x%p.\n", pFuncEnter, pFuncLeave, pFuncTailcall)); return g_profControlBlock.pProfInterface->SetEnterLeaveFunctionHooks2(pFuncEnter, pFuncLeave, pFuncTailcall); } HRESULT ProfToEEInterfaceImpl::SetEnterLeaveFunctionHooks3(FunctionEnter3 * pFuncEnter3, FunctionLeave3 * pFuncLeave3, FunctionTailcall3 * pFuncTailcall3) { CONTRACTL { // Yay! NOTHROW; // Yay! GC_NOTRIGGER; // Yay! MODE_ANY; // Yay! EE_THREAD_NOT_REQUIRED; CANNOT_TAKE_LOCK; SO_NOT_MAINLINE; } CONTRACTL_END; // The profiler must call SetEnterLeaveFunctionHooks3 during initialization, since // the enter/leave events are immutable and must also be set during initialization. PROFILER_TO_CLR_ENTRYPOINT_SET_ELT((LF_CORPROF, LL_INFO10, "**PROF: SetEnterLeaveFunctionHooks3 0x%p, 0x%p, 0x%p.\n", pFuncEnter3, pFuncLeave3, pFuncTailcall3)); return g_profControlBlock.pProfInterface->SetEnterLeaveFunctionHooks3(pFuncEnter3, pFuncLeave3, pFuncTailcall3); } HRESULT ProfToEEInterfaceImpl::SetEnterLeaveFunctionHooks3WithInfo(FunctionEnter3WithInfo * pFuncEnter3WithInfo, FunctionLeave3WithInfo * pFuncLeave3WithInfo, FunctionTailcall3WithInfo * pFuncTailcall3WithInfo) { CONTRACTL { // Yay! NOTHROW; // Yay! GC_NOTRIGGER; // Yay! MODE_ANY; // Yay! EE_THREAD_NOT_REQUIRED; CANNOT_TAKE_LOCK; SO_NOT_MAINLINE; } CONTRACTL_END; // The profiler must call SetEnterLeaveFunctionHooks3WithInfo during initialization, since // the enter/leave events are immutable and must also be set during initialization. PROFILER_TO_CLR_ENTRYPOINT_SET_ELT((LF_CORPROF, LL_INFO10, "**PROF: SetEnterLeaveFunctionHooks3WithInfo 0x%p, 0x%p, 0x%p.\n", pFuncEnter3WithInfo, pFuncLeave3WithInfo, pFuncTailcall3WithInfo)); return g_profControlBlock.pProfInterface->SetEnterLeaveFunctionHooks3WithInfo(pFuncEnter3WithInfo, pFuncLeave3WithInfo, pFuncTailcall3WithInfo); } HRESULT ProfToEEInterfaceImpl::SetFunctionIDMapper(FunctionIDMapper *pFunc) { CONTRACTL { // Yay! NOTHROW; // Yay! GC_NOTRIGGER; // Yay! MODE_ANY; // Yay! EE_THREAD_NOT_REQUIRED; // Yay! CANNOT_TAKE_LOCK; SO_NOT_MAINLINE; } CONTRACTL_END; PROFILER_TO_CLR_ENTRYPOINT_ASYNC((LF_CORPROF, LL_INFO10, "**PROF: SetFunctionIDMapper 0x%p.\n", pFunc)); g_profControlBlock.pProfInterface->SetFunctionIDMapper(pFunc); return (S_OK); } HRESULT ProfToEEInterfaceImpl::SetFunctionIDMapper2(FunctionIDMapper2 *pFunc, void * clientData) { CONTRACTL { // Yay! NOTHROW; // Yay! GC_NOTRIGGER; // Yay! MODE_ANY; // Yay! EE_THREAD_NOT_REQUIRED; // Yay! CANNOT_TAKE_LOCK; SO_NOT_MAINLINE; } CONTRACTL_END; PROFILER_TO_CLR_ENTRYPOINT_ASYNC((LF_CORPROF, LL_INFO10, "**PROF: SetFunctionIDMapper2. pFunc: 0x%p. clientData: 0x%p.\n", pFunc, clientData)); g_profControlBlock.pProfInterface->SetFunctionIDMapper2(pFunc, clientData); return (S_OK); } /* * GetFunctionInfo2 * * This function takes the frameInfo returned from a profiler callback and splays it * out into as much information as possible. * * Parameters: * funcId - The function that is being requested. * frameInfo - Frame specific information from a callback (for resolving generics). * pClassId - An optional parameter for returning the class id of the function. * pModuleId - An optional parameter for returning the module of the function. * pToken - An optional parameter for returning the metadata token of the function. * cTypeArgs - The count of the size of the array typeArgs * pcTypeArgs - Returns the number of elements of typeArgs filled in, or if typeArgs is NULL * the number that would be needed. * typeArgs - An array to store generic type parameters for the function. * * Returns: * S_OK if successful. */ HRESULT ProfToEEInterfaceImpl::GetFunctionInfo2(FunctionID funcId, COR_PRF_FRAME_INFO frameInfo, ClassID *pClassId, ModuleID *pModuleId, mdToken *pToken, ULONG32 cTypeArgs, ULONG32 *pcTypeArgs, ClassID typeArgs[]) { CONTRACTL { // Yay! NOTHROW; // Yay! GC_NOTRIGGER; // Yay! MODE_ANY; // Yay! EE_THREAD_NOT_REQUIRED; // Generics::GetExactInstantiationsOfMethodAndItsClassFromCallInformation eventually // reads metadata which causes us to take a reader lock. However, see // code:#DisableLockOnAsyncCalls DISABLED(CAN_TAKE_LOCK); // Asynchronous functions can be called at arbitrary times when runtime // is holding locks that cannot be reentered without causing deadlock. // This contract detects any attempts to reenter locks held at the time // this function was called. CANNOT_RETAKE_LOCK; SO_NOT_MAINLINE; PRECONDITION(CheckPointer(pClassId, NULL_OK)); PRECONDITION(CheckPointer(pModuleId, NULL_OK)); PRECONDITION(CheckPointer(pToken, NULL_OK)); PRECONDITION(CheckPointer(pcTypeArgs, NULL_OK)); PRECONDITION(CheckPointer(typeArgs, NULL_OK)); } CONTRACTL_END; // See code:#DisableLockOnAsyncCalls PERMANENT_CONTRACT_VIOLATION(TakesLockViolation, ReasonProfilerAsyncCannotRetakeLock); PROFILER_TO_CLR_ENTRYPOINT_ASYNC_EX(kP2EEAllowableAfterAttach, (LF_CORPROF, LL_INFO1000, "**PROF: GetFunctionInfo2 0x%p.\n", funcId)); // // Verify parameters. // COR_PRF_FRAME_INFO_INTERNAL *pFrameInfo = (COR_PRF_FRAME_INFO_INTERNAL *)frameInfo; if ((funcId == NULL) || ((pFrameInfo != NULL) && (pFrameInfo->funcID != funcId))) { return E_INVALIDARG; } MethodDesc *pMethDesc = FunctionIdToMethodDesc(funcId); if (pMethDesc == NULL) { return E_INVALIDARG; } if ((cTypeArgs != 0) && (typeArgs == NULL)) { return E_INVALIDARG; } // it's not safe to examine a methoddesc that has not been restored so do not do so if (!pMethDesc ->IsRestored()) return CORPROF_E_DATAINCOMPLETE; // // Find the exact instantiation of this function. // TypeHandle specificClass; MethodDesc* pActualMethod; ClassID classId = NULL; if (pMethDesc->IsSharedByGenericInstantiations()) { BOOL exactMatch; OBJECTREF pThis = NULL; if (pFrameInfo != NULL) { // If FunctionID represents a generic methoddesc on a struct, then pFrameInfo->thisArg // isn't an Object*. It's a pointer directly into the struct's members (i.e., it's not pointing at the // method table). That means pFrameInfo->thisArg cannot be casted to an OBJECTREF for // use by Generics::GetExactInstantiationsOfMethodAndItsClassFromCallInformation. However, // Generics::GetExactInstantiationsOfMethodAndItsClassFromCallInformation won't even need a this pointer // for the methoddesc it's processing if the methoddesc is on a value type. So we // can safely pass NULL for the methoddesc's this in such a case. if (pMethDesc->GetMethodTable()->IsValueType()) { _ASSERTE(!pMethDesc->AcquiresInstMethodTableFromThis()); _ASSERTE(pThis == NULL); } else { pThis = ObjectToOBJECTREF((PTR_Object)(pFrameInfo->thisArg)); } } exactMatch = Generics::GetExactInstantiationsOfMethodAndItsClassFromCallInformation( pMethDesc, pThis, PTR_VOID((pFrameInfo != NULL) ? pFrameInfo->extraArg : NULL), &specificClass, &pActualMethod); if (exactMatch) { classId = TypeHandleToClassID(specificClass); } else if (!specificClass.HasInstantiation() || !specificClass.IsSharedByGenericInstantiations()) { // // In this case we could not get the type args for the method, but if the class // is not a generic class or is instantiated with value types, this value is correct. // classId = TypeHandleToClassID(specificClass); } else { // // We could not get any class information. // classId = NULL; } } else { TypeHandle typeHandle(pMethDesc->GetMethodTable()); classId = TypeHandleToClassID(typeHandle); pActualMethod = pMethDesc; } // // Fill in the ClassId, if desired // if (pClassId != NULL) { *pClassId = classId; } // // Fill in the ModuleId, if desired. // if (pModuleId != NULL) { *pModuleId = (ModuleID)pMethDesc->GetModule(); } // // Fill in the token, if desired. // if (pToken != NULL) { *pToken = (mdToken)pMethDesc->GetMemberDef(); } if ((cTypeArgs == 0) && (pcTypeArgs != NULL)) { // // They are searching for the size of the array needed, we can return that now and // short-circuit all the work below. // if (pcTypeArgs != NULL) { *pcTypeArgs = pActualMethod->GetNumGenericMethodArgs(); } return S_OK; } // // If no place to store resulting count, quit now. // if (pcTypeArgs == NULL) { return S_OK; } // // Fill in the type args // DWORD cArgsToFill = pActualMethod->GetNumGenericMethodArgs(); if (cArgsToFill > cTypeArgs) { cArgsToFill = cTypeArgs; } *pcTypeArgs = cArgsToFill; if (cArgsToFill == 0) { return S_OK; } Instantiation inst = pActualMethod->GetMethodInstantiation(); for (DWORD i = 0; i < cArgsToFill; i++) { typeArgs[i] = TypeHandleToClassID(inst[i]); } return S_OK; } /* * IsFunctionDynamic * * This function takes a functionId that maybe of a metadata-less method like an IL Stub * or LCG method and returns true in the pHasNoMetadata if it is indeed a metadata-less * method. * * Parameters: * functionId - The function that is being requested. * isDynamic - An optional parameter for returning if the function has metadata or not. * * Returns: * S_OK if successful. */ HRESULT ProfToEEInterfaceImpl::IsFunctionDynamic(FunctionID functionId, BOOL *isDynamic) { CONTRACTL { NOTHROW; GC_NOTRIGGER; MODE_ANY; EE_THREAD_NOT_REQUIRED; // Generics::GetExactInstantiationsOfMethodAndItsClassFromCallInformation eventually // reads metadata which causes us to take a reader lock. However, see // code:#DisableLockOnAsyncCalls DISABLED(CAN_TAKE_LOCK); // Asynchronous functions can be called at arbitrary times when runtime // is holding locks that cannot be reentered without causing deadlock. // This contract detects any attempts to reenter locks held at the time // this function was called. CANNOT_RETAKE_LOCK; SO_NOT_MAINLINE; PRECONDITION(CheckPointer(isDynamic, NULL_OK)); } CONTRACTL_END; // See code:#DisableLockOnAsyncCalls PERMANENT_CONTRACT_VIOLATION(TakesLockViolation, ReasonProfilerAsyncCannotRetakeLock); PROFILER_TO_CLR_ENTRYPOINT_ASYNC_EX(kP2EEAllowableAfterAttach, (LF_CORPROF, LL_INFO1000, "**PROF: IsFunctionDynamic 0x%p.\n", functionId)); // // Verify parameters. // if (functionId == NULL) { return E_INVALIDARG; } MethodDesc *pMethDesc = FunctionIdToMethodDesc(functionId); if (pMethDesc == NULL) { return E_INVALIDARG; } // it's not safe to examine a methoddesc that has not been restored so do not do so if (!pMethDesc->IsRestored()) return CORPROF_E_DATAINCOMPLETE; // // Fill in the pHasNoMetadata, if desired. // if (isDynamic != NULL) { *isDynamic = pMethDesc->IsNoMetadata(); } return S_OK; } /* * GetFunctionFromIP3 * * This function takes an IP and determines if it is a managed function returning its * FunctionID. This method is different from GetFunctionFromIP in that will return * FunctionIDs even if they have no associated metadata. * * Parameters: * ip - The instruction pointer. * pFunctionId - An optional parameter for returning the FunctionID. * pReJitId - The ReJIT id. * * Returns: * S_OK if successful. */ HRESULT ProfToEEInterfaceImpl::GetFunctionFromIP3(LPCBYTE ip, FunctionID * pFunctionId, ReJITID * pReJitId) { CONTRACTL { NOTHROW; // Grabbing the rejitid requires entering the rejit manager's hash table & lock, // which can switch us to preemptive mode and trigger GCs GC_TRIGGERS; MODE_ANY; EE_THREAD_NOT_REQUIRED; // Grabbing the rejitid requires entering the rejit manager's hash table & lock, CAN_TAKE_LOCK; SO_NOT_MAINLINE; } CONTRACTL_END; // See code:#DisableLockOnAsyncCalls PERMANENT_CONTRACT_VIOLATION(TakesLockViolation, ReasonProfilerAsyncCannotRetakeLock); PROFILER_TO_CLR_ENTRYPOINT_SYNC_EX( kP2EEAllowableAfterAttach | kP2EETriggers, (LF_CORPROF, LL_INFO1000, "**PROF: GetFunctionFromIP3 0x%p.\n", ip)); HRESULT hr = S_OK; EECodeInfo codeInfo; hr = GetFunctionFromIPInternal(ip, &codeInfo, /* failOnNoMetadata */ FALSE); if (FAILED(hr)) { return hr; } if (pFunctionId) { *pFunctionId = MethodDescToFunctionID(codeInfo.GetMethodDesc()); } if (pReJitId != NULL) { MethodDesc * pMD = codeInfo.GetMethodDesc(); *pReJitId = ReJitManager::GetReJitId(pMD, codeInfo.GetStartAddress()); } return S_OK; } /* * GetDynamicFunctionInfo * * This function takes a functionId that maybe of a metadata-less method like an IL Stub * or LCG method and gives information about it without failing like GetFunctionInfo. * * Parameters: * functionId - The function that is being requested. * pModuleId - An optional parameter for returning the module of the function. * ppvSig - An optional parameter for returning the signature of the function. * pbSig - An optional parameter for returning the size of the signature of the function. * cchName - A parameter for indicating the size of buffer for the wszName parameter. * pcchName - An optional parameter for returning the true size of the wszName parameter. * wszName - A parameter to the caller allocated buffer of size cchName * * Returns: * S_OK if successful. */ HRESULT ProfToEEInterfaceImpl::GetDynamicFunctionInfo(FunctionID functionId, ModuleID *pModuleId, PCCOR_SIGNATURE* ppvSig, ULONG* pbSig, ULONG cchName, ULONG *pcchName, __out_ecount_part_opt(cchName, *pcchName) WCHAR wszName[]) { CONTRACTL { NOTHROW; GC_NOTRIGGER; MODE_ANY; EE_THREAD_NOT_REQUIRED; // Generics::GetExactInstantiationsOfMethodAndItsClassFromCallInformation eventually // reads metadata which causes us to take a reader lock. However, see // code:#DisableLockOnAsyncCalls DISABLED(CAN_TAKE_LOCK); // Asynchronous functions can be called at arbitrary times when runtime // is holding locks that cannot be reentered without causing deadlock. // This contract detects any attempts to reenter locks held at the time // this function was called. CANNOT_RETAKE_LOCK; SO_NOT_MAINLINE; PRECONDITION(CheckPointer(pModuleId, NULL_OK)); PRECONDITION(CheckPointer(ppvSig, NULL_OK)); PRECONDITION(CheckPointer(pbSig, NULL_OK)); PRECONDITION(CheckPointer(pcchName, NULL_OK)); } CONTRACTL_END; // See code:#DisableLockOnAsyncCalls PERMANENT_CONTRACT_VIOLATION(TakesLockViolation, ReasonProfilerAsyncCannotRetakeLock); PROFILER_TO_CLR_ENTRYPOINT_ASYNC_EX(kP2EEAllowableAfterAttach, (LF_CORPROF, LL_INFO1000, "**PROF: GetDynamicFunctionInfo 0x%p.\n", functionId)); // // Verify parameters. // if (functionId == NULL) { return E_INVALIDARG; } MethodDesc *pMethDesc = FunctionIdToMethodDesc(functionId); if (pMethDesc == NULL) { return E_INVALIDARG; } // it's not safe to examine a methoddesc that has not been restored so do not do so if (!pMethDesc->IsRestored()) return CORPROF_E_DATAINCOMPLETE; if (!pMethDesc->IsNoMetadata()) return E_INVALIDARG; // // Fill in the ModuleId, if desired. // if (pModuleId != NULL) { *pModuleId = (ModuleID)pMethDesc->GetModule(); } // // Fill in the ppvSig and pbSig, if desired // if (ppvSig != NULL && pbSig != NULL) { pMethDesc->GetSig(ppvSig, pbSig); } HRESULT hr = S_OK; EX_TRY { if (wszName != NULL) *wszName = 0; if (pcchName != NULL) *pcchName = 0; StackSString ss; ss.SetUTF8(pMethDesc->GetName()); ss.Normalize(); LPCWSTR methodName = ss.GetUnicode(); ULONG trueLen = (ULONG)(wcslen(methodName) + 1); // Return name of method as required. if (wszName && cchName > 0) { if (cchName < trueLen) { hr = HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER); } else { wcsncpy_s(wszName, cchName, methodName, trueLen); } } // If they request the actual length of the name if (pcchName) *pcchName = trueLen; } EX_CATCH_HRESULT(hr); return (hr); } /* * GetNativeCodeStartAddresses * * Gets all of the native code addresses associated with a particular function. iered compilation * potentially creates different native code versions for a method, and this function allows profilers * to view all native versions of a method. * * Parameters: * functionID - The function that is being requested. * reJitId - The ReJIT id. * cCodeStartAddresses - A parameter for indicating the size of buffer for the codeStartAddresses parameter. * pcCodeStartAddresses - An optional parameter for returning the true size of the codeStartAddresses parameter. * codeStartAddresses - The array to be filled up with native code addresses. * * Returns: * S_OK if successful * */ HRESULT ProfToEEInterfaceImpl::GetNativeCodeStartAddresses(FunctionID functionID, ReJITID reJitId, ULONG32 cCodeStartAddresses, ULONG32 *pcCodeStartAddresses, UINT_PTR codeStartAddresses[]) { CONTRACTL { NOTHROW; GC_NOTRIGGER; MODE_ANY; EE_THREAD_NOT_REQUIRED; CAN_TAKE_LOCK; SO_NOT_MAINLINE; PRECONDITION(CheckPointer(pcCodeStartAddresses, NULL_OK)); PRECONDITION(CheckPointer(codeStartAddresses, NULL_OK)); } CONTRACTL_END; if (functionID == NULL) { return E_INVALIDARG; } PROFILER_TO_CLR_ENTRYPOINT_ASYNC_EX(kP2EEAllowableAfterAttach, (LF_CORPROF, LL_INFO1000, "**PROF: GetNativeCodeStartAddresses 0x%p 0x%p.\n", functionID, reJitId)); HRESULT hr = S_OK; EX_TRY { if (pcCodeStartAddresses != NULL) { *pcCodeStartAddresses = 0; } MethodDesc * methodDesc = FunctionIdToMethodDesc(functionID); PTR_MethodDesc pMD = PTR_MethodDesc(methodDesc); ULONG32 trueLen = 0; StackSArray addresses; CodeVersionManager *pCodeVersionManager = pMD->GetCodeVersionManager(); ILCodeVersion ilCodeVersion = NULL; { CodeVersionManager::TableLockHolder lockHolder(pCodeVersionManager); ilCodeVersion = pCodeVersionManager->GetILCodeVersion(pMD, reJitId); NativeCodeVersionCollection nativeCodeVersions = ilCodeVersion.GetNativeCodeVersions(pMD); for (NativeCodeVersionIterator iter = nativeCodeVersions.Begin(); iter != nativeCodeVersions.End(); iter++) { addresses.Append((*iter).GetNativeCode()); ++trueLen; } } if (pcCodeStartAddresses != NULL) { *pcCodeStartAddresses = trueLen; } if (codeStartAddresses != NULL) { if (cCodeStartAddresses < trueLen) { hr = HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER); } else { for(ULONG32 i = 0; i < trueLen; ++i) { codeStartAddresses[i] = addresses[i]; } } } } EX_CATCH_HRESULT(hr); return hr; } /* * GetILToNativeMapping3 * * This overload behaves the same as GetILToNativeMapping2, except it allows the profiler * to address specific native code versions instead of defaulting to the first one. * * Parameters: * pNativeCodeStartAddress - start address of the native code version, returned by GetNativeCodeStartAddresses * cMap - size of the map array * pcMap - how many items are returned in the map array * map - an array to store the il to native mappings in * * Returns: * S_OK if successful * */ HRESULT ProfToEEInterfaceImpl::GetILToNativeMapping3(UINT_PTR pNativeCodeStartAddress, ULONG32 cMap, ULONG32 *pcMap, COR_DEBUG_IL_TO_NATIVE_MAP map[]) { CONTRACTL { THROWS; DISABLED(GC_NOTRIGGER); MODE_ANY; CAN_TAKE_LOCK; SO_NOT_MAINLINE; PRECONDITION(CheckPointer(pcMap, NULL_OK)); PRECONDITION(CheckPointer(map, NULL_OK)); } CONTRACTL_END; PROFILER_TO_CLR_ENTRYPOINT_SYNC_EX(kP2EEAllowableAfterAttach, (LF_CORPROF, LL_INFO1000, "**PROF: GetILToNativeMapping3 0x%p.\n", pNativeCodeStartAddress)); if (pNativeCodeStartAddress == NULL) { return E_INVALIDARG; } if ((cMap > 0) && ((pcMap == NULL) || (map == NULL))) { return E_INVALIDARG; } #ifdef DEBUGGING_SUPPORTED if (g_pDebugInterface == NULL) { return CORPROF_E_DEBUGGING_DISABLED; } return (g_pDebugInterface->GetILToNativeMapping(pNativeCodeStartAddress, cMap, pcMap, map)); #else return E_NOTIMPL; #endif } /* * GetCodeInfo4 * * Gets the location and size of a jitted function. Tiered compilation potentially creates different native code * versions for a method, and this overload allows profilers to specify which native version it would like the * code info for. * * Parameters: * pNativeCodeStartAddress - start address of the native code version, returned by GetNativeCodeStartAddresses * cCodeInfos - size of the codeInfos array * pcCodeInfos - how many items are returned in the codeInfos array * codeInfos - an array to store the code infos in * * Returns: * S_OK if successful * */ HRESULT ProfToEEInterfaceImpl::GetCodeInfo4(UINT_PTR pNativeCodeStartAddress, ULONG32 cCodeInfos, ULONG32* pcCodeInfos, COR_PRF_CODE_INFO codeInfos[]) { CONTRACTL { NOTHROW; GC_TRIGGERS; MODE_ANY; EE_THREAD_NOT_REQUIRED; CAN_TAKE_LOCK; SO_NOT_MAINLINE; PRECONDITION(CheckPointer(pcCodeInfos, NULL_OK)); PRECONDITION(CheckPointer(codeInfos, NULL_OK)); } CONTRACTL_END; PROFILER_TO_CLR_ENTRYPOINT_SYNC_EX( kP2EEAllowableAfterAttach | kP2EETriggers, (LF_CORPROF, LL_INFO1000, "**PROF: GetCodeInfo4 0x%p.\n", pNativeCodeStartAddress)); if ((cCodeInfos != 0) && (codeInfos == NULL)) { return E_INVALIDARG; } return GetCodeInfoFromCodeStart(pNativeCodeStartAddress, cCodeInfos, pcCodeInfos, codeInfos); } /* * GetStringLayout * * This function describes to a profiler the internal layout of a string. * * Parameters: * pBufferLengthOffset - Offset within an OBJECTREF of a string of the ArrayLength field. * pStringLengthOffset - Offset within an OBJECTREF of a string of the StringLength field. * pBufferOffset - Offset within an OBJECTREF of a string of the Buffer field. * * Returns: * S_OK if successful. */ HRESULT ProfToEEInterfaceImpl::GetStringLayout(ULONG *pBufferLengthOffset, ULONG *pStringLengthOffset, ULONG *pBufferOffset) { CONTRACTL { // Yay! NOTHROW; // Yay! GC_NOTRIGGER; // Yay! MODE_ANY; // Yay! EE_THREAD_NOT_REQUIRED; // Yay! CANNOT_TAKE_LOCK; SO_NOT_MAINLINE; PRECONDITION(CheckPointer(pBufferLengthOffset, NULL_OK)); PRECONDITION(CheckPointer(pStringLengthOffset, NULL_OK)); PRECONDITION(CheckPointer(pBufferOffset, NULL_OK)); } CONTRACTL_END; PROFILER_TO_CLR_ENTRYPOINT_SYNC_EX(kP2EEAllowableAfterAttach, (LF_CORPROF, LL_INFO1000, "**PROF: GetStringLayout.\n")); return this->GetStringLayoutHelper(pBufferLengthOffset, pStringLengthOffset, pBufferOffset); } /* * GetStringLayout2 * * This function describes to a profiler the internal layout of a string. * * Parameters: * pStringLengthOffset - Offset within an OBJECTREF of a string of the StringLength field. * pBufferOffset - Offset within an OBJECTREF of a string of the Buffer field. * * Returns: * S_OK if successful. */ HRESULT ProfToEEInterfaceImpl::GetStringLayout2(ULONG *pStringLengthOffset, ULONG *pBufferOffset) { CONTRACTL { // Yay! NOTHROW; // Yay! GC_NOTRIGGER; // Yay! MODE_ANY; // Yay! EE_THREAD_NOT_REQUIRED; // Yay! CANNOT_TAKE_LOCK; SO_NOT_MAINLINE; PRECONDITION(CheckPointer(pStringLengthOffset, NULL_OK)); PRECONDITION(CheckPointer(pBufferOffset, NULL_OK)); } CONTRACTL_END; PROFILER_TO_CLR_ENTRYPOINT_SYNC_EX(kP2EEAllowableAfterAttach, (LF_CORPROF, LL_INFO1000, "**PROF: GetStringLayout2.\n")); ULONG dummyBufferLengthOffset; return this->GetStringLayoutHelper(&dummyBufferLengthOffset, pStringLengthOffset, pBufferOffset); } /* * GetStringLayoutHelper * * This function describes to a profiler the internal layout of a string. * * Parameters: * pBufferLengthOffset - Offset within an OBJECTREF of a string of the ArrayLength field. * pStringLengthOffset - Offset within an OBJECTREF of a string of the StringLength field. * pBufferOffset - Offset within an OBJECTREF of a string of the Buffer field. * * Returns: * S_OK if successful. */ HRESULT ProfToEEInterfaceImpl::GetStringLayoutHelper(ULONG *pBufferLengthOffset, ULONG *pStringLengthOffset, ULONG *pBufferOffset) { CONTRACTL { // Yay! NOTHROW; // Yay! GC_NOTRIGGER; // Yay! MODE_ANY; // Yay! EE_THREAD_NOT_REQUIRED; // Yay! CANNOT_TAKE_LOCK; SO_NOT_MAINLINE; PRECONDITION(CheckPointer(pBufferLengthOffset, NULL_OK)); PRECONDITION(CheckPointer(pStringLengthOffset, NULL_OK)); PRECONDITION(CheckPointer(pBufferOffset, NULL_OK)); } CONTRACTL_END; // The String class no longer has a bufferLength field in it. // We are returning the offset of the stringLength because that is the closest we can get // This is most certainly a breaking change and a new method // ICorProfilerInfo3::GetStringLayout2 has been added on the interface ICorProfilerInfo3 if (pBufferLengthOffset != NULL) { *pBufferLengthOffset = StringObject::GetStringLengthOffset(); } if (pStringLengthOffset != NULL) { *pStringLengthOffset = StringObject::GetStringLengthOffset(); } if (pBufferOffset != NULL) { *pBufferOffset = StringObject::GetBufferOffset(); } return S_OK; } /* * GetClassLayout * * This function describes to a profiler the internal layout of a class. * * Parameters: * classID - The class that is being queried. It is really a TypeHandle. * rFieldOffset - An array to store information about each field in the class. * cFieldOffset - Count of the number of elements in rFieldOffset. * pcFieldOffset - Upon return contains the number of elements filled in, or if * cFieldOffset is zero, the number of elements needed. * pulClassSize - Optional parameter for containing the size in bytes of the underlying * internal class structure. * * Returns: * S_OK if successful. */ HRESULT ProfToEEInterfaceImpl::GetClassLayout(ClassID classID, COR_FIELD_OFFSET rFieldOffset[], ULONG cFieldOffset, ULONG *pcFieldOffset, ULONG *pulClassSize) { CONTRACTL { // Yay! NOTHROW; // Yay! GC_NOTRIGGER; // Yay! MODE_ANY; // Yay! EE_THREAD_NOT_REQUIRED; // Yay! CANNOT_TAKE_LOCK; SO_NOT_MAINLINE; PRECONDITION(CheckPointer(rFieldOffset, NULL_OK)); PRECONDITION(CheckPointer(pcFieldOffset)); PRECONDITION(CheckPointer(pulClassSize, NULL_OK)); } CONTRACTL_END; PROFILER_TO_CLR_ENTRYPOINT_ASYNC_EX(kP2EEAllowableAfterAttach, (LF_CORPROF, LL_INFO1000, "**PROF: GetClassLayout 0x%p.\n", classID)); // // Verify parameters // if ((pcFieldOffset == NULL) || (classID == NULL)) { return E_INVALIDARG; } if ((cFieldOffset != 0) && (rFieldOffset == NULL)) { return E_INVALIDARG; } TypeHandle typeHandle = TypeHandle::FromPtr((void *)classID); // // This is the incorrect API for arrays or strings. Use GetArrayObjectInfo, and GetStringLayout // if (typeHandle.IsTypeDesc() || typeHandle.AsMethodTable()->IsArray()) { return E_INVALIDARG; } // // We used to have a bug where this API incorrectly succeeded for strings during startup. Profilers // took dependency on this bug. Let the API to succeed for strings during startup for backward compatibility. // if (typeHandle.AsMethodTable()->IsString() && g_profControlBlock.fBaseSystemClassesLoaded) { return E_INVALIDARG; } // // If this class is not fully restored, that is all the information we can get at this time. // if (!typeHandle.IsRestored()) { return CORPROF_E_DATAINCOMPLETE; } // Types can be pre-restored, but they still aren't expected to handle queries before // eager fixups have run. This is a targetted band-aid for a bug intellitrace was // running into - attempting to get the class layout for all types at module load time. // If we don't detect this the runtime will AV during the field iteration below. Feel // free to eliminate this check when a more complete solution is available. if (MethodTable::IsParentMethodTableTagged(typeHandle.AsMethodTable())) { return CORPROF_E_DATAINCOMPLETE; } // !IsValueType = IsArray || IsReferenceType Since IsArry has been ruled out above, it must // be a reference type if !IsValueType. BOOL fReferenceType = !typeHandle.IsValueType(); // // Fill in class size now // // Move after the check for typeHandle.GetMethodTable()->IsRestored() // because an unrestored MethodTable may have a bad EE class pointer // which will be used by MethodTable::GetNumInstanceFieldBytes // if (pulClassSize != NULL) { if (fReferenceType) { // aligned size including the object header for reference types *pulClassSize = typeHandle.GetMethodTable()->GetBaseSize(); } else { // unboxed and unaligned size for value types *pulClassSize = typeHandle.GetMethodTable()->GetNumInstanceFieldBytes(); } } ApproxFieldDescIterator fieldDescIterator(typeHandle.GetMethodTable(), ApproxFieldDescIterator::INSTANCE_FIELDS); ULONG cFields = fieldDescIterator.Count(); // // If they are looking to just get the count, return that. // if ((cFieldOffset == 0) || (rFieldOffset == NULL)) { *pcFieldOffset = cFields; return S_OK; } // // Dont put too many in the array. // if (cFields > cFieldOffset) { cFields = cFieldOffset; } *pcFieldOffset = cFields; // // Now fill in the array // ULONG i; FieldDesc *pField; for (i = 0; i < cFields; i++) { pField = fieldDescIterator.Next(); rFieldOffset[i].ridOfField = (ULONG)pField->GetMemberDef(); rFieldOffset[i].ulOffset = (ULONG)pField->GetOffset() + (fReferenceType ? Object::GetOffsetOfFirstField() : 0); } return S_OK; } typedef struct _PROFILER_STACK_WALK_DATA { StackSnapshotCallback *callback; ULONG32 infoFlags; ULONG32 contextFlags; void *clientData; #ifdef WIN64EXCEPTIONS StackFrame sfParent; #endif } PROFILER_STACK_WALK_DATA; /* * ProfilerStackWalkCallback * * This routine is used as the callback from the general stack walker for * doing snapshot stack walks * */ StackWalkAction ProfilerStackWalkCallback(CrawlFrame *pCf, PROFILER_STACK_WALK_DATA *pData) { CONTRACTL { SO_NOT_MAINLINE; NOTHROW; // throw is RIGHT out... the throw at minimum allocates the thrown object which we *must* not do GC_NOTRIGGER; // the stack is not necessarily crawlable at this state !!!) we must not induce a GC } CONTRACTL_END; MethodDesc *pFunc = pCf->GetFunction(); COR_PRF_FRAME_INFO_INTERNAL frameInfo; ULONG32 contextSize = 0; BYTE *context = NULL; UINT_PTR currentIP = 0; REGDISPLAY *pRegDisplay = pCf->GetRegisterSet(); #if defined(_TARGET_X86_) CONTEXT builtContext; #endif // // For Unmanaged-to-managed transitions we get a NativeMarker back, which we want // to return to the profiler as the context seed if it wants to walk the unmanaged // stack frame, so we report the functionId as NULL to indicate this. // if (pCf->IsNativeMarker()) { pFunc = NULL; } // // Skip all Lightweight reflection/emit functions // if ((pFunc != NULL) && pFunc->IsNoMetadata()) { return SWA_CONTINUE; } // // If this is not a transition of any sort and not a managed // method, ignore it. // if (!pCf->IsNativeMarker() && !pCf->IsFrameless()) { return SWA_CONTINUE; } currentIP = (UINT_PTR)pRegDisplay->ControlPC; frameInfo.size = sizeof(COR_PRF_FRAME_INFO_INTERNAL); frameInfo.version = COR_PRF_FRAME_INFO_INTERNAL_CURRENT_VERSION; if (pFunc != NULL) { frameInfo.funcID = MethodDescToFunctionID(pFunc); frameInfo.extraArg = NULL; } else { frameInfo.funcID = NULL; frameInfo.extraArg = NULL; } frameInfo.IP = currentIP; frameInfo.thisArg = NULL; if (pData->infoFlags & COR_PRF_SNAPSHOT_REGISTER_CONTEXT) { #if defined(_TARGET_X86_) // // X86 stack walking does not keep the context up-to-date during the // walk. Instead it keeps the REGDISPLAY up-to-date. Thus, we need to // build a CONTEXT from the REGDISPLAY. // memset(&builtContext, 0, sizeof(builtContext)); builtContext.ContextFlags = CONTEXT_INTEGER | CONTEXT_CONTROL; CopyRegDisplay(pRegDisplay, NULL, &builtContext); context = (BYTE *)(&builtContext); #else context = (BYTE *)pRegDisplay->pCurrentContext; #endif contextSize = sizeof(CONTEXT); } // NOTE: We are intentionally not setting any callback state flags here (i.e., not using // SetCallbackStateFlagsHolder), as we want the DSS callback to "inherit" the // same callback state that DSS has: if DSS was called asynchronously, then consider // the DSS callback to be called asynchronously. if (pData->callback(frameInfo.funcID, frameInfo.IP, (COR_PRF_FRAME_INFO)&frameInfo, contextSize, context, pData->clientData) == S_OK) { return SWA_CONTINUE; } return SWA_ABORT; } #ifdef _TARGET_X86_ //--------------------------------------------------------------------------------------- // Normally, calling GetFunction() on the frame is sufficient to ensure // HelperMethodFrames are intialized. However, sometimes we need to be able to specify // that we should not enter the host while initializing, so we need to initialize such // frames more directly. This small helper function directly forces the initialization, // and ensures we don't enter the host as a result if we're executing in an asynchronous // call (i.e., hijacked thread) // // Arguments: // pFrame - Frame to initialize. // // Return Value: // TRUE iff pFrame was successfully initialized (or was already initialized). If // pFrame is not a HelperMethodFrame (or derived type), this returns TRUE // immediately. FALSE indicates we tried to initialize w/out entering the host, and // had to abort as a result when a reader lock was needed but unavailable. // static BOOL EnsureFrameInitialized(Frame * pFrame) { CONTRACTL { NOTHROW; GC_NOTRIGGER; // If this is called asynchronously (from a hijacked thread, as with F1), it must not re-enter the // host (SQL). Corners will be cut to ensure this is the case if (ShouldAvoidHostCalls()) { HOST_NOCALLS; } else { HOST_CALLS; } SUPPORTS_DAC; } CONTRACTL_END; if (pFrame->GetFrameType() != Frame::TYPE_HELPER_METHOD_FRAME) { // This frame is not a HelperMethodFrame or a frame derived from // HelperMethodFrame, so HMF-specific lazy initialization is not an issue. return TRUE; } HelperMethodFrame * pHMF = (HelperMethodFrame *) pFrame; if (pHMF->InsureInit( false, // initialInit NULL, // unwindState (ShouldAvoidHostCalls() ? NoHostCalls : AllowHostCalls) ) != NULL) { // InsureInit() succeeded and found the return address return TRUE; } // No return address was found. It must be because we asked InsureInit() to bail if // it would have entered the host _ASSERTE(ShouldAvoidHostCalls()); return FALSE; } //--------------------------------------------------------------------------------------- // // Implements the COR_PRF_SNAPSHOT_X86_OPTIMIZED algorithm called by DoStackSnapshot. // Does a simple EBP walk, rather than invoking all of StackWalkFramesEx. // // Arguments: // pThreadToSnapshot - Thread whose stack should be walked // pctxSeed - Register context with which to seed the walk // callback - Function to call at each frame found during the walk // clientData - Parameter to pass through to callback // // Return Value: // HRESULT indicating success or failure. // HRESULT ProfToEEInterfaceImpl::ProfilerEbpWalker( Thread * pThreadToSnapshot, LPCONTEXT pctxSeed, StackSnapshotCallback * callback, void * clientData) { CONTRACTL { GC_NOTRIGGER; NOTHROW; MODE_ANY; EE_THREAD_NOT_REQUIRED; // If this is called asynchronously (from a hijacked thread, as with F1), it must not re-enter the // host (SQL). Corners will be cut to ensure this is the case if (ShouldAvoidHostCalls()) { HOST_NOCALLS; } else { HOST_CALLS; } } CONTRACTL_END; HRESULT hr; // We haven't set the stackwalker thread type flag yet (see next line), so it shouldn't be set. Only // exception to this is if the current call is made by a hijacking profiler which // redirected this thread while it was previously in the middle of another stack walk _ASSERTE(IsCalledAsynchronously() || !IsStackWalkerThread()); // Remember that we're walking the stack. This holder will reinstate the original // value of the stackwalker flag (from the thread type mask) in its destructor. ClrFlsValueSwitch _threadStackWalking(TlsIdx_StackWalkerWalkingThread, pThreadToSnapshot); // This flag remembers if we reported a managed frame since the last unmanaged block // we reported. It's used to avoid reporting two unmanaged blocks in a row. BOOL fReportedAtLeastOneManagedFrameSinceLastUnmanagedBlock = FALSE; Frame * pFrameCur = pThreadToSnapshot->GetFrame(); CONTEXT ctxCur; ZeroMemory(&ctxCur, sizeof(ctxCur)); // Use seed if we got one. Otherwise, EE explicit Frame chain will seed the walk. if (pctxSeed != NULL) { ctxCur.Ebp = pctxSeed->Ebp; ctxCur.Eip = pctxSeed->Eip; ctxCur.Esp = pctxSeed->Esp; } while (TRUE) { // At each iteration of the loop: // * Analyze current frame (get managed data if it's a managed frame) // * Report current frame via callback() // * Walk down to next frame // **** Managed or unmanaged frame? **** EECodeInfo codeInfo; MethodDesc * pMethodDescCur = NULL; if (ctxCur.Eip != 0) { hr = GetFunctionInfoInternal( (LPCBYTE) ctxCur.Eip, &codeInfo); if (hr == CORPROF_E_ASYNCHRONOUS_UNSAFE) { _ASSERTE(ShouldAvoidHostCalls()); return hr; } if (SUCCEEDED(hr)) { pMethodDescCur = codeInfo.GetMethodDesc(); } } // **** Report frame to profiler **** if ( // Make sure the frame gave us an IP (ctxCur.Eip != 0) && // Make sure any managed frame isn't for an IL stub or LCG ((pMethodDescCur == NULL) || !pMethodDescCur->IsNoMetadata()) && // Only report unmanaged frames if the last frame we reported was managed // (avoid reporting two unmanaged blocks in a row) ((pMethodDescCur != NULL) || fReportedAtLeastOneManagedFrameSinceLastUnmanagedBlock)) { // Around the call to the profiler, temporarily clear the // ThreadType_StackWalker type flag, as we have no control over what the // profiler may do inside its callback (it could theoretically attempt to // load other types, though I don't personally know of profilers that // currently do this). CLEAR_THREAD_TYPE_STACKWALKER(); hr = callback( (FunctionID) pMethodDescCur, ctxCur.Eip, NULL, // COR_PRF_FRAME_INFO sizeof(ctxCur), // contextSize, (LPBYTE) &ctxCur, // context, clientData); SET_THREAD_TYPE_STACKWALKER(pThreadToSnapshot); if (hr != S_OK) { return hr; } if (pMethodDescCur == NULL) { // Just reported an unmanaged block, so reset the flag fReportedAtLeastOneManagedFrameSinceLastUnmanagedBlock = FALSE; } else { // Just reported a managed block, so remember it fReportedAtLeastOneManagedFrameSinceLastUnmanagedBlock = TRUE; } } // **** Walk down to next frame **** // Is current frame managed or unmanaged? if (pMethodDescCur == NULL) { // Unmanaged frame. Use explicit EE Frame chain to help REGDISPLAY frameRD; ZeroMemory(&frameRD, sizeof(frameRD)); while (pFrameCur != FRAME_TOP) { // Frame is only useful if it will contain register context info if (!pFrameCur->NeedsUpdateRegDisplay()) { goto Loop; } // This should be the first call we make to the Frame, as it will // ensure we force lazy initialize of HelperMethodFrames if (!EnsureFrameInitialized(pFrameCur)) { return CORPROF_E_ASYNCHRONOUS_UNSAFE; } // This frame is only useful if it gives us an actual return address, // and is situated on the stack at or below our current ESP (stack // grows up) if ((pFrameCur->GetReturnAddress() != NULL) && (dac_cast(pFrameCur) >= dac_cast(ctxCur.Esp))) { pFrameCur->UpdateRegDisplay(&frameRD); break; } Loop: pFrameCur = pFrameCur->PtrNextFrame(); } if (pFrameCur == FRAME_TOP) { // No more frames. Stackwalk is over return S_OK; } // Update ctxCur based on frame ctxCur.Eip = pFrameCur->GetReturnAddress(); ctxCur.Ebp = GetRegdisplayFP(&frameRD); ctxCur.Esp = GetRegdisplaySP(&frameRD); } else { // Managed frame. // GC info will assist us in determining whether this is a non-EBP frame and // info about pushed arguments. GCInfoToken gcInfoToken = codeInfo.GetGCInfoToken(); PTR_VOID gcInfo = gcInfoToken.Info; InfoHdr header; unsigned uiMethodSizeDummy; PTR_CBYTE table = PTR_CBYTE(gcInfo); table += decodeUnsigned(table, &uiMethodSizeDummy); table = decodeHeader(table, gcInfoToken.Version, &header); // Ok, GCInfo, can we do a simple EBP walk or what? if ((codeInfo.GetRelOffset() < header.prologSize) || (!header.ebpFrame && !header.doubleAlign)) { // We're either in the prolog or we're not in an EBP frame, in which case // we'll just defer to the code manager to unwind for us. This condition // is relatively rare, but can occur if: // // * The profiler did a DSS from its Enter hook, in which case we're // still inside the prolog, OR // * The seed context or explicit EE Frame chain seeded us with a // non-EBP frame function. In this case, using a naive EBP // unwinding algorithm would actually skip over the next EBP // frame, and would get SP all wrong as we try skipping over // the pushed parameters. So let's just ask the code manager for // help. // // Note that there are yet more conditions (much more rare) where the EBP // walk could get lost (e.g., we're inside an epilog). But we only care // about the most likely cases, and it's ok if the unlikely cases result // in truncated stacks, as unlikely cases will be statistically // irrelevant to CPU performance sampling profilers CodeManState codeManState; codeManState.dwIsSet = 0; REGDISPLAY rd; ZeroMemory(&rd, sizeof(rd)); rd.SetEbpLocation(&ctxCur.Ebp); rd.SP = ctxCur.Esp; rd.ControlPC = ctxCur.Eip; codeInfo.GetCodeManager()->UnwindStackFrame( &rd, &codeInfo, SpeculativeStackwalk, &codeManState, NULL); ctxCur.Ebp = *rd.GetEbpLocation(); ctxCur.Esp = rd.SP; ctxCur.Eip = rd.ControlPC; } else { // We're in an actual EBP frame, so we can simplistically walk down to // the next frame using EBP. // Return address is stored just below saved EBP (stack grows up) ctxCur.Eip = *(DWORD *) (ctxCur.Ebp + sizeof(DWORD)); ctxCur.Esp = // Stack location where current function pushed its EBP ctxCur.Ebp + // Skip past that EBP sizeof(DWORD) + // Skip past return address pushed by caller sizeof(DWORD) + // Skip past arguments to current function that were pushed by caller. // (Caller will pop varargs, so don't count those.) (header.varargs ? 0 : (header.argCount * sizeof(DWORD))); // EBP for frame below us (stack grows up) has been saved onto our own // frame. Dereference it now. ctxCur.Ebp = *(DWORD *) ctxCur.Ebp; } } } } #endif // _TARGET_X86_ //***************************************************************************** // The profiler stackwalk Wrapper //***************************************************************************** HRESULT ProfToEEInterfaceImpl::ProfilerStackWalkFramesWrapper(Thread * pThreadToSnapshot, PROFILER_STACK_WALK_DATA * pData, unsigned flags) { STATIC_CONTRACT_WRAPPER; StackWalkAction swaRet = pThreadToSnapshot->StackWalkFrames( (PSTACKWALKFRAMESCALLBACK)ProfilerStackWalkCallback, pData, flags, NULL); switch (swaRet) { default: _ASSERTE(!"Unexpected StackWalkAction returned from Thread::StackWalkFrames"); return E_FAIL; case SWA_FAILED: return E_FAIL; case SWA_ABORT: return CORPROF_E_STACKSNAPSHOT_ABORTED; case SWA_DONE: return S_OK; } } //--------------------------------------------------------------------------------------- // // DoStackSnapshot helper to call FindJitMan to determine if the specified // context is in managed code. // // Arguments: // pCtx - Context to look at // hostCallPreference - Describes how to acquire the reader lock--either AllowHostCalls // or NoHostCalls (see code:HostCallPreference). // // Return Value: // S_OK: The context is in managed code // S_FALSE: The context is not in managed code. // Error: Unable to determine (typically because hostCallPreference was NoHostCalls // and the reader lock was unattainable without yielding) // HRESULT IsContextInManagedCode(const CONTEXT * pCtx, HostCallPreference hostCallPreference) { WRAPPER_NO_CONTRACT; BOOL fFailedReaderLock = FALSE; // if there's no Jit Manager for the IP, it's not managed code. BOOL fIsManagedCode = ExecutionManager::IsManagedCode(GetIP(pCtx), hostCallPreference, &fFailedReaderLock); if (fFailedReaderLock) { return CORPROF_E_ASYNCHRONOUS_UNSAFE; } return fIsManagedCode ? S_OK : S_FALSE; } //***************************************************************************** // Perform a stack walk, calling back to callback at each managed frame. //***************************************************************************** HRESULT ProfToEEInterfaceImpl::DoStackSnapshot(ThreadID thread, StackSnapshotCallback *callback, ULONG32 infoFlags, void *clientData, BYTE * pbContext, ULONG32 contextSize) { #if !defined(FEATURE_HIJACK) // DoStackSnapshot needs Thread::Suspend/ResumeThread functionality. // On platforms w/o support for these APIs return E_NOTIMPL. return E_NOTIMPL; #else // !defined(FEATURE_HIJACK) CONTRACTL { // Yay! (Note: NOTHROW is vital. The throw at minimum allocates // the thrown object which we *must* not do.) NOTHROW; // Yay! (Note: this is called asynchronously to view the stack at arbitrary times, // so the stack is not necessarily crawlable for GC at this state!) GC_NOTRIGGER; // Yay! MODE_ANY; // Yay! EE_THREAD_NOT_REQUIRED; // #DisableLockOnAsyncCalls // This call is allowed asynchronously, however it does take locks. Therefore, // we will hit contract asserts if we happen to be in a CANNOT_TAKE_LOCK zone when // a hijacking profiler hijacks this thread to run DoStackSnapshot. CANNOT_RETAKE_LOCK // is a more granular locking contract that says "I promise that if I take locks, I // won't reenter any locks that were taken before this function was called". DISABLED(CAN_TAKE_LOCK); // Asynchronous functions can be called at arbitrary times when runtime // is holding locks that cannot be reentered without causing deadlock. // This contract detects any attempts to reenter locks held at the time // this function was called. CANNOT_RETAKE_LOCK; SO_NOT_MAINLINE; } CONTRACTL_END; // This CONTRACT_VIOLATION is still needed because DISABLED(CAN_TAKE_LOCK) does not // turn off contract violations. PERMANENT_CONTRACT_VIOLATION(TakesLockViolation, ReasonProfilerAsyncCannotRetakeLock); LPCONTEXT pctxSeed = reinterpret_cast (pbContext); PROFILER_TO_CLR_ENTRYPOINT_ASYNC_EX(kP2EEAllowableAfterAttach, (LF_CORPROF, LL_INFO1000, "**PROF: DoStackSnapshot 0x%p, 0x%p, 0x%08x, 0x%p, 0x%p, 0x%08x.\n", thread, callback, infoFlags, clientData, pctxSeed, contextSize)); HRESULT hr = E_UNEXPECTED; // (hr assignment is to appease the rotor compiler; we won't actually return without explicitly setting hr again) Thread *pThreadToSnapshot = NULL; Thread * pCurrentThread = GetThreadNULLOk(); BOOL fResumeThread = FALSE; INDEBUG(ULONG ulForbidTypeLoad = 0;) BOOL fResetSnapshotThreadExternalCount = FALSE; int cRefsSnapshotThread = 0; // Remember whether we've already determined the current context of the target thread // is in managed (S_OK), not in managed (S_FALSE), or unknown (error). HRESULT hrCurrentContextIsManaged = E_FAIL; CONTEXT ctxCurrent; memset(&ctxCurrent, 0, sizeof(ctxCurrent)); REGDISPLAY rd; PROFILER_STACK_WALK_DATA data; if (!g_fEEStarted ) { // no managed code has run and things are likely in a very bad have loaded state // this is a bad time to try to walk the stack // Returning directly as there is nothing to cleanup yet return CORPROF_E_STACKSNAPSHOT_UNSAFE; } if (!CORProfilerStackSnapshotEnabled()) { // Returning directly as there is nothing to cleanup yet, and can't skip gcholder ctor return CORPROF_E_INCONSISTENT_WITH_FLAGS; } if (thread == NULL) { pThreadToSnapshot = pCurrentThread; } else { pThreadToSnapshot = (Thread *)thread; } #ifdef _TARGET_X86_ if ((infoFlags & ~(COR_PRF_SNAPSHOT_REGISTER_CONTEXT | COR_PRF_SNAPSHOT_X86_OPTIMIZED)) != 0) #else if ((infoFlags & ~(COR_PRF_SNAPSHOT_REGISTER_CONTEXT)) != 0) #endif { // Returning directly as there is nothing to cleanup yet, and can't skip gcholder ctor return E_INVALIDARG; } if (!IsManagedThread(pThreadToSnapshot) || !IsGarbageCollectorFullyInitialized()) { // // No managed frames, return now. // // Returning directly as there is nothing to cleanup yet, and can't skip gcholder ctor return S_OK; } // We must make sure no other thread tries to hijack the thread we're about to walk // Hijacking means Thread::HijackThread, i.e. bashing return addresses which would break the stack walk Thread::HijackLockHolder hijackLockHolder(pThreadToSnapshot); if (!hijackLockHolder.Acquired()) { // Returning directly as there is nothing to cleanup yet, and can't skip gcholder ctor return CORPROF_E_STACKSNAPSHOT_UNSAFE; } if (pThreadToSnapshot != pCurrentThread // Walking separate thread && pCurrentThread != NULL // Walker (current) thread is a managed / VM thread && ThreadSuspend::SysIsSuspendInProgress()) // EE is trying suspend itself { // Since we're walking a separate thread, we'd have to suspend it first (see below). // And since the current thread is a VM thread, that means the current thread's // m_dwForbidSuspendThread count will go up while it's trying to suspend the // target thread (see Thread::SuspendThread). THAT means no one will be able // to suspend the current thread until its m_dwForbidSuspendThread is decremented // (which happens as soon as the target thread of DoStackSnapshot has been suspended). // Since we're in the process of suspending the entire runtime, now would be a bad time to // make the walker thread un-suspendable (see VsWhidbey bug 454936). So let's just abort // now. Note that there is no synchronization around calling Thread::SysIsSuspendInProgress(). // So we will get occasional false positives or false negatives. But that's benign, as the worst // that might happen is we might occasionally delay the EE suspension a little bit, or we might // too eagerly fail from ProfToEEInterfaceImpl::DoStackSnapshot sometimes. But there won't // be any corruption or AV. // // Returning directly as there is nothing to cleanup yet, and can't skip gcholder ctor return CORPROF_E_STACKSNAPSHOT_UNSAFE; } // We only allow stackwalking if: // 1) Target thread to walk == current thread OR Target thread is suspended, AND // 2) Target thread to walk is currently executing JITted / NGENd code, AND // 3) Target thread to walk is seeded OR currently NOT unwinding the stack, AND // 4) Target thread to walk != current thread OR current thread is NOT in a can't stop or forbid suspend region // If the thread is in a forbid suspend region, it's dangerous to do anything: // - The code manager datastructures accessed during the stackwalk may be in inconsistent state. // - Thread::Suspend won't be able to suspend the thread. if (pThreadToSnapshot->IsInForbidSuspendRegion()) { hr = CORPROF_E_STACKSNAPSHOT_UNSAFE; goto Cleanup; } HostCallPreference hostCallPreference; // First, check "1) Target thread to walk == current thread OR Target thread is suspended" if (pThreadToSnapshot != pCurrentThread) { #ifndef PLATFORM_SUPPORTS_SAFE_THREADSUSPEND hr = E_NOTIMPL; goto Cleanup; #else // Walking separate thread, so it must be suspended. First, ensure that // target thread exists. // // NOTE: We're using the "dangerous" variant of this refcount function, because we // rely on the profiler to ensure it never tries to walk a thread being destroyed. // (Profiler must block in its ThreadDestroyed() callback until all uses of that thread, // such as walking its stack, are complete.) cRefsSnapshotThread = pThreadToSnapshot->IncExternalCountDANGEROUSProfilerOnly(); fResetSnapshotThreadExternalCount = TRUE; if (cRefsSnapshotThread == 1 || !pThreadToSnapshot->HasValidThreadHandle()) { // At this point, we've modified the VM state based on bad input // (pThreadToSnapshot) from the profiler. This could cause // memory corruption and leave us vulnerable to security problems. // So destroy the process. _ASSERTE(!"Profiler trying to walk destroyed thread"); EEPOLICY_HANDLE_FATAL_ERROR(CORPROF_E_STACKSNAPSHOT_INVALID_TGT_THREAD); } // Thread::SuspendThread() ensures that no one else should try to suspend us // while we're suspending pThreadToSnapshot. // // TRUE: OneTryOnly. Don't loop waiting for others to get out of our way in // order to suspend the thread. If it's not safe, just return an error immediately. Thread::SuspendThreadResult str = pThreadToSnapshot->SuspendThread(TRUE); if (str == Thread::STR_Success) { fResumeThread = TRUE; } else { hr = CORPROF_E_STACKSNAPSHOT_UNSAFE; goto Cleanup; } #endif // !PLATFORM_SUPPORTS_SAFE_THREADSUSPEND } hostCallPreference = ShouldAvoidHostCalls() ? NoHostCalls : // Async call: Ensure this thread won't yield & re-enter host AllowHostCalls; // Synchronous calls may re-enter host just fine // If target thread is in pre-emptive mode, the profiler's seed context is unnecessary // because our frame chain is good enough: it will give us at least as accurate a // starting point as the profiler could. Also, since profiler contexts cannot be // trusted, we don't want to set the thread's profiler filter context to this, as a GC // that interrupts the profiler's stackwalk will end up using the profiler's (potentially // bogus) filter context. if (!pThreadToSnapshot->PreemptiveGCDisabledOther()) { // Thread to be walked is in preemptive mode. Throw out seed. pctxSeed = NULL; } else if (pThreadToSnapshot != pCurrentThread) { // With cross-thread stack-walks, the target thread's context could be unreliable. // That would shed doubt on either a profiler-provided context, or a default // context we chose. So check if we're in a potentially unreliable case, and return // an error if so. // // These heurisitics are based on an actual bug where GetThreadContext returned a // self-consistent, but stale, context for a thread suspended after being redirected by // the GC (TFS Dev 10 bug # 733263). // // (Note that this whole block is skipped if pThreadToSnapshot is in preemptive mode (the IF // above), as the context is unused in such a case--the EE Frame chain is used // to seed the walk instead.) #ifndef PLATFORM_SUPPORTS_SAFE_THREADSUSPEND hr = E_NOTIMPL; goto Cleanup; #else if (!pThreadToSnapshot->GetSafelyRedirectableThreadContext(Thread::kDefaultChecks, &ctxCurrent, &rd)) { LOG((LF_CORPROF, LL_INFO100, "**PROF: GetSafelyRedirectableThreadContext failure leads to CORPROF_E_STACKSNAPSHOT_UNSAFE.\n")); hr = CORPROF_E_STACKSNAPSHOT_UNSAFE; goto Cleanup; } hrCurrentContextIsManaged = IsContextInManagedCode(&ctxCurrent, hostCallPreference); if (FAILED(hrCurrentContextIsManaged)) { // Couldn't get the info. Try again later _ASSERTE(ShouldAvoidHostCalls()); hr = CORPROF_E_ASYNCHRONOUS_UNSAFE; goto Cleanup; } if ((hrCurrentContextIsManaged == S_OK) && (!pThreadToSnapshot->PreemptiveGCDisabledOther())) { // Thread is in preemptive mode while executing managed code?! This lie is // an early warning sign that the context is bogus. Bail. LOG((LF_CORPROF, LL_INFO100, "**PROF: Target thread context is likely bogus. Returning CORPROF_E_STACKSNAPSHOT_UNSAFE.\n")); hr = CORPROF_E_STACKSNAPSHOT_UNSAFE; goto Cleanup; } Frame * pFrame = pThreadToSnapshot->GetFrame(); if (pFrame != FRAME_TOP) { TADDR spTargetThread = GetSP(&ctxCurrent); if (dac_cast(pFrame) < spTargetThread) { // An Explicit EE Frame is more recent on the stack than the current // stack pointer itself? This lie is an early warning sign that the // context is bogus. Bail. LOG((LF_CORPROF, LL_INFO100, "**PROF: Target thread context is likely bogus. Returning CORPROF_E_STACKSNAPSHOT_UNSAFE.\n")); hr = CORPROF_E_STACKSNAPSHOT_UNSAFE; goto Cleanup; } } // If the profiler did not specify a seed context of its own, use the current one we // just produced. // // Failing to seed the walk can cause us to to "miss" functions on the stack. This is // because StackWalkFrames(), when doing an unseeded stackwalk, sets the // starting regdisplay's IP/SP to 0. This, in turn causes StackWalkFramesEx // to set cf.isFrameless = (pEEJM != NULL); (which is FALSE, since we have no // jit manager, since we have no IP). Once frameless is false, we look solely to // the Frame chain for our goodies, rather than looking at the code actually // being executed by the thread. The problem with the frame chain is that some // frames (e.g., GCFrame) don't point to any functions being executed. So // StackWalkFramesEx just skips such frames and moves to the next one. That // can cause a chunk of calls to be skipped. To prevent this from happening, we // "fake" a seed by just seeding the thread with its current context. This forces // StackWalkFramesEx() to look at the IP rather than just the frame chain. if (pctxSeed == NULL) { pctxSeed = &ctxCurrent; } #endif // !PLATFORM_SUPPORTS_SAFE_THREADSUSPEND } // Second, check "2) Target thread to walk is currently executing JITted / NGENd code" // To do this, we need to find the proper context to investigate. Start with // the seeded context, if available. If not, use the target thread's current context. if (pctxSeed != NULL) { BOOL fSeedIsManaged; // Short cut: If we're just using the current context as the seed, we may // already have determined whether it's in managed code. If so, just use that // result rather than calculating it again if ((pctxSeed == &ctxCurrent) && SUCCEEDED(hrCurrentContextIsManaged)) { fSeedIsManaged = (hrCurrentContextIsManaged == S_OK); } else { hr = IsContextInManagedCode(pctxSeed, hostCallPreference); if (FAILED(hr)) { hr = CORPROF_E_ASYNCHRONOUS_UNSAFE; goto Cleanup; } fSeedIsManaged = (hr == S_OK); } if (!fSeedIsManaged) { hr = CORPROF_E_STACKSNAPSHOT_UNMANAGED_CTX; goto Cleanup; } } #ifdef _DEBUG // // Sanity check: If we are doing a cross-thread walk and there is no seed context, then // we better not be in managed code, otw we do not have a Frame on the stack from which to start // walking and we may miss the leaf-most chain of managed calls due to the way StackWalkFrames // is implemented. However, there is an exception when the leaf-most EE frame of pThreadToSnapshot // is an InlinedCallFrame, which has an active call, implying pThreadToShanpshot is inside an // inlined P/Invoke. In this case, the InlinedCallFrame will be used to help start off our // stackwalk at the top of the stack. // if (pThreadToSnapshot != pCurrentThread) { #ifndef PLATFORM_SUPPORTS_SAFE_THREADSUSPEND hr = E_NOTIMPL; goto Cleanup; #else if (pctxSeed == NULL) { if (pThreadToSnapshot->GetSafelyRedirectableThreadContext(Thread::kDefaultChecks, &ctxCurrent, &rd)) { BOOL fFailedReaderLock = FALSE; BOOL fIsManagedCode = ExecutionManager::IsManagedCode(GetIP(&ctxCurrent), hostCallPreference, &fFailedReaderLock); if (!fFailedReaderLock) { // not in jitted or ngend code or inside an inlined P/Invoke (the leaf-most EE Frame is // an InlinedCallFrame with an active call) _ASSERTE(!fIsManagedCode || (InlinedCallFrame::FrameHasActiveCall(pThreadToSnapshot->GetFrame()))); } } } #endif // !PLATFORM_SUPPORTS_SAFE_THREADSUSPEND } #endif //_DEBUG // Third, verify the target thread is seeded or not in the midst of an unwind. if (pctxSeed == NULL) { ThreadExceptionState* pExState = pThreadToSnapshot->GetExceptionState(); // this tests to see if there is an exception in flight if (pExState->IsExceptionInProgress() && pExState->GetFlags()->UnwindHasStarted()) { EHClauseInfo *pCurrentEHClauseInfo = pThreadToSnapshot->GetExceptionState()->GetCurrentEHClauseInfo(); // if the exception code is telling us that we have entered a managed context then all is well if (!pCurrentEHClauseInfo->IsManagedCodeEntered()) { hr = CORPROF_E_STACKSNAPSHOT_UNMANAGED_CTX; goto Cleanup; } } } // Check if the exception state is consistent. See the comment for ThreadExceptionFlag for more information. if (pThreadToSnapshot->GetExceptionState()->HasThreadExceptionFlag(ThreadExceptionState::TEF_InconsistentExceptionState)) { hr = CORPROF_E_STACKSNAPSHOT_UNSAFE; goto Cleanup; } data.callback = callback; data.infoFlags = infoFlags; data.contextFlags = 0; data.clientData = clientData; #ifdef WIN64EXCEPTIONS data.sfParent.Clear(); #endif // workaround: The ForbidTypeLoad book keeping in the stackwalker is not robust against exceptions. // Unfortunately, it is hard to get it right in the stackwalker since it has to be exception // handling free (frame unwinding may never return). We restore the ForbidTypeLoad counter here // in case it got messed up by exception thrown during the stackwalk. INDEBUG(if (pCurrentThread) ulForbidTypeLoad = pCurrentThread->m_ulForbidTypeLoad;) { // An AV during a profiler stackwalk is an isolated event and shouldn't bring // down the runtime. Need to place the holder here, outside of ProfilerStackWalkFramesWrapper // since ProfilerStackWalkFramesWrapper uses __try, which doesn't like objects // with destructors. AVInRuntimeImplOkayHolder AVOkay; hr = DoStackSnapshotHelper( pThreadToSnapshot, &data, HANDLESKIPPEDFRAMES | FUNCTIONSONLY | NOTIFY_ON_U2M_TRANSITIONS | ((pThreadToSnapshot == pCurrentThread) ? 0 : ALLOW_ASYNC_STACK_WALK | THREAD_IS_SUSPENDED) | THREAD_EXECUTING_MANAGED_CODE | PROFILER_DO_STACK_SNAPSHOT | ALLOW_INVALID_OBJECTS, // stack walk logic should not look at objects - we could be in the middle of a gc. pctxSeed); } INDEBUG(if (pCurrentThread) pCurrentThread->m_ulForbidTypeLoad = ulForbidTypeLoad;) Cleanup: #if defined(PLATFORM_SUPPORTS_SAFE_THREADSUSPEND) if (fResumeThread) { pThreadToSnapshot->ResumeThread(); } #endif // PLATFORM_SUPPORTS_SAFE_THREADSUSPEND if (fResetSnapshotThreadExternalCount) { pThreadToSnapshot->DecExternalCountDANGEROUSProfilerOnly(); } return hr; #endif // !defined(FEATURE_HIJACK) } //--------------------------------------------------------------------------------------- // // Exception swallowing wrapper around the profiler stackwalk // // Arguments: // pThreadToSnapshot - Thread whose stack should be walked // pData - data for stack walker // flags - flags parameter to pass to StackWalkFramesEx, and StackFrameIterator // pctxSeed - Register context with which to seed the walk // // Return Value: // HRESULT indicating success or failure. // HRESULT ProfToEEInterfaceImpl::DoStackSnapshotHelper(Thread * pThreadToSnapshot, PROFILER_STACK_WALK_DATA * pData, unsigned flags, LPCONTEXT pctxSeed) { STATIC_CONTRACT_NOTHROW; // We want to catch and swallow AVs here. For example, if the profiler gives // us a bogus seed context (this happens), we could AV when inspecting memory pointed to // by the (bogus) EBP register. // // EX_TRY/EX_CATCH does a lot of extras that we do not need and that can go wrong for us. // E.g. It asserts in debug build for AVs in mscorwks or it synthetizes an object for the exception. // We use a plain PAL_TRY/PAL_EXCEPT since it is all we need. struct Param { HRESULT hr; Thread * pThreadToSnapshot; PROFILER_STACK_WALK_DATA * pData; unsigned flags; ProfToEEInterfaceImpl * pProfToEE; LPCONTEXT pctxSeed; BOOL fResetProfilerFilterContext; }; Param param; param.hr = E_UNEXPECTED; param.pThreadToSnapshot = pThreadToSnapshot; param.pData = pData; param.flags = flags; param.pProfToEE = this; param.pctxSeed = pctxSeed; param.fResetProfilerFilterContext = FALSE; PAL_TRY(Param *, pParam, ¶m) { if ((pParam->pData->infoFlags & COR_PRF_SNAPSHOT_X86_OPTIMIZED) != 0) { #ifndef _TARGET_X86_ // If check in the begining of DoStackSnapshot (to return E_INVALIDARG) should // make this unreachable _ASSERTE(!"COR_PRF_SNAPSHOT_X86_OPTIMIZED on non-X86 should be unreachable!"); #else // New, simple EBP walker pParam->hr = pParam->pProfToEE->ProfilerEbpWalker( pParam->pThreadToSnapshot, pParam->pctxSeed, pParam->pData->callback, pParam->pData->clientData); #endif // _TARGET_X86_ } else { // We're now fairly confident the stackwalk should be ok, so set // the context seed, if one was provided or cooked up. if (pParam->pctxSeed != NULL) { pParam->pThreadToSnapshot->SetProfilerFilterContext(pParam->pctxSeed); pParam->fResetProfilerFilterContext = TRUE; } // Whidbey-style walker, uses StackWalkFramesEx pParam->hr = pParam->pProfToEE->ProfilerStackWalkFramesWrapper( pParam->pThreadToSnapshot, pParam->pData, pParam->flags); } } PAL_EXCEPT(EXCEPTION_EXECUTE_HANDLER) { param.hr = E_UNEXPECTED; } PAL_ENDTRY; // Undo the context seeding & thread suspend we did (if any) // to ensure that the thread we walked stayed suspended if (param.fResetProfilerFilterContext) { pThreadToSnapshot->SetProfilerFilterContext(NULL); } return param.hr; } HRESULT ProfToEEInterfaceImpl::GetGenerationBounds(ULONG cObjectRanges, ULONG *pcObjectRanges, COR_PRF_GC_GENERATION_RANGE ranges[]) { CONTRACTL { // Yay! NOTHROW; // Yay! GC_NOTRIGGER; // Yay! MODE_ANY; // Yay! EE_THREAD_NOT_REQUIRED; // Yay! CANNOT_TAKE_LOCK; SO_NOT_MAINLINE; PRECONDITION(CheckPointer(pcObjectRanges)); PRECONDITION(cObjectRanges <= 0 || ranges != NULL); PRECONDITION(s_generationTableLock >= 0); } CONTRACTL_END; PROFILER_TO_CLR_ENTRYPOINT_SYNC_EX(kP2EEAllowableAfterAttach, (LF_CORPROF, LL_INFO1000, "**PROF: GetGenerationBounds.\n")); // Announce we are using the generation table now CounterHolder genTableLock(&s_generationTableLock); GenerationTable *generationTable = s_currentGenerationTable; if (generationTable == NULL) { return E_FAIL; } _ASSERTE(generationTable->magic == GENERATION_TABLE_MAGIC); GenerationDesc *genDescTable = generationTable->genDescTable; ULONG count = min(generationTable->count, cObjectRanges); for (ULONG i = 0; i < count; i++) { ranges[i].generation = (COR_PRF_GC_GENERATION)genDescTable[i].generation; ranges[i].rangeStart = (ObjectID)genDescTable[i].rangeStart; ranges[i].rangeLength = genDescTable[i].rangeEnd - genDescTable[i].rangeStart; ranges[i].rangeLengthReserved = genDescTable[i].rangeEndReserved - genDescTable[i].rangeStart; } *pcObjectRanges = generationTable->count; return S_OK; } HRESULT ProfToEEInterfaceImpl::GetNotifiedExceptionClauseInfo(COR_PRF_EX_CLAUSE_INFO * pinfo) { CONTRACTL { // Yay! NOTHROW; // Yay! GC_NOTRIGGER; // Yay! MODE_ANY; // Yay! CANNOT_TAKE_LOCK; SO_NOT_MAINLINE; PRECONDITION(CheckPointer(pinfo)); } CONTRACTL_END; PROFILER_TO_CLR_ENTRYPOINT_SYNC((LF_CORPROF, LL_INFO1000, "**PROF: GetNotifiedExceptionClauseInfo.\n")); HRESULT hr = S_OK; ThreadExceptionState* pExState = NULL; EHClauseInfo* pCurrentEHClauseInfo = NULL; // notification requires that we are on a managed thread with an exception in flight Thread *pThread = GetThread(); // If pThread is null, then the thread has never run managed code if (pThread == NULL) { hr = CORPROF_E_NOT_MANAGED_THREAD; goto NullReturn; } pExState = pThread->GetExceptionState(); if (!pExState->IsExceptionInProgress()) { // no exception is in flight -- successful failure hr = S_FALSE; goto NullReturn; } pCurrentEHClauseInfo = pExState->GetCurrentEHClauseInfo(); if (pCurrentEHClauseInfo->GetClauseType() == COR_PRF_CLAUSE_NONE) { // no exception is in flight -- successful failure hr = S_FALSE; goto NullReturn; } pinfo->clauseType = pCurrentEHClauseInfo->GetClauseType(); pinfo->programCounter = pCurrentEHClauseInfo->GetIPForEHClause(); pinfo->framePointer = pCurrentEHClauseInfo->GetFramePointerForEHClause(); pinfo->shadowStackPointer = 0; return S_OK; NullReturn: memset(pinfo, 0, sizeof(*pinfo)); return hr; } HRESULT ProfToEEInterfaceImpl::GetObjectGeneration(ObjectID objectId, COR_PRF_GC_GENERATION_RANGE *range) { CONTRACTL { // Yay! NOTHROW; // Yay! GC_NOTRIGGER; // Yay! MODE_ANY; // Yay! EE_THREAD_NOT_REQUIRED; // Yay! CANNOT_TAKE_LOCK; SO_NOT_MAINLINE; PRECONDITION(objectId != NULL); PRECONDITION(CheckPointer(range)); PRECONDITION(s_generationTableLock >= 0); } CONTRACTL_END; PROFILER_TO_CLR_ENTRYPOINT_SYNC_EX(kP2EEAllowableAfterAttach, (LF_CORPROF, LL_INFO1000, "**PROF: GetObjectGeneration 0x%p.\n", objectId)); BEGIN_GETTHREAD_ALLOWED; _ASSERTE((GetThread() == NULL) || (GetThread()->PreemptiveGCDisabled())); END_GETTHREAD_ALLOWED; // Announce we are using the generation table now CounterHolder genTableLock(&s_generationTableLock); GenerationTable *generationTable = s_currentGenerationTable; if (generationTable == NULL) { return E_FAIL; } _ASSERTE(generationTable->magic == GENERATION_TABLE_MAGIC); GenerationDesc *genDescTable = generationTable->genDescTable; ULONG count = generationTable->count; for (ULONG i = 0; i < count; i++) { if (genDescTable[i].rangeStart <= (BYTE *)objectId && (BYTE *)objectId < genDescTable[i].rangeEndReserved) { range->generation = (COR_PRF_GC_GENERATION)genDescTable[i].generation; range->rangeStart = (ObjectID)genDescTable[i].rangeStart; range->rangeLength = genDescTable[i].rangeEnd - genDescTable[i].rangeStart; range->rangeLengthReserved = genDescTable[i].rangeEndReserved - genDescTable[i].rangeStart; return S_OK; } } return E_FAIL; } HRESULT ProfToEEInterfaceImpl::GetReJITIDs( FunctionID functionId, // in ULONG cReJitIds, // in ULONG * pcReJitIds, // out ReJITID reJitIds[]) // out { CONTRACTL { // Yay! NOTHROW; // taking a lock causes a GC GC_TRIGGERS; // Yay! MODE_ANY; // The rejit tables use a lock CAN_TAKE_LOCK; SO_NOT_MAINLINE; PRECONDITION(CheckPointer(pcReJitIds, NULL_OK)); PRECONDITION(CheckPointer(reJitIds, NULL_OK)); } CONTRACTL_END; PROFILER_TO_CLR_ENTRYPOINT_SYNC((LF_CORPROF, LL_INFO1000, "**PROF: GetReJITIDs 0x%p.\n", functionId)); if (functionId == 0) { return E_INVALIDARG; } if ((cReJitIds == 0) || (pcReJitIds == NULL) || (reJitIds == NULL)) { return E_INVALIDARG; } MethodDesc * pMD = FunctionIdToMethodDesc(functionId); return ReJitManager::GetReJITIDs(pMD, cReJitIds, pcReJitIds, reJitIds); } HRESULT ProfToEEInterfaceImpl::RequestReJIT(ULONG cFunctions, // in ModuleID moduleIds[], // in mdMethodDef methodIds[]) // in { CONTRACTL { // Yay! NOTHROW; // When we suspend the runtime we drop into premptive mode GC_TRIGGERS; // Yay! MODE_ANY; // We need to suspend the runtime, this takes a lot of locks! CAN_TAKE_LOCK; SO_NOT_MAINLINE; PRECONDITION(CheckPointer(moduleIds, NULL_OK)); PRECONDITION(CheckPointer(methodIds, NULL_OK)); } CONTRACTL_END; PROFILER_TO_CLR_ENTRYPOINT_SYNC_EX( kP2EETriggers, (LF_CORPROF, LL_INFO1000, "**PROF: RequestReJIT.\n")); if (!g_profControlBlock.pProfInterface->IsCallback4Supported()) { return CORPROF_E_CALLBACK4_REQUIRED; } if (!CORProfilerEnableRejit()) { return CORPROF_E_REJIT_NOT_ENABLED; } // Request at least 1 method to reJIT! if ((cFunctions == 0) || (moduleIds == NULL) || (methodIds == NULL)) { return E_INVALIDARG; } // Remember the profiler is doing this, as that means we must never detach it! g_profControlBlock.pProfInterface->SetUnrevertiblyModifiedILFlag(); GCX_PREEMP(); return ReJitManager::RequestReJIT(cFunctions, moduleIds, methodIds); } HRESULT ProfToEEInterfaceImpl::RequestRevert(ULONG cFunctions, // in ModuleID moduleIds[], // in mdMethodDef methodIds[], // in HRESULT rgHrStatuses[]) // out { CONTRACTL { // Yay! NOTHROW; // The rejit manager requires a lock to iterate through methods to revert, and // taking the lock can drop us into preemptive mode. GC_TRIGGERS; // Yay! MODE_ANY; // The rejit manager requires a lock to iterate through methods to revert CAN_TAKE_LOCK; SO_NOT_MAINLINE; PRECONDITION(CheckPointer(moduleIds, NULL_OK)); PRECONDITION(CheckPointer(methodIds, NULL_OK)); PRECONDITION(CheckPointer(rgHrStatuses, NULL_OK)); } CONTRACTL_END; PROFILER_TO_CLR_ENTRYPOINT_SYNC_EX( kP2EETriggers, (LF_CORPROF, LL_INFO1000, "**PROF: RequestRevert.\n")); if (!CORProfilerEnableRejit()) { return CORPROF_E_REJIT_NOT_ENABLED; } // Request at least 1 method to revert! if ((cFunctions == 0) || (moduleIds == NULL) || (methodIds == NULL)) { return E_INVALIDARG; } // Remember the profiler is doing this, as that means we must never detach it! g_profControlBlock.pProfInterface->SetUnrevertiblyModifiedILFlag(); // Initialize the status array if (rgHrStatuses != NULL) { memset(rgHrStatuses, 0, sizeof(HRESULT) * cFunctions); _ASSERTE(S_OK == rgHrStatuses[0]); } GCX_PREEMP(); return ReJitManager::RequestRevert(cFunctions, moduleIds, methodIds, rgHrStatuses); } HRESULT ProfToEEInterfaceImpl::EnumJITedFunctions(ICorProfilerFunctionEnum ** ppEnum) { CONTRACTL { // Yay! NOTHROW; // Yay! GC_NOTRIGGER; // Yay! MODE_ANY; // If we're in preemptive mode we need to take a read lock to safely walk // the JIT data structures. CAN_TAKE_LOCK; SO_NOT_MAINLINE; PRECONDITION(CheckPointer(ppEnum, NULL_OK)); } CONTRACTL_END; PROFILER_TO_CLR_ENTRYPOINT_SYNC_EX(kP2EEAllowableAfterAttach, (LF_CORPROF, LL_INFO10, "**PROF: EnumJITedFunctions.\n")); if (ppEnum == NULL) { return E_INVALIDARG; } *ppEnum = NULL; NewHolder pJitEnum(new (nothrow) ProfilerFunctionEnum()); if (pJitEnum == NULL) { return E_OUTOFMEMORY; } if (!pJitEnum->Init()) { return E_OUTOFMEMORY; } // Ownership transferred to [out] param. Caller must Release() when done with this. *ppEnum = (ICorProfilerFunctionEnum *)pJitEnum.Extract(); return S_OK; } HRESULT ProfToEEInterfaceImpl::EnumJITedFunctions2(ICorProfilerFunctionEnum ** ppEnum) { CONTRACTL { // Yay! NOTHROW; // Gathering rejitids requires taking a lock and that lock might switch to // preemptimve mode... GC_TRIGGERS; // Yay! MODE_ANY; // If we're in preemptive mode we need to take a read lock to safely walk // the JIT data structures. // Gathering RejitIDs also takes a lock. CAN_TAKE_LOCK; SO_NOT_MAINLINE; PRECONDITION(CheckPointer(ppEnum, NULL_OK)); } CONTRACTL_END; PROFILER_TO_CLR_ENTRYPOINT_SYNC_EX( kP2EEAllowableAfterAttach | kP2EETriggers, (LF_CORPROF, LL_INFO10, "**PROF: EnumJITedFunctions.\n")); if (ppEnum == NULL) { return E_INVALIDARG; } *ppEnum = NULL; NewHolder pJitEnum(new (nothrow) ProfilerFunctionEnum()); if (pJitEnum == NULL) { return E_OUTOFMEMORY; } if (!pJitEnum->Init(TRUE /* fWithReJITIDs */)) { // If it fails, it's because of OOM. return E_OUTOFMEMORY; } // Ownership transferred to [out] param. Caller must Release() when done with this. *ppEnum = (ICorProfilerFunctionEnum *)pJitEnum.Extract(); return S_OK; } HRESULT ProfToEEInterfaceImpl::EnumModules(ICorProfilerModuleEnum ** ppEnum) { CONTRACTL { // Yay! NOTHROW; // This method populates the enumerator, which requires iterating over // AppDomains, which adds, then releases, a reference on each AppDomain iterated. // This causes locking, and can cause triggering if the AppDomain gets destroyed // as a result of the release. (See code:AppDomainIterator::Next and its call to // code:AppDomain::Release.) GC_TRIGGERS; // Yay! MODE_ANY; // (See comment above GC_TRIGGERS.) CAN_TAKE_LOCK; SO_NOT_MAINLINE; PRECONDITION(CheckPointer(ppEnum, NULL_OK)); } CONTRACTL_END; PROFILER_TO_CLR_ENTRYPOINT_SYNC_EX( kP2EEAllowableAfterAttach | kP2EETriggers, (LF_CORPROF, LL_INFO10, "**PROF: EnumModules.\n")); HRESULT hr; if (ppEnum == NULL) { return E_INVALIDARG; } *ppEnum = NULL; // ProfilerModuleEnum uese AppDomainIterator, which cannot be called while the current thead // is holding the ThreadStore lock. if (ThreadStore::HoldingThreadStore()) { return CORPROF_E_UNSUPPORTED_CALL_SEQUENCE; } NewHolder pModuleEnum(new (nothrow) ProfilerModuleEnum); if (pModuleEnum == NULL) { return E_OUTOFMEMORY; } hr = pModuleEnum->Init(); if (FAILED(hr)) { return hr; } // Ownership transferred to [out] param. Caller must Release() when done with this. *ppEnum = (ICorProfilerModuleEnum *) pModuleEnum.Extract(); return S_OK; } HRESULT ProfToEEInterfaceImpl::GetRuntimeInformation(USHORT * pClrInstanceId, COR_PRF_RUNTIME_TYPE * pRuntimeType, USHORT * pMajorVersion, USHORT * pMinorVersion, USHORT * pBuildNumber, USHORT * pQFEVersion, ULONG cchVersionString, ULONG * pcchVersionString, __out_ecount_part_opt(cchVersionString, *pcchVersionString) WCHAR szVersionString[]) { CONTRACTL { // Yay! NOTHROW; // Yay! GC_NOTRIGGER; // Yay! MODE_ANY; // Yay! EE_THREAD_NOT_REQUIRED; // Yay! CANNOT_TAKE_LOCK; SO_NOT_MAINLINE; } CONTRACTL_END; PROFILER_TO_CLR_ENTRYPOINT_SYNC_EX(kP2EEAllowableAfterAttach, (LF_CORPROF, LL_INFO1000, "**PROF: GetRuntimeInformation.\n")); if ((szVersionString != NULL) && (pcchVersionString == NULL)) { return E_INVALIDARG; } if (pcchVersionString != NULL) { HRESULT hr = GetCORVersionInternal(szVersionString, (DWORD)cchVersionString, (DWORD *)pcchVersionString); if (FAILED(hr)) return hr; } if (pClrInstanceId != NULL) *pClrInstanceId = static_cast(GetClrInstanceId()); if (pRuntimeType != NULL) { *pRuntimeType = COR_PRF_CORE_CLR; } if (pMajorVersion != NULL) *pMajorVersion = CLR_MAJOR_VERSION; if (pMinorVersion != NULL) *pMinorVersion = CLR_MINOR_VERSION; if (pBuildNumber != NULL) *pBuildNumber = CLR_BUILD_VERSION; if (pQFEVersion != NULL) *pQFEVersion = CLR_BUILD_VERSION_QFE; return S_OK; } HRESULT ProfToEEInterfaceImpl::RequestProfilerDetach(DWORD dwExpectedCompletionMilliseconds) { CONTRACTL { // Yay! NOTHROW; // Crst is used in ProfilingAPIDetach::RequestProfilerDetach so GC may be triggered GC_TRIGGERS; // Yay! MODE_ANY; // Yay! EE_THREAD_NOT_REQUIRED; // Crst is used in ProfilingAPIDetach::RequestProfilerDetach CAN_TAKE_LOCK; SO_NOT_MAINLINE; } CONTRACTL_END; PROFILER_TO_CLR_ENTRYPOINT_SYNC_EX( kP2EEAllowableAfterAttach | kP2EETriggers, (LF_CORPROF, LL_INFO1000, "**PROF: RequestProfilerDetach.\n")); #ifdef FEATURE_PROFAPI_ATTACH_DETACH return ProfilingAPIDetach::RequestProfilerDetach(dwExpectedCompletionMilliseconds); #else // FEATURE_PROFAPI_ATTACH_DETACH return E_NOTIMPL; #endif // FEATURE_PROFAPI_ATTACH_DETACH } typedef struct _COR_PRF_ELT_INFO_INTERNAL { // Point to a platform dependent structure ASM helper push on the stack void * platformSpecificHandle; // startAddress of COR_PRF_FUNCTION_ARGUMENT_RANGE structure needs to point // TO the argument value, not BE the argument value. So, when the argument // is this, we need to point TO this. Because of the calling sequence change // in ELT3, we need to reserve the pointer here instead of using one of our // stack variables. void * pThis; // Reserve space for output parameter COR_PRF_FRAME_INFO of // GetFunctionXXXX3Info functions COR_PRF_FRAME_INFO_INTERNAL frameInfo; } COR_PRF_ELT_INFO_INTERNAL; //--------------------------------------------------------------------------------------- // // ProfilingGetFunctionEnter3Info provides frame information and argument infomation of // the function ELT callback is inspecting. It is called either by the profiler or the // C helper function. // // Arguments: // * functionId - [in] FunctionId of the function being inspected by ELT3 // * eltInfo - [in] The opaque pointer FunctionEnter3WithInfo callback passed to the profiler // * pFrameInfo - [out] Pointer to COR_PRF_FRAME_INFO the profiler later can use to inspect // generic types // * pcbArgumentInfo - [in, out] Pointer to ULONG that specifies the size of structure // pointed by pArgumentInfo // * pArgumentInfo - [out] Pointer to COR_PRF_FUNCTION_ARGUMENT_INFO structure the profiler // must preserve enough space for the function it is inspecting // // Return Value: // HRESULT indicating success or failure. // HRESULT ProfilingGetFunctionEnter3Info(FunctionID functionId, // in COR_PRF_ELT_INFO eltInfo, // in COR_PRF_FRAME_INFO * pFrameInfo, // out ULONG * pcbArgumentInfo, // in, out COR_PRF_FUNCTION_ARGUMENT_INFO * pArgumentInfo) // out { CONTRACTL { // Yay! NOTHROW; // Yay! GC_NOTRIGGER; // Yay! MODE_ANY; // ProfileArgIterator::ProfileArgIterator may take locks CAN_TAKE_LOCK; SO_NOT_MAINLINE; } CONTRACTL_END; if ((functionId == NULL) || (eltInfo == NULL)) { return E_INVALIDARG; } COR_PRF_ELT_INFO_INTERNAL * pELTInfo = (COR_PRF_ELT_INFO_INTERNAL *)eltInfo; ProfileSetFunctionIDInPlatformSpecificHandle(pELTInfo->platformSpecificHandle, functionId); // The loader won't trigger a GC or throw for already loaded argument types. ENABLE_FORBID_GC_LOADER_USE_IN_THIS_SCOPE(); // // Find the method this is referring to, so we can get the signature // MethodDesc * pMethodDesc = FunctionIdToMethodDesc(functionId); MetaSig metaSig(pMethodDesc); NewHolder pProfileArgIterator; { // Can handle E_OUTOFMEMORY from ProfileArgIterator. FAULT_NOT_FATAL(); pProfileArgIterator = new (nothrow) ProfileArgIterator(&metaSig, pELTInfo->platformSpecificHandle); if (pProfileArgIterator == NULL) { return E_UNEXPECTED; } } if (CORProfilerFrameInfoEnabled()) { if (pFrameInfo == NULL) { return E_INVALIDARG; } // // Setup the COR_PRF_FRAME_INFO structure first. // COR_PRF_FRAME_INFO_INTERNAL * pCorPrfFrameInfo = &(pELTInfo->frameInfo); pCorPrfFrameInfo->size = sizeof(COR_PRF_FRAME_INFO_INTERNAL); pCorPrfFrameInfo->version = COR_PRF_FRAME_INFO_INTERNAL_CURRENT_VERSION; pCorPrfFrameInfo->funcID = functionId; pCorPrfFrameInfo->IP = ProfileGetIPFromPlatformSpecificHandle(pELTInfo->platformSpecificHandle); pCorPrfFrameInfo->extraArg = pProfileArgIterator->GetHiddenArgValue(); pCorPrfFrameInfo->thisArg = pProfileArgIterator->GetThis(); *pFrameInfo = (COR_PRF_FRAME_INFO)pCorPrfFrameInfo; } // // Do argument processing if desired. // if (CORProfilerFunctionArgsEnabled()) { if (pcbArgumentInfo == NULL) { return E_INVALIDARG; } if ((*pcbArgumentInfo != 0) && (pArgumentInfo == NULL)) { return E_INVALIDARG; } ULONG32 count = pProfileArgIterator->GetNumArgs(); if (metaSig.HasThis()) { count++; } ULONG ulArgInfoSize = sizeof(COR_PRF_FUNCTION_ARGUMENT_INFO) + (count * sizeof(COR_PRF_FUNCTION_ARGUMENT_RANGE)); if (*pcbArgumentInfo < ulArgInfoSize) { *pcbArgumentInfo = ulArgInfoSize; return HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER); } _ASSERTE(pArgumentInfo != NULL); pArgumentInfo->numRanges = count; pArgumentInfo->totalArgumentSize = 0; count = 0; if (metaSig.HasThis()) { pELTInfo->pThis = pProfileArgIterator->GetThis(); pArgumentInfo->ranges[count].startAddress = (UINT_PTR) (&(pELTInfo->pThis)); UINT length = sizeof(pELTInfo->pThis); pArgumentInfo->ranges[count].length = length; pArgumentInfo->totalArgumentSize += length; count++; } while (count < pArgumentInfo->numRanges) { pArgumentInfo->ranges[count].startAddress = (UINT_PTR)(pProfileArgIterator->GetNextArgAddr()); UINT length = pProfileArgIterator->GetArgSize(); pArgumentInfo->ranges[count].length = length; pArgumentInfo->totalArgumentSize += length; count++; } } return S_OK; } HRESULT ProfToEEInterfaceImpl::GetFunctionEnter3Info(FunctionID functionId, // in COR_PRF_ELT_INFO eltInfo, // in COR_PRF_FRAME_INFO * pFrameInfo, // out ULONG * pcbArgumentInfo, // in, out COR_PRF_FUNCTION_ARGUMENT_INFO * pArgumentInfo) // out { CONTRACTL { // Yay! NOTHROW; // Yay! GC_NOTRIGGER; // Yay! MODE_ANY; // ProfilingGetFunctionEnter3Info may take locks CAN_TAKE_LOCK; SO_NOT_MAINLINE; } CONTRACTL_END; PROFILER_TO_CLR_ENTRYPOINT_SYNC((LF_CORPROF, LL_INFO1000, "**PROF: GetFunctionEnter3Info.\n")); _ASSERTE(g_profControlBlock.pProfInterface->GetEnter3WithInfoHook() != NULL); if (!CORProfilerELT3SlowPathEnterEnabled()) { return CORPROF_E_INCONSISTENT_WITH_FLAGS; } return ProfilingGetFunctionEnter3Info(functionId, eltInfo, pFrameInfo, pcbArgumentInfo, pArgumentInfo); } //--------------------------------------------------------------------------------------- // // ProfilingGetFunctionLeave3Info provides frame information and return value infomation // of the function ELT callback is inspecting. It is called either by the profiler or the // C helper function. // // Arguments: // * functionId - [in] FunctionId of the function being inspected by ELT3 // * eltInfo - [in] The opaque pointer FunctionLeave3WithInfo callback passed to the profiler // * pFrameInfo - [out] Pointer to COR_PRF_FRAME_INFO the profiler later can use to inspect // generic types // * pRetvalRange - [out] Pointer to COR_PRF_FUNCTION_ARGUMENT_RANGE to store return value // // Return Value: // HRESULT indicating success or failure. // HRESULT ProfilingGetFunctionLeave3Info(FunctionID functionId, // in COR_PRF_ELT_INFO eltInfo, // in COR_PRF_FRAME_INFO * pFrameInfo, // out COR_PRF_FUNCTION_ARGUMENT_RANGE * pRetvalRange) // out { CONTRACTL { // Yay! NOTHROW; // Yay! GC_NOTRIGGER; // Yay! MODE_ANY; // ProfileArgIterator::ProfileArgIterator may take locks CAN_TAKE_LOCK; SO_NOT_MAINLINE; } CONTRACTL_END; if ((pFrameInfo == NULL) || (eltInfo == NULL)) { return E_INVALIDARG; } COR_PRF_ELT_INFO_INTERNAL * pELTInfo = (COR_PRF_ELT_INFO_INTERNAL *)eltInfo; ProfileSetFunctionIDInPlatformSpecificHandle(pELTInfo->platformSpecificHandle, functionId); // The loader won't trigger a GC or throw for already loaded argument types. ENABLE_FORBID_GC_LOADER_USE_IN_THIS_SCOPE(); // // Find the method this is referring to, so we can get the signature // MethodDesc * pMethodDesc = FunctionIdToMethodDesc(functionId); MetaSig metaSig(pMethodDesc); NewHolder pProfileArgIterator; { // Can handle E_OUTOFMEMORY from ProfileArgIterator. FAULT_NOT_FATAL(); pProfileArgIterator = new (nothrow) ProfileArgIterator(&metaSig, pELTInfo->platformSpecificHandle); if (pProfileArgIterator == NULL) { return E_UNEXPECTED; } } if (CORProfilerFrameInfoEnabled()) { if (pFrameInfo == NULL) { return E_INVALIDARG; } COR_PRF_FRAME_INFO_INTERNAL * pCorPrfFrameInfo = &(pELTInfo->frameInfo); // // Setup the COR_PRF_FRAME_INFO structure first. // pCorPrfFrameInfo->size = sizeof(COR_PRF_FRAME_INFO_INTERNAL); pCorPrfFrameInfo->version = COR_PRF_FRAME_INFO_INTERNAL_CURRENT_VERSION; pCorPrfFrameInfo->funcID = functionId; pCorPrfFrameInfo->IP = ProfileGetIPFromPlatformSpecificHandle(pELTInfo->platformSpecificHandle); // Upon entering Leave hook, the register assigned to store this pointer on function calls may // already be reused and is likely not to contain this pointer. pCorPrfFrameInfo->extraArg = NULL; pCorPrfFrameInfo->thisArg = NULL; *pFrameInfo = (COR_PRF_FRAME_INFO)pCorPrfFrameInfo; } // // Do argument processing if desired. // if (CORProfilerFunctionReturnValueEnabled()) { if (pRetvalRange == NULL) { return E_INVALIDARG; } if (!metaSig.IsReturnTypeVoid()) { pRetvalRange->length = metaSig.GetReturnTypeSize(); pRetvalRange->startAddress = (UINT_PTR)pProfileArgIterator->GetReturnBufferAddr(); } else { pRetvalRange->length = 0; pRetvalRange->startAddress = 0; } } return S_OK; } HRESULT ProfToEEInterfaceImpl::GetFunctionLeave3Info(FunctionID functionId, // in COR_PRF_ELT_INFO eltInfo, // in COR_PRF_FRAME_INFO * pFrameInfo, // out COR_PRF_FUNCTION_ARGUMENT_RANGE * pRetvalRange) // out { CONTRACTL { // Yay! NOTHROW; // Yay! GC_NOTRIGGER; // Yay! MODE_ANY; // ProfilingGetFunctionLeave3Info may take locks CAN_TAKE_LOCK; SO_NOT_MAINLINE; } CONTRACTL_END; PROFILER_TO_CLR_ENTRYPOINT_SYNC((LF_CORPROF, LL_INFO1000, "**PROF: GetFunctionLeave3Info.\n")); _ASSERTE(g_profControlBlock.pProfInterface->GetLeave3WithInfoHook() != NULL); if (!CORProfilerELT3SlowPathLeaveEnabled()) { return CORPROF_E_INCONSISTENT_WITH_FLAGS; } return ProfilingGetFunctionLeave3Info(functionId, eltInfo, pFrameInfo, pRetvalRange); } //--------------------------------------------------------------------------------------- // // ProfilingGetFunctionTailcall3Info provides frame information of the function ELT callback // is inspecting. It is called either by the profiler or the C helper function. // // Arguments: // * functionId - [in] FunctionId of the function being inspected by ELT3 // * eltInfo - [in] The opaque pointer FunctionTailcall3WithInfo callback passed to the // profiler // * pFrameInfo - [out] Pointer to COR_PRF_FRAME_INFO the profiler later can use to inspect // generic types // // Return Value: // HRESULT indicating success or failure. // HRESULT ProfilingGetFunctionTailcall3Info(FunctionID functionId, // in COR_PRF_ELT_INFO eltInfo, // in COR_PRF_FRAME_INFO * pFrameInfo) // out { CONTRACTL { // Yay! NOTHROW; // Yay! GC_NOTRIGGER; // Yay! MODE_ANY; // ProfileArgIterator::ProfileArgIterator may take locks CAN_TAKE_LOCK; SO_NOT_MAINLINE; } CONTRACTL_END; if ((functionId == NULL) || (eltInfo == NULL) || (pFrameInfo == NULL)) { return E_INVALIDARG; } COR_PRF_ELT_INFO_INTERNAL * pELTInfo = (COR_PRF_ELT_INFO_INTERNAL *)eltInfo; ProfileSetFunctionIDInPlatformSpecificHandle(pELTInfo->platformSpecificHandle, functionId); // The loader won't trigger a GC or throw for already loaded argument types. ENABLE_FORBID_GC_LOADER_USE_IN_THIS_SCOPE(); // // Find the method this is referring to, so we can get the signature // MethodDesc * pMethodDesc = FunctionIdToMethodDesc(functionId); MetaSig metaSig(pMethodDesc); NewHolder pProfileArgIterator; { // Can handle E_OUTOFMEMORY from ProfileArgIterator. FAULT_NOT_FATAL(); pProfileArgIterator = new (nothrow) ProfileArgIterator(&metaSig, pELTInfo->platformSpecificHandle); if (pProfileArgIterator == NULL) { return E_UNEXPECTED; } } COR_PRF_FRAME_INFO_INTERNAL * pCorPrfFrameInfo = &(pELTInfo->frameInfo); // // Setup the COR_PRF_FRAME_INFO structure first. // pCorPrfFrameInfo->size = sizeof(COR_PRF_FRAME_INFO_INTERNAL); pCorPrfFrameInfo->version = COR_PRF_FRAME_INFO_INTERNAL_CURRENT_VERSION; pCorPrfFrameInfo->funcID = functionId; pCorPrfFrameInfo->IP = ProfileGetIPFromPlatformSpecificHandle(pELTInfo->platformSpecificHandle); // Tailcall is designed to report the caller, not the callee. But the taillcall hook is invoked // with registers containing parameters passed to the callee before calling into the callee. // This pointer we get here is for the callee. Because of the constraints imposed on tailcall // optimization, this pointer passed to the callee accidentally happens to be the same this pointer // passed to the caller. // // It is a fragile coincidence we should not depend on because JIT is free to change the // implementation details in the future. pCorPrfFrameInfo->extraArg = NULL; pCorPrfFrameInfo->thisArg = NULL; *pFrameInfo = (COR_PRF_FRAME_INFO)pCorPrfFrameInfo; return S_OK; } HRESULT ProfToEEInterfaceImpl::GetFunctionTailcall3Info(FunctionID functionId, // in COR_PRF_ELT_INFO eltInfo, // in COR_PRF_FRAME_INFO * pFrameInfo) // out { CONTRACTL { // Yay! NOTHROW; // Yay! GC_NOTRIGGER; // Yay! MODE_ANY; // ProfilingGetFunctionTailcall3Info may take locks CAN_TAKE_LOCK; SO_NOT_MAINLINE; } CONTRACTL_END; PROFILER_TO_CLR_ENTRYPOINT_SYNC((LF_CORPROF, LL_INFO1000, "**PROF: GetFunctionTailcall3Info.\n")); _ASSERTE(g_profControlBlock.pProfInterface->GetTailcall3WithInfoHook() != NULL); if (!CORProfilerELT3SlowPathTailcallEnabled()) { return CORPROF_E_INCONSISTENT_WITH_FLAGS; } return ProfilingGetFunctionTailcall3Info(functionId, eltInfo, pFrameInfo); } HRESULT ProfToEEInterfaceImpl::EnumThreads( /* out */ ICorProfilerThreadEnum ** ppEnum) { CONTRACTL { // Yay! NOTHROW; // Yay! GC_NOTRIGGER; // Yay! MODE_ANY; // Need to acquire the thread store lock CAN_TAKE_LOCK; SO_NOT_MAINLINE; PRECONDITION(CheckPointer(ppEnum, NULL_OK)); } CONTRACTL_END; PROFILER_TO_CLR_ENTRYPOINT_SYNC_EX( kP2EEAllowableAfterAttach, (LF_CORPROF, LL_INFO10, "**PROF: EnumThreads.\n")); HRESULT hr; if (ppEnum == NULL) { return E_INVALIDARG; } *ppEnum = NULL; NewHolder pThreadEnum(new (nothrow) ProfilerThreadEnum); if (pThreadEnum == NULL) { return E_OUTOFMEMORY; } hr = pThreadEnum->Init(); if (FAILED(hr)) { return hr; } // Ownership transferred to [out] param. Caller must Release() when done with this. *ppEnum = (ICorProfilerThreadEnum *) pThreadEnum.Extract(); return S_OK; } // This function needs to be called on any thread before making any ICorProfilerInfo* calls and must be // made before any thread is suspended by this profiler. // As you might have already figured out, this is done to avoid deadlocks situation when // the suspended thread holds on the loader lock / heap lock while the current thread is trying to obtain // the same lock. HRESULT ProfToEEInterfaceImpl::InitializeCurrentThread() { CONTRACTL { // Yay! NOTHROW; // Yay! GC_NOTRIGGER; // Yay! MODE_ANY; // May take thread store lock and OS APIs may also take locks CAN_TAKE_LOCK; SO_NOT_MAINLINE; } CONTRACTL_END; PROFILER_TO_CLR_ENTRYPOINT_SYNC_EX( kP2EEAllowableAfterAttach, (LF_CORPROF, LL_INFO10, "**PROF: InitializeCurrentThread.\n")); HRESULT hr = S_OK; EX_TRY { CExecutionEngine::SetupTLSForThread(GetThread()); } EX_CATCH_HRESULT(hr); if (FAILED(hr)) return hr; return S_OK; } struct InternalProfilerModuleEnum : public ProfilerModuleEnum { CDynArray *GetRawElementsArray() { return &m_elements; } }; HRESULT ProfToEEInterfaceImpl::EnumNgenModuleMethodsInliningThisMethod( ModuleID inlinersModuleId, ModuleID inlineeModuleId, mdMethodDef inlineeMethodId, BOOL *incompleteData, ICorProfilerMethodEnum** ppEnum) { CONTRACTL { NOTHROW; GC_TRIGGERS; MODE_ANY; SO_NOT_MAINLINE; CAN_TAKE_LOCK; PRECONDITION(CheckPointer(ppEnum)); } CONTRACTL_END; PROFILER_TO_CLR_ENTRYPOINT_SYNC_EX(kP2EETriggers, (LF_CORPROF, LL_INFO1000, "**PROF: EnumNgenModuleMethodsInliningThisMethod.\n")); if (ppEnum == NULL) { return E_INVALIDARG; } *ppEnum = NULL; HRESULT hr = S_OK; Module *inlineeOwnerModule = reinterpret_cast(inlineeModuleId); if (inlineeOwnerModule == NULL) { return E_INVALIDARG; } if (inlineeOwnerModule->IsBeingUnloaded()) { return CORPROF_E_DATAINCOMPLETE; } Module *inlinersModule = reinterpret_cast(inlinersModuleId); if (inlinersModule == NULL) { return E_INVALIDARG; } if(inlinersModule->IsBeingUnloaded()) { return CORPROF_E_DATAINCOMPLETE; } if (!inlinersModule->HasInlineTrackingMap()) { return CORPROF_E_DATAINCOMPLETE; } CDynArray results; const COUNT_T staticBufferSize = 10; MethodInModule staticBuffer[staticBufferSize]; NewArrayHolder dynamicBuffer; MethodInModule *methodsBuffer = staticBuffer; EX_TRY { // Trying to use static buffer COUNT_T methodsAvailable = inlinersModule->GetInliners(inlineeOwnerModule, inlineeMethodId, staticBufferSize, staticBuffer, incompleteData); // If static buffer is not enough, allocate an array. if (methodsAvailable > staticBufferSize) { DWORD dynamicBufferSize = methodsAvailable; dynamicBuffer = methodsBuffer = new MethodInModule[dynamicBufferSize]; methodsAvailable = inlinersModule->GetInliners(inlineeOwnerModule, inlineeMethodId, dynamicBufferSize, dynamicBuffer, incompleteData); if (methodsAvailable > dynamicBufferSize) { _ASSERTE(!"Ngen image inlining info changed, this shouldn't be possible."); methodsAvailable = dynamicBufferSize; } } //Go through all inliners found in the inlinersModule and prepare them to export via results. results.AllocateBlockThrowing(methodsAvailable); for (COUNT_T j = 0; j < methodsAvailable; j++) { COR_PRF_METHOD *newPrfMethod = &results[j]; newPrfMethod->moduleId = reinterpret_cast(methodsBuffer[j].m_module); newPrfMethod->methodId = methodsBuffer[j].m_methodDef; } *ppEnum = new ProfilerMethodEnum(&results); } EX_CATCH_HRESULT(hr); return hr; } HRESULT ProfToEEInterfaceImpl::GetInMemorySymbolsLength( ModuleID moduleId, DWORD* pCountSymbolBytes) { CONTRACTL { NOTHROW; GC_NOTRIGGER; MODE_ANY; SO_NOT_MAINLINE; } CONTRACTL_END; PROFILER_TO_CLR_ENTRYPOINT_SYNC_EX( kP2EEAllowableAfterAttach, (LF_CORPROF, LL_INFO10, "**PROF: GetInMemorySymbolsLength.\n")); HRESULT hr = S_OK; if (pCountSymbolBytes == NULL) { return E_INVALIDARG; } *pCountSymbolBytes = 0; Module* pModule = reinterpret_cast< Module* >(moduleId); if (pModule == NULL) { return E_INVALIDARG; } if (pModule->IsBeingUnloaded()) { return CORPROF_E_DATAINCOMPLETE; } //This method would work fine on reflection.emit, but there would be no way to know //if some other thread was changing the size of the symbols before this method returned. //Adding events or locks to detect/prevent changes would make the scenario workable if (pModule->IsReflection()) { return COR_PRF_MODULE_DYNAMIC; } CGrowableStream* pStream = pModule->GetInMemorySymbolStream(); if (pStream == NULL) { return S_OK; } STATSTG SizeData = { 0 }; hr = pStream->Stat(&SizeData, STATFLAG_NONAME); if (FAILED(hr)) { return hr; } if (SizeData.cbSize.u.HighPart > 0) { return COR_E_OVERFLOW; } *pCountSymbolBytes = SizeData.cbSize.u.LowPart; return S_OK; } HRESULT ProfToEEInterfaceImpl::ReadInMemorySymbols( ModuleID moduleId, DWORD symbolsReadOffset, BYTE* pSymbolBytes, DWORD countSymbolBytes, DWORD* pCountSymbolBytesRead) { CONTRACTL { NOTHROW; GC_NOTRIGGER; MODE_ANY; SO_NOT_MAINLINE; } CONTRACTL_END; PROFILER_TO_CLR_ENTRYPOINT_SYNC_EX( kP2EEAllowableAfterAttach, (LF_CORPROF, LL_INFO10, "**PROF: ReadInMemorySymbols.\n")); HRESULT hr = S_OK; if (pSymbolBytes == NULL) { return E_INVALIDARG; } if (pCountSymbolBytesRead == NULL) { return E_INVALIDARG; } *pCountSymbolBytesRead = 0; Module* pModule = reinterpret_cast< Module* >(moduleId); if (pModule == NULL) { return E_INVALIDARG; } if (pModule->IsBeingUnloaded()) { return CORPROF_E_DATAINCOMPLETE; } //This method would work fine on reflection.emit, but there would be no way to know //if some other thread was changing the size of the symbols before this method returned. //Adding events or locks to detect/prevent changes would make the scenario workable if (pModule->IsReflection()) { return COR_PRF_MODULE_DYNAMIC; } CGrowableStream* pStream = pModule->GetInMemorySymbolStream(); if (pStream == NULL) { return E_INVALIDARG; } STATSTG SizeData = { 0 }; hr = pStream->Stat(&SizeData, STATFLAG_NONAME); if (FAILED(hr)) { return hr; } if (SizeData.cbSize.u.HighPart > 0) { return COR_E_OVERFLOW; } DWORD streamSize = SizeData.cbSize.u.LowPart; if (symbolsReadOffset >= streamSize) { return E_INVALIDARG; } *pCountSymbolBytesRead = min(streamSize - symbolsReadOffset, countSymbolBytes); memcpy_s(pSymbolBytes, countSymbolBytes, ((BYTE*)pStream->GetRawBuffer().StartAddress()) + symbolsReadOffset, *pCountSymbolBytesRead); return S_OK; } HRESULT ProfToEEInterfaceImpl::ApplyMetaData( ModuleID moduleId) { CONTRACTL { NOTHROW; GC_NOTRIGGER; MODE_ANY; SO_NOT_MAINLINE; } CONTRACTL_END; PROFILER_TO_CLR_ENTRYPOINT_SYNC_EX(kP2EEAllowableAfterAttach, (LF_CORPROF, LL_INFO1000, "**PROF: ApplyMetaData.\n")); if (moduleId == NULL) { return E_INVALIDARG; } HRESULT hr = S_OK; EX_TRY { Module *pModule = (Module *)moduleId; _ASSERTE(pModule != NULL); if (pModule->IsBeingUnloaded()) { hr = CORPROF_E_DATAINCOMPLETE; } else { pModule->ApplyMetaData(); } } EX_CATCH_HRESULT(hr); return hr; } //--------------------------------------------------------------------------------------- // // Simple wrapper around EEToProfInterfaceImpl::ManagedToUnmanagedTransition. This // can be called by C++ code and directly by generated stubs. // // Arguments: // pMD - MethodDesc for the managed function involved in the transition // reason - Passed on to profiler to indicate why the transition is occurring // void __stdcall ProfilerManagedToUnmanagedTransitionMD(MethodDesc *pMD, COR_PRF_TRANSITION_REASON reason) { CONTRACTL { NOTHROW; GC_TRIGGERS; MODE_PREEMPTIVE; SO_TOLERANT; } CONTRACTL_END; // This function is called within the runtime, not directly from managed code. // Also, the only case MD is NULL is the calli pinvoke case, and we still // want to notify the profiler in that case. // Do not notify the profiler about QCalls if (pMD == NULL || !pMD->IsQCall()) { BEGIN_PIN_PROFILER(CORProfilerPresent()); g_profControlBlock.pProfInterface->ManagedToUnmanagedTransition(MethodDescToFunctionID(pMD), reason); END_PIN_PROFILER(); } } //--------------------------------------------------------------------------------------- // // Simple wrapper around EEToProfInterfaceImpl::UnmanagedToManagedTransition. This // can be called by C++ code and directly by generated stubs. // // Arguments: // pMD - MethodDesc for the managed function involved in the transition // reason - Passed on to profiler to indicate why the transition is occurring // void __stdcall ProfilerUnmanagedToManagedTransitionMD(MethodDesc *pMD, COR_PRF_TRANSITION_REASON reason) { CONTRACTL { NOTHROW; GC_TRIGGERS; MODE_PREEMPTIVE; SO_TOLERANT; } CONTRACTL_END; // This function is called within the runtime, not directly from managed code. // Also, the only case MD is NULL is the calli pinvoke case, and we still // want to notify the profiler in that case. // Do not notify the profiler about QCalls if (pMD == NULL || !pMD->IsQCall()) { BEGIN_PIN_PROFILER(CORProfilerPresent()); g_profControlBlock.pProfInterface->UnmanagedToManagedTransition(MethodDescToFunctionID(pMD), reason); END_PIN_PROFILER(); } } #endif // PROFILING_SUPPORTED FCIMPL0(FC_BOOL_RET, ProfilingFCallHelper::FC_TrackRemoting) { FCALL_CONTRACT; #ifdef PROFILING_SUPPORTED FC_RETURN_BOOL(CORProfilerTrackRemoting()); #else // !PROFILING_SUPPORTED FC_RETURN_BOOL(FALSE); #endif // !PROFILING_SUPPORTED } FCIMPLEND FCIMPL0(FC_BOOL_RET, ProfilingFCallHelper::FC_TrackRemotingCookie) { FCALL_CONTRACT; #ifdef PROFILING_SUPPORTED FC_RETURN_BOOL(CORProfilerTrackRemotingCookie()); #else // !PROFILING_SUPPORTED FC_RETURN_BOOL(FALSE); #endif // !PROFILING_SUPPORTED } FCIMPLEND FCIMPL0(FC_BOOL_RET, ProfilingFCallHelper::FC_TrackRemotingAsync) { FCALL_CONTRACT; #ifdef PROFILING_SUPPORTED FC_RETURN_BOOL(CORProfilerTrackRemotingAsync()); #else // !PROFILING_SUPPORTED FC_RETURN_BOOL(FALSE); #endif // !PROFILING_SUPPORTED } FCIMPLEND FCIMPL2(void, ProfilingFCallHelper::FC_RemotingClientSendingMessage, GUID *pId, CLR_BOOL fIsAsync) { FCALL_CONTRACT; #ifdef PROFILING_SUPPORTED // Need to erect a GC frame so that GCs can occur without a problem // within the profiler code. // Note that we don't need to worry about pId moving around since // it is a value class declared on the stack and so GC doesn't // know about it. _ASSERTE (!GCHeapUtilities::GetGCHeap()->IsHeapPointer(pId)); // should be on the stack, not in the heap HELPER_METHOD_FRAME_BEGIN_NOPOLL(); { BEGIN_PIN_PROFILER(CORProfilerPresent()); GCX_PREEMP(); if (CORProfilerTrackRemotingCookie()) { g_profControlBlock.pProfInterface->GetGUID(pId); _ASSERTE(pId->Data1); g_profControlBlock.pProfInterface->RemotingClientSendingMessage(pId, fIsAsync); } else { g_profControlBlock.pProfInterface->RemotingClientSendingMessage(NULL, fIsAsync); } END_PIN_PROFILER(); } HELPER_METHOD_FRAME_END_POLL(); #endif // PROFILING_SUPPORTED } FCIMPLEND FCIMPL2_VI(void, ProfilingFCallHelper::FC_RemotingClientReceivingReply, GUID id, CLR_BOOL fIsAsync) { FCALL_CONTRACT; #ifdef PROFILING_SUPPORTED // Need to erect a GC frame so that GCs can occur without a problem // within the profiler code. // Note that we don't need to worry about pId moving around since // it is a value class declared on the stack and so GC doesn't // know about it. HELPER_METHOD_FRAME_BEGIN_NOPOLL(); { BEGIN_PIN_PROFILER(CORProfilerPresent()); GCX_PREEMP(); if (CORProfilerTrackRemotingCookie()) { g_profControlBlock.pProfInterface->RemotingClientReceivingReply(&id, fIsAsync); } else { g_profControlBlock.pProfInterface->RemotingClientReceivingReply(NULL, fIsAsync); } END_PIN_PROFILER(); } HELPER_METHOD_FRAME_END_POLL(); #endif // PROFILING_SUPPORTED } FCIMPLEND FCIMPL2_VI(void, ProfilingFCallHelper::FC_RemotingServerReceivingMessage, GUID id, CLR_BOOL fIsAsync) { FCALL_CONTRACT; #ifdef PROFILING_SUPPORTED // Need to erect a GC frame so that GCs can occur without a problem // within the profiler code. // Note that we don't need to worry about pId moving around since // it is a value class declared on the stack and so GC doesn't // know about it. HELPER_METHOD_FRAME_BEGIN_NOPOLL(); { BEGIN_PIN_PROFILER(CORProfilerPresent()); GCX_PREEMP(); if (CORProfilerTrackRemotingCookie()) { g_profControlBlock.pProfInterface->RemotingServerReceivingMessage(&id, fIsAsync); } else { g_profControlBlock.pProfInterface->RemotingServerReceivingMessage(NULL, fIsAsync); } END_PIN_PROFILER(); } HELPER_METHOD_FRAME_END_POLL(); #endif // PROFILING_SUPPORTED } FCIMPLEND FCIMPL2(void, ProfilingFCallHelper::FC_RemotingServerSendingReply, GUID *pId, CLR_BOOL fIsAsync) { FCALL_CONTRACT; #ifdef PROFILING_SUPPORTED // Need to erect a GC frame so that GCs can occur without a problem // within the profiler code. // Note that we don't need to worry about pId moving around since // it is a value class declared on the stack and so GC doesn't // know about it. HELPER_METHOD_FRAME_BEGIN_NOPOLL(); { BEGIN_PIN_PROFILER(CORProfilerPresent()); GCX_PREEMP(); if (CORProfilerTrackRemotingCookie()) { g_profControlBlock.pProfInterface->GetGUID(pId); _ASSERTE(pId->Data1); g_profControlBlock.pProfInterface->RemotingServerSendingReply(pId, fIsAsync); } else { g_profControlBlock.pProfInterface->RemotingServerSendingReply(NULL, fIsAsync); } END_PIN_PROFILER(); } HELPER_METHOD_FRAME_END_POLL(); #endif // PROFILING_SUPPORTED } FCIMPLEND //******************************************************************************************* // These do a lot of work for us, setting up Frames, gathering arg info and resolving generics. //******************************************************************************************* HCIMPL2(EXTERN_C void, ProfileEnter, UINT_PTR clientData, void * platformSpecificHandle) { FCALL_CONTRACT; #ifdef PROFILING_SUPPORTED #ifdef PROF_TEST_ONLY_FORCE_ELT // If this test-only flag is set, it's possible we might not have a profiler // attached, or might not have any of the hooks set. See // code:ProfControlBlock#TestOnlyELT if (g_profControlBlock.fTestOnlyForceEnterLeave) { if ((g_profControlBlock.pProfInterface.Load() == NULL) || ( (g_profControlBlock.pProfInterface->GetEnterHook() == NULL) && (g_profControlBlock.pProfInterface->GetEnter2Hook() == NULL) && (g_profControlBlock.pProfInterface->GetEnter3Hook() == NULL) && (g_profControlBlock.pProfInterface->GetEnter3WithInfoHook() == NULL) ) ) { return; } } #endif // PROF_TEST_ONLY_FORCE_ELT // ELT3 Fast-Path hooks should be NULL when ELT intermediary is used. _ASSERTE(g_profControlBlock.pProfInterface->GetEnter3Hook() == NULL); _ASSERTE(GetThread()->PreemptiveGCDisabled()); _ASSERTE(platformSpecificHandle != NULL); // Set up a frame HELPER_METHOD_FRAME_BEGIN_ATTRIB_NOPOLL(Frame::FRAME_ATTR_CAPTURE_DEPTH_2); // Our contract is FCALL_CONTRACT, which is considered triggers if you set up a // frame, like we're about to do. SetCallbackStateFlagsHolder csf( COR_PRF_CALLBACKSTATE_INCALLBACK | COR_PRF_CALLBACKSTATE_IN_TRIGGERS_SCOPE); COR_PRF_ELT_INFO_INTERNAL eltInfo; eltInfo.platformSpecificHandle = platformSpecificHandle; // // CLR v4 Slow-Path ELT // if (g_profControlBlock.pProfInterface->GetEnter3WithInfoHook() != NULL) { FunctionIDOrClientID functionIDOrClientID; functionIDOrClientID.clientID = clientData; REMOVE_STACK_GUARD_FOR_PROFILER_CALL; g_profControlBlock.pProfInterface->GetEnter3WithInfoHook()( functionIDOrClientID, (COR_PRF_ELT_INFO)&eltInfo); goto LExit; } if (g_profControlBlock.pProfInterface->GetEnter2Hook() != NULL) { // We have run out of heap memory, so the content of the mapping table becomes stale. // All Whidbey ETL hooks must be turned off. if (!g_profControlBlock.pProfInterface->IsClientIDToFunctionIDMappingEnabled()) { goto LExit; } // If ELT2 is in use, FunctionID will be returned to the JIT to be embedded into the ELT3 probes // instead of using clientID because the profiler may map several functionIDs to a clientID to // do things like code coverage analysis. FunctionID to clientID has the one-on-one relationship, // while the reverse may not have this one-on-one mapping. Therefore, FunctionID is used as the // key to retrieve the corresponding clientID from the internal FunctionID hash table. FunctionID functionId = clientData; _ASSERTE(functionId != NULL); clientData = g_profControlBlock.pProfInterface->LookupClientIDFromCache(functionId); // // Whidbey Fast-Path ELT // if (CORProfilerELT2FastPathEnterEnabled()) { REMOVE_STACK_GUARD_FOR_PROFILER_CALL; g_profControlBlock.pProfInterface->GetEnter2Hook()( functionId, clientData, NULL, NULL); goto LExit; } // // Whidbey Slow-Path ELT // ProfileSetFunctionIDInPlatformSpecificHandle(platformSpecificHandle, functionId); COR_PRF_FRAME_INFO frameInfo = NULL; COR_PRF_FUNCTION_ARGUMENT_INFO * pArgumentInfo = NULL; ULONG ulArgInfoSize = 0; if (CORProfilerFunctionArgsEnabled()) { // The loader won't trigger a GC or throw for already loaded argument types. ENABLE_FORBID_GC_LOADER_USE_IN_THIS_SCOPE(); // // Find the method this is referring to, so we can get the signature // MethodDesc * pMethodDesc = FunctionIdToMethodDesc(functionId); MetaSig metaSig(pMethodDesc); NewHolder pProfileArgIterator; { // Can handle E_OUTOFMEMORY from ProfileArgIterator. FAULT_NOT_FATAL(); pProfileArgIterator = new (nothrow) ProfileArgIterator(&metaSig, platformSpecificHandle); if (pProfileArgIterator == NULL) { goto LExit; } } ULONG32 count = pProfileArgIterator->GetNumArgs(); if (metaSig.HasThis()) { count++; } ulArgInfoSize = sizeof(COR_PRF_FUNCTION_ARGUMENT_INFO) + count * sizeof(COR_PRF_FUNCTION_ARGUMENT_RANGE); pArgumentInfo = (COR_PRF_FUNCTION_ARGUMENT_INFO *)_alloca(ulArgInfoSize); } HRESULT hr = ProfilingGetFunctionEnter3Info(functionId, (COR_PRF_ELT_INFO)&eltInfo, &frameInfo, &ulArgInfoSize, pArgumentInfo); _ASSERTE(hr == S_OK); REMOVE_STACK_GUARD_FOR_PROFILER_CALL; g_profControlBlock.pProfInterface->GetEnter2Hook()(functionId, clientData, frameInfo, pArgumentInfo); goto LExit; } // We will not be here unless the jit'd or ngen'd function we're about to enter // was backpatched with this wrapper around the profiler's hook, and that // wouldn't have happened unless the profiler supplied us with a hook // in the first place. (Note that SetEnterLeaveFunctionHooks* will return // an error unless it's called in the profiler's Initialize(), so a profiler can't change // its mind about where the hooks are.) _ASSERTE(g_profControlBlock.pProfInterface->GetEnterHook() != NULL); // Note that we cannot assert CORProfilerTrackEnterLeave() (i.e., profiler flag // COR_PRF_MONITOR_ENTERLEAVE), because the profiler may decide whether // to enable the jitter to add enter/leave callouts independently of whether // the profiler actually has enter/leave hooks. (If the profiler has no such hooks, // the callouts quickly return and do nothing.) // // Everett ELT // { REMOVE_STACK_GUARD_FOR_PROFILER_CALL; g_profControlBlock.pProfInterface->GetEnterHook()((FunctionID)clientData); } LExit: ; HELPER_METHOD_FRAME_END(); // Un-link the frame #endif // PROFILING_SUPPORTED } HCIMPLEND HCIMPL2(EXTERN_C void, ProfileLeave, UINT_PTR clientData, void * platformSpecificHandle) { FCALL_CONTRACT; FC_GC_POLL_NOT_NEEDED(); // we pulse GC mode, so we are doing a poll #ifdef PROFILING_SUPPORTED #ifdef PROF_TEST_ONLY_FORCE_ELT // If this test-only flag is set, it's possible we might not have a profiler // attached, or might not have any of the hooks set. See // code:ProfControlBlock#TestOnlyELT if (g_profControlBlock.fTestOnlyForceEnterLeave) { if ((g_profControlBlock.pProfInterface.Load() == NULL) || ( (g_profControlBlock.pProfInterface->GetLeaveHook() == NULL) && (g_profControlBlock.pProfInterface->GetLeave2Hook() == NULL) && (g_profControlBlock.pProfInterface->GetLeave3Hook() == NULL) && (g_profControlBlock.pProfInterface->GetLeave3WithInfoHook() == NULL) ) ) { return; } } #endif // PROF_TEST_ONLY_FORCE_ELT // ELT3 Fast-Path hooks should be NULL when ELT intermediary is used. _ASSERTE(g_profControlBlock.pProfInterface->GetLeave3Hook() == NULL); _ASSERTE(GetThread()->PreemptiveGCDisabled()); _ASSERTE(platformSpecificHandle != NULL); // Set up a frame HELPER_METHOD_FRAME_BEGIN_ATTRIB_NOPOLL(Frame::FRAME_ATTR_CAPTURE_DEPTH_2); // Our contract is FCALL_CONTRACT, which is considered triggers if you set up a // frame, like we're about to do. SetCallbackStateFlagsHolder csf( COR_PRF_CALLBACKSTATE_INCALLBACK | COR_PRF_CALLBACKSTATE_IN_TRIGGERS_SCOPE); COR_PRF_ELT_INFO_INTERNAL eltInfo; eltInfo.platformSpecificHandle = platformSpecificHandle; // // CLR v4 Slow-Path ELT // if (g_profControlBlock.pProfInterface->GetLeave3WithInfoHook() != NULL) { FunctionIDOrClientID functionIDOrClientID; functionIDOrClientID.clientID = clientData; REMOVE_STACK_GUARD_FOR_PROFILER_CALL; g_profControlBlock.pProfInterface->GetLeave3WithInfoHook()( functionIDOrClientID, (COR_PRF_ELT_INFO)&eltInfo); goto LExit; } if (g_profControlBlock.pProfInterface->GetLeave2Hook() != NULL) { // We have run out of heap memory, so the content of the mapping table becomes stale. // All Whidbey ETL hooks must be turned off. if (!g_profControlBlock.pProfInterface->IsClientIDToFunctionIDMappingEnabled()) { goto LExit; } // If ELT2 is in use, FunctionID will be returned to the JIT to be embedded into the ELT3 probes // instead of using clientID because the profiler may map several functionIDs to a clientID to // do things like code coverage analysis. FunctionID to clientID has the one-on-one relationship, // while the reverse may not have this one-on-one mapping. Therefore, FunctionID is used as the // key to retrieve the corresponding clientID from the internal FunctionID hash table. FunctionID functionId = clientData; _ASSERTE(functionId != NULL); clientData = g_profControlBlock.pProfInterface->LookupClientIDFromCache(functionId); // // Whidbey Fast-Path ELT // if (CORProfilerELT2FastPathLeaveEnabled()) { REMOVE_STACK_GUARD_FOR_PROFILER_CALL; g_profControlBlock.pProfInterface->GetLeave2Hook()( functionId, clientData, NULL, NULL); goto LExit; } // // Whidbey Slow-Path ELT // COR_PRF_FRAME_INFO frameInfo = NULL; COR_PRF_FUNCTION_ARGUMENT_RANGE argumentRange; HRESULT hr = ProfilingGetFunctionLeave3Info(functionId, (COR_PRF_ELT_INFO)&eltInfo, &frameInfo, &argumentRange); _ASSERTE(hr == S_OK); REMOVE_STACK_GUARD_FOR_PROFILER_CALL; g_profControlBlock.pProfInterface->GetLeave2Hook()(functionId, clientData, frameInfo, &argumentRange); goto LExit; } // We will not be here unless the jit'd or ngen'd function we're about to leave // was backpatched with this wrapper around the profiler's hook, and that // wouldn't have happened unless the profiler supplied us with a hook // in the first place. (Note that SetEnterLeaveFunctionHooks* will return // an error unless it's called in the profiler's Initialize(), so a profiler can't change // its mind about where the hooks are.) _ASSERTE(g_profControlBlock.pProfInterface->GetLeaveHook() != NULL); // Note that we cannot assert CORProfilerTrackEnterLeave() (i.e., profiler flag // COR_PRF_MONITOR_ENTERLEAVE), because the profiler may decide whether // to enable the jitter to add enter/leave callouts independently of whether // the profiler actually has enter/leave hooks. (If the profiler has no such hooks, // the callouts quickly return and do nothing.) // // Everett ELT // { REMOVE_STACK_GUARD_FOR_PROFILER_CALL; g_profControlBlock.pProfInterface->GetLeaveHook()((FunctionID)clientData); } LExit: ; HELPER_METHOD_FRAME_END(); // Un-link the frame #endif // PROFILING_SUPPORTED } HCIMPLEND HCIMPL2(EXTERN_C void, ProfileTailcall, UINT_PTR clientData, void * platformSpecificHandle) { FCALL_CONTRACT; FC_GC_POLL_NOT_NEEDED(); // we pulse GC mode, so we are doing a poll #ifdef PROFILING_SUPPORTED #ifdef PROF_TEST_ONLY_FORCE_ELT // If this test-only flag is set, it's possible we might not have a profiler // attached, or might not have any of the hooks set. See // code:ProfControlBlock#TestOnlyELT if (g_profControlBlock.fTestOnlyForceEnterLeave) { if ((g_profControlBlock.pProfInterface.Load() == NULL) || ( (g_profControlBlock.pProfInterface->GetTailcallHook() == NULL) && (g_profControlBlock.pProfInterface->GetTailcall2Hook() == NULL) && (g_profControlBlock.pProfInterface->GetTailcall3Hook() == NULL) && (g_profControlBlock.pProfInterface->GetTailcall3WithInfoHook() == NULL) ) ) { return; } } #endif // PROF_TEST_ONLY_FORCE_ELT // ELT3 fast-path hooks should be NULL when ELT intermediary is used. _ASSERTE(g_profControlBlock.pProfInterface->GetTailcall3Hook() == NULL); _ASSERTE(GetThread()->PreemptiveGCDisabled()); _ASSERTE(platformSpecificHandle != NULL); // Set up a frame HELPER_METHOD_FRAME_BEGIN_ATTRIB_NOPOLL(Frame::FRAME_ATTR_CAPTURE_DEPTH_2); // Our contract is FCALL_CONTRACT, which is considered triggers if you set up a // frame, like we're about to do. SetCallbackStateFlagsHolder csf( COR_PRF_CALLBACKSTATE_INCALLBACK | COR_PRF_CALLBACKSTATE_IN_TRIGGERS_SCOPE); COR_PRF_ELT_INFO_INTERNAL eltInfo; eltInfo.platformSpecificHandle = platformSpecificHandle; // // CLR v4 Slow-Path ELT // if (g_profControlBlock.pProfInterface->GetTailcall3WithInfoHook() != NULL) { FunctionIDOrClientID functionIDOrClientID; functionIDOrClientID.clientID = clientData; REMOVE_STACK_GUARD_FOR_PROFILER_CALL; g_profControlBlock.pProfInterface->GetTailcall3WithInfoHook()( functionIDOrClientID, (COR_PRF_ELT_INFO)&eltInfo); goto LExit; } if (g_profControlBlock.pProfInterface->GetTailcall2Hook() != NULL) { // We have run out of heap memory, so the content of the mapping table becomes stale. // All Whidbey ETL hooks must be turned off. if (!g_profControlBlock.pProfInterface->IsClientIDToFunctionIDMappingEnabled()) { goto LExit; } // If ELT2 is in use, FunctionID will be returned to the JIT to be embedded into the ELT3 probes // instead of using clientID because the profiler may map several functionIDs to a clientID to // do things like code coverage analysis. FunctionID to clientID has the one-on-one relationship, // while the reverse may not have this one-on-one mapping. Therefore, FunctionID is used as the // key to retrieve the corresponding clientID from the internal FunctionID hash table. FunctionID functionId = clientData; _ASSERTE(functionId != NULL); clientData = g_profControlBlock.pProfInterface->LookupClientIDFromCache(functionId); // // Whidbey Fast-Path ELT // if (CORProfilerELT2FastPathTailcallEnabled()) { REMOVE_STACK_GUARD_FOR_PROFILER_CALL; g_profControlBlock.pProfInterface->GetTailcall2Hook()( functionId, clientData, NULL); goto LExit; } // // Whidbey Slow-Path ELT // COR_PRF_FRAME_INFO frameInfo = NULL; HRESULT hr = ProfilingGetFunctionTailcall3Info(functionId, (COR_PRF_ELT_INFO)&eltInfo, &frameInfo); _ASSERTE(hr == S_OK); REMOVE_STACK_GUARD_FOR_PROFILER_CALL; g_profControlBlock.pProfInterface->GetTailcall2Hook()(functionId, clientData, frameInfo); goto LExit; } // We will not be here unless the jit'd or ngen'd function we're about to tailcall // was backpatched with this wrapper around the profiler's hook, and that // wouldn't have happened unless the profiler supplied us with a hook // in the first place. (Note that SetEnterLeaveFunctionHooks* will return // an error unless it's called in the profiler's Initialize(), so a profiler can't change // its mind about where the hooks are.) _ASSERTE(g_profControlBlock.pProfInterface->GetTailcallHook() != NULL); // Note that we cannot assert CORProfilerTrackEnterLeave() (i.e., profiler flag // COR_PRF_MONITOR_ENTERLEAVE), because the profiler may decide whether // to enable the jitter to add enter/leave callouts independently of whether // the profiler actually has enter/leave hooks. (If the profiler has no such hooks, // the callouts quickly return and do nothing.) // // Everett ELT // { REMOVE_STACK_GUARD_FOR_PROFILER_CALL; g_profControlBlock.pProfInterface->GetTailcallHook()((FunctionID)clientData); } LExit: ; HELPER_METHOD_FRAME_END(); // Un-link the frame #endif // PROFILING_SUPPORTED } HCIMPLEND