/*******************************************************************************
* Copyright (c) 2006, 2015 Wind River Systems and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Wind River Systems - initial API and implementation
* Ericsson AB - Modified for handling of multiple threads
*******************************************************************************/
package org.eclipse.cdt.examples.dsf.pda.service;
import java.util.Arrays;
import java.util.Hashtable;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.eclipse.cdt.dsf.concurrent.DataRequestMonitor;
import org.eclipse.cdt.dsf.concurrent.IDsfStatusConstants;
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.IDMEvent;
import org.eclipse.cdt.dsf.debug.service.IRunControl;
import org.eclipse.cdt.dsf.debug.service.command.IEventListener;
import org.eclipse.cdt.dsf.service.AbstractDsfService;
import org.eclipse.cdt.dsf.service.DsfServiceEventHandler;
import org.eclipse.cdt.dsf.service.DsfSession;
import org.eclipse.cdt.examples.dsf.pda.PDAPlugin;
import org.eclipse.cdt.examples.dsf.pda.service.commands.AbstractPDACommand;
import org.eclipse.cdt.examples.dsf.pda.service.commands.PDACommandResult;
import org.eclipse.cdt.examples.dsf.pda.service.commands.PDAResumeCommand;
import org.eclipse.cdt.examples.dsf.pda.service.commands.PDAStepCommand;
import org.eclipse.cdt.examples.dsf.pda.service.commands.PDAStepReturnCommand;
import org.eclipse.cdt.examples.dsf.pda.service.commands.PDASuspendCommand;
import org.eclipse.cdt.examples.dsf.pda.service.commands.PDAVMResumeCommand;
import org.eclipse.cdt.examples.dsf.pda.service.commands.PDAVMSuspendCommand;
import org.osgi.framework.BundleContext;
/**
* Service for monitoring and controlling execution state of the DPA
* program.
* <p>
* This service depends on the {@link PDACommandControl} service.
* It must be initialized before this service is initialized.
* </p>
*/
public class PDARunControl extends AbstractDsfService
implements IRunControl, IEventListener
{
// Implementation note about tracking execution state:
// This class implements event handlers for the events that are generated by
// this service itself. When the event is dispatched, these handlers will
// be called first, before any of the clients. These handlers update the
// service's internal state information to make them consistent with the
// events being issued. Doing this in the handlers as opposed to when
// the events are generated, guarantees that the state of the service will
// always be consistent with the events.
// The purpose of this pattern is to allow clients that listen to service
// events and track service state, to be perfectly in sync with the service
// state.
static final private IExecutionDMContext[] EMPTY_TRIGGERING_CONTEXTS_ARRAY = new IExecutionDMContext[0];
@Immutable
private static class ThreadResumedEvent extends AbstractDMEvent<IExecutionDMContext>
implements IResumedDMEvent
{
private final StateChangeReason fReason;
ThreadResumedEvent(PDAThreadDMContext ctx, StateChangeReason reason) {
super(ctx);
fReason = reason;
}
@Override
public StateChangeReason getReason() {
return fReason;
}
@Override
public String toString() {
return "THREAD RESUMED: " + getDMContext() + " (" + fReason + ")";
}
}
@Immutable
private static class VMResumedEvent extends AbstractDMEvent<IExecutionDMContext>
implements IContainerResumedDMEvent
{
private final StateChangeReason fReason;
VMResumedEvent(PDAVirtualMachineDMContext ctx, StateChangeReason reason) {
super(ctx);
fReason = reason;
}
@Override
public StateChangeReason getReason() {
return fReason;
}
@Override
public IExecutionDMContext[] getTriggeringContexts() {
return EMPTY_TRIGGERING_CONTEXTS_ARRAY;
}
@Override
public String toString() {
return "VM RESUMED: (" + fReason + ")";
}
}
@Immutable
private static class ThreadSuspendedEvent extends AbstractDMEvent<IExecutionDMContext>
implements ISuspendedDMEvent
{
private final StateChangeReason fReason;
ThreadSuspendedEvent(PDAThreadDMContext ctx, StateChangeReason reason) {
super(ctx);
fReason = reason;
}
@Override
public StateChangeReason getReason() {
return fReason;
}
@Override
public String toString() {
return "THREAD SUSPENDED: " + getDMContext() + " (" + fReason + ")";
}
}
@Immutable
private static class VMSuspendedEvent extends AbstractDMEvent<IExecutionDMContext>
implements IContainerSuspendedDMEvent
{
private final StateChangeReason fReason;
final private IExecutionDMContext[] fTriggeringThreads;
VMSuspendedEvent(PDAVirtualMachineDMContext ctx, PDAThreadDMContext threadCtx, StateChangeReason reason) {
super(ctx);
fReason = reason;
if (threadCtx != null) {
fTriggeringThreads = new IExecutionDMContext[] { threadCtx };
} else {
fTriggeringThreads = EMPTY_TRIGGERING_CONTEXTS_ARRAY;
}
}
@Override
public StateChangeReason getReason() {
return fReason;
}
@Override
public IExecutionDMContext[] getTriggeringContexts() {
return fTriggeringThreads;
}
@Override
public String toString() {
return "THREAD SUSPENDED: " + getDMContext() +
" (" + fReason +
", trigger = " + Arrays.asList(fTriggeringThreads) +
")";
}
}
@Immutable
private static class ExecutionDMData implements IExecutionDMData {
private final StateChangeReason fReason;
ExecutionDMData(StateChangeReason reason) {
fReason = reason;
}
@Override
public StateChangeReason getStateChangeReason() { return fReason; }
}
private static class ThreadStartedEvent extends AbstractDMEvent<IExecutionDMContext>
implements IStartedDMEvent
{
ThreadStartedEvent(PDAThreadDMContext threadCtx) {
super(threadCtx);
}
}
private static class ThreadExitedEvent extends AbstractDMEvent<IExecutionDMContext>
implements IExitedDMEvent
{
ThreadExitedEvent(PDAThreadDMContext threadCtx) {
super(threadCtx);
}
}
// Services
private PDACommandControl fCommandControl;
// Reference to the virtual machine data model context
private PDAVirtualMachineDMContext fDMContext;
// VM state flags
private boolean fVMSuspended = true;
private boolean fVMResumePending = false;
private boolean fVMSuspendPending = false;
private boolean fVMStepping = false;
private StateChangeReason fVMStateChangeReason = StateChangeReason.UNKNOWN;
// Threads' state data
private static class ThreadInfo {
final PDAThreadDMContext fContext;
boolean fSuspended = false;
boolean fResumePending = false;
boolean fSuspendPending = false;
boolean fStepping = false;
StateChangeReason fStateChangeReason = StateChangeReason.UNKNOWN;
ThreadInfo(PDAThreadDMContext context) {
fContext = context;
}
@Override
public String toString() {
return fContext.toString() + " (" +
(fSuspended ? "SUSPENDED, " : "SUSPENDED, ") +
fStateChangeReason +
(fResumePending ? ", RESUME_PENDING, " : "") +
(fSuspendPending ? ", SUSPEND_PENDING, " : "") +
(fStepping ? ", SUSPEND_PENDING, " : "") +
")";
}
}
private Map<Integer, ThreadInfo> fThreads = new LinkedHashMap<Integer, ThreadInfo>();
public PDARunControl(DsfSession session) {
super(session);
}
@Override
protected BundleContext getBundleContext() {
return PDAPlugin.getBundleContext();
}
@Override
public void initialize(final RequestMonitor rm) {
super.initialize(
new RequestMonitor(getExecutor(), rm) {
@Override
protected void handleSuccess() {
doInitialize(rm);
}});
}
private void doInitialize(final RequestMonitor rm) {
// Cache a reference to the command control and the virtual machine context
fCommandControl = getServicesTracker().getService(PDACommandControl.class);
fDMContext = fCommandControl.getContext();
// Create the main thread context.
fThreads.put(
1,
new ThreadInfo(new PDAThreadDMContext(getSession().getId(), fDMContext, 1)));
// Add the run control service as a listener to PDA events, to catch
// suspended/resumed/started/exited events from the command control.
fCommandControl.addEventListener(this);
// Add the run control service as a listener to service events as well,
// in order to process our own suspended/resumed/started/exited events.
getSession().addServiceEventListener(this, null);
// Register the service with OSGi
register(new String[]{IRunControl.class.getName(), PDARunControl.class.getName()}, new Hashtable<String,String>());
rm.done();
}
@Override
public void shutdown(final RequestMonitor rm) {
fCommandControl.removeEventListener(this);
getSession().removeServiceEventListener(this);
super.shutdown(rm);
}
@Override
public void eventReceived(Object output) {
if (!(output instanceof String)) return;
String event = (String)output;
int nameEnd = event.indexOf(' ');
nameEnd = nameEnd == -1 ? event.length() : nameEnd;
String eventName = event.substring(0, nameEnd);
PDAThreadDMContext thread = null;
StateChangeReason reason = StateChangeReason.UNKNOWN;
if (event.length() > nameEnd + 1) {
if ( Character.isDigit(event.charAt(nameEnd + 1)) ) {
int threadIdEnd = event.indexOf(' ', nameEnd + 1);
threadIdEnd = threadIdEnd == -1 ? event.length() : threadIdEnd;
try {
int threadId = Integer.parseInt(event.substring(nameEnd + 1, threadIdEnd));
if (fThreads.containsKey(threadId)) {
thread = fThreads.get(threadId).fContext;
} else {
// In case where a suspended event follows directly a
// started event, a thread may not be in the list of
// known threads yet. In this case create the
// thread context based on the ID.
thread = new PDAThreadDMContext(getSession().getId(), fDMContext, threadId);
}
} catch (NumberFormatException e) {}
if (threadIdEnd + 1 < event.length()) {
reason = parseStateChangeReason(event.substring(threadIdEnd + 1));
}
} else {
reason = parseStateChangeReason(event.substring(nameEnd + 1));
}
}
// Handle PDA debugger suspended/resumed events and issue the
// corresponding Data Model events. Do not update the state
// information until we start dispatching the service events.
IDMEvent<?> dmEvent = null;
if (eventName.equals("suspended") && thread != null) {
dmEvent = new ThreadSuspendedEvent(thread, reason);
} else if (eventName.equals("resumed") && thread != null) {
dmEvent = new ThreadResumedEvent(thread, reason);
} else if (event.startsWith("vmsuspended")) {
dmEvent = new VMSuspendedEvent(fDMContext, thread, reason);
} else if (event.startsWith("vmresumed")) {
dmEvent = new VMResumedEvent(fDMContext, reason);
} else if (event.startsWith("started") && thread != null) {
dmEvent = new ThreadStartedEvent(thread);
} else if (event.startsWith("exited") && thread != null) {
dmEvent = new ThreadExitedEvent(thread);
}
if (dmEvent != null) {
getSession().dispatchEvent(dmEvent, getProperties());
}
}
private StateChangeReason parseStateChangeReason(String reasonString) {
if (reasonString.startsWith("breakpoint") || reasonString.startsWith("watch")) {
return StateChangeReason.BREAKPOINT;
} else if (reasonString.equals("step") || reasonString.equals("drop")) {
return StateChangeReason.STEP;
} else if (reasonString.equals("client")) {
return StateChangeReason.USER_REQUEST;
} else if (reasonString.startsWith("event")) {
return StateChangeReason.SIGNAL;
} else {
return StateChangeReason.UNKNOWN;
}
}
@DsfServiceEventHandler
public void eventDispatched(ThreadResumedEvent e) {
ThreadInfo info = fThreads.get(((PDAThreadDMContext)e.getDMContext()).getID());
if (info != null) {
info.fSuspended = false;
info.fResumePending = false;
info.fStateChangeReason = e.getReason();
info.fStepping = e.getReason().equals(StateChangeReason.STEP);
}
}
@DsfServiceEventHandler
public void eventDispatched(VMResumedEvent e) {
fVMSuspended = false;
fVMResumePending = false;
fVMStateChangeReason = e.getReason();
fVMStepping = e.getReason().equals(StateChangeReason.STEP);
for (ThreadInfo info : fThreads.values()) {
info.fSuspended = false;
info.fStateChangeReason = StateChangeReason.CONTAINER;
info.fStepping = false;
}
}
@DsfServiceEventHandler
public void eventDispatched(ThreadSuspendedEvent e) {
ThreadInfo info = fThreads.get(((PDAThreadDMContext)e.getDMContext()).getID());
if (info != null) {
info.fSuspended = true;
info.fSuspendPending = false;
info.fStateChangeReason = e.getReason();
info.fStepping = e.getReason().equals(StateChangeReason.STEP);
}
}
@DsfServiceEventHandler
public void eventDispatched(VMSuspendedEvent e) {
fVMStateChangeReason = e.getReason();
fVMSuspendPending = false;
fVMSuspended = true;
fVMStepping = false;
List<IExecutionDMContext> triggeringContexts = Arrays.asList(e.getTriggeringContexts());
for (ThreadInfo info : fThreads.values()) {
info.fSuspended = true;
info.fStateChangeReason = triggeringContexts.contains(info.fContext)
? StateChangeReason.STEP : StateChangeReason.CONTAINER;
info.fStepping = false;
}
}
@DsfServiceEventHandler
public void eventDispatched(ThreadStartedEvent e) {
PDAThreadDMContext threadCtx = (PDAThreadDMContext)e.getDMContext();
fThreads.put(threadCtx.getID(), new ThreadInfo(threadCtx));
}
@DsfServiceEventHandler
public void eventDispatched(ThreadExitedEvent e) {
PDAThreadDMContext threadCtx = (PDAThreadDMContext)e.getDMContext();
fThreads.remove(threadCtx.getID());
}
@Override
public void canResume(IExecutionDMContext context, DataRequestMonitor<Boolean> rm) {
rm.setData(doCanResume(context));
rm.done();
}
private boolean doCanResume(IExecutionDMContext context) {
if (context instanceof PDAThreadDMContext) {
PDAThreadDMContext threadContext = (PDAThreadDMContext)context;
// Threads can be resumed only if the VM is not suspended.
if (!fVMSuspended) {
ThreadInfo state = fThreads.get(threadContext.getID());
if (state != null) {
return state.fSuspended && !state.fResumePending;
}
}
} else {
return fVMSuspended && !fVMResumePending;
}
return false;
}
private boolean doCanStep(IExecutionDMContext context, StepType stepType) {
if (stepType == StepType.STEP_OVER || stepType == StepType.STEP_RETURN) {
if (context instanceof PDAThreadDMContext) {
PDAThreadDMContext threadContext = (PDAThreadDMContext)context;
// Only threads can be stepped. But they can be stepped
// while the VM is suspended or when just the thread is suspended.
ThreadInfo state = fThreads.get(threadContext.getID());
if (state != null) {
return state.fSuspended && !state.fResumePending;
}
}
}
return false;
}
@Override
public void canSuspend(IExecutionDMContext context, DataRequestMonitor<Boolean> rm) {
rm.setData(doCanSuspend(context));
rm.done();
}
private boolean doCanSuspend(IExecutionDMContext context) {
if (context instanceof PDAThreadDMContext) {
PDAThreadDMContext threadContext = (PDAThreadDMContext)context;
// Threads can be resumed only if the VM is not suspended.
if (!fVMSuspended) {
ThreadInfo state = fThreads.get(threadContext.getID());
if (state != null) {
return !state.fSuspended && !state.fSuspendPending;
}
}
} else {
return !fVMSuspended && !fVMSuspendPending;
}
return false;
}
@Override
public boolean isSuspended(IExecutionDMContext context) {
if (context instanceof PDAThreadDMContext) {
PDAThreadDMContext threadContext = (PDAThreadDMContext)context;
// Threads can be resumed only if the VM is not suspended.
if (!fVMSuspended) {
ThreadInfo state = fThreads.get(threadContext.getID());
if (state != null) {
return state.fSuspended;
}
}
}
return fVMSuspended;
}
@Override
public boolean isStepping(IExecutionDMContext context) {
if (!isSuspended(context)) {
if (context instanceof PDAThreadDMContext) {
PDAThreadDMContext threadContext = (PDAThreadDMContext)context;
// Threads can be resumed only if the VM is not suspended.
if (!fVMStepping) {
ThreadInfo state = fThreads.get(threadContext.getID());
if (state != null) {
return state.fStepping;
}
}
}
return fVMStepping;
}
return false;
}
@Override
public void resume(IExecutionDMContext context, final RequestMonitor rm) {
assert context != null;
if (doCanResume(context)) {
if (context instanceof PDAThreadDMContext) {
final PDAThreadDMContext threadCtx = (PDAThreadDMContext)context;
fThreads.get(threadCtx.getID()).fResumePending = true;
fCommandControl.queueCommand(
new PDAResumeCommand(threadCtx),
new DataRequestMonitor<PDACommandResult>(getExecutor(), rm) {
@Override
protected void handleFailure() {
ThreadInfo threadState = fThreads.get(threadCtx.getID());
if (threadState != null) {
threadState.fResumePending = false;
}
super.handleFailure();
}
}
);
} else {
fVMResumePending = true;
fCommandControl.queueCommand(
new PDAVMResumeCommand(fDMContext),
new DataRequestMonitor<PDACommandResult>(getExecutor(), rm) {
@Override
protected void handleFailure() {
fVMResumePending = false;
super.handleFailure();
}
}
);
}
} else {
PDAPlugin.failRequest(rm, INVALID_STATE, "Given context: " + context + ", is already running.");
}
}
@Override
public void suspend(IExecutionDMContext context, final RequestMonitor rm){
assert context != null;
if (doCanSuspend(context)) {
if (context instanceof PDAThreadDMContext) {
final PDAThreadDMContext threadCtx = (PDAThreadDMContext)context;
fThreads.get(threadCtx.getID()).fSuspendPending = true;
fCommandControl.queueCommand(
new PDASuspendCommand(threadCtx),
new DataRequestMonitor<PDACommandResult>(getExecutor(), rm) {
@Override
protected void handleFailure() {
ThreadInfo threadState = fThreads.get(threadCtx.getID());
if (threadState != null) {
threadState.fSuspendPending = false;
}
super.handleFailure();
}
}
);
} else {
fVMSuspendPending = true;
fCommandControl.queueCommand(
new PDAVMSuspendCommand(fDMContext),
new DataRequestMonitor<PDACommandResult>(getExecutor(), rm) {
@Override
protected void handleFailure() {
fVMSuspendPending = false;
super.handleFailure();
}
}
);
}
} else {
PDAPlugin.failRequest(rm, IDsfStatusConstants.INVALID_STATE, "Given context: " + context + ", is already suspended.");
}
}
@Override
public void canStep(IExecutionDMContext context, StepType stepType, DataRequestMonitor<Boolean> rm) {
rm.setData(doCanStep(context, stepType));
rm.done();
}
@Override
public void step(IExecutionDMContext context, StepType stepType, final RequestMonitor rm) {
assert context != null;
if (doCanStep(context, stepType)) {
final PDAThreadDMContext threadCtx = (PDAThreadDMContext)context;
final boolean vmWasSuspneded = fVMSuspended;
if (vmWasSuspneded) {
fVMResumePending = true;
} else {
fThreads.get(threadCtx.getID()).fResumePending = true;
}
AbstractPDACommand<PDACommandResult> stepCommand =
stepType == StepType.STEP_RETURN
? new PDAStepReturnCommand(threadCtx)
: new PDAStepCommand(threadCtx);
fCommandControl.queueCommand(
stepCommand,
new DataRequestMonitor<PDACommandResult>(getExecutor(), rm) {
@Override
protected void handleFailure() {
// If the step command failed, we no longer
// expect to receive a resumed event.
if (vmWasSuspneded) {
fVMResumePending = false;
} else {
ThreadInfo threadState = fThreads.get(threadCtx.getID());
if (threadState != null) {
threadState.fResumePending = false;
}
}
}
});
} else {
PDAPlugin.failRequest(rm, INVALID_STATE, "Cannot resume context");
return;
}
}
@Override
public void getExecutionContexts(final IContainerDMContext containerDmc, final DataRequestMonitor<IExecutionDMContext[]> rm) {
IExecutionDMContext[] threads = new IExecutionDMContext[fThreads.size()];
int i = 0;
for (ThreadInfo info : fThreads.values()) {
threads[i++] = info.fContext;
}
rm.setData(threads);
rm.done();
}
@Override
public void getExecutionData(IExecutionDMContext dmc, DataRequestMonitor<IExecutionDMData> rm) {
if (dmc instanceof PDAThreadDMContext) {
ThreadInfo info = fThreads.get(((PDAThreadDMContext)dmc).getID());
if (info == null) {
PDAPlugin.failRequest(rm, INVALID_HANDLE, "Unknown DMC type");
return;
}
rm.setData( new ExecutionDMData(info.fStateChangeReason) );
} else {
rm.setData( new ExecutionDMData(fVMStateChangeReason) );
}
rm.done();
}
}