diff --git a/src/DebugEngineHost.Stub/DebugEngineHost.ref.cs b/src/DebugEngineHost.Stub/DebugEngineHost.ref.cs
index e9c460104..7681a12ce 100644
--- a/src/DebugEngineHost.Stub/DebugEngineHost.ref.cs
+++ b/src/DebugEngineHost.Stub/DebugEngineHost.ref.cs
@@ -317,9 +317,9 @@ public static void FindNatvisInSolution(NatvisLoader loader)
public static class HostDebugger
{
///
- /// Attach to a process using the provided options
+ /// Ask the host to async spin up a new instance of the debug engine and go through the launch sequence using the specified options
///
- public static void Attach(string filePath, string options, Guid engineId)
+ public static void StartDebugChildProcess(string filePath, string options, Guid engineId)
{
throw new NotImplementedException();
}
diff --git a/src/DebugEngineHost/HostDebugger.cs b/src/DebugEngineHost/HostDebugger.cs
index 410ad1b8e..a4086fe0e 100644
--- a/src/DebugEngineHost/HostDebugger.cs
+++ b/src/DebugEngineHost/HostDebugger.cs
@@ -1,4 +1,7 @@
-using System;
+// 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 Microsoft.VisualStudio.Shell.Interop;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio;
@@ -15,9 +18,9 @@ namespace Microsoft.DebugEngineHost
public static class HostDebugger
{
///
- /// Attach to a process using the provided options
+ /// Ask the host to async spin up a new instance of the debug engine and go through the launch sequence using the specified options
///
- public static void Attach(string filePath, string options, Guid engineId)
+ public static void StartDebugChildProcess(string filePath, string options, Guid engineId)
{
try
{
diff --git a/src/MICore/Debugger.cs b/src/MICore/Debugger.cs
index 184ea58af..56ff5b017 100755
--- a/src/MICore/Debugger.cs
+++ b/src/MICore/Debugger.cs
@@ -38,6 +38,7 @@ public class Debugger : ITransportCallback
public event EventHandler BreakCreatedEvent; // a breakpoint was created
public event EventHandler ThreadCreatedEvent;
public event EventHandler ThreadExitedEvent;
+ public event EventHandler ThreadGroupExitedEvent;
public event EventHandler MessageEvent;
public event EventHandler TelemetryEvent;
private int _exiting;
@@ -543,12 +544,21 @@ public async Task CmdDetach()
public Task CmdBreakInternal()
{
+<<<<<<< 17d1f39a11034234966c6bc4ddf9dc41d6c51d85
this.VerifyNotDebuggingCoreDump();
// TODO May need to fix attach on windows.
// Note that interrupt doesn't work when attached on OS X with gdb:
// https://sourceware.org/bugzilla/show_bug.cgi?id=20035
if (IsLocalGdb() && (PlatformUtilities.IsLinux() || PlatformUtilities.IsOSX()))
+=======
+ if (ProcessState != ProcessState.Running)
+ {
+ return Task.CompletedTask;
+ }
+ //TODO May need to fix attach on windows and osx.
+ if (IsLocalGdbAttach() && RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
+>>>>>>> Save per-thread forking state, interrupt over the pipe rat6her than using the mi
{
// for local linux debugging, send a signal to one of the debuggee processes rather than
// using -exec-interrupt. -exec-interrupt does not work with attach and, in some instances, launch.
@@ -570,6 +580,13 @@ public Task CmdBreakInternal()
return CmdUnixBreak(debuggeePid, ResultClass.done);
}
}
+ else if (_transport is PipeTransport)
+ {
+ if (((PipeTransport)_transport).Interrupt(PidByInferior("i1")))
+ {
+ return Task.FromResult(new Results(ResultClass.done));
+ }
+ }
var res = CmdAsync("-exec-interrupt", ResultClass.done);
return res.ContinueWith((t) =>
@@ -1140,6 +1157,7 @@ private void OnNotificationOutput(string cmd)
{
results = _miResults.ParseResultList(cmd.Substring("thread-group-exited,".Length));
HandleThreadGroupExited(results);
+ ThreadGroupExitedEvent(this, new ResultEventArgs(results, 0));
}
else if (cmd.StartsWith("thread-created,", StringComparison.Ordinal))
{
@@ -1230,21 +1248,31 @@ public uint InferiorByPid(int pid)
{
if ( grp.Value == pid)
{
- // Inferior names are of the form "iX" where X in the inferior number
- string name = grp.Key;
- if (name[0] == 'i')
- {
- uint id;
- if (UInt32.TryParse(name.Substring(1), out id))
- {
- return id;
- }
- }
+ return InferiorNumber(grp.Key);
}
}
return 0;
}
+ public int PidByInferior(string inf)
+ {
+ return _debuggeePids[inf];
+ }
+
+ public uint InferiorNumber(string groupId)
+ {
+ // Inferior names are of the form "iX" where X in the inferior number
+ if (groupId.Length >= 2 && groupId[0] == 'i')
+ {
+ uint id;
+ if (UInt32.TryParse(groupId.Substring(1), out id))
+ {
+ return id;
+ }
+ }
+ return 1; // default to the first inferior if group-id not understood
+ }
+
private void HandleThreadGroupExited(Results results)
{
string threadGroupId = results.TryFindString("id");
diff --git a/src/MICore/LaunchOptions.cs b/src/MICore/LaunchOptions.cs
index 2de749271..c5745b125 100755
--- a/src/MICore/LaunchOptions.cs
+++ b/src/MICore/LaunchOptions.cs
@@ -63,18 +63,19 @@ public enum LaunchCompleteCommand
///
public sealed class PipeLaunchOptions : LaunchOptions
{
- public PipeLaunchOptions(string PipePath, string PipeArguments)
+ public PipeLaunchOptions(string PipePath, string PipeArguments, string PipeCommandArguments)
{
if (string.IsNullOrEmpty(PipePath))
throw new ArgumentNullException("PipePath");
this.PipePath = PipePath;
this.PipeArguments = PipeArguments;
+ this.PipeCommandArguments = PipeCommandArguments;
}
static internal PipeLaunchOptions CreateFromXml(Xml.LaunchOptions.PipeLaunchOptions source)
{
- var options = new PipeLaunchOptions(RequireAttribute(source.PipePath, "PipePath"), source.PipeArguments);
+ var options = new PipeLaunchOptions(RequireAttribute(source.PipePath, "PipePath"), source.PipeArguments, source.PipeCommandArguments);
options.InitializeCommonOptions(source);
return options;
@@ -88,7 +89,13 @@ static internal PipeLaunchOptions CreateFromXml(Xml.LaunchOptions.PipeLaunchOpti
///
/// [Optional] Arguments to pass to the pipe executable.
///
+ ///
public string PipeArguments { get; private set; }
+
+ ///
+ /// [Optional] Arguments to pass to the PipePath program that include a format specifier ('{0}') for a custom command.
+ ///
+ public string PipeCommandArguments { get; private set; }
}
public sealed class TcpLaunchOptions : LaunchOptions
@@ -282,24 +289,6 @@ private static string ResolveFromPath(string command)
///
public string MIDebuggerServerAddress { get; private set; }
- ///
- /// [Required] Path to the executable file. This path must exist on the Visual Studio computer.
- ///
- public override string ExePath
- {
- get
- {
- return base.ExePath;
- }
- set
- {
- if (String.IsNullOrEmpty(value) || !LocalLaunchOptions.CheckPath(value))
- throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, MICoreResources.Error_InvalidLocalExePath, value));
-
- base.ExePath = value;
- }
- }
-
///
/// [Optional] List of environment variables to add to the launched process
///
@@ -397,12 +386,24 @@ public abstract class LaunchOptions
public MIMode DebuggerMIMode { get; set; }
- private string _exePath;
-
+ private Xml.LaunchOptions.BaseLaunchOptions _baseOptions;
///
/// Hold on to options in serializable form to support child process debugging
///
- public Xml.LaunchOptions.BaseLaunchOptions BaseOptions { get; set; }
+ public Xml.LaunchOptions.BaseLaunchOptions BaseOptions
+ {
+ get { return _baseOptions; }
+ protected set
+ {
+ if (value == null)
+ throw new ArgumentNullException("BaseOptions");
+ VerifyCanModifyProperty("BaseOptions");
+
+ _baseOptions = value;
+ }
+ }
+
+ private string _exePath;
///
/// [Required] Path to the executable file. This could be a path on the remote machine (for Pipe transport)
@@ -435,10 +436,20 @@ public string ExeArguments
}
}
+ private int _processId;
+
///
/// [Optional] If supplied, the debugger will attach to the process rather than launching a new one. Note that some operating systems will require admin rights to do this.
///
- public int ProcessId { get; set; }
+ public int ProcessId
+ {
+ get { return _processId; }
+ protected set
+ {
+ VerifyCanModifyProperty("ProcessId");
+ _processId = value;
+ }
+ }
private string _coreDumpPath;
///
@@ -450,8 +461,10 @@ public string CoreDumpPath
{
return _coreDumpPath;
}
- set
+ protected set
{
+ VerifyCanModifyProperty("CoreDumpPath");
+
// CoreDumpPath is allowed to be null/empty
_coreDumpPath = value;
}
@@ -615,9 +628,19 @@ public LaunchCompleteCommand LaunchCompleteCommand
}
}
- public bool DebugChildProcesses { get; set; }
+ private bool _debugChildProcesses;
+
+ public bool DebugChildProcesses
+ {
+ get { return _debugChildProcesses; }
+ protected set
+ {
+ VerifyCanModifyProperty("DebugChildProcesses");
+ _debugChildProcesses = value;
+ }
+ }
- public static string GetOptionsString(object o)
+ public string GetOptionsString()
{
try
{
@@ -625,20 +648,20 @@ public static string GetOptionsString(object o)
XmlSerializer serializer;
using (XmlWriter writer = XmlWriter.Create(strWriter))
{
- if (o is Xml.LaunchOptions.LocalLaunchOptions)
+ if (BaseOptions is Xml.LaunchOptions.LocalLaunchOptions)
{
serializer = new XmlSerializer(typeof(Xml.LaunchOptions.LocalLaunchOptions));
- Serialize(serializer, writer, o);
+ Serialize(serializer, writer, BaseOptions);
}
- else if (o is Xml.LaunchOptions.PipeLaunchOptions)
+ else if (BaseOptions is Xml.LaunchOptions.PipeLaunchOptions)
{
serializer = new XmlSerializer(typeof(Xml.LaunchOptions.PipeLaunchOptions));
- Serialize(serializer, writer, o);
+ Serialize(serializer, writer, BaseOptions);
}
- else if (o is Xml.LaunchOptions.TcpLaunchOptions)
+ else if (BaseOptions is Xml.LaunchOptions.TcpLaunchOptions)
{
serializer = new XmlSerializer(typeof(Xml.LaunchOptions.TcpLaunchOptions));
- Serialize(serializer, writer, o);
+ Serialize(serializer, writer, BaseOptions);
}
else
{
@@ -835,7 +858,7 @@ public static object Deserialize(XmlSerializer serializer, XmlReader reader)
}
}
- public static void Serialize(XmlSerializer serializer, XmlWriter writer, object o)
+ private static void Serialize(XmlSerializer serializer, XmlWriter writer, object o)
{
try
{
@@ -950,8 +973,11 @@ protected void InitializeCommonOptions(Xml.LaunchOptions.BaseLaunchOptions sourc
else
this.AdditionalSOLibSearchPath = string.Concat(this.AdditionalSOLibSearchPath, ";", additionalSOLibSearchPath);
}
+<<<<<<< 17d1f39a11034234966c6bc4ddf9dc41d6c51d85
if (string.IsNullOrEmpty(this.AbsolutePrefixSOLibSearchPath))
this.AbsolutePrefixSOLibSearchPath = source.AbsolutePrefixSOLibSearchPath;
+=======
+>>>>>>> Save per-thread forking state, interrupt over the pipe rat6her than using the mi
this.ProcessId = source.ProcessId;
this.CoreDumpPath = source.CoreDumpPath;
diff --git a/src/MICore/LaunchOptions.xsd b/src/MICore/LaunchOptions.xsd
index bf2670ca8..efc42121e 100644
--- a/src/MICore/LaunchOptions.xsd
+++ b/src/MICore/LaunchOptions.xsd
@@ -290,6 +290,11 @@
Any arguments to pass to this program.
+
+
+ Arguments to pass to the PipePath program that include a format specifier ('{0}') for a custom command.
+
+
@@ -320,7 +325,8 @@
-
+
+
diff --git a/src/MICore/LaunchOptions.xsd.types.designer.cs b/src/MICore/LaunchOptions.xsd.types.designer.cs
index 658f24189..bbed45929 100644
--- a/src/MICore/LaunchOptions.xsd.types.designer.cs
+++ b/src/MICore/LaunchOptions.xsd.types.designer.cs
@@ -496,6 +496,10 @@ public partial class PipeLaunchOptions : BaseLaunchOptions {
///
[System.Xml.Serialization.XmlAttributeAttribute()]
public string PipeArguments;
+
+ ///
+ [System.Xml.Serialization.XmlAttributeAttribute()]
+ public string PipeCommandArguments;
}
///
diff --git a/src/MICore/Transports/PipeTransport.cs b/src/MICore/Transports/PipeTransport.cs
index 438182538..d4c7415c2 100755
--- a/src/MICore/Transports/PipeTransport.cs
+++ b/src/MICore/Transports/PipeTransport.cs
@@ -23,7 +23,12 @@ public class PipeTransport : StreamTransport
private ManualResetEvent _allReadersDone = new ManualResetEvent(false);
private bool _killOnClose;
private bool _filterStderr;
+<<<<<<< 17d1f39a11034234966c6bc4ddf9dc41d6c51d85
private int _debuggerPid = -1;
+=======
+ private string _pipePath;
+ private string _cmdArgs;
+>>>>>>> Save per-thread forking state, interrupt over the pipe rat6her than using the mi
public PipeTransport(bool killOnClose = false, bool filterStderr = false, bool filterStdout = false) : base(filterStdout)
{
@@ -31,6 +36,29 @@ public PipeTransport(bool killOnClose = false, bool filterStderr = false, bool f
_filterStderr = filterStderr;
}
+ public bool Interrupt(int pid)
+ {
+ if (_cmdArgs == null)
+ {
+ return false;
+ }
+
+ Process proc = new Process();
+ string killCmd = string.Format(CultureInfo.InvariantCulture, "kill -2 {0}", pid);
+ proc.StartInfo.FileName = _pipePath;
+ proc.StartInfo.Arguments = string.Format(CultureInfo.InvariantCulture, _cmdArgs, killCmd);
+ proc.StartInfo.WorkingDirectory = System.IO.Path.GetDirectoryName(_pipePath);
+ proc.EnableRaisingEvents = false;
+ proc.StartInfo.RedirectStandardInput = false;
+ proc.StartInfo.RedirectStandardOutput = false;
+ proc.StartInfo.RedirectStandardError = false;
+ proc.StartInfo.UseShellExecute = false;
+ proc.StartInfo.CreateNoWindow = true;
+ proc.Start();
+ proc.WaitForExit();
+ return true;
+ }
+
protected override string GetThreadName()
{
return "MI.PipeTransport";
@@ -76,12 +104,14 @@ protected virtual void InitProcess(Process proc, out StreamReader stdout, out St
}
}
-
public override void InitStreams(LaunchOptions options, out StreamReader reader, out StreamWriter writer)
{
PipeLaunchOptions pipeOptions = (PipeLaunchOptions)options;
+ _cmdArgs = pipeOptions.PipeCommandArguments;
+
Process proc = new Process();
+ _pipePath = pipeOptions.PipePath;
proc.StartInfo.FileName = pipeOptions.PipePath;
proc.StartInfo.Arguments = pipeOptions.PipeArguments;
proc.StartInfo.WorkingDirectory = System.IO.Path.GetDirectoryName(pipeOptions.PipePath);
diff --git a/src/MIDebugEngine/AD7.Impl/AD7Engine.cs b/src/MIDebugEngine/AD7.Impl/AD7Engine.cs
index 4b1d3bb51..a46055abf 100755
--- a/src/MIDebugEngine/AD7.Impl/AD7Engine.cs
+++ b/src/MIDebugEngine/AD7.Impl/AD7Engine.cs
@@ -58,11 +58,11 @@ sealed public class AD7Engine : IDebugEngine2, IDebugEngineLaunch2, IDebugProgra
private IDebugSettingsCallback110 _settingsCallback;
- public static List ChildProcessLaunch;
+ private static List _childProcessLaunch;
static AD7Engine()
{
- ChildProcessLaunch = new List();
+ _childProcessLaunch = new List();
}
public AD7Engine()
@@ -80,6 +80,22 @@ public AD7Engine()
}
}
+ internal static void AddChildProcess(int processId)
+ {
+ lock(_childProcessLaunch)
+ {
+ _childProcessLaunch.Add(processId);
+ }
+ }
+
+ internal static bool RemoveChildProcess(int processId)
+ {
+ lock(_childProcessLaunch)
+ {
+ return _childProcessLaunch.Remove(processId);
+ }
+ }
+
internal EngineCallback Callback
{
get { return _engineCallback; }
@@ -844,6 +860,7 @@ public int Stop()
{
await _debuggedProcess.CmdBreak();
});
+ // TODO: this should be returning S_ASYNC_STOP
return Constants.S_OK;
}
diff --git a/src/MIDebugEngine/Engine.Impl/DebugUnixChildProcess.cs b/src/MIDebugEngine/Engine.Impl/DebugUnixChildProcess.cs
index f7f8144ca..2f4d84cac 100644
--- a/src/MIDebugEngine/Engine.Impl/DebugUnixChildProcess.cs
+++ b/src/MIDebugEngine/Engine.Impl/DebugUnixChildProcess.cs
@@ -1,4 +1,7 @@
-using System;
+// 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.Text;
@@ -6,24 +9,27 @@
using MICore;
using System.Diagnostics;
using Microsoft.DebugEngineHost;
+using System.Globalization;
namespace Microsoft.MIDebugEngine
{
public interface ProcessSequence
{
- Task Enable(bool enable);
+ Task Enable();
///
/// Handle a stopping event as part of a sequence of debugger operations
///
///
+ ///
/// true if stopping event was consumed (process must be running), false otherwise (indicting the debugger should process the event)
- Task Stopped(Results debugEvent);
+ Task Stopped(Results debugEvent, int tid);
///
/// Handle breakpoint created events
///
///
/// true if event was consumed, false otherwise (indicting the debugger should process the event)
bool BreakpointCreated(Results debugEvent);
+ void ThreadCreatedEvent(Results results);
}
class DebugUnixChild : ProcessSequence
@@ -33,32 +39,38 @@ class DebugUnixChild : ProcessSequence
private enum State
{
- Init,
- Enabled,
AtFork,
AtVfork,
AtSignal,
- AtExec
+ AtExec,
+ Complete
}
- private State _state;
private DebuggedProcess _process;
- private int _newpid;
private LaunchOptions _launchOptions;
private string _mainBreak;
+ private class ThreadProgress
+ {
+ public State State;
+ public int Newpid;
+ public int Newtid;
+ public string Exe;
+ }
+ private Dictionary _threadStates;
+
public DebugUnixChild(DebuggedProcess process, LaunchOptions launchOptions)
{
_process = process;
- _state = State.Init;
+ _threadStates = new Dictionary();
_launchOptions = launchOptions;
}
- private async Task ProcessChild()
+ private async Task ProcessChild(ThreadProgress state)
{
- Debug.Assert(_newpid != 0, "Child process id not found.");
- if (_newpid != 0)
+ Debug.Assert(state.Newpid != 0, "Child process id not found.");
+ if (state.Newpid != 0)
{
- uint inf = _process.InferiorByPid(_newpid);
+ uint inf = _process.InferiorByPid(state.Newpid);
if (inf != 0)
{
await _process.ConsoleCmdAsync("inferior " + inf.ToString());
@@ -67,34 +79,34 @@ private async Task ProcessChild()
await _process.MICommandFactory.BreakDelete(_mainBreak);
_mainBreak = null;
}
- _state = State.AtSignal;
+ state.State = State.AtSignal;
await _process.MICommandFactory.Signal("SIGSTOP"); // stop the child
}
}
}
- private async Task RunChildToMain()
+ private async Task RunChildToMain(ThreadProgress state)
{
- Debug.Assert(_newpid != 0, "Child process id not found.");
- if (_newpid != 0)
+ Debug.Assert(state.Newpid != 0, "Child process id not found.");
+ if (state.Newpid != 0)
{
- uint inf = _process.InferiorByPid(_newpid);
+ uint inf = _process.InferiorByPid(state.Newpid);
if (inf != 0)
{
await _process.ConsoleCmdAsync("inferior " + inf.ToString());
await SetBreakAtMain();
- _state = State.AtExec;
+ state.State = State.AtExec;
await _process.MICommandFactory.ExecContinue(); // run the child
}
}
}
- private async Task ContinueTheChild()
+ private async Task ContinueTheChild(ThreadProgress state)
{
- Debug.Assert(_state == State.AtExec, "wrong vfork processing state");
- if (_newpid != 0)
+ Debug.Assert(state.State == State.AtExec, "wrong vfork processing state");
+ if (state.Newpid != 0)
{
- uint inf = _process.InferiorByPid(_newpid);
+ uint inf = _process.InferiorByPid(state.Newpid);
if (inf != 0)
{
await _process.ConsoleCmdAsync("inferior " + inf.ToString());
@@ -103,53 +115,26 @@ private async Task ContinueTheChild()
}
}
- private async Task DetachAndContinue()
+ private async Task DetachAndContinue(ThreadProgress state)
{
- uint inf = _process.InferiorByPid(_newpid);
- if (inf == 0)
- return false; // cannot process the child
- await _process.ConsoleCmdAsync("inferior " + inf.ToString());
- await _process.MICommandFactory.TargetDetach(); // detach from the child
- await _process.ConsoleCmdAsync("inferior 1");
- lock(AD7Engine.ChildProcessLaunch)
- {
- AD7Engine.ChildProcessLaunch.Add(_newpid);
- }
+ await DetachFromChild(state);
+ AD7Engine.AddChildProcess(state.Newpid);
string engineName;
Guid engineGuid;
_process.Engine.GetEngineInfo(out engineName, out engineGuid);
- _launchOptions.BaseOptions.ProcessId = _newpid;
+ _launchOptions.BaseOptions.ProcessId = state.Newpid;
_launchOptions.BaseOptions.ProcessIdSpecified = true;
- _state = State.Enabled;
- HostDebugger.Attach(_launchOptions.ExePath, LaunchOptions.GetOptionsString(_launchOptions.BaseOptions), engineGuid);
+ _launchOptions.BaseOptions.ExePath = state.Exe ?? _launchOptions.ExePath;
+ HostDebugger.StartDebugChildProcess(_launchOptions.BaseOptions.ExePath, _launchOptions.GetOptionsString(), engineGuid);
await _process.MICommandFactory.ExecContinue(); // continue the parent
return true; // parent is running
}
- public async Task Enable(bool enable)
+ public async Task Enable()
{
- if (enable && _state == State.Init)
- {
- await _process.MICommandFactory.SetOption("detach-on-fork", "off");
- await _process.MICommandFactory.Catch("fork");
- await _process.MICommandFactory.Catch("vfork");
- _state = State.Enabled;
- _newpid = 0;
- }
- else if (!enable)
- {
- if (_forkBp != null)
- {
- await _process.MICommandFactory.BreakDelete(_forkBp);
- _forkBp = null;
- }
- if (_vforkBp != null)
- {
- await _process.MICommandFactory.BreakDelete(_vforkBp);
- _vforkBp = null;
- }
- _state = State.Init;
- }
+ await _process.MICommandFactory.SetOption("detach-on-fork", "off");
+ await _process.MICommandFactory.Catch("fork");
+ await _process.MICommandFactory.Catch("vfork");
}
public async Task SetBreakAtMain()
@@ -166,64 +151,124 @@ public async Task SetBreakAtMain()
}
}
- public async Task Stopped(Results results)
+ private async Task DetachFromChild(ThreadProgress state)
{
- string reason = null;
+ uint inf = _process.InferiorByPid(state.Newpid);
+ if (inf == 0)
+ return false; // cannot process the child
+ await _process.ConsoleCmdAsync("inferior " + inf.ToString());
+ await _process.MICommandFactory.TargetDetach(); // detach from the child
+ await _process.ConsoleCmdAsync("inferior 1");
+ return true;
+ }
- switch (_state)
+ public void ThreadCreatedEvent(Results evnt)
+ {
+ int tid = evnt.FindInt("id");
+ string groupId = evnt.FindString("group-id");
+ int pid = _process.PidByInferior(groupId);
+ foreach (var p in _threadStates)
+ {
+ if (p.Value.Newpid == pid)
+ {
+ p.Value.Newtid = tid;
+ }
+ }
+ }
+
+ private ThreadProgress StateFromTid(int tid)
+ {
+ if (_threadStates.ContainsKey(tid))
+ {
+ return _threadStates[tid];
+ }
+ foreach (var p in _threadStates)
+ {
+ if (p.Value.Newtid == tid)
+ {
+ return p.Value;
+ }
+ }
+ return null;
+ }
+
+ public async Task Stopped(Results results, int tid)
+ {
+ string reason = results.TryFindString("reason");
+ ThreadProgress s = StateFromTid(tid);
+
+ if (reason == "fork")
+ {
+ s = new ThreadProgress();
+ s.State = State.AtFork;
+ s.Newpid = results.FindInt("newpid");
+ _threadStates[tid] = s;
+ await _process.Step(tid, VisualStudio.Debugger.Interop.enum_STEPKIND.STEP_OUT, VisualStudio.Debugger.Interop.enum_STEPUNIT.STEP_LINE);
+ return true;
+ }
+ else if (reason == "vfork")
+ {
+ s = new ThreadProgress();
+ s.State = State.AtVfork;
+ s.Newpid = results.FindInt("newpid");
+ _threadStates[tid] = s;
+ await _process.MICommandFactory.SetOption("schedule-multiple", "on");
+ await _process.MICommandFactory.Catch("exec", onlyOnce: true);
+ var thread = await _process.ThreadCache.GetThread(tid);
+ await _process.Continue(thread);
+ return true;
+ }
+
+ if (s == null)
+ {
+ return false; // no activity being tracked on this thread
+ }
+
+ switch (s.State)
{
- case State.Enabled:
- reason = results.TryFindString("reason");
- if (reason == "fork")
- {
- int threadId = results.FindInt("thread-id");
- _newpid = results.FindInt("newpid");
- _state = State.AtFork;
- await _process.Step(threadId, VisualStudio.Debugger.Interop.enum_STEPKIND.STEP_OUT, VisualStudio.Debugger.Interop.enum_STEPUNIT.STEP_LINE);
- break;
- }
- else if (reason == "vfork")
- {
- int threadId = results.FindInt("thread-id");
- _newpid = results.FindInt("newpid");
- await _process.MICommandFactory.SetOption("schedule-multiple", "on");
- await _process.MICommandFactory.Catch("exec", onlyOnce: true);
- var thread = await _process.ThreadCache.GetThread(threadId);
- _state = State.AtVfork;
- await _process.Continue(thread);
- break;
- }
- return false;
case State.AtFork:
- await ProcessChild();
+ await ProcessChild(s);
break;
case State.AtVfork:
+ await _process.MICommandFactory.SetOption("schedule-multiple", "off");
if ("exec" == results.TryFindString("reason"))
{
- await _process.MICommandFactory.SetOption("schedule-multiple", "off");
- await RunChildToMain();
+ // The process doesn't handle the SIGSTOP correctly (just ignores it) when the process is at the start of program
+ // (after exec). Let it run some code so that it will correctly respond to the SIGSTOP.
+ s.Exe = results.TryFindString("new-exec");
+ await RunChildToMain(s);
}
else
{
// sometimes gdb misses the breakpoint at exec and execution will proceed to a breakpoint in the child
_process.Logger.WriteLine("Missed catching the exec after vfork. Spawning the child's debugger.");
- _state = State.AtExec;
+ s.State = State.AtExec;
goto missedExec;
}
break;
case State.AtSignal: // both child and parent are stopped
- return await DetachAndContinue();
+ s.State = State.Complete;
+ return await DetachAndContinue(s);
case State.AtExec:
- missedExec:
- if (results.TryFindString("reason") == "breakpoint-hit")
+ missedExec:
+ if (tid == s.Newtid) // stopped in the child
{
- await ProcessChild();
+ await ProcessChild(s);
}
else // sometime the parent will get a spurious signal before the child hits main
{
- await ContinueTheChild();
+ await ContinueTheChild(s);
}
break;
+ case State.Complete:
+ _threadStates.Remove(tid);
+ if (reason == "signal-received" && results.TryFindString("signal-name") == "SIGSTOP")
+ {
+ // SIGSTOP was propagated to the parent
+ await _process.MICommandFactory.Signal("SIGCONT");
+ return true;
+ }
+ return false;
default:
return false;
}
diff --git a/src/MIDebugEngine/Engine.Impl/DebuggedProcess.cs b/src/MIDebugEngine/Engine.Impl/DebuggedProcess.cs
index 0ee9a0d75..922bb358a 100755
--- a/src/MIDebugEngine/Engine.Impl/DebuggedProcess.cs
+++ b/src/MIDebugEngine/Engine.Impl/DebuggedProcess.cs
@@ -49,8 +49,12 @@ internal class DebuggedProcess : MICore.Debugger
private ReadOnlyCollection _registerGroups;
private readonly EngineTelemetry _engineTelemetry = new EngineTelemetry();
private bool _needTerminalReset;
+<<<<<<< 17d1f39a11034234966c6bc4ddf9dc41d6c51d85
private HashSet> _fileTimestampWarnings;
private ProcessSequence _inProgress;
+=======
+ private ProcessSequence _childProcessHandler;
+>>>>>>> Save per-thread forking state, interrupt over the pipe rat6her than using the mi
public DebuggedProcess(bool bLaunched, LaunchOptions launchOptions, ISampleEngineCallback callback, WorkerThread worker, BreakpointManager bpman, AD7Engine engine, HostConfigurationStore configStore) : base(launchOptions, engine.Logger)
{
@@ -374,13 +378,20 @@ public DebuggedProcess(bool bLaunched, LaunchOptions launchOptions, ISampleEngin
ThreadCreatedEvent += delegate (object o, EventArgs args)
{
ResultEventArgs result = (ResultEventArgs)args;
- ThreadCache.ThreadEvent(result.Results.FindInt("id"), /*deleted */false);
+ ThreadCache.ThreadCreatedEvent(result.Results.FindInt("id"), result.Results.TryFindString("group-id"));
+ _childProcessHandler?.ThreadCreatedEvent(result.Results);
};
ThreadExitedEvent += delegate (object o, EventArgs args)
{
ResultEventArgs result = (ResultEventArgs)args;
- ThreadCache.ThreadEvent(result.Results.FindInt("id"), /*deleted*/true);
+ ThreadCache.ThreadExitedEvent(result.Results.FindInt("id"));
+ };
+
+ ThreadGroupExitedEvent += delegate (object o, EventArgs args)
+ {
+ ResultEventArgs result = (ResultEventArgs)args;
+ ThreadCache.ThreadGroupExitedEvent(result.Results.FindString("id"));
};
MessageEvent += (object o, ResultEventArgs args) =>
@@ -403,12 +414,6 @@ public DebuggedProcess(bool bLaunched, LaunchOptions launchOptions, ISampleEngin
};
BreakChangeEvent += _breakpointManager.BreakpointModified;
-
- BreakCreatedEvent += (object o, EventArgs args) =>
- {
- ResultEventArgs result = (ResultEventArgs)args;
- _inProgress?.BreakpointCreated(result.Results);
- };
}
private async Task EnsureModulesLoaded()
@@ -463,7 +468,7 @@ public async Task Initialize(HostWaitLoop waitLoop, CancellationToken token)
{
await this.MICommandFactory.EnableTargetAsyncOption();
List commands = GetInitializeCommands();
- _inProgress?.Enable(true);
+ _childProcessHandler?.Enable();
total = commands.Count();
var i = 0;
@@ -581,7 +586,7 @@ private List GetInitializeCommands()
{
if (_launchOptions.DebugChildProcesses)
{
- _inProgress = new DebugUnixChild(this, this._launchOptions); // TODO: let the user enable/disable this functionality
+ _childProcessHandler = new DebugUnixChild(this, this._launchOptions); // TODO: let the user enable/disable this functionality
}
}
@@ -736,11 +741,6 @@ private void Dispose()
private async Task HandleBreakModeEvent(ResultEventArgs results)
{
- if (_inProgress != null && await _inProgress.Stopped(results.Results))
- {
- return;
- }
-
string reason = results.Results.TryFindString("reason");
int tid;
if (!results.Results.Contains("thread-id"))
@@ -753,6 +753,11 @@ private async Task HandleBreakModeEvent(ResultEventArgs results)
tid = results.Results.FindInt("thread-id");
}
+ if (_childProcessHandler != null && await _childProcessHandler.Stopped(results.Results, tid))
+ {
+ return;
+ }
+
// Any existing variable objects at this point are from the last time we were in break mode, and are
// therefore invalid. Dispose them so they're marked for cleanup.
lock (this.ActiveVariables)
@@ -928,20 +933,16 @@ private async Task HandleBreakModeEvent(ResultEventArgs results)
{
code = EngineUtils.SignalMap.Instance[sigName];
}
- bool _stoppedAtSIGSTOP = false;
+ bool stoppedAtSIGSTOP = false;
if (sigName == "SIGSTOP")
{
- lock (AD7Engine.ChildProcessLaunch)
+ if (AD7Engine.RemoveChildProcess(_launchOptions.ProcessId))
{
- if (AD7Engine.ChildProcessLaunch.Contains(_launchOptions.ProcessId))
- {
- AD7Engine.ChildProcessLaunch.Remove(_launchOptions.ProcessId);
- _stoppedAtSIGSTOP = true;
- }
+ stoppedAtSIGSTOP = true;
}
}
string message = results.Results.TryFindString("signal-meaning");
- if (_stoppedAtSIGSTOP)
+ if (stoppedAtSIGSTOP)
{
await MICommandFactory.Signal("SIGCONT");
}
diff --git a/src/MIDebugEngine/Engine.Impl/DebuggedThread.cs b/src/MIDebugEngine/Engine.Impl/DebuggedThread.cs
index 55c768580..9f3ae9bb7 100644
--- a/src/MIDebugEngine/Engine.Impl/DebuggedThread.cs
+++ b/src/MIDebugEngine/Engine.Impl/DebuggedThread.cs
@@ -19,6 +19,7 @@ public DebuggedThread(int id, MIDebugEngine.AD7Engine engine)
TargetId = (uint)id;
AD7Thread ad7Thread = new MIDebugEngine.AD7Thread(engine, this);
Client = ad7Thread;
+ ChildThread = false;
}
public int Id { get; private set; }
@@ -27,6 +28,7 @@ public DebuggedThread(int id, MIDebugEngine.AD7Engine engine)
public bool Alive { get; set; }
public bool Default { get; set; }
public string Name { get; set; }
+ public bool ChildThread { get; set; } // transient child thread, don't inform UI of this thread
}
internal class ThreadCache
@@ -40,12 +42,21 @@ internal class ThreadCache
private DebuggedProcess _debugger;
private List _deadThreads;
private List _newThreads;
+ private Dictionary> _threadGroups;
+ private static uint s_targetId;
+
+ static ThreadCache()
+ {
+ s_targetId = uint.MaxValue;
+ }
internal ThreadCache(ISampleEngineCallback callback, DebuggedProcess debugger)
{
_threadList = new List();
_stackFrames = new Dictionary>();
_topContext = new Dictionary();
+ _threadGroups = new Dictionary>();
+ _threadGroups["i1"] = new List(); // initialize the processes thread group
_stateChange = true;
_callback = callback;
_debugger = debugger;
@@ -135,18 +146,60 @@ internal void MarkDirty()
}
}
- internal void ThreadEvent(int id, bool deleted)
+ internal void ThreadCreatedEvent(int id, string groupId)
+ {
+ lock (_threadList)
+ {
+ var thread = _threadList.Find(t => t.Id == id);
+ if (thread == null)
+ {
+ _stateChange = true;
+ }
+ if (!_threadGroups.ContainsKey(groupId))
+ {
+ _threadGroups[groupId] = new List();
+ }
+ _threadGroups[groupId].Add(id);
+ }
+ }
+
+ internal void ThreadExitedEvent(int id)
{
lock (_threadList)
{
var thread = _threadList.Find(t => t.Id == id);
- if ((thread != null) == deleted)
+ if (thread != null)
{
_stateChange = true;
}
+ foreach (var g in _threadGroups)
+ {
+ if (g.Value.Contains(id))
+ {
+ g.Value.Remove(id);
+ break;
+ }
+ }
+ }
+ }
+
+ internal void ThreadGroupExitedEvent(string groupId)
+ {
+ lock (_threadList)
+ {
+ if (_threadGroups.ContainsKey(groupId))
+ {
+ _threadGroups.Remove(groupId);
+ }
}
}
+ private bool IsInParent(int tid)
+ {
+ // only those threads in the "i1" threadgroup are in the debugee, others are transient while attaching to a child process
+ return _threadGroups["i1"].Contains(tid);
+ }
+
private async Task> WalkStack(DebuggedThread thread)
{
List stack = null;
@@ -213,20 +266,35 @@ private async Task CollectThreadsInfo(int cxtThreadId)
{
thread.TargetId = tid;
}
- else if (targetId.StartsWith("Thread", StringComparison.OrdinalIgnoreCase) &&
+ else if (targetId.StartsWith("Thread ", StringComparison.OrdinalIgnoreCase) &&
System.UInt32.TryParse(targetId.Substring("Thread ".Length), out tid) &&
tid != 0
)
{
thread.TargetId = tid;
}
- else if (targetId.StartsWith("Process", StringComparison.OrdinalIgnoreCase) &&
+ else if (targetId.StartsWith("Process ", StringComparison.OrdinalIgnoreCase) &&
System.UInt32.TryParse(targetId.Substring("Process ".Length), out tid) &&
tid != 0
)
{ // First thread in a linux process has tid == pid
thread.TargetId = tid;
}
+ else if (targetId.StartsWith("Thread ", StringComparison.OrdinalIgnoreCase))
+ {
+ // In processes with pthreads the thread name is in form: "Thread <0x123456789abc> (LWP )"
+ int lwp_pos = targetId.IndexOf("(LWP ");
+ int paren_pos = targetId.LastIndexOf(')');
+ int len = paren_pos - (lwp_pos + 5);
+ if (len > 0 && System.UInt32.TryParse(targetId.Substring(lwp_pos + 5, len), out tid) && tid != 0)
+ {
+ thread.TargetId = tid;
+ }
+ }
+ else
+ {
+ thread.TargetId = --s_targetId;
+ }
}
if (t.Contains("name"))
{
@@ -295,13 +363,19 @@ internal void SendThreadEvents(object sender, EventArgs e)
if (newThreads != null)
foreach (var newt in newThreads)
{
- _callback.OnThreadStart(newt);
+ if (!newt.ChildThread)
+ {
+ _callback.OnThreadStart(newt);
+ }
}
if (deadThreads != null)
foreach (var dead in deadThreads)
{
- // Send the destroy event outside the lock
- _callback.OnThreadExit(dead, 0);
+ if (!dead.ChildThread)
+ {
+ // Send the destroy event outside the lock
+ _callback.OnThreadExit(dead, 0);
+ }
}
}
@@ -314,6 +388,10 @@ private DebuggedThread FindThread(int id, out bool bNew)
return thread;
// thread not found, so create it, and return it
newthread = new DebuggedThread(id, _debugger.Engine);
+ if (!IsInParent(id))
+ {
+ newthread.ChildThread = true;
+ }
newthread.Default = false;
_threadList.Add(newthread);
bNew = true;