/*******************************************************************************
* Copyright (c) 2007, 2009 Ericsson 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:
* Ericsson - Initial API and implementation
*******************************************************************************/
package org.eclipse.cdt.examples.dsf.pda.service;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Map;
import java.util.Set;
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.AbstractDMContext;
import org.eclipse.cdt.dsf.datamodel.DMContexts;
import org.eclipse.cdt.dsf.datamodel.IDMContext;
import org.eclipse.cdt.dsf.debug.service.IBreakpoints;
import org.eclipse.cdt.dsf.service.AbstractDsfService;
import org.eclipse.cdt.dsf.service.DsfSession;
import org.eclipse.cdt.examples.dsf.pda.PDAPlugin;
import org.eclipse.cdt.examples.dsf.pda.breakpoints.PDAWatchpoint;
import org.eclipse.cdt.examples.dsf.pda.service.commands.PDAClearBreakpointCommand;
import org.eclipse.cdt.examples.dsf.pda.service.commands.PDACommandResult;
import org.eclipse.cdt.examples.dsf.pda.service.commands.PDASetBreakpointCommand;
import org.eclipse.cdt.examples.dsf.pda.service.commands.PDAWatchCommand;
import org.eclipse.core.resources.IMarker;
import org.eclipse.debug.core.model.IBreakpoint;
import org.osgi.framework.BundleContext;
/**
* Initial breakpoint service implementation.
* Implements the IBreakpoints interface.
*/
public class PDABreakpoints extends AbstractDsfService implements IBreakpoints
{
/**
* Context representing a PDA line breakpoint. In PDA debugger, since there is only
* one file being debugged at a time, a breakpoint is uniquely identified using the
* line number only.
*/
@Immutable
private static class BreakpointDMContext extends AbstractDMContext implements IBreakpointDMContext {
final Integer fLine;
public BreakpointDMContext(String sessionId, PDAVirtualMachineDMContext commandControlCtx, Integer line) {
super(sessionId, new IDMContext[] { commandControlCtx });
fLine = line;
}
@Override
public boolean equals(Object obj) {
return baseEquals(obj) && (fLine.equals(((BreakpointDMContext) obj).fLine));
}
@Override
public int hashCode() {
return baseHashCode() + fLine.hashCode();
}
@Override
public String toString() {
return baseToString() + ".breakpoint(" + fLine + ")"; //$NON-NLS-1$//$NON-NLS-2$*/
}
}
/**
* Context representing a watch point. In PDA debugger, a watchpoint is
* uniquely identified using the function and variable.
*/
@Immutable
private static class WatchpointDMContext extends AbstractDMContext implements IBreakpointDMContext {
final String fFunction;
final String fVariable;
public WatchpointDMContext(String sessionId, PDAVirtualMachineDMContext commandControlCtx, String function,
String variable)
{
super(sessionId, new IDMContext[] { commandControlCtx });
fFunction = function;
fVariable = variable;
}
@Override
public boolean equals(Object obj) {
if (baseEquals(obj)) {
WatchpointDMContext watchpointCtx = (WatchpointDMContext)obj;
return fFunction.equals(watchpointCtx.fFunction) && fVariable.equals(watchpointCtx.fVariable);
}
return false;
}
@Override
public int hashCode() {
return baseHashCode() + fFunction.hashCode() + fVariable.hashCode();
}
@Override
public String toString() {
return baseToString() + ".watchpoint(" + fFunction + "::" + fVariable + ")";
}
}
// Attribute names
public static final String ATTR_BREAKPOINT_TYPE = PDAPlugin.PLUGIN_ID + ".pdaBreakpointType"; //$NON-NLS-1$
public static final String PDA_LINE_BREAKPOINT = "breakpoint"; //$NON-NLS-1$
public static final String PDA_WATCHPOINT = "watchpoint"; //$NON-NLS-1$
public static final String ATTR_PROGRAM_PATH = PDAPlugin.PLUGIN_ID + ".pdaProgramPath"; //$NON-NLS-1$
// Services
private PDACommandControl fCommandControl;
// Breakpoints currently installed
private Set<IBreakpointDMContext> fBreakpoints = new HashSet<IBreakpointDMContext>();
/**
* The service constructor
*
* @param session The debugging session this service belongs to.
*/
public PDABreakpoints(DsfSession session) {
super(session);
}
@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) {
// Get the services references
fCommandControl = getServicesTracker().getService(PDACommandControl.class);
// Register this service
register(new String[] { IBreakpoints.class.getName(), PDABreakpoints.class.getName() },
new Hashtable<String, String>());
rm.done();
}
@Override
public void shutdown(final RequestMonitor rm) {
unregister();
rm.done();
}
@Override
protected BundleContext getBundleContext() {
return PDAPlugin.getBundleContext();
}
public void getBreakpoints(final IBreakpointsTargetDMContext context, final DataRequestMonitor<IBreakpointDMContext[]> rm) {
// Validate the context
if (!fCommandControl.getContext().equals(context)) {
PDAPlugin.failRequest(rm, INVALID_HANDLE, "Invalid breakpoints target context");
return;
}
rm.setData(fBreakpoints.toArray(new IBreakpointDMContext[fBreakpoints.size()]));
rm.done();
}
public void getBreakpointDMData(IBreakpointDMContext dmc, DataRequestMonitor<IBreakpointDMData> rm) {
PDAPlugin.failRequest(rm, NOT_SUPPORTED, "Retrieving breakpoint data is not supported");
}
public void insertBreakpoint(IBreakpointsTargetDMContext context, Map<String, Object> attributes,
DataRequestMonitor<IBreakpointDMContext> rm)
{
Boolean enabled = (Boolean)attributes.get(IBreakpoint.ENABLED);
if (enabled != null && !enabled.booleanValue()) {
// If the breakpoint is disabled, just fail the request.
PDAPlugin.failRequest(rm, REQUEST_FAILED, "Breakpoint is disabled");
} else {
String type = (String) attributes.get(ATTR_BREAKPOINT_TYPE);
if (PDA_LINE_BREAKPOINT.equals(type)) {
// Retrieve the PDA program context from the context given in the
// argument. This service is typically only called by the
// breakpoints mediator, which was called with the program context
// in the services initialization sequence. So checking if
// programCtx != null is mostly a formality.
PDAVirtualMachineDMContext programCtx = DMContexts.getAncestorOfType(context, PDAVirtualMachineDMContext.class);
if (programCtx != null) {
doInsertBreakpoint(programCtx, attributes, rm);
} else {
PDAPlugin.failRequest(rm, INVALID_HANDLE, "Unknown breakpoint type");
}
}
else if (PDA_WATCHPOINT.equals(type)) {
doInsertWatchpoint(attributes, rm);
}
else {
PDAPlugin.failRequest(rm, REQUEST_FAILED, "Unknown breakpoint type");
}
}
}
private void doInsertBreakpoint(PDAVirtualMachineDMContext programCtx, final Map<String, Object> attributes, final DataRequestMonitor<IBreakpointDMContext> rm)
{
// Compare the program path in the breakpoint with the path in the PDA
// program context. Only insert the breakpoint if the program matches.
String program = (String)attributes.get(ATTR_PROGRAM_PATH);
if (!programCtx.getProgram().equals(program)) {
PDAPlugin.failRequest(rm, REQUEST_FAILED, "Invalid file name");
return;
}
// Retrieve the line.
Integer line = (Integer)attributes.get(IMarker.LINE_NUMBER);
if (line == null) {
PDAPlugin.failRequest(rm, REQUEST_FAILED, "No breakpoint line specified");
return;
}
// Create a new breakpoint context object and check that it's not
// installed already. PDA can only track a single breakpoint at a
// given line, attempting to set the second breakpoint should fail.
final BreakpointDMContext breakpointCtx =
new BreakpointDMContext(getSession().getId(), fCommandControl.getContext(), line);
if (fBreakpoints.contains(breakpointCtx)) {
PDAPlugin.failRequest(rm, REQUEST_FAILED, "Breakpoint already set");
return;
}
// Add the new breakpoint context to the list of known breakpoints.
// Adding it here, before the set command is completed will prevent
// a possibility of a second breakpoint being installed in the same
// location while this breakpoint is being processed. It will also
// allow the breakpoint to be removed or updated even while it is
// still being processed here.
fBreakpoints.add(breakpointCtx);
fCommandControl.queueCommand(
new PDASetBreakpointCommand(fCommandControl.getContext(), line, false),
new DataRequestMonitor<PDACommandResult>(getExecutor(), rm) {
@Override
protected void handleSuccess() {
rm.setData(breakpointCtx);
rm.done();
}
@Override
protected void handleFailure() {
// If inserting of the breakpoint failed, remove it from
// the set of installed breakpoints.
fBreakpoints.remove(breakpointCtx);
super.handleFailure();
}
});
}
private void doInsertWatchpoint(final Map<String, Object> attributes, final DataRequestMonitor<IBreakpointDMContext> rm)
{
String function = (String)attributes.get(PDAWatchpoint.FUNCTION_NAME);
if (function == null) {
PDAPlugin.failRequest(rm, REQUEST_FAILED, "No function specified");
return;
}
String variable = (String)attributes.get(PDAWatchpoint.VAR_NAME);
if (variable == null) {
PDAPlugin.failRequest(rm, REQUEST_FAILED, "No variable specified");
return;
}
Boolean isAccess = (Boolean)attributes.get(PDAWatchpoint.ACCESS);
isAccess = isAccess != null ? isAccess : Boolean.FALSE;
Boolean isModification = (Boolean)attributes.get(PDAWatchpoint.MODIFICATION);
isModification = isModification != null ? isModification : Boolean.FALSE;
// Create a new watchpoint context object and check that it's not
// installed already. PDA can only track a single watchpoint for a given
// function::variable, attempting to set the second breakpoint should fail.
final WatchpointDMContext watchpointCtx =
new WatchpointDMContext(getSession().getId(), fCommandControl.getContext(), function, variable);
if (fBreakpoints.contains(watchpointCtx)) {
PDAPlugin.failRequest(rm, REQUEST_FAILED, "Watchpoint already set");
return;
}
// Determine the watch operation to perform.
PDAWatchCommand.WatchOperation watchOperation = PDAWatchCommand.WatchOperation.NONE;
if (isAccess && isModification) {
watchOperation = PDAWatchCommand.WatchOperation.BOTH;
} else if (isAccess) {
watchOperation = PDAWatchCommand.WatchOperation.READ;
} else if (isModification) {
watchOperation = PDAWatchCommand.WatchOperation.WRITE;
}
// Add the new breakpoint context to the list of known breakpoints.
// Adding it here, before the set command is completed will prevent
// a possibility of a second breakpoint being installed in the same
// location while this breakpoint is being processed. It will also
// allow the breakpoint to be removed or updated even while it is
// still being processed here.
fBreakpoints.add(watchpointCtx);
fCommandControl.queueCommand(
new PDAWatchCommand(fCommandControl.getContext(), function, variable, watchOperation),
new DataRequestMonitor<PDACommandResult>(getExecutor(), rm) {
@Override
protected void handleSuccess() {
rm.setData(watchpointCtx);
rm.done();
}
@Override
protected void handleFailure() {
// Since the command failed, we need to remove the breakpoint from
// the existing breakpoint set.
fBreakpoints.remove(watchpointCtx);
super.handleFailure();
}
});
}
public void removeBreakpoint(IBreakpointDMContext bpCtx, RequestMonitor rm) {
if (!fBreakpoints.contains(bpCtx)) {
PDAPlugin.failRequest(rm, REQUEST_FAILED, "Breakpoint already removed");
return;
}
if (bpCtx instanceof BreakpointDMContext) {
doRemoveBreakpoint((BreakpointDMContext)bpCtx, rm);
} else if (bpCtx instanceof WatchpointDMContext) {
doRemoveWatchpoint((WatchpointDMContext)bpCtx, rm);
} else {
PDAPlugin.failRequest(rm, INVALID_HANDLE, "Invalid breakpoint");
}
}
private void doRemoveBreakpoint(BreakpointDMContext bpCtx, RequestMonitor rm) {
// Remove the breakpoint from the table right away, so that even when
// the remove is being processed, a new breakpoint can be created at the same
// location.
fBreakpoints.remove(bpCtx);
fCommandControl.queueCommand(
new PDAClearBreakpointCommand(fCommandControl.getContext(), bpCtx.fLine),
new DataRequestMonitor<PDACommandResult>(getExecutor(), rm));
}
private void doRemoveWatchpoint(WatchpointDMContext bpCtx, RequestMonitor rm) {
fBreakpoints.remove(bpCtx);
// Watchpoints are cleared using the same command, but with a "no watch" operation
fCommandControl.queueCommand(
new PDAWatchCommand(
fCommandControl.getContext(), bpCtx.fFunction, bpCtx.fVariable, PDAWatchCommand.WatchOperation.NONE),
new DataRequestMonitor<PDACommandResult>(getExecutor(), rm));
}
public void updateBreakpoint(final IBreakpointDMContext bpCtx, Map<String, Object> attributes, final RequestMonitor rm) {
if (!fBreakpoints.contains(bpCtx)) {
PDAPlugin.failRequest(rm, REQUEST_FAILED, "Breakpoint not installed");
return;
}
if (bpCtx instanceof BreakpointDMContext) {
PDAPlugin.failRequest(rm, NOT_SUPPORTED, "Modifying PDA breakpoints is not supported");
} else if (bpCtx instanceof WatchpointDMContext) {
WatchpointDMContext wpCtx = (WatchpointDMContext)bpCtx;
if (!wpCtx.fFunction.equals(attributes.get(PDAWatchpoint.FUNCTION_NAME)) ||
!wpCtx.fVariable.equals(attributes.get(PDAWatchpoint.VAR_NAME)) )
{
PDAPlugin.failRequest(rm, REQUEST_FAILED, "Cannot modify watchpoint function or variable");
return;
}
// PDA debugger can only track one watchpoint in the same location,
// so we can simply remove the existing context from the set and
// call insert again.
fBreakpoints.remove(bpCtx);
doInsertWatchpoint(
attributes,
new DataRequestMonitor<IBreakpointDMContext>(getExecutor(), rm) {
@Override
protected void handleSuccess() {
// The inserted watchpoint context will equal the
// current context.
assert bpCtx.equals(getData());
rm.done();
}
});
} else {
PDAPlugin.failRequest(rm, INVALID_HANDLE, "Invalid breakpoint");
}
}
}