/******************************************************************************* * Copyright (c) 2016 Ericsson 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 *******************************************************************************/ package org.eclipse.cdt.dsf.gdb.internal.ui.console; import java.util.concurrent.RejectedExecutionException; import org.eclipse.cdt.debug.ui.CDebugUIPlugin; import org.eclipse.cdt.debug.ui.debuggerconsole.IDebuggerConsole; import org.eclipse.cdt.debug.ui.debuggerconsole.IDebuggerConsoleManager; import org.eclipse.cdt.dsf.concurrent.ConfinedToDsfExecutor; import org.eclipse.cdt.dsf.concurrent.DsfRunnable; import org.eclipse.cdt.dsf.debug.service.command.ICommandControlService; import org.eclipse.cdt.dsf.debug.service.command.ICommandControlService.ICommandControlInitializedDMEvent; import org.eclipse.cdt.dsf.gdb.internal.ui.GdbUIPlugin; import org.eclipse.cdt.dsf.gdb.launching.GdbLaunch; import org.eclipse.cdt.dsf.gdb.service.IGDBBackend; import org.eclipse.cdt.dsf.gdb.service.command.IGDBControl; import org.eclipse.cdt.dsf.mi.service.IMIBackend; import org.eclipse.cdt.dsf.service.DsfServiceEventHandler; import org.eclipse.cdt.dsf.service.DsfServicesTracker; import org.eclipse.cdt.dsf.service.DsfSession; import org.eclipse.core.runtime.CoreException; import org.eclipse.debug.core.DebugPlugin; import org.eclipse.debug.core.ILaunch; import org.eclipse.debug.core.ILaunchesListener2; /** * A console manager for GDB sessions which adds and removes * gdb cli consoles. * * There is a single such console per debug session. * This console interacts directly with the GDB process using * the standard GDB CLI interface. * These consoles cannot be enabled/disabled by the user. * However, they are only supported by GDB >= 7.12; * to handle this limitation, the console manager will use the DSF Backend * service to establish if it should start a gdb cli console or not. */ public class GdbCliConsoleManager implements ILaunchesListener2 { /** * Start the tracing console. We don't do this in a constructor, because * we need to use <code>this</code>. */ public void startup() { // Listen to launch events DebugPlugin.getDefault().getLaunchManager().addLaunchListener(this); } public void shutdown() { DebugPlugin.getDefault().getLaunchManager().removeLaunchListener(this); } @Override public void launchesAdded(ILaunch[] launches) { for (ILaunch launch : launches) { handleConsoleForLaunch(launch); } } @Override public void launchesChanged(ILaunch[] launches) { } @Override public void launchesRemoved(ILaunch[] launches) { for (ILaunch launch : launches) { removeConsole(launch); } } @Override public void launchesTerminated(ILaunch[] launches) { for (ILaunch launch : launches) { renameConsole(launch); } } protected void handleConsoleForLaunch(ILaunch launch) { // Full CLI GDB consoles are only added for GdbLaunches if (launch instanceof GdbLaunch) { new GdbConsoleCreator((GdbLaunch)launch).init(); } } protected void removeConsole(ILaunch launch) { IDebuggerConsole console = getConsole(launch); if (console != null) { removeConsole(console); } } private void renameConsole(ILaunch launch) { IDebuggerConsole console = getConsole(launch); if (console != null) { console.resetName(); } } private IDebuggerConsole getConsole(ILaunch launch) { IDebuggerConsoleManager manager = CDebugUIPlugin.getDebuggerConsoleManager(); for (IDebuggerConsole console : manager.getConsoles()) { if (console.getLaunch().equals(launch)) { return console; } } return null; } private void addConsole(IDebuggerConsole console) { getDebuggerConsoleManager().addConsole(console); } private void removeConsole(IDebuggerConsole console) { getDebuggerConsoleManager().removeConsole(console); } private IDebuggerConsoleManager getDebuggerConsoleManager() { return CDebugUIPlugin.getDebuggerConsoleManager(); } /** * Class that determines what type of console should be created * for this particular Gdblaunch. It figures this out by asking the * Backend service. It then either creates a GdbFullCliConsole or * a GdbBasicCliConsole. */ private class GdbConsoleCreator { private GdbLaunch fLaunch; private DsfSession fSession; public GdbConsoleCreator(GdbLaunch launch) { fLaunch = launch; fSession = launch.getSession(); } public void init() { try { fSession.getExecutor().submit(new DsfRunnable() { @Override public void run() { // Look for backend service right away. It probably // won't be available yet but we must make sure. DsfServicesTracker tracker = new DsfServicesTracker(GdbUIPlugin.getBundleContext(), fSession.getId()); // Use the lowest level service name in case those are created but don't implement // the most specialized classes IGDBControl or IGDBBackend. ICommandControlService control = tracker.getService(ICommandControlService.class); IMIBackend backend = tracker.getService(IMIBackend.class); tracker.dispose(); // If we use the old console we not only need the backend service but // also the control service. For simplicity, always wait for both. if (backend != null && control != null) { // Backend and Control services already available, we can start! verifyAndCreateCliConsole(); } else { // Backend service or Control service not available yet, let's wait for them to start. new GdbServiceStartedListener(GdbConsoleCreator.this, fSession); } } }); } catch (RejectedExecutionException e) { } } @ConfinedToDsfExecutor("fSession.getExecutor()") private void verifyAndCreateCliConsole() { String gdbVersion; try { gdbVersion = fLaunch.getGDBVersion(); } catch (CoreException e) { gdbVersion = "???"; //$NON-NLS-1$ assert false : "Should not happen since the gdb version is cached"; //$NON-NLS-1$ } String consoleTitle = fLaunch.getGDBPath().toOSString().trim() + " (" + gdbVersion +")"; //$NON-NLS-1$ //$NON-NLS-2$ DsfServicesTracker tracker = new DsfServicesTracker(GdbUIPlugin.getBundleContext(), fSession.getId()); IGDBControl control = tracker.getService(IGDBControl.class); IGDBBackend backend = tracker.getService(IGDBBackend.class); tracker.dispose(); IDebuggerConsole console; if (backend != null && backend.isFullGdbConsoleSupported()) { // Create a full GDB cli console console = new GdbFullCliConsole(fLaunch, consoleTitle, backend.getProcess(), backend.getProcessPty()); } else if (control != null) { // Create a simple text console for the cli. console = new GdbBasicCliConsole(fLaunch, consoleTitle, control.getGDBBackendProcess()); } else { // Something is wrong. Don't create a console assert false; return; } addConsole(console); // Make sure the Debugger Console view is visible but do not force it to the top getDebuggerConsoleManager().openConsoleView(); } } /** * Class used to listen for started events for the services we need. * This class must be public to receive the event. */ public class GdbServiceStartedListener { private DsfSession fSession; private GdbConsoleCreator fCreator; public GdbServiceStartedListener(GdbConsoleCreator creator, DsfSession session) { fCreator = creator; fSession = session; fSession.addServiceEventListener(this, null); } @DsfServiceEventHandler public final void eventDispatched(ICommandControlInitializedDMEvent event) { // With the commandControl service started, we know the backend service // is also started. So we are good to go. fCreator.verifyAndCreateCliConsole(); // No longer need to receive events. fSession.removeServiceEventListener(this); } } }