// 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. // // ProfAttach.cpp // // // Definitions of functions that help with attaching and detaching profilers // // ====================================================================================== #include "common.h" #ifdef FEATURE_PROFAPI_ATTACH_DETACH #include // Windows security descriptor language #include #include "eeprofinterfaces.h" #include "eetoprofinterfaceimpl.h" #include "corprof.h" #include "proftoeeinterfaceimpl.h" #include "proftoeeinterfaceimpl.inl" #include "profilinghelper.h" #include "profilinghelper.inl" #include "profattach.h" #include "profattach.inl" #include "securitywrapper.h" #include "profattachserver.h" #include "profattachserver.inl" #include "profattachclient.h" #include "profdetach.h" PSECURITY_DESCRIPTOR ProfilingAPIAttachDetach::s_pSecurityDescriptor = NULL; HANDLE ProfilingAPIAttachDetach::s_hAttachEvent = NULL; ProfilingAPIAttachDetach::AttachThreadingMode ProfilingAPIAttachDetach::s_attachThreadingMode = ProfilingAPIAttachDetach::kUninitialized; BOOL ProfilingAPIAttachDetach::s_fInitializeCalled = FALSE; // Both the trigger (via code:ProfilingAPIAttachClient) and the target profilee (via // code:ProfilingAPIAttachServer) use this constant to identify their own version. const VersionBlock ProfilingAPIAttachDetach::kCurrentProcessVersion( CLR_MAJOR_VERSION, CLR_MINOR_VERSION, CLR_BUILD_VERSION, CLR_BUILD_VERSION_QFE); // Note that the following two VersionBlocks are initialized with static numerals rather // than using the VER_* preproc defines, as we don't want these VersionBlocks to change // on us from version to version unless we explicitly make a choice to begin breaking // compatibility between triggers and profilees (and hopefully we won't need to do this // ever!). // A profilee compiled into this mscorwks.dll states that it can only interoperate with // triggers (i.e., AttachProfiler() implementations (pipe clients)) whose runtime version // is >= this constant. // // This value should not change as new runtimes are released unless // code:ProfilingAPIAttachServer is modified to accept newer requests or send newer // response messages in a way incompatible with older code:ProfilingAPIAttachClient // objects implementing AttachProfiler(). And that is generally discouraged anyway. const VersionBlock ProfilingAPIAttachDetach::kMinimumAllowableTriggerVersion( 4, 0, 0, 0); // An AttachProfiler() implementation compiled into this mscorwks.dll, and called within // a trigger process, can only interoperate with target profilee apps (pipe servers) // whose runtime version is >= this constant. // // This value should not change as new runtimes are released unless // code:ProfilingAPIAttachClient is modified to send newer request or interpret newer // response messages in a way incompatible with older code:ProfilingAPIAttachServer // objects implementing the pipe server. And that is generally discouraged anyway. const VersionBlock ProfilingAPIAttachDetach::kMinimumAllowableProfileeVersion( 4, 0, 0, 0); // ---------------------------------------------------------------------------- // ProfilingAPIAttachDetach::OverlappedResultHolder implementation. See // code:ProfilingAPIAttachDetach::OverlappedResultHolder for more information // // ---------------------------------------------------------------------------- // ProfilingAPIAttachDetach::OverlappedResultHolder::Initialize // // Description: // Call this first! This initializes the contained OVERLAPPED structure // // Return Value: // Returns E_OUTOFMEMORY if OVERLAPPED structure could not be allocated. // Else S_OK. // HRESULT ProfilingAPIAttachDetach::OverlappedResultHolder::Initialize() { CONTRACTL { NOTHROW; GC_NOTRIGGER; SO_INTOLERANT; MODE_ANY; } CONTRACTL_END; Assign(new (nothrow) OVERLAPPED); if (m_value == NULL) { return E_OUTOFMEMORY; } memset(m_value, 0, sizeof(OVERLAPPED)); return S_OK; } // ---------------------------------------------------------------------------- // ProfilingAPIAttachDetach::OverlappedResultHolder::Wait // // Description: // Uses the contained OVERLAPPED structure (pointed to by m_value) to call // WaitForSingleObject to wait for an overlapped read or write on the pipe to complete // (or timeout). // // Arguments: // * dwMillisecondsMax - [in] Timeout for the wait // * hPipe - [in] Handle to the pipe object carrying out the request (may be either a // server or client pipe handle). // * pcbReceived - [out] Number of bytes received from the overlapped request // // Return Value: // HRESULT indicating success or failure // // Assumptions: // * Must call code:ProfilingAPIAttachDetach::OverlappedResultHolder::Initialize first HRESULT ProfilingAPIAttachDetach::OverlappedResultHolder::Wait( DWORD dwMillisecondsMax, HANDLE hPipe, DWORD * pcbReceived) { CONTRACTL { THROWS; GC_TRIGGERS; MODE_PREEMPTIVE; CAN_TAKE_LOCK; } CONTRACTL_END; _ASSERTE(IsValidHandle(hPipe)); _ASSERTE(m_value != NULL); _ASSERTE(pcbReceived != NULL); HRESULT hr = E_UNEXPECTED; // Since the OVERLAPPED structure referenced by m_value contains a NULL event, the OS // will signal hPipe itself when the operation is complete switch (WaitForSingleObject(hPipe, dwMillisecondsMax)) { default: _ASSERTE(!"Unexpected return from WaitForSingleObject()"); hr = E_UNEXPECTED; break; case WAIT_FAILED: hr = HRESULT_FROM_GetLastError(); break; case WAIT_TIMEOUT: hr = HRESULT_FROM_WIN32(ERROR_TIMEOUT); break; case WAIT_OBJECT_0: // Operation finished in time. Get the results if (!GetOverlappedResult( hPipe, m_value, pcbReceived, TRUE)) // bWait: operation is done, so this returns immediately anyway { hr = HRESULT_FROM_GetLastError(); } else { hr = S_OK; } break; } // The gymnastics below are to ensure that Windows is done with the overlapped // structure, so we know it's safe to allow the base class (NewHolder) to free it // when the destructor is called. if (SUCCEEDED(hr)) { // Operation successful, so we're done with the OVERLAPPED structure pointed to // by m_value and may return return hr; } _ASSERTE(FAILED(hr)); // There was a failure waiting for or retrieving the result. Cancel the operation and // wait again for verification that the operation is completed or canceled. // Note that we're ignoring whether CancelIo succeeds or fails, as our action is the // same either way: Wait on the pipe again to verify that no active operation remains. CancelIo(hPipe); if (WaitForSingleObject(hPipe, dwMillisecondsMax) == WAIT_OBJECT_0) { // Typical case: The wait returns successfully and quickly, so we have // verification that the OVERLAPPED structured pointed to by m_value is done // being used. return hr; } // Atypical case: For all our trying, we're unable to force this request to end // before returning. Therefore, we're intentionally leaking the OVERLAPPED structured // pointed to by m_value, as Windows may write to it at a later time. SuppressRelease(); return hr; } // ---------------------------------------------------------------------------- // ProfilingAPIAttachDetach::ProfilingAPIAttachThreadStart // // Description: // Thread proc for AttachThread. Serves as simple try/catch wrapper around // ProfilingAPIAttachThreadMain // // Arguments: // * LPVOID thread proc param is ignored // // Return Value: // Just returns 0 always. // // static DWORD WINAPI ProfilingAPIAttachDetach::ProfilingAPIAttachThreadStart(LPVOID) { CONTRACTL { NOTHROW; GC_TRIGGERS; MODE_PREEMPTIVE; CAN_TAKE_LOCK; } CONTRACTL_END; // At start of this thread, set its type so SOS !threads and anyone else knows who we // are. ClrFlsSetThreadType(ThreadType_ProfAPI_Attach); LOG(( LF_CORPROF, LL_INFO10, "**PROF: AttachThread created and executing.\n")); // This try block is a last-ditch stop-gap to prevent an unhandled exception on the // AttachThread from bringing down the process. Note that if the unhandled // exception is a terminal one, then hey, sure, let's tear everything down. Also // note that any naughtiness in the profiler (e.g., throwing an exception from its // Initialize callback) should already be handled before we pop back to here, so this // is just being super paranoid. EX_TRY { // Don't care about return value, thread proc will just return 0 regardless ProfilingAPIAttachThreadMain(); } EX_CATCH { _ASSERTE(!"Unhandled exception on profiling API attach / detach thread"); } EX_END_CATCH(RethrowTerminalExceptions); LOG(( LF_CORPROF, LL_INFO10, "**PROF: AttachThread exiting.\n")); return 0; } // ---------------------------------------------------------------------------- // ProfilingAPIAttachDetach::ProfilingAPIAttachThreadMain // // Description: // Main code for AttachThread. Includes all attach functionality. // // Return Value: // S_OK if a profiler ever attached, error HRESULT otherwise // // static HRESULT ProfilingAPIAttachDetach::ProfilingAPIAttachThreadMain() { CONTRACTL { THROWS; GC_TRIGGERS; MODE_PREEMPTIVE; CAN_TAKE_LOCK; } CONTRACTL_END; HRESULT hr; ProfilingAPIAttachServer attachServer; hr = attachServer.ExecutePipeRequests(); if (FAILED(hr)) { // No profiler got attached, so we're done return hr; } // If we made it here, a profiler was successfully attached. It would be nice to be // able to assert g_profControlBlock.curProfStatus.Get() == kProfStatusActive, but // that's prone to a theoretical race: the profiler might have attached and detached // by the time we get here. return S_OK; } // ---------------------------------------------------------------------------- // ProfilingAPIAttachDetach::InitSecurityAttributes // // Description: // Initializes a SECURITY_ATTRIBUTES struct using the result of // code:ProfilingAPIAttachDetach::GetSecurityDescriptor // // Arguments: // * pSecAttrs - [in/out] SECURITY_ATTRIBUTES struct to initialize // * cbSecAttrs - Size in bytes of *pSecAttrs // // Return Value: // HRESULT indicating success or failure // // static HRESULT ProfilingAPIAttachDetach::InitSecurityAttributes( SECURITY_ATTRIBUTES * pSecAttrs, DWORD cbSecAttrs) { CONTRACTL { THROWS; GC_TRIGGERS; MODE_PREEMPTIVE; CAN_TAKE_LOCK; } CONTRACTL_END; PSECURITY_DESCRIPTOR psd = NULL; HRESULT hr = GetSecurityDescriptor(&psd); if (FAILED(hr)) { return hr; } _ASSERTE(psd != NULL); memset(pSecAttrs, 0, cbSecAttrs); pSecAttrs->nLength = cbSecAttrs; pSecAttrs->lpSecurityDescriptor = psd; pSecAttrs->bInheritHandle = FALSE; return S_OK; } //--------------------------------------------------------------------------------------- // // Helper function that gets the string (SDDL) form of the mandatory SID for this // process. This encodes the integrity level of the process for use in security // descriptors. The integrity level is capped at "high". See code:#HighGoodEnough. // // Arguments: // * pwszIntegritySidString - [out] On return will point to a buffer allocated by // Windows that contains the string representation of the SID. If // GetIntegritySidString succeeds, the caller is responsible for freeing // *pwszIntegritySidString via LocalFree(). // // Return Value: // HRESULT indicating success or failure. // // static HRESULT GetIntegritySidString(__out LPWSTR * pwszIntegritySidString) { CONTRACTL { NOTHROW; GC_NOTRIGGER; MODE_ANY; CANNOT_TAKE_LOCK; } CONTRACTL_END; HRESULT hr; _ASSERTE(pwszIntegritySidString != NULL); NewArrayHolder pbLabel; // This grabs the mandatory label SID of the current process. We will write this // SID into the security descriptor, to ensure that triggers of lower integrity // levels may NOT access the object... with one exception. See code:#HighGoodEnough hr = SecurityUtil::GetMandatoryLabelFromProcess(GetCurrentProcess(), &pbLabel); if (FAILED(hr)) { return hr; } TOKEN_MANDATORY_LABEL * ptml = (TOKEN_MANDATORY_LABEL *) pbLabel.GetValue(); // #HighGoodEnough: // The mandatory label SID we write into the security descriptor is the same as that // of the current process, with one exception. If the current process's integrity // level > high (e.g., ASP.NET running at "system" integrity level), then write // "high" into the security descriptor instead of the current process's actual // integrity level. This allows a high integrity trigger to access the object. This // implements the policy that a high integrity level is "good enough" to profile any // process, even if the target process is at an even higher integrity level than // "high". Why have this policy: // * A high integrity process represents an elevated admin, which morally equates // to a principal that should have complete control over the machine. This // includes debugging or profiling any process. // * According to a security expert dev on Windows, integrity level is not a // "security feature". It's mainly useful as defense-in-depth or to protect // IE users and admins from themselves in most cases. // * It's impossible to spawn a system integrity trigger process outside of // session 0 services. So profiling ASP.NET would be crazy hard without this // policy. DWORD * pdwIntegrityLevel = SecurityUtil::GetIntegrityLevelFromMandatorySID(ptml->Label.Sid); if (*pdwIntegrityLevel > SECURITY_MANDATORY_HIGH_RID) { *pdwIntegrityLevel = SECURITY_MANDATORY_HIGH_RID; } if (!ConvertSidToStringSid(ptml->Label.Sid, pwszIntegritySidString)) { return HRESULT_FROM_GetLastError(); } return S_OK; } // ---------------------------------------------------------------------------- // ProfilingAPIAttachDetach::GetSecurityDescriptor // // Description: // Generates a security descriptor based on an ACL containing (1) an ACE that allows // the current user read / write and (2) an ACE that allows admins read / write. // Resulting security descriptor is returned in an [out] param, and is also cached for // future use. // // Arguments: // * ppsd - [out] Generated (or cached) security descriptor // // Return Value: // HRESULT indicating success or failure. // // static HRESULT ProfilingAPIAttachDetach::GetSecurityDescriptor(PSECURITY_DESCRIPTOR * ppsd) { CONTRACTL { THROWS; GC_TRIGGERS; MODE_PREEMPTIVE; CAN_TAKE_LOCK; } CONTRACTL_END; _ASSERTE(ppsd != NULL); if (s_pSecurityDescriptor != NULL) { *ppsd = s_pSecurityDescriptor; return S_OK; } // Get the user SID for the DACL PSID psidUser = NULL; HRESULT hr = ProfilingAPIUtility::GetCurrentProcessUserSid(&psidUser); if (FAILED(hr)) { return hr; } WinAllocatedBlockHolder pvCurrentUserSidString; if (!ConvertSidToStringSid(psidUser, (LPWSTR *)(LPVOID *) &pvCurrentUserSidString)) { return HRESULT_FROM_GetLastError(); } // Get the integrity / mandatory SID for the SACL, if Vista+ LPCWSTR pwszIntegritySid = NULL; WinAllocatedBlockHolder pvIntegritySidString; hr = GetIntegritySidString((LPWSTR *) (LPVOID *) &pvIntegritySidString); if (FAILED(hr)) { return hr; } pwszIntegritySid = (LPCWSTR) pvIntegritySidString.GetValue(); ULONG cbsd; StackSString sddlSecurityDescriptor; WinAllocatedBlockHolder pvSecurityDescriptor; // The following API (ConvertStringSecurityDescriptorToSecurityDescriptorW) takes a // string representation of a security descriptor (using the SDDL language), and // returns back the security descriptor object to be used when defining the globally // named event or pipe object. For a description of this language, go to the help on // the API, and click on "string-format security descriptor": // http://msdn.microsoft.com/library/default.asp?url=/library/en-us/secauthz/security/security_descriptor_string_format.asp // or look through sddl.h. // Cheat sheet for the subset of the format that we're using: // // Security Descriptor string: // D:dacl_flags(string_ace1)(string_ace2)... (string_acen) // Security SACL string: // S:sacl_flags(string_ace1)(string_ace2)... (string_acen) // Each string_ace: // ace_type;ace_flags;rights;object_guid;inherit_object_guid;account_sid // // The following portions of the security descriptor string are NOT used: // O:owner_sid (b/c we want current user to be the owner) // G:group_sid (b/c not setting the primary group of the object) // This reusable chunk defines the "(string_ace)" portion of the DACL. Given // a SID, this makes an ACE for the SID with GENERIC_READ | GENERIC_WRITE access #define ACE_STRING(AccountSidString) \ \ SDDL_ACE_BEGIN \ \ /* ace_type: "A;" An "allow" DACL (not "deny") */ \ SDDL_ACCESS_ALLOWED SDDL_SEPERATOR \ \ /* (skipping ace_flags, so that no child auto-inherits from this object) */ \ SDDL_SEPERATOR \ \ /* rights: "GRGW": GENERIC_READ | GENERIC_WRITE access allowed */ \ SDDL_GENERIC_READ SDDL_GENERIC_WRITE SDDL_SEPERATOR \ \ /* (skipping object_guid) */ \ SDDL_SEPERATOR \ \ /* (skipping inherit_object_guid) */ \ SDDL_SEPERATOR \ \ /* account_sid (filled in by macro user) */ \ AccountSidString \ \ SDDL_ACE_END // First, construct the DACL sddlSecurityDescriptor.Printf( // "D:" This is a DACL SDDL_DACL SDDL_DELIMINATOR // dacl_flags: // "P" This is protected (i.e., don't allow security descriptor to be modified // by inheritable ACEs) SDDL_PROTECTED // (string_ace1) // account_sid: "BA" built-in local administrators group ACE_STRING(SDDL_BUILTIN_ADMINISTRATORS) // (string_ace2) // account_sid: to be filled in with the current process token's primary SID ACE_STRING(W("%s")), // current process token's primary SID (LPCWSTR) (LPVOID) pvCurrentUserSidString); // Next, add the SACL (Vista+ only) if (pwszIntegritySid != NULL) { sddlSecurityDescriptor.AppendPrintf( // "S:" This is a SACL -- for the integrity level of the current process SDDL_SACL SDDL_DELIMINATOR // The SACL ACE begins here SDDL_ACE_BEGIN // ace_type: "ML;" A Mandatory Label ACE (i.e., integrity level) SDDL_MANDATORY_LABEL SDDL_SEPERATOR // (skipping ace_flags, so that no child auto-inherits from this object) SDDL_SEPERATOR // rights: "NWNR;" If the trigger's integrity level is lower than the // integrity level we're writing into this security descriptor, then that // trigger may not read or write to this object. SDDL_NO_WRITE_UP SDDL_NO_READ_UP SDDL_SEPERATOR // (skipping object_guid) SDDL_SEPERATOR // (skipping inherit_object_guid) SDDL_SEPERATOR // To be filled in with the current process's mandatory label SID (which // describes the current process's integrity level, capped at "high integrity") W("%s") SDDL_ACE_END, // current process's mandatory label SID pwszIntegritySid); } if (!ConvertStringSecurityDescriptorToSecurityDescriptorW( sddlSecurityDescriptor.GetUnicode(), SDDL_REVISION_1, (PSECURITY_DESCRIPTOR *) (LPVOID *) &pvSecurityDescriptor, &cbsd)) { return HRESULT_FROM_GetLastError(); } if (FastInterlockCompareExchangePointer( &s_pSecurityDescriptor, (PSECURITY_DESCRIPTOR) pvSecurityDescriptor, NULL) == NULL) { // Ownership transferred to s_pSecurityDescriptor, so don't free it here pvSecurityDescriptor.SuppressRelease(); } _ASSERTE(s_pSecurityDescriptor != NULL); *ppsd = s_pSecurityDescriptor; return S_OK; } // ---------------------------------------------------------------------------- // ProfilingAPIAttachDetach::Initialize // // Description: // Perform startup (one-time-only) initialization for attach / detach infrastructure. // This includes the Global Attach Event, but does NOT include the Global Attach Pipe // (which is created only on demand). This is lazily called the first time the // finalizer asks for the attach event. // // Return Value: // S_OK: Attach / detach infrastructure initialized ok // S_FALSE: Attach / detach infrastructure not initialized, but for an acceptable reason // (e.g., executing memory- or sync- hosted) // else: error HRESULT indicating an unacceptable failure that prevented attach / // detach infrastructure from initializing (e.g., security problem, OOM, etc.) // // Assumptions: // * By the time this is called: // * Configuration must have been read from the registry // * If there is a host, it has already initialized its state, including its // intent to memory-host or sync-host. // * Finalizer thread is initializing and is first asking for the attach event. // // static HRESULT ProfilingAPIAttachDetach::Initialize() { CONTRACTL { THROWS; GC_TRIGGERS; MODE_PREEMPTIVE; CAN_TAKE_LOCK; } CONTRACTL_END; // This one assert verifies two things: // * 1. Configuration has been read from the registry, AND // * 2. If there is a host, it has already initialized its state. // #2 is implied by this assert, because the host initializes its state before // EEStartup is even called: Host directly calls CorHost2::SetHostControl to // initialize itself, announce whether the CLR will be memory hosted, sync hosted, // etc., and then host calls CorHost2::Start, which calls EEStartup, which // initializes configuration information. So if configuration information is // available, the host must have already initialized itself. // // The reason we care is that, for profiling API attach to be enabled during this // run, we need to have the finalizer thread wait on multiple sync objects. And // waiting on multiple objects is disallowed if we're memory / sync-hosted. So we // need to know now whether waiting on multiple objects is allowed, so we know // whether we can initialize the Attach support objects. _ASSERTE(g_pConfig != NULL); // Even if we fail to create the event, this BOOL indicates we at least // tried to. _ASSERTE(!s_fInitializeCalled); s_fInitializeCalled = TRUE; INDEBUG(VerifyMessageStructureLayout()); InitializeAttachThreadingMode(); if (s_attachThreadingMode == kOnDemand) { return InitializeForOnDemandMode(); } _ASSERTE(s_attachThreadingMode == kAlwaysOn); return InitializeForAlwaysOnMode(); } #ifdef _DEBUG // ---------------------------------------------------------------------------- // ProfilingAPIAttachDetach::VerifyMessageStructureLayout // // Description: // Debug-only function that asserts if there appear to be changes to structures that // are not allowed to change (for backward-compatibility reasons). In particular: // * VersionBlock must not change // * BaseRequestMessage must not change // // static void ProfilingAPIAttachDetach::VerifyMessageStructureLayout() { LIMITED_METHOD_CONTRACT; // If any of these asserts fire, then VersionBlock is changing its binary // layout in an incompatible way. Bad! _ASSERTE(sizeof(VersionBlock) == 16); _ASSERTE(offsetof(VersionBlock, m_dwMajor) == 0); _ASSERTE(offsetof(VersionBlock, m_dwMinor) == 4); _ASSERTE(offsetof(VersionBlock, m_dwBuild) == 8); _ASSERTE(offsetof(VersionBlock, m_dwQFE) == 12); // If any of these asserts fire, then GetVersionRequestMessage is changing its binary // layout in an incompatible way. Bad! _ASSERTE(sizeof(GetVersionRequestMessage) == 8); _ASSERTE(offsetof(GetVersionRequestMessage, m_cbMessage) == 0); _ASSERTE(offsetof(GetVersionRequestMessage, m_requestMessageType) == 4); // If any of these asserts fire, then GetVersionResponseMessage is changing its binary // layout in an incompatible way. Bad! _ASSERTE(sizeof(GetVersionResponseMessage) == 36); _ASSERTE(offsetof(GetVersionResponseMessage, m_hr) == 0); _ASSERTE(offsetof(GetVersionResponseMessage, m_profileeVersion) == 4); _ASSERTE(offsetof(GetVersionResponseMessage, m_minimumAllowableTriggerVersion) == 20); } #endif //_DEBUG // ---------------------------------------------------------------------------- // ProfilingAPIAttachDetach::InitializeAttachThreadingMode // // Description: // Looks at environment and GC mode to determine whether the AttachThread should // always be around, or created only on demand. See // code:ProfilingAPIAttachDetach::AttachThreadingMode. // // static void ProfilingAPIAttachDetach::InitializeAttachThreadingMode() { CONTRACTL { THROWS; GC_TRIGGERS; MODE_PREEMPTIVE; CAN_TAKE_LOCK; } CONTRACTL_END; _ASSERTE(s_attachThreadingMode == kUninitialized); // Environment variable trumps all, so check it first DWORD dwAlwaysOn = g_pConfig->GetConfigDWORD_DontUse_( CLRConfig::EXTERNAL_AttachThreadAlwaysOn, GCHeapUtilities::IsServerHeap() ? 1 : 0); // Default depends on GC server mode if (dwAlwaysOn == 0) { s_attachThreadingMode = kOnDemand; } else { s_attachThreadingMode = kAlwaysOn; } } // ---------------------------------------------------------------------------- // ProfilingAPIAttachDetach::InitializeForAlwaysOnMode // // Description: // Performs initialization specific to running in Always On mode. Specifically, this // means creating the AttachThread. The attach event is not created in this case. // // Return Value: // HRESULT indicating success or failure. // // static HRESULT ProfilingAPIAttachDetach::InitializeForAlwaysOnMode() { CONTRACTL { THROWS; GC_TRIGGERS; MODE_PREEMPTIVE; CAN_TAKE_LOCK; } CONTRACTL_END; _ASSERTE(s_attachThreadingMode == kAlwaysOn); LOG((LF_CORPROF, LL_INFO10, "**PROF: Attach AlwaysOn mode invoked; creating new AttachThread.\n")); CreateAttachThread(); return S_OK; } // ---------------------------------------------------------------------------- // ProfilingAPIAttachDetach::InitializeForOnDemandMode // // Description: // Performs initialization specific to running in On Demand mode. Specifically, this // means creating the attach event. (The AttachThread will only be created when this // event is signaled by a trigger process.) // // Return Value: // HRESULT indicating success or failure. // // static HRESULT ProfilingAPIAttachDetach::InitializeForOnDemandMode() { CONTRACTL { THROWS; GC_TRIGGERS; MODE_PREEMPTIVE; CAN_TAKE_LOCK; } CONTRACTL_END; _ASSERTE(s_attachThreadingMode == kOnDemand); LOG((LF_CORPROF, LL_INFO10, "**PROF: Attach OnDemand mode invoked; creating attach event.\n")); // The only part of attach that gets initialized before a profiler has // actually requested to attach is the single global event that gets // signaled from out-of-process. StackSString attachEventName; HRESULT hr; hr = GetAttachEventName(::GetCurrentProcess(), &attachEventName); if (FAILED(hr)) { return hr; } // Deliberately NOT using CLREvent, as it does not have support for a global name. // It's ok not to use CLREvent, as we're assured above that we're not sync-hosted, // which means CLREvent would just use raw Windows events anyway. SECURITY_ATTRIBUTES *psa = NULL; SECURITY_ATTRIBUTES sa; // Only assign security attributes for non-app container scenario // We are assuming the default (blocking everything for app container scenario is good enough if (!IsAppContainerProcess(::GetCurrentProcess())) { hr = InitSecurityAttributes(&sa, sizeof(sa)); if (FAILED(hr)) { return hr; } psa = &sa; } _ASSERTE(s_hAttachEvent == NULL); s_hAttachEvent = WszCreateEvent( psa, // security attributes FALSE, // bManualReset = FALSE: autoreset after waiting thread is unblocked FALSE, // initial state = FALSE, i.e., unsignaled attachEventName.GetUnicode() // Global name seen out-of-proc ); if (s_hAttachEvent == NULL) { return HRESULT_FROM_GetLastError(); } return S_OK; } // ---------------------------------------------------------------------------- // ProfilingAPIAttachDetach::GetAttachEvent // // Description: // Used by finalizer thread to get the profiling API attach event. First time this is // called, the event and other supporting objects will be created. // // Return Value: // The attach event or NULL if attach event creation failed during startup. In either // case, do NOT call CloseHandle on the returned event handle. // // Assumptions: // * ProfilingAPIUtility::InitializeProfiling should already have been called before // this is called. That ensures that, if a profiler was configured to load on // startup, then that load has already occurred by now. // * The event's HANDLE refcount is managed solely by ProfilingAPIAttachDetach. So do // not call CloseHandle() on the HANDLE returned. // // Notes: // * If the attach event was not created on startup, then this will return NULL. // Possible reasons why this can occur: // * The current process is the NGEN service, OR // * The process is sync- or memory- hosted, OR // * Attach is running in "always on" mode, meaning we always have an AttachThread // with a pipe, so there's no need for an event. // // static HANDLE ProfilingAPIAttachDetach::GetAttachEvent() { CONTRACTL { THROWS; GC_TRIGGERS; MODE_PREEMPTIVE; CAN_TAKE_LOCK; } CONTRACTL_END; if (IsCompilationProcess()) { // No profiler attach on NGEN! return NULL; } if (!s_fInitializeCalled) { // If a profiler was supposed to load on startup, it's already happened // now. So it's safe to set up the attach support objects, and allow // an attaching profiler to make an attempt (which can now gracefully fail // if a startup profiler has loaded). HRESULT hr = Initialize(); if (FAILED(hr)) { LOG(( LF_CORPROF, LL_ERROR, "**PROF: ProfilingAPIAttachDetach::Initialize failed, so this process will not " "be able to attach a profiler. hr=0x%x.\n", hr)); ProfilingAPIUtility::LogProfError(IDS_E_PROF_ATTACH_INIT, hr); return NULL; } } if (s_attachThreadingMode == kAlwaysOn) { // In always-on mode, we always have an AttachThread listening on the pipe, so // there's no need for an event. _ASSERTE(s_hAttachEvent == NULL); } return s_hAttachEvent; } // ---------------------------------------------------------------------------- // ProfilingAPIAttachDetach::ProcessSignaledAttachEvent // // Description: // Called by finalizer thread when the finalizer thread detects that the globally // named Profiler Attach Event is signaled. This simply spins up the AttachThread // (starting in ProfilingAPIAttachThreadStart) and returns. // // static void ProfilingAPIAttachDetach::ProcessSignaledAttachEvent() { // This function is practically a leaf (though not quite), and is called from the // finalizer thread at various points, so keeping the contract strict to allow for // maximum flexibility on when this may called. CONTRACTL { NOTHROW; GC_NOTRIGGER; MODE_ANY; CANNOT_TAKE_LOCK; } CONTRACTL_END; LOG((LF_CORPROF, LL_INFO10, "**PROF: Attach event signaled; creating new AttachThread.\n")); CreateAttachThread(); } typedef BOOL (WINAPI *PFN_GetAppContainerNamedObjectPath)( HANDLE Token, PSID AppContainerSid, ULONG ObjectPathLength, WCHAR * ObjectPath, PULONG ReturnLength ); static Volatile g_pfnGetAppContainerNamedObjectPath = NULL; // ---------------------------------------------------------------------------- // GetAppContainerNamedObjectPath // // Description: // Retrieve named object path for the specified app container process // The name looks something like the following: // LowBoxNamedObjects\ // AppContainer_SID is the SID for the app container, for example: S-1-15-2-3-4-5-6-7-8 // // Arguments: // * hProcess - handle of the app container proces // * wszObjectPath - [out] Buffer to fill in // * dwObjectPathSizeInChar - Size of buffer // HRESULT ProfilingAPIAttachDetach::GetAppContainerNamedObjectPath(HANDLE hProcess, __out_ecount(dwObjectPathSizeInChar) WCHAR * wszObjectPath, DWORD dwObjectPathSizeInChar) { LIMITED_METHOD_CONTRACT; _ASSERTE(wszObjectPath != NULL); HandleHolder hToken; if (!OpenProcessToken(hProcess, TOKEN_QUERY, &hToken)) { return HRESULT_FROM_GetLastError(); } if (g_pfnGetAppContainerNamedObjectPath.Load() == NULL) { HMODULE hMod = WszGetModuleHandle(W("kernel32.dll")); if (hMod == NULL) { // This should never happen but I'm checking it anyway return HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND); } PFN_GetAppContainerNamedObjectPath pfnGetAppContainerNamedObjectPath = (PFN_GetAppContainerNamedObjectPath) ::GetProcAddress( hMod, "GetAppContainerNamedObjectPath"); if (!pfnGetAppContainerNamedObjectPath) { return HRESULT_FROM_GetLastError(); } // We should always get the same address back from GetProcAddress so there is no concern for race condition g_pfnGetAppContainerNamedObjectPath = pfnGetAppContainerNamedObjectPath; } DWORD dwBufferLength; if (!g_pfnGetAppContainerNamedObjectPath( hToken, // Process token NULL, // AppContainer package SID optional. dwObjectPathSizeInChar, // Object path length wszObjectPath, // Object path &dwBufferLength // return length )) { return HRESULT_FROM_GetLastError(); } return S_OK; } // @TODO: Update this once Windows header file is updated to Win8 #ifndef TokenIsAppContainer #define TokenIsAppContainer ((TOKEN_INFORMATION_CLASS) 29) #endif // ---------------------------------------------------------------------------- // ProfilingAPIAttachDetach::IsAppContainerProcess // // Description: // Return whether the specified process is a app container process // // static BOOL ProfilingAPIAttachDetach::IsAppContainerProcess(HANDLE hProcess) { CONTRACTL { NOTHROW; GC_NOTRIGGER; MODE_ANY; CANNOT_TAKE_LOCK; } CONTRACTL_END; HandleHolder hToken; if(!::OpenProcessToken(hProcess, TOKEN_QUERY, &hToken)) { return FALSE; } BOOL fIsAppContainerProcess; DWORD dwReturnLength; if (!::GetTokenInformation( hToken, TokenIsAppContainer, &fIsAppContainerProcess, sizeof(BOOL), &dwReturnLength) || dwReturnLength != sizeof(BOOL)) { return FALSE; } else { return fIsAppContainerProcess; } } //--------------------------------------------------------------------------------------- // // Called by other points in the runtime (e.g., finalizer thread) to create a new thread // to fill the role of the AttachThread. // // static void ProfilingAPIAttachDetach::CreateAttachThread() { // This function is practically a leaf (though not quite), and is called from the // finalizer thread at various points, so keeping the contract strict to allow for // maximum flexibility on when this may called. CONTRACTL { NOTHROW; GC_NOTRIGGER; MODE_ANY; CANNOT_TAKE_LOCK; } CONTRACTL_END; HandleHolder hAttachThread; // The AttachThread is intentionally not an EE Thread-object thread hAttachThread = ::CreateThread( NULL, // lpThreadAttributes; don't want child processes inheriting this handle 0, // dwStackSize (0 = use default) ProfilingAPIAttachThreadStart, NULL, // lpParameter (none to pass) 0, // dwCreationFlags (0 = use default flags, start thread immediately) NULL // lpThreadId (don't need therad ID) ); if (hAttachThread == NULL) { LOG(( LF_CORPROF, LL_ERROR, "**PROF: Failed to create AttachThread. GetLastError=%d.\n", GetLastError())); // No other error-specific code really makes much sense here. An error here is // probably due to serious OOM issues which would also probably prevent logging // an event. A trigger process will report that it waited for the pipe to be // created, and timed out during the wait. That should be enough for the user. } } // ---------------------------------------------------------------------------- // CLRProfilingClassFactoryImpl::CreateInstance // // Description: // A standard IClassFactory interface function to allow a profiling trigger // to query for IID_ICLRProfiling interface // HRESULT CLRProfilingClassFactoryImpl::CreateInstance(IUnknown * pUnkOuter, REFIID riid, void ** ppv) { CONTRACTL { NOTHROW; GC_NOTRIGGER; MODE_PREEMPTIVE; SO_NOT_MAINLINE; } CONTRACTL_END; if (ppv == NULL) return E_POINTER; *ppv = NULL; NewHolder pProfilingImpl = new (nothrow) CLRProfilingImpl(); if (pProfilingImpl == NULL) return E_OUTOFMEMORY; HRESULT hr = pProfilingImpl->QueryInterface(riid, ppv); if (SUCCEEDED(hr)) { pProfilingImpl.SuppressRelease(); } return hr; } // ---------------------------------------------------------------------------- // CLRProfilingClassFactoryImpl::LockServer // // Description: // A standard IClassFactory interface function that doesn't do anything interesting here // HRESULT CLRProfilingClassFactoryImpl::LockServer(BOOL fLock) { LIMITED_METHOD_CONTRACT; return S_OK; } // ---------------------------------------------------------------------------- // CLRProfilingImpl::AttachProfiler // // Description: // A wrapper COM function to invoke AttachProfiler with parameters from // profiling trigger along with a runtime version string // HRESULT CLRProfilingImpl::AttachProfiler(DWORD dwProfileeProcessID, DWORD dwMillisecondsMax, const CLSID *pClsidProfiler, LPCWSTR wszProfilerPath, void *pvClientData, UINT cbClientData) { CONTRACTL { NOTHROW; GC_TRIGGERS; MODE_PREEMPTIVE; CAN_TAKE_LOCK; SO_NOT_MAINLINE; } CONTRACTL_END; WCHAR wszRuntimeVersion[MAX_PATH_FNAME]; DWORD dwSize = _countof(wszRuntimeVersion); HRESULT hr = GetCORVersionInternal(wszRuntimeVersion, dwSize, &dwSize); if (FAILED(hr)) return hr; return ::AttachProfiler(dwProfileeProcessID, dwMillisecondsMax, pClsidProfiler, wszProfilerPath, pvClientData, cbClientData, wszRuntimeVersion); } // ---------------------------------------------------------------------------- // ICLRProfilingGetClassObject // // Description: // A wrapper to create a CLRProfilingImpl object and to QueryInterface on the CLRProfilingImpl object // HRESULT ICLRProfilingGetClassObject(REFCLSID rclsid, REFIID riid, void **ppv) { CONTRACTL { NOTHROW; GC_NOTRIGGER; MODE_PREEMPTIVE; SO_NOT_MAINLINE; PRECONDITION(rclsid == CLSID_CLRProfiling); } CONTRACTL_END; if (ppv == NULL) return E_POINTER; *ppv = NULL; NewHolder pCLRProfilingClassFactoryImpl = new (nothrow) CLRProfilingClassFactoryImpl(); if (pCLRProfilingClassFactoryImpl == NULL) return E_OUTOFMEMORY; HRESULT hr = pCLRProfilingClassFactoryImpl->QueryInterface(riid, ppv); if (SUCCEEDED(hr)) { pCLRProfilingClassFactoryImpl.SuppressRelease(); } return hr; } #endif // FEATURE_PROFAPI_ATTACH_DETACH