| /******************************************************************************* |
| * Copyright (c) 2006, 2014 Wind River Systems and others. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| * Contributors: |
| * Wind River Systems - initial API and implementation |
| * Ericsson - Modified for additional features in DSF Reference implementation |
| * Nokia - create and use backend service. |
| * Vladimir Prus (CodeSourcery) - Support for -data-read-memory-bytes (bug 322658) |
| * Jens Elmenthaler (Verigy) - Added Full GDB pretty-printing support (bug 302121) |
| * Mikhail Khodjaiants (Mentor Graphics) - Refactor common code in GDBControl* classes (bug 372795) |
| * Marc Khouzam (Ericsson) - Pass errorStream to startCommandProcessing() (Bug 350837) |
| * Mikhail Khodjaiants (Mentor Graphics) - Terminate should cancel the initialization sequence |
| * if it is still running (bug 373845) |
| * Marc Khouzam (Ericsson) - Terminate the session if we lose the connection to the remote target (bug 422586) |
| * Marc Khouzam (Ericsson) - Allow to override the creation of the ControlDMC (Bug 389945) |
| *******************************************************************************/ |
| package org.eclipse.cdt.dsf.gdb.service.command; |
| |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.OutputStream; |
| import java.util.ArrayList; |
| import java.util.Hashtable; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Map.Entry; |
| import java.util.Properties; |
| import java.util.concurrent.Future; |
| import java.util.concurrent.TimeUnit; |
| |
| import org.eclipse.cdt.dsf.concurrent.ConfinedToDsfExecutor; |
| 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.ImmediateExecutor; |
| import org.eclipse.cdt.dsf.concurrent.ImmediateRequestMonitor; |
| import org.eclipse.cdt.dsf.concurrent.RequestMonitor; |
| import org.eclipse.cdt.dsf.concurrent.RequestMonitorWithProgress; |
| import org.eclipse.cdt.dsf.concurrent.Sequence; |
| import org.eclipse.cdt.dsf.datamodel.AbstractDMEvent; |
| import org.eclipse.cdt.dsf.debug.service.IRunControl.IContainerDMContext; |
| import org.eclipse.cdt.dsf.debug.service.command.ICommandControl; |
| import org.eclipse.cdt.dsf.debug.service.command.ICommandControlService; |
| import org.eclipse.cdt.dsf.debug.service.command.ICommandResult; |
| import org.eclipse.cdt.dsf.debug.service.command.ICommandToken; |
| import org.eclipse.cdt.dsf.gdb.IGdbDebugConstants; |
| import org.eclipse.cdt.dsf.gdb.internal.GdbPlugin; |
| import org.eclipse.cdt.dsf.gdb.internal.Messages; |
| import org.eclipse.cdt.dsf.gdb.launching.FinalLaunchSequence; |
| import org.eclipse.cdt.dsf.gdb.service.IGDBBackend; |
| import org.eclipse.cdt.dsf.gdb.service.IGDBProcesses; |
| import org.eclipse.cdt.dsf.gdb.service.command.GdbCommandTimeoutManager.ICommandTimeoutListener; |
| import org.eclipse.cdt.dsf.mi.service.IMIBackend; |
| import org.eclipse.cdt.dsf.mi.service.IMIBackend.BackendStateChangedEvent; |
| import org.eclipse.cdt.dsf.mi.service.IMIBackend2; |
| import org.eclipse.cdt.dsf.mi.service.IMICommandControl; |
| import org.eclipse.cdt.dsf.mi.service.IMIRunControl; |
| import org.eclipse.cdt.dsf.mi.service.MIProcesses; |
| import org.eclipse.cdt.dsf.mi.service.MIProcesses.ContainerExitedDMEvent; |
| import org.eclipse.cdt.dsf.mi.service.command.AbstractCLIProcess; |
| import org.eclipse.cdt.dsf.mi.service.command.AbstractMIControl; |
| import org.eclipse.cdt.dsf.mi.service.command.CLIEventProcessor; |
| import org.eclipse.cdt.dsf.mi.service.command.CommandFactory; |
| import org.eclipse.cdt.dsf.mi.service.command.IEventProcessor; |
| import org.eclipse.cdt.dsf.mi.service.command.MIControlDMContext; |
| import org.eclipse.cdt.dsf.mi.service.command.MIRunControlEventProcessor; |
| import org.eclipse.cdt.dsf.mi.service.command.output.MIConsoleStreamOutput; |
| import org.eclipse.cdt.dsf.mi.service.command.output.MIConst; |
| import org.eclipse.cdt.dsf.mi.service.command.output.MIInfo; |
| import org.eclipse.cdt.dsf.mi.service.command.output.MILogStreamOutput; |
| import org.eclipse.cdt.dsf.mi.service.command.output.MIOOBRecord; |
| import org.eclipse.cdt.dsf.mi.service.command.output.MIOutput; |
| import org.eclipse.cdt.dsf.mi.service.command.output.MIResult; |
| import org.eclipse.cdt.dsf.mi.service.command.output.MIResultRecord; |
| import org.eclipse.cdt.dsf.mi.service.command.output.MIValue; |
| import org.eclipse.cdt.dsf.service.DsfServiceEventHandler; |
| import org.eclipse.cdt.dsf.service.DsfSession; |
| import org.eclipse.core.runtime.CoreException; |
| import org.eclipse.core.runtime.IProgressMonitor; |
| import org.eclipse.core.runtime.IStatus; |
| import org.eclipse.core.runtime.NullProgressMonitor; |
| import org.eclipse.core.runtime.Status; |
| import org.eclipse.debug.core.DebugPlugin; |
| import org.eclipse.debug.core.ILaunch; |
| import org.eclipse.debug.core.ILaunchConfiguration; |
| import org.eclipse.debug.core.IStatusHandler; |
| import org.osgi.framework.BundleContext; |
| |
| /** |
| * GDB Debugger control implementation. This implementation extends the |
| * base MI control implementation to provide the GDB-specific debugger |
| * features. This includes:<br> |
| * - CLI console support,<br> |
| * - inferior process status tracking.<br> |
| */ |
| public class GDBControl extends AbstractMIControl implements IGDBControl { |
| |
| private static final int STATUS_CODE_COMMAND_TIMED_OUT = 20100; |
| |
| /** |
| * Event indicating that the back end process has started. |
| */ |
| private static class GDBControlInitializedDMEvent extends AbstractDMEvent<ICommandControlDMContext> |
| implements ICommandControlInitializedDMEvent |
| { |
| public GDBControlInitializedDMEvent(ICommandControlDMContext context) { |
| super(context); |
| } |
| } |
| |
| /** |
| * Event indicating that the CommandControl (back end process) has terminated. |
| */ |
| private static class GDBControlShutdownDMEvent extends AbstractDMEvent<ICommandControlDMContext> |
| implements ICommandControlShutdownDMEvent |
| { |
| public GDBControlShutdownDMEvent(ICommandControlDMContext context) { |
| super(context); |
| } |
| } |
| |
| private class TimeoutListener implements ICommandTimeoutListener { |
| |
| @Override |
| public void commandTimedOut(final ICommandToken token) { |
| getExecutor().execute(new DsfRunnable() { |
| |
| @Override |
| public void run() { |
| GDBControl.this.commandTimedOut(token); |
| } |
| }); |
| } |
| } |
| |
| /** |
| * An event processor that handles some GDB life cycle events. |
| * Currently, it detects a lost connection with the remote. |
| */ |
| private class ControlEventProcessor implements IEventProcessor { |
| |
| public ControlEventProcessor() { |
| addCommandListener(this); |
| addEventListener(this); |
| } |
| |
| @Override |
| public void dispose() { |
| removeEventListener(this); |
| removeCommandListener(this); |
| } |
| |
| @Override |
| public void eventReceived(Object output) { |
| if (output instanceof MIOutput) { |
| verifyConnectionLost((MIOutput)output); |
| } else { |
| assert false; |
| } |
| } |
| |
| @Override |
| public void commandDone(ICommandToken token, ICommandResult cmdResult) { |
| if (cmdResult instanceof MIInfo) { |
| verifyConnectionLost(((MIInfo)cmdResult).getMIOutput()); |
| } else { |
| assert false; |
| } |
| } |
| |
| @Override |
| public void commandQueued(ICommandToken token) { |
| } |
| |
| @Override |
| public void commandSent(ICommandToken token) { |
| } |
| |
| @Override |
| public void commandRemoved(ICommandToken token) { |
| } |
| } |
| |
| private ICommandControlDMContext fControlDmc; |
| |
| private IGDBBackend fMIBackend; |
| |
| private IEventProcessor fMIEventProcessor; |
| private IEventProcessor fCLICommandProcessor; |
| private IEventProcessor fControlEventProcessor; |
| private AbstractCLIProcess fCLIProcess; |
| |
| private GdbCommandTimeoutManager fCommandTimeoutManager; |
| |
| private ICommandTimeoutListener fTimeoutListener = new TimeoutListener(); |
| |
| /** |
| * GDBControl is only used for GDB earlier that 7.0. Although -list-features |
| * is available in 6.8, it does not report anything we care about, so |
| * return empty list. |
| */ |
| private final List<String> fFeatures = new ArrayList<String>(); |
| |
| private Sequence fInitializationSequence; |
| |
| /** |
| * Indicator to distinguish whether this service is initialized. |
| * <code>fInitializationSequence</code> can not be used for this |
| * purpose because there is a period of time when the service is already |
| * initializing but the initialization sequence has not created yet. |
| */ |
| private boolean fInitialized = false; |
| |
| private boolean fTerminated; |
| |
| /** |
| * @since 3.0 |
| */ |
| public GDBControl(DsfSession session, ILaunchConfiguration config, CommandFactory factory) { |
| this(session, false, config, factory); |
| } |
| |
| /** |
| * @since 4.1 |
| */ |
| protected GDBControl(DsfSession session, boolean useThreadAndFrameOptions, ILaunchConfiguration config, CommandFactory factory) { |
| super(session, useThreadAndFrameOptions, factory); |
| } |
| |
| @Override |
| protected BundleContext getBundleContext() { |
| return GdbPlugin.getBundleContext(); |
| } |
| |
| @Override |
| public void initialize(final RequestMonitor requestMonitor) { |
| super.initialize( new ImmediateRequestMonitor(requestMonitor) { |
| @Override |
| protected void handleSuccess() { |
| doInitialize(requestMonitor); |
| } |
| }); |
| } |
| |
| private void doInitialize(final RequestMonitor requestMonitor) { |
| |
| fMIBackend = getServicesTracker().getService(IGDBBackend.class); |
| |
| // getId, called to create this context, uses the MIBackend service, |
| // which is why we must wait until we have MIBackend, before we can create the below context. |
| fControlDmc = createComandControlContext(); |
| |
| getExecutor().execute(getStartupSequence(requestMonitor)); |
| } |
| |
| @Override |
| public void shutdown(final RequestMonitor requestMonitor) { |
| getExecutor().execute(getShutdownSequence(new RequestMonitor(getExecutor(), requestMonitor) { |
| |
| @Override |
| protected void handleCompleted() { |
| GDBControl.super.shutdown(requestMonitor); |
| } |
| })); |
| } |
| |
| @Override |
| public String getId() { |
| return fMIBackend.getId(); |
| } |
| |
| /** |
| * Create the commandControl context. |
| * This method can be overridden to provide a different context. |
| * @since 4.4 |
| */ |
| protected ICommandControlDMContext createComandControlContext() { |
| return new GDBControlDMContext(getSession().getId(), getId()); |
| } |
| |
| @Deprecated |
| @Override |
| public MIControlDMContext getControlDMContext() { |
| assert fControlDmc instanceof MIControlDMContext; |
| if (fControlDmc instanceof MIControlDMContext) { |
| return (MIControlDMContext)fControlDmc; |
| } |
| return null; |
| } |
| |
| @Override |
| public ICommandControlDMContext getContext() { |
| return fControlDmc; |
| } |
| |
| @Override |
| public void terminate(final RequestMonitor rm) { |
| if (fTerminated) { |
| rm.done(); |
| return; |
| } |
| fTerminated = true; |
| |
| // If the initialization sequence is still running mark it as cancelled, |
| // to avoid reporting errors to the user, since we are terminating anyway. |
| if (fInitializationSequence != null) { |
| fInitializationSequence.getRequestMonitor().cancel(); |
| } |
| |
| // To fix bug 234467: |
| // Interrupt GDB in case the inferior is running. |
| // That way, the inferior will also be killed when we exit GDB. |
| // |
| IMIRunControl runControl = getServicesTracker().getService(IMIRunControl.class); |
| if (runControl != null && !runControl.isTargetAcceptingCommands()) { |
| fMIBackend.interrupt(); |
| } |
| |
| // Schedule a runnable to be executed 2 seconds from now. |
| // If we don't get a response to the quit command, this |
| // runnable will kill the task. |
| final Future<?> forceQuitTask = getExecutor().schedule( |
| new DsfRunnable() { |
| @Override |
| public void run() { |
| fMIBackend.destroy(); |
| rm.done(); |
| } |
| |
| @Override |
| protected boolean isExecutionRequired() { |
| return false; |
| } |
| }, |
| getGDBExitWaitTime(), TimeUnit.SECONDS); |
| |
| queueCommand( |
| getCommandFactory().createMIGDBExit(getContext()), |
| new DataRequestMonitor<MIInfo>(getExecutor(), rm) { |
| @Override |
| public void handleCompleted() { |
| if (isSuccess()) { |
| // Cancel the time out runnable (if it hasn't run yet). |
| if (forceQuitTask.cancel(false)) { |
| rm.done(); |
| } |
| } |
| // else: the forceQuitTask has or will handle it. |
| // It is good to wait for the forceQuitTask to trigger |
| // to leave enough time for the interrupt() to complete. |
| } |
| } |
| ); |
| } |
| |
| @Override |
| public AbstractCLIProcess getCLIProcess() { |
| return fCLIProcess; |
| } |
| |
| /** |
| * @since 2.0 |
| */ |
| @Override |
| public void setTracingStream(OutputStream tracingStream) { |
| setMITracingStream(tracingStream); |
| } |
| |
| /** @since 3.0 */ |
| @Override |
| public void setEnvironment(Properties props, boolean clear, final RequestMonitor rm) { |
| int count = 0; |
| CountingRequestMonitor countingRm = new CountingRequestMonitor(getExecutor(), rm); |
| |
| // First clear the environment if requested. |
| if (clear) { |
| count++; |
| queueCommand( |
| getCommandFactory().createCLIUnsetEnv(getContext()), |
| new DataRequestMonitor<MIInfo>(getExecutor(), countingRm)); |
| } |
| |
| // Now set the new variables |
| for (Entry<Object,Object> property : props.entrySet()) { |
| count++; |
| String name = (String)property.getKey(); |
| String value = (String)property.getValue(); |
| queueCommand( |
| getCommandFactory().createMIGDBSetEnv(getContext(), name, value), |
| new DataRequestMonitor<MIInfo>(getExecutor(), countingRm)); |
| } |
| countingRm.setDoneCount(count); |
| } |
| |
| /** |
| * @since 4.0 |
| */ |
| @Override |
| public void completeInitialization(final RequestMonitor rm) { |
| // We take the attributes from the launchConfiguration |
| ILaunch launch = (ILaunch)getSession().getModelAdapter(ILaunch.class); |
| Map<String, Object> attributes = null; |
| try { |
| attributes = launch.getLaunchConfiguration().getAttributes(); |
| } catch (CoreException e) {} |
| |
| // We need a RequestMonitorWithProgress, if we don't have one, we create one. |
| IProgressMonitor monitor = (rm instanceof RequestMonitorWithProgress) ? |
| ((RequestMonitorWithProgress)rm).getProgressMonitor() : new NullProgressMonitor(); |
| RequestMonitorWithProgress progressRm = new RequestMonitorWithProgress(getExecutor(), monitor) { |
| |
| @Override |
| protected void handleCompleted() { |
| fInitializationSequence = null; |
| fInitialized = true; |
| if (!isCanceled()) { |
| // Only set the status if the user has not cancelled the operation already. |
| rm.setStatus(getStatus()); |
| } else { |
| rm.cancel(); |
| } |
| rm.done(); |
| } |
| }; |
| |
| fInitializationSequence = getCompleteInitializationSequence(attributes, progressRm); |
| ImmediateExecutor.getInstance().execute(fInitializationSequence); |
| } |
| |
| /** |
| * Return the sequence that is to be used to complete the initialization of GDB. |
| * |
| * @param rm A RequestMonitorWithProgress that will indicate when the sequence is completed, but that |
| * also contains an IProgressMonitor to be able to cancel the launch. A NullProgressMonitor |
| * can be used if cancellation is not required. |
| * |
| * @since 4.0 |
| */ |
| protected Sequence getCompleteInitializationSequence(Map<String, Object> attributes, RequestMonitorWithProgress rm) { |
| return new FinalLaunchSequence(getSession(), attributes, rm); |
| } |
| |
| @DsfServiceEventHandler |
| public void eventDispatched(ICommandControlShutdownDMEvent e) { |
| // Handle our "GDB Exited" event and stop processing commands. |
| stopCommandProcessing(); |
| |
| // Before GDB 7.0, we have to send the containerExited event ourselves |
| IGDBProcesses procService = getServicesTracker().getService(IGDBProcesses.class); |
| if (procService != null) { |
| IContainerDMContext processContainerDmc = procService.createContainerContextFromGroupId(getContext(), MIProcesses.UNIQUE_GROUP_ID); |
| getSession().dispatchEvent( |
| new ContainerExitedDMEvent(processContainerDmc), getProperties()); |
| } |
| } |
| |
| @DsfServiceEventHandler |
| public void eventDispatched(BackendStateChangedEvent e) { |
| if (e.getState() == IMIBackend.State.TERMINATED && e.getBackendId().equals(fMIBackend.getId())) { |
| // Handle "GDB Exited" event, just relay to following event. |
| getSession().dispatchEvent(new GDBControlShutdownDMEvent(getContext()), getProperties()); |
| } |
| } |
| |
| public static class InitializationShutdownStep extends Sequence.Step { |
| public enum Direction { INITIALIZING, SHUTTING_DOWN } |
| |
| private Direction fDirection; |
| public InitializationShutdownStep(Direction direction) { fDirection = direction; } |
| |
| @Override |
| final public void execute(RequestMonitor requestMonitor) { |
| if (fDirection == Direction.INITIALIZING) { |
| initialize(requestMonitor); |
| } else { |
| shutdown(requestMonitor); |
| } |
| } |
| |
| @Override |
| final public void rollBack(RequestMonitor requestMonitor) { |
| if (fDirection == Direction.INITIALIZING) { |
| shutdown(requestMonitor); |
| } else { |
| super.rollBack(requestMonitor); |
| } |
| } |
| |
| protected void initialize(RequestMonitor requestMonitor) { |
| requestMonitor.done(); |
| } |
| |
| protected void shutdown(RequestMonitor requestMonitor) { |
| requestMonitor.done(); |
| } |
| } |
| |
| protected class CommandMonitoringStep extends InitializationShutdownStep { |
| CommandMonitoringStep(Direction direction) { super(direction); } |
| |
| @Override |
| protected void initialize(final RequestMonitor requestMonitor) { |
| doCommandMonitoringStep(requestMonitor); |
| } |
| |
| @Override |
| protected void shutdown(RequestMonitor requestMonitor) { |
| undoCommandMonitoringStep(requestMonitor); |
| } |
| } |
| |
| /** @since 5.1 */ |
| protected void doCommandMonitoringStep(final RequestMonitor requestMonitor) { |
| InputStream errorStream = null; |
| if (fMIBackend instanceof IMIBackend2) { |
| errorStream = ((IMIBackend2)fMIBackend).getMIErrorStream(); |
| } |
| startCommandProcessing(fMIBackend.getMIInputStream(), fMIBackend.getMIOutputStream(), errorStream); |
| requestMonitor.done(); |
| } |
| |
| /** @since 5.1 */ |
| protected void undoCommandMonitoringStep(RequestMonitor requestMonitor) { |
| stopCommandProcessing(); |
| requestMonitor.done(); |
| } |
| |
| protected class CommandProcessorsStep extends InitializationShutdownStep { |
| CommandProcessorsStep(Direction direction) { super(direction); } |
| |
| @Override |
| public void initialize(final RequestMonitor requestMonitor) { |
| doCommandProcessorsStep(requestMonitor); |
| } |
| |
| @Override |
| protected void shutdown(RequestMonitor requestMonitor) { |
| undoCommandProcessorsStep(requestMonitor); |
| } |
| } |
| |
| /** @since 5.1 */ |
| protected void doCommandProcessorsStep(final RequestMonitor requestMonitor) { |
| try { |
| fCLIProcess = new GDBBackendCLIProcess(GDBControl.this, fMIBackend); |
| } |
| catch(IOException e) { |
| requestMonitor.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, IDsfStatusConstants.REQUEST_FAILED, "Failed to create CLI Process", e)); //$NON-NLS-1$ |
| requestMonitor.done(); |
| return; |
| } |
| |
| fCLICommandProcessor = createCLIEventProcessor(GDBControl.this, getContext()); |
| fMIEventProcessor = createMIRunControlEventProcessor(GDBControl.this, getContext()); |
| fControlEventProcessor = createControlEventProcessor(); |
| |
| requestMonitor.done(); |
| } |
| |
| /** @since 5.1 */ |
| protected void undoCommandProcessorsStep(RequestMonitor requestMonitor) { |
| fControlEventProcessor.dispose(); |
| fCLICommandProcessor.dispose(); |
| fMIEventProcessor.dispose(); |
| fCLIProcess.dispose(); |
| |
| requestMonitor.done(); |
| } |
| |
| /** |
| * @since 4.1 |
| */ |
| protected class CommandTimeoutStep extends InitializationShutdownStep { |
| CommandTimeoutStep( Direction direction ) { |
| super( direction ); |
| } |
| |
| @Override |
| public void initialize( final RequestMonitor requestMonitor ) { |
| doCommandTimeoutStep(requestMonitor); |
| } |
| |
| @Override |
| protected void shutdown( RequestMonitor requestMonitor ) { |
| undoCommandTimeoutStep(requestMonitor); |
| } |
| } |
| |
| /** @since 5.1 */ |
| protected void doCommandTimeoutStep(final RequestMonitor requestMonitor) { |
| fCommandTimeoutManager = createCommandTimeoutManager( GDBControl.this ); |
| if (fCommandTimeoutManager != null) { |
| fCommandTimeoutManager.addCommandTimeoutListener(fTimeoutListener); |
| } |
| requestMonitor.done(); |
| } |
| |
| /** @since 5.1 */ |
| protected void undoCommandTimeoutStep(RequestMonitor requestMonitor) { |
| if ( fCommandTimeoutManager != null ) { |
| fCommandTimeoutManager.removeCommandTimeoutListener(fTimeoutListener); |
| fCommandTimeoutManager.dispose(); |
| } |
| requestMonitor.done(); |
| } |
| |
| protected class RegisterStep extends InitializationShutdownStep { |
| RegisterStep(Direction direction) { super(direction); } |
| @Override |
| public void initialize(final RequestMonitor requestMonitor) { |
| doRegisterStep(requestMonitor); |
| } |
| |
| @Override |
| protected void shutdown(RequestMonitor requestMonitor) { |
| undoRegisterStep(requestMonitor); |
| } |
| } |
| |
| /** @since 5.1 */ |
| protected void doRegisterStep(final RequestMonitor requestMonitor) { |
| getSession().addServiceEventListener(GDBControl.this, null); |
| register( |
| new String[]{ ICommandControl.class.getName(), |
| ICommandControlService.class.getName(), |
| IMICommandControl.class.getName(), |
| AbstractMIControl.class.getName(), |
| IGDBControl.class.getName() }, |
| new Hashtable<String,String>()); |
| getSession().dispatchEvent(new GDBControlInitializedDMEvent(getContext()), getProperties()); |
| requestMonitor.done(); |
| } |
| |
| /** @since 5.1 */ |
| protected void undoRegisterStep(RequestMonitor requestMonitor) { |
| unregister(); |
| getSession().removeServiceEventListener(GDBControl.this); |
| requestMonitor.done(); |
| } |
| |
| /** @since 4.0 */ |
| @Override |
| public List<String> getFeatures() { |
| return fFeatures; |
| } |
| |
| /** |
| * @since 4.0 |
| */ |
| @Override |
| public void enablePrettyPrintingForMIVariableObjects(RequestMonitor rm) { |
| rm.done(); |
| } |
| |
| /** |
| * @since 4.0 |
| */ |
| @Override |
| public void setPrintPythonErrors(boolean enabled, RequestMonitor rm) { |
| rm.done(); |
| } |
| |
| /** |
| * @since 4.1 |
| */ |
| protected Sequence getStartupSequence(final RequestMonitor requestMonitor) { |
| final Sequence.Step[] initializeSteps = new Sequence.Step[] { |
| new CommandMonitoringStep(InitializationShutdownStep.Direction.INITIALIZING), |
| new CommandProcessorsStep(InitializationShutdownStep.Direction.INITIALIZING), |
| new CommandTimeoutStep(InitializationShutdownStep.Direction.INITIALIZING), |
| new RegisterStep(InitializationShutdownStep.Direction.INITIALIZING), |
| }; |
| |
| return new Sequence(getExecutor(), requestMonitor) { |
| @Override public Step[] getSteps() { return initializeSteps; } |
| }; |
| } |
| |
| /** |
| * @since 4.1 |
| */ |
| protected Sequence getShutdownSequence(RequestMonitor requestMonitor) { |
| final Sequence.Step[] shutdownSteps = new Sequence.Step[] { |
| new RegisterStep(InitializationShutdownStep.Direction.SHUTTING_DOWN), |
| new CommandTimeoutStep(InitializationShutdownStep.Direction.SHUTTING_DOWN), |
| new CommandProcessorsStep(InitializationShutdownStep.Direction.SHUTTING_DOWN), |
| new CommandMonitoringStep(InitializationShutdownStep.Direction.SHUTTING_DOWN), |
| }; |
| return new Sequence(getExecutor(), requestMonitor) { |
| @Override public Step[] getSteps() { return shutdownSteps; } |
| }; |
| } |
| |
| /** |
| * @since 4.1 |
| */ |
| protected IEventProcessor createCLIEventProcessor(ICommandControlService connection, ICommandControlDMContext controlDmc) { |
| return new CLIEventProcessor(connection, controlDmc); |
| } |
| |
| /** |
| * @since 4.1 |
| */ |
| protected IEventProcessor createMIRunControlEventProcessor(AbstractMIControl connection, ICommandControlDMContext controlDmc) { |
| return new MIRunControlEventProcessor(connection, controlDmc); |
| } |
| |
| /** @since 4.3 */ |
| protected IEventProcessor createControlEventProcessor() { |
| return new ControlEventProcessor(); |
| } |
| |
| /** |
| * @since 4.1 |
| */ |
| protected void setFeatures(List<String> features) { |
| fFeatures.clear(); |
| fFeatures.addAll(features); |
| } |
| |
| /** |
| * @since 4.1 |
| */ |
| protected GdbCommandTimeoutManager createCommandTimeoutManager(ICommandControl commandControl) { |
| GdbCommandTimeoutManager manager = new GdbCommandTimeoutManager(commandControl); |
| manager.initialize(); |
| return manager; |
| } |
| |
| /** |
| * @since 4.1 |
| */ |
| @ConfinedToDsfExecutor("this.getExecutor()") |
| protected void commandTimedOut(ICommandToken token) { |
| String commandText = token.getCommand().toString(); |
| if (commandText.endsWith("\n")) //$NON-NLS-1$ |
| commandText = commandText.substring(0, commandText.length() - 1); |
| final String errorMessage = String.format("Command '%s' is timed out", commandText); //$NON-NLS-1$ |
| commandFailed(token, STATUS_CODE_COMMAND_TIMED_OUT, errorMessage); |
| |
| // If the timeout occurs while the launch sequence is running |
| // the error will be reported by the launcher's error reporting mechanism. |
| // We need to show the error message only when the session is initialized. |
| if (isInitialized()) { |
| // The session is terminated if a command is timed out. |
| terminate(new RequestMonitor(getExecutor(), null) { |
| |
| @Override |
| protected void handleErrorOrWarning() { |
| GdbPlugin.getDefault().getLog().log(getStatus()); |
| super.handleErrorOrWarning(); |
| }; |
| } ); |
| |
| IStatus status = new Status( |
| IStatus.ERROR, |
| GdbPlugin.PLUGIN_ID, |
| IGdbDebugConstants.STATUS_HANDLER_CODE, |
| String.format( Messages.GDBControl_Session_is_terminated, errorMessage ), |
| null); |
| IStatusHandler statusHandler = DebugPlugin.getDefault().getStatusHandler(status); |
| if (statusHandler != null) { |
| try { |
| statusHandler.handleStatus(status, null); |
| } |
| catch(CoreException e) { |
| GdbPlugin.getDefault().getLog().log(e.getStatus()); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Parse output from GDB to determine if the connection to the remote was lost. |
| * |
| * @param output The output received from GDB that must be parsed |
| * to determine if the connection to the remote was lost. |
| * |
| * @return True if the connection was lost, false otherwise. |
| * @since 4.3 |
| */ |
| protected boolean verifyConnectionLost(MIOutput output) { |
| boolean connectionLost = false; |
| String reason = null; |
| |
| // Check if any command has a result that indicates a lost connection. |
| // This can happen as a normal command result, or as an out-of-band event. |
| // The out-of-band case can happen when GDB sends another response to |
| // a previous command. This case can happen, for example, in all-stop |
| // when sending an -exec-continue and then killing gdbserver while connected |
| // to a process; in that case a second result to -exec-continue will be sent |
| // and will indicate the remote connection is closed. |
| MIResultRecord rr = output.getMIResultRecord(); |
| if (rr != null) { |
| String state = rr.getResultClass(); |
| if ("error".equals(state)) { //$NON-NLS-1$ |
| MIResult[] results = rr.getMIResults(); |
| for (MIResult result : results) { |
| String var = result.getVariable(); |
| if (var.equals("msg")) { //$NON-NLS-1$ |
| MIValue value = result.getMIValue(); |
| if (value instanceof MIConst) { |
| String str = ((MIConst)value).getCString(); |
| if (str != null && str.startsWith("Remote connection closed")) { //$NON-NLS-1$ |
| connectionLost = true; |
| reason = str; |
| } |
| } |
| } |
| } |
| } |
| } else { |
| // Only check for out-of-band records when there is no result record. |
| // This is because OOBRecords that precede a result are included in the |
| // result output even though they have already been processed. To avoid |
| // processing them a second time, we only handle them when not dealing |
| // with a result record. |
| for (MIOOBRecord oobr : output.getMIOOBRecords()) { |
| if (oobr instanceof MIConsoleStreamOutput) { |
| MIConsoleStreamOutput out = (MIConsoleStreamOutput) oobr; |
| String str = out.getString(); |
| if (str != null && str.startsWith("Ending remote debugging")) { //$NON-NLS-1$ |
| // This happens if the user types 'disconnect' in the console |
| connectionLost = true; |
| reason = str; |
| break; |
| |
| // Note that this will not trigger in the case where a |
| // -target-disconnect is used. This is a case that CDT handles |
| // explicitly as it is an MI command; we shouldn't have to catch it here. |
| // In fact, catching it here and terminating the session would break |
| // the workaround for GDB 7.2 handled by GDBProcesses_7_2.needFixForGDB72Bug352998() |
| } |
| } else if (oobr instanceof MILogStreamOutput) { |
| MILogStreamOutput out = (MILogStreamOutput) oobr; |
| String str = out.getString().trim(); |
| if (str != null && str.startsWith("Remote connection closed")) { //$NON-NLS-1$ |
| // This happens if gdbserver is killed or dies |
| connectionLost = true; |
| reason = str; |
| break; |
| } |
| } |
| } |
| } |
| |
| if (connectionLost) { |
| connectionLost(reason); |
| return true; |
| } |
| |
| return false; |
| } |
| |
| /** |
| * Handle the loss of the connection to the remote. |
| * The default implementation terminates the debug session. |
| * |
| * @param reason A string indicating as much as possible why the connection was lost. Can be null. |
| * @since 4.3 |
| */ |
| protected void connectionLost(String reason) { |
| terminate(new ImmediateRequestMonitor() { |
| @Override |
| protected void handleErrorOrWarning() { |
| GdbPlugin.getDefault().getLog().log(getStatus()); |
| super.handleErrorOrWarning(); |
| }; |
| }); |
| } |
| |
| /** |
| * @since 4.1 |
| */ |
| protected boolean isInitialized() { |
| return fInitialized; |
| } |
| |
| /** |
| * Returns the time (in seconds) the debugger will wait for "gdb-exit" to complete. |
| * |
| * @since 4.2 |
| */ |
| protected int getGDBExitWaitTime() { |
| return 2; |
| } |
| } |