From 77145535aed9a072623c034a090d2f5ed6ff2f1f Mon Sep 17 00:00:00 2001 From: Andrew Wang Date: Thu, 10 Mar 2022 13:31:52 -0800 Subject: [PATCH 01/11] Close pipeTransport writer on close (#1280) * Close pipeTransport writer on close This PR adds _writer.Close() for pipeTransport when it is being disposed. This ensures that the pipeProgram can read that the pipe has closed and should terminate. --- src/MICore/Transports/PipeTransport.cs | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/MICore/Transports/PipeTransport.cs b/src/MICore/Transports/PipeTransport.cs index a8716323d..93d9346d1 100644 --- a/src/MICore/Transports/PipeTransport.cs +++ b/src/MICore/Transports/PipeTransport.cs @@ -158,6 +158,12 @@ private void KillPipeProcessAndChildren(Process p) public override void Close() { + if (_process != null) + { + _process.EnableRaisingEvents = false; + _process.Exited -= OnProcessExit; + } + if (_writer != null) { try @@ -168,6 +174,15 @@ public override void Close() { // Ignore errors if logout couldn't be written } + + try + { + _writer.Close(); + } + catch (IOException) + { + // There are IO Issues with the writer, ignore since its shutting down. + } } base.Close(); @@ -181,8 +196,6 @@ public override void Close() if (_process != null) { - _process.EnableRaisingEvents = false; - _process.Exited -= OnProcessExit; if (_killOnClose && !_process.HasExited) { try From d2c322fb1af56e3d1800361a287a3f9c70e43d8f Mon Sep 17 00:00:00 2001 From: paulmaybee Date: Fri, 11 Mar 2022 13:45:34 -0800 Subject: [PATCH 02/11] fix 2nd echo processing with lldb (#1290) --- src/MICore/Debugger.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/MICore/Debugger.cs b/src/MICore/Debugger.cs index 1e22feb37..de2796da2 100755 --- a/src/MICore/Debugger.cs +++ b/src/MICore/Debugger.cs @@ -1253,10 +1253,10 @@ public void ProcessStdOutLine(string line) { WaitingOperationDescriptor waitingOperation; if (_waitingOperations.TryGetValue(id, out waitingOperation) && - !waitingOperation.EchoReceived && line == waitingOperation.Command) { // This is just the echo. Ignore. + // Sometimes with lldb we are seeing 2 command echos waitingOperation.EchoReceived = true; return; } @@ -1267,6 +1267,7 @@ public void ProcessStdOutLine(string line) switch (c) { case '~': + case '@': OnDebuggeeOutput(noprefix); // Console stream break; case '^': From 11fe3819d30036f772cce272137659d541947477 Mon Sep 17 00:00:00 2001 From: Andrew Wang Date: Fri, 25 Mar 2022 15:42:01 -0700 Subject: [PATCH 03/11] Add threadId to thread name (#1295) * Add threadId to thread name This PR adds a threadId to the Threads response to help users see what the thread is in the CallStacks view. * Addressing PR issues --- src/OpenDebugAD7/AD7DebugSession.cs | 32 +++++++++++++++++++++++++---- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/src/OpenDebugAD7/AD7DebugSession.cs b/src/OpenDebugAD7/AD7DebugSession.cs index 1987d4101..9db0c3b5a 100644 --- a/src/OpenDebugAD7/AD7DebugSession.cs +++ b/src/OpenDebugAD7/AD7DebugSession.cs @@ -9,6 +9,7 @@ using System.IO; using System.Linq; using System.Reflection; +using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using Microsoft.DebugEngineHost; @@ -2043,11 +2044,34 @@ protected override void HandleThreadsRequestAsync(IRequestResponder Date: Mon, 28 Mar 2022 18:46:42 -0700 Subject: [PATCH 04/11] Add Test Case for DataBreakpoints (#1296) * Add testing for data breakpoints * Address PR issues * Fix test * Got confused which test adapter we used --- src/OpenDebugAD7/AD7DebugSession.cs | 18 +-- test/CppTests/Tests/BreakpointTests.cs | 69 ++++++++++++ .../Commands/DataBreakpointInfoCommand.cs | 42 +++++++ .../DataBreakpointInfoResponseValue.cs | 17 +++ .../Responses/SetDataBreakpointsResponse.cs | 21 ++++ .../Commands/SetDataBreakpointsCommand.cs | 105 ++++++++++++++++++ .../Extensions/DebuggerRunnerExtensions.cs | 10 ++ 7 files changed, 273 insertions(+), 9 deletions(-) create mode 100644 test/DebuggerTesting/OpenDebug/Commands/DataBreakpointInfoCommand.cs create mode 100644 test/DebuggerTesting/OpenDebug/Commands/Responses/DataBreakpointInfoResponseValue.cs create mode 100644 test/DebuggerTesting/OpenDebug/Commands/Responses/SetDataBreakpointsResponse.cs create mode 100644 test/DebuggerTesting/OpenDebug/Commands/SetDataBreakpointsCommand.cs diff --git a/src/OpenDebugAD7/AD7DebugSession.cs b/src/OpenDebugAD7/AD7DebugSession.cs index 9db0c3b5a..e88d307ec 100644 --- a/src/OpenDebugAD7/AD7DebugSession.cs +++ b/src/OpenDebugAD7/AD7DebugSession.cs @@ -2459,7 +2459,7 @@ protected override void HandleDataBreakpointInfoRequestAsync(IRequestResponder() { DataBreakpointAccessType.Write }; } } - else if (response.Description == null) + else { - response.Description = string.Format(CultureInfo.CurrentCulture, AD7Resources.Error_DataBreakpointInfoFail, AD7Resources.Error_ChildPropertyNotFound); + throw new ProtocolException(string.Format(CultureInfo.CurrentCulture, AD7Resources.Error_DataBreakpointInfoFail, AD7Resources.Error_ChildPropertyNotFound)); } + + responder.SetResponse(response); } catch (Exception ex) { if (ex is AD7Exception ad7ex) response.Description = ad7ex.Message; + else if (ex is ProtocolException peEx) + responder.SetError(peEx); else - response.Description = string.Format(CultureInfo.CurrentCulture, AD7Resources.Error_DataBreakpointInfoFail, ""); - } - finally - { - responder.SetResponse(response); + responder.SetError(new ProtocolException(string.Format(CultureInfo.CurrentCulture, AD7Resources.Error_DataBreakpointInfoFail, ""))); } } diff --git a/test/CppTests/Tests/BreakpointTests.cs b/test/CppTests/Tests/BreakpointTests.cs index bb0be8c9b..d4b8be6df 100644 --- a/test/CppTests/Tests/BreakpointTests.cs +++ b/test/CppTests/Tests/BreakpointTests.cs @@ -424,6 +424,75 @@ public void ConditionalStringBreakpoints(ITestSettings settings) } } + [Theory] + [DependsOnTest(nameof(CompileKitchenSinkForBreakpointTests))] + [RequiresTestSettings] + // lldb-mi does not support -break-watch + [UnsupportedDebugger(SupportedDebugger.Lldb, SupportedArchitecture.x64 | SupportedArchitecture.x86)] + public void DataBreakpointTest(ITestSettings settings) + { + this.TestPurpose("Tests that data breakpoints work"); + this.WriteSettings(settings); + + IDebuggee debuggee = SinkHelper.Open(this, settings.CompilerSettings, DebuggeeMonikers.KitchenSink.Breakpoint); + + using (IDebuggerRunner runner = CreateDebugAdapterRunner(settings)) + { + runner.Launch(settings.DebuggerSettings, debuggee, "-fCalling"); + + SourceBreakpoints callingBreakpoints = debuggee.Breakpoints(SinkHelper.Calling, 15); + runner.SetBreakpoints(callingBreakpoints); + + runner.Expects.HitBreakpointEvent(SinkHelper.Calling, 15) + .AfterConfigurationDone(); + + using (IThreadInspector inspector = runner.GetThreadInspector()) + { + IFrameInspector mainFrame = inspector.Stack.First(); + mainFrame.GetVariable("total"); + + this.Comment("Get DataBreakpointInfo on 'total'"); + var response = runner.DataBreakpointInfo("total"); + Assert.NotNull(response?.body); + Assert.Contains("write", response.body.accessTypes); // Validate access type is "write" + Assert.Equal("When 'total' changes (8 bytes)", response.body.description, true, true, true); // Validate description matches DataBreakpointDisplayString + Assert.False(string.IsNullOrEmpty(response.body.dataId)); + Assert.EndsWith("total,8", response.body.dataId); // Validate dataId matches format
,, + + this.Comment("SetDataBreakpoint on 'total' Info"); + DataBreakpoints dataBreakpoints = new DataBreakpoints(); + dataBreakpoints.Add(response.body.dataId); + runner.SetDataBreakpoints(dataBreakpoints); + } + + this.Comment("Run to statement after data breakpoint"); + // Note this is going to be source line 15 for `i++`. + runner.Expects.HitBreakpointEvent(SinkHelper.Calling, 15) + .AfterContinue(); + + using (IThreadInspector inspector = runner.GetThreadInspector()) + { + IFrameInspector mainFrame = inspector.Stack.First(); + string value = mainFrame.GetVariable("total").Value; + Assert.True(double.TryParse(value, out double result)); + Assert.Equal(1.0, result); + } + + // Delete data breakpoint + this.Comment("Clear data breakpoint"); + runner.SetDataBreakpoints(new DataBreakpoints()); + + // Ensures that disabling the data bp works if it does not hit another + // stopping bp event but ends the program. + this.Comment("Run to completion"); + runner.Expects.ExitedEvent() + .TerminatedEvent() + .AfterContinue(); + + runner.DisconnectAndVerify(); + } + } + [Theory] [DependsOnTest(nameof(CompileKitchenSinkForBreakpointTests))] [RequiresTestSettings] diff --git a/test/DebuggerTesting/OpenDebug/Commands/DataBreakpointInfoCommand.cs b/test/DebuggerTesting/OpenDebug/Commands/DataBreakpointInfoCommand.cs new file mode 100644 index 000000000..323fe1a0d --- /dev/null +++ b/test/DebuggerTesting/OpenDebug/Commands/DataBreakpointInfoCommand.cs @@ -0,0 +1,42 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using DebuggerTesting.Compilation; +using DebuggerTesting.OpenDebug.Commands.Responses; +using Newtonsoft.Json; + +namespace DebuggerTesting.OpenDebug.Commands +{ + + #region DataBreakpointsInfoCommandArgs + + public sealed class DataBreakpointsInfoCommandArgs : JsonValue + { + public int? variableReference; + public string name; + } + + #endregion + + public class DataBreakpointsInfoCommand : CommandWithResponse + { + public DataBreakpointsInfoCommand() : base("dataBreakpointInfo") + { + } + + public DataBreakpointsInfoCommand(string name) : + this() + { + this.Args.name = name; + } + + public override string ToString() + { + return "{0} ({1})".FormatInvariantWithArgs(base.ToString(), this.Args.name); + } + } +} diff --git a/test/DebuggerTesting/OpenDebug/Commands/Responses/DataBreakpointInfoResponseValue.cs b/test/DebuggerTesting/OpenDebug/Commands/Responses/DataBreakpointInfoResponseValue.cs new file mode 100644 index 000000000..c40f4c071 --- /dev/null +++ b/test/DebuggerTesting/OpenDebug/Commands/Responses/DataBreakpointInfoResponseValue.cs @@ -0,0 +1,17 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace DebuggerTesting.OpenDebug.Commands.Responses +{ + public sealed class DataBreakpointsInfoResponseValue : CommandResponseValue + { + public sealed class Body + { + public string dataId; + public string description; + public string[] accessTypes; + public bool canPersist; + } + public Body body = new Body(); + } +} diff --git a/test/DebuggerTesting/OpenDebug/Commands/Responses/SetDataBreakpointsResponse.cs b/test/DebuggerTesting/OpenDebug/Commands/Responses/SetDataBreakpointsResponse.cs new file mode 100644 index 000000000..448bd49c2 --- /dev/null +++ b/test/DebuggerTesting/OpenDebug/Commands/Responses/SetDataBreakpointsResponse.cs @@ -0,0 +1,21 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace DebuggerTesting.OpenDebug.Commands.Responses +{ + public sealed class SetDataBreakpointsResponseValue : CommandResponseValue + { + public sealed class Body + { + public sealed class Breakpoint + { + public int? id; + public bool? verified; + public int? line; + public string message; + } + public Breakpoint[] breakpoints; + } + public Body body = new Body(); + } +} diff --git a/test/DebuggerTesting/OpenDebug/Commands/SetDataBreakpointsCommand.cs b/test/DebuggerTesting/OpenDebug/Commands/SetDataBreakpointsCommand.cs new file mode 100644 index 000000000..535af09a5 --- /dev/null +++ b/test/DebuggerTesting/OpenDebug/Commands/SetDataBreakpointsCommand.cs @@ -0,0 +1,105 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using DebuggerTesting.Compilation; +using DebuggerTesting.OpenDebug.Commands.Responses; +using Newtonsoft.Json; + +namespace DebuggerTesting.OpenDebug.Commands +{ + + #region SetBreakpointsCommandArgs + + public sealed class SetDataBreakpointsCommandArgs : JsonValue + { + public sealed class DataBreakpoint + { + public DataBreakpoint(string dataId, string accessTypes, string condition, string hitCondition) + { + this.dataId = dataId; + this.accessTypes = accessTypes; + this.condition = condition; + this.hitCondition = hitCondition; + } + + public string dataId; + + [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)] + public string accessTypes; + + [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)] + public string condition; + + [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)] + public string hitCondition; + } + public DataBreakpoint[] breakpoints; + } + + #endregion + + #region SourceBreakpoints + + /// + /// Contains information on all the breakpoints for a source file + /// + public sealed class DataBreakpoints + { + private List breakpoints; + + public DataBreakpoints() + { + this.breakpoints = new List(); + } + + public DataBreakpoints(params string[] dataIds) + : this() + { + foreach (string dataId in dataIds) + { + this.Add(dataId); + } + } + + public DataBreakpoints Add(string dataId, string accessType = "write", string condition = null, string hitCondition = null) + { + this.breakpoints.Add(new SetDataBreakpointsCommandArgs.DataBreakpoint(dataId, accessType, condition, null)); + return this; + } + + public DataBreakpoints Remove(string dataId) + { + this.breakpoints.RemoveAll(bp => String.Equals(bp.dataId, dataId, StringComparison.Ordinal)); + return this; + } + + internal IList Breakpoints + { + get { return this.breakpoints; } + } + } + + #endregion + + public class SetDataBreakpointsCommand : CommandWithResponse + { + public SetDataBreakpointsCommand() : base("setDataBreakpoints") + { + } + + public SetDataBreakpointsCommand(DataBreakpoints dataBreakpoints) : + this() + { + this.Args.breakpoints = dataBreakpoints.Breakpoints.ToArray(); + } + + public override string ToString() + { + return "{0} ({1})".FormatInvariantWithArgs(base.ToString(), String.Join(", ", this.Args.breakpoints.Select(bp => bp.dataId))); + } + } +} diff --git a/test/DebuggerTesting/OpenDebug/Extensions/DebuggerRunnerExtensions.cs b/test/DebuggerTesting/OpenDebug/Extensions/DebuggerRunnerExtensions.cs index 930e2ade6..195fa8225 100644 --- a/test/DebuggerTesting/OpenDebug/Extensions/DebuggerRunnerExtensions.cs +++ b/test/DebuggerTesting/OpenDebug/Extensions/DebuggerRunnerExtensions.cs @@ -212,6 +212,16 @@ public static SetBreakpointsResponseValue SetInstructionBreakpoints(this IDebugg return runner.RunCommand(new SetInstructionBreakpointsCommand(breakpoints)); } + public static DataBreakpointsInfoResponseValue DataBreakpointInfo(this IDebuggerRunner runner, string name) + { + return runner.RunCommand(new DataBreakpointsInfoCommand(name)); + } + + public static SetDataBreakpointsResponseValue SetDataBreakpoints(this IDebuggerRunner runner, DataBreakpoints breakpoints) + { + return runner.RunCommand(new SetDataBreakpointsCommand(breakpoints)); + } + public static string[] CompletionsRequest(this IDebuggerRunner runner, string text) { CompletionItem[] completionItems = runner.RunCommand(new CompletionsCommand(null, text, 0, null))?.body?.targets; From 8d70a37d2243862eccba70a6226bf684708871c7 Mon Sep 17 00:00:00 2001 From: Andrew Wang Date: Tue, 29 Mar 2022 13:55:00 -0700 Subject: [PATCH 05/11] Remove downgrade to GNU toolchain (#1298) --- .github/workflows/Build-And-Test.yml | 51 ---------------------------- 1 file changed, 51 deletions(-) diff --git a/.github/workflows/Build-And-Test.yml b/.github/workflows/Build-And-Test.yml index e9830b400..8fe1d1ca3 100644 --- a/.github/workflows/Build-And-Test.yml +++ b/.github/workflows/Build-And-Test.yml @@ -85,57 +85,6 @@ jobs: install: >- mingw-w64-x86_64-toolchain - # As of Nov 8, 2021, mingw-w64-x86_64-toolchain-11.1.x - # causes GDB to return "During startup program exited with code 0xc0000139" - # Downgrade to working toolchain (04-May-2021) - # TODO: Remove this task when there is a newer version of toolchain than 11.1.x - - shell: msys2 {0} - name: Downgrade toolchain to 10.x - run: | - # Download GDB - DOWNLOAD_DOWNGRADED_GDB_PATH='${{ github.workspace }}/mingw-w64-x86_64-gdb-10.2-2-any.pkg.tar.zst' - DOWNLOAD_DOWNGRADED_GDB_PATH=${DOWNLOAD_DOWNGRADED_GDB_PATH//\\//} - wget -O ${DOWNLOAD_DOWNGRADED_GDB_PATH} http://repo.msys2.org/mingw/x86_64/mingw-w64-x86_64-gdb-10.2-2-any.pkg.tar.zst - - # Download GCC - DOWNLOAD_DOWNGRADED_GCC_PATH='${{ github.workspace }}/mingw-w64-x86_64-gcc-10.3.0-8-any.pkg.tar.zst' - DOWNLOAD_DOWNGRADED_GCC_PATH=${DOWNLOAD_DOWNGRADED_GCC_PATH//\\//} - wget -O ${DOWNLOAD_DOWNGRADED_GCC_PATH} http://repo.msys2.org/mingw/x86_64/mingw-w64-x86_64-gcc-10.3.0-8-any.pkg.tar.zst - - # Download GCC Lib - DOWNLOAD_DOWNGRADED_GCCLIB_PATH='${{ github.workspace }}/mingw-w64-x86_64-gcc-libs-10.3.0-8-any.pkg.tar.zst' - DOWNLOAD_DOWNGRADED_GCCLIB_PATH=${DOWNLOAD_DOWNGRADED_GCCLIB_PATH//\\//} - wget -O ${DOWNLOAD_DOWNGRADED_GCCLIB_PATH} http://repo.msys2.org/mingw/x86_64/mingw-w64-x86_64-gcc-libs-10.3.0-8-any.pkg.tar.zst - - # Download GCC Ada - DOWNLOAD_DOWNGRADED_ADA_PATH='${{ github.workspace }}/mingw-w64-x86_64-gcc-ada-10.3.0-8-any.pkg.tar.zst' - DOWNLOAD_DOWNGRADED_ADA_PATH=${DOWNLOAD_DOWNGRADED_ADA_PATH//\\//} - wget -O ${DOWNLOAD_DOWNGRADED_ADA_PATH} http://repo.msys2.org/mingw/x86_64/mingw-w64-x86_64-gcc-ada-10.3.0-8-any.pkg.tar.zst - - # Download GCC Fortran - DOWNLOAD_DOWNGRADED_FORTRAN_PATH='${{ github.workspace }}/mingw-w64-x86_64-gcc-fortran-10.3.0-8-any.pkg.tar.zst' - DOWNLOAD_DOWNGRADED_FORTRAN_PATH=${DOWNLOAD_DOWNGRADED_FORTRAN_PATH//\\//} - wget -O ${DOWNLOAD_DOWNGRADED_FORTRAN_PATH} http://repo.msys2.org/mingw/x86_64/mingw-w64-x86_64-gcc-fortran-10.3.0-8-any.pkg.tar.zst - - # Download GCC Libfortran - DOWNLOAD_DOWNGRADED_LIBFORTRAN_PATH='${{ github.workspace }}/mingw-w64-x86_64-gcc-libgfortran-10.3.0-8-any.pkg.tar.zst' - DOWNLOAD_DOWNGRADED_LIBFORTRAN_PATH=${DOWNLOAD_DOWNGRADED_LIBFORTRAN_PATH//\\//} - wget -O ${DOWNLOAD_DOWNGRADED_LIBFORTRAN_PATH} http://repo.msys2.org/mingw/x86_64/mingw-w64-x86_64-gcc-libgfortran-10.3.0-8-any.pkg.tar.zst - - # Download GCC Obj-C - DOWNLOAD_DOWNGRADED_OBJC_PATH='${{ github.workspace }}/mingw-w64-x86_64-gcc-objc-10.3.0-8-any.pkg.tar.zst' - DOWNLOAD_DOWNGRADED_OBJC_PATH=${DOWNLOAD_DOWNGRADED_OBJC_PATH//\\//} - wget -O ${DOWNLOAD_DOWNGRADED_OBJC_PATH} http://repo.msys2.org/mingw/x86_64/mingw-w64-x86_64-gcc-objc-10.3.0-8-any.pkg.tar.zst - - # Download GCC libgccjit - DOWNLOAD_DOWNGRADED_LIBGCCJIT_PATH='${{ github.workspace }}/mingw-w64-x86_64-libgccjit-10.3.0-8-any.pkg.tar.zst' - DOWNLOAD_DOWNGRADED_LIBGCCJIT_PATH=${DOWNLOAD_DOWNGRADED_LIBGCCJIT_PATH//\\//} - wget -O ${DOWNLOAD_DOWNGRADED_LIBGCCJIT_PATH} http://repo.msys2.org/mingw/x86_64/mingw-w64-x86_64-libgccjit-10.3.0-8-any.pkg.tar.zst - - # Install - pacman -U --noconfirm $DOWNLOAD_DOWNGRADED_GDB_PATH $DOWNLOAD_DOWNGRADED_GCC_PATH $DOWNLOAD_DOWNGRADED_GCCLIB_PATH $DOWNLOAD_DOWNGRADED_ADA_PATH $DOWNLOAD_DOWNGRADED_FORTRAN_PATH $DOWNLOAD_DOWNGRADED_LIBFORTRAN_PATH $DOWNLOAD_DOWNGRADED_OBJC_PATH $DOWNLOAD_DOWNGRADED_LIBGCCJIT_PATH - - - shell: msys2 {0} name: Gather c++ toolchain paths run: | From 5db235308e6c446c21365e6b88323b8f7a2307de Mon Sep 17 00:00:00 2001 From: Andrew Wang Date: Wed, 30 Mar 2022 14:11:13 -0700 Subject: [PATCH 06/11] EnsureSettingsUpdated returning null for Task (#1299) * EnsureSettingsUpdated returning null for Task * Addressing PR issues --- src/MIDebugEngine/Engine.Impl/ExceptionManager.cs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/MIDebugEngine/Engine.Impl/ExceptionManager.cs b/src/MIDebugEngine/Engine.Impl/ExceptionManager.cs index 5a9f1d7c8..068d2a01f 100644 --- a/src/MIDebugEngine/Engine.Impl/ExceptionManager.cs +++ b/src/MIDebugEngine/Engine.Impl/ExceptionManager.cs @@ -379,12 +379,13 @@ public Task EnsureSettingsUpdated() { lock (_updateLock) { - if (_updateTask != null) + Task updateTask = _updateTask; + if (updateTask != null) { // If we are still delaying our processing, stop delaying it - _updateDelayCancelSource.Cancel(); + _updateDelayCancelSource?.Cancel(); - return _updateTask; + return updateTask; } else if (!_initialSettingssSent && _categoryMap.Count > 0) { @@ -395,7 +396,7 @@ public Task EnsureSettingsUpdated() // immediately cancel the delay since we don't want one _updateDelayCancelSource = new CancellationTokenSource(); _updateDelayCancelSource.Cancel(); - Task updateTask = FlushSettingsUpdates(); + updateTask = FlushSettingsUpdates(); if (!updateTask.IsCompleted) { _updateTask = updateTask; @@ -405,7 +406,7 @@ public Task EnsureSettingsUpdated() else { // No task is running, so just return an already signaled task - return Task.FromResult(null); + return Task.CompletedTask; } } } From 1d52791f01a13de392dd09191c14b6ecad7a781e Mon Sep 17 00:00:00 2001 From: Andrew Wang Date: Wed, 30 Mar 2022 18:15:05 -0700 Subject: [PATCH 07/11] Fix GitHub Action Test runs (#1301) * Fix GitHub Action Test runs This PR revert "Remove downgrade to GNU toolchain (#1298)" since machines seem to be flaky on which versions of GDB we get. This reverts commit 8d70a37d2243862eccba70a6226bf684708871c7. Also does a null check on writer.close since that seems to be the cause of most tests failing when disconnecting. --- .github/workflows/Build-And-Test.yml | 54 +++++++++++++++++++++++++- src/MICore/Transports/PipeTransport.cs | 2 +- 2 files changed, 54 insertions(+), 2 deletions(-) diff --git a/.github/workflows/Build-And-Test.yml b/.github/workflows/Build-And-Test.yml index 8fe1d1ca3..b5529230c 100644 --- a/.github/workflows/Build-And-Test.yml +++ b/.github/workflows/Build-And-Test.yml @@ -85,6 +85,57 @@ jobs: install: >- mingw-w64-x86_64-toolchain + # As of Nov 8, 2021, mingw-w64-x86_64-toolchain-11.1.x + # causes GDB to return "During startup program exited with code 0xc0000139" + # Downgrade to working toolchain (04-May-2021) + # TODO: Remove this task when there is a newer version of toolchain than 11.1.x + - shell: msys2 {0} + name: Downgrade toolchain to 10.x + run: | + # Download GDB + DOWNLOAD_DOWNGRADED_GDB_PATH='${{ github.workspace }}/mingw-w64-x86_64-gdb-10.2-2-any.pkg.tar.zst' + DOWNLOAD_DOWNGRADED_GDB_PATH=${DOWNLOAD_DOWNGRADED_GDB_PATH//\\//} + wget -O ${DOWNLOAD_DOWNGRADED_GDB_PATH} http://repo.msys2.org/mingw/x86_64/mingw-w64-x86_64-gdb-10.2-2-any.pkg.tar.zst + + # Download GCC + DOWNLOAD_DOWNGRADED_GCC_PATH='${{ github.workspace }}/mingw-w64-x86_64-gcc-10.3.0-8-any.pkg.tar.zst' + DOWNLOAD_DOWNGRADED_GCC_PATH=${DOWNLOAD_DOWNGRADED_GCC_PATH//\\//} + wget -O ${DOWNLOAD_DOWNGRADED_GCC_PATH} http://repo.msys2.org/mingw/x86_64/mingw-w64-x86_64-gcc-10.3.0-8-any.pkg.tar.zst + + # Download GCC Lib + DOWNLOAD_DOWNGRADED_GCCLIB_PATH='${{ github.workspace }}/mingw-w64-x86_64-gcc-libs-10.3.0-8-any.pkg.tar.zst' + DOWNLOAD_DOWNGRADED_GCCLIB_PATH=${DOWNLOAD_DOWNGRADED_GCCLIB_PATH//\\//} + wget -O ${DOWNLOAD_DOWNGRADED_GCCLIB_PATH} http://repo.msys2.org/mingw/x86_64/mingw-w64-x86_64-gcc-libs-10.3.0-8-any.pkg.tar.zst + + # Download GCC Ada + DOWNLOAD_DOWNGRADED_ADA_PATH='${{ github.workspace }}/mingw-w64-x86_64-gcc-ada-10.3.0-8-any.pkg.tar.zst' + DOWNLOAD_DOWNGRADED_ADA_PATH=${DOWNLOAD_DOWNGRADED_ADA_PATH//\\//} + wget -O ${DOWNLOAD_DOWNGRADED_ADA_PATH} http://repo.msys2.org/mingw/x86_64/mingw-w64-x86_64-gcc-ada-10.3.0-8-any.pkg.tar.zst + + # Download GCC Fortran + DOWNLOAD_DOWNGRADED_FORTRAN_PATH='${{ github.workspace }}/mingw-w64-x86_64-gcc-fortran-10.3.0-8-any.pkg.tar.zst' + DOWNLOAD_DOWNGRADED_FORTRAN_PATH=${DOWNLOAD_DOWNGRADED_FORTRAN_PATH//\\//} + wget -O ${DOWNLOAD_DOWNGRADED_FORTRAN_PATH} http://repo.msys2.org/mingw/x86_64/mingw-w64-x86_64-gcc-fortran-10.3.0-8-any.pkg.tar.zst + + # Download GCC Libfortran + DOWNLOAD_DOWNGRADED_LIBFORTRAN_PATH='${{ github.workspace }}/mingw-w64-x86_64-gcc-libgfortran-10.3.0-8-any.pkg.tar.zst' + DOWNLOAD_DOWNGRADED_LIBFORTRAN_PATH=${DOWNLOAD_DOWNGRADED_LIBFORTRAN_PATH//\\//} + wget -O ${DOWNLOAD_DOWNGRADED_LIBFORTRAN_PATH} http://repo.msys2.org/mingw/x86_64/mingw-w64-x86_64-gcc-libgfortran-10.3.0-8-any.pkg.tar.zst + + # Download GCC Obj-C + DOWNLOAD_DOWNGRADED_OBJC_PATH='${{ github.workspace }}/mingw-w64-x86_64-gcc-objc-10.3.0-8-any.pkg.tar.zst' + DOWNLOAD_DOWNGRADED_OBJC_PATH=${DOWNLOAD_DOWNGRADED_OBJC_PATH//\\//} + wget -O ${DOWNLOAD_DOWNGRADED_OBJC_PATH} http://repo.msys2.org/mingw/x86_64/mingw-w64-x86_64-gcc-objc-10.3.0-8-any.pkg.tar.zst + + # Download GCC libgccjit + DOWNLOAD_DOWNGRADED_LIBGCCJIT_PATH='${{ github.workspace }}/mingw-w64-x86_64-libgccjit-10.3.0-8-any.pkg.tar.zst' + DOWNLOAD_DOWNGRADED_LIBGCCJIT_PATH=${DOWNLOAD_DOWNGRADED_LIBGCCJIT_PATH//\\//} + wget -O ${DOWNLOAD_DOWNGRADED_LIBGCCJIT_PATH} http://repo.msys2.org/mingw/x86_64/mingw-w64-x86_64-libgccjit-10.3.0-8-any.pkg.tar.zst + + # Install + pacman -U --noconfirm $DOWNLOAD_DOWNGRADED_GDB_PATH $DOWNLOAD_DOWNGRADED_GCC_PATH $DOWNLOAD_DOWNGRADED_GCCLIB_PATH $DOWNLOAD_DOWNGRADED_ADA_PATH $DOWNLOAD_DOWNGRADED_FORTRAN_PATH $DOWNLOAD_DOWNGRADED_LIBFORTRAN_PATH $DOWNLOAD_DOWNGRADED_OBJC_PATH $DOWNLOAD_DOWNGRADED_LIBGCCJIT_PATH + + - shell: msys2 {0} name: Gather c++ toolchain paths run: | @@ -94,7 +145,8 @@ jobs: gdb --version - - run: > + - run: | + set PATH="%PATH%;D:\a\_temp\msys64\mingw64\bin\" dotnet test ${{ github.workspace }}\bin\DebugAdapterProtocolTests\Debug\CppTests\CppTests.dll --logger "trx;LogFileName=${{ github.workspace }}\bin\DebugAdapterProtocolTests\Debug\CppTests\results.trx" - name: 'Upload Test Results' diff --git a/src/MICore/Transports/PipeTransport.cs b/src/MICore/Transports/PipeTransport.cs index 93d9346d1..b71e53b26 100644 --- a/src/MICore/Transports/PipeTransport.cs +++ b/src/MICore/Transports/PipeTransport.cs @@ -177,7 +177,7 @@ public override void Close() try { - _writer.Close(); + _writer?.Close(); } catch (IOException) { From 56f50095b04aec5c3124190440d717ff22f02904 Mon Sep 17 00:00:00 2001 From: Andrew Wang Date: Thu, 31 Mar 2022 17:23:17 -0700 Subject: [PATCH 08/11] Fix deleting breakpoints created as pending (#1302) * Fix deleting breakpoints created as pending This PR fixes https://github.com/microsoft/vscode-cpptools/issues/9095. The issue is that breakpoints that are created on debug session start have not had their module loaded yet and BoundBreakpoints will be null and with the current check we will return E_FAIL and never cache the breakpoint. The original usage of this was to capture errors for data breakpoints, but they will come through as BreakpointEvents instead of the response of SetBreakpoints / SetDataBreakpoints. * Address PR issue and fix #1129 --- .../AD7.Impl/AD7PendingBreakpoint.cs | 25 +++++++------------ 1 file changed, 9 insertions(+), 16 deletions(-) diff --git a/src/MIDebugEngine/AD7.Impl/AD7PendingBreakpoint.cs b/src/MIDebugEngine/AD7.Impl/AD7PendingBreakpoint.cs index 3dab46a82..f929c73f2 100644 --- a/src/MIDebugEngine/AD7.Impl/AD7PendingBreakpoint.cs +++ b/src/MIDebugEngine/AD7.Impl/AD7PendingBreakpoint.cs @@ -321,24 +321,8 @@ private int BindWithTimeout() this.SetError(new AD7ErrorBreakpoint(this, ResourceStrings.LongBind, enum_BP_ERROR_TYPE.BPET_SEV_LOW | enum_BP_ERROR_TYPE.BPET_TYPE_WARNING), true); return Constants.S_FALSE; } - else if (this._BPError != null) - { - // Ran into some sort of error - return Constants.E_FAIL; - } else { - if ((enum_BP_LOCATION_TYPE)_bpRequestInfo.bpLocation.bpLocationType == enum_BP_LOCATION_TYPE.BPLT_DATA_STRING) - { - lock (_engine.DebuggedProcess.DataBreakpointVariables) - { - string addressName = HostMarshal.GetDataBreakpointStringForIntPtr(_bpRequestInfo.bpLocation.unionmember3); - if (!_engine.DebuggedProcess.DataBreakpointVariables.Contains(addressName)) // might need to expand condition - { - _engine.DebuggedProcess.DataBreakpointVariables.Add(addressName); - } - } - } return Constants.S_OK; } } @@ -372,6 +356,15 @@ internal async Task BindAsync() try { bindResult = await PendingBreakpoint.Bind(_address, _size, _engine.DebuggedProcess, _condition, this); + + lock (_engine.DebuggedProcess.DataBreakpointVariables) + { + string address = AddressId ?? _address; + if (!_engine.DebuggedProcess.DataBreakpointVariables.Contains(address)) // might need to expand condition + { + _engine.DebuggedProcess.DataBreakpointVariables.Add(address); + } + } } catch (ArgumentException ex) { From 11b2bca15dce73e7f15a92cfb899adf32820aa2a Mon Sep 17 00:00:00 2001 From: paulmaybee Date: Thu, 31 Mar 2022 18:04:16 -0700 Subject: [PATCH 09/11] Visual Studio: add support for custom UI visualizers (#1281) * add support for uivisualizers * fix csproj error * add ref to vscode host * Add VisualizerId type, other review comments * cleanup last nit * remove unused field --- IL/Microsoft.Internal.VisualStudio.Interop.il | 60 ++++++++++++++++++ .../DebugEngineHost.ref.cs | 4 +- ...rosoft.VisualStudio.Debugger.Interop.MI.cs | 24 ++++++++ .../DebugEngineHost.VSCode.csproj | 1 + .../HostNatvisProject.cs | 2 +- src/DebugEngineHost/DebugEngineHost.csproj | 1 + src/DebugEngineHost/HostNatvisProject.cs | 40 ++++++++++-- src/MIDebugEngine/AD7.Impl/AD7Property.cs | 52 ++++++++++++++-- src/MIDebugEngine/Natvis.Impl/Natvis.cs | 61 ++++++++++++++++--- 9 files changed, 223 insertions(+), 22 deletions(-) create mode 100644 src/DebugEngineHost.Stub/Shared/Microsoft.VisualStudio.Debugger.Interop.MI.cs diff --git a/IL/Microsoft.Internal.VisualStudio.Interop.il b/IL/Microsoft.Internal.VisualStudio.Interop.il index a49ba3920..d4446fd50 100644 --- a/IL/Microsoft.Internal.VisualStudio.Interop.il +++ b/IL/Microsoft.Internal.VisualStudio.Interop.il @@ -918,4 +918,64 @@ .get instance class [mscorlib]System.Array Microsoft.Internal.VisualStudio.Shell.Interop.IVsTelemetryPropertyBag::get_AllPropertyNames() } } + .class public interface abstract import IVsExtensionManagerPrivate + { + .custom instance void [mscorlib]System.Runtime.InteropServices.GuidAttribute::.ctor(string) = { string('753E55C6-E779-4A7A-BCD1-FD87181D52C0') } + .custom instance void [mscorlib]System.Runtime.InteropServices.InterfaceTypeAttribute::.ctor(valuetype [mscorlib]System.Runtime.InteropServices.ComInterfaceType) = { int32(1) } + .method public virtual hidebysig newslot abstract + instance int32 GetEnabledExtensionContentLocations([in] string marshal(lpwstr) szContentTypeName, [in] uint32 cContentLocations, [out] string[] marshal(bstr[ + 1]) rgbstrContentLocations, [out] string[] marshal(bstr[ + 1]) rgbstrUniqueExtensionStrings, [out] uint32& pcContentLocations) + preservesig internalcall + { + .param [1] + .custom instance void [mscorlib]System.Runtime.InteropServices.ComAliasNameAttribute::.ctor(string) = { string('OLE.LPCWSTR') } + .param [2] + .custom instance void [mscorlib]System.Runtime.InteropServices.ComAliasNameAttribute::.ctor(string) = { string('OLE.ULONG') } + .param [5] + .custom instance void [mscorlib]System.Runtime.InteropServices.ComAliasNameAttribute::.ctor(string) = { string('OLE.ULONG') } + } + .method public virtual hidebysig newslot abstract + instance int32 GetEnabledExtensionContentLocationsWithNames([in] string marshal(lpwstr) szContentTypeName, [in] uint32 cContentLocations, [out] string[] marshal(bstr[ + 1]) rgbstrContentLocations, [out] string[] marshal(bstr[ + 1]) rgbstrUniqueExtensionStrings, [out] string[] marshal(bstr[ + 1]) rgbstrExtensionNames, [out] uint32& pcContentLocations) + preservesig internalcall + { + .param [1] + .custom instance void [mscorlib]System.Runtime.InteropServices.ComAliasNameAttribute::.ctor(string) = { string('OLE.LPCWSTR') } + .param [2] + .custom instance void [mscorlib]System.Runtime.InteropServices.ComAliasNameAttribute::.ctor(string) = { string('OLE.ULONG') } + .param [6] + .custom instance void [mscorlib]System.Runtime.InteropServices.ComAliasNameAttribute::.ctor(string) = { string('OLE.ULONG') } + } + .method public virtual hidebysig newslot abstract + instance int32 GetDisabledExtensionContentLocations([in] string marshal(lpwstr) szContentTypeName, [in] uint32 cContentLocations, [out] string[] marshal(bstr[ + 1]) rgbstrContentLocations, [out] string[] marshal(bstr[ + 1]) rgbstrUniqueExtensionStrings, [out] uint32& pcContentLocations) + preservesig internalcall + { + .param [1] + .custom instance void [mscorlib]System.Runtime.InteropServices.ComAliasNameAttribute::.ctor(string) = { string('OLE.LPCWSTR') } + .param [2] + .custom instance void [mscorlib]System.Runtime.InteropServices.ComAliasNameAttribute::.ctor(string) = { string('OLE.ULONG') } + .param [5] + .custom instance void [mscorlib]System.Runtime.InteropServices.ComAliasNameAttribute::.ctor(string) = { string('OLE.ULONG') } + } + .method public virtual hidebysig newslot abstract + instance int32 GetLastConfigurationChange([out] valuetype [mscorlib]System.DateTime[] marshal([]) pTimestamp) + preservesig internalcall + { + } + .method public virtual hidebysig newslot abstract + instance int32 LogAllInstalledExtensions() + preservesig internalcall + { + } + .method public virtual hidebysig newslot abstract + instance int32 GetUniqueExtensionString([in] string marshal(lpwstr) szExtensionIdentifier, [out] string& marshal(bstr) pbstrUniqueString) + preservesig internalcall + { + .param [1] + .custom instance void [mscorlib]System.Runtime.InteropServices.ComAliasNameAttribute::.ctor(string) = { string('OLE.LPCWSTR') } + } + } + .class public interface abstract import SVsExtensionManager + { + .custom instance void [mscorlib]System.Runtime.InteropServices.GuidAttribute::.ctor(string) = { string('316F4DE6-3CA4-4F0D-B003-962D28F65238') } + .custom instance void [mscorlib]System.Runtime.InteropServices.InterfaceTypeAttribute::.ctor(valuetype [mscorlib]System.Runtime.InteropServices.ComInterfaceType) = { int32(1) } + } } \ No newline at end of file diff --git a/src/DebugEngineHost.Stub/DebugEngineHost.ref.cs b/src/DebugEngineHost.Stub/DebugEngineHost.ref.cs index f6bc4f5c6..9d528c1bd 100644 --- a/src/DebugEngineHost.Stub/DebugEngineHost.ref.cs +++ b/src/DebugEngineHost.Stub/DebugEngineHost.ref.cs @@ -385,11 +385,11 @@ public static class HostNatvisProject public delegate void NatvisLoader(string path); /// - /// Searches the solution for natvis files, invoking the loader on any which are found. + /// Searches the solution and VSIXs for natvis files, invoking the loader on any which are found. /// /// Natvis loader method to invoke [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Natvis")] - public static void FindNatvisInSolution(NatvisLoader loader) + public static void FindNatvis(NatvisLoader loader) { throw new NotImplementedException(); } diff --git a/src/DebugEngineHost.Stub/Shared/Microsoft.VisualStudio.Debugger.Interop.MI.cs b/src/DebugEngineHost.Stub/Shared/Microsoft.VisualStudio.Debugger.Interop.MI.cs new file mode 100644 index 000000000..ccab923cf --- /dev/null +++ b/src/DebugEngineHost.Stub/Shared/Microsoft.VisualStudio.Debugger.Interop.MI.cs @@ -0,0 +1,24 @@ +// // Copyright (c) Microsoft. All rights reserved. +// // Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Runtime.InteropServices; + +namespace Microsoft.VisualStudio.Debugger.Interop.MI +{ + /// + /// IDebugProperty for MIEngine + /// + [ComImport()] + [ComVisible(true)] + [Guid("27F5EFAF-9DBA-4AC0-A456-1F97E50F3CDA")] + [InterfaceType(1)] + public interface IDebugMIEngineProperty + { + /// + /// Get the expression context for the property + /// + [PreserveSig] + int GetExpressionContext([Out, MarshalAs(UnmanagedType.Interface)] out IDebugExpressionContext2 ppExpressionContext); + } +} diff --git a/src/DebugEngineHost.VSCode/DebugEngineHost.VSCode.csproj b/src/DebugEngineHost.VSCode/DebugEngineHost.VSCode.csproj index 8a7047f53..eef5e304b 100644 --- a/src/DebugEngineHost.VSCode/DebugEngineHost.VSCode.csproj +++ b/src/DebugEngineHost.VSCode/DebugEngineHost.VSCode.csproj @@ -22,6 +22,7 @@ + diff --git a/src/DebugEngineHost.VSCode/HostNatvisProject.cs b/src/DebugEngineHost.VSCode/HostNatvisProject.cs index 9d64954e0..7456f3b1d 100644 --- a/src/DebugEngineHost.VSCode/HostNatvisProject.cs +++ b/src/DebugEngineHost.VSCode/HostNatvisProject.cs @@ -9,7 +9,7 @@ public static class HostNatvisProject { public delegate void NatvisLoader(string path); - public static void FindNatvisInSolution(NatvisLoader loader) + public static void FindNatvis(NatvisLoader loader) { // In-solution natvis is not supported for VS Code now, so do nothing. } diff --git a/src/DebugEngineHost/DebugEngineHost.csproj b/src/DebugEngineHost/DebugEngineHost.csproj index 1be486b00..af2ead4bf 100755 --- a/src/DebugEngineHost/DebugEngineHost.csproj +++ b/src/DebugEngineHost/DebugEngineHost.csproj @@ -28,6 +28,7 @@ + diff --git a/src/DebugEngineHost/HostNatvisProject.cs b/src/DebugEngineHost/HostNatvisProject.cs index 7b2884249..5ccf3974d 100644 --- a/src/DebugEngineHost/HostNatvisProject.cs +++ b/src/DebugEngineHost/HostNatvisProject.cs @@ -9,6 +9,7 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; +using Microsoft.Internal.VisualStudio.Shell.Interop; using Microsoft.VisualStudio; using Microsoft.VisualStudio.ComponentModelHost; using Microsoft.VisualStudio.Shell.Interop; @@ -29,17 +30,18 @@ public static class HostNatvisProject public delegate void NatvisLoader(string path); /// - /// Searches the solution for natvis files, invoking the loader on any which are found. + /// Searches the solution and VSIXs for natvis files, invoking the loader on any which are found. /// /// Natvis loader method to invoke - public static void FindNatvisInSolution(NatvisLoader loader) + public static void FindNatvis(NatvisLoader loader) { List paths = new List(); try { - ThreadHelper.JoinableTaskFactory.Run(async () => - await Internal.FindNatvisInSolutionImplAsync(paths) - ); + ThreadHelper.JoinableTaskFactory.Run(async () => { + await Internal.FindNatvisInSolutionImplAsync(paths); + Internal.FindNatvisInVSIXImpl(paths); + }); } catch (Exception) { @@ -193,6 +195,17 @@ public async static System.Threading.Tasks.Task FindNatvisInSolutionImplAsync(Li } } + public static void FindNatvisInVSIXImpl(List paths) + { + var extManager = (IVsExtensionManagerPrivate)Package.GetGlobalService(typeof(SVsExtensionManager)); + if (extManager == null) + { + return; // failed to find the extension manager + } + + BuildEnvironmentPath("NativeCrossPlatformVisualizer", extManager, paths); + } + public static string FindSolutionRootImpl() { string root = null; @@ -207,6 +220,23 @@ public static string FindSolutionRootImpl() return root; } + private static void BuildEnvironmentPath(string name, IVsExtensionManagerPrivate pem, List paths) + { + pem.GetEnabledExtensionContentLocations(name, 0, null, null, out var contentLocations); + if (contentLocations > 0) + { + var rgStrings = new string[contentLocations]; + var rgbstrContentLocations = new string[contentLocations]; + var rgbstrUniqueStrings = new string[contentLocations]; + + var hr = pem.GetEnabledExtensionContentLocations(name, contentLocations, rgbstrContentLocations, rgbstrUniqueStrings, out var actualContentLocations); + if (hr == VSConstants.S_OK && actualContentLocations > 0) + { + paths.AddRange(rgbstrContentLocations); + } + } + } + private static void LoadNatvisFromProject(IVsHierarchy hier, List paths, bool solutionLevel) { IVsProject4 proj = hier as IVsProject4; diff --git a/src/MIDebugEngine/AD7.Impl/AD7Property.cs b/src/MIDebugEngine/AD7.Impl/AD7Property.cs index 596068765..2601d2c48 100644 --- a/src/MIDebugEngine/AD7.Impl/AD7Property.cs +++ b/src/MIDebugEngine/AD7.Impl/AD7Property.cs @@ -1,10 +1,14 @@ // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -using Microsoft.VisualStudio.Debugger.Interop; using System; +using System.Collections.Generic; using System.Diagnostics; using System.Linq; +using System.Runtime.InteropServices; +using Microsoft.MIDebugEngine.Natvis; +using Microsoft.VisualStudio.Debugger.Interop; +using Microsoft.VisualStudio.Debugger.Interop.MI; namespace Microsoft.MIDebugEngine { @@ -13,10 +17,11 @@ namespace Microsoft.MIDebugEngine // The property is usually the result of an expression evaluation. // // The sample engine only supports locals and parameters for functions that have symbols loaded. - internal class AD7Property : IDebugProperty3, IDebugProperty160 + internal class AD7Property : IDebugProperty3, IDebugProperty160, IDebugMIEngineProperty { private static uint s_maxChars = 1000000; private byte[] _bytes; + private VisualizerId[] _uiVisualizers = null; private AD7Engine _engine; private IVariableInformation _variableInformation; @@ -67,7 +72,7 @@ public DEBUG_PROPERTY_INFO ConstructDebugPropertyInfo(enum_DEBUGPROP_INFO_FLAGS if ((dwFields & enum_DEBUGPROP_INFO_FLAGS.DEBUGPROP_INFO_VALUE) != 0) { - propertyInfo.bstrValue = _engine.DebuggedProcess.Natvis.FormatDisplayString(variable); + (propertyInfo.bstrValue, _uiVisualizers) = _engine.DebuggedProcess.Natvis.FormatDisplayString(variable); propertyInfo.dwFields |= enum_DEBUGPROP_INFO_FLAGS.DEBUGPROP_INFO_VALUE; } @@ -111,6 +116,15 @@ public DEBUG_PROPERTY_INFO ConstructDebugPropertyInfo(enum_DEBUGPROP_INFO_FLAGS propertyInfo.dwAttrib |= enum_DBG_ATTRIB_FLAGS.DBG_ATTRIB_VALUE_RAW_STRING; } propertyInfo.dwAttrib |= variable.Access; + + if (_uiVisualizers != null && _uiVisualizers.Length > 0) + { + propertyInfo.dwAttrib |= enum_DBG_ATTRIB_FLAGS.DBG_ATTRIB_VALUE_CUSTOM_VIEWER; + if (_uiVisualizers.Length > 1) + { + propertyInfo.dwAttrib |= enum_DBG_ATTRIB_FLAGS.DBG_ATTRIB_MULTI_CUSTOM_VIEWERS; + } + } } // If the debugger has asked for the property, or the property has children (meaning it is a pointer in the sample) @@ -199,7 +213,8 @@ public int GetExtendedInfo(ref System.Guid guidExtendedInfo, out object pExtende // Returns the memory bytes for a property value. public int GetMemoryBytes(out IDebugMemoryBytes2 ppMemoryBytes) { - throw new NotImplementedException(); + ppMemoryBytes = _engine; + return Constants.S_OK; } // Returns the memory context for a property value. @@ -303,13 +318,32 @@ public int DestroyObjectID() public int GetCustomViewerCount(out uint pcelt) { - pcelt = 0; + pcelt = this._uiVisualizers == null ? 0 : (uint)this._uiVisualizers.Length; return Constants.S_OK; } public int GetCustomViewerList(uint celtSkip, uint celtRequested, DEBUG_CUSTOM_VIEWER[] rgViewers, out uint pceltFetched) { - throw new NotImplementedException(); + pceltFetched = 0; + if (this._uiVisualizers == null || (int)celtSkip >= this._uiVisualizers.Length) + { + return Constants.S_OK; + } + + int numleft = this._uiVisualizers.Length - (int)celtSkip; + var viewers = this._uiVisualizers.Skip((int)celtSkip).Take(Math.Min((int)celtRequested, numleft)); + + int i = 0; + foreach (var v in viewers) + { + rgViewers[i].bstrMetric = v.Name; + rgViewers[i].dwID = (uint)v.Id; + rgViewers[i].bstrMenuName = _engine.DebuggedProcess.Natvis.GetUIVisualizerName(v.Name, v.Id); + i++; + } + + pceltFetched = (uint)viewers.Count(); + return Constants.S_OK; } private void InitializeBytes() @@ -481,6 +515,12 @@ public int GetDataBreakpointInfo160(out string pbstrAddress, out uint pSize, out } return Constants.E_FAIL; } + + public int GetExpressionContext([MarshalAs(UnmanagedType.Interface), Out] out IDebugExpressionContext2 ppExpressionContext) + { + ppExpressionContext = new AD7StackFrame(_engine, _variableInformation.Client, _variableInformation.ThreadContext); + return Constants.S_OK; + } } internal class AD7ErrorProperty : IDebugProperty3 diff --git a/src/MIDebugEngine/Natvis.Impl/Natvis.cs b/src/MIDebugEngine/Natvis.Impl/Natvis.cs index eb15f85c1..273623beb 100755 --- a/src/MIDebugEngine/Natvis.Impl/Natvis.cs +++ b/src/MIDebugEngine/Natvis.Impl/Natvis.cs @@ -9,6 +9,7 @@ using System.Collections.Generic; using System.Globalization; using System.IO; +using System.Linq; using System.Reflection; using System.Text; using System.Text.RegularExpressions; @@ -74,7 +75,7 @@ public uint Size() internal class VisualizerWrapper : SimpleWrapper { public readonly Natvis.VisualizerInfo Visualizer; - private bool _isVisualizerView; + private readonly bool _isVisualizerView; public VisualizerWrapper(string name, AD7Engine engine, IVariableInformation underlyingVariable, Natvis.VisualizerInfo viz, bool isVisualizerView) : base(name, engine, underlyingVariable) @@ -90,6 +91,18 @@ public override string FullName() } } + internal struct VisualizerId + { + public string Name { get; } + public int Id { get; } + + public VisualizerId(string name,int id) + { + this.Name = name; + this.Id = id; + } + }; + public class Natvis { private class AliasInfo @@ -118,6 +131,7 @@ private class FileInfo { public List Visualizers { get; private set; } public List Aliases { get; private set; } + public List UIVisualizers { get; set; } = null; public readonly AutoVisualizer Environment; public FileInfo(AutoVisualizer env) @@ -133,6 +147,15 @@ internal class VisualizerInfo public VisualizerType Visualizer { get; private set; } public Dictionary ScopedNames { get; private set; } + public VisualizerId[] GetUIVisualizers() + { + return this.Visualizer.Items.Where((i) => i is UIVisualizerItemType).Select(i => + { + var visualizer = (UIVisualizerItemType)i; + return new VisualizerId(visualizer.ServiceId, visualizer.Id); + }).ToArray(); + } + public VisualizerInfo(VisualizerType viz, TypeName name) { Visualizer = viz; @@ -183,7 +206,7 @@ public void Initialize(string fileName) { try { - HostNatvisProject.FindNatvisInSolution((s) => LoadFile(s)); + HostNatvisProject.FindNatvis((s) => LoadFile(s)); } catch (FileNotFoundException) { @@ -315,6 +338,12 @@ private bool LoadFile(string path) } } } + + if (autoVis.UIVisualizer != null) + { + f.UIVisualizers = autoVis.UIVisualizer.ToList(); + } + _typeVisualizers.Add(f); } return autoVis != null; @@ -328,8 +357,9 @@ private bool LoadFile(string path) } } - internal string FormatDisplayString(IVariableInformation variable) + internal (string value, VisualizerId[] uiVisualizers) FormatDisplayString(IVariableInformation variable) { + VisualizerInfo visualizer = null; try { _depth++; @@ -340,10 +370,10 @@ internal string FormatDisplayString(IVariableInformation variable) || (ShowDisplayStrings == DisplayStringsState.ForVisualizedItems && variable.IsVisualized)) && !variable.IsPreformatted) { - VisualizerInfo visualizer = FindType(variable); + visualizer = FindType(variable); if (visualizer == null) { - return variable.Value; + return (variable.Value, null); } Cache.Add(variable); // vizualized value has been displayed @@ -357,7 +387,7 @@ internal string FormatDisplayString(IVariableInformation variable) { continue; } - return FormatValue(display.Value, variable, visualizer.ScopedNames); + return (FormatValue(display.Value, variable, visualizer.ScopedNames), visualizer.GetUIVisualizers()); } } } @@ -373,7 +403,7 @@ internal string FormatDisplayString(IVariableInformation variable) { _depth--; } - return variable.Value; + return (variable.Value, visualizer?.GetUIVisualizers()); } private IVariableInformation GetVisualizationWrapper(IVariableInformation variable) @@ -444,6 +474,21 @@ internal IVariableInformation GetVariable(string expr, AD7StackFrame frame) return variable; } + internal string GetUIVisualizerName(string serviceId, int id) + { + string result = string.Empty; + this._typeVisualizers.ForEach((f)=> + { + UIVisualizerType uiViz; + if ((uiViz = f.UIVisualizers?.FirstOrDefault((u) => u.ServiceId == serviceId && u.Id == id)) != null) + { + result = uiViz.MenuName; + } + }); + + return result; + } + private delegate IVariableInformation Traverse(IVariableInformation node); private IVariableInformation[] ExpandVisualized(IVariableInformation variable) @@ -1080,7 +1125,7 @@ private string GetExpressionValue(string expression, IVariableInformation variab string processedExpr = ReplaceNamesInExpression(expression, variable, scopedNames); IVariableInformation expressionVariable = new VariableInformation(processedExpr, variable, _process.Engine, null); expressionVariable.SyncEval(); - return FormatDisplayString(expressionVariable); + return FormatDisplayString(expressionVariable).value; } } } From 3ede7c927f1efd3c02cb4d72b082f0e85fd60897 Mon Sep 17 00:00:00 2001 From: gc46 Date: Fri, 1 Apr 2022 12:56:29 -0700 Subject: [PATCH 10/11] =?UTF-8?q?Bug=201447876:=20Local=20window=20variabl?= =?UTF-8?q?e=20value=20can=20not=20be=20synchronized=20with=E2=80=A6=20(#1?= =?UTF-8?q?300)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ...Immediate window value change --- src/MIDebugEngine/AD7.Impl/AD7StackFrame.cs | 70 +++++++++------------ 1 file changed, 29 insertions(+), 41 deletions(-) diff --git a/src/MIDebugEngine/AD7.Impl/AD7StackFrame.cs b/src/MIDebugEngine/AD7.Impl/AD7StackFrame.cs index 9033f3740..4711f1eab 100644 --- a/src/MIDebugEngine/AD7.Impl/AD7StackFrame.cs +++ b/src/MIDebugEngine/AD7.Impl/AD7StackFrame.cs @@ -22,18 +22,10 @@ internal sealed class AD7StackFrame : IDebugStackFrame2, IDebugExpressionContext private string _functionName; private MITextPosition _textPosition; - private bool _hasGottenLocalsAndParams = false; private uint _radix; private AD7MemoryAddress _codeCxt; private AD7DocumentContext _documentCxt; - // An array of this frame's parameters - private readonly List _parameters = new List(); - - // An array of this frame's locals - private readonly List _locals = new List(); - - public AD7StackFrame(AD7Engine engine, AD7Thread thread, ThreadContext threadContext) { Debug.Assert(threadContext != null, "ThreadContext is null"); @@ -239,8 +231,11 @@ public void SetFrameInfo(enum_FRAMEINFO_FLAGS dwFieldSpec, out FRAMEINFO frameIn } } - private void EnsureLocalsAndParameters() + private void GetLocalsAndParameters(out List locals, out List parameters) { + parameters = new List(); + locals = new List(); + uint radix = Engine.CurrentRadix(); if (radix != Engine.DebuggedProcess.MICommandFactory.Radix) { @@ -249,7 +244,7 @@ private void EnsureLocalsAndParameters() await Engine.UpdateRadixAsync(radix); }); } - if (_textPosition != null && (!_hasGottenLocalsAndParams || radix != _radix)) + if (_textPosition != null) { _radix = radix; List localsAndParameters = null; @@ -258,21 +253,17 @@ private void EnsureLocalsAndParameters() localsAndParameters = await Engine.DebuggedProcess.GetLocalsAndParameters(Thread, ThreadContext); }); - _parameters.Clear(); - _locals.Clear(); foreach (VariableInformation vi in localsAndParameters) { if (vi.IsParameter) { - _parameters.Add(vi); + parameters.Add(vi); } else { - _locals.Add(vi); + locals.Add(vi); } } - - _hasGottenLocalsAndParams = true; } } @@ -283,32 +274,34 @@ private void CreateLocalsPlusArgsProperties(enum_DEBUGPROP_INFO_FLAGS dwFields, int localsLength = 0; - if (_locals != null) + GetLocalsAndParameters(out List locals, out List parameters); + + if (locals != null) { - localsLength = _locals.Count; + localsLength = locals.Count; elementsReturned += (uint)localsLength; } - if (_parameters != null) + if (parameters != null) { - elementsReturned += (uint)_parameters.Count; + elementsReturned += (uint)parameters.Count; } DEBUG_PROPERTY_INFO[] propInfo = new DEBUG_PROPERTY_INFO[elementsReturned]; - if (_locals != null) + if (locals != null) { - for (int i = 0; i < _locals.Count; i++) + for (int i = 0; i < locals.Count; i++) { - AD7Property property = new AD7Property(Engine, _locals[i]); + AD7Property property = new AD7Property(Engine, locals[i]); propInfo[i] = property.ConstructDebugPropertyInfo(dwFields); } } - if (_parameters != null) + if (parameters != null) { - for (int i = 0; i < _parameters.Count; i++) + for (int i = 0; i < parameters.Count; i++) { - AD7Property property = new AD7Property(Engine, _parameters[i]); + AD7Property property = new AD7Property(Engine, parameters [i]); propInfo[localsLength + i] = property.ConstructDebugPropertyInfo(dwFields); } } @@ -319,12 +312,14 @@ private void CreateLocalsPlusArgsProperties(enum_DEBUGPROP_INFO_FLAGS dwFields, // Construct an instance of IEnumDebugPropertyInfo2 for the locals collection only. private void CreateLocalProperties(enum_DEBUGPROP_INFO_FLAGS dwFields, out uint elementsReturned, out IEnumDebugPropertyInfo2 enumObject) { - elementsReturned = (uint)_locals.Count; - DEBUG_PROPERTY_INFO[] propInfo = new DEBUG_PROPERTY_INFO[_locals.Count]; + GetLocalsAndParameters(out List locals, out _); + + elementsReturned = (uint)locals.Count; + DEBUG_PROPERTY_INFO[] propInfo = new DEBUG_PROPERTY_INFO[locals.Count]; for (int i = 0; i < propInfo.Length; i++) { - AD7Property property = new AD7Property(Engine, _locals[i]); + AD7Property property = new AD7Property(Engine, locals[i]); propInfo[i] = property.ConstructDebugPropertyInfo(dwFields); } @@ -334,12 +329,14 @@ private void CreateLocalProperties(enum_DEBUGPROP_INFO_FLAGS dwFields, out uint // Construct an instance of IEnumDebugPropertyInfo2 for the parameters collection only. private void CreateParameterProperties(enum_DEBUGPROP_INFO_FLAGS dwFields, out uint elementsReturned, out IEnumDebugPropertyInfo2 enumObject) { - elementsReturned = (uint)_parameters.Count; - DEBUG_PROPERTY_INFO[] propInfo = new DEBUG_PROPERTY_INFO[_parameters.Count]; + GetLocalsAndParameters(out _, out List parameters); + + elementsReturned = (uint)parameters.Count; + DEBUG_PROPERTY_INFO[] propInfo = new DEBUG_PROPERTY_INFO[parameters.Count]; for (int i = 0; i < propInfo.Length; i++) { - AD7Property property = new AD7Property(Engine, _parameters[i]); + AD7Property property = new AD7Property(Engine, parameters [i]); propInfo[i] = property.ConstructDebugPropertyInfo(dwFields); } @@ -393,15 +390,6 @@ int IDebugStackFrame2.EnumProperties(enum_DEBUGPROP_INFO_FLAGS dwFields, uint nR try { - if (guidFilter == AD7Guids.guidFilterAllLocals || - guidFilter == AD7Guids.guidFilterAllLocalsPlusArgs || - guidFilter == AD7Guids.guidFilterArgs || - guidFilter == AD7Guids.guidFilterLocals || - guidFilter == AD7Guids.guidFilterLocalsPlusArgs) - { - EnsureLocalsAndParameters(); - } - if (guidFilter == AD7Guids.guidFilterLocalsPlusArgs || guidFilter == AD7Guids.guidFilterAllLocalsPlusArgs || guidFilter == AD7Guids.guidFilterAllLocals) From 452572f8252ad6c5000503794d189278ddb38fee Mon Sep 17 00:00:00 2001 From: Andrew Wang Date: Fri, 1 Apr 2022 17:34:01 -0700 Subject: [PATCH 11/11] Support 'SetExpression' DAP Request (#1293) * Support 'SetExpression' DAP Request This PR adds SetExpression for OpenDebugAD7. --- src/OpenDebugAD7/AD7DebugSession.cs | 268 ++++++++++++------ src/OpenDebugAD7/AD7Resources.Designer.cs | 18 ++ src/OpenDebugAD7/AD7Resources.resx | 7 + test/CppTests/Tests/ExpressionTests.cs | 47 +++ .../Responses/SetExpressionResponse.cs | 18 ++ .../Commands/SetExpressionCommand.cs | 37 +++ .../Extensions/DebuggerRunnerExtensions.cs | 5 + 7 files changed, 310 insertions(+), 90 deletions(-) create mode 100644 test/DebuggerTesting/OpenDebug/Commands/Responses/SetExpressionResponse.cs create mode 100644 test/DebuggerTesting/OpenDebug/Commands/SetExpressionCommand.cs diff --git a/src/OpenDebugAD7/AD7DebugSession.cs b/src/OpenDebugAD7/AD7DebugSession.cs index e88d307ec..3366727b7 100644 --- a/src/OpenDebugAD7/AD7DebugSession.cs +++ b/src/OpenDebugAD7/AD7DebugSession.cs @@ -421,6 +421,87 @@ private int GetMemoryContext(string memoryReference, int? offset, out IDebugMemo return hr; } + /// + /// Given 'expression', it will query the engine for an IDebugProperty2 + /// + /// Error handler to use in this method. + /// The expression to evaluate. + /// Which frame to use and evaluate the expression on. + /// If the current expression is from a console exec. + /// Flags used for EvaluateSync + /// EvaluationFlags used for DAPEvaluateSync + /// The IDebugProperty2 of the 'expression' + /// In any step of the method that fails, it will throw a protocol execption using the ErrorBuilder. + private void GetDebugPropertyFromExpression(ErrorBuilder eb, string expression, int frameId, bool isExecInConsole, enum_EVALFLAGS flags, DAPEvalFlags dapEvalFlags, out IDebugProperty2 property) + { + property = null; + + IDebugStackFrame2 frame; + bool success; + if (frameId == -1 && isExecInConsole) + { + // If exec in console and no stack frame, evaluate off the top frame. + success = m_frameHandles.TryGetFirst(out frame); + } + else + { + success = m_frameHandles.TryGet(frameId, out frame); + } + + if (!success) + { + throw new ProtocolException(AD7Resources.Error_InvalidStackFrameOnEvaluateExpression); + } + + IDebugExpressionContext2 expressionContext; + int hr = frame.GetExpressionContext(out expressionContext); + eb.CheckHR(hr); + + IDebugExpression2 expressionObject; + string error; + uint errorIndex; + hr = expressionContext.ParseText(expression, enum_PARSEFLAGS.PARSE_EXPRESSION, Constants.ParseRadix, out expressionObject, out error, out errorIndex); + if (!string.IsNullOrEmpty(error)) + { + DebuggerTelemetry.ReportError(DebuggerTelemetry.TelemetryEvaluateEventName, 4001, "Error parsing expression"); + throw new ProtocolException(error); + } + eb.CheckHR(hr); + eb.CheckOutput(expressionObject); + + if (expressionObject is IDebugExpressionDAP expressionDapObject) + { + hr = expressionDapObject.EvaluateSync(flags, dapEvalFlags, Constants.EvaluationTimeout, null, out property); + } + else + { + hr = expressionObject.EvaluateSync(flags, Constants.EvaluationTimeout, null, out property); + } + + eb.CheckHR(hr); + eb.CheckOutput(property); + } + + private uint GetRadixFromValueForamt(ValueFormat format) + { + uint radix = Constants.EvaluationRadix; + + if (format != null) + { + if (format.Hex == true) + { + radix = 16; + } + } + + if (m_settingsCallback != null) + { + // MIEngine generally gets the radix from IDebugSettingsCallback110 rather than using the radix passed + m_settingsCallback.Radix = radix; + } + + return radix; + } #endregion #region AD7EventHandlers helper methods @@ -918,6 +999,7 @@ protected override void HandleInitializeRequestAsync(IRequestResponder AD7Resources.Error_Scenario_Evaluate); - IDebugStackFrame2 frame; - - bool success = false; - if (frameId == -1 && isExecInConsole) - { - // If exec in console and no stack frame, evaluate off the top frame. - success = m_frameHandles.TryGetFirst(out frame); - } - else - { - success = m_frameHandles.TryGet(frameId, out frame); - } - - if (!success) - { - Dictionary properties = new Dictionary(); - properties.Add(DebuggerTelemetry.TelemetryStackFrameId, frameId); - properties.Add(DebuggerTelemetry.TelemetryExecuteInConsole, isExecInConsole); - DebuggerTelemetry.ReportError(DebuggerTelemetry.TelemetryEvaluateEventName, 1108, "Invalid frameId", properties); - responder.SetError(new ProtocolException("Cannot evaluate expression on the specified stack frame.")); - return; - } - uint radix = Constants.EvaluationRadix; - - if (responder.Arguments.Format != null) - { - ValueFormat format = responder.Arguments.Format; - - if (format.Hex == true) - { - radix = 16; - } - } - - if (m_settingsCallback != null) - { - // MIEngine generally gets the radix from IDebugSettingsCallback110 rather than using the radix passed - m_settingsCallback.Radix = radix; - } - - IDebugExpressionContext2 expressionContext; - hr = frame.GetExpressionContext(out expressionContext); - eb.CheckHR(hr); - - IDebugExpression2 expressionObject; - string error; - uint errorIndex; - hr = expressionContext.ParseText(expression, enum_PARSEFLAGS.PARSE_EXPRESSION, Constants.ParseRadix, out expressionObject, out error, out errorIndex); - if (!string.IsNullOrEmpty(error)) - { - // TODO: Is this how errors should be returned? - DebuggerTelemetry.ReportError(DebuggerTelemetry.TelemetryEvaluateEventName, 4001, "Error parsing expression"); - responder.SetError(new ProtocolException(error)); - return; - } - eb.CheckHR(hr); - eb.CheckOutput(expressionObject); + uint radix = GetRadixFromValueForamt(responder.Arguments.Format); // NOTE: This is the same as what vssdebug normally passes for the watch window enum_EVALFLAGS flags = enum_EVALFLAGS.EVAL_RETURNVALUE | @@ -2960,24 +2969,12 @@ protected override void HandleEvaluateRequestAsync(IRequestResponder responder) + { + string expression = responder.Arguments.Expression; + string value = responder.Arguments.Value; + int frameId = responder.Arguments.FrameId.GetValueOrDefault(-1); + + // if we are not stopped don't try to set + if (!m_isStopped) + { + responder.SetError(new ProtocolException(AD7Resources.Error_TargetNotStopped, new Message(1105, AD7Resources.Error_TargetNotStopped))); + return; + } + + uint radix = GetRadixFromValueForamt(responder.Arguments.Format); + + var eb = new ErrorBuilder(() => string.Format(CultureInfo.CurrentCulture, AD7Resources.Error_SetExpression, expression, value)); + + try + { + // Create variable + GetDebugPropertyFromExpression( + eb, + expression, + frameId, + false, + enum_EVALFLAGS.EVAL_RETURNVALUE | enum_EVALFLAGS.EVAL_NOEVENTS | (enum_EVALFLAGS)enum_EVALFLAGS110.EVAL110_FORCE_REAL_FUNCEVAL, + DAPEvalFlags.NONE, + out IDebugProperty2 property + ); + + // Get property information to make sure it is not read-only. + DEBUG_PROPERTY_INFO[] propertyInfo = new DEBUG_PROPERTY_INFO[1]; + eb.CheckHR(property.GetPropertyInfo(enum_DEBUGPROP_INFO_FLAGS.DEBUGPROP_INFO_ATTRIB, radix, Constants.EvaluationTimeout, null, 0, propertyInfo)); + if (propertyInfo[0].dwAttrib.HasFlag(enum_DBG_ATTRIB_FLAGS.DBG_ATTRIB_VALUE_READONLY)) + { + string message = string.Format(CultureInfo.CurrentCulture, AD7Resources.Error_VariableIsReadonly, expression); + responder.SetError(new ProtocolException(message, new Message(1107, message))); + return; + } + + // Assign value + string error = null; + int hr; + if (property is IDebugProperty3 prop3) + { + hr = prop3.SetValueAsStringWithError(value, Constants.EvaluationRadix, Constants.EvaluationTimeout, out error); + } + else + { + hr = property.SetValueAsString(value, Constants.EvaluationRadix, Constants.EvaluationTimeout); + } + + if (hr != HRConstants.S_OK) + { + string message = error ?? AD7Resources.Error_SetVariableFailed; + throw new ProtocolException(message, new Message(1107, message)); + } + + // Query for new value + GetDebugPropertyFromExpression( + eb, + expression, + frameId, + true, // Treat SetExpression like expressions from the top frame. + enum_EVALFLAGS.EVAL_RETURNVALUE | enum_EVALFLAGS.EVAL_NOEVENTS | (enum_EVALFLAGS)enum_EVALFLAGS110.EVAL110_FORCE_REAL_FUNCEVAL, + DAPEvalFlags.NONE, + out property + ); + + enum_DEBUGPROP_INFO_FLAGS propertyInfoFlags = GetDefaultPropertyInfoFlags(); + eb.CheckHR(property.GetPropertyInfo(propertyInfoFlags, radix, Constants.EvaluationTimeout, null, 0, propertyInfo)); + + string memoryReference = AD7Utils.GetMemoryReferenceFromIDebugProperty(property); + Variable variable = m_variableManager.CreateVariable(ref propertyInfo[0], propertyInfoFlags, memoryReference); + + responder.SetResponse(new SetExpressionResponse + { + Value = variable.Value, + Type = variable.Type, + VariablesReference = variable.VariablesReference + }); + } + catch (Exception ex) + { + responder.SetError(new ProtocolException(ex.Message)); + } + + } + #endregion #region IDebugPortNotify2 diff --git a/src/OpenDebugAD7/AD7Resources.Designer.cs b/src/OpenDebugAD7/AD7Resources.Designer.cs index 516b91680..eea617771 100644 --- a/src/OpenDebugAD7/AD7Resources.Designer.cs +++ b/src/OpenDebugAD7/AD7Resources.Designer.cs @@ -251,6 +251,15 @@ internal static string Error_InvalidDataBreakpoint { } } + /// + /// Looks up a localized string similar to Cannot evaluate expression on the specified stack frame.. + /// + internal static string Error_InvalidStackFrameOnEvaluateExpression { + get { + return ResourceManager.GetString("Error_InvalidStackFrameOnEvaluateExpression", resourceCulture); + } + } + /// /// Looks up a localized string similar to DNX runtime process exited unexpectedly with error code {0}.. /// @@ -406,6 +415,15 @@ internal static string Error_Scenario_Step_Out { } } + /// + /// Looks up a localized string similar to Unable to set expression '{0}' to value '{1}'. + /// + internal static string Error_SetExpression { + get { + return ResourceManager.GetString("Error_SetExpression", resourceCulture); + } + } + /// /// Looks up a localized string similar to Could not set variable. /// diff --git a/src/OpenDebugAD7/AD7Resources.resx b/src/OpenDebugAD7/AD7Resources.resx index 443ed33ee..60be8f97f 100644 --- a/src/OpenDebugAD7/AD7Resources.resx +++ b/src/OpenDebugAD7/AD7Resources.resx @@ -331,4 +331,11 @@ Unable to set data breakpoint: {0} {0} is the reason + + Unable to set expression '{0}' to value '{1}' + {0} is the expression, {1} is the value the user wants to set. + + + Cannot evaluate expression on the specified stack frame. + \ No newline at end of file diff --git a/test/CppTests/Tests/ExpressionTests.cs b/test/CppTests/Tests/ExpressionTests.cs index 8fab44e6c..7c00c7839 100644 --- a/test/CppTests/Tests/ExpressionTests.cs +++ b/test/CppTests/Tests/ExpressionTests.cs @@ -460,6 +460,53 @@ public void AssignInvalidExpressionToVariable(ITestSettings settings) } } + [Theory] + [DependsOnTest(nameof(CompileKitchenSinkForExpressionTests))] + [RequiresTestSettings] + public void SetExpressionOnVariable(ITestSettings settings) + { + this.TestPurpose("Call set expression on a variable."); + this.WriteSettings(settings); + + IDebuggee debuggee = SinkHelper.Open(this, settings.CompilerSettings, DebuggeeMonikers.KitchenSink.Expression); + + using (IDebuggerRunner runner = CreateDebugAdapterRunner(settings)) + { + this.Comment("Configure launch."); + runner.Launch(settings.DebuggerSettings, debuggee, "-fExpression"); + + this.Comment("Set a breakpoint so that we can stop at a line."); + runner.SetBreakpoints(debuggee.Breakpoints(SinkHelper.Expression, 31)); + + this.Comment("Start debugging and hit breakpoint."); + runner.Expects.HitBreakpointEvent().AfterConfigurationDone(); + + this.Comment("Start setting expressions on variable in frame"); + using (IThreadInspector threadInspector = runner.GetThreadInspector()) + { + IFrameInspector currentFrame = threadInspector.Stack.First(); + + this.Comment("Set myint=9000 as decimal."); + string setExpressionValue = runner.SetExpression("myint", "9000", currentFrame.Id); + Assert.Equal("9000", setExpressionValue); + + + this.Comment("Validate SetExpression affected locals"); + string myIntStr = currentFrame.GetVariable("myint").Value; + Assert.Equal(setExpressionValue, myIntStr); + + this.Comment("Set myint=9000 as hex."); + string hexVal = runner.SetExpression("myint", "9000", currentFrame.Id, new ValueFormat { hex = true }); + Assert.Equal("0x2328", hexVal); + } + + this.Comment("Continue to run to exist."); + runner.Expects.TerminatedEvent().AfterContinue(); + + runner.DisconnectAndVerify(); + } + } + #endregion #endregion diff --git a/test/DebuggerTesting/OpenDebug/Commands/Responses/SetExpressionResponse.cs b/test/DebuggerTesting/OpenDebug/Commands/Responses/SetExpressionResponse.cs new file mode 100644 index 000000000..a949c2528 --- /dev/null +++ b/test/DebuggerTesting/OpenDebug/Commands/Responses/SetExpressionResponse.cs @@ -0,0 +1,18 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using Newtonsoft.Json; + +namespace DebuggerTesting.OpenDebug.Commands.Responses +{ + public sealed class SetExpressionResponseValue : CommandResponseValue + { + public sealed class Body + { + [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)] + public string value; + } + + public Body body = new Body(); + } +} diff --git a/test/DebuggerTesting/OpenDebug/Commands/SetExpressionCommand.cs b/test/DebuggerTesting/OpenDebug/Commands/SetExpressionCommand.cs new file mode 100644 index 000000000..bfbad0825 --- /dev/null +++ b/test/DebuggerTesting/OpenDebug/Commands/SetExpressionCommand.cs @@ -0,0 +1,37 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using DebuggerTesting.OpenDebug.Commands.Responses; + +namespace DebuggerTesting.OpenDebug.Commands +{ + public sealed class ValueFormat + { + public bool? hex; + } + public sealed class SetExpressionCommandArgs + { + public string expression; + public string value; + public int? frameId; + public ValueFormat format; + } + + public class SetExpressionCommand : CommandWithResponse + { + public SetExpressionCommand(string expression, string value, int frameId, ValueFormat format = null) : base("setExpression") + { + Parameter.ThrowIfNullOrWhiteSpace(expression, nameof(expression)); + Parameter.ThrowIfNullOrWhiteSpace(value, nameof(value)); + this.Args.expression = expression; + this.Args.value = value; + this.Args.frameId = frameId; + this.Args.format = format; + } + + public override string ToString() + { + return "{0} ({1}={2})".FormatInvariantWithArgs(base.ToString(), this.Args.expression, this.Args.value); + } + } +} diff --git a/test/DebuggerTesting/OpenDebug/Extensions/DebuggerRunnerExtensions.cs b/test/DebuggerTesting/OpenDebug/Extensions/DebuggerRunnerExtensions.cs index 195fa8225..17d93d694 100644 --- a/test/DebuggerTesting/OpenDebug/Extensions/DebuggerRunnerExtensions.cs +++ b/test/DebuggerTesting/OpenDebug/Extensions/DebuggerRunnerExtensions.cs @@ -222,6 +222,11 @@ public static SetDataBreakpointsResponseValue SetDataBreakpoints(this IDebuggerR return runner.RunCommand(new SetDataBreakpointsCommand(breakpoints)); } + public static string SetExpression(this IDebuggerRunner runner, string expression, string value, int frameId, ValueFormat format = null) + { + return runner.RunCommand(new SetExpressionCommand(expression, value, frameId, format))?.body?.value; + } + public static string[] CompletionsRequest(this IDebuggerRunner runner, string text) { CompletionItem[] completionItems = runner.RunCommand(new CompletionsCommand(null, text, 0, null))?.body?.targets;