From 39c781f81a1687260024c03dc30b8c92e19c327d Mon Sep 17 00:00:00 2001 From: Marc Khouzam Date: Fri, 14 Oct 2016 15:58:23 -0400 Subject: [PATCH] Bug 497166: Support the user using the 'run' command in the gdb console This commit introduces a PersistentPTY. By using it, we now allow the user to restart the process from the GDB console (by pressing 'run'). In this case, the I/O will continue using the PersistentPTY. Previously, the PTY would have been closed, and GDB would fail to restart the process because it would fail to use the closed PTY. Change-Id: I395b402e297a2043af8fce33df163eddef9e6c7a --- .../META-INF/MANIFEST.MF | 2 +- core/org.eclipse.cdt.core.native/pom.xml | 2 +- .../eclipse/cdt/utils/pty/PersistentPTY.java | 104 ++++++++++ .../.settings/.api_filters | 8 + .../cdt/dsf/gdb/service/GDBProcesses_7_0.java | 187 ++++++++++++++---- .../dsf/gdb/service/GDBProcesses_7_12.java | 110 +++++++++++ .../cdt/dsf/gdb/service/GDBProcesses_7_3.java | 14 ++ .../gdb/service/GdbDebugServicesFactory.java | 3 + .../cdt/dsf/gdb/service/IGDBProcesses.java | 19 ++ .../StartOrRestartProcessSequence_7_0.java | 106 ++++------ .../StartOrRestartProcessSequence_7_3.java | 4 + .../service/extensions/GDBProcesses_HEAD.java | 6 +- .../mi/service/command/MIInferiorProcess.java | 36 ++-- .../command/MIInferiorProcess_7_3.java | 6 + 14 files changed, 491 insertions(+), 116 deletions(-) create mode 100644 core/org.eclipse.cdt.core.native/src/org/eclipse/cdt/utils/pty/PersistentPTY.java create mode 100644 dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/gdb/service/GDBProcesses_7_12.java diff --git a/core/org.eclipse.cdt.core.native/META-INF/MANIFEST.MF b/core/org.eclipse.cdt.core.native/META-INF/MANIFEST.MF index d5dc688af3e..94f7b0d29f4 100644 --- a/core/org.eclipse.cdt.core.native/META-INF/MANIFEST.MF +++ b/core/org.eclipse.cdt.core.native/META-INF/MANIFEST.MF @@ -2,7 +2,7 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: %pluginName Bundle-SymbolicName: org.eclipse.cdt.core.native;singleton:=true -Bundle-Version: 5.9.0.qualifier +Bundle-Version: 5.10.0.qualifier Bundle-Activator: org.eclipse.cdt.internal.core.natives.CNativePlugin Bundle-Vendor: %providerName Bundle-Localization: plugin diff --git a/core/org.eclipse.cdt.core.native/pom.xml b/core/org.eclipse.cdt.core.native/pom.xml index 249c4e3223d..46091e422e6 100644 --- a/core/org.eclipse.cdt.core.native/pom.xml +++ b/core/org.eclipse.cdt.core.native/pom.xml @@ -11,7 +11,7 @@ ../../pom.xml - 5.9.0-SNAPSHOT + 5.10.0-SNAPSHOT org.eclipse.cdt.core.native eclipse-plugin diff --git a/core/org.eclipse.cdt.core.native/src/org/eclipse/cdt/utils/pty/PersistentPTY.java b/core/org.eclipse.cdt.core.native/src/org/eclipse/cdt/utils/pty/PersistentPTY.java new file mode 100644 index 00000000000..9780d405853 --- /dev/null +++ b/core/org.eclipse.cdt.core.native/src/org/eclipse/cdt/utils/pty/PersistentPTY.java @@ -0,0 +1,104 @@ +/******************************************************************************* + * Copyright (c) 2016 Ericsson and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + *******************************************************************************/ +package org.eclipse.cdt.utils.pty; + +import java.io.IOException; + +import org.eclipse.core.runtime.Platform; + +/** + * A type of PTY that is persistent. This means that closing + * its streams (e.g., once the connection to the process is lost) + * will not close the PTY or the streams; instead, they will + * remain open to be used again by reconnecting to the streams. + * {@link PersistentPTY#closeStreams()} must be called to properly + * cleanup the streams once the PersistentPTY is known not be needed + * anymore. + * + * @since 5.10 + */ +public class PersistentPTY extends PTY { + + private class PersistentPTYInputStream extends PTYInputStream { + public PersistentPTYInputStream(MasterFD fd) { + super(fd); + } + + @Override + public void close() throws IOException { + // This is the change to bring persistence. + // Don't actually close the stream. + } + + public void realClose() throws IOException { + // This method should be called to actually close + // the stream once we know it won't be needed anymore + super.close(); + } + + @Override + protected void finalize() throws IOException { + realClose(); + } + } + + private class PersistentPTYOutputStream extends PTYOutputStream { + public PersistentPTYOutputStream(MasterFD fd, boolean sendEotBeforeClose) { + super(fd, sendEotBeforeClose); + } + + @Override + public void close() throws IOException { + // This is the change to bring persistence. + // Don't actually close the stream. + } + + public void realClose() throws IOException { + // This method should be called to actually close + // the stream once we know it won't be needed anymore + super.close(); + } + + @Override + protected void finalize() throws IOException { + realClose(); + } + } + + final PersistentPTYInputStream in2; + final PersistentPTYOutputStream out2; + + public PersistentPTY() throws IOException { + this(Mode.CONSOLE); + } + + public PersistentPTY(Mode mode) throws IOException { + super(mode); + in2 = new PersistentPTYInputStream(new MasterFD()); + out2 = new PersistentPTYOutputStream(new MasterFD(), !Platform.OS_WIN32.equals(Platform.getOS())); + } + + @Override + public PTYInputStream getInputStream() { + return in2; + } + + @Override + public PTYOutputStream getOutputStream() { + return out2; + } + + /** + * This method must be called once the PersistentPTY is + * no longer needed, so that its streams can be closed. + */ + public void closeStreams() throws IOException { + in2.realClose(); + out2.realClose(); + } +} diff --git a/dsf-gdb/org.eclipse.cdt.dsf.gdb/.settings/.api_filters b/dsf-gdb/org.eclipse.cdt.dsf.gdb/.settings/.api_filters index b8ded6d151f..0b6030e35e1 100644 --- a/dsf-gdb/org.eclipse.cdt.dsf.gdb/.settings/.api_filters +++ b/dsf-gdb/org.eclipse.cdt.dsf.gdb/.settings/.api_filters @@ -20,6 +20,14 @@ + + + + + + + + diff --git a/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/gdb/service/GDBProcesses_7_0.java b/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/gdb/service/GDBProcesses_7_0.java index 18db6225464..1434dc3f877 100644 --- a/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/gdb/service/GDBProcesses_7_0.java +++ b/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/gdb/service/GDBProcesses_7_0.java @@ -17,6 +17,8 @@ *******************************************************************************/ package org.eclipse.cdt.dsf.gdb.service; +import java.io.IOException; +import java.io.OutputStream; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -29,12 +31,14 @@ import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; +import java.util.concurrent.TimeUnit; import org.eclipse.cdt.core.CCorePlugin; import org.eclipse.cdt.core.IProcessInfo; import org.eclipse.cdt.core.IProcessList; import org.eclipse.cdt.dsf.concurrent.DataRequestMonitor; import org.eclipse.cdt.dsf.concurrent.DsfExecutor; +import org.eclipse.cdt.dsf.concurrent.DsfRunnable; import org.eclipse.cdt.dsf.concurrent.ImmediateDataRequestMonitor; import org.eclipse.cdt.dsf.concurrent.ImmediateExecutor; import org.eclipse.cdt.dsf.concurrent.ImmediateRequestMonitor; @@ -62,6 +66,7 @@ import org.eclipse.cdt.dsf.debug.service.IRunControl.ISuspendedDMEvent; import org.eclipse.cdt.dsf.debug.service.command.BufferedCommandControl; import org.eclipse.cdt.dsf.debug.service.command.CommandCache; import org.eclipse.cdt.dsf.debug.service.command.ICommandControlService.ICommandControlDMContext; +import org.eclipse.cdt.dsf.debug.service.command.ICommandControlService.ICommandControlShutdownDMEvent; import org.eclipse.cdt.dsf.debug.service.command.IEventListener; import org.eclipse.cdt.dsf.gdb.IGDBLaunchConfigurationConstants; import org.eclipse.cdt.dsf.gdb.IGdbDebugConstants; @@ -79,6 +84,7 @@ import org.eclipse.cdt.dsf.mi.service.IMIRunControl.MIRunMode; import org.eclipse.cdt.dsf.mi.service.MIBreakpointsManager; import org.eclipse.cdt.dsf.mi.service.MIProcesses; import org.eclipse.cdt.dsf.mi.service.command.CommandFactory; +import org.eclipse.cdt.dsf.mi.service.command.MIInferiorProcess; import org.eclipse.cdt.dsf.mi.service.command.events.MIThreadGroupCreatedEvent; import org.eclipse.cdt.dsf.mi.service.command.events.MIThreadGroupExitedEvent; import org.eclipse.cdt.dsf.mi.service.command.output.MIConst; @@ -95,10 +101,13 @@ import org.eclipse.cdt.dsf.mi.service.command.output.MIValue; import org.eclipse.cdt.dsf.service.AbstractDsfService; import org.eclipse.cdt.dsf.service.DsfServiceEventHandler; import org.eclipse.cdt.dsf.service.DsfSession; +import org.eclipse.cdt.utils.pty.PTY; +import org.eclipse.cdt.utils.pty.PersistentPTY; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Platform; import org.eclipse.core.runtime.Status; +import org.eclipse.debug.core.DebugPlugin; import org.eclipse.debug.core.ILaunch; import org.eclipse.debug.core.model.IProcess; import org.osgi.framework.BundleContext; @@ -588,6 +597,15 @@ public class GDBProcesses_7_0 extends AbstractDsfService // be running at the time. Bug 303503 private Map fDebuggedProcessesAndNames = new HashMap<>(); + /** + * A map that keeps track of the PTY associated with an inferior (groupId) + */ + private Map fGroupIdToPTYMap = new HashMap<>(); + /** + * A list of groupIds that have exited. + */ + private List fExitedGroupId = new ArrayList<>(); + /** * Information about an exited process * @since 4.7 @@ -665,14 +683,6 @@ public class GDBProcesses_7_0 extends AbstractDsfService */ private boolean fInitialProcess = true; - /** - * Keeps track of the fact that we are restarting a process or not. - * This is important so that we know if we should automatically terminate - * GDB or not. If the process is being restarted, we have to make sure - * not to kill GDB. - */ - private boolean fProcRestarting; - public GDBProcesses_7_0(DsfSession session) { super(session); } @@ -1680,8 +1690,6 @@ public class GDBProcesses_7_0 extends AbstractDsfService /** @since 4.0 */ @Override public void restart(IContainerDMContext containerDmc, final Map attributes, final DataRequestMonitor rm) { - fProcRestarting = true; - // Before performing the restart, check if the process is properly suspended. // For such a case, we usually use IMIRunControl.isTargetAcceptingCommands(). // However, in non-stop, although the target is accepting command, a restart @@ -1708,11 +1716,10 @@ public class GDBProcesses_7_0 extends AbstractDsfService startOrRestart(newContainerDmc, attributes, true, new ImmediateDataRequestMonitor(rm) { @Override protected void handleCompleted() { - if (!isSuccess()) { - fProcRestarting = false; - } - // In case the process we restarted was already exited, remove it from our list + // We do this here for GDB 7.1, because we know the proper groupId here which + // will change when the new restarted process will start. For GDB >= 7.2 + // the groupId is fixed so we don't have to do this right away, but it won't hurt. getExitedProcesses().remove(groupId); setData(getData()); @@ -1757,11 +1764,12 @@ public class GDBProcesses_7_0 extends AbstractDsfService return new StartOrRestartProcessSequence_7_0(executor, containerDmc, attributes, restart, rm); } - /** * Removes the process with the specified groupId from the launch. + * + * @return The label used for the console of that process. */ - private void removeProcessFromLaunch(String groupId) { + private String removeProcessFromLaunch(String groupId) { ILaunch launch = (ILaunch)getSession().getModelAdapter(ILaunch.class); IProcess[] launchProcesses = launch.getProcesses(); for (IProcess process : launchProcesses) { @@ -1774,12 +1782,59 @@ public class GDBProcesses_7_0 extends AbstractDsfService if (groupAttribute == null || groupAttribute.equals(MIProcesses.UNIQUE_GROUP_ID) || groupAttribute.equals(groupId)) { launch.removeProcess(process); - break; + return process.getLabel(); } } } + return null; } + /** + * Add the specified process to the launch. + */ + private void addProcessToLaunch(Process inferior, String groupId, String label) { + // Add the inferior to the launch. + // This cannot be done on the executor or things deadlock. + DebugPlugin.getDefault().asyncExec(new Runnable() { + @Override + public void run() { + // Add the inferior + // Need to go through DebugPlugin.newProcess so that we can use + // the overrideable process factory to allow others to override. + // First set attribute to specify we want to create an inferior process. + // Bug 210366 + ILaunch launch = (ILaunch)getSession().getModelAdapter(ILaunch.class); + Map attributes = new HashMap(); + attributes.put(IGdbDebugConstants.PROCESS_TYPE_CREATION_ATTR, + IGdbDebugConstants.INFERIOR_PROCESS_CREATION_VALUE); + IProcess runtimeInferior = DebugPlugin.newProcess(launch, inferior, label != null ? label : "", attributes); //$NON-NLS-1$ + // Now set the inferior groupId + runtimeInferior.setAttribute(IGdbDebugConstants.INFERIOR_GROUPID_ATTR, groupId); + } + }); + } + + /** + * @since 5.2 + */ + @Override + public void addInferiorToLaunch(IContainerDMContext containerDmc, String label, PTY pty, RequestMonitor rm) { + if (containerDmc instanceof IMIContainerDMContext) { + String groupId = ((IMIContainerDMContext)containerDmc).getGroupId(); + // Create an MIInferiorProcess to track the new instance of the process, + // remove the old one from the launch, and add the new one to the launch. + Process inferiorProcess; + if (pty == null) { + inferiorProcess = createInferiorProcess(containerDmc, fBackend.getMIOutputStream()); + } else { + fGroupIdToPTYMap.put(groupId, pty); + inferiorProcess = createInferiorProcess(containerDmc, pty); + } + addProcessToLaunch(inferiorProcess, groupId, label); + } + rm.done(); + } + @DsfServiceEventHandler public void eventDispatched(final MIThreadGroupCreatedEvent e) { IProcessDMContext procDmc = e.getDMContext(); @@ -1807,6 +1862,25 @@ public class GDBProcesses_7_0 extends AbstractDsfService } } + /** @since 5.2 */ + protected MIInferiorProcess createInferiorProcess(IContainerDMContext container, OutputStream outputStream) { + return new MIInferiorProcess(container, outputStream); + } + + /** @since 5.2 */ + protected MIInferiorProcess createInferiorProcess(IContainerDMContext container, PTY pty) { + return new MIInferiorProcess(container, pty); + } + + private void handleRestartingProcess(IMIContainerDMContext containerDmc) { + String label = removeProcessFromLaunch(containerDmc.getGroupId()); + if (label != null) { + // We only add the process to the launch if the original process was part of the launch. + // For example, in the attach case, there is no process added to the launch + // We re-use the same PTY as the one used before the restart. + addInferiorToLaunch(containerDmc, label, fGroupIdToPTYMap.get(containerDmc.getGroupId()), new ImmediateRequestMonitor()); + } + } @DsfServiceEventHandler public void eventDispatched(ISuspendedDMEvent e) { @@ -1836,10 +1910,15 @@ public class GDBProcesses_7_0 extends AbstractDsfService // Event handler when a thread or threadGroup starts @DsfServiceEventHandler public void eventDispatched(IStartedDMEvent e) { - if (e instanceof ContainerStartedDMEvent) { + if (e.getDMContext() instanceof IMIContainerDMContext) { + String groupId = ((IMIContainerDMContext)e.getDMContext()).getGroupId(); + if (fExitedGroupId.remove(groupId)) { + // The process in question is restarting. + handleRestartingProcess((IMIContainerDMContext)e.getDMContext()); + } + fContainerCommandCache.reset(); fNumConnected++; - fProcRestarting = false; } else { fThreadCommandCache.reset(); } @@ -1848,30 +1927,58 @@ public class GDBProcesses_7_0 extends AbstractDsfService // Event handler when a thread or a threadGroup exits @DsfServiceEventHandler public void eventDispatched(IExitedDMEvent e) { - if (e instanceof ContainerExitedDMEvent) { + if (e.getDMContext() instanceof IMIContainerDMContext) { + fExitedGroupId.add(((IMIContainerDMContext)e.getDMContext()).getGroupId()); + fContainerCommandCache.reset(); assert fNumConnected > 0; fNumConnected--; - if (Platform.getPreferencesService().getBoolean(GdbPlugin.PLUGIN_ID, - IGdbDebugPreferenceConstants.PREF_AUTO_TERMINATE_GDB, - true, null)) { - if (fNumConnected == 0 && !fProcRestarting) { - // If the last process we are debugging finishes, and we are not restarting it, - // let's terminate GDB. - // We also do this for a remote attach session, since the 'auto terminate' preference - // is enabled. If users want to keep the session alive to attach to another process, - // they can simply disable that preference - fCommandControl.terminate(new ImmediateRequestMonitor()); - } + if (fNumConnected == 0 && + Platform.getPreferencesService().getBoolean(GdbPlugin.PLUGIN_ID, + IGdbDebugPreferenceConstants.PREF_AUTO_TERMINATE_GDB, + true, null)) { + // If the last process we are debugging finishes and does not restart + // let's terminate GDB. We wait a small delay to see if the process will restart. + // We also do this for a remote attach session, since the 'auto terminate' preference + // is enabled. If users want to keep the session alive to attach to another process, + // they can simply disable that preference + getExecutor().schedule(new DsfRunnable() { + @Override + public void run() { + // Verify the process didn't restart by checking that we still have nothing connected + if (fNumConnected == 0) { + fCommandControl.terminate(new ImmediateRequestMonitor()); + } + } + }, 500, TimeUnit.MILLISECONDS); } } else { fThreadCommandCache.reset(); } } - - @Override + + /** + * @since 5.2 + */ + @DsfServiceEventHandler + public void eventDispatched(ICommandControlShutdownDMEvent e) { + // Now that the debug session is over, close the persistent PTY streams + for (PTY pty : fGroupIdToPTYMap.values()) { + if (pty instanceof PersistentPTY) { + try { + ((PersistentPTY)pty).closeStreams(); + } catch (IOException e1) { + } + } + } + fGroupIdToPTYMap.clear(); + + fExitedGroupId.clear(); + } + + @Override public void flushCache(IDMContext context) { fContainerCommandCache.reset(context); fThreadCommandCache.reset(context); @@ -1944,6 +2051,16 @@ public class GDBProcesses_7_0 extends AbstractDsfService } if (groupId != null) { + // In case the process that just started was already exited (so we are dealing + // with a restart), remove it from our list. + // Do this here to handle the restart case triggered by GDB itself + // (user typing 'run' from the GDB console). In this case, we don't know yet + // we are dealing with a restart, but when we see the process come back, we + // know to remove it from the exited list. Note that this won't work + // for GDB 7.1 because the groupId of the new process is not the same as the old + // one. Not worth fixing for such an old version. + getExitedProcesses().remove(groupId); + getGroupToPidMap().put(groupId, pId); // Mark that we know this new process, but don't fetch its @@ -1969,8 +2086,8 @@ public class GDBProcesses_7_0 extends AbstractDsfService // GDB is no longer debugging this process. Remove it from our list String name = fDebuggedProcessesAndNames.remove(pId); - if (!fProcRestarting && !getDetachedProcesses().remove(groupId)) { - // If the process is not restarting and was not detached, + if (!getDetachedProcesses().remove(groupId)) { + // If the process was not detached, // store it in the list of exited processes. getExitedProcesses().put(groupId, new ExitedProcInfo(pId, name)); } diff --git a/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/gdb/service/GDBProcesses_7_12.java b/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/gdb/service/GDBProcesses_7_12.java new file mode 100644 index 00000000000..0e361157b7a --- /dev/null +++ b/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/gdb/service/GDBProcesses_7_12.java @@ -0,0 +1,110 @@ +/******************************************************************************* + * Copyright (c) 2016 Ericsson and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + *******************************************************************************/ +package org.eclipse.cdt.dsf.gdb.service; + +import org.eclipse.cdt.dsf.concurrent.ImmediateDataRequestMonitor; +import org.eclipse.cdt.dsf.concurrent.ImmediateRequestMonitor; +import org.eclipse.cdt.dsf.concurrent.RequestMonitor; +import org.eclipse.cdt.dsf.datamodel.DMContexts; +import org.eclipse.cdt.dsf.datamodel.IDMContext; +import org.eclipse.cdt.dsf.gdb.internal.GdbPlugin; +import org.eclipse.cdt.dsf.mi.service.IMIContainerDMContext; +import org.eclipse.cdt.dsf.mi.service.IMIProcessDMContext; +import org.eclipse.cdt.dsf.mi.service.IMIRunControl; +import org.eclipse.cdt.dsf.service.DsfSession; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; + +/** + * @since 5.2 + */ +public class GDBProcesses_7_12 extends GDBProcesses_7_10 { + + public GDBProcesses_7_12(DsfSession session) { + super(session); + } + + @Override + public void terminate(IThreadDMContext thread, RequestMonitor rm) { + IGDBBackend backend = getServicesTracker().getService(IGDBBackend.class); + if (!backend.isFullGdbConsoleSupported()) { + super.terminate(thread, rm); + return; + } + + // If we are running the full GDB console, there is a bug with GDB 7.12 + // where after we terminate the process, the GDB prompt does not come + // back in the console. As a workaround, we first interrupt the process + // to get the prompt back, and only then kill the process. + // https://sourceware.org/bugzilla/show_bug.cgi?id=20766 + if (thread instanceof IMIProcessDMContext) { + getDebuggingContext( + thread, + new ImmediateDataRequestMonitor(rm) { + @Override + protected void handleSuccess() { + if (getData() instanceof IMIContainerDMContext) { + IMIContainerDMContext containerDmc = (IMIContainerDMContext)getData(); + IMIRunControl runControl = getServicesTracker().getService(IMIRunControl.class); + if (runControl != null && !runControl.isSuspended(containerDmc)) { + runControl.suspend(containerDmc, new ImmediateRequestMonitor(rm) { + @Override + protected void handleCompleted() { + GDBProcesses_7_12.super.terminate(thread, rm); + } + }); + } else { + GDBProcesses_7_12.super.terminate(thread, rm); + } + } else { + rm.done(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, INTERNAL_ERROR, "Invalid process context.", null)); //$NON-NLS-1$ + } + } + }); + } else { + super.terminate(thread, rm); + } + } + + @Override + public void detachDebuggerFromProcess(IDMContext dmc, RequestMonitor rm) { + if (DMContexts.getAncestorOfType(dmc, MIExitedProcessDMC.class) != null) { + super.detachDebuggerFromProcess(dmc, rm); + return; + } + + IGDBBackend backend = getServicesTracker().getService(IGDBBackend.class); + if (!backend.isFullGdbConsoleSupported()) { + super.detachDebuggerFromProcess(dmc, rm); + return; + } + + // If we are running the full GDB console, there is a bug with GDB 7.12 + // where after we detach the process, the GDB prompt does not come + // back in the console. As a workaround, we first interrupt the process + // to get the prompt back, and only then detach the process. + // https://sourceware.org/bugzilla/show_bug.cgi?id=20766 + if (!doCanDetachDebuggerFromProcess()) { + rm.done(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, INTERNAL_ERROR, "Detach not supported.", null)); //$NON-NLS-1$ + return; + } + + IMIContainerDMContext containerDmc = DMContexts.getAncestorOfType(dmc, IMIContainerDMContext.class); + IMIRunControl runControl = getServicesTracker().getService(IMIRunControl.class); + if (containerDmc != null && runControl != null && !runControl.isSuspended(containerDmc)) { + runControl.suspend(containerDmc, new ImmediateRequestMonitor(rm) { + @Override + protected void handleCompleted() { + GDBProcesses_7_12.super.detachDebuggerFromProcess(dmc, rm); + } + }); + } else { + super.detachDebuggerFromProcess(dmc, rm); + } + } +} diff --git a/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/gdb/service/GDBProcesses_7_3.java b/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/gdb/service/GDBProcesses_7_3.java index dbf8fe657d9..d180666cb94 100644 --- a/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/gdb/service/GDBProcesses_7_3.java +++ b/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/gdb/service/GDBProcesses_7_3.java @@ -10,15 +10,19 @@ *******************************************************************************/ package org.eclipse.cdt.dsf.gdb.service; +import java.io.OutputStream; import java.util.Map; import org.eclipse.cdt.dsf.concurrent.DataRequestMonitor; import org.eclipse.cdt.dsf.concurrent.DsfExecutor; import org.eclipse.cdt.dsf.concurrent.Sequence; import org.eclipse.cdt.dsf.debug.service.IRunControl.IContainerDMContext; +import org.eclipse.cdt.dsf.mi.service.command.MIInferiorProcess; +import org.eclipse.cdt.dsf.mi.service.command.MIInferiorProcess_7_3; import org.eclipse.cdt.dsf.mi.service.command.events.MIThreadGroupExitedEvent; import org.eclipse.cdt.dsf.service.DsfServiceEventHandler; import org.eclipse.cdt.dsf.service.DsfSession; +import org.eclipse.cdt.utils.pty.PTY; /** * Version for GDB 7.3 @@ -38,6 +42,16 @@ public class GDBProcesses_7_3 extends GDBProcesses_7_2_1 { return new StartOrRestartProcessSequence_7_3(executor, containerDmc, attributes, restart, rm); } + @Override + protected MIInferiorProcess createInferiorProcess(IContainerDMContext container, OutputStream outputStream) { + return new MIInferiorProcess_7_3(container, outputStream); + } + + @Override + protected MIInferiorProcess createInferiorProcess(IContainerDMContext container, PTY pty) { + return new MIInferiorProcess_7_3(container, pty); + } + @Override @DsfServiceEventHandler public void eventDispatched(MIThreadGroupExitedEvent e) { diff --git a/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/gdb/service/GdbDebugServicesFactory.java b/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/gdb/service/GdbDebugServicesFactory.java index 62248f99972..77e40705ba7 100644 --- a/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/gdb/service/GdbDebugServicesFactory.java +++ b/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/gdb/service/GdbDebugServicesFactory.java @@ -275,6 +275,9 @@ public class GdbDebugServicesFactory extends AbstractDsfDebugServicesFactory { @Override protected IProcesses createProcessesService(DsfSession session) { + if (compareVersionWith(GDB_7_12_VERSION) >= 0) { + return new GDBProcesses_7_12(session); + } if (compareVersionWith(GDB_7_10_VERSION) >= 0) { return new GDBProcesses_7_10(session); } diff --git a/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/gdb/service/IGDBProcesses.java b/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/gdb/service/IGDBProcesses.java index 17d7eb933f0..da6ddda56ac 100644 --- a/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/gdb/service/IGDBProcesses.java +++ b/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/gdb/service/IGDBProcesses.java @@ -15,12 +15,18 @@ package org.eclipse.cdt.dsf.gdb.service; import java.util.Map; import org.eclipse.cdt.dsf.concurrent.DataRequestMonitor; +import org.eclipse.cdt.dsf.concurrent.IDsfStatusConstants; +import org.eclipse.cdt.dsf.concurrent.RequestMonitor; import org.eclipse.cdt.dsf.datamodel.IDMContext; import org.eclipse.cdt.dsf.datamodel.IDMEvent; import org.eclipse.cdt.dsf.debug.service.IRunControl.IContainerDMContext; +import org.eclipse.cdt.dsf.gdb.internal.GdbPlugin; import org.eclipse.cdt.dsf.mi.service.IMIContainerDMContext; import org.eclipse.cdt.dsf.mi.service.IMIExecutionDMContext; import org.eclipse.cdt.dsf.mi.service.IMIProcesses; +import org.eclipse.cdt.utils.pty.PTY; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; public interface IGDBProcesses extends IMIProcesses { @@ -127,4 +133,17 @@ public interface IGDBProcesses extends IMIProcesses { */ void attachDebuggerToProcess(IProcessDMContext procCtx, String file, DataRequestMonitor rm); + /** + * Adds a process representing the inferior to the launch. An I/O console will be created if necessary. + * + * @param containerDmc The inferior for which a a process will be added to the launch. + * @param label The name to use for the console if created. + * @param pty The PTY to be used by the console for I/O + * @param rm The requestMonitor that indicates that the request has been completed. + * + * @since 5.2 + */ + default void addInferiorToLaunch(IContainerDMContext containerDmc, String label, PTY pty, RequestMonitor rm) { + rm.done(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, IDsfStatusConstants.NOT_SUPPORTED, "Not supported", null)); //$NON-NLS-1$ + } } diff --git a/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/gdb/service/StartOrRestartProcessSequence_7_0.java b/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/gdb/service/StartOrRestartProcessSequence_7_0.java index 89c04add883..9886bf8d86e 100644 --- a/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/gdb/service/StartOrRestartProcessSequence_7_0.java +++ b/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/gdb/service/StartOrRestartProcessSequence_7_0.java @@ -24,6 +24,7 @@ import org.eclipse.cdt.dsf.concurrent.DataRequestMonitor; import org.eclipse.cdt.dsf.concurrent.DsfExecutor; import org.eclipse.cdt.dsf.concurrent.IDsfStatusConstants; import org.eclipse.cdt.dsf.concurrent.ImmediateDataRequestMonitor; +import org.eclipse.cdt.dsf.concurrent.ImmediateRequestMonitor; import org.eclipse.cdt.dsf.concurrent.ReflectionSequence; import org.eclipse.cdt.dsf.concurrent.RequestMonitor; import org.eclipse.cdt.dsf.datamodel.DMContexts; @@ -31,14 +32,11 @@ import org.eclipse.cdt.dsf.debug.service.IBreakpoints.IBreakpointsTargetDMContex import org.eclipse.cdt.dsf.debug.service.IRunControl.IContainerDMContext; import org.eclipse.cdt.dsf.debug.service.command.ICommand; import org.eclipse.cdt.dsf.gdb.IGDBLaunchConfigurationConstants; -import org.eclipse.cdt.dsf.gdb.IGdbDebugConstants; import org.eclipse.cdt.dsf.gdb.internal.GdbPlugin; -import org.eclipse.cdt.dsf.gdb.launching.InferiorRuntimeProcess; import org.eclipse.cdt.dsf.gdb.launching.LaunchUtils; import org.eclipse.cdt.dsf.gdb.service.command.IGDBControl; import org.eclipse.cdt.dsf.mi.service.IMICommandControl; import org.eclipse.cdt.dsf.mi.service.IMIContainerDMContext; -import org.eclipse.cdt.dsf.mi.service.MIProcesses; import org.eclipse.cdt.dsf.mi.service.command.CommandFactory; import org.eclipse.cdt.dsf.mi.service.command.MIInferiorProcess; import org.eclipse.cdt.dsf.mi.service.command.output.MIBreakInsertInfo; @@ -46,12 +44,10 @@ import org.eclipse.cdt.dsf.mi.service.command.output.MIBreakpoint; import org.eclipse.cdt.dsf.mi.service.command.output.MIInfo; import org.eclipse.cdt.dsf.service.DsfServicesTracker; import org.eclipse.cdt.utils.pty.PTY; +import org.eclipse.cdt.utils.pty.PersistentPTY; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Path; import org.eclipse.core.runtime.Status; -import org.eclipse.debug.core.DebugPlugin; -import org.eclipse.debug.core.ILaunch; -import org.eclipse.debug.core.model.IProcess; /** * This class causes a process to start (run for the first time), or to @@ -262,8 +258,8 @@ public class StartOrRestartProcessSequence_7_0 extends ReflectionSequence { /** * This method does the necessary work to setup the input/output streams for the * inferior process, by either preparing the PTY to be used, or by simply leaving - * the PTY null, which indicates that the input/output streams of the CLI should - * be used instead; this decision is based on the type of session. + * the PTY null, which indicates that the input/output streams are handled externally; + * this decision is based on the type of session. */ @Execute public void stepInitializeInputOutput(final RequestMonitor rm) { @@ -273,10 +269,22 @@ public class StartOrRestartProcessSequence_7_0 extends ReflectionSequence { fPty = null; rm.done(); } else { + if (fRestart) { + // For a restart we re-use the previous PersistentPTY (or no pty if we couldn't tell GDB about it) + assert fPty instanceof PersistentPTY || fPty == null; + rm.done(); + return; + } + // Every other type of session that can get to this code, is starting a new process // and requires a pty for it. try { - fPty = new PTY(); + // Use a PersistentPTY so it can be re-used for restarts. + // It is possible that the inferior will be restarted by the user from + // the GDB console, in which case, we are not able to create a new PTY + // for it; using a persistentPTY allows this to work since the persistentPTY + // does not need to be replaced but can continue to be used. + fPty = new PersistentPTY(); fPty.validateSlaveName(); // Tell GDB to use this PTY @@ -298,12 +306,20 @@ public class StartOrRestartProcessSequence_7_0 extends ReflectionSequence { } } - /** @since 4.7 */ + /** + * @since 4.7 + * @deprecated The creation of MIInferiorProcess has been moved to the IGDBProcesses service + */ + @Deprecated protected MIInferiorProcess createInferiorProcess(IContainerDMContext container, OutputStream outputStream) { return new MIInferiorProcess(container, outputStream); } - /** @since 4.7 */ + /** + * @since 4.7 + * @deprecated The creation of MIInferiorProcess has been moved to the IGDBProcesses service + */ + @Deprecated protected MIInferiorProcess createInferiorProcess(IContainerDMContext container, PTY pty) { return new MIInferiorProcess(container, pty); } @@ -312,29 +328,23 @@ public class StartOrRestartProcessSequence_7_0 extends ReflectionSequence { * Before running the program, we must create its console for IO. */ @Execute - public void stepCreateConsole(final RequestMonitor rm) { + public void stepCreateConsole(RequestMonitor rm) { if (fBackend.getSessionType() == SessionType.REMOTE) { // The program output for a remote session is handled by gdbserver. Therefore, // no need to create an inferior process and add it to the launch. rm.done(); return; } - - Process inferiorProcess; - if (fPty == null) { - inferiorProcess = createInferiorProcess(fContainerDmc, fBackend.getMIOutputStream()); - } else { - inferiorProcess = createInferiorProcess(fContainerDmc, fPty); + + if (fRestart) { + // For a restart, the IGDBProcesses service already handles creating the new + // console. We do it this way because a restart can be triggered by the user + // on the GDB console, so this class isn't even called in that case. + rm.done(); + return; } - final Process inferior = inferiorProcess; - final ILaunch launch = getContainerContext().getAdapter(ILaunch.class); - - // This is the groupId of the new process that will be started, even in the - // case of a restart. - final String groupId = ((IMIContainerDMContext)getContainerContext()).getGroupId(); - - // For multi-process, we cannot simply use the name given by the backend service + // For multi-process, we cannot simply use the name given by the backend service // because we may not be starting that process, but another one. // Instead, we can look in the attributes for the binary name, which we stored // there for this case, specifically. @@ -350,46 +360,12 @@ public class StartOrRestartProcessSequence_7_0 extends ReflectionSequence { defaultPathName); final String pathLabel = new Path(progPathName).lastSegment(); - // Add the inferior to the launch. - // This cannot be done on the executor or things deadlock. - DebugPlugin.getDefault().asyncExec(new Runnable() { + // Adds the inferior to the launch which will also create the console + fProcService.addInferiorToLaunch(fContainerDmc, pathLabel, fPty, new ImmediateRequestMonitor() { @Override - public void run() { - String label = pathLabel; - - if (fRestart) { - // For a restart, remove the old inferior - IProcess[] launchProcesses = launch.getProcesses(); - for (IProcess process : launchProcesses) { - if (process instanceof InferiorRuntimeProcess) { - String groupAttribute = process.getAttribute(IGdbDebugConstants.INFERIOR_GROUPID_ATTR); - - // if the groupAttribute is not set in the process we know we are dealing - // with single process debugging so the one process is the one we want. - // If the groupAttribute is set, then we must make sure it is the proper inferior - if (groupAttribute == null || groupAttribute.equals(MIProcesses.UNIQUE_GROUP_ID) || - groupAttribute.equals(groupId)) { - launch.removeProcess(process); - // Use the exact same label as before - label = process.getLabel(); - break; - } - } - } - } - - // Add the inferior - // Need to go through DebugPlugin.newProcess so that we can use - // the overrideable process factory to allow others to override. - // First set attribute to specify we want to create an inferior process. - // Bug 210366 - Map attributes = new HashMap(); - attributes.put(IGdbDebugConstants.PROCESS_TYPE_CREATION_ATTR, - IGdbDebugConstants.INFERIOR_PROCESS_CREATION_VALUE); - IProcess runtimeInferior = DebugPlugin.newProcess(launch, inferior, label, attributes); - // Now set the inferior groupId - runtimeInferior.setAttribute(IGdbDebugConstants.INFERIOR_GROUPID_ATTR, groupId); - + protected void handleCompleted() { + // Accept errors. The console won't be created but the rest will mostly work + // This can especially happen if extenders did not implement IGDBProcesses.addInferiorToLaunch() rm.done(); } }); diff --git a/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/gdb/service/StartOrRestartProcessSequence_7_3.java b/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/gdb/service/StartOrRestartProcessSequence_7_3.java index 5ea07188529..02ed62ce410 100644 --- a/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/gdb/service/StartOrRestartProcessSequence_7_3.java +++ b/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/gdb/service/StartOrRestartProcessSequence_7_3.java @@ -31,11 +31,15 @@ public class StartOrRestartProcessSequence_7_3 extends StartOrRestartProcessSequ super(executor, containerDmc, attributes, restart, rm); } + /** @deprecated The creation of MIInferiorProcess has been moved to the IGDBProcesses service */ + @Deprecated @Override protected MIInferiorProcess createInferiorProcess(IContainerDMContext container, OutputStream outputStream) { return new MIInferiorProcess_7_3(container, outputStream); } + /** @deprecated The creation of MIInferiorProcess has been moved to the IGDBProcesses service */ + @Deprecated @Override protected MIInferiorProcess createInferiorProcess(IContainerDMContext container, PTY pty) { return new MIInferiorProcess_7_3(container, pty); diff --git a/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/gdb/service/extensions/GDBProcesses_HEAD.java b/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/gdb/service/extensions/GDBProcesses_HEAD.java index fd49522341a..596d4b49092 100644 --- a/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/gdb/service/extensions/GDBProcesses_HEAD.java +++ b/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/gdb/service/extensions/GDBProcesses_HEAD.java @@ -8,7 +8,7 @@ package org.eclipse.cdt.dsf.gdb.service.extensions; import org.eclipse.cdt.dsf.debug.service.IProcesses; -import org.eclipse.cdt.dsf.gdb.service.GDBProcesses_7_10; +import org.eclipse.cdt.dsf.gdb.service.GDBProcesses_7_12; import org.eclipse.cdt.dsf.gdb.service.GdbDebugServicesFactory; import org.eclipse.cdt.dsf.service.DsfSession; @@ -36,14 +36,14 @@ import org.eclipse.cdt.dsf.service.DsfSession; * * @since 4.8 */ -public class GDBProcesses_HEAD extends GDBProcesses_7_10 { +public class GDBProcesses_HEAD extends GDBProcesses_7_12 { public GDBProcesses_HEAD(DsfSession session) { super(session); validateGdbVersion(session); } - protected String getMinGDBVersionSupported() { return GdbDebugServicesFactory.GDB_7_10_VERSION; } + protected String getMinGDBVersionSupported() { return GdbDebugServicesFactory.GDB_7_12_VERSION; } protected void validateGdbVersion(DsfSession session) { GdbDebugServicesFactory.validateGdbVersion(session, getMinGDBVersionSupported(), this); diff --git a/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/mi/service/command/MIInferiorProcess.java b/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/mi/service/command/MIInferiorProcess.java index d6e2b5eba01..b2dc5f2fbae 100644 --- a/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/mi/service/command/MIInferiorProcess.java +++ b/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/mi/service/command/MIInferiorProcess.java @@ -45,6 +45,8 @@ import org.eclipse.cdt.dsf.debug.service.command.IEventListener; import org.eclipse.cdt.dsf.gdb.internal.GdbPlugin; import org.eclipse.cdt.dsf.mi.service.IMICommandControl; import org.eclipse.cdt.dsf.mi.service.IMIContainerDMContext; +import org.eclipse.cdt.dsf.mi.service.IMIProcessDMContext; +import org.eclipse.cdt.dsf.mi.service.MIProcesses; import org.eclipse.cdt.dsf.mi.service.command.commands.CLICommand; import org.eclipse.cdt.dsf.mi.service.command.events.MIThreadGroupExitedEvent; import org.eclipse.cdt.dsf.mi.service.command.output.MIGDBShowExitCodeInfo; @@ -71,9 +73,8 @@ public class MIInferiorProcess extends Process { // Indicates that the inferior has been started - // This is important for the case of a restart - // where we need to make sure not to terminate - // the new inferior, which was not started yet. + // It implies the ContainerDMContext is fully-formed + // with the pid of the process (as a parent dmc) private boolean fStarted; // Indicates that the inferior has been terminated @@ -115,10 +116,8 @@ public class MIInferiorProcess extends Process Integer fExitCode = null; /** - * @returns if that the inferior has been started. - * This is important for the case of a restart - * where we need to make sure not to terminate - * the new inferior, which was not started yet. + * @returns whether the inferior has been started, which means + * we can obtain its process id. * @since 4.7 */ protected boolean isStarted() { @@ -168,6 +167,14 @@ public class MIInferiorProcess extends Process fSession.addServiceEventListener(this, null); fContainerDMContext = container; + IMIProcessDMContext processDmc = DMContexts.getAncestorOfType(fContainerDMContext, IMIProcessDMContext.class); + if (processDmc != null && processDmc.getProcId() != MIProcesses.UNKNOWN_PROCESS_ID) { + // If we already know the pid, it implies the process is already started. + // It also means we won't get the IStartedDMEvent for the process. + // This happens when the inferior is restarted, in which case, this class + // is created after the process is running and the IStartedDMEvent was sent. + fStarted = true; + } DsfServicesTracker tracker = new DsfServicesTracker(GdbPlugin.getBundleContext(), fSession.getId()); fCommandControl = tracker.getService(IMICommandControl.class); @@ -466,11 +473,18 @@ public class MIInferiorProcess extends Process // For multi-process, make sure the exited event // is actually for this inferior. if (e.getDMContext().equals(fContainerDMContext)) { + // With recent changes, we notice a restart by the exited/started + // events, and only then create the new inferior; this means the new + // inferior will no longer receive the exited event for the old inferior. + // But it does not hurt to leave the below if check just in case. + assert fStarted : "Exited event should only be received for a started inferior"; //$NON-NLS-1$ if (fStarted) { // Only mark this process as terminated if it was already - // started. This is to protect ourselves in the case of - // a restart, where the new inferior is already created - // and gets the exited event for the old inferior. + // started. This was to protect ourselves in the case of + // a restart, where we used to create the new inferior before + // killing the old one. In that case we would get the exited + // event of the old inferior, which we had to ignore. + // setTerminated(); } } @@ -489,7 +503,7 @@ public class MIInferiorProcess extends Process public void eventDispatched(IStartedDMEvent e) { if (e.getDMContext() instanceof IMIContainerDMContext) { // Mark the inferior started if the event is for this inferior. - // We may get other started events in the cases of a restarts + // We may get other started events in the case of a restart if (!fStarted) { // For multi-process, make sure the started event // is actually for this inferior. diff --git a/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/mi/service/command/MIInferiorProcess_7_3.java b/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/mi/service/command/MIInferiorProcess_7_3.java index 99fc21ebf8a..4fd92f117e8 100644 --- a/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/mi/service/command/MIInferiorProcess_7_3.java +++ b/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/mi/service/command/MIInferiorProcess_7_3.java @@ -77,6 +77,12 @@ public class MIInferiorProcess_7_3 extends MIInferiorProcess public void eventDispatched(MIThreadGroupExitedEvent e) { if (getContainer() instanceof IMIContainerDMContext) { if (((IMIContainerDMContext)getContainer()).getGroupId().equals(e.getGroupId())) { + // With recent changes, we notice a restart by the exited/started + // events, and only then create the new inferior; this means the new + // inferior will no longer receive the exited event for the old inferior. + // But it does not hurt to leave the below if check just in case. + assert isStarted() : "Exited event should only be received for a started inferior"; //$NON-NLS-1$ + if (isStarted()) { // Only handle this event if this process was already // started. This is to protect ourselves in the case of