From 12632f3280da94787c009c8a97accbe797ac6994 Mon Sep 17 00:00:00 2001 From: Paul Maybee Date: Fri, 22 Apr 2016 07:51:29 -0700 Subject: [PATCH] Save per-thread forking state, interrupt over the pipe rat6her than using the mi --- .../DebugEngineHost.ref.cs | 4 +- src/DebugEngineHost/HostDebugger.cs | 9 +- src/MICore/Debugger.cs | 48 +++- src/MICore/LaunchOptions.cs | 94 ++++--- src/MICore/LaunchOptions.xsd | 8 +- .../LaunchOptions.xsd.types.designer.cs | 4 + src/MICore/Transports/PipeTransport.cs | 32 ++- src/MIDebugEngine/AD7.Impl/AD7Engine.cs | 21 +- .../Engine.Impl/DebugUnixChildProcess.cs | 233 +++++++++++------- .../Engine.Impl/DebuggedProcess.cs | 47 ++-- .../Engine.Impl/DebuggedThread.cs | 92 ++++++- 11 files changed, 415 insertions(+), 177 deletions(-) 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;