| /******************************************************************************* |
| * 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; |
| } |
| } |