summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJohn Salem <josalem@microsoft.com>2019-05-23 20:18:33 -0700
committerGitHub <noreply@github.com>2019-05-23 20:18:33 -0700
commit267f8299ac1fd97430284fa6d44e5e9c6ed26850 (patch)
tree43a9f01ee054e5360e4c810aacd3c68b4e75cbbf
parent442d71f95973f2d3b4c1fa2fb2664dcabf69329c (diff)
downloadcoreclr-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.xml20
-rw-r--r--src/pal/prebuilt/inc/corerror.h4
-rw-r--r--src/vm/CMakeLists.txt3
-rw-r--r--src/vm/diagnosticprotocolhelper.cpp235
-rw-r--r--src/vm/diagnosticprotocolhelper.h32
-rw-r--r--src/vm/diagnosticserver.cpp38
-rw-r--r--src/vm/diagnosticsprotocol.h481
-rw-r--r--src/vm/dumpdiagnosticprotocolhelper.cpp119
-rw-r--r--src/vm/dumpdiagnosticprotocolhelper.h55
-rw-r--r--src/vm/eventpipeprotocolhelper.cpp148
-rw-r--r--src/vm/eventpipeprotocolhelper.h44
-rw-r--r--src/vm/fastserializer.cpp12
-rw-r--r--src/vm/profilerdiagnosticprotocolhelper.cpp125
-rw-r--r--src/vm/profilerdiagnosticprotocolhelper.h58
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__