blob: ac18ef3e340122677ae9a6f88b66880810b5b000 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2011, 2017 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
* Sergey Prigogin (Google)
* Anton Gorenkov - Need to use a process factory (Bug 210366)
* Intel Corporation - Added Reverse Debugging BTrace support
*******************************************************************************/
package org.eclipse.cdt.dsf.gdb.service;
import java.io.IOException;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.Map;
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.ImmediateRequestMonitor;
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.debug.service.IBreakpoints.IBreakpointsTargetDMContext;
import org.eclipse.cdt.dsf.debug.service.IRunControl.IContainerDMContext;
import org.eclipse.cdt.dsf.debug.service.command.ICommand;
import org.eclipse.cdt.dsf.gdb.IGDBLaunchConfigurationConstants;
import org.eclipse.cdt.dsf.gdb.IGdbDebugPreferenceConstants;
import org.eclipse.cdt.dsf.gdb.internal.GdbPlugin;
import org.eclipse.cdt.dsf.gdb.launching.LaunchUtils;
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.command.CommandFactory;
import org.eclipse.cdt.dsf.mi.service.command.MIInferiorProcess;
import org.eclipse.cdt.dsf.mi.service.command.output.MIBreakInsertInfo;
import org.eclipse.cdt.dsf.mi.service.command.output.MIBreakpoint;
import org.eclipse.cdt.dsf.mi.service.command.output.MIGDBShowNewConsoleInfo;
import org.eclipse.cdt.dsf.mi.service.command.output.MIInfo;
import org.eclipse.cdt.dsf.service.DsfServicesTracker;
import org.eclipse.cdt.utils.pty.PTY;
import org.eclipse.cdt.utils.pty.PersistentPTY;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Status;
/**
* This class causes a process to start (run for the first time), or to
* be restarted. The complexity is due to the handling of reverse debugging,
* which this class transparently enables if necessary.
*
* This sequence is used for GDB >= 7.0 which supports reverse debugging.
*
* @since 4.0
*/
public class StartOrRestartProcessSequence_7_0 extends ReflectionSequence {
private IGDBControl fCommandControl;
private CommandFactory fCommandFactory;
private IGDBProcesses fProcService;
private IReverseRunControl fReverseService;
private IGDBBackend fBackend;
private DsfServicesTracker fTracker;
// This variable will be used to store the original container context,
// but once the new process is started (restarted), it will contain the new
// container context. This new container context has for parent the process
// context, which holds the new pid.
private IContainerDMContext fContainerDmc;
// If the user requested a stop_on_main, this variable will hold the breakpoint
private MIBreakpoint fUserBreakpoint;
// Since the stop_on_main option allows the user to set the breakpoint on any
// symbol, we use this variable to know if the stop_on_main breakpoint was really
// on the main() method.
private boolean fUserBreakpointIsOnMain;
private MIBreakpoint fBreakPointForReverse;
private boolean fReverseEnabled;
private final Map<String, Object> fAttributes;
// Indicates if the sequence is being used for a restart or a start
private final boolean fRestart;
private PTY fPty;
// Store the dataRM so that we can fill it with the new container context, which we must return
// Although we can access this through Sequence.getRequestMonitor(), we would loose the type-checking.
// Therefore, doing it like this is more future-proof.
private final DataRequestMonitor<IContainerDMContext> fDataRequestMonitor;
protected IContainerDMContext getContainerContext() {
return fContainerDmc;
}
protected MIBreakpoint getUserBreakpoint() {
return fUserBreakpoint;
}
/**
* @since 5.2
*/
protected MIBreakpoint getBreakPointForReverse() {
return fBreakPointForReverse;
}
protected boolean getUserBreakpointIsOnMain() {
return fUserBreakpointIsOnMain;
}
/** @since 5.0 */
protected boolean getReverseEnabled() {
return fReverseEnabled;
}
public StartOrRestartProcessSequence_7_0(DsfExecutor executor, IContainerDMContext containerDmc,
Map<String, Object> attributes, boolean restart, DataRequestMonitor<IContainerDMContext> rm) {
super(executor, rm);
assert executor != null;
assert containerDmc != null;
if (attributes == null) {
// If no attributes are specified, simply use an empty map.
attributes = new HashMap<>();
}
fContainerDmc = containerDmc;
fAttributes = attributes;
fRestart = restart;
fDataRequestMonitor = rm;
}
@Override
protected String[] getExecutionOrder(String group) {
if (GROUP_TOP_LEVEL.equals(group)) {
return new String[] { "stepInitializeBaseSequence", //$NON-NLS-1$
"stepInsertStopOnMainBreakpoint", //$NON-NLS-1$
"stepSetBreakpointForReverse", //$NON-NLS-1$
"stepInitializeInputOutput", //$NON-NLS-1$
"stepCreateConsole", //$NON-NLS-1$
"stepRunProgram", //$NON-NLS-1$
"stepSetReverseOff", //$NON-NLS-1$
"stepEnableReverse", //$NON-NLS-1$
"stepContinue", //$NON-NLS-1$
"stepCleanupBaseSequence", //$NON-NLS-1$
};
}
return null;
}
/**
* Initialize the members of the StartOrRestartProcessSequence_7_0 class.
* This step is mandatory for the rest of the sequence to complete.
*/
@Execute
public void stepInitializeBaseSequence(RequestMonitor rm) {
fTracker = new DsfServicesTracker(GdbPlugin.getBundleContext(), fContainerDmc.getSessionId());
fCommandControl = fTracker.getService(IGDBControl.class);
fCommandFactory = fTracker.getService(IMICommandControl.class).getCommandFactory();
fProcService = fTracker.getService(IGDBProcesses.class);
fBackend = fTracker.getService(IGDBBackend.class);
if (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;
}
fReverseService = fTracker.getService(IReverseRunControl.class);
if (fReverseService != null) {
// Although the option to use reverse debugging could be on, we only check
// it if we actually have a reverse debugging service. There is no point
// in trying to handle reverse debugging if it is not available.
fReverseEnabled = CDebugUtils.getAttribute(fAttributes,
IGDBLaunchConfigurationConstants.ATTR_DEBUGGER_REVERSE,
IGDBLaunchConfigurationConstants.DEBUGGER_REVERSE_DEFAULT);
}
rm.done();
}
/**
* Rollback method for {@link #stepInitializeBaseSequence()}
*/
@RollBack("stepInitializeBaseSequence")
public void rollBackInitializeBaseSequence(RequestMonitor rm) {
if (fTracker != null)
fTracker.dispose();
fTracker = null;
rm.done();
}
/**
* If the user requested a 'stopAtMain', let's set the temporary breakpoint
* where the user specified.
*/
@Execute
public void stepInsertStopOnMainBreakpoint(final RequestMonitor rm) {
boolean userRequestedStop = CDebugUtils.getAttribute(fAttributes,
ICDTLaunchConfigurationConstants.ATTR_DEBUGGER_STOP_AT_MAIN, LaunchUtils.getStopAtMainDefault());
if (userRequestedStop) {
String userStopSymbol = CDebugUtils.getAttribute(fAttributes,
ICDTLaunchConfigurationConstants.ATTR_DEBUGGER_STOP_AT_MAIN_SYMBOL,
LaunchUtils.getStopAtMainSymbolDefault());
IBreakpointsTargetDMContext bpTargetDmc = DMContexts.getAncestorOfType(getContainerContext(),
IBreakpointsTargetDMContext.class);
fCommandControl.queueCommand(
fCommandFactory.createMIBreakInsert(bpTargetDmc, true, false, null, 0, userStopSymbol, "0"), //$NON-NLS-1$
new ImmediateDataRequestMonitor<MIBreakInsertInfo>(rm) {
@Override
public void handleSuccess() {
if (getData() != null) {
MIBreakpoint[] breakpoints = getData().getMIBreakpoints();
if (breakpoints.length > 0) {
fUserBreakpoint = breakpoints[0];
}
}
rm.done();
}
});
} else {
rm.done();
}
}
/**
* If reverse debugging, set a breakpoint on main to be able to enable reverse
* as early as possible.
* If the user has requested a stop at the same point, we could skip this breakpoint
* however, we have to first set it to find out! So, we just leave it.
*/
@Execute
public void stepSetBreakpointForReverse(final RequestMonitor rm) {
if (fReverseEnabled) {
IBreakpointsTargetDMContext bpTargetDmc = DMContexts.getAncestorOfType(getContainerContext(),
IBreakpointsTargetDMContext.class);
fCommandControl.queueCommand(
fCommandFactory.createMIBreakInsert(bpTargetDmc, true, false, null, 0,
ICDTLaunchConfigurationConstants.DEBUGGER_STOP_AT_MAIN_SYMBOL_DEFAULT, "0"), //$NON-NLS-1$
new ImmediateDataRequestMonitor<MIBreakInsertInfo>(rm) {
@Override
public void handleSuccess() {
if (getData() != null) {
MIBreakpoint[] breakpoints = getData().getMIBreakpoints();
if (breakpoints.length > 0) {
fBreakPointForReverse = breakpoints[0];
if (fUserBreakpoint != null) {
fUserBreakpointIsOnMain = fBreakPointForReverse.getAddress()
.equals(fUserBreakpoint.getAddress());
}
}
}
rm.done();
}
});
} else {
rm.done();
}
}
/**
* This method does the necessary work to setup the input/output streams for the
* inferior process, by either preparing the PTY to be used, or by simply leaving
* the PTY null, which indicates that the input/output streams are handled externally;
* this decision is based on the type of session.
*/
@Execute
public void stepInitializeInputOutput(final RequestMonitor rm) {
if (fBackend.getSessionType() == SessionType.REMOTE) {
// The program input and output for a remote session is handled by gdbserver.
// Therefore, no need to create a pty.
fPty = null;
rm.done();
} else {
if (fRestart) {
// For a restart we re-use the previous PersistentPTY (or no pty if we couldn't tell GDB about it)
assert fPty instanceof PersistentPTY || fPty == null;
rm.done();
return;
}
boolean externalConsoleDefault = Platform.getPreferencesService().getBoolean(GdbPlugin.PLUGIN_ID,
IGdbDebugPreferenceConstants.PREF_EXTERNAL_CONSOLE,
IGDBLaunchConfigurationConstants.DEBUGGER_EXTERNAL_CONSOLE_DEFAULT, null);
boolean externalConsole = CDebugUtils.getAttribute(fAttributes,
IGDBLaunchConfigurationConstants.ATTR_DEBUGGER_EXTERNAL_CONSOLE, externalConsoleDefault);
if (externalConsole) {
initializeExternalConsole(new ImmediateRequestMonitor(rm) {
@Override
protected void handleCompleted() {
if (isSuccess()) {
fPty = null;
rm.done();
} else {
initializePty(rm);
}
}
});
} else {
initializePty(rm);
}
}
}
private void initializeExternalConsole(final RequestMonitor rm) {
fCommandControl.queueCommand(fCommandFactory.createMIGDBShowNewConsole(getContainerContext()),
new ImmediateDataRequestMonitor<MIGDBShowNewConsoleInfo>(rm) {
@Override
protected void handleSuccess() {
fCommandControl.queueCommand(
fCommandFactory.createMIGDBSetNewConsole(getContainerContext(), true),
new ImmediateDataRequestMonitor<MIInfo>(rm));
}
});
}
private void initializePty(final RequestMonitor rm) {
// Every other type of session that can get to this code, is starting a new process
// and requires a pty for it.
try {
// Use a PersistentPTY so it can be re-used for restarts.
// It is possible that the inferior will be restarted by the user from
// the GDB console, in which case, we are not able to create a new PTY
// for it; using a persistentPTY allows this to work since the persistentPTY
// does not need to be replaced but can continue to be used.
fPty = new PersistentPTY();
fPty.validateSlaveName();
// Tell GDB to use this PTY
fCommandControl.queueCommand(fCommandFactory
.createMIInferiorTTYSet((IMIContainerDMContext) getContainerContext(), fPty.getSlaveName()),
new ImmediateDataRequestMonitor<MIInfo>(rm) {
@Override
protected void handleFailure() {
// We were not able to tell GDB to use the PTY
// so we won't use it at all.
fPty = null;
rm.done();
}
});
} catch (IOException e) {
fPty = null;
rm.done();
}
}
/**
* @since 4.7
* @deprecated The creation of MIInferiorProcess has been moved to the IGDBProcesses service
*/
@Deprecated
protected MIInferiorProcess createInferiorProcess(IContainerDMContext container, OutputStream outputStream) {
return new MIInferiorProcess(container, outputStream);
}
/**
* @since 4.7
* @deprecated The creation of MIInferiorProcess has been moved to the IGDBProcesses service
*/
@Deprecated
protected MIInferiorProcess createInferiorProcess(IContainerDMContext container, PTY pty) {
return new MIInferiorProcess(container, pty);
}
/**
* Before running the program, we must create its console for IO.
*/
@Execute
public void stepCreateConsole(RequestMonitor rm) {
if (fBackend.getSessionType() == SessionType.REMOTE) {
// The program output for a remote session is handled by gdbserver. Therefore,
// no need to create an inferior process and add it to the launch.
rm.done();
return;
}
if (fRestart) {
// For a restart, the IGDBProcesses service already handles creating the new
// console. We do it this way because a restart can be triggered by the user
// on the GDB console, so this class isn't even called in that case.
rm.done();
return;
}
// For multi-process, we cannot simply use the name given by the backend service
// because we may not be starting that process, but another one.
// Instead, we can look in the attributes for the binary name, which we stored
// there for this case, specifically.
// Bug 342351
IGDBBackend backend = fTracker.getService(IGDBBackend.class);
String defaultPathName = backend.getProgramPath().lastSegment();
if (defaultPathName == null) {
defaultPathName = ""; //$NON-NLS-1$
}
String progPathName = CDebugUtils.getAttribute(fAttributes, ICDTLaunchConfigurationConstants.ATTR_PROGRAM_NAME,
defaultPathName);
final String pathLabel = new Path(progPathName).lastSegment();
// Adds the inferior to the launch which will also create the console
fProcService.addInferiorToLaunch(fContainerDmc, pathLabel, fPty, new ImmediateRequestMonitor() {
@Override
protected void handleCompleted() {
// Accept errors. The console won't be created but the rest will mostly work
// This can especially happen if extenders did not implement IGDBProcesses.addInferiorToLaunch()
rm.done();
}
});
}
/**
* Now, run the program.
*/
@Execute
public void stepRunProgram(final RequestMonitor rm) {
ICommand<MIInfo> command;
if (useContinueCommand()) {
command = fCommandFactory.createMIExecContinue(fContainerDmc);
} else {
command = fCommandFactory.createMIExecRun(fContainerDmc);
}
fCommandControl.queueCommand(command, new ImmediateDataRequestMonitor<MIInfo>(rm) {
@Override
protected void handleSuccess() {
// Now that the process is started, the pid has been allocated
// so we need to fetch the proper container context
// We replace our current context which does not have the pid, with one that has the pid.
if (fContainerDmc instanceof IMIContainerDMContext) {
fContainerDmc = fProcService.createContainerContextFromGroupId(fCommandControl.getContext(),
((IMIContainerDMContext) fContainerDmc).getGroupId());
// This is the container context that this sequence is supposed to return: set the dataRm
fDataRequestMonitor.setData(fContainerDmc);
} else {
assert false : "Container context was not an IMIContainerDMContext"; //$NON-NLS-1$
}
rm.done();
}
});
}
/**
* In case of a restart, we must mark reverse debugging as disabled because
* GDB has turned it off. We may have to turn it back on after.
*/
@Execute
public void stepSetReverseOff(RequestMonitor rm) {
if (fRestart) {
GDBRunControl_7_0 reverseService = fTracker.getService(GDBRunControl_7_0.class);
if (reverseService != null) {
reverseService.setReverseModeEnabled(false);
}
}
rm.done();
}
/**
* Since we have started the program, we can turn on reverse debugging if needed.
* We know the program will stop since we set a breakpoint on main, to enable reverse.
*/
@Execute
public void stepEnableReverse(RequestMonitor rm) {
if (fReverseEnabled) {
fReverseService.enableReverseMode(fCommandControl.getContext(), true, rm);
} else {
rm.done();
}
}
/**
* Finally, if we are enabling reverse, and the userSymbolStop is not on main,
* we should do a continue because we are currently stopped on main but that
* is not what the user requested
*/
@Execute
public void stepContinue(RequestMonitor rm) {
if (fReverseEnabled && !fUserBreakpointIsOnMain) {
fCommandControl.queueCommand(fCommandFactory.createMIExecContinue(fContainerDmc),
new ImmediateDataRequestMonitor<MIInfo>(rm));
} else {
rm.done();
}
}
/**
* Cleanup now that the sequence has been run.
*/
@Execute
public void stepCleanupBaseSequence(final RequestMonitor rm) {
fTracker.dispose();
fTracker = null;
rm.done();
}
/**
* This method indicates if we should use the -exec-continue command
* instead of the -exec-run command.
* This method can be overridden to allow for customization.
*/
protected boolean useContinueCommand() {
// Note that restart does not apply to remote sessions
IGDBBackend backend = fTracker.getService(IGDBBackend.class);
if (backend == null) {
return false;
}
// When doing remote non-attach debugging, we use -exec-continue instead of -exec-run
// For remote attach, if we get here it is that we are starting a new process
// (multi-process), so we want to use -exec-run
return backend.getSessionType() == SessionType.REMOTE && !backend.getIsAttachSession();
}
}