/*******************************************************************************
* Copyright (c) 2008, 2011 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)
*******************************************************************************/
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.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.GdbDebugServicesFactoryNS;
import org.eclipse.cdt.dsf.gdb.service.SessionType;
import org.eclipse.cdt.dsf.gdb.service.command.IGDBControl;
import org.eclipse.cdt.dsf.gdb.service.macos.MacOSGdbDebugServicesFactory;
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.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 final static String GDB_DEBUG_MODEL_ID = "org.eclipse.cdt.dsf.gdb"; //$NON-NLS-1$
private final static String NON_STOP_FIRST_VERSION = "6.8.50"; //$NON-NLS-1$
// Can be removed once we remove the deprecated newServiceFactory(String)
private boolean fIsNonStopSession = false;
private final static 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();
}
}
private 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 );
// Must set this here for users that call directly the deprecated newServiceFactory(String)
fIsNonStopSession = LaunchUtils.getIsNonStopMode(config);
String gdbVersion = getGDBVersion(config);
// 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 only supported starting with GDB " + NON_STOP_FIRST_VERSION, null)); //$NON-NLS-1$
}
if (LaunchUtils.getIsPostMortemTracing(config) && !isPostMortemTracingSupportedInGdbVersion(gdbVersion)) {
throw new DebugException(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, DebugException.REQUEST_FAILED, "Post-mortem tracing is only supported starting with GDB " + TRACING_FIRST_VERSION, null)); //$NON-NLS-1$
}
launch.setServiceFactory(newServiceFactory(config, gdbVersion));
// 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);
final ServicesLaunchSequence servicesLaunchSequence =
new ServicesLaunchSequence(launch.getSession(), launch, subMon1);
launch.getSession().getExecutor().execute(servicesLaunchSequence);
try {
servicesLaunchSequence.get();
} 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;
}
if (monitor.isCanceled())
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("gdb"); //$NON-NLS-1$
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);
boolean 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)
//
Query<Object> launchShutdownQuery = new Query<Object>() {
@Override
protected void execute(DataRequestMonitor<Object> rm) {
launch.shutdownSession(rm);
}
};
launch.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, program and binary 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);
// Finally, make sure the program is a proper binary.
LaunchUtils.verifyBinary(config, exePath);
return exePath;
}
/**
* Returns the GDB version.
* Subclass can override for special need.
*
* @since 2.0
*/
protected String getGDBVersion(ILaunchConfiguration config) throws CoreException {
return LaunchUtils.getGDBVersion(config);
}
@Override
public boolean preLaunchCheck(ILaunchConfiguration config, String mode, IProgressMonitor monitor) throws CoreException {
// 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);
}
@Override
public ILaunch getLaunch(ILaunchConfiguration configuration, String mode) throws CoreException {
// Need to configure the source locator before creating the launch
// because once the launch is created and added to launch manager,
// the adapters will be created for the whole session, including
// the source lookup adapter.
GdbLaunch launch = createGdbLaunch(configuration, mode, null);
launch.initialize();
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);
}
/**
* 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 DsfSourceLookupDirector(session);
}
/**
* Returns true if the specified version of GDB supports
* non-stop mode.
* @since 4.0
*/
protected boolean isNonStopSupportedInGdbVersion(String version) {
if (version.contains(LaunchUtils.MACOS_GDB_MARKER)) {
// Mac OS's GDB does not support Non-Stop
return false;
}
if (NON_STOP_FIRST_VERSION.compareTo(version) <= 0) {
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 (version.contains(LaunchUtils.MACOS_GDB_MARKER)) {
// Mac OS's GDB does not support post-mortem tracing
return false;
}
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;
}
/**
* @deprecated Replaced by newServiceFactory(ILaunchConfiguration, String)
*/
@Deprecated
protected IDsfDebugServicesFactory newServiceFactory(String version) {
if (fIsNonStopSession && isNonStopSupportedInGdbVersion(version)) {
return new GdbDebugServicesFactoryNS(version);
}
if (version.contains(LaunchUtils.MACOS_GDB_MARKER)) {
// The version string at this point should look like
// 6.3.50-20050815APPLE1346, we extract the gdb version and apple version
String versions [] = version.split(LaunchUtils.MACOS_GDB_MARKER);
if (versions.length == 2) {
return new MacOSGdbDebugServicesFactory(versions[0], versions[1]);
}
}
return new GdbDebugServicesFactory(version);
}
/**
* 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) {
// Call the deprecated one for now to avoid code duplication.
// Once we get rid of the deprecated one, we can also get rid of fIsNonStopSession
fIsNonStopSession = LaunchUtils.getIsNonStopMode(config);
return newServiceFactory(version);
}
@Override
protected String getPluginID() {
return GdbPlugin.PLUGIN_ID;
}
}