diff --git a/documentation/design-docs/ipc-protocol.md b/documentation/design-docs/ipc-protocol.md index 2ee2a3440e..828cc4bbc7 100644 --- a/documentation/design-docs/ipc-protocol.md +++ b/documentation/design-docs/ipc-protocol.md @@ -1107,6 +1107,34 @@ The `metadata` either points at nothing if the event doesn’t have metadata, or The runtime will keep track per-session whether it has sent a particular event before. The first time each event is sent during a session, metadata will be included. Should multiple threads race to write the same event to the same session, they may all emit metadata. Afterwards, all instances of that event will not emit metadata, and the responsibility is on the reader to cache and link events with their previously sent metadata. As a special case, runtime events currently implemented in native code will never send metadata. +#### Capability probing with CollectTracing5 + +Clients may use `CollectTracing5` as a capability probe to determine whether a runtime supports user_events-based EventPipe sessions. + +A recommended probe is: + +* Header: `{ Magic; 24; 0x0206; 0x0000 }` + * `Magic` = `"DOTNET_IPC_V1"` + * `size` = `sizeof(IpcHeader) + sizeof(uint)` = `20 + 4 = 24` + * `command_set` = `0x02` (EventPipe) + * `command_id` = `0x06` (CollectTracing5) + * `reserved` = `0x0000` +* Payload: + * `uint session_type = 0xFFFFFFFF;` + +Expected behavior: + +* If the runtime does not implement `CollectTracing5`, it responds with an error message using the generic `Server/Error` pattern and an error code of `DS_IPC_E_UNKNOWN_COMMAND` (`0x80131385`). +* If the runtime implements `CollectTracing5`, it: + * Parses the header and `session_type`. + * Treats `0xFFFFFFFF` as an invalid `session_type` value. + * Responds with an error Diagnostic IPC Message (see [Errors](#errors)) of Server/Error with a payload of `DS_IPC_E_BAD_ENCODING` (`0x80131384L`). + +This allows clients to distinguish between: + +* Runtimes that do not support `CollectTracing5` (return `UnknownCommand`), and +* Runtimes that understand `CollectTracing5` and its `session_type` semantics (return a non-`UnknownCommand` error for the probe message). + ## Dump Commands ### `CreateCoreDump` @@ -1573,15 +1601,21 @@ In the event an error occurs in the handling of an Ipc Message, the Diagnostic S Errors are `HRESULTS` encoded as `int32_t` when sent back to the client. There are a few Diagnostics IPC specific `HRESULT`s: ```c -#define CORDIAGIPC_E_BAD_ENCODING = 0x80131384 -#define CORDIAGIPC_E_UNKNOWN_COMMAND = 0x80131385 -#define CORDIAGIPC_E_UNKNOWN_MAGIC = 0x80131386 -#define CORDIAGIPC_E_UNKNOWN_ERROR = 0x80131387 +#define DS_IPC_E_BAD_ENCODING ((ds_ipc_result_t)(0x80131384L)) +#define DS_IPC_E_UNKNOWN_COMMAND ((ds_ipc_result_t)(0x80131385L)) +#define DS_IPC_E_UNKNOWN_MAGIC ((ds_ipc_result_t)(0x80131386L)) +#define DS_IPC_E_NOTSUPPORTED ((ds_ipc_result_t)(0x80131515L)) +#define DS_IPC_E_FAIL ((ds_ipc_result_t)(0x80004005L)) +#define DS_IPC_E_NOT_YET_AVAILABLE ((ds_ipc_result_t)(0x8013135bL)) +#define DS_IPC_E_RUNTIME_UNINITIALIZED ((ds_ipc_result_t)(0x80131371L)) +#define DS_IPC_E_INVALIDARG ((ds_ipc_result_t)(0x80070057L)) +#define DS_IPC_E_INSUFFICIENT_BUFFER ((ds_ipc_result_t)(0x8007007A)) +#define DS_IPC_E_ENVVAR_NOT_FOUND ((ds_ipc_result_t)(0x800000CB)) ``` Diagnostic Server errors are sent as a Diagnostic IPC Message with: -* a `command_set` of `0xFF` -* a `command_id` of `0xFF` +* a `command_set` of `0xFF` (Server) +* a `command_id` of `0xFF` (Error) * a Payload consisting of a `int32_t` representing the error encountered (described above) All errors will result in the Server closing the connection. diff --git a/src/Microsoft.Diagnostics.NETCore.Client/DiagnosticsClient/DiagnosticsClient.cs b/src/Microsoft.Diagnostics.NETCore.Client/DiagnosticsClient/DiagnosticsClient.cs index dfd1c7f497..468eb120f8 100644 --- a/src/Microsoft.Diagnostics.NETCore.Client/DiagnosticsClient/DiagnosticsClient.cs +++ b/src/Microsoft.Diagnostics.NETCore.Client/DiagnosticsClient/DiagnosticsClient.cs @@ -21,6 +21,7 @@ namespace Microsoft.Diagnostics.NETCore.Client public sealed class DiagnosticsClient { private const int DefaultCircularBufferMB = 256; + private const uint UserEventsProbePayload = uint.MaxValue; private readonly IpcEndpoint _endpoint; @@ -149,6 +150,20 @@ public Task StartEventPipeSessionAsync(EventPipeSessionConfigu return EventPipeSession.StartAsync(_endpoint, configuration, token); } + /// + /// Check whether the target runtime understands the CollectTracing5 user_events IPC command. + /// + /// + /// A boolean indicating whether the runtime understands the CollectTracing5 user_events IPC command. + /// + internal bool SupportsEventPipeUserEventsCommand() + { + IpcMessage probe = CreateUserEventsCapabilityProbeMessage(); + IpcMessage response = IpcClient.SendMessage(_endpoint, probe); + + return IsCommandSupportedFromResponse(response); + } + /// /// Trigger a core dump generation. /// @@ -623,6 +638,12 @@ private static void SerializePayloadArgument(T obj, BinaryWriter writer) } } + private static IpcMessage CreateUserEventsCapabilityProbeMessage() + { + byte[] payload = SerializePayload(UserEventsProbePayload); + return new IpcMessage(DiagnosticsServerCommandSet.EventPipe, (byte)EventPipeCommandId.CollectTracing5, payload); + } + private static IpcMessage CreateAttachProfilerMessage(TimeSpan attachTimeout, Guid profilerGuid, string profilerPath, byte[] additionalData) { if (profilerGuid == null || profilerGuid == Guid.Empty) @@ -763,6 +784,27 @@ private static ProcessInfo TryGetProcessInfo3FromResponse(IpcResponse response, return ProcessInfo.ParseV3(response.Message.Payload); } + private static bool IsCommandSupportedFromResponse(IpcMessage responseMessage) + { + switch ((DiagnosticsServerResponseId)responseMessage.Header.CommandId) + { + case DiagnosticsServerResponseId.OK: + return true; + + case DiagnosticsServerResponseId.Error: + if (responseMessage.Payload == null || responseMessage.Payload.Length < sizeof(uint)) + { + return false; + } + + uint hr = BinaryPrimitives.ReadUInt32LittleEndian(new ReadOnlySpan(responseMessage.Payload, 0, sizeof(uint))); + return hr != (uint)DiagnosticsIpcError.UnknownCommand; + + default: + return false; + } + } + internal static bool ValidateResponseMessage(IpcMessage responseMessage, string operationName, ValidateResponseOptions options = ValidateResponseOptions.None) { switch ((DiagnosticsServerResponseId)responseMessage.Header.CommandId) diff --git a/src/Microsoft.Diagnostics.NETCore.Client/DiagnosticsIpc/IpcCommands.cs b/src/Microsoft.Diagnostics.NETCore.Client/DiagnosticsIpc/IpcCommands.cs index a92e79b542..e3fc7f4fa9 100644 --- a/src/Microsoft.Diagnostics.NETCore.Client/DiagnosticsIpc/IpcCommands.cs +++ b/src/Microsoft.Diagnostics.NETCore.Client/DiagnosticsIpc/IpcCommands.cs @@ -27,6 +27,7 @@ internal enum EventPipeCommandId : byte CollectTracing2 = 0x03, CollectTracing3 = 0x04, CollectTracing4 = 0x05, + CollectTracing5 = 0x06, } internal enum DumpCommandId : byte