diff --git a/src/DebugEngineHost.VSCode/HostMarshal.cs b/src/DebugEngineHost.VSCode/HostMarshal.cs
index c032b4503..f6a243124 100644
--- a/src/DebugEngineHost.VSCode/HostMarshal.cs
+++ b/src/DebugEngineHost.VSCode/HostMarshal.cs
@@ -75,20 +75,6 @@ public static void ReleaseCodeContextId(IntPtr codeContextId)
}
}
- public static IDebugCodeContext2 GetCodeContextForIntPtr(IntPtr codeContextId)
- {
- lock (s_codeContexts)
- {
- IDebugCodeContext2 codeContext;
- if (!s_codeContexts.TryGet(codeContextId.ToInt32(), out codeContext))
- {
- throw new ArgumentOutOfRangeException(nameof(codeContextId));
- }
-
- return codeContext;
- }
- }
-
public static IDebugDocumentPosition2 GetDocumentPositionForIntPtr(IntPtr documentPositionId)
{
lock (s_documentPositions)
@@ -145,7 +131,16 @@ public static IntPtr GetIntPtrForDataBreakpointAddress(string address)
/// code context object
public static IDebugCodeContext2 GetDebugCodeContextForIntPtr(IntPtr contextId)
{
- throw new NotImplementedException();
+ lock (s_codeContexts)
+ {
+ IDebugCodeContext2 codeContext;
+ if (!s_codeContexts.TryGet(contextId.ToInt32(), out codeContext))
+ {
+ throw new ArgumentOutOfRangeException(nameof(contextId));
+ }
+
+ return codeContext;
+ }
}
public static IDebugEventCallback2 GetThreadSafeEventCallback(IDebugEventCallback2 ad7Callback)
diff --git a/src/OpenDebugAD7/AD7DebugSession.cs b/src/OpenDebugAD7/AD7DebugSession.cs
index 648b90807..92bdb195b 100644
--- a/src/OpenDebugAD7/AD7DebugSession.cs
+++ b/src/OpenDebugAD7/AD7DebugSession.cs
@@ -50,6 +50,7 @@ internal sealed class AD7DebugSession : DebugAdapterBase, IDebugPortNotify2, IDe
private int m_nextContextId = 1;
private Dictionary m_functionBreakpoints;
+ private Dictionary m_instructionBreakpoints;
private readonly HandleCollection m_frameHandles;
private IDebugProgram2 m_program;
@@ -119,6 +120,7 @@ public AD7DebugSession(Stream debugAdapterStdIn, Stream debugAdapterStdOut, List
m_frameHandles = new HandleCollection();
m_breakpoints = new Dictionary>();
m_functionBreakpoints = new Dictionary();
+ m_instructionBreakpoints = new Dictionary();
m_variableManager = new VariableManager();
}
@@ -307,6 +309,49 @@ ppBPRequest is AD7BreakPointRequest ad7BreakpointRequest &&
return tracepoints;
}
+ public StoppedEvent.ReasonValue GetStoppedEventReason(IDebugBreakpointEvent2 breakpointEvent)
+ {
+ StoppedEvent.ReasonValue reason = StoppedEvent.ReasonValue.Breakpoint;
+
+ if (breakpointEvent != null)
+ {
+ if (breakpointEvent.EnumBreakpoints(out IEnumDebugBoundBreakpoints2 enumBreakpoints) == HRConstants.S_OK &&
+ enumBreakpoints.GetCount(out uint bpCount) == HRConstants.S_OK &&
+ bpCount > 0)
+ {
+
+ bool allInstructionBreakpoints = true;
+
+ IDebugBoundBreakpoint2[] boundBp = new IDebugBoundBreakpoint2[1];
+ uint fetched = 0;
+ while (enumBreakpoints.Next(1, boundBp, ref fetched) == HRConstants.S_OK)
+ {
+
+ if (boundBp[0].GetPendingBreakpoint(out IDebugPendingBreakpoint2 pendingBreakpoint) == HRConstants.S_OK)
+ {
+ if (pendingBreakpoint.GetBreakpointRequest(out IDebugBreakpointRequest2 breakpointRequest) == HRConstants.S_OK)
+ {
+ AD7BreakPointRequest request = breakpointRequest as AD7BreakPointRequest;
+
+ if (breakpointRequest != null && request.MemoryContext == null)
+ {
+ allInstructionBreakpoints = false;
+ break;
+ }
+ }
+ }
+ }
+
+ if (allInstructionBreakpoints)
+ {
+ reason = StoppedEvent.ReasonValue.InstructionBreakpoint;
+ }
+ }
+ }
+
+ return reason;
+ }
+
private static long FileTimeToPosix(FILETIME ft)
{
long date = ((long)ft.dwHighDateTime << 32) + ft.dwLowDateTime;
@@ -318,9 +363,9 @@ private static long FileTimeToPosix(FILETIME ft)
return date / 10000000;
}
- private int GetMemoryContext(string memoryReference, int? offset, out IDebugMemoryContext2 memoryContext, out ulong address)
+ private ulong ResolveInstructionReference(string memoryReference, int? offset)
{
- memoryContext = null;
+ ulong address;
if (memoryReference.StartsWith("0x", StringComparison.Ordinal))
{
@@ -343,6 +388,15 @@ private int GetMemoryContext(string memoryReference, int? offset, out IDebugMemo
}
}
+ return address;
+ }
+
+ private int GetMemoryContext(string memoryReference, int? offset, out IDebugMemoryContext2 memoryContext, out ulong address)
+ {
+ memoryContext = null;
+
+ address = ResolveInstructionReference(memoryReference, offset);
+
int hr = HRConstants.E_NOTIMPL; // Engine does not support IDebugMemoryBytesDAP
if (m_engine is IDebugMemoryBytesDAP debugMemoryBytesDAPEngine)
@@ -413,11 +467,12 @@ internal void FireStoppedEvent(IDebugThread2 thread, StoppedEvent.ReasonValue re
Protocol.SendEvent(new OpenDebugStoppedEvent()
{
Reason = reason,
+ Text = text,
+ ThreadId = thread.Id(),
+ // Additional Breakpoint Information for Testing/Logging
Source = textPosition.Source,
Line = textPosition.Line,
Column = textPosition.Column,
- Text = text,
- ThreadId = thread.Id()
});
});
@@ -492,7 +547,7 @@ private static IEnumerable GetBoundBreakpoints(IDebugBre
IDebugCodeContext2 codeContext;
try
{
- codeContext = HostMarshal.GetCodeContextForIntPtr(location.unionmember1);
+ codeContext = HostMarshal.GetDebugCodeContextForIntPtr(location.unionmember1);
HostMarshal.ReleaseCodeContextId(location.unionmember1);
location.unionmember1 = IntPtr.Zero;
}
@@ -825,6 +880,7 @@ protected override void HandleInitializeRequestAsync(IRequestResponder responder)
+ {
+ if (responder.Arguments.Breakpoints == null)
+ {
+ responder.SetError(new ProtocolException("HandleSetInstructionBreakpointsRequest failed: Missing 'breakpoints'."));
+ return;
+ }
+
+ ErrorBuilder eb = new ErrorBuilder(() => AD7Resources.Error_UnableToSetInstructionBreakpoint);
+
+ SetInstructionBreakpointsResponse response = new SetInstructionBreakpointsResponse();
+
+ List breakpoints = responder.Arguments.Breakpoints;
+ Dictionary newBreakpoints = new Dictionary();
+ try
+ {
+ HashSet requestAddresses = responder.Arguments.Breakpoints.Select(x => ResolveInstructionReference(x.InstructionReference, x.Offset)).ToHashSet();
+
+ foreach (KeyValuePair b in m_instructionBreakpoints)
+ {
+ if (requestAddresses.Contains(b.Key))
+ {
+ newBreakpoints[b.Key] = b.Value; // breakpoint still in new list
+ }
+ else
+ {
+ IDebugPendingBreakpoint2 pendingBp = b.Value;
+ if (pendingBp != null &&
+ pendingBp.GetBreakpointRequest(out IDebugBreakpointRequest2 request) == HRConstants.S_OK &&
+ request is AD7BreakPointRequest ad7Request)
+ {
+ HostMarshal.ReleaseCodeContextId(ad7Request.MemoryContextIntPtr);
+ }
+ else
+ {
+ Debug.Fail("Why can't we retrieve the MemoryContextIntPtr?");
+ }
+ b.Value.Delete(); // not in new list so delete it
+ }
+ }
+
+ foreach (var instructionBp in responder.Arguments.Breakpoints)
+ {
+ eb.CheckHR(GetMemoryContext(instructionBp.InstructionReference, instructionBp.Offset, out IDebugMemoryContext2 memoryContext, out ulong address));
+
+ if (m_instructionBreakpoints.ContainsKey(address))
+ {
+ IDebugBreakpointRequest2 breakpointRequest;
+ if (m_instructionBreakpoints[address].GetBreakpointRequest(out breakpointRequest) == 0 &&
+ breakpointRequest is AD7BreakPointRequest ad7BPRequest)
+ {
+ // Check to see if this breakpoint has a condition that has changed.
+ if (!StringComparer.Ordinal.Equals(ad7BPRequest.Condition, instructionBp.Condition))
+ {
+ // Condition has been modified. Delete breakpoint so it will be recreated with the updated condition.
+ var toRemove = m_instructionBreakpoints[address];
+ toRemove.Delete();
+ m_instructionBreakpoints.Remove(address);
+ }
+ else
+ {
+ if (ad7BPRequest.BindResult != null)
+ {
+ response.Breakpoints.Add(ad7BPRequest.BindResult);
+ }
+ else
+ {
+ response.Breakpoints.Add(new Breakpoint()
+ {
+ Id = (int)ad7BPRequest.Id,
+ Verified = true,
+ Line = 0
+ });
+
+ }
+ continue;
+ }
+ }
+ }
+ else
+ {
+ IDebugPendingBreakpoint2 pendingBp;
+ AD7BreakPointRequest pBPRequest = new AD7BreakPointRequest(memoryContext);
+
+ eb.CheckHR(m_engine.CreatePendingBreakpoint(pBPRequest, out pendingBp));
+
+ if (pendingBp != null && pendingBp.Bind() == HRConstants.S_OK)
+ {
+ newBreakpoints[address] = pendingBp;
+ response.Breakpoints.Add(new Breakpoint()
+ {
+ Id = (int)pBPRequest.Id,
+ Verified = true,
+ Line = 0
+ }); // success
+ }
+ else
+ {
+ response.Breakpoints.Add(new Breakpoint()
+ {
+ Id = (int)pBPRequest.Id,
+ Verified = false,
+ Line = 0,
+ Message = string.Format(CultureInfo.CurrentCulture, AD7Resources.Error_UnableToSetInstructionBreakpoint, address)
+ }); // couldn't create and/or bind
+ }
+ }
+ }
+
+ m_instructionBreakpoints = newBreakpoints;
+
+ responder.SetResponse(response);
+ }
+ catch (Exception e)
+ {
+ responder.SetError(new ProtocolException(e.Message));
+ }
+ }
+
+ #endregion
-#region IDebugPortNotify2
+ #region IDebugPortNotify2
int IDebugPortNotify2.AddProgramNode(IDebugProgramNode2 programNode)
{
@@ -2693,7 +2868,9 @@ public void HandleIDebugEntryPointEvent2(IDebugEngine2 pEngine, IDebugProcess2 p
public void HandleIDebugBreakpointEvent2(IDebugEngine2 pEngine, IDebugProcess2 pProcess, IDebugProgram2 pProgram, IDebugThread2 pThread, IDebugEvent2 pEvent)
{
- IList tracepoints = GetTracepoints(pEvent as IDebugBreakpointEvent2);
+ IDebugBreakpointEvent2 breakpointEvent = pEvent as IDebugBreakpointEvent2;
+ StoppedEvent.ReasonValue reason = GetStoppedEventReason(breakpointEvent);
+ IList tracepoints = GetTracepoints(breakpointEvent);
if (tracepoints.Any())
{
ThreadPool.QueueUserWorkItem((o) =>
@@ -2724,13 +2901,13 @@ public void HandleIDebugBreakpointEvent2(IDebugEngine2 pEngine, IDebugProcess2 p
}
else
{
- FireStoppedEvent(pThread, StoppedEvent.ReasonValue.Breakpoint);
+ FireStoppedEvent(pThread, reason);
}
});
}
else
{
- FireStoppedEvent(pThread, StoppedEvent.ReasonValue.Breakpoint);
+ FireStoppedEvent(pThread, reason);
}
}
diff --git a/src/OpenDebugAD7/AD7Impl/AD7BreakPointRequest.cs b/src/OpenDebugAD7/AD7Impl/AD7BreakPointRequest.cs
index 96d36f1d7..63f70afa0 100644
--- a/src/OpenDebugAD7/AD7Impl/AD7BreakPointRequest.cs
+++ b/src/OpenDebugAD7/AD7Impl/AD7BreakPointRequest.cs
@@ -23,6 +23,13 @@ static public uint GetNextBreakpointId()
public AD7FunctionPosition FunctionPosition { get; private set; }
+ public IDebugMemoryContext2 MemoryContext { get; private set; }
+
+ // Used for Releasing the MemoryContext.
+ // Caller of AD7BreakPointRequest(MemoryContext) is required to
+ // release it with HostMarshal.ReleaseCodeContextId
+ public IntPtr MemoryContextIntPtr { get; private set; }
+
// Unique identifier for breakpoint when communicating with VSCode
public uint Id { get; private set; }
@@ -41,6 +48,11 @@ public AD7BreakPointRequest(string functionName)
FunctionPosition = new AD7FunctionPosition(functionName);
}
+ public AD7BreakPointRequest(IDebugMemoryContext2 memoryContext)
+ {
+ MemoryContext = memoryContext;
+ }
+
public int GetLocationType(enum_BP_LOCATION_TYPE[] pBPLocationType)
{
if (DocumentPosition != null)
@@ -51,6 +63,10 @@ public int GetLocationType(enum_BP_LOCATION_TYPE[] pBPLocationType)
{
pBPLocationType[0] = enum_BP_LOCATION_TYPE.BPLT_CODE_FUNC_OFFSET;
}
+ else if (MemoryContext != null)
+ {
+ pBPLocationType[0] = enum_BP_LOCATION_TYPE.BPLT_CODE_CONTEXT;
+ }
return 0;
}
@@ -71,6 +87,13 @@ public int GetRequestInfo(enum_BPREQI_FIELDS dwFields, BP_REQUEST_INFO[] pBPRequ
pBPRequestInfo[0].bpLocation.bpLocationType = (uint)enum_BP_LOCATION_TYPE.BPLT_CODE_FUNC_OFFSET;
pBPRequestInfo[0].bpLocation.unionmember2 = HostMarshal.RegisterFunctionPosition(FunctionPosition);
}
+ else if (MemoryContext != null)
+ {
+ pBPRequestInfo[0].bpLocation.bpLocationType = (uint)enum_BP_LOCATION_TYPE.BPLT_CODE_CONTEXT;
+ MemoryContextIntPtr = HostMarshal.RegisterCodeContext(MemoryContext as IDebugCodeContext2);
+ pBPRequestInfo[0].bpLocation.unionmember1 = MemoryContextIntPtr;
+
+ }
}
if ((dwFields & enum_BPREQI_FIELDS.BPREQI_CONDITION) != 0 && !string.IsNullOrWhiteSpace(Condition))
{
diff --git a/src/OpenDebugAD7/AD7Resources.Designer.cs b/src/OpenDebugAD7/AD7Resources.Designer.cs
index b029c99c5..0fe3d5d0d 100644
--- a/src/OpenDebugAD7/AD7Resources.Designer.cs
+++ b/src/OpenDebugAD7/AD7Resources.Designer.cs
@@ -19,7 +19,7 @@ namespace OpenDebugAD7 {
// class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project.
- [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")]
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class AD7Resources {
@@ -406,6 +406,15 @@ internal static string Error_UnableToSetBreakpoint {
}
}
+ ///
+ /// Looks up a localized string similar to Error setting instruction breakpoint: {0}.
+ ///
+ internal static string Error_UnableToSetInstructionBreakpoint {
+ get {
+ return ResourceManager.GetString("Error_UnableToSetInstructionBreakpoint", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to '{0}' cannot be assigned to.
///
diff --git a/src/OpenDebugAD7/AD7Resources.resx b/src/OpenDebugAD7/AD7Resources.resx
index bc916064d..aa0a29773 100644
--- a/src/OpenDebugAD7/AD7Resources.resx
+++ b/src/OpenDebugAD7/AD7Resources.resx
@@ -301,4 +301,7 @@
Set next statement is not supported by the current debugger.
+
+ Error setting instruction breakpoint: {0}
+
\ No newline at end of file
diff --git a/test/CppTests/Tests/MemoryTests.cs b/test/CppTests/Tests/MemoryTests.cs
new file mode 100644
index 000000000..62269d5b4
--- /dev/null
+++ b/test/CppTests/Tests/MemoryTests.cs
@@ -0,0 +1,127 @@
+// 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.Linq;
+using System.Net;
+using System.Threading;
+using DebuggerTesting;
+using DebuggerTesting.Compilation;
+using DebuggerTesting.OpenDebug;
+using DebuggerTesting.OpenDebug.Commands;
+using DebuggerTesting.OpenDebug.CrossPlatCpp;
+using DebuggerTesting.OpenDebug.Events;
+using DebuggerTesting.OpenDebug.Extensions;
+using DebuggerTesting.Ordering;
+using DebuggerTesting.Utilities;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace CppTests.Tests
+{
+ [TestCaseOrderer(DependencyTestOrderer.TypeName, DependencyTestOrderer.AssemblyName)]
+ public class MemoryTests : TestBase
+ {
+ #region Constructor
+
+ public MemoryTests(ITestOutputHelper outputHelper) : base(outputHelper)
+ {
+ }
+
+ #endregion
+
+ #region Methods
+
+ [Theory]
+ [RequiresTestSettings]
+ public void CompileKitchenSinkForBreakpointTests(ITestSettings settings)
+ {
+ this.TestPurpose("Compiles the kitchen sink debuggee.");
+ this.WriteSettings(settings);
+
+ IDebuggee debuggee = SinkHelper.OpenAndCompile(this, settings.CompilerSettings, DebuggeeMonikers.KitchenSink.Breakpoint);
+ }
+
+ [Theory]
+ [DependsOnTest(nameof(CompileKitchenSinkForBreakpointTests))]
+ [RequiresTestSettings]
+ public void InstructionBreakpointsBasic(ITestSettings settings)
+ {
+ this.TestPurpose("Tests basic operation of instruction breakpoints");
+ this.WriteSettings(settings);
+
+ IDebuggee debuggee = SinkHelper.Open(this, settings.CompilerSettings, DebuggeeMonikers.KitchenSink.Breakpoint);
+
+ using (IDebuggerRunner runner = CreateDebugAdapterRunner(settings))
+ {
+ this.Comment("Configure launch");
+ runner.Launch(settings.DebuggerSettings, debuggee, "-fCalling");
+
+ SourceBreakpoints mainBreakpoints = debuggee.Breakpoints(SinkHelper.Main, 33);
+
+ this.Comment("Set initial breakpoints");
+ runner.SetBreakpoints(mainBreakpoints);
+
+ this.Comment("Launch and run until first breakpoint");
+ runner.Expects.HitBreakpointEvent(SinkHelper.Main, 33)
+ .AfterConfigurationDone();
+
+ string ip = string.Empty;
+
+ this.Comment("Inspect the stack and try evaluation.");
+ using (IThreadInspector inspector = runner.GetThreadInspector())
+ {
+ this.Comment("Get the stack trace");
+ IFrameInspector mainFrame = inspector.Stack.First();
+ inspector.AssertStackFrameNames(true, "main.*");
+
+ this.WriteLine("Main frame: {0}", mainFrame);
+ ip = mainFrame?.InstructionPointerReference;
+ }
+
+ Assert.False(string.IsNullOrEmpty(ip));
+
+ // Send Disassemble Request to get the current instruction and next one.
+ this.WriteLine("Disassemble to get current and next instruction.");
+ IEnumerable instructions = runner.Disassemble(ip, 2);
+
+ // Validate that we got two instructions.
+ Assert.Equal(2, instructions.Count());
+
+ // Get the next instruction's address
+ string nextIPAddress = instructions.Last().Address;
+ Assert.False(string.IsNullOrEmpty(nextIPAddress));
+
+ // Set an instruction breakpoint
+ this.Comment("Set Instruction Breakpoint");
+ InstructionBreakpoints instruction = new InstructionBreakpoints(new string[] { nextIPAddress });
+ runner.SetInstructionBreakpoints(instruction);
+
+ // Expect it to be hit.
+ runner.Expects.HitInstructionBreakpointEvent(nextIPAddress).AfterContinue();
+
+ // Get the Stack Trace to validate the current frame's ipReference is the one set from the InstructionBp
+ using (IThreadInspector inspector = runner.GetThreadInspector())
+ {
+ this.Comment("Get the instruction bp's stack trace");
+ IFrameInspector mainFrame = inspector.Stack.First();
+ ip = mainFrame?.InstructionPointerReference;
+
+ Assert.False(string.IsNullOrEmpty(ip));
+
+ Assert.Equal(nextIPAddress, ip);
+ }
+
+ this.Comment("Continue until end");
+ runner.Expects.ExitedEvent()
+ .TerminatedEvent()
+ .AfterContinue();
+
+ runner.DisconnectAndVerify();
+ }
+ }
+
+ #endregion
+ }
+}
diff --git a/test/DebuggerTesting/OpenDebug/Commands/DisassembleCommand.cs b/test/DebuggerTesting/OpenDebug/Commands/DisassembleCommand.cs
new file mode 100644
index 000000000..c8684f692
--- /dev/null
+++ b/test/DebuggerTesting/OpenDebug/Commands/DisassembleCommand.cs
@@ -0,0 +1,34 @@
+// 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;
+using Newtonsoft.Json;
+using System;
+
+namespace DebuggerTesting.OpenDebug.Commands
+{
+ public sealed class DisassembleArgs : JsonValue
+ {
+ public string memoryReference;
+
+ public int? offset;
+
+ public int? instructionOffset;
+
+ public int instructionCount;
+
+ public bool? resolveSymbols;
+ }
+
+ public class DisassembleCommand : CommandWithResponse
+ {
+ public DisassembleCommand(string memoryReference, int? offset, int? instructionOffset, int instructionCount, bool? resolveSymbols) : base("disassemble")
+ {
+ this.Args.memoryReference = memoryReference;
+ this.Args.offset = offset;
+ this.Args.instructionOffset = instructionOffset;
+ this.Args.instructionCount = instructionCount;
+ this.Args.resolveSymbols = resolveSymbols;
+ }
+ }
+}
diff --git a/test/DebuggerTesting/OpenDebug/Commands/ReadMemoryCommand.cs b/test/DebuggerTesting/OpenDebug/Commands/ReadMemoryCommand.cs
new file mode 100644
index 000000000..3dd9140ad
--- /dev/null
+++ b/test/DebuggerTesting/OpenDebug/Commands/ReadMemoryCommand.cs
@@ -0,0 +1,29 @@
+// 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;
+using Newtonsoft.Json;
+using System;
+
+namespace DebuggerTesting.OpenDebug.Commands
+{
+ public sealed class ReadMemoryArgs : JsonValue
+ {
+ public string memoryReference;
+
+ [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
+ public int? offset;
+
+ public int count;
+ }
+
+ public class ReadMemoryCommand : CommandWithResponse
+ {
+ public ReadMemoryCommand(string reference, int? offset, int count) : base("readMemory")
+ {
+ this.Args.memoryReference = reference;
+ this.Args.offset = offset;
+ this.Args.count = count;
+ }
+ }
+}
diff --git a/test/DebuggerTesting/OpenDebug/Commands/Responses/DisassembleResponseValue.cs b/test/DebuggerTesting/OpenDebug/Commands/Responses/DisassembleResponseValue.cs
new file mode 100644
index 000000000..9b9b8e8ec
--- /dev/null
+++ b/test/DebuggerTesting/OpenDebug/Commands/Responses/DisassembleResponseValue.cs
@@ -0,0 +1,45 @@
+// 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 DisassembleResponseValue : CommandResponseValue
+ {
+ public sealed class Body
+ {
+ public sealed class DisassembledInstruction
+ {
+ public string address;
+
+ [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
+ public string instructionBytes;
+
+ public string instruction;
+
+ [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
+ public string symbol;
+
+ [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
+ public Source location;
+
+ [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
+ public int? line;
+
+ [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
+ public int? column;
+
+ [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
+ public int? endLine;
+
+ [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
+ public int? endColumn;
+ }
+
+ public DisassembledInstruction[] instructions;
+ }
+
+ public Body body = new Body();
+ }
+}
diff --git a/test/DebuggerTesting/OpenDebug/Commands/Responses/ReadMemoryResponseValue.cs b/test/DebuggerTesting/OpenDebug/Commands/Responses/ReadMemoryResponseValue.cs
new file mode 100644
index 000000000..0f1878df5
--- /dev/null
+++ b/test/DebuggerTesting/OpenDebug/Commands/Responses/ReadMemoryResponseValue.cs
@@ -0,0 +1,23 @@
+// 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 ReadMemoryResponseValue : CommandResponseValue
+ {
+ public sealed class Body
+ {
+ public string address;
+
+ [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
+ public int unreadableBytes;
+
+ [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
+ public string data;
+ }
+
+ public Body body = new Body();
+ }
+}
diff --git a/test/DebuggerTesting/OpenDebug/Commands/Responses/StackTraceResponseValue.cs b/test/DebuggerTesting/OpenDebug/Commands/Responses/StackTraceResponseValue.cs
index 64b4611ce..65e2a95b8 100644
--- a/test/DebuggerTesting/OpenDebug/Commands/Responses/StackTraceResponseValue.cs
+++ b/test/DebuggerTesting/OpenDebug/Commands/Responses/StackTraceResponseValue.cs
@@ -24,6 +24,9 @@ public sealed class StackFrame
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
public Source source;
+
+ [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
+ public string instructionPointerReference;
}
public StackFrame[] stackFrames;
}
diff --git a/test/DebuggerTesting/OpenDebug/Commands/SetInstructionBreakpointCommand.cs b/test/DebuggerTesting/OpenDebug/Commands/SetInstructionBreakpointCommand.cs
new file mode 100644
index 000000000..82872460c
--- /dev/null
+++ b/test/DebuggerTesting/OpenDebug/Commands/SetInstructionBreakpointCommand.cs
@@ -0,0 +1,98 @@
+// // 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;
+using Newtonsoft.Json;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace DebuggerTesting.OpenDebug.Commands
+{
+
+ #region SetInstructionBreakpointCommandArgs
+
+ public sealed class SetInstructionBreakpointCommandArgs : JsonValue
+ {
+ public sealed class InstructionBreakpoint
+ {
+ public InstructionBreakpoint(string instructionReference, string condition = null)
+ {
+ this.instructionReference = instructionReference;
+ this.condition = condition;
+ }
+
+ public string instructionReference;
+
+ [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
+ public int? offset;
+
+ [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
+ public string condition;
+ }
+
+ public InstructionBreakpoint[] breakpoints;
+ }
+
+ #endregion
+
+ #region InstructionBreakpoints
+
+ public sealed class InstructionBreakpoints
+ {
+ private List breakpoints;
+
+ public InstructionBreakpoints()
+ {
+ this.breakpoints = new List();
+ }
+
+ public InstructionBreakpoints(params string[] addresses)
+ : this()
+ {
+ foreach (string address in addresses)
+ {
+ this.Add(address);
+ }
+ }
+
+ public InstructionBreakpoints Add(string address, string condition = null)
+ {
+ this.breakpoints.Add(new SetInstructionBreakpointCommandArgs.InstructionBreakpoint(address, condition));
+ return this;
+ }
+
+ public InstructionBreakpoints Remove(string address)
+ {
+ this.breakpoints.RemoveAll(bp => String.Equals(bp.instructionReference, address, StringComparison.Ordinal));
+ return this;
+ }
+
+ internal IList Breakpoints
+ {
+ get { return this.breakpoints; }
+ }
+ }
+
+ #endregion
+
+ public class SetInstructionBreakpointsCommand : CommandWithResponse
+ {
+ public SetInstructionBreakpointsCommand() : base("setInstructionBreakpoints")
+ {
+ }
+
+ public SetInstructionBreakpointsCommand(InstructionBreakpoints breakpoints) :
+ this()
+ {
+ this.Args.breakpoints = breakpoints.Breakpoints.ToArray();
+ }
+
+ public override string ToString()
+ {
+ return "{0} ({1})".FormatInvariantWithArgs(base.ToString(), String.Join(", ", this.Args.breakpoints.Select(bp => bp.instructionReference)));
+ }
+ }
+}
\ No newline at end of file
diff --git a/test/DebuggerTesting/OpenDebug/Events/StoppedEvent.cs b/test/DebuggerTesting/OpenDebug/Events/StoppedEvent.cs
index 6be870949..547fa007e 100644
--- a/test/DebuggerTesting/OpenDebug/Events/StoppedEvent.cs
+++ b/test/DebuggerTesting/OpenDebug/Events/StoppedEvent.cs
@@ -16,7 +16,8 @@ public enum StoppedReason
Breakpoint,
Pause,
Exception,
- Entry
+ Entry,
+ InstructionBreakpoint
}
#region StoppedEventValue
@@ -63,6 +64,18 @@ public class StoppedEvent : Event
private bool verifyLineRange;
private int startLine;
private int endLine;
+ private ulong address;
+
+ public StoppedEvent(ulong address)
+ : base("stopped")
+ {
+
+ this.address = address;
+
+ this.ExpectedResponse.body.reason = FromReason(StoppedReason.InstructionBreakpoint);
+
+ this.verifyLineRange = false;
+ }
public StoppedEvent(StoppedReason? reason = null, string fileName = null, int? lineNumber = null, string text = null)
: base("stopped")
@@ -106,6 +119,12 @@ private static string FromReason(StoppedReason? reason)
return null;
Parameter.ThrowIfIsInvalid(reason.Value, StoppedReason.Unknown, nameof(reason));
+
+ if (reason == StoppedReason.InstructionBreakpoint)
+ {
+ return "instruction breakpoint";
+ }
+
return Enum.GetName(typeof(StoppedReason), reason.Value).ToLowerInvariant();
}
diff --git a/test/DebuggerTesting/OpenDebug/Extensions/DebuggerRunnerExtensions.cs b/test/DebuggerTesting/OpenDebug/Extensions/DebuggerRunnerExtensions.cs
index a732fc94c..71373b9b2 100644
--- a/test/DebuggerTesting/OpenDebug/Extensions/DebuggerRunnerExtensions.cs
+++ b/test/DebuggerTesting/OpenDebug/Extensions/DebuggerRunnerExtensions.cs
@@ -7,7 +7,9 @@
using DebuggerTesting.OpenDebug.Commands;
using DebuggerTesting.OpenDebug.Commands.Responses;
using DebuggerTesting.OpenDebug.Events;
+using Newtonsoft.Json;
using Xunit;
+using static DebuggerTesting.OpenDebug.Commands.Responses.DisassembleResponseValue.Body;
namespace DebuggerTesting.OpenDebug.Extensions
{
@@ -18,6 +20,19 @@ public interface IThreadInfo
IThreadInspector GetThreadInspector();
}
+ public interface IDisassemblyInstruction
+ {
+ public string Address { get; }
+ public string InstructionBytes { get; }
+ public string Instruction { get; }
+ public string Symbol { get; }
+ public Source Location { get; }
+ public int? Line { get; }
+ public int? Column { get; }
+ public int? EndLine { get; }
+ public int? EndColumn { get; }
+ }
+
public static class DebuggerRunnerExtensions
{
///
@@ -141,6 +156,36 @@ public IThreadInspector GetThreadInspector()
#endregion
+ #region DisassembleInstruction
+
+ internal class DisassemblyInstruction: IDisassemblyInstruction
+ {
+ public string Address { get; private set; }
+ public string InstructionBytes { get; private set; }
+ public string Instruction { get; private set; }
+ public string Symbol { get; private set; }
+ public Source Location { get; private set; }
+ public int? Line { get; private set; }
+ public int? Column { get; private set; }
+ public int? EndLine { get; private set; }
+ public int? EndColumn { get; private set; }
+
+ public DisassemblyInstruction(DisassembledInstruction disassembledInstruction)
+ {
+ this.Address = disassembledInstruction.address;
+ this.InstructionBytes = disassembledInstruction.instructionBytes;
+ this.Instruction = disassembledInstruction.instruction;
+ this.Symbol = disassembledInstruction.symbol;
+ this.Location = disassembledInstruction.location;
+ this.Line = disassembledInstruction.line;
+ this.Column = disassembledInstruction.column;
+ this.EndLine = disassembledInstruction.endLine;
+ this.EndColumn = disassembledInstruction.endColumn;
+ }
+ }
+
+ #endregion
+
public static void RunCommandExpectFailure(this IDebuggerRunner runner, ICommand command)
{
command.ExpectsSuccess = false;
@@ -162,6 +207,11 @@ public static SetBreakpointsResponseValue SetFunctionBreakpoints(this IDebuggerR
return runner.RunCommand(new SetFunctionBreakpointsCommand(breakpoints));
}
+ public static SetBreakpointsResponseValue SetInstructionBreakpoints(this IDebuggerRunner runner, InstructionBreakpoints breakpoints)
+ {
+ return runner.RunCommand(new SetInstructionBreakpointsCommand(breakpoints));
+ }
+
public static void Continue(this IDebuggerRunner runner)
{
runner.RunCommand(new ContinueCommand(runner.StoppedThreadId));
@@ -172,6 +222,18 @@ public static void ConfigurationDone(this IDebuggerRunner runner)
runner.RunCommand(new ConfigurationDoneCommand());
}
+ public static ReadMemoryResponseValue ReadMemory(this IDebuggerRunner runner, string reference, int? offset, int count)
+ {
+ ReadMemoryResponseValue response = runner.RunCommand(new ReadMemoryCommand(reference, offset, count));
+ return response;
+ }
+
+ public static IEnumerable Disassemble(this IDebuggerRunner runner, string memoryReference, int instructionCount)
+ {
+ DisassembleResponseValue response = runner.RunCommand(new DisassembleCommand(memoryReference, 0, 0, instructionCount, false));
+ return response?.body?.instructions.Select(i => new DisassemblyInstruction(i));
+ }
+
///
/// Adds an expected stop event within a range of line numbers. If the stop does not occur on , then perform a step over
/// and verify that the debuggee breaks at the .
diff --git a/test/DebuggerTesting/OpenDebug/Extensions/FrameInspector.cs b/test/DebuggerTesting/OpenDebug/Extensions/FrameInspector.cs
index 05e549077..ce1e5b06b 100644
--- a/test/DebuggerTesting/OpenDebug/Extensions/FrameInspector.cs
+++ b/test/DebuggerTesting/OpenDebug/Extensions/FrameInspector.cs
@@ -20,10 +20,11 @@ internal class FrameInspector : DisposableObject, IFrameInspector
private int? line;
private int? column;
private int? sourceReference;
+ private string instructionPointerReference;
#region Constructor/Dispose
- public FrameInspector(IDebuggerRunner runner, string name, int id, string sourceName, string sourcePath, int? sourceReference, int? line, int? column)
+ public FrameInspector(IDebuggerRunner runner, string name, int id, string sourceName, string sourcePath, int? sourceReference, int? line, int? column, string instructionPointerReference)
{
Parameter.ThrowIfNull(runner, nameof(runner));
this.DebuggerRunner = runner;
@@ -34,6 +35,7 @@ public FrameInspector(IDebuggerRunner runner, string name, int id, string source
this.sourceReference = sourceReference;
this.line = line;
this.column = column;
+ this.instructionPointerReference = instructionPointerReference;
}
protected override void Dispose(bool isDisposing)
@@ -116,6 +118,15 @@ public int? Column
}
}
+ public string InstructionPointerReference
+ {
+ get
+ {
+ this.VerifyNotDisposed();
+ return this.instructionPointerReference;
+ }
+ }
+
#endregion
///
diff --git a/test/DebuggerTesting/OpenDebug/Extensions/IInspectors.cs b/test/DebuggerTesting/OpenDebug/Extensions/IInspectors.cs
index 593d82feb..caf6b2aff 100644
--- a/test/DebuggerTesting/OpenDebug/Extensions/IInspectors.cs
+++ b/test/DebuggerTesting/OpenDebug/Extensions/IInspectors.cs
@@ -67,8 +67,11 @@ public interface IFrameInspector : IVariableExpander, IInspector
int? SourceReference { get; }
int? Line { get; }
+
int? Column { get; }
+ string InstructionPointerReference { get; }
+
///
/// Evaluates an expression on this frame
///
diff --git a/test/DebuggerTesting/OpenDebug/Extensions/ThreadInspector.cs b/test/DebuggerTesting/OpenDebug/Extensions/ThreadInspector.cs
index 6b2510937..ba614f451 100644
--- a/test/DebuggerTesting/OpenDebug/Extensions/ThreadInspector.cs
+++ b/test/DebuggerTesting/OpenDebug/Extensions/ThreadInspector.cs
@@ -65,8 +65,9 @@ public IEnumerable Stack
int? sourceReference = stackFrame.source?.sourceReference;
int? line = stackFrame.line;
int? column = stackFrame.column;
+ string instructionPointerReference = stackFrame.instructionPointerReference;
- FrameInspector frame = new FrameInspector(this.DebuggerRunner, name, id, sourceName, sourcePath, sourceReference, line, column);
+ FrameInspector frame = new FrameInspector(this.DebuggerRunner, name, id, sourceName, sourcePath, sourceReference, line, column, instructionPointerReference);
this.frameInspectors.Add(frame);
yield return frame;
}
diff --git a/test/DebuggerTesting/OpenDebug/RunBuilder.cs b/test/DebuggerTesting/OpenDebug/RunBuilder.cs
index 217e673de..6e0df5945 100644
--- a/test/DebuggerTesting/OpenDebug/RunBuilder.cs
+++ b/test/DebuggerTesting/OpenDebug/RunBuilder.cs
@@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
+using System.Net;
using DebuggerTesting.OpenDebug.Commands;
using DebuggerTesting.OpenDebug.Events;
@@ -45,6 +46,22 @@ public static IRunBuilder StoppedEvent(this IRunBuilder runBuilder, StoppedReaso
return runBuilder.Event(new StoppedEvent(reason, fileName, lineNumber, text));
}
+ public static IRunBuilder HitInstructionBreakpointEvent(this IRunBuilder runBuilder, string address)
+ {
+ ulong nextAddress;
+
+ if (address.StartsWith("0x", StringComparison.Ordinal))
+ {
+ nextAddress = Convert.ToUInt64(address.Substring(2), 16);
+ }
+ else
+ {
+ nextAddress = Convert.ToUInt64(address, 10);
+ }
+
+ return runBuilder.Event(new StoppedEvent(nextAddress));
+ }
+
public static IRunBuilder HitBreakpointEvent(this IRunBuilder runBuilder, string fileName = null, int? lineNumber = null, string text = null)
{
return runBuilder.StoppedEvent(StoppedReason.Breakpoint, fileName, lineNumber, text);