/******************************************************************************* * Copyright (c) 2008, 2010 Wind River Systems 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 * Ericsson - Modified for additional functionality * Ericsson - Version 7.0 * Nokia - create and use backend service. * Ericsson - Added IReverseControl support *******************************************************************************/ package org.eclipse.cdt.dsf.gdb.service; import java.util.Hashtable; import org.eclipse.cdt.dsf.concurrent.DataRequestMonitor; import org.eclipse.cdt.dsf.concurrent.ImmediateExecutor; 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.IBreakpoints.IBreakpointsTargetDMContext; import org.eclipse.cdt.dsf.debug.service.IProcesses.IProcessDMContext; import org.eclipse.cdt.dsf.debug.service.IProcesses.IThreadDMContext; import org.eclipse.cdt.dsf.debug.service.IRunControl; import org.eclipse.cdt.dsf.debug.service.IRunControl2; import org.eclipse.cdt.dsf.debug.service.IStack.IFrameDMContext; import org.eclipse.cdt.dsf.debug.service.command.ICommand; import org.eclipse.cdt.dsf.debug.service.command.ICommandControlService.ICommandControlDMContext; import org.eclipse.cdt.dsf.gdb.internal.GdbPlugin; import org.eclipse.cdt.dsf.gdb.service.IGDBTraceControl.ITraceRecordSelectedChangedDMEvent; import org.eclipse.cdt.dsf.mi.service.IMICommandControl; import org.eclipse.cdt.dsf.mi.service.IMIExecutionDMContext; import org.eclipse.cdt.dsf.mi.service.IMIProcesses; import org.eclipse.cdt.dsf.mi.service.IMIRunControl; import org.eclipse.cdt.dsf.mi.service.MIRunControl; import org.eclipse.cdt.dsf.mi.service.MIStack; import org.eclipse.cdt.dsf.mi.service.command.CommandFactory; import org.eclipse.cdt.dsf.mi.service.command.events.MIBreakpointHitEvent; import org.eclipse.cdt.dsf.mi.service.command.events.MIInferiorExitEvent; import org.eclipse.cdt.dsf.mi.service.command.events.MIStoppedEvent; import org.eclipse.cdt.dsf.mi.service.command.output.MIBreakInsertInfo; import org.eclipse.cdt.dsf.mi.service.command.output.MIInfo; import org.eclipse.cdt.dsf.service.DsfServiceEventHandler; import org.eclipse.cdt.dsf.service.DsfSession; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; public class GDBRunControl_7_0 extends MIRunControl implements IReverseRunControl { private static class RunToLineActiveOperation { private IMIExecutionDMContext fThreadContext; private int fBpId; private String fFileLocation; private String fAddrLocation; private boolean fSkipBreakpoints; public RunToLineActiveOperation(IMIExecutionDMContext threadContext, int bpId, String fileLoc, String addr, boolean skipBreakpoints) { fThreadContext = threadContext; fBpId = bpId; fFileLocation = fileLoc; fAddrLocation = addr; fSkipBreakpoints = skipBreakpoints; } public IMIExecutionDMContext getThreadContext() { return fThreadContext; } public int getBreakointId() { return fBpId; } public String getFileLocation() { return fFileLocation; } public String getAddrLocation() { return fAddrLocation; } public boolean shouldSkipBreakpoints() { return fSkipBreakpoints; } } private IGDBBackend fGdb; private IMIProcesses fProcService; private CommandFactory fCommandFactory; private boolean fReverseSupported = true; private boolean fReverseStepping = false; private boolean fReverseModeEnabled = false; /** * This variable allows us to know if run control operation * should be enabled or disabled. Run control operations are * always enabled except when dealing with post-mortem debug * session, or when visualizing tracepoints. */ private boolean fRunControlOperationsEnabled = true; private RunToLineActiveOperation fRunToLineActiveOperation = null; public GDBRunControl_7_0(DsfSession session) { super(session); } @Override public void initialize(final RequestMonitor requestMonitor) { super.initialize( new RequestMonitor(ImmediateExecutor.getInstance(), requestMonitor) { @Override public void handleSuccess() { doInitialize(requestMonitor); }}); } private void doInitialize(final RequestMonitor requestMonitor) { fGdb = getServicesTracker().getService(IGDBBackend.class); fProcService = getServicesTracker().getService(IMIProcesses.class); fCommandFactory = getServicesTracker().getService(IMICommandControl.class).getCommandFactory(); if (fGdb.getSessionType() == SessionType.CORE) { // No execution for core files, so no support for reverse fRunControlOperationsEnabled = false; fReverseSupported = false; } register(new String[]{IRunControl.class.getName(), IRunControl2.class.getName(), IMIRunControl.class.getName(), MIRunControl.class.getName(), IReverseRunControl.class.getName()}, new Hashtable<String,String>()); requestMonitor.done(); } @Override public void shutdown(final RequestMonitor requestMonitor) { unregister(); super.shutdown(requestMonitor); } @Override public IMIExecutionDMContext createMIExecutionContext(IContainerDMContext container, int threadId) { IProcessDMContext procDmc = DMContexts.getAncestorOfType(container, IProcessDMContext.class); IThreadDMContext threadDmc = null; if (procDmc != null) { // For now, reuse the threadId as the OSThreadId threadDmc = fProcService.createThreadContext(procDmc, Integer.toString(threadId)); } return fProcService.createExecutionContext(container, threadDmc, Integer.toString(threadId)); } @Override public void suspend(IExecutionDMContext context, final RequestMonitor rm){ canSuspend( context, new DataRequestMonitor<Boolean>(getExecutor(), rm) { @Override protected void handleSuccess() { if (getData()) { fGdb.interruptAndWait(IGDBBackend.INTERRUPT_TIMEOUT_DEFAULT, rm); } else { rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, INVALID_STATE, "Context cannot be suspended.", null)); //$NON-NLS-1$ rm.done(); } } }); } @Override public void getExecutionContexts(IContainerDMContext containerDmc, final DataRequestMonitor<IExecutionDMContext[]> rm) { fProcService.getProcessesBeingDebugged( containerDmc, new DataRequestMonitor<IDMContext[]>(getExecutor(), rm) { @Override protected void handleSuccess() { if (getData() instanceof IExecutionDMContext[]) { IExecutionDMContext[] execDmcs = (IExecutionDMContext[])getData(); rm.setData(execDmcs); } else { rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, INTERNAL_ERROR, "Invalid contexts", null)); //$NON-NLS-1$ } rm.done(); } }); } /** * @since 2.0 */ @Override public void canResume(IExecutionDMContext context, DataRequestMonitor<Boolean> rm) { if (fRunControlOperationsEnabled == false) { rm.setData(false); rm.done(); return; } super.canResume(context, rm); } /** * @since 2.0 */ @Override public void canSuspend(IExecutionDMContext context, DataRequestMonitor<Boolean> rm) { if (fRunControlOperationsEnabled == false) { rm.setData(false); rm.done(); return; } super.canSuspend(context, rm); } /** * @since 2.0 */ @Override public void canStep(final IExecutionDMContext context, StepType stepType, final DataRequestMonitor<Boolean> rm) { if (fRunControlOperationsEnabled == false) { rm.setData(false); rm.done(); return; } if (context instanceof IContainerDMContext) { rm.setData(false); rm.done(); return; } if (stepType == StepType.STEP_RETURN) { // A step return will always be done in the top stack frame. // If the top stack frame is the only stack frame, it does not make sense // to do a step return since GDB will reject it. MIStack stackService = getServicesTracker().getService(MIStack.class); if (stackService != null) { // Check that the stack is at least two deep. stackService.getStackDepth(context, 2, new DataRequestMonitor<Integer>(getExecutor(), rm) { @Override public void handleCompleted() { if (isSuccess() && getData() == 1) { rm.setData(false); rm.done(); } else { canResume(context, rm); } } }); return; } } canResume(context, rm); } /** @since 2.0 */ public void canReverseResume(IExecutionDMContext context, DataRequestMonitor<Boolean> rm) { rm.setData(fReverseModeEnabled && doCanResume(context)); rm.done(); } /** @since 2.0 */ public void canReverseStep(final IExecutionDMContext context, StepType stepType, final DataRequestMonitor<Boolean> rm) { if (context instanceof IContainerDMContext) { rm.setData(false); rm.done(); return; } if (stepType == StepType.STEP_RETURN) { // Check the stuff we know first, before going to the backend for // stack info if (!fReverseModeEnabled || !doCanResume(context)) { rm.setData(false); rm.done(); return; } // A step return will always be done in the top stack frame. // If the top stack frame is the only stack frame, it does not make sense // to do a step return since GDB will reject it. MIStack stackService = getServicesTracker().getService(MIStack.class); if (stackService != null) { // Check that the stack is at least two deep. stackService.getStackDepth(context, 2, new DataRequestMonitor<Integer>(getExecutor(), rm) { @Override public void handleCompleted() { if (isSuccess() && getData() == 1) { rm.setData(false); rm.done(); } else { canReverseResume(context, rm); } } }); return; } } canReverseResume(context, rm); } /** @since 2.0 */ public boolean isReverseStepping(IExecutionDMContext context) { return !isTerminated() && fReverseStepping; } /** @since 2.0 */ public void reverseResume(final IExecutionDMContext context, final RequestMonitor rm) { if (fReverseModeEnabled && doCanResume(context)) { ICommand<MIInfo> cmd = null; if (context instanceof IContainerDMContext) { cmd = fCommandFactory.createMIExecReverseContinue(context); } else { IMIExecutionDMContext dmc = DMContexts.getAncestorOfType(context, IMIExecutionDMContext.class); if (dmc == null){ rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, INVALID_STATE, "Given context: " + context + " is not an execution context.", null)); //$NON-NLS-1$ //$NON-NLS-2$ rm.done(); return; } cmd = fCommandFactory.createMIExecReverseContinue(dmc); } setResumePending(true); // Cygwin GDB will accept commands and execute them after the step // which is not what we want, so mark the target as unavailable // as soon as we send a resume command. getCache().setContextAvailable(context, false); getConnection().queueCommand(cmd, new DataRequestMonitor<MIInfo>(getExecutor(), rm) { @Override public void handleFailure() { setResumePending(false); getCache().setContextAvailable(context, true); } }); } else { rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, INVALID_STATE, "Given context: " + context + ", is already running or reverse not enabled.", null)); //$NON-NLS-1$ //$NON-NLS-2$ rm.done(); } } /** @since 2.0 */ public void reverseStep(final IExecutionDMContext context, StepType stepType, final RequestMonitor rm) { assert context != null; IMIExecutionDMContext dmc = DMContexts.getAncestorOfType(context, IMIExecutionDMContext.class); if (dmc == null){ rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, NOT_SUPPORTED, "Given context: " + context + " is not an execution context.", null)); //$NON-NLS-1$ //$NON-NLS-2$ rm.done(); return; } if (!fReverseModeEnabled || !doCanResume(context)) { rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, INVALID_STATE, "Cannot resume context", null)); //$NON-NLS-1$ rm.done(); return; } ICommand<MIInfo> cmd = null; switch(stepType) { case STEP_INTO: cmd = fCommandFactory.createMIExecReverseStep(dmc, 1); break; case STEP_OVER: cmd = fCommandFactory.createMIExecReverseNext(dmc, 1); break; case STEP_RETURN: // The -exec-finish command operates on the selected stack frame, but here we always // want it to operate on the top stack frame. So we manually create a top-frame // context to use with the MI command. // We get a local instance of the stack service because the stack service can be shut // down before the run control service is shut down. So it is possible for the // getService() request below to return null. MIStack stackService = getServicesTracker().getService(MIStack.class); if (stackService != null) { IFrameDMContext topFrameDmc = stackService.createFrameDMContext(dmc, 0); cmd = fCommandFactory.createMIExecUncall(topFrameDmc); } else { rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, NOT_SUPPORTED, "Cannot create context for command, stack service not available.", null)); //$NON-NLS-1$ rm.done(); return; } break; case INSTRUCTION_STEP_INTO: cmd = fCommandFactory.createMIExecReverseStepInstruction(dmc, 1); break; case INSTRUCTION_STEP_OVER: cmd = fCommandFactory.createMIExecReverseNextInstruction(dmc, 1); break; default: rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, INTERNAL_ERROR, "Given step type not supported", null)); //$NON-NLS-1$ rm.done(); return; } setResumePending(true); fReverseStepping = true; getCache().setContextAvailable(context, false); getConnection().queueCommand(cmd, new DataRequestMonitor<MIInfo>(getExecutor(), rm) { @Override public void handleFailure() { setResumePending(false); fReverseStepping = false; getCache().setContextAvailable(context, true); } }); } /** @since 2.0 */ public void canEnableReverseMode(ICommandControlDMContext context, DataRequestMonitor<Boolean> rm) { rm.setData(fReverseSupported); rm.done(); } /** @since 2.0 */ public void isReverseModeEnabled(ICommandControlDMContext context, DataRequestMonitor<Boolean> rm) { rm.setData(fReverseModeEnabled); rm.done(); } /** @since 2.0 */ public void enableReverseMode(ICommandControlDMContext context, final boolean enable, final RequestMonitor rm) { if (!fReverseSupported) { rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, NOT_SUPPORTED, "Reverse mode is not supported.", null)); //$NON-NLS-1$ rm.done(); return; } if (fReverseModeEnabled == enable) { rm.done(); return; } getConnection().queueCommand( fCommandFactory.createCLIRecord(context, enable), new DataRequestMonitor<MIInfo>(getExecutor(), rm) { @Override public void handleSuccess() { setReverseModeEnabled(enable); rm.done(); } }); } /** @since 3.0 */ @Override public void runToLocation(final IExecutionDMContext context, final String location, final boolean skipBreakpoints, final RequestMonitor rm){ assert context != null; final IMIExecutionDMContext dmc = DMContexts.getAncestorOfType(context, IMIExecutionDMContext.class); if (dmc == null){ rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, NOT_SUPPORTED, "Given context: " + context + " is not an execution context.", null)); //$NON-NLS-1$ //$NON-NLS-2$ rm.done(); return; } if (doCanResume(dmc)) { IBreakpointsTargetDMContext bpDmc = DMContexts.getAncestorOfType(context, IBreakpointsTargetDMContext.class); getConnection().queueCommand( fCommandFactory.createMIBreakInsert(bpDmc, true, false, null, 0, location, dmc.getThreadId()), new DataRequestMonitor<MIBreakInsertInfo>(getExecutor(), rm) { @Override public void handleSuccess() { // We must set are RunToLineActiveOperation *before* we do the resume // or else we may get the stopped event, before we have set this variable. int bpId = getData().getMIBreakpoints()[0].getNumber(); String addr = getData().getMIBreakpoints()[0].getAddress(); fRunToLineActiveOperation = new RunToLineActiveOperation(dmc, bpId, location, addr, skipBreakpoints); resume(dmc, new RequestMonitor(getExecutor(), rm) { @Override public void handleFailure() { IBreakpointsTargetDMContext bpDmc = DMContexts.getAncestorOfType(fRunToLineActiveOperation.getThreadContext(), IBreakpointsTargetDMContext.class); int bpId = fRunToLineActiveOperation.getBreakointId(); getConnection().queueCommand(fCommandFactory.createMIBreakDelete(bpDmc, new int[] {bpId}), new DataRequestMonitor<MIInfo>(getExecutor(), null)); fRunToLineActiveOperation = null; super.handleFailure(); } }); } }); } else { rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, NOT_SUPPORTED, "Cannot resume given DMC.", null)); //$NON-NLS-1$ rm.done(); } } /** @since 3.0 */ @Override public void resumeAtLocation(IExecutionDMContext context, String location, RequestMonitor rm) { assert context != null; final IMIExecutionDMContext dmc = DMContexts.getAncestorOfType(context, IMIExecutionDMContext.class); if (dmc == null){ rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, INTERNAL_ERROR, "Given context: " + context + " is not an thread execution context.", null)); //$NON-NLS-1$ //$NON-NLS-2$ rm.done(); return; } if (doCanResume(dmc)) { setResumePending(true); getCache().setContextAvailable(dmc, false); getConnection().queueCommand( fCommandFactory.createMIExecJump(dmc, location), new DataRequestMonitor<MIInfo>(getExecutor(), rm) { @Override protected void handleFailure() { setResumePending(false); getCache().setContextAvailable(dmc, true); super.handleFailure(); } }); } else { rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, NOT_SUPPORTED, "Cannot resume given DMC.", null)); //$NON-NLS-1$ rm.done(); } } /** * @nooverride This method is not intended to be re-implemented or extended by clients. * @noreference This method is not intended to be referenced by clients. * * @since 2.0 */ @DsfServiceEventHandler public void eventDispatched(MIInferiorExitEvent e) { if (fRunToLineActiveOperation != null) { IBreakpointsTargetDMContext bpDmc = DMContexts.getAncestorOfType(fRunToLineActiveOperation.getThreadContext(), IBreakpointsTargetDMContext.class); int bpId = fRunToLineActiveOperation.getBreakointId(); getConnection().queueCommand(fCommandFactory.createMIBreakDelete(bpDmc, new int[] {bpId}), new DataRequestMonitor<MIInfo>(getExecutor(), null)); fRunToLineActiveOperation = null; } } /** @since 2.0 */ @Override @DsfServiceEventHandler public void eventDispatched(final MIStoppedEvent e) { if (fRunToLineActiveOperation != null) { int bpId = 0; if (e instanceof MIBreakpointHitEvent) { bpId = ((MIBreakpointHitEvent)e).getNumber(); } String fileLocation = e.getFrame().getFile() + ":" + e.getFrame().getLine(); //$NON-NLS-1$ String addrLocation = e.getFrame().getAddress(); // Here we check three different things to see if we are stopped at the right place // 1- The actual location in the file. But this does not work for breakpoints that // were set on non-executable lines // 2- The address where the breakpoint was set. But this does not work for breakpoints // that have multiple addresses (GDB returns <MULTIPLE>.) I think that is for multi-process // 3- The breakpoint id that was hit. But this does not work if another breakpoint // was also set on the same line because GDB may return that breakpoint as being hit. // // So this works for the large majority of cases. The case that won't work is when the user // does a runToLine to a line that is non-executable AND has another breakpoint AND // has multiple addresses for the breakpoint. I'm mean, come on! if (fileLocation.equals(fRunToLineActiveOperation.getFileLocation()) || addrLocation.equals(fRunToLineActiveOperation.getAddrLocation()) || bpId == fRunToLineActiveOperation.getBreakointId()) { // We stopped at the right place. All is well. fRunToLineActiveOperation = null; } else { // Didn't stop at the right place yet if (fRunToLineActiveOperation.shouldSkipBreakpoints() && e instanceof MIBreakpointHitEvent) { getConnection().queueCommand( fCommandFactory.createMIExecContinue(fRunToLineActiveOperation.getThreadContext()), new DataRequestMonitor<MIInfo>(getExecutor(), null)); // Don't send the stop event since we are resuming again. return; } else { // Stopped at another breakpoint that we should not skip. // Or got an interrupt signal from a suspend command. // Or got an interrupt signal because the user set/changed a breakpoint. This last case is tricky. // We could let the run-to-line continue its job, however, I'm thinking that if the user creates // a new breakpoint, she may want to force the program to stop, in a way to abort the run-to-line. // So, let's cancel the run-to-line in this case. // // Just remove our temporary one since we don't want it to hit later IBreakpointsTargetDMContext bpDmc = DMContexts.getAncestorOfType(fRunToLineActiveOperation.getThreadContext(), IBreakpointsTargetDMContext.class); getConnection().queueCommand(fCommandFactory.createMIBreakDelete(bpDmc, new int[] {fRunToLineActiveOperation.getBreakointId()}), new DataRequestMonitor<MIInfo>(getExecutor(), null)); fRunToLineActiveOperation = null; } } } super.eventDispatched(e); } /** * @since 3.0 */ @DsfServiceEventHandler public void eventDispatched(ITraceRecordSelectedChangedDMEvent e) { if (e.isVisualizationModeEnabled()) { // We have started looking at trace records. We can no longer // do run control operations. fRunControlOperationsEnabled = false; } else { // We stopped looking at trace data and gone back to debugger mode fRunControlOperationsEnabled = true; } } /** @since 2.0 */ public void setReverseModeEnabled(boolean enabled) { fReverseModeEnabled = enabled; } }