blob: e2600f3b9d59c7df5528822619592abeabb532d6 [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.List;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
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.debug.core.model.ISourceLocator;
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.runtime.services.ServiceUtils;
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.IGdbserverLaunchHandlerDelegate;
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.preferences.IPreferenceKeys;
import org.eclipse.tcf.te.tcf.launch.cdt.utils.TEHelper;
import org.eclipse.tcf.te.tcf.locator.interfaces.nodes.IPeerNode;
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#createGdbLaunch(org.eclipse.debug.core.ILaunchConfiguration, java.lang.String, org.eclipse.debug.core.model.ISourceLocator)
*/
@Override
protected GdbLaunch createGdbLaunch(ILaunchConfiguration configuration, String mode, ISourceLocator locator) throws CoreException {
return new TEGdbLaunch(configuration, mode, locator);
}
/* (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
final AtomicReference<String> gdbserverPortNumber = new AtomicReference<String>(config.getAttribute(IRemoteTEConfigurationConstants.ATTR_GDBSERVER_PORT, TEHelper.getPreferenceValue(IPreferenceKeys.PREF_GDBSERVER_PORT)));
final AtomicReference<String> gdbserverPortNumberMappedTo = new AtomicReference<String>(config.getAttribute(IRemoteTEConfigurationConstants.ATTR_GDBSERVER_PORT_MAPPED_TO, (String) null));
final String gdbserverCommand = config.getAttribute(IRemoteTEConfigurationConstants.ATTR_GDBSERVER_COMMAND, TEHelper.getPreferenceValue(IPreferenceKeys.PREF_GDBSERVER_COMMAND));
final List<String> gdbserverPortNumberAlternatives = config.getAttribute(IRemoteTEConfigurationConstants.ATTR_GDBSERVER_PORT_ALTERNATIVES, (List<String>) null);
final List<String> gdbserverPortNumberMappedToAlternatives = config.getAttribute(IRemoteTEConfigurationConstants.ATTR_GDBSERVER_PORT_MAPPED_TO_ALTERNATIVES, (List<String>) null);
// For quick testing.
// final List<String> gdbserverPortNumberAlternatives = new ArrayList<String>(Arrays.asList(new String[] { "49152", "49153", "49154", "49155", "49156" })); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$
// final List<String> gdbserverPortNumberMappedToAlternatives = new ArrayList<String>(Arrays.asList(new String[] { "49652", "49653", "49654", "49655", "49656" })); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$
// Remember the originally configured port number and mapped to port number
final String origGdbserverPortNumber = gdbserverPortNumber.get();
final String origGdbserverPortNumberMappedTo = gdbserverPortNumberMappedTo.get();
final AtomicReference<String> origGdbserverOutput = new AtomicReference<String>(null);
ProcessLauncher launcher = null;
final AtomicBoolean gdbserverLaunchRetry = new AtomicBoolean(false);
// final AtomicBoolean portRangeExceeded = new AtomicBoolean(false);
final AtomicInteger indexAlternatives = new AtomicInteger(0);
do {
gdbserverLaunchRetry.set(false);
final AtomicBoolean gdbServerStarted = new AtomicBoolean(false);
final AtomicBoolean gdbServerReady = new AtomicBoolean(false);
final AtomicBoolean gdbServerExited = new AtomicBoolean(false);
final StringBuilder gdbServerOutput = new StringBuilder();
final Object lock = new Object();
String commandArguments = ""; //$NON-NLS-1$
if (isAttachLaunch) {
commandArguments = "--attach :" + gdbserverPortNumber.get() + " " + remotePID; //$NON-NLS-1$ //$NON-NLS-2$
monitor.setTaskName(Messages.TEGdbAbstractLaunchDelegate_attaching_program);
} else {
commandArguments = ":" + gdbserverPortNumber.get() + " " + 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 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$
// Check if the gdbserver exited because the port is already in use
if (gdbServerOutput.toString().contains("Address already in use.")) { //$NON-NLS-1$
// If we have still alternatives, then retry the gdbserver launch
// with an alternative port
if (!gdbserverPortNumberAlternatives.isEmpty()) {
String newPort = null;
String newPortMappedTo = null;
do {
newPort = gdbserverPortNumberAlternatives.get(indexAlternatives.get());
if (gdbserverPortNumber.get().equals(newPort)) {
newPort = null;
} else {
newPortMappedTo = gdbserverPortNumberMappedToAlternatives != null && !gdbserverPortNumberMappedToAlternatives.isEmpty() ? gdbserverPortNumberMappedToAlternatives.get(indexAlternatives.get()) : null;
}
indexAlternatives.getAndIncrement();
} while (newPort == null && indexAlternatives.get() < gdbserverPortNumberAlternatives.size());
if (newPort != null) {
// Remember the original error
if (origGdbserverOutput.get() == null) origGdbserverOutput.set(gdbServerOutput.toString());
// Set the flag to retry the gdbserver launch
gdbserverLaunchRetry.set(true);
// Update the ports
gdbserverPortNumber.set(newPort);
gdbserverPortNumberMappedTo.set(newPortMappedTo);
}
}
}
gdbServerExited.set(true);
synchronized (lock) {
lock.notifyAll();
}
}
}
};
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, launcher);
}
if (gdbServerStarted.get() && launcher.getChannel() == null) {
// gdbserver died or exited. Wait a little bit to process
// possible gdbserver output before shutting down the session.
synchronized (lock) {
try {
lock.wait(500);
}
catch (InterruptedException e) { /* ignored on purpose */ }
}
if (!gdbserverLaunchRetry.get()) {
shutdownSession(l, origGdbserverOutput.get() != null ? origGdbserverOutput.get() : gdbServerOutput.toString(), launcher);
}
}
synchronized (lock) {
try {
lock.wait(300);
}
catch (InterruptedException e) { /* ignored on purpose */ }
}
}
// If the gdbserver exited, also shutdown the DSF launch session
if (!gdbserverLaunchRetry.get() && gdbServerExited.get()) {
shutdownSession(l, origGdbserverOutput.get() != null ? origGdbserverOutput.get() : gdbServerOutput.toString(), launcher);
}
} while (gdbserverLaunchRetry.get());
// Set the launcher to the launch
if (launch instanceof TEGdbLaunch) {
((TEGdbLaunch)launch).setLauncher(launcher);
}
// Let debugger know how gdbserver was started on the remote
ILaunchConfigurationWorkingCopy wc = config.getWorkingCopy();
if (!origGdbserverPortNumber.equals(gdbserverPortNumber.get())) {
wc.setAttribute(IRemoteTEConfigurationConstants.ATTR_GDBSERVER_PORT, gdbserverPortNumber.get());
}
if (origGdbserverPortNumberMappedTo != null && !origGdbserverPortNumberMappedTo.equals(gdbserverPortNumberMappedTo.get())) {
wc.setAttribute(IRemoteTEConfigurationConstants.ATTR_GDBSERVER_PORT_MAPPED_TO, gdbserverPortNumberMappedTo.get());
}
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.get() == null || "".equals(gdbserverPortNumberMappedTo.get()) ? gdbserverPortNumber.get() : gdbserverPortNumberMappedTo.get()); //$NON-NLS-1$
wc.doSave();
try {
super.launch(config, mode, launch, monitor);
}
catch (CoreException ex) {
// Launch failed, need to kill gdbserver
if (launcher != null) 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();
executor.execute(new DsfRunnable() {
@Override
public void run() {
DsfServicesTracker tracker = new DsfServicesTracker(Activator.getDefault().getBundle().getBundleContext(), launch.getSession().getId());
try {
IGDBControl commandControl = tracker.getService(IGDBControl.class);
CommandFactory commandFactory = tracker.getService(IMICommandControl.class).getCommandFactory();
commandControl.queueCommand(
commandFactory.createMIFileSymbolFile(commandControl.getContext(), exePath.toString()),
new ImmediateDataRequestMonitor<MIInfo>());
} finally {
tracker.dispose();
}
}
});
}
}
/**
* 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, final String details, final ProcessLauncher launcher) 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());
}
// Make sure that the gdbserver is killed and the launcher resources gets disposed
if (launcher != null) launcher.terminate();
}
}).get(1000, TimeUnit.MILLISECONDS);
}
catch (RejectedExecutionException e) {
// Session disposed.
}
catch (Exception e) {
// Ignore exceptions during shutdown.
}
// Normalize the gdbserver start failure details
String details2 = normalizeDetails(launch, details);
String msg = Messages.TEGdbAbstractLaunchDelegate_gdbserverFailedToStartErrorMessage;
if (details2 != null && details2.length() > 0) msg = NLS.bind(Messages.TEGdbAbstractLaunchDelegate_gdbserverFailedToStartErrorWithDetails, details2);
abort(msg, null, ICDTLaunchConfigurationConstants.ERR_DEBUGGER_NOT_INSTALLED);
}
/**
* Normalize the gdbserver launch failure details message.
*
* @param launch The launch. Must not be <code>null</code>
* @param details The details message or <code>null</code>.
*
* @return The normalized details message or <code>null</code>.
*
* @throws CoreException In case of an failure accessing any launch configuration attribute or similar.
*/
protected String normalizeDetails(final GdbLaunch launch, final String details) throws CoreException {
Assert.isNotNull(launch);
// Get the launch configuration from the launch
final ILaunchConfiguration lc = launch.getLaunchConfiguration();
String d = details;
if (d != null && !"".equals(d)) { //$NON-NLS-1$
// Try the delegate first if available
IPeerNode peerNode = TEHelper.getCurrentConnection(lc);
Assert.isNotNull(peerNode);
IGdbserverLaunchHandlerDelegate delegate = ServiceUtils.getDelegateServiceDelegate(peerNode, peerNode, IGdbserverLaunchHandlerDelegate.class);
if (delegate != null) {
d = delegate.normalizeGdbserverLaunchFailureDetailsMessage(launch, details);
} else {
// Rewrite "Address in use" error.
if (d.contains("Address already in use.")) { //$NON-NLS-1$
// Get host and port
String host = lc.getAttribute(IGDBLaunchConfigurationConstants.ATTR_HOST, (String)null);
String port = lc.getAttribute(IGDBLaunchConfigurationConstants.ATTR_PORT, (String)null);
String address = host + (port != null ? ":" + port : ""); //$NON-NLS-1$ //$NON-NLS-2$
d = NLS.bind(Messages.TEGdbAbstractLaunchDelegate_error_addressInUse, address);
}
}
}
return d;
}
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;
}
/* (non-Javadoc)
* @see org.eclipse.cdt.dsf.gdb.launching.GdbLaunchDelegate#getPluginID()
*/
@Override
protected String getPluginID() {
return Activator.getUniqueIdentifier();
}
}