execute();
}
diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/BreakpointActionSet.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/BreakpointActionSet.java
index db6f9761ba..7eae6d3a28 100644
--- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/BreakpointActionSet.java
+++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/BreakpointActionSet.java
@@ -20,62 +20,122 @@ import java.util.concurrent.CompletableFuture;
import ghidra.async.AsyncFence;
import ghidra.dbg.target.*;
+import ghidra.trace.model.breakpoint.TraceBreakpoint;
/**
- * A de-duplicated collection of target breakpoint actions necessary to implement a logical
- * breakpoint action.
+ * A de-duplicated collection of breakpoint action items necessary to implement a logical breakpoint
+ * action.
+ *
+ *
+ * This will de-duplicate action items, but it does not check them for sanity. For example, deleting
+ * a breakpoint then enabling it. Typically, all the items are the same type, so such sanity checks
+ * are not necessary.
*/
public class BreakpointActionSet extends LinkedHashSet {
- public EnableBreakpointActionItem planEnable(TargetBreakpointLocation loc) {
+ /**
+ * Add an item to enable a target breakpoint
+ *
+ * @param loc the target breakpoint
+ * @return the added item
+ */
+ public EnableTargetBreakpointActionItem planEnableTarget(TargetBreakpointLocation loc) {
if (loc instanceof TargetTogglable) {
- EnableBreakpointActionItem action =
- new EnableBreakpointActionItem((TargetTogglable) loc);
+ EnableTargetBreakpointActionItem action =
+ new EnableTargetBreakpointActionItem((TargetTogglable) loc);
add(action);
return action;
}
TargetBreakpointSpec spec = loc.getSpecification();
if (spec instanceof TargetTogglable) {
- EnableBreakpointActionItem action = new EnableBreakpointActionItem(spec);
+ EnableTargetBreakpointActionItem action = new EnableTargetBreakpointActionItem(spec);
add(action);
return action;
}
return null;
}
- public DisableBreakpointActionItem planDisable(TargetBreakpointLocation loc) {
+ /**
+ * Add an item to enable an emulated breakpoint
+ *
+ * @param bpt the trace breakpoint
+ * @return the added item
+ */
+ public EnableEmuBreakpointActionItem planEnableEmu(TraceBreakpoint bpt) {
+ EnableEmuBreakpointActionItem action = new EnableEmuBreakpointActionItem(bpt);
+ add(action);
+ return action;
+ }
+
+ /**
+ * Add an item to disable a target breakpoint
+ *
+ * @param loc the target breakpoint
+ * @return the added item
+ */
+ public DisableTargetBreakpointActionItem planDisableTarget(TargetBreakpointLocation loc) {
if (loc instanceof TargetTogglable) {
- DisableBreakpointActionItem action =
- new DisableBreakpointActionItem((TargetTogglable) loc);
+ DisableTargetBreakpointActionItem action =
+ new DisableTargetBreakpointActionItem((TargetTogglable) loc);
add(action);
return action;
}
TargetBreakpointSpec spec = loc.getSpecification();
if (spec instanceof TargetTogglable) {
- DisableBreakpointActionItem action = new DisableBreakpointActionItem(spec);
+ DisableTargetBreakpointActionItem action = new DisableTargetBreakpointActionItem(spec);
add(action);
return action;
}
return null;
}
- public DeleteBreakpointActionItem planDelete(TargetBreakpointLocation loc) {
+ /**
+ * Add an item to disable an emulated breakpoint
+ *
+ * @param bpt the trace breakpoint
+ * @return the added item
+ */
+ public DisableEmuBreakpointActionItem planDisableEmu(TraceBreakpoint bpt) {
+ DisableEmuBreakpointActionItem action = new DisableEmuBreakpointActionItem(bpt);
+ add(action);
+ return action;
+ }
+
+ /**
+ * Add an item to delete a target breakpoint
+ *
+ * @param loc the target breakpoint
+ * @return the added item
+ */
+ public DeleteTargetBreakpointActionItem planDeleteTarget(TargetBreakpointLocation loc) {
if (loc instanceof TargetDeletable) {
- DeleteBreakpointActionItem action =
- new DeleteBreakpointActionItem((TargetDeletable) loc);
+ DeleteTargetBreakpointActionItem action =
+ new DeleteTargetBreakpointActionItem((TargetDeletable) loc);
add(action);
return action;
}
TargetBreakpointSpec spec = loc.getSpecification();
if (spec instanceof TargetTogglable) {
- DeleteBreakpointActionItem action =
- new DeleteBreakpointActionItem((TargetDeletable) spec);
+ DeleteTargetBreakpointActionItem action =
+ new DeleteTargetBreakpointActionItem((TargetDeletable) spec);
add(action);
return action;
}
return null;
}
+ /**
+ * Add an item to delete an emulated breakpoint
+ *
+ * @param bpt the trace breakpoint
+ * @return the added item
+ */
+ public DeleteEmuBreakpointActionItem planDeleteEmu(TraceBreakpoint bpt) {
+ DeleteEmuBreakpointActionItem action = new DeleteEmuBreakpointActionItem(bpt);
+ add(action);
+ return action;
+ }
+
/**
* Carry out the actions in the order they were added
*
diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/DebuggerLogicalBreakpointServicePlugin.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/DebuggerLogicalBreakpointServicePlugin.java
index 5418a3d6dd..920aedec03 100644
--- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/DebuggerLogicalBreakpointServicePlugin.java
+++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/DebuggerLogicalBreakpointServicePlugin.java
@@ -27,11 +27,11 @@ import generic.CatenatedCollection;
import ghidra.app.events.ProgramClosedPluginEvent;
import ghidra.app.events.ProgramOpenedPluginEvent;
import ghidra.app.plugin.PluginCategoryNames;
+import ghidra.app.plugin.core.debug.DebuggerCoordinates;
import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
-import ghidra.app.plugin.core.debug.event.TraceClosedPluginEvent;
-import ghidra.app.plugin.core.debug.event.TraceOpenedPluginEvent;
-import ghidra.app.plugin.core.debug.service.breakpoint.LogicalBreakpointInternal.ProgramBreakpoint;
+import ghidra.app.plugin.core.debug.event.*;
import ghidra.app.services.*;
+import ghidra.app.services.DebuggerStateEditingService.StateEditingModeChangeListener;
import ghidra.app.services.LogicalBreakpoint.State;
import ghidra.async.SwingExecutorService;
import ghidra.dbg.target.TargetBreakpointLocation;
@@ -41,8 +41,7 @@ import ghidra.framework.model.*;
import ghidra.framework.plugintool.*;
import ghidra.framework.plugintool.annotation.AutoServiceConsumed;
import ghidra.framework.plugintool.util.PluginStatus;
-import ghidra.program.model.address.Address;
-import ghidra.program.model.address.AddressRange;
+import ghidra.program.model.address.*;
import ghidra.program.model.listing.*;
import ghidra.program.util.*;
import ghidra.trace.model.*;
@@ -65,6 +64,8 @@ import ghidra.util.datastruct.ListenerSet;
ProgramOpenedPluginEvent.class,
ProgramClosedPluginEvent.class,
TraceOpenedPluginEvent.class,
+ TraceActivatedPluginEvent.class,
+ TraceInactiveCoordinatesPluginEvent.class,
TraceClosedPluginEvent.class,
},
servicesRequired = {
@@ -186,6 +187,13 @@ public class DebuggerLogicalBreakpointServicePlugin extends Plugin
}
}
+ protected class TrackModesListener implements StateEditingModeChangeListener {
+ @Override
+ public void modeChanged(Trace trace, StateEditingMode mode) {
+ processChange(c -> evtModeChanged(c, trace), "modeChanged");
+ }
+ }
+
private class TraceBreakpointsListener extends TraceDomainObjectListener {
private final InfoPerTrace info;
private ChangeCollector c;
@@ -218,29 +226,28 @@ public class DebuggerLogicalBreakpointServicePlugin extends Plugin
}
private void breakpointAdded(TraceBreakpoint tb) {
- if (!tb.getLifespan().contains(info.recorder.getSnap())) {
- // NOTE: User/script probably added historical breakpoint
+ if (!tb.getLifespan().contains(info.snap)) {
return;
}
try {
info.trackTraceBreakpoint(c.a, tb, false);
}
catch (TrackedTooSoonException e) {
- Msg.info(this, "Ignoring " + tb +
- " added until service has finished loading its trace");
+ Msg.info(this,
+ "Ignoring " + tb + " added until service has finished loading its trace");
}
}
private void breakpointChanged(TraceBreakpoint tb) {
- if (!tb.getLifespan().contains(info.recorder.getSnap())) {
+ if (!tb.getLifespan().contains(info.snap)) {
return;
}
try {
info.trackTraceBreakpoint(c.a, tb, true);
}
catch (TrackedTooSoonException e) {
- Msg.info(this, "Ignoring " + tb +
- " changed until service has finished loading its trace");
+ Msg.info(this,
+ "Ignoring " + tb + " changed until service has finished loading its trace");
}
catch (NoSuchElementException e) {
// TODO: This catch clause should not be necessary.
@@ -252,8 +259,8 @@ public class DebuggerLogicalBreakpointServicePlugin extends Plugin
private void breakpointLifespanChanged(TraceAddressSpace spaceIsNull,
TraceBreakpoint tb, Lifespan oldSpan, Lifespan newSpan) {
// NOTE: User/script probably modified historical breakpoint
- boolean isInOld = oldSpan.contains(info.recorder.getSnap());
- boolean isInNew = newSpan.contains(info.recorder.getSnap());
+ boolean isInOld = oldSpan.contains(info.snap);
+ boolean isInNew = newSpan.contains(info.snap);
if (isInOld == isInNew) {
return;
}
@@ -272,11 +279,7 @@ public class DebuggerLogicalBreakpointServicePlugin extends Plugin
}
private void breakpointDeleted(TraceBreakpoint tb) {
- if (!tb.getLifespan().contains(info.recorder.getSnap())) {
- // NOTE: User/script probably removed historical breakpoint
- // assert false;
- return;
- }
+ // Could check snap, but might as well just be sure it's gone
info.forgetTraceBreakpoint(c.r, tb);
}
}
@@ -375,8 +378,6 @@ public class DebuggerLogicalBreakpointServicePlugin extends Plugin
Address address, TraceBreakpoint tb) {
Set set = breakpointsByAddress.get(address);
if (set == null) {
- Msg.warn(this, "Breakpoint to remove is not present: " + tb + ", trace=" +
- tb.getTrace());
return null;
}
for (LogicalBreakpointInternal lb : Set.copyOf(set)) {
@@ -393,8 +394,6 @@ public class DebuggerLogicalBreakpointServicePlugin extends Plugin
return lb;
}
}
- Msg.warn(this, "Breakpoint to remove is not present: " + tb + ", trace=" +
- tb.getTrace());
return null;
}
@@ -451,22 +450,41 @@ public class DebuggerLogicalBreakpointServicePlugin extends Plugin
}
protected class InfoPerTrace extends AbstractInfo {
- final TraceRecorder recorder;
final Trace trace;
final TraceBreakpointsListener breakpointListener;
- public InfoPerTrace(TraceRecorder recorder) {
- this.recorder = recorder;
- this.trace = recorder.getTrace();
+ TraceRecorder recorder;
+ long snap = -1;
+
+ public InfoPerTrace(Trace trace) {
+ this.trace = Objects.requireNonNull(trace);
this.breakpointListener = new TraceBreakpointsListener(this);
trace.addListener(breakpointListener);
}
+ protected void setRecorderAndSnap(TraceRecorder recorder, long snap, ChangeCollector c) {
+ if (this.recorder == recorder && this.snap == snap) {
+ return;
+ }
+ this.recorder = recorder;
+ this.snap = snap;
+
+ for (InfoPerProgram info : programInfos.values()) {
+ // This is heavy, but not sure a better method
+ // Could examine mapping service to narrow down relevant programs...
+ info.reloadBreakpoints(c);
+ }
+ reloadBreakpoints(c);
+ }
+
@Override
protected LogicalBreakpointInternal createLogicalBreakpoint(Address address, long length,
Collection kinds) {
- return new LoneLogicalBreakpoint(recorder, address, length, kinds);
+ LoneLogicalBreakpoint lb =
+ new LoneLogicalBreakpoint(tool, trace, address, length, kinds);
+ lb.setRecorder(trace, recorder);
+ return lb;
}
@Override
@@ -477,33 +495,45 @@ public class DebuggerLogicalBreakpointServicePlugin extends Plugin
protected void reloadBreakpoints(ChangeCollector c) {
forgetTraceInvalidBreakpoints(c.r);
- trackTraceLiveBreakpoints(c.a);
+ trackTraceBreakpoints(c.a);
}
protected void forgetAllBreakpoints(RemoveCollector r) {
- Collection live = new ArrayList<>();
+ Collection toForget = new ArrayList<>();
for (AddressRange range : trace.getBaseAddressFactory().getAddressSet()) {
- live.addAll(trace
+ toForget.addAll(trace
.getBreakpointManager()
- .getBreakpointsIntersecting(Lifespan.at(recorder.getSnap()), range));
+ .getBreakpointsIntersecting(Lifespan.ALL, range));
}
- for (TraceBreakpoint tb : live) {
+ for (TraceBreakpoint tb : toForget) {
forgetTraceBreakpoint(r, tb);
}
}
protected void forgetTraceInvalidBreakpoints(RemoveCollector r) {
- // Breakpoint can become invalid because it is itself invalid (deleted)
- // Or because the mapping to static space has changed or become invalid
-
+ /**
+ * Breakpoint can become invalid because it is itself invalid (deleted), because the
+ * snap has changed to where it's no longer present, because the mapping to static space
+ * has changed or become invalid, or because it has no live breakpoint in target mode.
+ */
+ StateEditingMode mode = getMode(trace);
for (Set set : List
.copyOf(breakpointsByAddress.values())) {
for (LogicalBreakpointInternal lb : Set.copyOf(set)) {
for (TraceBreakpoint tb : Set.copyOf(lb.getTraceBreakpoints(trace))) {
+ if (!mode.useEmulatedBreakpoints() &&
+ (recorder == null || recorder.getTargetBreakpoint(tb) == null)) {
+ forgetTraceBreakpoint(r, tb);
+ continue;
+ }
if (!trace.getBreakpointManager().getAllBreakpoints().contains(tb)) {
forgetTraceBreakpoint(r, tb);
continue;
}
+ if (!tb.getLifespan().contains(snap)) {
+ forgetTraceBreakpoint(r, tb);
+ continue;
+ }
ProgramLocation progLoc = computeStaticLocation(tb);
if (!Objects.equals(lb.getProgramLocation(), progLoc)) {
// NOTE: This can happen to Lone breakpoints.
@@ -516,14 +546,28 @@ public class DebuggerLogicalBreakpointServicePlugin extends Plugin
}
}
- protected void trackTraceLiveBreakpoints(AddCollector a) {
- Collection live = new ArrayList<>();
- for (AddressRange range : trace.getBaseAddressFactory().getAddressSet()) {
- live.addAll(trace
- .getBreakpointManager()
- .getBreakpointsIntersecting(Lifespan.at(recorder.getSnap()), range));
+ protected void trackTraceBreakpoints(AddCollector a) {
+ StateEditingMode mode = getMode(trace);
+ if (!mode.useEmulatedBreakpoints() && recorder == null) {
+ return;
}
- trackTraceBreakpoints(a, live);
+ Collection visible = new ArrayList<>();
+ for (AddressRange range : trace.getBaseAddressFactory().getAddressSet()) {
+ Collection extends TraceBreakpoint> breaks = trace
+ .getBreakpointManager()
+ .getBreakpointsIntersecting(Lifespan.at(snap), range);
+ if (mode.useEmulatedBreakpoints()) {
+ visible.addAll(breaks);
+ }
+ else {
+ for (TraceBreakpoint tb : breaks) {
+ if (recorder.getTargetBreakpoint(tb) != null) {
+ visible.add(tb);
+ }
+ }
+ }
+ }
+ trackTraceBreakpoints(a, visible);
}
protected void trackTraceBreakpoints(AddCollector a,
@@ -549,8 +593,8 @@ public class DebuggerLogicalBreakpointServicePlugin extends Plugin
*/
return null;
}
- return mappingService.getOpenMappedLocation(new DefaultTraceLocation(trace,
- null, Lifespan.at(recorder.getSnap()), tb.getMinAddress()));
+ return mappingService.getOpenMappedLocation(
+ new DefaultTraceLocation(trace, null, Lifespan.at(snap), tb.getMinAddress()));
}
protected void trackTraceBreakpoint(AddCollector a, TraceBreakpoint tb, boolean forceUpdate)
@@ -585,7 +629,7 @@ public class DebuggerLogicalBreakpointServicePlugin extends Plugin
// Must be shutting down
return null;
}
- return mappingService.getOpenMappedLocation(trace, loc, recorder.getSnap());
+ return mappingService.getOpenMappedLocation(trace, loc, snap);
}
}
@@ -606,7 +650,8 @@ public class DebuggerLogicalBreakpointServicePlugin extends Plugin
if (loc == null) {
continue;
}
- lb.setTraceAddress(ti.recorder, loc.getAddress());
+ lb.setTraceAddress(ti.trace, loc.getAddress());
+ lb.setRecorder(ti.trace, ti.recorder);
ti.breakpointsByAddress.computeIfAbsent(loc.getAddress(), __ -> new HashSet<>())
.add(lb);
}
@@ -616,7 +661,7 @@ public class DebuggerLogicalBreakpointServicePlugin extends Plugin
protected LogicalBreakpointInternal createLogicalBreakpoint(Address address, long length,
Collection kinds) {
MappedLogicalBreakpoint lb =
- new MappedLogicalBreakpoint(program, address, length, kinds);
+ new MappedLogicalBreakpoint(tool, program, address, length, kinds);
mapTraceAddresses(lb);
return lb;
}
@@ -763,6 +808,8 @@ public class DebuggerLogicalBreakpointServicePlugin extends Plugin
private DebuggerTraceManagerService traceManager;
// @AutoServiceConsumed via method
private DebuggerStaticMappingService mappingService;
+ // @AutoServiceConsumed via method
+ private DebuggerStateEditingService editingService;
@SuppressWarnings("unused")
private final AutoService.Wiring autoServiceWiring;
@@ -772,6 +819,7 @@ public class DebuggerLogicalBreakpointServicePlugin extends Plugin
private final TrackRecordersListener recorderListener = new TrackRecordersListener();
private final TrackMappingsListener mappingListener = new TrackMappingsListener();
+ private final TrackModesListener modeListener = new TrackModesListener();
private final Map traceInfos = new HashMap<>();
private final Map programInfos = new HashMap<>();
@@ -832,6 +880,13 @@ public class DebuggerLogicalBreakpointServicePlugin extends Plugin
}
}
+ protected void evtModeChanged(ChangeCollector c, Trace trace) {
+ InfoPerTrace info = traceInfos.get(trace);
+ if (info != null) {
+ info.reloadBreakpoints(c);
+ }
+ }
+
protected void removeLogicalBreakpointGlobally(LogicalBreakpoint lb) {
ProgramLocation pLoc = lb.getProgramLocation();
if (pLoc != null) {
@@ -871,6 +926,17 @@ public class DebuggerLogicalBreakpointServicePlugin extends Plugin
}
}
+ @AutoServiceConsumed
+ public void setEditingService(DebuggerStateEditingService editingService) {
+ if (this.editingService != null) {
+ this.editingService.removeModeChangeListener(modeListener);
+ }
+ this.editingService = editingService;
+ if (this.editingService != null) {
+ this.editingService.addModeChangeListener(modeListener);
+ }
+ }
+
private void programOpened(Program program) {
processChange(c -> evtProgramOpened(c, program), "programOpened");
}
@@ -899,14 +965,13 @@ public class DebuggerLogicalBreakpointServicePlugin extends Plugin
// The mapping removals, if any, will clean up related traces
}
- private void doTrackTrace(ChangeCollector c, Trace trace, TraceRecorder recorder) {
- if (traceInfos.containsKey(trace)) {
- Msg.warn(this, "Already tracking trace breakpoints");
- return;
+ private void doTrackTrace(ChangeCollector c, Trace trace, TraceRecorder recorder, long snap) {
+ InfoPerTrace info = traceInfos.get(trace);
+ if (info == null) {
+ info = new InfoPerTrace(trace);
+ traceInfos.put(trace, info);
}
- InfoPerTrace info = new InfoPerTrace(recorder);
- traceInfos.put(trace, info);
- info.reloadBreakpoints(c);
+ info.setRecorderAndSnap(recorder, snap, c);
}
private void doUntrackTrace(ChangeCollector c, Trace trace) {
@@ -932,23 +997,35 @@ public class DebuggerLogicalBreakpointServicePlugin extends Plugin
if (!traceManager.getOpenTraces().contains(trace)) {
return;
}
- doTrackTrace(c, trace, recorder);
+ long snap = traceManager.getCurrentFor(trace).getSnap();
+ doTrackTrace(c, trace, recorder, snap);
}
private void evtTraceRecordingStopped(ChangeCollector c, TraceRecorder recorder) {
- doUntrackTrace(c, recorder.getTrace());
+ Trace trace = recorder.getTrace();
+ if (!traceManager.getOpenTraces().contains(trace)) {
+ return;
+ }
+ long snap = traceManager.getCurrentFor(trace).getSnap();
+ doTrackTrace(c, trace, null, snap);
}
private void traceOpened(Trace trace) {
processChange(c -> {
TraceRecorder recorder = modelService.getRecorder(trace);
- if (recorder == null) {
- return;
- }
- doTrackTrace(c, trace, recorder);
+ long snap = traceManager.getCurrentFor(trace).getSnap();
+ doTrackTrace(c, trace, recorder, snap);
}, "traceOpened");
}
+ private void traceSnapChanged(DebuggerCoordinates coordinates) {
+ if (coordinates.getTrace() == null) {
+ return;
+ }
+ processChange(c -> doTrackTrace(c, coordinates.getTrace(), coordinates.getRecorder(),
+ coordinates.getSnap()), "coordinatesActivated");
+ }
+
private void traceClosed(Trace trace) {
processChange(c -> doUntrackTrace(c, trace), "traceClosed");
}
@@ -1074,14 +1151,16 @@ public class DebuggerLogicalBreakpointServicePlugin extends Plugin
* had to re-implement here, anyway. The actual logical breakpoint is created by event
* processors.
*/
- MappedLogicalBreakpoint lb = new MappedLogicalBreakpoint(program, address, length, kinds);
+ MappedLogicalBreakpoint lb =
+ new MappedLogicalBreakpoint(tool, program, address, length, kinds);
synchronized (lock) {
for (InfoPerTrace ti : traceInfos.values()) {
TraceLocation loc = ti.toDynamicLocation(lb.getProgramLocation());
if (loc == null) {
continue;
}
- lb.setTraceAddress(ti.recorder, loc.getAddress());
+ lb.setTraceAddress(ti.trace, loc.getAddress());
+ lb.setRecorder(ti.trace, ti.recorder);
}
}
return lb;
@@ -1097,21 +1176,22 @@ public class DebuggerLogicalBreakpointServicePlugin extends Plugin
@Override
public CompletableFuture placeBreakpointAt(Trace trace, Address address, long length,
Collection kinds, String name) {
+ long snap = traceManager.getCurrentFor(trace).getSnap();
TraceRecorder recorder = modelService.getRecorder(trace);
- if (recorder == null) {
- throw new IllegalArgumentException("Given trace is not live");
- }
ProgramLocation staticLocation = mappingService.getOpenMappedLocation(
- new DefaultTraceLocation(trace, null, Lifespan.at(recorder.getSnap()), address));
+ new DefaultTraceLocation(trace, null, Lifespan.at(snap), address));
if (staticLocation == null) {
- return new LoneLogicalBreakpoint(recorder, address, length, kinds)
- .enableForTrace(trace);
+ LoneLogicalBreakpoint lb =
+ new LoneLogicalBreakpoint(tool, trace, address, length, kinds);
+ lb.setRecorder(trace, recorder);
+ return lb.enableForTrace(trace);
}
- MappedLogicalBreakpoint lb = new MappedLogicalBreakpoint(staticLocation.getProgram(),
+ MappedLogicalBreakpoint lb = new MappedLogicalBreakpoint(tool, staticLocation.getProgram(),
staticLocation.getByteAddress(), length, kinds);
- lb.setTraceAddress(recorder, address);
+ lb.setTraceAddress(trace, address);
+ lb.setRecorder(trace, recorder);
lb.enableForProgramWithName(name);
return lb.enableForTrace(trace);
}
@@ -1126,7 +1206,7 @@ public class DebuggerLogicalBreakpointServicePlugin extends Plugin
protected CompletableFuture actOnAll(Collection col, Trace trace,
Consumer consumerForProgram,
- BiConsumer consumerForTarget) {
+ BiConsumer consumerForTrace) {
BreakpointActionSet actions = new BreakpointActionSet();
for (LogicalBreakpoint lb : col) {
Set participants = lb.getParticipatingTraces();
@@ -1136,7 +1216,7 @@ public class DebuggerLogicalBreakpointServicePlugin extends Plugin
if (!(lb instanceof LogicalBreakpointInternal lbi)) {
continue;
}
- consumerForTarget.accept(actions, lbi);
+ consumerForTrace.accept(actions, lbi);
}
return actions.execute();
}
@@ -1174,8 +1254,48 @@ public class DebuggerLogicalBreakpointServicePlugin extends Plugin
}, (actions, lbi) -> lbi.planDelete(actions, trace));
}
+ private StateEditingMode getMode(Trace trace) {
+ return editingService == null
+ ? StateEditingMode.DEFAULT
+ : editingService.getCurrentMode(trace);
+ }
+
+ private void planActOnLoc(BreakpointActionSet actions, TraceBreakpoint tb,
+ BiConsumer targetLocConsumer,
+ BiConsumer emuLocConsumer) {
+ StateEditingMode mode = getMode(tb.getTrace());
+ if (mode.useEmulatedBreakpoints()) {
+ planActOnLocEmu(actions, tb, emuLocConsumer);
+ }
+ else {
+ planActOnLocTarget(actions, tb, targetLocConsumer);
+ }
+ }
+
+ private void planActOnLocTarget(BreakpointActionSet actions, TraceBreakpoint tb,
+ BiConsumer targetLocConsumer) {
+ TraceRecorder recorder = modelService.getRecorder(tb.getTrace());
+ if (recorder == null) {
+ return;
+ }
+ List path = PathUtils.parse(tb.getPath());
+ TargetObject object = recorder.getTarget().getModel().getModelObject(path);
+ if (!(object instanceof TargetBreakpointLocation)) {
+ Msg.error(this, tb.getPath() + " is not a target breakpoint location");
+ return;
+ }
+ TargetBreakpointLocation loc = (TargetBreakpointLocation) object;
+ targetLocConsumer.accept(actions, loc);
+ }
+
+ private void planActOnLocEmu(BreakpointActionSet actions, TraceBreakpoint tb,
+ BiConsumer emuLocConsumer) {
+ emuLocConsumer.accept(actions, tb);
+ }
+
protected CompletableFuture actOnLocs(Collection col,
- BiConsumer locConsumer,
+ BiConsumer targetLocConsumer,
+ BiConsumer emuLocConsumer,
Consumer progConsumer) {
BreakpointActionSet actions = new BreakpointActionSet();
for (TraceBreakpoint tb : col) {
@@ -1183,38 +1303,35 @@ public class DebuggerLogicalBreakpointServicePlugin extends Plugin
if (col.containsAll(lb.getTraceBreakpoints())) {
progConsumer.accept(lb);
}
- TraceRecorder recorder = modelService.getRecorder(tb.getTrace());
- if (recorder == null) {
- continue;
- }
- List path = PathUtils.parse(tb.getPath());
- TargetObject object = recorder.getTarget().getModel().getModelObject(path);
- if (!(object instanceof TargetBreakpointLocation)) {
- Msg.error(this, tb.getPath() + " is not a target breakpoint location");
- continue;
- }
- TargetBreakpointLocation loc = (TargetBreakpointLocation) object;
- locConsumer.accept(actions, loc);
+ planActOnLoc(actions, tb, targetLocConsumer, emuLocConsumer);
}
return actions.execute();
}
@Override
public CompletableFuture enableLocs(Collection col) {
- return actOnLocs(col, BreakpointActionSet::planEnable, LogicalBreakpoint::enableForProgram);
+ return actOnLocs(col,
+ BreakpointActionSet::planEnableTarget,
+ BreakpointActionSet::planEnableEmu,
+ LogicalBreakpoint::enableForProgram);
}
@Override
public CompletableFuture disableLocs(Collection col) {
- return actOnLocs(col, BreakpointActionSet::planDisable,
+ return actOnLocs(col,
+ BreakpointActionSet::planDisableTarget,
+ BreakpointActionSet::planDisableEmu,
LogicalBreakpoint::disableForProgram);
}
@Override
public CompletableFuture deleteLocs(Collection col) {
- return actOnLocs(col, BreakpointActionSet::planDelete, lb -> {
- // Never delete bookmark when user requests deleting locations
- });
+ return actOnLocs(col,
+ BreakpointActionSet::planDeleteTarget,
+ BreakpointActionSet::planDeleteEmu,
+ lb -> {
+ // Never delete bookmark when user requests deleting locations
+ });
}
@Override
@@ -1266,21 +1383,23 @@ public class DebuggerLogicalBreakpointServicePlugin extends Plugin
@Override
public void processEvent(PluginEvent event) {
- if (event instanceof ProgramOpenedPluginEvent) {
- ProgramOpenedPluginEvent openedEvt = (ProgramOpenedPluginEvent) event;
- programOpened(openedEvt.getProgram());
+ if (event instanceof ProgramOpenedPluginEvent evt) {
+ programOpened(evt.getProgram());
}
- else if (event instanceof ProgramClosedPluginEvent) {
- ProgramClosedPluginEvent closedEvt = (ProgramClosedPluginEvent) event;
- programClosed(closedEvt.getProgram());
+ else if (event instanceof ProgramClosedPluginEvent evt) {
+ programClosed(evt.getProgram());
}
- else if (event instanceof TraceOpenedPluginEvent) {
- TraceOpenedPluginEvent openedEvt = (TraceOpenedPluginEvent) event;
- traceOpened(openedEvt.getTrace());
+ else if (event instanceof TraceOpenedPluginEvent evt) {
+ traceOpened(evt.getTrace());
}
- else if (event instanceof TraceClosedPluginEvent) {
- TraceClosedPluginEvent closedEvt = (TraceClosedPluginEvent) event;
- traceClosed(closedEvt.getTrace());
+ else if (event instanceof TraceActivatedPluginEvent evt) {
+ traceSnapChanged(evt.getActiveCoordinates());
+ }
+ else if (event instanceof TraceInactiveCoordinatesPluginEvent evt) {
+ traceSnapChanged(evt.getCoordinates());
+ }
+ else if (event instanceof TraceClosedPluginEvent evt) {
+ traceClosed(evt.getTrace());
}
}
}
diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/DeleteEmuBreakpointActionItem.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/DeleteEmuBreakpointActionItem.java
new file mode 100644
index 0000000000..731ff4927b
--- /dev/null
+++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/DeleteEmuBreakpointActionItem.java
@@ -0,0 +1,39 @@
+/* ###
+ * IP: GHIDRA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package ghidra.app.plugin.core.debug.service.breakpoint;
+
+import java.util.concurrent.CompletableFuture;
+
+import ghidra.async.AsyncUtils;
+import ghidra.trace.model.breakpoint.TraceBreakpoint;
+import ghidra.util.database.UndoableTransaction;
+
+public record DeleteEmuBreakpointActionItem(TraceBreakpoint bpt) implements BreakpointActionItem {
+ @Override
+ public CompletableFuture execute() {
+ try (UndoableTransaction tid =
+ UndoableTransaction.start(bpt.getTrace(), "Delete Emulated Breakpoint")) {
+ String emuName = PlaceEmuBreakpointActionItem.createName(bpt.getMinAddress());
+ if (bpt.getPath().contains(emuName)) {
+ bpt.delete();
+ }
+ else {
+ bpt.setEmuEnabled(false);
+ }
+ }
+ return AsyncUtils.NIL;
+ }
+}
diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/DeleteBreakpointActionItem.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/DeleteTargetBreakpointActionItem.java
similarity index 57%
rename from Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/DeleteBreakpointActionItem.java
rename to Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/DeleteTargetBreakpointActionItem.java
index c725b4dd61..1a8e6ae2a9 100644
--- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/DeleteBreakpointActionItem.java
+++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/DeleteTargetBreakpointActionItem.java
@@ -15,35 +15,12 @@
*/
package ghidra.app.plugin.core.debug.service.breakpoint;
-import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import ghidra.dbg.target.TargetDeletable;
-public class DeleteBreakpointActionItem implements BreakpointActionItem {
- private final TargetDeletable deletable;
-
- public DeleteBreakpointActionItem(TargetDeletable deletable) {
- this.deletable = deletable;
- }
-
- @Override
- public boolean equals(Object obj) {
- if (!(obj instanceof DeleteBreakpointActionItem)) {
- return false;
- }
- DeleteBreakpointActionItem that = (DeleteBreakpointActionItem) obj;
- if (this.deletable != that.deletable) {
- return false;
- }
- return true;
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(getClass(), deletable);
- }
-
+public record DeleteTargetBreakpointActionItem(TargetDeletable deletable)
+ implements BreakpointActionItem {
@Override
public CompletableFuture execute() {
return deletable.delete();
diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/DisableEmuBreakpointActionItem.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/DisableEmuBreakpointActionItem.java
new file mode 100644
index 0000000000..672138f009
--- /dev/null
+++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/DisableEmuBreakpointActionItem.java
@@ -0,0 +1,34 @@
+/* ###
+ * IP: GHIDRA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package ghidra.app.plugin.core.debug.service.breakpoint;
+
+import java.util.concurrent.CompletableFuture;
+
+import ghidra.async.AsyncUtils;
+import ghidra.trace.model.breakpoint.TraceBreakpoint;
+import ghidra.util.database.UndoableTransaction;
+
+public record DisableEmuBreakpointActionItem(TraceBreakpoint bpt)
+ implements BreakpointActionItem {
+ @Override
+ public CompletableFuture execute() {
+ try (UndoableTransaction tid =
+ UndoableTransaction.start(bpt.getTrace(), "Disable Emulated Breakpoint")) {
+ bpt.setEmuEnabled(false);
+ }
+ return AsyncUtils.NIL;
+ }
+}
diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/DisableBreakpointActionItem.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/DisableTargetBreakpointActionItem.java
similarity index 57%
rename from Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/DisableBreakpointActionItem.java
rename to Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/DisableTargetBreakpointActionItem.java
index 1936302739..69d2ca4363 100644
--- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/DisableBreakpointActionItem.java
+++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/DisableTargetBreakpointActionItem.java
@@ -15,35 +15,12 @@
*/
package ghidra.app.plugin.core.debug.service.breakpoint;
-import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import ghidra.dbg.target.TargetTogglable;
-public class DisableBreakpointActionItem implements BreakpointActionItem {
- private final TargetTogglable togglable;
-
- public DisableBreakpointActionItem(TargetTogglable togglable) {
- this.togglable = togglable;
- }
-
- @Override
- public boolean equals(Object obj) {
- if (!(obj instanceof DisableBreakpointActionItem)) {
- return false;
- }
- DisableBreakpointActionItem that = (DisableBreakpointActionItem) obj;
- if (this.togglable != that.togglable) {
- return false;
- }
- return true;
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(getClass(), togglable);
- }
-
+public record DisableTargetBreakpointActionItem(TargetTogglable togglable)
+ implements BreakpointActionItem {
@Override
public CompletableFuture execute() {
return togglable.disable();
diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/EnableEmuBreakpointActionItem.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/EnableEmuBreakpointActionItem.java
new file mode 100644
index 0000000000..2e830276c6
--- /dev/null
+++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/EnableEmuBreakpointActionItem.java
@@ -0,0 +1,33 @@
+/* ###
+ * IP: GHIDRA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package ghidra.app.plugin.core.debug.service.breakpoint;
+
+import java.util.concurrent.CompletableFuture;
+
+import ghidra.async.AsyncUtils;
+import ghidra.trace.model.breakpoint.TraceBreakpoint;
+import ghidra.util.database.UndoableTransaction;
+
+public record EnableEmuBreakpointActionItem(TraceBreakpoint bpt) implements BreakpointActionItem {
+ @Override
+ public CompletableFuture execute() {
+ try (UndoableTransaction tid =
+ UndoableTransaction.start(bpt.getTrace(), "Enable Emulated Breakpoint")) {
+ bpt.setEmuEnabled(true);
+ }
+ return AsyncUtils.NIL;
+ }
+}
diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/EnableBreakpointActionItem.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/EnableTargetBreakpointActionItem.java
similarity index 57%
rename from Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/EnableBreakpointActionItem.java
rename to Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/EnableTargetBreakpointActionItem.java
index c175235b6b..8138a885da 100644
--- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/EnableBreakpointActionItem.java
+++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/EnableTargetBreakpointActionItem.java
@@ -15,35 +15,12 @@
*/
package ghidra.app.plugin.core.debug.service.breakpoint;
-import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import ghidra.dbg.target.TargetTogglable;
-public class EnableBreakpointActionItem implements BreakpointActionItem {
- private final TargetTogglable togglable;
-
- public EnableBreakpointActionItem(TargetTogglable togglable) {
- this.togglable = togglable;
- }
-
- @Override
- public boolean equals(Object obj) {
- if (!(obj instanceof EnableBreakpointActionItem)) {
- return false;
- }
- EnableBreakpointActionItem that = (EnableBreakpointActionItem) obj;
- if (this.togglable != that.togglable) {
- return false;
- }
- return true;
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(getClass(), togglable);
- }
-
+public record EnableTargetBreakpointActionItem(TargetTogglable togglable)
+ implements BreakpointActionItem {
@Override
public CompletableFuture execute() {
return togglable.enable();
diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/LogicalBreakpointInternal.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/LogicalBreakpointInternal.java
index 9984273f3c..121288fcef 100644
--- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/LogicalBreakpointInternal.java
+++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/LogicalBreakpointInternal.java
@@ -15,427 +15,23 @@
*/
package ghidra.app.plugin.core.debug.service.breakpoint;
-import java.util.*;
-
-import com.google.common.collect.Collections2;
-
import ghidra.app.services.LogicalBreakpoint;
import ghidra.app.services.TraceRecorder;
-import ghidra.dbg.target.*;
-import ghidra.dbg.target.TargetBreakpointSpec.TargetBreakpointKind;
-import ghidra.program.model.address.*;
+import ghidra.program.model.address.Address;
import ghidra.program.model.listing.*;
-import ghidra.program.util.ProgramLocation;
import ghidra.trace.model.Trace;
import ghidra.trace.model.breakpoint.TraceBreakpoint;
-import ghidra.trace.model.breakpoint.TraceBreakpointKind;
-import ghidra.trace.model.breakpoint.TraceBreakpointKind.TraceBreakpointKindSet;
-import ghidra.util.Msg;
-import ghidra.util.database.UndoableTransaction;
-import ghidra.util.exception.CancelledException;
-import ghidra.util.task.TaskMonitor;
-import utilities.util.IDHashed;
public interface LogicalBreakpointInternal extends LogicalBreakpoint {
- public static class ProgramBreakpoint {
- public static Set kindsFromBookmark(Bookmark mark) {
- String[] parts = mark.getCategory().split(";");
- Set result = TraceBreakpointKindSet.decode(parts[0], false);
- if (result.isEmpty()) {
- Msg.warn(TraceBreakpointKind.class,
- "Decoded empty set of kinds from bookmark. Assuming SW_EXECUTE");
- return Set.of(TraceBreakpointKind.SW_EXECUTE);
- }
- return result;
- }
-
- public static long lengthFromBookmark(Bookmark mark) {
- String[] parts = mark.getCategory().split(";");
- if (parts.length < 2) {
- Msg.warn(DebuggerLogicalBreakpointServicePlugin.class,
- "No length for bookmark breakpoint. Assuming 1.");
- return 1;
- }
- try {
- long length = Long.parseLong(parts[1]);
- if (length <= 0) {
- Msg.warn(DebuggerLogicalBreakpointServicePlugin.class,
- "Non-positive length for bookmark breakpoint? Using 1.");
- return 1;
- }
- return length;
- }
- catch (NumberFormatException e) {
- Msg.warn(DebuggerLogicalBreakpointServicePlugin.class,
- "Ill-formatted bookmark breakpoint length: " + e + ". Using 1.");
- return 1;
- }
- }
-
- private final Program program;
- private final Address address;
- private final ProgramLocation location;
- private final long length;
- private final Set kinds;
-
- private Bookmark eBookmark; // when present
- private Bookmark dBookmark; // when present
-
- public ProgramBreakpoint(Program program, Address address, long length,
- Set kinds) {
- this.program = program;
- this.address = address;
- this.location = new ProgramLocation(program, address);
- this.length = length;
- this.kinds = kinds;
- }
-
- @Override
- public String toString() {
- // volatile reads
- Bookmark eBookmark = this.eBookmark;
- Bookmark dBookmark = this.dBookmark;
- if (eBookmark != null) {
- return String.format("", eBookmark.getTypeString(),
- eBookmark.getCategory(), eBookmark.getAddress(), program.getName());
- }
- else if (dBookmark != null) {
- return String.format("", dBookmark.getTypeString(),
- dBookmark.getCategory(), dBookmark.getAddress(), program.getName());
- }
- else {
- return String.format("", address, program.getName());
- }
- }
-
- public ProgramLocation getLocation() {
- return location;
- }
-
- public String getName() {
- // TODO: Be prepared to use JSON or something, if more fields are needed
- Bookmark bookmark = getBookmark();
- if (bookmark == null) {
- return "";
- }
- return bookmark.getComment();
- }
-
- public void setName(String name) {
- Bookmark bookmark = getBookmark();
- if (bookmark == null) {
- throw new IllegalStateException("Must save breakpoint to program before naming it");
- }
- try (UndoableTransaction tid =
- UndoableTransaction.start(program, "Rename breakpoint")) {
- bookmark.set(bookmark.getCategory(), name);
- }
- }
-
- public ProgramMode computeMode() {
- if (eBookmark != null) {
- return ProgramMode.ENABLED;
- }
- if (dBookmark != null) {
- return ProgramMode.DISABLED;
- }
- else {
- return ProgramMode.MISSING;
- }
- }
-
- public boolean isEmpty() {
- return eBookmark == null && dBookmark == null;
- }
-
- public void deleteFromProgram() {
- // volatile reads
- Bookmark eBookmark = this.eBookmark;
- Bookmark dBookmark = this.dBookmark;
- try (UndoableTransaction tid = UndoableTransaction.start(program, "Clear breakpoint")) {
- BookmarkManager bookmarkManager = program.getBookmarkManager();
- if (eBookmark != null) {
- bookmarkManager.removeBookmark(eBookmark);
- }
- if (dBookmark != null) {
- bookmarkManager.removeBookmark(dBookmark);
- }
- // (e,d)Bookmark Gets nulled on program change callback
- // If null here, logical breakpoint manager will get confused
- }
- }
-
- public boolean canMerge(Program candProgram, Bookmark candBookmark) {
- if (program != candProgram) {
- return false;
- }
- if (!address.equals(candBookmark.getAddress())) {
- return false;
- }
- if (length != lengthFromBookmark(candBookmark)) {
- return false;
- }
- if (!Objects.equals(kinds, kindsFromBookmark(candBookmark))) {
- return false;
- }
- return true;
- }
-
- public Program getProgram() {
- return program;
- }
-
- public boolean add(Bookmark bookmark) {
- if (BREAKPOINT_ENABLED_BOOKMARK_TYPE.equals(bookmark.getTypeString())) {
- if (eBookmark == bookmark) {
- return false;
- }
- eBookmark = bookmark;
- return true;
- }
- if (BREAKPOINT_DISABLED_BOOKMARK_TYPE.equals(bookmark.getTypeString())) {
- if (dBookmark == bookmark) {
- return false;
- }
- dBookmark = bookmark;
- return true;
- }
- return false;
- }
-
- public boolean remove(Bookmark bookmark) {
- if (eBookmark == bookmark) {
- eBookmark = null;
- return true;
- }
- if (dBookmark == bookmark) {
- dBookmark = null;
- return true;
- }
- return false;
- }
-
- public Bookmark getBookmark() {
- Bookmark eBookmark = this.eBookmark;
- if (eBookmark != null) {
- return eBookmark;
- }
- return dBookmark;
- }
-
- protected String getComment() {
- Bookmark bookmark = getBookmark();
- return bookmark == null ? "" : bookmark.getComment();
- }
-
- public boolean isEnabled() {
- return computeMode() == ProgramMode.ENABLED;
- }
-
- public boolean isDisabled() {
- return computeMode() == ProgramMode.DISABLED;
- }
-
- public String computeCategory() {
- return TraceBreakpointKindSet.encode(kinds) + ";" + Long.toUnsignedString(length);
- }
-
- public void toggleWithComment(boolean enabled, String comment) {
- String addType =
- enabled ? BREAKPOINT_ENABLED_BOOKMARK_TYPE : BREAKPOINT_DISABLED_BOOKMARK_TYPE;
- String delType =
- enabled ? BREAKPOINT_DISABLED_BOOKMARK_TYPE : BREAKPOINT_ENABLED_BOOKMARK_TYPE;
- try (UndoableTransaction tid =
- UndoableTransaction.start(program, "Enable breakpoint")) {
- BookmarkManager manager = program.getBookmarkManager();
- String catStr = computeCategory();
- manager.setBookmark(address, addType, catStr, comment);
- manager.removeBookmarks(new AddressSet(address), delType, catStr,
- TaskMonitor.DUMMY);
- }
- catch (CancelledException e) {
- throw new AssertionError(e);
- }
- }
-
- public void enable() {
- if (isEnabled()) {
- return;
- }
- toggleWithComment(true, getComment());
- }
-
- public void disable() {
- if (isDisabled()) {
- return;
- }
- toggleWithComment(false, getComment());
- }
- }
-
- static class TraceBreakpointSet {
- private final TraceRecorder recorder;
- private final Trace trace;
- private final Address address;
- private Set> breakpoints = new HashSet<>();
-
- public TraceBreakpointSet(TraceRecorder recorder, Address address) {
- this.recorder = recorder;
- this.trace = recorder.getTrace();
- this.address = address;
- }
-
- @Override
- public String toString() {
- return String.format("", address, trace.getName(), breakpoints);
- }
-
- public Trace getTrace() {
- return trace;
- }
-
- public Address getAddress() {
- return address;
- }
-
- public Address computeTargetAddress() {
- return recorder.getMemoryMapper().traceToTarget(address);
- }
-
- public TraceMode computeMode() {
- TraceMode mode = TraceMode.NONE;
- for (IDHashed bpt : breakpoints) {
- mode = mode.combine(computeMode(bpt.obj));
- if (mode == TraceMode.MISSING) {
- return mode;
- }
- }
- return mode;
- }
-
- public TraceMode computeMode(TraceBreakpoint bpt) {
- return TraceMode.fromBool(bpt.isEnabled(recorder.getSnap()));
- }
-
- public boolean isEmpty() {
- return breakpoints.isEmpty();
- }
-
- public Collection getBreakpoints() {
- return Collections2.transform(breakpoints, e -> e.obj);
- }
-
- public boolean add(TraceBreakpoint bpt) {
- return breakpoints.add(new IDHashed<>(bpt));
- }
-
- public boolean canMerge(TraceBreakpoint bpt) {
- if (trace != bpt.getTrace()) {
- return false;
- }
- if (!address.equals(bpt.getMinAddress())) {
- return false;
- }
- return true;
- }
-
- public boolean remove(TraceBreakpoint bpt) {
- return breakpoints.remove(new IDHashed<>(bpt));
- }
-
- /**
- * Plan to enable a logical breakpoint within the trace.
- *
- *
- * This method prefers to use the existing breakpoint specifications which result in
- * breakpoints at this address. In other words, it favors what the user has already done to
- * effect a breakpoint at this logical breakpoint's address. If there is no such existing
- * specification, then it attempts to place a new breakpoint via the target's breakpoint
- * container, usually resulting in a new spec, which should effect exactly the one specified
- * address.
- *
- *
- * This method must convert applicable addresses to the target space. If the address cannot
- * be mapped, it's usually because this logical breakpoint does not apply to the given
- * trace's target. E.g., the trace may not have a live target, or the logical breakpoint may
- * be in a module not loaded by the trace.
- *
- * @param actions the action set to populate
- * @param kind the kind of breakpoint
- * @return a future which completes when the plan is ready
- */
- public void planEnable(BreakpointActionSet actions, long length,
- Collection kinds) {
- if (breakpoints.isEmpty()) {
- Set tKinds =
- TraceRecorder.traceToTargetBreakpointKinds(kinds);
-
- for (TargetBreakpointSpecContainer cont : recorder
- .collectBreakpointContainers(null)) {
- LinkedHashSet supKinds = new LinkedHashSet<>(tKinds);
- supKinds.retainAll(cont.getSupportedBreakpointKinds());
- actions.add(new PlaceBreakpointActionItem(cont, computeTargetAddress(), length,
- supKinds));
- }
- return;
- }
- for (IDHashed bpt : breakpoints) {
- TargetBreakpointLocation loc = recorder.getTargetBreakpoint(bpt.obj);
- if (loc == null) {
- continue;
- }
- actions.planEnable(loc);
- }
- }
-
- public void planDisable(BreakpointActionSet actions, long length,
- Collection kinds) {
- Set tKinds = TraceRecorder.traceToTargetBreakpointKinds(kinds);
- Address targetAddr = computeTargetAddress();
- for (TargetBreakpointLocation loc : recorder.collectBreakpoints(null)) {
- AddressRange range = loc.getRange();
- if (!targetAddr.equals(range.getMinAddress())) {
- continue;
- }
- if (length != range.getLength()) {
- continue;
- }
- TargetBreakpointSpec spec = loc.getSpecification();
- if (!Objects.equals(spec.getKinds(), tKinds)) {
- continue;
- }
- actions.planDisable(loc);
- }
- }
-
- public void planDelete(BreakpointActionSet actions, long length,
- Set kinds) {
- Set tKinds = TraceRecorder.traceToTargetBreakpointKinds(kinds);
- Address targetAddr = computeTargetAddress();
- for (TargetBreakpointLocation loc : recorder.collectBreakpoints(null)) {
- AddressRange range = loc.getRange();
- if (!targetAddr.equals(range.getMinAddress())) {
- continue;
- }
- if (length != range.getLength()) {
- continue;
- }
- TargetBreakpointSpec spec = loc.getSpecification();
- if (!Objects.equals(spec.getKinds(), tKinds)) {
- continue;
- }
- actions.planDelete(loc);
- }
- }
- }
-
/**
* Set the expected address for trace breakpoints in the given trace
*
- * @param recorder the recorder for the given trace
+ * @param trace the trace
* @param address the address of this logical breakpoint in the given trace
*/
- void setTraceAddress(TraceRecorder recorder, Address address);
+ void setTraceAddress(Trace trace, Address address);
+
+ void setRecorder(Trace trace, TraceRecorder recorder);
/**
* Remove the given trace from this set
diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/LoneLogicalBreakpoint.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/LoneLogicalBreakpoint.java
index 9b7be1ca93..47ae1a0dd4 100644
--- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/LoneLogicalBreakpoint.java
+++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/LoneLogicalBreakpoint.java
@@ -21,6 +21,7 @@ import java.util.concurrent.CompletableFuture;
import ghidra.app.services.TraceRecorder;
import ghidra.async.AsyncUtils;
import ghidra.framework.model.DomainObject;
+import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.address.Address;
import ghidra.program.model.listing.Bookmark;
import ghidra.program.model.listing.Program;
@@ -35,13 +36,13 @@ public class LoneLogicalBreakpoint implements LogicalBreakpointInternal {
private final long length;
private final Set kinds;
- public LoneLogicalBreakpoint(TraceRecorder recorder, Address address, long length,
+ public LoneLogicalBreakpoint(PluginTool tool, Trace trace, Address address, long length,
Collection kinds) {
- this.breaks = new TraceBreakpointSet(recorder, address);
+ this.breaks = new TraceBreakpointSet(tool, trace, address);
this.length = length;
this.kinds = Set.copyOf(kinds);
- this.justThisTrace = Set.of(recorder.getTrace());
+ this.justThisTrace = Set.of(trace);
}
@Override
@@ -85,10 +86,25 @@ public class LoneLogicalBreakpoint implements LogicalBreakpointInternal {
}
@Override
- public void setTraceAddress(TraceRecorder recorder, Address address) {
+ public String getEmuSleigh() {
+ return breaks.computeSleigh();
+ }
+
+ @Override
+ public void setEmuSleigh(String sleigh) {
+ breaks.setEmuSleigh(sleigh);
+ }
+
+ @Override
+ public void setTraceAddress(Trace trace, Address address) {
throw new AssertionError();
}
+ @Override
+ public void setRecorder(Trace trace, TraceRecorder recorder) {
+ breaks.setRecorder(recorder);
+ }
+
@Override
public void removeTrace(Trace trace) {
throw new AssertionError();
diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/MappedLogicalBreakpoint.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/MappedLogicalBreakpoint.java
index 94b2cc3d4f..1a009898c6 100644
--- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/MappedLogicalBreakpoint.java
+++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/MappedLogicalBreakpoint.java
@@ -22,6 +22,8 @@ import ghidra.app.services.DebuggerModelService;
import ghidra.app.services.TraceRecorder;
import ghidra.async.AsyncUtils;
import ghidra.framework.model.DomainObject;
+import ghidra.framework.plugintool.PluginTool;
+import ghidra.pcode.exec.SleighUtils;
import ghidra.program.model.address.Address;
import ghidra.program.model.listing.Bookmark;
import ghidra.program.model.listing.Program;
@@ -32,6 +34,7 @@ import ghidra.trace.model.breakpoint.TraceBreakpointKind;
public class MappedLogicalBreakpoint implements LogicalBreakpointInternal {
+ private final PluginTool tool;
private final Set kinds;
private final long length;
@@ -41,10 +44,13 @@ public class MappedLogicalBreakpoint implements LogicalBreakpointInternal {
// They may have different names and/or different conditions
private final Map traceBreaks = new HashMap<>();
- protected MappedLogicalBreakpoint(Program program, Address progAddr, long length,
+ protected MappedLogicalBreakpoint(PluginTool tool, Program program, Address progAddr,
+ long length,
Collection kinds) {
- this.length = length;
+ this.tool = tool;
this.kinds = Set.copyOf(kinds);
+ this.length = length;
+
this.progBreak = new ProgramBreakpoint(program, progAddr, length, this.kinds);
}
@@ -83,26 +89,20 @@ public class MappedLogicalBreakpoint implements LogicalBreakpointInternal {
return recorder;
}
- /**
- * TODO: How best to allow the user to control behavior when a new static mapping is created
- * that could generate new breakpoints in the target. Options:
- *
- * 1) A toggle during mapping creation for how to sync breakpoints.
- *
- * 2) A prompt when the mapping is created, asking the user to sync breakpoints.
- *
- * 3) A UI indicator (probably in the breakpoints provider) that some breakpoints are not in
- * sync. Different actions for directions of sync.
- *
- * I'm thinking both 1 and 3. Option 2 is probably too abrasive, esp., if several mappings are
- * created at once.
- */
-
@Override
public void enableForProgram() {
progBreak.enable();
}
+ /**
+ * Place the program's bookmark with a comment specifying a desired name
+ *
+ *
+ * WARNING: Use only when this breakpoint was just placed, otherwise, this will reset
+ * other extrinsic properties, such as the sleigh injection.
+ *
+ * @param name the desired name
+ */
public void enableForProgramWithName(String name) {
progBreak.toggleWithComment(true, name);
}
@@ -194,21 +194,20 @@ public class MappedLogicalBreakpoint implements LogicalBreakpointInternal {
@Override
public String generateStatusEnable(Trace trace) {
+ // NB. It'll place a breakpoint if mappable but not present
if (trace == null) {
- for (TraceBreakpointSet breaks : traceBreaks.values()) {
- if (!breaks.isEmpty()) {
- return null;
- }
+ if (!traceBreaks.values().isEmpty()) {
+ return null;
}
- return "A breakpoint is not mapped to any live trace. Cannot enable it on target. " +
+ return "A breakpoint is not mapped to any trace. Cannot enable it. " +
"Is there a target? Check your module map.";
}
TraceBreakpointSet breaks = traceBreaks.get(trace);
- if (breaks != null && !breaks.isEmpty()) {
+ if (breaks != null) {
return null;
}
- return "A breakpoint is not mapped to the trace, or the trace is not live. " +
- "Cannot enable it on target. Is there a target? Check your module map.";
+ return "A breakpoint is not mapped to the trace. " +
+ "Cannot enable it. Is there a target? Check your module map.";
}
@Override
@@ -296,9 +295,18 @@ public class MappedLogicalBreakpoint implements LogicalBreakpointInternal {
}
@Override
- public void setTraceAddress(TraceRecorder recorder, Address address) {
+ public void setTraceAddress(Trace trace, Address address) {
synchronized (traceBreaks) {
- traceBreaks.put(recorder.getTrace(), new TraceBreakpointSet(recorder, address));
+ TraceBreakpointSet newSet = new TraceBreakpointSet(tool, trace, address);
+ newSet.setEmuSleigh(progBreak.getEmuSleigh());
+ traceBreaks.put(trace, newSet);
+ }
+ }
+
+ @Override
+ public void setRecorder(Trace trace, TraceRecorder recorder) {
+ synchronized (traceBreaks) {
+ traceBreaks.get(trace).setRecorder(recorder);
}
}
@@ -440,6 +448,39 @@ public class MappedLogicalBreakpoint implements LogicalBreakpointInternal {
return progMode.combineTrace(traceMode, Perspective.LOGICAL);
}
+ protected String computeTraceSleigh() {
+ String sleigh = null;
+ synchronized (traceBreaks) {
+ for (TraceBreakpointSet breaks : traceBreaks.values()) {
+ String s = breaks.computeSleigh();
+ if (sleigh != null && !sleigh.equals(s)) {
+ return null;
+ }
+ sleigh = s;
+ }
+ return sleigh;
+ }
+ }
+
+ @Override
+ public String getEmuSleigh() {
+ return progBreak.getEmuSleigh();
+ }
+
+ protected void setTraceBreakEmuSleigh(String sleigh) {
+ synchronized (traceBreaks) {
+ for (TraceBreakpointSet breaks : traceBreaks.values()) {
+ breaks.setEmuSleigh(sleigh);
+ }
+ }
+ }
+
+ @Override
+ public void setEmuSleigh(String sleigh) {
+ progBreak.setEmuSleigh(sleigh);
+ setTraceBreakEmuSleigh(sleigh);
+ }
+
@Override
public boolean canMerge(Program program, Bookmark bookmark) {
return progBreak.canMerge(program, bookmark);
@@ -473,10 +514,21 @@ public class MappedLogicalBreakpoint implements LogicalBreakpointInternal {
@Override
public boolean trackBreakpoint(Bookmark bookmark) {
- return progBreak.add(bookmark);
+ if (progBreak.add(bookmark)) {
+ String sleigh = progBreak.getEmuSleigh();
+ if (sleigh != null && !SleighUtils.UNCONDITIONAL_BREAK.equals(sleigh)) {
+ setEmuSleigh(sleigh);
+ }
+ return true;
+ }
+ return false;
}
protected void makeBookmarkConsistent() {
+ // NB. Apparently it is specified that the bookmark should be created automatically
+ /*if (progBreak.isEmpty()) {
+ return;
+ }*/
TraceMode traceMode = computeTraceMode();
if (traceMode == TraceMode.ENABLED) {
progBreak.enable();
@@ -493,6 +545,15 @@ public class MappedLogicalBreakpoint implements LogicalBreakpointInternal {
breaks = traceBreaks.get(breakpoint.getTrace());
}
boolean result = breaks.add(breakpoint);
+ if (result) {
+ String traceSleigh = computeTraceSleigh();
+ if (traceSleigh != null && !SleighUtils.UNCONDITIONAL_BREAK.equals(traceSleigh)) {
+ String progSleigh = progBreak.getEmuSleigh();
+ if (progSleigh == null || SleighUtils.UNCONDITIONAL_BREAK.equals(progSleigh)) {
+ progBreak.setEmuSleigh(traceSleigh);
+ }
+ }
+ }
makeBookmarkConsistent();
return result;
}
diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/PlaceBreakpointActionItem.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/PlaceBreakpointActionItem.java
deleted file mode 100644
index 76c2c9962b..0000000000
--- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/PlaceBreakpointActionItem.java
+++ /dev/null
@@ -1,73 +0,0 @@
-/* ###
- * IP: GHIDRA
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package ghidra.app.plugin.core.debug.service.breakpoint;
-
-import java.util.*;
-import java.util.concurrent.CompletableFuture;
-
-import ghidra.dbg.target.TargetBreakpointSpecContainer;
-import ghidra.dbg.target.TargetBreakpointSpec.TargetBreakpointKind;
-import ghidra.program.model.address.*;
-
-public class PlaceBreakpointActionItem implements BreakpointActionItem {
- private final TargetBreakpointSpecContainer container;
- private final Address address;
- private final long length;
- private final Set kinds;
-
- public PlaceBreakpointActionItem(TargetBreakpointSpecContainer container, Address address,
- long length, Collection kinds) {
- this.container = Objects.requireNonNull(container);
- this.address = Objects.requireNonNull(address);
- this.length = length;
- this.kinds = Set.copyOf(kinds);
- }
-
- @Override
- public boolean equals(Object obj) {
- if (!(obj instanceof PlaceBreakpointActionItem)) {
- return false;
- }
- PlaceBreakpointActionItem that = (PlaceBreakpointActionItem) obj;
- if (this.container != that.container) {
- return false;
- }
- if (!this.address.equals(that.address)) {
- return false;
- }
- if (!Objects.equals(this.kinds, that.kinds)) {
- return false;
- }
- return true;
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(getClass(), container, address, kinds);
- }
-
- @Override
- public CompletableFuture execute() {
- AddressRange range;
- try {
- range = new AddressRangeImpl(address, length);
- }
- catch (AddressOverflowException e) {
- throw new AssertionError(e);
- }
- return container.placeBreakpoint(range, kinds);
- }
-}
diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/PlaceEmuBreakpointActionItem.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/PlaceEmuBreakpointActionItem.java
new file mode 100644
index 0000000000..f7e0d5b92a
--- /dev/null
+++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/PlaceEmuBreakpointActionItem.java
@@ -0,0 +1,114 @@
+/* ###
+ * IP: GHIDRA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package ghidra.app.plugin.core.debug.service.breakpoint;
+
+import java.util.*;
+import java.util.concurrent.CompletableFuture;
+
+import ghidra.async.AsyncUtils;
+import ghidra.dbg.target.TargetBreakpointSpec;
+import ghidra.dbg.target.TargetBreakpointSpecContainer;
+import ghidra.dbg.util.PathMatcher;
+import ghidra.program.model.address.*;
+import ghidra.trace.model.Lifespan;
+import ghidra.trace.model.Trace;
+import ghidra.trace.model.breakpoint.TraceBreakpoint;
+import ghidra.trace.model.breakpoint.TraceBreakpointKind;
+import ghidra.trace.model.memory.TraceMemoryRegion;
+import ghidra.trace.model.memory.TraceObjectMemoryRegion;
+import ghidra.trace.model.target.TraceObject;
+import ghidra.util.database.UndoableTransaction;
+import ghidra.util.exception.DuplicateNameException;
+
+public record PlaceEmuBreakpointActionItem(Trace trace, long snap, Address address, long length,
+ Set kinds, String emuSleigh) implements BreakpointActionItem {
+
+ public static String createName(Address address) {
+ return "emu-" + address;
+ }
+
+ public PlaceEmuBreakpointActionItem(Trace trace, long snap, Address address, long length,
+ Set kinds, String emuSleigh) {
+ this.trace = trace;
+ this.snap = snap;
+ this.address = address;
+ this.length = length;
+ this.kinds = Set.copyOf(kinds);
+ this.emuSleigh = emuSleigh;
+ }
+
+ private TraceObjectMemoryRegion findRegion() {
+ TraceMemoryRegion region = trace.getMemoryManager().getRegionContaining(snap, address);
+ if (region != null) {
+ return (TraceObjectMemoryRegion) region;
+ }
+ AddressSpace space = address.getAddressSpace();
+ Collection extends TraceMemoryRegion> regionsInSpace = trace.getMemoryManager()
+ .getRegionsIntersecting(Lifespan.at(snap),
+ new AddressRangeImpl(space.getMinAddress(), space.getMaxAddress()));
+ if (!regionsInSpace.isEmpty()) {
+ return (TraceObjectMemoryRegion) regionsInSpace.iterator().next();
+ }
+ return null;
+ }
+
+ private TraceObject findBreakpointContainer() {
+ TraceObjectMemoryRegion region = findRegion();
+ if (region == null) {
+ throw new IllegalArgumentException("Address does not belong to a memory in the trace");
+ }
+ return region.getObject().querySuitableTargetInterface(TargetBreakpointSpecContainer.class);
+ }
+
+ private String computePath() {
+ String name = createName(address);
+ if (Trace.isLegacy(trace)) {
+ return "Breakpoints[" + name + "]";
+ }
+ TraceObject container = findBreakpointContainer();
+ if (container == null) {
+ throw new IllegalArgumentException(
+ "Address is not associated with a breakpoint container");
+ }
+ PathMatcher specMatcher =
+ container.getTargetSchema().searchFor(TargetBreakpointSpec.class, true);
+ if (specMatcher == null) {
+ throw new IllegalArgumentException("Cannot find path to breakpoint specifications");
+ }
+ List relPath = specMatcher.applyKeys(name).getSingletonPath();
+ if (relPath == null) {
+ throw new IllegalArgumentException("Too many wildcards to breakpoint specification");
+ }
+ return container.getCanonicalPath().extend(relPath).toString();
+ }
+
+ @Override
+ public CompletableFuture execute() {
+ try (UndoableTransaction tid =
+ UndoableTransaction.start(trace, "Place Emulated Breakpoint")) {
+ // Defaults with emuEnable=true
+ TraceBreakpoint bpt = trace.getBreakpointManager()
+ .addBreakpoint(computePath(), Lifespan.at(snap), range(address, length),
+ Set.of(), kinds, false, null);
+ bpt.setName(createName(address));
+ bpt.setEmuSleigh(emuSleigh);
+ return AsyncUtils.NIL;
+ }
+ catch (DuplicateNameException e) {
+ throw new AssertionError(e);
+ }
+ }
+}
diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/PlaceTargetBreakpointActionItem.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/PlaceTargetBreakpointActionItem.java
new file mode 100644
index 0000000000..d17406f773
--- /dev/null
+++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/PlaceTargetBreakpointActionItem.java
@@ -0,0 +1,41 @@
+/* ###
+ * IP: GHIDRA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package ghidra.app.plugin.core.debug.service.breakpoint;
+
+import java.util.*;
+import java.util.concurrent.CompletableFuture;
+
+import ghidra.dbg.target.TargetBreakpointSpecContainer;
+import ghidra.dbg.target.TargetBreakpointSpec.TargetBreakpointKind;
+import ghidra.program.model.address.*;
+
+public record PlaceTargetBreakpointActionItem(TargetBreakpointSpecContainer container,
+ Address address, long length, Set kinds)
+ implements BreakpointActionItem {
+
+ public PlaceTargetBreakpointActionItem(TargetBreakpointSpecContainer container, Address address,
+ long length, Set kinds) {
+ this.container = container;
+ this.address = address;
+ this.length = length;
+ this.kinds = Set.copyOf(kinds);
+ }
+
+ @Override
+ public CompletableFuture execute() {
+ return container.placeBreakpoint(range(address, length), kinds);
+ }
+}
diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/ProgramBreakpoint.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/ProgramBreakpoint.java
new file mode 100644
index 0000000000..6655789ddd
--- /dev/null
+++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/ProgramBreakpoint.java
@@ -0,0 +1,496 @@
+/* ###
+ * IP: GHIDRA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package ghidra.app.plugin.core.debug.service.breakpoint;
+
+import java.util.*;
+
+import com.google.gson.*;
+
+import ghidra.app.services.LogicalBreakpoint.ProgramMode;
+import ghidra.program.model.address.Address;
+import ghidra.program.model.address.AddressSet;
+import ghidra.program.model.listing.*;
+import ghidra.program.util.ProgramLocation;
+import ghidra.trace.model.breakpoint.TraceBreakpointKind;
+import ghidra.trace.model.breakpoint.TraceBreakpointKind.TraceBreakpointKindSet;
+import ghidra.util.Msg;
+import ghidra.util.database.UndoableTransaction;
+import ghidra.util.exception.CancelledException;
+import ghidra.util.task.TaskMonitor;
+
+/**
+ * The static side of a mapped logical breakpoint
+ *
+ *
+ * Programs don't have a built-in concept of breakpoints, so we store them as breakpoints with a
+ * specific type for each state. We also encode other intrinsic properties (length and kinds) to the
+ * category. Extrinsic properties (name and sleigh) are encoded in the comment. Because traces are
+ * fairly ephemeral, the program bookmarks are the primary means a user has to save and manage a
+ * breakpoint set.
+ */
+public class ProgramBreakpoint {
+ private static final Gson GSON = new GsonBuilder().create();
+
+ /**
+ * A class for (de)serializing breakoint properties in the bookmark's comments
+ */
+ static class BreakpointProperties {
+ public String name;
+ public String sleigh;
+
+ public BreakpointProperties(String name, String sleigh) {
+ this.name = name;
+ this.sleigh = sleigh;
+ }
+ }
+
+ /**
+ * Get the kinds of a breakpoint from its bookmark
+ *
+ * @param mark the bookmark representing a breakpoint
+ * @return the kinds
+ */
+ public static Set kindsFromBookmark(Bookmark mark) {
+ String[] parts = mark.getCategory().split(";");
+ Set result = TraceBreakpointKindSet.decode(parts[0], false);
+ if (result.isEmpty()) {
+ Msg.warn(TraceBreakpointKind.class,
+ "Decoded empty set of kinds from bookmark. Assuming SW_EXECUTE");
+ return Set.of(TraceBreakpointKind.SW_EXECUTE);
+ }
+ return result;
+ }
+
+ /**
+ * Get the length of a breakpoint from its bookmark
+ *
+ * @param mark the bookmark representing a breakpoint
+ * @return the length in bytes
+ */
+ public static long lengthFromBookmark(Bookmark mark) {
+ String[] parts = mark.getCategory().split(";");
+ if (parts.length < 2) {
+ Msg.warn(DebuggerLogicalBreakpointServicePlugin.class,
+ "No length for bookmark breakpoint. Assuming 1.");
+ return 1;
+ }
+ try {
+ long length = Long.parseLong(parts[1]);
+ if (length <= 0) {
+ Msg.warn(DebuggerLogicalBreakpointServicePlugin.class,
+ "Non-positive length for bookmark breakpoint? Using 1.");
+ return 1;
+ }
+ return length;
+ }
+ catch (NumberFormatException e) {
+ Msg.warn(DebuggerLogicalBreakpointServicePlugin.class,
+ "Ill-formatted bookmark breakpoint length: " + e + ". Using 1.");
+ return 1;
+ }
+ }
+
+ private final Program program;
+ private final Address address;
+ private final ProgramLocation location;
+ private final long length;
+ private final Set kinds;
+
+ private Bookmark eBookmark; // when present
+ private Bookmark dBookmark; // when present
+
+ private String name;
+ private String sleigh;
+
+ /**
+ * Construct a program breakpoint
+ *
+ * @param program the program
+ * @param address the static address of the breakpoint (even if a bookmark is not present there)
+ * @param length the length of the breakpoint in bytes
+ * @param kinds the kinds of the breakpoint
+ */
+ public ProgramBreakpoint(Program program, Address address, long length,
+ Set kinds) {
+ this.program = program;
+ this.address = address;
+ this.location = new ProgramLocation(program, address);
+ this.length = length;
+ this.kinds = kinds;
+ }
+
+ @Override
+ public String toString() {
+ // volatile reads
+ Bookmark eBookmark = this.eBookmark;
+ Bookmark dBookmark = this.dBookmark;
+ if (eBookmark != null) {
+ return String.format("", eBookmark.getTypeString(),
+ eBookmark.getCategory(), eBookmark.getAddress(), program.getName());
+ }
+ else if (dBookmark != null) {
+ return String.format("", dBookmark.getTypeString(),
+ dBookmark.getCategory(), dBookmark.getAddress(), program.getName());
+ }
+ else {
+ return String.format("", address, program.getName());
+ }
+ }
+
+ /**
+ * Get the breakpoint's static program location
+ *
+ * @return the location
+ */
+ public ProgramLocation getLocation() {
+ return location;
+ }
+
+ private void syncProperties(Bookmark bookmark) {
+ if (bookmark == null) {
+ name = "";
+ sleigh = null;
+ return;
+ }
+ String comment = bookmark.getComment();
+ if (comment == null || !comment.startsWith("{")) {
+ // Backward compatibility.
+ name = comment;
+ sleigh = null;
+ return;
+ }
+ try {
+ BreakpointProperties props = GSON.fromJson(comment, BreakpointProperties.class);
+ name = props.name;
+ sleigh = props.sleigh;
+ return;
+ }
+ catch (JsonSyntaxException e) {
+ Msg.error(this, "Could not parse breakpoint bookmark properties", e);
+ name = "";
+ sleigh = null;
+ return;
+ }
+ }
+
+ private String computeComment() {
+ if ((name == null || "".equals(name)) && (sleigh == null || "".equals(sleigh))) {
+ return null;
+ }
+ return GSON.toJson(new BreakpointProperties(name, sleigh));
+ }
+
+ private void writeProperties(Bookmark bookmark) {
+ try (UndoableTransaction tid =
+ UndoableTransaction.start(program, "Rename breakpoint")) {
+ bookmark.set(bookmark.getCategory(), computeComment());
+ }
+ catch (ConcurrentModificationException e) {
+ /**
+ * Can happen during breakpoint deletion. Doesn't seem like there's a good way to check.
+ * In any case, we need to keep processing events, so log and continue.
+ */
+ Msg.error(this, "Could not update breakpoint properties: " + e);
+ }
+ }
+
+ /**
+ * Get the user-defined name of the breakpoint
+ *
+ * @return the name
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * Set the name of the breakpoint
+ *
+ * @param name the name
+ */
+ public void setName(String name) {
+ Bookmark bookmark = getBookmark();
+ if (bookmark == null) {
+ throw new IllegalStateException("Must save breakpoint to program before naming it");
+ }
+ this.name = name;
+ writeProperties(bookmark);
+ }
+
+ /**
+ * Get the sleigh injection for this breakpoint
+ *
+ * @return the sleigh injection
+ */
+ public String getEmuSleigh() {
+ return sleigh;
+ }
+
+ /***
+ * Set the sleigh injection for this breakpoint
+ *
+ * @param sleigh the sleigh injection
+ */
+ public void setEmuSleigh(String sleigh) {
+ this.sleigh = sleigh;
+ Bookmark bookmark = getBookmark();
+ if (bookmark == null) {
+ return;
+ }
+ writeProperties(bookmark);
+ }
+
+ /**
+ * Compute the mode of this breakpoint
+ *
+ *
+ * In order to ensure at least the saved state (enablement) can be rendered in the marker margin
+ * in the absence of the breakpoint marker plugin, we use one type of bookmark for disabled
+ * breakpoints, and another for enabled breakpoints. As the state is changing, it's possible for
+ * a brief moment that both bookmarks are present. We thus have a variable for each bookmark and
+ * prefer the "enabled" state. We can determine are state by examining which variable is
+ * non-null. If both are null, the breakpoint is not actually saved to the program, yet. We
+ * cannot return {@link ProgramMode#NONE}, because that would imply there is no static location.
+ *
+ * @return the state
+ */
+ public ProgramMode computeMode() {
+ if (eBookmark != null) {
+ return ProgramMode.ENABLED;
+ }
+ if (dBookmark != null) {
+ return ProgramMode.DISABLED;
+ }
+ return ProgramMode.MISSING;
+ }
+
+ /**
+ * Check if either bookmark is present
+ *
+ * @return true if both are absent, false if either or both is present
+ */
+ public boolean isEmpty() {
+ return eBookmark == null && dBookmark == null;
+ }
+
+ /**
+ * Remove the bookmark
+ *
+ *
+ * Note this does not necessarily destroy the breakpoint, since it may still exist in one or
+ * more traces.
+ */
+ public void deleteFromProgram() {
+ // volatile reads
+ Bookmark eBookmark = this.eBookmark;
+ Bookmark dBookmark = this.dBookmark;
+ try (UndoableTransaction tid = UndoableTransaction.start(program, "Clear breakpoint")) {
+ BookmarkManager bookmarkManager = program.getBookmarkManager();
+ if (eBookmark != null) {
+ bookmarkManager.removeBookmark(eBookmark);
+ }
+ if (dBookmark != null) {
+ bookmarkManager.removeBookmark(dBookmark);
+ }
+ // (e,d)Bookmark Gets nulled on program change callback
+ // If null here, logical breakpoint manager will get confused
+ }
+ }
+
+ /**
+ * Check if the given bookmark can fill the static side of this breakpoint
+ *
+ * @param candProgram the program containing the bookmark
+ * @param candBookmark the bookmark
+ * @return true if the bookmark can represent this breakpoint, false otherwise
+ */
+ public boolean canMerge(Program candProgram, Bookmark candBookmark) {
+ if (program != candProgram) {
+ return false;
+ }
+ if (!address.equals(candBookmark.getAddress())) {
+ return false;
+ }
+ if (length != lengthFromBookmark(candBookmark)) {
+ return false;
+ }
+ if (!Objects.equals(kinds, kindsFromBookmark(candBookmark))) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Get the program where this breakpoint is located
+ *
+ * @return the program
+ */
+ public Program getProgram() {
+ return program;
+ }
+
+ /**
+ * Fill the static side of this breakpoint with the given bookmark
+ *
+ *
+ * The caller should first use {@link #canMerge(Program, Bookmark)} to ensure the bookmark can
+ * actually represent this breakpoint.
+ *
+ * @param bookmark the bookmark
+ * @return true if this changed the breakpoint state
+ */
+ public boolean add(Bookmark bookmark) {
+ if (LogicalBreakpointInternal.BREAKPOINT_ENABLED_BOOKMARK_TYPE
+ .equals(bookmark.getTypeString())) {
+ if (eBookmark == bookmark) {
+ return false;
+ }
+ eBookmark = bookmark;
+ syncProperties(bookmark);
+ return true;
+ }
+ if (LogicalBreakpointInternal.BREAKPOINT_DISABLED_BOOKMARK_TYPE
+ .equals(bookmark.getTypeString())) {
+ if (dBookmark == bookmark) {
+ return false;
+ }
+ dBookmark = bookmark;
+ syncProperties(bookmark);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Remove a bookmark from the static side of this breakpoint
+ *
+ * @param bookmark the bookmark
+ * @return true if this changed the breakpoint state
+ */
+ public boolean remove(Bookmark bookmark) {
+ if (eBookmark == bookmark) {
+ eBookmark = null;
+ return true;
+ }
+ if (dBookmark == bookmark) {
+ dBookmark = null;
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Get the bookmark representing this breakpoint, if present
+ *
+ * @return the bookmark or null
+ */
+ public Bookmark getBookmark() {
+ Bookmark eBookmark = this.eBookmark;
+ if (eBookmark != null) {
+ return eBookmark;
+ }
+ return dBookmark;
+ }
+
+ protected String getComment() {
+ Bookmark bookmark = getBookmark();
+ return bookmark == null ? computeComment() : bookmark.getComment();
+ }
+
+ /**
+ * Check if the bookmark represents an enabled breakpoint
+ *
+ * @return true if enabled, false if anything else
+ */
+ public boolean isEnabled() {
+ return computeMode() == ProgramMode.ENABLED;
+ }
+
+ /**
+ * Check if the bookmark represents a disabled breakpoint
+ *
+ * @return true if disabled, false if anything else
+ */
+ public boolean isDisabled() {
+ return computeMode() == ProgramMode.DISABLED;
+ }
+
+ /**
+ * Compute the category for a new bookmark representing this breakpoint
+ *
+ * @return the category
+ */
+ public String computeCategory() {
+ return TraceBreakpointKindSet.encode(kinds) + ";" + Long.toUnsignedString(length);
+ }
+
+ /**
+ * Change the state of this breakpoint by manipulating bookmarks
+ *
+ *
+ * If the breakpoint is already in the desired state, no change is made. Otherwise, this will
+ * delete the existing bookmark, if present, and create a new bookmark whose type indicates the
+ * desired state. Thus, some event processing may need to take place before this breakpoint's
+ * state is actually updated accordingly.
+ *
+ * @param enabled the desired state, true for {@link ProgramMode#ENABLED}, false for
+ * {@link ProgramMode#DISABLED}.
+ * @param comment the comment to give the breakpoint, almost always from {@link #getComment()}.
+ */
+ public void toggleWithComment(boolean enabled, String comment) {
+ String addType =
+ enabled ? LogicalBreakpointInternal.BREAKPOINT_ENABLED_BOOKMARK_TYPE
+ : LogicalBreakpointInternal.BREAKPOINT_DISABLED_BOOKMARK_TYPE;
+ String delType =
+ enabled ? LogicalBreakpointInternal.BREAKPOINT_DISABLED_BOOKMARK_TYPE
+ : LogicalBreakpointInternal.BREAKPOINT_ENABLED_BOOKMARK_TYPE;
+ try (UndoableTransaction tid =
+ UndoableTransaction.start(program, "Enable breakpoint")) {
+ BookmarkManager manager = program.getBookmarkManager();
+ String catStr = computeCategory();
+ manager.setBookmark(address, addType, catStr, comment);
+ manager.removeBookmarks(new AddressSet(address), delType, catStr,
+ TaskMonitor.DUMMY);
+ }
+ catch (CancelledException e) {
+ throw new AssertionError(e);
+ }
+ }
+
+ /**
+ * Enable this breakpoint
+ *
+ * @see #toggleWithComment(boolean, String)
+ */
+ public void enable() {
+ if (isEnabled()) {
+ return;
+ }
+ toggleWithComment(true, getComment());
+ }
+
+ /**
+ * Disable this breakpoint
+ *
+ * @see #toggleWithComment(boolean, String)
+ */
+ public void disable() {
+ if (isDisabled()) {
+ return;
+ }
+ toggleWithComment(false, getComment());
+ }
+}
diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/TraceBreakpointSet.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/TraceBreakpointSet.java
new file mode 100644
index 0000000000..f25cb18f8a
--- /dev/null
+++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/TraceBreakpointSet.java
@@ -0,0 +1,463 @@
+/* ###
+ * IP: GHIDRA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package ghidra.app.plugin.core.debug.service.breakpoint;
+
+import java.util.*;
+
+import com.google.common.collect.Collections2;
+
+import ghidra.app.services.*;
+import ghidra.app.services.LogicalBreakpoint.TraceMode;
+import ghidra.dbg.target.*;
+import ghidra.dbg.target.TargetBreakpointSpec.TargetBreakpointKind;
+import ghidra.framework.plugintool.PluginTool;
+import ghidra.pcode.exec.SleighUtils;
+import ghidra.program.model.address.Address;
+import ghidra.program.model.address.AddressRange;
+import ghidra.trace.model.Trace;
+import ghidra.trace.model.breakpoint.TraceBreakpoint;
+import ghidra.trace.model.breakpoint.TraceBreakpointKind;
+import ghidra.util.database.UndoableTransaction;
+import utilities.util.IDHashed;
+
+/**
+ * The trace side of a logical breakpoint
+ *
+ *
+ * If the logical breakpoint is a mapped, it will have one of these sets for each trace where the
+ * breakpoint has (or could have) a location. For a lone logical breakpoint, it will have just one
+ * of these for the one trace where its located.
+ */
+class TraceBreakpointSet {
+ private final PluginTool tool;
+ private final Trace trace;
+ private final Address address;
+
+ private final Set> breakpoints = new HashSet<>();
+
+ private TraceRecorder recorder;
+ private String emuSleigh;
+
+ /**
+ * Create a set of breakpoint locations for a given trace
+ *
+ * @param tool the plugin tool for the UI
+ * @param trace the trace whose locations this set collects
+ * @param address the dynamic address where the breakpoint is (or would be) located
+ */
+ public TraceBreakpointSet(PluginTool tool, Trace trace, Address address) {
+ this.tool = Objects.requireNonNull(tool);
+ this.trace = Objects.requireNonNull(trace);
+ this.address = Objects.requireNonNull(address);
+ }
+
+ @Override
+ public String toString() {
+ return String.format("", address, trace.getName(), breakpoints);
+ }
+
+ /**
+ * Set the recorder when the trace is associated to a live target
+ *
+ * @param recorder the recorder
+ */
+ public void setRecorder(TraceRecorder recorder) {
+ this.recorder = recorder;
+ }
+
+ private StateEditingMode getStateEditingMode() {
+ DebuggerStateEditingService service = tool.getService(DebuggerStateEditingService.class);
+ return service == null ? StateEditingMode.DEFAULT : service.getCurrentMode(trace);
+ }
+
+ private long getSnap() {
+ /**
+ * TODO: Not exactly ideal.... It'd be nice to have it passed in, but that's infecting a lot
+ * of methods and putting a burden on the caller, when in most cases, it's going to be the
+ * "current snap" anyway.
+ */
+ DebuggerTraceManagerService service = tool.getService(DebuggerTraceManagerService.class);
+ if (service == null) {
+ return trace.getProgramView().getViewport().getReversedSnaps().get(0);
+ }
+ return service.getCurrentFor(trace).getSnap();
+ }
+
+ /**
+ * Get the trace
+ *
+ * @return
+ */
+ public Trace getTrace() {
+ return trace;
+ }
+
+ /**
+ * Get the dynamic address where the breakpoint is (or would be) located in this trace
+ *
+ * @return the dynamic address
+ */
+ public Address getAddress() {
+ return address;
+ }
+
+ /**
+ * If there is a live target, get the dynamic address in the target's space
+ *
+ * @return the dynamic address on target
+ */
+ public Address computeTargetAddress() {
+ if (recorder == null) {
+ throw new AssertionError();
+ }
+ return recorder.getMemoryMapper().traceToTarget(address);
+ }
+
+ /**
+ * Compute the mode (enablement) of this set
+ *
+ *
+ * In most cases, there is 0 or 1 trace breakpoints that "fit" the logical breakpoint. The mode
+ * is derived from one of {@link TraceBreakpoint#isEnabled(long)} or
+ * {@link TraceBreakpoint#isEmuEnabled(long)}, depending on the UI's control mode for this
+ * trace.
+ *
+ * @return the mode
+ */
+ public TraceMode computeMode() {
+ TraceMode mode = TraceMode.NONE;
+ if (getStateEditingMode().useEmulatedBreakpoints()) {
+ for (IDHashed bpt : breakpoints) {
+ mode = mode.combine(computeEmuMode(bpt.obj));
+ if (mode == TraceMode.MISSING) {
+ return mode;
+ }
+ }
+ return mode;
+ }
+ for (IDHashed bpt : breakpoints) {
+ mode = mode.combine(computeTargetMode(bpt.obj));
+ if (mode == TraceMode.MISSING) {
+ return mode;
+ }
+ }
+ return mode;
+ }
+
+ /**
+ * Compute the mode (enablement) of the given breakpoint
+ *
+ *
+ * The mode is derived from one of {@link TraceBreakpoint#isEnabled(long)} or
+ * {@link TraceBreakpoint#isEmuEnabled(long)}, depending on the UI's control mode for this
+ * trace.
+ *
+ * @param bpt the breakpoint
+ * @return the mode
+ */
+ public TraceMode computeMode(TraceBreakpoint bpt) {
+ return getStateEditingMode().useEmulatedBreakpoints()
+ ? computeEmuMode(bpt)
+ : computeTargetMode(bpt);
+ }
+
+ /**
+ * Compute the mode of the given breakpoint for the target
+ *
+ * @param bpt the breakpoint
+ * @return the mode
+ */
+ public TraceMode computeTargetMode(TraceBreakpoint bpt) {
+ return TraceMode.fromBool(bpt.isEnabled(getSnap()));
+ }
+
+ /**
+ * Compute the mode of the given breakpoint for the emulator
+ *
+ * @param bpt the breakpoint
+ * @return the mode
+ */
+ public TraceMode computeEmuMode(TraceBreakpoint bpt) {
+ return TraceMode.fromBool(bpt.isEmuEnabled(getSnap()));
+ }
+
+ /**
+ * If all breakpoints agree on sleigh injection, get that injection
+ *
+ * @return the injection, or null if there's disagreement.
+ */
+ public String computeSleigh() {
+ String sleigh = null;
+ for (IDHashed bpt : breakpoints) {
+ String s = bpt.obj.getEmuSleigh();
+ if (sleigh != null && !sleigh.equals(s)) {
+ return null;
+ }
+ sleigh = s;
+ }
+ return sleigh;
+ }
+
+ /**
+ * Set the sleigh injection for all breakpoints in this set
+ *
+ * @param emuSleigh the sleigh injection
+ */
+ public void setEmuSleigh(String emuSleigh) {
+ this.emuSleigh = emuSleigh;
+ try (UndoableTransaction tid = UndoableTransaction.start(trace, "Set breakpoint Sleigh")) {
+ for (IDHashed bpt : breakpoints) {
+ bpt.obj.setEmuSleigh(emuSleigh);
+ }
+ }
+ }
+
+ /**
+ * Check if this set actually contains any trace breakpoints
+ *
+ * @return true if empty, false otherwise
+ */
+ public boolean isEmpty() {
+ return breakpoints.isEmpty();
+ }
+
+ /**
+ * Get the breakpoints in this set
+ *
+ * @return the breakpoints
+ */
+ public Collection getBreakpoints() {
+ return Collections2.transform(breakpoints, e -> e.obj);
+ }
+
+ /**
+ * Add a breakpoint to this set
+ *
+ *
+ * The caller should first call {@link #canMerge(TraceBreakpoint)} to check if the breakpoint
+ * "fits."
+ *
+ * @param bpt
+ * @return true if the set actually changed as a result
+ */
+ public boolean add(TraceBreakpoint bpt) {
+ if (SleighUtils.UNCONDITIONAL_BREAK.equals(bpt.getEmuSleigh()) && emuSleigh != null) {
+ try (UndoableTransaction tid =
+ UndoableTransaction.start(trace, "Set breakpoint Sleigh")) {
+ bpt.setEmuSleigh(emuSleigh);
+ }
+ }
+ return breakpoints.add(new IDHashed<>(bpt));
+ }
+
+ /**
+ * Check if the given trace breakpoint "fits" in this set
+ *
+ *
+ * The breakpoint fits if it's dynamic location matches that expected in this set
+ *
+ * @param bpt the breakpoint
+ * @return true if it fits
+ */
+ public boolean canMerge(TraceBreakpoint bpt) {
+ if (trace != bpt.getTrace()) {
+ return false;
+ }
+ if (!address.equals(bpt.getMinAddress())) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Remove a breakpoint from this set
+ *
+ * @param bpt the breakpoint
+ * @return true if the set actually changes as a result
+ */
+ public boolean remove(TraceBreakpoint bpt) {
+ return breakpoints.remove(new IDHashed<>(bpt));
+ }
+
+ /**
+ * Plan to enable the logical breakpoint within this trace
+ *
+ *
+ * This method prefers to use the existing breakpoint specifications which result in breakpoints
+ * at this address. In other words, it favors what the user has already done to effect a
+ * breakpoint at this logical breakpoint's address. If there is no such existing specification,
+ * then it attempts to place a new breakpoint via the target's breakpoint container, usually
+ * resulting in a new spec, which should effect exactly the one specified address. If the
+ * control mode indicates emulated breakpoints, then this simply writes the breakpoint to the
+ * trace database.
+ *
+ *
+ * This method may convert applicable addresses to the target space. If the address cannot be
+ * mapped, it's usually because this logical breakpoint does not apply to the given trace's
+ * target. E.g., the trace may not have a live target, or the logical breakpoint may be in a
+ * module not loaded by the trace.
+ *
+ * @param actions the action set to populate
+ * @param length the length in bytes of the breakpoint
+ * @param kinds the kinds of breakpoint
+ */
+ public void planEnable(BreakpointActionSet actions, long length,
+ Collection kinds) {
+ long snap = getSnap();
+ if (breakpoints.isEmpty()) {
+ if (recorder == null || getStateEditingMode().useEmulatedBreakpoints()) {
+ planPlaceEmu(actions, snap, length, kinds);
+ }
+ else {
+ planPlaceTarget(actions, snap, length, kinds);
+ }
+ }
+ else {
+ if (recorder == null || getStateEditingMode().useEmulatedBreakpoints()) {
+ planEnableEmu(actions);
+ }
+ else {
+ planEnableTarget(actions);
+ }
+ }
+ }
+
+ private void planPlaceTarget(BreakpointActionSet actions, long snap, long length,
+ Collection kinds) {
+ if (snap != recorder.getSnap()) {
+ throw new AssertionError("Target breakpoints must be requested at present snap");
+ }
+ Set tKinds =
+ TraceRecorder.traceToTargetBreakpointKinds(kinds);
+
+ for (TargetBreakpointSpecContainer cont : recorder
+ .collectBreakpointContainers(null)) {
+ LinkedHashSet supKinds = new LinkedHashSet<>(tKinds);
+ supKinds.retainAll(cont.getSupportedBreakpointKinds());
+ actions.add(new PlaceTargetBreakpointActionItem(cont, computeTargetAddress(),
+ length, supKinds));
+ }
+ }
+
+ private void planPlaceEmu(BreakpointActionSet actions, long snap, long length,
+ Collection kinds) {
+ actions.add(
+ new PlaceEmuBreakpointActionItem(trace, snap, address, length, Set.copyOf(kinds),
+ emuSleigh));
+ }
+
+ private void planEnableTarget(BreakpointActionSet actions) {
+ for (IDHashed bpt : breakpoints) {
+ TargetBreakpointLocation loc = recorder.getTargetBreakpoint(bpt.obj);
+ if (loc == null) {
+ continue;
+ }
+ actions.planEnableTarget(loc);
+ }
+ }
+
+ private void planEnableEmu(BreakpointActionSet actions) {
+ for (IDHashed bpt : breakpoints) {
+ actions.planEnableEmu(bpt.obj);
+ }
+ }
+
+ /**
+ * Plan to disable the logical breakpoint in this trace
+ *
+ * @param actions the action set to populate
+ * @param length the length in bytes of the breakpoint
+ * @param kinds the kinds of breakpoint
+ */
+ public void planDisable(BreakpointActionSet actions, long length,
+ Collection kinds) {
+ if (getStateEditingMode().useEmulatedBreakpoints()) {
+ planDisableEmu(actions);
+ }
+ else {
+ planDisableTarget(actions, length, kinds);
+ }
+ }
+
+ private void planDisableTarget(BreakpointActionSet actions, long length,
+ Collection kinds) {
+ Set tKinds = TraceRecorder.traceToTargetBreakpointKinds(kinds);
+ Address targetAddr = computeTargetAddress();
+ for (TargetBreakpointLocation loc : recorder.collectBreakpoints(null)) {
+ AddressRange range = loc.getRange();
+ if (!targetAddr.equals(range.getMinAddress())) {
+ continue;
+ }
+ if (length != range.getLength()) {
+ continue;
+ }
+ TargetBreakpointSpec spec = loc.getSpecification();
+ if (!Objects.equals(spec.getKinds(), tKinds)) {
+ continue;
+ }
+ actions.planDisableTarget(loc);
+ }
+ }
+
+ private void planDisableEmu(BreakpointActionSet actions) {
+ for (IDHashed bpt : breakpoints) {
+ actions.planDisableEmu(bpt.obj);
+ }
+ }
+
+ /**
+ * Plan to delete the logical breakpoint in this trace
+ *
+ * @param actions the action set to populate
+ * @param length the length in bytes of the breakpoint
+ * @param kinds the kinds of breakpoint
+ */
+ public void planDelete(BreakpointActionSet actions, long length,
+ Set kinds) {
+ if (getStateEditingMode().useEmulatedBreakpoints()) {
+ planDeleteEmu(actions);
+ }
+ else {
+ planDeleteTarget(actions, length, kinds);
+ }
+ }
+
+ private void planDeleteTarget(BreakpointActionSet actions, long length,
+ Set kinds) {
+ Set tKinds = TraceRecorder.traceToTargetBreakpointKinds(kinds);
+ Address targetAddr = computeTargetAddress();
+ for (TargetBreakpointLocation loc : recorder.collectBreakpoints(null)) {
+ AddressRange range = loc.getRange();
+ if (!targetAddr.equals(range.getMinAddress())) {
+ continue;
+ }
+ if (length != range.getLength()) {
+ continue;
+ }
+ TargetBreakpointSpec spec = loc.getSpecification();
+ if (!Objects.equals(spec.getKinds(), tKinds)) {
+ continue;
+ }
+ actions.planDeleteTarget(loc);
+ }
+ }
+
+ private void planDeleteEmu(BreakpointActionSet actions) {
+ for (IDHashed bpt : breakpoints) {
+ actions.planDeleteEmu(bpt.obj);
+ }
+ }
+}
diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/editing/DebuggerStateEditingServicePlugin.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/editing/DebuggerStateEditingServicePlugin.java
index 6477158de8..a270db4c80 100644
--- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/editing/DebuggerStateEditingServicePlugin.java
+++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/editing/DebuggerStateEditingServicePlugin.java
@@ -21,32 +21,19 @@ import java.util.concurrent.*;
import ghidra.app.plugin.PluginCategoryNames;
import ghidra.app.plugin.core.debug.*;
-import ghidra.app.plugin.core.debug.event.TraceClosedPluginEvent;
-import ghidra.app.plugin.core.debug.event.TraceOpenedPluginEvent;
+import ghidra.app.plugin.core.debug.event.*;
import ghidra.app.services.*;
-import ghidra.async.AsyncUtils;
+import ghidra.app.services.DebuggerTraceManagerService.ActivationCause;
import ghidra.framework.plugintool.*;
import ghidra.framework.plugintool.annotation.AutoServiceConsumed;
import ghidra.framework.plugintool.util.PluginStatus;
import ghidra.program.model.address.Address;
-import ghidra.program.model.address.AddressRange;
-import ghidra.program.model.lang.Language;
-import ghidra.program.model.lang.Register;
import ghidra.program.model.mem.*;
import ghidra.trace.model.Trace;
import ghidra.trace.model.Trace.TraceProgramViewListener;
-import ghidra.trace.model.guest.TracePlatform;
-import ghidra.trace.model.memory.TraceMemoryOperations;
-import ghidra.trace.model.memory.TraceMemorySpace;
-import ghidra.trace.model.program.*;
-import ghidra.trace.model.thread.TraceThread;
-import ghidra.trace.model.time.schedule.PatchStep;
-import ghidra.trace.model.time.schedule.TraceSchedule;
-import ghidra.trace.util.TraceRegisterUtils;
-import ghidra.util.database.UndoableTransaction;
+import ghidra.trace.model.program.TraceProgramView;
+import ghidra.trace.model.program.TraceProgramViewMemory;
import ghidra.util.datastruct.ListenerSet;
-import ghidra.util.exception.CancelledException;
-import ghidra.util.task.TaskMonitor;
@PluginInfo(
shortDescription = "Debugger machine-state editing service plugin",
@@ -56,6 +43,7 @@ import ghidra.util.task.TaskMonitor;
status = PluginStatus.RELEASED,
eventsConsumed = {
TraceOpenedPluginEvent.class,
+ TraceActivatedPluginEvent.class,
TraceClosedPluginEvent.class,
},
servicesRequired = {
@@ -73,158 +61,14 @@ public class DebuggerStateEditingServicePlugin extends AbstractDebuggerPlugin
public boolean isVariableEditable(Address address, int length) {
DebuggerCoordinates coordinates = getCoordinates();
Trace trace = coordinates.getTrace();
-
- switch (getCurrentMode(trace)) {
- case READ_ONLY:
- return false;
- case WRITE_TARGET:
- return isTargetVariableEditable(coordinates, address, length);
- case WRITE_TRACE:
- return isTraceVariableEditable(coordinates, address, length);
- case WRITE_EMULATOR:
- return isEmulatorVariableEditable(coordinates, address, length);
- }
- throw new AssertionError();
- }
-
- protected boolean isTargetVariableEditable(DebuggerCoordinates coordinates, Address address,
- int length) {
- if (!coordinates.isAliveAndPresent()) {
- return false;
- }
- TraceRecorder recorder = coordinates.getRecorder();
- return recorder.isVariableOnTarget(coordinates.getPlatform(), coordinates.getThread(),
- coordinates.getFrame(), address, length);
- }
-
- protected boolean isTraceVariableEditable(DebuggerCoordinates coordinates, Address address,
- int length) {
- return address.isMemoryAddress() || coordinates.getThread() != null;
- }
-
- protected boolean isEmulatorVariableEditable(DebuggerCoordinates coordinates,
- Address address, int length) {
- if (coordinates.getThread() == null) {
- // A limitation in TraceSchedule, which is used to manifest patches
- return false;
- }
- if (!isTraceVariableEditable(coordinates, address, length)) {
- return false;
- }
- // TODO: Limitation from using Sleigh for patching
- Register ctxReg = coordinates.getTrace().getBaseLanguage().getContextBaseRegister();
- if (ctxReg == Register.NO_CONTEXT) {
- return true;
- }
- AddressRange ctxRange = TraceRegisterUtils.rangeForRegister(ctxReg);
- if (ctxRange.contains(address)) {
- return false;
- }
- return true;
+ return getCurrentMode(trace).isVariableEditable(coordinates, address, length);
}
@Override
public CompletableFuture setVariable(Address address, byte[] data) {
DebuggerCoordinates coordinates = getCoordinates();
Trace trace = coordinates.getTrace();
-
- StateEditingMode mode = getCurrentMode(trace);
- switch (mode) {
- case READ_ONLY:
- return CompletableFuture
- .failedFuture(new MemoryAccessException("Read-only mode"));
- case WRITE_TARGET:
- return writeTargetVariable(coordinates, address, data);
- case WRITE_TRACE:
- return writeTraceVariable(coordinates, address, data);
- case WRITE_EMULATOR:
- return writeEmulatorVariable(coordinates, address, data);
- }
- throw new AssertionError();
- }
-
- protected CompletableFuture writeTargetVariable(DebuggerCoordinates coordinates,
- Address address, byte[] data) {
- TraceRecorder recorder = coordinates.getRecorder();
- if (recorder == null) {
- return CompletableFuture
- .failedFuture(new MemoryAccessException("Trace has no live target"));
- }
- if (!coordinates.isPresent()) {
- return CompletableFuture
- .failedFuture(new MemoryAccessException("View is not the present"));
- }
- return recorder.writeVariable(coordinates.getPlatform(), coordinates.getThread(),
- coordinates.getFrame(), address, data);
- }
-
- protected CompletableFuture writeTraceVariable(DebuggerCoordinates coordinates,
- Address guestAddress, byte[] data) {
- Trace trace = coordinates.getTrace();
- TracePlatform platform = coordinates.getPlatform();
- long snap = coordinates.getViewSnap();
- Address hostAddress = platform.mapGuestToHost(guestAddress);
- if (hostAddress == null) {
- throw new IllegalArgumentException(
- "Guest address " + guestAddress + " is not mapped");
- }
- TraceMemoryOperations memOrRegs;
- Address overlayAddress;
- try (UndoableTransaction txid =
- UndoableTransaction.start(trace, "Edit Variable")) {
- if (hostAddress.isRegisterAddress()) {
- TraceThread thread = coordinates.getThread();
- if (thread == null) {
- throw new IllegalArgumentException("Register edits require a thread.");
- }
- TraceMemorySpace regs = trace.getMemoryManager()
- .getMemoryRegisterSpace(thread, coordinates.getFrame(),
- true);
- memOrRegs = regs;
- overlayAddress = regs.getAddressSpace().getOverlayAddress(hostAddress);
- }
- else {
- memOrRegs = trace.getMemoryManager();
- overlayAddress = hostAddress;
- }
- if (memOrRegs.putBytes(snap, overlayAddress,
- ByteBuffer.wrap(data)) != data.length) {
- return CompletableFuture.failedFuture(new MemoryAccessException());
- }
- }
- return AsyncUtils.NIL;
- }
-
- protected CompletableFuture writeEmulatorVariable(DebuggerCoordinates coordinates,
- Address address, byte[] data) {
- if (!(coordinates.getView() instanceof TraceVariableSnapProgramView)) {
- throw new IllegalArgumentException("Cannot emulate using a Fixed Program View");
- }
- TraceThread thread = coordinates.getThread();
- if (thread == null) {
- // TODO: Well, technically, only for register edits
- // It's a limitation in TraceSchedule. Every step requires a thread
- throw new IllegalArgumentException("Emulator edits require a thread.");
- }
- Language language = coordinates.getPlatform().getLanguage();
- TraceSchedule time = coordinates.getTime()
- .patched(thread, language, PatchStep.generateSleigh(language, address, data));
-
- DebuggerCoordinates withTime = coordinates.time(time);
- Long found = traceManager.findSnapshot(withTime);
- // Materialize it on the same thread (even if swing)
- // It shouldn't take long, since we're only appending one step.
- if (found == null) {
- // TODO: Could still do it async on another thread, no?
- // Not sure it buys anything, since program view will call .get on swing thread
- try {
- emulationSerivce.emulate(coordinates.getPlatform(), time, TaskMonitor.DUMMY);
- }
- catch (CancelledException e) {
- throw new AssertionError(e);
- }
- }
- return traceManager.activateAndNotify(withTime, false);
+ return getCurrentMode(trace).setVariable(tool, coordinates, address, data);
}
}
@@ -356,10 +200,6 @@ public class DebuggerStateEditingServicePlugin extends AbstractDebuggerPlugin
//@AutoServiceConsumed // via method
private DebuggerTraceManagerService traceManager;
- @AutoServiceConsumed
- private DebuggerEmulationService emulationSerivce;
- @AutoServiceConsumed
- private DebuggerModelService modelService;
protected final ListenerForEditorInstallation listenerForEditorInstallation =
new ListenerForEditorInstallation();
@@ -368,8 +208,6 @@ public class DebuggerStateEditingServicePlugin extends AbstractDebuggerPlugin
super(tool);
}
- private static final StateEditingMode DEFAULT_MODE = StateEditingMode.WRITE_TARGET;
-
private final Map currentModes = new HashMap<>();
private final ListenerSet listeners =
@@ -378,23 +216,23 @@ public class DebuggerStateEditingServicePlugin extends AbstractDebuggerPlugin
@Override
public StateEditingMode getCurrentMode(Trace trace) {
synchronized (currentModes) {
- return currentModes.getOrDefault(Objects.requireNonNull(trace), DEFAULT_MODE);
+ return currentModes.getOrDefault(Objects.requireNonNull(trace),
+ StateEditingMode.DEFAULT);
}
}
@Override
- public void setCurrentMode(Trace trace, StateEditingMode mode) {
- boolean fire = false;
+ public void setCurrentMode(Trace trace, StateEditingMode newMode) {
+ StateEditingMode oldMode;
synchronized (currentModes) {
- StateEditingMode old =
- currentModes.getOrDefault(Objects.requireNonNull(trace), DEFAULT_MODE);
- if (mode != old) {
- currentModes.put(trace, mode);
- fire = true;
+ oldMode =
+ currentModes.getOrDefault(Objects.requireNonNull(trace), StateEditingMode.DEFAULT);
+ if (newMode != oldMode) {
+ currentModes.put(trace, newMode);
}
}
- if (fire) {
- listeners.fire.modeChanged(trace, mode);
+ if (newMode != oldMode) {
+ listeners.fire.modeChanged(trace, newMode);
tool.contextChanged(null);
}
}
@@ -424,6 +262,29 @@ public class DebuggerStateEditingServicePlugin extends AbstractDebuggerPlugin
return new FollowsViewStateEditor(view);
}
+ protected void coordinatesActivated(DebuggerCoordinates coordinates, ActivationCause cause) {
+ if (cause != ActivationCause.USER) {
+ return;
+ }
+ Trace trace = coordinates.getTrace();
+ if (trace == null) {
+ return;
+ }
+ StateEditingMode oldMode;
+ StateEditingMode newMode;
+ synchronized (currentModes) {
+ oldMode = currentModes.getOrDefault(trace, StateEditingMode.DEFAULT);
+ newMode = oldMode.modeOnChange(coordinates);
+ if (newMode != oldMode) {
+ currentModes.put(trace, newMode);
+ }
+ }
+ if (newMode != oldMode) {
+ listeners.fire.modeChanged(trace, newMode);
+ tool.contextChanged(null);
+ }
+ }
+
protected void installMemoryEditor(TraceProgramView view) {
TraceProgramViewMemory memory = view.getMemory();
if (memory.getLiveMemoryHandler() != null) {
@@ -481,13 +342,14 @@ public class DebuggerStateEditingServicePlugin extends AbstractDebuggerPlugin
@Override
public void processEvent(PluginEvent event) {
super.processEvent(event);
- if (event instanceof TraceOpenedPluginEvent) {
- TraceOpenedPluginEvent ev = (TraceOpenedPluginEvent) event;
- installAllMemoryEditors(ev.getTrace());
+ if (event instanceof TraceOpenedPluginEvent evt) {
+ installAllMemoryEditors(evt.getTrace());
}
- else if (event instanceof TraceClosedPluginEvent) {
- TraceClosedPluginEvent ev = (TraceClosedPluginEvent) event;
- uninstallAllMemoryEditors(ev.getTrace());
+ else if (event instanceof TraceActivatedPluginEvent evt) {
+ coordinatesActivated(evt.getActiveCoordinates(), evt.getCause());
+ }
+ else if (event instanceof TraceClosedPluginEvent evt) {
+ uninstallAllMemoryEditors(evt.getTrace());
}
}
diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/DebuggerEmulationServicePlugin.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/DebuggerEmulationServicePlugin.java
index a6ee33ed06..7008c30e85 100644
--- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/DebuggerEmulationServicePlugin.java
+++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/DebuggerEmulationServicePlugin.java
@@ -41,7 +41,8 @@ import ghidra.async.AsyncLazyMap;
import ghidra.framework.plugintool.*;
import ghidra.framework.plugintool.annotation.AutoServiceConsumed;
import ghidra.framework.plugintool.util.PluginStatus;
-import ghidra.pcode.emu.PcodeMachine.AccessKind;
+import ghidra.pcode.emu.PcodeMachine.*;
+import ghidra.pcode.exec.InjectionErrorPcodeExecutionException;
import ghidra.program.model.address.*;
import ghidra.program.model.listing.Program;
import ghidra.program.util.ProgramLocation;
@@ -191,7 +192,12 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm
@Override
protected EmulationResult compute(TaskMonitor monitor) throws CancelledException {
- return doRun(from, monitor, scheduler);
+ EmulationResult result = doRun(from, monitor, scheduler);
+ if (result.error() instanceof InjectionErrorPcodeExecutionException) {
+ Msg.showError(this, null, "Breakpoint Emulation Error",
+ "Compilation error in user-provided breakpoint Sleigh code.");
+ }
+ return result;
}
}
@@ -260,6 +266,8 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm
private DebuggerPlatformService platformService;
@AutoServiceConsumed
private DebuggerStaticMappingService staticMappings;
+ @AutoServiceConsumed
+ private DebuggerStateEditingService editingService;
@SuppressWarnings("unused")
private AutoService.Wiring autoServiceWiring;
@@ -359,6 +367,9 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm
trace = ProgramEmulationUtils.launchEmulationTrace(program, ctx.getAddress(), this);
traceManager.openTrace(trace);
traceManager.activateTrace(trace);
+ if (editingService != null) {
+ editingService.setCurrentMode(trace, StateEditingMode.RW_EMULATOR);
+ }
}
catch (IOException e) {
Msg.showError(this, null, actionEmulateProgram.getDescription(),
@@ -544,7 +555,7 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm
for (AddressSpace as : trace.getBaseAddressFactory().getAddressSpaces()) {
for (TraceBreakpoint bpt : bm.getBreakpointsIntersecting(span,
new AddressRangeImpl(as.getMinAddress(), as.getMaxAddress()))) {
- if (!bpt.isEnabled(snap)) {
+ if (!bpt.isEmuEnabled(snap)) {
continue;
}
Set kinds = bpt.getKinds();
@@ -554,7 +565,14 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm
boolean isRead = kinds.contains(TraceBreakpointKind.READ);
boolean isWrite = kinds.contains(TraceBreakpointKind.WRITE);
if (isExecute) {
- emu.addBreakpoint(bpt.getMinAddress(), "1:1");
+ try {
+ emu.inject(bpt.getMinAddress(), bpt.getEmuSleigh());
+ }
+ catch (Exception e) { // This is a bit broad...
+ Msg.error(this,
+ "Error compiling breakpoint Sleigh at " + bpt.getMinAddress(), e);
+ emu.inject(bpt.getMinAddress(), "emu_injection_err();");
+ }
}
if (isRead && isWrite) {
emu.addAccessBreakpoint(bpt.getRange(), AccessKind.RW);
@@ -592,6 +610,7 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm
emu.clearAllInjects();
emu.clearAccessBreakpoints();
emu.setSuspended(false);
+ installBreakpoints(key.trace, key.time.getSnap(), be.ce.emulator());
monitor.initialize(time.totalTickCount() - prevKey.time.totalTickCount());
createRegisterSpaces(trace, time, monitor);
@@ -603,6 +622,7 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm
DebuggerPcodeMachine> emu = emulatorFactory.create(tool, platform, time.getSnap(),
modelService == null ? null : modelService.getRecorder(trace));
try (BusyEmu be = new BusyEmu(new CachedEmulator(key.trace, emu))) {
+ installBreakpoints(key.trace, key.time.getSnap(), be.ce.emulator());
monitor.initialize(time.totalTickCount());
createRegisterSpaces(trace, time, monitor);
monitor.setMessage("Emulating");
@@ -627,7 +647,15 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm
protected TraceSnapshot writeToScratch(CacheKey key, CachedEmulator ce) {
try (UndoableTransaction tid = UndoableTransaction.start(key.trace, "Emulate")) {
TraceSnapshot destSnap = findScratch(key.trace, key.time);
- ce.emulator().writeDown(key.platform, destSnap.getKey(), key.time.getSnap());
+ try {
+ ce.emulator().writeDown(key.platform, destSnap.getKey(), key.time.getSnap());
+ }
+ catch (Throwable e) {
+ Msg.showError(this, null, "Emulate",
+ "There was an issue writing the emulation result to trace trace. " +
+ "The displayed state may be inaccurate and/or incomplete.",
+ e);
+ }
return destSnap;
}
}
@@ -643,10 +671,11 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm
protected EmulationResult doRun(CacheKey key, TaskMonitor monitor, Scheduler scheduler)
throws CancelledException {
try (BusyEmu be = doEmulateFromCached(key, monitor)) {
- installBreakpoints(key.trace, key.time.getSnap(), be.ce.emulator());
TraceThread eventThread = key.time.getEventThread(key.trace);
+ be.ce.emulator().setSoftwareInterruptMode(SwiMode.IGNORE_STEP);
RunResult result = scheduler.run(key.trace, eventThread, be.ce.emulator(), monitor);
key = new CacheKey(key.platform, key.time.advanced(result.schedule()));
+ Msg.info(this, "Stopped emulation at " + key.time);
TraceSnapshot destSnap = writeToScratch(key, be.ce);
cacheEmulator(key, be.ce);
return new RecordEmulationResult(key.time, destSnap.getKey(), result.error());
diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/ProgramEmulationUtils.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/ProgramEmulationUtils.java
index bc9b811203..cb0903a0c9 100644
--- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/ProgramEmulationUtils.java
+++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/ProgramEmulationUtils.java
@@ -23,6 +23,9 @@ import java.util.stream.Stream;
import ghidra.app.plugin.core.debug.service.modules.DebuggerStaticMappingUtils;
import ghidra.app.services.DebuggerEmulationService;
+import ghidra.dbg.target.*;
+import ghidra.dbg.target.schema.TargetObjectSchema;
+import ghidra.dbg.util.*;
import ghidra.framework.model.DomainFile;
import ghidra.program.model.address.*;
import ghidra.program.model.lang.*;
@@ -34,8 +37,9 @@ import ghidra.trace.database.DBTrace;
import ghidra.trace.model.*;
import ghidra.trace.model.memory.*;
import ghidra.trace.model.modules.TraceConflictedMappingException;
-import ghidra.trace.model.thread.TraceThread;
-import ghidra.trace.model.thread.TraceThreadManager;
+import ghidra.trace.model.target.TraceObject;
+import ghidra.trace.model.target.TraceObjectKeyPath;
+import ghidra.trace.model.thread.*;
import ghidra.trace.model.time.TraceSnapshot;
import ghidra.util.*;
import ghidra.util.database.UndoableTransaction;
@@ -137,10 +141,11 @@ public enum ProgramEmulationUtils {
* A transaction must already be started on the destination trace.
*
* @param snapshot the destination snapshot, usually 0
- * @param program the progam to load
+ * @param program the program to load
*/
public static void loadExecutable(TraceSnapshot snapshot, Program program) {
Trace trace = snapshot.getTrace();
+ PathPattern patRegion = computePatternRegion(trace);
Map extremaBySpace = new HashMap<>();
try {
for (MemoryBlock block : program.getMemory().getBlocks()) {
@@ -156,8 +161,9 @@ public enum ProgramEmulationUtils {
String modName = getModuleName(program);
// TODO: Do I populate modules, since the mapping will already be done?
- String path = "Modules[" + modName + "].Sections[" + block.getName() + "-" +
- block.getStart() + "]";
+ String path = PathUtils.toString(patRegion
+ .applyKeys(block.getStart() + "-" + modName + ":" + block.getName())
+ .getSingletonPath());
trace.getMemoryManager()
.createRegion(path, snapshot.getKey(), range, getRegionFlags(block));
}
@@ -176,6 +182,28 @@ public enum ProgramEmulationUtils {
// N.B. Bytes will be loaded lazily
}
+ public static PathPattern computePattern(Trace trace, Class extends TargetObject> iface) {
+ TargetObjectSchema root = trace.getObjectManager().getRootSchema();
+ if (root == null) {
+ return new PathPattern(PathUtils.parse("Memory[]"));
+ }
+ PathMatcher matcher = root.searchFor(iface, true);
+ PathPattern pattern = matcher.getSingletonPattern();
+ if (pattern == null || pattern.countWildcards() != 1) {
+ throw new IllegalArgumentException(
+ "Cannot find unique " + iface.getSimpleName() + " container");
+ }
+ return pattern;
+ }
+
+ public static PathPattern computePatternRegion(Trace trace) {
+ return computePattern(trace, TargetMemoryRegion.class);
+ }
+
+ public static PathPattern computePatternThread(Trace trace) {
+ return computePattern(trace, TargetThread.class);
+ }
+
/**
* Spawn a new thread in the given trace at the given creation snap
*
@@ -188,12 +216,16 @@ public enum ProgramEmulationUtils {
*/
public static TraceThread spawnThread(Trace trace, long snap) {
TraceThreadManager tm = trace.getThreadManager();
+ PathPattern patThread = computePatternThread(trace);
long next = tm.getAllThreads().size();
- while (!tm.getThreadsByPath("Threads[" + next + "]").isEmpty()) {
+ String path;
+ while (!tm.getThreadsByPath(path =
+ PathUtils.toString(patThread.applyKeys(Long.toString(next)).getSingletonPath()))
+ .isEmpty()) {
next++;
}
try {
- return tm.createThread("Threads[" + next + "]", "[" + next + "]", snap);
+ return tm.createThread(path, "[" + next + "]", snap);
}
catch (DuplicateNameException e) {
throw new AssertionError(e);
@@ -214,6 +246,20 @@ public enum ProgramEmulationUtils {
public static void initializeRegisters(Trace trace, long snap, TraceThread thread,
Program program, Address tracePc, Address programPc, TraceMemoryRegion stack) {
TraceMemoryManager memory = trace.getMemoryManager();
+ if (thread instanceof TraceObjectThread ot) {
+ TraceObject object = ot.getObject();
+ PathPredicates regsMatcher = object.getRoot()
+ .getTargetSchema()
+ .searchForRegisterContainer(0, object.getCanonicalPath().getKeyList());
+ if (regsMatcher.isEmpty()) {
+ throw new IllegalArgumentException("Cannot create register container");
+ }
+ for (PathPattern regsPattern : regsMatcher.getPatterns()) {
+ trace.getObjectManager()
+ .createObject(TraceObjectKeyPath.of(regsPattern.getSingletonPath()));
+ break;
+ }
+ }
TraceMemorySpace regSpace = memory.getMemoryRegisterSpace(thread, true);
if (program != null) {
ProgramContext ctx = program.getProgramContext();
@@ -269,11 +315,18 @@ public enum ProgramEmulationUtils {
TraceMemoryManager mm = trace.getMemoryManager();
AddressSetView left =
new DifferenceAddressSetView(except0, mm.getRegionsAddressSet(snap));
+ PathPattern patRegion = computePatternRegion(trace);
try {
for (AddressRange candidate : left) {
if (Long.compareUnsigned(candidate.getLength(), size) > 0) {
AddressRange alloc = new AddressRangeImpl(candidate.getMinAddress(), size);
- return mm.createRegion(thread.getPath() + ".Stack", snap, alloc,
+ String threadName = PathUtils.isIndex(thread.getName())
+ ? PathUtils.parseIndex(thread.getName())
+ : thread.getName();
+ String path = PathUtils.toString(
+ patRegion.applyKeys(alloc.getMinAddress() + "-stack " + threadName)
+ .getSingletonPath());
+ return mm.createRegion(path, snap, alloc,
TraceMemoryFlag.READ, TraceMemoryFlag.WRITE);
}
}
diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DebuggerModelServicePlugin.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DebuggerModelServicePlugin.java
index 45451c2cfc..9d06e75f10 100644
--- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DebuggerModelServicePlugin.java
+++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DebuggerModelServicePlugin.java
@@ -40,6 +40,7 @@ import ghidra.app.plugin.core.debug.mapping.*;
import ghidra.app.plugin.core.debug.service.model.launch.DebuggerProgramLaunchOffer;
import ghidra.app.plugin.core.debug.service.model.launch.DebuggerProgramLaunchOpinion;
import ghidra.app.services.*;
+import ghidra.app.services.DebuggerTraceManagerService.ActivationCause;
import ghidra.async.AsyncFence;
import ghidra.dbg.*;
import ghidra.dbg.target.*;
@@ -459,7 +460,8 @@ public class DebuggerModelServicePlugin extends Plugin
if (traceManager != null) {
Trace trace = recorder.getTrace();
traceManager.openTrace(trace);
- traceManager.activateTrace(trace);
+ traceManager.activate(traceManager.resolveTrace(trace),
+ ActivationCause.ACTIVATE_DEFAULT);
}
return recorder;
}
diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/tracemgr/DebuggerTraceManagerServicePlugin.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/tracemgr/DebuggerTraceManagerServicePlugin.java
index 273db9727d..be9ba6fc64 100644
--- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/tracemgr/DebuggerTraceManagerServicePlugin.java
+++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/tracemgr/DebuggerTraceManagerServicePlugin.java
@@ -32,6 +32,7 @@ import ghidra.app.plugin.core.debug.event.*;
import ghidra.app.plugin.core.debug.gui.DebuggerResources.*;
import ghidra.app.plugin.core.debug.mapping.DebuggerPlatformMapper;
import ghidra.app.services.*;
+import ghidra.app.services.DebuggerStateEditingService.StateEditingModeChangeListener;
import ghidra.async.*;
import ghidra.async.AsyncConfigFieldCodec.BooleanAsyncConfigFieldCodec;
import ghidra.dbg.target.*;
@@ -112,7 +113,7 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
if (supportsFocus(recorder)) {
// TODO: Same for stack frame? I can't imagine it's as common as this....
if (thread == recorder.getTraceThreadForSuccessor(recorder.getFocus())) {
- activate(current.thread(thread));
+ activate(current.thread(thread), ActivationCause.SYNC_MODEL);
}
return;
}
@@ -122,16 +123,18 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
if (current.getThread() != null) {
return;
}
- activate(current.thread(thread));
+ activate(current.thread(thread), ActivationCause.ACTIVATE_DEFAULT);
}
private void threadDeleted(TraceThread thread) {
- DebuggerCoordinates last = lastCoordsByTrace.get(trace);
- if (last != null && last.getThread() == thread) {
- lastCoordsByTrace.remove(trace);
+ synchronized (listenersByTrace) {
+ DebuggerCoordinates last = lastCoordsByTrace.get(trace);
+ if (last != null && last.getThread() == thread) {
+ lastCoordsByTrace.remove(trace);
+ }
}
if (current.getThread() == thread) {
- activate(current.thread(null));
+ activate(current.thread(null), ActivationCause.ACTIVATE_DEFAULT);
}
}
@@ -146,7 +149,7 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
if (!object.isRoot()) {
return;
}
- activate(current.object(object));
+ activate(current.object(object), ActivationCause.SYNC_MODEL);
}
}
@@ -231,17 +234,32 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
}
}
+ class ForFollowPresentListener implements StateEditingModeChangeListener {
+ @Override
+ public void modeChanged(Trace trace, StateEditingMode mode) {
+ if (trace != current.getTrace() || !mode.followsPresent()) {
+ return;
+ }
+ TraceRecorder curRecorder = current.getRecorder();
+ if (curRecorder == null) {
+ return;
+ }
+ // TODO: Also re-sync focused thread/frame?
+ activateNoFocus(current.snap(curRecorder.getSnap()), ActivationCause.FOLLOW_PRESENT);
+ }
+ }
+
protected final Map lastCoordsByTrace = new WeakHashMap<>();
protected final Map listenersByTrace = new WeakHashMap<>();
protected final Set tracesView = Collections.unmodifiableSet(listenersByTrace.keySet());
private final ForRecordersListener forRecordersListener = new ForRecordersListener();
+ private final ForFollowPresentListener forFollowPresentListener =
+ new ForFollowPresentListener();
protected DebuggerCoordinates current = DebuggerCoordinates.NOWHERE;
protected TargetObject curObj;
@AutoConfigStateField(codec = BooleanAsyncConfigFieldCodec.class)
- protected final AsyncReference autoActivatePresent = new AsyncReference<>(true);
- @AutoConfigStateField(codec = BooleanAsyncConfigFieldCodec.class)
protected final AsyncReference saveTracesByDefault = new AsyncReference<>(true);
@AutoConfigStateField(codec = BooleanAsyncConfigFieldCodec.class)
protected final AsyncReference synchronizeFocus = new AsyncReference<>(true);
@@ -254,6 +272,8 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
private DebuggerEmulationService emulationService;
@AutoServiceConsumed
private DebuggerPlatformService platformService;
+ // @AutoServiceConsumed via method
+ private DebuggerStateEditingService editingService;
@SuppressWarnings("unused")
private final AutoService.Wiring autoServiceWiring;
@@ -448,6 +468,17 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
}
}
+ @AutoServiceConsumed
+ private void setEditingService(DebuggerStateEditingService editingService) {
+ if (this.editingService != null) {
+ this.editingService.removeModeChangeListener(forFollowPresentListener);
+ }
+ this.editingService = editingService;
+ if (this.editingService != null) {
+ this.editingService.addModeChangeListener(forFollowPresentListener);
+ }
+ }
+
@Override
public Class>[] getSupportedDataTypes() {
return new Class>[] { Trace.class };
@@ -574,20 +605,38 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
return false;
}
}
- activateNoFocus(getCurrentFor(trace).object(obj));
+ activateNoFocus(getCurrentFor(trace).object(obj), ActivationCause.SYNC_MODEL);
return true;
}
+ private boolean isFollowsPresent(Trace trace) {
+ StateEditingMode mode = editingService == null
+ ? StateEditingMode.DEFAULT
+ : editingService.getCurrentMode(trace);
+ return mode.followsPresent();
+ }
+
protected void doTraceRecorderAdvanced(TraceRecorder recorder, long snap) {
- if (!autoActivatePresent.get()) {
+ Trace trace = recorder.getTrace();
+ if (!isFollowsPresent(trace)) {
return;
}
- if (recorder.getTrace() != current.getTrace()) {
- // TODO: Could advance view, which might be desirable anyway
- // Would also obviate checks in resolveCoordinates and updateCurrentRecorder
+ if (trace != current.getTrace()) {
+ /**
+ * The snap needs to match upon re-activating this trace, lest it look like the user
+ * intentionally navigated to the past, causing the mode to switch away from target.
+ */
+ DebuggerCoordinates inactive = null;
+ synchronized (listenersByTrace) {
+ DebuggerCoordinates curForTrace = getCurrentFor(trace);
+ inactive = curForTrace.snap(snap);
+ lastCoordsByTrace.put(trace, inactive);
+ }
+ trace.getProgramView().setSnap(snap);
+ firePluginEvent(new TraceInactiveCoordinatesPluginEvent(getName(), inactive));
return;
}
- activateSnap(snap);
+ activate(resolveSnap(snap), ActivationCause.FOLLOW_PRESENT);
}
protected TracePlatform getPlatformForMapper(Trace trace, TraceObject object,
@@ -607,7 +656,7 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
lastCoordsByTrace.put(trace, adj);
if (trace == current.getTrace()) {
current = adj;
- fireLocationEvent(adj);
+ fireLocationEvent(adj, ActivationCause.MAPPER_CHANGED);
}
}
}
@@ -628,12 +677,10 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
return;
}
DebuggerCoordinates toActivate = current.recorder(recorder);
- if (autoActivatePresent.get()) {
- activate(toActivate.snap(recorder.getSnap()));
- }
- else {
- activate(toActivate);
+ if (isFollowsPresent(current.getTrace())) {
+ toActivate = toActivate.snap(recorder.getSnap());
}
+ activate(toActivate, ActivationCause.FOLLOW_PRESENT);
}
@Override
@@ -740,10 +787,11 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
return emulationService.backgroundEmulate(coordinates.getPlatform(), coordinates.getTime());
}
- protected CompletableFuture prepareViewAndFireEvent(DebuggerCoordinates coordinates) {
+ protected CompletableFuture prepareViewAndFireEvent(DebuggerCoordinates coordinates,
+ ActivationCause cause) {
TraceVariableSnapProgramView varView = (TraceVariableSnapProgramView) coordinates.getView();
if (varView == null) { // Should only happen with NOWHERE
- fireLocationEvent(coordinates);
+ fireLocationEvent(coordinates, cause);
return AsyncUtils.NIL;
}
return materialize(coordinates).thenAcceptAsync(snap -> {
@@ -751,12 +799,12 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
return; // We navigated elsewhere before emulation completed
}
varView.setSnap(snap);
- fireLocationEvent(coordinates);
+ fireLocationEvent(coordinates, cause);
}, SwingExecutorService.MAYBE_NOW);
}
- protected void fireLocationEvent(DebuggerCoordinates coordinates) {
- firePluginEvent(new TraceActivatedPluginEvent(getName(), coordinates));
+ protected void fireLocationEvent(DebuggerCoordinates coordinates, ActivationCause cause) {
+ firePluginEvent(new TraceActivatedPluginEvent(getName(), coordinates, cause));
}
@Override
@@ -980,7 +1028,7 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
//Msg.debug(this, "Remaining Consumers of " + trace + ": " + trace.getConsumerList());
}
if (current.getTrace() == trace) {
- activate(DebuggerCoordinates.NOWHERE);
+ activate(DebuggerCoordinates.NOWHERE, ActivationCause.ACTIVATE_DEFAULT);
}
else {
contextChanged();
@@ -1004,7 +1052,7 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
@Override
protected void dispose() {
super.dispose();
- activate(DebuggerCoordinates.NOWHERE);
+ activate(DebuggerCoordinates.NOWHERE, ActivationCause.ACTIVATE_DEFAULT);
synchronized (listenersByTrace) {
Iterator it = listenersByTrace.keySet().iterator();
while (it.hasNext()) {
@@ -1027,12 +1075,12 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
return elem.toString();
}
- protected void activateNoFocus(DebuggerCoordinates coordinates) {
+ protected void activateNoFocus(DebuggerCoordinates coordinates, ActivationCause cause) {
DebuggerCoordinates resolved = doSetCurrent(coordinates);
if (resolved == null) {
return;
}
- prepareViewAndFireEvent(resolved);
+ prepareViewAndFireEvent(resolved, cause);
}
protected static boolean isSameFocus(DebuggerCoordinates prev, DebuggerCoordinates resolved) {
@@ -1082,7 +1130,7 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
@Override
public CompletableFuture activateAndNotify(DebuggerCoordinates coordinates,
- boolean syncTargetFocus) {
+ ActivationCause cause, boolean syncTargetFocus) {
DebuggerCoordinates prev;
DebuggerCoordinates resolved;
@@ -1098,7 +1146,7 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
if (resolved == null) {
return AsyncUtils.NIL;
}
- CompletableFuture future = prepareViewAndFireEvent(resolved);
+ CompletableFuture future = prepareViewAndFireEvent(resolved, cause);
if (!syncTargetFocus) {
return future;
}
@@ -1118,12 +1166,8 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
}
@Override
- public void activate(DebuggerCoordinates coordinates) {
- activateAndNotify(coordinates, true); // Drop future on floor
- }
-
- public void activateNoFocusChange(DebuggerCoordinates coordinates) {
- activateAndNotify(coordinates, false); // Drop future on floor
+ public void activate(DebuggerCoordinates coordinates, ActivationCause cause) {
+ activateAndNotify(coordinates, cause, true); // Drop future on floor
}
@Override
@@ -1169,38 +1213,6 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
return current.object(object);
}
- @Override
- public void setAutoActivatePresent(boolean enabled) {
- autoActivatePresent.set(enabled, null);
- TraceRecorder curRecorder = current.getRecorder();
- if (enabled) {
- // TODO: Re-sync focus. This wasn't working. Not sure it's appropriate anyway.
- /*if (synchronizeFocus && curRef != null) {
- if (doModelObjectFocused(curRef, false)) {
- return;
- }
- }*/
- if (curRecorder != null) {
- activateNoFocus(current.snap(curRecorder.getSnap()));
- }
- }
- }
-
- @Override
- public boolean isAutoActivatePresent() {
- return autoActivatePresent.get();
- }
-
- @Override
- public void addAutoActivatePresentChangeListener(BooleanChangeAdapter listener) {
- autoActivatePresent.addChangeListener(listener);
- }
-
- @Override
- public void removeAutoActivatePresentChangeListener(BooleanChangeAdapter listener) {
- autoActivatePresent.removeChangeListener(listener);
- }
-
@Override
public void setSynchronizeFocus(boolean enabled) {
synchronizeFocus.set(enabled, null);
@@ -1315,17 +1327,20 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
@Override
public void readDataState(SaveState saveState) {
- int traceCount = saveState.getInt(KEY_TRACE_COUNT, 0);
- for (int index = 0; index < traceCount; index++) {
- String stateName = PREFIX_OPEN_TRACE + index;
- // Trace will be opened by readDataState, resolve causes update to focus and view
- DebuggerCoordinates coords =
- DebuggerCoordinates.readDataState(tool, saveState, stateName);
- if (coords.getTrace() != null) {
- lastCoordsByTrace.put(coords.getTrace(), coords);
+ synchronized (listenersByTrace) {
+ int traceCount = saveState.getInt(KEY_TRACE_COUNT, 0);
+ for (int index = 0; index < traceCount; index++) {
+ String stateName = PREFIX_OPEN_TRACE + index;
+ // Trace will be opened by readDataState, resolve causes update to focus and view
+ DebuggerCoordinates coords =
+ DebuggerCoordinates.readDataState(tool, saveState, stateName);
+ if (coords.getTrace() != null) {
+ lastCoordsByTrace.put(coords.getTrace(), coords);
+ }
}
}
- activate(DebuggerCoordinates.readDataState(tool, saveState, KEY_CURRENT_COORDS));
+ activate(DebuggerCoordinates.readDataState(tool, saveState, KEY_CURRENT_COORDS),
+ ActivationCause.RESTORE_STATE);
}
}
diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/utils/DebouncedRowWrappedEnumeratedColumnTableModel.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/utils/DebouncedRowWrappedEnumeratedColumnTableModel.java
index b986a821db..8c5f32f6ed 100644
--- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/utils/DebouncedRowWrappedEnumeratedColumnTableModel.java
+++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/utils/DebouncedRowWrappedEnumeratedColumnTableModel.java
@@ -30,9 +30,9 @@ public class DebouncedRowWrappedEnumeratedColumnTableModel & E
AsyncDebouncer debouncer = new AsyncDebouncer(AsyncTimer.DEFAULT_TIMER, 100);
public DebouncedRowWrappedEnumeratedColumnTableModel(PluginTool tool, String name,
- Class colType,
- Function keyFunc, Function wrapper) {
- super(tool, name, colType, keyFunc, wrapper);
+ Class colType, Function keyFunc, Function wrapper,
+ Function getter) {
+ super(tool, name, colType, keyFunc, wrapper, getter);
debouncer.addListener(this::settled);
}
diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/services/DebuggerLogicalBreakpointService.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/services/DebuggerLogicalBreakpointService.java
index 6e9d14c5c1..c82255606a 100644
--- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/services/DebuggerLogicalBreakpointService.java
+++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/services/DebuggerLogicalBreakpointService.java
@@ -33,7 +33,7 @@ import ghidra.trace.model.program.TraceProgramView;
@ServiceInfo( //
defaultProvider = DebuggerLogicalBreakpointServicePlugin.class,
- description = "Aggregate breakpoints for programs and live traces")
+ description = "Aggregate breakpoints for programs and traces")
public interface DebuggerLogicalBreakpointService {
/**
* Get all logical breakpoints known to the tool.
@@ -54,11 +54,11 @@ public interface DebuggerLogicalBreakpointService {
NavigableMap> getBreakpoints(Program program);
/**
- * Get a map of addresses to collected logical breakpoints (at present) for a given trace.
+ * Get a map of addresses to collected logical breakpoints for a given trace.
*
*
- * The trace must be associated with a live target. The returned map collects live breakpoints
- * in the recorded target, using trace breakpoints from the recorder's current snapshot.
+ * The map only includes breakpoints visible in the trace's primary view. Visibility depends on
+ * the view's snapshot.
*
* @param trace the trace database
* @return the map of logical breakpoints
@@ -78,12 +78,11 @@ public interface DebuggerLogicalBreakpointService {
Set getBreakpointsAt(Program program, Address address);
/**
- * Get the collected logical breakpoints (at present) at the given trace location.
+ * Get the collected logical breakpoints at the given trace location.
*
*
- * The trace must be associated with a live target. The returned collection includes live
- * breakpoints in the recorded target, using trace breakpoints from the recorders' current
- * snapshot.
+ * The set only includes breakpoints visible in the trace's primary view. Visibility depends on
+ * the view's snapshot.
*
* @param trace the trace database
* @param address the address
@@ -95,8 +94,8 @@ public interface DebuggerLogicalBreakpointService {
* Get the logical breakpoint of which the given trace breakpoint is a part
*
*
- * If the given trace breakpoint is not part of any logical breakpoint, e.g., because it is not
- * on a live target, then null is returned.
+ * If the given trace breakpoint is not part of any logical breakpoint, e.g., because the trace
+ * is not opened in the tool or events are still being processed, then null is returned.
*
* @param bpt the trace breakpoint
* @return the logical breakpoint, or null
@@ -108,8 +107,8 @@ public interface DebuggerLogicalBreakpointService {
*
*
* The {@code program} field for the location may be either a program database (static image) or
- * a view for a trace associated with a live target. If it is the latter, the view's current
- * snapshot is ignored, in favor of the associated recorder's current snapshot.
+ * a view for a trace. If it is the latter, the view's snapshot is ignored in favor of the
+ * trace's primary view's snapshot.
*
*
* If {@code program} is a static image, this is equivalent to using
@@ -252,8 +251,7 @@ public interface DebuggerLogicalBreakpointService {
}
/**
- * Create an enabled breakpoint at the given program location and each mapped live trace
- * location.
+ * Create an enabled breakpoint at the given program location and each mapped trace location.
*
*
* The implementation should take care not to create the same breakpoint multiple times. The
@@ -278,12 +276,12 @@ public interface DebuggerLogicalBreakpointService {
* this is the case, the breakpoint will have no name.
*
*
- * Note, the debugger ultimately determines the placement behavior. If it is managing multiple
- * targets, it is possible the breakpoint will be effective in another trace. This fact should
- * be reflected correctly in the resulting logical markings once all resulting events have been
- * processed.
+ * Note for live targets, the debugger ultimately determines the placement behavior. If it is
+ * managing multiple targets, it is possible the breakpoint will be effective in another trace.
+ * This fact should be reflected correctly in the resulting logical markings once all resulting
+ * events have been processed.
*
- * @param trace the given trace, which must be live
+ * @param trace the given trace
* @param address the address in the trace (as viewed in the present)
* @param length size of the breakpoint, may be ignored by debugger
* @param kinds the kinds of breakpoint
@@ -367,7 +365,7 @@ public interface DebuggerLogicalBreakpointService {
CompletableFuture deleteAll(Collection col, Trace trace);
/**
- * Presuming the given locations are live, enable them
+ * Enable the given locations
*
* @param col the trace breakpoints
* @return a future which completes when the command has been processed
@@ -375,7 +373,7 @@ public interface DebuggerLogicalBreakpointService {
CompletableFuture enableLocs(Collection col);
/**
- * Presuming the given locations are live, disable them
+ * Disable the given locations
*
* @param col the trace breakpoints
* @return a future which completes when the command has been processed
@@ -383,7 +381,7 @@ public interface DebuggerLogicalBreakpointService {
CompletableFuture disableLocs(Collection col);
/**
- * Presuming the given locations are live, delete them
+ * Delete the given locations
*
* @param col the trace breakpoints
* @return a future which completes when the command has been processed
diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/services/DebuggerStateEditingService.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/services/DebuggerStateEditingService.java
index aec0106e2f..345be70c51 100644
--- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/services/DebuggerStateEditingService.java
+++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/services/DebuggerStateEditingService.java
@@ -17,16 +17,12 @@ package ghidra.app.services;
import java.util.concurrent.CompletableFuture;
-import javax.swing.Icon;
-
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
-import ghidra.app.plugin.core.debug.gui.DebuggerResources;
import ghidra.app.plugin.core.debug.service.editing.DebuggerStateEditingServicePlugin;
import ghidra.framework.plugintool.ServiceInfo;
import ghidra.pcode.utils.Utils;
import ghidra.program.model.address.Address;
-import ghidra.program.model.lang.Register;
-import ghidra.program.model.lang.RegisterValue;
+import ghidra.program.model.lang.*;
import ghidra.program.model.mem.LiveMemoryHandler;
import ghidra.trace.model.Trace;
import ghidra.trace.model.program.TraceProgramView;
@@ -35,47 +31,6 @@ import ghidra.trace.model.program.TraceProgramView;
defaultProvider = DebuggerStateEditingServicePlugin.class,
description = "Centralized service for modifying machine states")
public interface DebuggerStateEditingService {
- enum StateEditingMode {
- READ_ONLY(DebuggerResources.NAME_EDIT_MODE_READ_ONLY, //
- DebuggerResources.ICON_EDIT_MODE_READ_ONLY) {
- @Override
- public boolean canEdit(DebuggerCoordinates coordinates) {
- return false;
- }
- },
- WRITE_TARGET(DebuggerResources.NAME_EDIT_MODE_WRITE_TARGET, //
- DebuggerResources.ICON_EDIT_MODE_WRITE_TARGET) {
- @Override
- public boolean canEdit(DebuggerCoordinates coordinates) {
- return coordinates.isAliveAndPresent();
- }
- },
- WRITE_TRACE(DebuggerResources.NAME_EDIT_MODE_WRITE_TRACE, //
- DebuggerResources.ICON_EDIT_MODE_WRITE_TRACE) {
- @Override
- public boolean canEdit(DebuggerCoordinates coordinates) {
- return coordinates.getTrace() != null;
- }
- },
- WRITE_EMULATOR(DebuggerResources.NAME_EDIT_MODE_WRITE_EMULATOR, //
- DebuggerResources.ICON_EDIT_MODE_WRITE_EMULATOR) {
- @Override
- public boolean canEdit(DebuggerCoordinates coordinates) {
- return coordinates.getTrace() != null;
- }
- };
-
- public final String name;
- public final Icon icon;
-
- private StateEditingMode(String name, Icon icon) {
- this.name = name;
- this.icon = icon;
- }
-
- public abstract boolean canEdit(DebuggerCoordinates coordinates);
- }
-
interface StateEditor {
DebuggerStateEditingService getService();
diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/services/DebuggerTraceManagerService.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/services/DebuggerTraceManagerService.java
index 23b9593227..101fa0b922 100644
--- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/services/DebuggerTraceManagerService.java
+++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/services/DebuggerTraceManagerService.java
@@ -38,6 +38,49 @@ import ghidra.util.TriConsumer;
@ServiceInfo(defaultProvider = DebuggerTraceManagerServicePlugin.class)
public interface DebuggerTraceManagerService {
+ /**
+ * The reason coordinates were activated
+ */
+ public enum ActivationCause {
+ /**
+ * The change was driven by the user
+ *
+ *
+ * TODO: Distinguish between API and GUI?
+ */
+ USER,
+ /**
+ * A trace was activated because a recording was started, usually when a target is launched
+ */
+ START_RECORDING,
+ /**
+ * The change was driven by the model focus, possibly indirectly by the user
+ */
+ SYNC_MODEL,
+ /**
+ * The change was driven by the recorder advancing a snapshot
+ */
+ FOLLOW_PRESENT,
+ /**
+ * The change was caused by a change to the mapper selection, probably indirectly by the
+ * user
+ */
+ MAPPER_CHANGED,
+ /**
+ * Some default coordinates were activated
+ *
+ *
+ * Please don't misunderstand this as the "default cause." Rather, e.g., when the current
+ * trace is closed, and the manager needs to activate new coordinates, it is activating
+ * "default coordinates."
+ */
+ ACTIVATE_DEFAULT,
+ /**
+ * The tool is restoring its data state
+ */
+ RESTORE_STATE,
+ }
+
/**
* An adapter that works nicely with an {@link AsyncReference}
*
@@ -69,7 +112,7 @@ public interface DebuggerTraceManagerService {
* Get the current coordinates
*
*
- * This entails everything except the current address
+ * This entails everything except the current address.
*
* @return the current coordinates
*/
@@ -233,11 +276,22 @@ public interface DebuggerTraceManagerService {
* thread for the desired trace.
*
* @param coordinates the desired coordinates
+ * @param cause the cause of the activation
* @param syncTargetFocus true synchronize the current target to the same coordinates
* @return a future which completes when emulation and navigation is complete
*/
CompletableFuture activateAndNotify(DebuggerCoordinates coordinates,
- boolean syncTargetFocus);
+ ActivationCause cause, boolean syncTargetFocus);
+
+ /**
+ * Activate the given coordinates, caused by the user
+ *
+ * @see #activate(DebuggerCoordinates, ActivationCause)
+ * @param coordinates the desired coordinates
+ */
+ default void activate(DebuggerCoordinates coordinates) {
+ activate(coordinates, ActivationCause.USER);
+ }
/**
* Activate the given coordinates, synchronizing the current target, if possible
@@ -247,8 +301,9 @@ public interface DebuggerTraceManagerService {
* {@link #activateAndNotify(DebuggerCoordinates, boolean)}.
*
* @param coordinates the desired coordinates
+ * @param cause the cause of activation
*/
- void activate(DebuggerCoordinates coordinates);
+ void activate(DebuggerCoordinates coordinates, ActivationCause cause);
/**
* Resolve coordinates for the given trace using the manager's "best judgment"
@@ -388,38 +443,6 @@ public interface DebuggerTraceManagerService {
activate(resolveObject(object));
}
- /**
- * Control whether the trace manager automatically activates the "present snapshot"
- *
- *
- * Auto activation only applies when the current trace advances. It never changes to another
- * trace.
- *
- * @param enabled true to enable auto activation
- */
- void setAutoActivatePresent(boolean enabled);
-
- /**
- * Check if the trace manager automatically activate the "present snapshot"
- *
- * @return true if auto activation is enabled
- */
- boolean isAutoActivatePresent();
-
- /**
- * Add a listener for changes to auto activation enablement
- *
- * @param listener the listener to receive change notifications
- */
- void addAutoActivatePresentChangeListener(BooleanChangeAdapter listener);
-
- /**
- * Remove a listener for changes to auto activation enablement
- *
- * @param listener the listener receiving change notifications
- */
- void removeAutoActivatePresentChangeListener(BooleanChangeAdapter listener);
-
/**
* Control whether trace activation is synchronized with debugger focus/select
*
diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/services/LogicalBreakpoint.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/services/LogicalBreakpoint.java
index b770d1c814..c5b684825b 100644
--- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/services/LogicalBreakpoint.java
+++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/services/LogicalBreakpoint.java
@@ -30,6 +30,30 @@ import ghidra.trace.model.Trace;
import ghidra.trace.model.breakpoint.TraceBreakpoint;
import ghidra.trace.model.breakpoint.TraceBreakpointKind;
+/**
+ * A logical breakpoint
+ *
+ *
+ * This is a collection of at most one program breakpoint, which is actually a bookmark with a
+ * special type, and any number of trace breakpoints. The program breakpoint represents the logical
+ * breakpoint, as this is the most stable anchor for keeping the user's breakpoint set. All
+ * breakpoints in the set correspond to the same address when considering the module map (or other
+ * source of static-to-dynamic mapping), which may involve relocation. They also share the same
+ * kinds and length, since these are more or less intrinsic to the breakpoints specification. Thus,
+ * more than one logical breakpoint may occupy the same address. A logical breakpoints having a
+ * program bookmark (or that at least has a static address) is called a "mapped" breakpoint. This is
+ * the ideal, ordinary case. A breakpoint that cannot be mapped to a static address (and thus cannot
+ * have a program bookmark) is called a "lone" breakpoint.
+ *
+ *
+ * WARNING: The lifecycle of a logical breakpoint is fairly volatile. It is generally not
+ * safe to "hold onto" a logical breakpoint, since with any event, the logical breakpoint service
+ * may discard and re-create the object, even if it's composed of the same program and trace
+ * breakpoints. If it is truly necessary to hold onto logical breakpoints, consider using
+ * {@link DebuggerLogicalBreakpointService#addChangeListener(LogicalBreakpointsChangeListener)}. A
+ * logical breakpoint is valid until the service invokes
+ * {@link LogicalBreakpointsChangeListener#breakpointRemoved(LogicalBreakpoint)}.
+ */
public interface LogicalBreakpoint {
String BREAKPOINT_ENABLED_BOOKMARK_TYPE = "BreakpointEnabled";
String BREAKPOINT_DISABLED_BOOKMARK_TYPE = "BreakpointDisabled";
@@ -605,6 +629,22 @@ public interface LogicalBreakpoint {
*/
void setName(String name);
+ /**
+ * Get the sleigh injection when emulating this breakpoint
+ *
+ * @return the sleigh injection
+ * @see TraceBreakpoint#getEmuSleigh()
+ */
+ String getEmuSleigh();
+
+ /**
+ * Set the sleigh injection when emulating this breakpoint
+ *
+ * @param sleigh the sleigh injection
+ * @see TraceBreakpoint#setEmuSleigh(String)
+ */
+ void setEmuSleigh(String sleigh);
+
/**
* If the logical breakpoint has a mapped program location, get that location.
*
diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/services/StateEditingMode.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/services/StateEditingMode.java
new file mode 100644
index 0000000000..29254837a3
--- /dev/null
+++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/services/StateEditingMode.java
@@ -0,0 +1,431 @@
+/* ###
+ * IP: GHIDRA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package ghidra.app.services;
+
+import java.nio.ByteBuffer;
+import java.util.Objects;
+import java.util.concurrent.CompletableFuture;
+
+import javax.swing.Icon;
+
+import generic.theme.GIcon;
+import ghidra.app.plugin.core.debug.DebuggerCoordinates;
+import ghidra.app.services.DebuggerTraceManagerService.ActivationCause;
+import ghidra.async.AsyncUtils;
+import ghidra.framework.plugintool.PluginTool;
+import ghidra.program.model.address.Address;
+import ghidra.program.model.address.AddressRange;
+import ghidra.program.model.lang.Language;
+import ghidra.program.model.lang.Register;
+import ghidra.program.model.mem.MemoryAccessException;
+import ghidra.trace.model.Trace;
+import ghidra.trace.model.guest.TracePlatform;
+import ghidra.trace.model.memory.TraceMemoryOperations;
+import ghidra.trace.model.memory.TraceMemorySpace;
+import ghidra.trace.model.program.TraceVariableSnapProgramView;
+import ghidra.trace.model.thread.TraceThread;
+import ghidra.trace.model.time.schedule.PatchStep;
+import ghidra.trace.model.time.schedule.TraceSchedule;
+import ghidra.trace.util.TraceRegisterUtils;
+import ghidra.util.database.UndoableTransaction;
+import ghidra.util.exception.CancelledException;
+import ghidra.util.task.TaskMonitor;
+
+/**
+ * The control / state editing modes
+ */
+public enum StateEditingMode {
+ /**
+ * Control actions, breakpoint commands are directed to the target, but state edits are
+ * rejected.
+ */
+ RO_TARGET("Control Target w/ Edits Disabled", new GIcon("icon.debugger.edit.mode.ro.target")) {
+ @Override
+ public boolean followsPresent() {
+ return true;
+ }
+
+ @Override
+ public boolean canEdit(DebuggerCoordinates coordinates) {
+ return false;
+ }
+
+ @Override
+ public boolean isVariableEditable(DebuggerCoordinates coordinates, Address address,
+ int length) {
+ return false;
+ }
+
+ @Override
+ public CompletableFuture setVariable(PluginTool tool,
+ DebuggerCoordinates coordinates, Address address, byte[] data) {
+ return CompletableFuture.failedFuture(new MemoryAccessException("Read-only mode"));
+ }
+
+ @Override
+ public boolean useEmulatedBreakpoints() {
+ return false;
+ }
+
+ @Override
+ public boolean isSelectable(DebuggerCoordinates coordinates) {
+ return coordinates.isAliveAndPresent();
+ }
+
+ @Override
+ public StateEditingMode getAlternative(DebuggerCoordinates coordinates) {
+ return RO_TRACE;
+ }
+ },
+ /**
+ * Control actions, breakpoint commands, and state edits are all directed to the target.
+ */
+ RW_TARGET("Control Target", new GIcon("icon.debugger.edit.mode.rw.target")) {
+ @Override
+ public boolean followsPresent() {
+ return true;
+ }
+
+ @Override
+ public boolean canEdit(DebuggerCoordinates coordinates) {
+ return coordinates.isAliveAndPresent();
+ }
+
+ @Override
+ public boolean isVariableEditable(DebuggerCoordinates coordinates, Address address,
+ int length) {
+ if (!coordinates.isAliveAndPresent()) {
+ return false;
+ }
+ TraceRecorder recorder = coordinates.getRecorder();
+ return recorder.isVariableOnTarget(coordinates.getPlatform(),
+ coordinates.getThread(), coordinates.getFrame(), address, length);
+ }
+
+ @Override
+ public CompletableFuture setVariable(PluginTool tool,
+ DebuggerCoordinates coordinates, Address address, byte[] data) {
+ TraceRecorder recorder = coordinates.getRecorder();
+ if (recorder == null) {
+ return CompletableFuture
+ .failedFuture(new MemoryAccessException("Trace has no live target"));
+ }
+ if (!coordinates.isPresent()) {
+ return CompletableFuture
+ .failedFuture(new MemoryAccessException("View is not the present"));
+ }
+ return recorder.writeVariable(coordinates.getPlatform(), coordinates.getThread(),
+ coordinates.getFrame(), address, data);
+ }
+
+ @Override
+ public boolean useEmulatedBreakpoints() {
+ return false;
+ }
+
+ @Override
+ public boolean isSelectable(DebuggerCoordinates coordinates) {
+ return coordinates.isAliveAndPresent();
+ }
+
+ @Override
+ public StateEditingMode getAlternative(DebuggerCoordinates coordinates) {
+ return RW_EMULATOR;
+ }
+ },
+ /**
+ * Control actions activate trace snapshots, breakpoint commands are directed to the emulator,
+ * and state edits are rejected.
+ */
+ RO_TRACE("Control Trace w/ Edits Disabled", new GIcon("icon.debugger.edit.mode.ro.trace")) {
+ @Override
+ public boolean followsPresent() {
+ return false;
+ }
+
+ @Override
+ public boolean canEdit(DebuggerCoordinates coordinates) {
+ return false;
+ }
+
+ @Override
+ public boolean isVariableEditable(DebuggerCoordinates coordinates, Address address,
+ int length) {
+ return false;
+ }
+
+ @Override
+ public CompletableFuture setVariable(PluginTool tool,
+ DebuggerCoordinates coordinates, Address address, byte[] data) {
+ return CompletableFuture.failedFuture(new MemoryAccessException("Read-only mode"));
+ }
+
+ @Override
+ public boolean useEmulatedBreakpoints() {
+ return true;
+ }
+ },
+ /**
+ * Control actions activate trace snapshots, breakpoint commands are directed to the emulator,
+ * and state edits modify the current trace snapshot.
+ */
+ RW_TRACE("Control Trace", new GIcon("icon.debugger.edit.mode.rw.trace")) {
+ @Override
+ public boolean followsPresent() {
+ return false;
+ }
+
+ @Override
+ public boolean canEdit(DebuggerCoordinates coordinates) {
+ return coordinates.getTrace() != null;
+ }
+
+ @Override
+ public boolean isVariableEditable(DebuggerCoordinates coordinates, Address address,
+ int length) {
+ return address.isMemoryAddress() || coordinates.getThread() != null;
+ }
+
+ @Override
+ public CompletableFuture setVariable(PluginTool tool,
+ DebuggerCoordinates coordinates, Address guestAddress, byte[] data) {
+ Trace trace = coordinates.getTrace();
+ TracePlatform platform = coordinates.getPlatform();
+ long snap = coordinates.getViewSnap();
+ Address hostAddress = platform.mapGuestToHost(guestAddress);
+ if (hostAddress == null) {
+ throw new IllegalArgumentException(
+ "Guest address " + guestAddress + " is not mapped");
+ }
+ TraceMemoryOperations memOrRegs;
+ Address overlayAddress;
+ try (UndoableTransaction txid =
+ UndoableTransaction.start(trace, "Edit Variable")) {
+ if (hostAddress.isRegisterAddress()) {
+ TraceThread thread = coordinates.getThread();
+ if (thread == null) {
+ throw new IllegalArgumentException("Register edits require a thread.");
+ }
+ TraceMemorySpace regs = trace.getMemoryManager()
+ .getMemoryRegisterSpace(thread, coordinates.getFrame(),
+ true);
+ memOrRegs = regs;
+ overlayAddress = regs.getAddressSpace().getOverlayAddress(hostAddress);
+ }
+ else {
+ memOrRegs = trace.getMemoryManager();
+ overlayAddress = hostAddress;
+ }
+ if (memOrRegs.putBytes(snap, overlayAddress,
+ ByteBuffer.wrap(data)) != data.length) {
+ return CompletableFuture.failedFuture(new MemoryAccessException());
+ }
+ }
+ return AsyncUtils.NIL;
+ }
+
+ @Override
+ public boolean useEmulatedBreakpoints() {
+ return true;
+ }
+ },
+ /**
+ * Control actions, breakpoint commands, and state edits are directed to the emulator.
+ *
+ *
+ * Edits are accomplished by appending patch steps to the current schedule and activating that
+ * schedule.
+ */
+ RW_EMULATOR("Control Emulator", new GIcon("icon.debugger.edit.mode.rw.emulator")) {
+ @Override
+ public boolean followsPresent() {
+ return false;
+ }
+
+ @Override
+ public boolean canEdit(DebuggerCoordinates coordinates) {
+ return coordinates.getTrace() != null;
+ }
+
+ @Override
+ public boolean isVariableEditable(DebuggerCoordinates coordinates, Address address,
+ int length) {
+ if (coordinates.getThread() == null) {
+ // A limitation in TraceSchedule, which is used to manifest patches
+ return false;
+ }
+ if (!RW_TRACE.isVariableEditable(coordinates, address, length)) {
+ return false;
+ }
+ // TODO: Limitation from using Sleigh for patching
+ Register ctxReg = coordinates.getTrace().getBaseLanguage().getContextBaseRegister();
+ if (ctxReg == Register.NO_CONTEXT) {
+ return true;
+ }
+ AddressRange ctxRange = TraceRegisterUtils.rangeForRegister(ctxReg);
+ if (ctxRange.contains(address)) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public CompletableFuture setVariable(PluginTool tool,
+ DebuggerCoordinates coordinates, Address address, byte[] data) {
+ if (!(coordinates.getView() instanceof TraceVariableSnapProgramView)) {
+ throw new IllegalArgumentException("Cannot emulate using a Fixed Program View");
+ }
+ TraceThread thread = coordinates.getThread();
+ if (thread == null) {
+ // TODO: Well, technically, only for register edits
+ // It's a limitation in TraceSchedule. Every step requires a thread
+ throw new IllegalArgumentException("Emulator edits require a thread.");
+ }
+ Language language = coordinates.getPlatform().getLanguage();
+ TraceSchedule time = coordinates.getTime()
+ .patched(thread, language,
+ PatchStep.generateSleigh(language, address, data));
+
+ DebuggerCoordinates withTime = coordinates.time(time);
+ DebuggerTraceManagerService traceManager =
+ Objects.requireNonNull(tool.getService(DebuggerTraceManagerService.class),
+ "No trace manager service");
+ Long found = traceManager.findSnapshot(withTime);
+ // Materialize it on the same thread (even if swing)
+ // It shouldn't take long, since we're only appending one step.
+ if (found == null) {
+ // TODO: Could still do it async on another thread, no?
+ // Not sure it buys anything, since program view will call .get on swing thread
+ DebuggerEmulationService emulationService = Objects.requireNonNull(
+ tool.getService(DebuggerEmulationService.class), "No emulation service");
+ try {
+ emulationService.emulate(coordinates.getPlatform(), time,
+ TaskMonitor.DUMMY);
+ }
+ catch (CancelledException e) {
+ throw new AssertionError(e);
+ }
+ }
+ return traceManager.activateAndNotify(withTime, ActivationCause.USER, false);
+ }
+
+ @Override
+ public boolean useEmulatedBreakpoints() {
+ return true;
+ }
+ };
+
+ public static final StateEditingMode DEFAULT = RO_TARGET;
+
+ public final String name;
+ public final Icon icon;
+
+ private StateEditingMode(String name, Icon icon) {
+ this.name = name;
+ this.icon = icon;
+ }
+
+ /**
+ * Check if the UI should keep its active snapshot in sync with the recorder's latest.
+ *
+ * @return true to follow, false if not
+ */
+ public abstract boolean followsPresent();
+
+ /**
+ * Check if (broadly speaking) the mode supports editing the given coordinates
+ *
+ * @param coordinates the coordinates to check
+ * @return true if editable, false if not
+ */
+ public abstract boolean canEdit(DebuggerCoordinates coordinates);
+
+ /**
+ * Check if the given variable can be edited under this mode
+ *
+ * @param coordinates the coordinates to check
+ * @param address the address of the variable
+ * @param length the length of the variable, in bytes
+ * @return true if editable, false if not
+ */
+ public abstract boolean isVariableEditable(DebuggerCoordinates coordinates, Address address,
+ int length);
+
+ /**
+ * Set the value of a variable
+ *
+ *
+ * Because the edit may be directed to a live target, the return value is a
+ * {@link CompletableFuture}. Additionally, when directed to the emulator, this allows the
+ * emulated state to be computed in the background.
+ *
+ * @param tool the tool requesting the edit
+ * @param coordinates the coordinates of the edit
+ * @param address the address of the variable
+ * @param data the desired value of the variable
+ * @return a future which completes when the edit is finished
+ */
+ public abstract CompletableFuture setVariable(PluginTool tool,
+ DebuggerCoordinates coordinates, Address address, byte[] data);
+
+ /**
+ * Check if this mode operates on target breakpoints or emulator breakpoints
+ *
+ * @return false for target, true for emulator
+ */
+ public abstract boolean useEmulatedBreakpoints();
+
+ /**
+ * Check if this mode can be selected for the given coordinates
+ *
+ * @param coordinates the current coordinates
+ * @return true to enable selection, false to disable
+ */
+ public boolean isSelectable(DebuggerCoordinates coordinates) {
+ return true;
+ }
+
+ /**
+ * If the mode can no longer be selected for new coordinates, get the new mode
+ *
+ *
+ * For example, if a target terminates while the mode is {@link #RO_TARGET}, this specifies the
+ * new mode.
+ *
+ * @param coordinates the new coordinates
+ * @return the new mode
+ */
+ public StateEditingMode getAlternative(DebuggerCoordinates coordinates) {
+ throw new AssertionError("INTERNAL: Non-selectable mode must provide alternative");
+ }
+
+ /**
+ * Find the new mode (or same) mode when activating the given coordinates
+ *
+ *
+ * The default is implemented using {@link #isSelectable(DebuggerCoordinates)} followed by
+ * {@link #getAlternative(DebuggerCoordinates)}.
+ *
+ * @param coordinates the new coordinates
+ * @return the mode
+ */
+ public StateEditingMode modeOnChange(DebuggerCoordinates coordinates) {
+ if (isSelectable(coordinates)) {
+ return this;
+ }
+ return getAlternative(coordinates);
+ }
+}
diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/debug/flatapi/FlatDebuggerAPI.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/debug/flatapi/FlatDebuggerAPI.java
index 2a3ba7bb04..28501649fa 100644
--- a/Ghidra/Debug/Debugger/src/main/java/ghidra/debug/flatapi/FlatDebuggerAPI.java
+++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/debug/flatapi/FlatDebuggerAPI.java
@@ -30,7 +30,6 @@ import ghidra.app.plugin.core.debug.service.model.launch.DebuggerProgramLaunchOf
import ghidra.app.script.GhidraScript;
import ghidra.app.script.GhidraState;
import ghidra.app.services.*;
-import ghidra.app.services.DebuggerStateEditingService.StateEditingMode;
import ghidra.app.services.DebuggerStateEditingService.StateEditor;
import ghidra.dbg.AnnotatedDebuggerAttributeListener;
import ghidra.dbg.DebuggerObjectModel;
@@ -766,9 +765,9 @@ public interface FlatDebuggerAPI {
}
/**
- * Apply the given SLEIGH patch to the emulator
+ * Apply the given Sleigh patch to the emulator
*
- * @param sleigh the SLEIGH source, without terminating semicolon
+ * @param sleigh the Sleigh source, without terminating semicolon
* @param monitor a monitor for the emulation
* @return true if successful, false otherwise
* @throws CancelledException if the user cancelled via the given monitor
diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/pcode/exec/DebuggerPcodeUtils.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/pcode/exec/DebuggerPcodeUtils.java
index 55983a510d..b8948a7873 100644
--- a/Ghidra/Debug/Debugger/src/main/java/ghidra/pcode/exec/DebuggerPcodeUtils.java
+++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/pcode/exec/DebuggerPcodeUtils.java
@@ -16,12 +16,9 @@
package ghidra.pcode.exec;
import java.math.BigInteger;
-import java.util.HashMap;
-import java.util.Map;
+import java.util.*;
import java.util.Map.Entry;
-import org.bouncycastle.util.Arrays;
-
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
import ghidra.app.plugin.core.debug.service.emulation.*;
import ghidra.app.plugin.core.debug.service.emulation.data.DefaultPcodeDebuggerAccess;
@@ -214,7 +211,7 @@ public enum DebuggerPcodeUtils {
if (this.bigEndian != that.bigEndian) {
return false;
}
- return Arrays.areEqual(this.bytes, that.bytes);
+ return Arrays.equals(this.bytes, that.bytes);
}
/**
diff --git a/Ghidra/Debug/Debugger/src/screen/java/ghidra/app/plugin/core/debug/gui/breakpoint/DebuggerBreakpointsPluginScreenShots.java b/Ghidra/Debug/Debugger/src/screen/java/ghidra/app/plugin/core/debug/gui/breakpoint/DebuggerBreakpointsPluginScreenShots.java
index 458ceed800..803116cc5c 100644
--- a/Ghidra/Debug/Debugger/src/screen/java/ghidra/app/plugin/core/debug/gui/breakpoint/DebuggerBreakpointsPluginScreenShots.java
+++ b/Ghidra/Debug/Debugger/src/screen/java/ghidra/app/plugin/core/debug/gui/breakpoint/DebuggerBreakpointsPluginScreenShots.java
@@ -137,6 +137,18 @@ public class DebuggerBreakpointsPluginScreenShots extends GhidraScreenShotGenera
new DefaultTraceLocation(trace3, null, Lifespan.nowOn(0), addr(trace3, 0x7fac0000)),
new ProgramLocation(program, addr(program, 0x00400000)), 0x00010000, false);
}
+ waitForSwing();
+
+ try (UndoableTransaction tid = UndoableTransaction.start(program, "Add breakpoint")) {
+ program.getBookmarkManager()
+ .setBookmark(addr(program, 0x00401234),
+ LogicalBreakpoint.BREAKPOINT_ENABLED_BOOKMARK_TYPE, "SW_EXECUTE;1",
+ "before connect");
+ program.getBookmarkManager()
+ .setBookmark(addr(program, 0x00604321),
+ LogicalBreakpoint.BREAKPOINT_ENABLED_BOOKMARK_TYPE, "WRITE;4",
+ "write version");
+ }
TargetBreakpointSpecContainer bc1 =
waitFor(() -> Unique.assertAtMostOne(recorder1.collectBreakpointContainers(null)),
@@ -156,17 +168,6 @@ public class DebuggerBreakpointsPluginScreenShots extends GhidraScreenShotGenera
trace3.getBreakpointManager()
.getBreakpointsAt(recorder3.getSnap(), addr(trace3, 0x7fac1234))));
- try (UndoableTransaction tid = UndoableTransaction.start(program, "Add breakpoint")) {
- program.getBookmarkManager()
- .setBookmark(addr(program, 0x00401234),
- LogicalBreakpoint.BREAKPOINT_ENABLED_BOOKMARK_TYPE, "SW_EXECUTE;1",
- "before connect");
- program.getBookmarkManager()
- .setBookmark(addr(program, 0x00604321),
- LogicalBreakpoint.BREAKPOINT_ENABLED_BOOKMARK_TYPE, "WRITE;4",
- "write version");
- }
-
waitForPass(() -> {
Set allBreakpoints = breakpointService.getAllBreakpoints();
assertEquals(2, allBreakpoints.size());
diff --git a/Ghidra/Debug/Debugger/src/screen/java/ghidra/app/plugin/core/debug/gui/stack/DebuggerStackPluginScreenShots.java b/Ghidra/Debug/Debugger/src/screen/java/ghidra/app/plugin/core/debug/gui/stack/DebuggerStackPluginScreenShots.java
index 953847f7e8..0ca315d77c 100644
--- a/Ghidra/Debug/Debugger/src/screen/java/ghidra/app/plugin/core/debug/gui/stack/DebuggerStackPluginScreenShots.java
+++ b/Ghidra/Debug/Debugger/src/screen/java/ghidra/app/plugin/core/debug/gui/stack/DebuggerStackPluginScreenShots.java
@@ -38,7 +38,6 @@ import ghidra.app.plugin.core.debug.stack.*;
import ghidra.app.plugin.core.progmgr.ProgramManagerPlugin;
import ghidra.app.services.*;
import ghidra.app.services.DebuggerEmulationService.EmulationResult;
-import ghidra.app.services.DebuggerStateEditingService.StateEditingMode;
import ghidra.app.services.DebuggerStateEditingService.StateEditor;
import ghidra.async.AsyncTestUtils;
import ghidra.framework.model.DomainFolder;
@@ -297,7 +296,7 @@ public class DebuggerStackPluginScreenShots extends GhidraScreenShotGenerator
traceManager.activateThread(thread);
waitForSwing();
- editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_TRACE);
+ editingService.setCurrentMode(tb.trace, StateEditingMode.RW_TRACE);
StateEditor editor = editingService.createStateEditor(tb.trace);
DebuggerCoordinates atSetup = traceManager.getCurrent();
diff --git a/Ghidra/Debug/Debugger/src/screen/java/ghidra/app/plugin/core/debug/gui/stack/vars/VariableValueHoverPluginScreenShots.java b/Ghidra/Debug/Debugger/src/screen/java/ghidra/app/plugin/core/debug/gui/stack/vars/VariableValueHoverPluginScreenShots.java
index 109fedbd0a..faffaefba5 100644
--- a/Ghidra/Debug/Debugger/src/screen/java/ghidra/app/plugin/core/debug/gui/stack/vars/VariableValueHoverPluginScreenShots.java
+++ b/Ghidra/Debug/Debugger/src/screen/java/ghidra/app/plugin/core/debug/gui/stack/vars/VariableValueHoverPluginScreenShots.java
@@ -45,7 +45,6 @@ import ghidra.app.plugin.core.decompile.DecompilerProvider;
import ghidra.app.plugin.core.progmgr.ProgramManagerPlugin;
import ghidra.app.services.*;
import ghidra.app.services.DebuggerEmulationService.EmulationResult;
-import ghidra.app.services.DebuggerStateEditingService.StateEditingMode;
import ghidra.app.services.DebuggerStateEditingService.StateEditor;
import ghidra.app.util.viewer.listingpanel.ListingPanel;
import ghidra.async.AsyncTestUtils;
@@ -259,7 +258,7 @@ public class VariableValueHoverPluginScreenShots extends GhidraScreenShotGenerat
}
waitForDomainObject(tb.trace);
- editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_TRACE);
+ editingService.setCurrentMode(tb.trace, StateEditingMode.RW_TRACE);
StateEditor editor = editingService.createStateEditor(tb.trace);
DebuggerCoordinates atSetup = traceManager.getCurrent();
diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/disassemble/DebuggerDisassemblyTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/disassemble/DebuggerDisassemblyTest.java
index 2786eb68b2..9c76cad603 100644
--- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/disassemble/DebuggerDisassemblyTest.java
+++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/disassemble/DebuggerDisassemblyTest.java
@@ -39,7 +39,6 @@ import ghidra.app.plugin.core.debug.service.platform.DebuggerPlatformServicePlug
import ghidra.app.plugin.core.debug.service.workflow.DebuggerWorkflowServiceProxyPlugin;
import ghidra.app.plugin.core.debug.workflow.DisassembleAtPcDebuggerBot;
import ghidra.app.services.*;
-import ghidra.app.services.DebuggerStateEditingService.StateEditingMode;
import ghidra.dbg.target.TargetEnvironment;
import ghidra.dbg.target.schema.SchemaContext;
import ghidra.dbg.target.schema.TargetObjectSchema.SchemaName;
@@ -411,7 +410,7 @@ public class DebuggerDisassemblyTest extends AbstractGhidraHeadedDebuggerGUITest
createLegacyTrace("ARM:LE:32:v8", 0x00400000, () -> tb.buf(0x00, 0x00, 0x00, 0x00));
Address start = tb.addr(0x00400000);
- editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_TRACE);
+ editingService.setCurrentMode(tb.trace, StateEditingMode.RW_TRACE);
// Ensure the mapper is added to the trace
assertNotNull(platformService.getMapper(tb.trace, null, 0));
@@ -444,7 +443,7 @@ public class DebuggerDisassemblyTest extends AbstractGhidraHeadedDebuggerGUITest
// Don't cheat here and choose v8T!
createLegacyTrace("ARM:LE:32:v8", 0x00400000, () -> tb.buf(0x00, 0x00));
Address start = tb.addr(0x00400000);
- editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_TRACE);
+ editingService.setCurrentMode(tb.trace, StateEditingMode.RW_TRACE);
// Ensure the mapper is added to the trace
assertNotNull(platformService.getMapper(tb.trace, null, 0));
@@ -481,7 +480,7 @@ public class DebuggerDisassemblyTest extends AbstractGhidraHeadedDebuggerGUITest
TraceObjectThread thread =
createPolyglotTrace("armv8le", 0x00400000, () -> tb.buf(0x00, 0x00, 0x00, 0x00));
Address start = tb.addr(0x00400000);
- editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_TRACE);
+ editingService.setCurrentMode(tb.trace, StateEditingMode.RW_TRACE);
// Ensure the mapper is added to the trace
assertNotNull(platformService.getMapper(tb.trace, thread.getObject(), 0));
@@ -516,7 +515,7 @@ public class DebuggerDisassemblyTest extends AbstractGhidraHeadedDebuggerGUITest
TraceObjectThread thread =
createPolyglotTrace("armv8le", 0x00400000, () -> tb.buf(0x00, 0x00));
Address start = tb.addr(0x00400000);
- editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_TRACE);
+ editingService.setCurrentMode(tb.trace, StateEditingMode.RW_TRACE);
// Ensure the mapper is added to the trace
assertNotNull(platformService.getMapper(tb.trace, thread.getObject(), 0));
@@ -573,7 +572,7 @@ public class DebuggerDisassemblyTest extends AbstractGhidraHeadedDebuggerGUITest
createLegacyTrace("ARM:LE:32:v8", 0x00400000, () -> tb.buf());
Address start = tb.addr(0x00400000);
- editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_TRACE);
+ editingService.setCurrentMode(tb.trace, StateEditingMode.RW_TRACE);
// Ensure the mapper is added to the trace
assertNotNull(platformService.getMapper(tb.trace, null, 0));
@@ -593,7 +592,7 @@ public class DebuggerDisassemblyTest extends AbstractGhidraHeadedDebuggerGUITest
TraceObjectThread thread = createPolyglotTrace("armv8le", 0x00400000, () -> tb.buf());
Address start = tb.addr(0x00400000);
- editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_TRACE);
+ editingService.setCurrentMode(tb.trace, StateEditingMode.RW_TRACE);
// Ensure the mapper is added to the trace
assertNotNull(platformService.getMapper(tb.trace, thread.getObject(), 0));
@@ -613,7 +612,7 @@ public class DebuggerDisassemblyTest extends AbstractGhidraHeadedDebuggerGUITest
TraceObjectThread thread = createPolyglotTrace("armv8le", 0x00400000, () -> tb.buf());
Address start = tb.addr(0x00400000);
- editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_TRACE);
+ editingService.setCurrentMode(tb.trace, StateEditingMode.RW_TRACE);
// Ensure the mapper is added to the trace
assertNotNull(platformService.getMapper(tb.trace, thread.getObject(), 0));
diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/AbstractGhidraHeadedDebuggerGUITest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/AbstractGhidraHeadedDebuggerGUITest.java
index d4f7a275b6..9b07978308 100644
--- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/AbstractGhidraHeadedDebuggerGUITest.java
+++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/AbstractGhidraHeadedDebuggerGUITest.java
@@ -269,6 +269,24 @@ public abstract class AbstractGhidraHeadedDebuggerGUITest
}, () -> lastError.get().getMessage());
}
+ public static T waitForPass(Supplier supplier) {
+ var locals = new Object() {
+ AssertionError lastError;
+ T value;
+ };
+ waitForCondition(() -> {
+ try {
+ locals.value = supplier.get();
+ return true;
+ }
+ catch (AssertionError e) {
+ locals.lastError = e;
+ return false;
+ }
+ }, () -> locals.lastError.getMessage());
+ return locals.value;
+ }
+
protected static Set getMenuElementsText(MenuElement menu) {
Set result = new HashSet<>();
for (MenuElement sub : menu.getSubElements()) {
diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/breakpoint/DebuggerBreakpointMarkerPluginTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/breakpoint/DebuggerBreakpointMarkerPluginTest.java
index c74c63eda0..45edec6ed1 100644
--- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/breakpoint/DebuggerBreakpointMarkerPluginTest.java
+++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/breakpoint/DebuggerBreakpointMarkerPluginTest.java
@@ -455,8 +455,9 @@ public class DebuggerBreakpointMarkerPluginTest extends AbstractGhidraHeadedDebu
}
waitForDomainObject(program);
- performAction(breakpointMarkerPlugin.actionToggleBreakpoint,
- staticCtx(addr(program, 0x00400123)), false);
+ ProgramLocationActionContext ctx = staticCtx(addr(program, 0x00400123));
+ assertTrue(breakpointMarkerPlugin.actionToggleBreakpoint.isAddToPopup(ctx));
+ performAction(breakpointMarkerPlugin.actionToggleBreakpoint, ctx, false);
DebuggerPlaceBreakpointDialog dialog =
waitForDialogComponent(DebuggerPlaceBreakpointDialog.class);
runSwing(() -> dialog.okCallback());
@@ -482,8 +483,9 @@ public class DebuggerBreakpointMarkerPluginTest extends AbstractGhidraHeadedDebu
}
waitForDomainObject(program);
- performAction(breakpointMarkerPlugin.actionToggleBreakpoint,
- staticCtx(addr(program, 0x00400123)), false);
+ ProgramLocationActionContext ctx = staticCtx(addr(program, 0x00400123));
+ assertTrue(breakpointMarkerPlugin.actionToggleBreakpoint.isAddToPopup(ctx));
+ performAction(breakpointMarkerPlugin.actionToggleBreakpoint, ctx, false);
DebuggerPlaceBreakpointDialog dialog =
waitForDialogComponent(DebuggerPlaceBreakpointDialog.class);
runSwing(() -> dialog.okCallback());
@@ -502,13 +504,14 @@ public class DebuggerBreakpointMarkerPluginTest extends AbstractGhidraHeadedDebu
addMappedBreakpointOpenAndWait();
LogicalBreakpoint lb = Unique.assertOne(breakpointService.getAllBreakpoints());
- performAction(breakpointMarkerPlugin.actionToggleBreakpoint,
- staticCtx(addr(program, 0x00400123)), false);
+ ProgramLocationActionContext ctx = staticCtx(addr(program, 0x00400123));
+ assertTrue(breakpointMarkerPlugin.actionToggleBreakpoint.isAddToPopup(ctx));
+ performAction(breakpointMarkerPlugin.actionToggleBreakpoint, ctx, false);
waitForPass(() -> assertEquals(State.DISABLED, lb.computeState()));
- performAction(breakpointMarkerPlugin.actionToggleBreakpoint,
- staticCtx(addr(program, 0x00400123)), false);
+ assertTrue(breakpointMarkerPlugin.actionToggleBreakpoint.isAddToPopup(ctx));
+ performAction(breakpointMarkerPlugin.actionToggleBreakpoint, ctx, false);
waitForPass(() -> assertEquals(State.ENABLED, lb.computeState()));
}
@@ -522,13 +525,14 @@ public class DebuggerBreakpointMarkerPluginTest extends AbstractGhidraHeadedDebu
// NB. addMappedBreakpointOpenAndWait already makes this assertion. Just a reminder:
waitForPass(() -> assertEquals(State.ENABLED, lb.computeStateForTrace(trace)));
- performAction(breakpointMarkerPlugin.actionToggleBreakpoint,
- dynamicCtx(trace, addr(trace, 0x55550123)), true);
+ ProgramLocationActionContext ctx = dynamicCtx(trace, addr(trace, 0x55550123));
+ assertTrue(breakpointMarkerPlugin.actionToggleBreakpoint.isAddToPopup(ctx));
+ performAction(breakpointMarkerPlugin.actionToggleBreakpoint, ctx, true);
waitForPass(() -> assertEquals(State.DISABLED, lb.computeStateForTrace(trace)));
- performAction(breakpointMarkerPlugin.actionToggleBreakpoint,
- dynamicCtx(trace, addr(trace, 0x55550123)), true);
+ assertTrue(breakpointMarkerPlugin.actionToggleBreakpoint.isAddToPopup(ctx));
+ performAction(breakpointMarkerPlugin.actionToggleBreakpoint, ctx, true);
waitForPass(() -> assertEquals(State.ENABLED, lb.computeStateForTrace(trace)));
@@ -538,8 +542,8 @@ public class DebuggerBreakpointMarkerPluginTest extends AbstractGhidraHeadedDebu
lb.disableForProgram();
waitForPass(() -> assertEquals(State.INCONSISTENT_ENABLED, lb.computeStateForTrace(trace)));
- performAction(breakpointMarkerPlugin.actionToggleBreakpoint,
- dynamicCtx(trace, addr(trace, 0x55550123)), true);
+ assertTrue(breakpointMarkerPlugin.actionToggleBreakpoint.isAddToPopup(ctx));
+ performAction(breakpointMarkerPlugin.actionToggleBreakpoint, ctx, true);
// NB. toggling from dynamic view, this toggles trace bpt, not logical/program bpt
waitForPass(() -> assertEquals(State.DISABLED, lb.computeStateForTrace(trace)));
@@ -548,8 +552,8 @@ public class DebuggerBreakpointMarkerPluginTest extends AbstractGhidraHeadedDebu
waitForPass(
() -> assertEquals(State.INCONSISTENT_DISABLED, lb.computeStateForTrace(trace)));
- performAction(breakpointMarkerPlugin.actionToggleBreakpoint,
- dynamicCtx(trace, addr(trace, 0x55550123)), true);
+ assertTrue(breakpointMarkerPlugin.actionToggleBreakpoint.isAddToPopup(ctx));
+ performAction(breakpointMarkerPlugin.actionToggleBreakpoint, ctx, true);
waitForPass(() -> assertEquals(State.ENABLED, lb.computeStateForTrace(trace)));
}
@@ -558,7 +562,9 @@ public class DebuggerBreakpointMarkerPluginTest extends AbstractGhidraHeadedDebu
Set expectedKinds) throws Throwable {
addMappedBreakpointOpenAndWait(); // Adds an unneeded breakpoint. Aw well.
- performAction(action, staticCtx(addr(program, 0x0400321)), false);
+ ProgramLocationActionContext ctx = staticCtx(addr(program, 0x0400321));
+ assertTrue(action.isAddToPopup(ctx));
+ performAction(action, ctx, false);
DebuggerPlaceBreakpointDialog dialog =
waitForDialogComponent(DebuggerPlaceBreakpointDialog.class);
runSwing(() -> {
@@ -580,7 +586,9 @@ public class DebuggerBreakpointMarkerPluginTest extends AbstractGhidraHeadedDebu
TraceRecorder recorder = addMappedBreakpointOpenAndWait(); // Adds an unneeded breakpoint. Aw well.
Trace trace = recorder.getTrace();
- performAction(action, dynamicCtx(trace, addr(trace, 0x55550321)), false);
+ ProgramLocationActionContext ctx = dynamicCtx(trace, addr(trace, 0x55550321));
+ assertTrue(action.isAddToPopup(ctx));
+ performAction(action, ctx, false);
DebuggerPlaceBreakpointDialog dialog =
waitForDialogComponent(DebuggerPlaceBreakpointDialog.class);
runSwing(() -> dialog.okCallback());
@@ -664,8 +672,9 @@ public class DebuggerBreakpointMarkerPluginTest extends AbstractGhidraHeadedDebu
lb.disable();
waitForPass(() -> assertEquals(State.DISABLED, lb.computeState()));
- performAction(breakpointMarkerPlugin.actionEnableBreakpoint,
- staticCtx(addr(program, 0x00400123)), true);
+ ProgramLocationActionContext ctx = staticCtx(addr(program, 0x00400123));
+ assertTrue(breakpointMarkerPlugin.actionEnableBreakpoint.isAddToPopup(ctx));
+ performAction(breakpointMarkerPlugin.actionEnableBreakpoint, ctx, true);
waitForPass(() -> assertEquals(State.ENABLED, lb.computeState()));
}
@@ -678,8 +687,9 @@ public class DebuggerBreakpointMarkerPluginTest extends AbstractGhidraHeadedDebu
lb.disable();
waitForPass(() -> assertEquals(State.DISABLED, lb.computeState()));
- performAction(breakpointMarkerPlugin.actionEnableBreakpoint,
- dynamicCtx(trace, addr(trace, 0x55550123)), true);
+ ProgramLocationActionContext ctx = dynamicCtx(trace, addr(trace, 0x55550123));
+ assertTrue(breakpointMarkerPlugin.actionEnableBreakpoint.isAddToPopup(ctx));
+ performAction(breakpointMarkerPlugin.actionEnableBreakpoint, ctx, true);
waitForPass(() -> assertEquals(State.ENABLED, lb.computeStateForTrace(trace)));
// TODO: Same test but with multiple traces
@@ -690,8 +700,9 @@ public class DebuggerBreakpointMarkerPluginTest extends AbstractGhidraHeadedDebu
addMappedBreakpointOpenAndWait();
LogicalBreakpoint lb = Unique.assertOne(breakpointService.getAllBreakpoints());
- performAction(breakpointMarkerPlugin.actionDisableBreakpoint,
- staticCtx(addr(program, 0x00400123)), true);
+ ProgramLocationActionContext ctx = staticCtx(addr(program, 0x00400123));
+ assertTrue(breakpointMarkerPlugin.actionDisableBreakpoint.isAddToPopup(ctx));
+ performAction(breakpointMarkerPlugin.actionDisableBreakpoint, ctx, true);
waitForPass(() -> assertEquals(State.DISABLED, lb.computeState()));
}
@@ -702,8 +713,9 @@ public class DebuggerBreakpointMarkerPluginTest extends AbstractGhidraHeadedDebu
Trace trace = recorder.getTrace();
LogicalBreakpoint lb = Unique.assertOne(breakpointService.getAllBreakpoints());
- performAction(breakpointMarkerPlugin.actionDisableBreakpoint,
- dynamicCtx(trace, addr(trace, 0x55550123)), true);
+ ProgramLocationActionContext ctx = dynamicCtx(trace, addr(trace, 0x55550123));
+ assertTrue(breakpointMarkerPlugin.actionDisableBreakpoint.isAddToPopup(ctx));
+ performAction(breakpointMarkerPlugin.actionDisableBreakpoint, ctx, true);
waitForPass(() -> assertEquals(State.DISABLED, lb.computeStateForTrace(trace)));
// TODO: Same test but with multiple traces
@@ -713,8 +725,9 @@ public class DebuggerBreakpointMarkerPluginTest extends AbstractGhidraHeadedDebu
public void testActionClearBreakpointProgram() throws Throwable {
addMappedBreakpointOpenAndWait();
- performAction(breakpointMarkerPlugin.actionClearBreakpoint,
- staticCtx(addr(program, 0x00400123)), true);
+ ProgramLocationActionContext ctx = staticCtx(addr(program, 0x00400123));
+ assertTrue(breakpointMarkerPlugin.actionClearBreakpoint.isAddToPopup(ctx));
+ performAction(breakpointMarkerPlugin.actionClearBreakpoint, ctx, true);
waitForPass(() -> assertTrue(breakpointService.getAllBreakpoints().isEmpty()));
}
@@ -725,8 +738,9 @@ public class DebuggerBreakpointMarkerPluginTest extends AbstractGhidraHeadedDebu
Trace trace = recorder.getTrace();
LogicalBreakpoint lb = Unique.assertOne(breakpointService.getAllBreakpoints());
- performAction(breakpointMarkerPlugin.actionClearBreakpoint,
- dynamicCtx(trace, addr(trace, 0x55550123)), true);
+ ProgramLocationActionContext ctx = dynamicCtx(trace, addr(trace, 0x55550123));
+ assertTrue(breakpointMarkerPlugin.actionClearBreakpoint.isAddToPopup(ctx));
+ performAction(breakpointMarkerPlugin.actionClearBreakpoint, ctx, true);
waitForPass(() -> assertEquals(State.NONE, lb.computeStateForTrace(trace)));
// TODO: Same test but with multiple traces
diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/breakpoint/DebuggerBreakpointsProviderObjectTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/breakpoint/DebuggerBreakpointsProviderObjectTest.java
index 6ebabf53af..592d1bf08a 100644
--- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/breakpoint/DebuggerBreakpointsProviderObjectTest.java
+++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/breakpoint/DebuggerBreakpointsProviderObjectTest.java
@@ -24,6 +24,7 @@ import ghidra.dbg.target.schema.SchemaContext;
import ghidra.dbg.target.schema.TargetObjectSchema.SchemaName;
import ghidra.dbg.target.schema.XmlSchemaContext;
import ghidra.trace.model.Trace;
+import ghidra.trace.model.target.TraceObjectKeyPath;
import ghidra.util.database.UndoableTransaction;
@Category(NightlyCategory.class)
@@ -57,46 +58,58 @@ public class DebuggerBreakpointsProviderObjectTest extends DebuggerBreakpointsPr
// NOTE the use of index='...' allowing object-based managers to ID unique path
// TODO: I guess this'll burn down if the naming scheme changes....
int index = tb.trace.getName().startsWith("[3]") ? 3 : 1;
- ctx = XmlSchemaContext.deserialize("" + //
- "" + //
- " " + //
- " " + //
- " " + //
- " " + //
- " " + // <---- NOTE HERE
- " " + //
- " " + //
- " " + //
- " " + //
- " " + //
- " " + //
- " " + //
- " " + //
- " " + //
- " " + //
- " " + //
- " " + //
- " " + //
- " " + //
- " " + //
- " " + //
- " " + //
- " " + //
- " " + //
- " " + //
- " " + //
- " " + //
- " " + //
- " " + //
- " " + //
- "");
+ ctx = XmlSchemaContext.deserialize(String.format("""
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ """, index));
try (UndoableTransaction tid = tb.startTransaction()) {
tb.trace.getObjectManager().createRootObject(ctx.getSchema(new SchemaName("Session")));
+ tb.trace.getObjectManager()
+ .createObject(
+ TraceObjectKeyPath.of("Processes", "[" + index + "]", "Breakpoints"));
}
}
}
diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/breakpoint/DebuggerBreakpointsProviderTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/breakpoint/DebuggerBreakpointsProviderTest.java
index 76bf66db00..5e5d438475 100644
--- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/breakpoint/DebuggerBreakpointsProviderTest.java
+++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/breakpoint/DebuggerBreakpointsProviderTest.java
@@ -34,6 +34,8 @@ import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest;
import ghidra.app.plugin.core.debug.gui.DebuggerResources.*;
import ghidra.app.plugin.core.debug.gui.breakpoint.DebuggerBreakpointsProvider.LogicalBreakpointTableModel;
import ghidra.app.plugin.core.debug.gui.console.DebuggerConsolePlugin;
+import ghidra.app.plugin.core.debug.service.editing.DebuggerStateEditingServicePlugin;
+import ghidra.app.plugin.core.debug.service.emulation.ProgramEmulationUtils;
import ghidra.app.plugin.core.debug.service.modules.DebuggerStaticMappingUtils;
import ghidra.app.services.*;
import ghidra.app.services.LogicalBreakpoint.State;
@@ -41,12 +43,14 @@ import ghidra.dbg.model.TestTargetProcess;
import ghidra.dbg.target.TargetBreakpointSpec.TargetBreakpointKind;
import ghidra.dbg.target.TargetBreakpointSpecContainer;
import ghidra.framework.store.LockException;
+import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressOverflowException;
import ghidra.program.model.listing.Program;
import ghidra.program.model.mem.MemoryConflictException;
import ghidra.program.util.ProgramLocation;
import ghidra.trace.model.*;
import ghidra.trace.model.breakpoint.TraceBreakpoint;
+import ghidra.trace.model.time.TraceSnapshot;
import ghidra.util.SystemUtilities;
import ghidra.util.database.UndoableTransaction;
import ghidra.util.exception.CancelledException;
@@ -682,4 +686,110 @@ public class DebuggerBreakpointsProviderTest extends AbstractGhidraHeadedDebugge
// NOTE: With no selection, no actions (even table built-in) apply, so no menu
}
+
+ @Test
+ public void testEmuBreakpointState() throws Throwable {
+ addPlugin(tool, DebuggerStateEditingServicePlugin.class);
+
+ createProgram();
+ intoProject(program);
+ programManager.openProgram(program);
+ waitForSwing();
+
+ addStaticMemoryAndBreakpoint();
+ waitForProgram(program);
+
+ LogicalBreakpointRow row = waitForValue(
+ () -> Unique.assertAtMostOne(breakpointsProvider.breakpointTableModel.getModelData()));
+ assertEquals(State.INEFFECTIVE_ENABLED, row.getState());
+
+ // Do our own launch, so that object mode is enabled during load (region creation)
+ createTrace(program.getLanguageID().getIdAsString());
+ try (UndoableTransaction startTransaction = tb.startTransaction()) {
+ TraceSnapshot initial = tb.trace.getTimeManager().getSnapshot(0, true);
+ ProgramEmulationUtils.loadExecutable(initial, program);
+ Address pc = program.getMinAddress();
+ ProgramEmulationUtils.doLaunchEmulationThread(tb.trace, 0, program, pc, pc);
+ }
+
+ traceManager.openTrace(tb.trace);
+ traceManager.activateTrace(tb.trace);
+ waitForSwing();
+ waitOn(mappingService.changesSettled());
+ waitOn(breakpointService.changesSettled());
+ waitForSwing();
+
+ row = waitForValue(
+ () -> Unique.assertAtMostOne(breakpointsProvider.breakpointTableModel.getModelData()));
+ assertEquals(State.INEFFECTIVE_ENABLED, row.getState());
+
+ row.setEnabled(true);
+ waitForSwing();
+
+ row = waitForValue(
+ () -> Unique.assertAtMostOne(breakpointsProvider.breakpointTableModel.getModelData()));
+ assertEquals(State.ENABLED, row.getState());
+ }
+
+ @Test
+ public void testTablesAndStatesWhenhModeChanges() throws Throwable {
+ DebuggerStateEditingService editingService =
+ addPlugin(tool, DebuggerStateEditingServicePlugin.class);
+
+ createTestModel();
+ mb.createTestProcessesAndThreads();
+ TraceRecorder recorder = modelService.recordTarget(mb.testProcess1,
+ createTargetTraceMapper(mb.testProcess1), ActionSource.AUTOMATIC);
+ Trace trace = recorder.getTrace();
+ createProgramFromTrace(trace);
+ intoProject(trace);
+ intoProject(program);
+
+ mb.testProcess1.addRegion("bin:.text", mb.rng(0x55550000, 0x55550fff), "rx");
+ waitRecorder(recorder);
+ addMapping(trace, program);
+ addStaticMemoryAndBreakpoint();
+ programManager.openProgram(program);
+ traceManager.openTrace(trace);
+ waitForSwing();
+
+ LogicalBreakpointRow lbRow1 = waitForPass(() -> {
+ LogicalBreakpointRow newRow =
+ Unique.assertOne(breakpointsProvider.breakpointTableModel.getModelData());
+ LogicalBreakpoint lb = newRow.getLogicalBreakpoint();
+ assertEquals(program, lb.getProgram());
+ assertEquals(Set.of(trace), lb.getMappedTraces());
+ assertEquals(Set.of(), lb.getParticipatingTraces());
+ assertEquals(State.INEFFECTIVE_ENABLED, newRow.getState());
+ return newRow;
+ });
+
+ editingService.setCurrentMode(trace, StateEditingMode.RW_EMULATOR);
+ lbRow1.setEnabled(true);
+ TraceBreakpoint emuBpt = waitForValue(
+ () -> Unique.assertAtMostOne(trace.getBreakpointManager().getAllBreakpoints()));
+ assertNull(recorder.getTargetBreakpoint(emuBpt));
+
+ LogicalBreakpointRow lbRow2 =
+ Unique.assertOne(breakpointsProvider.breakpointTableModel.getModelData());
+ waitForPass(() -> assertEquals(State.ENABLED, lbRow2.getState()));
+
+ waitForPass(() -> {
+ BreakpointLocationRow newRow =
+ Unique.assertOne(breakpointsProvider.locationTableModel.getModelData());
+ assertEquals(State.ENABLED, newRow.getState());
+ });
+
+ for (int i = 0; i < 3; i++) {
+ editingService.setCurrentMode(trace, StateEditingMode.RO_TARGET);
+ waitOn(breakpointService.changesSettled());
+ waitForSwing();
+ assertEquals(0, breakpointsProvider.locationTableModel.getModelData().size());
+
+ editingService.setCurrentMode(trace, StateEditingMode.RW_EMULATOR);
+ waitOn(breakpointService.changesSettled());
+ waitForSwing();
+ assertEquals(1, breakpointsProvider.locationTableModel.getModelData().size());
+ }
+ }
}
diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/control/DebuggerControlPluginTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/control/DebuggerControlPluginTest.java
index e3b7952eb4..5e045bf9df 100644
--- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/control/DebuggerControlPluginTest.java
+++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/control/DebuggerControlPluginTest.java
@@ -48,7 +48,6 @@ import ghidra.app.plugin.core.debug.service.emulation.DebuggerEmulationServicePl
import ghidra.app.services.*;
import ghidra.app.services.DebuggerEmulationService.CachedEmulator;
import ghidra.app.services.DebuggerEmulationService.EmulationResult;
-import ghidra.app.services.DebuggerStateEditingService.StateEditingMode;
import ghidra.dbg.model.*;
import ghidra.dbg.target.TargetObject;
import ghidra.dbg.target.TargetSteppable.TargetStepKind;
@@ -73,8 +72,8 @@ import ghidra.util.database.UndoableTransaction;
*
*
* In these and other machine-state-editing integration tests, we use
- * {@link StateEditingMode#WRITE_EMULATOR} as a stand-in for any mode. We also use
- * {@link StateEditingMode#READ_ONLY} just to verify the mode is heeded. Other modes may be tested
+ * {@link StateEditingMode#RW_EMULATOR} as a stand-in for any mode. We also use
+ * {@link StateEditingMode#RO_TARGET} just to verify the mode is heeded. Other modes may be tested
* if bugs crop up in various combinations.
*/
public class DebuggerControlPluginTest extends AbstractGhidraHeadedDebuggerGUITest {
@@ -294,7 +293,7 @@ public class DebuggerControlPluginTest extends AbstractGhidraHeadedDebuggerGUITe
@Test
public void testEmulateResumeAction() throws Throwable {
TraceThread thread = createToyLoopTrace();
- editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_EMULATOR);
+ editingService.setCurrentMode(tb.trace, StateEditingMode.RW_EMULATOR);
traceManager.activateThread(thread);
waitForSwing();
@@ -311,7 +310,7 @@ public class DebuggerControlPluginTest extends AbstractGhidraHeadedDebuggerGUITe
@Test
public void testEmulateInterruptAction() throws Throwable {
TraceThread thread = createToyLoopTrace();
- editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_EMULATOR);
+ editingService.setCurrentMode(tb.trace, StateEditingMode.RW_EMULATOR);
traceManager.activateThread(thread);
waitForSwing();
@@ -333,7 +332,7 @@ public class DebuggerControlPluginTest extends AbstractGhidraHeadedDebuggerGUITe
@Test
public void testEmulateStepBackAction() throws Throwable {
TraceThread thread = createToyLoopTrace();
- editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_EMULATOR);
+ editingService.setCurrentMode(tb.trace, StateEditingMode.RW_EMULATOR);
traceManager.activateThread(thread);
waitForSwing();
@@ -352,7 +351,7 @@ public class DebuggerControlPluginTest extends AbstractGhidraHeadedDebuggerGUITe
@Test
public void testEmulateStepIntoAction() throws Throwable {
TraceThread thread = createToyLoopTrace();
- editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_EMULATOR);
+ editingService.setCurrentMode(tb.trace, StateEditingMode.RW_EMULATOR);
traceManager.activateThread(thread);
waitForSwing();
@@ -365,7 +364,7 @@ public class DebuggerControlPluginTest extends AbstractGhidraHeadedDebuggerGUITe
@Test
public void testEmulateSkipOverAction() throws Throwable {
TraceThread thread = createToyLoopTrace();
- editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_EMULATOR);
+ editingService.setCurrentMode(tb.trace, StateEditingMode.RW_EMULATOR);
traceManager.activateThread(thread);
waitForSwing();
@@ -386,7 +385,7 @@ public class DebuggerControlPluginTest extends AbstractGhidraHeadedDebuggerGUITe
@Test
public void testTraceSnapBackwardAction() throws Throwable {
create2SnapTrace();
- editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_TRACE);
+ editingService.setCurrentMode(tb.trace, StateEditingMode.RW_TRACE);
traceManager.activateTrace(tb.trace);
waitForSwing();
@@ -403,7 +402,7 @@ public class DebuggerControlPluginTest extends AbstractGhidraHeadedDebuggerGUITe
@Test
public void testTraceSnapForwardAction() throws Throwable {
create2SnapTrace();
- editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_TRACE);
+ editingService.setCurrentMode(tb.trace, StateEditingMode.RW_TRACE);
traceManager.activateTrace(tb.trace);
waitForSwing();
@@ -444,14 +443,14 @@ public class DebuggerControlPluginTest extends AbstractGhidraHeadedDebuggerGUITe
assertTrue(controlPlugin.actionEditMode.isEnabled());
runSwing(() -> controlPlugin.actionEditMode
- .setCurrentActionStateByUserData(StateEditingMode.READ_ONLY));
- assertEquals(StateEditingMode.READ_ONLY, editingService.getCurrentMode(tb.trace));
+ .setCurrentActionStateByUserData(StateEditingMode.RO_TARGET));
+ assertEquals(StateEditingMode.RO_TARGET, editingService.getCurrentMode(tb.trace));
assertFalse(
helper.patchInstructionAction.isAddToPopup(listingProvider.getActionContext(null)));
runSwing(() -> controlPlugin.actionEditMode
- .setCurrentActionStateByUserData(StateEditingMode.WRITE_EMULATOR));
- assertEquals(StateEditingMode.WRITE_EMULATOR, editingService.getCurrentMode(tb.trace));
+ .setCurrentActionStateByUserData(StateEditingMode.RW_EMULATOR));
+ assertEquals(StateEditingMode.RW_EMULATOR, editingService.getCurrentMode(tb.trace));
assertTrue(
helper.patchInstructionAction.isAddToPopup(listingProvider.getActionContext(null)));
@@ -494,13 +493,13 @@ public class DebuggerControlPluginTest extends AbstractGhidraHeadedDebuggerGUITe
assertTrue(controlPlugin.actionEditMode.isEnabled());
runSwing(() -> controlPlugin.actionEditMode
- .setCurrentActionStateByUserData(StateEditingMode.READ_ONLY));
- assertEquals(StateEditingMode.READ_ONLY, editingService.getCurrentMode(tb.trace));
+ .setCurrentActionStateByUserData(StateEditingMode.RO_TARGET));
+ assertEquals(StateEditingMode.RO_TARGET, editingService.getCurrentMode(tb.trace));
assertFalse(helper.patchDataAction.isAddToPopup(listingProvider.getActionContext(null)));
runSwing(() -> controlPlugin.actionEditMode
- .setCurrentActionStateByUserData(StateEditingMode.WRITE_EMULATOR));
- assertEquals(StateEditingMode.WRITE_EMULATOR, editingService.getCurrentMode(tb.trace));
+ .setCurrentActionStateByUserData(StateEditingMode.RW_EMULATOR));
+ assertEquals(StateEditingMode.RW_EMULATOR, editingService.getCurrentMode(tb.trace));
goTo(listingProvider.getListingPanel(), new ProgramLocation(view, tb.addr(0x00400123)));
assertTrue(helper.patchDataAction.isAddToPopup(listingProvider.getActionContext(null)));
@@ -545,15 +544,15 @@ public class DebuggerControlPluginTest extends AbstractGhidraHeadedDebuggerGUITe
assertTrue(controlPlugin.actionEditMode.isEnabled());
runSwing(() -> controlPlugin.actionEditMode
- .setCurrentActionStateByUserData(StateEditingMode.READ_ONLY));
- assertEquals(StateEditingMode.READ_ONLY, editingService.getCurrentMode(tb.trace));
+ .setCurrentActionStateByUserData(StateEditingMode.RO_TARGET));
+ assertEquals(StateEditingMode.RO_TARGET, editingService.getCurrentMode(tb.trace));
ctx = listingProvider.getActionContext(null);
assertTrue(pasteAction.isAddToPopup(ctx));
assertFalse(pasteAction.isEnabledForContext(ctx));
runSwing(() -> controlPlugin.actionEditMode
- .setCurrentActionStateByUserData(StateEditingMode.WRITE_EMULATOR));
- assertEquals(StateEditingMode.WRITE_EMULATOR, editingService.getCurrentMode(tb.trace));
+ .setCurrentActionStateByUserData(StateEditingMode.RW_EMULATOR));
+ assertEquals(StateEditingMode.RW_EMULATOR, editingService.getCurrentMode(tb.trace));
goTo(listingPlugin.getListingPanel(), new ProgramLocation(view, tb.addr(0x00400123)));
ctx = listingProvider.getActionContext(null);
diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerMemoryBytesProviderTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerMemoryBytesProviderTest.java
index 90856c40c3..f98fcdf479 100644
--- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerMemoryBytesProviderTest.java
+++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerMemoryBytesProviderTest.java
@@ -49,9 +49,7 @@ import ghidra.app.plugin.core.debug.gui.DebuggerResources.FollowsCurrentThreadAc
import ghidra.app.plugin.core.debug.gui.action.*;
import ghidra.app.plugin.core.debug.gui.listing.DebuggerListingPlugin;
import ghidra.app.plugin.core.debug.service.editing.DebuggerStateEditingServicePlugin;
-import ghidra.app.services.DebuggerStateEditingService;
-import ghidra.app.services.DebuggerStateEditingService.StateEditingMode;
-import ghidra.app.services.TraceRecorder;
+import ghidra.app.services.*;
import ghidra.async.SwingExecutorService;
import ghidra.program.model.address.*;
import ghidra.program.model.lang.Register;
@@ -510,6 +508,7 @@ public class DebuggerMemoryBytesProviderTest extends AbstractGhidraHeadedDebugge
* Assure ourselves the block under test is not on screen
*/
waitForPass(() -> {
+ goToDyn(addr(trace, 0x55551800));
AddressSetView visible = memBytesProvider.readsMemTrait.getVisible();
assertFalse(visible.isEmpty());
assertFalse(visible.contains(addr(trace, 0x55550000)));
@@ -1101,10 +1100,11 @@ public class DebuggerMemoryBytesProviderTest extends AbstractGhidraHeadedDebugge
createTargetTraceMapper(mb.testProcess1));
Trace trace = recorder.getTrace();
- editingService.setCurrentMode(trace, StateEditingMode.WRITE_TARGET);
+ editingService.setCurrentMode(trace, StateEditingMode.RW_TARGET);
DockingActionIf actionEdit = getAction(memBytesPlugin, "Enable/Disable Byteviewer Editing");
mb.testProcess1.addRegion("exe:.text", mb.rng(0x55550000, 0x5555ffff), "rx");
+ waitRecorder(recorder);
waitFor(() -> !trace.getMemoryManager().getAllRegions().isEmpty());
byte[] data = new byte[4];
@@ -1132,12 +1132,17 @@ public class DebuggerMemoryBytesProviderTest extends AbstractGhidraHeadedDebugge
createTargetTraceMapper(mb.testProcess1));
Trace trace = recorder.getTrace();
- editingService.setCurrentMode(trace, StateEditingMode.WRITE_TRACE);
+ editingService.setCurrentMode(trace, StateEditingMode.RW_TRACE);
DockingActionIf actionEdit = getAction(memBytesPlugin, "Enable/Disable Byteviewer Editing");
mb.testProcess1.addRegion("exe:.text", mb.rng(0x55550000, 0x5555ffff), "rx");
+ waitRecorder(recorder);
waitFor(() -> !trace.getMemoryManager().getAllRegions().isEmpty());
+ // Because mode is RW_TRACE, we're not necessarily at recorder's snap
+ traceManager.activateSnap(recorder.getSnap());
+ waitForSwing();
+
byte[] data = new byte[4];
performAction(actionEdit);
waitForPass(noExc(() -> {
@@ -1174,7 +1179,7 @@ public class DebuggerMemoryBytesProviderTest extends AbstractGhidraHeadedDebugge
createTargetTraceMapper(mb.testProcess1));
Trace trace = recorder.getTrace();
- editingService.setCurrentMode(trace, StateEditingMode.WRITE_TARGET);
+ editingService.setCurrentMode(trace, StateEditingMode.RW_TARGET);
mb.testProcess1.addRegion("exe:.text", mb.rng(0x55550000, 0x5555ffff), "rx");
waitFor(() -> !trace.getMemoryManager().getAllRegions().isEmpty());
diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/register/DebuggerRegistersProviderTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/register/DebuggerRegistersProviderTest.java
index e738cdb0ac..09c3c79d59 100644
--- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/register/DebuggerRegistersProviderTest.java
+++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/register/DebuggerRegistersProviderTest.java
@@ -33,9 +33,7 @@ import ghidra.app.plugin.core.debug.gui.listing.DebuggerListingPlugin;
import ghidra.app.plugin.core.debug.gui.register.DebuggerRegistersProvider.RegisterDataSettingsDialog;
import ghidra.app.plugin.core.debug.gui.register.DebuggerRegistersProvider.RegisterTableColumns;
import ghidra.app.plugin.core.debug.service.editing.DebuggerStateEditingServicePlugin;
-import ghidra.app.services.DebuggerStateEditingService;
-import ghidra.app.services.DebuggerStateEditingService.StateEditingMode;
-import ghidra.app.services.TraceRecorder;
+import ghidra.app.services.*;
import ghidra.docking.settings.FormatSettingsDefinition;
import ghidra.docking.settings.Settings;
import ghidra.program.model.address.AddressSpace;
@@ -385,7 +383,7 @@ public class DebuggerRegistersProviderTest extends AbstractGhidraHeadedDebuggerG
activateThread(thread);
waitForSwing();
- editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_EMULATOR);
+ editingService.setCurrentMode(tb.trace, StateEditingMode.RW_EMULATOR);
assertTrue(registersProvider.actionEnableEdits.isEnabled());
performAction(registersProvider.actionEnableEdits);
@@ -423,7 +421,7 @@ public class DebuggerRegistersProviderTest extends AbstractGhidraHeadedDebuggerG
activateThread(thread);
waitForSwing();
- editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_EMULATOR);
+ editingService.setCurrentMode(tb.trace, StateEditingMode.RW_EMULATOR);
assertTrue(registersProvider.actionEnableEdits.isEnabled());
performAction(registersProvider.actionEnableEdits);
diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/thread/DebuggerThreadsProviderLegacyTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/thread/DebuggerThreadsProviderLegacyTest.java
index 7d8f1256c5..0bc41451c9 100644
--- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/thread/DebuggerThreadsProviderLegacyTest.java
+++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/thread/DebuggerThreadsProviderLegacyTest.java
@@ -28,12 +28,10 @@ import org.junit.experimental.categories.Category;
import generic.test.category.NightlyCategory;
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest;
import ghidra.app.plugin.core.debug.gui.thread.DebuggerLegacyThreadsPanel.ThreadTableColumns;
-import ghidra.app.services.TraceRecorder;
import ghidra.trace.model.Lifespan;
import ghidra.trace.model.Trace;
import ghidra.trace.model.thread.TraceThread;
import ghidra.trace.model.thread.TraceThreadManager;
-import ghidra.trace.model.time.TraceSnapshot;
import ghidra.trace.model.time.TraceTimeManager;
import ghidra.util.database.UndoableTransaction;
@@ -400,53 +398,4 @@ public class DebuggerThreadsProviderLegacyTest extends AbstractGhidraHeadedDebug
assertEquals(6, threadsProvider.legacyPanel.headerRenderer.getCursorPosition().longValue());
}
- @Test
- public void testActionSeekTracePresent() throws Exception {
- assertTrue(threadsProvider.actionSeekTracePresent.isSelected());
-
- createAndOpenTrace();
- addThreads();
- traceManager.activateTrace(tb.trace);
- waitForSwing();
-
- assertEquals(0, traceManager.getCurrentSnap());
-
- try (UndoableTransaction tid = tb.startTransaction()) {
- tb.trace.getTimeManager().createSnapshot("Next snapshot");
- }
- waitForDomainObject(tb.trace);
-
- // Not live, so no seek
- assertEquals(0, traceManager.getCurrentSnap());
-
- tb.close();
-
- createTestModel();
- mb.createTestProcessesAndThreads();
- // Threads needs registers to be recognized by the recorder
- mb.createTestThreadRegisterBanks();
-
- TraceRecorder recorder = modelService.recordTargetAndActivateTrace(mb.testProcess1,
- createTargetTraceMapper(mb.testProcess1));
- Trace trace = recorder.getTrace();
-
- // Wait till two threads are observed in the database
- waitForPass(() -> assertEquals(2, trace.getThreadManager().getAllThreads().size()));
- waitForSwing();
-
- TraceSnapshot snapshot = recorder.forceSnapshot();
- waitForDomainObject(trace);
-
- assertEquals(snapshot.getKey(), traceManager.getCurrentSnap());
-
- performAction(threadsProvider.actionSeekTracePresent);
- waitForSwing();
-
- assertFalse(threadsProvider.actionSeekTracePresent.isSelected());
-
- recorder.forceSnapshot();
- waitForSwing();
-
- assertEquals(snapshot.getKey(), traceManager.getCurrentSnap());
- }
}
diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/thread/DebuggerThreadsProviderTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/thread/DebuggerThreadsProviderTest.java
index 8e08331c40..4e77bac93e 100644
--- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/thread/DebuggerThreadsProviderTest.java
+++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/thread/DebuggerThreadsProviderTest.java
@@ -31,13 +31,13 @@ import ghidra.app.plugin.core.debug.DebuggerCoordinates;
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest;
import ghidra.app.plugin.core.debug.gui.model.ObjectTableModel;
import ghidra.app.plugin.core.debug.gui.model.ObjectTableModel.*;
+import ghidra.app.plugin.core.debug.gui.model.QueryPanelTestHelper;
import ghidra.app.plugin.core.debug.mapping.DebuggerTargetTraceMapper;
import ghidra.app.plugin.core.debug.mapping.ObjectBasedDebuggerTargetTraceMapper;
-import ghidra.app.plugin.core.debug.gui.model.QueryPanelTestHelper;
import ghidra.app.services.TraceRecorder;
import ghidra.dbg.target.TargetExecutionStateful;
-import ghidra.dbg.target.TargetObject;
import ghidra.dbg.target.TargetExecutionStateful.TargetExecutionState;
+import ghidra.dbg.target.TargetObject;
import ghidra.dbg.target.schema.SchemaContext;
import ghidra.dbg.target.schema.TargetObjectSchema.SchemaName;
import ghidra.dbg.target.schema.XmlSchemaContext;
@@ -51,7 +51,6 @@ import ghidra.trace.model.target.TraceObject.ConflictResolution;
import ghidra.trace.model.target.TraceObjectKeyPath;
import ghidra.trace.model.target.TraceObjectManager;
import ghidra.trace.model.thread.TraceObjectThread;
-import ghidra.trace.model.time.TraceSnapshot;
import ghidra.trace.model.time.TraceTimeManager;
import ghidra.util.database.UndoableTransaction;
import ghidra.util.table.GhidraTable;
@@ -564,52 +563,4 @@ public class DebuggerThreadsProviderTest extends AbstractGhidraHeadedDebuggerGUI
waitForPass(() -> assertEquals(Long.valueOf(6), renderer.getCursorPosition()));
}
-
- @Test
- public void testActionSeekTracePresent() throws Throwable {
- assertTrue(provider.actionSeekTracePresent.isSelected());
-
- createAndOpenTrace();
- addThreads();
- traceManager.activateTrace(tb.trace);
- waitForTasks();
-
- assertEquals(0, traceManager.getCurrentSnap());
-
- try (UndoableTransaction tid = tb.startTransaction()) {
- tb.trace.getTimeManager().createSnapshot("Next snapshot");
- }
- waitForDomainObject(tb.trace);
- waitForTasks();
-
- // Not live, so no seek
- waitForPass(() -> assertEquals(0, traceManager.getCurrentSnap()));
-
- tb.close();
-
- createTestModel();
- mb.createTestProcessesAndThreads();
- // Threads needs registers to be recognized by the recorder
- mb.createTestThreadRegisterBanks();
-
- TraceRecorder recorder = recordAndWaitSync();
- traceManager.openTrace(tb.trace);
- traceManager.activateTrace(tb.trace);
-
- TraceSnapshot snapshot = recorder.forceSnapshot();
- waitForDomainObject(tb.trace);
- waitForTasks();
-
- waitForPass(() -> assertEquals(snapshot.getKey(), traceManager.getCurrentSnap()));
-
- performEnabledAction(provider, provider.actionSeekTracePresent, true);
- waitForTasks();
-
- assertFalse(provider.actionSeekTracePresent.isSelected());
-
- recorder.forceSnapshot();
- waitForTasks();
-
- waitForPass(() -> assertEquals(snapshot.getKey(), traceManager.getCurrentSnap()));
- }
}
diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/watch/DebuggerWatchesProviderTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/watch/DebuggerWatchesProviderTest.java
index 70a6b01b67..0b5e8da716 100644
--- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/watch/DebuggerWatchesProviderTest.java
+++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/watch/DebuggerWatchesProviderTest.java
@@ -35,7 +35,6 @@ import ghidra.app.plugin.core.debug.gui.watch.DebuggerWatchesProvider.WatchDataS
import ghidra.app.plugin.core.debug.service.editing.DebuggerStateEditingServicePlugin;
import ghidra.app.plugin.core.debug.service.modules.DebuggerStaticMappingServicePlugin;
import ghidra.app.services.*;
-import ghidra.app.services.DebuggerStateEditingService.StateEditingMode;
import ghidra.dbg.model.TestTargetRegisterBankInThread;
import ghidra.docking.settings.FormatSettingsDefinition;
import ghidra.docking.settings.Settings;
@@ -350,7 +349,7 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerGUI
assertFalse(row.isRawValueEditable());
traceManager.openTrace(tb.trace);
traceManager.activateThread(thread);
- editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_EMULATOR);
+ editingService.setCurrentMode(tb.trace, StateEditingMode.RW_EMULATOR);
waitForWatches();
assertNoErr(row);
@@ -384,7 +383,7 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerGUI
traceManager.openTrace(tb.trace);
traceManager.activateThread(thread);
- editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_EMULATOR);
+ editingService.setCurrentMode(tb.trace, StateEditingMode.RW_EMULATOR);
waitForWatches();
performAction(watchesProvider.actionEnableEdits);
@@ -547,7 +546,7 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerGUI
traceManager.openTrace(trace);
traceManager.activateThread(thread);
- editingService.setCurrentMode(trace, StateEditingMode.WRITE_TARGET);
+ editingService.setCurrentMode(trace, StateEditingMode.RW_TARGET);
waitForSwing();
performAction(watchesProvider.actionAdd);
diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/breakpoint/DebuggerLogicalBreakpointServiceObjectTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/breakpoint/DebuggerLogicalBreakpointServiceObjectTest.java
index acee8c2a9a..dbe87dcc97 100644
--- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/breakpoint/DebuggerLogicalBreakpointServiceObjectTest.java
+++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/breakpoint/DebuggerLogicalBreakpointServiceObjectTest.java
@@ -21,6 +21,7 @@ import ghidra.dbg.target.schema.SchemaContext;
import ghidra.dbg.target.schema.XmlSchemaContext;
import ghidra.dbg.target.schema.TargetObjectSchema.SchemaName;
import ghidra.trace.model.Trace;
+import ghidra.trace.model.target.TraceObjectKeyPath;
import ghidra.util.database.UndoableTransaction;
public class DebuggerLogicalBreakpointServiceObjectTest
@@ -54,46 +55,58 @@ public class DebuggerLogicalBreakpointServiceObjectTest
// NOTE the use of index='...' allowing object-based managers to ID unique path
// TODO: I guess this'll burn down if the naming scheme changes....
int index = tb.trace.getName().startsWith("[3]") ? 3 : 1;
- ctx = XmlSchemaContext.deserialize("" + //
- "" + //
- " " + //
- " " + //
- " " + //
- " " + //
- " " + // <---- NOTE HERE
- " " + //
- " " + //
- " " + //
- " " + //
- " " + //
- " " + //
- " " + //
- " " + //
- " " + //
- " " + //
- " " + //
- " " + //
- " " + //
- " " + //
- " " + //
- " " + //
- " " + //
- " " + //
- " " + //
- " " + //
- " " + //
- " " + //
- " " + //
- " " + //
- " " + //
- "");
+ ctx = XmlSchemaContext.deserialize(String.format("""
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ """, index));
try (UndoableTransaction tid = tb.startTransaction()) {
tb.trace.getObjectManager().createRootObject(ctx.getSchema(new SchemaName("Session")));
+ tb.trace.getObjectManager()
+ .createObject(
+ TraceObjectKeyPath.of("Processes", "[" + index + "]", "Breakpoints"));
}
}
}
diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/breakpoint/DebuggerLogicalBreakpointServiceTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/breakpoint/DebuggerLogicalBreakpointServiceTest.java
index e81e2856ba..291ac97444 100644
--- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/breakpoint/DebuggerLogicalBreakpointServiceTest.java
+++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/breakpoint/DebuggerLogicalBreakpointServiceTest.java
@@ -38,9 +38,10 @@ import ghidra.program.model.address.Address;
import ghidra.program.model.listing.Bookmark;
import ghidra.program.model.listing.Program;
import ghidra.program.util.ProgramLocation;
-import ghidra.trace.model.DefaultTraceLocation;
-import ghidra.trace.model.Trace;
+import ghidra.trace.database.ToyDBTraceBuilder;
+import ghidra.trace.model.*;
import ghidra.trace.model.breakpoint.*;
+import ghidra.trace.model.memory.TraceMemoryFlag;
import ghidra.trace.model.memory.TraceMemoryRegion;
import ghidra.trace.model.modules.TraceStaticMapping;
import ghidra.util.Msg;
@@ -1552,4 +1553,125 @@ public class DebuggerLogicalBreakpointServiceTest extends AbstractGhidraHeadedDe
waitForPass(
() -> assertEquals(State.MIXED, lbEx.computeState().sameAdddress(lbRw.computeState())));
}
+
+ protected void addTextMappingDead(Program p, ToyDBTraceBuilder tb) throws Throwable {
+ addProgramTextBlock(p);
+ try (UndoableTransaction tid = tb.startTransaction()) {
+ TraceMemoryRegion textRegion = tb.trace.getMemoryManager()
+ .addRegion("Processes[1].Memory[bin:.text]", Lifespan.nowOn(0),
+ tb.range(0x55550000, 0x55550fff),
+ Set.of(TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE));
+ DebuggerStaticMappingUtils.addMapping(
+ new DefaultTraceLocation(tb.trace, null, textRegion.getLifespan(),
+ textRegion.getMinAddress()),
+ new ProgramLocation(p, addr(p, 0x00400000)), 0x1000,
+ false);
+ }
+ }
+
+ protected void addEnabledProgramBreakpointWithSleigh(Program p) {
+ try (UndoableTransaction tid =
+ UndoableTransaction.start(p, "Create bookmark bp with sleigh")) {
+ enBm = p.getBookmarkManager()
+ .setBookmark(addr(p, 0x00400123),
+ LogicalBreakpoint.BREAKPOINT_ENABLED_BOOKMARK_TYPE, "SW_EXECUTE;1",
+ "{sleigh: 'r0=0xbeef;'}");
+ }
+ }
+
+ @Test
+ public void testMapThenAddProgramBreakpointWithSleighThenEnableOnTraceCopiesSleigh()
+ throws Throwable {
+ createTrace();
+ traceManager.openTrace(tb.trace);
+ createProgramFromTrace();
+ intoProject(program);
+ programManager.openProgram(program);
+
+ addTextMappingDead(program, tb);
+ waitForSwing();
+
+ addEnabledProgramBreakpointWithSleigh(program);
+ LogicalBreakpoint lb = waitForValue(() -> Unique.assertAtMostOne(
+ breakpointService.getBreakpointsAt(program, addr(program, 0x00400123))));
+
+ assertEquals("r0=0xbeef;", lb.getEmuSleigh());
+
+ waitOn(lb.enable());
+ waitForSwing();
+
+ TraceBreakpoint bpt = Unique.assertOne(
+ tb.trace.getBreakpointManager().getBreakpointsAt(0, tb.addr(0x55550123)));
+ assertEquals("r0=0xbeef;", bpt.getEmuSleigh());
+ }
+
+ @Test
+ public void testAddProgramBreakpointWithSleighThenMapThenEnableOnTraceCopiesSleigh()
+ throws Throwable {
+ createTrace();
+ traceManager.openTrace(tb.trace);
+ createProgramFromTrace();
+ intoProject(program);
+ programManager.openProgram(program);
+
+ addEnabledProgramBreakpointWithSleigh(program);
+ LogicalBreakpoint lb = waitForValue(() -> Unique.assertAtMostOne(
+ breakpointService.getBreakpointsAt(program, addr(program, 0x00400123))));
+
+ assertEquals("r0=0xbeef;", lb.getEmuSleigh());
+
+ addTextMappingDead(program, tb);
+ lb = waitForPass(() -> {
+ LogicalBreakpoint newLb = Unique.assertOne(
+ breakpointService.getBreakpointsAt(program, addr(program, 0x00400123)));
+ assertTrue(newLb.getMappedTraces().contains(tb.trace));
+ return newLb;
+ });
+
+ waitOn(lb.enable());
+ waitForSwing();
+
+ TraceBreakpoint bpt = Unique.assertOne(
+ tb.trace.getBreakpointManager().getBreakpointsAt(0, tb.addr(0x55550123)));
+ assertEquals("r0=0xbeef;", bpt.getEmuSleigh());
+ }
+
+ @Test
+ public void testAddTraceBreakpointSetSleighThenMapThenSaveToProgramCopiesSleigh()
+ throws Throwable {
+ // TODO: What if already mapped?
+ // Not sure I care about tb.setEmuSleigh() out of band
+
+ createTrace();
+ traceManager.openTrace(tb.trace);
+ createProgramFromTrace();
+ intoProject(program);
+ programManager.openProgram(program);
+
+ try (UndoableTransaction tid = tb.startTransaction()) {
+ TraceBreakpoint bpt = tb.trace.getBreakpointManager()
+ .addBreakpoint("Processes[1].Breakpoints[0]", Lifespan.nowOn(0),
+ tb.addr(0x55550123),
+ Set.of(), Set.of(TraceBreakpointKind.SW_EXECUTE),
+ false /* emuEnabled defaults to true */, "");
+ bpt.setEmuSleigh("r0=0xbeef;");
+ }
+ LogicalBreakpoint lb = waitForValue(() -> Unique.assertAtMostOne(
+ breakpointService.getBreakpointsAt(tb.trace, tb.addr(0x55550123))));
+
+ assertEquals("r0=0xbeef;", lb.getEmuSleigh());
+
+ addTextMappingDead(program, tb);
+ lb = waitForPass(() -> {
+ LogicalBreakpoint newLb = Unique.assertOne(
+ breakpointService.getBreakpointsAt(program, addr(program, 0x00400123)));
+ assertTrue(newLb.getMappedTraces().contains(tb.trace));
+ return newLb;
+ });
+
+ lb.enableForProgram();
+ waitForSwing();
+
+ assertEquals("{\"sleigh\":\"r0\\u003d0xbeef;\"}", lb.getProgramBookmark().getComment());
+ }
}
diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/editing/DebuggerStateEditingServiceTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/editing/DebuggerStateEditingServiceTest.java
index eec650971e..142965fabf 100644
--- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/editing/DebuggerStateEditingServiceTest.java
+++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/editing/DebuggerStateEditingServiceTest.java
@@ -26,10 +26,8 @@ import org.junit.Test;
import ghidra.app.plugin.assembler.*;
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest;
-import ghidra.app.services.DebuggerStateEditingService;
-import ghidra.app.services.DebuggerStateEditingService.StateEditingMode;
+import ghidra.app.services.*;
import ghidra.app.services.DebuggerStateEditingService.StateEditor;
-import ghidra.app.services.TraceRecorder;
import ghidra.async.AsyncUtils.TemperamentalRunnable;
import ghidra.dbg.target.TargetRegisterBank;
import ghidra.program.model.lang.*;
@@ -56,6 +54,7 @@ public class DebuggerStateEditingServiceTest extends AbstractGhidraHeadedDebugge
protected void activateTrace() {
traceManager.activateTrace(tb.trace);
+ waitForSwing();
}
protected TracePlatform getPlatform() {
@@ -105,7 +104,7 @@ public class DebuggerStateEditingServiceTest extends AbstractGhidraHeadedDebugge
createAndOpenTrace();
activateTrace();
- editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_EMULATOR);
+ editingService.setCurrentMode(tb.trace, StateEditingMode.RW_EMULATOR);
StateEditor editor = createStateEditor();
assertFalse(editor.isVariableEditable(tb.addr(0x00400000), 4));
@@ -117,7 +116,7 @@ public class DebuggerStateEditingServiceTest extends AbstractGhidraHeadedDebugge
@Test
public void testWriteEmuRegisterNoThreadErr() throws Throwable {
createAndOpenTrace();
- editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_EMULATOR);
+ editingService.setCurrentMode(tb.trace, StateEditingMode.RW_EMULATOR);
activateTrace();
waitForSwing();
@@ -132,7 +131,7 @@ public class DebuggerStateEditingServiceTest extends AbstractGhidraHeadedDebugge
@Test
public void testWriteEmuMemory() throws Throwable {
createAndOpenTrace();
- editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_EMULATOR);
+ editingService.setCurrentMode(tb.trace, StateEditingMode.RW_EMULATOR);
try (UndoableTransaction tid = tb.startTransaction()) {
// NB. TraceManager should automatically activate the first thread
@@ -158,7 +157,7 @@ public class DebuggerStateEditingServiceTest extends AbstractGhidraHeadedDebugge
@Test
public void testWriteEmuRegister() throws Throwable {
createAndOpenTrace();
- editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_EMULATOR);
+ editingService.setCurrentMode(tb.trace, StateEditingMode.RW_EMULATOR);
TraceThread thread;
try (UndoableTransaction tid = tb.startTransaction()) {
@@ -187,7 +186,7 @@ public class DebuggerStateEditingServiceTest extends AbstractGhidraHeadedDebugge
@Test
public void testWriteEmuMemoryAfterStep() throws Throwable {
createAndOpenTrace();
- editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_TRACE);
+ editingService.setCurrentMode(tb.trace, StateEditingMode.RW_TRACE);
try (UndoableTransaction tid = tb.startTransaction()) {
// NB. TraceManager should automatically activate the first thread
@@ -200,7 +199,7 @@ public class DebuggerStateEditingServiceTest extends AbstractGhidraHeadedDebugge
tb.exec(getPlatform(), 0, thread, 0, "pc = 0x00400000;");
}
activateTrace();
- editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_EMULATOR);
+ editingService.setCurrentMode(tb.trace, StateEditingMode.RW_EMULATOR);
waitForSwing();
TraceSchedule step1 = TraceSchedule.parse("0:t0-1");
@@ -225,7 +224,7 @@ public class DebuggerStateEditingServiceTest extends AbstractGhidraHeadedDebugge
@Test
public void testWriteEmuRegisterAfterStep() throws Throwable {
createAndOpenTrace();
- editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_TRACE);
+ editingService.setCurrentMode(tb.trace, StateEditingMode.RW_TRACE);
TraceThread thread;
try (UndoableTransaction tid = tb.startTransaction()) {
@@ -239,7 +238,7 @@ public class DebuggerStateEditingServiceTest extends AbstractGhidraHeadedDebugge
tb.exec(getPlatform(), 0, thread, 0, "pc = 0x00400000;");
}
activateTrace();
- editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_EMULATOR);
+ editingService.setCurrentMode(tb.trace, StateEditingMode.RW_EMULATOR);
waitForSwing();
TraceSchedule step1 = TraceSchedule.parse("0:t0-1");
@@ -265,7 +264,7 @@ public class DebuggerStateEditingServiceTest extends AbstractGhidraHeadedDebugge
@Test
public void testWriteEmuMemoryTwice() throws Throwable {
createAndOpenTrace();
- editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_EMULATOR);
+ editingService.setCurrentMode(tb.trace, StateEditingMode.RW_EMULATOR);
try (UndoableTransaction tid = tb.startTransaction()) {
// NB. TraceManager should automatically activate the first thread
@@ -294,7 +293,7 @@ public class DebuggerStateEditingServiceTest extends AbstractGhidraHeadedDebugge
@Test
public void testWriteEmuRegisterTwice() throws Throwable {
createAndOpenTrace();
- editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_EMULATOR);
+ editingService.setCurrentMode(tb.trace, StateEditingMode.RW_EMULATOR);
TraceThread thread;
try (UndoableTransaction tid = tb.startTransaction()) {
@@ -325,7 +324,7 @@ public class DebuggerStateEditingServiceTest extends AbstractGhidraHeadedDebugge
public void testWriteTraceMemory() throws Throwable {
// NB. Definitely no thread required
createAndOpenTrace();
- editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_TRACE);
+ editingService.setCurrentMode(tb.trace, StateEditingMode.RW_TRACE);
activateTrace();
waitForSwing();
@@ -348,7 +347,7 @@ public class DebuggerStateEditingServiceTest extends AbstractGhidraHeadedDebugge
public void testWriteTraceRegisterNoThreadErr() throws Throwable {
// NB. Definitely no thread required
createAndOpenTrace();
- editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_TRACE);
+ editingService.setCurrentMode(tb.trace, StateEditingMode.RW_TRACE);
activateTrace();
waitForSwing();
@@ -364,7 +363,7 @@ public class DebuggerStateEditingServiceTest extends AbstractGhidraHeadedDebugge
public void testWriteTraceRegister() throws Throwable {
// NB. Definitely no thread required
createAndOpenTrace();
- editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_TRACE);
+ editingService.setCurrentMode(tb.trace, StateEditingMode.RW_TRACE);
TraceThread thread;
try (UndoableTransaction tid = tb.startTransaction()) {
@@ -397,7 +396,7 @@ public class DebuggerStateEditingServiceTest extends AbstractGhidraHeadedDebugge
activateTrace();
traceManager.activateThread(recorder.getTraceThread(mb.testThread1));
waitForSwing();
- editingService.setCurrentMode(recorder.getTrace(), StateEditingMode.WRITE_TARGET);
+ editingService.setCurrentMode(recorder.getTrace(), StateEditingMode.RW_TARGET);
StateEditor editor = createStateEditor();
assertTrue(editor.isVariableEditable(tb.addr(0x00400000), 4));
@@ -417,7 +416,7 @@ public class DebuggerStateEditingServiceTest extends AbstractGhidraHeadedDebugge
waitForSwing();
traceManager.activateThread(recorder.getTraceThread(mb.testThread1));
waitForSwing();
- editingService.setCurrentMode(recorder.getTrace(), StateEditingMode.WRITE_TARGET);
+ editingService.setCurrentMode(recorder.getTrace(), StateEditingMode.RW_TARGET);
StateEditor editor = createStateEditor();
assertTrue(editor.isRegisterEditable(r0));
@@ -436,7 +435,7 @@ public class DebuggerStateEditingServiceTest extends AbstractGhidraHeadedDebugge
TraceThread thread = recorder.getTraceThread(mb.testThread1);
traceManager.activateThread(thread);
waitForSwing();
- editingService.setCurrentMode(recorder.getTrace(), StateEditingMode.WRITE_TARGET);
+ editingService.setCurrentMode(recorder.getTrace(), StateEditingMode.RW_TARGET);
StateEditor editor = createStateEditor();
assertTrue(editor.isRegisterEditable(r0));
@@ -461,7 +460,7 @@ public class DebuggerStateEditingServiceTest extends AbstractGhidraHeadedDebugge
activateTrace();
traceManager.activateThread(recorder.getTraceThread(mb.testThread1));
waitForSwing();
- editingService.setCurrentMode(recorder.getTrace(), StateEditingMode.WRITE_TARGET);
+ editingService.setCurrentMode(recorder.getTrace(), StateEditingMode.RW_TARGET);
traceManager.activateSnap(traceManager.getCurrentSnap() - 1);
@@ -479,7 +478,7 @@ public class DebuggerStateEditingServiceTest extends AbstractGhidraHeadedDebugge
activateTrace();
traceManager.activateThread(recorder.getTraceThread(mb.testThread1));
waitForSwing();
- editingService.setCurrentMode(recorder.getTrace(), StateEditingMode.WRITE_TARGET);
+ editingService.setCurrentMode(recorder.getTrace(), StateEditingMode.RW_TARGET);
traceManager.activateSnap(traceManager.getCurrentSnap() - 1);
@@ -494,7 +493,7 @@ public class DebuggerStateEditingServiceTest extends AbstractGhidraHeadedDebugge
public void testWriteTargetMemoryNotAliveErr() throws Throwable {
createAndOpenTrace();
activateTrace();
- editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_TARGET);
+ editingService.setCurrentMode(tb.trace, StateEditingMode.RW_TARGET);
StateEditor editor = createStateEditor();
assertFalse(editor.isVariableEditable(tb.addr(0x00400000), 4));
@@ -507,7 +506,7 @@ public class DebuggerStateEditingServiceTest extends AbstractGhidraHeadedDebugge
public void testWriteTargetRegisterNotAliveErr() throws Throwable {
createAndOpenTrace();
activateTrace();
- editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_TARGET);
+ editingService.setCurrentMode(tb.trace, StateEditingMode.RW_TARGET);
StateEditor editor = createStateEditor();
assertFalse(editor.isRegisterEditable(r0));
@@ -520,7 +519,7 @@ public class DebuggerStateEditingServiceTest extends AbstractGhidraHeadedDebugge
public void testWriteReadOnlyMemoryErr() throws Throwable {
createAndOpenTrace();
activateTrace();
- editingService.setCurrentMode(tb.trace, StateEditingMode.READ_ONLY);
+ editingService.setCurrentMode(tb.trace, StateEditingMode.RO_TARGET);
StateEditor editor = createStateEditor();
assertFalse(editor.isVariableEditable(tb.addr(0x00400000), 4));
@@ -533,7 +532,7 @@ public class DebuggerStateEditingServiceTest extends AbstractGhidraHeadedDebugge
public void testWriteReadOnlyRegisterErr() throws Throwable {
createAndOpenTrace();
activateTrace();
- editingService.setCurrentMode(tb.trace, StateEditingMode.READ_ONLY);
+ editingService.setCurrentMode(tb.trace, StateEditingMode.RO_TARGET);
StateEditor editor = createStateEditor();
assertFalse(editor.isRegisterEditable(r0));
diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/emulation/DebuggerEmulationServiceTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/emulation/DebuggerEmulationServiceTest.java
index ff687bd181..98ae36cdcd 100644
--- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/emulation/DebuggerEmulationServiceTest.java
+++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/emulation/DebuggerEmulationServiceTest.java
@@ -47,6 +47,7 @@ import ghidra.program.model.mem.Memory;
import ghidra.program.model.mem.MemoryBlock;
import ghidra.program.util.ProgramLocation;
import ghidra.trace.model.*;
+import ghidra.trace.model.breakpoint.TraceBreakpoint;
import ghidra.trace.model.breakpoint.TraceBreakpointKind;
import ghidra.trace.model.guest.TracePlatform;
import ghidra.trace.model.memory.TraceMemoryManager;
@@ -402,6 +403,132 @@ public class DebuggerEmulationServiceTest extends AbstractGhidraHeadedDebuggerGU
regs.getViewValue(scratch, regR2).getUnsignedValue());
}
+ @Test
+ public void testRunAfterExecutionBreakpoint() throws Exception {
+ createProgram();
+ intoProject(program);
+ Assembler asm = Assemblers.getAssembler(program);
+ Memory memory = program.getMemory();
+ Address addrText = addr(program, 0x000400000);
+ Address addrI1;
+ Address addrI2;
+ try (UndoableTransaction tid = UndoableTransaction.start(program, "Initialize")) {
+ MemoryBlock blockText = memory.createInitializedBlock(".text", addrText, 0x1000,
+ (byte) 0, TaskMonitor.DUMMY, false);
+ blockText.setExecute(true);
+ InstructionIterator ii = asm.assemble(addrText,
+ "mov r0, r0",
+ "mov r0, r1",
+ "mov r2, r0");
+ ii.next(); // addrText
+ addrI1 = ii.next().getMinAddress();
+ addrI2 = ii.next().getMinAddress();
+ }
+
+ programManager.openProgram(program);
+ waitForSwing();
+ codeBrowser.goTo(new ProgramLocation(program, addrText));
+ waitForSwing();
+
+ performEnabledAction(codeBrowser.getProvider(), emulationPlugin.actionEmulateProgram, true);
+
+ Trace trace = traceManager.getCurrentTrace();
+ assertNotNull(trace);
+
+ TraceThread thread = Unique.assertOne(trace.getThreadManager().getAllThreads());
+
+ try (UndoableTransaction tid = UndoableTransaction.start(trace, "Add breakpoint")) {
+ trace.getBreakpointManager()
+ .addBreakpoint("Breakpoints[0]", Lifespan.nowOn(0), addrText, Set.of(thread),
+ Set.of(TraceBreakpointKind.SW_EXECUTE), true, "test");
+ trace.getBreakpointManager()
+ .addBreakpoint("Breakpoints[1]", Lifespan.nowOn(0), addrI1, Set.of(thread),
+ Set.of(TraceBreakpointKind.SW_EXECUTE), true, "test");
+ trace.getBreakpointManager()
+ .addBreakpoint("Breakpoints[2]", Lifespan.nowOn(0), addrI2, Set.of(thread),
+ Set.of(TraceBreakpointKind.SW_EXECUTE), true, "test");
+ }
+
+ // This is already testing if the one set at the entry is ignored
+ EmulationResult result1 = emulationPlugin.run(trace.getPlatformManager().getHostPlatform(),
+ TraceSchedule.snap(0), monitor, Scheduler.oneThread(thread));
+ assertEquals(TraceSchedule.parse("0:t0-1"), result1.schedule());
+ assertTrue(result1.error() instanceof InterruptPcodeExecutionException);
+
+ // This will test if the one just hit gets ignored
+ EmulationResult result2 = emulationPlugin.run(trace.getPlatformManager().getHostPlatform(),
+ result1.schedule(), monitor, Scheduler.oneThread(thread));
+ assertEquals(TraceSchedule.parse("0:t0-2"), result2.schedule());
+ assertTrue(result1.error() instanceof InterruptPcodeExecutionException);
+ }
+
+ @Test
+ public void testExecutionInjection() throws Exception {
+ createProgram();
+ intoProject(program);
+ Assembler asm = Assemblers.getAssembler(program);
+ Memory memory = program.getMemory();
+ Address addrText = addr(program, 0x000400000);
+ Register regPC = program.getRegister("pc");
+ Register regR0 = program.getRegister("r0");
+ Register regR1 = program.getRegister("r1");
+ Register regR2 = program.getRegister("r2");
+ Address addrI2;
+ try (UndoableTransaction tid = UndoableTransaction.start(program, "Initialize")) {
+ MemoryBlock blockText = memory.createInitializedBlock(".text", addrText, 0x1000,
+ (byte) 0, TaskMonitor.DUMMY, false);
+ blockText.setExecute(true);
+ InstructionIterator ii = asm.assemble(addrText,
+ "mov r0, r1",
+ "mov r2, r0");
+ ii.next();
+ addrI2 = ii.next().getMinAddress();
+ program.getProgramContext()
+ .setValue(regR1, addrText, addrText, new BigInteger("1234", 16));
+ }
+
+ programManager.openProgram(program);
+ waitForSwing();
+ codeBrowser.goTo(new ProgramLocation(program, addrText));
+ waitForSwing();
+
+ performEnabledAction(codeBrowser.getProvider(), emulationPlugin.actionEmulateProgram, true);
+
+ Trace trace = traceManager.getCurrentTrace();
+ assertNotNull(trace);
+
+ TraceThread thread = Unique.assertOne(trace.getThreadManager().getAllThreads());
+ TraceMemorySpace regs = trace.getMemoryManager().getMemoryRegisterSpace(thread, false);
+
+ try (UndoableTransaction tid = UndoableTransaction.start(trace, "Add breakpoint")) {
+ TraceBreakpoint tb = trace.getBreakpointManager()
+ .addBreakpoint("Breakpoints[0]", Lifespan.nowOn(0), addrI2, Set.of(thread),
+ Set.of(TraceBreakpointKind.SW_EXECUTE), true, "test");
+ tb.setEmuSleigh("""
+ r1 = 0x5678;
+ emu_swi();
+ emu_exec_decoded();
+ """);
+ }
+
+ EmulationResult result = emulationPlugin.run(trace.getPlatformManager().getHostPlatform(),
+ TraceSchedule.snap(0), TaskMonitor.DUMMY, Scheduler.oneThread(thread));
+
+ assertEquals(TraceSchedule.parse("0:t0-1.t0-2"), result.schedule());
+ assertTrue(result.error() instanceof InterruptPcodeExecutionException);
+
+ long scratch = result.snapshot();
+
+ assertEquals(new BigInteger("00400002", 16),
+ regs.getViewValue(scratch, regPC).getUnsignedValue());
+ assertEquals(new BigInteger("1234", 16),
+ regs.getViewValue(scratch, regR0).getUnsignedValue());
+ assertEquals(new BigInteger("5678", 16),
+ regs.getViewValue(scratch, regR1).getUnsignedValue());
+ assertEquals(new BigInteger("0", 16),
+ regs.getViewValue(scratch, regR2).getUnsignedValue());
+ }
+
@Test
public void testAccessBreakpoint() throws Exception {
createProgram();
diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/tracemgr/DebuggerTraceManagerServiceTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/tracemgr/DebuggerTraceManagerServiceTest.java
index 8c8e8c2373..18502096f9 100644
--- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/tracemgr/DebuggerTraceManagerServiceTest.java
+++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/tracemgr/DebuggerTraceManagerServiceTest.java
@@ -19,14 +19,15 @@ import static org.junit.Assert.*;
import java.util.*;
+import org.junit.Before;
import org.junit.Test;
import org.junit.experimental.categories.Category;
import generic.test.category.NightlyCategory;
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest;
-import ghidra.app.services.ActionSource;
-import ghidra.app.services.TraceRecorder;
+import ghidra.app.plugin.core.debug.service.editing.DebuggerStateEditingServicePlugin;
+import ghidra.app.services.*;
import ghidra.dbg.model.TestTargetStack;
import ghidra.dbg.model.TestTargetStackFrameHasRegisterBank;
import ghidra.dbg.target.schema.SchemaContext;
@@ -46,6 +47,14 @@ import ghidra.util.database.UndoableTransaction;
@Category(NightlyCategory.class) // this may actually be an @PortSensitive test
public class DebuggerTraceManagerServiceTest extends AbstractGhidraHeadedDebuggerGUITest {
+ protected DebuggerStateEditingService editingService;
+
+ @Before
+ public void setUpTraceManagerTest() throws Exception {
+ addPlugin(tool, DebuggerStateEditingServicePlugin.class);
+ editingService = tool.getService(DebuggerStateEditingService.class);
+ }
+
@Test
public void testGetOpenTraces() throws Exception {
assertEquals(Set.of(), traceManager.getOpenTraces());
@@ -337,9 +346,7 @@ public class DebuggerTraceManagerServiceTest extends AbstractGhidraHeadedDebugge
}
@Test
- public void testAutoActivatePresent() throws Throwable {
- assertTrue(traceManager.isAutoActivatePresent());
-
+ public void testFollowPresent() throws Throwable {
createTestModel();
mb.createTestProcessesAndThreads();
@@ -352,6 +359,7 @@ public class DebuggerTraceManagerServiceTest extends AbstractGhidraHeadedDebugge
traceManager.activateTrace(trace);
waitForSwing();
+ assertEquals(StateEditingMode.RO_TARGET, editingService.getCurrentMode(trace));
long initSnap = recorder.getSnap();
assertEquals(initSnap, traceManager.getCurrentSnap());
@@ -361,7 +369,7 @@ public class DebuggerTraceManagerServiceTest extends AbstractGhidraHeadedDebugge
assertEquals(initSnap + 1, recorder.getSnap());
assertEquals(initSnap + 1, traceManager.getCurrentSnap());
- traceManager.setAutoActivatePresent(false);
+ editingService.setCurrentMode(trace, StateEditingMode.RO_TRACE);
recorder.forceSnapshot();
waitForSwing();
@@ -369,7 +377,11 @@ public class DebuggerTraceManagerServiceTest extends AbstractGhidraHeadedDebugge
assertEquals(initSnap + 2, recorder.getSnap());
assertEquals(initSnap + 1, traceManager.getCurrentSnap());
- traceManager.setAutoActivatePresent(true);
+ editingService.setCurrentMode(trace, StateEditingMode.RO_TARGET);
+ waitForSwing();
+
+ assertEquals(initSnap + 2, recorder.getSnap());
+ assertEquals(initSnap + 2, traceManager.getCurrentSnap());
recorder.forceSnapshot();
waitForSwing();
diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/stack/StackUnwinderTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/stack/StackUnwinderTest.java
index c8ba1092d7..1591c56a44 100644
--- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/stack/StackUnwinderTest.java
+++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/stack/StackUnwinderTest.java
@@ -49,7 +49,6 @@ import ghidra.app.plugin.core.decompile.DecompilerProvider;
import ghidra.app.plugin.core.disassembler.DisassemblerPlugin;
import ghidra.app.services.*;
import ghidra.app.services.DebuggerEmulationService.EmulationResult;
-import ghidra.app.services.DebuggerStateEditingService.StateEditingMode;
import ghidra.app.services.DebuggerStateEditingService.StateEditor;
import ghidra.app.util.viewer.field.FieldFactory;
import ghidra.app.util.viewer.field.ListingField;
@@ -692,7 +691,7 @@ public class StackUnwinderTest extends AbstractGhidraHeadedDebuggerGUITest {
traceManager.activateThread(thread);
waitForSwing();
- editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_TRACE);
+ editingService.setCurrentMode(tb.trace, StateEditingMode.RW_TRACE);
StateEditor editor = editingService.createStateEditor(tb.trace);
DebuggerCoordinates atSetup = traceManager.getCurrent();
@@ -752,7 +751,7 @@ public class StackUnwinderTest extends AbstractGhidraHeadedDebuggerGUITest {
traceManager.activateThread(thread);
waitForSwing();
- editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_TRACE);
+ editingService.setCurrentMode(tb.trace, StateEditingMode.RW_TRACE);
StateEditor editor = editingService.createStateEditor(tb.trace);
DebuggerCoordinates atSetup = traceManager.getCurrent();
@@ -895,7 +894,7 @@ public class StackUnwinderTest extends AbstractGhidraHeadedDebuggerGUITest {
traceManager.activateThread(thread);
waitForSwing();
- editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_TRACE);
+ editingService.setCurrentMode(tb.trace, StateEditingMode.RW_TRACE);
StateEditor editor = editingService.createStateEditor(tb.trace);
DebuggerCoordinates atSetup = traceManager.getCurrent();
@@ -945,7 +944,7 @@ public class StackUnwinderTest extends AbstractGhidraHeadedDebuggerGUITest {
traceManager.activateThread(thread);
waitForSwing();
- editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_TRACE);
+ editingService.setCurrentMode(tb.trace, StateEditingMode.RW_TRACE);
StateEditor editor = editingService.createStateEditor(tb.trace);
// Move stack where it shows in UI. Not required, but nice for debugging.
Register sp = program.getCompilerSpec().getStackPointer();
@@ -989,7 +988,7 @@ public class StackUnwinderTest extends AbstractGhidraHeadedDebuggerGUITest {
traceManager.activateThread(thread);
waitForSwing();
- editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_TRACE);
+ editingService.setCurrentMode(tb.trace, StateEditingMode.RW_TRACE);
StateEditor editor = editingService.createStateEditor(tb.trace);
// Move stack where it shows in UI. Not required, but nice for debugging.
Register sp = program.getCompilerSpec().getStackPointer();
@@ -1033,7 +1032,7 @@ public class StackUnwinderTest extends AbstractGhidraHeadedDebuggerGUITest {
traceManager.activateThread(thread);
waitForSwing();
- editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_TRACE);
+ editingService.setCurrentMode(tb.trace, StateEditingMode.RW_TRACE);
StateEditor editor = editingService.createStateEditor(tb.trace);
// Move stack where it shows in UI. Not required, but nice for debugging.
Register sp = program.getCompilerSpec().getStackPointer();
diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/debug/flatapi/FlatDebuggerAPITest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/debug/flatapi/FlatDebuggerAPITest.java
index 1682fe0355..0360f3ba75 100644
--- a/Ghidra/Debug/Debugger/src/test/java/ghidra/debug/flatapi/FlatDebuggerAPITest.java
+++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/debug/flatapi/FlatDebuggerAPITest.java
@@ -44,7 +44,6 @@ import ghidra.app.plugin.core.debug.service.model.launch.DebuggerProgramLaunchOf
import ghidra.app.plugin.core.debug.service.model.launch.DebuggerProgramLaunchOffer.LaunchResult;
import ghidra.app.script.GhidraState;
import ghidra.app.services.*;
-import ghidra.app.services.DebuggerStateEditingService.StateEditingMode;
import ghidra.app.services.LogicalBreakpoint.State;
import ghidra.dbg.DebuggerModelFactory;
import ghidra.dbg.DebuggerObjectModel;
@@ -690,7 +689,7 @@ public class FlatDebuggerAPITest extends AbstractGhidraHeadedDebuggerGUITest {
@Test
public void testWriteMemoryGivenContext() throws Throwable {
createTraceWithBinText();
- editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_TRACE);
+ editingService.setCurrentMode(tb.trace, StateEditingMode.RW_TRACE);
assertTrue(flat.writeMemory(tb.trace, 0, tb.addr(0x00400123), tb.arr(3, 2, 1)));
ByteBuffer buf = ByteBuffer.allocate(3);
@@ -701,7 +700,7 @@ public class FlatDebuggerAPITest extends AbstractGhidraHeadedDebuggerGUITest {
@Test
public void testWriteMemoryCurrentContext() throws Throwable {
createTraceWithBinText();
- editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_TRACE);
+ editingService.setCurrentMode(tb.trace, StateEditingMode.RW_TRACE);
assertTrue(flat.writeMemory(tb.addr(0x00400123), tb.arr(3, 2, 1)));
ByteBuffer buf = ByteBuffer.allocate(3);
@@ -712,7 +711,7 @@ public class FlatDebuggerAPITest extends AbstractGhidraHeadedDebuggerGUITest {
@Test
public void testWriteRegisterGivenContext() throws Throwable {
TraceThread thread = createTraceWithThreadAndStack(true);
- editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_TRACE);
+ editingService.setCurrentMode(tb.trace, StateEditingMode.RW_TRACE);
traceManager.activateThread(thread);
waitForSwing();
@@ -728,7 +727,7 @@ public class FlatDebuggerAPITest extends AbstractGhidraHeadedDebuggerGUITest {
@Test
public void testWriteRegisterCurrentContext() throws Throwable {
TraceThread thread = createTraceWithThreadAndStack(true);
- editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_TRACE);
+ editingService.setCurrentMode(tb.trace, StateEditingMode.RW_TRACE);
traceManager.activateThread(thread);
waitForSwing();
diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/TraceSleighUtils.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/TraceSleighUtils.java
index f029a85f81..b53f5929d7 100644
--- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/TraceSleighUtils.java
+++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/TraceSleighUtils.java
@@ -59,7 +59,7 @@ public enum TraceSleighUtils {
new DirectBytesTracePcodeExecutorState(platform, snap, thread, frame);
Language language = platform.getLanguage();
if (!(language instanceof SleighLanguage)) {
- throw new IllegalArgumentException("TracePlatform must use a SLEIGH language");
+ throw new IllegalArgumentException("TracePlatform must use a Sleigh language");
}
return new PcodeExecutor<>((SleighLanguage) language,
BytesPcodeArithmetic.forLanguage(language), state, Reason.INSPECT);
@@ -99,7 +99,7 @@ public enum TraceSleighUtils {
PcodeExecutorState> paired = state.withMemoryState();
Language language = platform.getLanguage();
if (!(language instanceof SleighLanguage)) {
- throw new IllegalArgumentException("TracePlatform must use a SLEIGH language");
+ throw new IllegalArgumentException("TracePlatform must use a Sleigh language");
}
return new PcodeExecutor<>((SleighLanguage) language, new PairedPcodeArithmetic<>(
BytesPcodeArithmetic.forLanguage(language), TraceMemoryStatePcodeArithmetic.INSTANCE),
diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/breakpoint/DBTraceBreakpoint.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/breakpoint/DBTraceBreakpoint.java
index 1415d917ef..0bdd7787a3 100644
--- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/breakpoint/DBTraceBreakpoint.java
+++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/breakpoint/DBTraceBreakpoint.java
@@ -19,6 +19,7 @@ import java.io.IOException;
import java.util.*;
import db.DBRecord;
+import ghidra.pcode.exec.SleighUtils;
import ghidra.program.model.address.*;
import ghidra.trace.database.DBTrace;
import ghidra.trace.database.DBTraceUtils;
@@ -38,19 +39,21 @@ import ghidra.util.database.DBObjectColumn;
import ghidra.util.database.annot.*;
import ghidra.util.exception.DuplicateNameException;
-@DBAnnotatedObjectInfo(version = 0)
+@DBAnnotatedObjectInfo(version = 1)
public class DBTraceBreakpoint
extends AbstractDBTraceAddressSnapRangePropertyMapData
implements TraceBreakpoint {
protected static final String TABLE_NAME = "Breakpoints";
private static final byte ENABLED_MASK = (byte) (1 << 7);
+ private static final byte EMU_ENABLED_MASK = (byte) (1 << 6);
static final String PATH_COLUMN_NAME = "Path";
static final String NAME_COLUMN_NAME = "Name";
static final String THREADS_COLUMN_NAME = "Threads";
static final String FLAGS_COLUMN_NAME = "Flags";
static final String COMMENT_COLUMN_NAME = "Comment";
+ static final String SLEIGH_COLUMN_NAME = "Sleigh";
@DBAnnotatedColumn(PATH_COLUMN_NAME)
static DBObjectColumn PATH_COLUMN;
@@ -62,6 +65,8 @@ public class DBTraceBreakpoint
static DBObjectColumn FLAGS_COLUMN;
@DBAnnotatedColumn(COMMENT_COLUMN_NAME)
static DBObjectColumn COMMENT_COLUMN;
+ @DBAnnotatedColumn(SLEIGH_COLUMN_NAME)
+ static DBObjectColumn SLEIGH_COLUMN;
protected static String tableName(AddressSpace space, long threadKey) {
return DBTraceUtils.tableName(TABLE_NAME, space, threadKey, 0);
@@ -77,10 +82,13 @@ public class DBTraceBreakpoint
private byte flagsByte;
@DBAnnotatedField(column = COMMENT_COLUMN_NAME)
private String comment;
+ @DBAnnotatedField(column = SLEIGH_COLUMN_NAME)
+ private String emuSleigh;
private final Set kinds = EnumSet.noneOf(TraceBreakpointKind.class);
private final Set kindsView = Collections.unmodifiableSet(kinds);
private boolean enabled;
+ private boolean emuEnabled;
protected final DBTraceBreakpointSpace space;
@@ -108,7 +116,7 @@ public class DBTraceBreakpoint
}
}
enabled = (flagsByte & ENABLED_MASK) != 0;
- // Msg.debug(this, "trace: breakpoint " + this + " enabled=" + enabled + ", because doFresh");
+ emuEnabled = (flagsByte & EMU_ENABLED_MASK) != 0;
}
@Override
@@ -127,7 +135,8 @@ public class DBTraceBreakpoint
}
public void set(String path, String name, Collection threads,
- Collection kinds, boolean enabled, String comment) {
+ Collection kinds, boolean enabled, boolean emuEnabled,
+ String comment) {
// TODO: Check that the threads exist and that each's lifespan covers the breakpoint's
// TODO: This would require additional validation any time those are updated
// TODO: For efficiency, would also require index of breakpoints by thread
@@ -150,9 +159,13 @@ public class DBTraceBreakpoint
if (enabled) {
this.flagsByte |= ENABLED_MASK;
}
+ if (emuEnabled) {
+ this.flagsByte |= EMU_ENABLED_MASK;
+ }
this.comment = comment;
update(PATH_COLUMN, NAME_COLUMN, THREADS_COLUMN, FLAGS_COLUMN, COMMENT_COLUMN);
this.enabled = enabled;
+ this.emuEnabled = emuEnabled;
// Msg.debug(this, "trace: breakpoint " + this + " enabled=" + enabled + ", because set");
}
@@ -360,6 +373,17 @@ public class DBTraceBreakpoint
update(FLAGS_COLUMN);
}
+ protected void doSetEmuEnabled(boolean emuEnabled) {
+ this.emuEnabled = emuEnabled;
+ if (emuEnabled) {
+ flagsByte |= EMU_ENABLED_MASK;
+ }
+ else {
+ flagsByte &= ~EMU_ENABLED_MASK;
+ }
+ update(FLAGS_COLUMN);
+ }
+
protected void doSetKinds(Collection kinds) {
for (TraceBreakpointKind k : TraceBreakpointKind.values()) {
if (kinds.contains(k)) {
@@ -391,6 +415,23 @@ public class DBTraceBreakpoint
}
}
+ @Override
+ public void setEmuEnabled(boolean enabled) {
+ try (LockHold hold = LockHold.lock(space.lock.writeLock())) {
+ doSetEmuEnabled(enabled);
+ }
+ space.trace.setChanged(new TraceChangeRecord<>(TraceBreakpointChangeType.CHANGED,
+ space, this));
+ }
+
+ @Override
+ public boolean isEmuEnabled(long snap) {
+ // NB. Only object mode support per-snap emu-enablement
+ try (LockHold hold = LockHold.lock(space.lock.readLock())) {
+ return emuEnabled;
+ }
+ }
+
@Override
public void setKinds(Collection kinds) {
try (LockHold hold = LockHold.lock(space.lock.writeLock())) {
@@ -424,6 +465,29 @@ public class DBTraceBreakpoint
}
}
+ @Override
+ public void setEmuSleigh(String emuSleigh) {
+ try (LockHold hold = LockHold.lock(space.lock.writeLock())) {
+ if (emuSleigh == null || SleighUtils.UNCONDITIONAL_BREAK.equals(emuSleigh)) {
+ this.emuSleigh = null;
+ }
+ else {
+ this.emuSleigh = emuSleigh.trim();
+ }
+ update(SLEIGH_COLUMN);
+ }
+ space.trace.setChanged(new TraceChangeRecord<>(TraceBreakpointChangeType.CHANGED,
+ space, this));
+ }
+
+ @Override
+ public String getEmuSleigh() {
+ try (LockHold hold = LockHold.lock(space.lock.readLock())) {
+ return emuSleigh == null || emuSleigh.isBlank() ? SleighUtils.UNCONDITIONAL_BREAK
+ : emuSleigh;
+ }
+ }
+
@Override
public void delete() {
space.deleteBreakpoint(this);
diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/breakpoint/DBTraceBreakpointSpace.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/breakpoint/DBTraceBreakpointSpace.java
index ce09ca2efb..fa1296c007 100644
--- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/breakpoint/DBTraceBreakpointSpace.java
+++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/breakpoint/DBTraceBreakpointSpace.java
@@ -100,7 +100,7 @@ public class DBTraceBreakpointSpace implements DBTraceSpaceBased {
}
DBTraceBreakpoint breakpoint =
breakpointMapSpace.put(new ImmutableTraceAddressSnapRange(range, lifespan), null);
- breakpoint.set(path, path, threads, kinds, enabled, comment);
+ breakpoint.set(path, path, threads, kinds, enabled, true, comment);
trace.setChanged(
new TraceChangeRecord<>(TraceBreakpointChangeType.ADDED, this, breakpoint));
return breakpoint;
diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/breakpoint/DBTraceObjectBreakpointLocation.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/breakpoint/DBTraceObjectBreakpointLocation.java
index b0a8b1a59c..22e46a0df4 100644
--- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/breakpoint/DBTraceObjectBreakpointLocation.java
+++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/breakpoint/DBTraceObjectBreakpointLocation.java
@@ -15,13 +15,13 @@
*/
package ghidra.trace.database.breakpoint;
-import java.util.Collection;
-import java.util.Set;
+import java.util.*;
import java.util.stream.Collectors;
import ghidra.dbg.target.*;
import ghidra.dbg.target.schema.TargetObjectSchema;
import ghidra.dbg.util.PathMatcher;
+import ghidra.pcode.exec.SleighUtils;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressRange;
import ghidra.trace.database.target.*;
@@ -208,6 +208,11 @@ public class DBTraceObjectBreakpointLocation
object.setValue(Lifespan.span(snap, getClearedSnap()),
TargetBreakpointSpec.ENABLED_ATTRIBUTE_NAME, enabled);
}
+ Set asSet =
+ kinds instanceof Set yes ? yes : Set.copyOf(kinds);
+ if (!Objects.equals(asSet, getKinds())) {
+ this.setKinds(Lifespan.span(snap, getClearedSnap()), asSet);
+ }
return this;
}
}
@@ -297,8 +302,55 @@ public class DBTraceObjectBreakpointLocation
@Override
public String getComment() {
- return TraceObjectInterfaceUtils.getValue(object, getPlacedSnap(), KEY_COMMENT,
- String.class, "");
+ try (LockHold hold = object.getTrace().lockRead()) {
+ return TraceObjectInterfaceUtils.getValue(object, getPlacedSnap(), KEY_COMMENT,
+ String.class, "");
+ }
+ }
+
+ @Override
+ public void setEmuEnabled(Lifespan lifespan, boolean emuEnabled) {
+ object.setValue(lifespan, KEY_EMU_ENABLED, emuEnabled ? null : false);
+ }
+
+ @Override
+ public void setEmuEnabled(boolean emuEnabled) {
+ try (LockHold hold = object.getTrace().lockWrite()) {
+ setEmuEnabled(getLifespan(), emuEnabled);
+ }
+ }
+
+ @Override
+ public boolean isEmuEnabled(long snap) {
+ try (LockHold hold = object.getTrace().lockRead()) {
+ return TraceObjectInterfaceUtils.getValue(object, getPlacedSnap(), KEY_EMU_ENABLED,
+ Boolean.class, true);
+ }
+ }
+
+ @Override
+ public void setEmuSleigh(Lifespan lifespan, String sleigh) {
+ if (sleigh == null || SleighUtils.UNCONDITIONAL_BREAK.equals(sleigh)) {
+ object.setValue(lifespan, KEY_EMU_SLEIGH, null);
+ }
+ else {
+ object.setValue(lifespan, KEY_EMU_SLEIGH, sleigh.trim());
+ }
+ }
+
+ @Override
+ public void setEmuSleigh(String sleigh) {
+ try (LockHold hold = object.getTrace().lockWrite()) {
+ setEmuSleigh(getLifespan(), sleigh);
+ }
+ }
+
+ @Override
+ public String getEmuSleigh() {
+ try (LockHold hold = object.getTrace().lockRead()) {
+ return TraceObjectInterfaceUtils.getValue(object, getPlacedSnap(), KEY_EMU_SLEIGH,
+ String.class, SleighUtils.UNCONDITIONAL_BREAK);
+ }
}
@Override
diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/breakpoint/DBTraceObjectBreakpointSpec.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/breakpoint/DBTraceObjectBreakpointSpec.java
index 47248d4647..4adde4af9a 100644
--- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/breakpoint/DBTraceObjectBreakpointSpec.java
+++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/breakpoint/DBTraceObjectBreakpointSpec.java
@@ -131,7 +131,8 @@ public class DBTraceObjectBreakpointSpec
@Override
public void setEnabled(boolean enabled) {
try (LockHold hold = object.getTrace().lockWrite()) {
- object.setValue(getLifespan(), TargetBreakpointSpec.ENABLED_ATTRIBUTE_NAME, enabled);
+ object.setValue(getLifespan(), TargetBreakpointSpec.ENABLED_ATTRIBUTE_NAME,
+ enabled ? true : null);
}
}
@@ -190,6 +191,26 @@ public class DBTraceObjectBreakpointSpec
throw new UnsupportedOperationException("Ask a location instead");
}
+ @Override
+ public void setEmuEnabled(boolean enabled) {
+ throw new UnsupportedOperationException("Set on a location instead");
+ }
+
+ @Override
+ public boolean isEmuEnabled(long snap) {
+ throw new UnsupportedOperationException("Ask a location instead");
+ }
+
+ @Override
+ public void setEmuSleigh(String sleigh) {
+ throw new UnsupportedOperationException("Set on a location instead");
+ }
+
+ @Override
+ public String getEmuSleigh() {
+ throw new UnsupportedOperationException("Ask a location instead");
+ }
+
@Override
public void delete() {
try (LockHold hold = object.getTrace().lockWrite()) {
diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/Trace.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/Trace.java
index 8b1dd3847e..e79a37e6d0 100644
--- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/Trace.java
+++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/Trace.java
@@ -53,6 +53,16 @@ import resources.ResourceManager;
public interface Trace extends DataTypeManagerDomainObject {
ImageIcon TRACE_ICON = ResourceManager.loadImage("images/video-x-generic16.png");
+ /**
+ * TEMPORARY: An a/b switch while both table- (legacy) and object-mode traces are supported
+ *
+ * @param trace the trace, or null
+ * @return true if the trace is non-null and has no root schema
+ */
+ public static boolean isLegacy(Trace trace) {
+ return trace != null && trace.getObjectManager().getRootSchema() == null;
+ }
+
public static final class TraceObjectChangeType
extends DefaultTraceChangeType {
/**
diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/breakpoint/TraceBreakpoint.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/breakpoint/TraceBreakpoint.java
index 370d7b57b2..320cf37b48 100644
--- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/breakpoint/TraceBreakpoint.java
+++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/breakpoint/TraceBreakpoint.java
@@ -28,7 +28,6 @@ import ghidra.util.exception.DuplicateNameException;
* A breakpoint in a trace
*/
public interface TraceBreakpoint extends TraceUniqueObject {
-
/**
* Get the trace containing this breakpoint
*
@@ -161,10 +160,30 @@ public interface TraceBreakpoint extends TraceUniqueObject {
/**
* Check whether this breakpoint is enabled or disabled at the given snap
*
+ * @param snap the snap
* @return true if enabled, false if disabled
*/
boolean isEnabled(long snap);
+ /**
+ * Set whether this breakpoint is enabled or disabled for emulation
+ *
+ *
+ * This change applies to the entire lifespan of the record. It's not intended to record a
+ * history, but to toggle the breakpoint in the integrated emulator.
+ *
+ * @param enabled true if enabled, false if disabled
+ */
+ void setEmuEnabled(boolean enabled);
+
+ /**
+ * Check whether this breakpoint is enabled or disabled for emulation at the given snap
+ *
+ * @param snap the snap
+ * @return true if enabled, false if disabled
+ */
+ boolean isEmuEnabled(long snap);
+
/**
* Set the kinds included in this breakpoint
*
@@ -213,6 +232,36 @@ public interface TraceBreakpoint extends TraceUniqueObject {
*/
String getComment();
+ /**
+ * Set Sleigh source to replace the breakpointed instruction in emulation
+ *
+ *
+ * The default is simply "{@link PcodeEmulationLibrary#emu_swi() emu_swi()};
+ * {@link PcodeEmulationLibrary#emu_exec_decoded() emu_exec_decoded()};
", effectively a
+ * non-conditional breakpoint followed by execution of the actual instruction. Modifying this
+ * allows clients to create conditional breakpoints or simply override or inject additional
+ * logic into an emulated target.
+ *
+ *
+ * NOTE: This current has no effect on access breakpoints, but only execution
+ * breakpoints.
+ *
+ *
+ * If the specified source fails to compile during emulator set-up, this will fall back to
+ * {@link PcodeEmulationLibrary#emu_err
+ *
+ * @see #DEFAULT_SLEIGH
+ * @param sleigh the Sleigh source
+ */
+ void setEmuSleigh(String sleigh);
+
+ /**
+ * Get the Sleigh source that replaces the breakpointed instruction in emulation
+ *
+ * @return the Sleigh source
+ */
+ String getEmuSleigh();
+
/**
* Delete this breakpoint from the trace
*/
diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/breakpoint/TraceBreakpointKind.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/breakpoint/TraceBreakpointKind.java
index a5a63c64d6..4d6d0543f4 100644
--- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/breakpoint/TraceBreakpointKind.java
+++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/breakpoint/TraceBreakpointKind.java
@@ -37,6 +37,8 @@ public enum TraceBreakpointKind {
HW_EXECUTE(1 << 2),
SW_EXECUTE(1 << 3);
+ public static final int COUNT = values().length;
+
public static class TraceBreakpointKindSet extends AbstractSetDecorator {
public static final TraceBreakpointKindSet SW_EXECUTE = of(TraceBreakpointKind.SW_EXECUTE);
public static final TraceBreakpointKindSet HW_EXECUTE = of(TraceBreakpointKind.HW_EXECUTE);
diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/breakpoint/TraceObjectBreakpointLocation.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/breakpoint/TraceObjectBreakpointLocation.java
index 9751cf53f6..b9f20ee67d 100644
--- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/breakpoint/TraceObjectBreakpointLocation.java
+++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/breakpoint/TraceObjectBreakpointLocation.java
@@ -32,9 +32,12 @@ import ghidra.util.exception.DuplicateNameException;
TargetObject.DISPLAY_ATTRIBUTE_NAME,
TargetBreakpointLocation.RANGE_ATTRIBUTE_NAME,
TraceObjectBreakpointLocation.KEY_COMMENT,
+ TraceObjectBreakpointLocation.KEY_EMU_ENABLED,
})
public interface TraceObjectBreakpointLocation extends TraceBreakpoint, TraceObjectInterface {
String KEY_COMMENT = "_comment";
+ String KEY_EMU_ENABLED = "_emu_enabled";
+ String KEY_EMU_SLEIGH = "_emu_sleigh";
TraceObjectBreakpointSpec getSpecification();
@@ -48,5 +51,9 @@ public interface TraceObjectBreakpointLocation extends TraceBreakpoint, TraceObj
void setEnabled(Lifespan lifespan, boolean enabled);
+ void setEmuEnabled(Lifespan lifespan, boolean emuEnabled);
+
+ void setEmuSleigh(Lifespan lifespan, String sleigh);
+
void setComment(Lifespan lifespan, String comment);
}
diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/schedule/Scheduler.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/schedule/Scheduler.java
index b45abe10aa..ee3b55aa27 100644
--- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/schedule/Scheduler.java
+++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/schedule/Scheduler.java
@@ -132,14 +132,16 @@ public interface Scheduler {
}
catch (PcodeExecutionException e) {
completedSteps = completedSteps.steppedForward(eventThread, completedTicks);
- if (emuThread.getInstruction() == null) {
+ PcodeFrame frame = emuThread.getFrame();
+ if (frame == null) {
return new RecordRunResult(completedSteps, e);
}
- PcodeFrame frame = e.getFrame();
// Rewind one so stepping retries the op causing the error
- int count = frame.count() - 1;
- if (frame == null || count == 0) {
- // If we've decoded, but could execute the first op, just drop the p-code steps
+ frame.stepBack();
+ int count = frame.count();
+ if (count == 0) {
+ // If we've decoded, but could not execute the first op, just drop the p-code steps
+ emuThread.dropInstruction();
return new RecordRunResult(completedSteps, e);
}
// The +1 accounts for the decode step
diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/schedule/TraceSchedule.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/schedule/TraceSchedule.java
index a436e195af..12b71d2fdd 100644
--- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/schedule/TraceSchedule.java
+++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/schedule/TraceSchedule.java
@@ -18,6 +18,7 @@ package ghidra.trace.model.time.schedule;
import java.util.*;
import ghidra.pcode.emu.PcodeMachine;
+import ghidra.pcode.emu.PcodeMachine.SwiMode;
import ghidra.program.model.lang.Language;
import ghidra.trace.model.Trace;
import ghidra.trace.model.thread.TraceThread;
@@ -25,9 +26,18 @@ import ghidra.trace.model.time.TraceSnapshot;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;
+/**
+ * A sequence of emulator stepping commands, essentially comprising a "point in time."
+ */
public class TraceSchedule implements Comparable {
public static final TraceSchedule ZERO = TraceSchedule.snap(0);
+ /**
+ * Create a schedule that consists solely of a snapshot
+ *
+ * @param snap the snapshot key
+ * @return the schedule
+ */
public static final TraceSchedule snap(long snap) {
return new TraceSchedule(snap, new Sequence(), new Sequence());
}
@@ -337,11 +347,10 @@ public class TraceSchedule implements Comparable {
*/
public void execute(Trace trace, PcodeMachine> machine, TaskMonitor monitor)
throws CancelledException {
+ machine.setSoftwareInterruptMode(SwiMode.IGNORE_ALL);
TraceThread lastThread = getEventThread(trace);
- lastThread =
- steps.execute(trace, lastThread, machine, Stepper.instruction(), monitor);
- lastThread =
- pSteps.execute(trace, lastThread, machine, Stepper.pcode(), monitor);
+ lastThread = steps.execute(trace, lastThread, machine, Stepper.instruction(), monitor);
+ lastThread = pSteps.execute(trace, lastThread, machine, Stepper.pcode(), monitor);
}
/**
@@ -380,6 +389,7 @@ public class TraceSchedule implements Comparable {
TaskMonitor monitor) throws CancelledException {
TraceThread lastThread = position.getLastThread(trace);
Sequence remains = steps.relativize(position.steps);
+ machine.setSoftwareInterruptMode(SwiMode.IGNORE_ALL);
if (remains.isNop()) {
Sequence pRemains = this.pSteps.relativize(position.pSteps);
lastThread =
@@ -388,8 +398,7 @@ public class TraceSchedule implements Comparable {
else {
lastThread =
remains.execute(trace, lastThread, machine, Stepper.instruction(), monitor);
- lastThread =
- pSteps.execute(trace, lastThread, machine, Stepper.pcode(), monitor);
+ lastThread = pSteps.execute(trace, lastThread, machine, Stepper.pcode(), monitor);
}
}
diff --git a/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/pcode/exec/trace/AbstractTracePcodeEmulatorTest.java b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/pcode/exec/trace/AbstractTracePcodeEmulatorTest.java
index 5b91d5469d..a3aaf25d98 100644
--- a/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/pcode/exec/trace/AbstractTracePcodeEmulatorTest.java
+++ b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/pcode/exec/trace/AbstractTracePcodeEmulatorTest.java
@@ -46,8 +46,8 @@ public class AbstractTracePcodeEmulatorTest extends AbstractGhidraHeadlessIntegr
*
* This creates a relatively bare-bones trace with initial state for testing trace
* emulation/interpolation. It adds ".text" and "stack" regions, creates a thread, assembles
- * given instructions, and then executes the given SLEIGH source (in the context of the new
- * thread) to finish initializing the trace. Note, though given first, the SLEIGH is executed
+ * given instructions, and then executes the given Sleigh source (in the context of the new
+ * thread) to finish initializing the trace. Note, though given first, the Sleigh is executed
* after assembly. Thus, it can be used to modify the resulting machine code by modifying the
* memory where it was assembled.
*
diff --git a/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/pcode/exec/trace/BytesTracePcodeEmulatorTest.java b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/pcode/exec/trace/BytesTracePcodeEmulatorTest.java
index d708d0f8e2..bced23293b 100644
--- a/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/pcode/exec/trace/BytesTracePcodeEmulatorTest.java
+++ b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/pcode/exec/trace/BytesTracePcodeEmulatorTest.java
@@ -312,7 +312,7 @@ public class BytesTracePcodeEmulatorTest extends AbstractTracePcodeEmulatorTest
* This may not reflect the semantics of an actual processor in these situations, since they may
* have instruction caching. Emulating such semantics is TODO, if at all. NB. This also tests
* that PC-relative addressing works, since internally the emulator advances the counter after
- * execution of each instruction. Addressing is computed by the SLEIGH instruction parser and
+ * execution of each instruction. Addressing is computed by the Sleigh instruction parser and
* encoded as a constant deref in the p-code.
*/
@Test
diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/docking/widgets/table/RowWrappedEnumeratedColumnTableModel.java b/Ghidra/Debug/ProposedUtils/src/main/java/docking/widgets/table/RowWrappedEnumeratedColumnTableModel.java
index f9ca2c1a90..c335224f37 100644
--- a/Ghidra/Debug/ProposedUtils/src/main/java/docking/widgets/table/RowWrappedEnumeratedColumnTableModel.java
+++ b/Ghidra/Debug/ProposedUtils/src/main/java/docking/widgets/table/RowWrappedEnumeratedColumnTableModel.java
@@ -17,6 +17,7 @@ package docking.widgets.table;
import java.util.*;
import java.util.function.Function;
+import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@@ -37,13 +38,15 @@ public class RowWrappedEnumeratedColumnTableModel & Enumerated
extends DefaultEnumeratedColumnTableModel {
private final Function keyFunc;
private final Function wrapper;
+ private final Function getter;
private final Map map = new HashMap<>();
public RowWrappedEnumeratedColumnTableModel(PluginTool tool, String name, Class colType,
- Function keyFunc, Function wrapper) {
+ Function keyFunc, Function wrapper, Function getter) {
super(tool, name, colType);
this.keyFunc = keyFunc;
this.wrapper = wrapper;
+ this.getter = getter;
}
protected synchronized R addRowFor(T t) {
@@ -130,6 +133,11 @@ public class RowWrappedEnumeratedColumnTableModel & Enumerated
return r;
}
+ public synchronized void deleteItemsWith(Predicate predicate) {
+ List deleted = deleteWith(r -> predicate.test(getter.apply(r)));
+ map.values().removeAll(deleted);
+ }
+
public synchronized void deleteAllItems(Collection c) {
deleteWith(getRows(c)::contains);
map.keySet().removeAll(c.stream().map(keyFunc).collect(Collectors.toList()));
diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/AbstractPcodeMachine.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/AbstractPcodeMachine.java
index 286aaeed24..b01c9922c8 100644
--- a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/AbstractPcodeMachine.java
+++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/AbstractPcodeMachine.java
@@ -64,6 +64,8 @@ public abstract class AbstractPcodeMachine implements PcodeMachine {
protected final PcodeUseropLibrary stubLibrary;
+ protected SwiMode swiMode = SwiMode.ACTIVE;
+
/* for abstract thread access */ PcodeStateInitializer initializer;
private PcodeExecutorState sharedState;
protected final Map> threads = new LinkedHashMap<>();
@@ -145,12 +147,12 @@ public abstract class AbstractPcodeMachine implements PcodeMachine {
protected abstract PcodeExecutorState createLocalState(PcodeThread thread);
/**
- * A factory method to create a stub library for compiling thread-local SLEIGH source
+ * A factory method to create a stub library for compiling thread-local Sleigh source
*
*
* Because threads may introduce p-code userops using libraries unique to that thread, it
* becomes necessary to at least export stub symbols, so that p-code programs can be compiled
- * from SLEIGH source before the thread has necessarily been created. A side effect of this
+ * from Sleigh source before the thread has necessarily been created. A side effect of this
* strategy is that all threads, though they may have independent libraries, must export
* identically-named symbols.
*
@@ -160,6 +162,16 @@ public abstract class AbstractPcodeMachine implements PcodeMachine {
return new DefaultPcodeThread.PcodeEmulationLibrary