blob: 8523e08b046bd3260e37ec27db761a63dbf1aa2e [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2011, 2015 Ericsson 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:
* Ericsson - initial API and implementation
* Marc Khouzam (Ericsson) - Support setting the path in which the core file
* dialog should start (Bug 362039)
* Sergey Prigogin (Google) - Bug 381804
*******************************************************************************/
package org.eclipse.cdt.dsf.gdb.service;
import java.io.File;
import java.util.Map;
import java.util.Properties;
import org.eclipse.cdt.debug.core.CDebugUtils;
import org.eclipse.cdt.debug.core.ICDTLaunchConfigurationConstants;
import org.eclipse.cdt.dsf.concurrent.DataRequestMonitor;
import org.eclipse.cdt.dsf.concurrent.DsfExecutor;
import org.eclipse.cdt.dsf.concurrent.IDsfStatusConstants;
import org.eclipse.cdt.dsf.concurrent.ImmediateDataRequestMonitor;
import org.eclipse.cdt.dsf.concurrent.ReflectionSequence;
import org.eclipse.cdt.dsf.concurrent.RequestMonitor;
import org.eclipse.cdt.dsf.datamodel.DMContexts;
import org.eclipse.cdt.dsf.datamodel.IDMContext;
import org.eclipse.cdt.dsf.debug.service.IMemory.IMemoryDMContext;
import org.eclipse.cdt.dsf.debug.service.IRunControl.IContainerDMContext;
import org.eclipse.cdt.dsf.gdb.IGDBLaunchConfigurationConstants;
import org.eclipse.cdt.dsf.gdb.internal.GdbPlugin;
import org.eclipse.cdt.dsf.gdb.launching.LaunchMessages;
import org.eclipse.cdt.dsf.gdb.service.IGDBTraceControl.ITraceTargetDMContext;
import org.eclipse.cdt.dsf.gdb.service.command.IGDBControl;
import org.eclipse.cdt.dsf.mi.service.IMICommandControl;
import org.eclipse.cdt.dsf.mi.service.IMIContainerDMContext;
import org.eclipse.cdt.dsf.mi.service.MIBreakpointsManager;
import org.eclipse.cdt.dsf.mi.service.MIProcesses;
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.cdt.utils.CommandLineUtil;
import org.eclipse.core.runtime.CoreException;
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.core.variables.VariablesPlugin;
import org.eclipse.debug.core.DebugPlugin;
import org.eclipse.debug.core.IStatusHandler;
/**
* This sequence is used to start debugging a new process.
*
* @since 4.0
*/
public class DebugNewProcessSequence extends ReflectionSequence {
private final static String INVALID = "invalid"; //$NON-NLS-1$
private IGDBControl fCommandControl;
private CommandFactory fCommandFactory;
private IGDBBackend fBackend;
private IGDBProcesses fProcService;
private DsfServicesTracker fTracker;
private IDMContext fContext;
private String fBinaryName;
private Map<String, Object> fAttributes;
private IMIContainerDMContext fContainerCtx;
// Store the dataRM so that we can fill it with the container context that we will be creating
private DataRequestMonitor<IDMContext> fDataRequestMonitor;
protected IMIContainerDMContext getContainerContext() {
return fContainerCtx;
}
protected void setContainerContext(IMIContainerDMContext ctx) {
fContainerCtx = ctx;
}
public DebugNewProcessSequence(DsfExecutor executor, boolean isInitial, IDMContext dmc, String file,
Map<String, Object> attributes, DataRequestMonitor<IDMContext> rm) {
super(executor, rm);
fContext = dmc;
fBinaryName = file;
fAttributes = attributes;
fDataRequestMonitor = rm;
}
@Override
protected String[] getExecutionOrder(String group) {
if (GROUP_TOP_LEVEL.equals(group)) {
return new String[] { "stepInitializeBaseSequence", //$NON-NLS-1$
"stepSetEnvironmentVariables", //$NON-NLS-1$
"stepSetExecutable", //$NON-NLS-1$
"stepSetArguments", //$NON-NLS-1$
// For remote non-attach only
"stepRemoteConnection", //$NON-NLS-1$
// For post-mortem launch only
"stepSpecifyCoreFile", //$NON-NLS-1$
"stepInitializeMemory", //$NON-NLS-1$
"stepStartTrackingBreakpoints", //$NON-NLS-1$
"stepStartExecution", //$NON-NLS-1$
"stepCleanupBaseSequence", //$NON-NLS-1$
};
}
return null;
}
/**
* Initialize the members of the DebugNewProcessSequence class.
* This step is mandatory for the rest of the sequence to complete.
*/
@Execute
public void stepInitializeBaseSequence(RequestMonitor rm) {
fTracker = new DsfServicesTracker(GdbPlugin.getBundleContext(), fContext.getSessionId());
fBackend = fTracker.getService(IGDBBackend.class);
fCommandControl = fTracker.getService(IGDBControl.class);
fCommandFactory = fTracker.getService(IMICommandControl.class).getCommandFactory();
fProcService = fTracker.getService(IGDBProcesses.class);
if (fBackend == null || fCommandControl == null || fCommandFactory == null || fProcService == null) {
rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, IDsfStatusConstants.INTERNAL_ERROR,
"Cannot obtain service", null)); //$NON-NLS-1$
rm.done();
return;
}
// When we are starting to debug a new process, the container is the default process used by GDB.
// We don't have a pid yet, so we can simply create the container with the UNIQUE_GROUP_ID
setContainerContext(fProcService.createContainerContextFromGroupId(fCommandControl.getContext(),
MIProcesses.UNIQUE_GROUP_ID));
rm.done();
}
/**
* Rollback method for {@link #stepInitializeBaseSequence()}
* @since 4.0
*/
@RollBack("stepInitializeBaseSequence")
public void rollBackInitializeBaseSequence(RequestMonitor rm) {
if (fTracker != null)
fTracker.dispose();
fTracker = null;
rm.done();
}
/**
* Specify environment variables if needed
*/
@Execute
public void stepSetEnvironmentVariables(RequestMonitor rm) {
boolean clear = false;
Properties properties = new Properties();
try {
// here we need to pass the proper container context
clear = fBackend.getClearEnvironment();
properties = fBackend.getEnvironmentVariables();
} catch (CoreException e) {
rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, IDsfStatusConstants.REQUEST_FAILED,
"Cannot get environment information", e)); //$NON-NLS-1$
rm.done();
return;
}
if (clear == true || !properties.isEmpty()) {
// here we need to pass the proper container context
fCommandControl.setEnvironment(properties, clear, rm);
} else {
rm.done();
}
}
/**
* Specify the executable file to be debugged and read the symbol table.
*/
@Execute
public void stepSetExecutable(RequestMonitor rm) {
boolean noFileCommand = CDebugUtils.getAttribute(fAttributes,
IGDBLaunchConfigurationConstants.ATTR_DEBUGGER_USE_SOLIB_SYMBOLS_FOR_APP,
IGDBLaunchConfigurationConstants.DEBUGGER_USE_SOLIB_SYMBOLS_FOR_APP_DEFAULT);
if (!noFileCommand && fBinaryName != null && !fBinaryName.isEmpty()) {
fCommandControl.queueCommand(fCommandFactory.createMIFileExecAndSymbols(getContainerContext(), fBinaryName),
new ImmediateDataRequestMonitor<MIInfo>(rm));
} else {
rm.done();
}
}
/**
* Specify the arguments to the program that will be run.
*/
@Execute
public void stepSetArguments(RequestMonitor rm) {
try {
String args = CDebugUtils.getAttribute(fAttributes, ICDTLaunchConfigurationConstants.ATTR_PROGRAM_ARGUMENTS,
""); //$NON-NLS-1$
if (args.length() != 0) {
args = VariablesPlugin.getDefault().getStringVariableManager().performStringSubstitution(args);
String[] argArray = CommandLineUtil.argumentsToArray(args);
fCommandControl.queueCommand(fCommandFactory.createMIGDBSetArgs(getContainerContext(), argArray),
new ImmediateDataRequestMonitor<MIInfo>(rm));
} else {
rm.done();
}
} catch (CoreException e) {
rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, IDsfStatusConstants.REQUEST_FAILED,
"Cannot get inferior arguments", e)); //$NON-NLS-1$
rm.done();
}
}
/**
* If we are dealing with a remote debugging session, connect to the target.
* @since 4.0
*/
@Execute
public void stepRemoteConnection(RequestMonitor rm) {
// If we are dealing with a non-attach remote session, it is now time to connect
// to the remote side. Note that this is the 'target remote' case
// and not the 'target extended-remote' case (remote attach session)
// This step is actually global for GDB. However, we have to do it after
// we have specified the executable, so we have to do it here.
// It is safe to do it here because a 'target remote' does not support
// multi-process so this step will not be executed more than once.
if (fBackend.getSessionType() == SessionType.REMOTE && !fBackend.getIsAttachSession()) {
boolean isTcpConnection = CDebugUtils.getAttribute(fAttributes,
IGDBLaunchConfigurationConstants.ATTR_REMOTE_TCP, false);
if (isTcpConnection) {
String remoteTcpHost = CDebugUtils.getAttribute(fAttributes, IGDBLaunchConfigurationConstants.ATTR_HOST,
INVALID);
String remoteTcpPort = CDebugUtils.getAttribute(fAttributes, IGDBLaunchConfigurationConstants.ATTR_PORT,
INVALID);
fCommandControl.queueCommand(fCommandFactory.createMITargetSelect(fCommandControl.getContext(),
remoteTcpHost, remoteTcpPort, false), new ImmediateDataRequestMonitor<MIInfo>(rm));
} else {
String serialDevice = CDebugUtils.getAttribute(fAttributes, IGDBLaunchConfigurationConstants.ATTR_DEV,
INVALID);
fCommandControl.queueCommand(
fCommandFactory.createMITargetSelect(fCommandControl.getContext(), serialDevice, false),
new ImmediateDataRequestMonitor<MIInfo>(rm));
}
} else {
rm.done();
}
}
/** @since 4.0 */
protected static class PromptForCoreJob extends Job {
/**
* The initial path that should be used in the prompt for the core file
* @since 4.1
*/
protected String fInitialPath;
protected DataRequestMonitor<String> fRequestMonitor;
public PromptForCoreJob(String name, DataRequestMonitor<String> rm) {
this(name, null, null, rm);
}
/** @since 4.1 */
public PromptForCoreJob(String name, String coreType, String initialPath, DataRequestMonitor<String> rm) {
super(name);
fInitialPath = initialPath;
fRequestMonitor = rm;
}
@Override
protected IStatus run(IProgressMonitor monitor) {
final IStatus promptStatus = new Status(IStatus.INFO, "org.eclipse.debug.ui", 200/*STATUS_HANDLER_PROMPT*/, //$NON-NLS-1$
"", null); //$NON-NLS-1$
final IStatus filePrompt = new Status(IStatus.INFO, "org.eclipse.cdt.dsf.gdb.ui", 1001, "", null); //$NON-NLS-1$//$NON-NLS-2$
// consult a status handler
final IStatusHandler prompter = DebugPlugin.getDefault().getStatusHandler(promptStatus);
final Status NO_CORE_STATUS = new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, -1,
LaunchMessages.getString("LocalCDILaunchDelegate.6"), //$NON-NLS-1$
null);
if (prompter == null) {
fRequestMonitor.setStatus(NO_CORE_STATUS);
fRequestMonitor.done();
return Status.OK_STATUS;
}
try {
Object result = prompter.handleStatus(filePrompt, fInitialPath);
if (result == null) {
fRequestMonitor.cancel();
} else if (result instanceof String) {
fRequestMonitor.setData((String) result);
} else {
fRequestMonitor.setStatus(NO_CORE_STATUS);
}
} catch (CoreException e) {
fRequestMonitor.setStatus(NO_CORE_STATUS);
}
fRequestMonitor.done();
return Status.OK_STATUS;
}
}
/**
* If we are dealing with a postmortem session, connect to the core/trace file.
* @since 4.0
*/
@Execute
public void stepSpecifyCoreFile(final RequestMonitor rm) {
// If we are dealing with a postmortem session, it is now time to connect
// to the core/trace file. We have to do this step after
// we have specified the executable, so we have to do it here.
// It is safe to do it here because a postmortem session does not support
// multi-process so this step will not be executed more than once.
// Bug 338730
if (fBackend.getSessionType() == SessionType.CORE) {
String coreFile = CDebugUtils.getAttribute(fAttributes, ICDTLaunchConfigurationConstants.ATTR_COREFILE_PATH,
""); //$NON-NLS-1$
try {
// Support variable substitution for the core file path
// Bug 362039
coreFile = VariablesPlugin.getDefault().getStringVariableManager().performStringSubstitution(coreFile,
false);
} catch (CoreException e) {
// Ignore and use core file string as is.
// This should not happen because the dialog will
// prevent the user from making such mistakes
}
final String coreType = CDebugUtils.getAttribute(fAttributes,
IGDBLaunchConfigurationConstants.ATTR_DEBUGGER_POST_MORTEM_TYPE,
IGDBLaunchConfigurationConstants.DEBUGGER_POST_MORTEM_TYPE_DEFAULT);
// We handle three cases:
// 1- Core file specified, in which case we use it
// 2- Nothing specified, in which case we prompt for a core file path
// 3- Path to a directory, in which case we prompt for a core file starting at the specified path
boolean shouldPrompt = false;
coreFile = coreFile.trim();
if (coreFile.length() == 0) {
shouldPrompt = true;
} else {
File filePath = new File(coreFile);
if (filePath.isDirectory()) {
// The user provided a directory. We need to prompt for an actual
// core file, but we'll start off in the specified directory
// Bug 362039
shouldPrompt = true;
}
// else not a directory but an actual core file: use it.
}
if (shouldPrompt) {
new PromptForCoreJob("Prompt for post mortem file", //$NON-NLS-1$
coreType, coreFile, new DataRequestMonitor<String>(getExecutor(), rm) {
@Override
protected void handleCancel() {
rm.cancel();
rm.done();
}
@Override
protected void handleSuccess() {
String newCoreFile = getData();
if (newCoreFile == null || newCoreFile.length() == 0) {
rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, -1,
Messages.Cannot_get_post_mortem_file_path_error, null));
rm.done();
} else {
targetSelectFile(coreType, newCoreFile, rm);
}
}
}).schedule();
} else {
// The user specified something that was not a directory,
// it therefore should be the core file itself. Let's use it.
// First convert to absolute path so that things work even if the user
// specifies a relative path. The reason we do this is that GDB
// may not be using the same root path as Eclipse.
String absoluteCoreFile = new Path(coreFile).toFile().getAbsolutePath();
targetSelectFile(coreType, absoluteCoreFile, rm);
}
} else {
rm.done();
}
}
private void targetSelectFile(String coreType, String file, RequestMonitor rm) {
if (coreType.equals(IGDBLaunchConfigurationConstants.DEBUGGER_POST_MORTEM_CORE_FILE)) {
fCommandControl.queueCommand(fCommandFactory.createMITargetSelectCore(fCommandControl.getContext(), file),
new DataRequestMonitor<MIInfo>(getExecutor(), rm));
} else if (coreType.equals(IGDBLaunchConfigurationConstants.DEBUGGER_POST_MORTEM_TRACE_FILE)) {
IGDBTraceControl traceControl = fTracker.getService(IGDBTraceControl.class);
if (traceControl != null) {
ITraceTargetDMContext targetDmc = DMContexts.getAncestorOfType(fCommandControl.getContext(),
ITraceTargetDMContext.class);
traceControl.loadTraceData(targetDmc, file, rm);
} else {
rm.setStatus(
new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, -1, Messages.Tracing_not_supported_error, null));
rm.done();
}
} else {
rm.setStatus(
new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, -1, Messages.Invalid_post_mortem_type_error, null));
rm.done();
}
}
/**
* Start tracking the breakpoints. Note that for remote debugging
* we should first connect to the target.
*/
@Execute
public void stepStartTrackingBreakpoints(RequestMonitor rm) {
if (fBackend.getSessionType() != SessionType.CORE) {
MIBreakpointsManager bpmService = fTracker.getService(MIBreakpointsManager.class);
bpmService.startTrackingBpForProcess(getContainerContext(), rm);
} else {
rm.done();
}
}
/**
* Initialize the memory service with the data for given process.
* @since 4.2
*/
@Execute
public void stepInitializeMemory(final RequestMonitor rm) {
IGDBMemory memory = fTracker.getService(IGDBMemory.class);
IMemoryDMContext memContext = DMContexts.getAncestorOfType(getContainerContext(), IMemoryDMContext.class);
if (memory == null || memContext == null) {
rm.done();
return;
}
memory.initializeMemoryData(memContext, rm);
}
/**
* Start executing the program.
*/
@Execute
public void stepStartExecution(final RequestMonitor rm) {
if (fBackend.getSessionType() != SessionType.CORE) {
// Overwrite the program name to use the binary name that was specified.
// This is important for multi-process
// Bug 342351
fAttributes.put(ICDTLaunchConfigurationConstants.ATTR_PROGRAM_NAME, fBinaryName);
fProcService.start(getContainerContext(), fAttributes,
new ImmediateDataRequestMonitor<IContainerDMContext>(rm) {
@Override
protected void handleSuccess() {
assert getData() instanceof IMIContainerDMContext;
// Set the container that we created
setContainerContext(DMContexts.getAncestorOfType(getData(), IMIContainerDMContext.class));
fDataRequestMonitor.setData(getContainerContext());
// Don't call fDataRequestMonitor.done(), the sequence will
// automatically do that when it completes;
rm.done();
}
});
} else {
fDataRequestMonitor.setData(getContainerContext());
rm.done();
}
}
/**
* Cleanup now that the sequence has been run.
* @since 4.0
*/
@Execute
public void stepCleanupBaseSequence(RequestMonitor rm) {
fTracker.dispose();
fTracker = null;
rm.done();
}
}