From 57c6ba5fec251d7f649ff7e56a540bc905377cb4 Mon Sep 17 00:00:00 2001 From: John Cortell Date: Tue, 6 Apr 2010 19:32:14 +0000 Subject: [PATCH] [248587] Support "Event' Breakpoints --- .../dsf/gdb/service/GDBRunControl_7_0_NS.java | 15 +- .../dsf/mi/service/MIBreakpointDMData.java | 27 +- .../cdt/dsf/mi/service/MIBreakpoints.java | 109 ++ .../dsf/mi/service/MIBreakpointsManager.java | 53 +- .../cdt/dsf/mi/service/MIRunControl.java | 9 +- .../mi/service/command/AbstractMIControl.java | 49 +- .../mi/service/command/CommandFactory.java | 6 + .../command/MIRunControlEventProcessor.java | 35 +- .../MIRunControlEventProcessor_7_0.java | 23 +- .../mi/service/command/commands/CLICatch.java | 52 + .../command/events/MIBreakpointHitEvent.java | 23 +- .../command/events/MICatchpointHitEvent.java | 43 + .../service/command/output/CLICatchInfo.java | 67 + .../service/command/output/MIBreakpoint.java | 104 +- .../mi/service/command/output/MIOutput.java | 59 +- .../data/launch/src/CatchpointTestApp.cc | 28 + .../dsf/gdb/tests/MICatchpointsTest.java | 1423 +++++++++++++++++ .../launch/ExecutionContextLabelText.java | 5 + .../viewmodel/launch/MessagesForLaunchVM.java | 1 + .../ui/viewmodel/launch/messages.properties | 3 +- .../cdt/dsf/debug/service/IRunControl.java | 6 +- 21 files changed, 2091 insertions(+), 49 deletions(-) create mode 100644 dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/mi/service/command/commands/CLICatch.java create mode 100644 dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/mi/service/command/events/MICatchpointHitEvent.java create mode 100644 dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/mi/service/command/output/CLICatchInfo.java create mode 100644 dsf-gdb/org.eclipse.cdt.tests.dsf.gdb/data/launch/src/CatchpointTestApp.cc create mode 100644 dsf-gdb/org.eclipse.cdt.tests.dsf.gdb/src/org/eclipse/cdt/tests/dsf/gdb/tests/MICatchpointsTest.java diff --git a/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/gdb/service/GDBRunControl_7_0_NS.java b/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/gdb/service/GDBRunControl_7_0_NS.java index c44b98c7699..8dd61210eb7 100644 --- a/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/gdb/service/GDBRunControl_7_0_NS.java +++ b/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/gdb/service/GDBRunControl_7_0_NS.java @@ -29,15 +29,15 @@ import org.eclipse.cdt.dsf.datamodel.DMContexts; import org.eclipse.cdt.dsf.datamodel.IDMContext; import org.eclipse.cdt.dsf.datamodel.IDMEvent; import org.eclipse.cdt.dsf.debug.service.IBreakpoints; -import org.eclipse.cdt.dsf.debug.service.ICachingService; -import org.eclipse.cdt.dsf.debug.service.IProcesses; -import org.eclipse.cdt.dsf.debug.service.IRunControl; -import org.eclipse.cdt.dsf.debug.service.IRunControl2; import org.eclipse.cdt.dsf.debug.service.IBreakpoints.IBreakpointDMContext; import org.eclipse.cdt.dsf.debug.service.IBreakpoints.IBreakpointsTargetDMContext; import org.eclipse.cdt.dsf.debug.service.IBreakpointsExtension.IBreakpointHitDMEvent; +import org.eclipse.cdt.dsf.debug.service.ICachingService; +import org.eclipse.cdt.dsf.debug.service.IProcesses; import org.eclipse.cdt.dsf.debug.service.IProcesses.IProcessDMContext; import org.eclipse.cdt.dsf.debug.service.IProcesses.IThreadDMContext; +import org.eclipse.cdt.dsf.debug.service.IRunControl; +import org.eclipse.cdt.dsf.debug.service.IRunControl2; import org.eclipse.cdt.dsf.debug.service.IStack.IFrameDMContext; import org.eclipse.cdt.dsf.debug.service.command.ICommand; import org.eclipse.cdt.dsf.debug.service.command.ICommandControlService; @@ -50,12 +50,13 @@ import org.eclipse.cdt.dsf.mi.service.IMIProcesses; import org.eclipse.cdt.dsf.mi.service.IMIRunControl; import org.eclipse.cdt.dsf.mi.service.MIBreakpointDMData; import org.eclipse.cdt.dsf.mi.service.MIBreakpoints; +import org.eclipse.cdt.dsf.mi.service.MIBreakpoints.MIBreakpointDMContext; import org.eclipse.cdt.dsf.mi.service.MIRunControl; import org.eclipse.cdt.dsf.mi.service.MIStack; -import org.eclipse.cdt.dsf.mi.service.MIBreakpoints.MIBreakpointDMContext; import org.eclipse.cdt.dsf.mi.service.command.CommandFactory; import org.eclipse.cdt.dsf.mi.service.command.events.IMIDMEvent; import org.eclipse.cdt.dsf.mi.service.command.events.MIBreakpointHitEvent; +import org.eclipse.cdt.dsf.mi.service.command.events.MICatchpointHitEvent; import org.eclipse.cdt.dsf.mi.service.command.events.MIErrorEvent; import org.eclipse.cdt.dsf.mi.service.command.events.MIEvent; import org.eclipse.cdt.dsf.mi.service.command.events.MIInferiorExitEvent; @@ -134,7 +135,9 @@ public class GDBRunControl_7_0_NS extends AbstractDsfService implements IMIRunCo } public StateChangeReason getReason() { - if (getMIEvent() instanceof MIBreakpointHitEvent) { + if (getMIEvent() instanceof MICatchpointHitEvent) { // must precede MIBreakpointHitEvent + return StateChangeReason.EVENT_BREAKPOINT; + } else if (getMIEvent() instanceof MIBreakpointHitEvent) { return StateChangeReason.BREAKPOINT; } else if (getMIEvent() instanceof MISteppingRangeEvent) { return StateChangeReason.STEP; diff --git a/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/mi/service/MIBreakpointDMData.java b/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/mi/service/MIBreakpointDMData.java index 58714b0ad56..a9073a564db 100644 --- a/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/mi/service/MIBreakpointDMData.java +++ b/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/mi/service/MIBreakpointDMData.java @@ -73,6 +73,8 @@ public class MIBreakpointDMData implements IBreakpointDMData { fNature = MIBreakpointNature.TRACEPOINT; } else if (dsfMIBreakpoint.isWatchpoint()) { fNature = MIBreakpointNature.WATCHPOINT; + } else if (dsfMIBreakpoint.isCatchpoint()) { + fNature = MIBreakpointNature.CATCHPOINT; } else { fNature = MIBreakpointNature.BREAKPOINT; } @@ -82,6 +84,9 @@ public class MIBreakpointDMData implements IBreakpointDMData { case BREAKPOINT: { + // Note that this may in fact be a catchpoint. See comment below in + // CATCHPOINT case + // Generic breakpoint attributes fProperties.put(MIBreakpoints.BREAKPOINT_TYPE, MIBreakpoints.BREAKPOINT); fProperties.put(MIBreakpoints.FILE_NAME, dsfMIBreakpoint.getFile()); @@ -142,7 +147,27 @@ public class MIBreakpointDMData implements IBreakpointDMData { fProperties.put(LOCATION, formatLocation()); break; } - + + case CATCHPOINT: + { + // Because gdb doesn't support catchpoints in mi, we end up using + // CLI to set the catchpoint. The sort of MIBreakpoint we create + // at that time is minimal as the only information we get back from + // gdb is the breakpoint number and type of the catchpoint we just + // set. See MIBreakpoint(String) + // + // The only type of MIBreakpoint that will be of this CATCHPOINT type + // is the instance we create from the response of the CLI command we + // use to set the catchpoint. If we later query gdb for the breakpoint + // list, we'll unfortunately end up creating an MIBreakpoint of type + // BREAKPOINT. Maybe one day gdb will treats catchpoints like first + // class citizens and this messy situation will go away. + + fProperties.put(MIBreakpoints.BREAKPOINT_TYPE, MIBreakpoints.CATCHPOINT); + fProperties.put(MIBreakpoints.CATCHPOINT_TYPE, dsfMIBreakpoint.getCatchpointType()); + break; + } + // Not reachable default: { diff --git a/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/mi/service/MIBreakpoints.java b/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/mi/service/MIBreakpoints.java index 92d4e33ff72..4d3f5f43290 100644 --- a/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/mi/service/MIBreakpoints.java +++ b/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/mi/service/MIBreakpoints.java @@ -40,6 +40,7 @@ import org.eclipse.cdt.dsf.debug.service.command.ICommandControlService.ICommand import org.eclipse.cdt.dsf.gdb.internal.GdbPlugin; import org.eclipse.cdt.dsf.mi.service.command.CommandFactory; import org.eclipse.cdt.dsf.mi.service.command.events.MIWatchpointScopeEvent; +import org.eclipse.cdt.dsf.mi.service.command.output.CLICatchInfo; import org.eclipse.cdt.dsf.mi.service.command.output.MIBreakInsertInfo; import org.eclipse.cdt.dsf.mi.service.command.output.MIBreakListInfo; import org.eclipse.cdt.dsf.mi.service.command.output.MIBreakpoint; @@ -90,6 +91,26 @@ public class MIBreakpoints extends AbstractDsfService implements IBreakpoints, I public static final String EXPRESSION = PREFIX + ".expression"; //$NON-NLS-1$ public static final String READ = PREFIX + ".read"; //$NON-NLS-1$ public static final String WRITE = PREFIX + ".write"; //$NON-NLS-1$ + + // Catchpoint properties + + /** + * Property that indicates the kind of catchpoint (.e.g, fork call, C++ + * exception throw). Value is the gdb keyword associated with that type, as + * listed in 'help catch'. + * + * @since 3.0 + */ + public static final String CATCHPOINT_TYPE = PREFIX + ".catchpoint_type"; //$NON-NLS-1$ + + /** + * Property that holds arguments for the catchpoint. Value is an array of + * Strings. Never null, but may be empty collection, as most catchpoints are + * argument-less. + * + * @since 3.0 + */ + public static final String CATCHPOINT_ARGS = PREFIX + ".catchpoint_args"; //$NON-NLS-1$ // Services private ICommandControl fConnection; @@ -151,6 +172,8 @@ public class MIBreakpoints extends AbstractDsfService implements IBreakpoints, I public final static String TRACEPOINT_INSERTION_FAILURE = "Tracepoint insertion failure"; //$NON-NLS-1$ /** @since 3.0 */ public final static String INVALID_BREAKPOINT_TYPE = "Invalid breakpoint type"; //$NON-NLS-1$ + /** @since 3.0 */ + public final static String CATCHPOINT_INSERTION_FAILURE = "Catchpoint insertion failure"; //$NON-NLS-1$ /////////////////////////////////////////////////////////////////////////// @@ -488,6 +511,9 @@ public class MIBreakpoints extends AbstractDsfService implements IBreakpoints, I else if (type.equals(MIBreakpoints.TRACEPOINT)) { addTracepoint(context, attributes, drm); } + else if (type.equals(MIBreakpoints.CATCHPOINT)) { + addCatchpoint(context, attributes, drm); + } else { drm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, REQUEST_FAILED, UNKNOWN_BREAKPOINT_TYPE, null)); drm.done(); @@ -526,6 +552,9 @@ public class MIBreakpoints extends AbstractDsfService implements IBreakpoints, I } /** + * Creates a gdb location string for a breakpoint/watchpoint/tracepoint + * given its set of properties. + * * @param attributes * @return * @since 3.0 @@ -740,6 +769,86 @@ public class MIBreakpoints extends AbstractDsfService implements IBreakpoints, I fConnection.queueCommand(fCommandFactory.createMIBreakWatch(context, isRead, isWrite, expression), addWatchpointDRM); } + /** + * @since 3.0 + */ + protected void addCatchpoint(final IBreakpointsTargetDMContext context, final Map attributes, final DataRequestMonitor finalRm) { + // Select the context breakpoints map + final Map contextBreakpoints = getBreakpointMap(context); + if (contextBreakpoints == null) { + finalRm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, REQUEST_FAILED, UNKNOWN_BREAKPOINT_CONTEXT, null)); + finalRm.done(); + return; + } + + // Though CDT allows setting a temporary catchpoint, CDT never makes use of it + assert (Boolean) getProperty(attributes, MIBreakpointDMData.IS_TEMPORARY, false) == false; + + // GDB has no support for hardware catchpoints + assert (Boolean) getProperty(attributes, MIBreakpointDMData.IS_HARDWARE, false) == false; + + final String event = (String) getProperty(attributes, CATCHPOINT_TYPE, NULL_STRING); + final String[] args = (String[]) getProperty(attributes, CATCHPOINT_ARGS, null); + + final Step insertBreakpointStep = new Step() { + @Override + public void execute(final RequestMonitor rm) { + fConnection.queueCommand( + fCommandFactory.createCLICatch(context, event, args == null ? new String[0] : args), + new DataRequestMonitor(getExecutor(), rm) { + @Override + protected void handleSuccess() { + + // Sanity check + MIBreakpoint miBkpt = getData().getMIBreakpoint(); + if (miBkpt == null) { + rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, REQUEST_FAILED, CATCHPOINT_INSERTION_FAILURE, null)); + rm.done(); + return; + } + + // Create a breakpoint object and store it in the map + final MIBreakpointDMData newBreakpoint = new MIBreakpointDMData(miBkpt); + int reference = newBreakpoint.getNumber(); + if (reference == -1) { + rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, REQUEST_FAILED, CATCHPOINT_INSERTION_FAILURE, null)); + rm.done(); + return; + } + contextBreakpoints.put(reference, newBreakpoint); + + // Format the return value + MIBreakpointDMContext dmc = new MIBreakpointDMContext(MIBreakpoints.this, new IDMContext[] { context }, reference); + finalRm.setData(dmc); + + // Flag the event + getSession().dispatchEvent(new BreakpointAddedEvent(dmc), getProperties()); + + // Break/Watch/Catchpoints that are disabled when set are delayed (we + // don't tell gdb about them until the user enables them). So, we shouldn't + // be here if this is a disabled breakpoint + assert ((Boolean)getProperty(attributes, IS_ENABLED, true)) == true; + + // Condition, ignore count and cannot be specified at creation time. + // Therefore, we have to update the catchpoint if any of these is present + Map delta = new HashMap(); + delta.put(CONDITION, getProperty(attributes, CONDITION, NULL_STRING)); + delta.put(IGNORE_COUNT, getProperty(attributes, IGNORE_COUNT, 0 )); + modifyBreakpoint(dmc, delta, rm, false); + } + + @Override + protected void handleError() { + rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, REQUEST_FAILED, CATCHPOINT_INSERTION_FAILURE, null)); + rm.done(); + } + }); + } + }; + + fRunControl.executeWithTargetAvailable(context, new Step[] { insertBreakpointStep }, finalRm); + } + //------------------------------------------------------------------------- // removeBreakpoint //------------------------------------------------------------------------- diff --git a/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/mi/service/MIBreakpointsManager.java b/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/mi/service/MIBreakpointsManager.java index f4848c0afde..c9e78826012 100644 --- a/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/mi/service/MIBreakpointsManager.java +++ b/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/mi/service/MIBreakpointsManager.java @@ -30,6 +30,7 @@ import org.eclipse.cdt.debug.core.breakpointactions.BreakpointActionManager; import org.eclipse.cdt.debug.core.model.ICAddressBreakpoint; import org.eclipse.cdt.debug.core.model.ICBreakpoint; import org.eclipse.cdt.debug.core.model.ICBreakpointExtension; +import org.eclipse.cdt.debug.core.model.ICEventBreakpoint; import org.eclipse.cdt.debug.core.model.ICLineBreakpoint; import org.eclipse.cdt.debug.core.model.ICTracepoint; import org.eclipse.cdt.debug.core.model.ICWatchpoint; @@ -42,13 +43,13 @@ import org.eclipse.cdt.dsf.concurrent.ThreadSafe; 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.debug.service.IDsfBreakpointExtension; -import org.eclipse.cdt.dsf.debug.service.IRunControl; -import org.eclipse.cdt.dsf.debug.service.ISourceLookup; import org.eclipse.cdt.dsf.debug.service.IBreakpoints.IBreakpointDMContext; import org.eclipse.cdt.dsf.debug.service.IBreakpoints.IBreakpointsTargetDMContext; +import org.eclipse.cdt.dsf.debug.service.IDsfBreakpointExtension; +import org.eclipse.cdt.dsf.debug.service.IRunControl; import org.eclipse.cdt.dsf.debug.service.IRunControl.IContainerDMContext; import org.eclipse.cdt.dsf.debug.service.IRunControl.IExecutionDMContext; +import org.eclipse.cdt.dsf.debug.service.ISourceLookup; import org.eclipse.cdt.dsf.debug.service.ISourceLookup.ISourceLookupDMContext; import org.eclipse.cdt.dsf.debug.service.command.ICommandControl; import org.eclipse.cdt.dsf.debug.service.command.ICommandControlService.ICommandControlShutdownDMEvent; @@ -91,7 +92,6 @@ import org.osgi.framework.BundleContext; * * It relies on MIBreakpoints for the actual back-end interface. */ -@SuppressWarnings("restriction") // we use an internal platform type (BreakpointProblems) public class MIBreakpointsManager extends AbstractDsfService implements IBreakpointManagerListener, IBreakpointListener { // Note: Find a way to import this (careful of circular dependencies) @@ -110,6 +110,30 @@ public class MIBreakpointsManager extends AbstractDsfService implements IBreakpo IBreakpointManager fBreakpointManager; // Platform breakpoint manager (not this!) BreakpointActionManager fBreakpointActionManager; + /** + * A mapping of ICEventBreakpoint event types to their corresponding gdb + * catchpoint keyword (as listed in gdb's 'help catch') + */ + private static final Map sEventBkptTypeToGdb = new HashMap(); + static { + // these Ids are also referenced in mi.ui plugin as contribution + // to event breakpoints selector + sEventBkptTypeToGdb.put(ICEventBreakpoint.EVENT_TYPE_CATCH, "catch"); //$NON-NLS-1$ + sEventBkptTypeToGdb.put(ICEventBreakpoint.EVENT_TYPE_THROW, "throw"); //$NON-NLS-1$ + sEventBkptTypeToGdb.put(ICEventBreakpoint.EVENT_TYPE_SIGNAL_CATCH, "signal"); //$NON-NLS-1$ + sEventBkptTypeToGdb.put(ICEventBreakpoint.EVENT_TYPE_EXEC, "exec"); //$NON-NLS-1$ + sEventBkptTypeToGdb.put(ICEventBreakpoint.EVENT_TYPE_FORK, "fork"); //$NON-NLS-1$ + sEventBkptTypeToGdb.put(ICEventBreakpoint.EVENT_TYPE_VFORK, "vfork"); //$NON-NLS-1$ + sEventBkptTypeToGdb.put(ICEventBreakpoint.EVENT_TYPE_EXIT, "exit"); //$NON-NLS-1$ + sEventBkptTypeToGdb.put(ICEventBreakpoint.EVENT_TYPE_PROCESS_START, "start"); //$NON-NLS-1$ + sEventBkptTypeToGdb.put(ICEventBreakpoint.EVENT_TYPE_PROCESS_STOP, "stop"); //$NON-NLS-1$ + sEventBkptTypeToGdb.put(ICEventBreakpoint.EVENT_TYPE_THREAD_START, "thread_start"); //$NON-NLS-1$ + sEventBkptTypeToGdb.put(ICEventBreakpoint.EVENT_TYPE_THREAD_EXIT, "thread_exit"); //$NON-NLS-1$ + sEventBkptTypeToGdb.put(ICEventBreakpoint.EVENT_TYPE_THREAD_JOIN, "thread_join"); //$NON-NLS-1$ + sEventBkptTypeToGdb.put(ICEventBreakpoint.EVENT_TYPE_LIBRARY_LOAD, "load"); //$NON-NLS-1$ + sEventBkptTypeToGdb.put(ICEventBreakpoint.EVENT_TYPE_LIBRARY_UNLOAD, "unload"); //$NON-NLS-1$ + } + /////////////////////////////////////////////////////////////////////////// // Breakpoints tracking /////////////////////////////////////////////////////////////////////////// @@ -1230,6 +1254,7 @@ public class MIBreakpointsManager extends AbstractDsfService implements IBreakpo public void eventDispatched(SuspendedEvent e) { if (e.getMIEvent() instanceof MIBreakpointHitEvent) { + // This covers catchpoints, too MIBreakpointHitEvent evt = (MIBreakpointHitEvent) e.getMIEvent(); performBreakpointAction(evt.getDMContext(), evt.getNumber()); return; @@ -1673,8 +1698,24 @@ public class MIBreakpointsManager extends AbstractDsfService implements IBreakpo properties.put(MIBreakpoints.BREAKPOINT_TYPE, MIBreakpoints.TRACEPOINT); properties.put(MIBreakpoints.PASS_COUNT, attributes.get(ICTracepoint.PASS_COUNT)); } - } else { - // catchpoint? + } + else if (breakpoint instanceof ICEventBreakpoint) { + properties.put(MIBreakpoints.BREAKPOINT_TYPE, MIBreakpoints.CATCHPOINT); + properties.put(MIBreakpoints.CATCHPOINT_TYPE, sEventBkptTypeToGdb.get(attributes.get(ICEventBreakpoint.EVENT_TYPE_ID))); + + String arg = (String)attributes.get(ICEventBreakpoint.EVENT_ARG); + String[] args; + if ((arg != null) && (arg.length() != 0)) { + args = new String[1]; + args[0] = arg; + } + else { + args = new String[0]; + } + properties.put(MIBreakpoints.CATCHPOINT_ARGS, args); + } + else { + assert false : "platform breakpoint is of an unexpected type: " + breakpoint.getClass().getName(); //$NON-NLS-1$ } // Common fields diff --git a/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/mi/service/MIRunControl.java b/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/mi/service/MIRunControl.java index 52e5972e41b..b672b40f09e 100644 --- a/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/mi/service/MIRunControl.java +++ b/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/mi/service/MIRunControl.java @@ -28,11 +28,11 @@ import org.eclipse.cdt.dsf.datamodel.DMContexts; import org.eclipse.cdt.dsf.datamodel.IDMContext; import org.eclipse.cdt.dsf.datamodel.IDMEvent; import org.eclipse.cdt.dsf.debug.service.IBreakpoints; -import org.eclipse.cdt.dsf.debug.service.ICachingService; -import org.eclipse.cdt.dsf.debug.service.IProcesses; import org.eclipse.cdt.dsf.debug.service.IBreakpoints.IBreakpointDMContext; import org.eclipse.cdt.dsf.debug.service.IBreakpoints.IBreakpointsTargetDMContext; import org.eclipse.cdt.dsf.debug.service.IBreakpointsExtension.IBreakpointHitDMEvent; +import org.eclipse.cdt.dsf.debug.service.ICachingService; +import org.eclipse.cdt.dsf.debug.service.IProcesses; import org.eclipse.cdt.dsf.debug.service.IStack.IFrameDMContext; import org.eclipse.cdt.dsf.debug.service.command.BufferedCommandControl; import org.eclipse.cdt.dsf.debug.service.command.CommandCache; @@ -44,6 +44,7 @@ import org.eclipse.cdt.dsf.mi.service.MIBreakpoints.MIBreakpointDMContext; import org.eclipse.cdt.dsf.mi.service.command.CommandFactory; import org.eclipse.cdt.dsf.mi.service.command.events.IMIDMEvent; import org.eclipse.cdt.dsf.mi.service.command.events.MIBreakpointHitEvent; +import org.eclipse.cdt.dsf.mi.service.command.events.MICatchpointHitEvent; import org.eclipse.cdt.dsf.mi.service.command.events.MIErrorEvent; import org.eclipse.cdt.dsf.mi.service.command.events.MIEvent; import org.eclipse.cdt.dsf.mi.service.command.events.MIRunningEvent; @@ -171,7 +172,9 @@ public class MIRunControl extends AbstractDsfService implements IMIRunControl, I } public StateChangeReason getReason() { - if (getMIEvent() instanceof MIBreakpointHitEvent) { + if (getMIEvent() instanceof MICatchpointHitEvent) { // must precede MIBreakpointHitEvent + return StateChangeReason.EVENT_BREAKPOINT; + } else if (getMIEvent() instanceof MIBreakpointHitEvent) { return StateChangeReason.BREAKPOINT; } else if (getMIEvent() instanceof MISteppingRangeEvent) { return StateChangeReason.STEP; diff --git a/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/mi/service/command/AbstractMIControl.java b/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/mi/service/command/AbstractMIControl.java index 550a6b499b6..0043a2a0bca 100644 --- a/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/mi/service/command/AbstractMIControl.java +++ b/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/mi/service/command/AbstractMIControl.java @@ -20,6 +20,7 @@ import java.io.OutputStream; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; +import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.concurrent.BlockingQueue; @@ -50,6 +51,7 @@ import org.eclipse.cdt.dsf.mi.service.command.output.MIOutput; import org.eclipse.cdt.dsf.mi.service.command.output.MIParser; import org.eclipse.cdt.dsf.mi.service.command.output.MIResult; import org.eclipse.cdt.dsf.mi.service.command.output.MIResultRecord; +import org.eclipse.cdt.dsf.mi.service.command.output.MIStreamRecord; import org.eclipse.cdt.dsf.mi.service.command.output.MIValue; import org.eclipse.cdt.dsf.service.AbstractDsfService; import org.eclipse.cdt.dsf.service.DsfSession; @@ -604,12 +606,20 @@ public abstract class AbstractMIControl extends AbstractDsfService private final InputStream fInputStream; private final MIParser fMiParser = new MIParser(); - /** - * List of out of band records since the last result record. Out of band records are - * required for processing the results of CLI commands. - */ - private final List fAccumulatedOOBRecords = new ArrayList(); - + /** + * List of out of band records since the last result record. Out of band + * records are required for processing the results of CLI commands. + */ + private final List fAccumulatedOOBRecords = new LinkedList(); + + /** + * List of stream records since the last result record, not including + * the record currently being processed (if it's a stream one). This is + * a subset of {@link #fAccumulatedOOBRecords}, as a stream record is a + * particular type of OOB record. + */ + private final List fAccumulatedStreamRecords = new LinkedList(); + public RxThread(InputStream inputStream) { super("MI RX Thread"); //$NON-NLS-1$ fInputStream = inputStream; @@ -784,6 +794,7 @@ public abstract class AbstractMIControl extends AbstractDsfService final MIOutput response = new MIOutput( rr, fAccumulatedOOBRecords.toArray(new MIOOBRecord[fAccumulatedOOBRecords.size()]) ); fAccumulatedOOBRecords.clear(); + fAccumulatedStreamRecords.clear(); MIInfo result = commandHandle.getCommand().getResult(response); DataRequestMonitor rm = commandHandle.getRequestMonitor(); @@ -870,12 +881,28 @@ public abstract class AbstractMIControl extends AbstractDsfService } else if (recordType == MIParser.RecordType.OOBRecord) { // Process OOBs final MIOOBRecord oob = fMiParser.parseMIOOBRecord(line); - if (!fRxCommands.isEmpty()) { - // This is for CLI commands, so only store if we are actually - // waiting for a command reply - fAccumulatedOOBRecords.add(oob); + + fAccumulatedOOBRecords.add(oob); + if (fAccumulatedOOBRecords.size() > 20) { // limit growth; see bug 302927 + fAccumulatedOOBRecords.remove(0); } - final MIOutput response = new MIOutput(oob); + + // The handling of this OOB record may need the stream records + // that preceded it. One such case is a stopped event caused by a + // catchpoint in gdb < 7.0. The stopped event provides no + // reason, but we can determine it was caused by a catchpoint by + // looking at the target stream. + + final MIOutput response = new MIOutput(oob, fAccumulatedStreamRecords.toArray(new MIStreamRecord[fAccumulatedStreamRecords.size()])); + + // If this is a stream record, add it to the accumulated bucket + // for possible use in handling a future OOB (see comment above) + if (oob instanceof MIStreamRecord) { + fAccumulatedStreamRecords.add((MIStreamRecord)oob); + if (fAccumulatedStreamRecords.size() > 20) { // limit growth; see bug 302927 + fAccumulatedStreamRecords.remove(0); + } + } /* * OOBS are events. So we pass them to any event listeners who want to see them. Again this must diff --git a/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/mi/service/command/CommandFactory.java b/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/mi/service/command/CommandFactory.java index 60b4e7a68a7..c7777f171b9 100644 --- a/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/mi/service/command/CommandFactory.java +++ b/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/mi/service/command/CommandFactory.java @@ -27,6 +27,7 @@ import org.eclipse.cdt.dsf.debug.service.command.ICommandControlService.ICommand import org.eclipse.cdt.dsf.gdb.service.IGDBTraceControl.ITraceTargetDMContext; import org.eclipse.cdt.dsf.mi.service.IMIExecutionDMContext; import org.eclipse.cdt.dsf.mi.service.command.commands.CLIAttach; +import org.eclipse.cdt.dsf.mi.service.command.commands.CLICatch; import org.eclipse.cdt.dsf.mi.service.command.commands.CLIDetach; import org.eclipse.cdt.dsf.mi.service.command.commands.CLIExecAbort; import org.eclipse.cdt.dsf.mi.service.command.commands.CLIInfoSharedLibrary; @@ -122,6 +123,7 @@ import org.eclipse.cdt.dsf.mi.service.command.commands.MIVarSetFormat; import org.eclipse.cdt.dsf.mi.service.command.commands.MIVarShowAttributes; import org.eclipse.cdt.dsf.mi.service.command.commands.MIVarShowFormat; import org.eclipse.cdt.dsf.mi.service.command.commands.MIVarUpdate; +import org.eclipse.cdt.dsf.mi.service.command.output.CLICatchInfo; import org.eclipse.cdt.dsf.mi.service.command.output.CLIInfoSharedLibraryInfo; import org.eclipse.cdt.dsf.mi.service.command.output.CLIInfoThreadsInfo; import org.eclipse.cdt.dsf.mi.service.command.output.CLIThreadInfo; @@ -176,6 +178,10 @@ public class CommandFactory { return new CLIAttach(ctx, pid); } + public ICommand createCLICatch(IBreakpointsTargetDMContext ctx, String event, String[] args) { + return new CLICatch(ctx, event, args); + } + public ICommand createCLIDetach(IDMContext ctx) { return new CLIDetach(ctx); } diff --git a/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/mi/service/command/MIRunControlEventProcessor.java b/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/mi/service/command/MIRunControlEventProcessor.java index 9bd691e2807..b598a890838 100644 --- a/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/mi/service/command/MIRunControlEventProcessor.java +++ b/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/mi/service/command/MIRunControlEventProcessor.java @@ -14,17 +14,17 @@ package org.eclipse.cdt.dsf.mi.service.command; import java.util.LinkedList; import java.util.List; -import org.eclipse.cdt.dsf.debug.service.IRunControl; import org.eclipse.cdt.dsf.debug.service.IProcesses.IProcessDMContext; import org.eclipse.cdt.dsf.debug.service.IProcesses.IThreadDMContext; +import org.eclipse.cdt.dsf.debug.service.IRunControl; import org.eclipse.cdt.dsf.debug.service.IRunControl.IContainerDMContext; import org.eclipse.cdt.dsf.debug.service.IRunControl.IExecutionDMContext; import org.eclipse.cdt.dsf.debug.service.command.ICommand; +import org.eclipse.cdt.dsf.debug.service.command.ICommandControlService.ICommandControlDMContext; import org.eclipse.cdt.dsf.debug.service.command.ICommandListener; import org.eclipse.cdt.dsf.debug.service.command.ICommandResult; import org.eclipse.cdt.dsf.debug.service.command.ICommandToken; import org.eclipse.cdt.dsf.debug.service.command.IEventListener; -import org.eclipse.cdt.dsf.debug.service.command.ICommandControlService.ICommandControlDMContext; import org.eclipse.cdt.dsf.gdb.internal.GdbPlugin; import org.eclipse.cdt.dsf.gdb.service.IGDBBackend; import org.eclipse.cdt.dsf.mi.service.IMIProcesses; @@ -40,6 +40,7 @@ import org.eclipse.cdt.dsf.mi.service.command.commands.MIExecStep; import org.eclipse.cdt.dsf.mi.service.command.commands.MIExecStepInstruction; import org.eclipse.cdt.dsf.mi.service.command.commands.MIExecUntil; import org.eclipse.cdt.dsf.mi.service.command.events.MIBreakpointHitEvent; +import org.eclipse.cdt.dsf.mi.service.command.events.MICatchpointHitEvent; import org.eclipse.cdt.dsf.mi.service.command.events.MIEvent; import org.eclipse.cdt.dsf.mi.service.command.events.MIFunctionFinishedEvent; import org.eclipse.cdt.dsf.mi.service.command.events.MIInferiorExitEvent; @@ -58,6 +59,7 @@ import org.eclipse.cdt.dsf.mi.service.command.output.MIOOBRecord; import org.eclipse.cdt.dsf.mi.service.command.output.MIOutput; import org.eclipse.cdt.dsf.mi.service.command.output.MIResult; import org.eclipse.cdt.dsf.mi.service.command.output.MIResultRecord; +import org.eclipse.cdt.dsf.mi.service.command.output.MIStreamRecord; import org.eclipse.cdt.dsf.mi.service.command.output.MIValue; import org.eclipse.cdt.dsf.service.DsfServicesTracker; @@ -136,6 +138,21 @@ public class MIRunControlEventProcessor } } } + + // GDB < 7.0 does not provide a reason when stopping on a + // catchpoint. However, the reason is contained in the + // stream records that precede the exec async output one. + // This is ugly, but we don't really have an alternative. + if (events.isEmpty()) { + MIStreamRecord[] streamRecords = ((MIOutput)output).getStreamRecords(); + for (MIStreamRecord streamRecord : streamRecords) { + String log = streamRecord.getString(); + if (log.startsWith("Catchpoint ")) { //$NON-NLS-1$ + events.add(MICatchpointHitEvent.parse(getExecutionContext(exec), exec.getToken(), results, streamRecord)); + } + } + } + // We were stopped for some unknown reason, for example // GDB for temporary breakpoints will not send the // "reason" ??? still fire a stopped event. @@ -153,8 +170,13 @@ public class MIRunControlEventProcessor } } } - - protected MIEvent createEvent(String reason, MIExecAsyncOutput exec) { + + /** + * Create an execution context given an exec-async-output OOB record + * + * @since 3.0 + */ + protected IExecutionDMContext getExecutionContext(MIExecAsyncOutput exec) { String threadId = null; MIResult[] results = exec.getMIResults(); @@ -185,6 +207,11 @@ public class MIRunControlEventProcessor execDmc = procService.createExecutionContext(processContainerDmc, threadDmc, threadId); } + return execDmc; + } + + protected MIEvent createEvent(String reason, MIExecAsyncOutput exec) { + IExecutionDMContext execDmc = getExecutionContext(exec); MIEvent event = null; if ("breakpoint-hit".equals(reason)) { //$NON-NLS-1$ event = MIBreakpointHitEvent.parse(execDmc, exec.getToken(), exec.getMIResults()); diff --git a/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/mi/service/command/MIRunControlEventProcessor_7_0.java b/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/mi/service/command/MIRunControlEventProcessor_7_0.java index 3d63f5517ba..51a833bc9c8 100644 --- a/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/mi/service/command/MIRunControlEventProcessor_7_0.java +++ b/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/mi/service/command/MIRunControlEventProcessor_7_0.java @@ -60,6 +60,7 @@ import org.eclipse.cdt.dsf.mi.service.command.output.MIOOBRecord; import org.eclipse.cdt.dsf.mi.service.command.output.MIOutput; import org.eclipse.cdt.dsf.mi.service.command.output.MIResult; import org.eclipse.cdt.dsf.mi.service.command.output.MIResultRecord; +import org.eclipse.cdt.dsf.mi.service.command.output.MIStreamRecord; import org.eclipse.cdt.dsf.mi.service.command.output.MIValue; import org.eclipse.cdt.dsf.service.DsfServicesTracker; @@ -132,7 +133,7 @@ public class MIRunControlEventProcessor_7_0 if (var.equals("reason")) { //$NON-NLS-1$ if (val instanceof MIConst) { String reason = ((MIConst) val).getString(); - MIEvent e = createEvent(reason, exec); + MIEvent e = createEvent(reason, exec, ((MIOutput)output).getStreamRecords()); if (e != null) { events.add(e); continue; @@ -144,7 +145,7 @@ public class MIRunControlEventProcessor_7_0 // GDB for temporary breakpoints will not send the // "reason" ??? still fire a stopped event. if (events.isEmpty()) { - MIEvent e = createEvent(STOPPED_REASON, exec); + MIEvent e = createEvent(STOPPED_REASON, exec, ((MIOutput)output).getStreamRecords()); if (e != null) { events.add(e); } @@ -155,7 +156,7 @@ public class MIRunControlEventProcessor_7_0 } } else if (RUNNING_REASON.equals(state)) { - MIEvent event = createEvent(RUNNING_REASON, exec); + MIEvent event = createEvent(RUNNING_REASON, exec, ((MIOutput)output).getStreamRecords()); if (event != null) { fCommandControl.getSession().dispatchEvent(event, fCommandControl.getProperties()); } @@ -240,8 +241,18 @@ public class MIRunControlEventProcessor_7_0 } } } - - protected MIEvent createEvent(String reason, MIExecAsyncOutput exec) { + + /** + * @since 3.0 + * @param miStreamRecords + * the stream records that preceded 'exec'. Determining which + * type of event to create may require additional insight + * available in those records. One example is catchpoint hits. + * They are reported by gdb (>= 7.0)as a simple breakpoint hit. + * However, gdb also sends a stream record that reveals that it's + * a catchpoint hit. + */ + protected MIEvent createEvent(String reason, MIExecAsyncOutput exec, MIStreamRecord[] miStreamRecords) { MIEvent event = null; if ("exited-normally".equals(reason) || "exited".equals(reason)) { //$NON-NLS-1$ //$NON-NLS-2$ @@ -300,7 +311,7 @@ public class MIRunControlEventProcessor_7_0 } if ("breakpoint-hit".equals(reason)) { //$NON-NLS-1$ - event = MIBreakpointHitEvent.parse(execDmc, exec.getToken(), exec.getMIResults()); + event = MIBreakpointHitEvent.parse(execDmc, exec.getToken(), exec.getMIResults(), miStreamRecords); } else if ( "watchpoint-trigger".equals(reason) //$NON-NLS-1$ || "read-watchpoint-trigger".equals(reason) //$NON-NLS-1$ diff --git a/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/mi/service/command/commands/CLICatch.java b/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/mi/service/command/commands/CLICatch.java new file mode 100644 index 00000000000..a6791ed2b38 --- /dev/null +++ b/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/mi/service/command/commands/CLICatch.java @@ -0,0 +1,52 @@ +/******************************************************************************* + * Copyright (c) 2010 Freescale Semiconductor 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: + * Freescale Semiconductor - Initial API and implementation + *******************************************************************************/ + +package org.eclipse.cdt.dsf.mi.service.command.commands; + +import org.eclipse.cdt.dsf.debug.service.IBreakpoints.IBreakpointsTargetDMContext; +import org.eclipse.cdt.dsf.mi.service.command.output.CLICatchInfo; +import org.eclipse.cdt.dsf.mi.service.command.output.MIInfo; +import org.eclipse.cdt.dsf.mi.service.command.output.MIOutput; + +/** + * gdb 'catch' command. Even though the command has been around since gdb 6.6, + * it's still not supported in gdb 7.0 MI. + * + * @since 3.0 + */ +public class CLICatch extends CLICommand { + + private static String formOperation(String event, String[] args) { + StringBuilder oper = new StringBuilder("catch " + event); //$NON-NLS-1$ + for (String arg : args) { + oper.append(" " + arg); //$NON-NLS-1$ + } + return oper.toString(); + } + + /** + * Constructor + * @param ctx the context for the command + * @param event the type of event to be caught; one of the keywords documented in 'help catch' + * @param args zero or more arguments particular to the 'event' + */ + public CLICatch(IBreakpointsTargetDMContext ctx, String event, String[] args) { + super(ctx, formOperation(event, args)); + } + + /* (non-Javadoc) + * @see org.eclipse.cdt.dsf.mi.service.command.commands.MICommand#getResult(org.eclipse.cdt.dsf.mi.service.command.output.MIOutput) + */ + @Override + public MIInfo getResult(MIOutput MIresult) { + return new CLICatchInfo(MIresult); + } +} diff --git a/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/mi/service/command/events/MIBreakpointHitEvent.java b/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/mi/service/command/events/MIBreakpointHitEvent.java index d82d6dfc75c..6bdc9083073 100644 --- a/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/mi/service/command/events/MIBreakpointHitEvent.java +++ b/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/mi/service/command/events/MIBreakpointHitEvent.java @@ -17,11 +17,17 @@ import org.eclipse.cdt.dsf.debug.service.IRunControl.IExecutionDMContext; import org.eclipse.cdt.dsf.mi.service.command.output.MIConst; import org.eclipse.cdt.dsf.mi.service.command.output.MIFrame; import org.eclipse.cdt.dsf.mi.service.command.output.MIResult; +import org.eclipse.cdt.dsf.mi.service.command.output.MIStreamRecord; import org.eclipse.cdt.dsf.mi.service.command.output.MIValue; /** - * ^stopped,reason="breakpoint-hit",bkptno="1",thread-id="0",frame={addr="0x08048468",func="main",args=[{name="argc",value="1"},{name="argv",value="0xbffff18c"}],file="hello.c",line="4"} - * + * Conveys that gdb reported the target stopped because of a breakpoint. This + * includes catchpoints, as gdb reports them as a breakpoint-hit. The + * async-exec-output record looks like this: + * + * + * ^stopped,reason="breakpoint-hit",bkptno="1",thread-id="0",frame={addr="0x08048468",func="main",args=[{name="argc",value="1"},{name="argv",value="0xbffff18c"}],file="hello.c",line="4"} + * */ @Immutable public class MIBreakpointHitEvent extends MIStoppedEvent { @@ -38,9 +44,10 @@ public class MIBreakpointHitEvent extends MIStoppedEvent { } /** - * @since 1.1 + * @param miStreamRecords + * @since 3.0 */ - public static MIBreakpointHitEvent parse(IExecutionDMContext dmc, int token, MIResult[] results) + public static MIBreakpointHitEvent parse(IExecutionDMContext dmc, int token, MIResult[] results, MIStreamRecord[] miStreamRecords) { int bkptno = -1; @@ -60,6 +67,14 @@ public class MIBreakpointHitEvent extends MIStoppedEvent { } } MIStoppedEvent stoppedEvent = MIStoppedEvent.parse(dmc, token, results); + + for (MIStreamRecord streamRecord : miStreamRecords) { + String log = streamRecord.getString(); + if (log.startsWith("Catchpoint ")) { //$NON-NLS-1$ + return new MICatchpointHitEvent(stoppedEvent.getDMContext(), token, results, stoppedEvent.getFrame(), bkptno); + } + } + return new MIBreakpointHitEvent(stoppedEvent.getDMContext(), token, results, stoppedEvent.getFrame(), bkptno); } } diff --git a/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/mi/service/command/events/MICatchpointHitEvent.java b/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/mi/service/command/events/MICatchpointHitEvent.java new file mode 100644 index 00000000000..156a062cd03 --- /dev/null +++ b/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/mi/service/command/events/MICatchpointHitEvent.java @@ -0,0 +1,43 @@ +package org.eclipse.cdt.dsf.mi.service.command.events; + +import java.util.StringTokenizer; + +import org.eclipse.cdt.dsf.debug.service.IRunControl.IExecutionDMContext; +import org.eclipse.cdt.dsf.mi.service.command.output.MIFrame; +import org.eclipse.cdt.dsf.mi.service.command.output.MIResult; +import org.eclipse.cdt.dsf.mi.service.command.output.MIStreamRecord; + +/** + * @since 3.0 + */ +public class MICatchpointHitEvent extends MIBreakpointHitEvent { + + protected MICatchpointHitEvent(IExecutionDMContext ctx, int token, + MIResult[] results, MIFrame frame, int bkptno) { + super(ctx, token, results, frame, bkptno); + } + + /** + * This variant is for catchpoint-hit in gdb < 7.0. For those versions, gdb + * sends us a stopped event, but it doesn't include a reason in it. + * Fortunately, it does output a stream record that tells us not only that a + * catchpoint was hit, but what its breakpoint number is. + * + * @param streamRecord + * the stream record that reveals that a catchpoint was hit + */ + public static MIBreakpointHitEvent parse(IExecutionDMContext dmc, int token, MIResult[] results, MIStreamRecord streamRecord) { + // stream record example: "Catchpoint 1 (exception caught)" + StringTokenizer tokenizer = new StringTokenizer(streamRecord.getString()); + tokenizer.nextToken(); // "Catchpoint" + try { + int bkptNumber = Integer.parseInt(tokenizer.nextToken()); // "1" (e.g.,) + MIStoppedEvent stoppedEvent = MIStoppedEvent.parse(dmc, token, results); + return new MICatchpointHitEvent(stoppedEvent.getDMContext(), token, results, stoppedEvent.getFrame(), bkptNumber); + } + catch (NumberFormatException exc) { + assert false : "unexpected catchpoint stream record format: " + streamRecord.getString(); //$NON-NLS-1$ + return null; + } + } +} diff --git a/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/mi/service/command/output/CLICatchInfo.java b/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/mi/service/command/output/CLICatchInfo.java new file mode 100644 index 00000000000..86488f46e48 --- /dev/null +++ b/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/mi/service/command/output/CLICatchInfo.java @@ -0,0 +1,67 @@ +/******************************************************************************* + * Copyright (c) 2010 Freescale Semiconductor 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: + * Freescale Semiconductor - Initial API and implementation + *******************************************************************************/ +package org.eclipse.cdt.dsf.mi.service.command.output; + +/** + * Processes the result of a gdb 'catch' command. Even though the command has + * been around since gdb 6.6, it's still not supported in gdb 7.0 MI. + * + * @since 3.0 + */ +public class CLICatchInfo extends MIInfo { + private MIBreakpoint fMiBreakpoint; + + /** + * Constructor + * @param record the result object for the command + */ + public CLICatchInfo(MIOutput record) { + super(record); + assert record != null; + parse(); + } + + /** + * sample output: Catchpoint 3 (catch) + */ + protected void parse() { + if (isDone()) { + MIOutput out = getMIOutput(); + for (MIOOBRecord oob : out.getMIOOBRecords()) { + if (oob instanceof MIConsoleStreamOutput) { + // We are interested in the catchpoint info + fMiBreakpoint = parseCatchpoint(((MIConsoleStreamOutput)oob).getString().trim()); + if (fMiBreakpoint != null) { + return; + } + } + } + } + } + + private MIBreakpoint parseCatchpoint(String str) { + if (str.startsWith("Catchpoint ")) { //$NON-NLS-1$ + return new MIBreakpoint(str); + } + assert false : "CLI catch command had an unexpected result: " + str; //$NON-NLS-1$ + return null; + } + + /** + * Return an MIBreakpoint object for the catchpoint that was created. + * + * @return an MIBreakpoint object or null if the command result had + * unexpected data + */ + public MIBreakpoint getMIBreakpoint() { + return fMiBreakpoint; + } +} diff --git a/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/mi/service/command/output/MIBreakpoint.java b/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/mi/service/command/output/MIBreakpoint.java index 1f8e74b6c56..9256378d246 100644 --- a/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/mi/service/command/output/MIBreakpoint.java +++ b/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/mi/service/command/output/MIBreakpoint.java @@ -14,6 +14,8 @@ package org.eclipse.cdt.dsf.mi.service.command.output; +import java.util.StringTokenizer; + /** * Contain info about the GDB/MI breakpoint. * @@ -78,7 +80,13 @@ public class MIBreakpoint { // Indicate if we are dealing with a tracepoint. // (if its a fast or slow tracepoint can be known through the 'type' field) boolean isTpt = false; - + + /** See {@link #isCatchpoint()} */ + boolean isCatchpoint; + + /** See {@link #getCatchpointType()} */ + private String catchpointType; + public MIBreakpoint() { } @@ -105,12 +113,80 @@ public class MIBreakpoint { isWWpt = other.isWWpt; isHdw = other.isHdw; isTpt = other.isTpt; + isCatchpoint = other.isCatchpoint; + catchpointType = other.catchpointType; } public MIBreakpoint(MITuple tuple) { parse(tuple); } + /** + * This constructor is used for catchpoints. Catchpoints are not yet + * supported in MI, so we end up using CLI. + * + *

