blob: 7270f44707a7cf9a503edce46eb6ce644033c905 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2009, 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) - Listen for IReverseModeChangedDMEvent (Bug 399163)
* Intel Corporation - Added Reverse Debugging BTrace support
*******************************************************************************/
package org.eclipse.cdt.dsf.gdb.internal.ui.commands;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import org.eclipse.cdt.debug.core.model.IChangeReverseMethodHandler;
import org.eclipse.cdt.dsf.concurrent.DataRequestMonitor;
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.Query;
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.IRunControl;
import org.eclipse.cdt.dsf.debug.service.IRunControl.IExecutionDMContext;
import org.eclipse.cdt.dsf.debug.service.command.ICommandControlService.ICommandControlDMContext;
import org.eclipse.cdt.dsf.gdb.IGdbDebugPreferenceConstants;
import org.eclipse.cdt.dsf.gdb.internal.GdbPlugin;
import org.eclipse.cdt.dsf.gdb.internal.ui.GdbUIPlugin;
import org.eclipse.cdt.dsf.gdb.service.IReverseRunControl;
import org.eclipse.cdt.dsf.gdb.service.IReverseRunControl.IReverseModeChangedDMEvent;
import org.eclipse.cdt.dsf.gdb.service.IReverseRunControl2;
import org.eclipse.cdt.dsf.service.DsfServiceEventHandler;
import org.eclipse.cdt.dsf.service.DsfServicesTracker;
import org.eclipse.cdt.dsf.service.DsfSession;
import org.eclipse.cdt.dsf.ui.viewmodel.datamodel.IDMVMContext;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Status;
import org.eclipse.debug.core.IRequest;
import org.eclipse.debug.core.commands.AbstractDebugCommand;
import org.eclipse.debug.core.commands.IDebugCommandRequest;
import org.eclipse.debug.core.commands.IEnabledStateRequest;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.commands.ICommandService;
import org.eclipse.ui.progress.WorkbenchJob;
import org.eclipse.ui.services.IEvaluationService;
/**
* Command that toggles the Reverse Debugging feature
*
* @since 2.1
*/
public class GdbReverseToggleCommand extends AbstractDebugCommand implements IChangeReverseMethodHandler {
private final DsfExecutor fExecutor;
private final DsfServicesTracker fTracker;
private final DsfSession fSession;
/** The reverse debugging method that was last returned by the service **/
private ReverseDebugMethod fCurrentMethod;
/** The reverse debugging method that was used before the new method was selected **/
private ReverseDebugMethod fPreviousMethod;
/** The reverse debugging method to be used when the toggle button is selected */
private ReverseDebugMethod fNextMethod;
public GdbReverseToggleCommand(DsfSession session) {
fExecutor = session.getExecutor();
fTracker = new DsfServicesTracker(GdbUIPlugin.getBundleContext(), session.getId());
fSession = session;
fCurrentMethod = ReverseDebugMethod.OFF;
fPreviousMethod = ReverseDebugMethod.OFF;
fNextMethod = ReverseDebugMethod.OFF;
try {
fExecutor.execute(new DsfRunnable() {
@Override
public void run() {
fSession.addServiceEventListener(GdbReverseToggleCommand.this, null);
}
});
} catch (RejectedExecutionException e) {
}
}
public void dispose() {
try {
fExecutor.execute(new DsfRunnable() {
@Override
public void run() {
fSession.removeServiceEventListener(GdbReverseToggleCommand.this);
}
});
} catch (RejectedExecutionException e) {
// Session already gone.
}
fTracker.dispose();
}
@Override
protected void doExecute(Object[] targets, IProgressMonitor monitor, final IRequest request) throws CoreException {
if (targets.length != 1) {
return;
}
IDMContext dmc = ((IDMVMContext) targets[0]).getDMContext();
final ICommandControlDMContext controlDmc = DMContexts.getAncestorOfType(dmc, ICommandControlDMContext.class);
if (controlDmc == null) {
return;
}
Query<Object> setReverseMode = new Query<>() {
@Override
public void execute(final DataRequestMonitor<Object> rm) {
final IReverseRunControl2 runControl = fTracker.getService(IReverseRunControl2.class);
if (runControl != null) {
final ReverseDebugMethod newMethod;
if (fNextMethod == ReverseDebugMethod.HARDWARE) {
String defaultValue = Platform.getPreferencesService().getString(GdbPlugin.PLUGIN_ID,
IGdbDebugPreferenceConstants.PREF_REVERSE_TRACE_METHOD_HARDWARE,
IGdbDebugPreferenceConstants.PREF_REVERSE_TRACE_METHOD_GDB_TRACE, null);
if (defaultValue.equals(IGdbDebugPreferenceConstants.PREF_REVERSE_TRACE_METHOD_BRANCH_TRACE)) {
newMethod = ReverseDebugMethod.BRANCH_TRACE;
} else if (defaultValue
.equals(IGdbDebugPreferenceConstants.PREF_REVERSE_TRACE_METHOD_PROCESSOR_TRACE)) {
newMethod = ReverseDebugMethod.PROCESSOR_TRACE;
} else {
newMethod = ReverseDebugMethod.GDB_TRACE;
}
} else {
newMethod = fNextMethod;
}
runControl.enableReverseMode(controlDmc, newMethod, new RequestMonitor(fExecutor, rm) {
@Override
public void handleError() {
// Call the parent function
// Since otherwise the status is not updated
super.handleError();
// Here we avoid setting any status other than OK, since we want to
// avoid the default dialog box from eclipse and we propagate the error
// with the plugin specific code of 1, here the ReverseToggleCommandHandler
// interprets it as, the selected trace method is not available
if (newMethod == ReverseDebugMethod.PROCESSOR_TRACE) {
request.setStatus(new Status(IStatus.OK, GdbPlugin.PLUGIN_ID, 1,
Messages.GdbReverseDebugging_ProcessorTraceReverseDebugNotAvailable, null));
} else if (newMethod == ReverseDebugMethod.BRANCH_TRACE
|| newMethod == ReverseDebugMethod.GDB_TRACE) {
request.setStatus(new Status(IStatus.OK, GdbPlugin.PLUGIN_ID, 1,
Messages.GdbReverseDebugging_HardwareReverseDebugNotAvailable, null));
} else {
request.setStatus(new Status(IStatus.OK, GdbPlugin.PLUGIN_ID, 1,
Messages.GdbReverseDebugging_ReverseDebugNotAvailable, null));
}
}
});
} else {
final IReverseRunControl runControl_old = fTracker.getService(IReverseRunControl.class);
if (runControl_old != null) {
if (fCurrentMethod != ReverseDebugMethod.OFF && fCurrentMethod != ReverseDebugMethod.SOFTWARE) {
runControl_old.enableReverseMode(controlDmc, false, rm); // Switch Off tracing
request.setStatus(new Status(IStatus.OK, GdbPlugin.PLUGIN_ID, 1,
Messages.GdbReverseDebugging_HardwareReverseDebugNotAvailable, null));
return;
}
runControl_old.isReverseModeEnabled(controlDmc, new DataRequestMonitor<Boolean>(fExecutor, rm) {
@Override
public void handleSuccess() {
runControl_old.enableReverseMode(controlDmc, !getData(), rm);
}
});
} else {
rm.done();
}
}
}
};
try {
fExecutor.execute(setReverseMode);
setReverseMode.get();
} catch (InterruptedException e) {
} catch (ExecutionException e) {
} catch (RejectedExecutionException e) {
// Can be thrown if the session is shutdown
}
}
@Override
protected boolean isExecutable(Object[] targets, IProgressMonitor monitor, IEnabledStateRequest request)
throws CoreException {
if (targets.length != 1) {
return false;
}
IDMContext dmc = ((IDMVMContext) targets[0]).getDMContext();
final ICommandControlDMContext controlDmc = DMContexts.getAncestorOfType(dmc, ICommandControlDMContext.class);
final IExecutionDMContext execDmc = DMContexts.getAncestorOfType(dmc, IExecutionDMContext.class);
if (controlDmc == null && execDmc == null) {
return false;
}
Query<Boolean> canSetReverseMode = new Query<>() {
@Override
public void execute(DataRequestMonitor<Boolean> rm) {
IReverseRunControl runControl = fTracker.getService(IReverseRunControl.class);
// Only allow to toggle reverse if the program is suspended.
// When the program is running, GDB will not answer our command
// in toggleReverse() and since it is blocking, it will hang the entire UI!
if (runControl != null && runControl instanceof IRunControl
&& ((IRunControl) runControl).isSuspended(execDmc)) {
runControl.canEnableReverseMode(controlDmc, rm);
} else {
rm.setData(false);
rm.done();
}
}
};
try {
fExecutor.execute(canSetReverseMode);
return canSetReverseMode.get();
} catch (InterruptedException e) {
} catch (ExecutionException e) {
} catch (RejectedExecutionException e) {
// Can be thrown if the session is shutdown
}
return false;
}
@Override
protected Object getTarget(Object element) {
if (element instanceof IDMVMContext) {
return element;
}
return null;
}
@Override
protected boolean isRemainEnabled(IDebugCommandRequest request) {
return true;
}
@Override
public boolean toggleNeedsUpdating() {
return true;
}
@Override
public boolean isReverseToggled(Object context) {
IDMContext dmc;
if (context instanceof IDMContext) {
dmc = (IDMContext) context;
} else if (context instanceof IDMVMContext) {
dmc = ((IDMVMContext) context).getDMContext();
} else {
return false;
}
final ICommandControlDMContext controlDmc = DMContexts.getAncestorOfType(dmc, ICommandControlDMContext.class);
if (controlDmc == null) {
return false;
}
Query<Boolean> isToggledQuery = new Query<>() {
@Override
public void execute(final DataRequestMonitor<Boolean> rm) {
final IReverseRunControl runControl = fTracker.getService(IReverseRunControl.class);
if (runControl != null) {
runControl.isReverseModeEnabled(controlDmc, rm);
} else {
rm.setData(false);
rm.done();
}
}
};
try {
fExecutor.execute(isToggledQuery);
return isToggledQuery.get(500, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
} catch (ExecutionException e) {
} catch (RejectedExecutionException e) {
// Can be thrown if the session is shutdown
} catch (TimeoutException e) {
// If we timeout, we default to false.
// This is to avoid a deadlock
}
return false;
}
/**
* @noreference This method is not intended to be referenced by clients.
*/
@DsfServiceEventHandler
public void eventDispatched(IReverseModeChangedDMEvent e) {
new WorkbenchJob("") { //$NON-NLS-1$
@Override
public IStatus runInUIThread(IProgressMonitor monitor) {
// Request re-evaluation of property "org.eclipse.cdt.debug.ui.isReverseDebuggingEnabled" to update
// visibility of reverse stepping commands.
IEvaluationService exprService = PlatformUI.getWorkbench().getService(IEvaluationService.class);
if (exprService != null) {
exprService.requestEvaluation("org.eclipse.cdt.debug.ui.isReverseDebuggingEnabled"); //$NON-NLS-1$
}
// Refresh reverse toggle commands with the new state of reverse enabled.
// This is in order to keep multiple toggle actions in UI in sync.
ICommandService commandService = PlatformUI.getWorkbench().getService(ICommandService.class);
if (commandService != null) {
commandService.refreshElements("org.eclipse.cdt.debug.ui.command.reverseToggle", null); //$NON-NLS-1$
}
return Status.OK_STATUS;
}
}.schedule();
}
@Override
public void setReverseDebugMethod(ReverseDebugMethod traceMethod) {
fNextMethod = traceMethod;
}
@Override
public ReverseDebugMethod getReverseDebugMethod(final Object context) {
IDMContext dmc;
if (context instanceof IDMContext) {
dmc = (IDMContext) context;
} else if (context instanceof IDMVMContext) {
dmc = ((IDMVMContext) context).getDMContext();
} else {
return ReverseDebugMethod.OFF;
}
final ICommandControlDMContext controlDmc = DMContexts.getAncestorOfType(dmc, ICommandControlDMContext.class);
if (controlDmc == null) {
return ReverseDebugMethod.OFF;
}
Query<ReverseDebugMethod> reverseMethodQuery = new Query<>() {
@Override
public void execute(DataRequestMonitor<ReverseDebugMethod> rm) {
IReverseRunControl2 runControl = fTracker.getService(IReverseRunControl2.class);
if (runControl != null) {
runControl.getReverseTraceMethod(controlDmc,
new ImmediateDataRequestMonitor<ReverseDebugMethod>(rm) {
@Override
protected void handleCompleted() {
if (!isSuccess()) {
rm.done(ReverseDebugMethod.OFF);
} else {
ReverseDebugMethod method = getData();
if (method == ReverseDebugMethod.BRANCH_TRACE
|| method == ReverseDebugMethod.PROCESSOR_TRACE
|| method == ReverseDebugMethod.GDB_TRACE) {
method = ReverseDebugMethod.HARDWARE;
}
rm.done(method);
}
}
});
} else {
IReverseRunControl runControl_old = fTracker.getService(IReverseRunControl.class);
if (runControl_old != null) {
runControl_old.isReverseModeEnabled(controlDmc, new ImmediateDataRequestMonitor<Boolean>(rm) {
@Override
protected void handleCompleted() {
if (isSuccess() && getData()) {
rm.done(ReverseDebugMethod.SOFTWARE);
} else {
rm.done(ReverseDebugMethod.OFF);
}
}
});
} else {
rm.done(ReverseDebugMethod.OFF);
}
}
}
};
try {
fExecutor.execute(reverseMethodQuery);
ReverseDebugMethod currMethod = reverseMethodQuery.get(500, TimeUnit.MILLISECONDS);
if (currMethod != fCurrentMethod) {
fPreviousMethod = fCurrentMethod;
fCurrentMethod = currMethod;
}
return fCurrentMethod;
} catch (InterruptedException e) {
} catch (ExecutionException e) {
} catch (RejectedExecutionException e) {
} catch (TimeoutException e) {
// If we timeout, we default to OFF.
// This is to avoid a deadlock
}
return ReverseDebugMethod.OFF;
}
@Override
public ReverseDebugMethod getPreviousReverseDebugMethod(Object context) {
return fPreviousMethod;
}
}