// 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.inl // // // Implementation of inlineable functions that help with attaching and detaching // profilers // // ====================================================================================== #ifndef __PROF_ATTACH_INL__ #define __PROF_ATTACH_INL__ // ---------------------------------------------------------------------------- // BaseRequestMessage::BaseRequestMessage // // Description: // Constructor for base class of all request messages sent from trigger (client) to // profilee (server). // // Arguments: // * cbMessage - Size, in bytes, of the entire request message (including size of // derived type, client data, etc., if present in the message) // * requestMessageType - Enum representing type of request this constitutes // inline BaseRequestMessage::BaseRequestMessage( DWORD cbMessage, RequestMessageType requestMessageType) : m_cbMessage(cbMessage), m_requestMessageType(requestMessageType) { LIMITED_METHOD_CONTRACT; } // ---------------------------------------------------------------------------- // GetVersionRequestMessage::GetVersionRequestMessage // // Description: // Constructor to create a fully initialized GetVersionRequestMessage // inline GetVersionRequestMessage::GetVersionRequestMessage() : BaseRequestMessage(sizeof(GetVersionRequestMessage), kMsgGetVersion) { LIMITED_METHOD_CONTRACT; } // ---------------------------------------------------------------------------- // AttachRequestMessage::AttachRequestMessage // // Description: // Constructor for request message of type kMsgAttach sent from trigger (client) to // profilee (server) // // Arguments: // * cbMessage - Size, in bytes, of the entire request message (including size of // derived type, client data, etc., if present in the message) // * triggerVersion - Uint representing profiler attach interface version used by trigger // * pClsidProfiler - CLSID of profiler to attach // * wszProfilerPath - path to profiler DLL // * dwClientDataStartOffset - see code:AttachRequestMessage // * cbClientDataLength - see code:AttachRequestMessage // inline AttachRequestMessage::AttachRequestMessage( DWORD cbMessage, const UINT & triggerVersion, const CLSID * pClsidProfiler, LPCWSTR wszProfilerPath, DWORD dwClientDataStartOffset, DWORD cbClientDataLength) : BaseRequestMessage(cbMessage, kMsgAttach), m_dwClientDataStartOffset(dwClientDataStartOffset), m_cbClientDataLength(cbClientDataLength) { LIMITED_METHOD_CONTRACT; _ASSERT(cbMessage >= sizeof(AttachRequestMessage) + cbClientDataLength); memcpy(&m_clsidProfiler, pClsidProfiler, sizeof(m_clsidProfiler)); m_triggerVersion = triggerVersion; if (wszProfilerPath != NULL) { _ASSERTE(wcslen(wszProfilerPath) < _countof(m_wszProfilerPath)); wcscpy_s(m_wszProfilerPath, _countof(m_wszProfilerPath), wszProfilerPath); } else { m_wszProfilerPath[0] = L'\0'; } } // ---------------------------------------------------------------------------- // AttachRequestMessageV2::AttachRequestMessageV2 // // Description: // Constructor for request message V2 of type kMsgAttach sent from trigger (client) to // profilee (server) // // Arguments: // * cbMessage - Size, in bytes, of the entire request message (including size of // derived type, client data, etc., if present in the message) // * triggerVersion - Uint representing profiler attach interface version used by trigger // * pClsidProfiler - CLSID of profiler to attach // * wszProfilerPath - path to profiler DLL // * dwClientDataStartOffset - see code:AttachRequestMessage // * cbClientDataLength - see code:AttachRequestMessage // * dwConcurrentGCWaitTimeoutInMs - the time out for wait operation on concurrent GC to finish. // Attach scenario only. // inline AttachRequestMessageV2::AttachRequestMessageV2( DWORD cbMessage, const UINT & triggerVersion, const CLSID * pClsidProfiler, LPCWSTR wszProfilerPath, DWORD dwClientDataStartOffset, DWORD cbClientDataLength, DWORD dwConcurrentGCWaitTimeoutInMs) :AttachRequestMessage( cbMessage, triggerVersion, pClsidProfiler, wszProfilerPath, dwClientDataStartOffset, cbClientDataLength), m_dwConcurrentGCWaitTimeoutInMs(dwConcurrentGCWaitTimeoutInMs) { LIMITED_METHOD_CONTRACT; _ASSERT(cbMessage >= sizeof(AttachRequestMessageV2) + cbClientDataLength); } // ---------------------------------------------------------------------------- // AttachRequestMessageV2::CanCastTo inline BOOL AttachRequestMessageV2::CanCastTo(const AttachRequestMessage *pMsg) { LIMITED_METHOD_CONTRACT; // We already have checks that the client data doesn't go beyond the message body. // If someone creates a bad message that pretends to be a V2 message, the worst scenario // is we got a bad time out. if (pMsg->m_cbMessage >= sizeof(AttachRequestMessageV2) + pMsg->m_cbClientDataLength) return TRUE; return FALSE; } // ---------------------------------------------------------------------------- // BaseResponseMessage::BaseResponseMessage // // Description: // Constructor for base class of all response messages returned by profilee (server) // to trigger (client) // // Arguments: // * hr - HRESULT indicating success or failure of executing the request that the // trigger had made to the profilee // inline BaseResponseMessage::BaseResponseMessage(HRESULT hr) : m_hr(hr) { LIMITED_METHOD_CONTRACT; } // ---------------------------------------------------------------------------- // BaseResponseMessage::BaseResponseMessage // // Description: // Zero-parameter constructor for BaseResponseMessage for use when hr is not yet // known. // inline BaseResponseMessage::BaseResponseMessage() : m_hr(E_UNEXPECTED) { LIMITED_METHOD_CONTRACT; } // ---------------------------------------------------------------------------- // GetVersionResponseMessage::GetVersionResponseMessage // // Description: // Constructor to create a fully initialized GetVersionResponseMessage // // Arguments: // * hr - Success / failure of carrying out the GetVersion request // * profileeVersion - Version of the target profilee app's runtime (server) // * minimumAllowableTriggerVersion - Oldest version of a trigger process that this // target profilee app is willing to talk to. // inline GetVersionResponseMessage::GetVersionResponseMessage( HRESULT hr, const UINT & profileeVersion, const UINT & minimumAllowableTriggerVersion) : BaseResponseMessage(hr) { LIMITED_METHOD_CONTRACT; m_profileeVersion = profileeVersion; m_minimumAllowableTriggerVersion = minimumAllowableTriggerVersion; } // ---------------------------------------------------------------------------- // GetVersionResponseMessage::GetVersionResponseMessage // // Description: // Constructor to use for GetVersionResponseMessage when the data is not known yet. // The trigger will typically use this constructor to create an empty // GetVersionResponseMessage as storage to receive the GetVersionResponseMessage data // that will come in over the pipe from the target profilee app. // inline GetVersionResponseMessage::GetVersionResponseMessage() { LIMITED_METHOD_CONTRACT; memset(this, 0, sizeof(*this)); m_hr = E_UNEXPECTED; } // ---------------------------------------------------------------------------- // AttachResponseMessage::AttachResponseMessage // // Description: // Constructor for AttachResponseMessage // // Arguments: // * hr - Success / failure of carrying out the attach request // inline AttachResponseMessage::AttachResponseMessage(HRESULT hr) : BaseResponseMessage(hr) { LIMITED_METHOD_CONTRACT; } // ---------------------------------------------------------------------------- // ProfilingAPIAttachDetach::GetAttachThreadingMode // // Description: // Returns the profiling attach threading mode for this runtime instance. See // code:ProfilingAPIAttachDetach::AttachThreadingMode. // // Return Value: // The profiling attach threading mode // // Assumptions: // * code:ProfilingAPIAttachDetach::Initialize must be called before this function. // // static inline ProfilingAPIAttachDetach::AttachThreadingMode ProfilingAPIAttachDetach::GetAttachThreadingMode() { LIMITED_METHOD_CONTRACT; // ProfilingAPIAttachDetach::Initialize must be called before this function. _ASSERTE(s_fInitializeCalled); return s_attachThreadingMode; } // ---------------------------------------------------------------------------- // ProfilingAPIAttachDetach::GetAttachEventNameForPidAndVersion // // Description: // Generates name for Globally Named Attach Event, based on PID and the runtime version // Name looks like this: // CPFATE_nnnn_RuntimeVersion // CPFATE stands for CLR Profiling API attach trigger event // nnnn is decimal process ID // RuntimeVersion is the string of the runtime version // // Arguments: // * hProfileeProcess - The profilee process we want to attach to // * wszRuntimeVersion - runtime version string // * pAttachEventName - [in/out] SString to hold the generated name // // static inline void ProfilingAPIAttachDetach::GetAttachEventNameForPidAndVersion(HANDLE hProfileeProcess, LPCWSTR wszRuntimeVersion, SString * pAttachEventName) { CONTRACTL { THROWS; GC_TRIGGERS; MODE_PREEMPTIVE; CAN_TAKE_LOCK; } CONTRACTL_END; // Convert to lower case using invariant culture SString strRuntimeVersion(wszRuntimeVersion); strRuntimeVersion.LowerCase(); DWORD dwProfileeProcessPid = ::GetProcessId(hProfileeProcess); if (IsAppContainerProcess(hProfileeProcess)) { HANDLE hCurrentProcess = ::GetCurrentProcess(); if (hProfileeProcess == hCurrentProcess || IsAppContainerProcess(::GetCurrentProcess())) { // App container to app container or the current process is the profilee process // In any case, use a local name pAttachEventName->Printf(L"NCPFATE_%d_%s", dwProfileeProcessPid, strRuntimeVersion.GetUnicode()); } else { // Otherwise, we'll assume it is full-trust to lowbox, and in this case we need to prefix the name with app container path WCHAR wszObjectPath[MAX_PATH]; HRESULT hr = GetAppContainerNamedObjectPath(hProfileeProcess, wszObjectPath, sizeof(wszObjectPath)/sizeof(WCHAR)); IfFailThrow(hr); // // Retrieve the session ID // DWORD dwSessionId; if (!ProcessIdToSessionId(dwProfileeProcessPid, &dwSessionId)) { COMPlusThrowHR(HRESULT_FROM_GetLastError()); } pAttachEventName->Printf(L"Session\\%d\\%s\\NCPFATE_%d_%s", dwSessionId, wszObjectPath, dwProfileeProcessPid, strRuntimeVersion.GetUnicode()); } } else { // Non-app conatiner scenario // Create in global namespace pAttachEventName->Printf(L"Global\\NCPFATE_%d_%s", dwProfileeProcessPid, strRuntimeVersion.GetUnicode()); } } // ---------------------------------------------------------------------------- // ProfilingAPIAttachDetach::GetAttachPipeNameForPidAndVersion // // Description: // Generates name for Globally Named Attach Pipe, based on PID and the runtime version // Name looks like this: // \\.\pipe\CPFATP_nnnn_RuntimeVersion // CPFATP stands for CLR Profiling API attach trigger pipe // nnnn is decimal process ID // RuntimeVersion is the string of the runtime version // // Arguments: // * hProfileeProcess - The profilee process we want to attach to // * wszRuntimeVersion - runtime version string // * pAttachPipeName - [in/out] SString to hold the generated name // // static inline void ProfilingAPIAttachDetach::GetAttachPipeNameForPidAndVersion(HANDLE hProfileeProcess, LPCWSTR wszRuntimeVersion, SString * pAttachPipeName) { CONTRACTL { THROWS; GC_TRIGGERS; MODE_PREEMPTIVE; CAN_TAKE_LOCK; } CONTRACTL_END; // Convert to lower case using invariant culture SString strRuntimeVersion(wszRuntimeVersion); strRuntimeVersion.LowerCase(); DWORD dwProfileeProcessPid = ::GetProcessId(hProfileeProcess); if (IsAppContainerProcess(hProfileeProcess)) { // // Retrieve low object path // WCHAR wszObjectPath[MAX_PATH]; HRESULT hr = GetAppContainerNamedObjectPath(hProfileeProcess, wszObjectPath, sizeof(wszObjectPath)/sizeof(WCHAR)); IfFailThrow(hr); // // Retrieve the session ID // DWORD dwSessionId; if (!ProcessIdToSessionId(dwProfileeProcessPid, &dwSessionId)) { COMPlusThrowHR(HRESULT_FROM_GetLastError()); } pAttachPipeName->Printf(L"\\\\.\\pipe\\Sessions\\%d\\%s\\NCPFATP_%d_%s", dwSessionId, wszObjectPath, dwProfileeProcessPid, strRuntimeVersion.GetUnicode()); } else { pAttachPipeName->Printf(L"\\\\.\\pipe\\NCPFATP_%d_%s", dwProfileeProcessPid, strRuntimeVersion.GetUnicode()); } } // Simple wrapper around code:ProfilingAPIAttachDetach::GetAttachEventNameForPidAndVersion using // current process's PID and current runtime directory // static inline HRESULT ProfilingAPIAttachDetach::GetAttachEventName(HANDLE hProfileeProcess, SString * pAttachEventName) { CONTRACTL { THROWS; GC_TRIGGERS; MODE_PREEMPTIVE; CAN_TAKE_LOCK; } CONTRACTL_END; WCHAR wszRuntimeVersion[MAX_PATH]; wszRuntimeVersion[0] = L'\0'; // Note: CoreCLR can have the same version as Desktop CLR. And it's possible to have mutilple // instances of the same version of the CoreCLR in the process. We need to come up with // something other than version When Attach is enabled for CoreCLR. DWORD dwSize = _countof(wszRuntimeVersion); HRESULT hr = GetCORVersionInternal(wszRuntimeVersion, dwSize, &dwSize); if (FAILED(hr)) { return hr; } GetAttachEventNameForPidAndVersion(hProfileeProcess, wszRuntimeVersion, pAttachEventName); return S_OK; } // Simple wrapper around code:ProfilingAPIAttachDetach::GetAttachPipeNameForPidAndVersion using // current process's PID and current runtime directory // static inline HRESULT ProfilingAPIAttachDetach::GetAttachPipeName(HANDLE hProfileeProcess, SString * pAttachPipeName) { CONTRACTL { THROWS; GC_TRIGGERS; MODE_PREEMPTIVE; CAN_TAKE_LOCK; } CONTRACTL_END; WCHAR wszRuntimeVersion[MAX_PATH]; wszRuntimeVersion[0] = L'\0'; // Note: CoreCLR can have the same version as Desktop CLR. And it's possible to have mutilple // instances of the same version of the CoreCLR in the process. We need to come up with // something other than version When Attach is enabled for CoreCLR. DWORD dwSize = _countof(wszRuntimeVersion); HRESULT hr = GetCORVersionInternal(wszRuntimeVersion, dwSize, &dwSize); if (FAILED(hr)) { return hr; } GetAttachPipeNameForPidAndVersion(hProfileeProcess, wszRuntimeVersion, pAttachPipeName); return S_OK; } #endif // __PROF_ATTACH_INL__