blob: 12b987b83268739a7f6ee38000e12c3e6c474ef9 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2008, 2016 QNX Software 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:
* QNX Software Systems - Initial API and implementation
* Windriver and Ericsson - Updated for DSF
* IBM Corporation
* Ericsson - Added support for Mac OS
* Ericsson - Added support for post-mortem trace files
* Abeer Bagul (Tensilica) - Allow to better override GdbLaunch (bug 339550)
* Anton Gorenkov - Need to use a process factory (Bug 210366)
* Marc Khouzam (Ericsson) - Cleanup the launch if it is cancelled (Bug 374374)
* Marc-Andre Laperle - Bug 382462
* Marc Khouzam (Ericsson - Show GDB version in debug view node label (Bug 455408)
*******************************************************************************/
package org.eclipse.cdt.dsf.gdb.launching;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import org.eclipse.cdt.core.model.ICProject;
import org.eclipse.cdt.dsf.concurrent.DataRequestMonitor;
import org.eclipse.cdt.dsf.concurrent.ImmediateExecutor;
import org.eclipse.cdt.dsf.concurrent.Query;
import org.eclipse.cdt.dsf.concurrent.RequestMonitorWithProgress;
import org.eclipse.cdt.dsf.concurrent.Sequence;
import org.eclipse.cdt.dsf.concurrent.ThreadSafe;
import org.eclipse.cdt.dsf.debug.service.IDsfDebugServicesFactory;
import org.eclipse.cdt.dsf.debug.sourcelookup.DsfSourceLookupDirector;
import org.eclipse.cdt.dsf.gdb.IGDBLaunchConfigurationConstants;
import org.eclipse.cdt.dsf.gdb.internal.GdbPlugin;
import org.eclipse.cdt.dsf.gdb.service.GdbDebugServicesFactory;
import org.eclipse.cdt.dsf.gdb.service.SessionType;
import org.eclipse.cdt.dsf.gdb.service.command.IGDBControl;
import org.eclipse.cdt.dsf.service.DsfServicesTracker;
import org.eclipse.cdt.dsf.service.DsfSession;
import org.eclipse.cdt.launch.AbstractCLaunchDelegate2;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
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.core.runtime.SubProgressMonitor;
import org.eclipse.debug.core.DebugException;
import org.eclipse.debug.core.DebugPlugin;
import org.eclipse.debug.core.ILaunch;
import org.eclipse.debug.core.ILaunchConfiguration;
import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy;
import org.eclipse.debug.core.ILaunchManager;
import org.eclipse.debug.core.model.ISourceLocator;
/**
* The shared launch configuration delegate for the DSF/GDB debugger.
* This delegate supports all configuration types (local, remote, attach, etc)
*/
@ThreadSafe
public class GdbLaunchDelegate extends AbstractCLaunchDelegate2
{
public static final String GDB_DEBUG_MODEL_ID = "org.eclipse.cdt.dsf.gdb"; //$NON-NLS-1$
private static final String NON_STOP_FIRST_VERSION = "6.8.50"; //$NON-NLS-1$
private static final String TRACING_FIRST_VERSION = "7.1.50"; //$NON-NLS-1$
public GdbLaunchDelegate() {
// We now fully support project-less debugging
// See bug 343861
this(false);
}
/**
* @since 4.0
*/
public GdbLaunchDelegate(boolean requireCProject) {
super(requireCProject);
}
@Override
public void launch( ILaunchConfiguration config, String mode, ILaunch launch, IProgressMonitor monitor ) throws CoreException {
org.eclipse.cdt.launch.LaunchUtils.enableActivity("org.eclipse.cdt.debug.dsfgdbActivity", true); //$NON-NLS-1$
if ( monitor == null ) {
monitor = new NullProgressMonitor();
}
if ( mode.equals( ILaunchManager.DEBUG_MODE ) ) {
launchDebugger( config, launch, monitor );
}
}
private void launchDebugger( ILaunchConfiguration config, ILaunch launch, IProgressMonitor monitor ) throws CoreException {
monitor.beginTask(LaunchMessages.getString("GdbLaunchDelegate.0"), 10); //$NON-NLS-1$
if ( monitor.isCanceled() ) {
return;
}
try {
launchDebugSession( config, launch, monitor );
}
finally {
monitor.done();
}
}
/** @since 4.1 */
protected void launchDebugSession( final ILaunchConfiguration config, ILaunch l, IProgressMonitor monitor ) throws CoreException {
if ( monitor.isCanceled() ) {
return;
}
SessionType sessionType = LaunchUtils.getSessionType(config);
boolean attach = LaunchUtils.getIsAttach(config);
final GdbLaunch launch = (GdbLaunch)l;
if (sessionType == SessionType.REMOTE) {
monitor.subTask( LaunchMessages.getString("GdbLaunchDelegate.1") ); //$NON-NLS-1$
} else if (sessionType == SessionType.CORE) {
monitor.subTask( LaunchMessages.getString("GdbLaunchDelegate.2") ); //$NON-NLS-1$
} else {
assert sessionType == SessionType.LOCAL : "Unexpected session type: " + sessionType.toString(); //$NON-NLS-1$
monitor.subTask( LaunchMessages.getString("GdbLaunchDelegate.3") ); //$NON-NLS-1$
}
// An attach session does not need to necessarily have an
// executable specified. This is because:
// - In remote multi-process attach, there will be more than one executable
// In this case executables need to be specified differently.
// The current solution is to use the solib-search-path to specify
// the path of any executable we can attach to.
// - In local single process, GDB has the ability to find the executable
// automatically.
if (!attach) {
checkBinaryDetails(config);
}
monitor.worked(1);
String gdbVersion = launch.getGDBVersion();
// First make sure non-stop is supported, if the user want to use this mode
if (LaunchUtils.getIsNonStopMode(config) && !isNonStopSupportedInGdbVersion(gdbVersion)) {
throw new DebugException(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, DebugException.REQUEST_FAILED,
"Non-stop mode is not supported for GDB " + gdbVersion + ", GDB " + NON_STOP_FIRST_VERSION + " or higher is required.", null)); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
}
if (LaunchUtils.getIsPostMortemTracing(config) && !isPostMortemTracingSupportedInGdbVersion(gdbVersion)) {
throw new DebugException(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, DebugException.REQUEST_FAILED,
"Post-mortem tracing is not supported for GDB " + gdbVersion + ", GDB " + NON_STOP_FIRST_VERSION + " or higher is required.", null)); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
}
launch.setServiceFactory(newServiceFactory(config, gdbVersion));
// Time to start the DSF stuff. First initialize the launch.
// We do this here to avoid having to cleanup in case
// the launch is cancelled above.
// This initialize() call is the first thing that requires cleanup
// followed by the steps further down which also need cleanup.
launch.initialize();
// Create and invoke the launch sequence to create the debug control and services
IProgressMonitor subMon1 = new SubProgressMonitor(monitor, 4, SubProgressMonitor.PREPEND_MAIN_LABEL_TO_SUBTASK);
Sequence servicesLaunchSequence = getServicesSequence(launch.getSession(), launch, subMon1);
launch.getSession().getExecutor().execute(servicesLaunchSequence);
boolean succeed = false;
try {
servicesLaunchSequence.get();
succeed = true;
} catch (InterruptedException e1) {
throw new DebugException(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, DebugException.INTERNAL_ERROR, "Interrupted Exception in dispatch thread", e1)); //$NON-NLS-1$
} catch (ExecutionException e1) {
throw new DebugException(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, DebugException.REQUEST_FAILED, "Error in services launch sequence", e1.getCause())); //$NON-NLS-1$
} catch (CancellationException e1) {
// Launch aborted, so exit cleanly
return;
} finally {
if (!succeed) {
cleanupLaunch(launch);
}
}
if (monitor.isCanceled()) {
cleanupLaunch(launch);
return;
}
// The initializeControl method should be called after the ICommandControlService
// is initialized in the ServicesLaunchSequence above. This is because it is that
// service that will trigger the launch cleanup (if we need it during this launch)
// through an ICommandControlShutdownDMEvent
launch.initializeControl();
// Add the GDB process object to the launch.
launch.addCLIProcess(getCLILabel(config, gdbVersion));
monitor.worked(1);
// Create and invoke the final launch sequence to setup GDB
final IProgressMonitor subMon2 = new SubProgressMonitor(monitor, 4, SubProgressMonitor.PREPEND_MAIN_LABEL_TO_SUBTASK);
Query<Object> completeLaunchQuery = new Query<Object>() {
@Override
protected void execute(final DataRequestMonitor<Object> rm) {
DsfServicesTracker tracker = new DsfServicesTracker(GdbPlugin.getBundleContext(), launch.getSession().getId());
IGDBControl control = tracker.getService(IGDBControl.class);
tracker.dispose();
control.completeInitialization(new RequestMonitorWithProgress(ImmediateExecutor.getInstance(), subMon2) {
@Override
protected void handleCompleted() {
if (isCanceled()) {
rm.cancel();
} else {
rm.setStatus(getStatus());
}
rm.done();
}
});
}
};
launch.getSession().getExecutor().execute(completeLaunchQuery);
succeed = false;
try {
completeLaunchQuery.get();
succeed = true;
} catch (InterruptedException e1) {
throw new DebugException(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, DebugException.INTERNAL_ERROR, "Interrupted Exception in dispatch thread", e1)); //$NON-NLS-1$
} catch (ExecutionException e1) {
throw new DebugException(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, DebugException.REQUEST_FAILED, "Error in final launch sequence", e1.getCause())); //$NON-NLS-1$
} catch (CancellationException e1) {
// Launch aborted, so exit cleanly
return;
} finally {
if (!succeed) {
// finalLaunchSequence failed. Shutdown the session so that all started
// services including any GDB process are shutdown. (bug 251486)
cleanupLaunch(launch);
}
}
}
/**
* Return the label to be used for the CLI node
* @since 4.6
*/
protected String getCLILabel(ILaunchConfiguration config, String gdbVersion) throws CoreException {
return LaunchUtils.getGDBPath(config).toString().trim() + " (" + gdbVersion +")"; //$NON-NLS-1$ //$NON-NLS-2$
}
/**
* This method takes care of cleaning up any resources allocated by the launch, as early as
* the call to getLaunch(), whenever the launch is cancelled or does not complete properly.
* @since 5.0 */
protected void cleanupLaunch(ILaunch launch) throws DebugException {
if (launch instanceof GdbLaunch) {
final GdbLaunch gdbLaunch = (GdbLaunch)launch;
Query<Object> launchShutdownQuery = new Query<Object>() {
@Override
protected void execute(DataRequestMonitor<Object> rm) {
gdbLaunch.shutdownSession(rm);
}
};
gdbLaunch.getSession().getExecutor().execute(launchShutdownQuery);
// wait for the shutdown to finish.
// The Query.get() method is a synchronous call which blocks until the
// query completes.
try {
launchShutdownQuery.get();
} catch (InterruptedException e) {
throw new DebugException( new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, DebugException.INTERNAL_ERROR, "InterruptedException while shutting down debugger launch " + launch, e)); //$NON-NLS-1$
} catch (ExecutionException e) {
throw new DebugException(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, DebugException.REQUEST_FAILED, "Error in shutting down debugger launch " + launch, e)); //$NON-NLS-1$
}
}
}
/**
* Method used to check that the project and program are correct.
* Can be overridden to avoid checking certain things.
* @since 3.0
*/
protected IPath checkBinaryDetails(final ILaunchConfiguration config) throws CoreException {
// First verify we are dealing with a proper project.
ICProject project = verifyCProject(config);
// Now verify we know the program to debug.
IPath exePath = LaunchUtils.verifyProgramPath(config, project);
// To allow users to debug with binary parsers turned off, we don't call
// LaunchUtils.verifyBinary here. Instead we simply rely on the debugger to
// report any issues with the binary.
return exePath;
}
/**
* Returns the GDB version. Subclass can override for special need.
*
* @since 2.0
* @deprecated Replaced by GdbLaunch.getGDBVersion() which can also be overridden
*/
@Deprecated
protected String getGDBVersion(ILaunchConfiguration config) throws CoreException {
return LaunchUtils.getGDBVersion(config);
}
@Override
public boolean preLaunchCheck(ILaunchConfiguration config, String mode, IProgressMonitor monitor) throws CoreException {
// Setup default GDB Process Factory
// Bug 210366
setDefaultProcessFactory(config);
// Forcibly turn off non-stop for post-mortem sessions.
// Non-stop does not apply to post-mortem sessions.
// Now that we can have non-stop defaulting to enabled, it will prevent
// post-mortem sessions from starting for GDBs <= 6.8 and there is no way to turn it off
// Bug 348091
if (LaunchUtils.getSessionType(config) == SessionType.CORE) {
if (LaunchUtils.getIsNonStopMode(config)) {
ILaunchConfigurationWorkingCopy wcConfig = config.getWorkingCopy();
wcConfig.setAttribute(IGDBLaunchConfigurationConstants.ATTR_DEBUGGER_NON_STOP, false);
wcConfig.doSave();
}
// no further prelaunch check for core files
return true;
}
return super.preLaunchCheck(config, mode, monitor);
// No need to cleanup in the case of errors: we haven't setup anything yet.
}
/**
* Modify the ILaunchConfiguration to set the DebugPlugin.ATTR_PROCESS_FACTORY_ID attribute,
* so as to specify the process factory to use.
*
* This attribute should only be set if it is not part of the configuration already, to allow
* other code to set it to something else.
* @since 4.1
*/
protected void setDefaultProcessFactory(ILaunchConfiguration config) throws CoreException {
// Bug 210366
if (!config.hasAttribute(DebugPlugin.ATTR_PROCESS_FACTORY_ID)) {
ILaunchConfigurationWorkingCopy wc = config.getWorkingCopy();
wc.setAttribute(DebugPlugin.ATTR_PROCESS_FACTORY_ID,
IGDBLaunchConfigurationConstants.DEBUGGER_ATTR_PROCESS_FACTORY_ID_DEFAULT);
wc.doSave();
}
}
// This is the first method to be called in the launch sequence, even before preLaunchCheck()
// If we cancel the launch, we need to cleanup what is allocated in this method. The cleanup
// can be performed by GdbLaunch.shutdownSession()
@Override
public ILaunch getLaunch(ILaunchConfiguration configuration, String mode) throws CoreException {
GdbLaunch launch = createGdbLaunch(configuration, mode, null);
// Don't initialize the GdbLaunch yet to avoid needing to cleanup.
// We will initialize the launch once we know it will proceed and
// that we need to start using it.
// Need to configure the source locator before returning the launch
// because once the launch is created and added to the launch manager,
// the adapters will be created for the whole session, including
// the source lookup adapter.
launch.setSourceLocator(getSourceLocator(configuration, launch.getSession()));
return launch;
}
/**
* Creates an object of GdbLaunch.
* Subclasses who wish to just replace the GdbLaunch object with a sub-classed GdbLaunch
* should override this method.
* Subclasses who wish to replace the GdbLaunch object as well as change the
* initialization sequence of the launch, should override getLaunch() as well as this method.
* Subclasses who wish to create a launch class which does not subclass GdbLaunch,
* are advised to override getLaunch() directly.
*
* @param configuration The launch configuration
* @param mode The launch mode - "run", "debug", "profile"
* @param locator The source locator. Can be null.
* @return The GdbLaunch object, or a sub-classed object
* @throws CoreException
* @since 4.1
*/
protected GdbLaunch createGdbLaunch(ILaunchConfiguration configuration, String mode, ISourceLocator locator) throws CoreException {
return new GdbLaunch(configuration, mode, locator);
}
/**
* Returns a sequence that will create and initialize the different DSF services.
* Subclasses that wish to add/remove services can override this method.
*
* @param session The current DSF session
* @param launch The current launch
* @param rm The progress monitor that is to be used to cancel the sequence if so desired.
* @since 4.5
*/
protected Sequence getServicesSequence(DsfSession session, ILaunch launch, IProgressMonitor rm) {
return new ServicesLaunchSequence(session, (GdbLaunch)launch, rm);
}
/**
* Creates and initializes the source locator for the given launch configuration and dsf session.
* @since 4.1
*/
protected ISourceLocator getSourceLocator(ILaunchConfiguration configuration, DsfSession session) throws CoreException {
DsfSourceLookupDirector locator = createDsfSourceLocator(configuration, session);
String memento = configuration.getAttribute(ILaunchConfiguration.ATTR_SOURCE_LOCATOR_MEMENTO, (String)null);
if (memento == null) {
locator.initializeDefaults(configuration);
} else {
locator.initializeFromMemento(memento, configuration);
}
return locator;
}
/**
* Creates an object of DsfSourceLookupDirector with the given DsfSession.
* Subclasses who wish to just replace the source locator object with a sub-classed source locator
* should override this method.
* Subclasses who wish to replace the source locator object as well as change the
* initialization sequence of the source locator, should override getSourceLocator()
* as well as this method.
* Subclasses who wish to create a source locator which does not subclass DsfSourceLookupDirector,
* are advised to override getSourceLocator() directly.
* @since 4.1
*/
protected DsfSourceLookupDirector createDsfSourceLocator(ILaunchConfiguration configuration, DsfSession session) throws CoreException {
return new GdbSourceLookupDirector(session);
}
/**
* Returns true if the specified version of GDB supports
* non-stop mode.
* @since 4.0
*/
protected boolean isNonStopSupportedInGdbVersion(String version) {
if (NON_STOP_FIRST_VERSION.compareTo(version) <= 0) {// XXX: 7.2 > 7.11 !!!
return true;
}
return false;
}
/**
* Returns true if the specified version of GDB supports
* post-mortem tracing.
* @since 4.0
*/
protected boolean isPostMortemTracingSupportedInGdbVersion(String version) {
if (TRACING_FIRST_VERSION.compareTo(version) <= 0
// This feature will be available for GDB 7.2. But until that GDB is itself available
// there is a pre-release that has a version of 6.8.50.20090414
|| "6.8.50.20090414".equals(version)) { //$NON-NLS-1$
return true;
}
return false;
}
/**
* Method called to create the services factory for this debug session.
* A subclass can override this method and provide its own ServiceFactory.
* @since 4.1
*/
protected IDsfDebugServicesFactory newServiceFactory(ILaunchConfiguration config, String version) {
return new GdbDebugServicesFactory(version, config);
}
@Override
protected String getPluginID() {
return GdbPlugin.PLUGIN_ID;
}
}