1
0
Fork 0
mirror of https://github.com/eclipse-cdt/cdt synced 2025-04-23 14:42:11 +02:00

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
This commit is contained in:
Marc Khouzam 2016-10-14 15:58:23 -04:00 committed by Gerrit Code Review @ Eclipse.org
parent e8480ca0f8
commit 39c781f81a
14 changed files with 491 additions and 116 deletions

View file

@ -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

View file

@ -11,7 +11,7 @@
<relativePath>../../pom.xml</relativePath>
</parent>
<version>5.9.0-SNAPSHOT</version>
<version>5.10.0-SNAPSHOT</version>
<artifactId>org.eclipse.cdt.core.native</artifactId>
<packaging>eclipse-plugin</packaging>
</project>

View file

@ -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();
}
}

View file

@ -20,6 +20,14 @@
</message_arguments>
</filter>
</resource>
<resource path="src/org/eclipse/cdt/dsf/gdb/service/IGDBProcesses.java" type="org.eclipse.cdt.dsf.gdb.service.IGDBProcesses">
<filter comment="CDT allows default methods to existing interfaces" id="404000815">
<message_arguments>
<message_argument value="org.eclipse.cdt.dsf.gdb.service.IGDBProcesses"/>
<message_argument value="addInferiorToLaunch(IRunControl.IContainerDMContext, String, PTY, RequestMonitor)"/>
</message_arguments>
</filter>
</resource>
<resource path="src/org/eclipse/cdt/dsf/gdb/service/command/IGDBControl.java" type="org.eclipse.cdt.dsf.gdb.service.command.IGDBControl">
<filter comment="CDT allows adding default methods in a minor release" id="404000815">
<message_arguments>

View file

@ -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<String, String> fDebuggedProcessesAndNames = new HashMap<>();
/**
* A map that keeps track of the PTY associated with an inferior (groupId)
*/
private Map<String, PTY> fGroupIdToPTYMap = new HashMap<>();
/**
* A list of groupIds that have exited.
*/
private List<String> 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<String, Object> attributes, final DataRequestMonitor<IContainerDMContext> 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<IContainerDMContext>(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<String, String> attributes = new HashMap<String, String>();
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));
}

View file

@ -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<IDMContext>(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);
}
}
}

View file

@ -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) {

View file

@ -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);
}

View file

@ -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<IDMContext> 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$
}
}

View file

@ -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,7 +328,7 @@ 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.
@ -320,21 +336,15 @@ public class StartOrRestartProcessSequence_7_0 extends ReflectionSequence {
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<String, String> attributes = new HashMap<String, String>();
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();
}
});

View file

@ -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);

View file

@ -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);

View file

@ -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.

View file

@ -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