blob: 72dd8b38dccb2ecde8f589ce13b7d69352250e3f [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2006, 2016 Wind River Systems and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Wind River Systems - initial API and implementation
* Ericsson AB - Modified for handling of multiple threads
* Indel AG - [369622] fixed moveToLine using MinGW
* Marc Khouzam (Ericsson) - Support for operations on multiple execution contexts (bug 330974)
* Alvaro Sanchez-Leon (Ericsson AB) - Support for Step into selection (bug 244865)
* Alvaro Sanchez-Leon (Ericsson AB) - Bug 415362
* Marc Khouzam (Ericsson) - Wait for *stopped event when suspending (bug 429621)
* Xavier Raynaud (Kalray) - Bug 438635
*******************************************************************************/
package org.eclipse.cdt.dsf.gdb.service;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.eclipse.cdt.core.IAddress;
import org.eclipse.cdt.core.model.IFunctionDeclaration;
import org.eclipse.cdt.dsf.concurrent.CountingRequestMonitor;
import org.eclipse.cdt.dsf.concurrent.DataRequestMonitor;
import org.eclipse.cdt.dsf.concurrent.DsfRunnable;
import org.eclipse.cdt.dsf.concurrent.IDsfStatusConstants;
import org.eclipse.cdt.dsf.concurrent.ImmediateCountingRequestMonitor;
import org.eclipse.cdt.dsf.concurrent.ImmediateDataRequestMonitor;
import org.eclipse.cdt.dsf.concurrent.ImmediateExecutor;
import org.eclipse.cdt.dsf.concurrent.ImmediateRequestMonitor;
import org.eclipse.cdt.dsf.concurrent.Immutable;
import org.eclipse.cdt.dsf.concurrent.MultiRequestMonitor;
import org.eclipse.cdt.dsf.concurrent.RequestMonitor;
import org.eclipse.cdt.dsf.concurrent.Sequence;
import org.eclipse.cdt.dsf.concurrent.Sequence.Step;
import org.eclipse.cdt.dsf.datamodel.AbstractDMEvent;
import org.eclipse.cdt.dsf.datamodel.DMContexts;
import org.eclipse.cdt.dsf.datamodel.IDMContext;
import org.eclipse.cdt.dsf.datamodel.IDMEvent;
import org.eclipse.cdt.dsf.debug.service.IBreakpoints;
import org.eclipse.cdt.dsf.debug.service.IBreakpoints.IBreakpointDMContext;
import org.eclipse.cdt.dsf.debug.service.IBreakpoints.IBreakpointsTargetDMContext;
import org.eclipse.cdt.dsf.debug.service.IBreakpointsExtension.IBreakpointHitDMEvent;
import org.eclipse.cdt.dsf.debug.service.ICachingService;
import org.eclipse.cdt.dsf.debug.service.IMultiRunControl;
import org.eclipse.cdt.dsf.debug.service.IProcesses.IProcessDMContext;
import org.eclipse.cdt.dsf.debug.service.IProcesses.IThreadDMContext;
import org.eclipse.cdt.dsf.debug.service.IRunControl;
import org.eclipse.cdt.dsf.debug.service.IRunControl2;
import org.eclipse.cdt.dsf.debug.service.IRunControl3;
import org.eclipse.cdt.dsf.debug.service.ISourceLookup;
import org.eclipse.cdt.dsf.debug.service.ISourceLookup.ISourceLookupDMContext;
import org.eclipse.cdt.dsf.debug.service.IStack.IFrameDMContext;
import org.eclipse.cdt.dsf.debug.service.command.ICommand;
import org.eclipse.cdt.dsf.debug.service.command.ICommandControlService;
import org.eclipse.cdt.dsf.debug.service.command.ICommandControlService.ICommandControlShutdownDMEvent;
import org.eclipse.cdt.dsf.gdb.internal.GdbPlugin;
import org.eclipse.cdt.dsf.gdb.internal.service.command.events.MITracepointSelectedEvent;
import org.eclipse.cdt.dsf.gdb.internal.service.control.StepIntoSelectionActiveOperation;
import org.eclipse.cdt.dsf.gdb.internal.service.control.StepIntoSelectionUtils;
import org.eclipse.cdt.dsf.mi.service.IMIBreakpointPathAdjuster;
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.IMIProcesses;
import org.eclipse.cdt.dsf.mi.service.IMIRunControl;
import org.eclipse.cdt.dsf.mi.service.MIBreakpointDMData;
import org.eclipse.cdt.dsf.mi.service.MIBreakpoints;
import org.eclipse.cdt.dsf.mi.service.MIBreakpoints.MIBreakpointDMContext;
import org.eclipse.cdt.dsf.mi.service.MIRunControl;
import org.eclipse.cdt.dsf.mi.service.MIStack;
import org.eclipse.cdt.dsf.mi.service.command.CommandFactory;
import org.eclipse.cdt.dsf.mi.service.command.events.IMIDMEvent;
import org.eclipse.cdt.dsf.mi.service.command.events.MIBreakpointHitEvent;
import org.eclipse.cdt.dsf.mi.service.command.events.MICatchpointHitEvent;
import org.eclipse.cdt.dsf.mi.service.command.events.MIErrorEvent;
import org.eclipse.cdt.dsf.mi.service.command.events.MIEvent;
import org.eclipse.cdt.dsf.mi.service.command.events.MIFunctionFinishedEvent;
import org.eclipse.cdt.dsf.mi.service.command.events.MIInferiorExitEvent;
import org.eclipse.cdt.dsf.mi.service.command.events.MIRunningEvent;
import org.eclipse.cdt.dsf.mi.service.command.events.MISharedLibEvent;
import org.eclipse.cdt.dsf.mi.service.command.events.MISignalEvent;
import org.eclipse.cdt.dsf.mi.service.command.events.MISteppingRangeEvent;
import org.eclipse.cdt.dsf.mi.service.command.events.MIStoppedEvent;
import org.eclipse.cdt.dsf.mi.service.command.events.MIThreadCreatedEvent;
import org.eclipse.cdt.dsf.mi.service.command.events.MIThreadExitEvent;
import org.eclipse.cdt.dsf.mi.service.command.events.MIWatchpointTriggerEvent;
import org.eclipse.cdt.dsf.mi.service.command.output.MIBreakInsertInfo;
import org.eclipse.cdt.dsf.mi.service.command.output.MIFrame;
import org.eclipse.cdt.dsf.mi.service.command.output.MIInfo;
import org.eclipse.cdt.dsf.mi.service.command.output.MIStackInfoDepthInfo;
import org.eclipse.cdt.dsf.mi.service.command.output.MIThread;
import org.eclipse.cdt.dsf.mi.service.command.output.MIThreadInfoInfo;
import org.eclipse.cdt.dsf.service.AbstractDsfService;
import org.eclipse.cdt.dsf.service.DsfServiceEventHandler;
import org.eclipse.cdt.dsf.service.DsfSession;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.debug.core.DebugException;
import org.osgi.framework.BundleContext;
/**
* Implementation note: This class implements event handlers for the events that
* are generated by this service itself. When the event is dispatched, these
* handlers will be called first, before any of the clients. These handlers
* update the service's internal state information to make them consistent with
* the events being issued. Doing this in the handlers as opposed to when the
* events are generated, guarantees that the state of the service will always be
* consistent with the events. The purpose of this pattern is to allow clients
* that listen to service events and track service state, to be perfectly in
* sync with the service state.
* @since 1.1
*/
public class GDBRunControl_7_0_NS extends AbstractDsfService
implements IMIRunControl, IMultiRunControl, ICachingService, IRunControl3 {
// /////////////////////////////////////////////////////////////////////////
// CONSTANTS
// /////////////////////////////////////////////////////////////////////////
@Immutable
private static class ExecutionData implements IExecutionDMData2 {
private final StateChangeReason fReason;
private final String fDetails;
ExecutionData(StateChangeReason reason, String details) {
fReason = reason;
fDetails = details;
}
@Override
public StateChangeReason getStateChangeReason() {
return fReason;
}
@Override
public String getDetails() {
return fDetails;
}
}
/**
* Base class for events generated by the MI Run Control service. Most events
* generated by the MI Run Control service are directly caused by some MI event.
* Other services may need access to the extended MI data carried in the event.
*
* @param <V> DMC that this event refers to
* @param <T> MIInfo object that is the direct cause of this event
* @see MIRunControl
*/
@Immutable
private static class RunControlEvent<V extends IDMContext, T extends MIEvent<? extends IDMContext>>
extends AbstractDMEvent<V> implements IMIDMEvent {
final private T fMIInfo;
public RunControlEvent(V dmc, T miInfo) {
super(dmc);
fMIInfo = miInfo;
}
@Override
public T getMIEvent() {
return fMIInfo;
}
}
/**
* Indicates that the given thread has been suspended.
* @since 4.0
*/
@Immutable
protected static class SuspendedEvent extends RunControlEvent<IExecutionDMContext, MIStoppedEvent>
implements ISuspendedDMEvent {
SuspendedEvent(IExecutionDMContext ctx, MIStoppedEvent miInfo) {
super(ctx, miInfo);
}
@Override
public StateChangeReason getReason() {
if (getMIEvent() instanceof MICatchpointHitEvent) { // must precede MIBreakpointHitEvent
return StateChangeReason.EVENT_BREAKPOINT;
} else if (getMIEvent() instanceof MITracepointSelectedEvent) { // must precede MIBreakpointHitEvent
return StateChangeReason.UNKNOWN; // Don't display anything here, the details will take care of it
} else if (getMIEvent() instanceof MIBreakpointHitEvent) {
return StateChangeReason.BREAKPOINT;
} else if (getMIEvent() instanceof MISteppingRangeEvent) {
return StateChangeReason.STEP;
} else if (getMIEvent() instanceof MIFunctionFinishedEvent) {
return StateChangeReason.STEP;
} else if (getMIEvent() instanceof MISharedLibEvent) {
return StateChangeReason.SHAREDLIB;
} else if (getMIEvent() instanceof MISignalEvent) {
return StateChangeReason.SIGNAL;
} else if (getMIEvent() instanceof MIWatchpointTriggerEvent) {
return StateChangeReason.WATCHPOINT;
} else if (getMIEvent() instanceof MIErrorEvent) {
return StateChangeReason.ERROR;
} else {
return StateChangeReason.USER_REQUEST;
}
}
public String getDetails() {
MIStoppedEvent event = getMIEvent();
if (event instanceof MICatchpointHitEvent) { // must precede MIBreakpointHitEvent
return ((MICatchpointHitEvent) event).getReason();
} else if (event instanceof MITracepointSelectedEvent) { // must precede MIBreakpointHitEvent
return ((MITracepointSelectedEvent) event).getReason();
} else if (event instanceof MISharedLibEvent) {
return ((MISharedLibEvent) event).getLibrary();
} else if (event instanceof MISignalEvent) {
return ((MISignalEvent) event).getName() + ':' + ((MISignalEvent) event).getMeaning();
} else if (event instanceof MIWatchpointTriggerEvent) {
return ((MIWatchpointTriggerEvent) event).getExpression();
} else if (event instanceof MIErrorEvent) {
return ((MIErrorEvent) event).getMessage();
}
return null;
}
}
/**
* Indicates that the given thread has been suspended on a breakpoint.
* @since 4.0
*/
@Immutable
protected static class BreakpointHitEvent extends SuspendedEvent implements IBreakpointHitDMEvent {
final private IBreakpointDMContext[] fBreakpoints;
BreakpointHitEvent(IExecutionDMContext ctx, MIBreakpointHitEvent miInfo, IBreakpointDMContext bpCtx) {
super(ctx, miInfo);
fBreakpoints = new IBreakpointDMContext[] { bpCtx };
}
@Override
public IBreakpointDMContext[] getBreakpoints() {
return fBreakpoints;
}
}
/**
* @since 4.0
*/
@Immutable
protected static class ResumedEvent extends RunControlEvent<IExecutionDMContext, MIRunningEvent>
implements IResumedDMEvent {
ResumedEvent(IExecutionDMContext ctx, MIRunningEvent miInfo) {
super(ctx, miInfo);
}
@Override
public StateChangeReason getReason() {
if (getMIEvent() != null) {
switch (getMIEvent().getType()) {
case MIRunningEvent.CONTINUE:
return StateChangeReason.USER_REQUEST;
case MIRunningEvent.NEXT:
case MIRunningEvent.NEXTI:
return StateChangeReason.STEP;
case MIRunningEvent.STEP:
case MIRunningEvent.STEPI:
return StateChangeReason.STEP;
case MIRunningEvent.FINISH:
return StateChangeReason.STEP;
case MIRunningEvent.UNTIL:
case MIRunningEvent.RETURN:
break;
}
}
return StateChangeReason.UNKNOWN;
}
}
/**
* @since 4.0
*/
@Immutable
protected static class StartedDMEvent extends RunControlEvent<IExecutionDMContext, MIThreadCreatedEvent>
implements IStartedDMEvent {
StartedDMEvent(IMIExecutionDMContext executionDmc, MIThreadCreatedEvent miInfo) {
super(executionDmc, miInfo);
}
}
/**
* @since 4.0
*/
@Immutable
protected static class ExitedDMEvent extends RunControlEvent<IExecutionDMContext, MIThreadExitEvent>
implements IExitedDMEvent {
ExitedDMEvent(IMIExecutionDMContext executionDmc, MIThreadExitEvent miInfo) {
super(executionDmc, miInfo);
}
}
protected class MIThreadRunState {
// State flags
boolean fSuspended = false;
boolean fResumePending = false;
boolean fStepping = false;
RunControlEvent<IExecutionDMContext, ?> fLatestEvent = null;
/**
* What caused the state change. E.g., a signal was thrown.
*/
StateChangeReason fStateChangeReason;
/**
* Further detail on what caused the state change. E.g., the specific signal
* that was throw was a SIGINT. The exact string comes from gdb in the mi
* event. May be null, as not all types of state change have additional
* detail of interest.
*/
String fStateChangeDetails;
}
/**
* @since 4.0
*/
protected static class RunToLineActiveOperation {
private IMIExecutionDMContext fThreadContext;
private String fBpId;
private String fFileLocation;
private String fAddrLocation;
private boolean fSkipBreakpoints;
/** @since 5.0 */
public RunToLineActiveOperation(IMIExecutionDMContext threadContext, String bpId, String fileLoc, String addr,
boolean skipBreakpoints) {
fThreadContext = threadContext;
fBpId = bpId;
fFileLocation = fileLoc;
fAddrLocation = addr;
fSkipBreakpoints = skipBreakpoints;
}
public IMIExecutionDMContext getThreadContext() {
return fThreadContext;
}
/** @since 5.0 */
public String getBreakpointId() {
return fBpId;
}
public String getFileLocation() {
return fFileLocation;
}
public String getAddrLocation() {
return fAddrLocation;
}
public boolean shouldSkipBreakpoints() {
return fSkipBreakpoints;
}
}
// /////////////////////////////////////////////////////////////////////////
// MIRunControlNS
///////////////////////////////////////////////////////////////////////////
private ICommandControlService fConnection;
private CommandFactory fCommandFactory;
private IGDBProcesses fProcessService;
private boolean fTerminated = false;
// ThreadStates indexed by the execution context
protected Map<IMIExecutionDMContext, MIThreadRunState> fThreadRunStates = new HashMap<>();
private RunToLineActiveOperation fRunToLineActiveOperation = null;
private StepIntoSelectionActiveOperation fStepInToSelectionActiveOperation = null;
/** @since 4.0 */
protected RunToLineActiveOperation getRunToLineActiveOperation() {
return fRunToLineActiveOperation;
}
/** @since 4.0 */
protected void setRunToLineActiveOperation(RunToLineActiveOperation operation) {
fRunToLineActiveOperation = operation;
}
/**
* Set of threads for which the next MIRunning event should be silenced.
*/
private Set<IMIExecutionDMContext> fDisableNextRunningEventDmcSet = new HashSet<>();
/**
* Set of threads for which the next MISignal (MIStopped) event should be silenced.
*/
private Set<IMIExecutionDMContext> fDisableNextSignalEventDmcSet = new HashSet<>();
/**
* Map that stores the silenced MIStopped event for the specified thread, in case we need to use it for a failure.
*/
private Map<IMIExecutionDMContext, MIStoppedEvent> fSilencedSignalEventMap = new HashMap<>();
/**
* This variable allows us to know if run control operation
* should be enabled or disabled. Run control operations are
* always enabled except when visualizing tracepoints.
*/
private boolean fRunControlOperationsEnabled = true;
///////////////////////////////////////////////////////////////////////////
// Initialization and shutdown
///////////////////////////////////////////////////////////////////////////
public GDBRunControl_7_0_NS(DsfSession session) {
super(session);
}
@Override
public void initialize(final RequestMonitor rm) {
super.initialize(new ImmediateRequestMonitor(rm) {
@Override
protected void handleSuccess() {
doInitialize(rm);
}
});
}
private void doInitialize(final RequestMonitor rm) {
register(
new String[] { IRunControl.class.getName(), IRunControl2.class.getName(), IMIRunControl.class.getName(),
IMultiRunControl.class.getName(), IRunControl3.class.getName() },
new Hashtable<String, String>());
fConnection = getServicesTracker().getService(ICommandControlService.class);
fCommandFactory = getServicesTracker().getService(IMICommandControl.class).getCommandFactory();
fProcessService = getServicesTracker().getService(IGDBProcesses.class);
getSession().addServiceEventListener(this, null);
rm.done();
}
@Override
public void shutdown(final RequestMonitor rm) {
unregister();
getSession().removeServiceEventListener(this);
super.shutdown(rm);
}
/** @since 4.1 */
protected boolean getRunControlOperationsEnabled() {
return fRunControlOperationsEnabled;
}
/** @since 4.1 */
protected void setRunControlOperationsEnabled(boolean runControlEnabled) {
fRunControlOperationsEnabled = runControlEnabled;
}
///////////////////////////////////////////////////////////////////////////
// AbstractDsfService
///////////////////////////////////////////////////////////////////////////
@Override
protected BundleContext getBundleContext() {
return GdbPlugin.getBundleContext();
}
///////////////////////////////////////////////////////////////////////////
// IRunControl
///////////////////////////////////////////////////////////////////////////
// ------------------------------------------------------------------------
// Suspend
// ------------------------------------------------------------------------
@Override
public boolean isSuspended(IExecutionDMContext context) {
// Thread case
if (context instanceof IMIExecutionDMContext) {
MIThreadRunState threadState = fThreadRunStates.get(context);
return (threadState == null) ? false : !fTerminated && threadState.fSuspended;
}
// Process case. The process is considered suspended as long
// as one of its thread is suspended
if (context instanceof IMIContainerDMContext) {
boolean hasThread = false;
for (IMIExecutionDMContext threadContext : fThreadRunStates.keySet()) {
if (DMContexts.isAncestorOf(threadContext, context)) {
hasThread = true;
if (isSuspended(threadContext))
return true;
}
}
// If this container does not have any threads, it means it wasn't started
// yet or it was terminated, so we can consider it suspended
if (hasThread == false)
return true;
}
// Default case
return false;
}
@Override
public void canSuspend(IExecutionDMContext context, DataRequestMonitor<Boolean> rm) {
if (fRunControlOperationsEnabled == false) {
rm.done(false);
return;
}
rm.done(doCanSuspend(context));
}
/**
* @since 4.5
*/
protected boolean doCanSuspend(IExecutionDMContext context) {
// Thread case
if (context instanceof IMIExecutionDMContext) {
MIThreadRunState threadState = fThreadRunStates.get(context);
return (threadState == null) ? false : !fTerminated && !threadState.fSuspended;
}
// Process case
if (context instanceof IMIContainerDMContext) {
for (IMIExecutionDMContext threadContext : fThreadRunStates.keySet()) {
if (DMContexts.isAncestorOf(threadContext, context)) {
if (doCanSuspend(threadContext)) {
return true;
}
}
}
return false;
}
// Default case
return false;
}
@Override
public void suspend(IExecutionDMContext context, final RequestMonitor rm) {
assert context != null;
// Thread case
IMIExecutionDMContext thread = DMContexts.getAncestorOfType(context, IMIExecutionDMContext.class);
if (thread != null) {
doSuspend(thread, rm);
return;
}
// Process case
IMIContainerDMContext container = DMContexts.getAncestorOfType(context, IMIContainerDMContext.class);
if (container != null) {
doSuspend(container, rm);
return;
}
// Default case
rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, NOT_SUPPORTED, "Invalid context type.", null)); //$NON-NLS-1$
rm.done();
}
/**
* Request the suspend for a single thread and wait for a proper *stopped event before
* indicating success.
*/
private void doSuspend(final IMIExecutionDMContext context, final RequestMonitor rm) {
if (!doCanSuspend(context)) {
rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, NOT_SUPPORTED,
"Given context: " + context + ", is already suspended.", null)); //$NON-NLS-1$ //$NON-NLS-2$
rm.done();
return;
}
// Start the job before sending the interrupt command
// to make sure we don't miss the *stopped event
final MonitorSuspendJob monitorJob = new MonitorSuspendJob(context, 0, rm);
fConnection.queueCommand(fCommandFactory.createMIExecInterrupt(context),
new ImmediateDataRequestMonitor<MIInfo>() {
@Override
protected void handleSuccess() {
// Nothing to do in the case of success, the monitoring job
// will take care of completing the RM once it gets the
// *stopped event.
}
@Override
protected void handleFailure() {
// In case of failure, we must cancel the monitoring job
// and indicate the failure in the rm.
monitorJob.cleanAndCancel();
rm.done(getStatus());
}
});
}
/**
* Request the suspend for a process. In this case we don't wait for any *stopped events explicitly
* because we would need to wait for one per thread and manage all those events. It is not necessary.
* @since 4.5
*/
protected void doSuspend(IMIContainerDMContext context, final RequestMonitor rm) {
if (!doCanSuspend(context)) {
rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, NOT_SUPPORTED,
"Given context: " + context + ", is already suspended.", null)); //$NON-NLS-1$ //$NON-NLS-2$
rm.done();
return;
}
String groupId = context.getGroupId();
fConnection.queueCommand(fCommandFactory.createMIExecInterrupt(context, groupId),
new DataRequestMonitor<MIInfo>(getExecutor(), rm));
}
/**
* Job that waits for a *stopped event after a suspend operation on a thread.
*
* If the suspend operation receives its corresponding *stopped event in time,
* the job will mark the RM with a success status. If the event is not received
* before the timeout, the job will fail the request monitor.
*
* @since 4.5
*/
protected class MonitorSuspendJob extends Job {
// Bug 310274. Until we have a preference to configure timeouts,
// we need a large enough default timeout to accommodate slow
// remote sessions.
private final static int TIMEOUT_DEFAULT_VALUE = 5000;
private final RequestMonitor fRequestMonitor;
private final IMIExecutionDMContext fThread;
public MonitorSuspendJob(IMIExecutionDMContext dmc, int timeout, RequestMonitor rm) {
super("Suspend monitor job."); //$NON-NLS-1$
setSystem(true);
fThread = dmc;
fRequestMonitor = rm;
if (timeout <= 0) {
timeout = TIMEOUT_DEFAULT_VALUE; // default of 5 seconds
}
// Register to listen for the stopped event
getSession().addServiceEventListener(this, null);
schedule(timeout);
}
/**
* Cleanup job and cancel it.
* This method is required because super.canceling() is only called
* if the job is actually running.
*/
public boolean cleanAndCancel() {
if (getExecutor().isInExecutorThread()) {
getSession().removeServiceEventListener(this);
} else {
getExecutor().submit(new DsfRunnable() {
@Override
public void run() {
getSession().removeServiceEventListener(MonitorSuspendJob.this);
}
});
}
return cancel();
}
@DsfServiceEventHandler
public void eventDispatched(MIStoppedEvent e) {
if (fThread.equals(e.getDMContext())) {
// The thread we were waiting for did stop
if (cleanAndCancel()) {
fRequestMonitor.done();
}
}
}
@Override
protected IStatus run(IProgressMonitor monitor) {
// This will be called when the timeout is hit and no *stopped event was received
getExecutor().submit(new DsfRunnable() {
@Override
public void run() {
getSession().removeServiceEventListener(MonitorSuspendJob.this);
fRequestMonitor.done(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID,
IDsfStatusConstants.REQUEST_FAILED, "Suspend operation timeout.", null)); //$NON-NLS-1$
}
});
return Status.OK_STATUS;
}
}
// ------------------------------------------------------------------------
// Resume
// ------------------------------------------------------------------------
@Override
public void canResume(IExecutionDMContext context, DataRequestMonitor<Boolean> rm) {
if (fRunControlOperationsEnabled == false) {
rm.done(false);
return;
}
rm.done(doCanResume(context));
}
/** @since 5.0 */
protected boolean doCanResume(IExecutionDMContext context) {
// Thread case
if (context instanceof IMIExecutionDMContext) {
MIThreadRunState threadState = fThreadRunStates.get(context);
return (threadState == null) ? false
: !fTerminated && threadState.fSuspended && !threadState.fResumePending;
}
// Process case
if (context instanceof IMIContainerDMContext) {
for (IMIExecutionDMContext threadContext : fThreadRunStates.keySet()) {
if (DMContexts.isAncestorOf(threadContext, context)) {
if (doCanResume(threadContext)) {
return true;
}
}
}
return false;
}
// Default case
return false;
}
@Override
public void resume(IExecutionDMContext context, final RequestMonitor rm) {
assert context != null;
// Thread case
IMIExecutionDMContext thread = DMContexts.getAncestorOfType(context, IMIExecutionDMContext.class);
if (thread != null) {
doResume(thread, rm);
return;
}
// Container case
IMIContainerDMContext container = DMContexts.getAncestorOfType(context, IMIContainerDMContext.class);
if (container != null) {
doResume(container, rm);
return;
}
// Default case
rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, NOT_SUPPORTED, "Invalid context type.", null)); //$NON-NLS-1$
rm.done();
}
private void doResume(IMIExecutionDMContext context, final RequestMonitor rm) {
if (!doCanResume(context)) {
rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, INVALID_STATE,
"Given context: " + context + ", is already running.", null)); //$NON-NLS-1$ //$NON-NLS-2$
rm.done();
return;
}
final MIThreadRunState threadState = fThreadRunStates.get(context);
if (threadState == null) {
rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, INVALID_STATE,
"Given context: " + context + " can't be found.", null)); //$NON-NLS-1$ //$NON-NLS-2$
rm.done();
return;
}
threadState.fResumePending = true;
fConnection.queueCommand(fCommandFactory.createMIExecContinue(context),
new DataRequestMonitor<MIInfo>(getExecutor(), rm) {
@Override
protected void handleFailure() {
threadState.fResumePending = false;
super.handleFailure();
}
});
}
/** @since 5.0 */
protected void doResume(IMIContainerDMContext context, final RequestMonitor rm) {
if (!doCanResume(context)) {
rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, INVALID_STATE,
"Given context: " + context + ", is already running.", null)); //$NON-NLS-1$ //$NON-NLS-2$
rm.done();
return;
}
String groupId = context.getGroupId();
fConnection.queueCommand(fCommandFactory.createMIExecContinue(context, groupId),
new DataRequestMonitor<MIInfo>(getExecutor(), rm));
}
// ------------------------------------------------------------------------
// Step
// ------------------------------------------------------------------------
@Override
public boolean isStepping(IExecutionDMContext context) {
// If it's a thread, just look it up
if (context instanceof IMIExecutionDMContext) {
MIThreadRunState threadState = fThreadRunStates.get(context);
return (threadState == null) ? false : !fTerminated && threadState.fStepping;
}
// Default case
return false;
}
@Override
public void canStep(final IExecutionDMContext context, StepType stepType, final DataRequestMonitor<Boolean> rm) {
if (fRunControlOperationsEnabled == false) {
rm.done(false);
return;
}
// If it's a thread, just look it up
if (context instanceof IMIExecutionDMContext) {
if (stepType == StepType.STEP_RETURN) {
// A step return will always be done in the top stack frame.
// If the top stack frame is the only stack frame, it does not make sense
// to do a step return since GDB will reject it.
MIStack stackService = getServicesTracker().getService(MIStack.class);
if (stackService != null) {
// Check that the stack is at least two deep.
stackService.getStackDepth(context, 2, new DataRequestMonitor<Integer>(getExecutor(), rm) {
@Override
public void handleCompleted() {
if (isSuccess() && getData() == 1) {
rm.done(false);
} else {
canResume(context, rm);
}
}
});
return;
}
}
canResume(context, rm);
return;
}
// If it's a container, then we don't want to step it
rm.done(false);
}
@Override
public void step(IExecutionDMContext context, StepType stepType, final RequestMonitor rm) {
step(context, stepType, true, rm);
}
private void step(IExecutionDMContext context, StepType stepType, boolean checkCanResume, final RequestMonitor rm) {
assert context != null;
IMIExecutionDMContext dmc = DMContexts.getAncestorOfType(context, IMIExecutionDMContext.class);
if (dmc == null) {
rm.done(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, INTERNAL_ERROR,
"Given context: " + context + " is not an MI execution context.", null)); //$NON-NLS-1$ //$NON-NLS-2$
return;
}
if (checkCanResume && !doCanResume(dmc)) {
rm.done(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, INVALID_STATE, "Cannot resume context", null)); //$NON-NLS-1$
return;
}
final MIThreadRunState threadState = fThreadRunStates.get(context);
if (threadState == null) {
rm.done(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, INVALID_STATE,
"Given context: " + context + " can't be found.", null)); //$NON-NLS-1$ //$NON-NLS-2$
return;
}
ICommand<MIInfo> cmd = null;
switch (stepType) {
case STEP_INTO:
cmd = fCommandFactory.createMIExecStep(dmc);
break;
case STEP_OVER:
cmd = fCommandFactory.createMIExecNext(dmc);
break;
case STEP_RETURN:
// The -exec-finish command operates on the selected stack frame, but here we always
// want it to operate on the stop stack frame. So we manually create a top-frame
// context to use with the MI command.
// We get a local instance of the stack service because the stack service can be shut
// down before the run control service is shut down. So it is possible for the
// getService() request below to return null.
MIStack stackService = getServicesTracker().getService(MIStack.class);
if (stackService != null) {
IFrameDMContext topFrameDmc = stackService.createFrameDMContext(dmc, 0);
cmd = fCommandFactory.createMIExecFinish(topFrameDmc);
} else {
rm.done(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, NOT_SUPPORTED,
"Cannot create context for command, stack service not available.", null)); //$NON-NLS-1$
return;
}
break;
case INSTRUCTION_STEP_INTO:
cmd = fCommandFactory.createMIExecStepInstruction(dmc);
break;
case INSTRUCTION_STEP_OVER:
cmd = fCommandFactory.createMIExecNextInstruction(dmc);
break;
default:
rm.done(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, INTERNAL_ERROR, "Given step type not supported", //$NON-NLS-1$
null));
return;
}
threadState.fResumePending = true;
threadState.fStepping = true;
fConnection.queueCommand(cmd, new DataRequestMonitor<MIInfo>(getExecutor(), rm) {
@Override
public void handleFailure() {
threadState.fResumePending = false;
threadState.fStepping = false;
super.handleFailure();
}
});
}
// ------------------------------------------------------------------------
// Run to line
// ------------------------------------------------------------------------
private void runToLocation(final IExecutionDMContext context, final String location, final boolean skipBreakpoints,
final RequestMonitor rm) {
assert context != null;
final IMIExecutionDMContext dmc = DMContexts.getAncestorOfType(context, IMIExecutionDMContext.class);
if (dmc == null) {
rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, INTERNAL_ERROR,
"Given context: " + context + " is not an MI execution context.", null)); //$NON-NLS-1$ //$NON-NLS-2$
rm.done();
return;
}
if (!doCanResume(dmc)) {
rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, INVALID_STATE, "Cannot resume context", null)); //$NON-NLS-1$
rm.done();
return;
}
MIThreadRunState threadState = fThreadRunStates.get(dmc);
if (threadState == null) {
rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, INVALID_STATE,
"Given context: " + context + " can't be found.", null)); //$NON-NLS-1$ //$NON-NLS-2$
rm.done();
return;
}
IBreakpointsTargetDMContext bpDmc = DMContexts.getAncestorOfType(context, IBreakpointsTargetDMContext.class);
fConnection.queueCommand(
fCommandFactory.createMIBreakInsert(bpDmc, true, false, null, 0, location, dmc.getThreadId()),
new DataRequestMonitor<MIBreakInsertInfo>(getExecutor(), rm) {
@Override
public void handleSuccess() {
// We must set are RunToLineActiveOperation *before* we do the resume
// or else we may get the stopped event, before we have set this variable.
String bpId = getData().getMIBreakpoints()[0].getNumber();
String addr = getData().getMIBreakpoints()[0].getAddress();
fRunToLineActiveOperation = new RunToLineActiveOperation(dmc, bpId, location, addr,
skipBreakpoints);
resume(dmc, new RequestMonitor(getExecutor(), rm) {
@Override
public void handleFailure() {
IBreakpointsTargetDMContext bpDmc = DMContexts.getAncestorOfType(
fRunToLineActiveOperation.getThreadContext(),
IBreakpointsTargetDMContext.class);
String bpId = fRunToLineActiveOperation.getBreakpointId();
fConnection.queueCommand(
fCommandFactory.createMIBreakDelete(bpDmc, new String[] { bpId }),
new DataRequestMonitor<MIInfo>(getExecutor(), null));
fRunToLineActiveOperation = null;
fStepInToSelectionActiveOperation = null;
super.handleFailure();
}
});
}
});
}
// ------------------------------------------------------------------------
// Step into Selection
// ------------------------------------------------------------------------
private void stepIntoSelection(final IExecutionDMContext context, final int baseLine, final String baseLineLocation,
final boolean skipBreakpoints, final IFunctionDeclaration targetFunction, final RequestMonitor rm) {
assert context != null;
final IMIExecutionDMContext dmc = DMContexts.getAncestorOfType(context, IMIExecutionDMContext.class);
if (dmc == null) {
rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, INTERNAL_ERROR,
"Given context: " + context + " is not an MI execution context.", null)); //$NON-NLS-1$ //$NON-NLS-2$
rm.done();
return;
}
if (!doCanResume(dmc)) {
rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, INVALID_STATE, "Cannot resume context", null)); //$NON-NLS-1$
rm.done();
return;
}
MIThreadRunState threadState = fThreadRunStates.get(dmc);
if (threadState == null) {
rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, INVALID_STATE,
"Given context: " + context + " can't be found.", null)); //$NON-NLS-1$ //$NON-NLS-2$
rm.done();
return;
}
if (threadState.fLatestEvent == null || !(threadState.fLatestEvent instanceof SuspendedEvent)) {
rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, INVALID_STATE,
"Given context: " + context + " invalid suspended event.", null)); //$NON-NLS-1$ //$NON-NLS-2$
rm.done();
return;
}
SuspendedEvent suspendedEvent = (SuspendedEvent) threadState.fLatestEvent;
final MIFrame currentFrame = suspendedEvent.getMIEvent().getFrame();
if (currentFrame == null) {
rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, INVALID_STATE,
"Given event: " + suspendedEvent + " invalid frame in suspended event.", null)); //$NON-NLS-1$ //$NON-NLS-2$
rm.done();
return;
}
getStackDepth(dmc, new DataRequestMonitor<Integer>(getExecutor(), rm) {
@Override
public void handleSuccess() {
if (getData() != null) {
final int framesSize = getData().intValue();
// make sure the operation is removed upon
// failure detection
final RequestMonitor rms = new RequestMonitor(getExecutor(), rm) {
@Override
protected void handleFailure() {
fStepInToSelectionActiveOperation = null;
super.handleFailure();
}
};
if ((currentFrame.getFile() + ":" + currentFrame.getLine()).endsWith(baseLineLocation)) { //$NON-NLS-1$
// Save the step into selection information
fStepInToSelectionActiveOperation = new StepIntoSelectionActiveOperation(dmc, baseLine,
targetFunction, framesSize, currentFrame);
// Ready to step into a function selected
// within a current line
step(dmc, StepType.STEP_INTO, rms);
} else {
// Save the step into selection information
fStepInToSelectionActiveOperation = new StepIntoSelectionActiveOperation(dmc, baseLine,
targetFunction, framesSize, null);
// Pointing to a line different than the current line
// Needs to RunToLine before stepping to the selection
runToLocation(dmc, baseLineLocation, skipBreakpoints, rms);
}
} else {
rm.done();
}
}
});
}
// ------------------------------------------------------------------------
// Resume at location
// ------------------------------------------------------------------------
private void resumeAtLocation(IExecutionDMContext context, String location, RequestMonitor rm) {
assert context != null;
final IMIExecutionDMContext dmc = DMContexts.getAncestorOfType(context, IMIExecutionDMContext.class);
if (dmc == null) {
rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, INTERNAL_ERROR,
"Given context: " + context + " is not an MI execution context.", null)); //$NON-NLS-1$ //$NON-NLS-2$
rm.done();
return;
}
if (!doCanResume(dmc)) {
rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, INVALID_STATE, "Cannot resume context", null)); //$NON-NLS-1$
rm.done();
return;
}
final MIThreadRunState threadState = fThreadRunStates.get(dmc);
if (threadState == null) {
rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, INVALID_STATE,
"Given context: " + context + " can't be found.", null)); //$NON-NLS-1$ //$NON-NLS-2$
rm.done();
return;
}
threadState.fResumePending = true;
fConnection.queueCommand(fCommandFactory.createMIExecJump(dmc, location),
new DataRequestMonitor<MIInfo>(getExecutor(), rm) {
@Override
protected void handleFailure() {
threadState.fResumePending = false;
super.handleFailure();
}
});
}
// ------------------------------------------------------------------------
// Support functions
// ------------------------------------------------------------------------
@Override
public void getExecutionContexts(final IContainerDMContext containerDmc,
final DataRequestMonitor<IExecutionDMContext[]> rm) {
IMIProcesses procService = getServicesTracker().getService(IMIProcesses.class);
procService.getProcessesBeingDebugged(containerDmc, new DataRequestMonitor<IDMContext[]>(getExecutor(), rm) {
@Override
protected void handleSuccess() {
if (getData() instanceof IExecutionDMContext[]) {
rm.setData((IExecutionDMContext[]) getData());
} else {
rm.setStatus(
new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, INTERNAL_ERROR, "Invalid contexts", null)); //$NON-NLS-1$
}
rm.done();
}
});
}
@Override
public void getExecutionData(IExecutionDMContext dmc, DataRequestMonitor<IExecutionDMData> rm) {
MIThreadRunState threadState = fThreadRunStates.get(dmc);
if (threadState == null) {
rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, INVALID_HANDLE,
"Given context: " + dmc + " is not a recognized execution context.", null)); //$NON-NLS-1$ //$NON-NLS-2$
rm.done();
return;
}
if (dmc instanceof IMIExecutionDMContext) {
rm.setData(new ExecutionData(threadState.fSuspended ? threadState.fStateChangeReason : null,
threadState.fStateChangeDetails));
} else {
rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, INVALID_HANDLE,
"Given context: " + dmc + " is not a recognized execution context.", null)); //$NON-NLS-1$ //$NON-NLS-2$
}
rm.done();
}
private IMIExecutionDMContext createMIExecutionContext(IContainerDMContext container, String threadId) {
IMIProcesses procService = getServicesTracker().getService(IMIProcesses.class);
IProcessDMContext procDmc = DMContexts.getAncestorOfType(container, IProcessDMContext.class);
IThreadDMContext threadDmc = null;
if (procDmc != null) {
// For now, reuse the threadId as the OSThreadId
threadDmc = procService.createThreadContext(procDmc, threadId);
}
return procService.createExecutionContext(container, threadDmc, threadId);
}
private void updateThreadState(IMIExecutionDMContext context, ResumedEvent event) {
StateChangeReason reason = event.getReason();
boolean isStepping = reason.equals(StateChangeReason.STEP);
MIThreadRunState threadState = fThreadRunStates.get(context);
if (threadState == null) {
threadState = new MIThreadRunState();
fThreadRunStates.put(context, threadState);
}
threadState.fSuspended = false;
threadState.fResumePending = false;
threadState.fStateChangeReason = reason;
threadState.fStateChangeDetails = null; // we have no details of interest for a resume
threadState.fStepping = isStepping;
threadState.fLatestEvent = event;
}
private void updateThreadState(IMIExecutionDMContext context, SuspendedEvent event) {
StateChangeReason reason = event.getReason();
MIThreadRunState threadState = fThreadRunStates.get(context);
if (threadState == null) {
threadState = new MIThreadRunState();
fThreadRunStates.put(context, threadState);
}
threadState.fSuspended = true;
threadState.fResumePending = false;
threadState.fStepping = false;
threadState.fStateChangeReason = reason;
threadState.fStateChangeDetails = event.getDetails();
threadState.fLatestEvent = event;
}
/* ******************************************************************************
* Section to support making operations even when the target is unavailable.
*
* Although one would expect to be able to make commands all the time when
* in non-stop mode, it turns out that GDB has trouble with some commands
* like breakpoints. The safe way to do it is to make sure we have at least
* one thread suspended.
*
* Basically, we must make sure one thread is suspended before making
* certain operations (currently breakpoints). If that is not the case, we must
* first suspend one thread, then perform the specified operations,
* and finally resume that thread..
* See https://bugs.eclipse.org/bugs/show_bug.cgi?id=242943
* and https://bugs.eclipse.org/bugs/show_bug.cgi?id=282273
*
* Note that for multi-process, we need to interrupt all processes
* that share the same binary before doing a breakpoint operation on any of
* those processes. For simplicity, the logic below interrupts one thread of
* every process being debugged, without differentiating on the executable.
* Although it may seem wasteful to interrupt all processes when not necessary,
* in truth it is not so much; when making a breakpoint operation in Eclipse, that
* operation is propagated to all processes anyway, so they will all need to be
* interrupted. The case where we are wasteful is when we start or stop debugging
* a process (starting a process, attaching to one, auto-attaching to one,
* detaching from one, terminating one); in those cases, we only want to apply the
* breakpoint operation to that one process and any other using the same binary.
* The wastefulness is not such a big deal for that case, and is worth the simpler
* solution.
* Of course, it can always be improved later on.
* See http://bugs.eclipse.org/337893
* ******************************************************************************/
/**
* Utility class to store the parameters of the executeWithTargetAvailable() operations.
* @since 4.0
*/
protected static class TargetAvailableOperationInfo {
public IDMContext ctx;
public Sequence.Step[] steps;
public RequestMonitor rm;
public TargetAvailableOperationInfo(IDMContext ctx, Step[] steps, RequestMonitor rm) {
super();
this.ctx = ctx;
this.steps = steps;
this.rm = rm;
}
}
// The set of threads that we will actually be suspended to make the containers suspended.
private Set<IMIExecutionDMContext> fExecutionDmcToSuspendSet = new HashSet<>();
// Do we currently have an executeWithTargetAvailable() operation ongoing?
private boolean fOngoingOperation;
// Are we currently executing steps passed into executeWithTargetAvailable()?
// This allows us to know if we can add more steps to execute or if we missed
// our opportunity
private boolean fCurrentlyExecutingSteps;
// MultiRequestMonitor that allows us to track all the different steps we are
// executing. Once all steps are executed, we can complete this MultiRM and
// allow the global sequence to continue.
// Note that we couldn't use a CountingRequestMonitor because that type of RM
// needs to know in advance how many subRms it will track; the MultiRM allows us
// to receive more steps to execute continuously, and be able to update the MultiRM.
private MultiRequestMonitor<RequestMonitor> fExecuteQueuedOpsStepMonitor;
// The number of batches of steps that are still being executing for potentially
// concurrent executeWithTargetAvailable() operations.
// Once this gets to zero, we know we have executed all the steps we were aware of
// and we can complete the operation.
private int fNumStepsStillExecuting;
// Queue of executeWithTargetAvailable() operations that need to be processed.
private LinkedList<TargetAvailableOperationInfo> fOperationsPending = new LinkedList<>();
/**
* Returns whether there is currently an ExecuteWithTargetAvailable() operation ongoing.
* @since 4.0
*/
protected boolean isTargetAvailableOperationOngoing() {
return fOngoingOperation;
}
/** @since 4.0 */
protected void setTargetAvailableOperationOngoing(boolean ongoing) {
fOngoingOperation = ongoing;
}
/**
* Returns whether we are current in the process of executing the steps
* that were passed to ExecuteWithTargetAvailable().
* When this value is true, we can send more steps to be executed.
* @since 4.0
*/
protected boolean isCurrentlyExecutingSteps() {
return fCurrentlyExecutingSteps;
}
/** @since 4.0 */
protected void setCurrentlyExecutingSteps(boolean executing) {
fCurrentlyExecutingSteps = executing;
}
/**
* Returns the requestMonitor that will be run once all steps sent to
* ExecuteWithTargetAvailable() have been executed.
* @since 4.0
*/
protected MultiRequestMonitor<RequestMonitor> getExecuteQueuedStepsRM() {
return fExecuteQueuedOpsStepMonitor;
}
/** @since 4.0 */
protected void setExecuteQueuedStepsRM(MultiRequestMonitor<RequestMonitor> rm) {
fExecuteQueuedOpsStepMonitor = rm;
}
/**
* Returns the number of batches of steps sent to ExecuteWithTargetAvailable()
* that are still executing. Once this number reaches zero, we can complete
* the overall ExecuteWithTargetAvailable() operation.
* @since 4.0
*/
protected int getNumStepsStillExecuting() {
return fNumStepsStillExecuting;
}
/** @since 4.0 */
protected void setNumStepsStillExecuting(int num) {
fNumStepsStillExecuting = num;
}
/**
* Returns the queue of executeWithTargetAvailable() operations that still need to be processed
* @since 4.0
*/
protected LinkedList<TargetAvailableOperationInfo> getOperationsPending() {
return fOperationsPending;
}
/**
* This method takes care of executing a batch of steps that were passed to
* ExecuteWithTargetAvailable(). The method is used to track the progress
* of all these batches of steps, so that we know exactly when all of them
* have been completed and the global sequence can be completed.
* @since 4.0
*/
protected void executeSteps(final TargetAvailableOperationInfo info) {
fNumStepsStillExecuting++;
// This RM propagates any error to the original rm of the actual steps.
// Even in case of errors for these steps, we want to continue the overall sequence
RequestMonitor stepsRm = new ImmediateRequestMonitor() {
@Override
protected void handleCompleted() {
info.rm.setStatus(getStatus());
// It is important to call rm.done() right away.
// This is because some other operation we are performing might be waiting
// for this one to be done. If we try to wait for the entire sequence to be
// done, then we will never finish because one monitor will never show as
// done, waiting for the second one.
info.rm.done();
fExecuteQueuedOpsStepMonitor.requestMonitorDone(this);
fNumStepsStillExecuting--;
if (fNumStepsStillExecuting == 0) {
fExecuteQueuedOpsStepMonitor.doneAdding();
}
}
};
fExecuteQueuedOpsStepMonitor.add(stepsRm);
getExecutor().execute(new Sequence(getExecutor(), stepsRm) {
@Override
public Step[] getSteps() {
return info.steps;
}
});
}
/**
* @since 3.0
*/
@Override
public void executeWithTargetAvailable(IDMContext ctx, final Sequence.Step[] steps, final RequestMonitor rm) {
if (!fOngoingOperation) {
// We are the first operation of this kind currently requested
// so we need to start the sequence
fOngoingOperation = true;
// We always go through our queue, even if we only have a single call to this method
fOperationsPending.add(new TargetAvailableOperationInfo(ctx, steps, rm));
// Steps that need to be executed to perform the operation
final Step[] sequenceSteps = new Step[] { new IsTargetAvailableStep(ctx), new MakeTargetAvailableStep(),
new ExecuteQueuedOperationsStep(), new RestoreTargetStateStep(), };
// Once all the sequence is completed, we need to see if we have received
// another request that we now need to process
RequestMonitor sequenceCompletedRm = new RequestMonitor(getExecutor(), null) {
@Override
protected void handleSuccess() {
fOngoingOperation = false;
if (!fOperationsPending.isEmpty()) {
// Darn, more operations came in. Trigger their processing
// by calling executeWithTargetAvailable() on the last one
TargetAvailableOperationInfo info = fOperationsPending.removeLast();
executeWithTargetAvailable(info.ctx, info.steps, info.rm);
}
// no other rm.done() needs to be called, they have all been handled already
}
@Override
protected void handleFailure() {
// If the sequence failed, we have to give up on the operation(s).
// If we don't, we risk an infinite loop where we try, over and over
// to perform an operation that keeps on failing.
fOngoingOperation = false;
// Complete each rm of the cancelled operations
while (!fOperationsPending.isEmpty()) {
RequestMonitor rm = fOperationsPending.poll().rm;
rm.setStatus(getStatus());
rm.done();
}
super.handleFailure();
}
};
getExecutor().execute(new Sequence(getExecutor(), sequenceCompletedRm) {
@Override
public Step[] getSteps() {
return sequenceSteps;
}
});
} else {
// We are currently already executing such an operation
// If we are still in the process of executing steps, let's include this new set of steps.
// This is important because some steps may depend on these new ones.
if (fCurrentlyExecutingSteps) {
executeSteps(new TargetAvailableOperationInfo(ctx, steps, rm));
} else {
// Too late to execute the new steps, so queue them for later
fOperationsPending.add(new TargetAvailableOperationInfo(ctx, steps, rm));
}
}
}
/**
* This part of the sequence looks for all threads that will need to be suspended.
* @since 3.0
*/
protected class IsTargetAvailableStep extends Sequence.Step {
final IDMContext fCtx;
public IsTargetAvailableStep(IDMContext ctx) {
fCtx = ctx;
}
private void getThreadToSuspend(IContainerDMContext containerDmc, final RequestMonitor rm) {
// If the process is running, get its first thread which we will need to suspend
fProcessService.getProcessesBeingDebugged(containerDmc, new ImmediateDataRequestMonitor<IDMContext[]>(rm) {
@Override
protected void handleSuccess() {
IDMContext[] threads = getData();
if (threads != null && threads.length > 0) {
// Choose the first thread as the one to suspend
fExecutionDmcToSuspendSet.add((IMIExecutionDMContext) threads[0]);
}
rm.done();
}
});
}
@Override
public void execute(final RequestMonitor rm) {
// Clear any old data before we start
fExecutionDmcToSuspendSet.clear();
// Get all processes being debugged to see which one are running
// and need to be interrupted
fProcessService.getProcessesBeingDebugged(fConnection.getContext(),
new ImmediateDataRequestMonitor<IDMContext[]>(rm) {
@Override
protected void handleSuccess() {
assert getData() != null;
if (getData().length == 0) {
// Happens at startup, starting with GDB 7.0.
// This means the target is available. Nothing to do.
rm.done();
} else {
// Go through every process to see if it is running.
// If it is running, get its first thread so we can interrupt it.
CountingRequestMonitor crm = new ImmediateCountingRequestMonitor(rm);
int numThreadsToSuspend = 0;
for (IDMContext dmc : getData()) {
IContainerDMContext containerDmc = (IContainerDMContext) dmc;
if (!isSuspended(containerDmc)) {
numThreadsToSuspend++;
getThreadToSuspend(containerDmc, crm);
}
}
crm.setDoneCount(numThreadsToSuspend);
}
}
});
}
}
/**
* Suspended all the threads we have selected.
* @since 3.0
*/
protected class MakeTargetAvailableStep extends Sequence.Step {
/* public constructor required, so upper classes can override executeWithTargetAvailable */
/** @since 4.5 */
public MakeTargetAvailableStep() {
}
@Override
public void execute(final RequestMonitor rm) {
// Interrupt every first thread of the running processes
CountingRequestMonitor crm = new ImmediateCountingRequestMonitor(rm);
crm.setDoneCount(fExecutionDmcToSuspendSet.size());
for (final IMIExecutionDMContext thread : fExecutionDmcToSuspendSet) {
assert !fDisableNextRunningEventDmcSet.contains(thread);
assert !fDisableNextSignalEventDmcSet.contains(thread);
// Don't broadcast the next stopped signal event
fDisableNextSignalEventDmcSet.add(thread);
suspend(thread, new ImmediateRequestMonitor(crm) {
@Override
protected void handleFailure() {
// We weren't able to suspend, so abort the operation
fDisableNextSignalEventDmcSet.remove(thread);
super.handleFailure();
}
});
}
}
@Override
public void rollBack(RequestMonitor rm) {
Sequence.Step restoreStep = new RestoreTargetStateStep();
restoreStep.execute(rm);
}
}
/**
* This step of the sequence takes care of executing all the steps that
* were passed to ExecuteWithTargetAvailable().
* @since 4.0
*/
protected class ExecuteQueuedOperationsStep extends Sequence.Step {
/* public constructor required, so upper classes can override executeWithTargetAvailable */
/** @since 4.5 */
public ExecuteQueuedOperationsStep() {
}
@Override
public void execute(final RequestMonitor rm) {
fCurrentlyExecutingSteps = true;
// It is important to use an ImmediateExecutor for this RM, to make sure we don't risk getting a new
// call to ExecuteWithTargetAvailable() when we just finished executing the steps.
fExecuteQueuedOpsStepMonitor = new MultiRequestMonitor<RequestMonitor>(ImmediateExecutor.getInstance(),
rm) {
@Override
protected void handleCompleted() {
assert fOperationsPending.size() == 0;
// We don't handle errors here. Instead, we have already propagated any
// errors to each rm for each set of steps
fCurrentlyExecutingSteps = false;
// Continue the sequence
rm.done();
}
};
// Tell the RM that we need to confirm when we are done adding sub-rms
fExecuteQueuedOpsStepMonitor.requireDoneAdding();
// All pending operations are independent of each other so we can
// run them concurrently.
while (!fOperationsPending.isEmpty()) {
executeSteps(fOperationsPending.poll());
}
}
}
/**
* If the sequence had to interrupt the execution context of interest,
* this step will resume it again to reach the same state as when we started.
* @since 3.0
*/
protected class RestoreTargetStateStep extends Sequence.Step {
/* public constructor required, so upper classes can override executeWithTargetAvailable */
/** @since 4.5 */
public RestoreTargetStateStep() {
}
@Override
public void execute(final RequestMonitor rm) {
// Resume every thread we had interrupted
CountingRequestMonitor crm = new ImmediateCountingRequestMonitor(rm);
crm.setDoneCount(fExecutionDmcToSuspendSet.size());
for (final IMIExecutionDMContext thread : fExecutionDmcToSuspendSet) {
assert !fDisableNextRunningEventDmcSet.contains(thread);
fDisableNextRunningEventDmcSet.add(thread);
// Can't use the resume() call because we 'silently' stopped
// so resume() will not know we are actually stopped
fConnection.queueCommand(fCommandFactory.createMIExecContinue(thread),
new ImmediateDataRequestMonitor<MIInfo>(crm) {
@Override
protected void handleSuccess() {
fSilencedSignalEventMap.remove(thread);
super.handleSuccess();
}
@Override
protected void handleFailure() {
// Darn, we're unable to restart the target. Must cleanup!
fDisableNextRunningEventDmcSet.remove(thread);
// We must also sent the Stopped event that we had kept silent
MIStoppedEvent event = fSilencedSignalEventMap.remove(thread);
if (event != null) {
eventDispatched(event);
} else {
// Maybe the stopped event didn't arrive yet.
// We don't want to silence it anymore
fDisableNextSignalEventDmcSet.remove(thread);
}
super.handleFailure();
}
});
}
}
}
/* ******************************************************************************
* End of section to support operations even when the target is unavailable.
* ******************************************************************************/
///////////////////////////////////////////////////////////////////////////
// Event handlers
///////////////////////////////////////////////////////////////////////////
/**
* @nooverride This method is not intended to be re-implemented or extended by clients.
* @noreference This method is not intended to be referenced by clients.
*/
@DsfServiceEventHandler
public void eventDispatched(final MIRunningEvent e) {
if (fDisableNextRunningEventDmcSet.remove(e.getDMContext())) {
// Don't broadcast the running event
return;
}
MIThreadRunState threadState = fThreadRunStates.get(e.getDMContext());
if (threadState != null && threadState.fLatestEvent instanceof IResumedDMEvent) {
// Ignore multiple running events in a row. They will only slow down the UI
// for no added value.
return;
}
if (fRunToLineActiveOperation == null && fStepInToSelectionActiveOperation == null) {
// No special case here, i.e. send notification
getSession().dispatchEvent(new ResumedEvent(e.getDMContext(), e), getProperties());
} else {
// Either RunToLine or StepIntoSelection operations are active
if (threadState == null || threadState.fLatestEvent instanceof ISuspendedDMEvent) {
// Need to send out Running event notification, only once per operation, then a stop event is expected
// at the end of the operation
getSession().dispatchEvent(new ResumedEvent(e.getDMContext(), e), getProperties());
}
}
}
/**
* @nooverride This method is not intended to be re-implemented or extended by clients.
* @noreference This method is not intended to be referenced by clients.
*/
@DsfServiceEventHandler
public void eventDispatched(final MIStoppedEvent e) {
// A disabled signal event is due to interrupting the target
// to set a breakpoint. This can happen during a run-to-line
// or step-into operation, so we need to check it first.
IMIExecutionDMContext threadDmc = DMContexts.getAncestorOfType(e.getDMContext(), IMIExecutionDMContext.class);
if (e instanceof MISignalEvent && fDisableNextSignalEventDmcSet.remove(threadDmc)) {
fSilencedSignalEventMap.put(threadDmc, e);
// Don't broadcast the stopped event
return;
}
if (processRunToLineStoppedEvent(e)) {
// If RunToLine is not completed
return;
}
if (!processStepIntoSelection(e)) {
//Step into Selection is not in progress
broadcastStop(e);
}
}
private void broadcastStop(final MIStoppedEvent e) {
IDMEvent<?> event = null;
MIBreakpointDMContext bp = null;
if (e instanceof MIBreakpointHitEvent) {
String bpId = ((MIBreakpointHitEvent) e).getNumber();
IBreakpointsTargetDMContext bpsTarget = DMContexts.getAncestorOfType(e.getDMContext(),
IBreakpointsTargetDMContext.class);
if (bpsTarget != null && !bpId.isEmpty()) {
bp = new MIBreakpointDMContext(getSession().getId(), new IDMContext[] { bpsTarget }, bpId);
event = new BreakpointHitEvent(e.getDMContext(), (MIBreakpointHitEvent) e, bp);
}
}
if (event == null) {
event = new SuspendedEvent(e.getDMContext(), e);
}
getSession().dispatchEvent(event, getProperties());
}
private boolean processStepIntoSelection(final MIStoppedEvent e) {
if (fStepInToSelectionActiveOperation == null) {
return false;
}
// First check if it is the right thread that stopped
final IMIExecutionDMContext threadDmc = DMContexts.getAncestorOfType(e.getDMContext(),
IMIExecutionDMContext.class);
if (fStepInToSelectionActiveOperation.getThreadContext().equals(threadDmc)) {
final MIFrame frame = e.getFrame();
assert (fRunToLineActiveOperation == null);
if (fStepInToSelectionActiveOperation.getRunToLineFrame() == null) {
assert (fStepInToSelectionActiveOperation.getLine() == frame.getLine());
// Shall now be at the runToline location
fStepInToSelectionActiveOperation.setRunToLineFrame(frame);
}
// Step - Not at the right place just yet
// Initiate an async call chain parent
getStackDepth(threadDmc, new DataRequestMonitor<Integer>(getExecutor(), null) {
private int originalStackDepth = fStepInToSelectionActiveOperation.getOriginalStackDepth();
@Override
protected void handleSuccess() {
int frameDepth = getStackDepth();
if (frameDepth > originalStackDepth) {
//shall be true as this is using stepinto step type vs instruction stepinto
assert (frameDepth == originalStackDepth + 1);
// Check for a match
if (StepIntoSelectionUtils.sameSignature(frame, fStepInToSelectionActiveOperation)) {
// Hit !!
stopStepIntoSelection(e);
return;
}
// Located deeper in the stack, Shall continue step / search
// Step return
continueStepping(e, StepType.STEP_RETURN);
} else if (frameDepth == originalStackDepth) {
// Continue step / search as long as
// this is the starting base line for the search
String currentLocation = frame.getFile() + ":" + frame.getLine(); //$NON-NLS-1$
String searchLineLocation = fStepInToSelectionActiveOperation.getFileLocation();
if (currentLocation.equals(searchLineLocation)) {
continueStepping(e, StepType.STEP_INTO);
} else {
// We have moved to a line
// different from the base
// search line i.e. missed the
// target function !!
StepIntoSelectionUtils.missedSelectedTarget(fStepInToSelectionActiveOperation);
stopStepIntoSelection(e);
}
} else {
// missed the target point
StepIntoSelectionUtils.missedSelectedTarget(fStepInToSelectionActiveOperation);
}
}
@Override
protected void handleFailure() {
// log error
if (getStatus() != null) {
GdbPlugin.getDefault().getLog().log(getStatus());
}
stopStepIntoSelection(e);
}
private int getStackDepth() {
Integer stackDepth = null;
if (isSuccess() && getData() != null) {
stackDepth = getData();
// This is the base frame, the original stack depth shall be updated
if (frame == fStepInToSelectionActiveOperation.getRunToLineFrame()) {
fStepInToSelectionActiveOperation.setOriginalStackDepth(stackDepth);
originalStackDepth = stackDepth;
}
}
if (stackDepth == null) {
// Unsuccessful resolution of stack depth, default to same stack depth to detect a change of line within the original frame
return fStepInToSelectionActiveOperation.getOriginalStackDepth();
}
return stackDepth.intValue();
}
});
//Processing step into selection
return true;
}
//The thread related to this event is outside the scope of the step into selection context
return false;
}
private void stopStepIntoSelection(final MIStoppedEvent e) {
fStepInToSelectionActiveOperation = null;
// Need to broadcast the stop
broadcastStop(e);
}
private void continueStepping(final MIStoppedEvent event, StepType steptype) {
step(fStepInToSelectionActiveOperation.getThreadContext(), steptype, false,
new RequestMonitor(getExecutor(), null) {
@Override
protected void handleFailure() {
// log error
if (getStatus() != null) {
GdbPlugin.getDefault().getLog().log(getStatus());
}
stopStepIntoSelection(event);
}
});
}
private boolean processRunToLineStoppedEvent(final MIStoppedEvent e) {
if (fRunToLineActiveOperation == null) {
return false;
}
// First check if it is the right thread that stopped
IMIExecutionDMContext threadDmc = DMContexts.getAncestorOfType(e.getDMContext(), IMIExecutionDMContext.class);
if (fRunToLineActiveOperation.getThreadContext().equals(threadDmc)) {
String bpId = ""; //$NON-NLS-1$
if (e instanceof MIBreakpointHitEvent) {
bpId = ((MIBreakpointHitEvent) e).getNumber();
}
String fileLocation = e.getFrame().getFile() + ":" + e.getFrame().getLine(); //$NON-NLS-1$
String addrLocation = e.getFrame().getAddress();
// Here we check three different things to see if we are stopped at the right place
// 1- The actual location in the file. But this does not work for breakpoints that
// were set on non-executable lines
// 2- The address where the breakpoint was set. But this does not work for breakpoints
// that have multiple addresses (GDB returns <MULTIPLE>.) I think that is for multi-process
// 3- The breakpoint id that was hit. But this does not work if another breakpoint
// was also set on the same line because GDB may return that breakpoint as being hit.
//
// So this works for the large majority of cases. The case that won't work is when the user
// does a runToLine to a line that is non-executable AND has another breakpoint AND
// has multiple addresses for the breakpoint. I'm mean, come on!
if (fileLocation.equals(fRunToLineActiveOperation.getFileLocation())
|| addrLocation.equals(fRunToLineActiveOperation.getAddrLocation())
|| bpId.equals(fRunToLineActiveOperation.getBreakpointId())) {
// We stopped at the right place. All is well.
// Run to line completed
fRunToLineActiveOperation = null;
} else {
// The right thread stopped but not at the right place yet
if (fRunToLineActiveOperation.shouldSkipBreakpoints() && e instanceof MIBreakpointHitEvent) {
fConnection.queueCommand(
fCommandFactory.createMIExecContinue(fRunToLineActiveOperation.getThreadContext()),
new DataRequestMonitor<MIInfo>(getExecutor(), null));
// Continue i.e. Don't send the stop event since we are
// resuming again.
return true;
} else {
// Stopped for any other reasons. Just remove our temporary one
// since we don't want it to hit later
//
// Note that in Non-stop, we don't cancel a run-to-line when a new
// breakpoint is inserted. This is because the new breakpoint could
// be for another thread altogether and should not affect the current thread.
IBreakpointsTargetDMContext bpDmc = DMContexts.getAncestorOfType(
fRunToLineActiveOperation.getThreadContext(), IBreakpointsTargetDMContext.class);
fConnection.queueCommand(
fCommandFactory.createMIBreakDelete(bpDmc,
new String[] { fRunToLineActiveOperation.getBreakpointId() }),
new DataRequestMonitor<MIInfo>(getExecutor(), null));
fRunToLineActiveOperation = null;
fStepInToSelectionActiveOperation = null;
}
}
}
return false;
}
/**
* @nooverride This method is not intended to be re-implemented or extended by clients.
* @noreference This method is not intended to be referenced by clients.
*/
@DsfServiceEventHandler
public void eventDispatched(final MIThreadCreatedEvent e) {
IContainerDMContext containerDmc = e.getDMContext();
IMIExecutionDMContext executionCtx = null;
if (e.getStrId() != null) {
executionCtx = createMIExecutionContext(containerDmc, e.getStrId());
}
getSession().dispatchEvent(new StartedDMEvent(executionCtx, e), getProperties());
}
/**
* @nooverride This method is not intended to be re-implemented or extended by clients.
* @noreference This method is not intended to be referenced by clients.
*/
@DsfServiceEventHandler
public void eventDispatched(final MIThreadExitEvent e) {
IContainerDMContext containerDmc = e.getDMContext();
IMIExecutionDMContext executionCtx = null;
if (e.getStrId() != null) {
executionCtx = createMIExecutionContext(containerDmc, e.getStrId());
}
getSession().dispatchEvent(new ExitedDMEvent(executionCtx, e), getProperties());
}
/**
* @nooverride This method is not intended to be re-implemented or extended by clients.
* @noreference This method is not intended to be referenced by clients.
*/
@DsfServiceEventHandler
public void eventDispatched(ResumedEvent e) {
IExecutionDMContext ctx = e.getDMContext();
if (ctx instanceof IMIExecutionDMContext) {
updateThreadState((IMIExecutionDMContext) ctx, e);
}
}
/**
* @nooverride This method is not intended to be re-implemented or extended by clients.
* @noreference This method is not intended to be referenced by clients.
*/
@DsfServiceEventHandler
public void eventDispatched(SuspendedEvent e) {
IExecutionDMContext ctx = e.getDMContext();
if (ctx instanceof IMIExecutionDMContext) {
updateThreadState((IMIExecutionDMContext) ctx, e);
}
}
/**
* @nooverride This method is not intended to be re-implemented or extended by clients.
* @noreference This method is not intended to be referenced by clients.
*/
@DsfServiceEventHandler
public void eventDispatched(StartedDMEvent e) {
IExecutionDMContext executionCtx = e.getDMContext();
if (executionCtx instanceof IMIExecutionDMContext) {
if (fThreadRunStates.get(executionCtx) == null) {
fThreadRunStates.put((IMIExecutionDMContext) executionCtx, new MIThreadRunState());
}
}
}
/**
* @nooverride This method is not intended to be re-implemented or extended by clients.
* @noreference This method is not intended to be referenced by clients.
*/
@DsfServiceEventHandler
public void eventDispatched(ExitedDMEvent e) {
fThreadRunStates.remove(e.getDMContext());
}
/**
* @nooverride This method is not intended to be re-implemented or extended by clients.
* @noreference This method is not intended to be referenced by clients.
*/
@DsfServiceEventHandler
public void eventDispatched(ICommandControlShutdownDMEvent e) {
fTerminated = true;
}
/**
* @nooverride This method is not intended to be re-implemented or extended by clients.
* @noreference This method is not intended to be referenced by clients.
*
* @since 2.0
*/
@DsfServiceEventHandler
public void eventDispatched(MIInferiorExitEvent e) {
if (fRunToLineActiveOperation != null) {
IBreakpointsTargetDMContext bpDmc = DMContexts
.getAncestorOfType(fRunToLineActiveOperation.getThreadContext(), IBreakpointsTargetDMContext.class);
String bpId = fRunToLineActiveOperation.getBreakpointId();
fConnection.queueCommand(fCommandFactory.createMIBreakDelete(bpDmc, new String[] { bpId }),
new DataRequestMonitor<MIInfo>(getExecutor(), null));
fRunToLineActiveOperation = null;
}
fStepInToSelectionActiveOperation = null;
}
@Override
public void flushCache(IDMContext context) {
refreshThreadStates();
}
/**
* Gets the state of each thread from GDB and updates our internal map.
* @since 4.1
*/
protected void refreshThreadStates() {
fConnection.queueCommand(fCommandFactory.createMIThreadInfo(fConnection.getContext()),
new DataRequestMonitor<MIThreadInfoInfo>(getExecutor(), null) {
@Override
protected void handleSuccess() {
MIThread[] threadList = getData().getThreadList();
for (MIThread thread : threadList) {
String threadId = thread.getThreadId();
IMIContainerDMContext containerDmc = fProcessService
.createContainerContextFromThreadId(fConnection.getContext(), threadId);
IProcessDMContext processDmc = DMContexts.getAncestorOfType(containerDmc,
IProcessDMContext.class);
IThreadDMContext threadDmc = fProcessService.createThreadContext(processDmc, threadId);
IMIExecutionDMContext execDmc = fProcessService.createExecutionContext(containerDmc,
threadDmc, threadId);
MIThreadRunState threadState = fThreadRunStates.get(execDmc);
if (threadState != null) {
// We may not know this thread. This can happen when dealing with a remote
// where thread events are not reported immediately.
// However, the -thread-info command we just sent will make
// GDB send those events. Therefore, we can just ignore threads we don't
// know about, and wait for those events.
if (MIThread.MI_THREAD_STATE_RUNNING.equals(thread.getState())) {
if (threadState.fSuspended == true) {
// We missed a resumed event! Send it now.
IResumedDMEvent resumedEvent = new ResumedEvent(execDmc, null);
fConnection.getSession().dispatchEvent(resumedEvent, getProperties());
}
} else if (MIThread.MI_THREAD_STATE_STOPPED.equals(thread.getState())) {
if (threadState.fSuspended == false) {
// We missed a suspend event! Send it now.
ISuspendedDMEvent suspendedEvent = new SuspendedEvent(execDmc, null);
fConnection.getSession().dispatchEvent(suspendedEvent, getProperties());
}
} else {
assert false : "Invalid thread state: " + thread.getState(); //$NON-NLS-1$
}
}
}
}
});
}
private void moveToLocation(final IExecutionDMContext context, final String location,
final Map<String, Object> bpAttributes, final RequestMonitor rm) {
// first create a temporary breakpoint to stop the execution at
// the location we are about to jump to
IBreakpoints bpService = getServicesTracker().getService(IBreakpoints.class);
IBreakpointsTargetDMContext bpDmc = DMContexts.getAncestorOfType(context, IBreakpointsTargetDMContext.class);
if (bpService != null && bpDmc != null) {
bpService.insertBreakpoint(bpDmc, bpAttributes,
new DataRequestMonitor<IBreakpointDMContext>(getExecutor(), rm) {
@Override
protected void handleSuccess() {
// Now resume at the proper location
resumeAtLocation(context, location, rm);
}
});
} else {
rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, IDsfStatusConstants.NOT_SUPPORTED,
"Unable to set breakpoint", null)); //$NON-NLS-1$
rm.done();
}
}
/* (non-Javadoc)
* @see org.eclipse.cdt.dsf.debug.service.IRunControl2#canRunToLine(org.eclipse.cdt.dsf.debug.service.IRunControl.IExecutionDMContext, java.lang.String, int, org.eclipse.cdt.dsf.concurrent.DataRequestMonitor)
*/
/**
* @since 3.0
*/
@Override
public void canRunToLine(IExecutionDMContext context, String sourceFile, int lineNumber,
DataRequestMonitor<Boolean> rm) {
canResume(context, rm);
}
/* (non-Javadoc)
* @see org.eclipse.cdt.dsf.debug.service.IRunControl2#runToLine(org.eclipse.cdt.dsf.debug.service.IRunControl.IExecutionDMContext, java.lang.String, int, boolean, org.eclipse.cdt.dsf.concurrent.RequestMonitor)
*/
/**
* @since 3.0
*/
@Override
public void runToLine(final IExecutionDMContext context, String sourceFile, final int lineNumber,
final boolean skipBreakpoints, final RequestMonitor rm) {
determineDebuggerPath(context, sourceFile, new ImmediateDataRequestMonitor<String>(rm) {
@Override
protected void handleSuccess() {
runToLocation(context, getData() + ":" + Integer.toString(lineNumber), skipBreakpoints, rm); //$NON-NLS-1$
}
});
}
/* (non-Javadoc)
* @see org.eclipse.cdt.dsf.debug.service.IRunControl2#canRunToAddress(org.eclipse.cdt.dsf.debug.service.IRunControl.IExecutionDMContext, org.eclipse.cdt.core.IAddress, org.eclipse.cdt.dsf.concurrent.DataRequestMonitor)
*/
/**
* @since 3.0
*/
@Override
public void canRunToAddress(IExecutionDMContext context, IAddress address, DataRequestMonitor<Boolean> rm) {
canResume(context, rm);
}
/* (non-Javadoc)
* @see org.eclipse.cdt.dsf.debug.service.IRunControl2#runToAddress(org.eclipse.cdt.dsf.debug.service.IRunControl.IExecutionDMContext, org.eclipse.cdt.core.IAddress, boolean, org.eclipse.cdt.dsf.concurrent.RequestMonitor)
*/
/**
* @since 3.0
*/
@Override
public void runToAddress(IExecutionDMContext context, IAddress address, boolean skipBreakpoints,
RequestMonitor rm) {
runToLocation(context, "*0x" + address.toString(16), skipBreakpoints, rm); //$NON-NLS-1$
}
/* (non-Javadoc)
* @see org.eclipse.cdt.dsf.debug.service.IRunControl2#canMoveToLine(org.eclipse.cdt.dsf.debug.service.IRunControl.IExecutionDMContext, java.lang.String, int, boolean, org.eclipse.cdt.dsf.concurrent.DataRequestMonitor)
*/
/**
* @since 3.0
*/
@Override
public void canMoveToLine(IExecutionDMContext context, String sourceFile, int lineNumber, boolean resume,
DataRequestMonitor<Boolean> rm) {
canResume(context, rm);
}
/* (non-Javadoc)
* @see org.eclipse.cdt.dsf.debug.service.IRunControl2#moveToLine(org.eclipse.cdt.dsf.debug.service.IRunControl.IExecutionDMContext, java.lang.String, int, boolean, org.eclipse.cdt.dsf.concurrent.RequestMonitor)
*/
/**
* @since 3.0
*/
@Override
public void moveToLine(final IExecutionDMContext context, String sourceFile, final int lineNumber,
final boolean resume, final RequestMonitor rm) {
final IMIExecutionDMContext threadExecDmc = DMContexts.getAncestorOfType(context, IMIExecutionDMContext.class);
if (threadExecDmc == null) {
rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, DebugException.REQUEST_FAILED,
"Invalid thread context", null)); //$NON-NLS-1$
rm.done();
} else {
determineDebuggerPath(context, sourceFile, new ImmediateDataRequestMonitor<String>(rm) {
@Override
protected void handleSuccess() {
String debuggerPath = getData();
String location = debuggerPath + ":" + lineNumber; //$NON-NLS-1$
if (resume) {
resumeAtLocation(context, location, rm);
} else {
// Create the breakpoint attributes
Map<String, Object> attr = new HashMap<>();
attr.put(MIBreakpoints.BREAKPOINT_TYPE, MIBreakpoints.BREAKPOINT);
attr.put(MIBreakpoints.FILE_NAME, debuggerPath);
attr.put(MIBreakpoints.LINE_NUMBER, lineNumber);
attr.put(MIBreakpointDMData.IS_TEMPORARY, true);
attr.put(MIBreakpointDMData.THREAD_ID, threadExecDmc.getThreadId());
// Now do the operation
moveToLocation(context, location, attr, rm);
}
}
});
}
}
/* (non-Javadoc)
* @see org.eclipse.cdt.dsf.debug.service.IRunControl2#canMoveToAddress(org.eclipse.cdt.dsf.debug.service.IRunControl.IExecutionDMContext, org.eclipse.cdt.core.IAddress, boolean, org.eclipse.cdt.dsf.concurrent.DataRequestMonitor)
*/
/**
* @since 3.0
*/
@Override
public void canMoveToAddress(IExecutionDMContext context, IAddress address, boolean resume,
DataRequestMonitor<Boolean> rm) {
canResume(context, rm);
}
/** (non-Javadoc)
* @see org.eclipse.cdt.dsf.debug.service.IRunControl2#moveToAddress(org.eclipse.cdt.dsf.debug.service.IRunControl.IExecutionDMContext, org.eclipse.cdt.core.IAddress, boolean, org.eclipse.cdt.dsf.concurrent.RequestMonitor)
* @since 3.0
*/
@Override
public void moveToAddress(IExecutionDMContext context, IAddress address, boolean resume, RequestMonitor rm) {
IMIExecutionDMContext threadExecDmc = DMContexts.getAncestorOfType(context, IMIExecutionDMContext.class);
if (threadExecDmc == null) {
rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, DebugException.REQUEST_FAILED,
"Invalid thread context", null)); //$NON-NLS-1$
rm.done();
} else {
String location = "*0x" + address.toString(16); //$NON-NLS-1$
if (resume)
resumeAtLocation(context, location, rm);
else {
// Create the breakpoint attributes
Map<String, Object> attr = new HashMap<>();
attr.put(MIBreakpoints.BREAKPOINT_TYPE, MIBreakpoints.BREAKPOINT);
attr.put(MIBreakpoints.ADDRESS, "0x" + address.toString(16)); //$NON-NLS-1$
attr.put(MIBreakpointDMData.IS_TEMPORARY, true);
attr.put(MIBreakpointDMData.THREAD_ID, threadExecDmc.getThreadId());
// Now do the operation
moveToLocation(context, location, attr, rm);
}
}
}
/** @since 4.0 */
@Override
public IRunMode getRunMode() {
return MIRunMode.NON_STOP;
}
/** @since 4.0 */
@Override
public boolean isTargetAcceptingCommands() {
// Always accepting commands in non-stop mode
return true;
}
/**
* Determine the path that should be sent to the debugger as per the source lookup service.
*
* @param dmc A context that can be used to obtain the sourcelookup context.
* @param hostPath The path of the file on the host, which must be converted.
* @param rm The result of the conversion.
*/
private void determineDebuggerPath(IDMContext dmc, String hostPath, final DataRequestMonitor<String> rm) {
ISourceLookup sourceLookup = getServicesTracker().getService(ISourceLookup.class);
ISourceLookupDMContext srcDmc = DMContexts.getAncestorOfType(dmc, ISourceLookupDMContext.class);
if (sourceLookup == null || srcDmc == null) {
// Source lookup not available for given context, use the host
// path for the debugger path.
// Hack around a MinGW bug; see 369622 (and also 196154 and 232415)
rm.done(adjustDebuggerPath(hostPath));
return;
}
sourceLookup.getDebuggerPath(srcDmc, hostPath, new DataRequestMonitor<String>(getExecutor(), rm) {
@Override
protected void handleSuccess() {
// Hack around a MinGW bug; see 369622 (and also 196154 and 232415)
rm.done(adjustDebuggerPath(getData()));
}
});
}
/**
* See bug 196154
*
* @param path
* the absolute path to the source file
* @return the adjusted path provided by the breakpoints service
*/
private String adjustDebuggerPath(String path) {
IBreakpoints breakpoints = getServicesTracker().getService(IBreakpoints.class);
return (breakpoints instanceof IMIBreakpointPathAdjuster)
? ((IMIBreakpointPathAdjuster) breakpoints).adjustDebuggerPath(path)
: path;
}
///////////////////////////////////////////////////////////////////////////
// IMultiRunControl implementation
///////////////////////////////////////////////////////////////////////////
// Although multi-process in only supported for GDB >= 7.2, it is simpler
// to code for the multi-process case all the time, since it is a superset
// of the single-process case.
///////////////////////////////////////////////////////////////////////////
// Multi-resume implementation:
//
// If one or more more threads of one or many processes are selected, we want to
// resume each thread (once).
//
// If one or more more processes are selected, we want to resume each process (once).
//
// If a process is selected along with one or more threads of that same process,
// what does the user want us to do? Selecting the process will resume all its
// threads, but what do we do with the selected threads? Why are they
// selected? In an attempt to be user friendly, lets assume that the user
// wants to resume the entire process, so we ignore the selected threads part of that
// process since they will be resumed anyway.
//
// The same logic applies to multi-suspend.
///////////////////////////////////////////////////////////////////////////
/** @since 4.1 */
@Override
public void canResumeSome(IExecutionDMContext[] contexts, DataRequestMonitor<Boolean> rm) {
assert contexts != null;
if (fRunControlOperationsEnabled == false) {
rm.done(false);
return;
}
List<IExecutionDMContext> execDmcToResumeList = extractContextsForOperation(contexts);
// If any of the threads or processes can be resumed, we allow
// the user to perform the operation.
for (IExecutionDMContext execDmc : execDmcToResumeList) {
if (doCanResume(execDmc)) {
rm.done(true);
return;
}
}
// Didn't find anything that could be resumed.
rm.done(false);
}
/** @since 4.1 */
@Override
public void canResumeAll(IExecutionDMContext[] contexts, DataRequestMonitor<Boolean> rm) {
assert contexts != null;
if (fRunControlOperationsEnabled == false) {
rm.done(false);
return;
}
List<IExecutionDMContext> execDmcToResumeList = extractContextsForOperation(contexts);
// If any of the threads or processes cannot be resumed, we don't allow
// the user to perform the operation.
for (IExecutionDMContext execDmc : execDmcToResumeList) {
if (!doCanResume(execDmc)) {
rm.done(false);
return;
}
}
// Everything can be resumed
rm.done(true);
}
/**
* {@inheritDoc}
*
* For GDB, a separate resume command will be sent, one for each context
* that can be resumed.
* @since 4.1
*/
@Override
public void resume(IExecutionDMContext[] contexts, RequestMonitor rm) {
assert contexts != null;
List<IExecutionDMContext> execDmcToResumeList = extractContextsForOperation(contexts);
CountingRequestMonitor crm = new CountingRequestMonitor(getExecutor(), rm);
int count = 0;
// Perform resume operation on each thread or process that can be resumed
for (IExecutionDMContext execDmc : execDmcToResumeList) {
if (doCanResume(execDmc)) {
count++;
resume(execDmc, crm);
}
}
crm.setDoneCount(count);
}
///////////////////////////////////////////////////////////////////////////
// Multi-suspend implementation:
// see details of the multi-resume implementation above.
///////////////////////////////////////////////////////////////////////////
/** @since 4.1 */
@Override
public void canSuspendSome(IExecutionDMContext[] contexts, DataRequestMonitor<Boolean> rm) {
assert contexts != null;
if (fRunControlOperationsEnabled == false) {
rm.done(false);
return;
}
List<IExecutionDMContext> execDmcToSuspendList = extractContextsForOperation(contexts);
// If any of the threads or processes can be suspended, we allow
// the user to perform the operation.
for (IExecutionDMContext execDmc : execDmcToSuspendList) {
if (doCanSuspend(execDmc)) {
rm.done(true);
return;
}
}
// Didn't find anything that could be suspended.
rm.done(false);
}
/** @since 4.1 */
@Override
public void canSuspendAll(IExecutionDMContext[] contexts, DataRequestMonitor<Boolean> rm) {
assert contexts != null;
if (fRunControlOperationsEnabled == false) {
rm.done(false);
return;
}
List<IExecutionDMContext> execDmcToSuspendList = extractContextsForOperation(contexts);
// If any of the threads or processes cannot be suspended, we don't allow
// the user to perform the operation.
for (IExecutionDMContext execDmc : execDmcToSuspendList) {
if (!doCanSuspend(execDmc)) {
rm.done(false);
return;
}
}
// Everything can be suspended
rm.done(true);
}
/** @since 4.1 */
@Override
public void isSuspendedSome(IExecutionDMContext[] contexts, DataRequestMonitor<Boolean> rm) {
assert contexts != null;
List<IExecutionDMContext> execDmcSuspendedList = extractContextsForOperation(contexts);
// Look for any thread or process that is suspended
for (IExecutionDMContext execDmc : execDmcSuspendedList) {
if (isSuspended(execDmc)) {
rm.done(true);
return;
}
}
// Didn't find anything that was suspended.
rm.done(false);
}
/** @since 4.1 */
@Override
public void isSuspendedAll(IExecutionDMContext[] contexts, DataRequestMonitor<Boolean> rm) {
assert contexts != null;
List<IExecutionDMContext> execDmcSuspendedList = extractContextsForOperation(contexts);
// Look for any thread or process that is not suspended
for (IExecutionDMContext execDmc : execDmcSuspendedList) {
if (!isSuspended(execDmc)) {
rm.done(false);
return;
}
}
// Everything is suspended.
rm.done(true);
}
/**
* {@inheritDoc}
*
* For GDB, a separate suspend command will be sent, one for each context
* that can be suspended.
* @since 4.1
*/
@Override
public void suspend(IExecutionDMContext[] contexts, RequestMonitor rm) {
assert contexts != null;
List<IExecutionDMContext> execDmcToSuspendList = extractContextsForOperation(contexts);
CountingRequestMonitor crm = new CountingRequestMonitor(getExecutor(), rm);
int count = 0;
// Perform resume operation on each thread or process that can be resumed
for (IExecutionDMContext execDmc : execDmcToSuspendList) {
if (doCanSuspend(execDmc)) {
count++;
suspend(execDmc, crm);
}
}
crm.setDoneCount(count);
}
///////////////////////////////////////////////////////////////////////////
// Multi-step implementation. Not implemented yet. See bug 330974.
///////////////////////////////////////////////////////////////////////////
/** @since 4.1 */
@Override
public void canStepSome(IExecutionDMContext[] contexts, StepType stepType, DataRequestMonitor<Boolean> rm) {
rm.done(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, IDsfStatusConstants.NOT_SUPPORTED, "Not implemented.", //$NON-NLS-1$
null));
}
/** @since 4.1 */
@Override
public void canStepAll(IExecutionDMContext[] contexts, StepType stepType, DataRequestMonitor<Boolean> rm) {
rm.done(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, IDsfStatusConstants.NOT_SUPPORTED, "Not implemented.", //$NON-NLS-1$
null));
}
/** @since 4.1 */
@Override
public void isSteppingSome(IExecutionDMContext[] contexts, DataRequestMonitor<Boolean> rm) {
rm.done(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, IDsfStatusConstants.NOT_SUPPORTED, "Not implemented.", //$NON-NLS-1$
null));
}
/** @since 4.1 */
@Override
public void isSteppingAll(IExecutionDMContext[] contexts, DataRequestMonitor<Boolean> rm) {
rm.done(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, IDsfStatusConstants.NOT_SUPPORTED, "Not implemented.", //$NON-NLS-1$
null));
}
/** @since 4.1 */
@Override
public void step(IExecutionDMContext[] contexts, StepType stepType, RequestMonitor rm) {
rm.done(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, IDsfStatusConstants.NOT_SUPPORTED, "Not implemented.", //$NON-NLS-1$
null));
}
/**
* Removes duplicates from the list of execution contexts, in case the same thread
* or process is present more than once.
*
* Also, remove any thread that is part of a process that is also present. This is
* because an operation on the process will affect all its threads anyway.
*/
private List<IExecutionDMContext> extractContextsForOperation(IExecutionDMContext[] contexts) {
// Remove duplicate contexts by using a set
Set<IExecutionDMContext> specifiedExedDmcSet = new HashSet<>(Arrays.asList(contexts));
// A list that ignores threads for which the process is also present
List<IExecutionDMContext> execDmcForOperationList = new ArrayList<>(specifiedExedDmcSet.size());
// Check for the case of a process selected along with some of its threads
for (IExecutionDMContext execDmc : specifiedExedDmcSet) {
if (execDmc instanceof IContainerDMContext) {
// This is a process: it is automatically part of our list
execDmcForOperationList.add(execDmc);
} else {
// Get the process for this thread
IContainerDMContext containerDmc = DMContexts.getAncestorOfType(execDmc, IContainerDMContext.class);
// Check if that process is also present
if (specifiedExedDmcSet.contains(containerDmc) == false) {
// This thread does not belong to a process that is selected, so we keep it.
execDmcForOperationList.add(execDmc);
}
}
}
return execDmcForOperationList;
}
/**
* @since 4.2
*/
@Override
public void canStepIntoSelection(IExecutionDMContext context, String sourceFile, int lineNumber,
IFunctionDeclaration selectedFunction, DataRequestMonitor<Boolean> rm) {
canStep(context, StepType.STEP_INTO, rm);
}
/**
* @since 4.2
*/
@Override
public void stepIntoSelection(final IExecutionDMContext context, String sourceFile, final int lineNumber,
final boolean skipBreakpoints, final IFunctionDeclaration selectedFunction, final RequestMonitor rm) {
determineDebuggerPath(context, sourceFile, new ImmediateDataRequestMonitor<String>(rm) {
@Override
protected void handleSuccess() {
stepIntoSelection(context, lineNumber, getData() + ":" + Integer.toString(lineNumber), skipBreakpoints, //$NON-NLS-1$
selectedFunction, rm);
}
});
}
/**
* Help method used when the stopped event has not been broadcasted e.g. in the middle of step into selection
*
* @param dmc
* @param rm
*/
private void getStackDepth(final IMIExecutionDMContext dmc, final DataRequestMonitor<Integer> rm) {
if (dmc != null) {
fConnection.queueCommand(fCommandFactory.createMIStackInfoDepth(dmc),
new DataRequestMonitor<MIStackInfoDepthInfo>(getExecutor(), rm) {
@Override
protected void handleSuccess() {
rm.setData(getData().getDepth());
rm.done();
}
});
} else {
rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, INVALID_HANDLE, "Invalid context", null)); //$NON-NLS-1$
rm.done();
}
}
}