blob: f22ea9820c4846e924308f0a5799252413d18cc2 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2015 Wind River Systems, Inc. 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:
* Wind River Systems - initial API and implementation
*******************************************************************************/
package org.eclipse.tcf.te.tcf.launch.cdt.launching;
import java.io.IOException;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import org.eclipse.cdt.debug.core.ICDTLaunchConfigurationConstants;
import org.eclipse.cdt.dsf.concurrent.DsfExecutor;
import org.eclipse.cdt.dsf.concurrent.DsfRunnable;
import org.eclipse.cdt.dsf.concurrent.ImmediateDataRequestMonitor;
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.mi.service.IMICommandControl;
import org.eclipse.cdt.dsf.mi.service.command.CommandFactory;
import org.eclipse.cdt.dsf.mi.service.command.output.MIInfo;
import org.eclipse.cdt.dsf.service.DsfServicesTracker;
import org.eclipse.core.runtime.Assert;
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.SubProgressMonitor;
import org.eclipse.core.variables.VariablesPlugin;
import org.eclipse.debug.core.ILaunch;
import org.eclipse.debug.core.ILaunchConfiguration;
import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy;
import org.eclipse.osgi.util.NLS;
import org.eclipse.tcf.protocol.IPeer;
import org.eclipse.tcf.te.runtime.callback.Callback;
import org.eclipse.tcf.te.tcf.core.streams.StreamsDataReceiver;
import org.eclipse.tcf.te.tcf.launch.cdt.activator.Activator;
import org.eclipse.tcf.te.tcf.launch.cdt.interfaces.IRemoteTEConfigurationConstants;
import org.eclipse.tcf.te.tcf.launch.cdt.nls.Messages;
import org.eclipse.tcf.te.tcf.launch.cdt.utils.TEHelper;
import org.eclipse.tcf.te.tcf.processes.core.launcher.ProcessLauncher;
/**
* Abstract launch delegate implementation handling launching the gdbserver via TCF/TE.
*/
public abstract class TEGdbAbstractLaunchDelegate extends GdbLaunchDelegate {
/**
* Constructor
*/
public TEGdbAbstractLaunchDelegate() {
super();
}
/**
* Constructor
*
* @param requireCProject <code>True</code> if a C project is required for launching,
* <code>false</code> otherwise.
*/
public TEGdbAbstractLaunchDelegate(boolean requireCProject) {
super(requireCProject);
}
/* (non-Javadoc)
* @see org.eclipse.cdt.dsf.gdb.launching.GdbLaunchDelegate#launch(org.eclipse.debug.core.ILaunchConfiguration, java.lang.String, org.eclipse.debug.core.ILaunch, org.eclipse.core.runtime.IProgressMonitor)
*/
@Override
public void launch(ILaunchConfiguration config, String mode, ILaunch launch, IProgressMonitor monitor) throws CoreException {
// If not of the expected type --> return immediately
if (!(launch instanceof GdbLaunch)) return;
// Initialize TE
Activator.getDefault().initializeTE();
// Get the peer from the launch configuration
IPeer peer = TEHelper.getCurrentConnection(config).getPeer();
// Determine if the launch is an attach launch
final boolean isAttachLaunch = ICDTLaunchConfigurationConstants.ID_LAUNCH_C_ATTACH.equals(config.getType().getIdentifier());
// Get the executable path (run/debug application) or the PID (attach to application)
IPath exePath = checkBinaryDetails(config);
String remoteExePath = config.getAttribute(IRemoteTEConfigurationConstants.ATTR_REMOTE_PATH, (String)null);
String remotePID = config.getAttribute(IRemoteTEConfigurationConstants.ATTR_REMOTE_PID, (String)null);
// Not an attach launch and the executable is not specified --> abort
if (!isAttachLaunch && exePath == null) {
abort(Messages.TEGdbAbstractLaunchDelegate_no_program, null, ICDTLaunchConfigurationConstants.ERR_PROGRAM_NOT_EXIST);
}
// Not an attach launch and the remote executable is not specified --> abort
if (!isAttachLaunch && remoteExePath == null) {
abort(Messages.TEGdbAbstractLaunchDelegate_no_remote_path, null, ICDTLaunchConfigurationConstants.ERR_PROGRAM_NOT_EXIST);
}
// Attach launch and the remote PID is not specified --> abort
if (isAttachLaunch && remotePID == null) {
abort(Messages.TEGdbAbstractLaunchDelegate_no_pid, null, ICDTLaunchConfigurationConstants.ERR_NO_PROCESSID);
}
// If an executable path is specified, download the binary if needed
if (!isAttachLaunch && exePath != null && remoteExePath != null) {
monitor.setTaskName(Messages.TEGdbAbstractLaunchDelegate_downloading);
boolean skipDownload = config.getAttribute(IRemoteTEConfigurationConstants.ATTR_SKIP_DOWNLOAD_TO_TARGET, false);
if (!skipDownload) {
try {
TEHelper.remoteFileTransfer(peer, exePath.toString(), remoteExePath, new SubProgressMonitor(monitor, 80));
}
catch (IOException e) {
abort(NLS.bind(Messages.TEGdbAbstractLaunchDelegate_filetransferFailed, e.getLocalizedMessage()), e, ICDTLaunchConfigurationConstants.ERR_PROGRAM_NOT_EXIST);
}
}
}
// Launch gdbserver on target
String gdbserverPortNumber = config.getAttribute(IRemoteTEConfigurationConstants.ATTR_GDBSERVER_PORT, IRemoteTEConfigurationConstants.ATTR_GDBSERVER_PORT_DEFAULT);
String gdbserverPortNumberMappedTo = config.getAttribute(IRemoteTEConfigurationConstants.ATTR_GDBSERVER_PORT_MAPPED_TO, (String) null);
String gdbserverCommand = config.getAttribute(IRemoteTEConfigurationConstants.ATTR_GDBSERVER_COMMAND, IRemoteTEConfigurationConstants.ATTR_GDBSERVER_COMMAND_DEFAULT);
String commandArguments = ""; //$NON-NLS-1$
if (isAttachLaunch) {
commandArguments = "--attach :" + gdbserverPortNumber + " " + remotePID; //$NON-NLS-1$ //$NON-NLS-2$
monitor.setTaskName(Messages.TEGdbAbstractLaunchDelegate_attaching_program);
} else {
commandArguments = ":" + gdbserverPortNumber + " " + TEHelper.spaceEscapify(remoteExePath); //$NON-NLS-1$ //$NON-NLS-2$
String arguments = getProgramArguments(config);
String prelaunchCmd = config.getAttribute(IRemoteTEConfigurationConstants.ATTR_PRERUN_COMMANDS, ""); //$NON-NLS-1$
TEHelper.launchCmd(peer, prelaunchCmd, null, new SubProgressMonitor(monitor, 2), new Callback());
if (arguments != null && !arguments.equals("")) { //$NON-NLS-1$
commandArguments += " " + arguments; //$NON-NLS-1$
}
monitor.setTaskName(Messages.TEGdbAbstractLaunchDelegate_starting_program);
}
final AtomicBoolean gdbServerStarted = new AtomicBoolean(false);
final AtomicBoolean gdbServerReady = new AtomicBoolean(false);
final AtomicBoolean gdbServerExited = new AtomicBoolean(false);
final StringBuffer gdbServerOutput = new StringBuffer();
final Object lock = new Object();
final GdbLaunch l = (GdbLaunch) launch;
final Callback callback = new Callback() {
@Override
protected void internalDone(Object caller, IStatus status) {
if (!status.isOK()) {
gdbServerOutput.append(status.getMessage());
gdbServerExited.set(true);
synchronized (lock) {
lock.notifyAll();
}
}
else {
gdbServerStarted.set(true);
}
super.internalDone(caller, status);
}
};
StreamsDataReceiver.Listener listener = new StreamsDataReceiver.Listener() {
@Override
public void dataReceived(String data) {
gdbServerOutput.append(data);
if (data.contains("Listening on port")) { //$NON-NLS-1$
gdbServerReady.set(true);
synchronized (lock) {
lock.notifyAll();
}
}
else if (data.contains("GDBserver exiting") || data.contains("Exiting")) { //$NON-NLS-1$ //$NON-NLS-2$
gdbServerExited.set(true);
synchronized (lock) {
lock.notifyAll();
}
}
}
};
ProcessLauncher launcher = TEHelper.launchCmd(peer, gdbserverCommand, commandArguments, listener, new SubProgressMonitor(monitor, 3), callback);
// Now wait until gdbserver is up and running on the remote host
while (!gdbServerReady.get() && !gdbServerExited.get()) {
if (monitor.isCanceled()) {
// gdbserver launch failed
// Need to shutdown the DSF launch session because it is
// partially started already.
shutdownSession(l, Messages.TEGdbAbstractLaunchDelegate_canceledMsg);
}
if (gdbServerStarted.get() && launcher.getChannel() == null) {
// gdbserver died
shutdownSession(l, gdbServerOutput.toString());
}
synchronized (lock) {
try {
lock.wait(300);
}
catch (InterruptedException e) {
}
}
}
// If the gdbserver exited, also shutdown the DSF launch session
if (gdbServerExited.get()) {
shutdownSession(l, gdbServerOutput.toString());
}
// 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, TEHelper.getCurrentConnection(config).getPeer().getAttributes().get(IPeer.ATTR_IP_HOST));
wc.setAttribute(IGDBLaunchConfigurationConstants.ATTR_PORT, gdbserverPortNumberMappedTo == null || "".equals(gdbserverPortNumberMappedTo) ? gdbserverPortNumber : gdbserverPortNumberMappedTo); //$NON-NLS-1$
wc.doSave();
try {
super.launch(config, mode, launch, monitor);
}
catch (CoreException ex) {
// Launch failed, need to kill gdbserver
launcher.terminate();
// report failure further
throw ex;
}
finally {
monitor.done();
}
}
/* (non-Javadoc)
* @see org.eclipse.cdt.dsf.gdb.launching.GdbLaunchDelegate#launchDebugSession(org.eclipse.debug.core.ILaunchConfiguration, org.eclipse.debug.core.ILaunch, org.eclipse.core.runtime.IProgressMonitor)
*/
@Override
protected void launchDebugSession(ILaunchConfiguration config, ILaunch l, IProgressMonitor monitor) throws CoreException {
super.launchDebugSession(config, l, monitor);
// Determine if the launch is an attach launch
final boolean isAttachLaunch = ICDTLaunchConfigurationConstants.ID_LAUNCH_C_ATTACH.equals(config.getType().getIdentifier());
if (!isAttachLaunch) return;
final IPath exePath = checkBinaryDetails(config);
if (l instanceof GdbLaunch && exePath != null) {
final GdbLaunch launch = (GdbLaunch) l;
final DsfExecutor executor = launch.getDsfExecutor();
final DsfServicesTracker tracker = new DsfServicesTracker(Activator.getDefault().getBundle().getBundleContext(), launch.getSession().getId());
executor.execute(new DsfRunnable() {
@Override
public void run() {
IGDBControl commandControl = tracker.getService(IGDBControl.class);
CommandFactory commandFactory = tracker.getService(IMICommandControl.class).getCommandFactory();
commandControl.queueCommand(
commandFactory.createMIFileSymbolFile(commandControl.getContext(), exePath.toString()),
new ImmediateDataRequestMonitor<MIInfo>());
}
});
}
}
/**
* Shutdown the GDB debug session.
*
* @param launch The GDB launch. Must not be <code>null</code>.
* @throws CoreException If the GDB debug session shutdown failed.
*/
protected void shutdownSession(final GdbLaunch launch) throws CoreException {
shutdownSession(launch, null);
}
/**
* Shutdown the GDB debug session.
*
* @param launch The GDB launch. Must not be <code>null</code>.
* @param details Error message, may be <code>null</code>
* @throws CoreException If the GDB debug session shutdown failed.
*/
protected void shutdownSession(final GdbLaunch launch, String details) throws CoreException {
Assert.isNotNull(launch);
try {
launch.getSession().getExecutor().submit(new DsfRunnable() {
@Override
public void run() {
// Avoid an NPE while running the shutdown
if (launch.getDsfExecutor() != null) {
launch.shutdownSession(new ImmediateRequestMonitor());
}
}
}).get(1000, TimeUnit.MILLISECONDS);
}
catch (RejectedExecutionException e) {
// Session disposed.
}
catch (Exception e) {
// Ignore exceptions during shutdown.
}
String msg = Messages.TEGdbAbstractLaunchDelegate_gdbserverFailedToStartErrorMessage;
if (details != null && details.length() > 0) msg = NLS.bind(Messages.TEGdbAbstractLaunchDelegate_gdbserverFailedToStartErrorWithDetails, details);
abort(msg, null, ICDTLaunchConfigurationConstants.ERR_DEBUGGER_NOT_INSTALLED);
}
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;
}
}