diff --git a/src/MICore/CommandFactories/MICommandFactory.cs b/src/MICore/CommandFactories/MICommandFactory.cs index c8b69dcd8..3298c01db 100644 --- a/src/MICore/CommandFactories/MICommandFactory.cs +++ b/src/MICore/CommandFactories/MICommandFactory.cs @@ -4,12 +4,10 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Globalization; using System.Threading.Tasks; -using System.IO; using System.Text; -using System.Collections.ObjectModel; using System.Linq; -using System.Globalization; using Microsoft.VisualStudio.Debugger.Interop; namespace MICore @@ -212,33 +210,43 @@ public async Task StackListVariables(PrintValue printValues, int #region Program Execution - public async Task ExecStep(int threadId, ResultClass resultClass = ResultClass.running) + public async Task ExecStep(int threadId, bool forward = true, ResultClass resultClass = ResultClass.running) { string command = "-exec-step"; + if (!forward) + command += " --reverse"; await ThreadFrameCmdAsync(command, resultClass, threadId, 0); } - public async Task ExecNext(int threadId, ResultClass resultClass = ResultClass.running) + public async Task ExecNext(int threadId, bool forward = true, ResultClass resultClass = ResultClass.running) { string command = "-exec-next"; + if (!forward) + command += " --reverse"; await ThreadFrameCmdAsync(command, resultClass, threadId, 0); } - public async Task ExecFinish(int threadId, ResultClass resultClass = ResultClass.running) + public async Task ExecFinish(int threadId, bool forward = true, ResultClass resultClass = ResultClass.running) { string command = "-exec-finish"; + if (!forward) + command += " --reverse"; await ThreadFrameCmdAsync(command, resultClass, threadId, 0); } - public async Task ExecStepInstruction(int threadId, ResultClass resultClass = ResultClass.running) + public async Task ExecStepInstruction(int threadId, bool forward = true, ResultClass resultClass = ResultClass.running) { string command = "-exec-step-instruction"; + if (!forward) + command += " --reverse"; await ThreadFrameCmdAsync(command, resultClass, threadId, 0); } - public async Task ExecNextInstruction(int threadId, ResultClass resultClass = ResultClass.running) + public async Task ExecNextInstruction(int threadId, bool forward = true, ResultClass resultClass = ResultClass.running) { string command = "-exec-next-instruction"; + if (!forward) + command += " --reverse"; await ThreadFrameCmdAsync(command, resultClass, threadId, 0); } @@ -254,9 +262,11 @@ public virtual async Task ExecRun() /// /// Continues running the target process /// - public async Task ExecContinue() + public async Task ExecContinue(bool forward = true) { string command = "-exec-continue"; + if (!forward) + command += " --reverse"; await _debugger.CmdAsync(command, ResultClass.running); } @@ -686,6 +696,8 @@ public virtual bool CanDetach() return true; } + abstract public Task GetTargetFeatures(); + abstract public Task> StartAddressesForLine(string file, uint line); /// diff --git a/src/MICore/CommandFactories/gdb.cs b/src/MICore/CommandFactories/gdb.cs index 92c28938b..08f8d0d1d 100644 --- a/src/MICore/CommandFactories/gdb.cs +++ b/src/MICore/CommandFactories/gdb.cs @@ -1,19 +1,15 @@ -// Copyright (c) Microsoft. All rights reserved. +// 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.Diagnostics; using System.Threading.Tasks; using System.IO; -using System.Text; -using System.Collections.ObjectModel; -using System.Linq; using System.Globalization; namespace MICore { - internal class GdbMICommandFactory : MICommandFactory + internal sealed class GdbMICommandFactory : MICommandFactory { private int _currentThreadId = 0; private uint _currentFrameLevel = 0; @@ -183,6 +179,12 @@ public override async Task> StartAddressesForLine(string file, uint return addresses; } + public override async Task GetTargetFeatures() + { + Results results = await _debugger.CmdAsync("-list-target-features", ResultClass.done); + return results.Find("features").AsStrings; + } + public override async Task EnableTargetAsyncOption() { // Linux attach TODO: GDB will fail this command when attaching. This is worked around diff --git a/src/MICore/CommandFactories/lldb.cs b/src/MICore/CommandFactories/lldb.cs index 738a47f5a..54446f96b 100644 --- a/src/MICore/CommandFactories/lldb.cs +++ b/src/MICore/CommandFactories/lldb.cs @@ -3,18 +3,15 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.Threading.Tasks; using System.IO; using System.Text; -using System.Collections.ObjectModel; -using System.Linq; using System.Globalization; using Microsoft.VisualStudio.Debugger.Interop; namespace MICore { - internal class LlldbMICommandFactory : MICommandFactory + internal sealed class LlldbMICommandFactory : MICommandFactory { public override string Name { @@ -119,6 +116,11 @@ public override Task EnableTargetAsyncOption() return Task.FromResult((object)null); } + public override Task GetTargetFeatures() + { + return Task.FromResult(new string[0]); + } + public override string GetTargetArchitectureCommand() { return "platform status"; diff --git a/src/MICore/Debugger.cs b/src/MICore/Debugger.cs index de2796da2..a48ac0d80 100755 --- a/src/MICore/Debugger.cs +++ b/src/MICore/Debugger.cs @@ -44,6 +44,8 @@ public class Debugger : ITransportCallback public event EventHandler ThreadCreatedEvent; public event EventHandler ThreadExitedEvent; public event EventHandler ThreadGroupExitedEvent; + public event EventHandler RecordStartedEvent; + public event EventHandler RecordStoppedEvent; public event EventHandler TelemetryEvent; private int _exiting; public ProcessState ProcessState { get; private set; } @@ -1446,6 +1448,16 @@ private void OnNotificationOutput(string cmd) results = _miResults.ParseResultList(cmd.Substring("thread-exited,".Length)); ThreadExitedEvent(this, new ResultEventArgs(results, 0)); } + else if (cmd.StartsWith("record-started,", StringComparison.Ordinal)) + { + results = _miResults.ParseResultList(cmd.Substring("record-started,".Length)); + RecordStartedEvent(this, new ResultEventArgs(results, 0)); + } + else if (cmd.StartsWith("record-stopped,", StringComparison.Ordinal)) + { + results = _miResults.ParseResultList(cmd.Substring("record-stopped,".Length)); + RecordStoppedEvent(this, new ResultEventArgs(results, 0)); + } else if (cmd.StartsWith("telemetry,", StringComparison.Ordinal)) { results = _miResults.ParseResultList(cmd.Substring("telemetry,".Length)); diff --git a/src/MIDebugEngine/AD7.Impl/AD7Engine.cs b/src/MIDebugEngine/AD7.Impl/AD7Engine.cs index 0535572a6..cb0ca2487 100755 --- a/src/MIDebugEngine/AD7.Impl/AD7Engine.cs +++ b/src/MIDebugEngine/AD7.Impl/AD7Engine.cs @@ -3,8 +3,6 @@ using System; using System.Collections.Generic; -using System.Text; -using System.Runtime.ExceptionServices; using Microsoft.VisualStudio.Debugger.Interop; using Microsoft.VisualStudio.Debugger.Interop.UnixPortSupplier; using System.Diagnostics; @@ -35,7 +33,7 @@ namespace Microsoft.MIDebugEngine [System.Runtime.InteropServices.ComVisible(true)] [System.Runtime.InteropServices.Guid("0fc2f352-2fc1-4f80-8736-51cd1ab28f16")] - sealed public class AD7Engine : IDebugEngine2, IDebugEngineLaunch2, IDebugEngine3, IDebugProgram3, IDebugEngineProgram2, IDebugMemoryBytes2, IDebugEngine110, IDebugProgramDAP, IDebugMemoryBytesDAP, IDisposable + sealed public class AD7Engine : IDebugEngine2, IDebugEngineLaunch2, IDebugEngine3, IDebugProgram3, IDebugEngineProgram2, IDebugReversibleEngineProgram160, IDebugMemoryBytes2, IDebugEngine110, IDebugProgramDAP, IDebugMemoryBytesDAP, IDisposable { // used to send events to the debugger. Some examples of these events are thread create, exception thrown, module load. private EngineCallback _engineCallback; @@ -171,6 +169,7 @@ internal bool ProgramCreateEventSent get; private set; } + public ExecuteDirection ExecutionDirection { get; private set; } public string GetAddressDescription(ulong ip) { @@ -778,11 +777,11 @@ public int Continue(IDebugThread2 pThread) { if (_pollThread.IsPollThread()) { - _debuggedProcess.Continue(thread?.GetDebuggedThread()); + _debuggedProcess.Continue(thread?.GetDebuggedThread(), ExecutionDirection); } else { - _pollThread.RunOperation(() => _debuggedProcess.Continue(thread?.GetDebuggedThread())); + _pollThread.RunOperation(() => _debuggedProcess.Continue(thread?.GetDebuggedThread(), ExecutionDirection)); } } catch (InvalidCoreDumpOperationException) @@ -973,7 +972,7 @@ public int Step(IDebugThread2 pThread, enum_STEPKIND kind, enum_STEPUNIT unit) return Constants.E_FAIL; } - _debuggedProcess.WorkerThread.RunOperation(() => _debuggedProcess.Step(thread.GetDebuggedThread().Id, kind, unit)); + _debuggedProcess.WorkerThread.RunOperation(() => _debuggedProcess.Step(thread.GetDebuggedThread().Id, kind, unit, ExecutionDirection)); } catch (InvalidCoreDumpOperationException) { @@ -1061,6 +1060,19 @@ public int WatchForThreadStep(IDebugProgram2 pOriginatingProgram, uint dwTid, in #endregion + #region IDebugEngineProgram2 Members + int IDebugReversibleEngineProgram160.CanReverse() + { + return DebuggedProcess.TargetFeatures.Contains("reverse") ? Constants.S_OK : Constants.S_FALSE; + } + + int IDebugReversibleEngineProgram160.SetExecuteDirection(ExecuteDirection ExecuteDirection) + { + ExecutionDirection = ExecuteDirection; + return Constants.S_OK; + } + #endregion + #region IDebugMemoryBytes2 Members public int GetSize(out ulong pqwSize) diff --git a/src/MIDebugEngine/Engine.Impl/DebuggedProcess.cs b/src/MIDebugEngine/Engine.Impl/DebuggedProcess.cs index 011ff4f28..6da4c90c8 100755 --- a/src/MIDebugEngine/Engine.Impl/DebuggedProcess.cs +++ b/src/MIDebugEngine/Engine.Impl/DebuggedProcess.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft. All rights reserved. +// Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. using MICore; @@ -19,7 +19,7 @@ namespace Microsoft.MIDebugEngine { - internal class DebuggedProcess : MICore.Debugger + internal sealed class DebuggedProcess : MICore.Debugger { public AD_PROCESS_ID Id { get; private set; } public AD7Engine Engine { get; private set; } @@ -32,6 +32,7 @@ internal class DebuggedProcess : MICore.Debugger public Disassembly Disassembly { get; private set; } public ExceptionManager ExceptionManager { get; private set; } public CygwinFilePathMapper CygwinFilePathMapper { get; private set; } + public string[] TargetFeatures { get; private set; } private List _moduleList; private ISampleEngineCallback _callback; @@ -433,6 +434,21 @@ public DebuggedProcess(bool bLaunched, LaunchOptions launchOptions, ISampleEngin ThreadCache.ThreadGroupExitedEvent(result.Results.FindString("id")); }; + RecordStartedEvent += async delegate (object o, EventArgs args) + { + var result = args as ResultEventArgs; + string threadGroup = result.Results.FindString("thread-group"); // e.g. "i1" + string method = result.Results.FindString("method"); // e.g. "full" + await UpdateTargetFeatures(); + }; + + RecordStoppedEvent += async delegate (object o, EventArgs args) + { + var result = args as ResultEventArgs; + string threadGroup = result.Results.FindString("thread-group"); // e.g. "i1" + await UpdateTargetFeatures(); + }; + TelemetryEvent += (object o, ResultEventArgs args) => { string eventName; @@ -540,6 +556,13 @@ private async Task EnsureModulesLoaded() } } + private async Task UpdateTargetFeatures() + { + TargetFeatures = await MICommandFactory.GetTargetFeatures(); + // NOTE: Currently OpenDebugAD7 polls if we can reverse execute on every stop. + // It would be better to notify it instead from here so it can update the Capabilities + } + public async Task Initialize(HostWaitLoop waitLoop, CancellationToken token) { bool success = false; @@ -600,6 +623,8 @@ public async Task Initialize(HostWaitLoop waitLoop, CancellationToken token) } } } + // now the exe is loaded and we can check target features + await UpdateTargetFeatures(); success = true; } @@ -1381,7 +1406,13 @@ private async Task HandleBreakModeEvent(ResultEventArgs results, BreakRequest br { if (breakRequest == BreakRequest.None) { + // FIXME: we also end up here when gdb prints + // "No more reverse-execution history." + // but does not set a stopped event reason Debug.Fail("Unknown stopping reason"); + // FIXME: this will show up as "Exception occurred" in vscode which + // confusingly suggests that an exception has occurred in the debuggee. + // But OnError() only prints something in the console rather than in the editor _callback.OnException(thread, "Unknown", "Unknown stopping event", 0); } } @@ -1669,10 +1700,16 @@ protected override void ScheduleResultProcessing(Action func) _worker.PostOperation(() => { func(); }); } - public async Task Execute(DebuggedThread thread) + public async Task Execute(DebuggedThread thread, ExecuteDirection executionDirection = ExecuteDirection.ExecuteDirection_Forward) { await ExceptionManager.EnsureSettingsUpdated(); + if (executionDirection == ExecuteDirection.ExecuteDirection_Reverse) + { + await MICommandFactory.ExecContinue(false); + return; + } + // Should clear stepping state if (_worker.IsPollThread()) { @@ -1684,30 +1721,32 @@ public async Task Execute(DebuggedThread thread) } } - public Task Continue(DebuggedThread thread) + public Task Continue(DebuggedThread thread, ExecuteDirection executionDirection = ExecuteDirection.ExecuteDirection_Forward) { // Called after Stopping event - return Execute(thread); + return Execute(thread, executionDirection); } - public async Task Step(int threadId, enum_STEPKIND kind, enum_STEPUNIT unit) + public async Task Step(int threadId, enum_STEPKIND kind, enum_STEPUNIT unit, ExecuteDirection direction = ExecuteDirection.ExecuteDirection_Forward) { this.VerifyNotDebuggingCoreDump(); await ExceptionManager.EnsureSettingsUpdated(); + // STEP_BACKWARDS is deprecated, use direction + bool isForwardStep = direction == ExecuteDirection.ExecuteDirection_Forward; if ((unit == enum_STEPUNIT.STEP_LINE) || (unit == enum_STEPUNIT.STEP_STATEMENT)) { switch (kind) { case enum_STEPKIND.STEP_INTO: - await MICommandFactory.ExecStep(threadId); + await MICommandFactory.ExecStep(threadId, isForwardStep); break; case enum_STEPKIND.STEP_OVER: - await MICommandFactory.ExecNext(threadId); + await MICommandFactory.ExecNext(threadId, isForwardStep); break; case enum_STEPKIND.STEP_OUT: - await MICommandFactory.ExecFinish(threadId); + await MICommandFactory.ExecFinish(threadId, isForwardStep); break; default: throw new NotImplementedException(); @@ -1718,13 +1757,13 @@ public async Task Step(int threadId, enum_STEPKIND kind, enum_STEPUNIT unit) switch (kind) { case enum_STEPKIND.STEP_INTO: - await MICommandFactory.ExecStepInstruction(threadId); + await MICommandFactory.ExecStepInstruction(threadId, isForwardStep); break; case enum_STEPKIND.STEP_OVER: - await MICommandFactory.ExecNextInstruction(threadId); + await MICommandFactory.ExecNextInstruction(threadId, isForwardStep); break; case enum_STEPKIND.STEP_OUT: - await MICommandFactory.ExecFinish(threadId); + await MICommandFactory.ExecFinish(threadId, isForwardStep); break; default: throw new NotImplementedException(); diff --git a/src/MIDebugEngine/Natvis.Impl/Natvis.cs b/src/MIDebugEngine/Natvis.Impl/Natvis.cs index 9c201d0e0..e648953ee 100755 --- a/src/MIDebugEngine/Natvis.Impl/Natvis.cs +++ b/src/MIDebugEngine/Natvis.Impl/Natvis.cs @@ -964,6 +964,10 @@ private VisualizerInfo FindType(IVariableInformation variable) { return ((VisualizerWrapper)variable).Visualizer; } + + if (string.IsNullOrEmpty(variable.TypeName)) + return null; + if (_vizCache.ContainsKey(variable.TypeName)) { return _vizCache[variable.TypeName]; diff --git a/src/OpenDebugAD7/AD7DebugSession.cs b/src/OpenDebugAD7/AD7DebugSession.cs index 5e0720b9f..d04f6e88b 100644 --- a/src/OpenDebugAD7/AD7DebugSession.cs +++ b/src/OpenDebugAD7/AD7DebugSession.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft. All rights reserved. +// Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System; @@ -515,7 +515,7 @@ public void BeforeContinue() m_gotoCodeContexts.Clear(); } - public void Stopped(IDebugThread2 thread) + private void Stopped(IDebugThread2 thread) { Debug.Assert(m_variableManager.IsEmpty(), "Why do we have variable handles?"); Debug.Assert(m_frameHandles.IsEmpty, "Why do we have frame handles?"); @@ -525,6 +525,7 @@ public void Stopped(IDebugThread2 thread) internal void FireStoppedEvent(IDebugThread2 thread, StoppedEvent.ReasonValue reason, string text = null) { Stopped(thread); + UpdateCapabilities(); // Switch to another thread as engines may not expect to be called back on their event thread ThreadPool.QueueUserWorkItem((o) => @@ -796,7 +797,7 @@ private void SetCategoryGuidExceptions(Guid categoryId, enum_EXCEPTION_STATE sta } } - private void StepInternal(int threadId, enum_STEPKIND stepKind, SteppingGranularity granularity, string errorMessage) + private void StepInternal(int threadId, enum_STEPKIND stepKind, SteppingGranularity granularity, ExecuteDirection stepDirection, string errorMessage) { // If we are already running ignore additional step requests if (!m_isStopped) @@ -830,6 +831,8 @@ private void StepInternal(int threadId, enum_STEPKIND stepKind, SteppingGranular } try { + if (m_program is IDebugReversibleEngineProgram160 rprog) + rprog.SetExecuteDirection(stepDirection); builder.CheckHR(m_program.Step(thread, stepKind, stepUnit)); } catch (AD7Exception) @@ -1020,6 +1023,31 @@ protected override void HandleInitializeRequestAsync(IRequestResponder responder) { const string telemetryEventName = DebuggerTelemetry.TelemetryLaunchEventName; @@ -1453,10 +1481,8 @@ protected override void HandleConfigurationDoneRequestAsync(IRequestResponder responder) + private void ContinueInternal(int threadId, ExecuteDirection direction) { - int threadId = responder.Arguments.ThreadId; - // Sometimes we can get a threadId of 0. Make sure we don't look it up in this case, otherwise we will crash. IDebugThread2 thread = null; lock (m_threads) @@ -1475,13 +1501,10 @@ protected override void HandleContinueRequestAsync(IRequestResponder responder) + { + try + { + ContinueInternal(responder.Arguments.ThreadId, ExecuteDirection.ExecuteDirection_Forward); + responder.SetResponse(new ContinueResponse()); + } + catch (AD7Exception e) + { + responder.SetError(new ProtocolException(e.Message)); + } + } + + protected override void HandleReverseContinueRequestAsync(IRequestResponder responder) + { + try + { + ContinueInternal(responder.Arguments.ThreadId, ExecuteDirection.ExecuteDirection_Reverse); + responder.SetResponse(new ContinueResponse()); + } + catch (AD7Exception e) + { + responder.SetError(new ProtocolException(e.Message)); + } + } + protected override void HandleStepInRequestAsync(IRequestResponder responder) { try { var granularity = responder.Arguments.Granularity.GetValueOrDefault(); - StepInternal(responder.Arguments.ThreadId, enum_STEPKIND.STEP_INTO, granularity, AD7Resources.Error_Scenario_Step_In); + StepInternal(responder.Arguments.ThreadId, enum_STEPKIND.STEP_INTO, granularity, ExecuteDirection.ExecuteDirection_Forward, AD7Resources.Error_Scenario_Step_In); responder.SetResponse(new StepInResponse()); } catch (AD7Exception e) @@ -1511,7 +1560,7 @@ protected override void HandleNextRequestAsync(IRequestResponder try { var granularity = responder.Arguments.Granularity.GetValueOrDefault(); - StepInternal(responder.Arguments.ThreadId, enum_STEPKIND.STEP_OVER, granularity, AD7Resources.Error_Scenario_Step_Next); + StepInternal(responder.Arguments.ThreadId, enum_STEPKIND.STEP_OVER, granularity, ExecuteDirection.ExecuteDirection_Forward, AD7Resources.Error_Scenario_Step_Next); responder.SetResponse(new NextResponse()); } catch (AD7Exception e) @@ -1525,7 +1574,7 @@ protected override void HandleStepOutRequestAsync(IRequestResponder responder) + { + try + { + var granularity = responder.Arguments.Granularity.GetValueOrDefault(); + StepInternal(responder.Arguments.ThreadId, enum_STEPKIND.STEP_OVER, granularity, ExecuteDirection.ExecuteDirection_Reverse, AD7Resources.Error_Scenario_Step_Next); + responder.SetResponse(new StepBackResponse()); + } + catch (AD7Exception e) + { + responder.SetError(new ProtocolException(e.Message)); + } + } + protected override void HandlePauseRequestAsync(IRequestResponder responder) { // TODO: wait for break event @@ -3438,8 +3501,6 @@ public void HandleIDebugBreakEvent2(IDebugEngine2 pEngine, IDebugProcess2 pProce public void HandleIDebugExceptionEvent2(IDebugEngine2 pEngine, IDebugProcess2 pProcess, IDebugProgram2 pProgram, IDebugThread2 pThread, IDebugEvent2 pEvent) { - Stopped(pThread); - IDebugExceptionEvent2 exceptionEvent = (IDebugExceptionEvent2)pEvent; string exceptionDescription; @@ -3455,6 +3516,7 @@ public Task HandleIDebugProgramCreateEvent2(IDebugEngine2 pEngine, IDebugProcess { m_program = pProgram; Protocol.SendEvent(new InitializedEvent()); + UpdateCapabilities(); } return m_configurationDoneTCS.Task; diff --git a/src/OpenDebugAD7/OpenDebug/CustomProtocolObjects.cs b/src/OpenDebugAD7/OpenDebug/CustomProtocolObjects.cs index 9fc599ae3..fef714d9f 100644 --- a/src/OpenDebugAD7/OpenDebug/CustomProtocolObjects.cs +++ b/src/OpenDebugAD7/OpenDebug/CustomProtocolObjects.cs @@ -7,7 +7,7 @@ namespace OpenDebug.CustomProtocolObjects { - public class OpenDebugStoppedEvent : DebugEvent + public sealed class OpenDebugStoppedEvent : DebugEvent { // Summary: // Protocol type for this event. @@ -67,7 +67,7 @@ public OpenDebugStoppedEvent() : base(EventType) public int Column { get; set; } } - public class OpenDebugThread : Thread + public sealed class OpenDebugThread : Thread { public OpenDebugThread(int id, string name) : base() {