| /******************************************************************************* |
| * Copyright (c) 2009, 2010 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.Collections; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| |
| import org.eclipse.cdt.core.IAddress; |
| 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.internal.EDCDebugger; |
| import org.eclipse.cdt.debug.edc.internal.IEDCTraceOptions; |
| 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.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.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.Registers; |
| 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.symbols.IScope; |
| 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.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.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.Status; |
| 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. |
| */ |
| public static final String |
| PROP_PARENT_ID = "ParentID", |
| PROP_IS_CONTAINER = "IsContainer", |
| PROP_HAS_STATE = "HasState", |
| PROP_CAN_RESUME = "CanResume", |
| PROP_CAN_COUNT = "CanCount", |
| PROP_CAN_SUSPEND = "CanSuspend", |
| PROP_CAN_TERMINATE = "CanTerminate", |
| PROP_IS_SUSPENDED = "State", |
| PROP_MESSAGE = "Message", |
| PROP_SUSPEND_PC = "SuspendPC"; |
| |
| // Whether module is being loaded (if true) or unloaded (if false) |
| |
| public static class SuspendedEvent extends AbstractDMEvent<IExecutionDMContext> implements ISuspendedDMEvent { |
| |
| private final StateChangeReason reason; |
| private final Map<String, Object> params; |
| |
| public SuspendedEvent(IExecutionDMContext dmc, StateChangeReason reason, Map<String, Object> params) { |
| super(dmc); |
| EDCDebugger.getDefault().getTrace().traceEntry(IEDCTraceOptions.RUN_CONTROL_TRACE, |
| 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 ResumedEvent extends AbstractDMEvent<IExecutionDMContext> implements IResumedDMEvent { |
| |
| public ResumedEvent(IExecutionDMContext dmc) { |
| super(dmc); |
| } |
| |
| public StateChangeReason getReason() { |
| return StateChangeReason.USER_REQUEST; |
| } |
| } |
| |
| private static StateChangeReason toStateChangeReason(String s) { |
| if (s == null) |
| return StateChangeReason.UNKNOWN; |
| if (s.equals(org.eclipse.tm.tcf.services.IRunControl.REASON_USER_REQUEST)) |
| return StateChangeReason.USER_REQUEST; |
| if (s.equals(org.eclipse.tm.tcf.services.IRunControl.REASON_STEP)) |
| return StateChangeReason.STEP; |
| if (s.equals(org.eclipse.tm.tcf.services.IRunControl.REASON_BREAKPOINT)) |
| return StateChangeReason.BREAKPOINT; |
| if (s.equals(org.eclipse.tm.tcf.services.IRunControl.REASON_EXCEPTION)) |
| return StateChangeReason.EXCEPTION; |
| if (s.equals(org.eclipse.tm.tcf.services.IRunControl.REASON_CONTAINER)) |
| return StateChangeReason.CONTAINER; |
| if (s.equals(org.eclipse.tm.tcf.services.IRunControl.REASON_WATCHPOINT)) |
| return StateChangeReason.WATCHPOINT; |
| if (s.equals(org.eclipse.tm.tcf.services.IRunControl.REASON_SIGNAL)) |
| return StateChangeReason.SIGNAL; |
| if (s.equals(org.eclipse.tm.tcf.services.IRunControl.REASON_SHAREDLIB)) |
| return StateChangeReason.SHAREDLIB; |
| if (s.equals(org.eclipse.tm.tcf.services.IRunControl.REASON_ERROR)) |
| return StateChangeReason.ERROR; |
| return StateChangeReason.UNKNOWN; |
| } |
| |
| @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; |
| private String latestPC = null; |
| private RequestMonitor steppingRM = null; |
| private boolean isStepping = false; |
| |
| public ExecutionDMC(ExecutionDMC parent, Map<String, Object> props, RunControlContext tcfContext) { |
| super(RunControl.this, parent == null ? new IDMContext[0] : new IDMContext[] { parent }, props); |
| EDCDebugger.getDefault().getTrace().traceEntry(IEDCTraceOptions.RUN_CONTROL_TRACE, |
| new Object[] { parent, properties }); |
| this.parentExecutionDMC = parent; |
| this.tcfContext = tcfContext; |
| if (props != null) { |
| dmcsByID.put(getID(), this); |
| } |
| if (parent != null) |
| parent.addChild(this); |
| EDCDebugger.getDefault().getTrace().traceExit(IEDCTraceOptions.RUN_CONTROL_TRACE); |
| } |
| |
| 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 abstract ExecutionDMC contextAdded(Map<String, Object> properties, RunControlContext tcfContext); |
| |
| public abstract boolean canDetach(); |
| |
| public void loadSnapshot(Element element) throws Exception { |
| EDCDebugger.getDefault().getTrace().traceEntry(IEDCTraceOptions.RUN_CONTROL_TRACE, 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); |
| } |
| } |
| |
| } |
| EDCDebugger.getDefault().getTrace().traceExit(IEDCTraceOptions.RUN_CONTROL_TRACE); |
| } |
| |
| public Element takeShapshot(IAlbum album, Document document, IProgressMonitor monitor) { |
| Element contextElement = document.createElement(EXECUTION_CONTEXT); |
| contextElement.setAttribute(PROP_ID, this.getID()); |
| |
| Element propsElement = SnapshotUtils.makeXMLFromProperties(document, getProperties()); |
| contextElement.appendChild(propsElement); |
| |
| ExecutionDMC[] dmcs = getChildren(); |
| |
| for (ExecutionDMC executionDMC : dmcs) { |
| Element dmcElement = executionDMC.takeShapshot(album, document, monitor); |
| contextElement.appendChild(dmcElement); |
| } |
| |
| return contextElement; |
| } |
| |
| public boolean isSuspended() { |
| synchronized (properties) { |
| Boolean suspended = (Boolean) properties.get(PROP_IS_SUSPENDED); |
| if (suspended != null) |
| return suspended; |
| } |
| return false; |
| } |
| |
| public StateChangeReason getStateChangeReason() { |
| return stateChangeReason; |
| } |
| |
| 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); |
| } |
| } |
| |
| public void contextException(String msg) { |
| setIsSuspended(true); |
| synchronized (properties) { |
| properties.put(PROP_MESSAGE, msg); |
| } |
| stateChangeReason = StateChangeReason.EXCEPTION; |
| getSession().dispatchEvent( |
| new SuspendedEvent(this, StateChangeReason.EXCEPTION, new HashMap<String, Object>()), |
| RunControl.this.getProperties()); |
| } |
| |
| public void contextSuspended(String pc, String reason, final Map<String, Object> params) { |
| EDCDebugger.getDefault().getTrace().traceEntry(IEDCTraceOptions.RUN_CONTROL_TRACE, |
| 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 = toStateChangeReason(reason); |
| |
| stateChangeDetails = (String) params.get("message"); |
| |
| if (stateChangeReason == StateChangeReason.SHAREDLIB) { |
| handleModuleEvent(this, params); |
| } else { |
| final IExecutionDMContext dmc = this; |
| |
| preprocessSuspend(pc, new DataRequestMonitor<Boolean>(getExecutor(), null) { |
| @Override |
| protected void handleCompleted() { |
| if (getData()) { // do suspend |
| |
| // Only after completion of adjustPC do we fire the |
| // event. |
| getSession().dispatchEvent(new SuspendedEvent(dmc, stateChangeReason, params), |
| RunControl.this.getProperties()); |
| |
| // All the following must be done in DSF dispatch |
| // thread |
| // to ensure data integrity. |
| |
| // Mark done of the single step RM, if any pending. |
| if (steppingRM != 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 = getServicesTracker().getService(Breakpoints.class); |
| bpService.removeAllTempBreakpoints(new RequestMonitor(getExecutor(), null)); |
| } else { // no suspend, say, due to breakpoint condition |
| // not met. |
| RunControl.this.resume(dmc, new RequestMonitor(getExecutor(), null)); |
| } |
| } |
| }); |
| } |
| EDCDebugger.getDefault().getTrace().traceExit(IEDCTraceOptions.RUN_CONTROL_TRACE); |
| } |
| |
| /** |
| * 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); |
| } |
| }); |
| } |
| |
| /** |
| * Preprocessing for suspend event. This is done before we broadcast the |
| * suspend event across the debugger. Here's what's done in the |
| * preprocessing: <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. |
| * |
| * @param pc |
| * program pointer value from the event, in the format of |
| * big-endian hex string. |
| * @param drm |
| * DataRequestMonitor whose result indicates whether to honor |
| * the suspend. |
| */ |
| private void preprocessSuspend(final String pc, final DataRequestMonitor<Boolean> drm) { |
| final ExecutionDMC dmc = this; |
| |
| // The following needs be done in DSF dispatch thread. |
| getSession().getExecutor().execute(new Runnable() { |
| |
| public void run() { |
| Breakpoints bpService = getServicesTracker().getService(Breakpoints.class); |
| Registers regService = getServicesTracker().getService(Registers.class); |
| String pcString; |
| |
| if (pc == null) { |
| // read PC register |
| pcString = regService.getRegisterValue(dmc, getTargetEnvironmentService().getPCRegisterID()); |
| } else |
| pcString = pc; |
| |
| latestPC = 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. |
| |
| if (stateChangeReason != StateChangeReason.BREAKPOINT) { |
| drm.setData(true); |
| drm.done(); |
| return; |
| } |
| |
| if (!bpService.usesTCFBreakpointService()) { |
| // generic software breakpoint is used. |
| // We need to move PC back to the breakpoint |
| // instruction. |
| long pcValue; |
| |
| pcValue = Long.valueOf(pcString, 16); |
| pcValue -= getTargetEnvironmentService() |
| .getBreakpointInstruction(dmc, new Addr64(pcString, 16)).length; |
| pcString = Long.toHexString(pcValue); |
| |
| // Stopped but not due to breakpoint set by debugger. |
| // For instance, some Windows DLL has "int 3" |
| // instructions in it. |
| // |
| if (bpService.findBreakpoint(new Addr64(pcString, 16)) != null) { |
| // Now adjust PC register. |
| regService.writeRegister(dmc, getTargetEnvironmentService().getPCRegisterID(), pcString); |
| latestPC = pcString; |
| } |
| } |
| |
| // check if a conditional breakpoint (must be a user bp) is |
| // hit |
| // |
| BreakpointDMData bp = bpService.findUserBreakpoint(new Addr64(latestPC, 16)); |
| if (bp != null) { |
| // evaluate the condition |
| bpService.evaluateBreakpointCondition(dmc, bp, drm); |
| } else { |
| drm.setData(true); |
| drm.done(); |
| } |
| } |
| }); |
| } |
| |
| public Boolean canTerminate() { |
| EDCDebugger.getDefault().getTrace().traceEntry(IEDCTraceOptions.RUN_CONTROL_TRACE); |
| Boolean result = false; |
| synchronized (properties) { |
| try { |
| result = (Boolean) properties.get(PROP_CAN_TERMINATE); |
| } catch (Exception e) { |
| EDCDebugger.getDefault().getTrace().trace(IEDCTraceOptions.RUN_CONTROL_TRACE, |
| "Error in canTerminate", e); |
| } |
| } |
| EDCDebugger.getDefault().getTrace().traceExit(IEDCTraceOptions.RUN_CONTROL_TRACE, result); |
| return result; |
| } |
| |
| /** |
| * Resume the context. |
| * |
| * @param rm |
| * this is marked done as long as the resume command |
| * succeeds. |
| */ |
| public boolean supportsStepMode(StepType type) { |
| EDCDebugger.getDefault().getTrace().traceEntry(IEDCTraceOptions.RUN_CONTROL_TRACE, 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; |
| } |
| |
| return tcfContext.canResume(mode); |
| } |
| |
| /** |
| * Resume the context. |
| * |
| * @param rm |
| * this is marked done as long as the resume command |
| * succeeds. |
| */ |
| public void resume(final RequestMonitor rm) { |
| EDCDebugger.getDefault().getTrace().traceEntry(IEDCTraceOptions.RUN_CONTROL_TRACE, this); |
| |
| flushCache(this); |
| |
| // Fire the resumed event here instead of in the doneCommand() below |
| // as otherwise an asynchronous suspend event may get in the way. |
| // |
| contextResumed(true); |
| |
| Protocol.invokeLater(new Runnable() { |
| public void run() { |
| tcfContext.resume(org.eclipse.tm.tcf.services.IRunControl.RM_RESUME, 0, new DoneCommand() { |
| |
| public void doneCommand(IToken token, Exception error) { |
| if (error == null) { |
| EDCDebugger.getDefault().getTrace().trace(IEDCTraceOptions.RUN_CONTROL_TRACE, |
| "Resume command succeeded."); |
| } else { |
| EDCDebugger.getDefault().getTrace().trace(IEDCTraceOptions.RUN_CONTROL_TRACE, |
| "Resume command failed."); |
| rm.setStatus(new Status(IStatus.ERROR, EDCDebugger.PLUGIN_ID, REQUEST_FAILED, |
| "Resume failed.", null)); |
| } |
| rm.done(); |
| } |
| }); |
| } |
| }); |
| EDCDebugger.getDefault().getTrace().traceExit(IEDCTraceOptions.RUN_CONTROL_TRACE); |
| } |
| |
| /** |
| * 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 |
| */ |
| public void resumeForStepping(final RequestMonitor rm) { |
| EDCDebugger.getDefault().getTrace().traceEntry(IEDCTraceOptions.RUN_CONTROL_TRACE, this); |
| |
| setStepping(true); |
| |
| flushCache(this); |
| |
| // Fire the resumed event here instead of in the doneCommand() below |
| // as otherwise an asynchronous suspend event may get in the way. |
| // |
| contextResumed(true); |
| |
| Protocol.invokeLater(new Runnable() { |
| public void run() { |
| tcfContext.resume(org.eclipse.tm.tcf.services.IRunControl.RM_RESUME, 0, new DoneCommand() { |
| |
| public void doneCommand(IToken token, Exception error) { |
| if (error == null) { |
| EDCDebugger.getDefault().getTrace().trace(IEDCTraceOptions.RUN_CONTROL_TRACE, |
| "Resume command succeeded."); |
| // we'll make it as done when we get next |
| // suspend event. |
| assert steppingRM == null; |
| steppingRM = rm; |
| } else { |
| EDCDebugger.getDefault().getTrace().trace(IEDCTraceOptions.RUN_CONTROL_TRACE, |
| "Resume command failed."); |
| rm.setStatus(new Status(IStatus.ERROR, EDCDebugger.PLUGIN_ID, REQUEST_FAILED, |
| "Resume failed.", null)); |
| rm.done(); |
| } |
| } |
| }); |
| } |
| }); |
| EDCDebugger.getDefault().getTrace().traceExit(IEDCTraceOptions.RUN_CONTROL_TRACE); |
| } |
| |
| public void suspend(final RequestMonitor requestMonitor) { |
| EDCDebugger.getDefault().getTrace().traceEntry(IEDCTraceOptions.RUN_CONTROL_TRACE, this); |
| Protocol.invokeLater(new Runnable() { |
| public void run() { |
| tcfContext.suspend(new DoneCommand() { |
| |
| public void doneCommand(IToken token, Exception error) { |
| EDCDebugger.getDefault().getTrace().traceEntry(IEDCTraceOptions.RUN_CONTROL_TRACE, this); |
| requestMonitor.done(); |
| EDCDebugger.getDefault().getTrace().traceExit(IEDCTraceOptions.RUN_CONTROL_TRACE); |
| } |
| }); |
| } |
| }); |
| EDCDebugger.getDefault().getTrace().traceExit(IEDCTraceOptions.RUN_CONTROL_TRACE); |
| } |
| |
| public void terminate(final RequestMonitor requestMonitor) { |
| EDCDebugger.getDefault().getTrace().traceEntry(IEDCTraceOptions.RUN_CONTROL_TRACE, this); |
| if (tcfContext != null) { |
| Protocol.invokeLater(new Runnable() { |
| public void run() { |
| tcfContext.terminate(new DoneCommand() { |
| |
| public void doneCommand(IToken token, Exception error) { |
| EDCDebugger.getDefault().getTrace() |
| .traceEntry(IEDCTraceOptions.RUN_CONTROL_TRACE, this); |
| if (error != null) { |
| requestMonitor.setStatus(new Status(IStatus.ERROR, EDCDebugger.PLUGIN_ID, |
| "terminate() failed.", error)); |
| } |
| |
| requestMonitor.done(); |
| EDCDebugger.getDefault().getTrace().traceExit(IEDCTraceOptions.RUN_CONTROL_TRACE); |
| } |
| }); |
| } |
| }); |
| } else { |
| // Snapshots, for e.g., don't have a TCF RunControlContext, so just remove all the contexts recursively |
| detachAllContexts(); |
| } |
| EDCDebugger.getDefault().getTrace().traceExit(IEDCTraceOptions.RUN_CONTROL_TRACE); |
| } |
| |
| 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 and grand-children. |
| * This is to purge the context from debugger UI and internal storage. |
| */ |
| public void detachFromDebugger(){ |
| EDCDebugger.getDefault().getTrace().traceEntry(IEDCTraceOptions.RUN_CONTROL_TRACE); |
| |
| for (ExecutionDMC e : getChildren()) |
| // recursively forget children first |
| e.detachFromDebugger(); |
| |
| ExecutionDMC parent = getParent(); |
| if (parent != null) |
| parent.removeChild(this); |
| |
| getSession().dispatchEvent(new ExitedEvent(this), 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). |
| getSession().dispatchEvent(new ExitedEvent(getRootDMC()), RunControl.this.getProperties()); |
| |
| EDCDebugger.getDefault().getTrace().traceExit(IEDCTraceOptions.RUN_CONTROL_TRACE); |
| } |
| |
| /** |
| * Recursively marks all execution contexts as resumed |
| * @param dmc |
| */ |
| public void resumeAll(){ |
| contextResumed(true); |
| for (ExecutionDMC e : getChildren()){ |
| e.resumeAll(); |
| } |
| } |
| |
| public void contextResumed(boolean sendEvent) { |
| for (ExecutionDMC e : getChildren()){ |
| e.contextResumed(sendEvent); |
| } |
| EDCDebugger.getDefault().getTrace().traceEntry(IEDCTraceOptions.RUN_CONTROL_TRACE, |
| new Object[] { this, sendEvent }); |
| setIsSuspended(false); |
| if (sendEvent) |
| getSession().dispatchEvent(new ResumedEvent(this), RunControl.this.getProperties()); |
| EDCDebugger.getDefault().getTrace().traceExit(IEDCTraceOptions.RUN_CONTROL_TRACE); |
| } |
| |
| /** |
| * 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 |
| */ |
| public void singleStep(final boolean stepInto, final RequestMonitor rm) { |
| EDCDebugger.getDefault().getTrace().traceEntry(IEDCTraceOptions.RUN_CONTROL_TRACE, this.getName()); |
| |
| setStepping(true); |
| |
| flushCache(this); |
| |
| contextResumed(true); |
| |
| 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; |
| tcfContext.resume(mode, 1, new DoneCommand() { |
| |
| public void doneCommand(IToken token, Exception error) { |
| if (error == null) { |
| EDCDebugger.getDefault().getTrace().trace(IEDCTraceOptions.RUN_CONTROL_TRACE, |
| "Single step command succeeded."); |
| // we'll make it as done when we get next |
| // suspend event. |
| assert steppingRM == null; |
| steppingRM = rm; |
| } else { |
| EDCDebugger.getDefault().getTrace().trace(IEDCTraceOptions.RUN_CONTROL_TRACE, |
| "Single step command failed."); |
| rm.setStatus(new Status(IStatus.ERROR, EDCDebugger.PLUGIN_ID, REQUEST_FAILED, |
| "singleStep() failed.", null)); |
| rm.done(); |
| } |
| } |
| }); |
| } |
| }); |
| EDCDebugger.getDefault().getTrace().traceExit(IEDCTraceOptions.RUN_CONTROL_TRACE); |
| } |
| |
| /** |
| * 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 |
| */ |
| public void stepOut(final RequestMonitor rm) { |
| EDCDebugger.getDefault().getTrace().traceEntry(IEDCTraceOptions.RUN_CONTROL_TRACE, this.getName()); |
| |
| setStepping(true); |
| |
| flushCache(this); |
| |
| contextResumed(true); |
| |
| Protocol.invokeLater(new Runnable() { |
| public void run() { |
| tcfContext.resume(org.eclipse.tm.tcf.services.IRunControl.RM_STEP_OUT, 0, new DoneCommand() { |
| |
| public void doneCommand(IToken token, Exception error) { |
| if (error == null) { |
| EDCDebugger.getDefault().getTrace().trace(IEDCTraceOptions.RUN_CONTROL_TRACE, |
| "Step out command succeeded."); |
| // we'll make it as done when we get next |
| // suspend event. |
| assert steppingRM == null; |
| steppingRM = rm; |
| } else { |
| EDCDebugger.getDefault().getTrace().trace(IEDCTraceOptions.RUN_CONTROL_TRACE, |
| "Step out command failed."); |
| rm.setStatus(new Status(IStatus.ERROR, EDCDebugger.PLUGIN_ID, REQUEST_FAILED, |
| "stepOut() failed.", null)); |
| rm.done(); |
| } |
| } |
| }); |
| } |
| }); |
| EDCDebugger.getDefault().getTrace().traceExit(IEDCTraceOptions.RUN_CONTROL_TRACE); |
| } |
| |
| public void stepRange(final boolean stepInto, final IAddress rangeStart, final IAddress rangeEnd, |
| final RequestMonitor rm) { |
| EDCDebugger.getDefault().getTrace().traceEntry(IEDCTraceOptions.RUN_CONTROL_TRACE, this.getName()); |
| |
| setStepping(true); |
| |
| flushCache(this); |
| |
| contextResumed(true); |
| |
| 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()); |
| |
| tcfContext.resume(mode, 0, params, new DoneCommand() { |
| |
| public void doneCommand(IToken token, Exception error) { |
| if (error == null) { |
| EDCDebugger.getDefault().getTrace().trace(IEDCTraceOptions.RUN_CONTROL_TRACE, |
| "Step range command succeeded."); |
| // we'll make it as done when we get next |
| // suspend event. |
| assert steppingRM == null; |
| steppingRM = rm; |
| } else { |
| EDCDebugger.getDefault().getTrace().trace(IEDCTraceOptions.RUN_CONTROL_TRACE, |
| "Step range command failed."); |
| rm.setStatus(new Status(IStatus.ERROR, EDCDebugger.PLUGIN_ID, REQUEST_FAILED, |
| "stepRange() failed.", null)); |
| rm.done(); |
| } |
| } |
| }); |
| } |
| }); |
| EDCDebugger.getDefault().getTrace().traceExit(IEDCTraceOptions.RUN_CONTROL_TRACE); |
| } |
| |
| /** |
| * 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; |
| } |
| |
| } |
| |
| 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) { |
| EDCDebugger.getDefault().getTrace().traceEntry(IEDCTraceOptions.RUN_CONTROL_TRACE, properties); |
| ThreadExecutionDMC newDMC = new ThreadExecutionDMC(this, properties, tcfContext); |
| getSession().dispatchEvent(new StartedEvent(newDMC), RunControl.this.getProperties()); |
| EDCDebugger.getDefault().getTrace().traceExit(IEDCTraceOptions.RUN_CONTROL_TRACE, 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 = getServicesTracker().getService(Modules.class); |
| modulesService.loadModulesForContext(this, element); |
| super.loadSnapshot(element); |
| } |
| |
| @Override |
| public Element takeShapshot(IAlbum album, Document document, IProgressMonitor monitor) { |
| Element contextElement = super.takeShapshot(album, document, monitor); |
| Element modulesElement = document.createElement(EXECUTION_CONTEXT_MODULES); |
| Modules modulesService = getServicesTracker().getService(Modules.class); |
| |
| IModuleDMContext[] modules = modulesService.getModulesForContext(this.getID()); |
| for (IModuleDMContext moduleContext : modules) { |
| ModuleDMC moduleDMC = (ModuleDMC) moduleContext; |
| modulesElement.appendChild(moduleDMC.takeShapshot(album, document, monitor)); |
| } |
| |
| contextElement.appendChild(modulesElement); |
| return contextElement; |
| } |
| |
| @Override |
| public boolean canDetach() { |
| // can detach from a process. |
| return true; |
| } |
| |
| } |
| |
| public class ThreadExecutionDMC extends ExecutionDMC implements IThreadDMContext, IDisassemblyDMContext { |
| |
| public ThreadExecutionDMC(ExecutionDMC parent, Map<String, Object> properties, RunControlContext tcfContext) { |
| super(parent, properties, tcfContext); |
| EDCDebugger.getDefault().getTrace().traceEntry(IEDCTraceOptions.RUN_CONTROL_TRACE, |
| new Object[] { parent, properties }); |
| ; |
| EDCDebugger.getDefault().getTrace().traceExit(IEDCTraceOptions.RUN_CONTROL_TRACE); |
| } |
| |
| public ISymbolDMContext getSymbolDMContext() { |
| return DMContexts.getAncestorOfType(this, ISymbolDMContext.class); |
| } |
| |
| @Override |
| public void loadSnapshot(Element element) throws Exception { |
| super.loadSnapshot(element); |
| Registers regService = getServicesTracker().getService(Registers.class); |
| regService.loadGroupsForContext(this, element); |
| |
| Stack stackService = getServicesTracker().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( |
| new SuspendedEvent(this, StateChangeReason.EXCEPTION, new HashMap<String, Object>()), |
| RunControl.this.getProperties()); |
| |
| } |
| |
| @Override |
| public Element takeShapshot(IAlbum album, Document document, IProgressMonitor monitor) { |
| Element contextElement = super.takeShapshot(album, document, monitor); |
| Element registersElement = document.createElement(EXECUTION_CONTEXT_REGISTERS); |
| Registers regService = getServicesTracker().getService(Registers.class); |
| |
| IRegisterGroupDMContext[] regGroups = regService.getGroupsForContext(this); |
| for (IRegisterGroupDMContext registerGroupDMContext : regGroups) { |
| RegisterGroupDMC regDMC = (RegisterGroupDMC) registerGroupDMContext; |
| registersElement.appendChild(regDMC.takeShapshot(album, document, monitor)); |
| } |
| |
| contextElement.appendChild(registersElement); |
| |
| Element framesElement = document.createElement(EXECUTION_CONTEXT_FRAMES); |
| Stack stackService = getServicesTracker().getService(Stack.class); |
| Expressions expressionsService = getServicesTracker().getService(Expressions.class); |
| |
| IFrameDMContext[] frames = stackService.getFramesForDMC(this, 0, IStack.ALL_FRAMES); |
| for (IFrameDMContext frameDMContext : frames) { |
| StackFrameDMC frameDMC = (StackFrameDMC) frameDMContext; |
| |
| // Get the local variables for each frame |
| IVariableDMContext[] variables = frameDMC.getLocals(); |
| 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); |
| } |
| |
| framesElement.appendChild(frameDMC.takeShapshot(album, document, monitor)); |
| } |
| |
| 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; |
| } |
| |
| } |
| |
| /** |
| * 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 !(Boolean)properties.get(PROP_IS_CONTAINER); |
| } |
| |
| @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) { |
| Boolean isContainer = (Boolean)(properties.get(PROP_IS_CONTAINER)); |
| 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 (isContainer == null || Boolean.TRUE.equals(isContainer)) |
| 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; |
| } |
| } |
| |
| 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).isSuspended() ? Boolean.TRUE : Boolean.FALSE); |
| rm.done(); |
| } |
| |
| public void canSuspend(IExecutionDMContext context, DataRequestMonitor<Boolean> rm) { |
| 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; |
| rm.setData(new ExecutionData(exedmc.isSuspended() ? exedmc.getStateChangeReason() |
| : StateChangeReason.UNKNOWN, exedmc.getStateChangeDetails())); |
| } 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; |
| } |
| |
| public void resume(IExecutionDMContext context, final RequestMonitor rm) { |
| EDCDebugger.getDefault().getTrace().traceEntry(IEDCTraceOptions.RUN_CONTROL_TRACE, |
| 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; |
| |
| final Breakpoints bpService = getServicesTracker().getService(Breakpoints.class); |
| if (bpService.usesTCFBreakpointService()) { |
| dmc.resume(rm); |
| EDCDebugger.getDefault().getTrace().traceExit(IEDCTraceOptions.RUN_CONTROL_TRACE, |
| MessageFormat.format("resume() done on context {0}", dmc)); |
| } else { |
| prepareToRun(dmc, new DataRequestMonitor<Boolean>(getExecutor(), rm) { |
| |
| @Override |
| protected void handleSuccess() { |
| dmc.resume(rm); |
| EDCDebugger.getDefault().getTrace().traceExit(IEDCTraceOptions.RUN_CONTROL_TRACE, |
| 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 is breakpoint at current PC, remove it => Single step => |
| // Restore it. |
| |
| String latestPC = dmc.getPC(); |
| |
| if (latestPC != null) { |
| final Breakpoints bpService = getServicesTracker().getService(Breakpoints.class); |
| 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.contextResumed(false); |
| dmc.singleStep(true, new RequestMonitor(getExecutor(), drm) { |
| @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(IExecutionDMContext context, StepType stepType, final RequestMonitor rm) { |
| EDCDebugger.getDefault().getTrace().traceEntry(IEDCTraceOptions.RUN_CONTROL_TRACE, |
| 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); |
| |
| 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 = getServicesTracker().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(); |
| 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 = 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 |
| // Note: Only do this if Step Over, if Step Into we use |
| // the endAddr we already have, so if are stepping into inline |
| // functions, this will work |
| if (stepType == StepType.STEP_OVER) { |
| ILineEntry nextLine = lineEntryProvider.getNextLineEntry(line); |
| if (nextLine != null) { |
| endAddr = module.toRuntimeAddress(nextLine.getLowAddress()); |
| } |
| } |
| |
| /* |
| * It's possible that PC is larger than startAddr |
| * (e.g. user does a few instruction level stepping |
| * then switch to source level stepping; or when we |
| * just step out a function). We just parse and |
| * stepping instructions within [pcAddr, endAddr) |
| * instead of all those within [startAddr, endAddr). |
| * One possible problem with the solution is when |
| * control jumps from within [pcAddress, endAddr) to |
| * somewhere within [startAddr, pcAddress), the |
| * stepping would stop at somewhere within |
| * [startAddr, pcAddress) 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 |
| */ |
| stepAddressRange(dmc, stepType == StepType.STEP_INTO, pcAddress, endAddr, rm); |
| |
| EDCDebugger.getDefault().getTrace().traceExit(IEDCTraceOptions.RUN_CONTROL_TRACE, |
| "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) |
| stepIntoOneInstruction(dmc, rm); |
| else if (stepType == StepType.INSTRUCTION_STEP_RETURN) |
| stepOut(dmc, pcAddress, rm); |
| |
| EDCDebugger.getDefault().getTrace().traceExit(IEDCTraceOptions.RUN_CONTROL_TRACE); |
| } |
| |
| private void stepOut(final ExecutionDMC dmc, IAddress pcAddress, final RequestMonitor rm) { |
| |
| EDCDebugger.getDefault().getTrace().traceEntry(IEDCTraceOptions.RUN_CONTROL_TRACE, |
| "Step out from address " + pcAddress.toHexAddressString()); |
| |
| if (dmc.supportsStepMode(StepType.STEP_RETURN)) { |
| dmc.stepOut(rm); |
| return; |
| } |
| |
| Stack stackService = getServicesTracker().getService(Stack.class); |
| IFrameDMContext[] frames = stackService.getFramesForDMC(dmc, 0, 1); |
| 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]).getIPAddress(); |
| |
| |
| boolean keepgoing = true; |
| if (!keepgoing) { |
| rm.setStatus(new Status(IStatus.ERROR, EDCDebugger.PLUGIN_ID, REQUEST_FAILED, |
| "Cannot step out as no caller frame is available.", null)); |
| rm.done(); |
| return; |
| } |
| |
| final Breakpoints bpService = getServicesTracker().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 |
| 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 = getServicesTracker().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 = getServicesTracker().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; |
| // Check to see if we are in an inlined function |
| StackFrameDMC currentFrame = ((StackFrameDMC) frames[0]); |
| IEDCSymbols symbolsService = getServicesTracker().getService(Symbols.class); |
| IFunctionScope functionScope = symbolsService |
| .getFunctionAtAddress(dmc.getSymbolDMContext(), currentFrame.getIPAddress()); |
| |
| if (functionScope != null) |
| { |
| IScope parentScope = functionScope.getParent(); |
| if (parentScope instanceof IFunctionScope && currentFrame.getModule() != null) |
| { |
| if (!currentFrame.getModule().toRuntimeAddress(functionScope.getLowAddress()).equals(currentFrame.getIPAddress())) |
| { |
| stepAddressRange(dmc, false, currentFrame.getIPAddress(), functionScope.getHighAddress(), new RequestMonitor(getExecutor(), rm){ |
| |
| @Override |
| protected void handleSuccess() { |
| step(dmc, StepType.STEP_OVER, new RequestMonitor(getExecutor(), new RequestMonitor(getExecutor(), rm))); |
| }}); |
| 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) { |
| |
| EDCDebugger.getDefault().getTrace().traceEntry(IEDCTraceOptions.RUN_CONTROL_TRACE, |
| "address " + pcAddress.toHexAddressString()); |
| |
| if (dmc.supportsStepMode(StepType.INSTRUCTION_STEP_OVER)) { |
| dmc.singleStep(false, rm); |
| return; |
| } |
| |
| final IDisassembler disassembler = getTargetEnvironmentService().getDisassembler(); |
| if (disassembler == null) { |
| rm.setStatus(new Status(IStatus.ERROR, EDCDebugger.PLUGIN_ID, REQUEST_FAILED, |
| "No disassembler is available yet.", null)); |
| rm.done(); |
| return; |
| } |
| |
| Memory memoryService = getServicesTracker().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. |
| int maxInstLength = getTargetEnvironmentService().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() { |
| MemoryByte[] data = getData(); |
| final byte[] bytes = new byte[data.length]; |
| for (int i = 0; i < data.length; i++) |
| bytes[i] = data[i].getValue(); |
| |
| ByteBuffer codeBuf = ByteBuffer.wrap(bytes); |
| |
| IDisassembledInstruction inst; |
| |
| Map<String, Object> options = new HashMap<String, Object>(); |
| try { |
| inst = disassembler.disassembleOneInstruction(pcAddress, codeBuf, options); |
| } 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 = getServicesTracker().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) { |
| |
| EDCDebugger.getDefault().getTrace().traceEntry( |
| IEDCTraceOptions.RUN_CONTROL_TRACE, |
| MessageFormat.format("address range [{0},{1})", startAddr.toHexAddressString(), endAddr |
| .toHexAddressString())); |
| |
| if (dmc.supportsStepMode(stepIn ? StepType.STEP_INTO : StepType.STEP_OVER)) { |
| dmc.stepRange(stepIn, startAddr, endAddr, rm); |
| return; |
| } |
| |
| final IDisassembler disassembler = getTargetEnvironmentService().getDisassembler(); |
| if (disassembler == null) { |
| rm.setStatus(new Status(IStatus.ERROR, EDCDebugger.PLUGIN_ID, REQUEST_FAILED, |
| "No disassembler is available yet.", null)); |
| rm.done(); |
| return; |
| } |
| |
| final Memory memoryService = getServicesTracker().getService(Memory.class); |
| IMemoryDMContext mem_dmc = DMContexts.getAncestorOfType(dmc, IMemoryDMContext.class); |
| |
| int memSize = startAddr.distanceTo(endAddr).intValue(); |
| |
| 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() { |
| MemoryByte[] data = getData(); |
| final byte[] bytes = new byte[data.length]; |
| for (int i = 0; i < data.length; i++) |
| bytes[i] = data[i].getValue(); |
| |
| ByteBuffer codeBuf = ByteBuffer.wrap(bytes); |
| |
| List<IDisassembledInstruction> instList; |
| |
| Map<String, Object> options = new HashMap<String, Object>(); |
| try { |
| instList = disassembler.disassembleInstructions(startAddr, endAddr, codeBuf, options); |
| } 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>(); |
| |
| for (IDisassembledInstruction inst : instList) { |
| final IAddress instAddr = inst.getAddress(); |
| |
| IJumpToAddress jta = inst.getJumpToAddress(); |
| if (jta == null) |
| 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)) { |
| // 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; |
| } else { // others |
| // evaluate the address expression |
| |
| if (!jta.isSubroutineAddress() || stepIn) |
| { |
| 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 = getServicesTracker().getService(Registers.class); |
| |
| IAddress addr; |
| try { |
| addr = evaluator.evaluate(dmc, expr, regService, memoryService); |
| } catch (CoreException e) { |
| rm.setStatus(e.getStatus()); |
| rm.done(); |
| return; |
| } |
| stopPoints.add(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) |
| */ |
| 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.add(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)) { |
| stopPoints.add(jumpAddress); |
| } |
| } |
| } else { |
| // conditional jump |
| // ignore jump within the address range |
| if (!(startAddr.compareTo(jumpAddress) <= 0 && jumpAddress.compareTo(endAddr) < 0)) |
| { |
| stopPoints.add(jumpAddress); |
| } |
| } |
| } |
| } // end of parsing instructions |
| |
| // need a temp breakpoint at the "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... |
| * |
| * There is no way we can solve the problem. Good thing |
| * is, in practice is the case even possible ? |
| */ |
| // Log (and show it, get rid of the "show" part after |
| // tons of test) 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)) { |
| doneWithStepping = true; // done with the |
| // stepping |
| break; |
| } |
| |
| Breakpoints bpService = getServicesTracker().getService(Breakpoints.class); |
| if (bpService.findUserBreakpoint(newPC) != null) { // hit |
| // a |
| // user |
| // breakpoint |
| 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 = getServicesTracker().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 breakpoint 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 |
| 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) { |
| |
| // TODO what about protocols that supports stepping past breakpoints |
| // like TRK? |
| |
| prepareToRun(dmc, new DataRequestMonitor<Boolean>(getExecutor(), rm) { |
| @Override |
| protected void handleSuccess() { |
| if (getData() == true /* already executed one instruction */) |
| // The "step" is over |
| rm.done(); |
| else { |
| dmc.setStepping(true); |
| 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 = getServicesTracker().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 { |
| |
| public ExitedEvent(IExecutionDMContext context) { |
| super(context); |
| } |
| |
| } |
| |
| 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(RunControlContext[] contexts) { |
| 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(String context, String msg) { |
| ExecutionDMC dmc = getContext(context); |
| dmc.contextException(msg); |
| } |
| |
| public void contextRemoved(String[] context_ids) { |
| for (String contextID : context_ids) { |
| ExecutionDMC dmc = getContext(contextID); |
| assert dmc != null; |
| if (dmc != null) |
| dmc.detachFromDebugger(); |
| } |
| } |
| |
| public void contextResumed(String context) { |
| ExecutionDMC dmc = getContext(context); |
| dmc.contextResumed(true); |
| } |
| |
| public void contextSuspended(final String context, final String pc, final String reason, |
| final Map<String, Object> params) { |
| 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 takeShapshot(IAlbum album, Document document, IProgressMonitor monitor) { |
| Element contextsElement = document.createElement(EXECUTION_CONTEXTS); |
| |
| ExecutionDMC[] dmcs = rootExecutionDMC.getChildren(); |
| |
| for (ExecutionDMC executionDMC : dmcs) { |
| Element dmcElement = executionDMC.takeShapshot(album, document, monitor); |
| 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().detachFromDebugger(); |
| } |
| |
| /** |
| * 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 make 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 in 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 = getServicesTracker().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 = getServicesTracker().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) { |
| |
| Registers regService = getServicesTracker().getService(Registers.class); |
| |
| assert(context instanceof ExecutionDMC); |
| ExecutionDMC dmc = (ExecutionDMC)context; |
| |
| String newPC = address.toString(16); |
| |
| if (! newPC.equals(dmc.getPC())) { |
| // Hmm, this interface should report status. |
| regService.writeRegister(dmc, getTargetEnvironmentService().getPCRegisterID(), newPC); |
| |
| // udpate cached PC. |
| dmc.setPC(newPC); |
| } |
| |
| if (resume) |
| resume(context, rm); |
| else { |
| // fire a suspendEvent so that PC arrow can be updated in UI. |
| getSession().dispatchEvent( |
| new SuspendedEvent(context, StateChangeReason.USER_REQUEST, new HashMap<String, Object>()), |
| RunControl.this.getProperties()); |
| |
| 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 = getServicesTracker().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); |
| } |
| } |