This commit is contained in:
Joshua "Yoshi" Askharoun
2025-12-03 14:03:19 -06:00
parent 27ee87c8f0
commit 4a1381ac56
5 changed files with 171 additions and 51 deletions
@@ -45,12 +45,6 @@ public class IrisDebugAdapterClient : IDebuggerClient, IRemoteDebuggerState, IDi
public event Action<string> DispatcherStep;
public event Action<IDebuggerState, object> Connected;
public IrisDebugAdapterClient(Stream inputStream, Stream outputStream)
{
_inputStream = inputStream;
_outputStream = outputStream;
}
public IrisDebugAdapterClient(string connectionString)
{
ConnectionString = connectionString;
@@ -58,20 +52,14 @@ public class IrisDebugAdapterClient : IDebuggerClient, IRemoteDebuggerState, IDi
public async Task StartAsync()
{
if (ConnectionString is not null)
{
ConnectionStringHelper.ConnectToString(ConnectionString, out _inputStream, out _outputStream);
}
_debugAdapter = await DebugAdapterClient.From(options =>
{
options
.WithInput(_inputStream)
.WithOutput(_outputStream)
.OnInitialize((server, _, cancellationToken) =>
.OnInitialize(async (server, _, cancellationToken) =>
{
var __ = server.RequestDebugAdapterInitialize(new());
return Task.CompletedTask;
//await server.RequestDebugAdapterInitialize(new(), default);
})
.OnInitialized((_, _, response, _) =>
{
@@ -90,6 +78,9 @@ public class IrisDebugAdapterClient : IDebuggerClient, IRemoteDebuggerState, IDi
})
;
}).ConfigureAwait(false);
var tcs = new TaskCompletionSource<object>();
await tcs.Task;
}
public void Dispose()
@@ -143,6 +134,11 @@ public class IrisDebugAdapterClient : IDebuggerClient, IRemoteDebuggerState, IDi
public void Start()
{
if (ConnectionString is not null)
{
ConnectionStringHelper.ConnectToString(ConnectionString, out _inputStream, out _outputStream);
}
System.Threading.Thread clientThread = new(() =>
{
_ = StartAsync();
@@ -65,8 +65,6 @@ public class IrisDebugAdapterServer : IDebuggerServer, IRemoteDebuggerState, IDi
/// <returns>A task that completes when the server is ready.</returns>
public async Task StartAsync()
{
ConnectionStringHelper.CreateFromString(ConnectionString, out _inputStream, out _outputStream);
Server = DebugAdapterServer.Create(options =>
{
// We need to let the PowerShell Context Service know that we are in a debug session
@@ -103,29 +101,13 @@ public class IrisDebugAdapterServer : IDebuggerServer, IRemoteDebuggerState, IDi
// https://microsoft.github.io/debug-adapter-protocol/specification#Requests_Initialize
.OnInitialize(async (server, _, cancellationToken) =>
{
// Start the host if not already started, and enable debug mode (required
// for remote debugging).
//
// TODO: We might need to fill in HostStartOptions here.
//_startedPses = !await _psesHost.TryStartAsync(new HostStartOptions(), cancellationToken).ConfigureAwait(false);
//_psesHost.DebugContext.EnableDebugMode();
// Clear any existing breakpoints before proceeding.
//BreakpointService breakpointService = server.GetService<BreakpointService>();
//await breakpointService.RemoveAllBreakpointsAsync().ConfigureAwait(false);
System.Diagnostics.Debug.WriteLine("SERVER: OnInitialize called");
Console.WriteLine("SERVER: OnInitialize called");
})
// The OnInitialized delegate gets run right before the server responds to the _Initialize_ request:
// https://microsoft.github.io/debug-adapter-protocol/specification#Requests_Initialize
.OnInitialized((_, _, response, _) =>
{
//response.SupportsConditionalBreakpoints = true;
//response.SupportsConfigurationDoneRequest = true;
//response.SupportsFunctionBreakpoints = true;
//response.SupportsHitConditionalBreakpoints = true;
//response.SupportsLogPoints = true;
//response.SupportsSetVariable = true;
//response.SupportsDelayedStackTraceLoading = true;
System.Diagnostics.Debug.WriteLine("SERVER: OnInitialized called");
Console.WriteLine("SERVER: OnInitialized called");
return Task.CompletedTask;
})
;
@@ -134,6 +116,8 @@ public class IrisDebugAdapterServer : IDebuggerServer, IRemoteDebuggerState, IDi
await Server.Initialize(default).ConfigureAwait(false);
Connected?.Invoke(this, EventArgs.Empty);
await WaitForShutdownAsync().ConfigureAwait(false);
}
public void Dispose()
@@ -179,6 +163,8 @@ public class IrisDebugAdapterServer : IDebuggerServer, IRemoteDebuggerState, IDi
public void Start()
{
ConnectionStringHelper.CreateFromString(ConnectionString, out _inputStream, out _outputStream);
Thread serverThread = new(() =>
{
_ = StartAsync();
@@ -1,33 +1,81 @@
using System.IO;
using System;
using System.IO;
using System.IO.Pipes;
using System.Text;
namespace Microsoft.Iris.DebugAdapter;
public static class ConnectionStringHelper
{
private const string PIPE_PREFIX = @"\\.\pipe\";
public static void CreateFromString(string connectionString, out Stream input, out Stream output)
{
// TODO: Support other transports
var pipeName = connectionString.StartsWith(PIPE_PREFIX)
? connectionString
: NamedPipeUtils.GenerateValidNamedPipeName();
if (NamedPipeUtils.TryGetPipeName(connectionString, out var pipeName))
{
var pipeToClient = NamedPipeUtils.CreateNamedPipe(pipeName + "_ToClient", PipeDirection.Out);
var pipeFromClient = NamedPipeUtils.CreateNamedPipe(pipeName + "_FromClient", PipeDirection.In);
var pipe = NamedPipeUtils.CreateNamedPipe(pipeName, PipeDirection.InOut);
pipe.WaitForConnection();
pipeToClient.WaitForConnection();
pipeFromClient.WaitForConnection();
output = input = pipe;
var loggingInputStream = new LoggingStream(pipeFromClient, LoggingStream_OnRead, LoggingStream_OnWrite);
var loggingOutputStream = new LoggingStream(pipeToClient, LoggingStream_OnRead, LoggingStream_OnWrite);
input = loggingInputStream;
output = loggingOutputStream;
return;
}
else if (Uri.TryCreate(connectionString, UriKind.Absolute, out var connectionUri))
{
if (connectionUri.Scheme == "tcp")
{
}
}
throw new ArgumentException("Invalid connection string", nameof(connectionString));
}
private static void LoggingStream_OnWrite(object? sender, ArraySegment<byte> obj)
{
try
{
var str = Encoding.UTF8.GetString(obj.Array!, obj.Offset, obj.Count);
System.Diagnostics.Debug.WriteLine($"Sent `{str}`");
}
catch
{
System.Diagnostics.Debug.WriteLine($"Sent {obj.Count} bytes");
}
}
private static void LoggingStream_OnRead(object? sender, ArraySegment<byte> obj)
{
try
{
var str = Encoding.UTF8.GetString(obj.Array!, obj.Offset, obj.Count);
System.Diagnostics.Debug.WriteLine($"Received `{str}`");
}
catch
{
System.Diagnostics.Debug.WriteLine($"Received {obj.Count} bytes");
}
}
public static void ConnectToString(string connectionString, out Stream input, out Stream output)
{
if (!connectionString.StartsWith(PIPE_PREFIX))
throw new System.NotSupportedException();
var pipeName = NamedPipeUtils.GetPipeName(connectionString);
var pipe = new NamedPipeClientStream(connectionString);
pipe.Connect();
var pipeToServer = new NamedPipeClientStream(".", pipeName + "_FromClient", PipeDirection.Out, PipeOptions.Asynchronous);
var pipeFromServer = new NamedPipeClientStream(".", pipeName + "_ToClient", PipeDirection.In, PipeOptions.Asynchronous);
output = input = pipe;
pipeToServer.Connect();
pipeFromServer.Connect();
var loggingInputStream = new LoggingStream(pipeFromServer, LoggingStream_OnRead, LoggingStream_OnWrite);
var loggingOutputStream = new LoggingStream(pipeToServer, LoggingStream_OnRead, LoggingStream_OnWrite);
input = loggingInputStream;
output = loggingOutputStream;
}
}
@@ -0,0 +1,67 @@
using System;
using System.IO;
using System.Threading.Tasks;
namespace Microsoft.Iris.DebugAdapter;
public class LoggingStream(Stream stream) : Stream
{
public LoggingStream(Stream stream, EventHandler<ArraySegment<byte>>? onRead, EventHandler<ArraySegment<byte>>? onWrite)
: this(stream)
{
OnRead += onRead;
OnWrite += onWrite;
}
public Stream InnerStream { get; } = stream;
public event EventHandler<ArraySegment<byte>>? OnRead;
public event EventHandler<ArraySegment<byte>>? OnWrite;
public override bool CanRead => InnerStream.CanRead;
public override bool CanSeek => InnerStream.CanSeek;
public override bool CanWrite => InnerStream.CanWrite;
public override long Length => InnerStream.Length;
public override long Position { get => InnerStream.Position; set => InnerStream.Position = value; }
public override void Flush()
{
InnerStream.Flush();
}
public override int Read(byte[] buffer, int offset, int count)
{
var length = InnerStream.Read(buffer, offset, count);
OnRead?.Invoke(this, new(buffer, offset, length));
return length;
}
public override long Seek(long offset, SeekOrigin origin)
{
return InnerStream.Seek(offset, origin);
}
public override void SetLength(long value)
{
InnerStream.SetLength(value);
}
public override void Write(byte[] buffer, int offset, int count)
{
OnWrite?.Invoke(this, new(buffer, offset, count));
InnerStream.Write(buffer, offset, count);
}
protected override void Dispose(bool disposing)
{
InnerStream.Dispose();
}
#if NET6_0_OR_GREATER
public override ValueTask DisposeAsync() => InnerStream.DisposeAsync();
#endif
}
@@ -4,6 +4,8 @@
using System.Collections.Generic;
using System.IO;
using System.IO.Pipes;
using System.Diagnostics.CodeAnalysis;
#if !NET
@@ -20,6 +22,8 @@ namespace Microsoft.Iris.DebugAdapter;
/// </summary>
public static class NamedPipeUtils
{
private const string PIPE_PREFIX = @"\\.\pipe\";
#if !NET
// .NET Framework requires the buffer size to be specified
private const int PipeBufferSize = 1024;
@@ -35,7 +39,7 @@ public static class NamedPipeUtils
direction: pipeDirection,
maxNumberOfServerInstances: 1,
transmissionMode: PipeTransmissionMode.Byte,
options: PipeOptions.CurrentUserOnly | PipeOptions.Asynchronous);
options: PipeOptions.Asynchronous);
#else
// In .NET Framework, we must manually ACL the named pipes we create
@@ -116,6 +120,25 @@ public static class NamedPipeUtils
throw new IOException("Unable to create named pipe; no available names");
}
public static bool TryGetPipeName(string pipePath, [NotNullWhen(true)] out string? pipeName)
{
if (!IsPipeNameValid(pipePath))
{
pipeName = null;
return false;
}
pipeName = pipePath[PIPE_PREFIX.Length..];
return true;
}
public static string GetPipeName(string pipePath)
{
if (!TryGetPipeName(pipePath, out var pipeName))
throw new System.ArgumentException(nameof(pipePath));
return pipeName;
}
/// <summary>
/// Validate that a named pipe file name is a legitimate named pipe file name and is not already in use.
/// </summary>