diff options
Diffstat (limited to 'src/vm/profilinghelper.cpp')
-rw-r--r-- | src/vm/profilinghelper.cpp | 1493 |
1 files changed, 1493 insertions, 0 deletions
diff --git a/src/vm/profilinghelper.cpp b/src/vm/profilinghelper.cpp new file mode 100644 index 0000000000..139ba89ec0 --- /dev/null +++ b/src/vm/profilinghelper.cpp @@ -0,0 +1,1493 @@ +// 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. +// +// ProfilingHelper.cpp +// + +// +// Implementation of helper classes used for miscellaneous purposes within the profiling +// API +// +// ====================================================================================== + +// +// #LoadUnloadCallbackSynchronization +// +// There is synchronization around loading profilers, unloading profilers, and issuing +// callbacks to profilers, to ensure that we know when it's safe to detach profilers or +// to call into profilers. The synchronization scheme is intentionally lockless on the +// mainline path (issuing callbacks into the profiler), with heavy locking on the +// non-mainline path (loading / unloading profilers). +// +// PROTECTED DATA +// +// The synchronization protects the following data: +// +// * ProfilingAPIDetach::s_profilerDetachInfo +// * (volatile) g_profControlBlock.curProfStatus.m_profStatus +// * (volatile) g_profControlBlock.pProfInterface +// * latter implies the profiler DLL's load status is protected as well, as +// pProfInterface changes between non-NULL and NULL as a profiler DLL is +// loaded and unloaded, respectively. +// +// SYNCHRONIZATION COMPONENTS +// +// * Simple Crst: code:ProfilingAPIUtility::s_csStatus +// * Lockless, volatile per-thread counters: code:EvacuationCounterHolder +// * Profiler status transition invariants and CPU buffer flushing: +// code:CurrentProfilerStatus::Set +// +// WRITERS +// +// The above data is considered to be "written to" when a profiler is loaded or unloaded, +// or the status changes (see code:ProfilerStatus), or a request to detach the profiler +// is received (see code:ProfilingAPIDetach::RequestProfilerDetach), or the DetachThread +// consumes or modifies the contents of code:ProfilingAPIDetach::s_profilerDetachInfo. +// All these cases are serialized with each other by the simple Crst: +// code:ProfilingAPIUtility::s_csStatus +// +// READERS +// +// Readers are the mainline case and are lockless. A "reader" is anyone who wants to +// issue a profiler callback. Readers are scattered throughout the runtime, and have the +// following format: +// { +// BEGIN_PIN_PROFILER(CORProfilerTrackAppDomainLoads()); +// g_profControlBlock.pProfInterface->AppDomainCreationStarted(MyAppDomainID); +// END_PIN_PROFILER(); +// } +// The BEGIN / END macros do the following: +// * Evaluate the expression argument (e.g., CORProfilerTrackAppDomainLoads()). This is a +// "dirty read" as the profiler could be detached at any moment during or after that +// evaluation. +// * If true, push a code:EvacuationCounterHolder on the stack, which increments the +// per-thread evacuation counter (not interlocked). +// * Re-evaluate the expression argument. This time, it's a "clean read" (see below for +// why). +// * If still true, execute the statements inside the BEGIN/END block. Inside that block, +// the profiler is guaranteed to remain loaded, because the evacuation counter +// remains nonzero (again, see below). +// * Once the BEGIN/END block is exited, the evacuation counter is decremented, and the +// profiler is unpinned and allowed to detach. +// +// READER / WRITER COORDINATION +// +// The above ensures that a reader never touches g_profControlBlock.pProfInterface and +// all it embodies (including the profiler DLL code and callback implementations) unless +// the reader was able to increment its thread's evacuation counter AND re-verify that +// the profiler's status is still active (the status check is included in the macro's +// expression argument, such as CORProfilerTrackAppDomainLoads()). +// +// At the same time, a profiler DLL is never unloaded (nor +// g_profControlBlock.pProfInterface deleted and NULLed out) UNLESS the writer performs +// these actions: +// * (a) Set the profiler's status to a non-active state like kProfStatusDetaching or +// kProfStatusNone +// * (b) Call FlushProcessWriteBuffers() +// * (c) Grab thread store lock, iterate through all threads, and verify each per-thread +// evacuation counter is zero. +// +// The above steps are why it's considered a "clean read" if a reader first increments +// its evacuation counter and then checks the profiler status. Once the writer flushes +// the CPU buffers (b), the reader will see the updated status (from a) and know not to +// use g_profControlBlock.pProfInterface. And if the reader clean-reads the status before +// the buffers were flushed, then the reader will have incremented its evacuation counter +// first, which the writer will be sure to see in (c). For more details about how the +// evacuation counters work, see code:ProfilingAPIDetach::IsProfilerEvacuated. +// +// WHEN ARE BEGIN/END_PIN_PROFILER REQUIRED? +// +// In general, any time you access g_profControlBlock.pProfInterface, you must be inside +// a BEGIN/END_PIN_PROFILER block. This is pretty much always true throughout the EE, but +// there are some exceptions inside the profiling API code itself, where the BEGIN / END +// macros are unnecessary: +// * If you are inside a public ICorProfilerInfo function's implementation, the +// profiler is already pinned. This is because the profiler called the Info +// function from either: +// * a callback implemented inside of g_profControlBlock.pProfInterface, in which +// case the BEGIN/END macros are already in place around the call to that +// callback, OR +// * a hijacked thread or a thread of the profiler's own creation. In either +// case, it's the profiler's responsibility to end hijacking and end its own +// threads before requesting a detach. So the profiler DLL is guaranteed not +// to disappear while hijacking or profiler-created threads are in action. +// * If you're executing while code:ProfilingAPIUtility::s_csStatus is held, then +// you're explicitly serialized against all code that might unload the profiler's +// DLL and delete g_profControlBlock.pProfInterface. So the profiler is therefore +// still guaranteed not to disappear. +// * If slow ELT helpers, fast ELT hooks, or profiler-instrumented code is on the +// stack, then the profiler cannot be detached yet anyway. Today, we outright +// refuse a detach request from a profiler that instrumented code or enabled ELT. +// Once rejit / revert is implemented, the evacuation checks will ensure all +// instrumented code (including ELT) are reverted and off all stacks before +// attempting to unload the profielr. + + +#include "common.h" + +#ifdef PROFILING_SUPPORTED + +#include "eeprofinterfaces.h" +#include "eetoprofinterfaceimpl.h" +#include "eetoprofinterfaceimpl.inl" +#include "corprof.h" +#include "proftoeeinterfaceimpl.h" +#include "proftoeeinterfaceimpl.inl" +#include "profilinghelper.h" +#include "profilinghelper.inl" +#include "eemessagebox.h" + +#if defined(FEATURE_PROFAPI_EVENT_LOGGING) && !defined(FEATURE_CORECLR) +#include <eventmsg.h> +#endif // FEATURE_PROFAPI_EVENT_LOGGING) && !FEATURE_CORECLR + +#ifdef FEATURE_PROFAPI_ATTACH_DETACH +#include "profattach.h" +#include "profdetach.h" +#endif // FEATURE_PROFAPI_ATTACH_DETACH + +#include "utilcode.h" + +#ifndef FEATURE_PAL +#include "securitywrapper.h" +#endif // !FEATURE_PAL + +//--------------------------------------------------------------------------------------- +// Normally, this would go in profilepriv.inl, but it's not easily inlineable because of +// the use of BEGIN/END_PIN_PROFILER +// +// Return Value: +// TRUE iff security transparency checks in full trust assemblies should be disabled +// due to the profiler. +// +BOOL CORProfilerBypassSecurityChecks() +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + CANNOT_TAKE_LOCK; + SO_NOT_MAINLINE; + } + CONTRACTL_END; + + { + BEGIN_PIN_PROFILER(CORProfilerPresent()); + + // V2 profiler binaries, for compatibility purposes, should bypass transparency + // checks in full trust assemblies. + if (!(&g_profControlBlock)->pProfInterface->IsCallback3Supported()) + return TRUE; + + // V4 profiler binaries must opt in to bypasssing transparency checks in full trust + // assemblies. + if (((&g_profControlBlock)->dwEventMask & COR_PRF_DISABLE_TRANSPARENCY_CHECKS_UNDER_FULL_TRUST) != 0) + return TRUE; + + END_PIN_PROFILER(); + } + + // All other cases, including no profiler loaded at all: Don't bypass + return FALSE; +} + +// ---------------------------------------------------------------------------- +// CurrentProfilerStatus methods + + +//--------------------------------------------------------------------------------------- +// +// Updates the value indicating the profiler's current status +// +// Arguments: +// profStatus - New value (from enum ProfilerStatus) to set. +// +// Notes: +// Sets the status under a lock, and performs a debug-only check to verify that the +// status transition is a legal one. Also performs a FlushStoreBuffers() after +// changing the status when necessary. +// + +void CurrentProfilerStatus::Set(ProfilerStatus newProfStatus) +{ + CONTRACTL + { + NOTHROW; + GC_TRIGGERS; + MODE_ANY; + CAN_TAKE_LOCK; + } + CONTRACTL_END; + + _ASSERTE(ProfilingAPIUtility::GetStatusCrst() != NULL); + + { + // Need to serialize attempts to transition the profiler status. For example, a + // profiler in one thread could request a detach, while the CLR in another + // thread is transitioning the profiler from kProfStatusInitializing* to + // kProfStatusActive + CRITSEC_Holder csh(ProfilingAPIUtility::GetStatusCrst()); + + // Based on what the old status is, verify the new status is a legal transition. + switch(m_profStatus) + { + default: + _ASSERTE(!"Unknown ProfilerStatus"); + break; + + case kProfStatusNone: + _ASSERTE((newProfStatus == kProfStatusInitializingForStartupLoad) || + (newProfStatus == kProfStatusInitializingForAttachLoad)); + break; + + case kProfStatusDetaching: + _ASSERTE(newProfStatus == kProfStatusNone); + break; + + case kProfStatusInitializingForStartupLoad: + case kProfStatusInitializingForAttachLoad: + _ASSERTE((newProfStatus == kProfStatusActive) || + (newProfStatus == kProfStatusNone)); + break; + + case kProfStatusActive: + _ASSERTE((newProfStatus == kProfStatusNone) || + (newProfStatus == kProfStatusDetaching)); + break; + } + + m_profStatus = newProfStatus; + } + +#if !defined(DACCESS_COMPILE) + if (((newProfStatus == kProfStatusNone) || + (newProfStatus == kProfStatusDetaching) || + (newProfStatus == kProfStatusActive))) + { + // Flush the store buffers on all CPUs, to ensure other threads see that + // g_profControlBlock.curProfStatus has changed. The important status changes to + // flush are: + // * to kProfStatusNone or kProfStatusDetaching so other threads know to stop + // making calls into the profiler + // * to kProfStatusActive, to ensure callbacks can be issued by the time an + // attaching profiler receives ProfilerAttachComplete(), so the profiler + // can safely perform catchup at that time (see + // code:#ProfCatchUpSynchronization). + // + ::FlushProcessWriteBuffers(); + } +#endif // !defined(DACCESS_COMPILE) +} + + +//--------------------------------------------------------------------------------------- +// ProfilingAPIUtility members + + +// See code:#LoadUnloadCallbackSynchronization. +CRITSEC_COOKIE ProfilingAPIUtility::s_csStatus = NULL; + + +SidBuffer * ProfilingAPIUtility::s_pSidBuffer = NULL; + +// ---------------------------------------------------------------------------- +// ProfilingAPIUtility::AppendSupplementaryInformation +// +// Description: +// Helper to the event logging functions to append the process ID and string +// resource ID to the end of the message. +// +// Arguments: +// * iStringResource - [in] String resource ID to append to message. +// * pString - [in/out] On input, the string to log so far. On output, the original +// string with the process ID info appended. +// + +// static +void ProfilingAPIUtility::AppendSupplementaryInformation(int iStringResource, SString * pString) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_ANY; + + // This loads resource strings, which takes locks. + CAN_TAKE_LOCK; + } + CONTRACTL_END; + + StackSString supplementaryInformation; + + if (!supplementaryInformation.LoadResource( + CCompRC::Debugging, + IDS_PROF_SUPPLEMENTARY_INFO + )) + { + // Resource not found; should never happen. + return; + } + + pString->Append(W(" ")); + pString->AppendPrintf( + supplementaryInformation, + GetCurrentProcessId(), + iStringResource); +} + +//--------------------------------------------------------------------------------------- +// +// Helper function to log publicly-viewable errors about profiler loading and +// initialization. +// +// +// Arguments: +// * iStringResourceID - resource ID of string containing message to log +// * wEventType - same constant used in win32 to specify the type of event: +// usually EVENTLOG_ERROR_TYPE, EVENTLOG_WARNING_TYPE, or +// EVENTLOG_INFORMATION_TYPE +// * insertionArgs - 0 or more values to be inserted into the string to be logged +// (>0 only if iStringResourceID contains format arguments (%)). +// + +// static +void ProfilingAPIUtility::LogProfEventVA( + int iStringResourceID, + WORD wEventType, + va_list insertionArgs) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_ANY; + + // This loads resource strings, which takes locks. + CAN_TAKE_LOCK; + } + CONTRACTL_END; + +#ifndef FEATURE_PROFAPI_EVENT_LOGGING + + + // Rotor messages go to message boxes + + EEMessageBoxCatastrophic( + iStringResourceID, // Text message to display + IDS_EE_PROFILING_FAILURE, // Titlebar of message box + insertionArgs); // Insertion strings for text message + +#else // FEATURE_PROFAPI_EVENT_LOGGING + + // Non-rotor messages go to the event log + + StackSString messageFromResource; + StackSString messageToLog; + + if (!messageFromResource.LoadResource( + CCompRC::Debugging, + iStringResourceID + )) + { + // Resource not found; should never happen. + return; + } + + messageToLog.VPrintf(messageFromResource, insertionArgs); + + AppendSupplementaryInformation(iStringResourceID, &messageToLog); + +#if defined(FEATURE_CORECLR) + // CoreCLR on Windows ouputs debug strings for diagnostic messages. + WszOutputDebugString(messageToLog); +#else + // Get the user SID for the current process, so it can be provided to the event + // logging API, which will then fill out the "User" field in the event log entry. If + // this fails, that's not fatal. We can just pass NULL for the PSID, and the "User" + // field will be left blank. + PSID psid = NULL; + HRESULT hr = GetCurrentProcessUserSid(&psid); + if (FAILED(hr)) + { + // No biggie. Just pass in a NULL psid, and the User field will be empty + _ASSERTE(psid == NULL); + } + + // On desktop CLR builds, the profiling API uses the event log for end-user-friendly + // diagnostic messages. + ReportEventCLR(wEventType, // wType + 0, // wCategory + COR_Profiler, // dwEventID + psid, // lpUserSid + &messageToLog); // uh duh +#endif // FEATURE_CORECLR + +#endif // FEATURE_PROFAPI_EVENT_LOGGING +} + +// See code:ProfilingAPIUtility.LogProfEventVA for description of arguments. +// static +void ProfilingAPIUtility::LogProfError(int iStringResourceID, ...) +{ + CONTRACTL +{ + THROWS; + GC_TRIGGERS; + MODE_ANY; + + // This loads resource strings, which takes locks. + CAN_TAKE_LOCK; + } + CONTRACTL_END; + + va_list insertionArgs; + va_start(insertionArgs, iStringResourceID); + LogProfEventVA( + iStringResourceID, + EVENTLOG_ERROR_TYPE, + insertionArgs); + va_end(insertionArgs); +} + +// See code:ProfilingAPIUtility.LogProfEventVA for description of arguments. +// static +void ProfilingAPIUtility::LogProfInfo(int iStringResourceID, ...) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_ANY; + + // This loads resource strings, which takes locks. + CAN_TAKE_LOCK; + } + CONTRACTL_END; + +// Rotor uses message boxes instead of event log, and it would be disruptive to +// pop a messagebox in the user's face every time an app runs with a profiler +// configured to load. So only log this only when we don't do a pop-up. +#ifdef FEATURE_PROFAPI_EVENT_LOGGING + va_list insertionArgs; + va_start(insertionArgs, iStringResourceID); + LogProfEventVA( + iStringResourceID, + EVENTLOG_INFORMATION_TYPE, + insertionArgs); + va_end(insertionArgs); +#endif //FEATURE_PROFAPI_EVENT_LOGGING +} + +#ifdef PROF_TEST_ONLY_FORCE_ELT +// Special forward-declarations of the profiling API's slow-path enter/leave/tailcall +// hooks. These need to be forward-declared here so that they may be referenced in +// InitializeProfiling() below solely for the debug-only, test-only code to allow +// enter/leave/tailcall to be turned on at startup without a profiler. See +// code:ProfControlBlock#TestOnlyELT +EXTERN_C void __stdcall ProfileEnterNaked(UINT_PTR clientData); +EXTERN_C void __stdcall ProfileLeaveNaked(UINT_PTR clientData); +EXTERN_C void __stdcall ProfileTailcallNaked(UINT_PTR clientData); +#endif //PROF_TEST_ONLY_FORCE_ELT + +// ---------------------------------------------------------------------------- +// ProfilingAPIUtility::InitializeProfiling +// +// This is the top-most level of profiling API initialization, and is called directly by +// EEStartupHelper() (in ceemain.cpp). This initializes internal structures relating to the +// Profiling API. This also orchestrates loading the profiler and initializing it (if +// its GUID is specified in the environment). +// +// Return Value: +// HRESULT indicating success or failure. This is generally very lenient about internal +// failures, as we don't want them to prevent the startup of the app: +// S_OK = Environment didn't request a profiler, or +// Environment did request a profiler, and it was loaded successfully +// S_FALSE = There was a problem loading the profiler, but that shouldn't prevent the app +// from starting up +// else (failure) = There was a serious problem that should be dealt with by the caller +// +// Notes: +// This function (or one of its callees) will log an error to the event log +// if there is a failure +// +// Assumptions: +// InitializeProfiling is called during startup, AFTER the host has initialized its +// settings and the config variables have been read, but BEFORE the finalizer thread +// has entered its first wait state. ASSERTs are placed in +// code:ProfilingAPIAttachDetach::Initialize (which is called by this function, and +// which depends on these assumptions) to verify. + +// static +HRESULT ProfilingAPIUtility::InitializeProfiling() +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + + // This causes events to be logged, which loads resource strings, + // which takes locks. + CAN_TAKE_LOCK; + + MODE_PREEMPTIVE; + } + CONTRACTL_END; + + InitializeLogging(); + + // NULL out / initialize members of the global profapi structure + g_profControlBlock.Init(); + + if (IsCompilationProcess()) + { + LOG((LF_CORPROF, LL_INFO10, "**PROF: Profiling disabled for ngen process.\n")); + return S_OK; + } + + AttemptLoadProfilerForStartup(); + // For now, the return value from AttemptLoadProfilerForStartup is of no use to us. + // Any event has been logged already by AttemptLoadProfilerForStartup, and + // regardless of whether a profiler got loaded, we still need to continue. + + +#ifdef PROF_TEST_ONLY_FORCE_ELT + // Test-only, debug-only code to enable ELT on startup regardless of whether a + // startup profiler is loaded. See code:ProfControlBlock#TestOnlyELT. + DWORD dwEnableSlowELTHooks = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_TestOnlyEnableSlowELTHooks); + if (dwEnableSlowELTHooks != 0) + { + (&g_profControlBlock)->fTestOnlyForceEnterLeave = TRUE; + SetJitHelperFunction(CORINFO_HELP_PROF_FCN_ENTER, (void *) ProfileEnterNaked); + SetJitHelperFunction(CORINFO_HELP_PROF_FCN_LEAVE, (void *) ProfileLeaveNaked); + SetJitHelperFunction(CORINFO_HELP_PROF_FCN_TAILCALL, (void *) ProfileTailcallNaked); + LOG((LF_CORPROF, LL_INFO10, "**PROF: Enabled test-only slow ELT hooks.\n")); + } +#endif //PROF_TEST_ONLY_FORCE_ELT + +#ifdef PROF_TEST_ONLY_FORCE_OBJECT_ALLOCATED + // Test-only, debug-only code to enable ObjectAllocated callbacks on startup regardless of whether a + // startup profiler is loaded. See code:ProfControlBlock#TestOnlyObjectAllocated. + DWORD dwEnableObjectAllocated = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_TestOnlyEnableObjectAllocatedHook); + if (dwEnableObjectAllocated != 0) + { + (&g_profControlBlock)->fTestOnlyForceObjectAllocated = TRUE; + LOG((LF_CORPROF, LL_INFO10, "**PROF: Enabled test-only object ObjectAllocated hooks.\n")); + } +#endif //PROF_TEST_ONLY_FORCE_ELT + + +#ifdef _DEBUG + // Test-only, debug-only code to allow attaching profilers to call ICorProfilerInfo inteface, + // which would otherwise be disallowed for attaching profilers + DWORD dwTestOnlyEnableICorProfilerInfo = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_TestOnlyEnableICorProfilerInfo); + if (dwTestOnlyEnableICorProfilerInfo != 0) + { + (&g_profControlBlock)->fTestOnlyEnableICorProfilerInfo = TRUE; + } +#endif // _DEBUG + + return S_OK; +} + + +// ---------------------------------------------------------------------------- +// ProfilingAPIUtility::ProfilerCLSIDFromString +// +// Description: +// Takes a string form of a CLSID (or progid, believe it or not), and returns the +// corresponding CLSID structure. +// +// Arguments: +// * wszClsid - [in / out] CLSID string to convert. This may also be a progid. This +// ensures our behavior is backward-compatible with previous CLR versions. I don't +// know why previous versions allowed the user to set a progid in the environment, +// but well whatever. On [out], this string is normalized in-place (e.g., +// double-quotes around progid are removed). +// * pClsid - [out] CLSID structure corresponding to wszClsid +// +// Return Value: +// HRESULT indicating success or failure. +// +// Notes: +// * An event is logged if there is a failure. +// + +// static +HRESULT ProfilingAPIUtility::ProfilerCLSIDFromString( + __inout_z LPWSTR wszClsid, + CLSID * pClsid) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + + // This causes events to be logged, which loads resource strings, + // which takes locks. + CAN_TAKE_LOCK; + + MODE_PREEMPTIVE; + } + CONTRACTL_END; + + _ASSERTE(wszClsid != NULL); + _ASSERTE(pClsid != NULL); + + HRESULT hr; + + // Translate the string into a CLSID + if (*wszClsid == W('{')) + { + hr = IIDFromString(wszClsid, pClsid); + } + else + { +#ifndef FEATURE_PAL + WCHAR *szFrom, *szTo; + +#ifdef _PREFAST_ +#pragma warning(push) +#pragma warning(disable:26000) // "espX thinks there is an overflow here, but there isn't any" +#endif + for (szFrom=szTo=wszClsid; *szFrom; ) + { + if (*szFrom == W('"')) + { + ++szFrom; + continue; + } + *szTo++ = *szFrom++; + } + *szTo = 0; + hr = CLSIDFromProgID(wszClsid, pClsid); +#ifdef _PREFAST_ +#pragma warning(pop) +#endif /*_PREFAST_*/ + +#else // !FEATURE_PAL + // ProgID not supported on FEATURE_PAL + hr = E_INVALIDARG; +#endif // !FEATURE_PAL + } + + if (FAILED(hr)) + { + LOG(( + LF_CORPROF, + LL_INFO10, + "**PROF: Invalid CLSID or ProgID (%S). hr=0x%x.\n", + wszClsid, + hr)); + ProfilingAPIUtility::LogProfError(IDS_E_PROF_BAD_CLSID, wszClsid, hr); + return hr; + } + + return S_OK; +} + +// ---------------------------------------------------------------------------- +// ProfilingAPIUtility::AttemptLoadProfilerForStartup +// +// Description: +// Checks environment or registry to see if the app is configured to run with a +// profiler loaded on startup. If so, this calls LoadProfiler() to load it up. +// +// Arguments: +// +// Return Value: +// * S_OK: Startup-profiler has been loaded +// * S_FALSE: No profiler is configured for startup load +// * else, HRESULT indicating failure that occurred +// +// Assumptions: +// * This should be called on startup, after g_profControlBlock is initialized, but +// before any attach infrastructure is initialized. This ensures we don't receive +// an attach request while startup-loading a profiler. +// +// Notes: +// * This or its callees will ensure an event is logged on failure (though will be +// silent if no profiler is configured for startup load (which causes S_FALSE to +// be returned) +// + +// static +HRESULT ProfilingAPIUtility::AttemptLoadProfilerForStartup() +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + + // This causes events to be logged, which loads resource strings, + // which takes locks. + CAN_TAKE_LOCK; + + MODE_PREEMPTIVE; + } + CONTRACTL_END; + + HRESULT hr; + + // Find out if profiling is enabled + DWORD fProfEnabled = 0; + +#ifdef FEATURE_CORECLR + fProfEnabled = CLRConfig::GetConfigValue(CLRConfig::EXTERNAL_CORECLR_ENABLE_PROFILING); +#else //FEATURE_CORECLR + fProfEnabled = CLRConfig::GetConfigValue(CLRConfig::EXTERNAL_COR_ENABLE_PROFILING); +#endif //FEATURE_CORECLR + + // If profiling is not enabled, return. + if (fProfEnabled == 0) + { + LOG((LF_CORPROF, LL_INFO10, "**PROF: Profiling not enabled.\n")); + return S_FALSE; + } + + LOG((LF_CORPROF, LL_INFO10, "**PROF: Initializing Profiling Services.\n")); + + // Get the CLSID of the profiler to CoCreate + NewArrayHolder<WCHAR> wszClsid(NULL); + NewArrayHolder<WCHAR> wszProfilerDLL(NULL); + +#ifdef FEATURE_CORECLR + IfFailRet(CLRConfig::GetConfigValue(CLRConfig::EXTERNAL_CORECLR_PROFILER, &wszClsid)); + +#if defined(_TARGET_X86_) + IfFailRet(CLRConfig::GetConfigValue(CLRConfig::EXTERNAL_CORECLR_PROFILER_PATH_32, &wszProfilerDLL)); +#elif defined(_TARGET_AMD64_) + IfFailRet(CLRConfig::GetConfigValue(CLRConfig::EXTERNAL_CORECLR_PROFILER_PATH_64, &wszProfilerDLL)); +#endif + if(wszProfilerDLL == NULL) + { + IfFailRet(CLRConfig::GetConfigValue(CLRConfig::EXTERNAL_CORECLR_PROFILER_PATH, &wszProfilerDLL)); + } +#else // FEATURE_CORECLR + IfFailRet(CLRConfig::GetConfigValue(CLRConfig::EXTERNAL_COR_PROFILER, &wszClsid)); + +#if defined(_TARGET_X86_) + IfFailRet(CLRConfig::GetConfigValue(CLRConfig::EXTERNAL_COR_PROFILER_PATH_32, &wszProfilerDLL)); +#elif defined(_TARGET_AMD64_) + IfFailRet(CLRConfig::GetConfigValue(CLRConfig::EXTERNAL_COR_PROFILER_PATH_64, &wszProfilerDLL)); +#endif + if(wszProfilerDLL == NULL) + { + IfFailRet(CLRConfig::GetConfigValue(CLRConfig::EXTERNAL_COR_PROFILER_PATH, &wszProfilerDLL)); + } +#endif // FEATURE_CORECLR + + // If the environment variable doesn't exist, profiling is not enabled. + if (wszClsid == NULL) + { + LOG((LF_CORPROF, LL_INFO10, "**PROF: Profiling flag set, but required " + "environment variable does not exist.\n")); + + LogProfError(IDS_E_PROF_NO_CLSID); + + return S_FALSE; + } + + if ((wszProfilerDLL != NULL) && (wcslen(wszProfilerDLL) >= MAX_LONGPATH)) + { + LOG((LF_CORPROF, LL_INFO10, "**PROF: Profiling flag set, but COR_PROFILER_PATH was not set properly.\n")); + + LogProfError(IDS_E_PROF_BAD_PATH); + + return S_FALSE; + } + +#ifdef FEATURE_PAL + // If the environment variable doesn't exist, profiling is not enabled. + if (wszProfilerDLL == NULL) + { + LOG((LF_CORPROF, LL_INFO10, "**PROF: Profiling flag set, but required " + "environment variable does not exist.\n")); + + LogProfError(IDS_E_PROF_BAD_PATH); + + return S_FALSE; + } +#endif // FEATURE_PAL + + CLSID clsid; + hr = ProfilingAPIUtility::ProfilerCLSIDFromString(wszClsid, &clsid); + if (FAILED(hr)) + { + // ProfilerCLSIDFromString already logged an event if there was a failure + return hr; + } + + hr = LoadProfiler( + kStartupLoad, + &clsid, + wszClsid, + wszProfilerDLL, + NULL, // No client data for startup load + 0); // No client data for startup load + if (FAILED(hr)) + { + // A failure in either the CLR or the profiler prevented it from + // loading. Event has been logged. Propagate hr + return hr; + } + + return S_OK; +} + + +//--------------------------------------------------------------------------------------- +// +// Performs lazy initialization that need not occur on startup, but does need to occur +// before trying to load a profiler. +// +// Return Value: +// HRESULT indicating success or failure. +// + +HRESULT ProfilingAPIUtility::PerformDeferredInit() +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + CAN_TAKE_LOCK; + MODE_ANY; + } + CONTRACTL_END; + +#ifdef FEATURE_PROFAPI_ATTACH_DETACH + // Initialize internal resources for detaching + HRESULT hr = ProfilingAPIDetach::Initialize(); + if (FAILED(hr)) + { + LOG(( + LF_CORPROF, + LL_ERROR, + "**PROF: Unable to initialize resources for detaching. hr=0x%x.\n", + hr)); + return hr; + } +#endif // FEATURE_PROFAPI_ATTACH_DETACH + + if (s_csStatus == NULL) + { + s_csStatus = ClrCreateCriticalSection( + CrstProfilingAPIStatus, + (CrstFlags) (CRST_REENTRANCY | CRST_TAKEN_DURING_SHUTDOWN)); + if (s_csStatus == NULL) + { + return E_OUTOFMEMORY; + } + } + + return S_OK; +} + +// ---------------------------------------------------------------------------- +// ProfilingAPIUtility::LoadProfiler +// +// Description: +// Outermost common code for loading the profiler DLL. Both startup and attach code +// paths use this. +// +// Arguments: +// * loadType - Startup load or attach load? +// * pClsid - Profiler's CLSID +// * wszClsid - Profiler's CLSID (or progid) in string form, for event log messages +// * wszProfilerDLL - Profiler's DLL path +// * pvClientData - For attach loads, this is the client data the trigger wants to +// pass to the profiler DLL +// * cbClientData - For attach loads, size of client data in bytes +// * dwConcurrentGCWaitTimeoutInMs - Time out for wait operation on concurrent GC. Attach scenario only +// +// Return Value: +// HRESULT indicating success or failure of the load +// +// Notes: +// * On failure, this function or a callee will have logged an event +// + +// static +HRESULT ProfilingAPIUtility::LoadProfiler( + LoadType loadType, + const CLSID * pClsid, + LPCWSTR wszClsid, + LPCWSTR wszProfilerDLL, + LPVOID pvClientData, + UINT cbClientData, + DWORD dwConcurrentGCWaitTimeoutInMs) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + + // This causes events to be logged, which loads resource strings, + // which takes locks. + CAN_TAKE_LOCK; + + MODE_ANY; + } + CONTRACTL_END; + + if (g_fEEShutDown) + { + return CORPROF_E_RUNTIME_UNINITIALIZED; + } + + enum ProfilerCompatibilityFlag + { + // Default: disable V2 profiler + kDisableV2Profiler = 0x0, + + // Enable V2 profilers + kEnableV2Profiler = 0x1, + + // Disable Profiling + kPreventLoad = 0x2, + }; + + ProfilerCompatibilityFlag profilerCompatibilityFlag = kDisableV2Profiler; + NewArrayHolder<WCHAR> wszProfilerCompatibilitySetting(NULL); + + if (loadType == kStartupLoad) + { + CLRConfig::GetConfigValue(CLRConfig::EXTERNAL_ProfAPI_ProfilerCompatibilitySetting, &wszProfilerCompatibilitySetting); + if (wszProfilerCompatibilitySetting != NULL) + { + if (SString::_wcsicmp(wszProfilerCompatibilitySetting, W("EnableV2Profiler")) == 0) + { + profilerCompatibilityFlag = kEnableV2Profiler; + } + else if (SString::_wcsicmp(wszProfilerCompatibilitySetting, W("PreventLoad")) == 0) + { + profilerCompatibilityFlag = kPreventLoad; + } + } + + if (profilerCompatibilityFlag == kPreventLoad) + { + LOG((LF_CORPROF, LL_INFO10, "**PROF: COMPlus_ProfAPI_ProfilerCompatibilitySetting is set to PreventLoad. " + "Profiler will not be loaded.\n")); + + LogProfInfo(IDS_PROF_PROFILER_DISABLED, + CLRConfig::EXTERNAL_ProfAPI_ProfilerCompatibilitySetting.name, + wszProfilerCompatibilitySetting.GetValue(), + wszClsid); + + return S_OK; + } + } + + HRESULT hr; + + hr = PerformDeferredInit(); + if (FAILED(hr)) + { + LOG(( + LF_CORPROF, + LL_ERROR, + "**PROF: ProfilingAPIUtility::PerformDeferredInit failed. hr=0x%x.\n", + hr)); + LogProfError(IDS_E_PROF_INTERNAL_INIT, wszClsid, hr); + return hr; + } + + // Valid loadType? + _ASSERTE((loadType == kStartupLoad) || (loadType == kAttachLoad)); + + // If a nonzero client data size is reported, there'd better be client data! + _ASSERTE((cbClientData == 0) || (pvClientData != NULL)); + + // Client data is currently only specified on attach + _ASSERTE((pvClientData == NULL) || (loadType == kAttachLoad)); + + // Don't be telling me to load a profiler if there already is one. + _ASSERTE(g_profControlBlock.curProfStatus.Get() == kProfStatusNone); + + // Create the ProfToEE interface to provide to the profiling services + NewHolder<ProfToEEInterfaceImpl> pProfEE(new (nothrow) ProfToEEInterfaceImpl()); + if (pProfEE == NULL) + { + LOG((LF_CORPROF, LL_ERROR, "**PROF: Unable to allocate ProfToEEInterfaceImpl.\n")); + LogProfError(IDS_E_PROF_INTERNAL_INIT, wszClsid, E_OUTOFMEMORY); + return E_OUTOFMEMORY; + } + + // Initialize the interface + hr = pProfEE->Init(); + if (FAILED(hr)) + { + LOG((LF_CORPROF, LL_ERROR, "**PROF: ProfToEEInterface::Init failed.\n")); + LogProfError(IDS_E_PROF_INTERNAL_INIT, wszClsid, hr); + return hr; + } + + // Provide the newly created and inited interface + LOG((LF_CORPROF, LL_INFO10, "**PROF: Profiling code being provided with EE interface.\n")); + + // Create a new EEToProf object + NewHolder<EEToProfInterfaceImpl> pEEProf(new (nothrow) EEToProfInterfaceImpl()); + if (pEEProf == NULL) + { + LOG((LF_CORPROF, LL_ERROR, "**PROF: Unable to allocate EEToProfInterfaceImpl.\n")); + LogProfError(IDS_E_PROF_INTERNAL_INIT, wszClsid, E_OUTOFMEMORY); + return E_OUTOFMEMORY; + } + +#ifdef FEATURE_PROFAPI_ATTACH_DETACH + // We're about to load the profiler, so first make sure we successfully create the + // DetachThread and abort the load of the profiler if we can't. This ensures we don't + // load a profiler unless we're prepared to detach it later. + hr = ProfilingAPIDetach::CreateDetachThread(); + if (FAILED(hr)) + { + LOG(( + LF_CORPROF, + LL_ERROR, + "**PROF: Unable to create DetachThread. hr=0x%x.\n", + hr)); + ProfilingAPIUtility::LogProfError(IDS_E_PROF_INTERNAL_INIT, wszClsid, hr); + return hr; + } +#endif // FEATURE_PROFAPI_ATTACH_DETACH + + // Initialize internal state of our EEToProfInterfaceImpl. This also loads the + // profiler itself, but does not yet call its Initalize() callback + hr = pEEProf->Init(pProfEE, pClsid, wszClsid, wszProfilerDLL, (loadType == kAttachLoad), dwConcurrentGCWaitTimeoutInMs); + if (FAILED(hr)) + { + LOG((LF_CORPROF, LL_ERROR, "**PROF: EEToProfInterfaceImpl::Init failed.\n")); + // EEToProfInterfaceImpl::Init logs an event log error on failure + return hr; + } + + // EEToProfInterfaceImpl::Init takes over the ownership of pProfEE when Init succeeds, and + // EEToProfInterfaceImpl::~EEToProfInterfaceImpl is responsible for releasing the resource pointed + // by pProfEE. Calling SuppressRelease here is necessary to avoid double release that + // the resource pointed by pProfEE are released by both pProfEE and pEEProf's destructor. + pProfEE.SuppressRelease(); + pProfEE = NULL; + + if (loadType == kAttachLoad) // V4 profiler from attach + { + // Profiler must support ICorProfilerCallback3 to be attachable + if (!pEEProf->IsCallback3Supported()) + { + LogProfError(IDS_E_PROF_NOT_ATTACHABLE, wszClsid); + return CORPROF_E_PROFILER_NOT_ATTACHABLE; + } + } + else if (!pEEProf->IsCallback3Supported()) // V2 profiler from startup + { + if (profilerCompatibilityFlag == kDisableV2Profiler) + { + LOG((LF_CORPROF, LL_INFO10, "**PROF: COMPlus_ProfAPI_ProfilerCompatibilitySetting is set to DisableV2Profiler (the default). " + "V2 profilers are not allowed, so that the configured V2 profiler is going to be unloaded.\n")); + + LogProfInfo(IDS_PROF_V2PROFILER_DISABLED, wszClsid); + return S_OK; + } + + _ASSERTE(profilerCompatibilityFlag == kEnableV2Profiler); + + // To prevent V2 profilers from AV, once a V2 profiler is already loaded by a V2 rutnime in the process, + // V4 runtime will not try to load the V2 profiler again. + if (IsV2RuntimeLoaded()) + { + LogProfInfo(IDS_PROF_V2PROFILER_ALREADY_LOADED, wszClsid); + return S_OK; + } + + LOG((LF_CORPROF, LL_INFO10, "**PROF: COMPlus_ProfAPI_ProfilerCompatibilitySetting is set to EnableV2Profiler. " + "The configured V2 profiler is going to be initialized.\n")); + + LogProfInfo(IDS_PROF_V2PROFILER_ENABLED, + CLRConfig::EXTERNAL_ProfAPI_ProfilerCompatibilitySetting.name, + wszProfilerCompatibilitySetting.GetValue(), + wszClsid); + } + + _ASSERTE(s_csStatus != NULL); + { + // All modification of the profiler's status and + // g_profControlBlock.pProfInterface need to be serialized against each other, + // in particular, this code should be serialized against detach and unloading + // code. + CRITSEC_Holder csh(s_csStatus); + + // We've successfully allocated and initialized the callback wrapper object and the + // Info interface implementation objects. The profiler DLL is therefore also + // successfully loaded (but not yet Initialized). Transfer ownership of the + // callback wrapper object to globals (thus suppress a release when the local + // vars go out of scope). + // + // Setting this state now enables us to call into the profiler's Initialize() + // callback (which we do immediately below), and have it successfully call + // back into us via the Info interface (ProfToEEInterfaceImpl) to perform its + // initialization. + g_profControlBlock.pProfInterface = pEEProf.GetValue(); + pEEProf.SuppressRelease(); + pEEProf = NULL; + + // Set global status to reflect the proper type of Init we're doing (attach vs + // startup) + g_profControlBlock.curProfStatus.Set( + (loadType == kStartupLoad) ? + kProfStatusInitializingForStartupLoad : + kProfStatusInitializingForAttachLoad); + } + + // Now that the profiler is officially loaded and in Init status, call into the + // profiler's appropriate Initialize() callback. Note that if the profiler fails this + // call, we should abort the rest of the profiler loading, and reset our state so we + // appear as if we never attempted to load the profiler. + + if (loadType == kStartupLoad) + { + hr = g_profControlBlock.pProfInterface->Initialize(); + } + else + { + _ASSERTE(loadType == kAttachLoad); + _ASSERTE(g_profControlBlock.pProfInterface->IsCallback3Supported()); + hr = g_profControlBlock.pProfInterface->InitializeForAttach(pvClientData, cbClientData); + } + + if (FAILED(hr)) + { + LOG(( + LF_CORPROF, + LL_INFO10, + "**PROF: Profiler failed its Initialize callback. hr=0x%x.\n", + hr)); + + // If we timed out due to waiting on concurrent GC to finish, it is very likely this is + // the reason InitializeForAttach callback failed even though we cannot be sure and we cannot + // cannot assume hr is going to be CORPROF_E_TIMEOUT_WAITING_FOR_CONCURRENT_GC. + // The best we can do in this case is to report this failure anyway. + if (g_profControlBlock.pProfInterface->HasTimedOutWaitingForConcurrentGC()) + { + ProfilingAPIUtility::LogProfError(IDS_E_PROF_TIMEOUT_WAITING_FOR_CONCURRENT_GC, dwConcurrentGCWaitTimeoutInMs, wszClsid); + } + + // Check for known failure types, to customize the event we log + if ((loadType == kAttachLoad) && + ((hr == CORPROF_E_PROFILER_NOT_ATTACHABLE) || (hr == E_NOTIMPL))) + { + _ASSERTE(g_profControlBlock.pProfInterface->IsCallback3Supported()); + + // Profiler supports ICorProfilerCallback3, but explicitly doesn't support + // Attach loading. So log specialized event + LogProfError(IDS_E_PROF_NOT_ATTACHABLE, wszClsid); + + // Normalize (CORPROF_E_PROFILER_NOT_ATTACHABLE || E_NOTIMPL) down to + // CORPROF_E_PROFILER_NOT_ATTACHABLE + hr = CORPROF_E_PROFILER_NOT_ATTACHABLE; + } + else if (hr == CORPROF_E_PROFILER_CANCEL_ACTIVATION) + { + // Profiler didn't encounter a bad error, but is voluntarily choosing not to + // profile this runtime. Profilers that need to set system environment + // variables to be able to profile services may use this HRESULT to avoid + // profiling all the other managed apps on the box. + LogProfInfo(IDS_PROF_CANCEL_ACTIVATION, wszClsid); + } + else + { + LogProfError(IDS_E_PROF_INIT_CALLBACK_FAILED, wszClsid, hr); + } + + // Profiler failed; reset everything. This will automatically reset + // g_profControlBlock and will unload the profiler's DLL. + TerminateProfiling(); + return hr; + } + +#ifdef FEATURE_MULTICOREJIT + + // Disable multicore JIT when profiling is enabled + if (g_profControlBlock.dwEventMask & COR_PRF_MONITOR_JIT_COMPILATION) + { + MulticoreJitManager::DisableMulticoreJit(); + } + +#endif + + // Indicate that profiling is properly initialized. On an attach-load, this will + // force a FlushStoreBuffers(), which is important for catch-up synchronization (see + // code:#ProfCatchUpSynchronization) + g_profControlBlock.curProfStatus.Set(kProfStatusActive); + + LOG(( + LF_CORPROF, + LL_INFO10, + "**PROF: Profiler successfully loaded and initialized.\n")); + + LogProfInfo(IDS_PROF_LOAD_COMPLETE, wszClsid); + + LOG((LF_CORPROF, LL_INFO10, "**PROF: Profiler created and enabled.\n")); + + if (loadType == kStartupLoad) + { + // For startup profilers only: If the profiler is interested in tracking GC + // events, then we must disable concurrent GC since concurrent GC can allocate + // and kill objects without relocating and thus not doing a heap walk. + if (CORProfilerTrackGC()) + { + LOG((LF_CORPROF, LL_INFO10, "**PROF: Turning off concurrent GC at startup.\n")); + g_pConfig->SetGCconcurrent(0); + LOG((LF_CORPROF, LL_INFO10, "**PROF: Concurrent GC has been turned off at startup.\n")); + } + } + + if (loadType == kAttachLoad) + { + // #ProfCatchUpSynchronization + // + // Now that callbacks are enabled (and all threads are aware), tell an attaching + // profiler that it's safe to request catchup information. + // + // There's a race we're preventing that's worthwhile to spell out. An attaching + // profiler should be able to get a COMPLETE set of data through the use of + // callbacks unioned with the use of catch-up enumeration Info functions. To + // achieve this, we must ensure that there is no "hole"--any new data the + // profiler seeks must be available from a callback or a catch-up info function + // (or both, as dupes are ok). That means that: + // + // * callbacks must be enabled on other threads NO LATER THAN the profiler begins + // requesting catch-up information on this thread + // * Abbreviate: callbacks <= catch-up. + // + // Otherwise, if catch-up < callbacks, then it would be possible to have this: + // + // * catch-up < new data arrives < callbacks. + // + // In this nightmare scenario, the new data would not be accessible from the + // catch-up calls made by the profiler (cuz the profiler made the calls too + // early) or the callbacks made into the profiler (cuz the callbacks were enabled + // too late). That's a hole, and that's bad. So we ensure callbacks <= catch-up + // by the following order of operations: + // + // * This thread: + // * a: Set (volatile) currentProfStatus = kProfStatusActive (done above) and + // event mask bits (profiler did this in Initialize() callback above, + // when it called SetEventMask) + // * b: Flush CPU buffers (done automatically when we set status to + // kProfStatusActive) + // * c: CLR->Profiler call: ProfilerAttachComplete() (below). Inside this + // call: + // * Profiler->CLR calls: Catch-up Info functions + // * Other threads: + // * a: New data (thread, JIT info, etc.) is created + // * b: This new data is now available to a catch-up Info call + // * c: currentProfStatus & event mask bits are accurately visible to thread + // in determining whether to make a callback + // * d: Read currentProfStatus & event mask bits and make callback + // (CLR->Profiler) if necessary + // + // So as long as OtherThreads.c <= ThisThread.c we're ok. This means other + // threads must be able to get a clean read of the (volatile) currentProfStatus & + // event mask bits BEFORE this thread calls ProfilerAttachComplete(). Use of the + // "volatile" keyword ensures that compiler optimizations and (w/ VC2005+ + // compilers) the CPU's instruction reordering optimizations at runtime are + // disabled enough such that they do not hinder the order above. Use of + // FlushStoreBuffers() ensures that multiple caches on multiple CPUs do not + // hinder the order above (by causing other threads to get stale reads of the + // volatiles). + // + // For more information about catch-up enumerations and exactly which entities, + // and which stage of loading, are permitted to appear in the enumerations, see + // code:ProfilerFunctionEnum::Init#ProfilerEnumGeneral + + { + BEGIN_PIN_PROFILER(CORProfilerPresent()); + g_profControlBlock.pProfInterface->ProfilerAttachComplete(); + END_PIN_PROFILER(); + } + } + return S_OK; +} + + +//--------------------------------------------------------------------------------------- +// +// This is the top-most level of profiling API teardown, and is called directly by +// EEShutDownHelper() (in ceemain.cpp). This cleans up internal structures relating to +// the Profiling API. If we're not in process teardown, then this also releases the +// profiler COM object and frees the profiler DLL +// + +// static +void ProfilingAPIUtility::TerminateProfiling() +{ + CONTRACTL + { + NOTHROW; + GC_TRIGGERS; + MODE_ANY; + CAN_TAKE_LOCK; + } + CONTRACTL_END; + + if (IsAtProcessExit()) + { + // We're tearing down the process so don't bother trying to clean everything up. + // There's no reliable way to verify other threads won't be trying to re-enter + // the profiler anyway, so cleaning up here could cause AVs. + return; + } + + _ASSERTE(s_csStatus != NULL); + { + // We're modifying status and possibly unloading the profiler DLL below, so + // serialize this code with any other loading / unloading / detaching code. + CRITSEC_Holder csh(s_csStatus); + + +#ifdef FEATURE_PROFAPI_ATTACH_DETACH + if (ProfilingAPIDetach::GetEEToProfPtr() != NULL) + { + // The profiler is still being referenced by + // ProfilingAPIDetach::s_profilerDetachInfo, so don't try to release and + // unload it. This can happen if Shutdown and Detach race, and Shutdown wins. + // For example, we could be called as part of Shutdown, but the profiler + // called RequestProfilerDetach near shutdown time as well (or even earlier + // but remains un-evacuated as shutdown begins). Whatever the cause, just + // don't unload the profiler here (as part of shutdown), and let the Detach + // Thread deal with it (if it gets the chance). + // + // Note: Since this check occurs inside s_csStatus, we don't have to worry + // that ProfilingAPIDetach::GetEEToProfPtr() will suddenly change during the + // code below. + // + // FUTURE: For reattach-with-neutered-profilers feature crew, change the + // above to scan through list of detaching profilers to make sure none of + // them give a GetEEToProfPtr() equal to g_profControlBlock.pProfInterface. + return; + } + + if (g_profControlBlock.curProfStatus.Get() == kProfStatusActive) + { + g_profControlBlock.curProfStatus.Set(kProfStatusDetaching); + + // Profiler was active when TerminateProfiling() was called, so we're unloading + // it due to shutdown. But other threads may still be trying to enter profiler + // callbacks (e.g., ClassUnloadStarted() can get called during shutdown). Now + // that the status has been changed to kProfStatusDetaching, no new threads will + // attempt to enter the profiler. But use the detach evacuation counters to see + // if other threads already began to enter the profiler. + if (!ProfilingAPIDetach::IsProfilerEvacuated()) + { + // Other threads might be entering the profiler, so just skip cleanup + return; + } + } +#endif // FEATURE_PROFAPI_ATTACH_DETACH + + // If we have a profiler callback wrapper and / or info implementation + // active, then terminate them. + + if (g_profControlBlock.pProfInterface.Load() != NULL) + { + // This destructor takes care of releasing the profiler's ICorProfilerCallback* + // interface, and unloading the DLL when we're not in process teardown. + delete g_profControlBlock.pProfInterface; + g_profControlBlock.pProfInterface.Store(NULL); + } + + // NOTE: Intentionally not deleting s_pSidBuffer. Doing so can cause annoying races + // with other threads that lazily create and initialize it when needed. (Example: + // it's used to fill out the "User" field of profiler event log entries.) Keeping + // s_pSidBuffer around after a profiler detaches and before a new one attaches + // consumes a bit more memory unnecessarily, but it'll get paged out if another + // profiler doesn't attach. + + // NOTE: Similarly, intentionally not destroying / NULLing s_csStatus. If + // s_csStatus is already initialized, we can reuse it each time we do another + // attach / detach, so no need to destroy it. + + // If we disabled concurrent GC and somehow failed later during the initialization + if (g_profControlBlock.fConcurrentGCDisabledForAttach) + { + // We know for sure GC has been fully initialized as we've turned off concurrent GC before + _ASSERTE(IsGarbageCollectorFullyInitialized()); + GCHeap::GetGCHeap()->TemporaryEnableConcurrentGC(); + g_profControlBlock.fConcurrentGCDisabledForAttach = FALSE; + } + + // #ProfileResetSessionStatus Reset all the status variables that are for the current + // profiling attach session. + // When you are adding new status in g_profControlBlock, you need to think about whether + // your new status is per-session, or consistent across sessions + g_profControlBlock.ResetPerSessionStatus(); + + g_profControlBlock.curProfStatus.Set(kProfStatusNone); + } +} + +#ifndef FEATURE_PAL + +// ---------------------------------------------------------------------------- +// ProfilingAPIUtility::GetCurrentProcessUserSid +// +// Description: +// Generates a SID of the current user from the current process's token. SID is +// returned in an [out] param, and is also cached for future use. The SID is used for +// two purposes: event log entries (for filling out the User field) and the ACL used +// on the globally named pipe object for attaching profilers. +// +// Arguments: +// * ppsid - [out] Generated (or cached) SID +// +// Return Value: +// HRESULT indicating success or failure. +// + +// static +HRESULT ProfilingAPIUtility::GetCurrentProcessUserSid(PSID * ppsid) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_ANY; + } + CONTRACTL_END; + + if (s_pSidBuffer == NULL) + { + HRESULT hr; + NewHolder<SidBuffer> pSidBuffer(new (nothrow) SidBuffer); + if (pSidBuffer == NULL) + { + return E_OUTOFMEMORY; + } + + // This gets the SID of the user from the process token + hr = pSidBuffer->InitFromProcessUserNoThrow(GetCurrentProcessId()); + if (FAILED(hr)) + { + return hr; + } + + if (FastInterlockCompareExchangePointer( + &s_pSidBuffer, + pSidBuffer.GetValue(), + NULL) == NULL) + { + // Lifetime successfully transferred to s_pSidBuffer, so don't delete it here + pSidBuffer.SuppressRelease(); + } + } + + _ASSERTE(s_pSidBuffer != NULL); + _ASSERTE(s_pSidBuffer->GetSid().RawSid() != NULL); + *ppsid = s_pSidBuffer->GetSid().RawSid(); + return S_OK; +} + +#endif // !FEATURE_PAL + +#endif // PROFILING_SUPPORTED |