/*******************************************************************************
* Copyright (c) 2010, 2011 TUBITAK BILGEM-ITI 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
*
* Contributors:
* Onur Akdemir (TUBITAK BILGEM-ITI) - Multi-process debugging (Bug 237306)
* Marc Khouzam (Ericsson) - Workaround for Bug 352998
*******************************************************************************/
package org.eclipse.cdt.dsf.gdb.service;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.eclipse.cdt.debug.core.CDebugUtils;
import org.eclipse.cdt.dsf.concurrent.DataRequestMonitor;
import org.eclipse.cdt.dsf.concurrent.DsfExecutor;
import org.eclipse.cdt.dsf.concurrent.ImmediateExecutor;
import org.eclipse.cdt.dsf.concurrent.RequestMonitor;
import org.eclipse.cdt.dsf.concurrent.Sequence;
import org.eclipse.cdt.dsf.datamodel.DMContexts;
import org.eclipse.cdt.dsf.datamodel.IDMContext;
import org.eclipse.cdt.dsf.debug.service.IBreakpoints.IBreakpointsTargetDMContext;
import org.eclipse.cdt.dsf.debug.service.IRunControl.IContainerDMContext;
import org.eclipse.cdt.dsf.debug.service.IRunControl.IExitedDMEvent;
import org.eclipse.cdt.dsf.debug.service.command.ICommandControlService.ICommandControlDMContext;
import org.eclipse.cdt.dsf.gdb.IGDBLaunchConfigurationConstants;
import org.eclipse.cdt.dsf.gdb.internal.GdbPlugin;
import org.eclipse.cdt.dsf.gdb.service.IGDBTraceControl.ITraceRecordSelectedChangedDMEvent;
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.IMIExecutionDMContext;
import org.eclipse.cdt.dsf.mi.service.IMIProcessDMContext;
import org.eclipse.cdt.dsf.mi.service.IMIRunControl;
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.output.MIAddInferiorInfo;
import org.eclipse.cdt.dsf.mi.service.command.output.MIInfo;
import org.eclipse.cdt.dsf.service.DsfServiceEventHandler;
import org.eclipse.cdt.dsf.service.DsfSession;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.debug.core.ILaunch;
/**
* Adding support for multi-process with GDB 7.2
*
* @since 4.0
*/
public class GDBProcesses_7_2 extends GDBProcesses_7_1 {
/**
* The id of the single thread to be used during event visualization.
* @since 4.1
*/
protected static final String TRACE_VISUALIZATION_THREAD_ID = "1"; //$NON-NLS-1$
private CommandFactory fCommandFactory;
private IGDBControl fCommandControl;
private IGDBBackend fBackend;
private final static String INVALID = "invalid"; //$NON-NLS-1$
/**
* Keep track if we need to reconnect to the target
* due to a workaround because of a GDB 7.2 bug.
* Bug 352998
*/
private boolean fNeedToReconnect;
/**
* Set of processes that are currently being restarted.
* We use this set for such things as not removing breakpoints
* because we know the process will be restarted.
*/
private Set<IContainerDMContext> fProcRestarting = new HashSet<IContainerDMContext>();
/**
* Indicates that we are currently visualizing trace data.
*/
private boolean fTraceVisualization;
public GDBProcesses_7_2(DsfSession session) {
super(session);
}
@Override
public void initialize(final RequestMonitor requestMonitor) {
super.initialize(new RequestMonitor(ImmediateExecutor.getInstance(), requestMonitor) {
@Override
protected void handleSuccess() {
doInitialize(requestMonitor);
}
});
}
/**
* This method initializes this service after our superclass's initialize()
* method succeeds.
*
* @param requestMonitor
* The call-back object to notify when this service's
* initialization is done.
*/
private void doInitialize(RequestMonitor requestMonitor) {
fCommandControl = getServicesTracker().getService(IGDBControl.class);
fCommandFactory = getServicesTracker().getService(IMICommandControl.class).getCommandFactory();
fBackend = getServicesTracker().getService(IGDBBackend.class);
requestMonitor.done();
}
@Override
public void shutdown(RequestMonitor requestMonitor) {
super.shutdown(requestMonitor);
}
/** @since 4.1 */
protected boolean getTraceVisualization() {
return fTraceVisualization;
}
/** @since 4.1 */
protected void setTraceVisualization(boolean visualizing) {
fTraceVisualization = visualizing;
}
@Override
public IMIContainerDMContext createContainerContextFromGroupId(ICommandControlDMContext controlDmc, String groupId) {
String pid = getGroupToPidMap().get(groupId);
if (pid == null) {
// For GDB 7.2, the groupId is no longer the pid, so use our wildcard pid instead
pid = MIProcesses.UNKNOWN_PROCESS_ID;
}
IProcessDMContext processDmc = createProcessContext(controlDmc, pid);
return createContainerContext(processDmc, groupId);
}
@Override
protected boolean doIsDebuggerAttachSupported() {
// Multi-process is not applicable to post-mortem sessions (core)
// or to non-attach remote sessions.
if (fBackend.getSessionType() == SessionType.CORE) {
return false;
}
if (fBackend.getSessionType() == SessionType.REMOTE && !fBackend.getIsAttachSession()) {
return false;
}
// Multi-process does not work for all-stop right now
IMIRunControl runControl = getServicesTracker().getService(IMIRunControl.class);
if (runControl != null && runControl.getRunMode() == MIRunMode.ALL_STOP) {
// Only one process is allowed in all-stop (for now)
return getNumConnected() == 0;
// NOTE: when we support multi-process in all-stop mode,
// we will need to interrupt the target to when doing the attach.
}
return true;
}
@Override
public void attachDebuggerToProcess(IProcessDMContext procCtx, DataRequestMonitor<IDMContext> rm) {
attachDebuggerToProcess(procCtx, null, rm);
}
/**
* @since 4.0
*/
@Override
public void attachDebuggerToProcess(final IProcessDMContext procCtx, final String binaryPath, final DataRequestMonitor<IDMContext> dataRm) {
if (procCtx instanceof IMIProcessDMContext) {
if (!doIsDebuggerAttachSupported()) {
dataRm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, INTERNAL_ERROR, "Attach not supported.", null)); //$NON-NLS-1$
dataRm.done();
return;
}
// Use a sequence for better control of each step
ImmediateExecutor.getInstance().execute(new Sequence(getExecutor(), dataRm) {
private IMIContainerDMContext fContainerDmc;
private Step[] steps = new Step[] {
// If this is not the very first inferior, we first need create the new inferior
new Step() {
@Override
public void execute(final RequestMonitor rm) {
if (isInitialProcess()) {
// If it is the first inferior, GDB has already created it for us
// We really should get the id from GDB instead of hard-coding it
fContainerDmc = createContainerContext(procCtx, "i1"); //$NON-NLS-1$
rm.done();
return;
}
ICommandControlDMContext controlDmc = DMContexts.getAncestorOfType(procCtx, ICommandControlDMContext.class);
fCommandControl.queueCommand(
fCommandFactory.createMIAddInferior(controlDmc),
new DataRequestMonitor<MIAddInferiorInfo>(ImmediateExecutor.getInstance(), rm) {
@Override
protected void handleSuccess() {
final String groupId = getData().getGroupId();
if (groupId == null || groupId.trim().length() == 0) {
rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, INTERNAL_ERROR, "Invalid gdb group id.", null)); //$NON-NLS-1$
} else {
fContainerDmc = createContainerContext(procCtx, groupId);
}
rm.done();
}
});
}
},
new Step() {
@Override
public void execute(final RequestMonitor rm) {
// Because of a GDB 7.2 bug, for remote-attach sessions,
// we need to be disconnected from the target
// when we set the very first binary to be used.
// So, lets disconnect.
// Bug 352998
if (needFixForGDB72Bug352998()) {
// The bug only applies to remote sessions
if (fBackend.getSessionType() == SessionType.REMOTE) {
assert fBackend.getIsAttachSession();
assert binaryPath != null;
// We only need the workaround for the very first process we attach to
if (isInitialProcess()) {
ICommandControlDMContext controlDmc = DMContexts.getAncestorOfType(procCtx, ICommandControlDMContext.class);
fCommandControl.queueCommand(
fCommandFactory.createMITargetDisconnect(controlDmc),
new DataRequestMonitor<MIInfo>(ImmediateExecutor.getInstance(), rm) {
@Override
protected void handleSuccess() {
fNeedToReconnect = true;
rm.done();
}
});
return;
}
}
}
rm.done();
}
},
new Step() {
@Override
public void execute(final RequestMonitor rm) {
// Now, set the binary to be used.
if (binaryPath != null) {
fCommandControl.queueCommand(
fCommandFactory.createMIFileExecAndSymbols(fContainerDmc, binaryPath),
new DataRequestMonitor<MIInfo>(ImmediateExecutor.getInstance(), rm) {
@Override
protected void handleCompleted() {
// Because of a GDB 7.2 bug, for remote-attach sessions,
// we need to be disconnected from the target
// when we set the very first binary to be used.
// Now that we have disconnected and set the binary,
// we may need to reconnect to the target.
// If we were unable to set the binary (e.g., if the specified path
// is invalid) we also need to reconnect to the target before
// aborting the rest of the sequence.
// Bug 352998
if (fNeedToReconnect) {
fNeedToReconnect = false;
// Set the status in case it is an error, so that when rm.done() is automatically
// called, we continue to abort the sequence if we are dealing with a failure.
rm.setStatus(getStatus());
connectToTarget(procCtx, rm);
} else {
super.handleCompleted();
}
};
});
return;
}
assert fNeedToReconnect == false;
rm.done();
}
},
// Now, actually do the attach
new Step() {
@Override
public void execute(RequestMonitor rm) {
// For non-stop mode, we do a non-interrupting attach
// Bug 333284
boolean shouldInterrupt = true;
IMIRunControl runControl = getServicesTracker().getService(IMIRunControl.class);
if (runControl != null && runControl.getRunMode() == MIRunMode.NON_STOP) {
shouldInterrupt = false;
}
fCommandControl.queueCommand(
fCommandFactory.createMITargetAttach(fContainerDmc, ((IMIProcessDMContext)procCtx).getProcId(), shouldInterrupt),
new DataRequestMonitor<MIInfo>(ImmediateExecutor.getInstance(), rm));
}
},
// Start tracking this process' breakpoints.
new Step() {
@Override
public void execute(RequestMonitor rm) {
MIBreakpointsManager bpmService = getServicesTracker().getService(MIBreakpointsManager.class);
IBreakpointsTargetDMContext bpTargetDmc = DMContexts.getAncestorOfType(fContainerDmc, IBreakpointsTargetDMContext.class);
bpmService.startTrackingBreakpoints(bpTargetDmc, rm);
}
},
// Turn on reverse debugging if it was enabled as a launch option
new Step() {
@Override
public void execute(RequestMonitor rm) {
IReverseRunControl reverseService = getServicesTracker().getService(IReverseRunControl.class);
if (reverseService != null) {
ILaunch launch = (ILaunch)procCtx.getAdapter(ILaunch.class);
if (launch != null) {
try {
boolean reverseEnabled =
launch.getLaunchConfiguration().getAttribute(IGDBLaunchConfigurationConstants.ATTR_DEBUGGER_REVERSE,
IGDBLaunchConfigurationConstants.DEBUGGER_REVERSE_DEFAULT);
if (reverseEnabled) {
reverseService.enableReverseMode(fCommandControl.getContext(), true, rm);
return;
}
} catch (CoreException e) {
// Ignore, just don't set reverse
}
}
}
rm.done();
}
},
// Store the fully formed container context so it can be returned to the caller
// and mark that we are not dealing with the first process anymore.
new Step() {
@Override
public void execute(RequestMonitor rm) {
dataRm.setData(fContainerDmc);
setIsInitialProcess(false);
rm.done();
}
},
};
@Override public Step[] getSteps() { return steps; }
});
} else {
dataRm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, INTERNAL_ERROR, "Invalid process context.", null)); //$NON-NLS-1$
dataRm.done();
}
}
@SuppressWarnings("unchecked")
private void connectToTarget(IProcessDMContext procCtx, RequestMonitor rm) {
ILaunch launch = (ILaunch)procCtx.getAdapter(ILaunch.class);
assert launch != null;
if (launch != null) {
Map<String, Object> attributes = null;
try {
attributes = launch.getLaunchConfiguration().getAttributes();
} catch (CoreException e) {}
boolean isTcpConnection = CDebugUtils.getAttribute(
attributes,
IGDBLaunchConfigurationConstants.ATTR_REMOTE_TCP,
false);
if (isTcpConnection) {
String remoteTcpHost = CDebugUtils.getAttribute(
attributes,
IGDBLaunchConfigurationConstants.ATTR_HOST, INVALID);
String remoteTcpPort = CDebugUtils.getAttribute(
attributes,
IGDBLaunchConfigurationConstants.ATTR_PORT, INVALID);
fCommandControl.queueCommand(
fCommandFactory.createMITargetSelect(fCommandControl.getContext(),
remoteTcpHost, remoteTcpPort, true),
new DataRequestMonitor<MIInfo>(ImmediateExecutor.getInstance(), rm));
} else {
String serialDevice = CDebugUtils.getAttribute(
attributes,
IGDBLaunchConfigurationConstants.ATTR_DEV, INVALID);
fCommandControl.queueCommand(
fCommandFactory.createMITargetSelect(fCommandControl.getContext(),
serialDevice, true),
new DataRequestMonitor<MIInfo>(ImmediateExecutor.getInstance(), rm));
}
} else {
rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, INTERNAL_ERROR, "Cannot reconnect to target.", null)); //$NON-NLS-1$
rm.done();
}
}
@Override
public void detachDebuggerFromProcess(IDMContext dmc, final RequestMonitor rm) {
ICommandControlDMContext controlDmc = DMContexts.getAncestorOfType(dmc, ICommandControlDMContext.class);
final IMIContainerDMContext containerDmc = DMContexts.getAncestorOfType(dmc, IMIContainerDMContext.class);
if (controlDmc != null && containerDmc != null) {
if (!doCanDetachDebuggerFromProcess()) {
rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, INTERNAL_ERROR, "Detach not supported.", null)); //$NON-NLS-1$
rm.done();
return;
}
IMIRunControl runControl = getServicesTracker().getService(IMIRunControl.class);
if (runControl != null && !runControl.isTargetAcceptingCommands()) {
fBackend.interrupt();
}
fCommandControl.queueCommand(
fCommandFactory.createMITargetDetach(controlDmc, containerDmc.getGroupId()),
new DataRequestMonitor<MIInfo>(getExecutor(), rm) {
@Override
protected void handleCompleted() {
if (isSuccess()) {
// Bug in GDB 7.2 where removing an inferior will lead to a crash when running other processes.
// I'm hoping it will be fixed in 7.2.1
// fCommandControl.queueCommand(
// fCommandFactory.createMIRemoveInferior(fCommandControl.getContext(), containerDmc.getGroupId()),
// new DataRequestMonitor<MIInfo>(getExecutor(), rm));
rm.done();
} else {
// This command fails with GDB 7.2 because of a GDB bug, which was fixed with GDB 7.2.1
// In case we get here, we assume we are using GDB 7.2 (although we should not) and we work
// around it.
// Also, with GDB 7.2, removing the inferior does not work because of another bug, so we just don't do it.
fCommandControl.queueCommand(
fCommandFactory.createMITargetDetach(containerDmc),
new DataRequestMonitor<MIInfo>(getExecutor(), rm));
}
}
});
} else {
rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, INTERNAL_ERROR, "Invalid context.", null)); //$NON-NLS-1$
rm.done();
}
}
@Override
protected boolean doIsDebugNewProcessSupported() {
// Multi-process is not applicable to post-mortem sessions (core)
// or to non-attach remote sessions.
SessionType type = fBackend.getSessionType();
if (type == SessionType.CORE) {
return false;
}
if (type == SessionType.REMOTE && !fBackend.getIsAttachSession()) {
return false;
}
// We don't yet support starting a new process on a remote target
// Bug 344890
if (type == SessionType.REMOTE && fBackend.getIsAttachSession()) {
return false;
}
return true;
}
@Override
protected Sequence getDebugNewProcessSequence(DsfExecutor executor, boolean isInitial, IDMContext dmc, String file,
Map<String, Object> attributes, DataRequestMonitor<IDMContext> rm) {
return new DebugNewProcessSequence_7_2(executor, isInitial, dmc, file, attributes, rm);
}
@Override
public void getProcessesBeingDebugged(final IDMContext dmc, final DataRequestMonitor<IDMContext[]> rm) {
if (getTraceVisualization()) {
// If we are visualizing data during a live session, we should not ask GDB for the list of threads,
// because we will get the list of active threads, while GDB only cares about thread 1 for visualization.
final IMIContainerDMContext containerDmc = DMContexts.getAncestorOfType(dmc, IMIContainerDMContext.class);
if (containerDmc != null) {
IProcessDMContext procDmc = DMContexts.getAncestorOfType(containerDmc, IProcessDMContext.class);
rm.setData(new IMIExecutionDMContext[]{createExecutionContext(containerDmc,
createThreadContext(procDmc, TRACE_VISUALIZATION_THREAD_ID),
TRACE_VISUALIZATION_THREAD_ID)});
rm.done();
return;
}
}
super.getProcessesBeingDebugged(dmc, rm);
}
/**
* Creates the container context that is to be used for the new process that will
* be created by the restart operation.
* This container does not have its pid yet, while the container of the process
* that is being restarted does have its pid.
* Starting with GDB 7.2, the groupId stays the same when restarting a process, so
* we should re-use it; this is particularly important since we support multi-process
* and we need the proper groupId
*
* @since 4.0
*/
@Override
protected IMIContainerDMContext createContainerContextForRestart(String groupId) {
IProcessDMContext processDmc = createProcessContext(fCommandControl.getContext(), MIProcesses.UNKNOWN_PROCESS_ID);
return createContainerContext(processDmc, groupId);
}
@Override
public void restart(final IContainerDMContext containerDmc, Map<String, Object> attributes,
DataRequestMonitor<IContainerDMContext> rm) {
fProcRestarting.add(containerDmc);
super.restart(containerDmc, attributes, new DataRequestMonitor<IContainerDMContext>(ImmediateExecutor.getInstance(), rm) {
@Override
protected void handleCompleted() {
if (!isSuccess()) {
fProcRestarting.remove(containerDmc);
}
setData(getData());
super.handleCompleted();
}
});
}
/** @since 4.0 */
@DsfServiceEventHandler
@Override
public void eventDispatched(IExitedDMEvent e) {
IDMContext dmc = e.getDMContext();
if (dmc instanceof IContainerDMContext) {
// A process has died, we should stop tracking its breakpoints, but only if it is not restarting
if (!fProcRestarting.remove(dmc)) {
if (fBackend.getSessionType() != SessionType.CORE) {
IBreakpointsTargetDMContext bpTargetDmc = DMContexts.getAncestorOfType(dmc, IBreakpointsTargetDMContext.class);
MIBreakpointsManager bpmService = getServicesTracker().getService(MIBreakpointsManager.class);
if (bpmService != null) {
bpmService.stopTrackingBreakpoints(bpTargetDmc, new RequestMonitor(ImmediateExecutor.getInstance(), null) {
@Override
protected void handleCompleted() {
// Ok, no need to report any error because we may have already shutdown.
// We need to override handleCompleted to avoid risking having a error printout in the log
}
});
}
}
}
}
super.eventDispatched(e);
}
/**
* GDB 7.2 has a bug which causes a gdbserver crash if we set the binary after we
* have connected to the target. Because GDB 7.2.1 was not released when CDT 8.0
* was released, we need to workaround the bug in Eclipse.
*
* This method can be overridden to easily disable the workaround, for versions
* of GDB that no longer have the bug.
*
* See http://sourceware.org/ml/gdb-patches/2011-03/msg00531.html
* and Bug 352998
*
* @since 4.1
*/
protected boolean needFixForGDB72Bug352998() {
return true;
}
/**
* @since 4.1
*/
@DsfServiceEventHandler
public void eventDispatched(ITraceRecordSelectedChangedDMEvent e) {
setTraceVisualization(e.isVisualizationModeEnabled());
}
}