/*******************************************************************************
* This program and the accompanying materials
* are made available under the terms of the Common Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/cpl-v10.html
*
* Contributors:
* Synopsys, Inc. - ARC GNU Toolchain support
*******************************************************************************/
package com.arc.embeddedcdt.dsf;
import java.util.HashMap;
import java.io.File;
import java.io.IOException;
import java.util.Hashtable;
import java.util.Map;
import org.eclipse.cdt.core.parser.util.StringUtil;
import org.eclipse.cdt.dsf.concurrent.RequestMonitor;
import org.eclipse.cdt.dsf.gdb.IGdbDebugConstants;
import org.eclipse.cdt.dsf.gdb.launching.LaunchUtils;
import org.eclipse.cdt.dsf.gdb.service.GDBBackend;
import org.eclipse.cdt.dsf.gdb.service.SessionType;
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.debug.core.DebugPlugin;
import org.eclipse.debug.core.ILaunch;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.debug.core.ILaunchConfiguration;
import org.eclipse.debug.core.model.IProcess;
import org.osgi.framework.BundleContext;
import com.arc.embeddedcdt.LaunchConfigurationConstants;
import com.arc.embeddedcdt.LaunchPlugin;
import com.arc.embeddedcdt.dsf.utils.ConfigurationReader;
/**
* DSF service containing GDB server-related logic:
* commands to start a server,
* commands for GDB to connect to the server,
* process label,
* working directory for the server,
* server start and shutdown methods,
* creating console for the server.
*/
public abstract class GdbServerBackend extends GDBBackend {
private Process process;
private DsfSession session;
protected ILaunchConfiguration launchConfiguration;
public String[] getCommandLineArray() {
return CommandLineUtil.argumentsToArray(getCommandLine());
}
public abstract String getCommandLine();
public abstract String getProcessLabel();
public boolean doLaunchProcess() {
return true;
}
/**
* Get estimated time in milliseconds that is required for server to startup - before it would
* start to listen on a TCP port. This value is passed to Thread.sleep(). Without this delay
* there is a risk that GDB will try to connect before server starts to listen so GDB will exit
* prematurely.
*
* This solution is far from perfect because it uses same delay for all cases, so it might not
* help in some cases where start is even slower - it would be better to at least make this user
* configurable, or even better to scan server output for a line that notifies that it is
* listening. Then that could be handled via some asynchronous DSF magic, I presume. However
* right we don't have time for fancy solutions, and this gets it working at least in most
* cases.
*
* @return Estimated time in milliseconds for server start listening on TCP socket.
*/
protected int getStartupDelayEstimate() {
return 500;
}
public String getCommandToConnect() {
return String.format("\ntarget remote %s:%s\nload\n", getHostAddress(), getPortToConnect());
}
// For OpenOCD on AXS10x this port might be different from the one in the launch configuration
protected String getPortToConnect() {
ConfigurationReader cfgReader = new ConfigurationReader(launchConfiguration);
return cfgReader.getGdbServerPort();
}
protected String getHostAddress() {
return LaunchConfigurationConstants.DEFAULT_GDB_HOST;
}
public File getWorkingDirectory() {
return null;
}
public GdbServerBackend(DsfSession session, ILaunchConfiguration launchConfiguration) {
super(session, launchConfiguration);
this.session = session;
this.launchConfiguration = launchConfiguration;
}
@Override
protected BundleContext getBundleContext() {
return LaunchPlugin.getDefault().getBundle().getBundleContext();
}
@Override
public void initialize(final RequestMonitor requestMonitor) {
register( new String[]{ GdbServerBackend.class.getName() },
new Hashtable<String,String>() );
if (!doLaunchProcess()) {
requestMonitor.done();
return;
}
try {
process = launchGDBProcess(getCommandLineArray(), getWorkingDirectory());
} catch (CoreException e) {
e.printStackTrace();
} finally {
requestMonitor.done();
}
}
/**
* Add server process to the launch and assign to the process a label and a command line.
*
* @throws CoreException
*/
public void initializeServerConsole() throws CoreException, InterruptedException {
if (doLaunchProcess()) {
IProcess newProcess = addServerProcess(getProcessLabel());
newProcess.setAttribute(IProcess.ATTR_CMDLINE, getCommandLine());
}
}
/**
* Add server process to the launch: show it in the debug tree and add its console.
*
* @param label
* Label to assign to the process
* @return process added to the launch
* @throws CoreException
*/
private IProcess addServerProcess(String label) throws CoreException, InterruptedException {
Map<String, String> attributes = new HashMap<String, String>();
attributes.put(IGdbDebugConstants.PROCESS_TYPE_CREATION_ATTR,
IGdbDebugConstants.GDB_PROCESS_CREATION_VALUE);
ILaunch launch = (ILaunch) session.getModelAdapter(ILaunch.class);
IProcess newProcess = null;
Process serverProc = getProcess();
if (serverProc != null) {
newProcess = DebugPlugin.newProcess(launch, serverProc, label, attributes);
}
if (getStartupDelayEstimate() > 0) {
Thread.sleep(getStartupDelayEstimate());
}
return newProcess;
}
private Process launchGDBProcess(String[] cmdArray, File workingDir) throws CoreException {
Process proc = null;
try {
proc = ProcessFactory.getFactory().exec(cmdArray,
LaunchUtils.getLaunchEnvironment(launchConfiguration), workingDir);
} catch (IOException e) {
String message = "Error while launching command: " + StringUtil.join(cmdArray, " ");
throw new CoreException(new Status(IStatus.ERROR, LaunchPlugin.PLUGIN_ID, -1, message, e));
}
return proc;
}
@Override
public void shutdown(RequestMonitor requestMonitor) {
unregister();
interrupt();
requestMonitor.done();
}
@Override
public Process getProcess() {
return process;
}
@Override
public void interrupt() {
if (process != null && process instanceof Spawner) {
Spawner gdbSpawner = (Spawner) process;
if (getSessionType() == SessionType.REMOTE) {
gdbSpawner.interrupt();
} else {
gdbSpawner.interruptCTRLC();
}
}
}
}