+ * Note that this poses at least one challenge for us. Normally, upon + * creating a breakpoint/watchpoint/tracepoint via mi, we get back a command + * result from gdb that contains all the details of the newly created + * object, and we use that detailed information to create the MIBreakpoint. + * This is the same data we'll get back if we later ask gdb for the + * breakpoint list. However, because we're using CLI for cathpoints, we + * don't get that detailed information from gdb at creation time, but the + * detail will be there if we later ask for the breakpoint list. What this + * all means is that we can't compare the two MIBreakponts (the one we + * construct at creation time, and the one we get by asking gdb for the + * breakpoint list). The most we can do is compare the breakpoint number. + * That for sure should be the same. + * + *

+ * The detail we get from querying the breakpoint list, BTW, won't even + * reveal that it's a catchpoint. gdb simply reports it as a breakpoint, + * probably because that's what it really sets under the cover--an address + * breakpoint. This is another thing we need to keep in mind and creatively + * deal with. When we set the catchpoint, this constructor is used. When the + * breakpoint list is queried, {@link #MIBreakpoint(MITuple)} is used for + * that same breakpoint number, and a consumer of that MIBreakpoint won't be + * able to tell it's a catchpoint. Quite the mess. Wish gdb would treat + * catchpoints like first class citizens. + * + * @param cliResult + * the output from the CLI command. Example: + * "Catchpoint 1 (catch)" + * @since 3.0 + */ + public MIBreakpoint(String cliResult) { + if (cliResult.startsWith("Catchpoint ")) { //$NON-NLS-1$ + int bkptNumber = 0; + + StringTokenizer tokenizer = new StringTokenizer(cliResult); + for (int i = 0; tokenizer.hasMoreTokens(); i++) { + String sub = tokenizer.nextToken(); + switch (i) { + case 0: // first token is "Catchpoint" + break; + case 1: // second token is the breakpoint number + bkptNumber = Integer.parseInt(sub); + break; + case 2: // third token is the event type; drop the parenthesis + if (sub.startsWith("(")) { //$NON-NLS-1$ + sub = sub.substring(1, sub.length()-1); + } + catchpointType = sub; + break; + } + } + + number = bkptNumber; + isCatchpoint = true; + enabled = true; + } + else { + assert false : "unexpected CLI output: " + cliResult; //$NON-NLS-1$ + } + } + + /////////////////////////////////////////////////////////////////////////// // Properties getters /////////////////////////////////////////////////////////////////////////// @@ -183,6 +259,17 @@ public class MIBreakpoint { return exp; } + /** + * If isCatchpoint is true, then this indicates the type of catchpoint + * (event), as reported by gdb in its response to the CLI catch command. + * E.g., 'catch' or 'fork' + * + * @since 3.0 + */ + public String getCatchpointType() { + return catchpointType; + } + public boolean isTemporary() { return getDisposition().equals("del"); //$NON-NLS-1$ } @@ -247,6 +334,15 @@ public class MIBreakpoint { public boolean isTracepoint() { return isTpt; } + + /** + * Indicates if we are dealing with a catchpoint. + * + * @since 3.0 + */ + public boolean isCatchpoint() { + return isCatchpoint; + } /** * Returns the passcount of a tracepoint. Will return 0 if this @@ -303,6 +399,11 @@ public class MIBreakpoint { } catch (NumberFormatException e) { } } else if (var.equals("type")) { //$NON-NLS-1$ + // Note that catchpoints are reported by gdb as address + // breakpoints; there's really nothing we can go on to determine + // that it's actually a catchpoint (short of using a really ugly + // and fragile hack--looking at the 'what' field for specific values) + type = str; //type="hw watchpoint" if (type.startsWith("hw")) { //$NON-NLS-1$ @@ -370,5 +471,4 @@ public class MIBreakpoint { } } } - } diff --git a/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/mi/service/command/output/MIOutput.java b/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/mi/service/command/output/MIOutput.java index ae728c3a76c..1e85b79a13d 100644 --- a/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/mi/service/command/output/MIOutput.java +++ b/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/mi/service/command/output/MIOutput.java @@ -20,17 +20,54 @@ public class MIOutput { private final MIResultRecord rr; private final MIOOBRecord[] oobs; + private MIStreamRecord[] streamRecords; public MIOutput() { - this(null, null); + this(null, (MIOOBRecord[])null); } - public MIOutput(MIOOBRecord oob) { + /** + * @param oob + * @deprecated Use {@link #MIOutput(MIOOBRecord, MIStreamRecord[])} + */ + @Deprecated + public MIOutput(MIOOBRecord oob) { this(null, new MIOOBRecord[] { oob }); } - - + /** + * Constructor used when handling a single out-of-band record + * + * @param the + * out-of-bound record + * @param streamRecords + * any stream records that preceded the out-of-bound record, + * since the last command result. This need not contain *all* + * such records if it's been a while since the last command (for + * practical reasons, there is a cap on the number of stream + * records that are remembered). This will have the most recent + * records. Must not be null; may be empty + * @since 3.0 + */ + public MIOutput(MIOOBRecord oob, MIStreamRecord[] streamRecords) { + this(null, new MIOOBRecord[] { oob }); + this.streamRecords = streamRecords; + assert streamRecords != null; + } + + /** + * Constructor used when handling a command result. + * + * @param rr + * the result record + * @param oobs + * any out-of-band records that preceded this particular command + * result. This need not contain *all* such records if it's been + * a while since the last command (for practical reasons, there + * is a cap on the number of OOB records that are remembered). + * This will have the most recent records. + * + */ public MIOutput(MIResultRecord rr, MIOOBRecord[] oobs) { this.rr = rr; this.oobs = oobs; @@ -44,6 +81,20 @@ public class MIOutput { return oobs; } + /** + * See param in {@link #MIOutput(MIOOBRecord, MIStreamRecord[])} + * + * @return Only instances created for an OOB record will have stream + * records; may be an empty collection in that case, but not null. + * Instances created for a command result will return null from this + * method. Stream records can be retrieved from + * {@link #getMIOOBRecords()} in that case. + * @since 3.0 + */ + public MIStreamRecord[] getStreamRecords() { + return streamRecords; + } + @Override public String toString() { StringBuffer buffer = new StringBuffer(); diff --git a/dsf-gdb/org.eclipse.cdt.tests.dsf.gdb/data/launch/src/CatchpointTestApp.cc b/dsf-gdb/org.eclipse.cdt.tests.dsf.gdb/data/launch/src/CatchpointTestApp.cc new file mode 100644 index 00000000000..921f73f5418 --- /dev/null +++ b/dsf-gdb/org.eclipse.cdt.tests.dsf.gdb/data/launch/src/CatchpointTestApp.cc @@ -0,0 +1,28 @@ +#include +#include +int g_i = 0; +int main() { + for (; g_i < 8; g_i++) { + try { + std::cout << "Throwing exception" << std::endl; + throw 1; + } + catch (int exc) { + std::cout << "Exception caught" << std::endl; + } + } + + // For setting a catchpoint while target is running + std::cout << "Sleeping..." << std::endl; + sleep(2); + std::cout << "...awake!" << std::endl; + try { + std::cout << "Throwing exception" << std::endl; + throw 1; + } + catch (int exc) { + std::cout << "Exception caught" << std::endl; + } + + return 0; +} diff --git a/dsf-gdb/org.eclipse.cdt.tests.dsf.gdb/src/org/eclipse/cdt/tests/dsf/gdb/tests/MICatchpointsTest.java b/dsf-gdb/org.eclipse.cdt.tests.dsf.gdb/src/org/eclipse/cdt/tests/dsf/gdb/tests/MICatchpointsTest.java new file mode 100644 index 00000000000..29e4e942ff8 --- /dev/null +++ b/dsf-gdb/org.eclipse.cdt.tests.dsf.gdb/src/org/eclipse/cdt/tests/dsf/gdb/tests/MICatchpointsTest.java @@ -0,0 +1,1423 @@ +/******************************************************************************* + * Copyright (c) 2007, 2010 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 Implementation + *******************************************************************************/ +package org.eclipse.cdt.tests.dsf.gdb.tests; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import java.math.BigInteger; +import java.util.Arrays; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.ListIterator; +import java.util.Map; + +import org.eclipse.cdt.debug.core.ICDTLaunchConfigurationConstants; +import org.eclipse.cdt.debug.core.model.ICEventBreakpoint; +import org.eclipse.cdt.dsf.concurrent.DataRequestMonitor; +import org.eclipse.cdt.dsf.concurrent.RequestMonitor; +import org.eclipse.cdt.dsf.datamodel.IDMContext; +import org.eclipse.cdt.dsf.debug.service.IBreakpoints; +import org.eclipse.cdt.dsf.debug.service.IBreakpoints.IBreakpointDMContext; +import org.eclipse.cdt.dsf.debug.service.IBreakpoints.IBreakpointDMData; +import org.eclipse.cdt.dsf.debug.service.IBreakpoints.IBreakpointsAddedEvent; +import org.eclipse.cdt.dsf.debug.service.IBreakpoints.IBreakpointsRemovedEvent; +import org.eclipse.cdt.dsf.debug.service.IBreakpoints.IBreakpointsTargetDMContext; +import org.eclipse.cdt.dsf.debug.service.IBreakpoints.IBreakpointsUpdatedEvent; +import org.eclipse.cdt.dsf.debug.service.IExpressions; +import org.eclipse.cdt.dsf.debug.service.IExpressions.IExpressionDMContext; +import org.eclipse.cdt.dsf.debug.service.IFormattedValues; +import org.eclipse.cdt.dsf.debug.service.IFormattedValues.FormattedValueDMContext; +import org.eclipse.cdt.dsf.debug.service.IFormattedValues.FormattedValueDMData; +import org.eclipse.cdt.dsf.debug.service.IStack.IFrameDMContext; +import org.eclipse.cdt.dsf.debug.service.command.ICommandControlService; +import org.eclipse.cdt.dsf.internal.DsfPlugin; +import org.eclipse.cdt.dsf.mi.service.MIBreakpointDMData; +import org.eclipse.cdt.dsf.mi.service.MIBreakpoints; +import org.eclipse.cdt.dsf.mi.service.MIBreakpoints.MIBreakpointDMContext; +import org.eclipse.cdt.dsf.mi.service.MIRunControl; +import org.eclipse.cdt.dsf.mi.service.command.events.MIBreakpointHitEvent; +import org.eclipse.cdt.dsf.mi.service.command.events.MIStoppedEvent; +import org.eclipse.cdt.dsf.service.DsfServiceEventHandler; +import org.eclipse.cdt.dsf.service.DsfServicesTracker; +import org.eclipse.cdt.dsf.service.DsfSession; +import org.eclipse.cdt.tests.dsf.gdb.framework.AsyncCompletionWaitor; +import org.eclipse.cdt.tests.dsf.gdb.framework.BackgroundRunner; +import org.eclipse.cdt.tests.dsf.gdb.framework.BaseTestCase; +import org.eclipse.cdt.tests.dsf.gdb.framework.SyncUtil; +import org.eclipse.cdt.tests.dsf.gdb.launching.TestsPlugin; +import org.eclipse.core.runtime.Platform; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * This is the test suite for the catchpoint support in DSF-GDB. + * + * It is meant to be a regression suite to be executed automatically against the + * DSF nightly builds. + * + * It is also meant to be augmented with a proper test case(s) every time a + * feature is added or in the event (unlikely :-) that a bug is found in the + * Breakpoint Service. + * + * Refer to the JUnit4 documentation for an explanation of the annotations. + * + * NOTE: the WhileTargetRunning tests intermittently fail with MinGW gdb 7.0 and + * fail 100% with cygwin. Bug 304096 will address this. + */ + +@RunWith(BackgroundRunner.class) +public class MICatchpointsTest extends BaseTestCase { + + private static final String TEST_APPL = "data/launch/bin/CatchpointTestApp.exe"; //$NON-NLS-1$ + + public static final String SOURCE_FILE = "CatchpointTestApp.cc"; //$NON-NLS-1$ + + public static final int LINE_NUMBER_SLEEP_CALL = 17; + + // Asynchronous Completion + private final AsyncCompletionWaitor fWait = new AsyncCompletionWaitor(); + + // Services references + private DsfSession fSession; + private IBreakpointsTargetDMContext fBreakpointsDmc; + private DsfServicesTracker fServicesTracker; + private MIRunControl fRunControl; + private IBreakpoints fBreakpointService; + + // Event Management + private static Boolean fEventHandlerLock = true; + private enum Events { BP_ADDED, BP_UPDATED, BP_REMOVED, BP_HIT } + private final int BP_ADDED = Events.BP_ADDED.ordinal(); + private final int BP_UPDATED = Events.BP_UPDATED.ordinal(); + private final int BP_REMOVED = Events.BP_REMOVED.ordinal(); + private final int BP_HIT = Events.BP_HIT.ordinal(); + + /** number of times a breakpoint event was received, broken down by event type */ + private int[] fBreakpointEvents = new int[Events.values().length]; + + /** total number of breakpoint events received */ + private int totalBreakpointEventsCount() { + synchronized (fEventHandlerLock) { + int total = 0; + for (int count : fBreakpointEvents) { + total += count; + } + return total; + } + } + + + /** + * The gdb breakpoint number associated with the most recent breakpoint event + */ + private int fBreakpointRef; + + // NOTE: The back-end can reformat the condition. In order for the + // comparison to work, better specify the condition as the back-end + // would have it. + private final String CONDITION_VAR = "g_i"; + private final String CONDITION_NONE = ""; + private final String CONDITION_1 = CONDITION_VAR + " == 2"; + private final String CONDITION_2 = CONDITION_VAR + " == 4"; + private final String CONDITION_NEVER_MET = CONDITION_VAR + " == 10000"; + private final String CONDITION_ALWAYS_MET = CONDITION_VAR + " >= 0"; + + // Error messages + private final String UNKNOWN_EXECUTION_CONTEXT = "Unknown execution context"; + private final String UNKNOWN_BREAKPOINT = "Unknown breakpoint"; + + // ======================================================================== + // Housekeeping stuff + // ======================================================================== + + @BeforeClass + public static void testSuiteInitialization() { + // Select the binary to run the tests against + setLaunchAttribute(ICDTLaunchConfigurationConstants.ATTR_PROGRAM_NAME, TEST_APPL); + } + + @AfterClass + public static void testSuiteCleanup() { + } + + @Before + public void testCaseInitialization() { + + // Get a reference to the breakpoint service + fSession = getGDBLaunch().getSession(); + fServicesTracker = new DsfServicesTracker(TestsPlugin.getBundleContext(), fSession.getId()); + assertNotNull(fServicesTracker); + + ICommandControlService commandControl = fServicesTracker.getService(ICommandControlService.class); + fBreakpointsDmc = (IBreakpointsTargetDMContext)commandControl.getContext(); + assertNotNull(fBreakpointsDmc); + + fRunControl = fServicesTracker.getService(MIRunControl.class); + assertNotNull(fRunControl); + + fBreakpointService = fServicesTracker.getService(IBreakpoints.class); + assertNotNull(fBreakpointService); + + // Register to receive breakpoint events + fRunControl.getSession().addServiceEventListener(MICatchpointsTest.this, null); + + clearEventCounters(); + } + + @After + public void testCaseCleanup() { + + // Clear the references (not strictly necessary) + fRunControl.getSession().removeServiceEventListener(MICatchpointsTest.this); + fBreakpointService = null; + fRunControl = null; + fServicesTracker.dispose(); + fServicesTracker = null; + + clearEventCounters(); + } + + // ======================================================================== + // Event Management Functions + // ======================================================================== + + /* ----------------------------------------------------------------------- + * eventDispatched + * ------------------------------------------------------------------------ + * Processes BreakpointHitEvent. + * ------------------------------------------------------------------------ + * @param e The BreakpointEvent + * ------------------------------------------------------------------------ + */ + @DsfServiceEventHandler + public void eventDispatched(IBreakpointsAddedEvent e) { + synchronized (fEventHandlerLock) { + fBreakpointEvents[BP_ADDED]++; + fBreakpointRef = ((MIBreakpointDMContext) e.getBreakpoints()[0]).getReference(); + System.out.println(DsfPlugin.getDebugTime() + " Got bp added event (#" + fBreakpointRef + ")"); + fEventHandlerLock.notifyAll(); + } + } + + @DsfServiceEventHandler + public void eventDispatched(IBreakpointsUpdatedEvent e) { + synchronized (fEventHandlerLock) { + fBreakpointEvents[BP_UPDATED]++; + fBreakpointRef = ((MIBreakpointDMContext) e.getBreakpoints()[0]).getReference(); + System.out.println(DsfPlugin.getDebugTime() + " Got bp updated event (#" + fBreakpointRef + ")"); + fEventHandlerLock.notifyAll(); + } + } + + @DsfServiceEventHandler + public void eventDispatched(IBreakpointsRemovedEvent e) { + synchronized (fEventHandlerLock) { + fBreakpointEvents[BP_REMOVED]++; + fBreakpointRef = ((MIBreakpointDMContext) e.getBreakpoints()[0]).getReference(); + System.out.println(DsfPlugin.getDebugTime() + " Got bp removed event (#" + fBreakpointRef + ")"); + fEventHandlerLock.notifyAll(); + } + } + + @DsfServiceEventHandler + public void eventDispatched(MIBreakpointHitEvent e) { + synchronized (fEventHandlerLock) { + fBreakpointEvents[BP_HIT]++; + fBreakpointRef = e.getNumber(); + System.out.println(DsfPlugin.getDebugTime() + " Got bp hit event (#" + fBreakpointRef + ")"); + fEventHandlerLock.notifyAll(); + } + } + + // Clears the counters + private void clearEventCounters() { + synchronized (fEventHandlerLock) { + for (int i = 0; i < fBreakpointEvents.length; i++) { + fBreakpointEvents[i] = 0; + } + } + } + + // Get the breakpoint hit count + private int getBreakpointEventCount(int event) { + int count = 0; + synchronized (fEventHandlerLock) { + count = fBreakpointEvents[event]; + } + return count; + } + + /** + * Suspends the calling thread until [count] number of breakpoint events + * have been received in the current test. NOTE: too simple for real life + * but good enough for this test suite + * + * @param count + * the number breakpoint events to wait for + * @param timeout + * max wait time, in milliseconds + */ + private void waitForBreakpointEvent(int count, int timeout) throws Exception { + long startMs = System.currentTimeMillis(); + synchronized (fEventHandlerLock) { + // Make sure we don't wait forever, in case an event never + // arrives. The test will check if everything was received + int receivedCount; + while ((receivedCount = totalBreakpointEventsCount()) < count) { + try { + fEventHandlerLock.wait(30); + } catch (InterruptedException ex) { + } + if (System.currentTimeMillis() - startMs > timeout) { + throw new Exception("Timed out waiting for " + count + " breakpoint events to occur. Only " + receivedCount + " occurred."); + } + } + } + } + + /** + * Simplified variant that just waits up to two seconds + */ + private void waitForBreakpointEvent(int count) throws Exception { + waitForBreakpointEvent(count, TestsPlugin.massageTimeout(2000)); + } + + // ======================================================================== + // Helper Functions + // ======================================================================== + + /* ------------------------------------------------------------------------ + * evaluateExpression + * ------------------------------------------------------------------------ + * Invokes the ExpressionService to evaluate an expression. In theory, + * we shouldn't rely on another service to test this one but we need a + * way to access a variable from the test application in order verify + * that the memory operations (read/write) are working properly. + * ------------------------------------------------------------------------ + * @param expression Expression to resolve @return Resolved expression + * @throws InterruptedException + * ------------------------------------------------------------------------ + */ + private BigInteger evaluateExpression(IDMContext ctx, String expression) throws Throwable { + + final IExpressions fExpressionService = fServicesTracker.getService(IExpressions.class); + assert (fExpressionService != null); + + // Get a stack context (temporary - should be an MIcontainerDMC) + final IExpressionDMContext expressionDMC = SyncUtil.createExpression(ctx, expression); + final FormattedValueDMContext formattedValueDMC = SyncUtil.getFormattedValue(fExpressionService, + expressionDMC, IFormattedValues.DECIMAL_FORMAT); + + // Create the DataRequestMonitor which will store the operation result in the wait object + final DataRequestMonitor drm = + new DataRequestMonitor(fSession.getExecutor(), null) { + @Override + protected void handleCompleted() { + if (isSuccess()) { + fWait.setReturnInfo(getData()); + } + fWait.waitFinished(getStatus()); + } + }; + + // Evaluate the expression (asynchronously) + fWait.waitReset(); + fSession.getExecutor().submit(new Runnable() { + public void run() { + fExpressionService.getFormattedExpressionValue(formattedValueDMC, drm); + } + }); + + // Wait for completion + fWait.waitUntilDone(TestsPlugin.massageTimeout(5000)); + assertTrue(fWait.getMessage(), fWait.isOK()); + + // Return the string formatted by the back-end + String result = ""; + Object returnInfo = fWait.getReturnInfo(); + if (returnInfo instanceof FormattedValueDMData) + result = ((FormattedValueDMData) returnInfo).getFormattedValue(); + return new BigInteger(result); + } + + /* ------------------------------------------------------------------------ + * getBreakpoints + * ------------------------------------------------------------------------ + * Retrieves the installed breakpoints list + * ------------------------------------------------------------------------ + * Typical usage: + * IBreakpointDMContext[] breakpoints = getBreakpoints(context); + * ------------------------------------------------------------------------ + * @param context the execution context + * ------------------------------------------------------------------------ + */ + private IBreakpointDMContext[] getBreakpoints(final IBreakpointsTargetDMContext context) throws InterruptedException + { + // Clear the completion waiter + fWait.waitReset(); + + // Set the Request Monitor + final DataRequestMonitor drm = + new DataRequestMonitor(fBreakpointService.getExecutor(), null) { + @Override + protected void handleCompleted() { + fWait.waitFinished(getStatus()); + } + }; + + // Issue the breakpoint request + fWait.waitReset(); + fBreakpointService.getExecutor().submit(new Runnable() { + public void run() { + fBreakpointService.getBreakpoints(context, drm); + } + }); + + // Wait for completion + fWait.waitUntilDone(TestsPlugin.massageTimeout(5000)); + assertTrue(fWait.getMessage(), fWait.isOK()); + + // Return the string formatted by the back-end + return drm.getData(); + } + + /* ------------------------------------------------------------------------ + * getBreakpoint + * ------------------------------------------------------------------------ + * Retrieves the installed breakpoint + * ------------------------------------------------------------------------ + * Typical usage: + * IBreakpointDMContext breakpoint = ...; + * IBreakpointDMData bp = getBreakpoint(breakpoint); + * ------------------------------------------------------------------------ + * @param breakpoint the breakpoint to retrieve + * ------------------------------------------------------------------------ + */ + private IBreakpointDMData getBreakpoint(final IBreakpointDMContext breakpoint) throws InterruptedException + { + // Clear the completion waiter + fWait.waitReset(); + + // Set the Request Monitor + final DataRequestMonitor drm = + new DataRequestMonitor(fBreakpointService.getExecutor(), null) { + @Override + protected void handleCompleted() { + fWait.waitFinished(getStatus()); + } + }; + + // Issue the breakpoint request + fWait.waitReset(); + fBreakpointService.getExecutor().submit(new Runnable() { + public void run() { + fBreakpointService.getBreakpointDMData(breakpoint, drm); + } + }); + + // Wait for completion + fWait.waitUntilDone(TestsPlugin.massageTimeout(5000)); + assertTrue(fWait.getMessage(), fWait.isOK()); + + // Return the string formatted by the back-end + return drm.getData(); + } + + /* ------------------------------------------------------------------------ + * insertBreakpoint + * ------------------------------------------------------------------------ + * Issues an add breakpoint request. + * ------------------------------------------------------------------------ + * Typical usage: + * bp = insertBreakpoint(context, attributes); + * assertTrue(fWait.getMessage(), fWait.isOK()); + * ------------------------------------------------------------------------ + * @param context the execution context + * @param attributes the breakpoint attributes + * ------------------------------------------------------------------------ + */ + private IBreakpointDMContext insertBreakpoint(final IBreakpointsTargetDMContext context, + final Map attributes) throws InterruptedException + { + // Clear the completion waiter + fWait.waitReset(); + + // Set the Request Monitor + final DataRequestMonitor drm = + new DataRequestMonitor(fBreakpointService.getExecutor(), null) { + @Override + protected void handleCompleted() { + fWait.waitFinished(getStatus()); + } + }; + + // Issue the remove breakpoint request + fBreakpointService.getExecutor().submit(new Runnable() { + public void run() { + fBreakpointService.insertBreakpoint(context, attributes, drm); + } + }); + + // Wait for the result and return the breakpoint id + fWait.waitUntilDone(TestsPlugin.massageTimeout(5000)); + return drm.getData(); + } + + /* ------------------------------------------------------------------------ + * removeBreakpoint + * ------------------------------------------------------------------------ + * Issues a remove breakpoint request. + * ------------------------------------------------------------------------ + * Typical usage: + * IBreakpointDMContext breakpoint = ...; + * removeBreakpoint(context, breakpoint); + * assertTrue(fWait.getMessage(), fWait.isOK()); + * ------------------------------------------------------------------------ + * @param breakpoint the breakpoint to remove + * ------------------------------------------------------------------------ + */ + private void removeBreakpoint(final IBreakpointDMContext breakpoint) throws InterruptedException + { + // Clear the completion waiter + fWait.waitReset(); + + // Set the Request Monitor + final RequestMonitor rm = + new RequestMonitor(fBreakpointService.getExecutor(), null) { + @Override + protected void handleCompleted() { + fWait.waitFinished(getStatus()); + } + }; + + // Issue the add breakpoint request + fBreakpointService.getExecutor().submit(new Runnable() { + public void run() { + fBreakpointService.removeBreakpoint(breakpoint, rm); + } + }); + + // Wait for the result + fWait.waitUntilDone(TestsPlugin.massageTimeout(5000)); + } + + /* ------------------------------------------------------------------------ + * updateBreakpoint + * ------------------------------------------------------------------------ + * Issues an update breakpoint request. + * ------------------------------------------------------------------------ + * Typical usage: + * updateBreakpoint(context, breakpoint, properties); + * assertTrue(fWait.getMessage(), fWait.isOK()); + * ------------------------------------------------------------------------ + * @param breakpoint the breakpoint to update + * @param delta the delta properties + * ------------------------------------------------------------------------ + */ + private void updateBreakpoint(final IBreakpointDMContext breakpoint, + final Map delta) throws InterruptedException + { + // Clear the completion waiter + fWait.waitReset(); + + // Set the Request Monitor + final RequestMonitor rm = + new RequestMonitor(fBreakpointService.getExecutor(), null) { + @Override + protected void handleCompleted() { + fWait.waitFinished(getStatus()); + } + }; + + // Issue the update breakpoint request + fBreakpointService.getExecutor().submit(new Runnable() { + public void run() { + fBreakpointService.updateBreakpoint(breakpoint, delta, rm); + } + }); + + // Wait for the result + fWait.waitUntilDone(TestsPlugin.massageTimeout(5000)); + } + + // ======================================================================== + // Test Cases + // ======================================================================== + + /////////////////////////////////////////////////////////////////////////// + // Add Catchpoint tests + /////////////////////////////////////////////////////////////////////////// + + @Test + public void insertCatchpoint_InvalidContext() throws Throwable { + + // Attempt to create a catchpoint with an invalid execution context (should fail) + Map breakpoint = new HashMap(); + breakpoint.put(MIBreakpoints.BREAKPOINT_TYPE, MIBreakpoints.CATCHPOINT); + breakpoint.put(MIBreakpoints.CATCHPOINT_TYPE, ICEventBreakpoint.EVENT_TYPE_THROW); + insertBreakpoint(null, breakpoint); + + // Ensure it failed + String expected = UNKNOWN_EXECUTION_CONTEXT; + assertFalse(fWait.getMessage(), fWait.isOK()); + assertTrue("Wrong error message: expected message to contain: '" + expected + "', received '" + fWait.getMessage() + "'", + fWait.getMessage().contains(expected)); + + // Ensure that no breakpoint events were received + assertEquals("Unexpected number of breakpoint events", 0, totalBreakpointEventsCount()); + } + + // Long story. There's really no way for the user to set a disabled + // catchpoint/breakpoint/tracepoint, so this test is invalid. If a + // catchpoint is disabled prior to launching a session, then we simply defer + // telling gdb about it until the user enables it. It was done this way + // because until recently, gdb did not support indicating the enable state + // at creation time, and changing the enable state after creation is + // susceptible to race condition problems (a non-stopped thread could hit it + // during the small window where it's enabled). At some point, we should + // change the implementation to use the new gdb capability to create a + // disabled breakpoint. When we do, this test will become relevant. + // @Test +// public void insertCatchpoint_Disabled() throws Throwable { +// // Create a catchpoint +// Map breakpoint = new HashMap(); +// breakpoint.put(MIBreakpoints.BREAKPOINT_TYPE, MIBreakpoints.CATCHPOINT); +// breakpoint.put(MIBreakpoints.CATCHPOINT_TYPE, "throw"); +// breakpoint.put(MIBreakpoints.IS_ENABLED, false); +// +// // Perform the test +// IBreakpointDMContext ref = insertBreakpoint(fBreakpointsDmc, breakpoint); +// assertTrue(fWait.getMessage(), fWait.isOK()); +// +// // Ensure that right BreakpointEvents were received +// waitForBreakpointEvent(1); +// int count = totalBreakpointEventsCount(); +// assertTrue("BreakpointEvent problem: expected " + 1 + " BREAKPOINT event(s), received " +// + count, count == 1); +// assertTrue("BreakpointEvent problem: expected " + 1 + " BREAKPOINT_ADDED event(s), received " +// + getBreakpointEventCount(BP_ADDED), getBreakpointEventCount(BP_ADDED) == 1); +// clearEventCounters(); +// +// // Ensure that the breakpoint was correctly installed +// MIBreakpointDMData breakpoint1 = (MIBreakpointDMData) getBreakpoint(ref); +// assertTrue("BreakpointService problem: breakpoint mismatch (wrong condition)", +// breakpoint1.getCondition().equals(NO_CONDITION)); +// assertTrue("BreakpointService problem: breakpoint mismatch (wrong ignore count)", +// breakpoint1.getIgnoreCount() == 0); +// assertTrue("BreakpointService problem: breakpoint mismatch (wrong state)", +// !breakpoint1.isEnabled()); +// +// // Ensure the BreakpointService holds only the right breakpoints +// IBreakpointDMContext[] breakpoints = getBreakpoints(fBreakpointsDmc); +// assertTrue("BreakpointService problem: expected " + 1 + " breakpoint(s), received " +// + breakpoints.length, breakpoints.length == 1); +// MIBreakpointDMData breakpoint2 = (MIBreakpointDMData) getBreakpoint(breakpoints[0]); +// assertEquals(breakpoint1.getNumber(), breakpoint2.getNumber()); +// assertFalse(breakpoint2.isEnabled()); +// } + + @Test + public void insertCatchpoint_Simple() throws Throwable { + IBreakpointDMContext ref = setCatchpoint("throw", null, null); + resumeAndExpectBkptHit(((MIBreakpointDMData)getBreakpoint(ref)).getNumber(), 0); + } + + /** + * Set a conditional catchpoint. Ensure that it is set correctly in the + * back-end. This doesn't actually run the target to see if the catchpoint + * behaves correctly. + */ + @Test + public void insertCatchpoint_Condition() throws Throwable { + IBreakpointDMContext ref = setCatchpoint("throw", CONDITION_1, null); + resumeAndExpectBkptHit(((MIBreakpointDMData)getBreakpoint(ref)).getNumber(), 2); + } + + /** + * Set a catchpoint with an ignore count. Ensure that it is set correctly in + * the back-end. This doesn't actually run the target to see if the + * catchpoint behaves correctly. + */ + @Test + public void insertCatchpoint_IgnoreCnt() throws Throwable { + IBreakpointDMContext ref = setCatchpoint("throw", null, 3); + resumeAndExpectBkptHit(((MIBreakpointDMData)getBreakpoint(ref)).getNumber(), 3); + } + + /** + * Set two different catchpoints and ensure they are set correctly in the back-end. + * This doesn't actually run the target to see if the catchpoints behaves + * correctly. + */ + @Test + public void insertCatchpoint_MultipleCatchpoints() throws Throwable { + // Set a throw catchpoint + IBreakpointDMContext ref = setCatchpoint("throw", null, null); + MIBreakpointDMData bkpt1_set = (MIBreakpointDMData) getBreakpoint(ref); + + // Set a catch catchpoint + ref = setCatchpoint("catch", null, null); + MIBreakpointDMData bkpt2_set = (MIBreakpointDMData) getBreakpoint(ref); + + // Ensure the breakpoint service sees what we expect + IBreakpointDMContext[] breakpoints = getBreakpoints(fBreakpointsDmc); + assertEquals("Breakpoint service reports unexpected number of breakpoints", 2, breakpoints.length); + MIBreakpointDMData bkpt1_svc = (MIBreakpointDMData) getBreakpoint(breakpoints[0]); + MIBreakpointDMData bkpt2_svc = (MIBreakpointDMData) getBreakpoint(breakpoints[1]); + + // The breakpoint references are not necessarily retrieved in the order the + // breakpoints were initially set... + if (bkpt1_svc.getNumber() == bkpt1_set.getNumber()) { + assertEquals(bkpt2_svc.getNumber(), bkpt2_set.getNumber()); + } + else { + assertEquals(bkpt1_svc.getNumber(), bkpt2_set.getNumber()); + assertEquals(bkpt2_svc.getNumber(), bkpt1_set.getNumber()); + } + } + + /** + * Set two identical catchpoints and ensure they are set correctly in the + * back-end. GDB has no problem with this. This doesn't actually run the + * target to see if the catchpoints behaves correctly. + */ + @Test + public void insertCatchpoint_Duplicate() throws Throwable { + // Set a throw catchpoint + IBreakpointDMContext ref = setCatchpoint("throw", null, null); + MIBreakpointDMData bkpt1_set = (MIBreakpointDMData) getBreakpoint(ref); + + // Tell gdb to set a throw catchpoint AGAIN + ref = setCatchpoint("throw", null, null); + MIBreakpointDMData bkpt2_set = (MIBreakpointDMData) getBreakpoint(ref); + + // Ensure the breakpoint service sees what we expect + IBreakpointDMContext[] breakpoints = getBreakpoints(fBreakpointsDmc); + assertEquals("Breakpoint service reports unexpected number of breakpoints", 2, breakpoints.length); + MIBreakpointDMData bkpt1_svc = (MIBreakpointDMData) getBreakpoint(breakpoints[0]); + MIBreakpointDMData bkpt2_svc = (MIBreakpointDMData) getBreakpoint(breakpoints[1]); + + // The breakpoint references are not necessarily retrieved in the order the + // breakpoints were initially set... + if (bkpt1_svc.getNumber() == bkpt1_set.getNumber()) { + assertEquals(bkpt2_svc.getNumber(), bkpt2_set.getNumber()); + } + else { + assertEquals(bkpt1_svc.getNumber(), bkpt2_set.getNumber()); + assertEquals(bkpt2_svc.getNumber(), bkpt1_set.getNumber()); + } + } + + /** + * Set a catchpoint while the target is running and ensure it gets hit. + */ + @Test + public void insertCatchpoint_WhileTargetRunning() throws Throwable { + // Run the program. It will make a two second sleep() call, during which time... + SyncUtil.resume(); + + // Set a throw catchpoint; don't use the utility method since it assumes + // the target is running + Map bkptsProps = new HashMap(); + bkptsProps.put(MIBreakpoints.BREAKPOINT_TYPE, MIBreakpoints.CATCHPOINT); + bkptsProps.put(MIBreakpoints.CATCHPOINT_TYPE, "throw"); + insertBreakpoint(fBreakpointsDmc, bkptsProps); + assertTrue(fWait.getMessage(), fWait.isOK()); + + // After the sleep, the test app throws a C++ exception. Wait for the + // catchpoint to hit. + MIStoppedEvent event = waitForBreakpointEventsAfterBreakpointOperationWhileTargetRunning(true, 2); + + // Ensure that right breakpoint events were received. One indicating the + // catchpoint was created, another indicating it was hit + waitForBreakpointEvent(1); + assertEquals("Unexpected number of breakpoint events", 2, totalBreakpointEventsCount()); + assertEquals("Unexpected number of breakpoint-added events", 1, getBreakpointEventCount(BP_ADDED)); + assertEquals("Unexpected number of breakpoint-hit events", 1, getBreakpointEventCount(BP_HIT)); + clearEventCounters(); + + assertTrue("Did not stop because of catchpoint, but stopped because of: " + + event.getClass().getCanonicalName(), event instanceof MIBreakpointHitEvent); + } + + /** + * Set a catchpoint and remove it. This doesn't actually run the target to + * see if the removed catchpoint has no effect. + */ + @Test + public void removeCatchpoint_SimpleCase() throws Throwable { + // Set a throw catchpoint + IBreakpointDMContext ref = setCatchpoint("throw", null, null); + + // Remove the cachpoint + clearEventCounters(); + removeBreakpoint(ref); + assertTrue(fWait.getMessage(), fWait.isOK()); + + // Ensure that right breakpoint events were received + waitForBreakpointEvent(1); + assertEquals("Unexpected number of breakpoint events", 1, totalBreakpointEventsCount()); + assertEquals("Unexpected number of breakpoint-added events", 1, getBreakpointEventCount(BP_REMOVED)); + clearEventCounters(); + + // Ensure the breakpoint was effectively removed + IBreakpointDMContext[] breakpoints = getBreakpoints(fBreakpointsDmc); + assertEquals("Breakpoints service reports unexpected number of breakpoints", 0, breakpoints.length); + } + + /** + * Set a catchpoint, remove it, then try to remove it again; that should + * fail. Set a second catchpoint, try removing the first one (again), which + * should again fail, but this time make sure the second catchpoint is + * unaffected. This doesn't actually run the target to see if the + * insalled/removed catchpoints behave correctly. + */ + @Test + public void removeCatchpoint_InvalidBreakpoint() throws Throwable { + // set a catchpoint + IBreakpointDMContext bkptRef1 = setCatchpoint("throw", null, null); + + // Remove the installed breakpoint + clearEventCounters(); + removeBreakpoint(bkptRef1); + assertTrue(fWait.getMessage(), fWait.isOK()); + + // Ensure that right breakpoints events were received + waitForBreakpointEvent(1); + assertEquals("Unexpected number of breakpoint events", 1, totalBreakpointEventsCount()); + assertEquals("Unexpected number of breakpoint-added events", 1, getBreakpointEventCount(BP_REMOVED)); + clearEventCounters(); + + // Ensure the breakpoint service sees what we expect + IBreakpointDMContext[] breakpoints = getBreakpoints(fBreakpointsDmc); + assertEquals("Breakpoints service reports unexpected number of breakpoints", 0, breakpoints.length); + + // Try removing the catchpoint again; should fail + removeBreakpoint(bkptRef1); + assertFalse(fWait.getMessage(), fWait.isOK()); + String expected = UNKNOWN_BREAKPOINT; + assertTrue("Wrong error message: expected '" + expected + "', received '" + fWait.getMessage() + "'", + fWait.getMessage().contains(expected)); + + // Ensure no breakpoint events were received + assertEquals("Unexpected number of breakpoint events", 0, totalBreakpointEventsCount()); + + // Ensure the breakpoint service sees what we expect + breakpoints = getBreakpoints(fBreakpointsDmc); + assertEquals("Breakpoints service reports unexpected number of breakpoints", 0, breakpoints.length); + + // Re-install the catchpoint + IBreakpointDMContext bkptRef2 = setCatchpoint("throw", null, null); + clearEventCounters(); + + // Try removing the un-installed breakpoint again; should fail + removeBreakpoint(bkptRef1); + assertFalse(fWait.getMessage(), fWait.isOK()); + assertTrue("Wrong error message: expected '" + expected + "', received '" + fWait.getMessage() + "'", + fWait.getMessage().contains(expected)); + + // Ensure no breakpoint events were received + assertEquals("Unexpected number of breakpoint events", 0, totalBreakpointEventsCount()); + + // Ensure that the recently set breakpoint is unaffected + breakpoints = getBreakpoints(fBreakpointsDmc); + assertEquals("Breakpoints service reports unexpected number of breakpoints", 1, breakpoints.length); + MIBreakpointDMData bkpt2_set = (MIBreakpointDMData) getBreakpoint(bkptRef2); + MIBreakpointDMData bkpt2_svc = (MIBreakpointDMData) getBreakpoint(breakpoints[0]); + assertEquals(bkpt2_set.getNumber(), bkpt2_svc.getNumber()); + } + + /** + * Set a series of distinct catchpoints then remove them in a different + * order. This doesn't actually run the target to see if the + * installed/removed catchpoints behave correctly. + */ + @Test + public void removeCatchpoint_MixedOrder() throws Throwable { + final String[] events = new String[] { "throw", "catch", "exec", "fork" }; + + // Set the catchpoints + for (String event : events) { + setCatchpoint(event, null, null); + } + + // Get the list of breakpoints + IBreakpointDMContext[] breakpoints = getBreakpoints(fBreakpointsDmc); + assertEquals("Breakpoint service reports unexpected number of breakpoints", events.length, breakpoints.length); + + // Remove the catchpoints one at a time but in an order different than how they were added + int[] whichOne = { 0, 2, 1, 3 }; + int breakpoints_left = 4; + for (int i = 0; i < whichOne.length; i++) { + clearEventCounters(); + + // Remove one of the catchpoints + IBreakpointDMContext removeThisBreakpoint = breakpoints[whichOne[i]]; + removeBreakpoint(removeThisBreakpoint); + fWait.waitUntilDone(TestsPlugin.massageTimeout(5000)); + assertTrue(fWait.getMessage(), fWait.isOK()); + + // Ensure that right breakpoint events were received + waitForBreakpointEvent(1); + assertEquals("Unexpected number of breakpoint events", 1, totalBreakpointEventsCount()); + assertEquals("Unexpected number of breakpoint-added events", 1, getBreakpointEventCount(BP_REMOVED)); + + // Ensure the breakpoint service sees what we expect + IBreakpointDMContext[] remaining_breakpoints = getBreakpoints(fBreakpointsDmc); + assertEquals("Breakpoints service reports unexpected number of breakpoints", --breakpoints_left, remaining_breakpoints.length); + for (int j = 0; j < breakpoints_left; j++) { + assertTrue("BreakpointService problem: removed breakpoint still present (" + removeThisBreakpoint + ")", + !remaining_breakpoints[j].equals(removeThisBreakpoint)); + } + } + } + + /** + * Set a throw and a catch catchpoint while the target is + * stopped, then remove the throw catchpoint while the target is running and + * ensure the catch catchpoint is hit. + */ + @Test + public void removeCatchpoint_WhileTargetRunning1() throws Throwable { + removeCatchpoint_WhileTargetRunning(true); + } + + /** + * Variant that removes the catch catchpoint instead of the throw one. See + * {@link #removeCatchpoint_WhileTargetRunning1()} + */ + @Test + public void removeCatchpoint_WhileTargetRunning2() throws Throwable { + removeCatchpoint_WhileTargetRunning(false); + } + + /** + * See {@link #removeCatchpoint_WhileTargetRunning1()} + * @param removeThrow + * if true, we remove the throw catchpoint, otherwise the catch + * one. + */ + private void removeCatchpoint_WhileTargetRunning(boolean removeThrow) throws Throwable { + // Set a line breakpoint at the sleep() call. We need to get the program + // past the initial loop that throws and catches C++ exceptions. + IBreakpointDMContext refLineBkpt = setLineBreakpoint(LINE_NUMBER_SLEEP_CALL); + + // Run to the breakpoint + resumeAndExpectBkptHit(((MIBreakpointDMData) getBreakpoint(refLineBkpt)).getNumber(), null); + + // Set the two catchpoints + IBreakpointDMContext refThrow = setCatchpoint("throw", null, null); + IBreakpointDMContext refCatch = setCatchpoint("catch", null, null); + + // Run the program. It will make a two second sleep() call, during which time... + clearEventCounters(); + SyncUtil.resume(); + + // ...we remove one of the catchpoints + removeBreakpoint(removeThrow ? refThrow : refCatch); + assertTrue(fWait.getMessage(), fWait.isOK()); + + // After the sleep, the test app throws a C++ exception and catches it. + // The catchpoint we DIDN'T remove should stop the program + MIStoppedEvent event = waitForBreakpointEventsAfterBreakpointOperationWhileTargetRunning(true, 2); + assertTrue("stopped event is of an unexpected type: " + event.getClass().getName(), event instanceof MIBreakpointHitEvent); + MIBreakpointHitEvent bkptHitEvent = (MIBreakpointHitEvent)event; + MIBreakpointDMData bkptNotRemoved = (MIBreakpointDMData) getBreakpoint(removeThrow ? refCatch : refThrow); + assertEquals("Target stopped as expected, but the responsible breakpoint was not the expected one", bkptNotRemoved.getNumber(), bkptHitEvent.getNumber()); + + // If we removed the catch exception, we don't know at this point that + // it won't get hit; we're stopped at the throw catchpoint. So resume + // the target and make sure it doesn't get hit. + if (!removeThrow) { + clearEventCounters(); + SyncUtil.resume(); + Thread.sleep(1000); // give the program a second to run to completion + assertEquals("Unexpected number of breakpoint events", 0, totalBreakpointEventsCount()); + } + } + + /////////////////////////////////////////////////////////////////////////// + // Catchpoint Update tests + /////////////////////////////////////////////////////////////////////////// + + + /** + * Add a catchpoint with no condition then modify the condition. + */ + @Test + public void updateCatchpoint_AddCondition() throws Throwable { + // Set a catchpoint with no condition + IBreakpointDMContext ref = setCatchpoint("throw", null, null); + + // Update the catchpoint to have a condition + modifyBkptProperty(ref, MIBreakpoints.CONDITION, CONDITION_1); + + // Ensure the breakpoint service sees what we expect + IBreakpointDMContext[] breakpoints = getBreakpoints(fBreakpointsDmc); + assertEquals("Breakpoints service reports unexpected number of breakpoints", 1, breakpoints.length); + MIBreakpointDMData bkpt_svc = (MIBreakpointDMData) getBreakpoint(breakpoints[0]); + assertEquals("Incorrect breakpoint condition", CONDITION_1, bkpt_svc.getCondition()); + + resumeAndExpectBkptHit(bkpt_svc.getNumber(), 2); + } + + /** + * Add a catchpoint with a condition then remove the condition + */ + @Test + public void updateCatchpoint_RemoveCondition() throws Throwable { + // Set a catchpoint with a condition + IBreakpointDMContext ref = setCatchpoint("throw", CONDITION_1, null); + + // Remove the condition + modifyBkptProperty(ref, MIBreakpoints.CONDITION, null); + + // Ensure the breakpoint service sees what we expect + IBreakpointDMContext[] breakpoints = getBreakpoints(fBreakpointsDmc); + assertEquals("Breakpoints service reports unexpected number of breakpoints", 1, breakpoints.length); + MIBreakpointDMData bkpt_svc = (MIBreakpointDMData) getBreakpoint(breakpoints[0]); + assertEquals("Incorrect breakpoint condition", CONDITION_NONE, bkpt_svc.getCondition()); + + resumeAndExpectBkptHit(bkpt_svc.getNumber(), 0); + } + + /** + * Add a catchpoint with a condition then modify the condition + */ + @Test + public void updateCatchpoint_ModifyCondition() throws Throwable { + // Set the catchpoint with a particular condition + IBreakpointDMContext ref = setCatchpoint("throw", CONDITION_1, null); + + // Modify the catchpoint to have a different condition + modifyBkptProperty(ref, MIBreakpoints.CONDITION, CONDITION_2); + + // Ensure the breakpoint service sees what we expect + IBreakpointDMContext[] breakpoints = getBreakpoints(fBreakpointsDmc); + assertEquals("Breakpoints service reports unexpected number of breakpoints", 1, breakpoints.length); + MIBreakpointDMData bkpt_svc = (MIBreakpointDMData) getBreakpoint(breakpoints[0]); + assertEquals("Incorrect breakpoint condition", CONDITION_2, bkpt_svc.getCondition()); + + resumeAndExpectBkptHit(bkpt_svc.getNumber(), 4); + } + + /** + * Set a throw and a catch catchpoint while the target is stopped, with a + * condition that will never resolve to true. The program should breeze + * through the loop that throws and catches C++ exceptions and then enter a + * sleep call. During the sleep call, Then remove the throw catchpoint while + * the target is running and ensure the catch catchpoint is hit. + * + */ + @Test + public void updateCatchpoint_WhileTargetRunning1() throws Throwable { + updateCatchpoint_WhileTargetRunning(true); + } + + /** + * Variant that removes the catch catchpoint instead of the throw one. See + * {@link #removeCatchpoint_WhileTargetRunning1()} + */ + @Test + public void updateCatchpoint_WhileTargetRunning2() throws Throwable { + updateCatchpoint_WhileTargetRunning(false); + } + + /** + * Set catch and throw catchpoints with a condition that will never be true, + * and also a line breakpoint at the sleep call, then resume the target. The + * initial part of the program has a loop that throws and catches C++ + * exceptions. We should breeze on past that loop because of the invalid + * catchpoint conditions and end up stopped at the line breakpoint. We + * resume the target. The program makes a sleep call (two seconds), during + * which time we attempt to update the condition of one of the catchpooints + * to something that will resolve to true. After the sleep, the program does + * one more round of throwing and catching. Ensure that the target stops and + * that it's because of the catchpoint we updated. + * + * @param removeThrow + * if true, we update the throw catchpoint, otherwise the catch + * one. + */ + private void updateCatchpoint_WhileTargetRunning(boolean modifyThrow) throws Throwable { + // Set a line breakpoint at the sleep() call. + IBreakpointDMContext refLineBkpt = setLineBreakpoint(LINE_NUMBER_SLEEP_CALL); + + // Set the two catchpoints + IBreakpointDMContext refThrow = setCatchpoint("throw", CONDITION_NEVER_MET, null); + IBreakpointDMContext refCatch = setCatchpoint("catch", CONDITION_NEVER_MET, null); + + // Run the program. The catchpoints should not get hit, but the line + // breakpoint should + clearEventCounters(); + SyncUtil.resume(); + waitForBreakpointEvent(1); + assertEquals("Unexpected number of breakpoint events", 1, totalBreakpointEventsCount()); + assertEquals("Unexpected number of breakpoint-added events", 1, getBreakpointEventCount(BP_HIT)); + MIBreakpointDMData lineBkpt = (MIBreakpointDMData) getBreakpoint(refLineBkpt); + assertEquals("Target stopped as expected, but the responsible breakpoint was not the expected one", lineBkpt.getNumber(), fBreakpointRef); + clearEventCounters(); + + // Resume the program. It will make a two second sleep() call, during which time... + SyncUtil.resume(); + + // ...we modify one of the catchpoints's condition + modifyBkptProperty(modifyThrow ? refThrow : refCatch, MIBreakpoints.CONDITION, CONDITION_ALWAYS_MET); + + // After the sleep, the test app throws a C++ exception and catches it. + // So, the catchpoint whose condition we modified should get hit + MIStoppedEvent event = waitForBreakpointEventsAfterBreakpointOperationWhileTargetRunning(true, 2); + assertTrue("stopped event is of an unexpected type: " + event.getClass().getName(), event instanceof MIBreakpointHitEvent); + MIBreakpointHitEvent bkptHitEvent = (MIBreakpointHitEvent)event; + MIBreakpointDMData bkptUpdated = (MIBreakpointDMData) getBreakpoint(modifyThrow ? refThrow : refCatch); + assertEquals("Target stopped as expected, but the responsible breakpoint was not the expected one", bkptUpdated.getNumber(), bkptHitEvent.getNumber()); + } + + @Test + public void updateCatchpoint_AddCount() throws Throwable { + // Set the catchpoint with a particular condition + IBreakpointDMContext ref = setCatchpoint("throw", null, null); + + // Modify the catchpoint to have a different condition + modifyBkptProperty(ref, MIBreakpoints.IGNORE_COUNT, 3); + + // Ensure the breakpoint service sees what we expect + IBreakpointDMContext[] breakpoints = getBreakpoints(fBreakpointsDmc); + assertEquals("Breakpoints service reports unexpected number of breakpoints", 1, breakpoints.length); + MIBreakpointDMData bkpt_svc = (MIBreakpointDMData) getBreakpoint(breakpoints[0]); + assertEquals("Incorrect breakpoint condition", 3, bkpt_svc.getIgnoreCount()); + + // Resume and validate catchpoint hit + resumeAndExpectBkptHit(bkpt_svc.getNumber(), 3); + } + + /** + * Set a catchpoint with an ignore count, then remove the ignore count. + */ + @Test + public void updateCatchpoint_RemoveCount() throws Throwable { + // Set the catchpoint with a particular condition + IBreakpointDMContext ref = setCatchpoint("throw", null, 3); + + // Modify the catchpoint to not have an ignore count + modifyBkptProperty(ref, MIBreakpoints.IGNORE_COUNT, null); + + // Ensure the breakpoint service sees what we expect + IBreakpointDMContext[] breakpoints = getBreakpoints(fBreakpointsDmc); + assertEquals("Breakpoints service reports unexpected number of breakpoints", 1, breakpoints.length); + MIBreakpointDMData bkpt_svc = (MIBreakpointDMData) getBreakpoint(breakpoints[0]); + assertEquals("Incorrect breakpoint ignore count", 0, bkpt_svc.getIgnoreCount()); + + // Resume and validate catchpoint hit + resumeAndExpectBkptHit(bkpt_svc.getNumber(), 0); + } + + /** + * Set a catchpoint with a particular ignore count and then update the + * catchpoint to have a different ignore count + */ + @Test + public void updateCatchpoint_ModifyCount() throws Throwable { + // Set the catchpoint with a particular ignore count + IBreakpointDMContext ref = setCatchpoint("throw", null, 3); + + // Modify the catchpoint to have a different ignore count + modifyBkptProperty(ref, MIBreakpoints.IGNORE_COUNT, 5); + + // Ensure the breakpoint service sees what we expect + IBreakpointDMContext[] breakpoints = getBreakpoints(fBreakpointsDmc); + assertEquals("Breakpoints service reports unexpected number of breakpoints", 1, breakpoints.length); + MIBreakpointDMData bkpt_svc = (MIBreakpointDMData) getBreakpoint(breakpoints[0]); + assertEquals("Incorrect breakpoint ignore count", 5, bkpt_svc.getIgnoreCount()); + + // Resume and validate catchpoint hit + resumeAndExpectBkptHit(bkpt_svc.getNumber(), 5); + } + + /** + * Set two catchpoints. Disable one and ensure it isn't hit. Enable it and + * ensure it is hit. + */ + @Test + public void updateCatchpoint_Disable() throws Throwable { + + // Set the catchpoints + IBreakpointDMContext refThrow = setCatchpoint("throw", null, null); + IBreakpointDMContext refCatch = setCatchpoint("catch", null, null); + + // Disable the throw catchpoint + modifyBkptProperty(refThrow, MIBreakpoints.IS_ENABLED, false); + + // Ensure the breakpoint service sees what we expect + IBreakpointDMContext[] breakpoints = getBreakpoints(fBreakpointsDmc); + assertEquals("Breakpoints service reports unexpected number of breakpoints", 2, breakpoints.length); + int throwCatchpointNumber = ((MIBreakpointDMData)getBreakpoint(refThrow)).getNumber(); + for (IBreakpointDMContext bkpt : breakpoints) { + MIBreakpointDMData bkpt_svc = (MIBreakpointDMData) getBreakpoint(bkpt); + assertEquals("Incorrect breakpoint condition", throwCatchpointNumber != bkpt_svc.getNumber(), bkpt_svc.isEnabled()); + } + + // Resume the target. Should miss the throw catchpoint and stop at the catch one + int catchCatchpointNumber = ((MIBreakpointDMData)getBreakpoint(refCatch)).getNumber(); + resumeAndExpectBkptHit(catchCatchpointNumber, null); + + // Ee-enable the throw catchpoint + modifyBkptProperty(refThrow, MIBreakpoints.IS_ENABLED, true); + + // Ensure the breakpoint service sees what we expect + breakpoints = getBreakpoints(fBreakpointsDmc); + assertEquals("Breakpoints service reports unexpected number of breakpoints", 2, breakpoints.length); + for (IBreakpointDMContext bkpt : breakpoints) { + MIBreakpointDMData bkpt_svc = (MIBreakpointDMData) getBreakpoint(bkpt); + assertEquals("Incorrect breakpoint condition", true, bkpt_svc.isEnabled()); + } + + // Resume the target. Should miss the throw catchpoint and stop at the catch one + resumeAndExpectBkptHit(throwCatchpointNumber, null); + } + + /** + * This method allows the while-target-running tests to side-step a bug in + * Windows gdb. To perform a breakpoint operation while the target is + * running, the DSF debugger temporarily suspends the target (gdb) with a + * SIGINT, carries out the operation, and then resumes the target. On + * Windows gdbs, there's a race condition problem with the SIGINT handling + * resulting in the target suspending for a reason other than the SIGINT. + * Resuming the target, though, will result in the target suspending + * again...this time for the SIGINT. + * + * See http://sourceware.org/ml/gdb-patches/2008-05/msg00264.html + * + * I've looked at the latest gdb sources and the problem has not yet been + * fixed. Search "FIXME: brobecker/2008-05-20" in windows-nat.c + * + * @param waitForStop + * indicates whether we should first wait for the target to stop. + * Otherwise we just wait until [eventCount] number of breakpoint + * events have occurred. If this arg is true, we return the + * MIStoppedEvent. + * @param eventCount + * the number of breakpoint events to wait for + * @return the stopped event, if [waitForStop] is true; otherwise null + * @throws Throwable + */ + MIStoppedEvent waitForBreakpointEventsAfterBreakpointOperationWhileTargetRunning(boolean waitForStop, int eventCount) throws Throwable { + MIStoppedEvent event = null; + if (waitForStop) { + // The target program should be in the middle of a one second + // sleep() call. Most tests involve setting a breakpoint at + // some line after the sleep call. Allow up to three seconds + // for the sleep() call to complete and the breakpoint to be + // hit. + event = SyncUtil.waitForStop(3000); + } + + if (Platform.getOS().equals(Platform.OS_WIN32)) { + // Normally, we would now give a couple of seconds for any + // additional expected breakpoint events to occur after the target's + // sleep call returns and fail the test if they don't. But the + // Windws gdb bug may have gotten in the way. If it has, the target + // is in an unexpected suspended state instead of running or stopped + // at the test breakpoint. We just don't know. So, we wait two + // seconds in case everything is actually behaving normally, since + // that's how patient waitForBreakpointEvent() is. If waitForStop is + // false, we need to allow enough time for the sleep() to return AND + // any subsequent breakpoint events to occur. + Thread.sleep(TestsPlugin.massageTimeout(waitForStop ? 2000 : 5000)); + int eventsReceived = totalBreakpointEventsCount(); + assertTrue("We should have gotten at least one breakpoint event", eventsReceived >= 1); + if (eventsReceived < eventCount) { + // It's likely we ran into the gdb bug. We can ignore it (since + // it's out of our control) and proceed to test whether the + // breakpoint operation was properly carried out. + System.out.println("Performing an additional resume to work around Windows gdb bug"); + SyncUtil.resume(); + if (waitForStop) { + event = SyncUtil.waitForStop(4000); + } + } + } + + // OK, at this point, all the expected breakpoint events should have + // occurred, or we really have a problem (unrelated to the gdb bug) + waitForBreakpointEvent(eventCount); + + return event; + } + + /** + * Set a line breakpoint and validate it was set correctly. + * + * @param lineNumber + * the line where to set the breakpoint + * @return the breakpoint context + */ + private IBreakpointDMContext setLineBreakpoint(int lineNumber) throws Exception { + clearEventCounters(); + + IBreakpointDMContext[] bkptsBefore = getBreakpoints(fBreakpointsDmc); + + // Set the breakpoint + Map breakpoint = new HashMap(); + breakpoint.put(MIBreakpoints.BREAKPOINT_TYPE, MIBreakpoints.BREAKPOINT); + breakpoint.put(MIBreakpoints.FILE_NAME, SOURCE_FILE); + breakpoint.put(MIBreakpoints.LINE_NUMBER, lineNumber); + IBreakpointDMContext refLineBkpt = insertBreakpoint(fBreakpointsDmc, breakpoint); + assertTrue(fWait.getMessage(), fWait.isOK()); + + // Ensure that right breakpoint events were received. + waitForBreakpointEvent(1); + assertEquals("Unexpected number of breakpoint events", 1, totalBreakpointEventsCount()); + assertEquals("Unexpected number of breakpoint-added events", 1, getBreakpointEventCount(BP_ADDED)); + + // Ensure the breakpoint service sees what we expect + List bkptsAfter = new LinkedList(Arrays.asList(getBreakpoints(fBreakpointsDmc))); + assertEquals("Breakpoints service reports unexpected number of breakpoints", bkptsBefore.length + 1, bkptsAfter.size()); + + ListIterator iter = bkptsAfter.listIterator(); + while (iter.hasNext()) { + IBreakpointDMContext bkptAfter = iter.next(); + boolean found = false; + for (IBreakpointDMContext bkptBefore : bkptsBefore) { + if (bkptAfter.equals(bkptBefore)) { + assertFalse("shouldn't have been more than one match", found); + iter.remove(); + found = true; + } + } + } + assertEquals("All but the new bkpt should have been removed from bkptsAfter", bkptsAfter.size(), 1); + + return refLineBkpt; + } + + /** + * Set a catchpoint for the given event and validate it was set correctly + * + * @param event + * the event; the gdb keyword for it (e.g., "catch", "throw") + * @param condition + * an optional condition, or null to indicate not condition + * @param ignoreCount + * an optional ignore count, or null to indicate no ignore count + * @return the breakpoint context + */ + private IBreakpointDMContext setCatchpoint(String event, String condition, Integer ignoreCount) throws Exception { + clearEventCounters(); + + IBreakpointDMContext[] bkptsBefore = getBreakpoints(fBreakpointsDmc); + + // set the catchpoint + Map bkptsProps = new HashMap(); + bkptsProps.put(MIBreakpoints.BREAKPOINT_TYPE, MIBreakpoints.CATCHPOINT); + bkptsProps.put(MIBreakpoints.CATCHPOINT_TYPE, event); + if (condition != null) { + bkptsProps.put(MIBreakpoints.CONDITION, condition); + } + if (ignoreCount != null) { + bkptsProps.put(MIBreakpoints.IGNORE_COUNT, ignoreCount); + } + IBreakpointDMContext refCatchpoint = insertBreakpoint(fBreakpointsDmc, bkptsProps); + assertTrue(fWait.getMessage(), fWait.isOK()); + + // Ensure that right breakpoint events were received. + waitForBreakpointEvent(1); + assertEquals("Unexpected number of breakpoint events", 1, totalBreakpointEventsCount()); + assertEquals("Unexpected number of breakpoint-added events", 1, getBreakpointEventCount(BP_ADDED)); + + // Ensure the breakpoint service sees what we expect. Ask the breakpoint + // service for the list of breakpoint against and make sure it differs + // only by the newly added one + List bkptsAfter = new LinkedList(Arrays.asList(getBreakpoints(fBreakpointsDmc))); + assertEquals("Breakpoints service reports unexpected number of breakpoints", bkptsBefore.length + 1, bkptsAfter.size()); + ListIterator iter = bkptsAfter.listIterator(); + while (iter.hasNext()) { + IBreakpointDMContext bkptAfter = iter.next(); + boolean found = false; + for (IBreakpointDMContext bkptBefore : bkptsBefore) { + if (bkptAfter.equals(bkptBefore)) { + assertFalse("shouldn't have been more than one match", found); + iter.remove(); + found = true; + } + } + } + assertEquals("All but the new bkpt should have been removed from bkptsAfter", bkptsAfter.size(), 1); + + MIBreakpointDMData bkpt_set = (MIBreakpointDMData) getBreakpoint(refCatchpoint); + MIBreakpointDMData bkpt_svc = (MIBreakpointDMData) getBreakpoint(bkptsAfter.get(0)); + + assertEquals(bkpt_set.getNumber(), bkpt_svc.getNumber()); + assertEquals("Incorrect breakpoint condition", condition != null ? condition : CONDITION_NONE, bkpt_svc.getCondition()); + assertEquals("Incorrect breakpoint ignore count", ignoreCount != null ? ignoreCount : 0, bkpt_svc.getIgnoreCount()); + + return refCatchpoint; + } + + /** + * Resume the target and expect it to be stopped by the given breakpoint. + * Optionally, check that the program's single global int variable has the + * given value. + * + * @param bkptNumber + * the GDB breakpoint number + * @param expectedVarValue + * the expected value of the program variable; can be null to + * indicate a check isn't wanted + * @return the stoppped event + */ + private MIStoppedEvent resumeAndExpectBkptHit(int bkptNumber, Integer expectedVarValue) throws Throwable { + // Resume the target. The throw catchpoint should get hit. + clearEventCounters(); + MIStoppedEvent event = SyncUtil.resumeUntilStopped(); + + // Ensure the right breakpoint events were received + waitForBreakpointEvent(1); + assertEquals("Unexpected number of breakpoint events", 1, totalBreakpointEventsCount()); + assertEquals("Unexpected type of breakpoint event", 1, getBreakpointEventCount(BP_HIT)); + + // Ensure the target stopped because of the throw catchpoint + assertEquals("Target stopped as expected, but the responsible breakpoint was not the expected one", bkptNumber, fBreakpointRef); + + if (expectedVarValue != null) { + IFrameDMContext frameDmc = SyncUtil.getStackFrame(event.getDMContext(), 0); + assertEquals("program variable has unexpected value", expectedVarValue.intValue(), evaluateExpression(frameDmc, CONDITION_VAR).intValue()); + } + return event; + } + + /** + * Modify a single property of a single breakpoint and validate that a + * breakpoint updated event occurs + */ + private void modifyBkptProperty(IBreakpointDMContext bkptRef, String property, Object value) throws Throwable { + // Modify the catchpoint to not have an ignore count + clearEventCounters(); + Map bkptProps = new HashMap(); + bkptProps.put(property, value); + updateBreakpoint(bkptRef, bkptProps); + assertTrue(fWait.getMessage(), fWait.isOK()); + + // Ensure that right breakpoint events were received + waitForBreakpointEvent(1); + assertEquals("Unexpected number of breakpoint events", 1, totalBreakpointEventsCount()); + assertEquals("Unexpected number of breakpoint added events", 1, getBreakpointEventCount(BP_UPDATED)); + } + +} diff --git a/dsf/org.eclipse.cdt.dsf.ui/src/org/eclipse/cdt/dsf/debug/ui/viewmodel/launch/ExecutionContextLabelText.java b/dsf/org.eclipse.cdt.dsf.ui/src/org/eclipse/cdt/dsf/debug/ui/viewmodel/launch/ExecutionContextLabelText.java index 8750303b51e..540f45ac7d6 100644 --- a/dsf/org.eclipse.cdt.dsf.ui/src/org/eclipse/cdt/dsf/debug/ui/viewmodel/launch/ExecutionContextLabelText.java +++ b/dsf/org.eclipse.cdt.dsf.ui/src/org/eclipse/cdt/dsf/debug/ui/viewmodel/launch/ExecutionContextLabelText.java @@ -72,7 +72,12 @@ public class ExecutionContextLabelText extends LabelText { reasonLabel = MessagesForLaunchVM.State_change_reason__User_request__label; } else if (StateChangeReason.WATCHPOINT.name().equals(reason)) { reasonLabel = MessagesForLaunchVM.State_change_reason__Watchpoint__label; + } else if (StateChangeReason.EVENT_BREAKPOINT.name().equals(reason)) { + reasonLabel = MessagesForLaunchVM.State_change_reason__EventBreakpoint__label; + } else { + assert false : "unexpected state change reason: " + reason; //$NON-NLS-1$ } + return reasonLabel; } else if ( ILaunchVMConstants.PROP_STATE_CHANGE_DETAILS.equals(propertyName) ) { return properties.get(propertyName); diff --git a/dsf/org.eclipse.cdt.dsf.ui/src/org/eclipse/cdt/dsf/debug/ui/viewmodel/launch/MessagesForLaunchVM.java b/dsf/org.eclipse.cdt.dsf.ui/src/org/eclipse/cdt/dsf/debug/ui/viewmodel/launch/MessagesForLaunchVM.java index 0a76995a03f..0c9a4a4f4bd 100644 --- a/dsf/org.eclipse.cdt.dsf.ui/src/org/eclipse/cdt/dsf/debug/ui/viewmodel/launch/MessagesForLaunchVM.java +++ b/dsf/org.eclipse.cdt.dsf.ui/src/org/eclipse/cdt/dsf/debug/ui/viewmodel/launch/MessagesForLaunchVM.java @@ -51,4 +51,5 @@ public class MessagesForLaunchVM extends NLS { public static String State_change_reason__Shared_lib__label; public static String State_change_reason__Error__label; public static String State_change_reason__Evaluation__label; + public static String State_change_reason__EventBreakpoint__label; } diff --git a/dsf/org.eclipse.cdt.dsf.ui/src/org/eclipse/cdt/dsf/debug/ui/viewmodel/launch/messages.properties b/dsf/org.eclipse.cdt.dsf.ui/src/org/eclipse/cdt/dsf/debug/ui/viewmodel/launch/messages.properties index 7cef5597a9a..b41bebc7020 100644 --- a/dsf/org.eclipse.cdt.dsf.ui/src/org/eclipse/cdt/dsf/debug/ui/viewmodel/launch/messages.properties +++ b/dsf/org.eclipse.cdt.dsf.ui/src/org/eclipse/cdt/dsf/debug/ui/viewmodel/launch/messages.properties @@ -66,4 +66,5 @@ State_change_reason__Watchpoint__label = Watchpoint State_change_reason__Signal__label = Signal State_change_reason__Shared_lib__label = Shared Library State_change_reason__Error__label = Error -State_change_reason__Evaluation__label = Evaluation \ No newline at end of file +State_change_reason__Evaluation__label = Evaluation +State_change_reason__EventBreakpoint__label = Event Breakpoint \ No newline at end of file diff --git a/dsf/org.eclipse.cdt.dsf/src/org/eclipse/cdt/dsf/debug/service/IRunControl.java b/dsf/org.eclipse.cdt.dsf/src/org/eclipse/cdt/dsf/debug/service/IRunControl.java index 5fa6ebaa8c5..5abb794c252 100644 --- a/dsf/org.eclipse.cdt.dsf/src/org/eclipse/cdt/dsf/debug/service/IRunControl.java +++ b/dsf/org.eclipse.cdt.dsf/src/org/eclipse/cdt/dsf/debug/service/IRunControl.java @@ -46,7 +46,11 @@ public interface IRunControl extends IDsfService public interface IContainerDMContext extends IExecutionDMContext {} /** Flag indicating reason context state change. */ - public enum StateChangeReason { UNKNOWN, USER_REQUEST, STEP, BREAKPOINT, EXCEPTION, CONTAINER, WATCHPOINT, SIGNAL, SHAREDLIB, ERROR, EVALUATION }; + public enum StateChangeReason { + UNKNOWN, USER_REQUEST, STEP, BREAKPOINT, EXCEPTION, CONTAINER, WATCHPOINT, SIGNAL, SHAREDLIB, ERROR, EVALUATION, + + /** @since 2.1 */ + EVENT_BREAKPOINT }; /** * Indicates that the given thread has suspended.