summaryrefslogtreecommitdiff
path: root/src/vm/profattach.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/vm/profattach.cpp')
-rw-r--r--src/vm/profattach.cpp1336
1 files changed, 1336 insertions, 0 deletions
diff --git a/src/vm/profattach.cpp b/src/vm/profattach.cpp
new file mode 100644
index 0000000000..f03db361f0
--- /dev/null
+++ b/src/vm/profattach.cpp
@@ -0,0 +1,1336 @@
+// 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 <sddl.h> // Windows security descriptor language
+#include <SecurityUtil.h>
+#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(
+ VER_MAJORVERSION,
+ VER_MINORVERSION,
+ VER_PRODUCTBUILD,
+ VER_PRODUCTBUILD_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<BYTE> 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());
+
+ // If the CLR is being memory- or sync-hosted, then attach is not supported
+ // (see comments above)
+ if (CLRMemoryHosted() || CLRSyncHosted())
+ {
+ LOG((
+ LF_CORPROF,
+ LL_INFO10,
+ "**PROF: Process is running with a host that implements custom memory or "
+ "synchronization management. So it will not be possible to attach a "
+ "profiler to this process.\n"));
+
+ // NOTE: Intentionally not logging this to the event log, as it would be
+ // obnoxious to see such a message every time SQL started up
+
+ return S_FALSE;
+ }
+
+ 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,
+ GCHeap::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<PFN_GetAppContainerNamedObjectPath> 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>
+// 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<CLRProfilingImpl> 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<CLRProfilingClassFactoryImpl> 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