// 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. // // EEToProfInterfaceImpl.h // // // Declaration of class that wraps calling into the profiler's implementation // of ICorProfilerCallback* // // ====================================================================================== #ifndef __EETOPROFINTERFACEIMPL_H__ #define __EETOPROFINTERFACEIMPL_H__ #include #include "profilepriv.h" #include "eeprofinterfaces.h" #include "shash.h" #include "eventtracebase.h" #include "gcinterface.h" class SimpleRWLock; class ProfToEEInterfaceImpl; interface IAssemblyBindingClosure; struct AssemblyReferenceClosureWalkContextForProfAPI; const GUID k_guidZero = {0}; class EEToProfInterfaceImpl { public: // // Internal initialization / cleanup // EEToProfInterfaceImpl(); ~EEToProfInterfaceImpl(); HRESULT Init( ProfToEEInterfaceImpl * pProfToEE, const CLSID * pClsid, __inout_z LPCWSTR wszClsid, __in_z LPCWSTR wszProfileDLL, BOOL fLoadedViaAttach, DWORD dwConcurrentGCWaitTimeoutInMs); BOOL IsCallback3Supported(); BOOL IsCallback4Supported(); BOOL IsCallback5Supported(); BOOL IsCallback6Supported(); BOOL IsCallback7Supported(); BOOL IsCallback8Supported(); HRESULT SetEventMask(DWORD dwEventMask, DWORD dwEventMaskHigh); // Used in ProfToEEInterfaceImpl.cpp to set this to the profiler's hook's // function pointer (see SetFunctionIDMapper). void SetFunctionIDMapper(FunctionIDMapper * pFunc); void SetFunctionIDMapper2(FunctionIDMapper2 * pFunc, void * clientData); FunctionIDMapper * GetFunctionIDMapper(); FunctionIDMapper2 * GetFunctionIDMapper2(); BOOL IsLoadedViaAttach(); HRESULT EnsureProfilerDetachable(); void SetUnrevertiblyModifiedILFlag(); FunctionEnter * GetEnterHook(); FunctionLeave * GetLeaveHook(); FunctionTailcall * GetTailcallHook(); FunctionEnter2 * GetEnter2Hook(); FunctionLeave2 * GetLeave2Hook(); FunctionTailcall2 * GetTailcall2Hook(); FunctionEnter3 * GetEnter3Hook(); FunctionLeave3 * GetLeave3Hook(); FunctionTailcall3 * GetTailcall3Hook(); FunctionEnter3WithInfo * GetEnter3WithInfoHook(); FunctionLeave3WithInfo * GetLeave3WithInfoHook(); FunctionTailcall3WithInfo * GetTailcall3WithInfoHook(); BOOL IsClientIDToFunctionIDMappingEnabled(); UINT_PTR LookupClientIDFromCache(FunctionID functionID); HRESULT SetEnterLeaveFunctionHooks( FunctionEnter * pFuncEnter, FunctionLeave * pFuncLeave, FunctionTailcall * pFuncTailcall); HRESULT SetEnterLeaveFunctionHooks2( FunctionEnter2 * pFuncEnter, FunctionLeave2 * pFuncLeave, FunctionTailcall2 * pFuncTailcall); HRESULT SetEnterLeaveFunctionHooks3( FunctionEnter3 * pFuncEnter3, FunctionLeave3 * pFuncLeave3, FunctionTailcall3 * pFuncTailcall3); HRESULT SetEnterLeaveFunctionHooks3WithInfo( FunctionEnter3WithInfo * pFuncEnter3WithInfo, FunctionLeave3WithInfo * pFuncLeave3WithInfo, FunctionTailcall3WithInfo * pFuncTailcall3WithInfo); BOOL RequiresGenericsContextForEnterLeave(); UINT_PTR EEFunctionIDMapper(FunctionID funcId, BOOL * pbHookFunction); // This fills in the non call-specific portions of the cookie GUID. // This should only be called once at startup if necessary. HRESULT InitGUID(); // This will assign a mostly-unique GUID. If enough calls to GetGUID // are made from the same thread, then the GUIDs will cycle. // (Current, it will cycle every 256 calls) void GetGUID(GUID * pGUID); // // Initialize callback // HRESULT Initialize(); HRESULT InitializeForAttach(void * pvClientData, UINT cbClientData); HRESULT ProfilerAttachComplete(); // // Thread Events // HRESULT ThreadCreated( ThreadID threadID); HRESULT ThreadDestroyed( ThreadID threadID); HRESULT ThreadAssignedToOSThread(ThreadID managedThreadId, DWORD osThreadId); HRESULT ThreadNameChanged(ThreadID managedThreadId, ULONG cchName, __in_ecount_opt(cchName) WCHAR name[]); // // Startup/Shutdown Events // HRESULT Shutdown(); // // JIT/Function Events // HRESULT FunctionUnloadStarted( FunctionID functionId); HRESULT JITCompilationFinished( FunctionID functionId, HRESULT hrStatus, BOOL fIsSafeToBlock); HRESULT JITCompilationStarted( FunctionID functionId, BOOL fIsSafeToBlock); HRESULT DynamicMethodJITCompilationStarted( FunctionID functionId, BOOL fIsSafeToBlock, LPCBYTE pILHeader, ULONG cbILHeader); HRESULT DynamicMethodJITCompilationFinished( FunctionID functionId, HRESULT hrStatus, BOOL fIsSafeToBlock); HRESULT DynamicMethodUnloaded( FunctionID functionId); HRESULT JITCachedFunctionSearchStarted( /* [in] */ FunctionID functionId, /* [out] */ BOOL * pbUseCachedFunction); HRESULT JITCachedFunctionSearchFinished( /* [in] */ FunctionID functionId, /* [in] */ COR_PRF_JIT_CACHE result); HRESULT JITFunctionPitched(FunctionID functionId); HRESULT JITInlining( /* [in] */ FunctionID callerId, /* [in] */ FunctionID calleeId, /* [out] */ BOOL * pfShouldInline); HRESULT ReJITCompilationStarted( /* [in] */ FunctionID functionId, /* [in] */ ReJITID reJitId, /* [in] */ BOOL fIsSafeToBlock); HRESULT GetReJITParameters( /* [in] */ ModuleID moduleId, /* [in] */ mdMethodDef methodId, /* [in] */ ICorProfilerFunctionControl * pFunctionControl); HRESULT ReJITCompilationFinished( /* [in] */ FunctionID functionId, /* [in] */ ReJITID reJitId, /* [in] */ HRESULT hrStatus, /* [in] */ BOOL fIsSafeToBlock); HRESULT ReJITError( /* [in] */ ModuleID moduleId, /* [in] */ mdMethodDef methodId, /* [in] */ FunctionID functionId, /* [in] */ HRESULT hrStatus); // // Module Events // HRESULT ModuleLoadStarted( ModuleID moduleId); HRESULT ModuleLoadFinished( ModuleID moduleId, HRESULT hrStatus); HRESULT ModuleUnloadStarted( ModuleID moduleId); HRESULT ModuleUnloadFinished( ModuleID moduleId, HRESULT hrStatus); HRESULT ModuleAttachedToAssembly( ModuleID moduleId, AssemblyID AssemblyId); HRESULT ModuleInMemorySymbolsUpdated( ModuleID moduleId); // // Class Events // HRESULT ClassLoadStarted( ClassID classId); HRESULT ClassLoadFinished( ClassID classId, HRESULT hrStatus); HRESULT ClassUnloadStarted( ClassID classId); HRESULT ClassUnloadFinished( ClassID classId, HRESULT hrStatus); // // AppDomain Events // HRESULT AppDomainCreationStarted( AppDomainID appDomainId); HRESULT AppDomainCreationFinished( AppDomainID appDomainId, HRESULT hrStatus); HRESULT AppDomainShutdownStarted( AppDomainID appDomainId); HRESULT AppDomainShutdownFinished( AppDomainID appDomainId, HRESULT hrStatus); // // Assembly Events // HRESULT AssemblyLoadStarted( AssemblyID assemblyId); HRESULT AssemblyLoadFinished( AssemblyID assemblyId, HRESULT hrStatus); HRESULT AssemblyUnloadStarted( AssemblyID assemblyId); HRESULT AssemblyUnloadFinished( AssemblyID assemblyId, HRESULT hrStatus); // // Transition Events // HRESULT UnmanagedToManagedTransition( FunctionID functionId, COR_PRF_TRANSITION_REASON reason); HRESULT ManagedToUnmanagedTransition( FunctionID functionId, COR_PRF_TRANSITION_REASON reason); // // Exception Events // HRESULT ExceptionThrown( ObjectID thrownObjectId); HRESULT ExceptionSearchFunctionEnter( FunctionID functionId); HRESULT ExceptionSearchFunctionLeave(); HRESULT ExceptionSearchFilterEnter( FunctionID funcId); HRESULT ExceptionSearchFilterLeave(); HRESULT ExceptionSearchCatcherFound( FunctionID functionId); HRESULT ExceptionOSHandlerEnter( FunctionID funcId); HRESULT ExceptionOSHandlerLeave( FunctionID funcId); HRESULT ExceptionUnwindFunctionEnter( FunctionID functionId); HRESULT ExceptionUnwindFunctionLeave(); HRESULT ExceptionUnwindFinallyEnter( FunctionID functionId); HRESULT ExceptionUnwindFinallyLeave(); HRESULT ExceptionCatcherEnter( FunctionID functionId, ObjectID objectId); HRESULT ExceptionCatcherLeave(); // // CCW Events // HRESULT COMClassicVTableCreated( /* [in] */ ClassID wrappedClassId, /* [in] */ REFGUID implementedIID, /* [in] */ void * pVTable, /* [in] */ ULONG cSlots); HRESULT COMClassicVTableDestroyed( /* [in] */ ClassID wrappedClassId, /* [in] */ REFGUID implementedIID, /* [in] */ void * pVTable); // // Remoting Events // HRESULT RemotingClientInvocationStarted(); HRESULT RemotingClientSendingMessage(GUID * pCookie, BOOL fIsAsync); HRESULT RemotingClientReceivingReply(GUID * pCookie, BOOL fIsAsync); HRESULT RemotingClientInvocationFinished(); HRESULT RemotingServerReceivingMessage(GUID * pCookie, BOOL fIsAsync); HRESULT RemotingServerInvocationStarted(); HRESULT RemotingServerInvocationReturned(); HRESULT RemotingServerSendingReply(GUID * pCookie, BOOL fIsAsync); // // GC Events // HRESULT RuntimeSuspendStarted(COR_PRF_SUSPEND_REASON suspendReason); HRESULT RuntimeSuspendFinished(); HRESULT RuntimeSuspendAborted(); HRESULT RuntimeResumeStarted(); HRESULT RuntimeResumeFinished(); HRESULT RuntimeThreadSuspended(ThreadID suspendedThreadId); HRESULT RuntimeThreadResumed(ThreadID resumedThreadId); HRESULT ObjectAllocated( /* [in] */ ObjectID objectId, /* [in] */ ClassID classId); HRESULT FinalizeableObjectQueued(BOOL isCritical, ObjectID objectID); // // GC Moved References and RootReferences2 Notification Stuff // HRESULT MovedReference(BYTE * pbMemBlockStart, BYTE * pbMemBlockEnd, ptrdiff_t cbRelocDistance, void * pHeapId, BOOL fCompacting); HRESULT EndMovedReferences(void * pHeapId); HRESULT RootReference2(BYTE * objectId, EtwGCRootKind dwEtwRootKind, EtwGCRootFlags dwEtwRootFlags, void * rootID, void * pHeapId); HRESULT EndRootReferences2(void * pHeapId); HRESULT ConditionalWeakTableElementReference(BYTE * primaryObjectId, BYTE * secondaryObjectId, void * rootID, void * pHeapId); HRESULT EndConditionalWeakTableElementReferences(void * pHeapId); // // GC Root notification stuff // HRESULT AllocByClass(ObjectID objId, ClassID classId, void* pHeapId); HRESULT EndAllocByClass(void * pHeapId); // // Heap walk notification stuff // HRESULT ObjectReference(ObjectID objId, ClassID classId, ULONG cNumRefs, ObjectID * arrObjRef); // // GC Handle creation / destruction notifications // HRESULT HandleCreated(UINT_PTR handleId, ObjectID initialObjectId); HRESULT HandleDestroyed(UINT_PTR handleId); HRESULT GarbageCollectionStarted(int cGenerations, BOOL generationCollected[], COR_PRF_GC_REASON reason); HRESULT GarbageCollectionFinished(); // // Detach // HRESULT ProfilerDetachSucceeded(); BOOL HasTimedOutWaitingForConcurrentGC(); HRESULT GetAssemblyReferences(LPCWSTR wszAssemblyPath, IAssemblyBindingClosure * pClosure, AssemblyReferenceClosureWalkContextForProfAPI * pContext); private: // // Generation 0 Allocation by Class notification stuff // // This is for a hashing of ClassID values struct CLASSHASHENTRY : HASHENTRY { ClassID m_clsId; // The class ID (also the key) size_t m_count; // How many of this class have been counted }; // This is a simple implementation of CHashTable to provide a very simple // implementation of the Cmp pure virtual function class CHashTableImpl : public CHashTable { public: CHashTableImpl(ULONG iBuckets); virtual ~CHashTableImpl(); protected: virtual BOOL Cmp(SIZE_T k1, const HASHENTRY * pc2); }; // This contains the data for storing allocation information // in terms of numbers of objects sorted by class. struct AllocByClassData { CHashTableImpl * pHashTable; // The hash table CLASSHASHENTRY * arrHash; // Array that the hashtable uses for linking ULONG cHash; // The total number of elements in arrHash ULONG iHash; // Next empty entry in the hash array ClassID * arrClsId; // Array of ClassIDs for the call to ObjectsAllocatedByClass ULONG * arrcObjects; // Array of counts for the call to ObjectsAllocatedByClass size_t cLength; // Length of the above two parallel arrays }; static const UINT kcReferencesMax = 512; struct GCReferencesData { size_t curIdx; size_t compactingCount; BYTE * arrpbMemBlockStartOld[kcReferencesMax]; BYTE * arrpbMemBlockStartNew[kcReferencesMax]; union { size_t arrMemBlockSize[kcReferencesMax]; ULONG arrULONG[kcReferencesMax]; BYTE * arrpbRootId[kcReferencesMax]; }; GCReferencesData * pNext; }; // Since this stuff can only be performed by one thread (right now), we don't need // to make this thread safe and can just have one block we reuse every time around static AllocByClassData * m_pSavedAllocDataBlock; // Pointer to the profiler's implementation of the callback interface(s). // Profilers MUST support ICorProfilerCallback2. // Profilers MAY optionally support ICorProfilerCallback3,4,5,6,7,8,9 ICorProfilerCallback2 * m_pCallback2; ICorProfilerCallback3 * m_pCallback3; ICorProfilerCallback4 * m_pCallback4; ICorProfilerCallback5 * m_pCallback5; ICorProfilerCallback6 * m_pCallback6; ICorProfilerCallback7 * m_pCallback7; ICorProfilerCallback8 * m_pCallback8; ICorProfilerCallback9 * m_pCallback9; HMODULE m_hmodProfilerDLL; BOOL m_fLoadedViaAttach; ProfToEEInterfaceImpl * m_pProfToEE; // Used in EEToProfInterfaceImpl.cpp to call into the profiler (see EEFunctionIDMapper) FunctionIDMapper * m_pProfilersFuncIDMapper; FunctionIDMapper2 * m_pProfilersFuncIDMapper2; void * m_pProfilersFuncIDMapper2ClientData; // This is used as a cookie template for remoting calls GUID m_GUID; // This is an incrementing counter for constructing unique GUIDS from // m_GUID LONG m_lGUIDCount; // This will contain a list of free ref data structs, so they // don't have to be re-allocated on every GC GCReferencesData * m_pGCRefDataFreeList; // This is for managing access to the free list above. CRITSEC_COOKIE m_csGCRefDataFreeList; FunctionEnter * m_pEnter; FunctionLeave * m_pLeave; FunctionTailcall * m_pTailcall; FunctionEnter2 * m_pEnter2; FunctionLeave2 * m_pLeave2; FunctionTailcall2 * m_pTailcall2; BOOL m_fIsClientIDToFunctionIDMappingEnabled; FunctionEnter3 * m_pEnter3; FunctionLeave3 * m_pLeave3; FunctionTailcall3 * m_pTailcall3; FunctionEnter3WithInfo * m_pEnter3WithInfo; FunctionLeave3WithInfo * m_pLeave3WithInfo; FunctionTailcall3WithInfo * m_pTailcall3WithInfo; // Remembers whether the profiler used SetILFunctionBody() which modifies IL in a // way that cannot be reverted. This prevents a detach from succeeding. BOOL m_fUnrevertiblyModifiedIL; GCReferencesData * AllocateMovedReferencesData(); void FreeMovedReferencesData(GCReferencesData * pData); HRESULT MovedReferences(GCReferencesData * pData); HRESULT RootReferences2(GCReferencesData * pData); HRESULT ConditionalWeakTableElementReferences(GCReferencesData * pData); HRESULT NotifyAllocByClass(AllocByClassData * pData); HRESULT CreateProfiler( const CLSID * pClsid, __in_z LPCWSTR wszClsid, __in_z LPCWSTR wszProfileDLL); HRESULT DetermineAndSetEnterLeaveFunctionHooksForJit(); HRESULT STDMETHODCALLTYPE SetEnterLeaveFunctionHooksForJit( FunctionEnter3 * pFuncEnter, FunctionLeave3 * pFuncLeave, FunctionTailcall3 * pFuncTailcall); struct FunctionIDAndClientID { FunctionID functionID; UINT_PTR clientID; }; class FunctionIDHashTableTraits : public NoRemoveSHashTraits > { public: static const COUNT_T s_minimum_allocation = 31; typedef DefaultSHashTraits::count_t count_t; typedef UINT_PTR key_t; static key_t GetKey(FunctionIDAndClientID e) { LIMITED_METHOD_CONTRACT; return e.functionID; } static BOOL Equals(key_t k1, key_t k2) { LIMITED_METHOD_CONTRACT; return k1 == k2; } static count_t Hash(key_t k) { LIMITED_METHOD_CONTRACT; return (count_t)k; } static const FunctionIDAndClientID Null() { LIMITED_METHOD_CONTRACT; FunctionIDAndClientID functionIDAndClientID; functionIDAndClientID.functionID = NULL; functionIDAndClientID.clientID = NULL; return functionIDAndClientID; } static bool IsNull(const FunctionIDAndClientID &functionIDAndClientID) { LIMITED_METHOD_CONTRACT; _ASSERTE((functionIDAndClientID.functionID != NULL) || (functionIDAndClientID.clientID == NULL)); return functionIDAndClientID.functionID == NULL; } }; typedef SHash FunctionIDHashTable; // ELT3 no long keeps track of FunctionID of current managed method. Therefore, a hash table of bookkeeping // the mapping from FunctionID to clientID is needed to build up ELT2 on top of ELT3. When ELT2 (slow-path // or fast-path) is registered by the profiler and the profiler's IDFunctionMapper requests to hook up the // function being loading, the clientID returned by FunctionIDMapper will be saved as the value to be looked // up by the corresponding FunctionID in the hash table. FunctionIDs can be recycled after an app domain // that contains the function bodies is unloaded so this hash table needs to replace the existing FunctionID // with new FunctionID if a duplication is found in the hash table. FunctionIDHashTable * m_pFunctionIDHashTable; // Since the hash table can be read and writen concurrently, a reader-writer lock is used to synchronize // all accesses to the hash table. SimpleRWLock * m_pFunctionIDHashTableRWLock; // Timeout for wait operation on concurrent GC. Only used for attach scenario DWORD m_dwConcurrentGCWaitTimeoutInMs; // Remember the fact we've timed out when waiting for concurrent GC. Will report the error later BOOL m_bHasTimedOutWaitingForConcurrentGC; }; #endif // __EETOPROFINTERFACEIMPL_H__