| /******************************************************************************* |
| * 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 |
| * Vladimir Prus (Mentor Graphics) - Add proper stop reason for step return (Bug 362274) |
| * Indel AG - [369622] fixed moveToLine using MinGW |
| * Marc Khouzam (Ericsson) - Make each thread an IDisassemblyDMContext (bug 352748) |
| * Alvaro Sanchez-Leon (Ericsson AB) - Support for Step into selection (bug 244865) |
| * Alvaro Sanchez-Leon (Ericsson AB) - Bug 415362 |
| * Xavier Raynaud (Kalray) - Bug 438635 |
| *******************************************************************************/ |
| package org.eclipse.cdt.dsf.mi.service; |
| |
| import java.util.HashMap; |
| import java.util.LinkedList; |
| import java.util.Map; |
| |
| import org.eclipse.cdt.core.IAddress; |
| import org.eclipse.cdt.core.model.IFunctionDeclaration; |
| import org.eclipse.cdt.dsf.concurrent.DataRequestMonitor; |
| import org.eclipse.cdt.dsf.concurrent.IDsfStatusConstants; |
| 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.AbstractDMContext; |
| 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.IDisassembly.IDisassemblyDMContext; |
| import org.eclipse.cdt.dsf.debug.service.IProcesses; |
| 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.BufferedCommandControl; |
| import org.eclipse.cdt.dsf.debug.service.command.CommandCache; |
| 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.ICommandControlDMContext; |
| 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.mi.service.MIBreakpoints.MIBreakpointDMContext; |
| 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.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.CLIThreadInfo; |
| import org.eclipse.cdt.dsf.mi.service.command.output.MIInfo; |
| import org.eclipse.cdt.dsf.mi.service.command.output.MIThreadListIdsInfo; |
| 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.IStatus; |
| import org.eclipse.core.runtime.Status; |
| import org.eclipse.debug.core.DebugException; |
| import org.osgi.framework.BundleContext; |
| |
| /** |
| * |
| * <p> |
| * 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 3.0 |
| */ |
| public class MIRunControl extends AbstractDsfService implements IMIRunControl, ICachingService, IRunControl3 { |
| private static class MIExecutionDMC extends AbstractDMContext |
| implements IMIExecutionDMContext, IDisassemblyDMContext { |
| /** |
| * Integer ID that is used to identify the thread in the GDB/MI protocol. |
| */ |
| private final String fThreadId; |
| |
| /** |
| * Constructor for the context. It should not be called directly by clients. |
| * Instead clients should call {@link MIRunControl#createMIExecutionContext(IContainerDMContext, int)} |
| * to create instances of this context based on the thread ID. |
| * <p/> |
| * Classes extending {@link MIRunControl} may also extend this class to include |
| * additional information in the context. |
| * |
| * @param sessionId Session that this context belongs to. |
| * @param containerDmc The container that this context belongs to. |
| * @param threadId GDB/MI thread identifier. |
| */ |
| protected MIExecutionDMC(String sessionId, IContainerDMContext containerDmc, String threadId) { |
| super(sessionId, containerDmc != null ? new IDMContext[] { containerDmc } : new IDMContext[0]); |
| fThreadId = threadId; |
| } |
| |
| /** |
| * Returns the GDB/MI thread identifier of this context. |
| * @return |
| */ |
| @Override |
| public String getThreadId() { |
| return fThreadId; |
| } |
| |
| @Override |
| public String toString() { |
| return baseToString() + ".thread[" + fThreadId + "]"; //$NON-NLS-1$//$NON-NLS-2$ |
| } |
| |
| @Override |
| public boolean equals(Object obj) { |
| return super.baseEquals(obj) && ((MIExecutionDMC) obj).fThreadId.equals(fThreadId); |
| } |
| |
| @Override |
| public int hashCode() { |
| return super.baseHashCode() + fThreadId.hashCode(); |
| } |
| } |
| |
| @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 |
| protected 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. |
| */ |
| @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; |
| } |
| } |
| |
| /** |
| * @since 3.0 |
| */ |
| 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 3.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; |
| } |
| } |
| |
| @Immutable |
| protected static class ContainerSuspendedEvent extends SuspendedEvent implements IContainerSuspendedDMEvent { |
| final IExecutionDMContext[] triggeringDmcs; |
| |
| ContainerSuspendedEvent(IContainerDMContext containerDmc, MIStoppedEvent miInfo, |
| IExecutionDMContext triggeringDmc) { |
| super(containerDmc, miInfo); |
| this.triggeringDmcs = triggeringDmc != null ? new IExecutionDMContext[] { triggeringDmc } |
| : new IExecutionDMContext[0]; |
| } |
| |
| @Override |
| public IExecutionDMContext[] getTriggeringContexts() { |
| return triggeringDmcs; |
| } |
| } |
| |
| /** |
| * Indicates that the given container has been suspended on a breakpoint. |
| * @since 3.0 |
| */ |
| @Immutable |
| protected static class ContainerBreakpointHitEvent extends ContainerSuspendedEvent |
| implements IBreakpointHitDMEvent { |
| final private IBreakpointDMContext[] fBreakpoints; |
| |
| ContainerBreakpointHitEvent(IContainerDMContext containerDmc, MIBreakpointHitEvent miInfo, |
| IExecutionDMContext triggeringDmc, IBreakpointDMContext bpCtx) { |
| super(containerDmc, miInfo, triggeringDmc); |
| |
| fBreakpoints = new IBreakpointDMContext[] { bpCtx }; |
| } |
| |
| @Override |
| public IBreakpointDMContext[] getBreakpoints() { |
| return fBreakpoints; |
| } |
| } |
| |
| @Immutable |
| protected static class ResumedEvent extends RunControlEvent<IExecutionDMContext, MIRunningEvent> |
| implements IResumedDMEvent { |
| ResumedEvent(IExecutionDMContext ctx, MIRunningEvent miInfo) { |
| super(ctx, miInfo); |
| } |
| |
| @Override |
| public StateChangeReason getReason() { |
| 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; |
| } |
| } |
| |
| @Immutable |
| protected static class ContainerResumedEvent extends ResumedEvent implements IContainerResumedDMEvent { |
| final IExecutionDMContext[] triggeringDmcs; |
| |
| ContainerResumedEvent(IContainerDMContext containerDmc, MIRunningEvent miInfo, |
| IExecutionDMContext triggeringDmc) { |
| super(containerDmc, miInfo); |
| this.triggeringDmcs = triggeringDmc != null ? new IExecutionDMContext[] { triggeringDmc } |
| : new IExecutionDMContext[0]; |
| } |
| |
| @Override |
| public IExecutionDMContext[] getTriggeringContexts() { |
| return triggeringDmcs; |
| } |
| } |
| |
| @Immutable |
| protected static class StartedDMEvent extends RunControlEvent<IExecutionDMContext, MIThreadCreatedEvent> |
| implements IStartedDMEvent { |
| StartedDMEvent(IMIExecutionDMContext executionDmc, MIThreadCreatedEvent miInfo) { |
| super(executionDmc, miInfo); |
| } |
| } |
| |
| @Immutable |
| protected static class ExitedDMEvent extends RunControlEvent<IExecutionDMContext, MIThreadExitEvent> |
| implements IExitedDMEvent { |
| ExitedDMEvent(IMIExecutionDMContext executionDmc, MIThreadExitEvent miInfo) { |
| super(executionDmc, miInfo); |
| } |
| } |
| |
| private ICommandControlService fConnection; |
| private CommandCache fMICommandCache; |
| private CommandFactory fCommandFactory; |
| |
| // State flags |
| private boolean fSuspended = true; |
| private boolean fResumePending = false; |
| private boolean fStepping = false; |
| private boolean fTerminated = false; |
| /** |
| * @since 4.2 |
| */ |
| protected RunControlEvent<IExecutionDMContext, ?> fLatestEvent = null; |
| |
| /** |
| * What caused the state change. E.g., a signal was thrown. |
| */ |
| private 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. |
| */ |
| private String fStateChangeDetails; |
| |
| private IExecutionDMContext fStateChangeTriggeringContext; |
| /** |
| * Indicates that the next MIRunning event should be silenced. |
| * @since 4.3 |
| */ |
| protected boolean fDisableNextRunningEvent; |
| /** |
| * Indicates that the next MISignal (MIStopped) event should be silenced. |
| * @since 4.3 |
| */ |
| protected boolean fDisableNextSignalEvent; |
| /** |
| * Stores the silenced MIStopped event in case we need to use it |
| * for a failure. |
| * @since 4.3 |
| */ |
| protected MIStoppedEvent fSilencedSignalEvent; |
| |
| private static final String FAKE_THREAD_ID = "0"; //$NON-NLS-1$ |
| |
| public MIRunControl(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) { |
| fConnection = getServicesTracker().getService(ICommandControlService.class); |
| BufferedCommandControl bufferedCommandControl = new BufferedCommandControl(fConnection, getExecutor(), 2); |
| |
| fCommandFactory = getServicesTracker().getService(IMICommandControl.class).getCommandFactory(); |
| // This cache stores the result of a command when received; also, this cache |
| // is manipulated when receiving events. Currently, events are received after |
| // three scheduling of the executor, while command results after only one. This |
| // can cause problems because command results might be processed before an event |
| // that actually arrived before the command result. |
| // To solve this, we use a bufferedCommandControl that will delay the command |
| // result by two scheduling of the executor. |
| // See bug 280461 |
| fMICommandCache = new CommandCache(getSession(), bufferedCommandControl); |
| fMICommandCache.setContextAvailable(fConnection.getContext(), true); |
| getSession().addServiceEventListener(this, null); |
| rm.done(); |
| } |
| |
| @Override |
| public void shutdown(final RequestMonitor rm) { |
| getSession().removeServiceEventListener(this); |
| fMICommandCache.reset(); |
| super.shutdown(rm); |
| } |
| |
| public boolean isValid() { |
| return true; |
| } |
| |
| /** @since 2.0 */ |
| protected boolean isResumePending() { |
| return fResumePending; |
| } |
| |
| /** @since 2.0 */ |
| protected void setResumePending(boolean pending) { |
| fResumePending = pending; |
| } |
| |
| /** @since 2.0 */ |
| protected boolean isTerminated() { |
| return fTerminated; |
| } |
| |
| /** @since 2.0 */ |
| protected void setTerminated(boolean terminated) { |
| fTerminated = terminated; |
| } |
| |
| public CommandCache getCache() { |
| return fMICommandCache; |
| } |
| |
| /** @since 2.0 */ |
| protected ICommandControlService getConnection() { |
| return fConnection; |
| } |
| |
| /** |
| * @since 5.0 |
| */ |
| public IMIExecutionDMContext createMIExecutionContext(IContainerDMContext container, String threadId) { |
| return new MIExecutionDMC(getSession().getId(), container, threadId); |
| } |
| |
| /** |
| * @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(MIRunningEvent e) { |
| if (fDisableNextRunningEvent) { |
| fDisableNextRunningEvent = false; |
| // We don't broadcast this running event |
| return; |
| } |
| |
| if (fLatestEvent instanceof IResumedDMEvent) { |
| // Ignore multiple running events in a row. They will only slow down the UI |
| // for no added value. |
| return; |
| } |
| |
| IDMEvent<?> event = null; |
| // Find the container context, which is used in multi-threaded debugging. |
| IContainerDMContext containerDmc = DMContexts.getAncestorOfType(e.getDMContext(), IContainerDMContext.class); |
| if (containerDmc != null) { |
| // Set the triggering context only if it's different than the container context. |
| IExecutionDMContext triggeringCtx = !e.getDMContext().equals(containerDmc) ? e.getDMContext() : null; |
| event = new ContainerResumedEvent(containerDmc, e, triggeringCtx); |
| } else { |
| event = new ResumedEvent(e.getDMContext(), e); |
| } |
| getSession().dispatchEvent(event, 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) { |
| if (fDisableNextSignalEvent && e instanceof MISignalEvent) { |
| fDisableNextSignalEvent = false; |
| fSilencedSignalEvent = e; |
| // We don't broadcast this stopped event |
| return; |
| } |
| |
| 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); |
| } |
| } |
| final MIBreakpointDMContext bp = _bp; |
| |
| IDMEvent<?> event = null; |
| // Find the container context, which is used in multi-threaded debugging. |
| final IContainerDMContext containerDmc = DMContexts.getAncestorOfType(e.getDMContext(), |
| IContainerDMContext.class); |
| if (containerDmc != null) { |
| // Set the triggering context only if it's not the container context, since we are looking for a thread. |
| IExecutionDMContext triggeringCtx = !e.getDMContext().equals(containerDmc) ? e.getDMContext() : null; |
| if (triggeringCtx == null) { |
| // Still no thread. Let's ask the backend for one. |
| // We need a proper thread id for the debug view to select the right thread |
| // Bug 300096 comment #15 and Bug 302597 |
| getConnection().queueCommand(fCommandFactory.createCLIThread(containerDmc), |
| new DataRequestMonitor<CLIThreadInfo>(getExecutor(), null) { |
| @Override |
| protected void handleCompleted() { |
| IExecutionDMContext triggeringCtx2 = null; |
| if (isSuccess() && getData().getCurrentThread() != null) { |
| triggeringCtx2 = createMIExecutionContext(containerDmc, |
| getData().getCurrentThread()); |
| } |
| IDMEvent<?> event2 = bp != null |
| ? new ContainerBreakpointHitEvent(containerDmc, (MIBreakpointHitEvent) e, |
| triggeringCtx2, bp) |
| : new ContainerSuspendedEvent(containerDmc, e, triggeringCtx2); |
| getSession().dispatchEvent(event2, getProperties()); |
| } |
| }); |
| return; |
| } |
| if (bp != null) { |
| event = new ContainerBreakpointHitEvent(containerDmc, (MIBreakpointHitEvent) e, triggeringCtx, bp); |
| } else { |
| event = new ContainerSuspendedEvent(containerDmc, e, triggeringCtx); |
| } |
| } else { |
| if (bp != null) { |
| event = new BreakpointHitEvent(e.getDMContext(), (MIBreakpointHitEvent) e, bp); |
| } else { |
| event = new SuspendedEvent(e.getDMContext(), e); |
| } |
| } |
| getSession().dispatchEvent(event, getProperties()); |
| } |
| |
| /** |
| * Thread Created event handling |
| * When a new thread is created - OOB Event fired ~"[New Thread 1077300144 (LWP 7973)]\n" |
| * @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 = e.getStrId() != null ? createMIExecutionContext(containerDmc, e.getStrId()) |
| : null; |
| getSession().dispatchEvent(new StartedDMEvent(executionCtx, e), getProperties()); |
| } |
| |
| /** |
| * Thread exit event handling |
| * When a new thread is destroyed - OOB Event fired " |
| * @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 = e.getStrId() != null ? createMIExecutionContext(containerDmc, e.getStrId()) |
| : null; |
| 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(ContainerResumedEvent e) { |
| fSuspended = false; |
| fResumePending = false; |
| fStateChangeReason = e.getReason(); |
| fStateChangeDetails = null; // we have no details of interest for a resume |
| fMICommandCache.setContextAvailable(e.getDMContext(), false); |
| fLatestEvent = e; |
| |
| //fStateChangeTriggeringContext = e.getTriggeringContext(); |
| if (e.getReason().equals(StateChangeReason.STEP)) { |
| fStepping = true; |
| } else { |
| fMICommandCache.reset(); |
| } |
| } |
| |
| /** |
| * @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(ContainerSuspendedEvent e) { |
| fMICommandCache.setContextAvailable(e.getDMContext(), true); |
| fMICommandCache.reset(); |
| fStateChangeReason = e.getReason(); |
| fStateChangeDetails = e.getDetails(); |
| fStateChangeTriggeringContext = e.getTriggeringContexts().length != 0 ? e.getTriggeringContexts()[0] : null; |
| fSuspended = true; |
| fStepping = false; |
| fLatestEvent = e; |
| |
| fResumePending = 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. |
| * @since 1.1 |
| */ |
| @DsfServiceEventHandler |
| public void eventDispatched(ICommandControlShutdownDMEvent e) { |
| fTerminated = true; |
| } |
| |
| /** |
| * Event handler when New thread is created |
| * @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) { |
| |
| } |
| |
| /** |
| * @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(IExitedDMEvent e) { |
| if (e.getDMContext() instanceof IContainerDMContext) { |
| // When the process terminates, we should consider it as suspended |
| // In fact, we did get a stopped event, but our processing of it |
| // needs some cleaning up. Until then, let's trigger of this event |
| // Bug 342358 |
| fMICommandCache.setContextAvailable(e.getDMContext(), true); |
| fMICommandCache.reset(); |
| |
| fSuspended = true; |
| fStepping = false; |
| fResumePending = false; |
| } else { |
| fMICommandCache.reset(e.getDMContext()); |
| } |
| } |
| |
| /////////////////////////////////////////////////////////////////////////// |
| // AbstractService |
| @Override |
| protected BundleContext getBundleContext() { |
| return GdbPlugin.getBundleContext(); |
| } |
| |
| /////////////////////////////////////////////////////////////////////////// |
| // IRunControl |
| @Override |
| public void canResume(IExecutionDMContext context, DataRequestMonitor<Boolean> rm) { |
| rm.setData(doCanResume(context)); |
| rm.done(); |
| } |
| |
| /** @since 2.0 */ |
| protected boolean doCanResume(IExecutionDMContext context) { |
| return !fTerminated && isSuspended(context) && !fResumePending; |
| } |
| |
| @Override |
| public void canSuspend(IExecutionDMContext context, DataRequestMonitor<Boolean> rm) { |
| rm.setData(doCanSuspend(context)); |
| rm.done(); |
| } |
| |
| private boolean doCanSuspend(IExecutionDMContext context) { |
| return !fTerminated && !isSuspended(context); |
| } |
| |
| @Override |
| public boolean isSuspended(IExecutionDMContext context) { |
| return !fTerminated && fSuspended; |
| } |
| |
| @Override |
| public boolean isStepping(IExecutionDMContext context) { |
| return !fTerminated && fStepping; |
| } |
| |
| @Override |
| public void resume(final IExecutionDMContext context, final RequestMonitor rm) { |
| assert context != null; |
| |
| if (doCanResume(context)) { |
| ICommand<MIInfo> cmd = null; |
| if (context instanceof IContainerDMContext) { |
| cmd = fCommandFactory.createMIExecContinue(context); |
| } else { |
| IMIExecutionDMContext dmc = DMContexts.getAncestorOfType(context, IMIExecutionDMContext.class); |
| if (dmc == null) { |
| rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, INVALID_STATE, |
| "Given context: " + context + " is not an execution context.", null)); //$NON-NLS-1$ //$NON-NLS-2$ |
| rm.done(); |
| return; |
| } |
| cmd = fCommandFactory.createMIExecContinue(dmc);//, new String[0]); |
| } |
| |
| fResumePending = true; |
| // Cygwin GDB will accept commands and execute them after the step |
| // which is not what we want, so mark the target as unavailable |
| // as soon as we send a resume command. |
| fMICommandCache.setContextAvailable(context, false); |
| |
| fConnection.queueCommand(cmd, new DataRequestMonitor<MIInfo>(getExecutor(), rm) { |
| @Override |
| protected void handleFailure() { |
| fResumePending = false; |
| fMICommandCache.setContextAvailable(context, true); |
| |
| super.handleFailure(); |
| } |
| }); |
| } else { |
| 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(); |
| } |
| } |
| |
| @Override |
| public void suspend(IExecutionDMContext context, final RequestMonitor rm) { |
| assert context != null; |
| |
| if (doCanSuspend(context)) { |
| ICommand<MIInfo> cmd = null; |
| if (context instanceof IContainerDMContext) { |
| cmd = fCommandFactory.createMIExecInterrupt(context); |
| } else { |
| IMIExecutionDMContext dmc = DMContexts.getAncestorOfType(context, IMIExecutionDMContext.class); |
| if (dmc == null) { |
| rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, NOT_SUPPORTED, |
| "Given context: " + context + " is not an execution context.", null)); //$NON-NLS-1$ //$NON-NLS-2$ |
| rm.done(); |
| return; |
| } |
| cmd = fCommandFactory.createMIExecInterrupt(dmc); |
| } |
| fConnection.queueCommand(cmd, new DataRequestMonitor<MIInfo>(getExecutor(), rm)); |
| } else { |
| 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(); |
| } |
| } |
| |
| @Override |
| public void canStep(IExecutionDMContext context, StepType stepType, DataRequestMonitor<Boolean> rm) { |
| if (context instanceof IContainerDMContext) { |
| rm.setData(false); |
| rm.done(); |
| return; |
| } |
| canResume(context, rm); |
| } |
| |
| @Override |
| public void step(IExecutionDMContext context, StepType stepType, final RequestMonitor rm) { |
| step(context, stepType, true, rm); |
| } |
| |
| /** |
| * @since 4.2 |
| */ |
| protected void step(final IExecutionDMContext context, StepType stepType, boolean checkCanResume, |
| final RequestMonitor rm) { |
| assert context != null; |
| |
| IMIExecutionDMContext dmc = DMContexts.getAncestorOfType(context, IMIExecutionDMContext.class); |
| if (dmc == null) { |
| rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, NOT_SUPPORTED, |
| "Given context: " + context + " is not an execution context.", null)); //$NON-NLS-1$ //$NON-NLS-2$ |
| rm.done(); |
| return; |
| } |
| |
| if (checkCanResume && !doCanResume(context)) { |
| rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, INVALID_STATE, "Cannot resume context", null)); //$NON-NLS-1$ |
| rm.done(); |
| return; |
| } |
| |
| ICommand<MIInfo> cmd = null; |
| switch (stepType) { |
| case STEP_INTO: |
| cmd = fCommandFactory.createMIExecStep(dmc, 1); |
| 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 top 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.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, NOT_SUPPORTED, |
| "Cannot create context for command, stack service not available.", null)); //$NON-NLS-1$ |
| rm.done(); |
| return; |
| } |
| break; |
| case INSTRUCTION_STEP_INTO: |
| cmd = fCommandFactory.createMIExecStepInstruction(dmc, 1); |
| break; |
| case INSTRUCTION_STEP_OVER: |
| cmd = fCommandFactory.createMIExecNextInstruction(dmc, 1); |
| break; |
| default: |
| rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, INTERNAL_ERROR, "Given step type not supported", //$NON-NLS-1$ |
| null)); |
| rm.done(); |
| return; |
| } |
| |
| fResumePending = true; |
| fStepping = true; |
| fMICommandCache.setContextAvailable(context, false); |
| |
| fConnection.queueCommand(cmd, new DataRequestMonitor<MIInfo>(getExecutor(), rm) { |
| @Override |
| public void handleFailure() { |
| fResumePending = false; |
| fStepping = false; |
| fMICommandCache.setContextAvailable(context, true); |
| |
| super.handleFailure(); |
| } |
| }); |
| |
| } |
| |
| @Override |
| public void getExecutionContexts(final IContainerDMContext containerDmc, |
| final DataRequestMonitor<IExecutionDMContext[]> rm) { |
| fMICommandCache.execute(fCommandFactory.createMIThreadListIds(containerDmc), |
| new DataRequestMonitor<MIThreadListIdsInfo>(getExecutor(), rm) { |
| @Override |
| protected void handleSuccess() { |
| rm.setData(makeExecutionDMCs(containerDmc, getData())); |
| rm.done(); |
| } |
| }); |
| } |
| |
| private IExecutionDMContext[] makeExecutionDMCs(IContainerDMContext containerCtx, MIThreadListIdsInfo info) { |
| if (info.getStrThreadIds().length == 0) { |
| // Main thread always exist even if it is not reported by GDB. |
| // So create thread-id = 0 when no thread is reported. |
| // This hack is necessary to prevent AbstractMIControl from issuing a thread-select |
| // because it doesn't work if the application was not compiled with pthread. |
| return new IMIExecutionDMContext[] { createMIExecutionContext(containerCtx, FAKE_THREAD_ID) }; |
| } else { |
| IExecutionDMContext[] executionDmcs = new IMIExecutionDMContext[info.getStrThreadIds().length]; |
| for (int i = 0; i < info.getStrThreadIds().length; i++) { |
| executionDmcs[i] = createMIExecutionContext(containerCtx, info.getStrThreadIds()[i]); |
| } |
| return executionDmcs; |
| } |
| } |
| |
| @Override |
| public void getExecutionData(IExecutionDMContext dmc, DataRequestMonitor<IExecutionDMData> rm) { |
| if (dmc instanceof IContainerDMContext) { |
| rm.setData(new ExecutionData(fStateChangeReason, fStateChangeDetails)); |
| } else if (dmc instanceof IMIExecutionDMContext) { |
| boolean thisThreadCausedStateChange = dmc.equals(fStateChangeTriggeringContext); |
| StateChangeReason reason = thisThreadCausedStateChange ? fStateChangeReason : StateChangeReason.CONTAINER; |
| String details = thisThreadCausedStateChange ? fStateChangeDetails : null; |
| rm.setData(new ExecutionData(reason, details)); |
| } else { |
| rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, INVALID_HANDLE, |
| "Given context: " + dmc + " is not an execution context.", null)); //$NON-NLS-1$ //$NON-NLS-2$ |
| } |
| rm.done(); |
| } |
| |
| /** @since 3.0 */ |
| protected void runToLocation(IExecutionDMContext context, String location, boolean skipBreakpoints, |
| final RequestMonitor rm) { |
| // skipBreakpoints is not used at the moment. Implement later |
| |
| assert context != null; |
| |
| IMIExecutionDMContext dmc = DMContexts.getAncestorOfType(context, IMIExecutionDMContext.class); |
| if (dmc == null) { |
| rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, NOT_SUPPORTED, |
| "Given context: " + context + " is not an execution context.", null)); //$NON-NLS-1$ //$NON-NLS-2$ |
| rm.done(); |
| return; |
| } |
| |
| if (doCanResume(dmc)) { |
| fResumePending = true; |
| fMICommandCache.setContextAvailable(dmc, false); |
| fConnection.queueCommand(fCommandFactory.createMIExecUntil(dmc, location), |
| new DataRequestMonitor<MIInfo>(getExecutor(), rm)); |
| } else { |
| rm.setStatus( |
| new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, NOT_SUPPORTED, "Cannot resume given DMC.", null)); //$NON-NLS-1$ |
| rm.done(); |
| } |
| } |
| |
| /** @since 3.0 */ |
| protected 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 thread execution context.", null)); //$NON-NLS-1$ //$NON-NLS-2$ |
| rm.done(); |
| return; |
| } |
| |
| if (doCanResume(dmc)) { |
| fResumePending = true; |
| fMICommandCache.setContextAvailable(dmc, false); |
| fConnection.queueCommand(fCommandFactory.createCLIJump(dmc, location), |
| new DataRequestMonitor<MIInfo>(getExecutor(), rm) { |
| @Override |
| protected void handleFailure() { |
| fResumePending = false; |
| fMICommandCache.setContextAvailable(dmc, true); |
| |
| super.handleFailure(); |
| } |
| }); |
| } else { |
| rm.setStatus( |
| new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, NOT_SUPPORTED, "Cannot resume given DMC.", null)); //$NON-NLS-1$ |
| rm.done(); |
| } |
| } |
| |
| /* ****************************************************************************** |
| * Section to support operations even when the target is unavailable. |
| * |
| * Basically, we must make sure the container is suspended before making |
| * certain operations (currently breakpoints). If we don't, we must first |
| * suspend the container, then perform the specified operations, |
| * and finally resume the container. |
| * See http://bugs.eclipse.org/242943 |
| * and http://bugs.eclipse.org/282273 |
| * |
| * Note that for multi-process, the correct container must be suspended for the |
| * breakpoint to be inserted on that container. Not a big deal though, since |
| * a breakpointDmc is mapped to a specific container. Also, since we are in |
| * all-stop mode here, it does not really matter what we stop, everything will |
| * stop. |
| * 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; |
| } |
| } |
| |
| // Keep track of if the target was available or not when we started the operation |
| private boolean fTargetAvailable; |
| // The execution context that need to be available. |
| private IExecutionDMContext fExecutionDmc; |
| // 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 upate 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 the target is available to perform operations |
| * @since 3.0 |
| */ |
| protected boolean isTargetAvailable() { |
| return fTargetAvailable; |
| } |
| |
| /** @since 4.0 */ |
| protected void setTargetAvailable(boolean available) { |
| fTargetAvailable = available; |
| } |
| |
| /** |
| * Returns the execution context that needs to be suspended to perform the |
| * required operation. |
| * @since 3.0 |
| */ |
| protected IExecutionDMContext getContextToSuspend() { |
| return fExecutionDmc; |
| } |
| |
| /** @since 4.0 */ |
| protected void setContextToSuspend(IExecutionDMContext context) { |
| fExecutionDmc = context; |
| } |
| |
| /** |
| * 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 verifies if the execution context of interest |
| * is suspended or not. |
| * @since 3.0 |
| */ |
| protected class IsTargetAvailableStep extends Sequence.Step { |
| final IDMContext fCtx; |
| |
| public IsTargetAvailableStep(IDMContext ctx) { |
| fCtx = ctx; |
| } |
| |
| @Override |
| public void execute(final RequestMonitor rm) { |
| fExecutionDmc = DMContexts.getAncestorOfType(fCtx, IMIContainerDMContext.class); |
| if (fExecutionDmc != null) { |
| fTargetAvailable = isSuspended(fExecutionDmc); |
| rm.done(); |
| return; |
| } |
| |
| ICommandControlDMContext controlDmc = DMContexts.getAncestorOfType(fCtx, ICommandControlDMContext.class); |
| IProcesses processControl = getServicesTracker().getService(IProcesses.class); |
| processControl.getProcessesBeingDebugged(controlDmc, |
| new DataRequestMonitor<IDMContext[]>(getExecutor(), 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 |
| fTargetAvailable = true; |
| } else { |
| // In all-stop, if any process is suspended, then all of them are suspended |
| // so we only need to check the first process. |
| fExecutionDmc = (IExecutionDMContext) (getData()[0]); |
| fTargetAvailable = isSuspended(fExecutionDmc); |
| } |
| rm.done(); |
| } |
| }); |
| } |
| } |
| |
| /** |
| * If the execution context of interest is not suspended, this step |
| * will interrupt it. |
| * @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) { |
| if (!isTargetAvailable()) { |
| assert fDisableNextRunningEvent == false; |
| assert fDisableNextSignalEvent == false; |
| |
| // Don't broadcast the coming stopped signal event |
| fDisableNextSignalEvent = true; |
| suspend(getContextToSuspend(), new RequestMonitor(getExecutor(), rm) { |
| @Override |
| protected void handleFailure() { |
| // We weren't able to suspend, so abort the operation |
| fDisableNextSignalEvent = false; |
| super.handleFailure(); |
| } |
| }); |
| } else { |
| rm.done(); |
| } |
| } |
| |
| @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) { |
| if (!isTargetAvailable()) { |
| assert fDisableNextRunningEvent == false; |
| fDisableNextRunningEvent = true; |
| |
| // Can't use the resume() call because we 'silently' stopped |
| // so resume() will not know we are actually stopped |
| fConnection.queueCommand(fCommandFactory.createMIExecContinue(getContextToSuspend()), |
| new DataRequestMonitor<MIInfo>(getExecutor(), rm) { |
| @Override |
| protected void handleSuccess() { |
| fSilencedSignalEvent = null; |
| super.handleSuccess(); |
| } |
| |
| @Override |
| protected void handleFailure() { |
| // Darn, we're unable to restart the target. Must cleanup! |
| fDisableNextRunningEvent = false; |
| |
| // We must also sent the Stopped event that we had kept silent |
| if (fSilencedSignalEvent != null) { |
| eventDispatched(fSilencedSignalEvent); |
| fSilencedSignalEvent = null; |
| } else { |
| // Maybe the stopped event didn't arrive yet. |
| // We don't want to silence it anymore |
| fDisableNextSignalEvent = false; |
| } |
| |
| super.handleFailure(); |
| } |
| }); |
| } else { |
| // We didn't suspend the container, so we don't need to resume it |
| rm.done(); |
| } |
| } |
| } |
| |
| /* ****************************************************************************** |
| * End of section to support operations even when the target is unavailable. |
| * ******************************************************************************/ |
| |
| /** |
| * {@inheritDoc} |
| * @since 1.1 |
| */ |
| @Override |
| public void flushCache(IDMContext context) { |
| fMICommandCache.reset(context); |
| } |
| |
| 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.ALL_STOP; |
| } |
| |
| /** @since 4.0 */ |
| @Override |
| public boolean isTargetAcceptingCommands() { |
| // For all-stop mode: |
| // 1- if GDB is not terminated and |
| // 2- if execution is suspended and |
| // 3- if we didn't just send a resume/stop command, then |
| // we know GDB is accepting commands |
| return !fTerminated && fSuspended && !fResumePending; |
| } |
| |
| /** |
| * 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. |
| * @since 4.2 |
| */ |
| protected void determineDebuggerPath(IDMContext dmc, String hostPath, final DataRequestMonitor<String> rm) { |
| final IBreakpoints breakpoints = getServicesTracker().getService(IBreakpoints.class); |
| if (!(breakpoints instanceof IMIBreakpointPathAdjuster)) { |
| rm.done(hostPath); |
| return; |
| } |
| 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(((IMIBreakpointPathAdjuster) breakpoints).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(((IMIBreakpointPathAdjuster) breakpoints).adjustDebuggerPath(getData())); |
| } |
| }); |
| } |
| |
| /** |
| * @since 4.2 |
| */ |
| @Override |
| public void canStepIntoSelection(IExecutionDMContext context, String sourceFile, int lineNumber, |
| IFunctionDeclaration selectedFunction, DataRequestMonitor<Boolean> rm) { |
| rm.done(false); |
| } |
| |
| /** |
| * @since 4.2 |
| */ |
| @Override |
| public void stepIntoSelection(IExecutionDMContext context, String sourceFile, int lineNumber, |
| boolean skipBreakpoints, IFunctionDeclaration selectedFunction, RequestMonitor rm) { |
| IStatus status = new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, DebugException.REQUEST_FAILED, |
| "Step Into Selection not supported", null); //$NON-NLS-1$ |
| rm.done(status); |
| } |
| |
| } |