diff --git a/src/MICore/CommandFactories/MICommandFactory.cs b/src/MICore/CommandFactories/MICommandFactory.cs index c73d5c088..e224f761a 100644 --- a/src/MICore/CommandFactories/MICommandFactory.cs +++ b/src/MICore/CommandFactories/MICommandFactory.cs @@ -246,6 +246,12 @@ public async Task ExecNextInstruction(int threadId, ResultClass resultClass = Re await ThreadFrameCmdAsync(command, resultClass, threadId, 0); } + /// + /// Jumps to a specified target location + /// + abstract public Task ExecJump(string filename, int line); + abstract public Task ExecJump(ulong address); + /// /// Tells GDB to spawn a target process previous setup with -file-exec-and-symbols or similar /// diff --git a/src/MICore/CommandFactories/clrdbg.cs b/src/MICore/CommandFactories/clrdbg.cs index 05523bf6e..13006e318 100644 --- a/src/MICore/CommandFactories/clrdbg.cs +++ b/src/MICore/CommandFactories/clrdbg.cs @@ -237,6 +237,16 @@ public override Task Catch(string name, bool onlyOnce = false, ResultClass resul throw new NotImplementedException("clrdbg catch command"); } + public override Task ExecJump(string filename, int line) + { + throw new NotImplementedException("clrdbg jump command"); + } + + public override Task ExecJump(ulong address) + { + throw new NotImplementedException("clrdbg jump command"); + } + public override string GetTargetArchitectureCommand() { return null; diff --git a/src/MICore/CommandFactories/gdb.cs b/src/MICore/CommandFactories/gdb.cs index 3382fbfe0..0a4cb0602 100644 --- a/src/MICore/CommandFactories/gdb.cs +++ b/src/MICore/CommandFactories/gdb.cs @@ -157,7 +157,7 @@ public override async Task ThreadInfo(uint? threadId = null) public override async Task> StartAddressesForLine(string file, uint line) { - string cmd = "info line " + file + ":" + line; + string cmd = "info line -s " + file + " -li " + line; var result = await _debugger.ConsoleCmdAsync(cmd, allowWhileRunning: false); List addresses = new List(); using (StringReader stringReader = new StringReader(result)) @@ -173,7 +173,7 @@ public override async Task> StartAddressesForLine(string file, uint { ulong address; string addrStr = resultLine.Substring(pos + 18); - if (MICommandFactory.SpanNextAddr(addrStr, out address) != null) + if (SpanNextAddr(addrStr, out address) != null) { addresses.Add(address); } @@ -183,6 +183,25 @@ public override async Task> StartAddressesForLine(string file, uint return addresses; } + private async Task JumpInternal(string target) + { + // temporary breakpoint + jump + await _debugger.CmdAsync("-break-insert -t " + target, ResultClass.done); + await _debugger.CmdAsync("-exec-jump " + target, ResultClass.running); + } + + public override Task ExecJump(string filename, int line) + { + string target = "--source " + filename + " --line " + line.ToString(CultureInfo.InvariantCulture); + return JumpInternal(target); + } + + public override Task ExecJump(ulong address) + { + string target = "*" + string.Format(CultureInfo.InvariantCulture, "0x{0:X}", address); + return JumpInternal(target); + } + public override 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 e571841ca..60bff3b68 100644 --- a/src/MICore/CommandFactories/lldb.cs +++ b/src/MICore/CommandFactories/lldb.cs @@ -177,6 +177,19 @@ public override Task Catch(string name, bool onlyOnce = false, ResultClass resul throw new NotImplementedException("lldb catch command"); } + // TODO: update these if they become available in lldb-mi + public override async Task ExecJump(string filename, int line) + { + string command = "jump " + filename + ":" + line; + await _debugger.CmdAsync(command, ResultClass.running); + } + + public override async Task ExecJump(ulong address) + { + string command = "jump *" + string.Format("0x{0:X}", address); + await _debugger.CmdAsync(command, ResultClass.running); + } + /// /// Assigns the value of an expression to a variable. /// Since LLDB only accepts assigning values to variables, the expression may need to be evaluated. diff --git a/src/MIDebugEngine/AD7.Impl/AD7Engine.cs b/src/MIDebugEngine/AD7.Impl/AD7Engine.cs index f3f404244..f44448a38 100755 --- a/src/MIDebugEngine/AD7.Impl/AD7Engine.cs +++ b/src/MIDebugEngine/AD7.Impl/AD7Engine.cs @@ -187,6 +187,44 @@ public object GetMetric(string metric) return _configStore.GetEngineMetric(metric); } + public int Jump(string filename, int line) + { + try + { + _debuggedProcess.WorkerThread.RunOperation(() => _debuggedProcess.Jump(filename, line)); + } + catch (InvalidCoreDumpOperationException) + { + return AD7_HRESULT.E_CRASHDUMP_UNSUPPORTED; + } + catch (Exception e) + { + _engineCallback.OnError(EngineUtils.GetExceptionDescription(e)); + return Constants.E_ABORT; + } + + return Constants.S_OK; + } + + public int Jump(ulong address) + { + try + { + _debuggedProcess.WorkerThread.RunOperation(() => _debuggedProcess.Jump(address)); + } + catch (InvalidCoreDumpOperationException) + { + return AD7_HRESULT.E_CRASHDUMP_UNSUPPORTED; + } + catch (Exception e) + { + _engineCallback.OnError(EngineUtils.GetExceptionDescription(e)); + return Constants.E_ABORT; + } + + return Constants.S_OK; + } + #region IDebugEngine2 Members // Attach the debug engine to a program. diff --git a/src/MIDebugEngine/AD7.Impl/AD7MemoryAddress.cs b/src/MIDebugEngine/AD7.Impl/AD7MemoryAddress.cs index f478379ad..0be7d6e79 100644 --- a/src/MIDebugEngine/AD7.Impl/AD7MemoryAddress.cs +++ b/src/MIDebugEngine/AD7.Impl/AD7MemoryAddress.cs @@ -2,11 +2,8 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System; -using System.Collections.Generic; -using System.Text; using Microsoft.VisualStudio.Debugger.Interop; using MICore; -using Microsoft.MIDebugEngine.Natvis; namespace Microsoft.MIDebugEngine { @@ -14,7 +11,7 @@ namespace Microsoft.MIDebugEngine // IDebugMemoryContext2 represents a position in the address space of the machine running the program being debugged. // IDebugCodeContext2 represents the starting position of a code instruction. // For most run-time architectures today, a code context can be thought of as an address in a program's execution stream. - internal class AD7MemoryAddress : IDebugCodeContext2 + internal sealed class AD7MemoryAddress : IDebugCodeContext2 { private readonly AD7Engine _engine; private readonly ulong _address; @@ -42,6 +39,7 @@ public void SetDocumentContext(IDebugDocumentContext2 docContext) // Adds a specified value to the current context's address to create a new context. public int Add(ulong dwCount, out IDebugMemoryContext2 newAddress) { + // FIXME: this is not correct for IDebugCodeContext2 newAddress = new AD7MemoryAddress(_engine, (uint)dwCount + _address, null); return Constants.S_OK; } @@ -160,19 +158,15 @@ public int GetInfo(enum_CONTEXT_INFO_FIELDS dwFields, CONTEXT_INFO[] pinfo) { pinfo[0].dwFields = 0; - if ((dwFields & enum_CONTEXT_INFO_FIELDS.CIF_ADDRESS) != 0) + if ((dwFields & enum_CONTEXT_INFO_FIELDS.CIF_ADDRESS) != 0 || + (dwFields & enum_CONTEXT_INFO_FIELDS.CIF_ADDRESSABSOLUTE) != 0) { pinfo[0].bstrAddress = EngineUtils.AsAddr(_address, _engine.DebuggedProcess.Is64BitArch); - pinfo[0].dwFields |= enum_CONTEXT_INFO_FIELDS.CIF_ADDRESS; + pinfo[0].bstrAddressAbsolute = pinfo[0].bstrAddress; + pinfo[0].dwFields |= enum_CONTEXT_INFO_FIELDS.CIF_ADDRESS | enum_CONTEXT_INFO_FIELDS.CIF_ADDRESSABSOLUTE; } - // Fields not supported by the sample if ((dwFields & enum_CONTEXT_INFO_FIELDS.CIF_ADDRESSOFFSET) != 0) { } - if ((dwFields & enum_CONTEXT_INFO_FIELDS.CIF_ADDRESSABSOLUTE) != 0) - { - pinfo[0].bstrAddressAbsolute = EngineUtils.AsAddr(_address, _engine.DebuggedProcess.Is64BitArch); - pinfo[0].dwFields |= enum_CONTEXT_INFO_FIELDS.CIF_ADDRESSABSOLUTE; - } if ((dwFields & enum_CONTEXT_INFO_FIELDS.CIF_MODULEURL) != 0) { DebuggedModule module = _engine.DebuggedProcess.ResolveAddress(_address); @@ -195,7 +189,10 @@ public int GetInfo(enum_CONTEXT_INFO_FIELDS dwFields, CONTEXT_INFO[] pinfo) pinfo[0].dwFields |= enum_CONTEXT_INFO_FIELDS.CIF_FUNCTION; } } - if ((dwFields & enum_CONTEXT_INFO_FIELDS.CIF_FUNCTIONOFFSET) != 0) { } + if ((dwFields & enum_CONTEXT_INFO_FIELDS.CIF_FUNCTIONOFFSET) != 0) + { + // TODO: + } return Constants.S_OK; } @@ -210,10 +207,10 @@ public int GetInfo(enum_CONTEXT_INFO_FIELDS dwFields, CONTEXT_INFO[] pinfo) } // Gets the user-displayable name for this context - // This is not supported by the sample engine. public int GetName(out string pbstrName) { - throw new NotImplementedException(); + pbstrName = _functionName ?? Engine.GetAddressDescription(_address); + return Constants.S_OK; } // Subtracts a specified value from the current context's address to create a new context. diff --git a/src/MIDebugEngine/AD7.Impl/AD7Thread.cs b/src/MIDebugEngine/AD7.Impl/AD7Thread.cs index 212845a2c..561743d34 100644 --- a/src/MIDebugEngine/AD7.Impl/AD7Thread.cs +++ b/src/MIDebugEngine/AD7.Impl/AD7Thread.cs @@ -276,26 +276,19 @@ int IDebugThread2.Resume(out uint suspendCount) // Sets the next statement to the given stack frame and code context. int IDebugThread2.SetNextStatement(IDebugStackFrame2 stackFrame, IDebugCodeContext2 codeContext) { - // CLRDBG TODO: This implementation should be changed to call an MI command - ulong addr = ((AD7MemoryAddress)codeContext).Address; - AD7StackFrame frame = ((AD7StackFrame)stackFrame); - if (frame.ThreadContext.Level != 0 || frame.Thread != this || !frame.ThreadContext.pc.HasValue || _engine.DebuggedProcess.MICommandFactory.Mode == MIMode.Clrdbg) - { + var infos = new CONTEXT_INFO[1]; + if (codeContext.GetInfo(enum_CONTEXT_INFO_FIELDS.CIF_ADDRESS, infos) != Constants.S_OK) return Constants.S_FALSE; - } - string toFunc = EngineUtils.GetAddressDescription(_engine.DebuggedProcess, addr); - string fromFunc = EngineUtils.GetAddressDescription(_engine.DebuggedProcess, frame.ThreadContext.pc.Value); - if (toFunc != fromFunc) + + try { - return Constants.S_FALSE; + ulong address = Convert.ToUInt64(infos[0].bstrAddress, 16); + return _engine.Jump(address); } - string result = frame.EvaluateExpression("$pc=" + EngineUtils.AsAddr(addr, _engine.DebuggedProcess.Is64BitArch)); - if (result != null) + catch (Exception) { - _engine.DebuggedProcess.ThreadCache.MarkDirty(); - return Constants.S_OK; + return Constants.S_FALSE; } - return Constants.S_FALSE; } // suspend a thread. diff --git a/src/MIDebugEngine/Engine.Impl/DebuggedProcess.cs b/src/MIDebugEngine/Engine.Impl/DebuggedProcess.cs index b0db63117..bc7c33482 100755 --- a/src/MIDebugEngine/Engine.Impl/DebuggedProcess.cs +++ b/src/MIDebugEngine/Engine.Impl/DebuggedProcess.cs @@ -1618,6 +1618,16 @@ public Task Continue(DebuggedThread thread) return Execute(thread); } + public async Task Jump(string filename, int line) + { + await MICommandFactory.ExecJump(filename, line); + } + + public async Task Jump(ulong address) + { + await MICommandFactory.ExecJump(address); + } + public async Task Step(int threadId, enum_STEPKIND kind, enum_STEPUNIT unit) { this.VerifyNotDebuggingCoreDump(); diff --git a/src/OpenDebugAD7/AD7DebugSession.cs b/src/OpenDebugAD7/AD7DebugSession.cs index 315a70396..16e35fa1a 100644 --- a/src/OpenDebugAD7/AD7DebugSession.cs +++ b/src/OpenDebugAD7/AD7DebugSession.cs @@ -27,7 +27,7 @@ namespace OpenDebugAD7 { - internal class AD7DebugSession : DebugAdapterBase, IDebugPortNotify2, IDebugEventCallback2 + internal sealed class AD7DebugSession : DebugAdapterBase, IDebugPortNotify2, IDebugEventCallback2 { // This is a general purpose lock. Don't hold it across long operations. private readonly object m_lock = new object(); @@ -42,6 +42,8 @@ internal class AD7DebugSession : DebugAdapterBase, IDebugPortNotify2, IDebugEven private readonly DebugEventLogger m_logger; private readonly Dictionary> m_breakpoints; + private readonly List m_gotoCodeContexts = new List(); + private Dictionary m_functionBreakpoints; private readonly Dictionary m_threadFrameEnumInfos = new Dictionary(); private readonly HandleCollection m_frameHandles; @@ -277,6 +279,7 @@ public void BeforeContinue() m_variableManager.Reset(); m_frameHandles.Reset(); m_threadFrameEnumInfos.Clear(); + m_gotoCodeContexts.Clear(); } } @@ -620,7 +623,8 @@ protected override void HandleInitializeRequestAsync(IRequestResponder new ExceptionBreakpointsFilter() { Default = item.@default, Filter = item.filter, Label = item.label }).ToList(), SupportsClipboardContext = m_engineConfiguration.ClipboardContext, SupportsLogPoints = true, - SupportsReadMemoryRequest = true + SupportsReadMemoryRequest = true, + SupportsGotoTargetsRequest = true, }; responder.SetResponse(initializeResponse); @@ -1193,6 +1197,87 @@ protected override void HandlePauseRequestAsync(IRequestResponder responder) + { + var response = new GotoResponse(); + if (!m_isStopped) + { + responder.SetResponse(response); + return; + } + + var gotoTarget = m_gotoCodeContexts[responder.Arguments.TargetId]; + IDebugThread2 thread = null; + lock (m_threads) + { + if (!m_threads.TryGetValue(responder.Arguments.ThreadId, out thread)) + throw new AD7Exception("Could not find thread!"); + } + BeforeContinue(); + var builder = new ErrorBuilder(() => AD7Resources.Error_UnableToSetNextStatement); + try + { + builder.CheckHR(thread.SetNextStatement(null, gotoTarget)); + } + catch (AD7Exception e) + { + m_isStopped = true; + responder.SetError(new ProtocolException(e.Message)); + } + + responder.SetResponse(response); + } + + protected override void HandleGotoTargetsRequestAsync(IRequestResponder responder) + { + var response = new GotoTargetsResponse(); + + var source = responder.Arguments.Source; + // TODO: handle this for disassembly debugging + if (source.Path == null) + { + responder.SetResponse(response); + return; + } + + try + { + string convertedPath = m_pathConverter.ConvertClientPathToDebugger(source.Path); + int line = m_pathConverter.ConvertClientLineToDebugger(responder.Arguments.Line); + var docPos = new AD7DocumentPosition(m_sessionConfig, convertedPath, line); + + var targets = new List(); + + IEnumDebugCodeContexts2 codeContextsEnum; + if (m_program.EnumCodeContexts(docPos, out codeContextsEnum) == HRConstants.S_OK) + { + var codeContexts = new IDebugCodeContext2[1]; + uint nProps = 0; + while (codeContextsEnum.Next(1, codeContexts, ref nProps) == HRConstants.S_OK) + { + var codeContext = codeContexts[0]; + string contextName; + codeContext.GetName(out contextName); + m_gotoCodeContexts.Add(codeContext); + targets.Add(new GotoTarget(m_gotoCodeContexts.Count - 1, contextName, responder.Arguments.Line)); // TODO: get the real line + } + } + + response.Targets = targets; + } + catch (Exception e) + { + e = Utilities.GetInnerMost(e); + if (Utilities.IsCorruptingException(e)) + Utilities.ReportException(e); + + responder.SetError(new ProtocolException(e.Message)); + return; + } + + responder.SetResponse(response); + } protected override void HandleStackTraceRequestAsync(IRequestResponder responder) { diff --git a/src/OpenDebugAD7/AD7Resources.Designer.cs b/src/OpenDebugAD7/AD7Resources.Designer.cs index c8a973fef..d2bb945d2 100644 --- a/src/OpenDebugAD7/AD7Resources.Designer.cs +++ b/src/OpenDebugAD7/AD7Resources.Designer.cs @@ -370,6 +370,17 @@ internal static string Error_UnableToSetBreakpoint { } } + /// + /// Looks up a localized string similar to Error setting next statement. {0}. + /// + internal static string Error_UnableToSetNextStatement + { + get + { + return ResourceManager.GetString("Error_UnableToSetNextStatement", 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 b100bbd55..40621c371 100644 --- a/src/OpenDebugAD7/AD7Resources.resx +++ b/src/OpenDebugAD7/AD7Resources.resx @@ -192,6 +192,9 @@ Error setting breakpoint. {0} + + Error setting next statement. {0} + Unable to parse 'logMessage'.