| /******************************************************************************* |
| * Copyright (c) 2009, 2011 Nokia 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: |
| * Nokia - Initial API and implementation |
| *******************************************************************************/ |
| package org.eclipse.cdt.debug.edc.internal.services.dsf; |
| |
| import java.nio.ByteBuffer; |
| import java.text.MessageFormat; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.concurrent.TimeUnit; |
| |
| import org.eclipse.cdt.core.IAddress; |
| import org.eclipse.cdt.debug.core.breakpointactions.ILogActionEnabler; |
| import org.eclipse.cdt.debug.core.breakpointactions.IResumeActionEnabler; |
| import org.eclipse.cdt.debug.edc.IAddressExpressionEvaluator; |
| import org.eclipse.cdt.debug.edc.IJumpToAddress; |
| import org.eclipse.cdt.debug.edc.JumpToAddress; |
| import org.eclipse.cdt.debug.edc.disassembler.IDisassembledInstruction; |
| import org.eclipse.cdt.debug.edc.disassembler.IDisassembler; |
| import org.eclipse.cdt.debug.edc.disassembler.IDisassembler.IDisassemblerOptions; |
| import org.eclipse.cdt.debug.edc.internal.EDCDebugger; |
| import org.eclipse.cdt.debug.edc.internal.EDCTrace; |
| import org.eclipse.cdt.debug.edc.internal.breakpointactions.EDCLogActionEnabler; |
| import org.eclipse.cdt.debug.edc.internal.formatter.FormatExtensionManager; |
| import org.eclipse.cdt.debug.edc.internal.services.dsf.Breakpoints.BreakpointDMData; |
| import org.eclipse.cdt.debug.edc.internal.services.dsf.Modules.EDCAddressRange; |
| import org.eclipse.cdt.debug.edc.internal.services.dsf.Modules.ModuleDMC; |
| import org.eclipse.cdt.debug.edc.internal.snapshot.Album; |
| import org.eclipse.cdt.debug.edc.internal.snapshot.SnapshotUtils; |
| import org.eclipse.cdt.debug.edc.services.AbstractEDCService; |
| import org.eclipse.cdt.debug.edc.services.DMContext; |
| import org.eclipse.cdt.debug.edc.services.Disassembly; |
| import org.eclipse.cdt.debug.edc.services.IDSFServiceUsingTCF; |
| import org.eclipse.cdt.debug.edc.services.IEDCDMContext; |
| import org.eclipse.cdt.debug.edc.services.IEDCExecutionDMC; |
| import org.eclipse.cdt.debug.edc.services.IEDCModuleDMContext; |
| import org.eclipse.cdt.debug.edc.services.IEDCModules; |
| import org.eclipse.cdt.debug.edc.services.IEDCSymbols; |
| import org.eclipse.cdt.debug.edc.services.ITargetEnvironment; |
| import org.eclipse.cdt.debug.edc.services.Registers; |
| import org.eclipse.cdt.debug.edc.services.Registers.RegisterDMC; |
| import org.eclipse.cdt.debug.edc.services.Registers.RegisterGroupDMC; |
| import org.eclipse.cdt.debug.edc.services.Stack; |
| import org.eclipse.cdt.debug.edc.services.Stack.StackFrameDMC; |
| import org.eclipse.cdt.debug.edc.services.Stack.VariableDMC; |
| import org.eclipse.cdt.debug.edc.snapshot.IAlbum; |
| import org.eclipse.cdt.debug.edc.snapshot.ISnapshotContributor; |
| import org.eclipse.cdt.debug.edc.symbols.IEDCSymbolReader; |
| import org.eclipse.cdt.debug.edc.symbols.IFunctionScope; |
| import org.eclipse.cdt.debug.edc.symbols.ILineEntry; |
| import org.eclipse.cdt.debug.edc.symbols.IModuleLineEntryProvider; |
| import org.eclipse.cdt.debug.edc.tcf.extension.ProtocolConstants; |
| import org.eclipse.cdt.debug.edc.tcf.extension.ProtocolConstants.IModuleProperty; |
| import org.eclipse.cdt.dsf.concurrent.CountingRequestMonitor; |
| import org.eclipse.cdt.dsf.concurrent.DataRequestMonitor; |
| import org.eclipse.cdt.dsf.concurrent.Immutable; |
| import org.eclipse.cdt.dsf.concurrent.RequestMonitor; |
| import org.eclipse.cdt.dsf.concurrent.RequestMonitorWithProgress; |
| import org.eclipse.cdt.dsf.datamodel.AbstractDMEvent; |
| 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.ICachingService; |
| import org.eclipse.cdt.dsf.debug.service.IDisassembly.IDisassemblyDMContext; |
| import org.eclipse.cdt.dsf.debug.service.IExpressions.IExpressionDMContext; |
| import org.eclipse.cdt.dsf.debug.service.IFormattedValues; |
| import org.eclipse.cdt.dsf.debug.service.IMemory.IMemoryDMContext; |
| import org.eclipse.cdt.dsf.debug.service.IModules.IModuleDMContext; |
| import org.eclipse.cdt.dsf.debug.service.IModules.ISymbolDMContext; |
| 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.IRegisters.IRegisterGroupDMContext; |
| import org.eclipse.cdt.dsf.debug.service.IRunControl; |
| import org.eclipse.cdt.dsf.debug.service.IRunControl2; |
| import org.eclipse.cdt.dsf.debug.service.ISourceLookup.ISourceLookupDMContext; |
| import org.eclipse.cdt.dsf.debug.service.IStack; |
| import org.eclipse.cdt.dsf.debug.service.IStack.IFrameDMContext; |
| import org.eclipse.cdt.dsf.debug.service.IStack.IVariableDMContext; |
| import org.eclipse.cdt.dsf.service.DsfSession; |
| import org.eclipse.cdt.utils.Addr64; |
| import org.eclipse.core.runtime.CoreException; |
| import org.eclipse.core.runtime.IProgressMonitor; |
| import org.eclipse.core.runtime.IStatus; |
| import org.eclipse.core.runtime.NullProgressMonitor; |
| import org.eclipse.core.runtime.Status; |
| import org.eclipse.core.runtime.SubMonitor; |
| import org.eclipse.debug.core.model.MemoryByte; |
| import org.eclipse.tm.tcf.protocol.IService; |
| import org.eclipse.tm.tcf.protocol.IToken; |
| import org.eclipse.tm.tcf.protocol.Protocol; |
| import org.eclipse.tm.tcf.services.IRunControl.DoneCommand; |
| import org.eclipse.tm.tcf.services.IRunControl.RunControlContext; |
| import org.w3c.dom.Document; |
| import org.w3c.dom.Element; |
| import org.w3c.dom.NodeList; |
| |
| public class RunControl extends AbstractEDCService implements IRunControl2, ICachingService, ISnapshotContributor, |
| IDSFServiceUsingTCF { |
| |
| public static final String EXECUTION_CONTEXT = "execution_context"; |
| public static final String EXECUTION_CONTEXT_REGISTERS = "execution_context_registers"; |
| public static final String EXECUTION_CONTEXT_MODULES = "execution_context_modules"; |
| public static final String EXECUTION_CONTEXT_FRAMES = "execution_context_frames"; |
| /** |
| * Context property names. Properties that are optional but have default |
| * implicit values are indicated below |
| */ |
| public static final String |
| PROP_PARENT_ID = "ParentID", |
| PROP_IS_CONTAINER = "IsContainer", // default = true |
| PROP_HAS_STATE = "HasState", |
| PROP_CAN_RESUME = "CanResume", // default = true |
| PROP_CAN_COUNT = "CanCount", |
| PROP_CAN_SUSPEND = "CanSuspend", // default = true |
| PROP_CAN_TERMINATE = "CanTerminate", // default = false |
| PROP_IS_SUSPENDED = "State", // default = false |
| PROP_MESSAGE = "Message", |
| PROP_SUSPEND_PC = "SuspendPC", |
| PROP_DISABLE_STEPPING = "DisableStepping"; |
| |
| public static final String STEP_RETURN_NOT_SUPPORTED |
| = "the current Execution context does not support StepType.STEP_RETURN"; //$NON-NLS-1$ |
| |
| /* |
| * See where this is used for more. |
| */ |
| private static final int RESUME_NOTIFICATION_DELAY = 1000; // milliseconds |
| |
| // Whether module is being loaded (if true) or unloaded (if false) |
| |
| public abstract static class DMCSuspendedEvent extends AbstractDMEvent<IExecutionDMContext> { |
| |
| private final StateChangeReason reason; |
| private final Map<String, Object> params; |
| |
| public DMCSuspendedEvent(IExecutionDMContext dmc, StateChangeReason reason, Map<String, Object> params) { |
| super(dmc); |
| if (EDCTrace.RUN_CONTROL_TRACE_ON) { EDCTrace.getTrace().traceEntry(null, EDCTrace.fixArgs(new Object[] { dmc, reason, params })); } |
| this.reason = reason; |
| this.params = params; |
| } |
| |
| public StateChangeReason getReason() { |
| return reason; |
| } |
| |
| public Map<String, Object> getParams() { |
| return params; |
| } |
| |
| } |
| |
| public static class SuspendedEvent extends DMCSuspendedEvent implements ISuspendedDMEvent { |
| |
| public SuspendedEvent(IExecutionDMContext dmc, |
| StateChangeReason reason, Map<String, Object> params) { |
| super(dmc, reason, params); |
| } |
| |
| } |
| |
| public static class ContainerSuspendedEvent extends DMCSuspendedEvent implements IContainerSuspendedDMEvent { |
| |
| public ContainerSuspendedEvent(IExecutionDMContext dmc, |
| StateChangeReason reason, Map<String, Object> params) { |
| super(dmc, reason, params); |
| } |
| |
| public IExecutionDMContext[] getTriggeringContexts() { |
| return new IExecutionDMContext[]{getDMContext()}; |
| } |
| } |
| |
| public abstract static class DMCResumedEvent extends AbstractDMEvent<IExecutionDMContext> { |
| |
| public DMCResumedEvent(IExecutionDMContext dmc) { |
| super(dmc); |
| } |
| |
| public StateChangeReason getReason() { |
| return StateChangeReason.USER_REQUEST; |
| } |
| } |
| |
| public static class ResumedEvent extends DMCResumedEvent implements IResumedDMEvent { |
| |
| public ResumedEvent(IExecutionDMContext dmc) { |
| super(dmc); |
| } |
| } |
| |
| public static class ContainerResumedEvent extends DMCResumedEvent implements IContainerResumedDMEvent { |
| |
| public ContainerResumedEvent(IExecutionDMContext dmc) { |
| super(dmc); |
| } |
| |
| public IExecutionDMContext[] getTriggeringContexts() { |
| return new IExecutionDMContext[]{getDMContext()}; |
| } |
| } |
| |
| private static Map<String, StateChangeReason> reasons; |
| private static StateChangeReason toDsfStateChangeReason(String tcfReason) { |
| if (tcfReason == null) |
| return StateChangeReason.UNKNOWN; |
| if (reasons == null) |
| reasons = new HashMap<String, IRunControl.StateChangeReason>(); |
| StateChangeReason reason = reasons.get(tcfReason); |
| if (reason == null) { |
| |
| if (tcfReason.equals(org.eclipse.tm.tcf.services.IRunControl.REASON_USER_REQUEST)) |
| reason = StateChangeReason.USER_REQUEST; |
| else if (tcfReason.equals(org.eclipse.tm.tcf.services.IRunControl.REASON_STEP)) |
| reason = StateChangeReason.STEP; |
| else if (tcfReason.equals(org.eclipse.tm.tcf.services.IRunControl.REASON_BREAKPOINT)) |
| reason = StateChangeReason.BREAKPOINT; |
| else if (tcfReason.equals(org.eclipse.tm.tcf.services.IRunControl.REASON_EXCEPTION)) |
| reason = StateChangeReason.EXCEPTION; |
| else if (tcfReason.equals(org.eclipse.tm.tcf.services.IRunControl.REASON_CONTAINER)) |
| reason = StateChangeReason.CONTAINER; |
| else if (tcfReason.equals(org.eclipse.tm.tcf.services.IRunControl.REASON_WATCHPOINT)) |
| reason = StateChangeReason.WATCHPOINT; |
| else if (tcfReason.equals(org.eclipse.tm.tcf.services.IRunControl.REASON_SIGNAL)) |
| reason = StateChangeReason.SIGNAL; |
| else if (tcfReason.equals(org.eclipse.tm.tcf.services.IRunControl.REASON_SHAREDLIB)) |
| reason = StateChangeReason.SHAREDLIB; |
| else if (tcfReason.equals(org.eclipse.tm.tcf.services.IRunControl.REASON_ERROR)) |
| reason = StateChangeReason.ERROR; |
| else |
| reason = StateChangeReason.UNKNOWN; |
| reasons.put(tcfReason, reason); |
| } |
| return reason; |
| } |
| |
| @Immutable |
| private static class ExecutionData implements IExecutionDMData2 { |
| private final StateChangeReason reason; |
| private final String details; |
| |
| ExecutionData(StateChangeReason reason, String details) { |
| this.reason = reason; |
| this.details = details; |
| } |
| |
| public StateChangeReason getStateChangeReason() { |
| return reason; |
| } |
| |
| public String getDetails() { |
| return details; |
| } |
| } |
| |
| public abstract class ExecutionDMC extends DMContext implements IExecutionDMContext, |
| ISnapshotContributor, IEDCExecutionDMC { |
| |
| private final List<ExecutionDMC> children = Collections.synchronizedList(new ArrayList<ExecutionDMC>()); |
| private StateChangeReason stateChangeReason = StateChangeReason.UNKNOWN; |
| private String stateChangeDetails = null; |
| private final RunControlContext tcfContext; |
| private final ExecutionDMC parentExecutionDMC; |
| /** |
| * Hex string without "0x". |
| */ |
| private String latestPC = null; |
| private RequestMonitor steppingRM = null; |
| private boolean isStepping = false; |
| private RequestMonitorWithProgress bpActionRM = null; |
| |
| // See where this is used for more. |
| private int countOfScheduledNotifications = 0 ; |
| |
| /** |
| * Whether user chose to "terminate" or "disconnect" the context. |
| */ |
| private boolean isTerminatingThanDisconnecting = false; |
| |
| /** |
| * Code ranges to step outside of. |
| * |
| * Certain source line may have two separate sections of |
| * . For instance, the following lines <pre> |
| * for (i=0; i<3; i++) |
| * k *= k; |
| * </pre> |
| * will have such code generated by MinGW GCC compiler: |
| * <pre> |
| 184 for (i=0; i<3; i++) |
| 00000000004017f7: movl $0x0,-0x8(%ebp) |
| 00000000004017fe: jmp 0x40180d |
| 185 k *= k; |
| 0000000000401800: mov -0x10(%ebp),%eax |
| 0000000000401803: imul -0x10(%ebp),%eax |
| 0000000000401807: mov %eax,-0x10(%ebp) |
| 184 for (i=0; i<3; i++) |
| 000000000040180a: incl -0x8(%ebp) |
| 000000000040180d: cmpl $0x2,-0x8(%ebp) |
| 0000000000401811: setle %al |
| 0000000000401814: test %al,%al |
| 0000000000401816: jne 0x401800 |
| </pre> |
| * To step over the above "for()" statement, we need |
| * to make sure stepping does not stop in its second |
| * code section. |
| */ |
| private List<EDCAddressRange> stepRanges = Collections.synchronizedList(new ArrayList<EDCAddressRange>()); |
| |
| private boolean suspendEventsEnabled = true; |
| private DMCSuspendedEvent cachedSuspendedEvent = null; |
| |
| /** |
| * All possible function call destination addresses when we perform a |
| * StepIn.<br> |
| * This is to help auto-step through glue code in a function call (e.g. |
| * the jmp instruction in a jump table for calling a Win32 DLL). |
| */ |
| private List<IAddress> functionCallDestinations = Collections.synchronizedList(new ArrayList<IAddress>()); |
| |
| public ExecutionDMC(ExecutionDMC parent, Map<String, Object> props, RunControlContext tcfContext) { |
| super(RunControl.this, parent == null ? new IDMContext[0] : new IDMContext[] { parent }, props); |
| if (EDCTrace.RUN_CONTROL_TRACE_ON) { EDCTrace.getTrace().traceEntry(null, EDCTrace.fixArgs(new Object[] { parent, properties })); } |
| this.parentExecutionDMC = parent; |
| this.tcfContext = tcfContext; |
| if (props != null) { |
| dmcsByID.put(getID(), this); |
| } |
| if (parent != null) |
| parent.addChild(this); |
| if (EDCTrace.RUN_CONTROL_TRACE_ON) { EDCTrace.getTrace().traceExit(null); } |
| } |
| |
| private void addChild(ExecutionDMC executionDMC) { |
| synchronized (children) { |
| children.add(executionDMC); |
| } |
| } |
| |
| private void removeChild(IEDCExecutionDMC executionDMC) { |
| synchronized (children) { |
| children.remove(executionDMC); |
| } |
| } |
| |
| public ExecutionDMC[] getChildren() { |
| synchronized (children) { |
| return children.toArray(new ExecutionDMC[children.size()]); |
| } |
| } |
| |
| public boolean wantFocusInUI() { |
| Boolean wantFocus = (Boolean)properties.get(ProtocolConstants.PROP_WANT_FOCUS_IN_UI); |
| if (wantFocus == null) |
| wantFocus = true; // default if unknown (not set by debug agent). |
| return wantFocus; |
| } |
| |
| public abstract ExecutionDMC contextAdded(Map<String, Object> properties, RunControlContext tcfContext); |
| |
| public abstract boolean canDetach(); |
| |
| public abstract boolean canStep(); |
| |
| public void loadSnapshot(Element element) throws Exception { |
| if (EDCTrace.RUN_CONTROL_TRACE_ON) { EDCTrace.getTrace().traceEntry(null, EDCTrace.fixArg(element)); } |
| NodeList ecElements = element.getElementsByTagName(EXECUTION_CONTEXT); |
| int numcontexts = ecElements.getLength(); |
| for (int i = 0; i < numcontexts; i++) { |
| Element contextElement = (Element) ecElements.item(i); |
| if (contextElement.getParentNode().equals(element)) { |
| try { |
| Element propElement = (Element) contextElement.getElementsByTagName(SnapshotUtils.PROPERTIES) |
| .item(0); |
| HashMap<String, Object> properties = new HashMap<String, Object>(); |
| SnapshotUtils.initializeFromXML(propElement, properties); |
| ExecutionDMC exeDMC = contextAdded(properties, null); |
| exeDMC.loadSnapshot(contextElement); |
| } catch (CoreException e) { |
| EDCDebugger.getMessageLogger().logError(null, e); |
| } |
| } |
| |
| } |
| if (EDCTrace.RUN_CONTROL_TRACE_ON) { EDCTrace.getTrace().traceExit(null); } |
| } |
| |
| public Element takeSnapshot(IAlbum album, Document document, IProgressMonitor monitor)throws Exception { |
| Element contextElement = document.createElement(EXECUTION_CONTEXT); |
| contextElement.setAttribute(PROP_ID, this.getID()); |
| |
| Element propsElement = SnapshotUtils.makeXMLFromProperties(document, getProperties()); |
| contextElement.appendChild(propsElement); |
| |
| ExecutionDMC[] dmcs = getChildren(); |
| SubMonitor progress = SubMonitor.convert(monitor, dmcs.length * 1000); |
| progress.subTask(getName()); |
| |
| for (ExecutionDMC executionDMC : dmcs) { |
| Element dmcElement = executionDMC.takeSnapshot(album, document, progress.newChild(1000)); |
| contextElement.appendChild(dmcElement); |
| } |
| |
| return contextElement; |
| } |
| |
| public boolean isSuspended() { |
| synchronized (properties) { |
| return RunControl.getProperty(properties, PROP_IS_SUSPENDED, false); |
| } |
| } |
| |
| public StateChangeReason getStateChangeReason() { |
| return stateChangeReason; |
| } |
| |
| protected void setStateChangeDetails(String newStateChangeDetails) { |
| stateChangeDetails = newStateChangeDetails; |
| } |
| |
| public String getStateChangeDetails() { |
| return stateChangeDetails; |
| } |
| |
| public void setIsSuspended(boolean isSuspended) { |
| synchronized (properties) { |
| properties.put(PROP_IS_SUSPENDED, isSuspended); |
| } |
| if (getParent() != null) |
| getParent().childIsSuspended(isSuspended); |
| } |
| |
| private void childIsSuspended(boolean isSuspended) { |
| if (isSuspended) { |
| setIsSuspended(true); |
| } else { |
| boolean anySuspended = false; |
| for (ExecutionDMC childDMC : getChildren()) { |
| if (childDMC.isSuspended()) { |
| anySuspended = true; |
| break; |
| } |
| } |
| if (!anySuspended) |
| setIsSuspended(false); |
| } |
| } |
| |
| protected void contextException(String msg) { |
| assert getExecutor().isInExecutorThread(); |
| |
| setIsSuspended(true); |
| synchronized (properties) { |
| properties.put(PROP_MESSAGE, msg); |
| } |
| stateChangeReason = StateChangeReason.EXCEPTION; |
| getSession().dispatchEvent( |
| createSuspendedEvent(StateChangeReason.EXCEPTION, new HashMap<String, Object>()), |
| RunControl.this.getProperties()); |
| } |
| |
| protected void contextSuspended(String pc, String reason, final Map<String, Object> params) { |
| assert getExecutor().isInExecutorThread(); |
| |
| if (EDCTrace.RUN_CONTROL_TRACE_ON) { EDCTrace.getTrace().traceEntry(null, EDCTrace.fixArg(new Object[] { pc, reason, params })); } |
| if (pc != null) { |
| // the PC from TCF agent is decimal string. |
| // convert it to hex string. |
| pc = Long.toHexString(Long.parseLong(pc)); |
| } |
| |
| latestPC = pc; |
| |
| setIsSuspended(true); |
| synchronized (properties) { |
| properties.put(PROP_MESSAGE, reason); |
| properties.put(PROP_SUSPEND_PC, pc); |
| } |
| stateChangeReason = toDsfStateChangeReason(reason); |
| |
| if (stateChangeReason == StateChangeReason.SHAREDLIB) { |
| handleModuleEvent(this, params); |
| } else { |
| |
| properties.put(PROP_DISABLE_STEPPING, params.get(ProtocolConstants.PROP_DISABLE_STEPPING)); |
| properties.put(ProtocolConstants.PROP_WANT_FOCUS_IN_UI, params.get(ProtocolConstants.PROP_WANT_FOCUS_IN_UI)); |
| |
| stateChangeDetails = (String) params.get(ProtocolConstants.PROP_SUSPEND_DETAIL); |
| |
| // TODO This is not what the stateChangeDetails is for, we need an extended thread description |
| // and is "foreground" really the right term? |
| |
| // Show the context is foreground one, if possible. |
| // |
| Boolean isForeground = (Boolean)params.get(ProtocolConstants.PROP_IS_FOREGROUND); |
| if (isForeground != null) |
| stateChangeDetails += isForeground ? " [foreground]" : ""; |
| |
| final ExecutionDMC dmc = this; |
| |
| final DataRequestMonitor<Object> preprocessDrm = new DataRequestMonitor<Object>(getExecutor(), null) { |
| @Override |
| protected void handleCompleted() { |
| Boolean honorSuspend; |
| Breakpoints.BreakpointDMData bp; |
| Object drmData = getData(); |
| if (drmData instanceof Breakpoints.BreakpointDMData) { |
| bp = (Breakpoints.BreakpointDMData)drmData; |
| honorSuspend = true; |
| } else { |
| bp = null; |
| honorSuspend = (drmData instanceof Boolean) ? (Boolean)getData() : true; |
| } |
| |
| if (honorSuspend!=null && honorSuspend) { // do suspend |
| |
| // All the following must be done in DSF dispatch |
| // thread to ensure data integrity. |
| |
| // see if there are any actions, and perform them if so |
| if (bp != null && bp.hasActions()) { |
| bp.executeActions(ExecutionDMC.this, newBreakpointActionRM()); |
| } |
| |
| // Mark done of the single step RM, if any pending. |
| if (steppingRM != null) { |
| if (bp == null) { |
| stateChangeReason = StateChangeReason.STEP; |
| stateChangeDetails = null; |
| } |
| steppingRM.done(); |
| steppingRM = null; |
| } |
| |
| // Mark any stepping as done. |
| setStepping(false); |
| |
| // Remove temporary breakpoints set by stepping. |
| // Note we don't want to do this on a sharedLibrary |
| // event as otherwise |
| // stepping will be screwed up by that event. |
| // |
| Breakpoints bpService = getService(Breakpoints.class); |
| bpService.removeAllTempBreakpoints(new RequestMonitor(getExecutor(), null)); |
| |
| /* |
| * Don't report suspendedEvent to upper layer |
| * resulting from prepareToRun() to avoid |
| * unnecessary suspend-handling. But we need to |
| * fire the event if the stepping is finished after |
| * prepareToRun(). So remember it. |
| */ |
| DMCSuspendedEvent e = dmc.createSuspendedEvent(stateChangeReason, params); |
| if (dmc.suspendEventsEnabled()) |
| getSession().dispatchEvent(e, RunControl.this.getProperties()); |
| else |
| dmc.cacheSuspendedEvent(e); |
| } else { |
| // ignore suspend if, say, breakpoint condition is not met. |
| RunControl.this.resume(dmc, new RequestMonitor(getExecutor(), null)); |
| } |
| } |
| }; |
| |
| preprocessOnSuspend(dmc, latestPC, preprocessDrm); |
| } |
| if (EDCTrace.RUN_CONTROL_TRACE_ON) { EDCTrace.getTrace().traceExit(null); } |
| } |
| |
| void clearBreakpointActionRM() { |
| synchronized (this) { |
| if (bpActionRM != null) { |
| if (!bpActionRM.getSubmitted()) { |
| bpActionRM.cancel(); |
| } |
| IProgressMonitor progress = bpActionRM.getProgressMonitor(); |
| if (progress != null && !progress.isCanceled()) { |
| progress.setCanceled(true); |
| } |
| bpActionRM = null; |
| } |
| } |
| } |
| |
| public RequestMonitorWithProgress getBreakpointActionRM() { |
| return bpActionRM; |
| } |
| |
| RequestMonitorWithProgress newBreakpointActionRM() { |
| clearBreakpointActionRM(); |
| bpActionRM = new RequestMonitorWithProgress(getExecutor(), new NullProgressMonitor()) { |
| @Override |
| public void handleCompleted() { |
| super.handleCompleted(); |
| clearBreakpointActionRM(); |
| } |
| }; |
| return bpActionRM; |
| } |
| |
| protected boolean suspendEventsEnabled() { |
| return suspendEventsEnabled ; |
| } |
| |
| protected void setSuspendEventsEnabled(boolean enabled) |
| { |
| suspendEventsEnabled = enabled; |
| } |
| |
| /** |
| * handle module load event and unload event. A module is an executable file |
| * or a library (e.g. DLL or shared lib). |
| * |
| * @param dmc |
| * @param moduleProperties |
| */ |
| private void handleModuleEvent(final IEDCExecutionDMC dmc, final Map<String, Object> moduleProperties) { |
| // The following needs be done in DSF dispatch thread. |
| getSession().getExecutor().execute(new Runnable() { |
| public void run() { |
| // based on properties, either load or unload the module |
| boolean loaded = true; |
| Object loadedValue = moduleProperties.get(IModuleProperty.PROP_MODULE_LOADED); |
| if (loadedValue != null) { |
| if (loadedValue instanceof Boolean) |
| loaded = (Boolean) loadedValue; |
| } |
| |
| if (loaded) |
| handleModuleLoadedEvent(dmc, moduleProperties); |
| else |
| handleModuleUnloadedEvent(dmc, moduleProperties); |
| } |
| }); |
| } |
| |
| public Boolean canTerminate() { |
| if (EDCTrace.RUN_CONTROL_TRACE_ON) { EDCTrace.getTrace().traceEntry(null); } |
| boolean result = false; |
| synchronized (properties) { |
| result = RunControl.getProperty(properties, PROP_CAN_TERMINATE, result); |
| } |
| if (EDCTrace.RUN_CONTROL_TRACE_ON) { EDCTrace.getTrace().traceExit(null, result); } |
| return result; |
| } |
| |
| /** |
| * Resume the context. |
| * |
| * @param rm |
| * this is marked done as long as the resume command |
| * succeeds. |
| */ |
| public boolean supportsStepMode(StepType type) { |
| if (EDCTrace.RUN_CONTROL_TRACE_ON) { EDCTrace.getTrace().traceEntry(null, EDCTrace.fixArg(this)); } |
| |
| int mode = 0; |
| switch (type) { |
| case STEP_OVER: |
| mode = org.eclipse.tm.tcf.services.IRunControl.RM_STEP_OVER_RANGE; |
| break; |
| case STEP_INTO: |
| mode = org.eclipse.tm.tcf.services.IRunControl.RM_STEP_INTO_RANGE; |
| break; |
| case STEP_RETURN: |
| mode = org.eclipse.tm.tcf.services.IRunControl.RM_STEP_OUT; |
| break; |
| case INSTRUCTION_STEP_OVER: |
| mode = org.eclipse.tm.tcf.services.IRunControl.RM_STEP_OVER; |
| break; |
| case INSTRUCTION_STEP_INTO: |
| mode = org.eclipse.tm.tcf.services.IRunControl.RM_STEP_INTO; |
| break; |
| } |
| |
| if (hasTCFContext()) |
| return getTCFContext().canResume(mode); |
| else |
| return false; |
| } |
| |
| /** |
| * Resume the context. |
| * |
| * @param rm |
| * this is marked done as long as the resume command |
| * succeeds. |
| */ |
| public void resume(final RequestMonitor rm) { |
| if (EDCTrace.RUN_CONTROL_TRACE_ON) { EDCTrace.getTrace().traceEntry(null, EDCTrace.fixArg(this)); } |
| |
| flushCache(this); |
| |
| if (hasTCFContext()) { |
| Protocol.invokeLater(new Runnable() { |
| public void run() { |
| getTCFContext() |
| .resume(org.eclipse.tm.tcf.services.IRunControl.RM_RESUME, |
| 0, new DoneCommand() { |
| |
| public void doneCommand( |
| IToken token, |
| final Exception error) { |
| getExecutor().execute( |
| new Runnable() { |
| public void run() { |
| if (error == null) { |
| contextResumed(true); |
| if (EDCTrace.RUN_CONTROL_TRACE_ON) { |
| EDCTrace.getTrace().trace(null, "Resume command succeeded."); |
| } |
| } else { |
| if (EDCTrace.RUN_CONTROL_TRACE_ON) { |
| EDCTrace.getTrace().trace(null, "Resume command failed."); |
| } |
| rm.setStatus(new Status( |
| IStatus.ERROR, |
| EDCDebugger.PLUGIN_ID, |
| REQUEST_FAILED, |
| "Resume failed.", |
| null)); |
| } |
| rm.done(); |
| } |
| }); |
| } |
| }); |
| } |
| }); |
| } |
| if (EDCTrace.RUN_CONTROL_TRACE_ON) { EDCTrace.getTrace().traceExit(null); } |
| } |
| |
| /** |
| * Resume the context but the request monitor is only marked done when |
| * the context is suspended. (vs. regular resume()). <br> |
| * Note this method does not wait for suspended-event. |
| * |
| * @param rm |
| */ |
| protected void resumeForStepping(final RequestMonitor rm) { |
| if (EDCTrace.RUN_CONTROL_TRACE_ON) { EDCTrace.getTrace().traceEntry(null, EDCTrace.fixArg(this)); } |
| |
| setStepping(true); |
| |
| flushCache(this); |
| |
| if (hasTCFContext()) { |
| Protocol.invokeLater(new Runnable() { |
| public void run() { |
| getTCFContext().resume(org.eclipse.tm.tcf.services.IRunControl.RM_RESUME, 0, new DoneCommand() { |
| |
| public void doneCommand(IToken token, final Exception error) { |
| // do this in DSF executor thread. |
| getExecutor().execute(new Runnable() { |
| public void run() { |
| handleTCFResumeDoneForStepping( |
| "ResumeForStepping", |
| error, |
| rm); |
| } |
| }); |
| } |
| }); |
| } |
| }); |
| } |
| if (EDCTrace.RUN_CONTROL_TRACE_ON) { EDCTrace.getTrace().traceExit(null); } |
| } |
| |
| private void handleTCFResumeDoneForStepping(String command, Exception tcfError, RequestMonitor rm) { |
| assert getExecutor().isInExecutorThread(); |
| |
| String msg = command; |
| if (tcfError == null) { |
| msg += " succeeded."; |
| if (EDCTrace.RUN_CONTROL_TRACE_ON) { EDCTrace.getTrace().trace(null, msg); } |
| contextResumed(false); |
| |
| // we'll mark it as done when we get next |
| // suspend event. |
| assert steppingRM == null; |
| steppingRM = rm; |
| } else { |
| msg += " failed."; |
| if (EDCTrace.RUN_CONTROL_TRACE_ON) { EDCTrace.getTrace().trace(null, msg); } |
| |
| setStepping(false); |
| rm.setStatus(new Status(IStatus.ERROR, EDCDebugger.PLUGIN_ID, REQUEST_FAILED, msg, tcfError)); |
| rm.done(); |
| } |
| } |
| |
| public void suspend(final RequestMonitor requestMonitor) { |
| if (EDCTrace.RUN_CONTROL_TRACE_ON) { EDCTrace.getTrace().traceEntry(null, EDCTrace.fixArg(this)); } |
| if (isSnapshot()) { |
| Album.getAlbumBySession(getSession().getId()).stopPlayingSnapshots(); |
| } else if (hasTCFContext()) { |
| Protocol.invokeLater(new Runnable() { |
| public void run() { |
| getTCFContext().suspend(new DoneCommand() { |
| |
| public void doneCommand(IToken token, |
| Exception error) { |
| if (EDCTrace.RUN_CONTROL_TRACE_ON) { |
| EDCTrace.getTrace().traceEntry(null, EDCTrace.fixArg(this)); |
| } |
| requestMonitor.done(); |
| if (EDCTrace.RUN_CONTROL_TRACE_ON) { |
| EDCTrace.getTrace().traceExit(null); |
| } |
| } |
| }); |
| } |
| }); |
| } |
| if (EDCTrace.RUN_CONTROL_TRACE_ON) { EDCTrace.getTrace().traceExit(null); } |
| } |
| |
| public void terminate(final RequestMonitor requestMonitor) { |
| if (EDCTrace.RUN_CONTROL_TRACE_ON) { EDCTrace.getTrace().traceEntry(null, EDCTrace.fixArg(this)); } |
| |
| isTerminatingThanDisconnecting = true; |
| |
| clearBreakpointActionRM(); |
| |
| if (hasTCFContext()) { |
| Protocol.invokeLater(new Runnable() { |
| public void run() { |
| getTCFContext().terminate(new DoneCommand() { |
| |
| public void doneCommand(IToken token, Exception error) { |
| if (EDCTrace.RUN_CONTROL_TRACE_ON) { EDCTrace.getTrace().traceEntry(null, EDCTrace.fixArg(this)); } |
| if (error != null) { |
| requestMonitor.setStatus(new Status(IStatus.ERROR, EDCDebugger.PLUGIN_ID, |
| "terminate() failed.", error)); |
| } |
| |
| requestMonitor.done(); |
| if (EDCTrace.RUN_CONTROL_TRACE_ON) { EDCTrace.getTrace().traceExit(null); } |
| } |
| }); |
| } |
| }); |
| } else { |
| // Snapshots, for e.g., don't have a TCF RunControlContext, so just remove all the contexts recursively |
| detachAllContexts(); |
| } |
| if (EDCTrace.RUN_CONTROL_TRACE_ON) { EDCTrace.getTrace().traceExit(null); } |
| } |
| |
| protected ExecutionDMC getParent() { |
| return parentExecutionDMC; |
| } |
| |
| /** |
| * get latest PC register value of the context. |
| * |
| * @return hex string of the PC value. |
| */ |
| public String getPC() { |
| return latestPC; |
| } |
| |
| /** |
| * Change cached PC value. |
| * This is only supposed to be used for move-to-line & resume-from-line commands. |
| * |
| * @param pc |
| */ |
| private void setPC(String pc) { |
| latestPC = pc; |
| } |
| |
| /** |
| * Detach debugger from this context and all its children. |
| */ |
| public void detach(){ |
| isTerminatingThanDisconnecting = false; |
| /** |
| * agent side detaching is invoked by Processes service. |
| * Here we just purge the context. |
| */ |
| purgeFromDebugger(); |
| } |
| |
| /** |
| * Purge this context and all its children and grand-children |
| * from debugger UI and internal data cache. |
| */ |
| public void purgeFromDebugger(){ |
| if (EDCTrace.RUN_CONTROL_TRACE_ON) { EDCTrace.getTrace().traceEntry(null); } |
| |
| for (ExecutionDMC e : getChildren()) |
| // recursively forget children first |
| e.purgeFromDebugger(); |
| |
| ExecutionDMC parent = getParent(); |
| if (parent != null) |
| parent.removeChild(this); |
| |
| getSession().dispatchEvent(new ExitedEvent(this, isTerminatingThanDisconnecting), RunControl.this.getProperties()); |
| |
| if (getRootDMC().getChildren().length == 0) |
| // no more contexts under debug, fire exitedEvent for the rootDMC which |
| // will trigger shutdown of the debug session. |
| // See EDCLaunch.eventDispatched(IExitedDMEvent e). |
| // Whether the root is terminated or disconnected depends on whether |
| // the last context is terminated or disconnected. |
| getSession().dispatchEvent(new ExitedEvent(getRootDMC(), isTerminatingThanDisconnecting), RunControl.this.getProperties()); |
| |
| if (EDCTrace.RUN_CONTROL_TRACE_ON) { EDCTrace.getTrace().traceExit(null); } |
| } |
| |
| /** |
| * Recursively marks all execution contexts as resumed |
| * @param dmc |
| */ |
| public void resumeAll(){ |
| contextResumed(true); |
| for (ExecutionDMC e : getChildren()){ |
| e.resumeAll(); |
| } |
| } |
| |
| protected void contextResumed(boolean fireResumeEventNow) { |
| assert getExecutor().isInExecutorThread(); |
| |
| if (children.size() > 0) { |
| // If it has kids (e.g. a process has threads), only need |
| // to mark the kids as resumed. |
| for (ExecutionDMC e : children){ |
| e.contextResumed(fireResumeEventNow); |
| } |
| return; |
| } |
| |
| if (EDCTrace.RUN_CONTROL_TRACE_ON) { EDCTrace.getTrace().traceEntry(null, EDCTrace.fixArgs(new Object[] { this, fireResumeEventNow })); } |
| |
| setIsSuspended(false); |
| |
| if (fireResumeEventNow) |
| getSession().dispatchEvent(this.createResumedEvent(), RunControl.this.getProperties()); |
| else |
| scheduleResumeEvent(); |
| |
| if (EDCTrace.RUN_CONTROL_TRACE_ON) { EDCTrace.getTrace().traceExit(null); } |
| } |
| |
| /** |
| * Schedule a task to run after some time which will |
| * notify platform that the context is running. |
| */ |
| private void scheduleResumeEvent() { |
| countOfScheduledNotifications++; |
| |
| final ExecutionDMC dmc = this; |
| |
| Runnable notifyPlatformTask = new Runnable() { |
| public void run() { |
| /* |
| * Notify platform the context is running. |
| * |
| * But don't do that if another such task is scheduled |
| * (namely current stepping is done within the RESUME_NOTIFICATION_DELAY and |
| * another stepping/resume is underway). |
| */ |
| countOfScheduledNotifications--; |
| if (countOfScheduledNotifications == 0 && !isSuspended()) |
| getSession().dispatchEvent(dmc.createResumedEvent(), RunControl.this.getProperties()); |
| }}; |
| |
| getExecutor().schedule(notifyPlatformTask, RESUME_NOTIFICATION_DELAY, TimeUnit.MILLISECONDS); |
| } |
| |
| /** |
| * Execute a single instruction. Note the "rm" is marked done() only |
| * when we get the suspend event, not when we successfully send the |
| * command to TCF agent. |
| * |
| * @param rm |
| */ |
| protected void singleStep(final boolean stepInto, final RequestMonitor rm) { |
| if (EDCTrace.RUN_CONTROL_TRACE_ON) { EDCTrace.getTrace().traceEntry(null, EDCTrace.fixArg(this.getName())); } |
| |
| setStepping(true); |
| |
| flushCache(this); |
| |
| if (hasTCFContext()) |
| { |
| Protocol.invokeLater(new Runnable() { |
| public void run() { |
| int mode = stepInto ? org.eclipse.tm.tcf.services.IRunControl.RM_STEP_INTO |
| : org.eclipse.tm.tcf.services.IRunControl.RM_STEP_OVER; |
| getTCFContext().resume(mode, 1, new DoneCommand() { |
| public void doneCommand(IToken token, final Exception error) { |
| // do this in DSF executor thread. |
| getExecutor().execute(new Runnable() { |
| public void run() { |
| handleTCFResumeDoneForStepping("SingleStep", error, rm); |
| } |
| }); |
| } |
| }); |
| } |
| }); |
| } |
| if (EDCTrace.RUN_CONTROL_TRACE_ON) { EDCTrace.getTrace().traceExit(null); } |
| } |
| |
| /** |
| * Step out of the current function. Note the "rm" is marked done() only |
| * when we get the suspend event, not when we successfully send the |
| * command to TCF agent. |
| * |
| * @param rm |
| */ |
| protected void stepOut(final RequestMonitor rm) { |
| assert supportsStepMode(StepType.STEP_RETURN) : STEP_RETURN_NOT_SUPPORTED; |
| |
| if (EDCTrace.RUN_CONTROL_TRACE_ON) { EDCTrace.getTrace().traceEntry(null, EDCTrace.fixArg(this.getName())); } |
| |
| setStepping(true); |
| |
| flushCache(this); |
| |
| if (hasTCFContext()) { |
| Protocol.invokeLater(new Runnable() { |
| public void run() { |
| getTCFContext() |
| .resume(org.eclipse.tm.tcf.services.IRunControl.RM_STEP_OUT, |
| 0, new DoneCommand() { |
| |
| public void doneCommand( |
| IToken token, |
| final Exception error) { |
| // do this in DSF executor thread. |
| getExecutor().execute( |
| new Runnable() { |
| public void run() { |
| handleTCFResumeDoneForStepping( |
| "StepOut", |
| error, |
| rm); |
| } |
| }); |
| } |
| }); |
| } |
| }); |
| } |
| if (EDCTrace.RUN_CONTROL_TRACE_ON) { EDCTrace.getTrace().traceExit(null); } |
| } |
| |
| protected void stepRange(final boolean stepInto, final IAddress rangeStart, final IAddress rangeEnd, |
| final RequestMonitor rm) { |
| if (EDCTrace.RUN_CONTROL_TRACE_ON) { EDCTrace.getTrace().traceEntry(null, EDCTrace.fixArg(this.getName())); } |
| |
| setStepping(true); |
| |
| flushCache(this); |
| |
| if (hasTCFContext()) { |
| Protocol.invokeLater(new Runnable() { |
| public void run() { |
| int mode = stepInto ? org.eclipse.tm.tcf.services.IRunControl.RM_STEP_INTO_RANGE |
| : org.eclipse.tm.tcf.services.IRunControl.RM_STEP_OVER_RANGE; |
| Map<String, Object> params = new HashMap<String, Object>(); |
| params.put("RANGE_START", rangeStart.getValue()); |
| params.put("RANGE_END", rangeEnd.getValue()); |
| |
| getTCFContext().resume(mode, 0, params, new DoneCommand() { |
| |
| public void doneCommand(IToken token, |
| final Exception error) { |
| // do this in DSF executor thread. |
| getExecutor().execute(new Runnable() { |
| public void run() { |
| handleTCFResumeDoneForStepping( |
| "StepRange", error, rm); |
| } |
| }); |
| } |
| }); |
| } |
| }); |
| } |
| if (EDCTrace.RUN_CONTROL_TRACE_ON) { EDCTrace.getTrace().traceExit(null); } |
| } |
| |
| /** |
| * set whether debugger is stepping in the context. |
| * |
| * @param isStepping |
| */ |
| public void setStepping(boolean isStepping) { |
| this.isStepping = isStepping; |
| } |
| |
| /** |
| * @return whether debugger is stepping the context. |
| */ |
| public boolean isStepping() { |
| return isStepping; |
| } |
| |
| private void addStepRange(IAddress lowAddress, IAddress highAddress) { |
| stepRanges.add(new EDCAddressRange(lowAddress, highAddress)); |
| } |
| |
| /** |
| * Check if the give address is in current step ranges, if yes, return |
| * the range that contains it. |
| * |
| * @param address |
| * @return null if not found. |
| */ |
| private EDCAddressRange findStepRange(IAddress address) { |
| for (EDCAddressRange r : stepRanges) { |
| if (r.contains(address)) |
| return r; |
| } |
| return null; |
| } |
| |
| private void clearStepRanges() { |
| stepRanges.clear(); |
| } |
| |
| protected DMCSuspendedEvent createSuspendedEvent(StateChangeReason reason, Map<String, Object> properties) { |
| return new SuspendedEvent(this, reason, properties); |
| } |
| |
| /** |
| * Cache a suspendedEvent for this context. Note only one |
| * event will be cached at a time. |
| * |
| * @param e |
| * The event to cache. |
| */ |
| private void cacheSuspendedEvent(DMCSuspendedEvent e) { |
| cachedSuspendedEvent = e; |
| } |
| |
| /** |
| * The event should be used only once, thus this will clear |
| * the cache for that sake. |
| * @return |
| */ |
| private DMCSuspendedEvent getCachedSuspendedEvent() { |
| DMCSuspendedEvent e = cachedSuspendedEvent; |
| cachedSuspendedEvent = null; |
| return e; |
| } |
| |
| protected DMCResumedEvent createResumedEvent() { |
| return new ResumedEvent(this); |
| } |
| |
| public RunControlContext getTCFContext() { |
| return tcfContext; |
| } |
| |
| public boolean hasTCFContext() { |
| return tcfContext != null; |
| } |
| |
| @Override |
| public Object getAdapter(@SuppressWarnings("rawtypes") Class adapterType) { |
| if (adapterType.equals(ILogActionEnabler.class)) { |
| Stack stackService = getService(Stack.class); |
| IFrameDMContext[] frames; |
| try { |
| frames = stackService.getFramesForDMC(this, 0, 0); |
| Expressions exprService = getService(Expressions.class); |
| return new EDCLogActionEnabler(exprService, frames[0]); |
| } catch (CoreException e) { |
| return null; |
| } |
| } |
| if (adapterType.equals(IResumeActionEnabler.class)) { |
| RunControl.ThreadExecutionDMC threadDMC |
| = DMContexts.getAncestorOfType(this, RunControl.ThreadExecutionDMC.class); |
| return new ResumeActionEnabler(threadDMC); |
| } |
| if (adapterType.equals(AbstractEDCService.class)) { |
| return RunControl.this; |
| } |
| return super.getAdapter(adapterType); |
| } |
| |
| private void addFunctionCallDestination(IAddress addr) { |
| functionCallDestinations.add(addr); |
| } |
| |
| private void clearFunctionCallDestinations() { |
| functionCallDestinations.clear(); |
| } |
| |
| private boolean isFunctionCallDestination(IAddress addr) { |
| return functionCallDestinations.contains(addr); |
| } |
| } |
| |
| public class ProcessExecutionDMC extends ExecutionDMC implements IContainerDMContext, IProcessDMContext, |
| ISymbolDMContext, IBreakpointsTargetDMContext, IDisassemblyDMContext { |
| |
| public ProcessExecutionDMC(ExecutionDMC parent, Map<String, Object> properties, RunControlContext tcfContext) { |
| super(parent, properties, tcfContext); |
| } |
| |
| @Override |
| public ExecutionDMC contextAdded(Map<String, Object> properties, RunControlContext tcfContext) { |
| if (EDCTrace.RUN_CONTROL_TRACE_ON) { EDCTrace.getTrace().traceEntry(null, EDCTrace.fixArg(properties)); } |
| ThreadExecutionDMC newDMC = new ThreadExecutionDMC(this, properties, tcfContext); |
| getSession().dispatchEvent(new StartedEvent(newDMC), RunControl.this.getProperties()); |
| if (EDCTrace.RUN_CONTROL_TRACE_ON) { EDCTrace.getTrace().traceExit(null, EDCTrace.fixArg(newDMC)); } |
| return newDMC; |
| } |
| |
| public ISymbolDMContext getSymbolDMContext() { |
| return this; |
| } |
| |
| @Override |
| public void loadSnapshot(Element element) throws Exception { |
| // load modules first, since this loads a stack which must consult modules and symbolics |
| Modules modulesService = getService(Modules.class); |
| modulesService.loadModulesForContext(this, element); |
| super.loadSnapshot(element); |
| } |
| |
| @Override |
| public Element takeSnapshot(IAlbum album, Document document, IProgressMonitor monitor)throws Exception { |
| SubMonitor progress = SubMonitor.convert(monitor, 1000); |
| progress.subTask(getName()); |
| Element contextElement = super.takeSnapshot(album, document, progress.newChild(500)); |
| Element modulesElement = document.createElement(EXECUTION_CONTEXT_MODULES); |
| Modules modulesService = getService(Modules.class); |
| |
| IModuleDMContext[] modules = modulesService.getModulesForContext(this.getID()); |
| SubMonitor modulesMonitor = progress.newChild(500); |
| modulesMonitor.setWorkRemaining(modules.length * 1000); |
| modulesMonitor.subTask("Modules"); |
| for (IModuleDMContext moduleContext : modules) { |
| ModuleDMC moduleDMC = (ModuleDMC) moduleContext; |
| modulesElement.appendChild(moduleDMC.takeSnapshot(album, document, modulesMonitor.newChild(1000))); |
| } |
| |
| contextElement.appendChild(modulesElement); |
| return contextElement; |
| } |
| |
| @Override |
| public boolean canDetach() { |
| // Can detach from a process unless we're part of a snapshot. |
| return hasTCFContext(); |
| } |
| |
| @Override |
| public boolean canStep() { |
| // can't step a process. |
| return false; |
| } |
| } |
| |
| public class ThreadExecutionDMC extends ExecutionDMC implements IThreadDMContext, IDisassemblyDMContext { |
| |
| public ThreadExecutionDMC(ExecutionDMC parent, Map<String, Object> properties, RunControlContext tcfContext) { |
| super(parent, properties, tcfContext); |
| if (EDCTrace.RUN_CONTROL_TRACE_ON) { |
| EDCTrace.getTrace().traceEntry(null, EDCTrace.fixArgs(new Object[] { parent, properties })); |
| EDCTrace.getTrace().traceExit(null); |
| } |
| } |
| |
| public ISymbolDMContext getSymbolDMContext() { |
| return DMContexts.getAncestorOfType(this, ISymbolDMContext.class); |
| } |
| |
| @Override |
| public void loadSnapshot(Element element) throws Exception { |
| super.loadSnapshot(element); |
| Registers regService = getService(Registers.class); |
| regService.loadGroupsForContext(this, element); |
| |
| Stack stackService = getService(Stack.class); |
| NodeList frameElements = element.getElementsByTagName(EXECUTION_CONTEXT_FRAMES); |
| for (int i = 0; i < frameElements.getLength(); i++) { |
| Element frameElement = (Element) frameElements.item(i); |
| stackService.loadFramesForContext(this, frameElement); |
| } |
| |
| getSession().dispatchEvent( |
| createSuspendedEvent(StateChangeReason.EXCEPTION, new HashMap<String, Object>()), |
| RunControl.this.getProperties()); |
| } |
| |
| @Override |
| public Element takeSnapshot(IAlbum album, Document document, IProgressMonitor monitor)throws Exception { |
| SubMonitor progress = SubMonitor.convert(monitor, 1000); |
| progress.subTask(getName()); |
| Element contextElement = super.takeSnapshot(album, document, progress.newChild(100)); |
| Element registersElement = document.createElement(EXECUTION_CONTEXT_REGISTERS); |
| Registers regService = getService(Registers.class); |
| |
| IRegisterGroupDMContext[] regGroups = regService.getGroupsForContext(this); |
| SubMonitor registerMonitor = progress.newChild(300); |
| registerMonitor.setWorkRemaining(regGroups.length * 1000); |
| registerMonitor.subTask("Registers"); |
| for (IRegisterGroupDMContext registerGroupDMContext : regGroups) { |
| RegisterGroupDMC regDMC = (RegisterGroupDMC) registerGroupDMContext; |
| registersElement.appendChild(regDMC.takeSnapshot(album, document, registerMonitor.newChild(1000))); |
| } |
| |
| contextElement.appendChild(registersElement); |
| |
| Element framesElement = document.createElement(EXECUTION_CONTEXT_FRAMES); |
| Stack stackService = getService(Stack.class); |
| Expressions expressionsService = getService(Expressions.class); |
| |
| IFrameDMContext[] frames = stackService.getFramesForDMC(this, 0, IStack.ALL_FRAMES); |
| SubMonitor framesMonitor = progress.newChild(600); |
| framesMonitor.setWorkRemaining(frames.length * 2000); |
| framesMonitor.subTask("Stack Frames"); |
| for (IFrameDMContext frameDMContext : frames) { |
| StackFrameDMC frameDMC = (StackFrameDMC) frameDMContext; |
| |
| // Get the local variables for each frame |
| IVariableDMContext[] variables = frameDMC.getLocals(); |
| SubMonitor variablesMonitor = framesMonitor.newChild(1000); |
| variablesMonitor.setWorkRemaining(variables.length * 10); |
| variablesMonitor.subTask("Variables"); |
| for (IVariableDMContext iVariableDMContext : variables) { |
| VariableDMC varDMC = (VariableDMC) iVariableDMContext; |
| IExpressionDMContext expression = expressionsService.createExpression(frameDMContext, varDMC.getName()); |
| boolean wasEnabled = FormatExtensionManager.instance().isEnabled(); |
| FormatExtensionManager.instance().setEnabled(true); |
| expressionsService.loadExpressionValues(expression, Album.getVariableCaptureDepth()); |
| FormatExtensionManager.instance().setEnabled(wasEnabled); |
| variablesMonitor.worked(10); |
| variablesMonitor.subTask("Variables - " + varDMC.getName()); |
| } |
| |
| framesElement.appendChild(frameDMC.takeSnapshot(album, document, framesMonitor.newChild(1000))); |
| } |
| contextElement.appendChild(framesElement); |
| |
| return contextElement; |
| } |
| |
| @Override |
| public ExecutionDMC contextAdded(Map<String, Object> properties, RunControlContext tcfContext) { |
| assert (false); |
| return null; |
| } |
| |
| @Override |
| public boolean canDetach() { |
| // Cannot detach from a thread. |
| return false; |
| } |
| |
| @Override |
| public boolean canStep() { |
| if (isSuspended()) { |
| synchronized (properties) { |
| return !RunControl.getProperty(properties, PROP_DISABLE_STEPPING, false); |
| } |
| } |
| |
| return false; |
| } |
| } |
| |
| /** |
| * Context representing a program running on a bare device without OS, which |
| * can also be the boot-up "process" of an OS. |
| * <p> |
| * It's like a thread context as it has its registers and stack frames, but |
| * also like a process as it has modules associated with it. Currently we |
| * set it as an IProcessDMContext so that it appears as a ContainerVMNode in |
| * debug view. See LaunchVMProvider for more. Also it's treated like a |
| * process in |
| * {@link Processes#getProcessesBeingDebugged(IDMContext, DataRequestMonitor)} |
| */ |
| public class BareDeviceExecutionDMC extends ThreadExecutionDMC |
| implements IProcessDMContext, ISymbolDMContext, IBreakpointsTargetDMContext { |
| |
| public BareDeviceExecutionDMC(ExecutionDMC parent, |
| Map<String, Object> properties, RunControlContext tcfContext) { |
| super(parent, properties, tcfContext); |
| assert !RunControl.getProperty(properties, PROP_IS_CONTAINER, true); |
| } |
| |
| @Override |
| protected DMCSuspendedEvent createSuspendedEvent(StateChangeReason reason, Map<String, Object> properties) { |
| return new ContainerSuspendedEvent(this, reason, properties); |
| } |
| |
| @Override |
| protected DMCResumedEvent createResumedEvent() { |
| return new ContainerResumedEvent(this); |
| } |
| |
| @Override |
| public boolean canDetach() { |
| return true; |
| } |
| |
| } |
| |
| public class RootExecutionDMC extends ExecutionDMC implements ISourceLookupDMContext { |
| |
| public RootExecutionDMC(Map<String, Object> props) { |
| super(null, props, null); |
| } |
| |
| @Override |
| public ExecutionDMC contextAdded(Map<String, Object> properties, RunControlContext tcfContext) { |
| ExecutionDMC newDMC; |
| // If the new context being added under root is a container context, |
| // we treat it as a Process, otherwise a bare device program context. |
| // |
| if (RunControl.getProperty(properties, PROP_IS_CONTAINER, true)) |
| newDMC = new ProcessExecutionDMC(this, properties, tcfContext); |
| else |
| newDMC = new BareDeviceExecutionDMC(this, properties, tcfContext); |
| |
| getSession().dispatchEvent(new StartedEvent(newDMC), RunControl.this.getProperties()); |
| return newDMC; |
| } |
| |
| public ISymbolDMContext getSymbolDMContext() { |
| return null; |
| } |
| |
| @Override |
| public boolean canDetach() { |
| return false; |
| } |
| |
| @Override |
| public boolean canStep() { |
| return false; |
| } |
| } |
| |
| public class ResumeActionEnabler implements IResumeActionEnabler { |
| |
| ExecutionDMC executionDMC; |
| |
| public ResumeActionEnabler(final ExecutionDMC exeDMC) { |
| executionDMC = exeDMC; |
| } |
| |
| public void resume() throws Exception { |
| RunControl.this.resume(executionDMC, new RequestMonitor(getExecutor(), null)); |
| } |
| |
| } |
| |
| private static final String EXECUTION_CONTEXTS = "execution_contexts"; |
| |
| private org.eclipse.tm.tcf.services.IRunControl tcfRunService; |
| private RootExecutionDMC rootExecutionDMC; |
| private final Map<String, ExecutionDMC> dmcsByID = new HashMap<String, ExecutionDMC>(); |
| |
| public RunControl(DsfSession session) { |
| super(session, new String[] { |
| IRunControl.class.getName(), |
| IRunControl2.class.getName(), |
| RunControl.class.getName(), |
| ISnapshotContributor.class.getName() }); |
| initializeRootExecutionDMC(); |
| } |
| |
| private void initializeRootExecutionDMC() { |
| HashMap<String, Object> props = new HashMap<String, Object>(); |
| props.put(IEDCDMContext.PROP_ID, "root"); |
| rootExecutionDMC = new RootExecutionDMC(props); |
| } |
| |
| public void canResume(IExecutionDMContext context, DataRequestMonitor<Boolean> rm) { |
| rm.setData(((ExecutionDMC) context).isSuspended() ? Boolean.TRUE : Boolean.FALSE); |
| rm.done(); |
| } |
| |
| public void canStep(IExecutionDMContext context, StepType stepType, DataRequestMonitor<Boolean> rm) { |
| rm.setData(((ExecutionDMC) context).canStep() ? Boolean.TRUE : Boolean.FALSE); |
| rm.done(); |
| } |
| |
| public void canSuspend(IExecutionDMContext context, DataRequestMonitor<Boolean> rm) { |
| if (isSnapshot()) |
| rm.setData(Album.getAlbumBySession(getSession().getId()).isPlayingSnapshots()); |
| else |
| rm.setData(((ExecutionDMC) context).isSuspended() ? Boolean.FALSE : Boolean.TRUE); |
| rm.done(); |
| } |
| |
| public void getExecutionContexts(IContainerDMContext c, DataRequestMonitor<IExecutionDMContext[]> rm) { |
| if (c instanceof ProcessExecutionDMC) { |
| ProcessExecutionDMC edmc = (ProcessExecutionDMC) c; |
| IEDCExecutionDMC[] threads = edmc.getChildren(); |
| IExecutionDMContext[] threadArray = new IExecutionDMContext[threads.length]; |
| System.arraycopy(threads, 0, threadArray, 0, threads.length); |
| rm.setData(threadArray); |
| } |
| rm.done(); |
| } |
| |
| public void getExecutionData(IExecutionDMContext dmc, DataRequestMonitor<IExecutionDMData> rm) { |
| if (dmc instanceof ExecutionDMC) { |
| ExecutionDMC exedmc = (ExecutionDMC) dmc; |
| if (exedmc.isSuspended()) { |
| rm.setData(new ExecutionData(exedmc.getStateChangeReason(), exedmc.getStateChangeDetails())); |
| } else { |
| rm.setData(new ExecutionData(StateChangeReason.UNKNOWN, null)); |
| } |
| } else |
| rm.setStatus(new Status(IStatus.ERROR, EDCDebugger.PLUGIN_ID, INVALID_HANDLE, |
| "Given context: " + dmc + " is not a recognized execution context.", null)); //$NON-NLS-1$ //$NON-NLS-2$ |
| rm.done(); |
| } |
| |
| public boolean isStepping(IExecutionDMContext context) { |
| if (context instanceof ExecutionDMC) { |
| ExecutionDMC exedmc = (ExecutionDMC) context; |
| return exedmc.isStepping(); |
| } |
| return false; |
| } |
| |
| public boolean isSuspended(IExecutionDMContext context) { |
| if (context instanceof ExecutionDMC) { |
| ExecutionDMC exedmc = (ExecutionDMC) context; |
| return exedmc.isSuspended(); |
| } |
| return false; |
| } |
| |
| /** |
| * Preprocessing for suspend event. This is done before we broadcast the |
| * suspend event across the debugger. Here's what's done in the |
| * preprocessing by default: <br> |
| * 1. Adjust PC after control hits a software breakpoint where the PC |
| * points at the byte right after the breakpoint instruction. This is to |
| * move PC back to the address of the breakpoint instruction.<br> |
| * 2. If we stops at a breakpoint, evaluate condition of the breakpoint |
| * and determine if we should ignore the suspend event and resume or |
| * should honor the suspend event and sent it up the ladder. |
| * <p> |
| * Subclass can override this method to add their own special preprocessing, |
| * while calling super implementation to carry out the default common. |
| * <p> |
| * This must be called in DSF executor thread. |
| * |
| * @param pc |
| * program pointer value from the event, in the format of |
| * big-endian hex string. Can be null. |
| * @param drm |
| * DataRequestMonitor whose result indicates whether to honor |
| * the suspend. |
| */ |
| protected void preprocessOnSuspend(final ExecutionDMC dmc, final String pc, |
| final DataRequestMonitor<Object> drm) { |
| |
| assert getExecutor().isInExecutorThread(); |
| |
| asyncExec(new Runnable() { |
| |
| public void run() { |
| try { |
| Breakpoints bpService = getService(Breakpoints.class); |
| Registers regService = getService(Registers.class); |
| String pcString = pc; |
| |
| if (pc == null) { |
| // read PC register |
| pcString = regService.getRegisterValue(dmc, getTargetEnvironmentService().getPCRegisterID()); |
| } |
| |
| dmc.setPC(pcString); |
| |
| // This check is to speed up handling of suspend due to |
| // other reasons such as "step". |
| // The TCF agents should always report the |
| // "stateChangeReason" as BREAKPOINT when a breakpoint |
| // is hit. |
| |
| StateChangeReason stateChangeReason = dmc.getStateChangeReason(); |
| if (stateChangeReason != StateChangeReason.BREAKPOINT |
| && stateChangeReason != StateChangeReason.WATCHPOINT) { |
| drm.setData(true); |
| drm.done(); |
| return; |
| } |
| |
| BreakpointDMData bp; |
| if (!bpService.usesTCFBreakpointService()) { |
| // generic software breakpoint is used. |
| // We need to move PC back to the breakpoint |
| // instruction. |
| |
| long pcValue |
| = Long.valueOf(pcString, 16) |
| - getTargetEnvironmentService() |
| .getBreakpointInstruction(dmc, new Addr64(pcString, 16)) |
| .length; |
| pcString = Long.toHexString(pcValue); |
| |
| bp = bpService.findBreakpoint(new Addr64(pcString, 16)); |
| |
| // Stopped but not due to breakpoint set by debugger. |
| // For instance, some Windows DLL has "int 3" |
| // instructions in it. |
| if (bp != null) { |
| // Now adjust PC register. |
| regService.writeRegister(dmc, getTargetEnvironmentService().getPCRegisterID(), pcString); |
| dmc.setPC(pcString); |
| } |
| } else { |
| if (stateChangeReason == StateChangeReason.BREAKPOINT) |
| bp = bpService.findUserBreakpoint(new Addr64(pcString, 16)); |
| else {// condition above means this is a StateChangeReason.WATCHPOINT |
| bp = bpService.findUserBreakpoint(new Addr64(dmc.getStateChangeDetails(), 16)); |
| if (bp != null) |
| dmc.setStateChangeDetails("[" + bp.getExpression() + "]"); |
| } |
| } |
| |
| // check if a conditional breakpoint (must be a user bp) is hit |
| // |
| if (bp != null) { |
| // evaluate the condition |
| bpService.evaluateBreakpointCondition(dmc, bp, drm); |
| } else { |
| drm.setData(true); |
| drm.done(); |
| } |
| } catch (CoreException e) { |
| Status s = new Status(IStatus.ERROR, EDCDebugger.getUniqueIdentifier(), null, e); |
| EDCDebugger.getMessageLogger().log(s); |
| drm.setStatus(s); |
| drm.done(); |
| } |
| if (EDCTrace.RUN_CONTROL_TRACE_ON) { EDCTrace.getTrace().traceExit(null, EDCTrace.fixArg(drm.getData())); } |
| } |
| |
| }, drm); |
| } |
| |
| public void resume(IExecutionDMContext context, final RequestMonitor rm) { |
| if (EDCTrace.RUN_CONTROL_TRACE_ON) { EDCTrace.getTrace().traceEntry(null, EDCTrace.fixArg(MessageFormat.format("resume context {0}", context))); } |
| |
| if (!(context instanceof ExecutionDMC)) { |
| rm.setStatus(new Status(IStatus.ERROR, EDCDebugger.PLUGIN_ID, INVALID_HANDLE, MessageFormat.format( |
| "The context [{0}] is not a recognized execution context.", context), null)); |
| rm.done(); |
| } |
| |
| final ExecutionDMC dmc = (ExecutionDMC) context; |
| |
| prepareToRun(dmc, new DataRequestMonitor<Boolean>(getExecutor(), rm) { |
| |
| @Override |
| protected void handleSuccess() { |
| dmc.resume(rm); |
| if (EDCTrace.RUN_CONTROL_TRACE_ON) { EDCTrace.getTrace().traceExit(null, EDCTrace.fixArg(MessageFormat.format("resume() done on context {0}", dmc))); } |
| } |
| }); |
| } |
| |
| /** |
| * Prepare for resuming or stepping by <br> |
| * - executing current instruction if PC is at a breakpoint. |
| * |
| * @param dmc |
| * - the execution context, usually a thread. |
| * @param drm |
| * - data request monitor which will contain boolean value on |
| * done indicating whether an instruction is executed during the |
| * preparation. |
| */ |
| private void prepareToRun(final ExecutionDMC dmc, final DataRequestMonitor<Boolean> drm) { |
| // if there are actions associated with the last breakpoint, |
| // cancel the RM (and the action list for them) and resume |
| dmc.clearBreakpointActionRM(); |
| |
| // If there is breakpoint at current PC, remove it => Single step => |
| // Restore it. |
| |
| final Breakpoints bpService = getService(Breakpoints.class); |
| if (bpService.usesTCFBreakpointService()) { |
| // It's currently required that the agent can single-step past a breakpoint |
| // if it offers TCF breakpoints service. It's not a solid requirement but just |
| // nice for the sake of stepping performance. |
| drm.setData(false); |
| drm.done(); |
| return; |
| } |
| |
| String latestPC = dmc.getPC(); |
| |
| if (latestPC != null) { |
| final BreakpointDMData bp = bpService.findUserBreakpoint(new Addr64(latestPC, 16)); |
| if (bp != null) { |
| bpService.disableBreakpoint(bp, new RequestMonitor(getExecutor(), drm) { |
| |
| @Override |
| protected void handleSuccess() { |
| // Now step over the instruction |
| // |
| dmc.setSuspendEventsEnabled(false); |
| dmc.singleStep(true, new RequestMonitor(getExecutor(), drm) { |
| @Override |
| protected void handleCompleted() { |
| dmc.setSuspendEventsEnabled(true); |
| super.handleCompleted(); |
| } |
| |
| @Override |
| protected void handleSuccess() { |
| // At this point the single instruction |
| // execution should be done |
| // and the context being suspended. |
| // |
| drm.setData(true); // indicates an instruction |
| // is executed |
| |
| // Now restore the breakpoint. |
| bpService.enableBreakpoint(bp, drm); |
| } |
| }); |
| } |
| }); |
| } else { // no breakpoint at PC |
| drm.setData(false); |
| drm.done(); |
| } |
| } else { |
| drm.setData(false); |
| drm.done(); |
| } |
| } |
| |
| // This is a coarse timer on stepping for internal use. |
| // When needed, turn it on and watch output in console. |
| // |
| private static long steppingStartTime = 0; |
| public static boolean timeStepping() { |
| return false; |
| } |
| |
| public static long getSteppingStartTime() { |
| return steppingStartTime; |
| } |
| |
| public void step(final IExecutionDMContext context, final StepType outerStepType, final RequestMonitor rm) { |
| /* |
| * Step from current PC in "context" |
| */ |
| asyncExec(new Runnable() { |
| |
| public void run() { |
| StepType stepType = outerStepType; |
| if (EDCTrace.RUN_CONTROL_TRACE_ON) { EDCTrace.getTrace().traceEntry(null, EDCTrace.fixArg(MessageFormat.format("{0} context {1}", stepType, context))); } |
| |
| if (!(context instanceof ExecutionDMC)) { |
| rm.setStatus(new Status(IStatus.ERROR, EDCDebugger.PLUGIN_ID, INVALID_HANDLE, MessageFormat.format( |
| "The context [{0}] is not a recognized execution context.", context), null)); |
| rm.done(); |
| } |
| |
| if (timeStepping()) |
| steppingStartTime = System.currentTimeMillis(); |
| |
| final ExecutionDMC dmc = (ExecutionDMC) context; |
| |
| dmc.setStepping(true); |
| dmc.clearFunctionCallDestinations(); |
| |
| IAddress pcAddress = null; |
| |
| if (dmc.getPC() == null) { // PC is even unknown, can only do |
| // one-instruction step. |
| stepType = StepType.INSTRUCTION_STEP_INTO; |
| } else |
| pcAddress = new Addr64(dmc.getPC(), 16); |
| |
| // For step-out (step-return), no difference between source level or |
| // instruction level. |
| // |
| if (stepType == StepType.STEP_RETURN) |
| stepType = StepType.INSTRUCTION_STEP_RETURN; |
| |
| // Source level stepping request. |
| // |
| if (stepType == StepType.STEP_OVER || stepType == StepType.STEP_INTO) { |
| IEDCModules moduleService = getService(Modules.class); |
| |
| ISymbolDMContext symCtx = DMContexts.getAncestorOfType(context, ISymbolDMContext.class); |
| |
| IEDCModuleDMContext module = moduleService.getModuleByAddress(symCtx, pcAddress); |
| |
| // Check if there is source info for PC address. |
| // |
| if (module != null) { |
| IEDCSymbolReader reader = module.getSymbolReader(); |
| assert pcAddress != null; |
| if (reader != null) { |
| IAddress linkAddress = module.toLinkAddress(pcAddress); |
| IModuleLineEntryProvider lineEntryProvider |
| = reader.getModuleScope().getModuleLineEntryProvider(); |
| ILineEntry line = lineEntryProvider.getLineEntryAtAddress(linkAddress); |
| if (line != null) { |
| // get runtime addresses of the line boundaries. |
| IAddress endAddr = getAddressForNextLine(dmc, pcAddress, module, |
| linkAddress, lineEntryProvider, line, |
| stepType == StepType.STEP_OVER); |
| |
| dmc.clearStepRanges(); |
| |
| // If the line has two or more code ranges, record them |
| // |
| Collection<ILineEntry> ranges |
| = lineEntryProvider.getLineEntriesForLines(line.getFilePath(), |
| line.getLineNumber(), |
| line.getLineNumber()); |
| if (ranges.size() > 1) |
| { |
| for (ILineEntry iLineEntry : ranges) { |
| dmc.addStepRange(module.toRuntimeAddress(iLineEntry.getLowAddress()), |
| module.toRuntimeAddress(iLineEntry.getHighAddress())); |
| } |
| } |
| |
| /* |
| * It's possible that PC is larger than |
| * startAddr (e.g. user does a few instruction |
| * level stepping then switch to source level |
| * stepping). We just parse and step past |
| * instructions within [pcAddr, endAddr) instead |
| * of all those within [startAddr, endAddr). One |
| * possible problem with the solution is when |
| * control jumps from a point within [pcAddress, |
| * endAddr) to a point within [startAddr, |
| * pcAddress), the stepping would stop within |
| * instead of outside of the [startAddr, |
| * endAddr). But that case is rare (e.g. a |
| * source line contains a bunch of statements) |
| * and that "problem" is not unacceptable as |
| * user could just keep stepping or set a |
| * breakpoint and run. |
| * |
| * We can overcome the problem but that would |
| * incur much more complexity in the stepping |
| * code and brings down the stepping speed. |
| * ........................ 08/30/2009 |
| */ |
| final boolean stepIn = stepType == StepType.STEP_INTO; |
| stepAddressRange(dmc, stepIn, pcAddress, endAddr, new RequestMonitor(getExecutor(), rm) { |
| @Override |
| protected void handleSuccess() { |
| handleStepAddressRangeDone(stepIn, dmc, rm); |
| }} |
| ); |
| |
| if (EDCTrace.RUN_CONTROL_TRACE_ON) { EDCTrace.getTrace().traceExit(null, "source level stepping."); } |
| return; |
| } |
| } |
| } |
| |
| // No source found, fall back to instruction level step. |
| if (stepType == StepType.STEP_INTO) |
| stepType = StepType.INSTRUCTION_STEP_INTO; |
| else |
| stepType = StepType.INSTRUCTION_STEP_OVER; |
| } |
| |
| // instruction level step |
| // |
| if (stepType == StepType.INSTRUCTION_STEP_OVER) |
| stepOverOneInstruction(dmc, pcAddress, rm); |
| else if (stepType == StepType.INSTRUCTION_STEP_INTO) |
| // Note when do StepIn at instruction level, we |
| // don't bother checking and stepping past glue code. |
| // |
| stepIntoOneInstruction(dmc, rm); |
| else if (stepType == StepType.INSTRUCTION_STEP_RETURN) |
| stepOut(dmc, pcAddress, rm); |
| |
| if (EDCTrace.RUN_CONTROL_TRACE_ON) { EDCTrace.getTrace().traceExit(null); } |
| } |
| }, rm); |
| } |
| |
| private void handleStepAddressRangeDone(final boolean stepIn, final ExecutionDMC dmc, final RequestMonitor rm) { |
| IAddress newPC = new Addr64(dmc.getPC(), 16); |
| |
| boolean done = false; |
| EDCAddressRange r = dmc.findStepRange(newPC); |
| |
| if (r == null) |
| // PC is out of line code ranges, done |
| done = true; |
| else { |
| Breakpoints bpService = getService(Breakpoints.class); |
| if (bpService.findUserBreakpoint(newPC) != null) |
| // hit a user breakpoint |
| done = true; |
| } |
| |
| if (done) { |
| if (stepIn) { |
| // Only when we step into a function (not jump to some place) |
| // do we check if there is glue code and step past it if any |
| // ...........................08/07/11 |
| if (dmc.isFunctionCallDestination(newPC)) |
| stepPastGlueCode(dmc, newPC, rm); |
| else |
| rm.done(); |
| } |
| else |
| rm.done(); |
| } |
| else if (r != null) |
| // Still in a code range of the line, keep going by recursive call. |
| stepAddressRange(dmc, stepIn, newPC, r.getEndAddress(), new RequestMonitor(getExecutor(), rm) { |
| @Override |
| protected void handleSuccess() { |
| // recursive |
| handleStepAddressRangeDone(stepIn, dmc, rm); |
| }}); |
| } |
| |
| /** |
| * If instructions at PC are glue code (e.g. jump table for call to function in DLL), |
| * step past them. Otherwise just do nothing. |
| * |
| * @param dmc the execution context, usually a thread. |
| * @param pc program counter. |
| * @param rm |
| */ |
| private void stepPastGlueCode(ExecutionDMC dmc, IAddress pc, |
| RequestMonitor rm) { |
| // Glue code is totally processor specific. So |
| // let TargetEnvironment service handle it. |
| ITargetEnvironment te = getService(ITargetEnvironment.class); |
| te.stepPastGlueCode(dmc, pc, rm); |
| } |
| |
| private void stepOut(final ExecutionDMC dmc, IAddress pcAddress, final RequestMonitor rm) { |
| |
| if (EDCTrace.RUN_CONTROL_TRACE_ON) { EDCTrace.getTrace().traceEntry(null, "Step out from address " + pcAddress.toHexAddressString()); } |
| |
| if (dmc.supportsStepMode(StepType.STEP_RETURN)) { |
| dmc.stepOut(rm); |
| return; |
| } |
| |
| Stack stackService = getService(Stack.class); |
| IFrameDMContext[] frames; |
| try { |
| frames = stackService.getFramesForDMC(dmc, 0, 1); |
| } catch (CoreException e) { |
| Status s = new Status(IStatus.ERROR, EDCDebugger.getUniqueIdentifier(), null, e); |
| EDCDebugger.getMessageLogger().log(s); |
| rm.setStatus(s); |
| rm.done(); |
| return; |
| } |
| if (frames.length <= 1) { |
| rm.setStatus(new Status(IStatus.ERROR, EDCDebugger.PLUGIN_ID, REQUEST_FAILED, |
| "Cannot step out as no caller frame is available.", null)); |
| rm.done(); |
| return; |
| } |
| |
| if (handleSteppingOutOfInLineFunctions(dmc, frames, rm)) |
| return; |
| |
| final IAddress stepToAddress = ((StackFrameDMC) frames[1]).getInstructionPtrAddress(); |
| |
| final Breakpoints bpService = getService(Breakpoints.class); |
| |
| prepareToRun(dmc, new DataRequestMonitor<Boolean>(getExecutor(), rm) { |
| @Override |
| protected void handleSuccess() { |
| |
| boolean goon = true; |
| |
| if (getData() == true) { |
| // one instruction has been executed |
| IAddress newPC = new Addr64(dmc.getPC(), 16); |
| |
| // And we already stepped out (that instruction is return |
| // instruction). |
| // |
| if (newPC.equals(stepToAddress)) { |
| goon = false; |
| } |
| } |
| |
| if (goon) { |
| bpService.setTempBreakpoint(dmc, stepToAddress, new RequestMonitor(getExecutor(), rm) { |
| @Override |
| protected void handleSuccess() { |
| dmc.resumeForStepping(rm); |
| } |
| }); |
| } else { |
| // Stepping finished after prepareToRun(). |
| DMCSuspendedEvent e = dmc.getCachedSuspendedEvent(); |
| if (e != null) |
| getSession().dispatchEvent(e, RunControl.this.getProperties()); |
| |
| rm.done(); |
| } |
| } |
| }); |
| } |
| |
| /** |
| * handle module load event. A module is an executable file |
| * or a library (e.g. DLL or shared lib). |
| * Allow subclass to override for special handling if needed. |
| * This must be called in DSF dispatch thread. |
| * |
| * @param dmc |
| * @param moduleProperties |
| */ |
| protected void handleModuleLoadedEvent(IEDCExecutionDMC dmc, Map<String, Object> moduleProperties) { |
| ISymbolDMContext symbolContext = dmc.getSymbolDMContext(); |
| |
| if (symbolContext != null) { |
| Modules modulesService = getService(Modules.class); |
| modulesService.moduleLoaded(symbolContext, dmc, moduleProperties); |
| } |
| } |
| |
| /** |
| * handle module unload event. A module is an executable file |
| * or a library (e.g. DLL or shared lib). |
| * Allow subclass to override for special handling if needed. |
| * This must be called in DSF dispatch thread. |
| * |
| * @param dmc |
| * @param moduleProperties |
| */ |
| protected void handleModuleUnloadedEvent(IEDCExecutionDMC dmc, Map<String, Object> moduleProperties) { |
| ISymbolDMContext symbolContext = dmc.getSymbolDMContext(); |
| |
| if (symbolContext != null) { |
| Modules modulesService = getService(Modules.class); |
| modulesService.moduleUnloaded(symbolContext, dmc, moduleProperties); |
| } |
| } |
| |
| private boolean handleSteppingOutOfInLineFunctions(final ExecutionDMC dmc, |
| IFrameDMContext[] frames, final RequestMonitor rm) { |
| |
| assert frames.length > 1 && frames[0] instanceof StackFrameDMC; |
| |
| StackFrameDMC currentFrame = ((StackFrameDMC) frames[0]); |
| |
| IEDCModuleDMContext module = currentFrame.getModule(); |
| if (module != null) { |
| IFunctionScope func = currentFrame.getFunctionScope(); |
| // if inline ... |
| if (func != null && (func.getParent() instanceof IFunctionScope)) { |
| |
| // ... but if PC is at beginning of function, then act like not in inline |
| // (i.e. step-out as though standing at call to any non-inline function) |
| if (currentFrame.isInlineShouldBeHidden(null)) |
| return false; |
| |
| // ... or if PC at at high-address, that means we're actually done with it |
| IAddress functRuntimeHighAddr = module.toRuntimeAddress(func.getHighAddress()); |
| IAddress frameInstrPtr = currentFrame.getInstructionPtrAddress(); |
| if (functRuntimeHighAddr.equals(frameInstrPtr)) |
| return false; |
| |
| // getting here means treat the line as a regular line to step over |
| stepAddressRange(dmc, false, frameInstrPtr, functRuntimeHighAddr, |
| new RequestMonitor(getExecutor(), rm) { |
| @Override |
| protected void handleSuccess() { |
| rm.done(); |
| }}); |
| |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * check if the instruction at PC is a subroutine call. If yes, set a |
| * breakpoint after it and resume; otherwise just execute one instruction. |
| * |
| * @param dmc |
| * @param pcAddress |
| * @param rm |
| */ |
| private void stepOverOneInstruction(final ExecutionDMC dmc, final IAddress pcAddress, final RequestMonitor rm) { |
| |
| if (EDCTrace.RUN_CONTROL_TRACE_ON) { EDCTrace.getTrace().traceEntry(null, "address " + pcAddress.toHexAddressString()); } |
| |
| if (dmc.supportsStepMode(StepType.INSTRUCTION_STEP_OVER)) { |
| dmc.singleStep(false, rm); |
| return; |
| } |
| |
| ITargetEnvironment env = getTargetEnvironmentService(); |
| final IDisassembler disassembler = (env != null) ? env.getDisassembler() : null; |
| if (disassembler == null) { |
| rm.setStatus(Disassembly.statusNoDisassembler()); |
| rm.done(); |
| return; |
| } |
| |
| Memory memoryService = getService(Memory.class); |
| IMemoryDMContext mem_dmc = DMContexts.getAncestorOfType(dmc, IMemoryDMContext.class); |
| |
| // We need to get the instruction at the PC. We have to |
| // retrieve memory bytes for longest instruction. |
| @SuppressWarnings("null") // (env == null) -> (disassembler == null) -> return above |
| int maxInstLength = env.getLongestInstructionLength(); |
| |
| // Note this memory read will give us memory bytes with |
| // debugger breakpoints removed, which is just what we want. |
| memoryService.getMemory(mem_dmc, pcAddress, 0, 1, maxInstLength, |
| new DataRequestMonitor<MemoryByte[]>(getExecutor(), rm) { |
| @Override |
| protected void handleSuccess() { |
| ByteBuffer codeBuf = Disassembly.translateMemoryBytes(getData(), pcAddress, rm); |
| if (codeBuf == null) { |
| return; // rm status set in translateMemoryBytes() |
| } |
| |
| IDisassemblyDMContext dis_dmc |
| = DMContexts.getAncestorOfType(dmc, IDisassemblyDMContext.class); |
| Map<String, Object> options = new HashMap<String, Object>(); |
| options.put(IDisassemblerOptions.ADDRESS_IS_PC, 1); |
| IDisassembledInstruction inst; |
| try { |
| inst = disassembler.disassembleOneInstruction(pcAddress, codeBuf, options, dis_dmc); |
| } catch (CoreException e) { |
| rm.setStatus(e.getStatus()); |
| rm.done(); |
| return; |
| } |
| |
| final boolean isSubroutineCall = inst.getJumpToAddress() != null |
| && inst.getJumpToAddress().isSubroutineAddress(); |
| final IAddress nextInstructionAddress = pcAddress.add(inst.getSize()); |
| |
| stepIntoOneInstruction(dmc, new RequestMonitor(getExecutor(), rm) { |
| @Override |
| protected void handleSuccess() { |
| if (!isSubroutineCall) |
| rm.done(); |
| else { |
| // If current instruction is subroutine call, set a |
| // temp |
| // breakpoint at next instruction and resume ... |
| // |
| Breakpoints bpService = getService(Breakpoints.class); |
| bpService.setTempBreakpoint(dmc, nextInstructionAddress, |
| new RequestMonitor(getExecutor(), rm) { |
| @Override |
| protected void handleSuccess() { |
| dmc.resumeForStepping(rm); |
| } |
| }); |
| } |
| } |
| }); |
| } |
| }); |
| } |
| |
| /** |
| * Step into or over an address range. Note the startAddr is also the PC |
| * value. |
| * |
| * @param dmc |
| * @param stepIn |
| * - whether to step-in. |
| * @param startAddr |
| * - also the PC register value. |
| * @param endAddr |
| * @param rm |
| * - marked done after the stepping is over and context is |
| * suspended again. |
| */ |
| private void stepAddressRange(final ExecutionDMC dmc, final boolean stepIn, final IAddress startAddr, |
| final IAddress endAddr, final RequestMonitor rm) { |
| if (EDCTrace.RUN_CONTROL_TRACE_ON) { EDCTrace.getTrace().traceEntry(null, MessageFormat.format("address range [{0},{1})", startAddr.toHexAddressString(), endAddr.toHexAddressString())); } |
| |
| int memSize = startAddr.distanceTo(endAddr).intValue(); |
| if (memSize < 0) { // endAddr < startAddr |
| rm.setStatus(new Status(IStatus.ERROR, EDCDebugger.PLUGIN_ID, |
| MessageFormat.format( |
| "Invalid arguments for StepAddressRange(): ending address {0} is smaller than start address {1}.", |
| endAddr.toHexAddressString(), startAddr.toHexAddressString()))); |
| rm.done(); |
| return; |
| } |
| |
| if (dmc.supportsStepMode(stepIn ? StepType.STEP_INTO : StepType.STEP_OVER)) { |
| dmc.stepRange(stepIn, startAddr, endAddr, rm); |
| return; |
| } |
| |
| ITargetEnvironment env = getTargetEnvironmentService(); |
| final IDisassembler disassembler = (env != null) ? env.getDisassembler() : null; |
| if (disassembler == null) { |
| rm.setStatus(Disassembly.statusNoDisassembler()); |
| rm.done(); |
| return; |
| } |
| |
| final Memory memoryService = getService(Memory.class); |
| IMemoryDMContext mem_dmc = DMContexts.getAncestorOfType(dmc, IMemoryDMContext.class); |
| |
| final IAddress pcAddress = startAddr; |
| |
| // Note this memory read will give us memory bytes with |
| // debugger breakpoints removed, which is just what we want. |
| memoryService.getMemory(mem_dmc, startAddr, 0, 1, memSize, |
| new DataRequestMonitor<MemoryByte[]>(getExecutor(), rm) { |
| @Override |
| protected void handleSuccess() { |
| ByteBuffer codeBuf = Disassembly.translateMemoryBytes(getData(), startAddr, rm); |
| if (codeBuf == null) { |
| return; // rm status set in checkMemoryBytes() |
| } |
| |
| IDisassemblyDMContext dis_dmc |
| = DMContexts.getAncestorOfType(dmc, IDisassemblyDMContext.class); |
| |
| Map<String, Object> options = new HashMap<String, Object>(); |
| |
| List<IDisassembledInstruction> instList; |
| try { |
| instList |
| = disassembler.disassembleInstructions(startAddr, endAddr, codeBuf, |
| options, dis_dmc); |
| } catch (CoreException e) { |
| rm.setStatus(e.getStatus()); |
| rm.done(); |
| return; |
| } |
| |
| // Now collect all possible stop points |
| // |
| final List<IAddress> stopPoints = new ArrayList<IAddress>(); |
| final List<IAddress> runToAndCheckPoints = new ArrayList<IAddress>(); |
| boolean insertBPatRangeEnd = true; |
| |
| for (IDisassembledInstruction inst : instList) { |
| final IAddress instAddr = inst.getAddress(); |
| if (insertBPatRangeEnd == false) |
| insertBPatRangeEnd = true; |
| IJumpToAddress jta = inst.getJumpToAddress(); |
| if (jta == null) // not control-change instruction, ignore. |
| continue; |
| |
| // the instruction is a control-change instruction |
| // |
| if (!jta.isImmediate()) { |
| |
| if (inst.getAddress().equals(pcAddress)) { |
| // Control is already at the instruction, evaluate |
| // it. |
| // |
| String expr = (String) jta.getValue(); |
| if (expr.equals(JumpToAddress.EXPRESSION_RETURN_FAR) |
| || expr.equals(JumpToAddress.EXPRESSION_RETURN_NEAR) |
| || expr.equals(JumpToAddress.EXPRESSION_LR)) { |
| // The current instruction is return instruction. Just execute it |
| // to step-out and we are done with the stepping. This way we avoid |
| // looking for return address from caller stack frame which may not |
| // even available. |
| // Is it possible that the destination address of the step-out |
| // is still within the [startAddr, endAddr)range ? In theory |
| // yes, but in practice it means one source line has several |
| // function bodies in it, who would do that? |
| // |
| stepIntoOneInstruction(dmc, rm); |
| return; |
| } |
| |
| if (!jta.isSubroutineAddress() || stepIn) |
| { |
| // evaluate the address expression |
| IAddressExpressionEvaluator evaluator = |
| getTargetEnvironmentService().getAddressExpressionEvaluator(); |
| if (evaluator == null) { |
| rm.setStatus(new Status(IStatus.ERROR, EDCDebugger.PLUGIN_ID, REQUEST_FAILED, |
| "No evaluator for address expression yet.", null)); |
| rm.done(); |
| return; |
| } |
| |
| Registers regService = getService(Registers.class); |
| |
| IAddress addr; |
| try { |
| addr = evaluator.evaluate(dmc, expr, regService, memoryService); |
| } catch (CoreException e) { |
| rm.setStatus(e.getStatus()); |
| rm.done(); |
| return; |
| } |
| // don't add an address if we already have it |
| if (!stopPoints.contains(addr)) |
| stopPoints.add(addr); |
| |
| if (jta.isSubroutineAddress()) // step-in a function |
| dmc.addFunctionCallDestination(addr); |
| } |
| } else { |
| // we must run to this instruction first |
| // |
| /* |
| * What if control would skip (jump-over) this |
| * instruction within the [startAddr, endAddr) range |
| * ? So we should go on collecting stop points from |
| * the remaining instructions in the range and then |
| * do our two-phase stepping (see below) |
| */ |
| if (!runToAndCheckPoints.contains(instAddr)) |
| runToAndCheckPoints.add(instAddr); |
| } |
| } |
| else { // "jta" is immediate address. |
| |
| IAddress jumpAddress = (IAddress) jta.getValue(); |
| |
| if (jta.isSoleDestination()) { |
| if (jta.isSubroutineAddress()) { |
| // is subroutine call |
| if (stepIn && !stopPoints.contains(jumpAddress)) { |
| stopPoints.add(jumpAddress); |
| |
| dmc.addFunctionCallDestination(jumpAddress); |
| |
| // no need to check remaining instructions |
| // !! Wrong. Control may jump over (skip)this instruction |
| // within the [startAddr, endAddr) range, so we still need |
| // to parse instructions after this instruction. |
| // break; |
| } else { |
| // step over the call instruction. Just stop |
| // at next instruction. |
| // nothing to do. |
| } |
| } else { |
| // Unconditional jump instruction |
| // ignore jump within the address range |
| if (!(startAddr.compareTo(jumpAddress) <= 0 && jumpAddress.compareTo(endAddr) < 0)) { |
| insertBPatRangeEnd = false; |
| if (!stopPoints.contains(jumpAddress)) |
| stopPoints.add(jumpAddress); |
| } |
| } |
| } else { |
| // conditional jump |
| // ignore jump within the address range |
| if (!(startAddr.compareTo(jumpAddress) <= 0 && jumpAddress.compareTo(endAddr) < 0) |
| && !stopPoints.contains(jumpAddress)) |
| { |
| stopPoints.add(jumpAddress); |
| } |
| } |
| } |
| } // end of parsing instructions |
| |
| // need a temp breakpoint at the "endAddr". |
| if (insertBPatRangeEnd && !stopPoints.contains(endAddr)) |
| stopPoints.add(endAddr); |
| |
| if (runToAndCheckPoints.size() > 0) { |
| // Now do our two-phase stepping. |
| // |
| |
| if (runToAndCheckPoints.size() > 1) { |
| /* |
| * Wow, there are two control-change instructions in the |
| * range that requires run-to-check (let's call them RTC |
| * point). In theory the stepping might fail (not stop |
| * as desired) in such case: When we try to run to the |
| * first RTC, the control may skip the first RTC and run |
| * to second RTC (note we don't know the stop points of |
| * the second RTC yet) and run out of the range and be |
| * gone with the wind... |
| * |
| * TODO: we could solve it by keeping stepping till we are out |
| * of the range. Do it when really needed in practice (namely |
| * when the following warning is seen). |
| */ |
| // Log a warning here. |
| EDCDebugger.getMessageLogger().log( |
| new Status(IStatus.WARNING, EDCDebugger.PLUGIN_ID, |
| MessageFormat.format( |
| "More than one run-to-check points in the address range [{0},{1}). Stepping might fail.", |
| startAddr.toHexAddressString(), endAddr.toHexAddressString()))); |
| } |
| |
| // ------------ Phase 1: run to the first RTC. |
| // |
| // recursive call |
| stepAddressRange(dmc, stepIn, startAddr, runToAndCheckPoints.get(0), new RequestMonitor( |
| getExecutor(), rm) { |
| @Override |
| protected void handleSuccess() { |
| IAddress newPC = new Addr64(dmc.getPC(), 16); |
| |
| boolean doneWithStepping = false; |
| for (IAddress addr : stopPoints) |
| if (newPC.equals(addr)) { |
| // done with the stepping |
| doneWithStepping = true; |
| break; |
| } |
| |
| Breakpoints bpService = getService(Breakpoints.class); |
| if (bpService.findUserBreakpoint(newPC) != null) { |
| // hit a user bp |
| doneWithStepping = true; |
| } |
| |
| if (!doneWithStepping) |
| // -------- Phase 2: run to the "endAddr". |
| // |
| stepAddressRange(dmc, stepIn, newPC, endAddr, rm); // Recursive call |
| else |
| rm.done(); |
| } |
| }); |
| } else { |
| // no RTC points, set temp breakpoints at stopPoints |
| // and run... |
| |
| // Make sure we step over breakpoint at PC (if any) |
| // |
| prepareToRun(dmc, new DataRequestMonitor<Boolean>(getExecutor(), rm) { |
| @Override |
| protected void handleSuccess() { |
| |
| boolean goon = true; |
| |
| Breakpoints bpService = getService(Breakpoints.class); |
| |
| if (getData() == true) { |
| // one instruction has been executed |
| IAddress newPC = new Addr64(dmc.getPC(), 16); |
| |
| if (bpService.findUserBreakpoint(newPC) != null) { |
| // hit a user breakpoint. Stepping finishes. |
| goon = false; |
| } else { |
| // Check if we finish the stepping by |
| // checking the newPC against |
| // our stopPoints instead of checking if |
| // newPC is outside of [startAddr, endAddr) |
| // so that such case would not fail: step |
| // over this address range: |
| // |
| // 0x10000 call ...(a user bp is set here) |
| // 0x10004 ... |
| // 0x1000c ... |
| // |
| // |
| for (IAddress addr : stopPoints) |
| if (newPC.equals(addr)) { |
| goon = false; |
| break; |
| } |
| } |
| } |
| |
| if (goon) { |
| // Now set temp breakpoints at our stop points. |
| // |
| CountingRequestMonitor setTempBpRM = new CountingRequestMonitor(getExecutor(), rm) { |
| @Override |
| protected void handleSuccess() { |
| // we are done setting all temporary |
| // breakpoints |
| dmc.resumeForStepping(rm); |
| } |
| }; |
| |
| setTempBpRM.setDoneCount(stopPoints.size()); |
| |
| for (IAddress addr : stopPoints) { |
| bpService.setTempBreakpoint(dmc, addr, setTempBpRM); |
| } |
| } else { |
| // Stepping finished after prepareToRun(). |
| DMCSuspendedEvent e = dmc.getCachedSuspendedEvent(); |
| if (e != null) |
| getSession().dispatchEvent(e, RunControl.this.getProperties()); |
| rm.done(); |
| } |
| } |
| }); |
| } |
| |
| } |
| }); |
| } |
| |
| /** |
| * step-into one instruction at current PC, namely execute only one |
| * instruction. |
| * |
| * @param dmc |
| * @param rm |
| * - this RequestMonitor is marked done when the execution |
| * finishes and target suspends again. |
| */ |
| private void stepIntoOneInstruction(final ExecutionDMC dmc, final RequestMonitor rm) { |
| |
| prepareToRun(dmc, new DataRequestMonitor<Boolean>(getExecutor(), rm) { |
| @Override |
| protected void handleSuccess() { |
| if (getData() == true /* already executed one instruction */) { |
| // Stepping finished after prepareToRun(). |
| DMCSuspendedEvent e = dmc.getCachedSuspendedEvent(); |
| if (e != null) |
| getSession().dispatchEvent(e, RunControl.this.getProperties()); |
| rm.done(); |
| } |
| else { |
| dmc.singleStep(true, rm); |
| } |
| } |
| }); |
| } |
| |
| public void suspend(IExecutionDMContext context, RequestMonitor requestMonitor) { |
| if (context instanceof ExecutionDMC) { |
| ((ExecutionDMC) context).suspend(requestMonitor); |
| } else { |
| requestMonitor.setStatus(new Status(IStatus.ERROR, EDCDebugger.PLUGIN_ID, INVALID_HANDLE, MessageFormat |
| .format("The context [{0}] is not a recognized execution context.", context), null)); |
| requestMonitor.done(); |
| } |
| } |
| |
| public void getModelData(IDMContext dmc, DataRequestMonitor<?> rm) { |
| rm.done(); |
| } |
| |
| public void flushCache(IDMContext context) { |
| if (isSnapshot()) |
| return; |
| // Flush the Registers cache immediately |
| // For instance the readPCRegister() may get wrong PC value when an |
| // asynchronous suspend event comes too quick after resume. |
| Registers regService = getService(Registers.class); |
| regService.flushCache(context); |
| } |
| |
| @Override |
| public void shutdown(RequestMonitor monitor) { |
| if (tcfRunService != null) { |
| Protocol.invokeLater(new Runnable() { |
| public void run() { |
| tcfRunService.removeListener(runListener); |
| } |
| }); |
| } |
| unregister(); |
| super.shutdown(monitor); |
| } |
| |
| public RootExecutionDMC getRootDMC() { |
| return rootExecutionDMC; |
| } |
| |
| public static class StartedEvent extends AbstractDMEvent<IExecutionDMContext> implements IStartedDMEvent { |
| |
| public StartedEvent(IExecutionDMContext context) { |
| super(context); |
| } |
| } |
| |
| public static class ExitedEvent extends AbstractDMEvent<IExecutionDMContext> implements IExitedDMEvent { |
| private boolean isTerminatedThanDisconnected; |
| |
| public ExitedEvent(IExecutionDMContext context, boolean isTerminatedThanDisconnected) { |
| super(context); |
| this.isTerminatedThanDisconnected = isTerminatedThanDisconnected; |
| } |
| |
| public boolean isTerminatedThanDisconnected() { |
| return isTerminatedThanDisconnected; |
| } |
| } |
| |
| /* |
| * NOTE: |
| * Methods in this listener are invoked in TCF dispatch thread. |
| * When they call into DSF services/objects, make sure it's done in |
| * DSF executor thread so as to avoid possible racing condition. |
| */ |
| private final org.eclipse.tm.tcf.services.IRunControl.RunControlListener runListener = new org.eclipse.tm.tcf.services.IRunControl.RunControlListener() { |
| |
| public void containerResumed(String[] context_ids) { |
| } |
| |
| public void containerSuspended(String context, String pc, String reason, Map<String, Object> params, |
| String[] suspended_ids) { |
| } |
| |
| public void contextAdded(final RunControlContext[] contexts) { |
| getExecutor().execute(new Runnable() { |
| public void run() { |
| for (RunControlContext ctx : contexts) { |
| ExecutionDMC dmc = rootExecutionDMC; |
| String parentID = ctx.getParentID(); |
| if (parentID != null) |
| dmc = dmcsByID.get(parentID); |
| if (dmc != null) { |
| dmc.contextAdded(ctx.getProperties(), ctx); |
| } |
| } |
| } |
| }); |
| } |
| |
| public void contextChanged(RunControlContext[] contexts) { |
| } |
| |
| public void contextException(final String context, final String msg) { |
| getExecutor().execute(new Runnable() { |
| public void run() { |
| ExecutionDMC dmc = getContext(context); |
| if (dmc != null) |
| dmc.contextException(msg); |
| } |
| }); |
| } |
| |
| public void contextRemoved(final String[] context_ids) { |
| getExecutor().execute(new Runnable() { |
| public void run() { |
| for (String contextID : context_ids) { |
| ExecutionDMC dmc = getContext(contextID); |
| assert dmc != null; |
| if (dmc != null) |
| dmc.purgeFromDebugger(); |
| } |
| } |
| }); |
| } |
| |
| public void contextResumed(final String context) { |
| getExecutor().execute(new Runnable() { |
| public void run() { |
| ExecutionDMC dmc = getContext(context); |
| if (dmc != null) |
| dmc.contextResumed(false); |
| } |
| }); |
| } |
| |
| public void contextSuspended(final String context, final String pc, final String reason, |
| final Map<String, Object> params) { |
| getExecutor().execute(new Runnable() { |
| public void run() { |
| ExecutionDMC dmc = getContext(context); |
| if (dmc != null) |
| dmc.contextSuspended(pc, reason, params); |
| else { |
| EDCDebugger.getMessageLogger().logError( |
| MessageFormat.format("Unkown context [{0}] is reported in suspended event. Make sure TCF agent has reported contextAdded event first.", context), |
| null); |
| } |
| } |
| }); |
| } |
| }; |
| |
| public Element takeSnapshot(IAlbum album, Document document, IProgressMonitor monitor)throws Exception { |
| Element contextsElement = document.createElement(EXECUTION_CONTEXTS); |
| ExecutionDMC[] dmcs = rootExecutionDMC.getChildren(); |
| SubMonitor progress = SubMonitor.convert(monitor, dmcs.length * 1000); |
| |
| for (ExecutionDMC executionDMC : dmcs) { |
| Element dmcElement = executionDMC.takeSnapshot(album, document, progress.newChild(1000)); |
| contextsElement.appendChild(dmcElement); |
| } |
| return contextsElement; |
| } |
| |
| public ExecutionDMC getContext(String contextID) { |
| return dmcsByID.get(contextID); |
| } |
| |
| public void loadSnapshot(Element snapshotRoot) throws Exception { |
| NodeList ecElements = snapshotRoot.getElementsByTagName(EXECUTION_CONTEXTS); |
| rootExecutionDMC.resumeAll(); |
| initializeRootExecutionDMC(); |
| rootExecutionDMC.loadSnapshot((Element) ecElements.item(0)); |
| } |
| |
| public void tcfServiceReady(IService service) { |
| if (service instanceof org.eclipse.tm.tcf.services.IRunControl) { |
| tcfRunService = (org.eclipse.tm.tcf.services.IRunControl) service; |
| Protocol.invokeLater(new Runnable() { |
| public void run() { |
| tcfRunService.addListener(runListener); |
| } |
| }); |
| } else |
| assert false; |
| } |
| |
| /** |
| * Stop debugging all execution contexts. This does not kill/terminate |
| * the actual process or thread. |
| * See: {@link #terminateAllContexts(RequestMonitor)} |
| */ |
| private void detachAllContexts(){ |
| getRootDMC().detach(); |
| } |
| |
| /** |
| * Terminate all contexts so as to terminate the debug session. |
| * |
| * @param rm can be null. |
| */ |
| public void terminateAllContexts(final RequestMonitor rm){ |
| |
| CountingRequestMonitor crm = new CountingRequestMonitor(getExecutor(), rm) { |
| @Override |
| protected void handleError() { |
| // failed to terminate at least one process, usually |
| // because connection to target is lost, or some processes |
| // cannot be killed (e.g. OS does not permit that). |
| // Just untarget the contexts. |
| detachAllContexts(); |
| |
| if (rm != null) |
| rm.done(); |
| } |
| |
| }; |
| |
| // It's assumed |
| // 1. First level of children under rootDMC are processes. |
| // 2. Killing them would kill all contexts (processes and threads) being debugged. |
| // |
| ExecutionDMC[] processes = getRootDMC().getChildren(); |
| crm.setDoneCount(processes.length); |
| |
| for (ExecutionDMC e : processes) { |
| e.terminate(crm); |
| } |
| } |
| |
| public void canRunToLine(IExecutionDMContext context, String sourceFile, |
| int lineNumber, final DataRequestMonitor<Boolean> rm) { |
| // I tried to have better filtering as shown in commented code. But that |
| // just made the command fail to be enabled as desired. Not sure about the |
| // exact cause yet, but one problem (from the upper framework) I've seen is |
| // this API is not called whenever user selects a line in source editor (or |
| // disassembly view) and bring up context menu. |
| // Hence we blindly answer yes. The behavior is on par with DSF-GDB. |
| // ................. 03/11/10 |
| rm.setData(true); |
| rm.done(); |
| |
| // // Return true if we can find address(es) for the line in the context. |
| // // |
| // getLineAddress(context, sourceFile, lineNumber, new DataRequestMonitor<List<IAddress>>(getExecutor(), rm){ |
| // @Override |
| // protected void handleCompleted() { |
| // if (! isSuccess()) |
| // rm.setData(false); |
| // else { |
| // rm.setData(getData().size() > 0); |
| // } |
| // rm.done(); |
| // }}); |
| } |
| |
| public void runToLine(final IExecutionDMContext context, String sourceFile, |
| int lineNumber, boolean skipBreakpoints, final RequestMonitor rm) { |
| |
| getLineAddress(context, sourceFile, lineNumber, new DataRequestMonitor<List<IAddress>>(getExecutor(), rm){ |
| @Override |
| protected void handleCompleted() { |
| if (! isSuccess()) { |
| rm.setStatus(getStatus()); |
| rm.done(); |
| } |
| else { |
| runToAddresses(context, getData(), rm); |
| } |
| }}); |
| } |
| |
| private void runToAddresses(IExecutionDMContext context, |
| final List<IAddress> addrs, final RequestMonitor rm) { |
| // 1. Single step over breakpoint, if PC is at a breakpoint. |
| // 2. Set temp breakpoint at the addresses. |
| // 3. Resume the context. |
| // |
| final ExecutionDMC dmc = (ExecutionDMC)context; |
| assert dmc != null; |
| |
| prepareToRun(dmc, new DataRequestMonitor<Boolean>(getExecutor(), rm){ |
| |
| @Override |
| protected void handleCompleted() { |
| if (! isSuccess()) { |
| rm.setStatus(getStatus()); |
| rm.done(); |
| return; |
| } |
| |
| CountingRequestMonitor settingBP_crm = new CountingRequestMonitor(getExecutor(), rm) { |
| @Override |
| protected void handleCompleted() { |
| if (! isSuccess()) { |
| // as long as we fail to set on temp breakpoint, we bail out. |
| rm.setStatus(getStatus()); |
| rm.done(); |
| } |
| else { |
| // all temp breakpoints are successfully set. |
| // Now resume the context. |
| dmc.resume(rm); |
| } |
| }}; |
| |
| settingBP_crm.setDoneCount(addrs.size()); |
| |
| Breakpoints bpService = getService(Breakpoints.class); |
| |
| for (IAddress a : addrs) |
| bpService.setTempBreakpoint(dmc, a, settingBP_crm); |
| }} |
| ); |
| } |
| |
| public void canRunToAddress(IExecutionDMContext context, IAddress address, |
| DataRequestMonitor<Boolean> rm) { |
| // See comment in canRunToLine() for more. |
| rm.setData(true); |
| rm.done(); |
| |
| // // If the address is not in any module of the run context, return false. |
| // Modules moduleService = getService(Modules.class); |
| // |
| // ISymbolDMContext symCtx = DMContexts.getAncestorOfType(context, ISymbolDMContext.class); |
| // |
| // ModuleDMC m = moduleService.getModuleByAddress(symCtx, address); |
| // rm.setData(m == null); |
| // rm.done(); |
| } |
| |
| public void runToAddress(IExecutionDMContext context, IAddress address, |
| boolean skipBreakpoints, RequestMonitor rm) { |
| List<IAddress> addrs = new ArrayList<IAddress>(1); |
| addrs.add(address); |
| runToAddresses(context, addrs, rm); |
| } |
| |
| public void canMoveToLine(IExecutionDMContext context, String sourceFile, |
| int lineNumber, boolean resume, final DataRequestMonitor<Boolean> rm) { |
| // See comment in canRunToLine() for more. |
| rm.setData(true); |
| rm.done(); |
| |
| // Return true if we can find one and only one address for the line in the context. |
| // |
| // getLineAddress(context, sourceFile, lineNumber, new DataRequestMonitor<List<IAddress>>(getExecutor(), rm){ |
| // @Override |
| // protected void handleCompleted() { |
| // if (! isSuccess()) |
| // rm.setData(false); |
| // else { |
| // rm.setData(getData().size() == 1); |
| // } |
| // rm.done(); |
| // }}); |
| } |
| |
| public void moveToLine(final IExecutionDMContext context, String sourceFile, |
| int lineNumber, final boolean resume, final RequestMonitor rm) { |
| getLineAddress(context, sourceFile, lineNumber, new DataRequestMonitor<List<IAddress>>(getExecutor(), rm){ |
| @Override |
| protected void handleCompleted() { |
| if (! isSuccess()) { |
| rm.setStatus(getStatus()); |
| rm.done(); |
| } |
| else { |
| List<IAddress> addrs = getData(); |
| // No, canMoveToLine() does not do sanity check now. |
| // We just move to the first address we found, which may or |
| // may not be the address user wants. Is it better we return |
| // error if "addrs.size() > 1" ? .......03/28/10 |
| // assert addrs.size() == 1; // ensured by canMoveToLine(). |
| moveToAddress(context, addrs.get(0), resume, rm); |
| } |
| }}); |
| } |
| |
| public void canMoveToAddress(IExecutionDMContext context, IAddress address, |
| boolean resume, DataRequestMonitor<Boolean> rm) { |
| // Allow moving to any address. |
| rm.setData(true); |
| rm.done(); |
| } |
| |
| public void moveToAddress(IExecutionDMContext context, IAddress address, |
| boolean resume, RequestMonitor rm) { |
| |
| assert(context instanceof ExecutionDMC); |
| final ExecutionDMC dmc = (ExecutionDMC)context; |
| |
| String newPC = address.toString(16); |
| |
| if (! newPC.equals(dmc.getPC())) { |
| Registers regService = getService(Registers.class); |
| |
| try { |
| // synchronously change PC, so that change occurs before any resume |
| String regID = getTargetEnvironmentService().getPCRegisterID(); |
| RegisterDMC regDMC = regService.findRegisterDMCByName(dmc, regID); |
| assert regDMC != null; |
| |
| regService.writeRegister(regDMC, newPC, IFormattedValues.HEX_FORMAT); |
| } catch (CoreException e) { |
| Status s = new Status(IStatus.ERROR, EDCDebugger.getUniqueIdentifier(), "Error adjusting the PC register", e); |
| EDCDebugger.getMessageLogger().log(s); |
| rm.setStatus(s); |
| rm.done(); |
| return; |
| } |
| |
| // update cached PC. |
| dmc.setPC(newPC); |
| } |
| |
| if (resume) { |
| resume(context, rm); |
| } |
| else if (rm == dmc.getBreakpointActionRM()) { |
| // if resume is false and our request monitor is |
| // THE breakpointActionRM, then the caller (currently |
| // only SkipAction) expects to stop at that bkpt. |
| // but that bkpt may have actions to be executed. |
| Breakpoints bpService = getService(Breakpoints.class); |
| final Breakpoints.BreakpointDMData bp = bpService.findUserBreakpoint(address); |
| if (bp.hasActions()) { |
| bp.executeActions(dmc, dmc.getBreakpointActionRM()); |
| } else |
| rm.done(); |
| } else { |
| rm.done(); |
| } |
| } |
| |
| /** |
| * Get runtime addresses mapped to given source line in given run context. |
| * |
| * @param context |
| * @param sourceFile |
| * @param lineNumber |
| * @param drm holds an empty list if no address found, or the run context is not suspended. |
| */ |
| private void getLineAddress(IExecutionDMContext context, |
| String sourceFile, int lineNumber, DataRequestMonitor<List<IAddress>> drm) { |
| List<IAddress> addrs = new ArrayList<IAddress>(1); |
| |
| ExecutionDMC dmc = (ExecutionDMC) context; |
| if (dmc == null || ! dmc.isSuspended()) { |
| drm.setData(addrs); |
| drm.done(); |
| return; |
| } |
| |
| Modules moduleService = getService(Modules.class); |
| |
| moduleService.getLineAddress(dmc, sourceFile, lineNumber, drm); |
| } |
| |
| /** |
| * Check if this context is non-container. Only non-container context |
| * (thread and bare device context) can have register, stack frames, etc. |
| * |
| * @param dmc |
| * @return |
| */ |
| static public boolean isNonContainer(IDMContext dmc) { |
| return ! (dmc instanceof IContainerDMContext); |
| } |
| |
| public ThreadExecutionDMC[] getSuspendedThreads() |
| { |
| ExecutionDMC[] dmcs = null; |
| List<ThreadExecutionDMC> result = new ArrayList<ThreadExecutionDMC>(); |
| synchronized (dmcsByID) |
| { |
| Collection<ExecutionDMC> allDMCs = dmcsByID.values(); |
| dmcs = allDMCs.toArray(new ExecutionDMC[allDMCs.size()]); |
| } |
| for (ExecutionDMC executionDMC : dmcs) { |
| if (executionDMC instanceof ThreadExecutionDMC && executionDMC.isSuspended()) |
| result.add((ThreadExecutionDMC) executionDMC); |
| } |
| return result.toArray(new ThreadExecutionDMC[result.size()]); |
| } |
| |
| public IAddress getAddressForNextLine(final ExecutionDMC dmc, IAddress pcAddress, |
| IEDCModuleDMContext module, IAddress linkAddress, |
| IModuleLineEntryProvider lineEntryProvider, ILineEntry line, |
| boolean treatAsStepOver) { |
| IAddress endAddr = module.toRuntimeAddress(line.getHighAddress()); |
| |
| // get the next source line entry that has a line # |
| // greater than the current line # (and in the same file), |
| // but is not outside of the function address range |
| // if found, the start addr of that entry is our end |
| // address, otherwise use the existing end address |
| ILineEntry nextLine |
| = lineEntryProvider.getNextLineEntry( |
| lineEntryProvider.getLineEntryAtAddress(linkAddress), |
| treatAsStepOver); |
| if (nextLine != null) { |
| endAddr = module.toRuntimeAddress(nextLine.getLowAddress()); |
| } else { // nextLine == null probably means last line |
| IEDCSymbols symbolsService = getService(Symbols.class); |
| IFunctionScope functionScope |
| = symbolsService.getFunctionAtAddress(dmc.getSymbolDMContext(), pcAddress); |
| if (treatAsStepOver) { |
| while (functionScope != null |
| && functionScope.getParent() instanceof IFunctionScope) { |
| functionScope = (IFunctionScope)functionScope.getParent(); |
| } |
| } |
| if (functionScope != null) |
| endAddr = module.toRuntimeAddress(functionScope.getHighAddress()); |
| } |
| return endAddr; |
| } |
| |
| /** |
| * Utility method for getting a context property using a default value |
| * if missing |
| */ |
| private static boolean getProperty(Map<String, Object> properties, String name, boolean defaultValue) { |
| Boolean b = (Boolean)properties.get(name); |
| return (b == null ? defaultValue : b); |
| } |
| } |