Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/dotnet/runtime.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorJohn Salem <josalem@microsoft.com>2021-06-09 22:52:29 +0300
committerGitHub <noreply@github.com>2021-06-09 22:52:29 +0300
commit197cfb60f37626ce9e07285517b129fc01f9621e (patch)
tree20779d370f399094ec3dc65fe9db507b2ec04e28 /src
parent879879e6a109a6db4908b0f0081017bb2ba1f13b (diff)
ProcessInfo2 Diagnostics IPC Command (#52258)
* Stash the entrypoint assembly path * Add ep funcs for accessing entrypoint and version * TODO: get Mono's entrypoint asm * Add ProcessInfo2 command * command includes everything the first one did + product ver and entrypoint asm path * Add a test too * Mono build fixes * fix Mono build * PR feedback * fetch mono info from main assembly * remove ref naming * remove lazy method * handle bundled host * need to test still * Use assembly name * simplify access patterns * change API to utf8 * updated test * update comment * PR feedback * save before I commit this time... * fix merge conflict * Update ds-process-protocol.c add missing break statement from merge conflict fix * Update src/native/eventpipe/ds-process-protocol.h * Update ds-process-protocol.c add missing frees * PR feedback
Diffstat (limited to 'src')
-rw-r--r--src/coreclr/vm/eventing/eventpipe/ep-rt-coreclr.h20
-rw-r--r--src/mono/mono/eventpipe/ep-rt-mono.c1
-rw-r--r--src/mono/mono/eventpipe/ep-rt-mono.h19
-rw-r--r--src/native/eventpipe/ds-process-protocol.c218
-rw-r--r--src/native/eventpipe/ds-process-protocol.h51
-rw-r--r--src/native/eventpipe/ds-types.h2
-rw-r--r--src/native/eventpipe/ep-rt.h8
-rw-r--r--src/tests/tracing/eventpipe/common/IpcUtils.cs47
-rw-r--r--src/tests/tracing/eventpipe/processinfo2/processinfo2.cs235
-rw-r--r--src/tests/tracing/eventpipe/processinfo2/processinfo2.csproj19
10 files changed, 619 insertions, 1 deletions
diff --git a/src/coreclr/vm/eventing/eventpipe/ep-rt-coreclr.h b/src/coreclr/vm/eventing/eventpipe/ep-rt-coreclr.h
index a6400416a4e..38d5598be0b 100644
--- a/src/coreclr/vm/eventing/eventpipe/ep-rt-coreclr.h
+++ b/src/coreclr/vm/eventing/eventpipe/ep-rt-coreclr.h
@@ -12,6 +12,7 @@
#include "fstream.h"
#include "typestring.h"
#include "win32threadpool.h"
+#include "clrversion.h"
#undef EP_ARRAY_SIZE
#define EP_ARRAY_SIZE(expr) (sizeof(expr) / sizeof ((expr) [0]))
@@ -1161,6 +1162,25 @@ ep_rt_coreclr_config_lock_get (void)
return &_ep_rt_coreclr_config_lock_handle;
}
+static
+inline
+const ep_char8_t *
+ep_rt_entrypoint_assembly_name_get_utf8 (void)
+{
+ STATIC_CONTRACT_NOTHROW;
+
+ return reinterpret_cast<const ep_char8_t*>(GetAppDomain ()->GetRootAssembly ()->GetSimpleName ());
+}
+
+static
+const ep_char8_t *
+ep_rt_runtime_version_get_utf8 (void)
+{
+ STATIC_CONTRACT_NOTHROW;
+
+ return reinterpret_cast<const ep_char8_t*>(CLR_PRODUCT_VERSION);
+}
+
/*
* Atomics.
*/
diff --git a/src/mono/mono/eventpipe/ep-rt-mono.c b/src/mono/mono/eventpipe/ep-rt-mono.c
index 8416cf0ad8f..f8b85538db0 100644
--- a/src/mono/mono/eventpipe/ep-rt-mono.c
+++ b/src/mono/mono/eventpipe/ep-rt-mono.c
@@ -44,6 +44,7 @@ char *_ep_rt_mono_os_cmd_line = NULL;
mono_lazy_init_t _ep_rt_mono_managed_cmd_line_init = MONO_LAZY_INIT_STATUS_NOT_INITIALIZED;
char *_ep_rt_mono_managed_cmd_line = NULL;
+
// Sample profiler.
static GArray * _ep_rt_mono_sampled_thread_callstacks = NULL;
static uint32_t _ep_rt_mono_max_sampled_thread_count = 32;
diff --git a/src/mono/mono/eventpipe/ep-rt-mono.h b/src/mono/mono/eventpipe/ep-rt-mono.h
index c048682445c..22bdccdcd7d 100644
--- a/src/mono/mono/eventpipe/ep-rt-mono.h
+++ b/src/mono/mono/eventpipe/ep-rt-mono.h
@@ -20,9 +20,12 @@
#include <mono/utils/mono-rand.h>
#include <mono/utils/mono-lazy-init.h>
#include <mono/utils/w32api.h>
+#include <mono/metadata/assembly.h>
#include <mono/metadata/w32file.h>
#include <mono/metadata/w32event.h>
#include <mono/metadata/environment-internals.h>
+#include <mono/metadata/metadata-internals.h>
+#include <runtime_version.h>
#include <mono/metadata/profiler.h>
#undef EP_ARRAY_SIZE
@@ -1824,6 +1827,22 @@ ep_rt_diagnostics_command_line_get (void)
return cmd_line;
}
+static
+inline
+const ep_char8_t *
+ep_rt_entrypoint_assembly_name_get_utf8 (void)
+{
+ return (const ep_char8_t *)m_image_get_assembly_name (mono_assembly_get_main ()->image);
+}
+
+static
+inline
+const ep_char8_t *
+ep_rt_runtime_version_get_utf8 (void)
+{
+ return (const ep_char8_t *)EGLIB_TOSTRING (RuntimeProductVersion);
+}
+
/*
* Thread.
*/
diff --git a/src/native/eventpipe/ds-process-protocol.c b/src/native/eventpipe/ds-process-protocol.c
index 2ce9ddc4fc4..75886a79ff3 100644
--- a/src/native/eventpipe/ds-process-protocol.c
+++ b/src/native/eventpipe/ds-process-protocol.c
@@ -55,6 +55,12 @@ process_protocol_helper_get_process_info (
static
bool
+process_protocol_helper_get_process_info_2 (
+ DiagnosticsIpcMessage *message,
+ DiagnosticsIpcStream *stream);
+
+static
+bool
process_protocol_helper_get_process_env (
DiagnosticsIpcMessage *message,
DiagnosticsIpcStream *stream);
@@ -192,6 +198,143 @@ ds_process_info_payload_fini (DiagnosticsProcessInfoPayload *payload)
}
/*
+ * DiagnosticsProcessInfo2Payload.
+ */
+
+static
+uint16_t
+process_info_2_payload_get_size (DiagnosticsProcessInfo2Payload *payload)
+{
+ // see IPC spec @ https://github.com/dotnet/diagnostics/blob/master/documentation/design-docs/ipc-protocol.md
+ // for definition of serialization format
+
+ // uint64_t ProcessId; -> 8 bytes
+ // GUID RuntimeCookie; -> 16 bytes
+ // LPCWSTR CommandLine; -> 4 bytes + strlen * sizeof(WCHAR)
+ // LPCWSTR OS; -> 4 bytes + strlen * sizeof(WCHAR)
+ // LPCWSTR Arch; -> 4 bytes + strlen * sizeof(WCHAR)
+ // LPCWSTR managed_entrypoint_assembly_name; -> 4 bytes + strlen * sizeof(WCHAR)
+ // LPCWSTR clr_product_version; -> 4 bytes + strlen * sizeof(WCHAR)
+
+ EP_ASSERT (payload != NULL);
+
+ size_t size = 0;
+ size += sizeof(payload->process_id);
+ size += sizeof(payload->runtime_cookie);
+
+ size += sizeof(uint32_t);
+ size += (payload->command_line != NULL) ?
+ (ep_rt_utf16_string_len (payload->command_line) + 1) * sizeof(ep_char16_t) : 0;
+
+ size += sizeof(uint32_t);
+ size += (payload->os != NULL) ?
+ (ep_rt_utf16_string_len (payload->os) + 1) * sizeof(ep_char16_t) : 0;
+
+ size += sizeof(uint32_t);
+ size += (payload->arch != NULL) ?
+ (ep_rt_utf16_string_len (payload->arch) + 1) * sizeof(ep_char16_t) : 0;
+
+ size += sizeof(uint32_t);
+ size += (payload->managed_entrypoint_assembly_name != NULL) ?
+ (ep_rt_utf16_string_len (payload->managed_entrypoint_assembly_name) + 1) * sizeof(ep_char16_t) : 0;
+
+ size += sizeof(uint32_t);
+ size += (payload->clr_product_version != NULL) ?
+ (ep_rt_utf16_string_len (payload->clr_product_version) + 1) * sizeof(ep_char16_t) : 0;
+
+ EP_ASSERT (size <= UINT16_MAX);
+ return (uint16_t)size;
+}
+
+static
+bool
+process_info_2_payload_flatten (
+ void *payload,
+ uint8_t **buffer,
+ uint16_t *size)
+{
+ DiagnosticsProcessInfo2Payload *process_info = (DiagnosticsProcessInfo2Payload*)payload;
+
+ EP_ASSERT (payload != NULL);
+ EP_ASSERT (buffer != NULL);
+ EP_ASSERT (*buffer != NULL);
+ EP_ASSERT (size != NULL);
+ EP_ASSERT (process_info_2_payload_get_size (process_info) == *size);
+
+ // see IPC spec @ https://github.com/dotnet/diagnostics/blob/master/documentation/design-docs/ipc-protocol.md
+ // for definition of serialization format
+
+ bool success = true;
+
+ // uint64_t ProcessId;
+ memcpy (*buffer, &process_info->process_id, sizeof (process_info->process_id));
+ *buffer += sizeof (process_info->process_id);
+ *size -= sizeof (process_info->process_id);
+
+ // GUID RuntimeCookie;
+ memcpy(*buffer, &process_info->runtime_cookie, sizeof (process_info->runtime_cookie));
+ *buffer += sizeof (process_info->runtime_cookie);
+ *size -= sizeof (process_info->runtime_cookie);
+
+ // LPCWSTR CommandLine;
+ success &= ds_ipc_message_try_write_string_utf16_t (buffer, size, process_info->command_line);
+
+ // LPCWSTR OS;
+ if (success)
+ success &= ds_ipc_message_try_write_string_utf16_t (buffer, size, process_info->os);
+
+ // LPCWSTR Arch;
+ if (success)
+ success &= ds_ipc_message_try_write_string_utf16_t (buffer, size, process_info->arch);
+
+ // LPCWSTR managed_entrypoint_assembly_name;
+ if (success)
+ success &= ds_ipc_message_try_write_string_utf16_t (buffer, size, process_info->managed_entrypoint_assembly_name);
+
+ // LPCWSTR clr_product_version;
+ if (success)
+ success &= ds_ipc_message_try_write_string_utf16_t (buffer, size, process_info->clr_product_version);
+
+ // Assert we've used the whole buffer we were given
+ EP_ASSERT(*size == 0);
+
+ return success;
+}
+
+DiagnosticsProcessInfo2Payload *
+ds_process_info_2_payload_init (
+ DiagnosticsProcessInfo2Payload *payload,
+ const ep_char16_t *command_line,
+ const ep_char16_t *os,
+ const ep_char16_t *arch,
+ uint32_t process_id,
+ const uint8_t *runtime_cookie,
+ const ep_char16_t *managed_entrypoint_assembly_name,
+ const ep_char16_t *clr_product_version)
+{
+ ep_return_null_if_nok (payload != NULL);
+
+ payload->command_line = command_line;
+ payload->os = os;
+ payload->arch = arch;
+ payload->process_id = process_id;
+ payload->managed_entrypoint_assembly_name = managed_entrypoint_assembly_name;
+ payload->clr_product_version = clr_product_version;
+
+ if (runtime_cookie)
+ memcpy (&payload->runtime_cookie, runtime_cookie, EP_GUID_SIZE);
+
+ return payload;
+}
+
+void
+ds_process_info_2_payload_fini (DiagnosticsProcessInfo2Payload *payload)
+{
+ ;
+}
+
+
+/*
* DiagnosticsEnvironmentInfoPayload.
*/
@@ -388,6 +531,78 @@ ep_on_error:
static
bool
+process_protocol_helper_get_process_info_2 (
+ DiagnosticsIpcMessage *message,
+ DiagnosticsIpcStream *stream)
+{
+ EP_ASSERT (message != NULL);
+ EP_ASSERT (stream != NULL);
+
+ bool result = false;
+ ep_char16_t *command_line = NULL;
+ ep_char16_t *os_info = NULL;
+ ep_char16_t *arch_info = NULL;
+ ep_char16_t *managed_entrypoint_assembly_name = NULL;
+ ep_char16_t *clr_product_version = NULL;
+ DiagnosticsProcessInfo2Payload payload;
+ DiagnosticsProcessInfo2Payload *process_info_2_payload = NULL;
+
+ command_line = ep_rt_utf8_to_utf16_string (ep_rt_diagnostics_command_line_get (), -1);
+ ep_raise_error_if_nok (command_line != NULL);
+
+ os_info = ep_rt_utf8_to_utf16_string (ep_event_source_get_os_info (), -1);
+ ep_raise_error_if_nok (os_info != NULL);
+
+ arch_info = ep_rt_utf8_to_utf16_string (ep_event_source_get_arch_info (), -1);
+ ep_raise_error_if_nok (arch_info != NULL);
+
+ managed_entrypoint_assembly_name = ep_rt_utf8_to_utf16_string (ep_rt_entrypoint_assembly_name_get_utf8 (), -1);
+ ep_raise_error_if_nok (managed_entrypoint_assembly_name != NULL);
+
+ clr_product_version = ep_rt_utf8_to_utf16_string (ep_rt_runtime_version_get_utf8 (), -1);
+ ep_raise_error_if_nok (clr_product_version != NULL);
+
+ process_info_2_payload = ds_process_info_2_payload_init (
+ &payload,
+ command_line,
+ os_info,
+ arch_info,
+ ep_rt_current_process_get_id (),
+ ds_ipc_advertise_cookie_v1_get (),
+ managed_entrypoint_assembly_name,
+ clr_product_version);
+ ep_raise_error_if_nok (process_info_2_payload != NULL);
+
+ ep_raise_error_if_nok (ds_ipc_message_initialize_buffer (
+ message,
+ ds_ipc_header_get_generic_success (),
+ (void *)process_info_2_payload,
+ process_info_2_payload_get_size (process_info_2_payload),
+ process_info_2_payload_flatten));
+
+ ep_raise_error_if_nok (ds_ipc_message_send (message, stream));
+
+ result = true;
+
+ep_on_exit:
+ ds_process_info_2_payload_fini (process_info_2_payload);
+ ep_rt_utf16_string_free (arch_info);
+ ep_rt_utf16_string_free (os_info);
+ ep_rt_utf16_string_free (command_line);
+ ep_rt_utf16_string_free (managed_entrypoint_assembly_name);
+ ep_rt_utf16_string_free (clr_product_version);
+ ds_ipc_stream_free (stream);
+ return result;
+
+ep_on_error:
+ EP_ASSERT (!result);
+ ds_ipc_message_send_error (stream, DS_IPC_E_FAIL);
+ DS_LOG_WARNING_0 ("Failed to send DiagnosticsIPC response");
+ ep_exit_error_handler ();
+}
+
+static
+bool
process_protocol_helper_get_process_env (
DiagnosticsIpcMessage *message,
DiagnosticsIpcStream *stream)
@@ -566,6 +781,9 @@ ds_process_protocol_helper_handle_ipc_message (
break;
case DS_PROCESS_COMMANDID_SET_ENV_VAR:
result = process_protocol_helper_set_environment_variable (message, stream);
+ break;
+ case DS_PROCESS_COMMANDID_GET_PROCESS_INFO_2:
+ result = process_protocol_helper_get_process_info_2 (message, stream);
break;
default:
result = process_protocol_helper_unknown_command (message, stream);
diff --git a/src/native/eventpipe/ds-process-protocol.h b/src/native/eventpipe/ds-process-protocol.h
index 1b2c279aeee..ac145c0bd1d 100644
--- a/src/native/eventpipe/ds-process-protocol.h
+++ b/src/native/eventpipe/ds-process-protocol.h
@@ -59,6 +59,56 @@ void
ds_process_info_payload_fini (DiagnosticsProcessInfoPayload *payload);
/*
+* DiagnosticsProcessInfo2Payload
+*/
+
+// command = 0x0400
+#if defined(DS_INLINE_GETTER_SETTER) || defined(DS_IMPL_PROCESS_PROTOCOL_GETTER_SETTER)
+struct _DiagnosticsProcessInfo2Payload {
+#else
+struct _DiagnosticsProcessInfo2Payload_Internal {
+#endif
+ // The protocol buffer is defined as:
+ // X, Y, Z means encode bytes for X followed by bytes for Y followed by bytes for Z
+ // uint = 4 little endian bytes
+ // long = 8 little endian bytes
+ // GUID = 16 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)
+
+ // ProcessInfo = long pid, GUID runtimeCookie, string cmdline, string OS, string arch, string managed entrypoint assembly path, string clr product version
+ uint64_t process_id;
+ const ep_char16_t *command_line;
+ const ep_char16_t *os;
+ const ep_char16_t *arch;
+ uint8_t runtime_cookie [EP_GUID_SIZE];
+ const ep_char16_t *managed_entrypoint_assembly_name;
+ const ep_char16_t *clr_product_version;
+
+};
+
+#if !defined(DS_INLINE_GETTER_SETTER) && !defined(DS_IMPL_PROCESS_PROTOCOL_GETTER_SETTER)
+struct _DiagnosticsProcessInfo2Payload {
+ uint8_t _internal [sizeof (struct _DiagnosticsProcessInfo2Payload_Internal)];
+};
+#endif
+
+DiagnosticsProcessInfo2Payload *
+ds_process_info_2_payload_init (
+ DiagnosticsProcessInfo2Payload *payload,
+ const ep_char16_t *command_line,
+ const ep_char16_t *os,
+ const ep_char16_t *arch,
+ uint32_t process_id,
+ const uint8_t *runtime_cookie,
+ const ep_char16_t *managed_entrypoint_assembly_name,
+ const ep_char16_t *clr_product_version);
+
+void
+ds_process_info_2_payload_fini (DiagnosticsProcessInfo2Payload *payload);
+
+/*
* DiagnosticsEnvironmentInfoPayload
*/
@@ -125,4 +175,3 @@ ds_process_protocol_helper_handle_ipc_message (
#endif /* ENABLE_PERFTRACING */
#endif /* __DIAGNOSTICS_PROCESS_PROTOCOL_H__ */
-
diff --git a/src/native/eventpipe/ds-types.h b/src/native/eventpipe/ds-types.h
index 243951d243d..fc5d3e1a2ec 100644
--- a/src/native/eventpipe/ds-types.h
+++ b/src/native/eventpipe/ds-types.h
@@ -29,6 +29,7 @@ typedef struct _DiagnosticsPort DiagnosticsPort;
typedef struct _DiagnosticsPortBuilder DiagnosticsPortBuilder;
typedef struct _DiagnosticsPortVtable DiagnosticsPortVtable;
typedef struct _DiagnosticsProcessInfoPayload DiagnosticsProcessInfoPayload;
+typedef struct _DiagnosticsProcessInfo2Payload DiagnosticsProcessInfo2Payload;
typedef struct _EventPipeCollectTracingCommandPayload EventPipeCollectTracingCommandPayload;
typedef struct _EventPipeCollectTracing2CommandPayload EventPipeCollectTracing2CommandPayload;
typedef struct _EventPipeStopTracingCommandPayload EventPipeStopTracingCommandPayload;
@@ -67,6 +68,7 @@ typedef enum {
DS_PROCESS_COMMANDID_RESUME_RUNTIME = 0x01,
DS_PROCESS_COMMANDID_GET_PROCESS_ENV = 0x02,
DS_PROCESS_COMMANDID_SET_ENV_VAR = 0x03,
+ DS_PROCESS_COMMANDID_GET_PROCESS_INFO_2 = 0x04
// future
} DiagnosticsProcessCommandId;
diff --git a/src/native/eventpipe/ep-rt.h b/src/native/eventpipe/ep-rt.h
index 405bb4fb038..d043710f46c 100644
--- a/src/native/eventpipe/ep-rt.h
+++ b/src/native/eventpipe/ep-rt.h
@@ -600,6 +600,14 @@ static
void
ep_rt_os_environment_get_utf16 (ep_rt_env_array_utf16_t *env_array);
+static
+const ep_char8_t *
+ep_rt_entrypoint_assembly_name_get_utf8 (void);
+
+static
+const ep_char8_t *
+ep_rt_runtime_version_get_utf8 (void);
+
/*
* Lock
*/
diff --git a/src/tests/tracing/eventpipe/common/IpcUtils.cs b/src/tests/tracing/eventpipe/common/IpcUtils.cs
index 6f60368894d..b3448f704f2 100644
--- a/src/tests/tracing/eventpipe/common/IpcUtils.cs
+++ b/src/tests/tracing/eventpipe/common/IpcUtils.cs
@@ -339,6 +339,53 @@ namespace Tracing.Tests.Common
}
}
+ public class ProcessInfo2
+ {
+ // uint64_t ProcessId;
+ // GUID RuntimeCookie;
+ // LPCWSTR CommandLine;
+ // LPCWSTR OS;
+ // LPCWSTR Arch;
+ public UInt64 ProcessId;
+ public Guid RuntimeCookie;
+ public string Commandline;
+ public string OS;
+ public string Arch;
+ public string ManagedEntrypointAssemblyName;
+ public string ClrProductVersion;
+
+ public static ProcessInfo2 TryParse(byte[] buf)
+ {
+ var info = new ProcessInfo2();
+ int start = 0;
+ int end = 8; /* sizeof(uint64_t) */
+ info.ProcessId = BitConverter.ToUInt64(buf[start..end]);
+
+ start = end;
+ end = start + 16; /* sizeof(guid) */
+ info.RuntimeCookie = new Guid(buf[start..end]);
+
+ string ParseString(ref int start, ref int end)
+ {
+ start = end;
+ end = start + 4; /* sizeof(uint32_t) */
+ uint nChars = BitConverter.ToUInt32(buf[start..end]);
+
+ start = end;
+ end = start + ((int)nChars * sizeof(char));
+ return System.Text.Encoding.Unicode.GetString(buf[start..end]).TrimEnd('\0');
+ }
+
+ info.Commandline = ParseString(ref start, ref end);
+ info.OS = ParseString(ref start, ref end);
+ info.Arch = ParseString(ref start, ref end);
+ info.ManagedEntrypointAssemblyName = ParseString(ref start, ref end);
+ info.ClrProductVersion = ParseString(ref start, ref end);
+
+ return info;
+ }
+ }
+
public class IpcClient
{
public static IpcMessage SendMessage(Stream stream, IpcMessage message)
diff --git a/src/tests/tracing/eventpipe/processinfo2/processinfo2.cs b/src/tests/tracing/eventpipe/processinfo2/processinfo2.cs
new file mode 100644
index 00000000000..08a10821041
--- /dev/null
+++ b/src/tests/tracing/eventpipe/processinfo2/processinfo2.cs
@@ -0,0 +1,235 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics.Tracing;
+using System.Diagnostics;
+using System.IO;
+using System.Linq;
+using System.Runtime.InteropServices;
+using System.Reflection;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.Diagnostics.Tools.RuntimeClient;
+using Microsoft.Diagnostics.Tracing;
+using Tracing.Tests.Common;
+
+namespace Tracing.Tests.ProcessInfoValidation
+{
+ public class ProcessInfoValidation
+ {
+ public static string NormalizeCommandLine(string cmdline)
+ {
+ // ASSUMPTION: double quotes (") and single quotes (') are used for paths with spaces
+ // ASSUMPTION: This test will only have two parts to the commandline
+
+ // check for quotes in first part
+ var parts = new List<string>();
+ bool isQuoted = false;
+ int start = 0;
+
+ for (int i = 0; i < cmdline.Length; i++)
+ {
+ if (isQuoted)
+ {
+ if (cmdline[i] == '"' || cmdline[i] == '\'')
+ {
+ parts.Add(cmdline.Substring(start, i - start));
+ isQuoted = false;
+ start = i + 1;
+ }
+ }
+ else if (cmdline[i] == '"' || cmdline[i] == '\'')
+ {
+ isQuoted = true;
+ start = i + 1;
+ }
+ else if (cmdline[i] == ' ')
+ {
+ parts.Add(cmdline.Substring(start, i - start));
+ start = i + 1;
+ }
+ else if (i == cmdline.Length - 1)
+ {
+ parts.Add(cmdline.Substring(start));
+ }
+ }
+
+ string normalizedCommandLine = parts
+ .Where(part => !string.IsNullOrWhiteSpace(part))
+ .Select(part => (new FileInfo(part)).FullName)
+ .Aggregate((s1, s2) => string.Join(' ', s1, s2));
+
+ // Tests are run out of /tmp on Mac and linux, but on Mac /tmp is actually a symlink that points to /private/tmp.
+ // This isn't represented in the output from FileInfo.FullName unfortunately, so we'll fake that completion in that case.
+ if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX) && normalizedCommandLine.StartsWith("/tmp/"))
+ normalizedCommandLine = "/private" + normalizedCommandLine;
+
+ return normalizedCommandLine;
+ }
+
+ public static int Main(string[] args)
+ {
+
+ Process currentProcess = Process.GetCurrentProcess();
+ int pid = currentProcess.Id;
+ Logger.logger.Log($"Test PID: {pid}");
+
+ Stream stream = ConnectionHelper.GetStandardTransport(pid);
+
+ // 0x04 = ProcessCommandSet, 0x04 = ProcessInfo2
+ var processInfoMessage = new IpcMessage(0x04, 0x04);
+ Logger.logger.Log($"Wrote: {processInfoMessage}");
+ IpcMessage response = IpcClient.SendMessage(stream, processInfoMessage);
+ Logger.logger.Log($"Received: <omitted>");
+
+ Utils.Assert(response.Header.CommandSet == 0xFF, $"Response must have Server command set. Expected: 0xFF, Received: 0x{response.Header.CommandSet:X2}"); // server
+ Utils.Assert(response.Header.CommandId == 0x00, $"Response must have OK command id. Expected: 0x00, Received: 0x{response.Header.CommandId:X2}"); // OK
+
+ // Parse payload
+ // uint64_t ProcessId;
+ // GUID RuntimeCookie;
+ // LPCWSTR CommandLine;
+ // LPCWSTR OS;
+ // LPCWSTR Arch;
+
+ int totalSize = response.Payload.Length;
+ Logger.logger.Log($"Total size of Payload = {totalSize} bytes");
+
+ // VALIDATE PID
+ int start = 0;
+ int end = start + 8 /* sizeof(uint63_t) */;
+ UInt64 processId = BitConverter.ToUInt64(response.Payload[start..end]);
+ Utils.Assert((int)processId == pid, $"PID in process info must match. Expected: {pid}, Received: {processId}");
+ Logger.logger.Log($"pid: {processId}");
+
+ // VALIDATE RUNTIME COOKIE
+ start = end;
+ end = start + 16 /* sizeof(GUID) */;
+ Guid runtimeCookie = new Guid(response.Payload[start..end]);
+ Logger.logger.Log($"runtimeCookie: {runtimeCookie}");
+
+ // VALIDATE COMMAND LINE
+ start = end;
+ end = start + 4 /* sizeof(uint32_t) */;
+ UInt32 commandLineLength = BitConverter.ToUInt32(response.Payload[start..end]);
+ Logger.logger.Log($"commandLineLength: {commandLineLength}");
+
+ start = end;
+ end = start + ((int)commandLineLength * sizeof(char));
+ Utils.Assert(end <= totalSize, $"String end can't exceed payload size. Expected: <{totalSize}, Received: {end} (decoded length: {commandLineLength})");
+ Logger.logger.Log($"commandLine bytes: [ {response.Payload[start..end].Select(b => b.ToString("X2") + " ").Aggregate(string.Concat)}]");
+ string commandLine = System.Text.Encoding.Unicode.GetString(response.Payload[start..end]).TrimEnd('\0');
+ Logger.logger.Log($"commandLine: \"{commandLine}\"");
+
+ // The following logic is tailored to this specific test where the cmdline _should_ look like the following:
+ // /path/to/corerun /path/to/processinfo.dll
+ // or
+ // "C:\path\to\CoreRun.exe" C:\path\to\processinfo.dll
+ string currentProcessCommandLine = $"{currentProcess.MainModule.FileName} {System.Reflection.Assembly.GetExecutingAssembly().Location}";
+ string receivedCommandLine = NormalizeCommandLine(commandLine);
+
+ Utils.Assert(currentProcessCommandLine.Equals(receivedCommandLine, StringComparison.OrdinalIgnoreCase), $"CommandLine must match current process. Expected: {currentProcessCommandLine}, Received: {receivedCommandLine} (original: {commandLine})");
+
+ // VALIDATE OS
+ start = end;
+ end = start + 4 /* sizeof(uint32_t) */;
+ UInt32 OSLength = BitConverter.ToUInt32(response.Payload[start..end]);
+ Logger.logger.Log($"OSLength: {OSLength}");
+
+ start = end;
+ end = start + ((int)OSLength * sizeof(char));
+ Utils.Assert(end <= totalSize, $"String end can't exceed payload size. Expected: <{totalSize}, Received: {end} (decoded length: {OSLength})");
+ Logger.logger.Log($"OS bytes: [ {response.Payload[start..end].Select(b => b.ToString("X2") + " ").Aggregate(string.Concat)}]");
+ string OS = System.Text.Encoding.Unicode.GetString(response.Payload[start..end]).TrimEnd('\0');
+ Logger.logger.Log($"OS: \"{OS}\"");
+
+ // see eventpipeeventsource.cpp for these values
+ string expectedOSValue = null;
+ if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+ {
+ expectedOSValue = "Windows";
+ }
+ else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
+ {
+ expectedOSValue = "macOS";
+ }
+ else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
+ {
+ expectedOSValue = "Linux";
+ }
+ else
+ {
+ expectedOSValue = "Unknown";
+ }
+
+ Utils.Assert(expectedOSValue.Equals(OS), $"OS must match current Operating System. Expected: \"{expectedOSValue}\", Received: \"{OS}\"");
+
+ // VALIDATE ARCH
+ start = end;
+ end = start + 4 /* sizeof(uint32_t) */;
+ UInt32 archLength = BitConverter.ToUInt32(response.Payload[start..end]);
+ Logger.logger.Log($"archLength: {archLength}");
+
+ start = end;
+ end = start + ((int)archLength * sizeof(char));
+ Utils.Assert(end <= totalSize, $"String end can't exceed payload size. Expected: <{totalSize}, Received: {end} (decoded length: {archLength})");
+ Logger.logger.Log($"arch bytes: [ {response.Payload[start..end].Select(b => b.ToString("X2") + " ").Aggregate(string.Concat)}]");
+ string arch = System.Text.Encoding.Unicode.GetString(response.Payload[start..end]).TrimEnd('\0');
+ Logger.logger.Log($"arch: \"{arch}\"");
+
+ // see eventpipeeventsource.cpp for these values
+ string expectedArchValue = RuntimeInformation.ProcessArchitecture switch
+ {
+ Architecture.X86 => "x86",
+ Architecture.X64 => "x64",
+ Architecture.Arm => "arm32",
+ Architecture.Arm64 => "arm64",
+ _ => "Unknown"
+ };
+
+ Utils.Assert(expectedArchValue.Equals(arch), $"OS must match current Operating System. Expected: \"{expectedArchValue}\", Received: \"{arch}\"");
+
+ // VALIDATE ManagedEntrypointAssemblyName
+ start = end;
+ end = start + 4 /* sizeof(uint32_t) */;
+ UInt32 managedEntrypointAssemblyNameLength = BitConverter.ToUInt32(response.Payload[start..end]);
+ Logger.logger.Log($"managedEntrypointAssemblyNameLength: {managedEntrypointAssemblyNameLength}");
+
+ start = end;
+ end = start + ((int)managedEntrypointAssemblyNameLength * sizeof(char));
+ Utils.Assert(end <= totalSize, $"String end can't exceed payload size. Expected: <{totalSize}, Received: {end} (decoded length: {managedEntrypointAssemblyNameLength})");
+ Logger.logger.Log($"ManagedEntrypointAssemblyName bytes: [ {response.Payload[start..end].Select(b => b.ToString("X2") + " ").Aggregate(string.Concat)}]");
+ string managedEntrypointAssemblyName = System.Text.Encoding.Unicode.GetString(response.Payload[start..end]).TrimEnd('\0');
+ Logger.logger.Log($"ManagedEntrypointAssemblyName: \"{managedEntrypointAssemblyName}\"");
+
+ string expectedManagedEntrypointAssemblyName = System.Reflection.Assembly.GetExecutingAssembly().GetName().Name;
+
+ Utils.Assert(expectedManagedEntrypointAssemblyName.Equals(managedEntrypointAssemblyName), $"ManagedEntrypointAssemblyName must match. Expected: \"{expectedManagedEntrypointAssemblyName}\", received: \"{managedEntrypointAssemblyName}\"");
+
+ // VALIDATE ClrProductVersion
+ start = end;
+ end = start + 4 /* sizeof(uint32_t) */;
+ UInt32 clrProductVersionSize = BitConverter.ToUInt32(response.Payload[start..end]);
+ Logger.logger.Log($"clrProductVersionSize: {clrProductVersionSize}");
+
+ start = end;
+ end = start + ((int)clrProductVersionSize * sizeof(char));
+ Utils.Assert(end <= totalSize, $"String end can't exceed payload size. Expected: <{totalSize}, Received: {end} (decoded length: {clrProductVersionSize})");
+ Logger.logger.Log($"ClrProductVersion bytes: [ {response.Payload[start..end].Select(b => b.ToString("X2") + " ").Aggregate(string.Concat)}]");
+ string clrProductVersion = System.Text.Encoding.Unicode.GetString(response.Payload[start..end]).TrimEnd('\0');
+ Logger.logger.Log($"ClrProductVersion: \"{clrProductVersion}\"");
+
+ string expectedClrProductVersion = typeof(object).Assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion;
+
+ Utils.Assert(expectedClrProductVersion.Equals(clrProductVersion), $"ClrProductVersion must match. Expected: \"{expectedClrProductVersion}\", received: \"{clrProductVersion}\"");
+
+ Utils.Assert(end == totalSize, $"Full payload should have been read. Expected: {totalSize}, Received: {end}");
+
+ Logger.logger.Log($"\n{{\n\tprocessId: {processId},\n\truntimeCookie: {runtimeCookie},\n\tcommandLine: {commandLine},\n\tOS: {OS},\n\tArch: {arch},\n\tManagedEntrypointAssemblyName: {managedEntrypointAssemblyName},\n\tClrProductVersion: {clrProductVersion}\n}}");
+
+ return 100;
+ }
+ }
+} \ No newline at end of file
diff --git a/src/tests/tracing/eventpipe/processinfo2/processinfo2.csproj b/src/tests/tracing/eventpipe/processinfo2/processinfo2.csproj
new file mode 100644
index 00000000000..69e7a9fba00
--- /dev/null
+++ b/src/tests/tracing/eventpipe/processinfo2/processinfo2.csproj
@@ -0,0 +1,19 @@
+<Project Sdk="Microsoft.NET.Sdk">
+ <PropertyGroup>
+ <TargetFrameworkIdentifier>.NETCoreApp</TargetFrameworkIdentifier>
+ <OutputType>exe</OutputType>
+ <CLRTestKind>BuildAndRun</CLRTestKind>
+ <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+ <CLRTestPriority>0</CLRTestPriority>
+ <UnloadabilityIncompatible>true</UnloadabilityIncompatible>
+ <JitOptimizationSensitive>true</JitOptimizationSensitive>
+ <!-- ilasm round-trip testing doesn't work, as test expects unchanged assembly name.
+ Issue: https://github.com/dotnet/runtime/issues/39935
+ -->
+ <IlasmRoundTripIncompatible>true</IlasmRoundTripIncompatible>
+ </PropertyGroup>
+ <ItemGroup>
+ <Compile Include="$(MSBuildProjectName).cs" />
+ <ProjectReference Include="../common/common.csproj" />
+ </ItemGroup>
+</Project>