mirror of
https://github.com/ZuneDev/ZuneUIXTools.git
synced 2026-07-27 13:11:59 -07:00
First pass at debug adapter support
This commit is contained in:
@@ -0,0 +1,84 @@
|
||||
using Microsoft.Iris.Debug;
|
||||
using Microsoft.Iris.Debug.Data;
|
||||
using OmniSharp.Extensions.DebugAdapter.Protocol.Events;
|
||||
using System;
|
||||
|
||||
namespace Microsoft.Iris.DebugAdapter.Server;
|
||||
|
||||
public class DapDebuggerServer : IDebuggerServer, IRemoteDebuggerState
|
||||
{
|
||||
private readonly IrisDebugServerOptions _debugAdapterOptions;
|
||||
private IrisDebugAdapterServer? _debugAdapter;
|
||||
private InterpreterCommand _debuggerCommand;
|
||||
|
||||
public InterpreterCommand DebuggerCommand
|
||||
{
|
||||
get => _debuggerCommand;
|
||||
set
|
||||
{
|
||||
_debuggerCommand = value;
|
||||
|
||||
if (_debugAdapter?.Server is null)
|
||||
return;
|
||||
|
||||
if (value is InterpreterCommand.Continue)
|
||||
{
|
||||
_debugAdapter.Server.SendContinued(new()
|
||||
{
|
||||
ThreadId = Environment.CurrentManagedThreadId,
|
||||
});
|
||||
}
|
||||
else if (value is InterpreterCommand.Break)
|
||||
{
|
||||
_debugAdapter.Server.SendStopped(new()
|
||||
{
|
||||
Reason = new("unknown"),
|
||||
ThreadId = Environment.CurrentManagedThreadId,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public string ConnectionString { get; }
|
||||
|
||||
public event Action<IDebuggerState, object>? Connected;
|
||||
|
||||
public DapDebuggerServer(string connectionString, IrisDebugServerOptions options)
|
||||
{
|
||||
ConnectionString = connectionString;
|
||||
|
||||
_debugAdapterOptions = options;
|
||||
}
|
||||
|
||||
public void LogDispatcher(string message)
|
||||
{
|
||||
}
|
||||
|
||||
public void LogInterpreterDecode(object context, InterpreterInstruction instruction)
|
||||
{
|
||||
}
|
||||
|
||||
public void LogInterpreterExecute(object context, InterpreterEntry entry)
|
||||
{
|
||||
}
|
||||
|
||||
public MarkupLineNumberEntry[] OnLineNumberTableRequested(string uri)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public void Start()
|
||||
{
|
||||
ConnectionStringHelper.CreateFromString(ConnectionString, out var input, out var output);
|
||||
|
||||
_debugAdapter = new(input, output, _debugAdapterOptions);
|
||||
_debugAdapter.StartAsync().RunSynchronously();
|
||||
|
||||
Connected?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
public void WaitForContinue()
|
||||
{
|
||||
while (DebuggerCommand is InterpreterCommand.Break) ;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
using Microsoft.Iris.Debug.Symbols;
|
||||
using OmniSharp.Extensions.DebugAdapter.Protocol.Requests;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using DapBreakpoint = OmniSharp.Extensions.DebugAdapter.Protocol.Models.Breakpoint;
|
||||
using IrisBreakpoint = Microsoft.Iris.Debug.Data.Breakpoint;
|
||||
|
||||
namespace Microsoft.Iris.DebugAdapter.Server.Handlers;
|
||||
|
||||
internal class BreakpointHandler(DebugSymbolResolver symbolResolver)
|
||||
: ISetInstructionBreakpointsHandler, ISetBreakpointsHandler
|
||||
{
|
||||
public Task<SetInstructionBreakpointsResponse> Handle(SetInstructionBreakpointsArguments request, CancellationToken cancellationToken)
|
||||
{
|
||||
List<DapBreakpoint> dapBreakpoints = [];
|
||||
|
||||
foreach (var requestedBreakpoint in request.Breakpoints)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
SetInstructionBreakpointsResponse response = new()
|
||||
{
|
||||
Breakpoints = dapBreakpoints
|
||||
};
|
||||
return Task.FromResult(response);
|
||||
}
|
||||
|
||||
public Task<SetBreakpointsResponse> Handle(SetBreakpointsArguments request, CancellationToken cancellationToken)
|
||||
{
|
||||
// Debug symbols are required for setting source (line:column) breakpoints
|
||||
var fsym = symbolResolver?.GetForFile(request.Source.Path);
|
||||
if (fsym is null)
|
||||
return Task.FromResult(new SetBreakpointsResponse());
|
||||
|
||||
List<DapBreakpoint> dapBreakpoints = [];
|
||||
|
||||
// TODO: Clear existing breakpoints for file
|
||||
|
||||
foreach (var requestedBreakpoint in request.Breakpoints ?? [])
|
||||
{
|
||||
SourceMap.Entry location;
|
||||
|
||||
if (requestedBreakpoint.Column is not null)
|
||||
{
|
||||
SourcePosition position = new(requestedBreakpoint.Line, requestedBreakpoint.Column.Value);
|
||||
location = fsym.SourceMap.GetLocationFromPosition(position);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Get first location that contains this line
|
||||
location = fsym.SourceMap.GetLocationFromLine(requestedBreakpoint.Line);
|
||||
}
|
||||
|
||||
IrisBreakpoint irisBreakpoint = new(fsym.CompiledFileName, location.Offset);
|
||||
Application.DebugSettings.Breakpoints.Add(irisBreakpoint);
|
||||
|
||||
DapBreakpoint dapBreakpoint = new()
|
||||
{
|
||||
Line = location.Span.Start.Line,
|
||||
Column = location.Span.Start.Column,
|
||||
EndLine = location.Span.End.Line,
|
||||
EndColumn = location.Span.End.Column,
|
||||
};
|
||||
dapBreakpoints.Add(dapBreakpoint);
|
||||
}
|
||||
|
||||
SetBreakpointsResponse response = new()
|
||||
{
|
||||
Breakpoints = dapBreakpoints
|
||||
};
|
||||
return Task.FromResult(response);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Iris.Debug;
|
||||
using OmniSharp.Extensions.DebugAdapter.Protocol.Requests;
|
||||
|
||||
namespace Microsoft.Iris.DebugAdapter.Server.Handlers;
|
||||
|
||||
internal class ContinueHandler : ContinueHandlerBase
|
||||
{
|
||||
private readonly IDebuggerServer _debugService;
|
||||
|
||||
public ContinueHandler(IDebuggerServer debugService) => _debugService = debugService;
|
||||
|
||||
public override Task<ContinueResponse> Handle(ContinueArguments request, CancellationToken cancellationToken)
|
||||
{
|
||||
_debugService.DebuggerCommand = Debug.Data.InterpreterCommand.Continue;
|
||||
return Task.FromResult(new ContinueResponse());
|
||||
}
|
||||
}
|
||||
|
||||
internal class NextHandler : NextHandlerBase
|
||||
{
|
||||
private readonly IDebuggerServer _debugService;
|
||||
|
||||
public NextHandler(IDebuggerServer debugService) => _debugService = debugService;
|
||||
|
||||
public override Task<NextResponse> Handle(NextArguments request, CancellationToken cancellationToken)
|
||||
{
|
||||
_debugService.DebuggerCommand = Debug.Data.InterpreterCommand.Step;
|
||||
return Task.FromResult(new NextResponse());
|
||||
}
|
||||
}
|
||||
|
||||
internal class PauseHandler : PauseHandlerBase
|
||||
{
|
||||
private readonly IDebuggerServer _debugService;
|
||||
|
||||
public PauseHandler(IDebuggerServer debugService) => _debugService = debugService;
|
||||
|
||||
public override Task<PauseResponse> Handle(PauseArguments request, CancellationToken cancellationToken)
|
||||
{
|
||||
_debugService.DebuggerCommand = Debug.Data.InterpreterCommand.Break;
|
||||
return Task.FromResult(new PauseResponse());
|
||||
}
|
||||
}
|
||||
|
||||
internal class StepInHandler : StepInHandlerBase
|
||||
{
|
||||
private readonly IDebuggerServer _debugService;
|
||||
|
||||
public StepInHandler(IDebuggerServer debugService) => _debugService = debugService;
|
||||
|
||||
public override Task<StepInResponse> Handle(StepInArguments request, CancellationToken cancellationToken)
|
||||
{
|
||||
_debugService.DebuggerCommand = Debug.Data.InterpreterCommand.Step;
|
||||
return Task.FromResult(new StepInResponse());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Iris.Debug;
|
||||
using Microsoft.Iris.DebugAdapter.Server.Handlers;
|
||||
using OmniSharp.Extensions.DebugAdapter.Server;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.Iris.DebugAdapter.Server;
|
||||
|
||||
public class IrisDebugAdapterServer : IDisposable
|
||||
{
|
||||
private readonly Stream _inputStream;
|
||||
private readonly Stream _outputStream;
|
||||
private readonly TaskCompletionSource<bool> _serverStopped;
|
||||
|
||||
public IrisDebugAdapterServer(
|
||||
Stream inputStream,
|
||||
Stream outputStream,
|
||||
IrisDebugServerOptions options)
|
||||
{
|
||||
_inputStream = inputStream;
|
||||
_outputStream = outputStream;
|
||||
_serverStopped = new();
|
||||
|
||||
Options = options;
|
||||
}
|
||||
|
||||
internal IrisDebugServerOptions Options { get; }
|
||||
|
||||
internal DebugAdapterServer? Server { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Start the debug server listening.
|
||||
/// </summary>
|
||||
/// <returns>A task that completes when the server is ready.</returns>
|
||||
public async Task StartAsync()
|
||||
{
|
||||
Server = await DebugAdapterServer.From(options =>
|
||||
{
|
||||
// We need to let the PowerShell Context Service know that we are in a debug session
|
||||
// so that it doesn't send the powerShell/startDebugger message.
|
||||
//_psesHost = ServiceProvider.GetService<PsesInternalHost>();
|
||||
//_psesHost.DebugContext.IsDebugServerActive = true;
|
||||
|
||||
options
|
||||
.WithInput(_inputStream)
|
||||
.WithOutput(_outputStream)
|
||||
.WithServices(serviceCollection =>
|
||||
serviceCollection
|
||||
.AddOptions()
|
||||
.AddIrisDebugServices(Options.SymbolDir, Options.SourceDir)
|
||||
)
|
||||
// TODO: Consider replacing all WithHandler with AddSingleton
|
||||
//.WithHandler<AttachHandler>()
|
||||
//.WithHandler<DisconnectHandler>()
|
||||
.WithHandler<BreakpointHandler>()
|
||||
//.WithHandler<ConfigurationDoneHandler>()
|
||||
//.WithHandler<ThreadsHandler>()
|
||||
//.WithHandler<StackTraceHandler>()
|
||||
//.WithHandler<ScopesHandler>()
|
||||
//.WithHandler<VariablesHandler>()
|
||||
.WithHandler<ContinueHandler>()
|
||||
.WithHandler<NextHandler>()
|
||||
.WithHandler<PauseHandler>()
|
||||
.WithHandler<StepInHandler>()
|
||||
//.WithHandler<StepOutHandler>()
|
||||
//.WithHandler<SourceHandler>()
|
||||
//.WithHandler<SetVariableHandler>()
|
||||
//.WithHandler<DebugEvaluateHandler>()
|
||||
// The OnInitialize delegate gets run when we first receive the _Initialize_ request:
|
||||
// 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);
|
||||
})
|
||||
// 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;
|
||||
|
||||
return Task.CompletedTask;
|
||||
})
|
||||
;
|
||||
}).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
// Note that the lifetime of the DebugContext is longer than the debug server;
|
||||
// It represents the debugger on the PowerShell process we're in,
|
||||
// while a new debug server is spun up for every debugging session
|
||||
//_psesHost.DebugContext.IsDebugServerActive = false;
|
||||
Server?.Dispose();
|
||||
_inputStream.Dispose();
|
||||
_outputStream.Dispose();
|
||||
_serverStopped.SetResult(true);
|
||||
}
|
||||
|
||||
public async Task WaitForShutdownAsync() => await _serverStopped.Task.ConfigureAwait(false);
|
||||
|
||||
public event EventHandler? SessionEnded;
|
||||
|
||||
internal void OnSessionEnded() => SessionEnded?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace Microsoft.Iris.DebugAdapter;
|
||||
|
||||
public class IrisDebugServerOptions
|
||||
{
|
||||
public string SymbolDir { get; set; }
|
||||
|
||||
public string? SourceDir { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Microsoft.Iris.DebugAdapter.Server;
|
||||
|
||||
internal static class ServiceCollectionExtensions
|
||||
{
|
||||
public static IServiceCollection AddIrisDebugServices(this IServiceCollection services, string symbolDir, string? sourceDir)
|
||||
{
|
||||
DebugSymbolResolver symbolResolver = new(symbolDir, sourceDir);
|
||||
|
||||
return services
|
||||
.AddSingleton(symbolResolver);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net461;net6.0;net6.0-windows10.0.22000</TargetFrameworks>
|
||||
<LangVersion>12</LangVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
<RootNamespace>Microsoft.Iris.DebugAdapter.Server</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="OmniSharp.Extensions.DebugAdapter.Server" Version="0.19.9" />
|
||||
<ProjectReference Include="..\..\libs\MicrosoftIris\UIX\UIX.csproj" />
|
||||
<ProjectReference Include="..\UIX.DebugAdapter.Shared\UIX.DebugAdapter.Shared.csproj" />
|
||||
|
||||
<PackageReference Include="PolySharp" Version="1.15.0">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
Reference in New Issue
Block a user