/*******************************************************************************
* Copyright (c) 2010, 2016 Mentor Graphics Corporation 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:
* Anna Dushistova (Mentor Graphics) - initial API and implementation
* Anna Dushistova (Mentor Graphics) - moved to org.eclipse.cdt.launch.remote.launching
* Anna Dushistova (MontaVista) - [318051][remote debug] Terminating when "Remote shell" process is selected doesn't work
* Anna Dushistova (MontaVista) - [368597][remote debug] if gdbserver fails to launch on target, launch doesn't get terminated
*******************************************************************************/
package org.eclipse.cdt.launch.remote.launching;
import java.util.concurrent.RejectedExecutionException;
import org.eclipse.cdt.debug.core.ICDTLaunchConfigurationConstants;
import org.eclipse.cdt.dsf.concurrent.DsfRunnable;
import org.eclipse.cdt.dsf.concurrent.ImmediateRequestMonitor;
import org.eclipse.cdt.dsf.gdb.IGDBLaunchConfigurationConstants;
import org.eclipse.cdt.dsf.gdb.launching.GdbLaunch;
import org.eclipse.cdt.dsf.gdb.launching.GdbLaunchDelegate;
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.internal.launch.remote.Activator;
import org.eclipse.cdt.internal.launch.remote.Messages;
import org.eclipse.cdt.launch.remote.IRemoteConnectionConfigurationConstants;
import org.eclipse.cdt.launch.remote.RemoteHelper;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.SubProgressMonitor;
import org.eclipse.core.variables.VariablesPlugin;
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.IStreamListener;
import org.eclipse.debug.core.model.IProcess;
import org.eclipse.debug.core.model.IStreamMonitor;
import org.eclipse.debug.core.model.IStreamsProxy;
import org.eclipse.remote.core.IRemoteProcess;
import org.eclipse.remote.core.RemoteProcessAdapter;
public class RemoteGdbLaunchDelegate extends GdbLaunchDelegate {
@Override
public void launch(ILaunchConfiguration config, String mode,
ILaunch launch, IProgressMonitor monitor) throws CoreException {
IPath exePath = checkBinaryDetails(config);
Process remoteShellProcess = null;
if (exePath != null) {
// 1.Download binary if needed
String remoteExePath = config.getAttribute(
IRemoteConnectionConfigurationConstants.ATTR_REMOTE_PATH,
""); //$NON-NLS-1$
monitor.setTaskName(Messages.RemoteRunLaunchDelegate_2);
RemoteHelper.remoteFileDownload(config, launch, exePath.toString(),
remoteExePath, new SubProgressMonitor(monitor, 80));
// 2.Launch gdbserver on target
String gdbserverPortNumber = config
.getAttribute(
IRemoteConnectionConfigurationConstants.ATTR_GDBSERVER_PORT,
IRemoteConnectionConfigurationConstants.ATTR_GDBSERVER_PORT_DEFAULT);
String gdbserverCommand = config
.getAttribute(
IRemoteConnectionConfigurationConstants.ATTR_GDBSERVER_COMMAND,
IRemoteConnectionConfigurationConstants.ATTR_GDBSERVER_COMMAND_DEFAULT);
String gdbserverOptions = config
.getAttribute(
IRemoteConnectionConfigurationConstants.ATTR_GDBSERVER_OPTIONS,
IRemoteConnectionConfigurationConstants.ATTR_GDBSERVER_OPTIONS_DEFAULT);
String commandArguments = gdbserverOptions + " " //$NON-NLS-1$
+ ":" + gdbserverPortNumber + " " //$NON-NLS-1$ //$NON-NLS-2$
+ RemoteHelper.spaceEscapify(remoteExePath);
String arguments = getProgramArguments(config);
String prelaunchCmd = config
.getAttribute(
IRemoteConnectionConfigurationConstants.ATTR_PRERUN_COMMANDS,
""); //$NON-NLS-1$
if (arguments != null && !arguments.isEmpty())
commandArguments += " " + arguments; //$NON-NLS-1$
monitor.setTaskName(Messages.RemoteRunLaunchDelegate_9);
// extending HostShellProcessAdapter here
final GdbLaunch l = (GdbLaunch)launch;
IRemoteProcess remoteShell = null;
try {
remoteShell = RemoteHelper.execCmdInRemoteShell(config, prelaunchCmd,
gdbserverCommand, commandArguments,
new SubProgressMonitor(monitor, 5));
} catch (Exception e1) {
RemoteHelper.abort(e1.getMessage(), e1,
ICDTLaunchConfigurationConstants.ERR_INTERNAL_ERROR);
}
// We cannot use a global variable because multiple launches
// could access them at the same time. We need a different
// variable for each launch, but we also need it be final.
// Use a final array to do that.
final boolean gdbServerReady[] = new boolean[1];
gdbServerReady[0] = false;
final Object lock = new Object();
if (remoteShell != null) {
try {
remoteShellProcess = new RemoteProcessAdapter(remoteShell) {
@Override
public synchronized void destroy() {
final DsfSession session = l.getSession();
if (session != null) {
try {
session.getExecutor().execute(new DsfRunnable() {
public void run() {
DsfServicesTracker tracker = new DsfServicesTracker(
Activator.getBundleContext(),
session.getId());
IGDBControl control = tracker
.getService(IGDBControl.class);
if (control != null) {
control.terminate(new ImmediateRequestMonitor());
}
tracker.dispose();
}
});
} catch (RejectedExecutionException e) {
// Session disposed.
}
}
super.destroy();
}
};
} catch (Exception e) {
if (remoteShellProcess != null) {
remoteShellProcess.destroy();
}
RemoteHelper.abort(Messages.RemoteRunLaunchDelegate_7, e,
ICDTLaunchConfigurationConstants.ERR_INTERNAL_ERROR);
}
IProcess iProcess = DebugPlugin.newProcess(launch, remoteShellProcess,
Messages.RemoteRunLaunchDelegate_RemoteShell);
// Listen process' output to determine gdbserver is up and running.
IStreamsProxy streams = iProcess.getStreamsProxy();
IStreamMonitor monitorStream = streams.getOutputStreamMonitor();
monitorStream.addListener(new IStreamListener() {
@Override
public void streamAppended(String text, IStreamMonitor monitor) {
if (text.contains("Listening on port")) { //$NON-NLS-1$
synchronized (lock) {
gdbServerReady[0] = true;
lock.notifyAll();
}
monitor.removeListener(this);
}
}
});
// Now wait until gdbserver is up and running on the remote host
synchronized (lock) {
while (gdbServerReady[0] == false) {
if (monitor.isCanceled() || iProcess.isTerminated()) {
//gdbserver launch failed
if (remoteShellProcess != null) {
remoteShellProcess.destroy();
}
// Need to shutdown the DSF launch session because it is
// partially started already.
try {
l.getSession().getExecutor().execute(new DsfRunnable() {
public void run() {
l.shutdownSession(new ImmediateRequestMonitor());
}
});
} catch (RejectedExecutionException e) {
// Session disposed.
}
RemoteHelper.abort(Messages.RemoteGdbLaunchDelegate_gdbserverFailedToStartErrorMessage, null,
ICDTLaunchConfigurationConstants.ERR_DEBUGGER_NOT_INSTALLED);
}
try {
lock.wait(300);
} catch (InterruptedException e) {
}
}
}
// 3. Let debugger know how gdbserver was started on the remote
ILaunchConfigurationWorkingCopy wc = config.getWorkingCopy();
wc.setAttribute(IGDBLaunchConfigurationConstants.ATTR_REMOTE_TCP,
true);
wc.setAttribute(IGDBLaunchConfigurationConstants.ATTR_HOST,
RemoteHelper.getRemoteHostname(config));
wc.setAttribute(IGDBLaunchConfigurationConstants.ATTR_PORT,
gdbserverPortNumber);
wc.doSave();
}
try{
super.launch(config, mode, launch, monitor);
} catch(CoreException ex) {
//launch failed, need to kill gdbserver
if (remoteShellProcess != null) {
remoteShellProcess.destroy();
}
//report failure further
throw ex;
} finally {
monitor.done();
}
}
}
protected String getProgramArguments(ILaunchConfiguration config)
throws CoreException {
String args = config.getAttribute(
ICDTLaunchConfigurationConstants.ATTR_PROGRAM_ARGUMENTS,
(String) null);
if (args != null) {
args = VariablesPlugin.getDefault().getStringVariableManager()
.performStringSubstitution(args);
}
return args;
}
@Override
protected String getPluginID() {
return Activator.PLUGIN_ID;
}
}