From 451b423ec30e948f859e6a360427f0c3206b89aa Mon Sep 17 00:00:00 2001 From: Nikita Romanov Date: Wed, 3 Jan 2024 17:27:26 +0300 Subject: [PATCH] Refactoring and Bug Fixes. Prepare for GCDump --- build.cake | 2 - package.json | 49 ++-- .../Agents/BaseLaunchAgent.cs | 24 ++ .../DebugLaunchAgent.cs} | 78 +++--- .../Agents/GCDumpLaunchAgent.cs | 39 +++ .../Agents/NoDebugLaunchAgent.cs | 70 +++++ .../TraceLaunchAgent.cs} | 103 ++++--- src/DotNet.Meteor.Debug/DebugSession.cs | 252 ++++++------------ .../Extensions/MonoExtensions.cs | 50 ++++ .../Extensions/ServerExtensions.cs | 23 +- src/DotNet.Meteor.Debug/Handles.cs | 24 +- .../LaunchConfiguration.cs | 52 ++-- .../ExternalTypeResolver.cs | 2 +- .../{Extensions => Plugins}/SymbolServer.cs | 2 +- .../Sdk/Profiling/Trace.cs | 11 - src/DotNet.Meteor.Debug/Session.cs | 3 +- src/DotNet.Meteor.Shared/ProjectExtensions.cs | 6 +- src/DotNet.Meteor.Shared/Sdk/MicrosoftSdk.cs | 5 +- src/DotNet.Meteor.Shared/TrimmableContext.cs | 1 + .../tasks/monoDebugConfigurationProvider.ts | 25 +- 20 files changed, 448 insertions(+), 373 deletions(-) create mode 100644 src/DotNet.Meteor.Debug/Agents/BaseLaunchAgent.cs rename src/DotNet.Meteor.Debug/{DebugLauncher.cs => Agents/DebugLaunchAgent.cs} (66%) create mode 100644 src/DotNet.Meteor.Debug/Agents/GCDumpLaunchAgent.cs create mode 100644 src/DotNet.Meteor.Debug/Agents/NoDebugLaunchAgent.cs rename src/DotNet.Meteor.Debug/{DebugProfiler.cs => Agents/TraceLaunchAgent.cs} (52%) create mode 100644 src/DotNet.Meteor.Debug/Extensions/MonoExtensions.cs rename src/DotNet.Meteor.Debug/{Extensions => Plugins}/ExternalTypeResolver.cs (98%) rename src/DotNet.Meteor.Debug/{Extensions => Plugins}/SymbolServer.cs (98%) diff --git a/build.cake b/build.cake index 25e174aa..15c09c71 100644 --- a/build.cake +++ b/build.cake @@ -27,8 +27,6 @@ var configuration = Argument("configuration", "debug"); Task("clean").Does(() => { EnsureDirectoryExists(ArtifactsDirectory); CleanDirectory(ExtensionStagingDirectory); - CleanDirectories(_Path.Combine(RootDirectory, "src", "**", "bin")); - CleanDirectories(_Path.Combine(RootDirectory, "src", "**", "obj")); }); /////////////////////////////////////////////////////////////////////////////// diff --git a/package.json b/package.json index add7436b..bedfad46 100644 --- a/package.json +++ b/package.json @@ -70,7 +70,10 @@ "target": { "type": "string", "default": "build", - "enum": [ "build", "publish" ], + "enum": [ + "build", + "publish" + ], "description": "%task.meteor.description.target%" }, "args": { @@ -88,7 +91,7 @@ { "name": "%task.meteor.problemMatcher%", "source": "%extension.displayName%", - "fileLocation":"relative", + "fileLocation": "relative", "owner": "%extension.displayName%", "pattern": { "regexp": "^(.*.xaml)\\((\\d+),(\\d+)\\):.*?(error|warning)\\s(.*?):\\s(.*)$", @@ -102,17 +105,17 @@ } ], "breakpoints": [ - { - "language": "csharp" + { + "language": "csharp" }, - { - "language": "fsharp" + { + "language": "fsharp" }, - { - "language": "razor" + { + "language": "razor" }, - { - "language": "aspnetcorerazor" + { + "language": "aspnetcorerazor" } ], "debuggers": [ @@ -154,17 +157,12 @@ "configurationAttributes": { "launch": { "properties": { - "device": { - "type": "string", - "description": "%debugger.meteor.description.device%" - }, - "runtime": { - "type": "string", - "description": "%debugger.meteor.description.runtime%" - }, "profilerMode": { "type": "string", - "enum": [ "trace" ], + "enum": [ + "trace", + "gcdump" + ], "description": "%debugger.meteor.description.profilerMode%" } } @@ -178,21 +176,25 @@ "dotnetMeteor.monoSdbDebuggerPortAndroid": { "type": "integer", "default": 10000, + "minimum": 0, "description": "%configuration.description.monoSdbDebuggerPortAndroid%" }, "dotnetMeteor.monoSdbDebuggerPortApple": { "type": "integer", "default": 55551, + "minimum": 0, "description": "%configuration.description.monoSdbDebuggerPortApple%" }, "dotnetMeteor.hotReloadHostPort": { "type": "integer", "default": 9988, + "minimum": 0, "description": "%configuration.description.hotReloadHostPort%" }, "dotnetMeteor.profilerHostPort": { "type": "integer", "default": 9000, + "minimum": 0, "description": "%configuration.description.profilerHostPort%" }, "dotnetMeteor.uninstallApplicationBeforeInstalling": { @@ -200,15 +202,13 @@ "default": true, "description": "%configuration.description.uninstallApplicationBeforeInstalling%" }, - - "dotnetMeteor.debuggerOptions.evaluationTimeout": { - "type":"integer", + "type": "integer", "default": 5000, "description": "%configuration.description.debuggerOptions.evaluationTimeout%" }, "dotnetMeteor.debuggerOptions.memberEvaluationTimeout": { - "type":"integer", + "type": "integer", "default": 5000, "description": "%configuration.description.debuggerOptions.memberEvaluationTimeout%" }, @@ -260,6 +260,7 @@ "dotnetMeteor.debuggerOptions.ellipsizedLength": { "type": "integer", "default": 260, + "minimum": 0, "description": "%configuration.description.debuggerOptions.ellipsizedLength%" }, "dotnetMeteor.debuggerOptions.integerDisplayFormat": { @@ -274,4 +275,4 @@ } } } -} +} \ No newline at end of file diff --git a/src/DotNet.Meteor.Debug/Agents/BaseLaunchAgent.cs b/src/DotNet.Meteor.Debug/Agents/BaseLaunchAgent.cs new file mode 100644 index 00000000..58c89262 --- /dev/null +++ b/src/DotNet.Meteor.Debug/Agents/BaseLaunchAgent.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using DotNet.Meteor.Processes; +using Mono.Debugging.Soft; + +namespace DotNet.Meteor.Debug; + +public abstract class BaseLaunchAgent { + public List Disposables { get; set; } + + protected BaseLaunchAgent() { + Disposables = new List(); + } + + public abstract void Connect(SoftDebuggerSession session, LaunchConfiguration configuration); + public abstract void Launch(LaunchConfiguration configuration, IProcessLogger logger); + + public virtual void Dispose() { + foreach(var disposable in Disposables) + disposable.Invoke(); + + Disposables.Clear(); + } +} \ No newline at end of file diff --git a/src/DotNet.Meteor.Debug/DebugLauncher.cs b/src/DotNet.Meteor.Debug/Agents/DebugLaunchAgent.cs similarity index 66% rename from src/DotNet.Meteor.Debug/DebugLauncher.cs rename to src/DotNet.Meteor.Debug/Agents/DebugLaunchAgent.cs index 4b78ebe9..1f428a83 100644 --- a/src/DotNet.Meteor.Debug/DebugLauncher.cs +++ b/src/DotNet.Meteor.Debug/Agents/DebugLaunchAgent.cs @@ -1,17 +1,17 @@ -using System; -using System.IO; -using DotNet.Meteor.Shared; -using DotNet.Meteor.Processes; +using System; using System.Net; -using Mono.Debugging.Soft; +using DotNet.Meteor.Debug.Extensions; using DotNet.Meteor.Debug.Sdb; using DotNet.Meteor.Debug.Sdk; +using DotNet.Meteor.Processes; +using DotNet.Meteor.Shared; using Microsoft.VisualStudio.Shared.VSCodeDebugProtocol; +using Mono.Debugging.Soft; namespace DotNet.Meteor.Debug; -public partial class DebugSession { - private void Connect(LaunchConfiguration configuration) { +public class DebugLaunchAgent : BaseLaunchAgent { + public override void Connect(SoftDebuggerSession session, LaunchConfiguration configuration) { SoftDebuggerStartArgs arguments = null; if (configuration.Device.IsAndroid || (configuration.Device.IsIPhone && !configuration.Device.IsEmulator)) @@ -21,22 +21,19 @@ private void Connect(LaunchConfiguration configuration) { ArgumentNullException.ThrowIfNull(arguments, "Debugger connection arguments not implemented."); session.Run(new SoftDebuggerStartInfo(arguments), configuration.DebuggerSessionOptions); - OnOutputDataReceived("Debugger is ready and listening..."); } - private void LaunchApplication(LaunchConfiguration configuration) { - DoSafe(() => { - if (configuration.Device.IsAndroid) - LaunchAndroid(configuration); - if (configuration.Device.IsIPhone) - LaunchAppleMobile(configuration); - if (configuration.Device.IsMacCatalyst) - LaunchMacCatalyst(configuration); - if (configuration.Device.IsWindows) - LaunchWindows(configuration); - }); + public override void Launch(LaunchConfiguration configuration, IProcessLogger logger) { + if (configuration.Device.IsAndroid) + LaunchAndroid(configuration, logger); + if (configuration.Device.IsIPhone) + LaunchAppleMobile(configuration, logger); + if (configuration.Device.IsMacCatalyst) + LaunchMacCatalyst(configuration, logger); + if (configuration.Device.IsWindows) + throw new NotSupportedException(); } - private void LaunchAppleMobile(LaunchConfiguration configuration) { + private void LaunchAppleMobile(LaunchConfiguration configuration, IProcessLogger logger) { // TODO: Implement Apple launching for Windows // if (RuntimeSystem.IsWindows) { // IDeviceTool.Installer(configuration.Device.Serial, configuration.OutputAssembly, this); @@ -47,18 +44,18 @@ private void LaunchAppleMobile(LaunchConfiguration configuration) { // } if (configuration.Device.IsEmulator) { - var debugProcess = MonoLaunch.DebugSim(configuration.Device.Serial, configuration.OutputAssembly, configuration.DebugPort, this); - disposables.Add(() => debugProcess.Terminate()); + var debugProcess = MonoLaunch.DebugSim(configuration.Device.Serial, configuration.OutputAssembly, configuration.DebugPort, logger); + Disposables.Add(() => debugProcess.Terminate()); } else { - var forwardingProcess = MonoLaunch.TcpTunnel(configuration.Device.Serial, configuration.DebugPort, this); - MonoLaunch.InstallDev(configuration.Device.Serial, configuration.OutputAssembly, this); + var forwardingProcess = MonoLaunch.TcpTunnel(configuration.Device.Serial, configuration.DebugPort, logger); + MonoLaunch.InstallDev(configuration.Device.Serial, configuration.OutputAssembly, logger); - var debugProcess = MonoLaunch.DebugDev(configuration.Device.Serial, configuration.OutputAssembly, configuration.DebugPort, this); - disposables.Add(() => debugProcess.Terminate()); - disposables.Add(() => forwardingProcess.Terminate()); + var debugProcess = MonoLaunch.DebugDev(configuration.Device.Serial, configuration.OutputAssembly, configuration.DebugPort, logger); + Disposables.Add(() => debugProcess.Terminate()); + Disposables.Add(() => forwardingProcess.Terminate()); } } - private void LaunchMacCatalyst(LaunchConfiguration configuration) { + private void LaunchMacCatalyst(LaunchConfiguration configuration, IProcessLogger logger) { var tool = AppleSdk.OpenTool(); var processRunner = new ProcessRunner(tool, new ProcessArgumentBuilder().AppendQuoted(configuration.OutputAssembly)); processRunner.SetEnvironmentVariable("__XAMARIN_DEBUG_HOSTS__", "127.0.0.1"); @@ -66,14 +63,9 @@ private void LaunchMacCatalyst(LaunchConfiguration configuration) { var result = processRunner.WaitForExit(); if (!result.Success) - throw new ProtocolException(string.Join(Environment.NewLine, result.StandardError)); - } - private void LaunchWindows(LaunchConfiguration configuration) { - var program = new FileInfo(configuration.OutputAssembly); - var process = new ProcessRunner(program, new ProcessArgumentBuilder(), this).Start(); - disposables.Add(() => process.Terminate()); + ServerExtensions.ThrowException(string.Join(Environment.NewLine, result.StandardError)); } - private void LaunchAndroid(LaunchConfiguration configuration) { + private void LaunchAndroid(LaunchConfiguration configuration, IProcessLogger logger) { var applicationId = configuration.GetApplicationName(); if (configuration.Device.IsEmulator) configuration.Device.Serial = AndroidEmulator.Run(configuration.Device.Name).Serial; @@ -84,18 +76,18 @@ private void LaunchAndroid(LaunchConfiguration configuration) { DeviceBridge.Forward(configuration.Device.Serial, configuration.DebugPort); if (configuration.UninstallApp) - DeviceBridge.Uninstall(configuration.Device.Serial, applicationId, this); + DeviceBridge.Uninstall(configuration.Device.Serial, applicationId, logger); - DeviceBridge.Install(configuration.Device.Serial, configuration.OutputAssembly, this); + DeviceBridge.Install(configuration.Device.Serial, configuration.OutputAssembly, logger); DeviceBridge.Shell(configuration.Device.Serial, "setprop", "debug.mono.connect", $"port={configuration.DebugPort}"); - DeviceBridge.Launch(configuration.Device.Serial, applicationId, this); + DeviceBridge.Launch(configuration.Device.Serial, applicationId, logger); DeviceBridge.Flush(configuration.Device.Serial); - var logcatFirstChannelProcess = DeviceBridge.Logcat(configuration.Device.Serial, "system,crash", "*:I", this); - var logcatSecondChannelProcess = DeviceBridge.Logcat(configuration.Device.Serial, "main", "DOTNET:I", this); + var logcatFirstChannelProcess = DeviceBridge.Logcat(configuration.Device.Serial, "system,crash", "*:I", logger); + var logcatSecondChannelProcess = DeviceBridge.Logcat(configuration.Device.Serial, "main", "DOTNET:I", logger); - disposables.Add(() => logcatFirstChannelProcess.Terminate()); - disposables.Add(() => logcatSecondChannelProcess.Terminate()); - disposables.Add(() => DeviceBridge.RemoveForward(configuration.Device.Serial)); + Disposables.Add(() => logcatFirstChannelProcess.Terminate()); + Disposables.Add(() => logcatSecondChannelProcess.Terminate()); + Disposables.Add(() => DeviceBridge.RemoveForward(configuration.Device.Serial)); } } \ No newline at end of file diff --git a/src/DotNet.Meteor.Debug/Agents/GCDumpLaunchAgent.cs b/src/DotNet.Meteor.Debug/Agents/GCDumpLaunchAgent.cs new file mode 100644 index 00000000..f025d8bb --- /dev/null +++ b/src/DotNet.Meteor.Debug/Agents/GCDumpLaunchAgent.cs @@ -0,0 +1,39 @@ +using System; +using System.IO; +using DotNet.Meteor.Debug.Extensions; +using DotNet.Meteor.Debug.Sdk; +using DotNet.Meteor.Debug.Sdk.Profiling; +using DotNet.Meteor.Processes; +using DotNet.Meteor.Shared; +using Mono.Debugging.Soft; + +namespace DotNet.Meteor.Debug; + +public class GCDumpLaunchAgent : BaseLaunchAgent { + public override void Connect(SoftDebuggerSession session, LaunchConfiguration configuration) {} + public override void Launch(LaunchConfiguration configuration, IProcessLogger logger) { + // var nettracePath = Path.Combine(configuration.TempDirectoryPath, $"{configuration.GetApplicationName()}.nettrace"); + // var diagnosticPort = Path.Combine(RuntimeSystem.HomeDirectory, $"{configuration.Device.Platform}-port.lock"); + // ServerExtensions.TryDeleteFile(diagnosticPort); + + // if (configuration.Device.IsAndroid) + // LaunchAndroid(configuration, logger, diagnosticPort, nettracePath); + // if (configuration.Device.IsIPhone) + // LaunchAppleMobile(configuration, logger, diagnosticPort, nettracePath); + // if (configuration.Device.IsMacCatalyst) + // LaunchMacCatalyst(configuration, logger, diagnosticPort, nettracePath); + // if (configuration.Device.IsWindows) + // LaunchWindows(configuration, logger, diagnosticPort, nettracePath); + + // Disposables.Add(() => ServerExtensions.TryDeleteFile(diagnosticPort)); + } + + private void LaunchAppleMobile(LaunchConfiguration configuration, IProcessLogger logger, string diagnosticPort, string nettracePath) { + } + private void LaunchMacCatalyst(LaunchConfiguration configuration, IProcessLogger logger, string diagnosticPort, string nettracePath) { + } + private void LaunchAndroid(LaunchConfiguration configuration, IProcessLogger logger, string diagnosticPort, string nettracePath) { + } + private void LaunchWindows(LaunchConfiguration configuration, IProcessLogger logger, string diagnosticPort, string nettracePath) { + } +} \ No newline at end of file diff --git a/src/DotNet.Meteor.Debug/Agents/NoDebugLaunchAgent.cs b/src/DotNet.Meteor.Debug/Agents/NoDebugLaunchAgent.cs new file mode 100644 index 00000000..18ea4e05 --- /dev/null +++ b/src/DotNet.Meteor.Debug/Agents/NoDebugLaunchAgent.cs @@ -0,0 +1,70 @@ +using System; +using System.IO; +using DotNet.Meteor.Debug.Extensions; +using DotNet.Meteor.Debug.Sdk; +using DotNet.Meteor.Processes; +using DotNet.Meteor.Shared; +using Microsoft.VisualStudio.Shared.VSCodeDebugProtocol; +using Mono.Debugging.Soft; + +namespace DotNet.Meteor.Debug; + +public class NoDebugLaunchAgent : BaseLaunchAgent { + public override void Connect(SoftDebuggerSession session, LaunchConfiguration configuration) {} + public override void Launch(LaunchConfiguration configuration, IProcessLogger logger) { + if (configuration.Device.IsAndroid) + LaunchAndroid(configuration, logger); + if (configuration.Device.IsIPhone) + LaunchAppleMobile(configuration, logger); + if (configuration.Device.IsMacCatalyst) + LaunchMacCatalyst(configuration, logger); + if (configuration.Device.IsWindows) + LaunchWindows(configuration, logger); + } + + private void LaunchAppleMobile(LaunchConfiguration configuration, IProcessLogger logger) { + if (configuration.Device.IsEmulator) { + var debugProcess = MonoLaunch.DebugSim(configuration.Device.Serial, configuration.OutputAssembly, configuration.DebugPort, logger); + Disposables.Add(() => debugProcess.Terminate()); + } else { + MonoLaunch.InstallDev(configuration.Device.Serial, configuration.OutputAssembly, logger); + var debugProcess = MonoLaunch.DebugDev(configuration.Device.Serial, configuration.OutputAssembly, configuration.DebugPort, logger); + Disposables.Add(() => debugProcess.Terminate()); + } + } + private void LaunchMacCatalyst(LaunchConfiguration configuration, IProcessLogger logger) { + var tool = AppleSdk.OpenTool(); + var processRunner = new ProcessRunner(tool, new ProcessArgumentBuilder().AppendQuoted(configuration.OutputAssembly)); + var result = processRunner.WaitForExit(); + + if (!result.Success) + ServerExtensions.ThrowException(string.Join(Environment.NewLine, result.StandardError)); + } + private void LaunchWindows(LaunchConfiguration configuration, IProcessLogger logger) { + var program = new FileInfo(configuration.OutputAssembly); + var process = new ProcessRunner(program, new ProcessArgumentBuilder(), logger).Start(); + Disposables.Add(() => process.Terminate()); + } + private void LaunchAndroid(LaunchConfiguration configuration, IProcessLogger logger) { + var applicationId = configuration.GetApplicationName(); + if (configuration.Device.IsEmulator) + configuration.Device.Serial = AndroidEmulator.Run(configuration.Device.Name).Serial; + + if (configuration.ReloadHostPort > 0) + DeviceBridge.Forward(configuration.Device.Serial, configuration.ReloadHostPort); + + if (configuration.UninstallApp) + DeviceBridge.Uninstall(configuration.Device.Serial, applicationId, logger); + + DeviceBridge.Install(configuration.Device.Serial, configuration.OutputAssembly, logger); + DeviceBridge.Launch(configuration.Device.Serial, applicationId, logger); + DeviceBridge.Flush(configuration.Device.Serial); + + var logcatFirstChannelProcess = DeviceBridge.Logcat(configuration.Device.Serial, "system,crash", "*:I", logger); + var logcatSecondChannelProcess = DeviceBridge.Logcat(configuration.Device.Serial, "main", "DOTNET:I", logger); + + Disposables.Add(() => logcatFirstChannelProcess.Terminate()); + Disposables.Add(() => logcatSecondChannelProcess.Terminate()); + Disposables.Add(() => DeviceBridge.RemoveForward(configuration.Device.Serial)); + } +} \ No newline at end of file diff --git a/src/DotNet.Meteor.Debug/DebugProfiler.cs b/src/DotNet.Meteor.Debug/Agents/TraceLaunchAgent.cs similarity index 52% rename from src/DotNet.Meteor.Debug/DebugProfiler.cs rename to src/DotNet.Meteor.Debug/Agents/TraceLaunchAgent.cs index a557eb68..c2dcffed 100644 --- a/src/DotNet.Meteor.Debug/DebugProfiler.cs +++ b/src/DotNet.Meteor.Debug/Agents/TraceLaunchAgent.cs @@ -1,71 +1,69 @@ using System; using System.IO; -using DotNet.Meteor.Shared; -using DotNet.Meteor.Processes; +using DotNet.Meteor.Debug.Extensions; using DotNet.Meteor.Debug.Sdk; using DotNet.Meteor.Debug.Sdk.Profiling; -using DotNet.Meteor.Debug.Extensions; -using Microsoft.VisualStudio.Shared.VSCodeDebugProtocol.Messages; +using DotNet.Meteor.Processes; +using DotNet.Meteor.Shared; +using Mono.Debugging.Soft; namespace DotNet.Meteor.Debug; -public partial class DebugSession { - private void ProfileApplication(LaunchConfiguration configuration) { +public class TraceLaunchAgent : BaseLaunchAgent { + public override void Connect(SoftDebuggerSession session, LaunchConfiguration configuration) {} + public override void Launch(LaunchConfiguration configuration, IProcessLogger logger) { var nettracePath = Path.Combine(configuration.TempDirectoryPath, $"{configuration.GetApplicationName()}.nettrace"); var diagnosticPort = Path.Combine(RuntimeSystem.HomeDirectory, $"{configuration.Device.Platform}-port.lock"); ServerExtensions.TryDeleteFile(diagnosticPort); - - DoSafe(() => { - if (configuration.Device.IsAndroid) - ProfileAndroid(configuration, diagnosticPort, nettracePath); - if (configuration.Device.IsIPhone) - ProfileAppleMobile(configuration, diagnosticPort, nettracePath); - if (configuration.Device.IsMacCatalyst) - ProfileMacCatalyst(configuration, diagnosticPort, nettracePath); - if (configuration.Device.IsWindows) - ProfileWindows(configuration, diagnosticPort, nettracePath); - }); - disposables.Add(() => ServerExtensions.TryDeleteFile(diagnosticPort)); - disposables.Add(() => Protocol.SendEvent(new TerminatedEvent())); + if (configuration.Device.IsAndroid) + LaunchAndroid(configuration, logger, diagnosticPort, nettracePath); + if (configuration.Device.IsIPhone) + LaunchAppleMobile(configuration, logger, diagnosticPort, nettracePath); + if (configuration.Device.IsMacCatalyst) + LaunchMacCatalyst(configuration, logger, diagnosticPort, nettracePath); + if (configuration.Device.IsWindows) + LaunchWindows(configuration, logger, diagnosticPort, nettracePath); + + Disposables.Add(() => ServerExtensions.TryDeleteFile(diagnosticPort)); } - private void ProfileAppleMobile(LaunchConfiguration configuration, string diagnosticPort, string nettracePath) { + private void LaunchAppleMobile(LaunchConfiguration configuration, IProcessLogger logger, string diagnosticPort, string nettracePath) { if (configuration.Device.IsEmulator) { - var routerProcess = DSRouter.ClientToServer(diagnosticPort, $"127.0.0.1:{configuration.ProfilerPort}", this); - var simProcess = MonoLaunch.ProfileSim(configuration.Device.Serial, configuration.OutputAssembly, configuration.ProfilerPort, new CatchStartLogger(this, () => { - var traceProcess = Trace.Collect(diagnosticPort, nettracePath, configuration.ProfilerMode, this); - disposables.Insert(0, () => traceProcess.Terminate()); + var routerProcess = DSRouter.ClientToServer(diagnosticPort, $"127.0.0.1:{configuration.ProfilerPort}", logger); + var simProcess = MonoLaunch.ProfileSim(configuration.Device.Serial, configuration.OutputAssembly, configuration.ProfilerPort, new CatchStartLogger(logger, () => { + var traceProcess = Trace.Collect(diagnosticPort, nettracePath, configuration.ProfilerMode, logger); + Disposables.Insert(0, () => traceProcess.Terminate()); })); - disposables.Add(() => routerProcess.Terminate()); - disposables.Add(() => simProcess.Terminate()); + Disposables.Add(() => routerProcess.Terminate()); + Disposables.Add(() => simProcess.Terminate()); } else { - var routerProcess = DSRouter.ServerToClient(diagnosticPort, $"127.0.0.1:{configuration.ProfilerPort}", forwardApple: true, this); - MonoLaunch.InstallDev(configuration.Device.Serial, configuration.OutputAssembly, this); - var devProcess = MonoLaunch.ProfileDev(configuration.Device.Serial, configuration.OutputAssembly, configuration.ProfilerPort, new CatchStartLogger(this, () => { - var traceProcess = Trace.Collect($"{diagnosticPort},connect", nettracePath, configuration.ProfilerMode, this); - disposables.Insert(0, () => traceProcess.Terminate()); + var routerProcess = DSRouter.ServerToClient(diagnosticPort, $"127.0.0.1:{configuration.ProfilerPort}", forwardApple: true, logger); + MonoLaunch.InstallDev(configuration.Device.Serial, configuration.OutputAssembly, logger); + var devProcess = MonoLaunch.ProfileDev(configuration.Device.Serial, configuration.OutputAssembly, configuration.ProfilerPort, new CatchStartLogger(logger, () => { + var traceProcess = Trace.Collect($"{diagnosticPort},connect", nettracePath, configuration.ProfilerMode, logger); + Disposables.Insert(0, () => traceProcess.Terminate()); })); - disposables.Add(() => routerProcess.Terminate()); - disposables.Add(() => devProcess.Terminate()); + Disposables.Add(() => routerProcess.Terminate()); + Disposables.Add(() => devProcess.Terminate()); } } - private void ProfileMacCatalyst(LaunchConfiguration configuration, string diagnosticPort, string nettracePath) { + private void LaunchMacCatalyst(LaunchConfiguration configuration, IProcessLogger logger, string diagnosticPort, string nettracePath) { var tool = AppleSdk.OpenTool(); var processRunner = new ProcessRunner(tool, new ProcessArgumentBuilder().AppendQuoted(configuration.OutputAssembly)); processRunner.SetEnvironmentVariable("DOTNET_DiagnosticPorts", $"{diagnosticPort},suspend"); - var traceProcess = Trace.Collect(diagnosticPort, nettracePath, configuration.ProfilerMode, this); + var traceProcess = Trace.Collect(diagnosticPort, nettracePath, configuration.ProfilerMode, logger); var appLaunchResult = processRunner.WaitForExit(); - disposables.Add(() => traceProcess.Terminate()); + Disposables.Add(() => traceProcess.Terminate()); if (!appLaunchResult.Success) throw new Exception(string.Join(Environment.NewLine, appLaunchResult.StandardError)); } - private void ProfileAndroid(LaunchConfiguration configuration, string diagnosticPort, string nettracePath) { + private void LaunchAndroid(LaunchConfiguration configuration, IProcessLogger logger, string diagnosticPort, string nettracePath) { var applicationId = configuration.GetApplicationName(); if (configuration.Device.IsEmulator) configuration.Device.Serial = AndroidEmulator.Run(configuration.Device.Name).Serial; @@ -73,29 +71,26 @@ private void ProfileAndroid(LaunchConfiguration configuration, string diagnostic DeviceBridge.Reverse(configuration.Device.Serial, configuration.ProfilerPort, configuration.ProfilerPort+1); DeviceBridge.Shell(configuration.Device.Serial, "setprop", "debug.mono.profile", $"127.0.0.1:{configuration.ProfilerPort},suspend"); - var routerProcess = DSRouter.ServerToServer(configuration.ProfilerPort+1, this); + var routerProcess = DSRouter.ServerToServer(configuration.ProfilerPort+1, logger); System.Threading.Thread.Sleep(1000); // wait for router to start - var traceProcess = Trace.Collect(routerProcess.Id, nettracePath, configuration.ProfilerMode, this); + var traceProcess = Trace.Collect(routerProcess.Id, nettracePath, configuration.ProfilerMode, logger); System.Threading.Thread.Sleep(1000); // wait for trace to start if (configuration.UninstallApp) - DeviceBridge.Uninstall(configuration.Device.Serial, applicationId, this); - DeviceBridge.Install(configuration.Device.Serial, configuration.OutputAssembly, this); - DeviceBridge.Launch(configuration.Device.Serial, applicationId, this); + DeviceBridge.Uninstall(configuration.Device.Serial, applicationId, logger); + DeviceBridge.Install(configuration.Device.Serial, configuration.OutputAssembly, logger); + DeviceBridge.Launch(configuration.Device.Serial, applicationId, logger); - disposables.Add(() => traceProcess.Terminate()); - disposables.Add(() => routerProcess.Terminate()); - disposables.Add(() => DeviceBridge.Shell(configuration.Device.Serial, "am", "force-stop", applicationId)); - disposables.Add(() => DeviceBridge.RemoveReverse(configuration.Device.Serial)); + Disposables.Add(() => traceProcess.Terminate()); + Disposables.Add(() => routerProcess.Terminate()); + Disposables.Add(() => DeviceBridge.Shell(configuration.Device.Serial, "am", "force-stop", applicationId)); + Disposables.Add(() => DeviceBridge.RemoveReverse(configuration.Device.Serial)); } - private void ProfileWindows(LaunchConfiguration configuration, string diagnosticPort, string nettracePath) { - if (configuration.IsGCDumpProfiling) - throw new NotSupportedException("GCDump profiling is not supported on Windows"); - - var exeProcess = new ProcessRunner(new FileInfo(configuration.OutputAssembly), null, this).Start(); - var traceProcess = Trace.Collect(exeProcess.Id, nettracePath, configuration.ProfilerMode, this); + private void LaunchWindows(LaunchConfiguration configuration, IProcessLogger logger, string diagnosticPort, string nettracePath) { + var exeProcess = new ProcessRunner(new FileInfo(configuration.OutputAssembly), null, logger).Start(); + var traceProcess = Trace.Collect(exeProcess.Id, nettracePath, configuration.ProfilerMode, logger); - disposables.Add(() => traceProcess.Terminate()); - disposables.Add(() => exeProcess.Terminate()); + Disposables.Add(() => traceProcess.Terminate()); + Disposables.Add(() => exeProcess.Terminate()); } } \ No newline at end of file diff --git a/src/DotNet.Meteor.Debug/DebugSession.cs b/src/DotNet.Meteor.Debug/DebugSession.cs index 80d3a418..7c58b440 100644 --- a/src/DotNet.Meteor.Debug/DebugSession.cs +++ b/src/DotNet.Meteor.Debug/DebugSession.cs @@ -12,17 +12,13 @@ namespace DotNet.Meteor.Debug; -public partial class DebugSession : Session { - private MonoClient.ObjectValue exception; - private MonoClient.ProcessInfo activeProcess; +public class DebugSession : Session { private ExternalTypeResolver typeResolver; private SymbolServer symbolServer; + private BaseLaunchAgent launchAgent; - private readonly List disposables = new List(); - private readonly AutoResetEvent suspendEvent = new AutoResetEvent(false); private readonly Handles frameHandles = new Handles(); private readonly Handles variableHandles = new Handles(); - private readonly Dictionary seenThreads = new Dictionary(); private readonly SoftDebuggerSession session = new SoftDebuggerSession(); public DebugSession(Stream input, Stream output): base(input, output) { @@ -39,13 +35,12 @@ public DebugSession(Stream input, Stream output): base(input, output) { session.TargetUnhandledException += TargetExceptionThrown; session.TargetReady += TargetReady; session.TargetExited += TargetExited; - session.TargetInterrupted += TargetInterrupted; session.TargetThreadStarted += TargetThreadStarted; session.TargetThreadStopped += TargetThreadStopped; } protected override MonoClient.ICustomLogger GetLogger() => MonoClient.DebuggerLoggingService.CustomLogger; - protected override void OnUnhandledException(Exception ex) => Dispose(); + protected override void OnUnhandledException(Exception ex) => launchAgent?.Dispose(); #region request: Initialize protected override InitializeResponse HandleInitializeRequest(InitializeArguments arguments) { @@ -68,49 +63,45 @@ protected override InitializeResponse HandleInitializeRequest(InitializeArgument #endregion request: Initialize #region request: Launch protected override LaunchResponse HandleLaunchRequest(LaunchArguments arguments) { - var configuration = new LaunchConfiguration(arguments.ConfigurationProperties); - symbolServer = new SymbolServer(configuration.TempDirectoryPath); - typeResolver = new ExternalTypeResolver(configuration.TempDirectoryPath, configuration.DebuggerSessionOptions); - - disposables.Add(() => symbolServer.Dispose()); - disposables.Add(() => typeResolver.Dispose()); - session.TypeResolverHandler = typeResolver.Handle; - - if (configuration.DebugPort == 0) - configuration.DebugPort = ServerExtensions.FindFreePort(); - if (configuration.DebugPort < 1) - throw new ProtocolException($"Invalid port '{configuration.DebugPort}'"); - - if (configuration.IsProfilingConfiguration) { - ProfileApplication(configuration); + return DoSafe(() => { + var configuration = new LaunchConfiguration(arguments.ConfigurationProperties); + launchAgent = configuration.GetLauchAgent(); + + symbolServer = new SymbolServer(configuration.TempDirectoryPath); + typeResolver = new ExternalTypeResolver(configuration.TempDirectoryPath, configuration.DebuggerSessionOptions); + launchAgent.Disposables.Add(() => symbolServer.Dispose()); + launchAgent.Disposables.Add(() => typeResolver.Dispose()); + session.TypeResolverHandler = typeResolver.Handle; + + launchAgent.Launch(configuration, this); + launchAgent.Connect(session, configuration); return new LaunchResponse(); - } - - LaunchApplication(configuration); - Connect(configuration); - return new LaunchResponse(); + }); } #endregion request: Launch #region request: Terminate protected override TerminateResponse HandleTerminateRequest(TerminateArguments arguments) { if (!session.HasExited) session.Exit(); - - Dispose(); + + launchAgent?.Dispose(); + if (launchAgent is not DebugLaunchAgent) + Protocol.SendEvent(new TerminatedEvent()); + return new TerminateResponse(); } #endregion request: Terminate #region request: Disconnect protected override DisconnectResponse HandleDisconnectRequest(DisconnectArguments arguments) { - session?.Dispose(); + session.Dispose(); return new DisconnectResponse(); } #endregion request: Disconnect #region request: Continue protected override ContinueResponse HandleContinueRequest(ContinueArguments arguments) { return DoSafe(() => { - if (this.session?.IsRunning == false && !this.session.HasExited) - this.session.Continue(); + if (!session.IsRunning && !session.HasExited) + session.Continue(); return new ContinueResponse(); }); @@ -119,8 +110,8 @@ protected override ContinueResponse HandleContinueRequest(ContinueArguments argu #region request: Next protected override NextResponse HandleNextRequest(NextArguments arguments) { return DoSafe(() => { - if (this.session?.IsRunning == false && !this.session.HasExited) - this.session.NextLine(); + if (!session.IsRunning && !session.HasExited) + session.NextLine(); return new NextResponse(); }); @@ -129,8 +120,8 @@ protected override NextResponse HandleNextRequest(NextArguments arguments) { #region request: StepIn protected override StepInResponse HandleStepInRequest(StepInArguments arguments) { return DoSafe(() => { - if (this.session?.IsRunning == false && !this.session.HasExited) - this.session.StepLine(); + if (!session.IsRunning && !session.HasExited) + session.StepLine(); return new StepInResponse(); }); @@ -139,8 +130,8 @@ protected override StepInResponse HandleStepInRequest(StepInArguments arguments) #region request: StepOut protected override StepOutResponse HandleStepOutRequest(StepOutArguments arguments) { return DoSafe(() => { - if (this.session?.IsRunning == false && !this.session.HasExited) - this.session.Finish(); + if (!session.IsRunning && !session.HasExited) + session.Finish(); return new StepOutResponse(); }); @@ -149,8 +140,8 @@ protected override StepOutResponse HandleStepOutRequest(StepOutArguments argumen #region request: Pause protected override PauseResponse HandlePauseRequest(PauseArguments arguments) { return DoSafe(() => { - if (this.session?.IsRunning == true) - this.session.Stop(); + if (session.IsRunning) + session.Stop(); return new PauseResponse(); }); @@ -158,7 +149,7 @@ protected override PauseResponse HandlePauseRequest(PauseArguments arguments) { #endregion request: Pause #region request: SetExceptionBreakpoints protected override SetExceptionBreakpointsResponse HandleSetExceptionBreakpointsRequest(SetExceptionBreakpointsArguments arguments) { - this.session.Breakpoints.ClearCatchpoints(); + session.Breakpoints.ClearCatchpoints(); if (arguments.FilterOptions == null || arguments.FilterOptions.Count == 0) return new SetExceptionBreakpointsResponse(); @@ -170,7 +161,7 @@ protected override SetExceptionBreakpointsResponse HandleSetExceptionBreakpoints exceptionFilter = option.Condition; foreach(var exception in exceptionFilter.Split(',')) - this.session.Breakpoints.AddCatchpoint(exception); + session.Breakpoints.AddCatchpoint(exception); } } return new SetExceptionBreakpointsResponse(); @@ -183,13 +174,13 @@ protected override SetBreakpointsResponse HandleSetBreakpointsRequest(SetBreakpo var sourcePath = arguments.Source?.Path; // Remove all file breakpoints - var fileBreakpoints = this.session.Breakpoints.GetBreakpointsAtFile(sourcePath); - foreach(var fileBreakpoint in fileBreakpoints) { - this.session.Breakpoints.Remove(fileBreakpoint); - } + var fileBreakpoints = session.Breakpoints.GetBreakpointsAtFile(sourcePath); + foreach(var fileBreakpoint in fileBreakpoints) + session.Breakpoints.Remove(fileBreakpoint); + // Add all new breakpoints foreach(var breakpointInfo in breakpointsInfos) { - MonoClient.Breakpoint breakpoint = this.session.Breakpoints.Add(sourcePath, breakpointInfo.Line, breakpointInfo.Column ?? 1); + MonoClient.Breakpoint breakpoint = session.Breakpoints.Add(sourcePath, breakpointInfo.Line, breakpointInfo.Column ?? 1); // Conditional breakpoint if (breakpoint != null && breakpointInfo.Condition != null) breakpoint.ConditionExpression = breakpointInfo.Condition; @@ -217,9 +208,9 @@ protected override SetBreakpointsResponse HandleSetBreakpointsRequest(SetBreakpo #region request: StackTrace protected override StackTraceResponse HandleStackTraceRequest(StackTraceArguments arguments) { return DoSafe(() => { - var thread = this.session.ActiveThread; + var thread = session.ActiveThread; if (thread.Id != arguments.ThreadId) { - thread = FindThread(arguments.ThreadId); + thread = session.FindThread(arguments.ThreadId); thread?.SetActive(); } @@ -280,7 +271,7 @@ protected override StackTraceResponse HandleStackTraceRequest(StackTraceArgument } stackFrames.Add(new DebugProtocol.StackFrame() { - Id = this.frameHandles.Create(frame), + Id = frameHandles.Create(frame), Source = source, PresentationHint = hint, Name = frame.SourceLocation.MethodName, @@ -299,22 +290,15 @@ protected override StackTraceResponse HandleStackTraceRequest(StackTraceArgument protected override ScopesResponse HandleScopesRequest(ScopesArguments arguments) { return DoSafe(() => { int frameId = arguments.FrameId; - var frame = this.frameHandles.Get(frameId, null); + var frame = frameHandles.Get(frameId, null); var scopes = new List(); if (frame == null) throw new ProtocolException("frame not found"); - if (this.exception != null) { - scopes.Add(new DebugProtocol.Scope() { - Name = "Exception", - VariablesReference = this.variableHandles.Create(new MonoClient.ObjectValue[] { this.exception }) - }); - } - scopes.Add(new DebugProtocol.Scope() { - Name = "Local", - VariablesReference = this.variableHandles.Create(frame.GetAllLocals()) + Name = "Locals", + VariablesReference = variableHandles.Create(frame.GetAllLocals()) }); return new ScopesResponse(scopes); @@ -324,13 +308,9 @@ protected override ScopesResponse HandleScopesRequest(ScopesArguments arguments) #region request: Variables protected override VariablesResponse HandleVariablesRequest(VariablesArguments arguments) { return DoSafe(() => { - int reference = arguments.VariablesReference; - if (reference == -1) - throw new ProtocolException("variables: property 'variablesReference' is missing"); - + var reference = arguments.VariablesReference; var variables = new List(); - - if (this.variableHandles.TryGet(reference, out MonoClient.ObjectValue[] children) && children?.Length > 0) { + if (variableHandles.TryGet(reference, out MonoClient.ObjectValue[] children) && children?.Length > 0) { if (children.Length < 20) { // Wait for all values at once. WaitHandle.WaitAll(children.Select(x => x.WaitHandle).ToArray()); @@ -339,7 +319,7 @@ protected override VariablesResponse HandleVariablesRequest(VariablesArguments a } } else { foreach (var v in children) { - v.WaitHandle.WaitOne(this.session.EvaluationOptions.EvaluationTimeout); + v.WaitHandle.WaitOne(session.EvaluationOptions.EvaluationTimeout); variables.Add(CreateVariable(v)); } } @@ -352,20 +332,17 @@ protected override VariablesResponse HandleVariablesRequest(VariablesArguments a #region request: Threads protected override ThreadsResponse HandleThreadsRequest(ThreadsArguments arguments) { return DoSafe(() => { - var threads = new List(); - var process = this.activeProcess; - if (process != null) { - Dictionary d; - lock (this.seenThreads) { - d = new Dictionary(this.seenThreads); - } - foreach (var t in process.GetThreads()) { - int tid = (int)t.Id; - d[tid] = new DebugProtocol.Thread(tid, t.Name.ToThreadName(tid)); - } - threads = d.Values.ToList(); + var threads = new Dictionary(); + var process = session.GetProcesses().FirstOrDefault(); + if (process == null) + return new ThreadsResponse(); + + foreach (var thread in process.GetThreads()) { + int tid = (int)thread.Id; + threads[tid] = new DebugProtocol.Thread(tid, thread.Name.ToThreadName(tid)); } - return new ThreadsResponse(threads); + + return new ThreadsResponse(threads.Values.ToList()); }); } #endregion request: Threads @@ -375,30 +352,30 @@ protected override EvaluateResponse HandleEvaluateRequest(EvaluateArguments argu if (string.IsNullOrEmpty(arguments.Expression)) throw new ProtocolException("expression missing"); - var frame = this.frameHandles.Get(arguments.FrameId ?? 0, null); + var frame = frameHandles.Get(arguments.FrameId ?? 0, null); if (frame == null) throw new ProtocolException("no active stackframe"); if (!frame.ValidateExpression(arguments.Expression)) throw new ProtocolException("invalid expression"); - var val = frame.GetExpressionValue(arguments.Expression, session.Options.EvaluationOptions); - val.WaitHandle.WaitOne(session.Options.EvaluationOptions.EvaluationTimeout); + var value = frame.GetExpressionValue(arguments.Expression, session.Options.EvaluationOptions); + value.WaitHandle.WaitOne(session.Options.EvaluationOptions.EvaluationTimeout); - if (val.IsEvaluating) + if (value.IsEvaluating) throw new ProtocolException("evaluation timeout expected"); - if (val.Flags.HasFlag(MonoClient.ObjectValueFlags.Error) || val.Flags.HasFlag(MonoClient.ObjectValueFlags.NotSupported)) - throw new ProtocolException(val.DisplayValue); - if (val.Flags.HasFlag(MonoClient.ObjectValueFlags.Unknown)) + if (value.Flags.HasFlag(MonoClient.ObjectValueFlags.Error) || value.Flags.HasFlag(MonoClient.ObjectValueFlags.NotSupported)) + throw new ProtocolException(value.DisplayValue); + if (value.Flags.HasFlag(MonoClient.ObjectValueFlags.Unknown)) throw new ProtocolException("invalid expression"); - if (val.Flags.HasFlag(MonoClient.ObjectValueFlags.Object) && val.Flags.HasFlag(MonoClient.ObjectValueFlags.Namespace)) + if (value.Flags.HasFlag(MonoClient.ObjectValueFlags.Object) && value.Flags.HasFlag(MonoClient.ObjectValueFlags.Namespace)) throw new ProtocolException("not available"); int handle = 0; - if (val.HasChildren) - handle = this.variableHandles.Create(val.GetAllChildren()); + if (value.HasChildren) + handle = variableHandles.Create(value.GetAllChildren()); - return new EvaluateResponse(val.DisplayValue, handle); + return new EvaluateResponse(value.ToDisplayValue(), handle); }); } #endregion request: Evaluate @@ -410,7 +387,7 @@ protected override SourceResponse HandleSourceRequest(SourceArguments arguments) #region request: ExceptionInfo protected override ExceptionInfoResponse HandleExceptionInfoRequest(ExceptionInfoArguments arguments) { return DoSafe(() => { - var ex = GetActiveException(arguments.ThreadId); + var ex = session.FindException(arguments.ThreadId); if (ex == null) throw new ProtocolException("No exception available"); @@ -426,7 +403,7 @@ protected override CompletionsResponse HandleCompletionsRequest(CompletionsArgum if (string.IsNullOrEmpty(arguments.Text)) throw new ProtocolException("expression missing"); - var frame = this.frameHandles.Get(arguments.FrameId ?? 0, null); + var frame = frameHandles.Get(arguments.FrameId ?? 0, null); if (frame == null) throw new ProtocolException("no active stackframe"); @@ -448,67 +425,48 @@ protected override CompletionsResponse HandleCompletionsRequest(CompletionsArgum } #endregion request: Completions -#region Event handlers - private void TargetStopped(object sender, MonoClient.TargetEventArgs e) { - Reset(); - this.suspendEvent.Set(); + ResetHandles(); Protocol.SendEvent(new StoppedEvent(StoppedEvent.ReasonValue.Pause) { ThreadId = (int)e.Thread.Id, AllThreadsStopped = true, }); } private void TargetHitBreakpoint(object sender, MonoClient.TargetEventArgs e) { - Reset(); - this.suspendEvent.Set(); + ResetHandles(); Protocol.SendEvent(new StoppedEvent(StoppedEvent.ReasonValue.Breakpoint) { ThreadId = (int)e.Thread.Id, AllThreadsStopped = true, }); } private void TargetExceptionThrown(object sender, MonoClient.TargetEventArgs e) { - Reset(); - this.suspendEvent.Set(); - var ex = GetActiveException((int)e.Thread.Id); - if (ex != null) { - Protocol.SendEvent(new StoppedEvent(StoppedEvent.ReasonValue.Exception) { - Description = "Paused on exception", - ThreadId = (int)e.Thread.Id, - AllThreadsStopped = true, - Text = ex.Type - }); - } + ResetHandles(); + var ex = session.FindException(e.Thread.Id); + Protocol.SendEvent(new StoppedEvent(StoppedEvent.ReasonValue.Exception) { + Description = "Paused on exception", + Text = ex.Type ?? "Exception", + ThreadId = (int)e.Thread.Id, + AllThreadsStopped = true, + }); } private void TargetReady(object sender, MonoClient.TargetEventArgs e) { - this.activeProcess = this.session.GetProcesses().SingleOrDefault(); Protocol.SendEvent(new InitializedEvent()); } private void TargetExited(object sender, MonoClient.TargetEventArgs e) { Protocol.SendEvent(new TerminatedEvent()); } - private void TargetInterrupted(object sender, MonoClient.TargetEventArgs e) { - this.suspendEvent.Set(); - } private void TargetThreadStarted(object sender, MonoClient.TargetEventArgs e) { int tid = (int)e.Thread.Id; - lock (this.seenThreads) { - this.seenThreads[tid] = new DebugProtocol.Thread(tid, e.Thread.Name.ToThreadName(tid)); - } Protocol.SendEvent(new ThreadEvent(ThreadEvent.ReasonValue.Started, tid)); } private void TargetThreadStopped(object sender, MonoClient.TargetEventArgs e) { int tid = (int)e.Thread.Id; - lock (this.seenThreads) { - this.seenThreads.Remove(tid); - } Protocol.SendEvent(new ThreadEvent(ThreadEvent.ReasonValue.Exited, tid)); } - private bool OnExceptionHandled(Exception ex) { GetLogger().LogError($"[Handled] {ex.Message}", ex); return true; } - private void OnSessionLog(bool isError, string message) { if (isError) GetLogger().LogError($"[Error] {message.Trim()}", null); else GetLogger().LogMessage($"[Info] {message.Trim()}"); @@ -523,61 +481,19 @@ private void OnDebugLog(int level, string category, string message) { SendConsoleEvent(OutputEvent.CategoryValue.Console, message); } -#endregion Event handlers -#region Helpers - private void Reset() { - exception = null; + private void ResetHandles() { variableHandles.Reset(); frameHandles.Reset(); } - private void Dispose() { - foreach(var disposable in disposables) - DoSafe(() => disposable.Invoke()); - - disposables.Clear(); - } - - private MonoClient.ThreadInfo FindThread(int threadReference) { - if (activeProcess != null) { - foreach (var t in activeProcess.GetThreads()) { - if (t.Id == threadReference) - return t; - } - } - - return null; - } private DebugProtocol.Variable CreateVariable(MonoClient.ObjectValue v) { - var dv = v.DisplayValue ?? ""; - int childrenReference = 0; - - if (dv.Length > 1 && dv[0] == '{' && dv[dv.Length - 1] == '}') - dv = dv.Substring(1, dv.Length - 2); - + var dv = v.ToDisplayValue(); + var childrenReference = 0; if (v.HasChildren) { var objectValues = v.GetAllChildren(); childrenReference = variableHandles.Create(objectValues); } - return new DebugProtocol.Variable(v.Name, dv, childrenReference) { VariablesReference = childrenReference }; } - - private MonoClient.ExceptionInfo GetActiveException(int threadId) { - var thread = FindThread(threadId); - if (thread == null) - return null; - - for (int i = 0; i < thread.Backtrace.FrameCount; i++) { - var frame = thread.Backtrace.GetFrameSafe(i); - var ex = frame?.GetException(); - if (ex != null) { - exception = ex.Instance; - return ex; - } - } - return null; - } -#endregion Helpers } \ No newline at end of file diff --git a/src/DotNet.Meteor.Debug/Extensions/MonoExtensions.cs b/src/DotNet.Meteor.Debug/Extensions/MonoExtensions.cs new file mode 100644 index 00000000..16e8838c --- /dev/null +++ b/src/DotNet.Meteor.Debug/Extensions/MonoExtensions.cs @@ -0,0 +1,50 @@ +using System; +using System.Linq; +using Mono.Debugging.Client; +using Mono.Debugging.Soft; + +namespace DotNet.Meteor.Debug.Extensions; + +public static class MonoExtensions { + public static string ToThreadName(this string threadName, int threadId) { + if (!string.IsNullOrEmpty(threadName)) + return threadName; + if (threadId == 1) + return "Main Thread"; + return $"Thread #{threadId}"; + } + public static string ToDisplayValue(this ObjectValue value) { + var dv = value.DisplayValue ?? ""; + if (dv.Length > 1 && dv[0] == '{' && dv[dv.Length - 1] == '}') + dv = dv.Substring(1, dv.Length - 2).Replace(Environment.NewLine, " "); + return dv; + } + public static StackFrame GetFrameSafe(this Backtrace bt, int n) { + try { + return bt.GetFrame(n); + } catch (Exception) { + return null; + } + } + public static ThreadInfo FindThread(this SoftDebuggerSession session, long id) { + var process = session.GetProcesses().FirstOrDefault(); + if (process == null) + return null; + + return process.GetThreads().FirstOrDefault(it => it.Id == id); + } + public static ExceptionInfo FindException(this SoftDebuggerSession session, long id) { + var thread = session.FindThread(id); + if (thread == null) + return null; + + for (int i = 0; i < thread.Backtrace.FrameCount; i++) { + var frame = thread.Backtrace.GetFrameSafe(i); + var ex = frame?.GetException(); + if (ex != null) + return ex; + } + + return null; + } +} \ No newline at end of file diff --git a/src/DotNet.Meteor.Debug/Extensions/ServerExtensions.cs b/src/DotNet.Meteor.Debug/Extensions/ServerExtensions.cs index 0ef47889..1354f99f 100644 --- a/src/DotNet.Meteor.Debug/Extensions/ServerExtensions.cs +++ b/src/DotNet.Meteor.Debug/Extensions/ServerExtensions.cs @@ -1,4 +1,3 @@ -using System; using System.Net; using System.Net.Sockets; using Mono.Debugging.Client; @@ -8,6 +7,8 @@ using NewtonConverter = Newtonsoft.Json.JsonConvert; using DebugProtocol = Microsoft.VisualStudio.Shared.VSCodeDebugProtocol.Messages; using System.IO; +using Microsoft.VisualStudio.Shared.VSCodeDebugProtocol; +using DotNet.Meteor.Shared; namespace DotNet.Meteor.Debug.Extensions; @@ -30,7 +31,6 @@ public static class ServerExtensions { }, }; - public static int FindFreePort() { TcpListener listener = null; try { @@ -46,22 +46,8 @@ public static bool TryDeleteFile(string path) { File.Delete(path); return !File.Exists(path); } - - public static StackFrame GetFrameSafe(this Backtrace bt, int n) { - try { - return bt.GetFrame(n); - } catch (Exception) { - return null; - } - } - - - public static string ToThreadName(this string threadName, int threadId) { - if (!string.IsNullOrEmpty(threadName)) - return threadName; - if (threadId == 1) - return "Main Thread"; - return $"Thread #{threadId}"; + public static void ThrowException(string message) { + throw new ProtocolException(message, 0, message, url: $"file://{LogConfig.DebugLogFile}"); } public static T ToObject(this JToken jtoken, JsonTypeInfo type) { @@ -82,7 +68,6 @@ public static DebugProtocol.CompletionItem ToCompletionItem(this CompletionItem Label = item.Name, }; } - private static DebugProtocol.CompletionItemType ToCompletionItemType(this ObjectValueFlags flags) { if (flags.HasFlag(ObjectValueFlags.Method)) return DebugProtocol.CompletionItemType.Method; diff --git a/src/DotNet.Meteor.Debug/Handles.cs b/src/DotNet.Meteor.Debug/Handles.cs index b7d6d82d..73dce4e5 100644 --- a/src/DotNet.Meteor.Debug/Handles.cs +++ b/src/DotNet.Meteor.Debug/Handles.cs @@ -1,35 +1,35 @@ using System.Collections.Generic; -namespace DotNet.Meteor.Debug.Extensions; +namespace DotNet.Meteor.Debug; public class Handles { - private const int START_HANDLE = 1000; + private const int StartHandle = 1000; - private int _nextHandle; - private readonly Dictionary _handleMap; + private int nextHandle; + private readonly Dictionary handleMap; public Handles() { - this._nextHandle = START_HANDLE; - this._handleMap = new Dictionary(); + nextHandle = StartHandle; + handleMap = new Dictionary(); } public void Reset() { - this._nextHandle = START_HANDLE; - this._handleMap.Clear(); + nextHandle = StartHandle; + handleMap.Clear(); } public int Create(T value) { - var handle = this._nextHandle++; - this._handleMap[handle] = value; + var handle = nextHandle++; + handleMap[handle] = value; return handle; } public bool TryGet(int handle, out T value) { - return this._handleMap.TryGetValue(handle, out value); + return handleMap.TryGetValue(handle, out value); } public T Get(int handle, T defaultValue) { - if (this._handleMap.TryGetValue(handle, out T value)) + if (handleMap.TryGetValue(handle, out T value)) return value; return defaultValue; } diff --git a/src/DotNet.Meteor.Debug/LaunchConfiguration.cs b/src/DotNet.Meteor.Debug/LaunchConfiguration.cs index 8ec43420..2796ac71 100644 --- a/src/DotNet.Meteor.Debug/LaunchConfiguration.cs +++ b/src/DotNet.Meteor.Debug/LaunchConfiguration.cs @@ -1,10 +1,8 @@ using System; using System.Collections.Generic; using System.IO; -using System.Linq; using DotNet.Meteor.Shared; using DotNet.Meteor.Debug.Extensions; -using Microsoft.VisualStudio.Shared.VSCodeDebugProtocol; using Newtonsoft.Json.Linq; using Mono.Debugging.Client; @@ -12,22 +10,17 @@ namespace DotNet.Meteor.Debug; public class LaunchConfiguration { public DebuggerSessionOptions DebuggerSessionOptions { get; init; } + public string OutputAssembly { get; init; } public DeviceData Device { get; init; } public Project Project { get; init; } - - public string OutputAssembly { get; init; } - public string Framework { get; init; } public string Target { get; init; } + public string ProfilerMode { get; init; } public bool UninstallApp { get; init; } + public bool SkipDebug { get; init; } - public int DebugPort { get; set; } - public int ReloadHostPort { get; init; } + public int DebugPort { get; init; } public int ProfilerPort { get; init; } - public string ProfilerMode { get; init; } - - public bool IsProfilingConfiguration => !string.IsNullOrEmpty(ProfilerMode); - public bool IsTraceProfiling => ProfilerMode.Equals("trace", StringComparison.OrdinalIgnoreCase); - public bool IsGCDumpProfiling => ProfilerMode.Equals("gcdump", StringComparison.OrdinalIgnoreCase); + public int ReloadHostPort { get; init; } public string TempDirectoryPath => Path.Combine(Path.GetDirectoryName(Project.Path), ".meteor"); @@ -35,19 +28,25 @@ public LaunchConfiguration(Dictionary configurationProperties) { Project = configurationProperties["selectedProject"].ToObject(TrimmableContext.Default.Project); Device = configurationProperties["selectedDevice"].ToObject(TrimmableContext.Default.DeviceData); Target = configurationProperties["selectedTarget"].ToObject(TrimmableContext.Default.String); - ReloadHostPort = configurationProperties["reloadHost"].ToObject(TrimmableContext.Default.Int32); - ProfilerPort = configurationProperties["profilerPort"].ToObject(TrimmableContext.Default.Int32); UninstallApp = configurationProperties["uninstallApp"].ToObject(TrimmableContext.Default.Boolean); + SkipDebug = configurationProperties["skipDebug"].ToObject(TrimmableContext.Default.Boolean); + DebugPort = configurationProperties["debuggingPort"].ToObject(TrimmableContext.Default.Int32); + ReloadHostPort = configurationProperties["reloadHost"].ToObject(TrimmableContext.Default.Int32); + ProfilerPort = configurationProperties["profilerPort"].ToObject(TrimmableContext.Default.Int32); + + DebuggerSessionOptions = GetDebuggerSessionOptions(configurationProperties["debuggerOptions"]); + OutputAssembly = Project.FindOutputApplication(Target, Device, message => { + ServerExtensions.ThrowException(message); + return string.Empty; + }); if (configurationProperties.TryGetValue("profilerMode", out var profilerModeToken)) ProfilerMode = profilerModeToken.ToObject(TrimmableContext.Default.String); - - DebuggerSessionOptions = GetDebuggerSessionOptions(configurationProperties["debuggerOptions"]); - Framework = Project.Frameworks.First(it => it.ContainsInsensitive(Device.Platform)); - OutputAssembly = Project.FindOutputApplication(Target, Framework, Device, message => { - throw new ProtocolException($"Failed to load launch configuration. {message}"); - }); + + DebugPort = DebugPort == 0 ? ServerExtensions.FindFreePort() : DebugPort; + ReloadHostPort = ReloadHostPort == 0 ? ServerExtensions.FindFreePort() : ReloadHostPort; + ProfilerPort = ProfilerPort == 0 ? ServerExtensions.FindFreePort() : ProfilerPort; } public string GetApplicationName() { @@ -57,6 +56,19 @@ public string GetApplicationName() { var assemblyName = Path.GetFileNameWithoutExtension(OutputAssembly); return assemblyName.Replace("-Signed", ""); } + public BaseLaunchAgent GetLauchAgent() { + if (ProfilerMode.EqualsInsensitive("trace")) + return new TraceLaunchAgent(); + if (ProfilerMode.EqualsInsensitive("gcdump")) + return new GCDumpLaunchAgent(); + + if (SkipDebug || Target.EqualsInsensitive("release")) + return new NoDebugLaunchAgent(); + if (Target.EqualsInsensitive("debug")) + return new DebugLaunchAgent(); + + throw new NotSupportedException("Could not create launch agent for current configuration"); + } private DebuggerSessionOptions GetDebuggerSessionOptions(JToken debuggerJsonToken) { var debuggerOptions = ServerExtensions.DefaultDebuggerOptions; diff --git a/src/DotNet.Meteor.Debug/Extensions/ExternalTypeResolver.cs b/src/DotNet.Meteor.Debug/Plugins/ExternalTypeResolver.cs similarity index 98% rename from src/DotNet.Meteor.Debug/Extensions/ExternalTypeResolver.cs rename to src/DotNet.Meteor.Debug/Plugins/ExternalTypeResolver.cs index e51f697c..d66f26ef 100644 --- a/src/DotNet.Meteor.Debug/Extensions/ExternalTypeResolver.cs +++ b/src/DotNet.Meteor.Debug/Plugins/ExternalTypeResolver.cs @@ -3,7 +3,7 @@ using System.Net.Sockets; using Mono.Debugging.Client; -namespace DotNet.Meteor.Debug.Extensions; +namespace DotNet.Meteor.Debug; internal class ExternalTypeResolver { private readonly DebuggerSessionOptions sessionOptions; diff --git a/src/DotNet.Meteor.Debug/Extensions/SymbolServer.cs b/src/DotNet.Meteor.Debug/Plugins/SymbolServer.cs similarity index 98% rename from src/DotNet.Meteor.Debug/Extensions/SymbolServer.cs rename to src/DotNet.Meteor.Debug/Plugins/SymbolServer.cs index 88d667a3..4dec96a6 100644 --- a/src/DotNet.Meteor.Debug/Extensions/SymbolServer.cs +++ b/src/DotNet.Meteor.Debug/Plugins/SymbolServer.cs @@ -5,7 +5,7 @@ using System.Net.Http.Headers; using System.Text; -namespace DotNet.Meteor.Debug.Extensions; +namespace DotNet.Meteor.Debug; public class SymbolServer { private readonly string sourceDirectory; diff --git a/src/DotNet.Meteor.Debug/Sdk/Profiling/Trace.cs b/src/DotNet.Meteor.Debug/Sdk/Profiling/Trace.cs index 1480d09f..e84561f2 100644 --- a/src/DotNet.Meteor.Debug/Sdk/Profiling/Trace.cs +++ b/src/DotNet.Meteor.Debug/Sdk/Profiling/Trace.cs @@ -1,4 +1,3 @@ -using System; using System.IO; using System.Threading; using System.Threading.Tasks; @@ -6,7 +5,6 @@ using DotNet.Meteor.Processes; using TraceFileFormat = Microsoft.Diagnostics.Tools.Trace.TraceFileFormat; using TraceCollectHandler = Microsoft.Diagnostics.Tools.Trace.CollectCommandHandler; -// using GCDumpCollectHandler = Microsoft.Diagnostics.Tools.GCDump.CollectCommandHandler; namespace DotNet.Meteor.Debug.Sdk.Profiling; @@ -25,15 +23,6 @@ private static ProfilingTask CollectCore(int pid, string diagnosticPort, string var fileFormat = TraceFileFormat.Speedscope; var providers = string.Empty; - if (mode?.Equals("gcdump", StringComparison.OrdinalIgnoreCase) == true) { - //"Microsoft-DotNETRuntimeMonoProfiler:0x8900001:4:"; - //"Microsoft-DotNETRuntimeMonoProfiler:0xC900001:4"; - //"Microsoft-DotNETRuntimeMonoProfiler:0xC900003:4:heapcollect=ondemand"; - //"Microsoft-DotNETRuntimeMonoProfiler:0x4000000:4"; - providers = "Microsoft-DotNETRuntimeMonoProfiler:0xC900001:4"; - fileFormat = TraceFileFormat.NetTrace; - } - if (TraceCollectHandler.ProcessLogger.WriteLine == null) TraceCollectHandler.ProcessLogger.WriteLine = logger.OnOutputDataReceived; if (TraceCollectHandler.ProcessLogger.ErrorWriteLine == null) diff --git a/src/DotNet.Meteor.Debug/Session.cs b/src/DotNet.Meteor.Debug/Session.cs index f7dedc05..a664128d 100644 --- a/src/DotNet.Meteor.Debug/Session.cs +++ b/src/DotNet.Meteor.Debug/Session.cs @@ -5,6 +5,7 @@ using Microsoft.VisualStudio.Shared.VSCodeDebugProtocol; using Microsoft.VisualStudio.Shared.VSCodeDebugProtocol.Messages; using DotNet.Meteor.Shared; +using DotNet.Meteor.Debug.Extensions; namespace DotNet.Meteor.Debug; @@ -63,6 +64,6 @@ private void LogException(Exception ex) { if (ex is ProtocolException) throw ex; GetLogger().LogError($"[Handled] {ex.Message}", ex); - throw new ProtocolException(ex.Message); + ServerExtensions.ThrowException(ex.Message); } } \ No newline at end of file diff --git a/src/DotNet.Meteor.Shared/ProjectExtensions.cs b/src/DotNet.Meteor.Shared/ProjectExtensions.cs index a38d49e8..87c85c88 100644 --- a/src/DotNet.Meteor.Shared/ProjectExtensions.cs +++ b/src/DotNet.Meteor.Shared/ProjectExtensions.cs @@ -75,7 +75,11 @@ private static string GetPropertyValue(this Project project, string propertyName return resultSequence.ToString(); } - public static string FindOutputApplication(this Project project, string configuration, string framework, DeviceData device, Func errorHandler = null) { + public static string FindOutputApplication(this Project project, string configuration, DeviceData device, Func errorHandler = null) { + var framework = project.Frameworks.FirstOrDefault(it => it.ContainsInsensitive(device.Platform)); + if (string.IsNullOrEmpty(framework)) + return errorHandler?.Invoke($"Could not find targetFramework for platform {device.Platform}"); + var rootDirectory = Path.GetDirectoryName(project.Path); var baseOutputDirectory = Path.Combine(rootDirectory, "bin", configuration, framework); var outputAssemblyPath = string.Empty; diff --git a/src/DotNet.Meteor.Shared/Sdk/MicrosoftSdk.cs b/src/DotNet.Meteor.Shared/Sdk/MicrosoftSdk.cs index 31383245..343bd017 100644 --- a/src/DotNet.Meteor.Shared/Sdk/MicrosoftSdk.cs +++ b/src/DotNet.Meteor.Shared/Sdk/MicrosoftSdk.cs @@ -39,7 +39,10 @@ public static string DotNetRootLocation() { } public static bool ContainsInsensitive(this string source, string value) { - return source.IndexOf(value, StringComparison.OrdinalIgnoreCase) >= 0; + return source?.IndexOf(value, StringComparison.OrdinalIgnoreCase) >= 0; + } + public static bool EqualsInsensitive(this string source, string value) { + return source?.Equals(value, StringComparison.OrdinalIgnoreCase) == true; } public static string ToPlatformPath(this string path) { diff --git a/src/DotNet.Meteor.Shared/TrimmableContext.cs b/src/DotNet.Meteor.Shared/TrimmableContext.cs index bc89c41a..66f7317f 100644 --- a/src/DotNet.Meteor.Shared/TrimmableContext.cs +++ b/src/DotNet.Meteor.Shared/TrimmableContext.cs @@ -10,4 +10,5 @@ namespace DotNet.Meteor.Shared; [JsonSerializable(typeof(string))] [JsonSerializable(typeof(bool))] [JsonSerializable(typeof(int))] +[JsonSerializable(typeof(int[]))] public partial class TrimmableContext : JsonSerializerContext {} \ No newline at end of file diff --git a/src/VSCode.Extension/tasks/monoDebugConfigurationProvider.ts b/src/VSCode.Extension/tasks/monoDebugConfigurationProvider.ts index 865b3d92..545be321 100644 --- a/src/VSCode.Extension/tasks/monoDebugConfigurationProvider.ts +++ b/src/VSCode.Extension/tasks/monoDebugConfigurationProvider.ts @@ -1,11 +1,9 @@ import { ConfigurationController } from '../configurationController'; -import { StatusBarController } from '../statusbarController'; import { WorkspaceFolder, DebugConfiguration } from 'vscode'; import { Target } from '../models/target'; import * as res from '../resources/constants'; import * as vscode from 'vscode'; - export class MonoDebugConfigurationProvider implements vscode.DebugConfigurationProvider { async resolveDebugConfiguration(folder: WorkspaceFolder | undefined, config: DebugConfiguration, @@ -16,7 +14,7 @@ export class MonoDebugConfigurationProvider implements vscode.DebugConfiguration if (!ConfigurationController.isValid()) return undefined; - ConfigurationController.profiler = config['profilerMode']; + ConfigurationController.profiler = config.profilerMode; if (!config.noDebug && (ConfigurationController.target === Target.Release || ConfigurationController.profiler)) { vscode.window.showErrorMessage(res.messageDebugNotSupported); return undefined; @@ -27,10 +25,6 @@ export class MonoDebugConfigurationProvider implements vscode.DebugConfiguration } const targetDevice = { ...ConfigurationController.device }; - const targetProject = { ...ConfigurationController.project }; - - if (config.device !== undefined) - StatusBarController.performSelectDevice(StatusBarController.devices.find(d => d.name === config.device)); if (config.runtime !== undefined) targetDevice!.runtime_id = config.runtime; @@ -41,14 +35,15 @@ export class MonoDebugConfigurationProvider implements vscode.DebugConfiguration config.request = 'launch'; } - config['selectedDevice'] = targetDevice; - config['selectedProject'] = targetProject; - config['selectedTarget'] = ConfigurationController.target; - config['debuggingPort'] = ConfigurationController.getDebuggingPort(); - config['uninstallApp'] = ConfigurationController.getUninstallAppOption(); - config['reloadHost'] = ConfigurationController.getReloadHostPort(); - config['profilerPort'] = ConfigurationController.getProfilerPort(); - config['debuggerOptions'] = ConfigurationController.getDebuggerOptions(); + config.skipDebug = config.noDebug ?? false; + config.selectedDevice = targetDevice; + config.selectedProject = ConfigurationController.project; + config.selectedTarget = ConfigurationController.target; + config.debuggingPort = ConfigurationController.getDebuggingPort(); + config.uninstallApp = ConfigurationController.getUninstallAppOption(); + config.reloadHost = ConfigurationController.getReloadHostPort(); + config.profilerPort = ConfigurationController.getProfilerPort(); + config.debuggerOptions = ConfigurationController.getDebuggerOptions(); return config; }