summaryrefslogtreecommitdiff
path: root/src/vm/profattachclient.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/vm/profattachclient.cpp')
-rw-r--r--src/vm/profattachclient.cpp948
1 files changed, 948 insertions, 0 deletions
diff --git a/src/vm/profattachclient.cpp b/src/vm/profattachclient.cpp
new file mode 100644
index 0000000000..22e16fb207
--- /dev/null
+++ b/src/vm/profattachclient.cpp
@@ -0,0 +1,948 @@
+// 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.
+//
+// ProfAttachClient.cpp
+//
+
+//
+// Implementation of the AttachProfiler() API, used by CLRProfilingImpl::AttachProfiler.
+//
+// CLRProfilingImpl::AttachProfiler (in ndp\clr\src\DLLS\shim\shimapi.cpp) just thunks down
+// to mscorwks!AttachProfiler (below), which calls other functions in this file, all of
+// which are in mscorwks.dll. The AttachProfiler() API is consumed by trigger processes
+// in order to force the runtime of a target process to load a profiler. The prime
+// portion of this implementation lives in ProfilingAPIAttachClient, which handles
+// opening a client connection to the pipe created by the target profilee, and sending
+// requests across that pipe to force the target profilee (which acts as the pipe server)
+// to attach a profiler.
+
+//
+// Since these functions are executed by the trigger process, they intentionally seek the
+// event and pipe objects by names based on the PID of the target app to profile (which
+// is NOT the PID of the current process, as the current process is just the trigger
+// process). This implies, for example, that the variable
+// ProfilingAPIAttachDetach::s_hAttachEvent is of no use to the current process, as
+// s_hAttachEvent is only applicable to the target profilee app's process.
+//
+// Most of the contracts in this file follow the lead of default contracts throughout the
+// CLR (triggers, throws, etc.). Since AttachProfiler() is called by native code either
+// on a native thread created by the trigger process, or via a P/Invoke, these functions
+// will all run on threads in MODE_PREEMPTIVE.
+// * MODE_PREEMPTIVE also allows for GetThread() == NULL, which will be the case for
+// a native-only thread calling AttachProfiler()
+//
+
+// ======================================================================================
+
+#include "common.h"
+
+#ifdef FEATURE_PROFAPI_ATTACH_DETACH
+#include "tlhelp32.h" // For CreateToolhelp32Snapshot, etc. in MightProcessExist()
+#include "profilinghelper.h"
+#include "profattach.h"
+#include "profattach.inl"
+#include "profattachclient.h"
+
+// CLRProfilingImpl::AttachProfiler calls this, which itself is just a simple wrapper around
+// code:ProfilingAPIAttachClient::AttachProfiler. See public documentation for a
+// description of the parameters, return value, etc.
+extern "C" HRESULT STDMETHODCALLTYPE AttachProfiler(
+ DWORD dwProfileeProcessID,
+ DWORD dwMillisecondsMax,
+ const CLSID * pClsidProfiler,
+ LPCWSTR wszProfilerPath,
+ void * pvClientData,
+ UINT cbClientData,
+ LPCWSTR wszRuntimeVersion)
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_TRIGGERS;
+ MODE_PREEMPTIVE;
+ CAN_TAKE_LOCK;
+
+ // This is the entrypoint into the EE by a trigger process. As such, this
+ // is profiling-specific and not considered mainline EE code.
+ SO_NOT_MAINLINE;
+ }
+ CONTRACTL_END;
+
+ HRESULT hr = E_UNEXPECTED;
+
+ EX_TRY
+ {
+ ProfilingAPIAttachClient attachClient;
+ hr = attachClient.AttachProfiler(
+ dwProfileeProcessID,
+ dwMillisecondsMax,
+ pClsidProfiler,
+ wszProfilerPath,
+ pvClientData,
+ cbClientData,
+ wszRuntimeVersion);
+ }
+ EX_CATCH
+ {
+ hr = GET_EXCEPTION()->GetHR();
+ _ASSERTE(!"Unhandled exception executing AttachProfiler API");
+ }
+ EX_END_CATCH(RethrowTerminalExceptions);
+
+ // For ease-of-use by profilers, normalize similar HRESULTs down.
+ if ((hr == HRESULT_FROM_WIN32(ERROR_BROKEN_PIPE)) ||
+ (hr == HRESULT_FROM_WIN32(ERROR_PIPE_NOT_CONNECTED)) ||
+ (hr == HRESULT_FROM_WIN32(ERROR_BAD_PIPE)))
+ {
+ hr = CORPROF_E_IPC_FAILED;
+ }
+
+ return hr;
+}
+
+
+// ----------------------------------------------------------------------------
+// AdjustRemainingMs
+//
+// Description:
+// Simple helper to do timeout arithmetic. Timeout arithmetic is based on
+// CLRGetTickCount64, which returns an unsigned 64-bit int representing the number of
+// milliseconds transpired since the machine has been up. Since a machine is unlikely
+// to be up for > 500 million years, wraparound issues may be ignored.
+//
+// Caller repeatedly calls this function (usually once before a lenghty operation
+// with a timeout) to check on its remaining time allotment and get alerted when time
+// runs out.
+//
+// Arguments:
+// * ui64StartTimeMs - [in] When did caller begin, in tick counts (ms)?
+// * dwMillisecondsMax - [in] How much time does caller have, total?
+// * pdwMillisecondsRemaining - [out] Remaining ms caller has before exceeding its
+// timeout.
+//
+// Return Value:
+// HRESULT_FROM_WIN32(ERROR_TIMEOUT) if caller is out of time; else S_OK
+//
+
+static HRESULT AdjustRemainingMs(
+ ULONGLONG ui64StartTimeMs,
+ DWORD dwMillisecondsMax,
+ DWORD * pdwMillisecondsRemaining)
+{
+ CONTRACTL
+ {
+ THROWS;
+ GC_TRIGGERS;
+ MODE_PREEMPTIVE;
+ CAN_TAKE_LOCK;
+ }
+ CONTRACTL_END;
+
+ _ASSERTE(pdwMillisecondsRemaining != NULL);
+
+ ULONGLONG ui64NowMs = CLRGetTickCount64();
+
+ if (ui64NowMs - ui64StartTimeMs > dwMillisecondsMax)
+ {
+ // Out of time!
+ return HRESULT_FROM_WIN32(ERROR_TIMEOUT);
+ }
+
+ // How much of dwMillisecondsMax remain to be used?
+ *pdwMillisecondsRemaining = dwMillisecondsMax - static_cast<DWORD>(ui64NowMs - ui64StartTimeMs);
+ return S_OK;
+}
+
+
+// ----------------------------------------------------------------------------
+// ProfilingAPIAttachClient::AttachProfiler
+//
+// Description:
+// Main worker for AttachProfiler API. Trigger process calls mscoree!AttachProfiler
+// which just defers to this function to do all the work.
+//
+// ** See public API docs for description of params / return value. **
+//
+// Note that, in the trigger process, the dwMillisecondsMax timeouts are cumulative:
+// the caller specifies a single timeout value for the entire AttachProfiler API call.
+// So we must constantly adjust the timeouts we use so they're based on the time
+// remaining from the original dwMillisecondsMax specified by the AttachProfiler API
+// client.
+//
+
+HRESULT ProfilingAPIAttachClient::AttachProfiler(
+ DWORD dwProfileeProcessID,
+ DWORD dwMillisecondsMax,
+ const CLSID * pClsidProfiler,
+ LPCWSTR wszProfilerPath,
+ void * pvClientData,
+ UINT cbClientData,
+ LPCWSTR wszRuntimeVersion)
+{
+ CONTRACTL
+ {
+ THROWS;
+ GC_TRIGGERS;
+ MODE_PREEMPTIVE;
+ CAN_TAKE_LOCK;
+ }
+ CONTRACTL_END;
+
+ InitializeLogging();
+
+ HRESULT hr;
+
+ // Is cbClientData just crazy-sick-overflow big?
+ if (cbClientData >= 0xFFFFffffUL - sizeof(AttachRequestMessage))
+ {
+ return E_OUTOFMEMORY;
+ }
+
+ if ((pvClientData == NULL) && (cbClientData != 0))
+ {
+ return E_INVALIDARG;
+ }
+
+ if (pClsidProfiler == NULL)
+ {
+ return E_INVALIDARG;
+ }
+
+ if ((wszProfilerPath != NULL) && (wcslen(wszProfilerPath) >= MAX_LONGPATH))
+ {
+ return E_INVALIDARG;
+ }
+
+ // See if we can early-out due to the profilee process ID not existing.
+ // MightProcessExist() only returns FALSE if it has positively verified the process
+ // ID didn't exist when MightProcessExist() was called. So it might incorrectly
+ // return TRUE (if it hit an error trying to determine whether the process exists).
+ // But that's ok, as we'll catch a nonexistent process later on when we try to fiddle
+ // with its event & pipe. MightProcessExist() is used strictly as an optional
+ // optimization to early-out before waiting for the event to appear.
+ if (!MightProcessExist(dwProfileeProcessID))
+ {
+ return CORPROF_E_PROFILEE_PROCESS_NOT_FOUND;
+ }
+
+ // Adjust time out value according to env var COMPlus_ProfAPI_AttachProfilerTimeoutInMs
+ // The default is 10 seconds as we want to avoid client (trigger process) time out too early
+ // due to wait operation for concurrent GC in the server (profilee side)
+ DWORD dwMillisecondsMinFromEnv = CLRConfig::GetConfigValue(CLRConfig::EXTERNAL_ProfAPI_AttachProfilerMinTimeoutInMs);
+
+ if (dwMillisecondsMax < dwMillisecondsMinFromEnv)
+ dwMillisecondsMax = dwMillisecondsMinFromEnv;
+
+#ifdef _DEBUG
+ {
+ WCHAR wszClsidProfiler[40];
+ if (!StringFromGUID2(*pClsidProfiler, wszClsidProfiler, _countof(wszClsidProfiler)))
+ {
+ wcscpy_s(&wszClsidProfiler[0], _countof(wszClsidProfiler), W("(error)"));
+ }
+ LOG((
+ LF_CORPROF,
+ LL_INFO10,
+ "**PROF TRIGGER: mscorwks!AttachProfiler invoked with Trigger Process ID: '%d', "
+ "Target Profilee Process ID: '%d', dwMillisecondsMax: '%d', pClsidProfiler: '%S',"
+ "wszProfilerPath: '%S'\n",
+ GetProcessId(GetCurrentProcess()),
+ dwProfileeProcessID,
+ dwMillisecondsMax,
+ wszClsidProfiler,
+ wszProfilerPath == NULL ? W("") : wszProfilerPath));
+ }
+#endif // _DEBUG
+
+ // See code:AdjustRemainingMs
+ ULONGLONG ui64StartTimeMs = CLRGetTickCount64();
+ DWORD dwMillisecondsRemaining = dwMillisecondsMax;
+
+ HandleHolder hProfileeProcess = ::OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, dwProfileeProcessID);
+ if (!hProfileeProcess)
+ {
+ LOG((
+ LF_CORPROF,
+ LL_ERROR,
+ "**PROF TRIGGER: OpenProcess failed. LastError=0x%x.\n",
+ ::GetLastError()));
+ return HRESULT_FROM_GetLastError();
+ }
+
+ StackSString attachPipeName;
+ ProfilingAPIAttachDetach::GetAttachPipeNameForPidAndVersion(hProfileeProcess, wszRuntimeVersion, &attachPipeName);
+
+ // Try to open pipe with 0ms timeout in case the pipe is still around from
+ // a previous attach request
+ hr = OpenPipeClient(attachPipeName.GetUnicode(), 0);
+ if (hr == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND))
+ {
+ // Pipe doesn't exist, so signal attach event and retry. Note that any other
+ // failure from the above OpenPipeClient call will NOT cause us to signal
+ // the attach event, as signaling the attach event can only help with making
+ // sure the pipe gets created, and nothing else.
+ StackSString attachEventName;
+ ProfilingAPIAttachDetach::GetAttachEventNameForPidAndVersion(hProfileeProcess, wszRuntimeVersion, &attachEventName);
+ hr = SignalAttachEvent(attachEventName.GetUnicode());
+ if (FAILED(hr))
+ {
+ LOG((
+ LF_CORPROF,
+ LL_ERROR,
+ "**PROF TRIGGER: Unable to signal the global attach event. hr=0x%x.\n",
+ hr));
+
+ // It's reasonable for SignalAttachEvent to err out if the event
+ // simply doesn't exist. This happens on server apps that just circumvent
+ // using an event. They just create the AttachThread and attach pipe on
+ // startup, and are always listening on the pipe. So if event signaling
+ // failed due to nonexistent event, keep on going and try connecting to the
+ // pipe again. But if event signaling failed for any other reason, that's
+ // unexpected so give up.
+ if (hr != HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND))
+ {
+ return hr;
+ }
+ }
+
+ hr = AdjustRemainingMs(ui64StartTimeMs, dwMillisecondsMax, &dwMillisecondsRemaining);
+ if (FAILED(hr))
+ {
+ return hr;
+ }
+
+ hr = OpenPipeClient(attachPipeName.GetUnicode(), dwMillisecondsRemaining);
+ }
+
+ // hr now holds the result of either the original OpenPipeClient call (if it
+ // failed for a reason other than ERROR_FILE_NOT_FOUND) or the 2nd
+ // OpenPipeClient call (if the first call yielded ERROR_FILE_NOT_FOUND and we
+ // signaled the event and retried).
+ if (FAILED(hr))
+ {
+ LOG((
+ LF_CORPROF,
+ LL_ERROR,
+ "**PROF TRIGGER: Unable to open a client connection to the pipe. hr=0x%x.\n",
+ hr));
+ return hr;
+ }
+
+ // At this point the pipe is definitely open
+ _ASSERTE(IsValidHandle(m_hPipeClient));
+
+ hr = AdjustRemainingMs(ui64StartTimeMs, dwMillisecondsMax, &dwMillisecondsRemaining);
+ if (FAILED(hr))
+ {
+ return hr;
+ }
+
+ // Send the GetVersion message and verify we're talking the same language
+ hr = VerifyVersionIsCompatible(dwMillisecondsRemaining);
+ if (FAILED(hr))
+ {
+ return hr;
+ }
+
+ hr = AdjustRemainingMs(ui64StartTimeMs, dwMillisecondsMax, &dwMillisecondsRemaining);
+ if (FAILED(hr))
+ {
+ return hr;
+ }
+
+ // Send the attach message!
+ HRESULT hrAttach;
+ hr = SendAttachRequest(
+ dwMillisecondsRemaining,
+ pClsidProfiler,
+ wszProfilerPath,
+ pvClientData,
+ cbClientData,
+ &hrAttach);
+ if (FAILED(hr))
+ {
+ return hr;
+ }
+
+ LOG((
+ LF_CORPROF,
+ LL_INFO10,
+ "**PROF TRIGGER: AttachProfiler succeeded sending attach request. Trigger Process ID: '%d', "
+ "Target Profilee Process ID: '%d', Attach HRESULT: '0x%x'\n",
+ GetProcessId(GetCurrentProcess()),
+ dwProfileeProcessID,
+ hrAttach));
+
+ return hrAttach;
+}
+
+// ----------------------------------------------------------------------------
+// ProfilingAPIAttachClient::MightProcessExist
+//
+// Description:
+// Returns BOOL indicating whether a process with the specified process ID might exist
+// on the local computer.
+//
+// Arguments:
+// * dwProcessID - Process ID to look up
+//
+// Return Value:
+// nonzero if process might possibly exist; FALSE if not
+//
+// Notes:
+// * Since processes come and go while this function executes, this should only be
+// used on a process ID that is supposed to exist both before and after this
+// function returns. A return of FALSE reliably tells you that supposition is
+// wrong. A return of TRUE, however, only means the process ID existed when this
+// function did its search. It's still possible the process has exited by the time
+// this function returns.
+// * If this function is unsure of a process's existence (e.g., if it encounters an
+// error while trying to find out), it errs on the side of optimism and returns
+// TRUE.
+//
+
+BOOL ProfilingAPIAttachClient::MightProcessExist(DWORD dwProcessID)
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+ FORBID_FAULT;
+ MODE_ANY;
+ CANNOT_TAKE_LOCK;
+ }
+ CONTRACTL_END;
+
+ // There are a few ways to check whether a process exists. Some dismissed
+ // alternatives:
+ //
+ // * OpenProcess() with a "limited" access right.
+ // * Even relatively limited access rights such as SYNCHRONIZE and
+ // PROCESS_QUERY_INFORMATION often fail with ERROR_ACCESS_DENIED, even if
+ // the caller is running as administrator.
+ //
+ // * EnumProcesses() + search through returned PIDs
+ // * EnumProcesses() requires psychic powers to know how big to allocate the
+ // array of PIDs to receive (EnumProcesses() won't give you a hint if
+ // you're wrong).
+ //
+ // Method of choice is CreateToolhelp32Snapshot, which gives an enumerator to iterate
+ // through all processes.
+
+ // Take a snapshot of all processes in the system.
+ HandleHolder hProcessSnap = CreateToolhelp32Snapshot(
+ TH32CS_SNAPPROCESS,
+ 0 // Unused when snap type is TH32CS_SNAPPROCESS
+ );
+ if (hProcessSnap == INVALID_HANDLE_VALUE)
+ {
+ // Dunno if process exists. Err on the side of optimism
+ return TRUE;
+ }
+
+ // Set the size of the structure before using it.
+ PROCESSENTRY32 entry;
+ ZeroMemory(&entry, sizeof(entry));
+ entry.dwSize = sizeof(PROCESSENTRY32);
+
+ // Start enumeration with Process32First. It will set dwSize to tell us how many
+ // members of PROCESSENTRY32 we can trust. We only need th32ProcessID
+ if (!Process32First(hProcessSnap, &entry) ||
+ (offsetof(PROCESSENTRY32, th32ProcessID) + sizeof(entry.th32ProcessID) > entry.dwSize))
+ {
+ // Can't tell if process exists, so assume it might
+ return TRUE;
+ }
+
+ do
+ {
+ if (entry.th32ProcessID == dwProcessID)
+ {
+ // Definitely exists
+ return TRUE;
+ }
+ } while (Process32Next(hProcessSnap, &entry));
+
+ // Process32Next() failed. Return FALSE only if we exhausted our search
+ return (GetLastError() != ERROR_NO_MORE_FILES);
+}
+
+
+
+// ----------------------------------------------------------------------------
+// ProfilingAPIAttachClient::OpenPipeClient
+//
+// Description:
+// Attempts to create a client connection to the remote server pipe
+//
+// Arguments:
+// * wszPipeName - Name of pipe to connect to.
+// * dwMillisecondsMax - Total ms to spend trying to connect to the pipe.
+//
+// Return Value:
+// HRESULT indicating success / failure
+//
+
+HRESULT ProfilingAPIAttachClient::OpenPipeClient(
+ LPCWSTR wszPipeName,
+ DWORD dwMillisecondsMax)
+{
+ CONTRACTL
+ {
+ THROWS;
+ GC_TRIGGERS;
+ MODE_PREEMPTIVE;
+ CAN_TAKE_LOCK;
+ }
+ CONTRACTL_END;
+
+ const DWORD kSleepMsUntilRetryCreateFile = 100;
+ HRESULT hr;
+ DWORD dwErr;
+
+ // See code:AdjustRemainingMs
+ ULONGLONG ui64StartTimeMs = CLRGetTickCount64();
+ DWORD dwMillisecondsRemaining = dwMillisecondsMax;
+
+ HandleHolder hPipeClient;
+
+ // We need to wait until the pipe is both CREATED (i.e., target profilee app has
+ // created the server end of the pipe) and AVAILABLE (i.e., no other trigger has opened
+ // the client end to the pipe). There is no Win32 API to wait until the pipe is
+ // CREATED, so we must make our own retry loop that calls CreateFileW. Once the pipe
+ // is known to be CREATED, we can use WaitNamedPipe to wait until the pipe is
+ // AVAILABLE. (Note: It would have been nice if we could use WaitNamedPipe to wait
+ // until the pipe is both CREATED and AVAILABLE. But WaitNamedPipe just returns an
+ // error immediately if the pipe is not yet CREATED, regardless of the timeout value
+ // specified.)
+ while (TRUE)
+ {
+ // This CreateFile call doesn't create the pipe. The pipe must be created by the
+ // target profilee. This CreateFile call attempts to open a client connection to
+ // the pipe. If CreateFile succeeds, that implies the pipe had already been
+ // successfully CREATED by the target profilee, and is AVAILABLE, and we now have
+ // a client connection to the pipe ready to go.
+ hPipeClient = CreateFileW(
+ wszPipeName,
+ GENERIC_READ | GENERIC_WRITE,
+ 0, // dwShareMode (i.e., no sharing)
+ NULL, // lpSecurityAttributes (i.e., handle not inheritable and
+ // only current user may access this handle)
+ OPEN_EXISTING, // Only open (don't create) the pipe
+ FILE_FLAG_OVERLAPPED, // Using overlapped I/O allows async ops w/ timeout
+ NULL); // hTemplateFile
+
+ if (hPipeClient != INVALID_HANDLE_VALUE)
+ {
+ // CreateFile succeeded! Pipe is CREATED (by target profilee)
+ // and AVAILABLE and we're connected
+ break;
+ }
+
+ // Opening the pipe failed. Why?
+ dwErr = GetLastError();
+ switch(dwErr)
+ {
+ default:
+ // Any error other than the ones specifically brought out below isn't
+ // retry-able (e.g., security failure)
+ return HRESULT_FROM_WIN32(dwErr);
+
+ case ERROR_FILE_NOT_FOUND:
+ // Pipe not CREATED yet. Can we retry?
+ if (dwMillisecondsRemaining <= kSleepMsUntilRetryCreateFile)
+ {
+ // No time left, gotta bail!
+ return HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND);
+ }
+
+ // Sleep and retry
+ // (bAlertable=FALSE: don't wake up due to overlapped I/O)
+ ClrSleepEx(kSleepMsUntilRetryCreateFile, FALSE);
+ dwMillisecondsRemaining -= kSleepMsUntilRetryCreateFile;
+ break;
+
+ case ERROR_PIPE_BUSY:
+ // Pipe CREATED, but it's not AVAILABLE. Wait until it's AVAILABLE
+
+ LOG((
+ LF_CORPROF,
+ LL_INFO10,
+ "**PROF TRIGGER: Found pipe, but pipe is busy. Waiting until pipe is available.\n"));
+
+ hr = AdjustRemainingMs(ui64StartTimeMs, dwMillisecondsMax, &dwMillisecondsRemaining);
+ if (FAILED(hr))
+ {
+ return HRESULT_FROM_WIN32(ERROR_PIPE_BUSY);
+ }
+
+ if (!WaitNamedPipeW(wszPipeName, dwMillisecondsRemaining))
+ {
+ // If we timeout here, convert the error into something more useful
+ dwErr = GetLastError();
+ if ((dwErr == ERROR_TIMEOUT) || (dwErr == ERROR_SEM_TIMEOUT))
+ {
+ return HRESULT_FROM_WIN32(ERROR_PIPE_BUSY);
+ }
+
+ // Failed for a reason other timeout. Send that reason back to caller
+ LOG((
+ LF_CORPROF,
+ LL_ERROR,
+ "**PROF TRIGGER: WaitNamedPipe failed for a reason other timeout. hr=0x%x.\n",
+ HRESULT_FROM_WIN32(dwErr)));
+ return HRESULT_FROM_WIN32(dwErr);
+ }
+
+ // Pipe should be ready to open now, so retry. Note that it's still
+ // possible that another client sneaks in and connects before we get a
+ // chance to. If that happens, CreateFile will fail again, and we'll end up
+ // here waiting again (until we timeout).
+ break;
+ }
+ }
+
+ // Only way to exit loop above is if pipe is CREATED and AVAILABLE.
+ _ASSERTE(IsValidHandle(hPipeClient));
+
+ // We now have a valid handle on the pipe, which means we're connected
+ // to the pipe, and no one else is
+
+ // change to message-read mode.
+ DWORD dwMode = PIPE_READMODE_MESSAGE;
+ if (!SetNamedPipeHandleState(
+ hPipeClient, // pipe handle
+ &dwMode, // new pipe mode (PIPE_READMODE_MESSAGE)
+ NULL, // lpMaxCollectionCount, must be NULL when client & server on same box
+ NULL)) // lpCollectDataTimeout, must be NULL when client & server on same box
+ {
+ hr = HRESULT_FROM_GetLastError();
+ LOG((
+ LF_CORPROF,
+ LL_ERROR,
+ "**PROF TRIGGER: SetNamedPipeHandleState failed. hr=0x%x.\n",
+ hr));
+ return hr;
+ }
+
+ // Pipe's client handle is now ready for use by this class
+ m_hPipeClient = (HANDLE) hPipeClient;
+
+ // Ownership transferred to this class, so this function shouldn't call CloseHandle()
+ hPipeClient.SuppressRelease();
+
+ return S_OK;
+}
+
+
+// ----------------------------------------------------------------------------
+// ProfilingAPIAttachClient::SignalAttachEvent
+//
+// Description:
+// Trigger process calls this (indirectly via AttachProfiler()) to find, open, and
+// signal the Globally Named Attach Event.
+//
+// Arguments:
+// * wszEventName - Name of event to signal
+//
+// Return Value:
+// HRESULT indicating success or failure.
+//
+
+HRESULT ProfilingAPIAttachClient::SignalAttachEvent(LPCWSTR wszEventName)
+{
+ CONTRACTL
+ {
+ THROWS;
+ GC_TRIGGERS;
+ MODE_PREEMPTIVE;
+ CAN_TAKE_LOCK;
+ }
+ CONTRACTL_END;
+
+ HandleHolder hAttachEvent;
+
+ hAttachEvent = OpenEventW(
+ EVENT_MODIFY_STATE, // dwDesiredAccess
+ FALSE, // bInheritHandle
+ wszEventName);
+ if (hAttachEvent == NULL)
+ {
+ return HRESULT_FROM_GetLastError();
+ }
+
+ // Dealing directly with Windows event objects, not CLR event cookies, so
+ // using Win32 API directly. Note that none of this code executes on rotor
+ // or if we're memory / sync-hosted, so the CLR wrapper is of no use to us anyway.
+#pragma push_macro("SetEvent")
+#undef SetEvent
+ if (!SetEvent(hAttachEvent))
+#pragma pop_macro("SetEvent")
+ {
+ return HRESULT_FROM_GetLastError();
+ }
+
+ return S_OK;
+}
+
+
+// ----------------------------------------------------------------------------
+// ProfilingAPIAttachClient::VerifyVersionIsCompatible
+//
+// Description:
+// Sends a GetVersion request message across the pipe to the target profilee, reads
+// the response, and determines if the response allows for compatible communication.
+//
+// Arguments:
+// * dwMillisecondsMax - How much time do we have left to wait for the response?
+//
+// Return Value:
+// HRESULT indicating success or failure. If pipe communication succeeds, but we
+// determine that the response doesn't allow for compatible communication, return
+// CORPROF_E_PROFILEE_INCOMPATIBLE_WITH_TRIGGER.
+//
+// Assumptions:
+// * Client connection should be established before calling this function (or a
+// callee will assert).
+//
+
+HRESULT ProfilingAPIAttachClient::VerifyVersionIsCompatible(
+ DWORD dwMillisecondsMax)
+{
+ STANDARD_VM_CONTRACT;
+ HRESULT hr;
+ DWORD cbReceived;
+ GetVersionRequestMessage requestMsg;
+ GetVersionResponseMessage responseMsg;
+
+ hr = SendAndReceive(
+ dwMillisecondsMax,
+ reinterpret_cast<LPVOID>(&requestMsg),
+ static_cast<DWORD>(sizeof(requestMsg)),
+ reinterpret_cast<LPVOID>(&responseMsg),
+ static_cast<DWORD>(sizeof(responseMsg)),
+ &cbReceived);
+ if (FAILED(hr))
+ {
+ return hr;
+ }
+
+ // Did profilee successfully carry out the GetVersion request?
+ if (FAILED(responseMsg.m_hr))
+ {
+ return responseMsg.m_hr;
+ }
+
+ // We should have valid version info for the target profilee. Now do the
+ // comparisons to determine if we're compatible.
+ if (
+ // Am I too old (i.e., profilee requires a newer trigger)?
+ (ProfilingAPIAttachDetach::kCurrentProcessVersion <
+ responseMsg.m_minimumAllowableTriggerVersion) ||
+
+ // Is the profilee too old (i.e., this trigger requires a newer profilee)?
+ (responseMsg.m_profileeVersion <
+ ProfilingAPIAttachDetach::kMinimumAllowableProfileeVersion))
+ {
+ return CORPROF_E_PROFILEE_INCOMPATIBLE_WITH_TRIGGER;
+ }
+
+ return S_OK;
+}
+
+
+// ----------------------------------------------------------------------------
+// ProfilingAPIAttachClient::SendAttachRequest
+//
+// Description:
+// Sends an Attach request message across the pipe to the target profilee, and returns
+// the response.
+//
+// Arguments:
+// * dwMillisecondsMax - [in] How much time is left to wait for response?
+// * pClsidProfiler - [in] CLSID of profiler to attach
+// * pvClientData - [in] Client data to pass to profiler's InitializeForAttach
+// callback
+// * cbClientData - [in] Size of client data
+// * phrAttach - [out] Response HRESULT sent back by target profilee
+//
+// Return Value:
+// HRESULT indicating success / failure with sending request & receiving response. If
+// S_OK is returned, consult phrAttach to determine success / failure of the actual
+// attach operation.
+//
+// Assumptions:
+// * Client connection should be established before calling this function (or a callee
+// will assert).
+//
+
+HRESULT ProfilingAPIAttachClient::SendAttachRequest(
+ DWORD dwMillisecondsMax,
+ const CLSID * pClsidProfiler,
+ LPCWSTR wszProfilerPath,
+ void * pvClientData,
+ UINT cbClientData,
+ HRESULT * phrAttach)
+{
+ CONTRACTL
+ {
+ THROWS;
+ GC_TRIGGERS;
+ MODE_PREEMPTIVE;
+ CAN_TAKE_LOCK;
+ }
+ CONTRACTL_END;
+
+ _ASSERTE(phrAttach != NULL);
+
+ // These were already verified early on
+ _ASSERTE(cbClientData < 0xFFFFffffUL - sizeof(AttachRequestMessageV2));
+ _ASSERTE((pvClientData != NULL) || (cbClientData == 0));
+
+ // Allocate enough space for the message, including the variable-length client data.
+ DWORD cbMessage = sizeof(AttachRequestMessageV2) + cbClientData;
+ _ASSERTE(cbMessage >= sizeof(AttachRequestMessageV2));
+ NewHolder<BYTE> pbMessageStart(new (nothrow) BYTE[cbMessage]);
+ if (pbMessageStart == NULL)
+ {
+ return E_OUTOFMEMORY;
+ }
+
+ // Initialize the message. First the client data at the tail end...
+ memcpy(pbMessageStart + sizeof(AttachRequestMessageV2), pvClientData, cbClientData);
+
+ // ...then the message struct fields (use constructor in-place)
+ new ((void *) pbMessageStart) AttachRequestMessageV2(
+ cbMessage,
+ ProfilingAPIAttachDetach::kCurrentProcessVersion, // Version of the trigger process
+ pClsidProfiler,
+ wszProfilerPath,
+ sizeof(AttachRequestMessageV2), // dwClientDataStartOffset
+ cbClientData,
+ dwMillisecondsMax
+ );
+
+ HRESULT hr;
+ DWORD cbReceived;
+ AttachResponseMessage attachResponseMessage(E_UNEXPECTED);
+
+ hr = SendAndReceive(
+ dwMillisecondsMax,
+ (LPVOID) pbMessageStart,
+ cbMessage,
+ reinterpret_cast<LPVOID>(&attachResponseMessage),
+ static_cast<DWORD>(sizeof(attachResponseMessage)),
+ &cbReceived);
+ if (FAILED(hr))
+ {
+ return hr;
+ }
+
+ // Successfully got a response from target. The response contained the HRESULT
+ // indicating whether the attach was successful, so return that HRESULT in the [out]
+ // param.
+ *phrAttach = attachResponseMessage.m_hr;
+ return S_OK;
+}
+
+
+// ----------------------------------------------------------------------------
+// ProfilingAPIAttachClient::SendAndReceive
+//
+// Description:
+// Used in trigger process to send a request and receive the response.
+//
+// Arguments:
+// * dwMillisecondsMax - [in] Timeout for entire send/receive operation
+// * pvInBuffer - [in] Buffer contaning the request message
+// * cbInBuffer - [in] Number of bytes in the request message
+// * pvOutBuffer - [in/out] Buffer to write the response into
+// * cbOutBuffer - [in] Size of the response buffer
+// * pcbReceived - [out] Number of bytes actually written into response buffer
+//
+// Return Value:
+// HRESULT indicating success or failure
+//
+// Notes:
+// * The [out] parameters may be written to even if this function fails. But their
+// contents should be ignored by the caller in this case.
+//
+
+HRESULT ProfilingAPIAttachClient::SendAndReceive(
+ DWORD dwMillisecondsMax,
+ LPVOID pvInBuffer,
+ DWORD cbInBuffer,
+ LPVOID pvOutBuffer,
+ DWORD cbOutBuffer,
+ DWORD * pcbReceived)
+{
+ CONTRACTL
+ {
+ THROWS;
+ GC_TRIGGERS;
+ MODE_PREEMPTIVE;
+ CAN_TAKE_LOCK;
+ }
+ CONTRACTL_END;
+
+ _ASSERTE(IsValidHandle(m_hPipeClient));
+ _ASSERTE(pvInBuffer != NULL);
+ _ASSERTE(pvOutBuffer != NULL);
+ _ASSERTE(pcbReceived != NULL);
+
+ HRESULT hr;
+ DWORD dwErr;
+ ProfilingAPIAttachDetach::OverlappedResultHolder overlapped;
+ hr = overlapped.Initialize();
+ if (FAILED(hr))
+ {
+ return hr;
+ }
+
+ if (TransactNamedPipe(
+ m_hPipeClient,
+ pvInBuffer,
+ cbInBuffer,
+ pvOutBuffer,
+ cbOutBuffer,
+ pcbReceived,
+ overlapped))
+ {
+ // Hot dog! Send and receive succeeded immediately! All done.
+ return S_OK;
+ }
+
+ dwErr = GetLastError();
+ if (dwErr != ERROR_IO_PENDING)
+ {
+ // An unexpected error. Caller has to deal with it
+ hr = HRESULT_FROM_WIN32(dwErr);
+ LOG((
+ LF_CORPROF,
+ LL_ERROR,
+ "**PROF TRIGGER: TransactNamedPipe failed. hr=0x%x.\n",
+ hr));
+ return hr;
+ }
+
+ // Typical case=ERROR_IO_PENDING: TransactNamedPipe has begun the transaction, and
+ // it's still in progress. Wait until it's done (or timeout expires).
+ hr = overlapped.Wait(
+ dwMillisecondsMax,
+ m_hPipeClient,
+ pcbReceived);
+ if (FAILED(hr))
+ {
+ LOG((
+ LF_CORPROF,
+ LL_ERROR,
+ "**PROF TRIGGER: Waiting for overlapped result for TransactNamedPipe failed. hr=0x%x.\n",
+ hr));
+ return hr;
+ }
+
+ return S_OK;
+}
+
+#endif // FEATURE_PROFAPI_ATTACH_DETACH