/*******************************************************************************
* Copyright (c) 2006, 2011 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
* Ericsson - New version for 7_0
* Vladimir Prus (CodeSourcery) - Support for -data-read-memory-bytes (bug 322658)
* Jens Elmenthaler (Verigy) - Added Full GDB pretty-printing support (bug 302121)
*******************************************************************************/
package org.eclipse.cdt.dsf.gdb.service.command;
import java.io.IOException;
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.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.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.command.ICommandControl;
import org.eclipse.cdt.dsf.debug.service.command.ICommandControlService;
import org.eclipse.cdt.dsf.gdb.internal.GdbPlugin;
import org.eclipse.cdt.dsf.gdb.launching.FinalLaunchSequence;
import org.eclipse.cdt.dsf.gdb.service.IGDBBackend;
import org.eclipse.cdt.dsf.gdb.service.IGDBTraceControl.ITraceRecordSelectedChangedDMEvent;
import org.eclipse.cdt.dsf.mi.service.IMIBackend;
import org.eclipse.cdt.dsf.mi.service.IMIBackend.BackendStateChangedEvent;
import org.eclipse.cdt.dsf.mi.service.IMICommandControl;
import org.eclipse.cdt.dsf.mi.service.IMIRunControl;
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_7_0;
import org.eclipse.cdt.dsf.mi.service.command.CommandFactory;
import org.eclipse.cdt.dsf.mi.service.command.MIControlDMContext;
import org.eclipse.cdt.dsf.mi.service.command.MIRunControlEventProcessor_7_0;
import org.eclipse.cdt.dsf.mi.service.command.output.MIInfo;
import org.eclipse.cdt.dsf.mi.service.command.output.MIListFeaturesInfo;
import org.eclipse.cdt.dsf.service.DsfServiceEventHandler;
import org.eclipse.cdt.dsf.service.DsfSession;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Status;
import org.eclipse.debug.core.ILaunch;
import org.eclipse.debug.core.ILaunchConfiguration;
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_7_0 extends AbstractMIControl implements IGDBControl {
/**
* 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 GDBControlDMContext fControlDmc;
private IGDBBackend fMIBackend;
private MIRunControlEventProcessor_7_0 fMIEventProcessor;
private CLIEventProcessor_7_0 fCLICommandProcessor;
private AbstractCLIProcess fCLIProcess;
private List<String> fFeatures = new ArrayList<String>();
private boolean fTerminated;
/**
* @since 3.0
*/
public GDBControl_7_0(DsfSession session, ILaunchConfiguration config, CommandFactory factory) {
super(session, true, factory);
}
@Override
protected BundleContext getBundleContext() {
return GdbPlugin.getBundleContext();
}
@Override
public void initialize(final RequestMonitor requestMonitor) {
super.initialize( new RequestMonitor(ImmediateExecutor.getInstance(), requestMonitor) {
@Override
protected void handleSuccess() {
doInitialize(requestMonitor);
}
});
}
private void doInitialize(final RequestMonitor requestMonitor) {
fMIBackend = getServicesTracker().getService(IGDBBackend.class);
// getId uses the MIBackend service, which is why we must wait until we
// have it, before we can create this context.
fControlDmc = new GDBControlDMContext(getSession().getId(), getId());
final Sequence.Step[] initializeSteps = new Sequence.Step[] {
new CommandMonitoringStep(InitializationShutdownStep.Direction.INITIALIZING),
new CommandProcessorsStep(InitializationShutdownStep.Direction.INITIALIZING),
new ListFeaturesStep(InitializationShutdownStep.Direction.INITIALIZING),
new RegisterStep(InitializationShutdownStep.Direction.INITIALIZING),
};
Sequence startupSequence = new Sequence(getExecutor(), requestMonitor) {
@Override public Step[] getSteps() { return initializeSteps; }
};
getExecutor().execute(startupSequence);
}
@Override
public void shutdown(final RequestMonitor requestMonitor) {
final Sequence.Step[] shutdownSteps = new Sequence.Step[] {
new RegisterStep(InitializationShutdownStep.Direction.SHUTTING_DOWN),
new ListFeaturesStep(InitializationShutdownStep.Direction.SHUTTING_DOWN),
new CommandProcessorsStep(InitializationShutdownStep.Direction.SHUTTING_DOWN),
new CommandMonitoringStep(InitializationShutdownStep.Direction.SHUTTING_DOWN),
};
Sequence shutdownSequence =
new Sequence(getExecutor(),
new RequestMonitor(getExecutor(), requestMonitor) {
@Override
protected void handleCompleted() {
GDBControl_7_0.super.shutdown(requestMonitor);
}
}) {
@Override public Step[] getSteps() { return shutdownSteps; }
};
getExecutor().execute(shutdownSequence);
}
public String getId() {
return fMIBackend.getId();
}
@Override
public MIControlDMContext getControlDMContext() {
return fControlDmc;
}
public ICommandControlDMContext getContext() {
return fControlDmc;
}
public void terminate(final RequestMonitor rm) {
if (fTerminated) {
rm.done();
return;
}
fTerminated = true;
// 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() {
public void run() {
fMIBackend.destroy();
rm.done();
}
@Override
protected boolean isExecutionRequired() {
return false;
}
},
2, TimeUnit.SECONDS);
queueCommand(
getCommandFactory().createMIGDBExit(fControlDmc),
new DataRequestMonitor<MIInfo>(getExecutor(), rm) {
@Override
public void handleCompleted() {
if (isSuccess()) {
// Cancel the time out runnable (if it hasn't run yet).
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.
}
}
);
}
private void listFeatures(final RequestMonitor requestMonitor) {
queueCommand(
getCommandFactory().createMIListFeatures(fControlDmc),
new DataRequestMonitor<MIListFeaturesInfo>(getExecutor(), requestMonitor) {
@Override
public void handleSuccess() {
fFeatures = getData().getFeatures();
super.handleSuccess();
}
});
}
public AbstractCLIProcess getCLIProcess() {
return fCLIProcess;
}
/**
* @since 2.0
*/
public void setTracingStream(OutputStream tracingStream) {
setMITracingStream(tracingStream);
}
/** @since 3.0 */
public void setEnvironment(Properties props, boolean clear, 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
*/
@SuppressWarnings("unchecked")
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.
RequestMonitorWithProgress progressRm;
if (rm instanceof RequestMonitorWithProgress) {
progressRm = (RequestMonitorWithProgress)rm;
} else {
progressRm = new RequestMonitorWithProgress(getExecutor(), new NullProgressMonitor()) {
@Override
protected void handleCompleted() {
rm.setStatus(getStatus());
rm.done();
}
};
}
ImmediateExecutor.getInstance().execute(getCompleteInitializationSequence(attributes, progressRm));
}
/**
* 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);
}
/**@since 4.0 */
public List<String> getFeatures() {
return fFeatures;
}
@DsfServiceEventHandler
public void eventDispatched(ICommandControlShutdownDMEvent e) {
// Handle our "GDB Exited" event and stop processing commands.
stopCommandProcessing();
}
@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(fControlDmc), getProperties());
}
}
/** @since 3.0 */
@DsfServiceEventHandler
public void eventDispatched(ITraceRecordSelectedChangedDMEvent e) {
}
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) {
startCommandProcessing(fMIBackend.getMIInputStream(), fMIBackend.getMIOutputStream());
requestMonitor.done();
}
@Override
protected void shutdown(RequestMonitor requestMonitor) {
stopCommandProcessing();
requestMonitor.done();
}
}
protected class CommandProcessorsStep extends InitializationShutdownStep {
CommandProcessorsStep(Direction direction) { super(direction); }
@Override
public void initialize(final RequestMonitor requestMonitor) {
try {
fCLIProcess = new GDBBackendCLIProcess(GDBControl_7_0.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 = new CLIEventProcessor_7_0(GDBControl_7_0.this, fControlDmc);
fMIEventProcessor = new MIRunControlEventProcessor_7_0(GDBControl_7_0.this, fControlDmc);
requestMonitor.done();
}
@Override
protected void shutdown(RequestMonitor requestMonitor) {
fCLICommandProcessor.dispose();
fMIEventProcessor.dispose();
fCLIProcess.dispose();
requestMonitor.done();
}
}
/** @since 4.0 */
protected class ListFeaturesStep extends InitializationShutdownStep {
ListFeaturesStep(Direction direction) { super(direction); }
@Override
protected void initialize(final RequestMonitor requestMonitor) {
listFeatures(requestMonitor);
}
@Override
protected void shutdown(RequestMonitor requestMonitor) {
requestMonitor.done();
}
}
protected class RegisterStep extends InitializationShutdownStep {
RegisterStep(Direction direction) { super(direction); }
@Override
public void initialize(final RequestMonitor requestMonitor) {
getSession().addServiceEventListener(GDBControl_7_0.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(fControlDmc), getProperties());
requestMonitor.done();
}
@Override
protected void shutdown(RequestMonitor requestMonitor) {
unregister();
getSession().removeServiceEventListener(GDBControl_7_0.this);
requestMonitor.done();
}
}
/**
* @since 4.0
*/
public void enablePrettyPrintingForMIVariableObjects(
final RequestMonitor rm) {
queueCommand(
getCommandFactory().createMIEnablePrettyPrinting(fControlDmc),
new DataRequestMonitor<MIInfo>(getExecutor(), rm));
}
/**
* @since 4.0
*/
public void setPrintPythonErrors(boolean enabled, RequestMonitor rm) {
String subCommand = "set python print-stack " + (enabled ? "on" : "off"); //$NON-NLS-1$//$NON-NLS-2$//$NON-NLS-3$
queueCommand(
getCommandFactory().createCLIMaintenance(fControlDmc, subCommand),
new DataRequestMonitor<MIInfo>(getExecutor(), rm));
}
}