| /******************************************************************************* |
| * Copyright (c) 2006, 2015 Wind River Systems, Nokia and others. |
| * |
| * This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License 2.0 |
| * which accompanies this distribution, and is available at |
| * https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| * |
| * Contributors: |
| * Nokia - initial API and implementation with some code moved from GDBControl. |
| * Wind River System |
| * Ericsson |
| * Marc Khouzam (Ericsson) - Use the new IMIBackend2 interface (Bug 350837) |
| * Mark Bozeman (Mentor Graphics) - Report GDB start failures (Bug 376203) |
| * Iulia Vasii (Freescale Semiconductor) - Separate GDB command from its arguments (Bug 445360) |
| *******************************************************************************/ |
| package org.eclipse.cdt.dsf.gdb.service; |
| |
| import java.io.BufferedReader; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.InputStreamReader; |
| import java.io.OutputStream; |
| import java.util.Hashtable; |
| import java.util.List; |
| import java.util.Properties; |
| import java.util.concurrent.ExecutionException; |
| import java.util.concurrent.ScheduledFuture; |
| import java.util.concurrent.TimeUnit; |
| |
| import org.eclipse.cdt.core.parser.util.StringUtil; |
| import org.eclipse.cdt.dsf.concurrent.DsfRunnable; |
| import org.eclipse.cdt.dsf.concurrent.IDsfStatusConstants; |
| import org.eclipse.cdt.dsf.concurrent.ImmediateRequestMonitor; |
| import org.eclipse.cdt.dsf.concurrent.RequestMonitor; |
| import org.eclipse.cdt.dsf.concurrent.Sequence; |
| import org.eclipse.cdt.dsf.gdb.internal.GdbPlugin; |
| import org.eclipse.cdt.dsf.gdb.launching.GdbLaunch; |
| import org.eclipse.cdt.dsf.gdb.launching.LaunchUtils; |
| import org.eclipse.cdt.dsf.gdb.service.command.GDBControl.InitializationShutdownStep; |
| import org.eclipse.cdt.dsf.mi.service.IMIBackend; |
| import org.eclipse.cdt.dsf.mi.service.IMIBackend2; |
| import org.eclipse.cdt.dsf.mi.service.command.events.MIStoppedEvent; |
| import org.eclipse.cdt.dsf.service.AbstractDsfService; |
| import org.eclipse.cdt.dsf.service.DsfServiceEventHandler; |
| import org.eclipse.cdt.dsf.service.DsfSession; |
| import org.eclipse.cdt.utils.CommandLineUtil; |
| import org.eclipse.cdt.utils.spawner.ProcessFactory; |
| import org.eclipse.cdt.utils.spawner.Spawner; |
| 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.Path; |
| import org.eclipse.core.runtime.Status; |
| import org.eclipse.core.runtime.jobs.Job; |
| import org.eclipse.debug.core.DebugException; |
| import org.eclipse.debug.core.ILaunch; |
| import org.eclipse.debug.core.ILaunchConfiguration; |
| import org.osgi.framework.BundleContext; |
| |
| /** |
| * Implementation of {@link IGDBBackend} for the common case where GDB is |
| * launched in local file system on host PC where Eclipse runs. This also |
| * manages some GDB parameters from a given launch configuration.<br> |
| * <br> |
| * You can subclass for you special needs. |
| * |
| * @since 1.1 |
| */ |
| public class GDBBackend extends AbstractDsfService implements IGDBBackend, IMIBackend2 { |
| |
| private final ILaunchConfiguration fLaunchConfiguration; |
| |
| /* |
| * Parameters for launching GDB. |
| */ |
| private SessionType fSessionType; |
| private Boolean fAttach; |
| private State fBackendState = State.NOT_INITIALIZED; |
| |
| /* |
| * Unique ID of this service instance. |
| */ |
| private final String fBackendId; |
| private static int fgInstanceCounter = 0; |
| |
| /* |
| * Service state parameters. |
| */ |
| private MonitorJob fMonitorJob; |
| private Process fProcess; |
| private int fGDBExitValue; |
| private int fGDBLaunchTimeout = 30; |
| |
| /** |
| * A Job that will set a failed status in the proper request monitor, if the |
| * interrupt did not succeed after a certain time. |
| */ |
| private MonitorInterruptJob fInterruptFailedJob; |
| |
| public GDBBackend(DsfSession session, ILaunchConfiguration lc) { |
| super(session); |
| this.fLaunchConfiguration = lc; |
| fBackendId = "gdb[" + Integer.toString(fgInstanceCounter++) + "]"; //$NON-NLS-1$//$NON-NLS-2$ |
| } |
| |
| @Override |
| public void initialize(final RequestMonitor requestMonitor) { |
| super.initialize(new ImmediateRequestMonitor(requestMonitor) { |
| @Override |
| protected void handleSuccess() { |
| doInitialize(requestMonitor); |
| } |
| }); |
| } |
| |
| private void doInitialize(final RequestMonitor requestMonitor) { |
| getExecutor().execute(getStartupSequence(requestMonitor)); |
| } |
| |
| /** @since 5.0 */ |
| protected Sequence getStartupSequence(final RequestMonitor requestMonitor) { |
| final Sequence.Step[] initializeSteps = new Sequence.Step[] { |
| new GDBProcessStep(InitializationShutdownStep.Direction.INITIALIZING), |
| new MonitorJobStep(InitializationShutdownStep.Direction.INITIALIZING), |
| new RegisterStep(InitializationShutdownStep.Direction.INITIALIZING), }; |
| |
| return new Sequence(getExecutor(), requestMonitor) { |
| @Override |
| public Step[] getSteps() { |
| return initializeSteps; |
| } |
| }; |
| } |
| |
| @Override |
| public void shutdown(final RequestMonitor requestMonitor) { |
| getExecutor().execute(getShutdownSequence(new RequestMonitor(getExecutor(), requestMonitor) { |
| @Override |
| protected void handleCompleted() { |
| GDBBackend.super.shutdown(requestMonitor); |
| } |
| })); |
| } |
| |
| /** @since 5.0 */ |
| protected Sequence getShutdownSequence(RequestMonitor requestMonitor) { |
| final Sequence.Step[] shutdownSteps = new Sequence.Step[] { |
| new RegisterStep(InitializationShutdownStep.Direction.SHUTTING_DOWN), |
| new MonitorJobStep(InitializationShutdownStep.Direction.SHUTTING_DOWN), |
| new GDBProcessStep(InitializationShutdownStep.Direction.SHUTTING_DOWN), }; |
| |
| return new Sequence(getExecutor(), requestMonitor) { |
| @Override |
| public Step[] getSteps() { |
| return shutdownSteps; |
| } |
| }; |
| } |
| |
| /** @since 5.2 */ |
| protected GdbLaunch getGDBLaunch() { |
| return (GdbLaunch) getSession().getModelAdapter(ILaunch.class); |
| } |
| |
| /** @since 4.0 */ |
| protected IPath getGDBPath() { |
| return getGDBLaunch().getGDBPath(); |
| } |
| |
| /** |
| * Options for GDB process. Returns the GDB command and its arguments as an |
| * array. Allow subclass to override. |
| * |
| * @since 4.6 |
| * @deprecated Replaced by getDebuggerCommandLine() |
| */ |
| @Deprecated |
| protected String[] getGDBCommandLineArray() { |
| // The goal here is to keep options to an absolute minimum. |
| // All configuration should be done in the final launch sequence |
| // to allow for more flexibility. |
| |
| String cmd = getGDBPath().toOSString() + " --interpreter" + //$NON-NLS-1$ |
| // We currently work with MI version 2. Don't use just 'mi' because it |
| // points to the latest MI version, while we want mi2 specifically. |
| " mi2" + //$NON-NLS-1$ |
| // Don't read the gdbinit file here. It is read explicitly in |
| // the LaunchSequence to make it easier to customize. |
| " --nx"; //$NON-NLS-1$ |
| |
| // Parse to properly handle spaces and such things (bug 458499) |
| return CommandLineUtil.argumentsToArray(cmd); |
| } |
| |
| /** |
| * Returns the GDB command and its arguments as an array. |
| * Allow subclass to override. |
| * @since 5.2 |
| */ |
| // This method replaces getGDBCommandLineArray() because we need |
| // to override it for GDB 7.12 even if an extender has overridden |
| // getGDBCommandLineArray(). |
| // Here is the scenario: |
| // An extender has overridden getGDBCommandLineArray() to launch |
| // GDB in MI mode but with extra parameters. Once GDBBackend_7_12 |
| // is released, the extender may likely point their extension to |
| // GDBBackend_7_12 instead of GDBBackend (which will even happen |
| // automatically if the extender extends GDBBackend_HEAD). |
| // In such a case, they would override the changes in |
| // GDBBackend_7_12.getGDBCommandLineArray() and the debug session |
| // is likely to fail since with GDBBackend_7_12, we launch GDB |
| // in CLI mode. |
| // |
| // Instead, we use getDebuggerCommandLine() and override that method in |
| // GDBBackend_7_12. That way an extender will not override it |
| // without noticing (since it didn't exist before). Then we can call |
| // the overridden getGDBCommandLineArray() and work with that to |
| // make it work with the new way to launch GDB of GDBBackend_7_12 |
| // |
| // Note that we didn't name this method getGDBCommandLine() because |
| // this name had been used in CDT 8.8 and could still be part of |
| // extenders' code. |
| protected String[] getDebuggerCommandLine() { |
| // Call the old method in case it was overridden |
| return getGDBCommandLineArray(); |
| } |
| |
| @Override |
| public String getGDBInitFile() throws CoreException { |
| return getGDBLaunch().getGDBInitFile(); |
| } |
| |
| @Override |
| public IPath getGDBWorkingDirectory() throws CoreException { |
| return getGDBLaunch().getGDBWorkingDirectory(); |
| } |
| |
| @Override |
| public String getProgramArguments() throws CoreException { |
| return getGDBLaunch().getProgramArguments(); |
| } |
| |
| @Override |
| public IPath getProgramPath() { |
| try { |
| return new Path(getGDBLaunch().getProgramPath()); |
| } catch (CoreException e) { |
| return new Path(""); //$NON-NLS-1$ |
| } |
| } |
| |
| @Override |
| public List<String> getSharedLibraryPaths() throws CoreException { |
| return getGDBLaunch().getSharedLibraryPaths(); |
| } |
| |
| /** @since 3.0 */ |
| @Override |
| public Properties getEnvironmentVariables() throws CoreException { |
| return getGDBLaunch().getEnvironmentVariables(); |
| } |
| |
| /** @since 3.0 */ |
| @Override |
| public boolean getClearEnvironment() throws CoreException { |
| return getGDBLaunch().getClearEnvironment(); |
| } |
| |
| /** @since 3.0 */ |
| @Override |
| public boolean getUpdateThreadListOnSuspend() throws CoreException { |
| return getGDBLaunch().getUpdateThreadListOnSuspend(); |
| } |
| |
| /** |
| * Launch GDB process. Allow subclass to override. |
| * |
| * @since 5.2 |
| */ |
| // Again, we create a new method that we know has not been already |
| // overridden. That way, even if extenders have overridden the |
| // original launchGDBProcess(String[]), we will instead use |
| // the GDBBackend_7_12#launchGDBProcess() method when appropriate. |
| // This is important because if we didn't, the new console would |
| // not work properly. |
| // |
| // Of course, in that case, we won't call the extenders overridden |
| // launchGDBProcess(String[]) and therefore will not get their |
| // specialized code. I feel this is still a lower risk than |
| // not starting the full GDB console properly. |
| protected Process launchGDBProcess() throws CoreException { |
| // Call the old method in case it was overridden |
| return launchGDBProcess(getDebuggerCommandLine()); |
| } |
| |
| /** |
| * Launch GDB process with command and arguments. Allow subclass to |
| * override. |
| * |
| * @since 4.6 |
| * @deprecated Replace by launchGDBProcess() |
| */ |
| @Deprecated |
| protected Process launchGDBProcess(String[] commandLine) throws CoreException { |
| Process proc = null; |
| try { |
| proc = ProcessFactory.getFactory().exec(commandLine, getGDBLaunch().getLaunchEnvironment()); |
| } catch (IOException e) { |
| String message = "Error while launching command: " + StringUtil.join(commandLine, " "); //$NON-NLS-1$ //$NON-NLS-2$ |
| throw new CoreException(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, -1, message, e)); |
| } |
| |
| return proc; |
| } |
| |
| @Override |
| public Process getProcess() { |
| return fProcess; |
| } |
| |
| @Override |
| public OutputStream getMIOutputStream() { |
| return fProcess.getOutputStream(); |
| } |
| |
| @Override |
| public InputStream getMIInputStream() { |
| return fProcess.getInputStream(); |
| } |
| |
| /** @since 4.1 */ |
| @Override |
| public InputStream getMIErrorStream() { |
| return fProcess.getErrorStream(); |
| } |
| |
| @Override |
| public String getId() { |
| return fBackendId; |
| } |
| |
| @Override |
| public void interrupt() { |
| if (fProcess instanceof Spawner) { |
| Spawner gdbSpawner = (Spawner) fProcess; |
| |
| // Cygwin gdb 6.8 is capricious when it comes to interrupting the |
| // target. The same logic here will work with MinGW, though. And on |
| // linux it's irrelevant since interruptCTRLC()==interrupt(). So, |
| // one odd size fits all. |
| // See https://bugs.eclipse.org/bugs/show_bug.cgi?id=304096#c54 |
| if (getSessionType() == SessionType.REMOTE) { |
| gdbSpawner.interrupt(); |
| } else { |
| gdbSpawner.interruptCTRLC(); |
| } |
| } |
| } |
| |
| /** |
| * @since 3.0 |
| */ |
| @Override |
| public void interruptAndWait(int timeout, RequestMonitor rm) { |
| if (fProcess instanceof Spawner) { |
| Spawner gdbSpawner = (Spawner) fProcess; |
| |
| // Cygwin gdb 6.8 is capricious when it comes to interrupting the |
| // target. The same logic here will work with MinGW, though. And on |
| // linux it's irrelevant since interruptCTRLC()==interrupt(). So, |
| // one odd size fits all. |
| // See https://bugs.eclipse.org/bugs/show_bug.cgi?id=304096#c54 |
| if (getSessionType() == SessionType.REMOTE) { |
| gdbSpawner.interrupt(); |
| } else { |
| gdbSpawner.interruptCTRLC(); |
| } |
| fInterruptFailedJob = new MonitorInterruptJob(timeout, rm); |
| } else { |
| rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, IDsfStatusConstants.NOT_SUPPORTED, |
| "Cannot interrupt.", null)); //$NON-NLS-1$ |
| rm.done(); |
| } |
| } |
| |
| /** |
| * @since 3.0 |
| */ |
| @Override |
| public void interruptInferiorAndWait(long pid, int timeout, RequestMonitor rm) { |
| if (fProcess instanceof Spawner) { |
| Spawner gdbSpawner = (Spawner) fProcess; |
| gdbSpawner.raise((int) pid, gdbSpawner.INT); |
| fInterruptFailedJob = new MonitorInterruptJob(timeout, rm); |
| } else { |
| rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, IDsfStatusConstants.NOT_SUPPORTED, |
| "Cannot interrupt.", null)); //$NON-NLS-1$ |
| rm.done(); |
| } |
| } |
| |
| @Override |
| public void destroy() { |
| // Don't close the streams ourselves as it may be too early. |
| // Wait for the actual user of the streams to close it. |
| // Bug 339379 |
| |
| // destroy() should be supported even if it's not spawner. |
| if (getState() == State.STARTED) { |
| fProcess.destroy(); |
| } |
| } |
| |
| @Override |
| public State getState() { |
| return fBackendState; |
| } |
| |
| @Override |
| public int getExitCode() { |
| return fGDBExitValue; |
| } |
| |
| @Override |
| public SessionType getSessionType() { |
| if (fSessionType == null) { |
| fSessionType = LaunchUtils.getSessionType(fLaunchConfiguration); |
| } |
| return fSessionType; |
| } |
| |
| @Override |
| public boolean getIsAttachSession() { |
| if (fAttach == null) { |
| fAttach = LaunchUtils.getIsAttach(fLaunchConfiguration); |
| } |
| return fAttach; |
| } |
| |
| @Override |
| protected BundleContext getBundleContext() { |
| return GdbPlugin.getBundleContext(); |
| } |
| |
| protected class GDBProcessStep extends InitializationShutdownStep { |
| GDBProcessStep(Direction direction) { |
| super(direction); |
| } |
| |
| @Override |
| public void initialize(final RequestMonitor requestMonitor) { |
| doGDBProcessStep(requestMonitor); |
| } |
| |
| @Override |
| protected void shutdown(final RequestMonitor requestMonitor) { |
| undoGDBProcessStep(requestMonitor); |
| } |
| } |
| |
| protected class MonitorJobStep extends InitializationShutdownStep { |
| MonitorJobStep(Direction direction) { |
| super(direction); |
| } |
| |
| @Override |
| public void initialize(final RequestMonitor requestMonitor) { |
| doMonitorJobStep(requestMonitor); |
| } |
| |
| @Override |
| protected void shutdown(RequestMonitor requestMonitor) { |
| undoMonitorJobStep(requestMonitor); |
| } |
| } |
| |
| protected class RegisterStep extends InitializationShutdownStep { |
| RegisterStep(Direction direction) { |
| super(direction); |
| } |
| |
| @Override |
| public void initialize(RequestMonitor requestMonitor) { |
| doRegisterStep(requestMonitor); |
| } |
| |
| @Override |
| protected void shutdown(RequestMonitor requestMonitor) { |
| undoRegisterStep(requestMonitor); |
| } |
| } |
| |
| /** @since 5.0 */ |
| protected void doGDBProcessStep(final RequestMonitor requestMonitor) { |
| class GDBLaunchMonitor { |
| boolean fLaunched = false; |
| boolean fTimedOut = false; |
| public ScheduledFuture<?> fTimeoutFuture; |
| } |
| final GDBLaunchMonitor fGDBLaunchMonitor = new GDBLaunchMonitor(); |
| |
| final RequestMonitor gdbLaunchRequestMonitor = new RequestMonitor(getExecutor(), requestMonitor) { |
| @Override |
| protected void handleCompleted() { |
| if (!fGDBLaunchMonitor.fTimedOut) { |
| fGDBLaunchMonitor.fLaunched = true; |
| fGDBLaunchMonitor.fTimeoutFuture.cancel(false); |
| if (!isSuccess()) { |
| requestMonitor.setStatus(getStatus()); |
| } |
| requestMonitor.done(); |
| } |
| } |
| }; |
| |
| final Job startGdbJob = new Job("Start GDB Process Job") { //$NON-NLS-1$ |
| { |
| setSystem(true); |
| } |
| |
| @Override |
| protected IStatus run(IProgressMonitor monitor) { |
| if (gdbLaunchRequestMonitor.isCanceled()) { |
| gdbLaunchRequestMonitor.setStatus( |
| new Status(IStatus.CANCEL, GdbPlugin.PLUGIN_ID, -1, "Canceled starting GDB", null)); //$NON-NLS-1$ |
| gdbLaunchRequestMonitor.done(); |
| return Status.OK_STATUS; |
| } |
| |
| try { |
| fProcess = launchGDBProcess(); |
| |
| // Need to do this on the executor for thread-safety |
| getExecutor().submit(new DsfRunnable() { |
| @Override |
| public void run() { |
| fBackendState = State.STARTED; |
| } |
| }); |
| // Don't send the backendStarted event yet. We wait |
| // until we have registered this service |
| // so that other services can have access to it. |
| } catch (CoreException e) { |
| gdbLaunchRequestMonitor |
| .setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, -1, e.getMessage(), e)); |
| gdbLaunchRequestMonitor.done(); |
| return Status.OK_STATUS; |
| } |
| |
| BufferedReader inputReader = null; |
| BufferedReader errorReader = null; |
| boolean success = false; |
| try { |
| // Must call getMIInputStream() because we always want to read from the MI stream, |
| // which is not always the same as the input stream of fProcess. They are |
| // different when we use the full GDB console |
| InputStream inputStream = getMIInputStream(); |
| // Read initial GDB prompt |
| inputReader = new BufferedReader(new InputStreamReader(inputStream)); |
| String line; |
| while ((line = inputReader.readLine()) != null) { |
| line = line.trim(); |
| if (line.endsWith("(gdb)")) { //$NON-NLS-1$ |
| success = true; |
| break; |
| } |
| } |
| |
| // Failed to read initial prompt, check for error |
| if (!success) { |
| // Don't call getMIErrorStream() because it can be overridden with a |
| // dummy stream in the case of the full GDB console. |
| // Instead, make sure we read the error from the process itself. |
| InputStream errorStream = fProcess.getErrorStream(); |
| errorReader = new BufferedReader(new InputStreamReader(errorStream)); |
| String errorInfo = errorReader.readLine(); |
| if (errorInfo == null) { |
| errorInfo = "GDB prompt not read"; //$NON-NLS-1$ |
| } |
| gdbLaunchRequestMonitor |
| .setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, -1, errorInfo, null)); |
| } |
| } catch (IOException e) { |
| success = false; |
| gdbLaunchRequestMonitor.setStatus( |
| new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, -1, "Error reading GDB output", e)); //$NON-NLS-1$ |
| } |
| |
| // In the case of failure, close the MI streams so |
| // they are not leaked. |
| if (!success) { |
| if (inputReader != null) { |
| try { |
| inputReader.close(); |
| } catch (IOException e) { |
| } |
| } |
| if (errorReader != null) { |
| try { |
| errorReader.close(); |
| } catch (IOException e) { |
| } |
| } |
| } |
| |
| gdbLaunchRequestMonitor.done(); |
| return Status.OK_STATUS; |
| } |
| }; |
| startGdbJob.schedule(); |
| |
| fGDBLaunchMonitor.fTimeoutFuture = getExecutor().schedule(() -> { |
| // Only process the event if we have not finished yet (hit |
| // the breakpoint). |
| if (!fGDBLaunchMonitor.fLaunched) { |
| fGDBLaunchMonitor.fTimedOut = true; |
| Thread jobThread = startGdbJob.getThread(); |
| if (jobThread != null) { |
| jobThread.interrupt(); |
| } |
| |
| destroy(); |
| |
| requestMonitor.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, |
| DebugException.TARGET_REQUEST_FAILED, "Timed out trying to launch GDB.", null)); //$NON-NLS-1$ |
| requestMonitor.done(); |
| } |
| }, fGDBLaunchTimeout, TimeUnit.SECONDS); |
| } |
| |
| /** @since 5.0 */ |
| protected void undoGDBProcessStep(final RequestMonitor requestMonitor) { |
| if (getState() != State.STARTED) { |
| // gdb not started yet or already killed, don't bother starting |
| // a job to kill it |
| requestMonitor.done(); |
| return; |
| } |
| |
| new Job("Terminating GDB process.") { //$NON-NLS-1$ |
| { |
| setSystem(true); |
| } |
| |
| @Override |
| protected IStatus run(IProgressMonitor monitor) { |
| try { |
| // Need to do this on the executor for thread-safety |
| // And we should wait for it to complete since we then |
| // check if the killing of GDB worked. |
| getExecutor().submit(new DsfRunnable() { |
| @Override |
| public void run() { |
| destroy(); |
| |
| if (fMonitorJob.fMonitorExited) { |
| // Now that we have destroyed the process, and |
| // that the monitoring thread was killed, we |
| // need to set our state and send the event |
| fBackendState = State.TERMINATED; |
| getSession().dispatchEvent( |
| new BackendStateChangedEvent(getSession().getId(), getId(), State.TERMINATED), |
| getProperties()); |
| } |
| } |
| }).get(); |
| } catch (InterruptedException e1) { |
| } catch (ExecutionException e1) { |
| } |
| |
| int attempts = 0; |
| while (attempts < 10) { |
| try { |
| // Don't know if we really need the exit value... |
| // but what the heck. |
| // throws exception if process not exited |
| fGDBExitValue = fProcess.exitValue(); |
| |
| requestMonitor.done(); |
| return Status.OK_STATUS; |
| } catch (IllegalThreadStateException ie) { |
| } |
| try { |
| Thread.sleep(500); |
| } catch (InterruptedException e) { |
| } |
| attempts++; |
| } |
| requestMonitor.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, |
| IDsfStatusConstants.REQUEST_FAILED, "GDB terminate failed", null)); //$NON-NLS-1$ |
| requestMonitor.done(); |
| return Status.OK_STATUS; |
| } |
| }.schedule(); |
| } |
| |
| /** @since 5.0 */ |
| protected void doMonitorJobStep(final RequestMonitor requestMonitor) { |
| fMonitorJob = new MonitorJob(fProcess, new DsfRunnable() { |
| @Override |
| public void run() { |
| requestMonitor.done(); |
| } |
| }); |
| fMonitorJob.schedule(); |
| } |
| |
| /** @since 5.0 */ |
| protected void undoMonitorJobStep(RequestMonitor requestMonitor) { |
| if (fMonitorJob != null) { |
| fMonitorJob.kill(); |
| } |
| requestMonitor.done(); |
| } |
| |
| /** @since 5.0 */ |
| protected void doRegisterStep(RequestMonitor requestMonitor) { |
| register(new String[] { IMIBackend.class.getName(), IMIBackend2.class.getName(), IGDBBackend.class.getName() }, |
| new Hashtable<String, String>()); |
| |
| getSession().addServiceEventListener(GDBBackend.this, null); |
| |
| /* |
| * This event is not consumed by any one at present, instead it's the |
| * GDBControlInitializedDMEvent that's used to indicate that GDB back |
| * end is ready for MI commands. But we still fire the event as it does |
| * no harm and may be needed sometime.... 09/29/2008 |
| * |
| * We send the event in the register step because that is when other |
| * services have access to it. |
| */ |
| getSession().dispatchEvent(new BackendStateChangedEvent(getSession().getId(), getId(), State.STARTED), |
| getProperties()); |
| |
| requestMonitor.done(); |
| } |
| |
| /** @since 5.0 */ |
| protected void undoRegisterStep(RequestMonitor requestMonitor) { |
| unregister(); |
| getSession().removeServiceEventListener(GDBBackend.this); |
| requestMonitor.done(); |
| } |
| |
| /** |
| * Monitors a system process, waiting for it to terminate, and then notifies |
| * the associated runtime process. |
| */ |
| private class MonitorJob extends Job { |
| boolean fMonitorExited = false; |
| DsfRunnable fMonitorStarted; |
| Process fMonProcess; |
| |
| @Override |
| protected IStatus run(IProgressMonitor monitor) { |
| synchronized (fMonProcess) { |
| getExecutor().submit(fMonitorStarted); |
| try { |
| fMonProcess.waitFor(); |
| fGDBExitValue = fMonProcess.exitValue(); |
| |
| // Need to do this on the executor for thread-safety |
| getExecutor().submit(new DsfRunnable() { |
| @Override |
| public void run() { |
| destroy(); |
| fBackendState = State.TERMINATED; |
| getSession().dispatchEvent( |
| new BackendStateChangedEvent(getSession().getId(), getId(), State.TERMINATED), |
| getProperties()); |
| } |
| }); |
| } catch (InterruptedException ie) { |
| // clear interrupted state |
| Thread.interrupted(); |
| } |
| |
| fMonitorExited = true; |
| } |
| return Status.OK_STATUS; |
| } |
| |
| MonitorJob(Process process, DsfRunnable monitorStarted) { |
| super("GDB process monitor job."); //$NON-NLS-1$ |
| fMonProcess = process; |
| fMonitorStarted = monitorStarted; |
| setSystem(true); |
| } |
| |
| void kill() { |
| synchronized (fMonProcess) { |
| if (!fMonitorExited) { |
| getThread().interrupt(); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Stores the request monitor that must be dealt with for the result of the |
| * interrupt operation. If the interrupt successfully suspends the backend, |
| * the request monitor can be retrieved and completed successfully, and then |
| * this job should be canceled. If this job is not canceled before the time |
| * is up, it will imply the interrupt did not successfully suspend the |
| * backend, and the current job will indicate this in the request monitor. |
| * |
| * The specified timeout is used to indicate how many milliseconds this job |
| * should wait for. INTERRUPT_TIMEOUT_DEFAULT indicates to use the default |
| * of 5 seconds. The default is also use if the timeout value is 0 or |
| * negative. |
| * |
| * @since 3.0 |
| */ |
| protected class MonitorInterruptJob extends Job { |
| // Bug 310274. Until we have a preference to configure timeouts, |
| // we need a large enough default timeout to accommodate slow |
| // remote sessions. |
| private final static int TIMEOUT_DEFAULT_VALUE = 5000; |
| private final RequestMonitor fRequestMonitor; |
| |
| public MonitorInterruptJob(int timeout, RequestMonitor rm) { |
| super("Interrupt monitor job."); //$NON-NLS-1$ |
| setSystem(true); |
| fRequestMonitor = rm; |
| |
| if (timeout == INTERRUPT_TIMEOUT_DEFAULT || timeout <= 0) { |
| timeout = TIMEOUT_DEFAULT_VALUE; // default of 5 seconds |
| } |
| |
| schedule(timeout); |
| } |
| |
| @Override |
| protected IStatus run(IProgressMonitor monitor) { |
| getExecutor().submit(new DsfRunnable() { |
| @Override |
| public void run() { |
| fInterruptFailedJob = null; |
| fRequestMonitor.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, |
| IDsfStatusConstants.REQUEST_FAILED, "Interrupt failed.", null)); //$NON-NLS-1$ |
| fRequestMonitor.done(); |
| } |
| }); |
| return Status.OK_STATUS; |
| } |
| |
| public RequestMonitor getRequestMonitor() { |
| return fRequestMonitor; |
| } |
| |
| } |
| |
| /** |
| * We use this handler to determine if the SIGINT we sent to GDB has been |
| * effective. We must listen for an MI event and not a higher-level |
| * ISuspendedEvent. The reason is that some ISuspendedEvent are not sent |
| * when the target stops, in cases where we don't want to views to update. |
| * For example, if we want to interrupt the target to set a breakpoint, this |
| * interruption is done silently; we will receive the MI event though. |
| * |
| * <p> |
| * Though we send a SIGINT, we may not specifically get an MISignalEvent. |
| * Typically we will, but not always, so wait for an MIStoppedEvent. See |
| * https://bugs.eclipse.org/bugs/show_bug.cgi?id=305178#c21 |
| * |
| * @since 3.0 |
| * |
| */ |
| @DsfServiceEventHandler |
| public void eventDispatched(final MIStoppedEvent e) { |
| if (fInterruptFailedJob != null) { |
| if (fInterruptFailedJob.cancel()) { |
| fInterruptFailedJob.getRequestMonitor().done(); |
| } |
| fInterruptFailedJob = null; |
| } |
| } |
| } |