diff options
author | John Salem <josalem@microsoft.com> | 2019-05-23 20:18:33 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2019-05-23 20:18:33 -0700 |
commit | 267f8299ac1fd97430284fa6d44e5e9c6ed26850 (patch) | |
tree | 43a9f01ee054e5360e4c810aacd3c68b4e75cbbf | |
parent | 442d71f95973f2d3b4c1fa2fb2664dcabf69329c (diff) | |
download | coreclr-267f8299ac1fd97430284fa6d44e5e9c6ed26850.tar.gz coreclr-267f8299ac1fd97430284fa6d44e5e9c6ed26850.tar.bz2 coreclr-267f8299ac1fd97430284fa6d44e5e9c6ed26850.zip |
Diagnostics IPC (#24582)
* Initial draft of structs and classes for Diagnostic Server IPC Protocol
* Fix some syntax/name issues that weren't getting caught by intellisense in vscode for mac
* * Add member checkers for Flatten and GetSize
* Split Flatten impl to have a default version for simple, fixed-size structs
* * Remove unnecessary abstract class
* Add documentation about templates
* * Change templating for IpcMessage class to be more limited (only on message creation and payload parsing)
* flesh out parse and tryparse
* add requirement for non-fixed-size payloads to implement a static TryParse(buffer, bufferlen)
* refactor namespace to bottom of file
* * moved DiagnosticsIpc namespace into diagnosticprotocol.h to avoid being referenced by debug-pal project
* converted diagnostic server to use DiagnosticsIpc code
* converted EventPipeProtocolHelper to use DiagnosticsIpc code
* made EventPipe end to end use DiagnosticsIpc code
* * Add ASSERTs where relevant
* Refactor stream ownershpi back to previous way for collect tracing response
* * Add contracts where applicable
* * Updating GenerateCoreDump code to use new IPC work after rebase
* * modify contract in DiagnosticProtocolHelper to be more restrictive
* remove comments
* Add comment documentation of SFINAE pattern above usage
* simplify Has* checks
* Fix function resolution for static member check
* Add Constructor for rvalue references to IpcMessage
* avoid code path that would result in a blocking read on 0 bytes
* Fix silly strcmp bug
* fix contractin EventPipeProtocolHelper
* * Modify header declaration to make static initialization easier
* fix type in template code
* * Make all messages use Initialize instead of constructor for hydrating themselves, to make error paths easier
* * Expand and streamline error model to match spec
* Simplify writing error messages to a static function
* memory management
* *modify function signatures for SFINAE to remove clang warning
* * add braces to static initialization to remove clang warnings
* * modify IpcMessage::TryParseImpl, to reset the internal data pointer when passing back a reinterpret_cast to prevent double frees on destruction
* Add notes on requirement that user free the memory for a payload
* * fix bug in TryParse for generateCoreDump
* change INT to uint32_t in GenerateCoreDump payloads
* Remove unused error code
* * rename Miscellaneous command set to Diagnostic
* Remove unnecessary command ids in EventPipe
* Rename Diagnostic command set to Dump
* * Move payload into Holder to simplify cleanup
* Add buffer holder to payloads to ensure the buffers are being cleaned up after use
* updated Profiler attach to use IPC work after rebase
* * Fix typos
* Fix placement of ifdefs for profiler helpers
* * Fix accidental char hidden in ifdef...
* * Add ASSERT to signify we shouldn't be re-using IpcMessages
* * fix another typo hidden behind inactive ifdef...
* * Change errors to use HRESULTS to increase transparency
* fix bug in profiler attach for checking if entire client data is in buffer
-rw-r--r-- | src/inc/corerror.xml | 20 | ||||
-rw-r--r-- | src/pal/prebuilt/inc/corerror.h | 4 | ||||
-rw-r--r-- | src/vm/CMakeLists.txt | 3 | ||||
-rw-r--r-- | src/vm/diagnosticprotocolhelper.cpp | 235 | ||||
-rw-r--r-- | src/vm/diagnosticprotocolhelper.h | 32 | ||||
-rw-r--r-- | src/vm/diagnosticserver.cpp | 38 | ||||
-rw-r--r-- | src/vm/diagnosticsprotocol.h | 481 | ||||
-rw-r--r-- | src/vm/dumpdiagnosticprotocolhelper.cpp | 119 | ||||
-rw-r--r-- | src/vm/dumpdiagnosticprotocolhelper.h | 55 | ||||
-rw-r--r-- | src/vm/eventpipeprotocolhelper.cpp | 148 | ||||
-rw-r--r-- | src/vm/eventpipeprotocolhelper.h | 44 | ||||
-rw-r--r-- | src/vm/fastserializer.cpp | 12 | ||||
-rw-r--r-- | src/vm/profilerdiagnosticprotocolhelper.cpp | 125 | ||||
-rw-r--r-- | src/vm/profilerdiagnosticprotocolhelper.h | 58 |
14 files changed, 1019 insertions, 355 deletions
diff --git a/src/inc/corerror.xml b/src/inc/corerror.xml index 4c6362f268..994195f27e 100644 --- a/src/inc/corerror.xml +++ b/src/inc/corerror.xml @@ -1265,6 +1265,26 @@ <Comment> The runtime's tracking of inlined methods for ReJIT is not enabled. </Comment> </HRESULT> +<HRESULT NumericValue="0x80131384"> + <SymbolicName>CORDIAGIPC_E_BAD_ENCODING</SymbolicName> + <Comment> The runtime was unable to decode the Header or Payload. </Comment> +</HRESULT> + +<HRESULT NumericValue="0x80131385"> + <SymbolicName>CORDIAGIPC_E_UNKNOWN_COMMAND</SymbolicName> + <Comment> The specified CommandSet or CommandId is unknown. </Comment> +</HRESULT> + +<HRESULT NumericValue="0x80131386"> + <SymbolicName>CORDIAGIPC_E_UNKNOWN_MAGIC</SymbolicName> + <Comment> The magic version of Diagnostics IPC is unknown. </Comment> +</HRESULT> + +<HRESULT NumericValue="0x80131387"> + <SymbolicName>CORDIAGIPC_E_UNKNOWN_ERROR</SymbolicName> + <Comment> An unknown error occurred in the Diagnpostics IPC Server. </Comment> +</HRESULT> + <HRESULT NumericValue="0x80131401"> <SymbolicName>SECURITY_E_INCOMPATIBLE_SHARE</SymbolicName> <Message>"Loading this assembly would produce a different grant set from other instances."</Message> diff --git a/src/pal/prebuilt/inc/corerror.h b/src/pal/prebuilt/inc/corerror.h index f8578f182d..3d92d63fd7 100644 --- a/src/pal/prebuilt/inc/corerror.h +++ b/src/pal/prebuilt/inc/corerror.h @@ -228,6 +228,10 @@ #define CORPROF_E_CALLBACK6_REQUIRED EMAKEHR(0x1380) #define CORPROF_E_CALLBACK7_REQUIRED EMAKEHR(0x1382) #define CORPROF_E_REJIT_INLINING_DISABLED EMAKEHR(0x1383) +#define CORDIAGIPC_E_BAD_ENCODING EMAKEHR(0x1384) +#define CORDIAGIPC_E_UNKNOWN_COMMAND EMAKEHR(0x1385) +#define CORDIAGIPC_E_UNKNOWN_MAGIC EMAKEHR(0x1386) +#define CORDIAGIPC_E_UNKNOWN_ERROR EMAKEHR(0x1387) #define SECURITY_E_INCOMPATIBLE_SHARE EMAKEHR(0x1401) #define SECURITY_E_UNVERIFIABLE EMAKEHR(0x1402) #define SECURITY_E_INCOMPATIBLE_EVIDENCE EMAKEHR(0x1403) diff --git a/src/vm/CMakeLists.txt b/src/vm/CMakeLists.txt index 61ee6809ec..9db2594201 100644 --- a/src/vm/CMakeLists.txt +++ b/src/vm/CMakeLists.txt @@ -58,10 +58,10 @@ set(VM_SOURCES_DAC_AND_WKS_COMMON debughelp.cpp debuginfostore.cpp decodemd.cpp - diagnosticprotocolhelper.cpp disassembler.cpp dllimport.cpp domainfile.cpp + dumpdiagnosticprotocolhelper.cpp dynamicmethod.cpp ecall.cpp eedbginterfaceimpl.cpp @@ -103,6 +103,7 @@ set(VM_SOURCES_DAC_AND_WKS_COMMON perfinfo.cpp precode.cpp prestub.cpp + profilerdiagnosticprotocolhelper.cpp rejit.cpp sigformat.cpp siginfo.cpp diff --git a/src/vm/diagnosticprotocolhelper.cpp b/src/vm/diagnosticprotocolhelper.cpp deleted file mode 100644 index b48432df3f..0000000000 --- a/src/vm/diagnosticprotocolhelper.cpp +++ /dev/null @@ -1,235 +0,0 @@ -// 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. - -#include "common.h" -#include "fastserializer.h" -#include "diagnosticprotocolhelper.h" -#include "diagnosticsipc.h" -#include "diagnosticsprotocol.h" -#if defined(FEATURE_PROFAPI_ATTACH_DETACH) && !defined(DACCESS_COMPILE) -#include "profilinghelper.h" -#include "profilinghelper.inl" -#endif // defined(FEATURE_PROFAPI_ATTACH_DETACH) && !defined(DACCESS_COMPILE) - -#ifdef FEATURE_PERFTRACING - -static void WriteStatus(uint64_t result, IpcStream* pStream) -{ - uint32_t nBytesWritten = 0; - bool fSuccess = pStream->Write(&result, sizeof(result), nBytesWritten); - if (fSuccess) - { - fSuccess = pStream->Flush(); - } -} - -#ifdef FEATURE_PAL - -void DiagnosticProtocolHelper::GenerateCoreDump(IpcStream* pStream) -{ - CONTRACTL - { - THROWS; - GC_TRIGGERS; - MODE_PREEMPTIVE; - PRECONDITION(pStream != nullptr); - } - CONTRACTL_END; - - if (pStream == nullptr) - return; - - HRESULT hr = S_OK; - - // TODO: Read within a loop. - uint8_t buffer[IpcStreamReadBufferSize] { }; - uint32_t nNumberOfBytesRead = 0; - bool fSuccess = pStream->Read(buffer, sizeof(buffer), nNumberOfBytesRead); - if (fSuccess) - { - // The protocol buffer is defined as: - // string - dumpName (UTF16) - // int - dumpType - // int - diagnostics - // returns - // ulong - status - LPCWSTR pwszDumpName; - INT dumpType; - INT diagnostics; - - uint8_t *pBufferCursor = buffer; - uint32_t bufferLen = nNumberOfBytesRead; - - if (TryParseString(pBufferCursor, bufferLen, pwszDumpName) && - TryParse(pBufferCursor, bufferLen, dumpType) && - TryParse(pBufferCursor, bufferLen, diagnostics)) - { - MAKE_UTF8PTR_FROMWIDE_NOTHROW(szDumpName, pwszDumpName); - if (szDumpName != nullptr) - { - if (!PAL_GenerateCoreDump(szDumpName, dumpType, diagnostics)) - { - hr = E_FAIL; - } - } - else - { - hr = E_OUTOFMEMORY; - } - } - else - { - hr = E_INVALIDARG; - } - } - else - { - hr = E_UNEXPECTED; - } - - WriteStatus(hr, pStream); - delete pStream; -} - -#endif // FEATURE_PAL - -#ifdef FEATURE_PROFAPI_ATTACH_DETACH -void DiagnosticProtocolHelper::AttachProfiler(IpcStream *pStream) -{ - CONTRACTL - { - THROWS; - GC_TRIGGERS; - MODE_PREEMPTIVE; - PRECONDITION(pStream != nullptr); - } - CONTRACTL_END; - - if (pStream == nullptr) - { - return; - } - - HRESULT hr = S_OK; - uint8_t buffer[IpcStreamReadBufferSize] { }; - uint32_t nNumberOfBytesRead = 0; - uint32_t dwAttachTimeout = 0; - CLSID profilerGuid = { }; - uint32_t cbProfilerPath = 0; - NewArrayHolder<WCHAR> pwszProfilerPath = nullptr; - uint32_t cbClientData = 0; - NewArrayHolder<uint8_t> pClientData = nullptr; - - uint8_t *pBufferCursor = 0; - uint32_t bufferLen = 0; - uint32_t pathSize = 0; - uint32_t bytesToRead = 0; - uint32_t bufferBytesRead = 0; - uint8_t *pClientDataCursor = 0; - - bool fSuccess = pStream->Read(buffer, sizeof(buffer), nNumberOfBytesRead); - if (!fSuccess) - { - hr = E_UNEXPECTED; - goto ErrExit; - } - - pBufferCursor = buffer; - bufferLen = nNumberOfBytesRead; - if (!(TryParse(pBufferCursor, bufferLen, dwAttachTimeout) && - TryParse(pBufferCursor, bufferLen, profilerGuid))) - { - hr = E_INVALIDARG; - goto ErrExit; - } - - if (!TryParse(pBufferCursor, bufferLen, cbProfilerPath) || - cbProfilerPath > bufferLen) - { - // TODO: A really long path (thousands of characters) could be longer than - // the buffer here, if that happens we'll return E_INVALIDARG. The read - // buffer is 8192 bytes, so has room for 4096 16 bit characters. Minus a few for - // the header, etc, the realistic max is around 4000 characters. - hr = E_INVALIDARG; - goto ErrExit; - } - - pwszProfilerPath = new (nothrow) WCHAR[cbProfilerPath]; - if (pwszProfilerPath == nullptr) - { - hr = E_INVALIDARG; - goto ErrExit; - } - - pathSize = cbProfilerPath * sizeof(WCHAR); - memcpy(pwszProfilerPath, pBufferCursor, pathSize); - bufferLen -= pathSize; - pBufferCursor += pathSize; - - if (!TryParse(pBufferCursor, bufferLen, cbClientData)) - { - hr = E_INVALIDARG; - goto ErrExit; - } - - pClientData = new (nothrow) uint8_t[cbClientData]; - if (pClientData == nullptr) - { - hr = E_OUTOFMEMORY; - goto ErrExit; - } - - bufferBytesRead = 0; - pClientDataCursor = pClientData; - // TODO: get rid of this ad-hoc byte[] parsing code - while (bufferBytesRead < cbClientData) - { - if (bufferLen == 0) - { - // Client data was bigger than the buffer, need to read more - fSuccess = pStream->Read(buffer, sizeof(buffer), nNumberOfBytesRead); - if (!fSuccess) - { - hr = E_UNEXPECTED; - goto ErrExit; - } - - pBufferCursor = buffer; - bufferLen = nNumberOfBytesRead; - } - - bytesToRead = min((cbClientData - bufferBytesRead), bufferLen); - memcpy(pClientDataCursor, pBufferCursor, bytesToRead); - pClientDataCursor += bytesToRead; - - _ASSERTE(bytesToRead <= bufferLen && "bytesToRead > bufferLen means we overran the buffer"); - bufferLen -= bytesToRead; - bufferBytesRead += bytesToRead; - } - - _ASSERTE(bufferBytesRead == cbClientData && "bufferBytesRead > cbClientData means we read too far"); - - if (cbClientData == 0) - { - pClientData = nullptr; - } - - if (!g_profControlBlock.fProfControlBlockInitialized) - { - hr = CORPROF_E_RUNTIME_UNINITIALIZED; - goto ErrExit; - } - - hr = ProfilingAPIUtility::LoadProfilerForAttach(&profilerGuid, - pwszProfilerPath, - pClientData, - cbClientData, - dwAttachTimeout); -ErrExit: - WriteStatus(hr, pStream); - delete pStream; -} -#endif // FEATURE_PROFAPI_ATTACH_DETACH - -#endif // FEATURE_PERFTRACING diff --git a/src/vm/diagnosticprotocolhelper.h b/src/vm/diagnosticprotocolhelper.h deleted file mode 100644 index 30a7be63a1..0000000000 --- a/src/vm/diagnosticprotocolhelper.h +++ /dev/null @@ -1,32 +0,0 @@ -// 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. - -#ifndef __DIAGNOSTIC_PROTOCOL_HELPER_H__ -#define __DIAGNOSTIC_PROTOCOL_HELPER_H__ - -#ifdef FEATURE_PERFTRACING - -#include "common.h" - -class IpcStream; - -class DiagnosticProtocolHelper -{ -public: - // IPC event handlers. -#ifdef FEATURE_PAL - static void GenerateCoreDump(IpcStream *pStream); // `dotnet-dump collect` -#endif - -#ifdef FEATURE_PROFAPI_ATTACH_DETACH - static void AttachProfiler(IpcStream *pStream); -#endif // FEATURE_PROFAPI_ATTACH_DETACH - -private: - const static uint32_t IpcStreamReadBufferSize = 8192; -}; - -#endif // FEATURE_PERFTRACING - -#endif // __DIAGNOSTIC_PROTOCOL_HELPER_H__ diff --git a/src/vm/diagnosticserver.cpp b/src/vm/diagnosticserver.cpp index e94fa81511..ae26ec9ca2 100644 --- a/src/vm/diagnosticserver.cpp +++ b/src/vm/diagnosticserver.cpp @@ -5,7 +5,9 @@ #include "common.h" #include "diagnosticserver.h" #include "eventpipeprotocolhelper.h" -#include "diagnosticprotocolhelper.h" +#include "dumpdiagnosticprotocolhelper.h" +#include "profilerdiagnosticprotocolhelper.h" +#include "diagnosticsprotocol.h" #ifdef FEATURE_PAL #include "pal.h" @@ -46,40 +48,42 @@ static DWORD WINAPI DiagnosticsServerThread(LPVOID lpThreadParameter) if (pStream == nullptr) continue; - // TODO: Read operation should happen in a loop. - uint32_t nNumberOfBytesRead = 0; - MessageHeader header; - bool fSuccess = pStream->Read(&header, sizeof(header), nNumberOfBytesRead); - if (!fSuccess || nNumberOfBytesRead != sizeof(header)) + DiagnosticsIpc::IpcMessage message; + if (!message.Initialize(pStream)) { + DiagnosticsIpc::IpcMessage::SendErrorMessage(pStream, CORDIAGIPC_E_BAD_ENCODING); delete pStream; continue; } - switch (header.RequestType) + if (::strcmp((char *)message.GetHeader().Magic, (char *)DiagnosticsIpc::DotnetIpcMagic_V1.Magic) != 0) { - case DiagnosticMessageType::StopEventPipeTracing: - EventPipeProtocolHelper::StopTracing(pStream); - break; + DiagnosticsIpc::IpcMessage::SendErrorMessage(pStream, CORDIAGIPC_E_UNKNOWN_MAGIC); + delete pStream; + continue; + } - case DiagnosticMessageType::CollectEventPipeTracing: - EventPipeProtocolHelper::CollectTracing(pStream); + switch ((DiagnosticsIpc::DiagnosticServerCommandSet)message.GetHeader().CommandSet) + { + case DiagnosticsIpc::DiagnosticServerCommandSet::EventPipe: + EventPipeProtocolHelper::HandleIpcMessage(message, pStream); break; #ifdef FEATURE_PAL - case DiagnosticMessageType::GenerateCoreDump: - DiagnosticProtocolHelper::GenerateCoreDump(pStream); + case DiagnosticsIpc::DiagnosticServerCommandSet::Dump: + DumpDiagnosticProtocolHelper::HandleIpcMessage(message, pStream); break; #endif #ifdef FEATURE_PROFAPI_ATTACH_DETACH - case DiagnosticMessageType::AttachProfiler: - DiagnosticProtocolHelper::AttachProfiler(pStream); + case DiagnosticsIpc::DiagnosticServerCommandSet::Profiler: + ProfilerDiagnosticProtocolHelper::AttachProfiler(message, pStream); break; #endif // FEATURE_PROFAPI_ATTACH_DETACH default: - STRESS_LOG1(LF_DIAGNOSTICS_PORT, LL_WARNING, "Received unknown request type (%d)\n", header.RequestType); + STRESS_LOG1(LF_DIAGNOSTICS_PORT, LL_WARNING, "Received unknown request type (%d)\n", message.GetHeader().CommandSet); + DiagnosticsIpc::IpcMessage::SendErrorMessage(pStream, CORDIAGIPC_E_UNKNOWN_COMMAND); delete pStream; break; } diff --git a/src/vm/diagnosticsprotocol.h b/src/vm/diagnosticsprotocol.h index 418e927ec4..a565030447 100644 --- a/src/vm/diagnosticsprotocol.h +++ b/src/vm/diagnosticsprotocol.h @@ -7,6 +7,13 @@ #ifdef FEATURE_PERFTRACING +#include "clr_std/type_traits" +#include "new.hpp" +#include "diagnosticsipc.h" +#include "corerror.h" + +#define DOTNET_IPC_V1_MAGIC "DOTNET_IPC_V1" + template <typename T> bool TryParse(uint8_t *&bufferCursor, uint32_t &bufferLen, T &result) { @@ -50,6 +57,480 @@ bool TryParseString(uint8_t *&bufferCursor, uint32_t &bufferLen, const T *&resul return true; } +namespace DiagnosticsIpc +{ + enum class IpcMagicVersion : uint8_t + { + DOTNET_IPC_V1 = 0x01, + // FUTURE + }; + + enum class DiagnosticServerCommandSet : uint8_t + { + // reserved = 0x00, + Dump = 0x01, + EventPipe = 0x02, + Profiler = 0x03, + + Server = 0xFF, + }; + + enum class DiagnosticServerCommandId : uint8_t + { + OK = 0x00, + Error = 0xFF, + }; + + struct MagicVersion + { + uint8_t Magic[14]; + }; + + // The header to be associated with every command and response + // to/from the diagnostics server + struct IpcHeader + { + union + { + MagicVersion _magic; + uint8_t Magic[14]; // Magic Version number; a 0 terminated char array + }; + uint16_t Size; // The size of the incoming packet, size = header + payload size + uint8_t CommandSet; // The scope of the Command. + uint8_t CommandId; // The command being sent + uint16_t Reserved; // reserved for future use + }; + + const MagicVersion DotnetIpcMagic_V1 = { "DOTNET_IPC_V1" }; + + const IpcHeader GenericSuccessHeader = + { + { DotnetIpcMagic_V1 }, + (uint16_t)sizeof(IpcHeader), + (uint8_t)DiagnosticServerCommandSet::Server, + (uint8_t)DiagnosticServerCommandId::OK, + (uint16_t)0x0000 + }; + + const IpcHeader GenericErrorHeader = + { + { DotnetIpcMagic_V1 }, + (uint16_t)sizeof(IpcHeader), + (uint8_t)DiagnosticServerCommandSet::Server, + (uint8_t)DiagnosticServerCommandId::Error, + (uint16_t)0x0000 + }; + + // The Following structs are template, meta-programming to enable + // users of the IpcMessage class to get free serialization for fixed-size structures. + // They check that the template parameter has a member (or static) function that + // has a specified signature and returns true or false based on that check. + // + // std::enable_if (and enable_if_t) act as a compile time flag to enable or + // disable a template specialization based on a boolean value. + // + // The Has* structs can be used as the boolean check in std::enable_if to + // enable a specific overload of a function based on whether the template parameter + // has that member function. + // + // These "switches" can be used in a variety of ways, but are used in the function + // template parameters below, e.g., + // + // template <typename T, + // typename = enable_if_t<HasTryParse<T>::value, const T*> = nullptr> + // const T* FnName(...) + // + // For more details on this pattern, look up "Substitution Failure Is Not An Error" or SFINAE + + // template meta-programming to check for bool(Flatten)(void*) member function + template <typename T> + struct HasFlatten + { + template <typename U, U u> struct Has; + template <typename U> static std::true_type test(Has<bool (U::*)(void*), &U::Flatten>*); + template <typename U> static std::false_type test(...); + static constexpr bool value = decltype(test<T>(nullptr))::value; + }; + + // template meta-programming to check for uint16_t(GetSize)() member function + template <typename T> + struct HasGetSize + { + template <typename U, U u> struct Has; + template <typename U> static std::true_type test(Has<uint16_t(U::*)(), &U::GetSize>*); + template <typename U> static std::false_type test(...); + static constexpr bool value = decltype(test<T>(nullptr))::value; + }; + + // template meta-programming to check for a const T*(TryParse)(BYTE*,uint16_t&) static function + template <typename T> + struct HasTryParse + { + template <typename U, U u> struct Has; + template <typename U> static std::true_type test(Has<const U* (*)(BYTE*, uint16_t&), &U::TryParse>*); + template <typename U> static std::false_type test(...); + static constexpr bool value = decltype(test<T>(nullptr))::value; + }; + + // Encodes the messages sent and received by the Diagnostics Server. + // + // Payloads that are fixed-size structs don't require any custom functionality. + // + // Payloads that are NOT fixed-size simply need to implement the following methods: + // * uint16_t GetSize() -> should return the flattened size of the payload + // * bool Flatten(BYTE *lpBuffer) -> Should serialize and write the payload to the provided buffer + // * const T *TryParse(BYTE *lpBuffer, uint16_t& bufferLen) -> should decode payload or return nullptr + class IpcMessage + { + public: + + // empty constructor for default values. Use Initialize. + IpcMessage() + : m_pData(nullptr), m_Header(), m_Size(0) + { + LIMITED_METHOD_CONTRACT; + }; + + // Initialize an outgoing IpcMessage with a header and payload + template <typename T> + bool Initialize(IpcHeader header, T& payload) + { + CONTRACTL + { + NOTHROW; + GC_TRIGGERS; + MODE_PREEMPTIVE; + } + CONTRACTL_END; + + return FlattenImpl<T>(payload); + }; + + // Initialize an outgoing IpcMessage with a header and payload + template <typename T> + bool Initialize(IpcHeader header, T&& payload) + { + CONTRACTL + { + NOTHROW; + GC_TRIGGERS; + MODE_PREEMPTIVE; + } + CONTRACTL_END; + + return FlattenImpl<T>(payload); + }; + + // Initialize an outgoing IpcMessage for an error + bool Initialize(HRESULT error) + { + CONTRACTL + { + NOTHROW; + GC_TRIGGERS; + MODE_PREEMPTIVE; + } + CONTRACTL_END; + + return Initialize(GenericErrorHeader, error); + } + + // Initialize an incoming IpcMessage from a stream by parsing + // the header and payload. + // + // If either fail, this returns false, true otherwise + bool Initialize(::IpcStream* pStream) + { + CONTRACTL + { + NOTHROW; + GC_TRIGGERS; + MODE_PREEMPTIVE; + } + CONTRACTL_END; + + return TryParse(pStream); + } + + ~IpcMessage() + { + CONTRACTL + { + NOTHROW; + GC_TRIGGERS; + MODE_ANY; + } + CONTRACTL_END; + + delete[] m_pData; + }; + + // Given a buffer, attempt to parse out a given payload type + // If a payload type is fixed-size, this will simply return + // a pointer to the buffer of data reinterpreted as a const pointer. + // Otherwise, your payload type should implement the following static method: + // > const T *TryParse(BYTE *lpBuffer) + // which this will call if it exists. + // + // user is expected to check for a nullptr in the error case for non fixed-size payloads + // user owns the memory returned and is expected to free it when finished + template <typename T> + const T* TryParsePayload() + { + CONTRACTL + { + NOTHROW; + GC_TRIGGERS; + MODE_PREEMPTIVE; + } + CONTRACTL_END; + + ASSERT(IsFlattened()); + return TryParsePayloadImpl<T>(); + }; + + const IpcHeader& GetHeader() const + { + LIMITED_METHOD_CONTRACT; + + return m_Header; + }; + + bool Send(IpcStream* pStream) + { + CONTRACTL + { + NOTHROW; + GC_TRIGGERS; + MODE_ANY; + PRECONDITION(pStream != nullptr); + } + CONTRACTL_END; + + ASSERT(IsFlattened()); + uint32_t nBytesWritten; + bool success = pStream->Write(m_pData, m_Size, nBytesWritten); + + return nBytesWritten == m_Size && success; + }; + + // Send an Error message across the pipe. + // Will return false on failure of any step (init or send). + // Regardless of success of this function, the spec + // dictates that the connection be closed on error, + // so the user is expected to delete the IpcStream + // after handling error cases. + static bool SendErrorMessage(IpcStream* pStream, HRESULT error) + { + CONTRACTL + { + NOTHROW; + GC_TRIGGERS; + MODE_PREEMPTIVE; + PRECONDITION(pStream != nullptr); + } + CONTRACTL_END; + + IpcMessage errorMessage; + bool success = errorMessage.Initialize((int32_t)error); + if (success) + success = errorMessage.Send(pStream); + return success; + }; + private: + // Pointer to flattened buffer filled with: + // incoming message: payload (could be empty which would be nullptr) + // outgoing message: header + payload + BYTE* m_pData; + // header associated with this message + struct IpcHeader m_Header; + // The total size of the message (header + payload) + uint16_t m_Size; + + bool IsFlattened() const + { + LIMITED_METHOD_CONTRACT; + + return m_pData != NULL; + }; + + // Attempt to populate header and payload from a buffer. + // Payload is left opaque as a flattened buffer in m_pData + bool TryParse(::IpcStream* pStream) + { + CONTRACTL + { + NOTHROW; + GC_TRIGGERS; + MODE_PREEMPTIVE; + PRECONDITION(pStream != nullptr); + } + CONTRACTL_END; + + // Read out header first + uint32_t nBytesRead; + bool success = pStream->Read(&m_Header, sizeof(IpcHeader), nBytesRead); + if (!success || nBytesRead < sizeof(IpcHeader)) + { + return false; + } + + m_Size = m_Header.Size; + + // Then read out payload to buffer + uint16_t payloadSize = m_Header.Size - sizeof(IpcHeader); + if (payloadSize != 0) + { + BYTE* temp_buffer = new (nothrow) BYTE[payloadSize]; + if (temp_buffer == nullptr) + { + // OOM + return false; + } + + success = pStream->Read(temp_buffer, payloadSize, nBytesRead); + if (!success || nBytesRead < payloadSize) + { + delete[] temp_buffer; + return false; + } + m_pData = temp_buffer; + } + + return true; + }; + + // Create a buffer of the correct size filled with + // header + payload. Correctly handles flattening of + // trivial structures, but uses a bool(Flatten)(void*) + // and uint16_t(GetSize)() when available. + + // Handles the case where the payload structure exposes Flatten + // and GetSize methods + template <typename U, + typename std::enable_if<HasFlatten<U>::value&& HasGetSize<U>::value, int>::type = 0> + bool FlattenImpl(U& payload) + { + CONTRACTL + { + NOTHROW; + GC_TRIGGERS; + MODE_PREEMPTIVE; + } + CONTRACTL_END; + + if (IsFlattened()) + return true; + + S_UINT16 temp_size = S_UINT16(0); + temp_size += sizeof(struct IpcHeader) + payload.GetSize(); + ASSERT(!temp_size.IsOverflow()); + + m_Size = temp_size.Value(); + + BYTE* temp_buffer = new (nothrow) BYTE[m_Size]; + if (temp_buffer == nullptr) + { + // OOM + return false; + } + + BYTE* temp_buffer_cursor = temp_buffer; + + m_Header.Size = m_Size; + + memcpy(temp_buffer_cursor, &m_Header, sizeof(struct IpcHeader)); + temp_buffer_cursor += sizeof(struct IpcHeader); + + payload.Flatten(temp_buffer_cursor); + + ASSERT(m_pData == nullptr); + m_pData = temp_buffer; + + return true; + }; + + // handles the case where we were handed a struct with no Flatten or GetSize method + template <typename U, + typename std::enable_if<!HasFlatten<U>::value && !HasGetSize<U>::value, int>::type = 0> + bool FlattenImpl(U& payload) + { + CONTRACTL + { + NOTHROW; + GC_TRIGGERS; + MODE_PREEMPTIVE; + } + CONTRACTL_END; + + if (IsFlattened()) + return true; + + S_UINT16 temp_size = S_UINT16(0); + temp_size += sizeof(struct IpcHeader) + sizeof(payload); + ASSERT(!temp_size.IsOverflow()); + + m_Size = temp_size.Value(); + + BYTE* temp_buffer = new (nothrow) BYTE[m_Size]; + if (temp_buffer == nullptr) + { + // OOM + return false; + } + + BYTE* temp_buffer_cursor = temp_buffer; + + m_Header.Size = m_Size; + + memcpy(temp_buffer_cursor, &m_Header, sizeof(struct IpcHeader)); + temp_buffer_cursor += sizeof(struct IpcHeader); + + memcpy(temp_buffer_cursor, &payload, sizeof(payload)); + + ASSERT(m_pData == nullptr); + m_pData = temp_buffer; + + return true; + }; + + template <typename U, + typename std::enable_if<HasTryParse<U>::value, int>::type = 0> + const U* TryParsePayloadImpl() + { + CONTRACTL + { + NOTHROW; + GC_TRIGGERS; + MODE_ANY; + } + CONTRACTL_END; + + uint16_t payloadSize = m_Size - (uint16_t)sizeof(IpcHeader); + const U* payload = U::TryParse(m_pData, payloadSize); + m_pData = nullptr; // user is expected to clean up buffer when finished with it + return payload; + }; + + template <typename U, + typename std::enable_if<!HasTryParse<U>::value, int>::type = 0> + const U* TryParsePayloadImpl() + { + CONTRACTL + { + NOTHROW; + GC_TRIGGERS; + MODE_ANY; + } + CONTRACTL_END; + + const U* payload = reinterpret_cast<const U*>(m_pData); + m_pData = nullptr; // user is expected to clean up buffer when finished with it + return payload; + }; + }; +}; + #endif // FEATURE_PERFTRACING #endif // __DIAGNOSTICS_PROTOCOL_H__ diff --git a/src/vm/dumpdiagnosticprotocolhelper.cpp b/src/vm/dumpdiagnosticprotocolhelper.cpp new file mode 100644 index 0000000000..60d1fd05a0 --- /dev/null +++ b/src/vm/dumpdiagnosticprotocolhelper.cpp @@ -0,0 +1,119 @@ +// 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. + +#include "common.h" +#include "fastserializer.h" +#include "dumpdiagnosticprotocolhelper.h" +#include "diagnosticsipc.h" +#include "diagnosticsprotocol.h" + +#ifdef FEATURE_PERFTRACING + +#ifdef FEATURE_PAL + +void DumpDiagnosticProtocolHelper::HandleIpcMessage(DiagnosticsIpc::IpcMessage& message, IpcStream* pStream) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_PREEMPTIVE; + PRECONDITION(pStream != nullptr); + } + CONTRACTL_END; + + switch ((DumpCommandId)message.GetHeader().CommandId) + { + case DumpCommandId::GenerateCoreDump: + DumpDiagnosticProtocolHelper::GenerateCoreDump(message, pStream); + break; + + default: + STRESS_LOG1(LF_DIAGNOSTICS_PORT, LL_WARNING, "Received unknown request type (%d)\n", message.GetHeader().CommandSet); + DiagnosticsIpc::IpcMessage::SendErrorMessage(pStream, CORDIAGIPC_E_UNKNOWN_COMMAND); + delete pStream; + break; + } +} + +const GenerateCoreDumpCommandPayload* GenerateCoreDumpCommandPayload::TryParse(BYTE* lpBuffer, uint16_t& BufferSize) +{ + CONTRACTL + { + NOTHROW; + GC_TRIGGERS; + MODE_PREEMPTIVE; + PRECONDITION(lpBuffer != nullptr); + } + CONTRACTL_END; + + GenerateCoreDumpCommandPayload* payload = new (nothrow) GenerateCoreDumpCommandPayload; + if (payload == nullptr) + { + // OOM + return nullptr; + } + + payload->incomingBuffer = lpBuffer; + uint8_t* pBufferCursor = payload->incomingBuffer; + uint32_t bufferLen = BufferSize; + if (!TryParseString(pBufferCursor, bufferLen, payload->dumpName) || + !::TryParse(pBufferCursor, bufferLen, payload->dumpType) || + !::TryParse(pBufferCursor, bufferLen, payload->diagnostics)) + { + delete payload; + return nullptr; + } + + return payload; +} + +void DumpDiagnosticProtocolHelper::GenerateCoreDump(DiagnosticsIpc::IpcMessage& message, IpcStream* pStream) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_PREEMPTIVE; + PRECONDITION(pStream != nullptr); + } + CONTRACTL_END; + + if (pStream == nullptr) + return; + + NewHolder<const GenerateCoreDumpCommandPayload> payload = message.TryParsePayload<GenerateCoreDumpCommandPayload>(); + if (payload == nullptr) + { + DiagnosticsIpc::IpcMessage::SendErrorMessage(pStream, CORDIAGIPC_E_BAD_ENCODING); + delete pStream; + return; + } + + MAKE_UTF8PTR_FROMWIDE_NOTHROW(szDumpName, payload->dumpName); + if (szDumpName != nullptr) + { + if (!PAL_GenerateCoreDump(szDumpName, payload->dumpType, payload->diagnostics)) + { + DiagnosticsIpc::IpcMessage::SendErrorMessage(pStream, E_FAIL); + delete pStream; + return; + } + } + else + { + DiagnosticsIpc::IpcMessage::SendErrorMessage(pStream, E_OUTOFMEMORY); + delete pStream; + return; + } + + DiagnosticsIpc::IpcMessage successResponse; + if (successResponse.Initialize(DiagnosticsIpc::GenericSuccessHeader, S_OK)) + successResponse.Send(pStream); + delete pStream; +} + +#endif // FEATURE_PAL + +#endif // FEATURE_PERFTRACING diff --git a/src/vm/dumpdiagnosticprotocolhelper.h b/src/vm/dumpdiagnosticprotocolhelper.h new file mode 100644 index 0000000000..a5e0aef89d --- /dev/null +++ b/src/vm/dumpdiagnosticprotocolhelper.h @@ -0,0 +1,55 @@ +// 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. + +#ifndef __DUMP_DIAGNOSTIC_PROTOCOL_HELPER_H__ +#define __DUMP_DIAGNOSTIC_PROTOCOL_HELPER_H__ + +#ifdef FEATURE_PERFTRACING + +#include "common.h" +#include <diagnosticsprotocol.h> + + +class IpcStream; + +// The Diagnostic command set is 0x01 +enum class DumpCommandId : uint8_t +{ + // reserved = 0x00, + GenerateCoreDump = 0x01, + // future +}; + +struct GenerateCoreDumpCommandPayload +{ + NewArrayHolder<BYTE> incomingBuffer; + + // The protocol buffer is defined as: + // string - dumpName (UTF16) + // int - dumpType + // int - diagnostics + // returns + // ulong - status + LPCWSTR dumpName; + uint32_t dumpType; + uint32_t diagnostics; + static const GenerateCoreDumpCommandPayload* TryParse(BYTE* lpBuffer, uint16_t& BufferSize); +}; + +class DumpDiagnosticProtocolHelper +{ +public: + // IPC event handlers. +#ifdef FEATURE_PAL + static void GenerateCoreDump(DiagnosticsIpc::IpcMessage& message, IpcStream *pStream); // `dotnet-dump collect` + static void HandleIpcMessage(DiagnosticsIpc::IpcMessage& message, IpcStream* pStream); +#endif + +private: + const static uint32_t IpcStreamReadBufferSize = 8192; +}; + +#endif // FEATURE_PERFTRACING + +#endif // __DUMP_DIAGNOSTIC_PROTOCOL_HELPER_H__ diff --git a/src/vm/eventpipeprotocolhelper.cpp b/src/vm/eventpipeprotocolhelper.cpp index b30f479ca1..7ec9fc94f5 100644 --- a/src/vm/eventpipeprotocolhelper.cpp +++ b/src/vm/eventpipeprotocolhelper.cpp @@ -26,6 +26,73 @@ static bool IsNullOrWhiteSpace(LPCWSTR value) return true; } +static bool TryParseCircularBufferSize(uint8_t*& bufferCursor, uint32_t& bufferLen, uint32_t& circularBufferSizeInMB) +{ + const bool CanParse = TryParse(bufferCursor, bufferLen, circularBufferSizeInMB); + return CanParse && (circularBufferSizeInMB > 0); +} + +const EventPipeCollectTracingCommandPayload* EventPipeCollectTracingCommandPayload::TryParse(BYTE* lpBuffer, uint16_t& BufferSize) +{ + CONTRACTL + { + NOTHROW; + GC_TRIGGERS; + MODE_PREEMPTIVE; + PRECONDITION(lpBuffer != nullptr); + } + CONTRACTL_END; + + EventPipeCollectTracingCommandPayload *payload = new (nothrow) EventPipeCollectTracingCommandPayload; + if (payload == nullptr) + { + // OOM + return nullptr; + } + + payload->incomingBuffer = lpBuffer; + uint8_t* pBufferCursor = payload->incomingBuffer; + uint32_t bufferLen = BufferSize; + if (!TryParseCircularBufferSize(pBufferCursor, bufferLen, payload->circularBufferSizeInMB) || + !TryParseString(pBufferCursor, bufferLen, payload->outputPath) || + !EventPipeProtocolHelper::TryParseProviderConfiguration(pBufferCursor, bufferLen, payload->providerConfigs)) + { + delete payload; + return nullptr; + } + + return payload; +} + +void EventPipeProtocolHelper::HandleIpcMessage(DiagnosticsIpc::IpcMessage& message, IpcStream* pStream) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_ANY; + PRECONDITION(pStream != nullptr); + } + CONTRACTL_END; + + switch ((EventPipeCommandId)message.GetHeader().CommandId) + { + case EventPipeCommandId::CollectTracing: + EventPipeProtocolHelper::CollectTracing(message, pStream); + break; + + case EventPipeCommandId::StopTracing: + EventPipeProtocolHelper::StopTracing(message, pStream); + break; + + default: + STRESS_LOG1(LF_DIAGNOSTICS_PORT, LL_WARNING, "Received unknown request type (%d)\n", message.GetHeader().CommandSet); + DiagnosticsIpc::IpcMessage::SendErrorMessage(pStream, CORDIAGIPC_E_UNKNOWN_COMMAND); + delete pStream; + break; + } +} + bool EventPipeProtocolHelper::TryParseProviderConfiguration(uint8_t *&bufferCursor, uint32_t &bufferLen, CQuickArray<EventPipeProviderConfiguration> &result) { // Picking an arbitrary upper bound, @@ -67,7 +134,7 @@ bool EventPipeProtocolHelper::TryParseProviderConfiguration(uint8_t *&bufferCurs return (countConfigs > 0); } -void EventPipeProtocolHelper::StopTracing(IpcStream *pStream) +void EventPipeProtocolHelper::StopTracing(DiagnosticsIpc::IpcMessage& message, IpcStream *pStream) { CONTRACTL { @@ -78,27 +145,21 @@ void EventPipeProtocolHelper::StopTracing(IpcStream *pStream) } CONTRACTL_END; - uint32_t nNumberOfBytesRead = 0; - EventPipeSessionID sessionId = (EventPipeSessionID) nullptr; - bool fSuccess = pStream->Read(&sessionId, sizeof(sessionId), nNumberOfBytesRead); - if (!fSuccess || nNumberOfBytesRead != sizeof(sessionId)) + NewHolder<const EventPipeStopTracingCommandPayload> payload = message.TryParsePayload<EventPipeStopTracingCommandPayload>(); + if (payload == nullptr) { - // TODO: Add error handling. + DiagnosticsIpc::IpcMessage::SendErrorMessage(pStream, CORDIAGIPC_E_BAD_ENCODING); delete pStream; return; } - EventPipe::Disable(sessionId); - uint32_t nBytesWritten = 0; - fSuccess = pStream->Write(&sessionId, sizeof(sessionId), nBytesWritten); - if (!fSuccess) - { - // TODO: Add error handling. - delete pStream; - return; - } + EventPipe::Disable(payload->sessionId); + + DiagnosticsIpc::IpcMessage stopTracingResponse; + if (stopTracingResponse.Initialize(DiagnosticsIpc::GenericSuccessHeader, payload->sessionId)) + stopTracingResponse.Send(pStream); - fSuccess = pStream->Flush(); + bool fSuccess = pStream->Flush(); if (!fSuccess) { // TODO: Add error handling. @@ -106,13 +167,7 @@ void EventPipeProtocolHelper::StopTracing(IpcStream *pStream) delete pStream; } -static bool TryParseCircularBufferSize(uint8_t *&bufferCursor, uint32_t &bufferLen, uint32_t &circularBufferSizeInMB) -{ - const bool CanParse = TryParse(bufferCursor, bufferLen, circularBufferSizeInMB); - return CanParse && (circularBufferSizeInMB > 0); -} - -void EventPipeProtocolHelper::CollectTracing(IpcStream *pStream) +void EventPipeProtocolHelper::CollectTracing(DiagnosticsIpc::IpcMessage& message, IpcStream *pStream) { CONTRACTL { @@ -123,55 +178,30 @@ void EventPipeProtocolHelper::CollectTracing(IpcStream *pStream) } CONTRACTL_END; - if (pStream == nullptr) - return; - - // TODO: Read within a loop. - uint8_t buffer[IpcStreamReadBufferSize]{}; - uint32_t nNumberOfBytesRead = 0; - bool fSuccess = pStream->Read(buffer, sizeof(buffer), nNumberOfBytesRead); - if (!fSuccess) + const EventPipeCollectTracingCommandPayload* payload = message.TryParsePayload<EventPipeCollectTracingCommandPayload>(); + if (payload == nullptr) { - // TODO: Add error handling. - delete pStream; - return; - } - - // The protocol buffer is defined as: - // X, Y, Z means encode bytes for X followed by bytes for Y followed by bytes for Z - // message = uint circularBufferMB, string outputPath, array<provider_config> providers - // uint = 4 little endian bytes - // wchar = 2 little endian bytes, UTF16 encoding - // array<T> = uint length, length # of Ts - // string = (array<char> where the last char must = 0) or (length = 0) - // provider_config = ulong keywords, uint logLevel, string provider_name, string filter_data - - LPCWSTR strOutputPath; - uint32_t circularBufferSizeInMB = EventPipeProtocolHelper::DefaultCircularBufferMB; - CQuickArray<EventPipeProviderConfiguration> providerConfigs; - - uint8_t *pBufferCursor = buffer; - uint32_t bufferLen = nNumberOfBytesRead; - if (!TryParseCircularBufferSize(pBufferCursor, bufferLen, circularBufferSizeInMB) || - !TryParseString(pBufferCursor, bufferLen, strOutputPath) || // TODO: Remove. Currently ignored in this scenario. - !TryParseProviderConfiguration(pBufferCursor, bufferLen, providerConfigs)) - { - // TODO: error handling + DiagnosticsIpc::IpcMessage::SendErrorMessage(pStream, CORDIAGIPC_E_BAD_ENCODING); + delete payload; delete pStream; return; } auto sessionId = EventPipe::Enable( nullptr, // strOutputPath (ignored in this scenario) - circularBufferSizeInMB, // circularBufferSizeInMB + payload->circularBufferSizeInMB, // circularBufferSizeInMB DefaultProfilerSamplingRateInNanoseconds, // ProfilerSamplingRateInNanoseconds - providerConfigs.Ptr(), // pConfigs - static_cast<uint32_t>(providerConfigs.Size()), // numConfigs + payload->providerConfigs.Ptr(), // pConfigs + static_cast<uint32_t>(payload->providerConfigs.Size()), // numConfigs EventPipeSessionType::IpcStream, // EventPipeSessionType pStream); // IpcStream if (sessionId == 0) + { + DiagnosticsIpc::IpcMessage::SendErrorMessage(pStream, E_FAIL); + delete payload; delete pStream; + } } #endif // FEATURE_PERFTRACING diff --git a/src/vm/eventpipeprotocolhelper.h b/src/vm/eventpipeprotocolhelper.h index 8832521165..3df5a05af4 100644 --- a/src/vm/eventpipeprotocolhelper.h +++ b/src/vm/eventpipeprotocolhelper.h @@ -9,22 +9,58 @@ #include "common.h" #include "eventpipe.h" +#include "diagnosticsipc.h" +#include "diagnosticsprotocol.h" class IpcStream; +// The event pipe command set is 0x02 +// see diagnosticsipc.h and diagnosticserver.h for more details +enum class EventPipeCommandId : uint8_t +{ + StopTracing = 0x01, + CollectTracing = 0x02, + // future +}; + +// Command = 0x0202 +struct EventPipeCollectTracingCommandPayload +{ + NewArrayHolder<BYTE> incomingBuffer; + + // The protocol buffer is defined as: + // X, Y, Z means encode bytes for X followed by bytes for Y followed by bytes for Z + // message = uint circularBufferMB, string outputPath, array<provider_config> providers + // uint = 4 little endian bytes + // wchar = 2 little endian bytes, UTF16 encoding + // array<T> = uint length, length # of Ts + // string = (array<char> where the last char must = 0) or (length = 0) + // provider_config = ulong keywords, uint logLevel, string provider_name, string filter_data + uint32_t circularBufferSizeInMB; + LPCWSTR outputPath; + CQuickArray<EventPipeProviderConfiguration> providerConfigs; + static const EventPipeCollectTracingCommandPayload* TryParse(BYTE* lpBuffer, uint16_t& BufferSize); +}; + +// Command = 0x0201 +struct EventPipeStopTracingCommandPayload +{ + EventPipeSessionID sessionId; +}; + class EventPipeProtocolHelper { public: // IPC event handlers. - static void StopTracing(IpcStream *pStream); - static void CollectTracing(IpcStream *pStream); // `dotnet-trace collect` + static void HandleIpcMessage(DiagnosticsIpc::IpcMessage& message, IpcStream *pStream); + static void StopTracing(DiagnosticsIpc::IpcMessage& message, IpcStream *pStream); + static void CollectTracing(DiagnosticsIpc::IpcMessage& message, IpcStream *pStream); // `dotnet-trace collect` + static bool TryParseProviderConfiguration(uint8_t *&bufferCursor, uint32_t &bufferLen, CQuickArray<EventPipeProviderConfiguration> &result); private: const static uint32_t DefaultCircularBufferMB = 1024; // 1 GB const static uint32_t DefaultProfilerSamplingRateInNanoseconds = 1000000; // 1 msec. const static uint32_t IpcStreamReadBufferSize = 8192; - - static bool TryParseProviderConfiguration(uint8_t *&bufferCursor, uint32_t &bufferLen, CQuickArray<EventPipeProviderConfiguration> &result); }; #endif // FEATURE_PERFTRACING diff --git a/src/vm/fastserializer.cpp b/src/vm/fastserializer.cpp index 73a8da553e..3e0cb73143 100644 --- a/src/vm/fastserializer.cpp +++ b/src/vm/fastserializer.cpp @@ -5,6 +5,8 @@ #include "common.h" #include "fastserializer.h" #include "diagnosticsipc.h" +#include <diagnosticsprotocol.h> +#include <eventpipeprotocolhelper.h> #ifdef FEATURE_PERFTRACING @@ -26,13 +28,9 @@ IpcStreamWriter::IpcStreamWriter(uint64_t id, IpcStream *pStream) : _pStream(pSt if (_pStream == nullptr) return; - uint32_t nBytesWritten = 0; - bool fSuccess = _pStream->Write(&id, sizeof(id), nBytesWritten); - if (!fSuccess) - { - delete _pStream; - _pStream = nullptr; - } + DiagnosticsIpc::IpcMessage successResponse; + if (successResponse.Initialize(DiagnosticsIpc::GenericSuccessHeader, id)) + successResponse.Send(pStream); } IpcStreamWriter::~IpcStreamWriter() diff --git a/src/vm/profilerdiagnosticprotocolhelper.cpp b/src/vm/profilerdiagnosticprotocolhelper.cpp new file mode 100644 index 0000000000..3f81b51226 --- /dev/null +++ b/src/vm/profilerdiagnosticprotocolhelper.cpp @@ -0,0 +1,125 @@ +// 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. + +#include "common.h" +#include "fastserializer.h" +#include "profilerdiagnosticprotocolhelper.h" +#include "diagnosticsipc.h" +#include "diagnosticsprotocol.h" + +#if defined(FEATURE_PERFTRACING) && defined(FEATURE_PROFAPI_ATTACH_DETACH) && !defined(DACCESS_COMPILE) +#include "profilinghelper.h" +#include "profilinghelper.inl" + +void ProfilerDiagnosticProtocolHelper::HandleIpcMessage(DiagnosticsIpc::IpcMessage& message, IpcStream* pStream) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_PREEMPTIVE; + PRECONDITION(pStream != nullptr); + } + CONTRACTL_END; + + switch ((ProfilerCommandId)message.GetHeader().CommandId) + { + case ProfilerCommandId::AttachProfiler: + ProfilerDiagnosticProtocolHelper::AttachProfiler(message, pStream); + break; + + default: + STRESS_LOG1(LF_DIAGNOSTICS_PORT, LL_WARNING, "Received unknown request type (%d)\n", message.GetHeader().CommandSet); + DiagnosticsIpc::IpcMessage::SendErrorMessage(pStream, CORDIAGIPC_E_UNKNOWN_COMMAND); + delete pStream; + break; + } +} + +const AttachProfilerCommandPayload* AttachProfilerCommandPayload::TryParse(BYTE* lpBuffer, uint16_t& BufferSize) +{ + CONTRACTL + { + NOTHROW; + GC_TRIGGERS; + MODE_PREEMPTIVE; + PRECONDITION(lpBuffer != nullptr); + } + CONTRACTL_END; + + AttachProfilerCommandPayload* payload = new (nothrow) AttachProfilerCommandPayload; + if (payload == nullptr) + { + // OOM + return nullptr; + } + + payload->incomingBuffer = lpBuffer; + uint8_t* pBufferCursor = payload->incomingBuffer; + uint32_t bufferLen = BufferSize; + if (!::TryParse(pBufferCursor, bufferLen, payload->dwAttachTimeout) || + !::TryParse(pBufferCursor, bufferLen, payload->profilerGuid) || + !TryParseString(pBufferCursor, bufferLen, payload->pwszProfilerPath) || + !::TryParse(pBufferCursor, bufferLen, payload->cbClientData) || + !(bufferLen <= payload->cbClientData)) + { + delete payload; + return nullptr; + } + + payload->pClientData = pBufferCursor; + + return payload; +} + +void ProfilerDiagnosticProtocolHelper::AttachProfiler(DiagnosticsIpc::IpcMessage& message, IpcStream *pStream) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_PREEMPTIVE; + PRECONDITION(pStream != nullptr); + } + CONTRACTL_END; + + if (pStream == nullptr) + { + return; + } + + HRESULT hr = S_OK; + NewHolder<const AttachProfilerCommandPayload> payload = message.TryParsePayload<AttachProfilerCommandPayload>(); + if (payload == nullptr) + { + hr = CORDIAGIPC_E_BAD_ENCODING; + goto ErrExit; + } + + if (!g_profControlBlock.fProfControlBlockInitialized) + { + hr = CORPROF_E_RUNTIME_UNINITIALIZED; + goto ErrExit; + } + + hr = ProfilingAPIUtility::LoadProfilerForAttach(&payload->profilerGuid, + payload->pwszProfilerPath, + payload->pClientData, + payload->cbClientData, + payload->dwAttachTimeout); +ErrExit: + if (hr != S_OK) + { + DiagnosticsIpc::IpcMessage::SendErrorMessage(pStream, hr); + } + else + { + DiagnosticsIpc::IpcMessage profilerAttachResponse; + if (profilerAttachResponse.Initialize(DiagnosticsIpc::GenericSuccessHeader, hr)) + profilerAttachResponse.Send(pStream); + } + delete pStream; +} + +#endif // defined(FEATURE_PERFTRACING) && defined(FEATURE_PROFAPI_ATTACH_DETACH) && !defined(DACCESS_COMPILE) diff --git a/src/vm/profilerdiagnosticprotocolhelper.h b/src/vm/profilerdiagnosticprotocolhelper.h new file mode 100644 index 0000000000..e5b08f771f --- /dev/null +++ b/src/vm/profilerdiagnosticprotocolhelper.h @@ -0,0 +1,58 @@ +// 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. + +#ifndef __PROFILER_DIAGNOSTIC_PROTOCOL_HELPER_H__ +#define __PROFILER_DIAGNOSTIC_PROTOCOL_HELPER_H__ + +#if defined(FEATURE_PROFAPI_ATTACH_DETACH) && defined(FEATURE_PERFTRACING) + +#include "common.h" +#include <diagnosticsprotocol.h> + + +class IpcStream; + +// The Diagnostic command set is 0x01 +enum class ProfilerCommandId : uint8_t +{ + // reserved = 0x00, + AttachProfiler = 0x01, + // future +}; + +struct AttachProfilerCommandPayload +{ + NewArrayHolder<BYTE> incomingBuffer; + + // The protocol buffer is defined as: + // uint - attach timeout + // CLSID - profiler GUID + // string - profiler path + // array<char> - client data + // returns + // ulong - status + + uint32_t dwAttachTimeout; + CLSID profilerGuid; + LPCWSTR pwszProfilerPath; + uint32_t cbClientData; + uint8_t* pClientData; + + static const AttachProfilerCommandPayload* TryParse(BYTE* lpBuffer, uint16_t& BufferSize); +}; + +class ProfilerDiagnosticProtocolHelper +{ +public: + // IPC event handlers. + static void HandleIpcMessage(DiagnosticsIpc::IpcMessage& message, IpcStream* pStream); + static void AttachProfiler(DiagnosticsIpc::IpcMessage& message, IpcStream *pStream); + +private: + const static uint32_t IpcStreamReadBufferSize = 8192; +}; + +#endif // defined(FEATURE_PROFAPI_ATTACH_DETACH) && defined(FEATURE_PERFTRACING) + +#endif // __PROFILER_DIAGNOSTIC_PROTOCOL_HELPER_H__ |