未验证 提交 58b8de99 编写于 作者: M Mitchell Hwang 提交者: GitHub

[tests][eventpipe] Port over Dotnet/Diagnostics to enable eventpipe tests on Android (#64358)

* [tests][eventpipe] Port over Dotnet/Diagnostics files for mobile eventpipe tests

Copied over files from src/Microsoft.Diagnostics.NETCore.Client based off of 99fab307

* [tests][eventpipe] Add TCP/IP logic for mobile eventpipe tests

* [tests] Remove Microsoft.Diagnostics.NETCore.Client package reference

* [tests][eventpipe] Downstream Diagnostics IpcTraceTest DiagnosticsClient bootstrap

https://github.com/dotnet/diagnostics/pull/720

* [tests][eventpipe] Downstream Diagnostics roslyn analyzer IpcTraceTest change

https://github.com/dotnet/diagnostics/pull/1044

* [tests][eventpipe] Enable TCPIP DiagnosticsClient in IpcTraceTest for Android

* [tests][eventpipe] Aesthetic IpcTraceTest modifications

* [tests][eventpipe] Disable subprocesses tests on Android

* [tests][eventpipe] Update processinfo

* [tests][eventpipe] Update processinfo2

* [tests][eventpipe] Update eventsourceerror

* [tests][eventpipe] Update bigevent

* [tests][eventpipe] Update buffersize

* [tests][eventpipe] Update rundownvalidation

* [tests][eventpipe] Update providervalidation

* [tests][eventpipe] Update gcdump

* [tests][JIT] Update debuginfo/tester

* [tests] Segment Microsoft.Diagnostics.NETCore.Client relevant tests for Linux arm coreclr

* Account for nonspecified RuntimeFlavor

* [tests] Moveup Default coreclr RuntimeFlavor property explicit declaration

* [tests] Duplicate Microsoft.Diagnostics.NETCore.Client dependent tests for Linux arm

* Fix debuginfo/tester test skip

* Temporarily enable bigevent on Linux arm and remove duplicate exclude

* Fix unaligned UTF16 string read in collect tracing EventPipe command.

Collect tracing 2 EventPipe command triggers an unaligned UTF16 string
read that could cause a SIGBUS on platforms not supporting unalinged
reads of UTF16 strings (so far only seen on 32-bit ARM Linux CI machine).

On CoreCLR this could even cause a unalinged int read due to
optimizations used in UTF8Encoding::GetByteCount.

* Revert "[tests] Duplicate Microsoft.Diagnostics.NETCore.Client dependent tests for Linux arm"

This reverts commit cb2cacd93bb61b74b36c2d2d26cccec38b51b8f7.

* Revert "[tests] Segment Microsoft.Diagnostics.NETCore.Client relevant tests for Linux arm coreclr"

This reverts commit dc29676bcea3a6073decf15a9f1dbc9b2e7b3bd8.

* Revert "Fix debuginfo/tester test skip"

This reverts commit 1e90d7e4667c0570c8f4c2536a25620697027c97.
Co-authored-by: NMitchell Hwang <mitchell.hwang@microsoft.com>
Co-authored-by: NlateralusX <lateralusx.github@gmail.com>
上级 ca3c95b8
......@@ -137,7 +137,6 @@
<NETStandardLibraryRefVersion>2.1.0</NETStandardLibraryRefVersion>
<NetStandardLibraryVersion>2.0.3</NetStandardLibraryVersion>
<MicrosoftDiagnosticsToolsRuntimeClientVersion>1.0.4-preview6.19326.1</MicrosoftDiagnosticsToolsRuntimeClientVersion>
<MicrosoftDiagnosticsNETCoreClientVersion>0.2.61701</MicrosoftDiagnosticsNETCoreClientVersion>
<DNNEVersion>1.0.27</DNNEVersion>
<MicrosoftBuildVersion>16.10.0</MicrosoftBuildVersion>
<MicrosoftBuildTasksCoreVersion>$(MicrosoftBuildVersion)</MicrosoftBuildTasksCoreVersion>
......
......@@ -140,7 +140,7 @@ eventpipe_collect_tracing_command_try_parse_rundown_requested (
EP_ASSERT (buffer_len != NULL);
EP_ASSERT (rundown_requested != NULL);
return ds_ipc_message_try_parse_value (buffer, buffer_len, (uint8_t *)rundown_requested, (uint32_t)sizeof (bool));
return ds_ipc_message_try_parse_value (buffer, buffer_len, (uint8_t *)rundown_requested, 1);
}
static
......@@ -160,6 +160,9 @@ eventpipe_collect_tracing_command_try_parse_config (
const uint32_t max_count_configs = 1000;
uint32_t count_configs = 0;
uint8_t *provider_name_byte_array = NULL;
uint8_t *filter_data_byte_array = NULL;
ep_char8_t *provider_name_utf8 = NULL;
ep_char8_t *filter_data_utf8 = NULL;
......@@ -176,20 +179,27 @@ eventpipe_collect_tracing_command_try_parse_config (
ep_raise_error_if_nok (ds_ipc_message_try_parse_uint32_t (buffer, buffer_len, &log_level));
ep_raise_error_if_nok (log_level <= EP_EVENT_LEVEL_VERBOSE);
const ep_char16_t *provider_name = NULL;
ep_raise_error_if_nok (ds_ipc_message_try_parse_string_utf16_t (buffer, buffer_len, &provider_name));
uint32_t provider_name_byte_array_len = 0;
ep_raise_error_if_nok (ds_ipc_message_try_parse_string_utf16_t_byte_array_alloc (buffer, buffer_len, &provider_name_byte_array, &provider_name_byte_array_len));
provider_name_utf8 = ep_rt_utf16_to_utf8_string (provider_name, -1);
provider_name_utf8 = ep_rt_utf16_to_utf8_string ((const ep_char16_t *)provider_name_byte_array, -1);
ep_raise_error_if_nok (provider_name_utf8 != NULL);
ep_raise_error_if_nok (!ep_rt_utf8_string_is_null_or_empty (provider_name_utf8));
const ep_char16_t *filter_data = NULL; // This parameter is optional.
ds_ipc_message_try_parse_string_utf16_t (buffer, buffer_len, &filter_data);
ep_rt_byte_array_free (provider_name_byte_array);
provider_name_byte_array = NULL;
uint32_t filter_data_byte_array_len = 0;
ep_raise_error_if_nok (ds_ipc_message_try_parse_string_utf16_t_byte_array_alloc (buffer, buffer_len, &filter_data_byte_array, &filter_data_byte_array_len));
if (filter_data) {
filter_data_utf8 = ep_rt_utf16_to_utf8_string (filter_data, -1);
// This parameter is optional.
if (filter_data_byte_array) {
filter_data_utf8 = ep_rt_utf16_to_utf8_string ((const ep_char16_t *)filter_data_byte_array, -1);
ep_raise_error_if_nok (filter_data_utf8 != NULL);
ep_rt_byte_array_free (filter_data_byte_array);
filter_data_byte_array = NULL;
}
EventPipeProviderConfiguration provider_config;
......@@ -209,7 +219,9 @@ ep_on_exit:
ep_on_error:
count_configs = 0;
ep_rt_byte_array_free (provider_name_byte_array);
ep_rt_utf8_string_free (provider_name_utf8);
ep_rt_byte_array_free (filter_data_byte_array);
ep_rt_utf8_string_free (filter_data_utf8);
ep_exit_error_handler ();
}
......
......@@ -59,6 +59,14 @@ ipc_message_flatten (
uint16_t payload_len,
ds_ipc_flatten_payload_func flatten_payload);
static
bool
ipc_message_try_parse_string_utf16_t_byte_array (
uint8_t **buffer,
uint32_t *buffer_len,
const uint8_t **string_byte_array,
uint32_t *string_byte_array_len);
/*
* DiagnosticsIpc
*/
......@@ -306,6 +314,50 @@ ep_on_error:
ep_exit_error_handler ();
}
static
bool
ipc_message_try_parse_string_utf16_t_byte_array (
uint8_t **buffer,
uint32_t *buffer_len,
const uint8_t **string_byte_array,
uint32_t *string_byte_array_len)
{
EP_ASSERT (buffer != NULL);
EP_ASSERT (buffer_len != NULL);
EP_ASSERT (string_byte_array != NULL);
EP_ASSERT (string_byte_array_len != NULL);
bool result = false;
ep_raise_error_if_nok (ds_ipc_message_try_parse_uint32_t (buffer, buffer_len, string_byte_array_len));
*string_byte_array_len *= sizeof (ep_char16_t);
if (*string_byte_array_len != 0) {
if (*string_byte_array_len > *buffer_len)
ep_raise_error ();
if (((const ep_char16_t *)*buffer) [(*string_byte_array_len / sizeof (ep_char16_t)) - 1] != 0)
ep_raise_error ();
*string_byte_array = *buffer;
} else {
*string_byte_array = NULL;
}
*buffer = *buffer + *string_byte_array_len;
*buffer_len = *buffer_len - *string_byte_array_len;
result = true;
ep_on_exit:
return result;
ep_on_error:
EP_ASSERT (!result);
ep_exit_error_handler ();
}
DiagnosticsIpcMessage *
ds_ipc_message_init (DiagnosticsIpcMessage *message)
{
......@@ -383,38 +435,35 @@ ds_ipc_message_try_parse_uint32_t (
return result;
}
// TODO: Strings are in little endian format in buffer.
bool
ds_ipc_message_try_parse_string_utf16_t (
ds_ipc_message_try_parse_string_utf16_t_byte_array_alloc (
uint8_t **buffer,
uint32_t *buffer_len,
const ep_char16_t **value)
uint8_t **string_byte_array,
uint32_t *string_byte_array_len)
{
EP_ASSERT (buffer != NULL);
EP_ASSERT (buffer_len != NULL);
EP_ASSERT (value != NULL);
EP_ASSERT (string_byte_array != NULL);
EP_ASSERT (string_byte_array_len != NULL);
bool result = false;
uint32_t string_len = 0;
ep_raise_error_if_nok (ds_ipc_message_try_parse_uint32_t (buffer, buffer_len, &string_len));
const uint8_t *temp_buffer = NULL;
uint32_t temp_buffer_len = 0;
if (string_len != 0) {
if (string_len > (*buffer_len / sizeof (ep_char16_t)))
ep_raise_error ();
if (((const ep_char16_t *)*buffer) [string_len - 1] != 0)
ep_raise_error ();
ep_raise_error_if_nok (ipc_message_try_parse_string_utf16_t_byte_array (buffer, buffer_len, (const uint8_t **)&temp_buffer, &temp_buffer_len));
*value = (ep_char16_t *)*buffer;
if (temp_buffer_len != 0) {
*string_byte_array = ep_rt_byte_array_alloc (temp_buffer_len);
ep_raise_error_if_nok (*string_byte_array != NULL);
memcpy (*string_byte_array, temp_buffer, temp_buffer_len);
} else {
*value = NULL;
*string_byte_array = NULL;
}
*buffer = *buffer + (string_len * sizeof (ep_char16_t));
*buffer_len = *buffer_len - (string_len * sizeof (ep_char16_t));
*string_byte_array_len = temp_buffer_len;
result = true;
ep_on_exit:
......@@ -425,6 +474,21 @@ ep_on_error:
ep_exit_error_handler ();
}
bool
ds_ipc_message_try_parse_string_utf16_t (
uint8_t **buffer,
uint32_t *buffer_len,
const ep_char16_t **value)
{
EP_ASSERT (buffer != NULL);
EP_ASSERT (buffer_len != NULL);
EP_ASSERT (value != NULL);
EP_ASSERT (!(((size_t)*buffer) & 0x1));
uint32_t string_byte_array_len = 0;
return ipc_message_try_parse_string_utf16_t_byte_array (buffer, buffer_len, (const uint8_t **)value, &string_byte_array_len);
}
bool
ds_ipc_message_initialize_header_uint32_t_payload (
DiagnosticsIpcMessage *message,
......
......@@ -145,6 +145,13 @@ ds_ipc_message_try_parse_int32_t (
return ds_ipc_message_try_parse_uint32_t (buffer, buffer_len, (uint32_t *)value);
}
bool
ds_ipc_message_try_parse_string_utf16_t_byte_array_alloc (
uint8_t **buffer,
uint32_t *buffer_len,
uint8_t **string_byte_array,
uint32_t *string_byte_array_len);
bool
ds_ipc_message_try_parse_string_utf16_t (
uint8_t **buffer,
......
......@@ -10,7 +10,6 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Diagnostics.Tools.RuntimeClient" Version="$(MicrosoftDiagnosticsToolsRuntimeClientVersion)" />
<PackageReference Include="Microsoft.Diagnostics.NETCore.Client" Version="$(MicrosoftDiagnosticsNETCoreClientVersion)" />
</ItemGroup>
<Target Name="Build" DependsOnTargets="$(TraversalBuildDependsOn)" />
......
......@@ -71,6 +71,7 @@
<TestBuildMode Condition="'$(__TestBuildMode)' != ''">$(__TestBuildMode)</TestBuildMode>
<RuntimeFlavor Condition="'$(__RuntimeFlavor)' != ''">$(__RuntimeFlavor)</RuntimeFlavor>
<RuntimeFlavor Condition="'$(RuntimeFlavor)' == ''">coreclr</RuntimeFlavor>
<RestoreDefaultOptimizationDataPackage Condition="'$(RestoreDefaultOptimizationDataPackage)' == ''">false</RestoreDefaultOptimizationDataPackage>
<PortableBuild Condition="'$(PortableBuild)' == ''">true</PortableBuild>
......
......@@ -8,7 +8,7 @@
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using Microsoft.Diagnostics.Tools.RuntimeClient;
using Microsoft.Diagnostics.NETCore.Client;
using Microsoft.Diagnostics.Tracing;
using Microsoft.Diagnostics.Tracing.Parsers;
using Microsoft.Diagnostics.Tracing.Parsers.Clr;
......@@ -23,18 +23,17 @@ public static unsafe int Main()
var keywords =
ClrTraceEventParser.Keywords.Jit | ClrTraceEventParser.Keywords.JittedMethodILToNativeMap;
var dotnetRuntimeProvider = new List<Provider>
var dotnetRuntimeProvider = new List<EventPipeProvider>
{
new Provider("Microsoft-Windows-DotNETRuntime", eventLevel: EventLevel.Verbose, keywords: (ulong)keywords)
new EventPipeProvider("Microsoft-Windows-DotNETRuntime", eventLevel: EventLevel.Verbose, keywords: (long)keywords)
};
var config = new SessionConfiguration(1024, EventPipeSerializationFormat.NetTrace, dotnetRuntimeProvider);
return
IpcTraceTest.RunAndValidateEventCounts(
new Dictionary<string, ExpectedEventCount>(),
JitMethods,
config,
dotnetRuntimeProvider,
1024,
ValidateMappings);
}
......
......@@ -12,6 +12,7 @@
<ProjectReference Include="tests_r.ilproj" Aliases="tests_r" />
<ProjectReference Include="attribute.csproj" />
<ProjectReference Include="../../../../tracing/eventpipe/common/common.csproj" />
<ProjectReference Include="../../../../tracing/eventpipe/common/Microsoft.Diagnostics.NETCore.Client/Microsoft.Diagnostics.NETCore.Client.csproj" />
<Compile Include="$(MSBuildProjectName).cs" />
</ItemGroup>
</Project>
......@@ -3626,9 +3626,6 @@
<ExcludeList Include = "$(XunitTestBinBase)/tracing/eventpipe/complus_config/name_config_with_pid/**">
<Issue>https://github.com/dotnet/runtime/issues/54974</Issue>
</ExcludeList>
<ExcludeList Include = "$(XunitTestBinBase)/tracing/eventpipe/**">
<Issue>Need to update with Microsoft.Diagnostics.NETCore.Client port https://github.com/dotnet/runtime/pull/64358</Issue>
</ExcludeList>
<ExcludeList Include = "$(XunitTestBinBase)/Interop/UnmanagedCallConv/UnmanagedCallConvTest/**">
<Issue>https://github.com/dotnet/runtime/issues/53077</Issue>
</ExcludeList>
......@@ -3641,6 +3638,21 @@
<ExcludeList Include = "$(XunitTestBinBase)/JIT/Regression/JitBlue/DevDiv_461649/DevDiv_461649/**">
<Issue>https://github.com/dotnet/runtime/issues/53353</Issue>
</ExcludeList>
<ExcludeList Include = "$(XunitTestBinBase)/tracing/eventpipe/diagnosticport/**">
<Issue>Cannot run multiple apps on Android for subprocesses</Issue>
</ExcludeList>
<ExcludeList Include = "$(XunitTestBinBase)/tracing/eventpipe/pauseonstart/**">
<Issue>Cannot run multiple apps on Android for subprocesses</Issue>
</ExcludeList>
<ExcludeList Include = "$(XunitTestBinBase)/tracing/eventpipe/processenvironment/**">
<Issue>Cannot run multiple apps on Android for subprocesses</Issue>
</ExcludeList>
<ExcludeList Include = "$(XunitTestBinBase)/tracing/eventpipe/reverse/**">
<Issue>Cannot run multiple apps on Android for subprocesses</Issue>
</ExcludeList>
<ExcludeList Include = "$(XunitTestBinBase)/tracing/eventpipe/reverseouter/**">
<Issue>Cannot run multiple apps on Android for subprocesses</Issue>
</ExcludeList>
</ItemGroup>
<ItemGroup Condition=" $(TargetOS) == 'Android' And '$(TargetArchitecture)' == 'arm64' " >
......
......@@ -10,5 +10,6 @@
<Compile Include="DiagnosticsIPCWorkaround.cs" />
<ProjectReference Include="$(TestSourceDir)Common/CoreCLRTestLibrary/CoreCLRTestLibrary.csproj" />
<ProjectReference Include="$(TestSourceDir)tracing/eventpipe/common/common.csproj" />
<ProjectReference Include="$(TestSourceDir)tracing/eventpipe/common/Microsoft.Diagnostics.NETCore.Client/Microsoft.Diagnostics.NETCore.Client.csproj" />
</ItemGroup>
</Project>
......@@ -17,5 +17,6 @@
<ProjectReference Include="$(TestSourceDir)Common/CoreCLRTestLibrary/CoreCLRTestLibrary.csproj" />
<ProjectReference Include="../common/profiler_common.csproj" />
<ProjectReference Include="$(MSBuildThisFileDirectory)/../native/CMakeLists.txt" />
<ProjectReference Include="$(TestSourceDir)tracing/eventpipe/common/Microsoft.Diagnostics.NETCore.Client/Microsoft.Diagnostics.NETCore.Client.csproj" />
</ItemGroup>
</Project>
......@@ -18,5 +18,6 @@
<ProjectReference Include="../common/profiler_common.csproj" />
<ProjectReference Include="$(TestSourceDir)tracing/eventpipe/common/common.csproj" />
<ProjectReference Include="$(MSBuildThisFileDirectory)/../native/CMakeLists.txt" />
<ProjectReference Include="$(TestSourceDir)tracing/eventpipe/common/Microsoft.Diagnostics.NETCore.Client/Microsoft.Diagnostics.NETCore.Client.csproj" />
</ItemGroup>
</Project>
......@@ -8,10 +8,10 @@
using System.Threading;
using System.Threading.Tasks;
using System.Collections.Generic;
using Microsoft.Diagnostics.Tools.RuntimeClient;
using Microsoft.Diagnostics.Tracing;
using Tracing.Tests.Common;
using Microsoft.Diagnostics.Tracing.Parsers.Clr;
using Microsoft.Diagnostics.NETCore.Client;
namespace Tracing.Tests.BigEventsValidation
{
......@@ -26,10 +26,10 @@ private BigEventSource()
}
public static BigEventSource Log = new BigEventSource();
public void BigEvent()
{
WriteEvent(1, bigString);
WriteEvent(1, bigString);
}
public void SmallEvent()
......@@ -38,20 +38,19 @@ public void SmallEvent()
}
}
public class BigEventsValidation
{
public static int Main(string[] args)
{
// This test tries to send a big event (>100KB) and checks that the app does not crash
// See https://github.com/dotnet/runtime/issues/50515 for the regression issue
var providers = new List<Provider>()
var providers = new List<EventPipeProvider>()
{
new Provider("BigEventSource")
new EventPipeProvider("BigEventSource", EventLevel.Verbose)
};
var configuration = new SessionConfiguration(circularBufferSizeMB: 1024, format: EventPipeSerializationFormat.NetTrace, providers: providers);
return IpcTraceTest.RunAndValidateEventCounts(_expectedEventCounts, _eventGeneratingAction, configuration, _Verify);
return IpcTraceTest.RunAndValidateEventCounts(_expectedEventCounts, _eventGeneratingAction, providers, 1024, _Verify);
}
private static Dictionary<string, ExpectedEventCount> _expectedEventCounts = new Dictionary<string, ExpectedEventCount>()
......@@ -59,7 +58,7 @@ public static int Main(string[] args)
{ "BigEventSource", -1 }
};
private static Action _eventGeneratingAction = () =>
private static Action _eventGeneratingAction = () =>
{
// Write 10 big events
for (int i = 0; i < 10; i++)
......
......@@ -11,5 +11,6 @@
<ItemGroup>
<Compile Include="$(MSBuildProjectName).cs" />
<ProjectReference Include="../common/common.csproj" />
<ProjectReference Include="../common/Microsoft.Diagnostics.NETCore.Client/Microsoft.Diagnostics.NETCore.Client.csproj" />
</ItemGroup>
</Project>
......@@ -8,8 +8,9 @@
using System.Threading;
using System.Threading.Tasks;
using System.Collections.Generic;
using Microsoft.Diagnostics.Tools.RuntimeClient;
using System.Text.RegularExpressions;
using Tracing.Tests.Common;
using Microsoft.Diagnostics.NETCore.Client;
namespace Tracing.Tests.BufferValidation
{
......@@ -27,20 +28,18 @@ public static int Main(string[] args)
// This tests the resilience of message sending with
// smaller buffers, specifically 1MB and 4MB
var providers = new List<Provider>()
var providers = new List<EventPipeProvider>()
{
new Provider("MyEventSource")
new EventPipeProvider("MyEventSource", EventLevel.Verbose)
};
var tests = new int[] { 0, 2 }
.Select(x => (uint)Math.Pow(2, x))
.Select(bufferSize => new SessionConfiguration(circularBufferSizeMB: bufferSize, format: EventPipeSerializationFormat.NetTrace, providers: providers))
.Select<SessionConfiguration, Func<int>>(configuration => () => IpcTraceTest.RunAndValidateEventCounts(_expectedEventCounts, _eventGeneratingAction, configuration));
var buffersizes = new int[] { 0, 2 }
.Select(x => (int)Math.Pow(2, x));
foreach (var test in tests)
foreach (var buffersize in buffersizes)
{
var ret = test();
if (ret < 0)
var ret = IpcTraceTest.RunAndValidateEventCounts(_expectedEventCounts, _eventGeneratingAction, providers, buffersize);
if (ret != 100)
return ret;
}
......@@ -56,7 +55,7 @@ public static int Main(string[] args)
{ "MyEventSource", -1 }
};
private static Action _eventGeneratingAction = () =>
private static Action _eventGeneratingAction = () =>
{
foreach (var _ in Enumerable.Range(0,1000))
{
......
......@@ -10,5 +10,6 @@
<ItemGroup>
<Compile Include="$(MSBuildProjectName).cs" />
<ProjectReference Include="../common/common.csproj" />
<ProjectReference Include="../common/Microsoft.Diagnostics.NETCore.Client/Microsoft.Diagnostics.NETCore.Client.csproj" />
</ItemGroup>
</Project>
......@@ -2,18 +2,18 @@
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.Collections.Generic;
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Diagnostics.Tracing;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using System.Collections.Generic;
using System.Collections.Concurrent;
using Microsoft.Diagnostics.NETCore.Client;
using Microsoft.Diagnostics.Tracing;
using Microsoft.Diagnostics.Tools.RuntimeClient;
using System.Runtime.InteropServices;
using System.Linq;
using System.Text.RegularExpressions;
namespace Tracing.Tests.Common
{
......@@ -91,16 +91,6 @@ public sealed class SentinelEventSource : EventSource
public void SentinelEvent() { WriteEvent(1, "SentinelEvent"); }
}
public static class SessionConfigurationExtensions
{
public static SessionConfiguration InjectSentinel(this SessionConfiguration sessionConfiguration)
{
var newProviderList = new List<Provider>(sessionConfiguration.Providers);
newProviderList.Add(new Provider("SentinelEventSource"));
return new SessionConfiguration(sessionConfiguration.CircularBufferSizeInMB, sessionConfiguration.Format, newProviderList.AsReadOnly());
}
}
public class IpcTraceTest
{
// This Action is executed while the trace is being collected.
......@@ -111,8 +101,6 @@ public class IpcTraceTest
// and don't care about the number of events sent
private Dictionary<string, ExpectedEventCount> _expectedEventCounts;
private Dictionary<string, int> _actualEventCounts = new Dictionary<string, int>();
private int _droppedEvents = 0;
private SessionConfiguration _sessionConfiguration;
// A function to be called with the EventPipeEventSource _before_
// the call to `source.Process()`. The function should return another
......@@ -120,21 +108,34 @@ public class IpcTraceTest
// Example in situ: providervalidation.cs
private Func<EventPipeEventSource, Func<int>> _optionalTraceValidator;
/// <summary>
/// This is list of the EventPipe providers to turn on for the test execution
/// </summary>
private List<EventPipeProvider> _testProviders;
/// <summary>
/// This represents the current EventPipeSession
/// </summary>
private EventPipeSession _eventPipeSession;
/// <summary>
/// This is the list of EventPipe providers for the sentinel EventSource that indicates that the process is ready
/// </summary>
private List<EventPipeProvider> _sentinelProviders = new List<EventPipeProvider>()
{
new EventPipeProvider("SentinelEventSource", EventLevel.Verbose, -1)
};
IpcTraceTest(
Dictionary<string, ExpectedEventCount> expectedEventCounts,
Action eventGeneratingAction,
SessionConfiguration? sessionConfiguration = null,
List<EventPipeProvider> providers,
int circularBufferMB,
Func<EventPipeEventSource, Func<int>> optionalTraceValidator = null)
{
_eventGeneratingAction = eventGeneratingAction;
_expectedEventCounts = expectedEventCounts;
_sessionConfiguration = sessionConfiguration?.InjectSentinel() ?? new SessionConfiguration(
circularBufferSizeMB: 1000,
format: EventPipeSerializationFormat.NetTrace,
providers: new List<Provider> {
new Provider("Microsoft-Windows-DotNETRuntime"),
new Provider("SentinelEventSource")
});
_testProviders = providers;
_optionalTraceValidator = optionalTraceValidator;
}
......@@ -144,12 +145,7 @@ private int Fail(string message = "")
Logger.logger.Log(message);
Logger.logger.Log("Configuration:");
Logger.logger.Log("{");
Logger.logger.Log($"\tbufferSize: {_sessionConfiguration.CircularBufferSizeInMB},");
Logger.logger.Log("\tproviders: [");
foreach (var provider in _sessionConfiguration.Providers)
{
Logger.logger.Log($"\t\t{provider.ToString()},");
}
Logger.logger.Log("\t]");
Logger.logger.Log("}\n");
Logger.logger.Log("Expected:");
......@@ -204,26 +200,28 @@ private int Validate()
sentinelTask.Start();
int processId = Process.GetCurrentProcess().Id;
object threadSync = new object(); // for locking eventpipeSessionId access
ulong eventpipeSessionId = 0;
object threadSync = new object(); // for locking eventpipeSession access
Func<int> optionalTraceValidationCallback = null;
DiagnosticsClient client = new DiagnosticsClient(processId);
#if DIAGNOSTICS_RUNTIME
if (OperatingSystem.IsAndroid())
client = new DiagnosticsClient(new IpcEndpointConfig("127.0.0.1:9000", IpcEndpointConfig.TransportType.TcpSocket, IpcEndpointConfig.PortType.Listen));
#endif
var readerTask = new Task(() =>
{
Logger.logger.Log("Connecting to EventPipe...");
using (var eventPipeStream = new StreamProxy(EventPipeClient.CollectTracing(processId, _sessionConfiguration, out var sessionId)))
try
{
_eventPipeSession = client.StartEventPipeSession(_testProviders.Concat(_sentinelProviders));
}
catch (DiagnosticsClientException ex)
{
Logger.logger.Log("Failed to connect to EventPipe!");
Logger.logger.Log(ex.ToString());
throw new ApplicationException("Failed to connect to EventPipe");
}
using (var eventPipeStream = new StreamProxy(_eventPipeSession.EventStream))
{
if (sessionId == 0)
{
Logger.logger.Log("Failed to connect to EventPipe!");
throw new ApplicationException("Failed to connect to EventPipe");
}
Logger.logger.Log($"Connected to EventPipe with sessionID '0x{sessionId:x}'");
lock (threadSync)
{
eventpipeSessionId = sessionId;
}
Logger.logger.Log("Creating EventPipeEventSource...");
using (EventPipeEventSource source = new EventPipeEventSource(eventPipeStream))
{
......@@ -239,7 +237,6 @@ private int Validate()
Logger.logger.Log("Saw sentinel event");
sentinelEventReceived.Set();
}
else if (_actualEventCounts.TryGetValue(eventData.ProviderName, out _))
{
_actualEventCounts[eventData.ProviderName]++;
......@@ -269,12 +266,12 @@ private int Validate()
{
source.Process();
}
catch (Exception e)
catch (Exception)
{
Logger.logger.Log($"Exception thrown while reading; dumping culprit stream to disk...");
eventPipeStream.DumpStreamToDisk();
// rethrow it to fail the test
throw e;
throw;
}
Logger.logger.Log("Stopping stream processing");
Logger.logger.Log($"Dropped {source.EventsLost} events");
......@@ -296,12 +293,12 @@ private int Validate()
_eventGeneratingAction();
Logger.logger.Log("Stopping event generating action");
var stopTask = Task.Run(() =>
var stopTask = Task.Run(() =>
{
Logger.logger.Log("Sending StopTracing command...");
lock (threadSync) // eventpipeSessionId
lock (threadSync) // eventpipeSession
{
EventPipeClient.StopTracing(processId, eventpipeSessionId);
_eventPipeSession.Stop();
}
Logger.logger.Log("Finished StopTracing command");
});
......@@ -338,7 +335,7 @@ private int Validate()
}
// Ensure that we have a clean environment for running the test.
// Specifically check that we don't have more than one match for
// Specifically check that we don't have more than one match for
// Diagnostic IPC sockets in the TempPath. These can be left behind
// by bugs, catastrophic test failures, etc. from previous testing.
// The tmp directory is only cleared on reboot, so it is possible to
......@@ -393,11 +390,12 @@ static public bool EnsureCleanEnvironment()
public static int RunAndValidateEventCounts(
Dictionary<string, ExpectedEventCount> expectedEventCounts,
Action eventGeneratingAction,
SessionConfiguration? sessionConfiguration = null,
List<EventPipeProvider> providers,
int circularBufferMB=1024,
Func<EventPipeEventSource, Func<int>> optionalTraceValidator = null)
{
Logger.logger.Log("==TEST STARTING==");
var test = new IpcTraceTest(expectedEventCounts, eventGeneratingAction, sessionConfiguration, optionalTraceValidator);
var test = new IpcTraceTest(expectedEventCounts, eventGeneratingAction, providers, circularBufferMB, optionalTraceValidator);
try
{
var ret = test.Validate();
......
#if DIAGNOSTICS_RUNTIME
// As of https://github.com/dotnet/runtime/pull/64358, InternalsVisibleTo MSBuild Item does not seem to work in Dotnet/Runtime like it does on Dotnet/Diagnostics
using System;
using System.IO;
using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("common")]
#endif
\ No newline at end of file
// 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.IO;
using System.Runtime.InteropServices;
using System.Text;
namespace Microsoft.Diagnostics.NETCore.Client
{
internal static class BinaryWriterExtensions
{
public static void WriteString(this BinaryWriter @this, string value)
{
if (@this == null)
throw new ArgumentNullException(nameof(@this));
@this.Write(value != null ? (value.Length + 1) : 0);
if (value != null)
@this.Write(Encoding.Unicode.GetBytes(value + '\0'));
}
}
}
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
namespace Microsoft.Diagnostics.NETCore.Client
{
public class DiagnosticsClientException : Exception
{
public DiagnosticsClientException(string msg) : base(msg) {}
}
// When a certian command is not supported by either the library or the target process' runtime
public class UnsupportedProtocolException : DiagnosticsClientException
{
public UnsupportedProtocolException(string msg) : base(msg) {}
}
// When the runtime is no longer availble for attaching.
public class ServerNotAvailableException : DiagnosticsClientException
{
public ServerNotAvailableException(string msg) : base(msg) {}
}
// When the runtime responded with an error
public class ServerErrorException : DiagnosticsClientException
{
public ServerErrorException(string msg): base(msg) {}
}
// When the runtime doesn't support the command
public class UnsupportedCommandException : ServerErrorException
{
public UnsupportedCommandException(string msg): base(msg) {}
}
// When the runtime already has loaded profiler
public class ProfilerAlreadyActiveException : ServerErrorException
{
public ProfilerAlreadyActiveException(string msg): base(msg) {}
}
}
\ No newline at end of file
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
namespace Microsoft.Diagnostics.NETCore.Client
{
public enum DumpType
{
Normal = 1,
WithHeap = 2,
Triage = 3,
Full = 4
}
}
\ No newline at end of file
// 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.Linq;
using System.Text;
namespace Microsoft.Diagnostics.NETCore.Client
{
public sealed class EventPipeProvider
{
public EventPipeProvider(string name, EventLevel eventLevel, long keywords = 0xF00000000000, IDictionary<string, string> arguments = null)
{
Name = name;
EventLevel = eventLevel;
Keywords = keywords;
Arguments = arguments;
}
public long Keywords { get; }
public EventLevel EventLevel { get; }
public string Name { get; }
public IDictionary<string, string> Arguments { get; }
public override string ToString()
{
return $"{Name}:0x{Keywords:X16}:{(uint)EventLevel}{(Arguments == null ? "" : $":{GetArgumentString()}")}";
}
public override bool Equals(object obj)
{
if (obj == null || GetType() != obj.GetType())
{
return false;
}
return this == (EventPipeProvider)obj;
}
public override int GetHashCode()
{
int hash = 0;
hash ^= this.Name.GetHashCode();
hash ^= this.Keywords.GetHashCode();
hash ^= this.EventLevel.GetHashCode();
hash ^= GetArgumentString().GetHashCode();
return hash;
}
public static bool operator ==(EventPipeProvider left, EventPipeProvider right)
{
return left.ToString() == right.ToString();
}
public static bool operator !=(EventPipeProvider left, EventPipeProvider right)
{
return !(left == right);
}
internal string GetArgumentString()
{
if (Arguments == null)
{
return "";
}
return string.Join(";", Arguments.Select(a => {
var escapedKey = a.Key.Contains(";") || a.Key.Contains("=") ? $"\"{a.Key}\"" : a.Key;
var escapedValue = a.Value.Contains(";") || a.Value.Contains("=") ? $"\"{a.Value}\"" : a.Value;
return $"{escapedKey}={escapedValue}";
}));
}
}
}
// 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;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
namespace Microsoft.Diagnostics.NETCore.Client
{
public class EventPipeSession : IDisposable
{
private long _sessionId;
private IpcEndpoint _endpoint;
private bool _disposedValue = false; // To detect redundant calls
private bool _stopped = false; // To detect redundant calls
private readonly IpcResponse _response;
private EventPipeSession(IpcEndpoint endpoint, IpcResponse response, long sessionId)
{
_endpoint = endpoint;
_response = response;
_sessionId = sessionId;
}
public Stream EventStream => _response.Continuation;
internal static EventPipeSession Start(IpcEndpoint endpoint, IEnumerable<EventPipeProvider> providers, bool requestRundown, int circularBufferMB)
{
IpcMessage requestMessage = CreateStartMessage(providers, requestRundown, circularBufferMB);
IpcResponse? response = IpcClient.SendMessageGetContinuation(endpoint, requestMessage);
return CreateSessionFromResponse(endpoint, ref response, nameof(Start));
}
internal static async Task<EventPipeSession> StartAsync(IpcEndpoint endpoint, IEnumerable<EventPipeProvider> providers, bool requestRundown, int circularBufferMB, CancellationToken cancellationToken)
{
IpcMessage requestMessage = CreateStartMessage(providers, requestRundown, circularBufferMB);
IpcResponse? response = await IpcClient.SendMessageGetContinuationAsync(endpoint, requestMessage, cancellationToken).ConfigureAwait(false);
return CreateSessionFromResponse(endpoint, ref response, nameof(StartAsync));
}
///<summary>
/// Stops the given session
///</summary>
public void Stop()
{
if (TryCreateStopMessage(out IpcMessage requestMessage))
{
try
{
IpcMessage response = IpcClient.SendMessage(_endpoint, requestMessage);
DiagnosticsClient.ValidateResponseMessage(response, nameof(Stop));
}
// On non-abrupt exits (i.e. the target process has already exited and pipe is gone, sending Stop command will fail).
catch (IOException)
{
throw new ServerNotAvailableException("Could not send Stop command. The target process may have exited.");
}
}
}
public async Task StopAsync(CancellationToken cancellationToken)
{
if (TryCreateStopMessage(out IpcMessage requestMessage))
{
try
{
IpcMessage response = await IpcClient.SendMessageAsync(_endpoint, requestMessage, cancellationToken).ConfigureAwait(false);
DiagnosticsClient.ValidateResponseMessage(response, nameof(StopAsync));
}
// On non-abrupt exits (i.e. the target process has already exited and pipe is gone, sending Stop command will fail).
catch (IOException)
{
throw new ServerNotAvailableException("Could not send Stop command. The target process may have exited.");
}
}
}
private static IpcMessage CreateStartMessage(IEnumerable<EventPipeProvider> providers, bool requestRundown, int circularBufferMB)
{
var config = new EventPipeSessionConfiguration(circularBufferMB, EventPipeSerializationFormat.NetTrace, providers, requestRundown);
return new IpcMessage(DiagnosticsServerCommandSet.EventPipe, (byte)EventPipeCommandId.CollectTracing2, config.SerializeV2());
}
private static EventPipeSession CreateSessionFromResponse(IpcEndpoint endpoint, ref IpcResponse? response, string operationName)
{
try
{
DiagnosticsClient.ValidateResponseMessage(response.Value.Message, operationName);
long sessionId = BitConverter.ToInt64(response.Value.Message.Payload, 0);
var session = new EventPipeSession(endpoint, response.Value, sessionId);
response = null;
return session;
}
finally
{
response?.Dispose();
}
}
private bool TryCreateStopMessage(out IpcMessage stopMessage)
{
Debug.Assert(_sessionId > 0);
// Do not issue another Stop command if it has already been issued for this session instance.
if (_stopped)
{
stopMessage = null;
return false;
}
else
{
_stopped = true;
}
byte[] payload = BitConverter.GetBytes(_sessionId);
stopMessage = new IpcMessage(DiagnosticsServerCommandSet.EventPipe, (byte)EventPipeCommandId.StopTracing, payload);
return true;
}
protected virtual void Dispose(bool disposing)
{
// If session being disposed hasn't been stopped, attempt to stop it first
if (!_stopped)
{
try
{
Stop();
}
catch {} // swallow any exceptions that may be thrown from Stop.
}
if (!_disposedValue)
{
if (disposing)
{
_response.Dispose();
}
_disposedValue = true;
}
}
public void Dispose()
{
Dispose(true);
}
}
}
\ No newline at end of file
// 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.IO;
using System.Linq;
namespace Microsoft.Diagnostics.NETCore.Client
{
internal enum EventPipeSerializationFormat
{
NetPerf,
NetTrace
}
internal class EventPipeSessionConfiguration
{
public EventPipeSessionConfiguration(int circularBufferSizeMB, EventPipeSerializationFormat format, IEnumerable<EventPipeProvider> providers, bool requestRundown=true)
{
if (circularBufferSizeMB == 0)
throw new ArgumentException($"Buffer size cannot be zero.");
if (format != EventPipeSerializationFormat.NetPerf && format != EventPipeSerializationFormat.NetTrace)
throw new ArgumentException("Unrecognized format");
if (providers == null)
throw new ArgumentNullException(nameof(providers));
CircularBufferSizeInMB = circularBufferSizeMB;
Format = format;
RequestRundown = requestRundown;
_providers = new List<EventPipeProvider>(providers);
}
public bool RequestRundown { get; }
public int CircularBufferSizeInMB { get; }
public EventPipeSerializationFormat Format { get; }
public IReadOnlyCollection<EventPipeProvider> Providers => _providers.AsReadOnly();
private readonly List<EventPipeProvider> _providers;
public byte[] SerializeV2()
{
byte[] serializedData = null;
using (var stream = new MemoryStream())
using (var writer = new BinaryWriter(stream))
{
writer.Write(CircularBufferSizeInMB);
writer.Write((uint)Format);
writer.Write(RequestRundown);
writer.Write(Providers.Count);
foreach (var provider in Providers)
{
writer.Write(provider.Keywords);
writer.Write((uint)provider.EventLevel);
writer.WriteString(provider.Name);
writer.WriteString(provider.GetArgumentString());
}
writer.Flush();
serializedData = stream.ToArray();
}
return serializedData;
}
}
}
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
namespace Microsoft.Diagnostics.NETCore.Client
{
public enum WriteDumpFlags
{
None = 0x00,
LoggingEnabled = 0x01,
VerboseLoggingEnabled = 0x02,
CrashReportEnabled = 0x04
}
}
\ No newline at end of file
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Net.Sockets;
namespace Microsoft.Diagnostics.NETCore.Client
{
internal sealed class ExposedSocketNetworkStream :
NetworkStream
{
public ExposedSocketNetworkStream(Socket socket, bool ownsSocket)
: base(socket, ownsSocket)
{
}
public new Socket Socket => base.Socket;
}
}
// 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.Linq;
using System.IO;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace Microsoft.Diagnostics.NETCore.Client
{
/**
* ==ADVERTISE PROTOCOL==
* Before standard IPC Protocol communication can occur on a client-mode connection
* the runtime must advertise itself over the connection. ALL SUBSEQUENT COMMUNICATION
* IS STANDARD DIAGNOSTICS IPC PROTOCOL COMMUNICATION.
*
* The flow for Advertise is a one-way burst of 34 bytes consisting of
* 8 bytes - "ADVR_V1\0" (ASCII chars + null byte)
* 16 bytes - CLR Instance Cookie (little-endian)
* 8 bytes - PID (little-endian)
* 2 bytes - future
*/
internal sealed class IpcAdvertise
{
private static byte[] Magic_V1 => Encoding.ASCII.GetBytes("ADVR_V1" + '\0');
private static readonly int IpcAdvertiseV1SizeInBytes = Magic_V1.Length + 16 + 8 + 2; // 34 bytes
private IpcAdvertise(byte[] magic, Guid cookie, UInt64 pid, UInt16 future)
{
Future = future;
Magic = magic;
ProcessId = pid;
RuntimeInstanceCookie = cookie;
}
public static int V1SizeInBytes { get; } = IpcAdvertiseV1SizeInBytes;
public static async Task<IpcAdvertise> ParseAsync(Stream stream, CancellationToken token)
{
byte[] buffer = new byte[IpcAdvertiseV1SizeInBytes];
int totalRead = 0;
do
{
int read = await stream.ReadAsync(buffer, totalRead, buffer.Length - totalRead, token).ConfigureAwait(false);
if (0 == read)
{
throw new EndOfStreamException();
}
totalRead += read;
}
while (totalRead < buffer.Length);
int index = 0;
byte[] magic = new byte[Magic_V1.Length];
Array.Copy(buffer, magic, Magic_V1.Length);
index += Magic_V1.Length;
if (!Magic_V1.SequenceEqual(magic))
{
throw new Exception("Invalid advertise message from client connection");
}
byte[] cookieBuffer = new byte[16];
Array.Copy(buffer, index, cookieBuffer, 0, 16);
Guid cookie = new Guid(cookieBuffer);
index += 16;
UInt64 pid = BitConverter.ToUInt64(buffer, index);
index += 8;
UInt16 future = BitConverter.ToUInt16(buffer, index);
index += 2;
// FUTURE: switch on incoming magic and change if version ever increments
return new IpcAdvertise(magic, cookie, pid, future);
}
public static async Task SerializeAsync(Stream stream, Guid runtimeInstanceCookie, ulong processId, CancellationToken token)
{
int index = 0;
byte[] buffer = new byte[IpcAdvertiseV1SizeInBytes];
Array.Copy(Magic_V1, buffer, Magic_V1.Length);
index += Magic_V1.Length;
byte[] cookieBuffer = runtimeInstanceCookie.ToByteArray();
Array.Copy(cookieBuffer, 0, buffer, index, cookieBuffer.Length);
index += cookieBuffer.Length;
Array.Copy(BitConverter.GetBytes(processId), 0, buffer, index, sizeof(ulong));
index += sizeof(ulong);
short future = 0;
Array.Copy(BitConverter.GetBytes(future), 0, buffer, index, sizeof(short));
index += sizeof(short);
await stream.WriteAsync(buffer, 0, index).ConfigureAwait(false);
}
public override string ToString()
{
return $"{{ Magic={Magic}; ClrInstanceId={RuntimeInstanceCookie}; ProcessId={ProcessId}; Future={Future} }}";
}
private UInt16 Future { get; } = 0;
public byte[] Magic { get; } = Magic_V1;
public UInt64 ProcessId { get; } = 0;
public Guid RuntimeInstanceCookie { get; } = Guid.Empty;
}
}
// 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.IO;
using System.Threading;
using System.Threading.Tasks;
namespace Microsoft.Diagnostics.NETCore.Client
{
internal class IpcClient
{
// The amount of time to wait for a stream to be available for consumption by the Connect method.
// Normally expect the runtime to respond quickly but resource constrained machines may take longer.
internal static readonly TimeSpan ConnectTimeout = TimeSpan.FromSeconds(30);
/// <summary>
/// Sends a single DiagnosticsIpc Message to the dotnet process associated with the <paramref name="endpoint"/>.
/// </summary>
/// <param name="endpoint">An endpoint that provides a diagnostics connection to a runtime instance.</param>
/// <param name="message">The DiagnosticsIpc Message to be sent</param>
/// <returns>An <see cref="IpcMessage"/> that is the response message.</returns>
public static IpcMessage SendMessage(IpcEndpoint endpoint, IpcMessage message)
{
using IpcResponse response = SendMessageGetContinuation(endpoint, message);
return response.Message;
}
/// <summary>
/// Sends a single DiagnosticsIpc Message to the dotnet process associated with the <paramref name="endpoint"/>.
/// </summary>
/// <param name="endpoint">An endpoint that provides a diagnostics connection to a runtime instance.</param>
/// <param name="message">The DiagnosticsIpc Message to be sent</param>
/// <returns>An <see cref="IpcResponse"/> containing the response message and continuation stream.</returns>
public static IpcResponse SendMessageGetContinuation(IpcEndpoint endpoint, IpcMessage message)
{
Stream stream = null;
try
{
stream = endpoint.Connect(ConnectTimeout);
Write(stream, message);
IpcMessage response = Read(stream);
return new IpcResponse(response, Release(ref stream));
}
finally
{
stream?.Dispose();
}
}
/// <summary>
/// Sends a single DiagnosticsIpc Message to the dotnet process associated with the <paramref name="endpoint"/>.
/// </summary>
/// <param name="endpoint">An endpoint that provides a diagnostics connection to a runtime instance.</param>
/// <param name="message">The DiagnosticsIpc Message to be sent</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <returns>An <see cref="IpcMessage"/> that is the response message.</returns>
public static async Task<IpcMessage> SendMessageAsync(IpcEndpoint endpoint, IpcMessage message, CancellationToken cancellationToken)
{
using IpcResponse response = await SendMessageGetContinuationAsync(endpoint, message, cancellationToken).ConfigureAwait(false);
return response.Message;
}
/// <summary>
/// Sends a single DiagnosticsIpc Message to the dotnet process associated with the <paramref name="endpoint"/>.
/// </summary>
/// <param name="endpoint">An endpoint that provides a diagnostics connection to a runtime instance.</param>
/// <param name="message">The DiagnosticsIpc Message to be sent</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <returns>An <see cref="IpcResponse"/> containing the response message and continuation stream.</returns>
public static async Task<IpcResponse> SendMessageGetContinuationAsync(IpcEndpoint endpoint, IpcMessage message, CancellationToken cancellationToken)
{
Stream stream = null;
try
{
stream = await endpoint.ConnectAsync(cancellationToken).ConfigureAwait(false);
await WriteAsync(stream, message, cancellationToken).ConfigureAwait(false);
IpcMessage response = await ReadAsync(stream, cancellationToken).ConfigureAwait(false);
return new IpcResponse(response, Release(ref stream));
}
finally
{
stream?.Dispose();
}
}
private static void Write(Stream stream, IpcMessage message)
{
byte[] buffer = message.Serialize();
stream.Write(buffer, 0, buffer.Length);
}
private static Task WriteAsync(Stream stream, IpcMessage message, CancellationToken cancellationToken)
{
byte[] buffer = message.Serialize();
return stream.WriteAsync(buffer, 0, buffer.Length, cancellationToken);
}
private static IpcMessage Read(Stream stream)
{
return IpcMessage.Parse(stream);
}
private static Task<IpcMessage> ReadAsync(Stream stream, CancellationToken cancellationToken)
{
return IpcMessage.ParseAsync(stream, cancellationToken);
}
private static Stream Release(ref Stream stream1)
{
Stream intermediate = stream1;
stream1 = null;
return intermediate;
}
}
}
// 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.Text;
namespace Microsoft.Diagnostics.NETCore.Client
{
internal enum DiagnosticsServerCommandSet : byte
{
Dump = 0x01,
EventPipe = 0x02,
Profiler = 0x03,
Process = 0x04,
Server = 0xFF,
}
internal enum DiagnosticsServerResponseId : byte
{
OK = 0x00,
// future
Error = 0xFF,
}
internal enum EventPipeCommandId : byte
{
StopTracing = 0x01,
CollectTracing = 0x02,
CollectTracing2 = 0x03,
}
internal enum DumpCommandId : byte
{
GenerateCoreDump = 0x01,
GenerateCoreDump2 = 0x02,
}
internal enum ProfilerCommandId : byte
{
AttachProfiler = 0x01,
StartupProfiler = 0x02,
}
internal enum ProcessCommandId : byte
{
GetProcessInfo = 0x00,
ResumeRuntime = 0x01,
GetProcessEnvironment = 0x02,
SetEnvironmentVariable = 0x03,
GetProcessInfo2 = 0x04
}
}
// 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.Runtime.InteropServices;
namespace Microsoft.Diagnostics.NETCore.Client
{
internal class IpcEndpointConfig
{
public enum PortType
{
Connect,
Listen
}
public enum TransportType
{
NamedPipe,
UnixDomainSocket,
#if DIAGNOSTICS_RUNTIME
TcpSocket
#endif
}
PortType _portType;
TransportType _transportType;
// For TcpSocket TransportType, the address format will be <hostname_or_ip>:<port>
public string Address { get; }
public bool IsConnectConfig => _portType == PortType.Connect;
public bool IsListenConfig => _portType == PortType.Listen;
public TransportType Transport => _transportType;
const string NamedPipeSchema = "namedpipe";
const string UnixDomainSocketSchema = "uds";
const string NamedPipeDefaultIPCRoot = @"\\.\pipe\";
const string NamedPipeSchemaDefaultIPCRootPath = "/pipe/";
public IpcEndpointConfig(string address, TransportType transportType, PortType portType)
{
if (string.IsNullOrEmpty(address))
throw new ArgumentException("Address is null or empty.");
switch (transportType)
{
case TransportType.NamedPipe:
{
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
throw new PlatformNotSupportedException($"{NamedPipeSchema} is only supported on Windows.");
break;
}
case TransportType.UnixDomainSocket:
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
throw new PlatformNotSupportedException($"{UnixDomainSocketSchema} is not supported on Windows, use {NamedPipeSchema}.");
break;
}
#if DIAGNOSTICS_RUNTIME
case TransportType.TcpSocket:
{
break;
}
#endif
default:
{
throw new NotSupportedException($"{transportType} not supported.");
}
}
Address = address;
_transportType = transportType;
_portType = portType;
}
// Config format: [Address],[PortType]
//
// Address in UnixDomainSocket formats:
// myport => myport
// uds:myport => myport
// /User/mrx/myport.sock => /User/mrx/myport.sock
// uds:/User/mrx/myport.sock => /User/mrx/myport.sock
// uds://authority/User/mrx/myport.sock => /User/mrx/myport.sock
// uds:///User/mrx/myport.sock => /User/mrx/myport.sock
//
// Address in NamedPipe formats:
// myport => myport
// namedpipe:myport => myport
// \\.\pipe\myport => myport (dropping \\.\pipe\ is inline with implemented namedpipe client/server)
// namedpipe://./pipe/myport => myport (dropping authority and /pipe/ is inline with implemented namedpipe client/server)
// namedpipe:/pipe/myport => myport (dropping /pipe/ is inline with implemented namedpipe client/server)
// namedpipe://authority/myport => myport
// namedpipe:///myport => myport
//
// PortType: Listen|Connect, default Listen.
public static bool TryParse(string config, out IpcEndpointConfig result)
{
try
{
result = Parse(config);
}
catch(Exception)
{
result = null;
}
return result != null;
}
public static IpcEndpointConfig Parse(string config)
{
if (string.IsNullOrEmpty(config))
throw new FormatException("Missing IPC endpoint config.");
string address = "";
PortType portType = PortType.Connect;
TransportType transportType = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? TransportType.NamedPipe : TransportType.UnixDomainSocket;
if (!string.IsNullOrEmpty(config))
{
var parts = config.Split(',');
if (parts.Length > 2)
throw new FormatException($"Unknow IPC endpoint config format, {config}.");
if (string.IsNullOrEmpty(parts[0]))
throw new FormatException($"Missing IPC endpoint config address, {config}.");
portType = PortType.Listen;
address = parts[0];
if (parts.Length == 2)
{
if (string.Equals(parts[1], "connect", StringComparison.OrdinalIgnoreCase))
{
portType = PortType.Connect;
}
else if (string.Equals(parts[1], "listen", StringComparison.OrdinalIgnoreCase))
{
portType = PortType.Listen;
}
else
{
throw new FormatException($"Unknow IPC endpoint config keyword, {parts[1]} in {config}.");
}
}
}
if (Uri.TryCreate(address, UriKind.Absolute, out Uri parsedAddress))
{
if (string.Equals(parsedAddress.Scheme, NamedPipeSchema, StringComparison.OrdinalIgnoreCase))
{
transportType = TransportType.NamedPipe;
address = parsedAddress.AbsolutePath;
}
else if (string.Equals(parsedAddress.Scheme, UnixDomainSocketSchema, StringComparison.OrdinalIgnoreCase))
{
transportType = TransportType.UnixDomainSocket;
address = parsedAddress.AbsolutePath;
}
else if (string.Equals(parsedAddress.Scheme, Uri.UriSchemeFile, StringComparison.OrdinalIgnoreCase))
{
address = parsedAddress.AbsolutePath;
}
else if (!string.IsNullOrEmpty(parsedAddress.Scheme))
{
throw new FormatException($"{parsedAddress.Scheme} not supported.");
}
}
else
{
if (address.StartsWith(NamedPipeDefaultIPCRoot, StringComparison.OrdinalIgnoreCase))
transportType = TransportType.NamedPipe;
}
if (transportType == TransportType.NamedPipe)
{
if (address.StartsWith(NamedPipeDefaultIPCRoot, StringComparison.OrdinalIgnoreCase))
address = address.Substring(NamedPipeDefaultIPCRoot.Length);
else if (address.StartsWith(NamedPipeSchemaDefaultIPCRootPath, StringComparison.OrdinalIgnoreCase))
address = address.Substring(NamedPipeSchemaDefaultIPCRootPath.Length);
else if (address.StartsWith("/", StringComparison.OrdinalIgnoreCase))
address = address.Substring("/".Length);
}
return new IpcEndpointConfig(address, transportType, portType);
}
}
}
// 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.Diagnostics;
using System.IO;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace Microsoft.Diagnostics.NETCore.Client
{
internal class IpcHeader
{
IpcHeader() { }
public IpcHeader(DiagnosticsServerCommandSet commandSet, byte commandId)
{
CommandSet = (byte)commandSet;
CommandId = commandId;
}
// the number of bytes for the DiagnosticsIpc::IpcHeader type in native code
public static readonly UInt16 HeaderSizeInBytes = 20;
private static readonly UInt16 MagicSizeInBytes = 14;
public byte[] Magic = DotnetIpcV1; // byte[14] in native code
public UInt16 Size = HeaderSizeInBytes;
public byte CommandSet;
public byte CommandId;
public UInt16 Reserved = 0x0000;
// Helper expression to quickly get V1 magic string for comparison
// should be 14 bytes long
public static byte[] DotnetIpcV1 => Encoding.ASCII.GetBytes("DOTNET_IPC_V1" + '\0');
public byte[] Serialize()
{
using (var stream = new MemoryStream())
using (var writer = new BinaryWriter(stream))
{
writer.Write(Magic);
Debug.Assert(Magic.Length == MagicSizeInBytes);
writer.Write(Size);
writer.Write(CommandSet);
writer.Write(CommandId);
writer.Write((UInt16)0x0000);
writer.Flush();
return stream.ToArray();
}
}
public static IpcHeader Parse(BinaryReader reader)
{
IpcHeader header = new IpcHeader
{
Magic = reader.ReadBytes(14),
Size = reader.ReadUInt16(),
CommandSet = reader.ReadByte(),
CommandId = reader.ReadByte(),
Reserved = reader.ReadUInt16()
};
return header;
}
public static async Task<IpcHeader> ParseAsync(Stream stream, CancellationToken cancellationToken)
{
byte[] buffer = await stream.ReadBytesAsync(HeaderSizeInBytes, cancellationToken).ConfigureAwait(false);
using MemoryStream bufferStream = new MemoryStream(buffer);
using BinaryReader bufferReader = new BinaryReader(bufferStream);
IpcHeader header = Parse(bufferReader);
Debug.Assert(bufferStream.Position == bufferStream.Length);
return header;
}
override public string ToString()
{
return $"{{ Magic={Magic}; Size={Size}; CommandSet={CommandSet}; CommandId={CommandId}; Reserved={Reserved} }}";
}
}
}
// 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.IO;
using System.Text;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
namespace Microsoft.Diagnostics.NETCore.Client
{
/// <summary>
/// Errors (HRESULT) returned for DiagnosticsServerCommandId.Error responses.
/// </summary>
internal enum DiagnosticsIpcError : uint
{
InvalidArgument = 0x80070057,
ProfilerAlreadyActive = 0x8013136A,
BadEncoding = 0x80131384,
UnknownCommand = 0x80131385,
UnknownMagic = 0x80131386,
UnknownError = 0x80131387,
}
/// <summary>
/// Different diagnostic message types that are handled by the runtime.
/// </summary>
internal enum DiagnosticsMessageType : uint
{
/// <summary>
/// Initiates core dump generation
/// </summary>
GenerateCoreDump = 1,
/// <summary>
/// Starts an EventPipe session that writes events to a file when the session is stopped or the application exits.
/// </summary>
StartEventPipeTracing = 1024,
/// <summary>
/// Stops an EventPipe session.
/// </summary>
StopEventPipeTracing = 1025,
/// <summary>
/// Starts an EventPipe session that sends events out-of-proc through IPC.
/// </summary>
CollectEventPipeTracing = 1026,
/// <summary>
/// Attaches a profiler to an existing process
/// </summary>
AttachProfiler = 2048,
}
/// <summary>
/// Message header used to send commands to the .NET Core runtime through IPC.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
internal struct MessageHeader
{
/// <summary>
/// Request type.
/// </summary>
public DiagnosticsMessageType RequestType;
/// <summary>
/// Remote process Id.
/// </summary>
public uint Pid;
}
internal class IpcMessage
{
public IpcMessage()
{ }
public IpcMessage(IpcHeader header, byte[] payload = null)
{
Payload = payload ?? Array.Empty<byte>();
Header = header;
}
public IpcMessage(DiagnosticsServerCommandSet commandSet, byte commandId, byte[] payload = null)
: this(new IpcHeader(commandSet, commandId), payload)
{
}
public byte[] Payload { get; private set; } = null;
public IpcHeader Header { get; private set; } = default;
public byte[] Serialize()
{
byte[] serializedData = null;
// Verify things will fit in the size capacity
Header.Size = checked((UInt16)(IpcHeader.HeaderSizeInBytes + Payload.Length));
byte[] headerBytes = Header.Serialize();
using (var stream = new MemoryStream())
using (var writer = new BinaryWriter(stream))
{
writer.Write(headerBytes);
writer.Write(Payload);
writer.Flush();
serializedData = stream.ToArray();
}
return serializedData;
}
public static IpcMessage Parse(Stream stream)
{
IpcMessage message = new IpcMessage();
using (var reader = new BinaryReader(stream, Encoding.UTF8, true))
{
message.Header = IpcHeader.Parse(reader);
message.Payload = reader.ReadBytes(message.Header.Size - IpcHeader.HeaderSizeInBytes);
return message;
}
}
public static async Task<IpcMessage> ParseAsync(Stream stream, CancellationToken cancellationToken)
{
IpcMessage message = new IpcMessage();
message.Header = await IpcHeader.ParseAsync(stream, cancellationToken).ConfigureAwait(false);
message.Payload = await stream.ReadBytesAsync(message.Header.Size - IpcHeader.HeaderSizeInBytes, cancellationToken).ConfigureAwait(false);
return message;
}
}
}
// 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.IO;
namespace Microsoft.Diagnostics.NETCore.Client
{
internal struct IpcResponse : IDisposable
{
public readonly IpcMessage Message;
public readonly Stream Continuation;
public IpcResponse(IpcMessage message, Stream continuation)
{
Message = message;
Continuation = continuation;
}
public void Dispose()
{
Continuation?.Dispose();
}
}
}
// 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.IO;
using System.IO.Pipes;
using System.Net;
using System.Net.Sockets;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
namespace Microsoft.Diagnostics.NETCore.Client
{
internal abstract class IpcServerTransport : IDisposable
{
private IIpcServerTransportCallbackInternal _callback;
private bool _disposed;
public static IpcServerTransport Create(string address, int maxConnections, bool enableTcpIpProtocol, IIpcServerTransportCallbackInternal transportCallback = null)
{
if (!enableTcpIpProtocol || !IpcTcpSocketEndPoint.IsTcpIpEndPoint(address))
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
return new IpcWindowsNamedPipeServerTransport(address, maxConnections, transportCallback);
}
else
{
return new IpcUnixDomainSocketServerTransport(address, maxConnections, transportCallback);
}
}
else
{
return new IpcTcpSocketServerTransport(address, maxConnections, transportCallback);
}
}
protected IpcServerTransport(IIpcServerTransportCallbackInternal transportCallback = null)
{
_callback = transportCallback;
}
public void Dispose()
{
if (!_disposed)
{
Dispose(disposing: true);
_disposed = true;
}
}
protected virtual void Dispose(bool disposing)
{
}
public abstract Task<Stream> AcceptAsync(CancellationToken token);
public static int MaxAllowedConnections {
get
{
return -1;
}
}
protected void VerifyNotDisposed()
{
if (_disposed)
{
throw new ObjectDisposedException(this.GetType().Name);
}
}
internal void SetCallback(IIpcServerTransportCallbackInternal callback)
{
_callback = callback;
}
protected void OnCreateNewServer(EndPoint localEP)
{
_callback?.CreatedNewServer(localEP);
}
}
internal sealed class IpcWindowsNamedPipeServerTransport : IpcServerTransport
{
private const string PipePrefix = @"\\.\pipe\";
private NamedPipeServerStream _stream;
private readonly CancellationTokenSource _cancellation = new CancellationTokenSource();
private readonly string _pipeName;
private readonly int _maxInstances;
public IpcWindowsNamedPipeServerTransport(string pipeName, int maxInstances, IIpcServerTransportCallbackInternal transportCallback = null)
: base(transportCallback)
{
_maxInstances = maxInstances != MaxAllowedConnections ? maxInstances : NamedPipeServerStream.MaxAllowedServerInstances;
_pipeName = pipeName.StartsWith(PipePrefix) ? pipeName.Substring(PipePrefix.Length) : pipeName;
_stream = CreateNewNamedPipeServer(_pipeName, _maxInstances);
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
_cancellation.Cancel();
_stream.Dispose();
_cancellation.Dispose();
}
}
public override async Task<Stream> AcceptAsync(CancellationToken token)
{
VerifyNotDisposed();
using var linkedSource = CancellationTokenSource.CreateLinkedTokenSource(token, _cancellation.Token);
try
{
// Connect client to named pipe server stream.
await _stream.WaitForConnectionAsync(linkedSource.Token).ConfigureAwait(false);
// Transfer ownership of connected named pipe.
var connectedStream = _stream;
// Setup new named pipe server stream used in upcomming accept calls.
_stream = CreateNewNamedPipeServer(_pipeName, _maxInstances);
return connectedStream;
}
catch (Exception)
{
// Keep named pipe server stream when getting any kind of cancel request.
// Cancel happens when complete transport is about to disposed or caller
// cancels out specific accept call, no need to recycle named pipe server stream.
// In all other exception scenarios named pipe server stream will be re-created.
if (!linkedSource.IsCancellationRequested)
{
_stream.Dispose();
_stream = CreateNewNamedPipeServer(_pipeName, _maxInstances);
}
throw;
}
}
private NamedPipeServerStream CreateNewNamedPipeServer(string pipeName, int maxInstances)
{
var stream = new NamedPipeServerStream(pipeName, PipeDirection.InOut, maxInstances, PipeTransmissionMode.Byte, PipeOptions.Asynchronous, 16 * 1024, 16 * 1024);
OnCreateNewServer(null);
return stream;
}
}
internal abstract class IpcSocketServerTransport : IpcServerTransport
{
private readonly CancellationTokenSource _cancellation = new CancellationTokenSource();
protected IpcSocket _socket;
protected IpcSocketServerTransport(IIpcServerTransportCallbackInternal transportCallback = null)
: base(transportCallback)
{
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
_cancellation.Cancel();
try
{
_socket.Shutdown(SocketShutdown.Both);
}
catch { }
finally
{
_socket.Close(0);
}
_socket.Dispose();
_cancellation.Dispose();
}
}
public override async Task<Stream> AcceptAsync(CancellationToken token)
{
VerifyNotDisposed();
using var linkedSource = CancellationTokenSource.CreateLinkedTokenSource(token, _cancellation.Token);
try
{
// Accept next client socket.
var socket = await _socket.AcceptAsync(linkedSource.Token).ConfigureAwait(false);
// Configure client socket based on transport type.
OnAccept(socket);
return new ExposedSocketNetworkStream(socket, ownsSocket: true);
}
catch (Exception)
{
// Keep server socket when getting any kind of cancel request.
// Cancel happens when complete transport is about to disposed or caller
// cancels out specific accept call, no need to recycle server socket.
// In all other exception scenarios server socket will be re-created.
if (!linkedSource.IsCancellationRequested)
{
_socket = CreateNewSocketServer();
}
throw;
}
}
internal abstract bool OnAccept(Socket socket);
internal abstract IpcSocket CreateNewSocketServer();
}
internal sealed class IpcTcpSocketServerTransport : IpcSocketServerTransport
{
private readonly int _backlog;
private readonly IpcTcpSocketEndPoint _endPoint;
public IpcTcpSocketServerTransport(string address, int backlog, IIpcServerTransportCallbackInternal transportCallback = null)
: base(transportCallback)
{
_endPoint = new IpcTcpSocketEndPoint(address);
_backlog = backlog != MaxAllowedConnections ? backlog : 100;
_socket = CreateNewSocketServer();
}
internal override bool OnAccept(Socket socket)
{
socket.NoDelay = true;
return true;
}
internal override IpcSocket CreateNewSocketServer()
{
var socket = new IpcSocket(SocketType.Stream, ProtocolType.Tcp);
if (_endPoint.DualMode)
socket.DualMode = _endPoint.DualMode;
socket.Bind(_endPoint);
socket.Listen(_backlog);
socket.LingerState.Enabled = false;
OnCreateNewServer(socket.LocalEndPoint);
return socket;
}
}
internal sealed class IpcUnixDomainSocketServerTransport : IpcSocketServerTransport
{
private readonly int _backlog;
private readonly IpcUnixDomainSocketEndPoint _endPoint;
public IpcUnixDomainSocketServerTransport(string path, int backlog, IIpcServerTransportCallbackInternal transportCallback = null)
: base(transportCallback)
{
_backlog = backlog != MaxAllowedConnections ? backlog : (int)SocketOptionName.MaxConnections;
_endPoint = new IpcUnixDomainSocketEndPoint(path);
_socket = CreateNewSocketServer();
}
internal override bool OnAccept(Socket socket)
{
return true;
}
internal override IpcSocket CreateNewSocketServer()
{
var socket = new IpcUnixDomainSocket();
socket.Bind(_endPoint);
socket.Listen(_backlog);
socket.LingerState.Enabled = false;
OnCreateNewServer(null);
return socket;
}
}
internal interface IIpcServerTransportCallbackInternal
{
void CreatedNewServer(EndPoint localEP);
}
}
// 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.Diagnostics;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
namespace Microsoft.Diagnostics.NETCore.Client
{
internal class IpcSocket : Socket
{
public IpcSocket(SocketType socketType, ProtocolType protocolType)
: base(socketType, protocolType)
{
}
public IpcSocket(AddressFamily addressFamily, SocketType socketType, ProtocolType protocolType)
: base(addressFamily, socketType, protocolType)
{
}
// .NET 6 implements this method directly on Socket, but for earlier runtimes we need a polyfill
#if !NET6_0
public async Task<Socket> AcceptAsync(CancellationToken token)
{
using (token.Register(() => Close(0)))
{
try
{
return await Task.Factory.FromAsync(BeginAccept, EndAccept, this).ConfigureAwait(false);
}
// When the socket is closed, the FromAsync logic will try to call EndAccept on the socket,
// but that will throw an ObjectDisposedException. Only catch the exception if due to cancellation.
catch (ObjectDisposedException) when (token.IsCancellationRequested)
{
// First check if the cancellation token caused the closing of the socket,
// then rethrow the exception if it did not.
token.ThrowIfCancellationRequested();
Debug.Fail("Token should have thrown cancellation exception.");
return null;
}
}
}
#endif
public virtual void Connect(EndPoint remoteEP, TimeSpan timeout)
{
IAsyncResult result = BeginConnect(remoteEP, null, null);
if (result.AsyncWaitHandle.WaitOne(timeout))
{
EndConnect(result);
}
else
{
Close(0);
throw new TimeoutException();
}
}
// .NET 6 implements this method directly on Socket, but for earlier runtimes we need a polyfill
#if !NET6_0
public async Task ConnectAsync(EndPoint remoteEP, CancellationToken token)
{
using (token.Register(() => Close(0)))
{
try
{
Func<AsyncCallback, object, IAsyncResult> beginConnect = (callback, state) =>
{
return BeginConnect(remoteEP, callback, state);
};
await Task.Factory.FromAsync(beginConnect, EndConnect, this).ConfigureAwait(false);
}
// When the socket is closed, the FromAsync logic will try to call EndAccept on the socket,
// but that will throw an ObjectDisposedException. Only catch the exception if due to cancellation.
catch (ObjectDisposedException) when (token.IsCancellationRequested)
{
// First check if the cancellation token caused the closing of the socket,
// then rethrow the exception if it did not.
token.ThrowIfCancellationRequested();
}
}
}
#endif
}
}
// 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.Net;
using System.Net.Sockets;
namespace Microsoft.Diagnostics.NETCore.Client
{
internal sealed class IpcTcpSocketEndPoint
{
public bool DualMode { get; }
public IPEndPoint EndPoint { get; }
public static bool IsTcpIpEndPoint(string endPoint)
{
bool result = true;
try
{
ParseTcpIpEndPoint(endPoint, out _, out _);
}
catch(Exception)
{
result = false;
}
return result;
}
public static string NormalizeTcpIpEndPoint(string endPoint)
{
ParseTcpIpEndPoint(endPoint, out string host, out int port);
return string.Format("{0}:{1}", host, port);
}
public IpcTcpSocketEndPoint(string endPoint)
{
ParseTcpIpEndPoint(endPoint, out string host, out int port);
EndPoint = CreateEndPoint(host, port);
DualMode = string.CompareOrdinal(host, "*") == 0;
}
public static implicit operator EndPoint(IpcTcpSocketEndPoint endPoint) => endPoint.EndPoint;
private static void ParseTcpIpEndPoint(string endPoint, out string host, out int port)
{
host = "";
port = -1;
bool usesWildcardHost = false;
string uriToParse= "";
if (endPoint.Contains("://"))
{
// Host can contain wildcard (*) that is a reserved charachter in URI's.
// Replace with dummy localhost representation just for parsing purpose.
if (endPoint.IndexOf("//*", StringComparison.Ordinal) != -1)
{
usesWildcardHost = true;
uriToParse = endPoint.Replace("//*", "//localhost");
}
else
{
uriToParse = endPoint;
}
}
try
{
if (!string.IsNullOrEmpty(uriToParse) && Uri.TryCreate(uriToParse, UriKind.RelativeOrAbsolute, out Uri uri))
{
if (string.Compare(uri.Scheme, Uri.UriSchemeNetTcp, StringComparison.OrdinalIgnoreCase) != 0 &&
string.Compare(uri.Scheme, "tcp", StringComparison.OrdinalIgnoreCase) != 0)
{
throw new ArgumentException(string.Format("Unsupported Uri schema, \"{0}\"", uri.Scheme));
}
host = usesWildcardHost ? "*" : uri.Host;
port = uri.IsDefaultPort ? 0 : uri.Port;
}
}
catch (InvalidOperationException)
{
}
if (string.IsNullOrEmpty(host) || port == -1)
{
string[] segments = endPoint.Split(':');
if (segments.Length > 2)
{
host = string.Join(":", segments, 0, segments.Length - 1);
port = int.Parse(segments[segments.Length - 1]);
}
else if (segments.Length == 2)
{
host = segments[0];
port = int.Parse(segments[1]);
}
if (string.CompareOrdinal(host, "*") != 0)
{
if (!IPAddress.TryParse(host, out _))
{
if (!Uri.TryCreate(Uri.UriSchemeNetTcp + "://" + host + ":" + port, UriKind.RelativeOrAbsolute, out _))
{
host = "";
port = -1;
}
}
}
}
if (string.IsNullOrEmpty(host) || port == -1)
{
throw new ArgumentException(string.Format("Could not parse {0} into host, port", endPoint));
}
}
private static IPEndPoint CreateEndPoint(string host, int port)
{
IPAddress ipAddress = null;
try
{
if (string.CompareOrdinal(host, "*") == 0)
{
if (Socket.OSSupportsIPv6)
{
ipAddress = IPAddress.IPv6Any;
}
else
{
ipAddress = IPAddress.Any;
}
}
else if (!IPAddress.TryParse(host, out ipAddress))
{
var hostEntry = Dns.GetHostEntry(host);
if (hostEntry.AddressList.Length > 0)
ipAddress = hostEntry.AddressList[0];
}
}
catch(Exception)
{
}
if (ipAddress == null)
throw new ArgumentException(string.Format("Could not resolve {0} into an IP address", host));
return new IPEndPoint(ipAddress, port);
}
}
}
// 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.Diagnostics;
using System.IO;
using System.IO.Pipes;
using System.Linq;
using System.Net.Sockets;
using System.Runtime.InteropServices;
using System.Security.Principal;
using System.Threading;
using System.Threading.Tasks;
namespace Microsoft.Diagnostics.NETCore.Client
{
internal abstract class IpcEndpoint
{
/// <summary>
/// Connects to the underlying IPC transport and opens a read/write-able Stream
/// </summary>
/// <param name="timeout">The amount of time to block attempting to connect</param>
/// <returns>A stream used for writing and reading data to and from the target .NET process</returns>
public abstract Stream Connect(TimeSpan timeout);
/// <summary>
/// Connects to the underlying IPC transport and opens a read/write-able Stream
/// </summary>
/// <param name="token">The token to monitor for cancellation requests.</param>
/// <returns>
/// A task that completes with a stream used for writing and reading data to and from the target .NET process.
/// </returns>
public abstract Task<Stream> ConnectAsync(CancellationToken token);
/// <summary>
/// Wait for an available diagnostic endpoint to the runtime instance.
/// </summary>
/// <param name="timeout">The amount of time to wait before cancelling the wait for the connection.</param>
public abstract void WaitForConnection(TimeSpan timeout);
/// <summary>
/// Wait for an available diagnostic endpoint to the runtime instance.
/// </summary>
/// <param name="token">The token to monitor for cancellation requests.</param>
/// <returns>
/// A task that completes when a diagnostic endpoint to the runtime instance becomes available.
/// </returns>
public abstract Task WaitForConnectionAsync(CancellationToken token);
}
internal class IpcEndpointHelper
{
public static Stream Connect(IpcEndpointConfig config, TimeSpan timeout)
{
if (config.Transport == IpcEndpointConfig.TransportType.NamedPipe)
{
var namedPipe = new NamedPipeClientStream(
".",
config.Address,
PipeDirection.InOut,
PipeOptions.None,
TokenImpersonationLevel.Impersonation);
namedPipe.Connect((int)timeout.TotalMilliseconds);
return namedPipe;
}
else if (config.Transport == IpcEndpointConfig.TransportType.UnixDomainSocket)
{
var socket = new IpcUnixDomainSocket();
socket.Connect(new IpcUnixDomainSocketEndPoint(config.Address), timeout);
return new ExposedSocketNetworkStream(socket, ownsSocket: true);
}
#if DIAGNOSTICS_RUNTIME
else if (config.Transport == IpcEndpointConfig.TransportType.TcpSocket)
{
var tcpClient = new TcpClient ();
var endPoint = new IpcTcpSocketEndPoint(config.Address);
tcpClient.Connect(endPoint.EndPoint);
return tcpClient.GetStream();
}
#endif
else
{
throw new ArgumentException($"Unsupported IpcEndpointConfig transport type {config.Transport}");
}
}
public static async Task<Stream> ConnectAsync(IpcEndpointConfig config, CancellationToken token)
{
if (config.Transport == IpcEndpointConfig.TransportType.NamedPipe)
{
var namedPipe = new NamedPipeClientStream(
".",
config.Address,
PipeDirection.InOut,
PipeOptions.Asynchronous,
TokenImpersonationLevel.Impersonation);
// Pass non-infinite timeout in order to cause internal connection algorithm
// to check the CancellationToken periodically. Otherwise, if the named pipe
// is waited using WaitNamedPipe with an infinite timeout, then the
// CancellationToken cannot be observed.
await namedPipe.ConnectAsync(int.MaxValue, token).ConfigureAwait(false);
return namedPipe;
}
else if (config.Transport == IpcEndpointConfig.TransportType.UnixDomainSocket)
{
var socket = new IpcUnixDomainSocket();
await socket.ConnectAsync(new IpcUnixDomainSocketEndPoint(config.Address), token).ConfigureAwait(false);
return new ExposedSocketNetworkStream(socket, ownsSocket: true);
}
else
{
throw new ArgumentException($"Unsupported IpcEndpointConfig transport type {config.Transport}");
}
}
}
internal class ServerIpcEndpoint : IpcEndpoint
{
private readonly Guid _runtimeId;
private readonly ReversedDiagnosticsServer _server;
public ServerIpcEndpoint(ReversedDiagnosticsServer server, Guid runtimeId)
{
_runtimeId = runtimeId;
_server = server;
}
/// <remarks>
/// This will block until the diagnostic stream is provided. This block can happen if
/// the stream is acquired previously and the runtime instance has not yet reconnected
/// to the reversed diagnostics server.
/// </remarks>
public override Stream Connect(TimeSpan timeout)
{
return _server.Connect(_runtimeId, timeout);
}
public override Task<Stream> ConnectAsync(CancellationToken token)
{
return _server.ConnectAsync(_runtimeId, token);
}
public override void WaitForConnection(TimeSpan timeout)
{
_server.WaitForConnection(_runtimeId, timeout);
}
public override Task WaitForConnectionAsync(CancellationToken token)
{
return _server.WaitForConnectionAsync(_runtimeId, token);
}
public override bool Equals(object obj)
{
return Equals(obj as ServerIpcEndpoint);
}
public bool Equals(ServerIpcEndpoint other)
{
return other != null && other._runtimeId == _runtimeId && other._server == _server;
}
public override int GetHashCode()
{
return _runtimeId.GetHashCode() ^ _server.GetHashCode();
}
}
internal class DiagnosticPortIpcEndpoint : IpcEndpoint
{
IpcEndpointConfig _config;
public DiagnosticPortIpcEndpoint(string diagnosticPort)
{
_config = IpcEndpointConfig.Parse(diagnosticPort);
}
public DiagnosticPortIpcEndpoint(IpcEndpointConfig config)
{
_config = config;
}
public override Stream Connect(TimeSpan timeout)
{
return IpcEndpointHelper.Connect(_config, timeout);
}
public override async Task<Stream> ConnectAsync(CancellationToken token)
{
return await IpcEndpointHelper.ConnectAsync(_config, token).ConfigureAwait(false);
}
public override void WaitForConnection(TimeSpan timeout)
{
using var _ = Connect(timeout);
}
public override async Task WaitForConnectionAsync(CancellationToken token)
{
using var _ = await ConnectAsync(token).ConfigureAwait(false);
}
public override bool Equals(object obj)
{
return Equals(obj as DiagnosticPortIpcEndpoint);
}
public bool Equals(DiagnosticPortIpcEndpoint other)
{
return other != null && other._config == _config;
}
public override int GetHashCode()
{
return _config.GetHashCode();
}
}
internal class PidIpcEndpoint : IpcEndpoint
{
public static string IpcRootPath { get; } = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? @"\\.\pipe\" : Path.GetTempPath();
public static string DiagnosticsPortPattern { get; } = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? @"^dotnet-diagnostic-(\d+)$" : @"^dotnet-diagnostic-(\d+)-(\d+)-socket$";
int _pid;
IpcEndpointConfig _config;
/// <summary>
/// Creates a reference to a .NET process's IPC Transport
/// using the default rules for a given pid
/// </summary>
/// <param name="pid">The pid of the target process</param>
/// <returns>A reference to the IPC Transport</returns>
public PidIpcEndpoint(int pid)
{
_pid = pid;
}
public override Stream Connect(TimeSpan timeout)
{
string address = GetDefaultAddress();
_config = IpcEndpointConfig.Parse(address + ",connect");
return IpcEndpointHelper.Connect(_config, timeout);
}
public override async Task<Stream> ConnectAsync(CancellationToken token)
{
string address = GetDefaultAddress();
_config = IpcEndpointConfig.Parse(address + ",connect");
return await IpcEndpointHelper.ConnectAsync(_config, token).ConfigureAwait(false);
}
public override void WaitForConnection(TimeSpan timeout)
{
using var _ = Connect(timeout);
}
public override async Task WaitForConnectionAsync(CancellationToken token)
{
using var _ = await ConnectAsync(token).ConfigureAwait(false);
}
private string GetDefaultAddress()
{
try
{
var process = Process.GetProcessById(_pid);
}
catch (ArgumentException)
{
throw new ServerNotAvailableException($"Process {_pid} is not running.");
}
catch (InvalidOperationException)
{
throw new ServerNotAvailableException($"Process {_pid} seems to be elevated.");
}
if (!TryGetDefaultAddress(_pid, out string transportName))
{
throw new ServerNotAvailableException($"Process {_pid} not running compatible .NET runtime.");
}
return transportName;
}
private static bool TryGetDefaultAddress(int pid, out string defaultAddress)
{
defaultAddress = null;
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
defaultAddress = $"dotnet-diagnostic-{pid}";
}
else
{
try
{
defaultAddress = Directory.GetFiles(IpcRootPath, $"dotnet-diagnostic-{pid}-*-socket") // Try best match.
.OrderByDescending(f => new FileInfo(f).LastWriteTime)
.FirstOrDefault();
}
catch (InvalidOperationException)
{
}
}
return !string.IsNullOrEmpty(defaultAddress);
}
public override bool Equals(object obj)
{
return Equals(obj as PidIpcEndpoint);
}
public bool Equals(PidIpcEndpoint other)
{
return other != null && other._pid == _pid;
}
public override int GetHashCode()
{
return _pid.GetHashCode();
}
}
}
\ No newline at end of file
// 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.IO;
using System.Net;
using System.Net.Sockets;
namespace Microsoft.Diagnostics.NETCore.Client
{
internal sealed class IpcUnixDomainSocket : IpcSocket
{
private bool _ownsSocketFile;
private string _path;
internal IpcUnixDomainSocket()
: base(AddressFamily.Unix, SocketType.Stream, ProtocolType.Unspecified)
{
}
public void Bind(IpcUnixDomainSocketEndPoint localEP)
{
base.Bind(localEP);
_path = localEP.Path;
_ownsSocketFile = true;
}
public override void Connect(EndPoint localEP, TimeSpan timeout)
{
base.Connect(localEP, timeout);
_ownsSocketFile = false;
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
if (_ownsSocketFile && !string.IsNullOrEmpty(_path) && File.Exists(_path))
{
File.Delete(_path);
}
}
base.Dispose(disposing);
}
}
}
// 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.IO;
using System.Net;
using System.Net.Sockets;
namespace Microsoft.Diagnostics.NETCore.Client
{
internal sealed class IpcUnixDomainSocketEndPoint
{
public string Path { get; }
public EndPoint EndPoint { get; }
public IpcUnixDomainSocketEndPoint(string endPoint)
{
Path = endPoint;
EndPoint = CreateEndPoint(endPoint);
}
public static implicit operator EndPoint(IpcUnixDomainSocketEndPoint endPoint) => endPoint.EndPoint;
private static EndPoint CreateEndPoint(string endPoint)
{
#if NETCOREAPP
return new UnixDomainSocketEndPoint(endPoint);
#elif NETSTANDARD2_0
// UnixDomainSocketEndPoint is not part of .NET Standard 2.0
var type = typeof(Socket).Assembly.GetType("System.Net.Sockets.UnixDomainSocketEndPoint")
?? Type.GetType("System.Net.Sockets.UnixDomainSocketEndPoint, System.Core");
if (type == null)
{
throw new PlatformNotSupportedException("Current process is not running a compatible .NET runtime.");
}
var ctor = type.GetConstructor(new[] { typeof(string) });
return (EndPoint)ctor.Invoke(new object[] { endPoint });
#endif
}
}
}
// 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.IO;
using System.Threading;
using System.Threading.Tasks;
namespace Microsoft.Diagnostics.NETCore.Client
{
internal class ProcessEnvironmentHelper
{
private const int CopyBufferSize = (16 << 10) /* 16KiB */;
private ProcessEnvironmentHelper() {}
public static ProcessEnvironmentHelper Parse(byte[] payload)
{
ProcessEnvironmentHelper helper = new ProcessEnvironmentHelper();
helper.ExpectedSizeInBytes = BitConverter.ToUInt32(payload, 0);
helper.Future = BitConverter.ToUInt16(payload, 4);
return helper;
}
public Dictionary<string, string> ReadEnvironment(Stream continuation)
{
using var memoryStream = new MemoryStream();
continuation.CopyTo(memoryStream, CopyBufferSize);
return ReadEnvironmentCore(memoryStream);
}
public async Task<Dictionary<string,string>> ReadEnvironmentAsync(Stream continuation, CancellationToken token = default(CancellationToken))
{
using var memoryStream = new MemoryStream();
await continuation.CopyToAsync(memoryStream, CopyBufferSize, token);
return ReadEnvironmentCore(memoryStream);
}
private Dictionary<string, string> ReadEnvironmentCore(MemoryStream stream)
{
stream.Seek(0, SeekOrigin.Begin);
byte[] envBlock = stream.ToArray();
if (envBlock.Length != (long)ExpectedSizeInBytes)
throw new ApplicationException($"ProcessEnvironment continuation length did not match expected length. Expected: {ExpectedSizeInBytes} bytes, Received: {envBlock.Length} bytes");
var env = new Dictionary<string, string>();
int cursor = 0;
UInt32 nElements = BitConverter.ToUInt32(envBlock, cursor);
cursor += sizeof(UInt32);
while (cursor < envBlock.Length)
{
string pair = IpcHelpers.ReadString(envBlock, ref cursor);
int equalsIdx = pair.IndexOf('=');
env[pair.Substring(0, equalsIdx)] = equalsIdx != pair.Length - 1 ? pair.Substring(equalsIdx + 1) : "";
}
return env;
}
private UInt32 ExpectedSizeInBytes { get; set; }
private UInt16 Future { get; set; }
}
}
\ No newline at end of file
// 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.Text;
namespace Microsoft.Diagnostics.NETCore.Client
{
/**
* ==ProcessInfo==
* The response payload to issuing the GetProcessInfo command.
*
* 8 bytes - PID (little-endian)
* 16 bytes - CLR Runtime Instance Cookie (little-endian)
* # bytes - Command line string length and data
* # bytes - Operating system string length and data
* # bytes - Process architecture string length and data
*
* ==ProcessInfo2==
* The response payload to issuing the GetProcessInfo2 command.
*
* 8 bytes - PID (little-endian)
* 16 bytes - CLR Runtime Instance Cookie (little-endian)
* # bytes - Command line string length and data
* # bytes - Operating system string length and data
* # bytes - Process architecture string length and data
* # bytes - Managed entrypoint assembly name
* # bytes - CLR product version string (may include prerelease labels)
*
*
* The "string length and data" fields are variable length:
* 4 bytes - Length of string data in UTF-16 characters
* (2 * length) bytes - The data of the string encoded using Unicode
* (includes null terminating character)
*/
internal class ProcessInfo
{
private static readonly int GuidSizeInBytes = 16;
/// <summary>
/// Parses a ProcessInfo payload.
/// </summary>
internal static ProcessInfo ParseV1(byte[] payload)
{
int index = 0;
return ParseCommon(payload, ref index);
}
/// <summary>
/// Parses a ProcessInfo2 payload.
/// </summary>
internal static ProcessInfo ParseV2(byte[] payload)
{
int index = 0;
ProcessInfo processInfo = ParseCommon(payload, ref index);
processInfo.ManagedEntrypointAssemblyName = IpcHelpers.ReadString(payload, ref index);
processInfo.ClrProductVersionString = IpcHelpers.ReadString(payload, ref index);
return processInfo;
}
private static ProcessInfo ParseCommon(byte[] payload, ref int index)
{
ProcessInfo processInfo = new ProcessInfo();
processInfo.ProcessId = BitConverter.ToUInt64(payload, index);
index += sizeof(UInt64);
byte[] cookieBuffer = new byte[GuidSizeInBytes];
Array.Copy(payload, index, cookieBuffer, 0, GuidSizeInBytes);
processInfo.RuntimeInstanceCookie = new Guid(cookieBuffer);
index += GuidSizeInBytes;
processInfo.CommandLine = IpcHelpers.ReadString(payload, ref index);
processInfo.OperatingSystem = IpcHelpers.ReadString(payload, ref index);
processInfo.ProcessArchitecture = IpcHelpers.ReadString(payload, ref index);
return processInfo;
}
public UInt64 ProcessId { get; private set; }
public Guid RuntimeInstanceCookie { get; private set; }
public string CommandLine { get; private set; }
public string OperatingSystem { get; private set; }
public string ProcessArchitecture { get; private set; }
public string ManagedEntrypointAssemblyName { get; private set; }
public string ClrProductVersionString { get; private set; }
}
}
\ No newline at end of file
using System;
using System.Collections.Generic;
using System.Text;
namespace Microsoft.Diagnostics.NETCore.Client
{
internal static class IpcHelpers
{
public static string ReadString(byte[] buffer, ref int index)
{
// Length of the string of UTF-16 characters
int length = (int)BitConverter.ToUInt32(buffer, index);
index += sizeof(UInt32);
int size = (int)length * sizeof(char);
// The string contains an ending null character; remove it before returning the value
string value = Encoding.Unicode.GetString(buffer, index, size).Substring(0, length - 1);
index += size;
return value;
}
}
}
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Library</OutputType>
<TargetFrameworks>netstandard2.0;netcoreapp3.1</TargetFrameworks>
<RootNamespace>Microsoft.Diagnostics.NETCore.Client</RootNamespace>
<Description>.NET Core Diagnostics Client Library</Description>
<VersionPrefix>0.2.0</VersionPrefix>
<IsPackable>true</IsPackable>
<PackageTags>Diagnostic</PackageTags>
<PackageReleaseNotes>$(Description)</PackageReleaseNotes>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<IncludeSymbols>true</IncludeSymbols>
<IsShipping>true</IsShipping>
</PropertyGroup>
<PropertyGroup Condition="'$(GitHubRepositoryName)' == 'runtime'">
<DefineConstants>$(DefineConstants);DIAGNOSTICS_RUNTIME</DefineConstants>
<NoWarn>CS1591,CS8073,CS0162</NoWarn>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" Version="$(MicrosoftBclAsyncInterfacesVersion)" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="$(MicrosoftExtensionsLoggingVersion)" />
</ItemGroup>
<ItemGroup>
<InternalsVisibleTo Include="dotnet-counters" />
<InternalsVisibleTo Include="dotnet-dsrouter" />
<InternalsVisibleTo Include="dotnet-monitor" />
<InternalsVisibleTo Include="dotnet-trace" />
<InternalsVisibleTo Include="Microsoft.Diagnostics.Monitoring" />
<InternalsVisibleTo Include="Microsoft.Diagnostics.Monitoring.EventPipe" />
<!-- Temporary until Diagnostic Apis are finalized-->
<InternalsVisibleTo Include="Microsoft.Diagnostics.Monitoring.WebApi" />
<InternalsVisibleTo Include="Microsoft.Diagnostics.NETCore.Client.UnitTests" />
</ItemGroup>
<ItemGroup>
<Compile Condition="'$(GitHubRepositoryName)' == 'runtime'" Include="**/*.cs" />
</ItemGroup>
</Project>
// 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.Runtime.InteropServices;
using Microsoft.Win32.SafeHandles;
namespace Microsoft.Diagnostics.NETCore.Client
{
internal class NativeMethods
{
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool PeekNamedPipe(
SafePipeHandle hNamedPipe,
byte[] lpBuffer,
int bufferSize,
IntPtr lpBytesRead,
IntPtr lpTotalBytesAvail,
IntPtr lpBytesLeftThisMessage
);
}
}
......@@ -6,10 +6,17 @@
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<IsTestProject>false</IsTestProject>
</PropertyGroup>
<PropertyGroup Condition="'$(GitHubRepositoryName)' == 'runtime'">
<DefineConstants>$(DefineConstants);DIAGNOSTICS_RUNTIME</DefineConstants>
<NoWarn>CS1591,CS8073,CS0162</NoWarn>
</PropertyGroup>
<ItemGroup>
<Compile Include="IpcTraceTest.cs" />
<Compile Include="StreamProxy.cs" />
<Compile Include="Reverse.cs" />
<Compile Include="IpcUtils.cs" />
<Compile Include="Reverse.cs" />
<Compile Include="StreamProxy.cs" />
<ProjectReference Include="Microsoft.Diagnostics.NETCore.Client/Microsoft.Diagnostics.NETCore.Client.csproj" />
</ItemGroup>
</Project>
......@@ -10,5 +10,6 @@
<ItemGroup>
<Compile Include="$(MSBuildProjectName).cs" />
<ProjectReference Include="../common/common.csproj" />
<ProjectReference Include="../common/Microsoft.Diagnostics.NETCore.Client/Microsoft.Diagnostics.NETCore.Client.csproj" />
</ItemGroup>
</Project>
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册