GP-896: Recording changes in breakpoint spec kinds

This commit is contained in:
Dan 2021-04-27 09:40:43 -04:00
parent d1c1d9d5e8
commit f1b200fbfd
5 changed files with 98 additions and 23 deletions

View file

@ -186,8 +186,9 @@ public class DefaultBreakpointRecorder implements ManagedBreakpointRecorder {
}, path); }, path);
} }
protected void doBreakpointToggled(long snap, protected void doBreakpointSpecChanged(long snap,
Collection<? extends TargetBreakpointLocation> bpts, boolean enabled) { Collection<? extends TargetBreakpointLocation> bpts, boolean enabled,
Collection<TraceBreakpointKind> kinds) {
for (TargetBreakpointLocation bl : bpts) { for (TargetBreakpointLocation bl : bpts) {
String path = PathUtils.toString(bl.getPath()); String path = PathUtils.toString(bl.getPath());
recorder.parTx.execute("Breakpoint " + path + " toggled", () -> { recorder.parTx.execute("Breakpoint " + path + " toggled", () -> {
@ -197,16 +198,17 @@ public class DefaultBreakpointRecorder implements ManagedBreakpointRecorder {
return; return;
} }
// Verify attributes match? Eh. If they don't, someone has fiddled with it. // Verify attributes match? Eh. If they don't, someone has fiddled with it.
traceBpt.splitWithEnabled(snap, enabled); traceBpt.splitAndSet(snap, enabled, kinds);
}, path); }, path);
} }
} }
@Override @Override
public void breakpointToggled(TargetBreakpointSpec spec, boolean enabled) { public void breakpointSpecChanged(TargetBreakpointSpec spec, boolean enabled,
Collection<TraceBreakpointKind> kinds) {
long snap = recorder.getSnap(); long snap = recorder.getSnap();
spec.getLocations().thenAccept(bpts -> { spec.getLocations().thenAccept(bpts -> {
doBreakpointToggled(snap, bpts, enabled); doBreakpointSpecChanged(snap, bpts, enabled, kinds);
}).exceptionally(ex -> { }).exceptionally(ex -> {
Msg.error(this, "Error recording toggled breakpoint spec: " + spec.getJoinedPath("."), Msg.error(this, "Error recording toggled breakpoint spec: " + spec.getJoinedPath("."),
ex); ex);

View file

@ -23,6 +23,7 @@ import java.util.stream.Collectors;
import ghidra.app.plugin.core.debug.mapping.*; import ghidra.app.plugin.core.debug.mapping.*;
import ghidra.app.plugin.core.debug.service.model.interfaces.*; import ghidra.app.plugin.core.debug.service.model.interfaces.*;
import ghidra.app.services.TraceRecorder;
import ghidra.app.services.TraceRecorderListener; import ghidra.app.services.TraceRecorderListener;
import ghidra.async.AsyncLazyMap; import ghidra.async.AsyncLazyMap;
import ghidra.dbg.target.*; import ghidra.dbg.target.*;
@ -30,6 +31,7 @@ import ghidra.dbg.util.PathUtils;
import ghidra.dbg.util.PathUtils.PathComparator; import ghidra.dbg.util.PathUtils.PathComparator;
import ghidra.program.model.address.Address; import ghidra.program.model.address.Address;
import ghidra.trace.model.breakpoint.TraceBreakpoint; import ghidra.trace.model.breakpoint.TraceBreakpoint;
import ghidra.trace.model.breakpoint.TraceBreakpointKind;
import ghidra.trace.model.memory.TraceMemoryRegion; import ghidra.trace.model.memory.TraceMemoryRegion;
import ghidra.trace.model.modules.TraceModule; import ghidra.trace.model.modules.TraceModule;
import ghidra.trace.model.modules.TraceSection; import ghidra.trace.model.modules.TraceSection;
@ -471,10 +473,13 @@ public class TraceObjectManager {
} }
public void attributesChangedBreakpointSpec(TargetObject bpt, Map<String, ?> added) { public void attributesChangedBreakpointSpec(TargetObject bpt, Map<String, ?> added) {
if (added.containsKey(TargetBreakpointSpec.ENABLED_ATTRIBUTE_NAME)) { if (added.containsKey(TargetBreakpointSpec.ENABLED_ATTRIBUTE_NAME) ||
added.containsKey(TargetBreakpointSpec.KINDS_ATTRIBUTE_NAME)) {
TargetBreakpointSpec spec = (TargetBreakpointSpec) bpt; TargetBreakpointSpec spec = (TargetBreakpointSpec) bpt;
boolean enabled = (Boolean) added.get(TargetBreakpointSpec.ENABLED_ATTRIBUTE_NAME); boolean enabled = spec.isEnabled();
recorder.breakpointRecorder.breakpointToggled(spec, enabled); Set<TraceBreakpointKind> traceKinds =
TraceRecorder.targetToTraceBreakpointKinds(spec.getKinds());
recorder.breakpointRecorder.breakpointSpecChanged(spec, enabled, traceKinds);
} }
} }

View file

@ -15,11 +15,13 @@
*/ */
package ghidra.app.plugin.core.debug.service.model.interfaces; package ghidra.app.plugin.core.debug.service.model.interfaces;
import java.util.Collection;
import java.util.Set; import java.util.Set;
import ghidra.dbg.target.*; import ghidra.dbg.target.*;
import ghidra.program.model.address.Address; import ghidra.program.model.address.Address;
import ghidra.trace.model.breakpoint.TraceBreakpoint; import ghidra.trace.model.breakpoint.TraceBreakpoint;
import ghidra.trace.model.breakpoint.TraceBreakpointKind;
import ghidra.trace.model.thread.TraceThread; import ghidra.trace.model.thread.TraceThread;
public interface ManagedBreakpointRecorder { public interface ManagedBreakpointRecorder {
@ -37,7 +39,7 @@ public interface ManagedBreakpointRecorder {
TraceBreakpoint getTraceBreakpoint(TargetBreakpointLocation bpt); TraceBreakpoint getTraceBreakpoint(TargetBreakpointLocation bpt);
/** /**
* The length of a breakpoint location has changed * The range of a breakpoint location has changed
* *
* @param length the new length * @param length the new length
* @param traceAddr the address of the location in the trace * @param traceAddr the address of the location in the trace
@ -45,5 +47,13 @@ public interface ManagedBreakpointRecorder {
*/ */
void breakpointLocationChanged(int length, Address traceAddr, String path); void breakpointLocationChanged(int length, Address traceAddr, String path);
void breakpointToggled(TargetBreakpointSpec spec, boolean enabled); /**
* A breakpoint specification has changed (typically, toggled)
*
* @param spec the specification that changed
* @param enabled whether or not the spec is enabled
* @param kinds the kinds of the spec
*/
void breakpointSpecChanged(TargetBreakpointSpec spec, boolean enabled,
Collection<TraceBreakpointKind> kinds);
} }

View file

@ -284,7 +284,8 @@ public class DBTraceBreakpoint
} }
@Override @Override
public DBTraceBreakpoint splitWithEnabled(long snap, boolean en) { public DBTraceBreakpoint splitAndSet(long snap, boolean en,
Collection<TraceBreakpointKind> kinds) {
DBTraceBreakpoint that; DBTraceBreakpoint that;
Range<Long> oldLifespan; Range<Long> oldLifespan;
Range<Long> newLifespan; Range<Long> newLifespan;
@ -292,7 +293,7 @@ public class DBTraceBreakpoint
if (!lifespan.contains(snap)) { if (!lifespan.contains(snap)) {
throw new IllegalArgumentException("snap = " + snap); throw new IllegalArgumentException("snap = " + snap);
} }
if (en == enabled) { if (flagsByte == computeFlagsByte(enabled, kinds)) {
return this; return this;
} }
if (snap == getPlacedSnap()) { if (snap == getPlacedSnap()) {
@ -302,7 +303,7 @@ public class DBTraceBreakpoint
that = doCopy(); that = doCopy();
that.doSetLifespan(DBTraceUtils.toRange(snap, getClearedSnap())); that.doSetLifespan(DBTraceUtils.toRange(snap, getClearedSnap()));
that.doSetEnabled(en); that.doSetFlags(en, kinds);
oldLifespan = lifespan; oldLifespan = lifespan;
newLifespan = DBTraceUtils.toRange(getPlacedSnap(), snap - 1); newLifespan = DBTraceUtils.toRange(getPlacedSnap(), snap - 1);
this.doSetLifespan(newLifespan); this.doSetLifespan(newLifespan);
@ -315,7 +316,26 @@ public class DBTraceBreakpoint
return that; return that;
} }
protected void doSetEnabled(@SuppressWarnings("hiding") boolean enabled) { protected static byte computeFlagsByte(boolean enabled, Collection<TraceBreakpointKind> kinds) {
byte flags = 0;
for (TraceBreakpointKind k : kinds) {
flags |= k.getBits();
}
if (enabled) {
flags |= ENABLED_MASK;
}
return flags;
}
protected void doSetFlags(boolean enabled, Collection<TraceBreakpointKind> kinds) {
this.flagsByte = computeFlagsByte(enabled, kinds);
this.kinds.clear();
this.kinds.addAll(kinds);
this.enabled = enabled;
update(FLAGS_COLUMN);
}
protected void doSetEnabled(boolean enabled) {
this.enabled = enabled; this.enabled = enabled;
if (enabled) { if (enabled) {
flagsByte |= ENABLED_MASK; flagsByte |= ENABLED_MASK;
@ -326,6 +346,20 @@ public class DBTraceBreakpoint
update(FLAGS_COLUMN); update(FLAGS_COLUMN);
} }
protected void doSetKinds(Collection<TraceBreakpointKind> kinds) {
for (TraceBreakpointKind k : TraceBreakpointKind.values()) {
if (kinds.contains(k)) {
this.flagsByte |= k.getBits();
this.kinds.add(k);
}
else {
this.flagsByte &= ~k.getBits();
this.kinds.remove(k);
}
}
update(FLAGS_COLUMN);
}
@Override @Override
public void setEnabled(boolean enabled) { public void setEnabled(boolean enabled) {
try (LockHold hold = LockHold.lock(space.lock.writeLock())) { try (LockHold hold = LockHold.lock(space.lock.writeLock())) {
@ -342,6 +376,15 @@ public class DBTraceBreakpoint
} }
} }
@Override
public void setKinds(Collection<TraceBreakpointKind> kinds) {
try (LockHold hold = LockHold.lock(space.lock.writeLock())) {
doSetKinds(kinds);
}
space.trace.setChanged(
new TraceChangeRecord<>(TraceBreakpointChangeType.CHANGED, space, this));
}
@Override @Override
public Set<TraceBreakpointKind> getKinds() { public Set<TraceBreakpointKind> getKinds() {
try (LockHold hold = LockHold.lock(space.lock.readLock())) { try (LockHold hold = LockHold.lock(space.lock.readLock())) {

View file

@ -15,6 +15,7 @@
*/ */
package ghidra.trace.model.breakpoint; package ghidra.trace.model.breakpoint;
import java.util.Collection;
import java.util.Set; import java.util.Set;
import com.google.common.collect.Range; import com.google.common.collect.Range;
@ -130,18 +131,18 @@ public interface TraceBreakpoint {
long getClearedSnap(); long getClearedSnap();
/** /**
* Split this breakpoint at the given snap, the later having the given "enabled" value. * Split this breakpoint at the given snap, and set the later's fields.
* *
* <p> * <p>
* This breakpoint's lifespan must contain the given snap. This method first creates a copy of * This breakpoint's lifespan must contain the given snap. This method first creates a copy of
* this breakpoint, replacing the copy's placed snap and "enabled" value with those given. Then, * this breakpoint, replacing the copy's placed snap and additional fields. Then, it sets this
* it sets this breakpoint's cleared snap to one less than the given snap, so that the two * breakpoint's cleared snap to one less than the given snap, so that the two breakpoints do not
* breakpoints do not overlap. * overlap.
* *
* <p> * <p>
* Note the following special cases: 1) If the given snap is equal to the placed snap, this * Note the following special cases: 1) If the given snap is equal to the placed snap, this
* method behaves like {@link #setEnabled(boolean)} and return this breakpoint. 2) If "enabled" * method simply sets the fields on this breakpoint and returns this. 2) If the field values
* indicates no change, this method does nothing and returns this breakpoint. * indicate no change, this method does nothing and returns this breakpoint.
* *
* @implNote Listeners on breakpoint changes will see the added record before the lifespan * @implNote Listeners on breakpoint changes will see the added record before the lifespan
* change of the old record, despite those two records having the same path and * change of the old record, despite those two records having the same path and
@ -150,9 +151,10 @@ public interface TraceBreakpoint {
* *
* @param snap the placed snap for the later breakpoint * @param snap the placed snap for the later breakpoint
* @param enabled true if the later breakpoint is enabled, false if disabled * @param enabled true if the later breakpoint is enabled, false if disabled
* @param kinds the kinds of the later breakpoint
* @return the new breakpoint, or this breakpoint (see special case) * @return the new breakpoint, or this breakpoint (see special case)
*/ */
TraceBreakpoint splitWithEnabled(long snap, boolean enabled); TraceBreakpoint splitAndSet(long snap, boolean enabled, Collection<TraceBreakpointKind> kinds);
/** /**
* Set whether this breakpoint was enabled or disabled * Set whether this breakpoint was enabled or disabled
@ -160,7 +162,7 @@ public interface TraceBreakpoint {
* <p> * <p>
* This change applies to the entire lifespan of this record. If a breakpoint is enabled for * This change applies to the entire lifespan of this record. If a breakpoint is enabled for
* some duration and then later disabled, this breakpoint should be split instead. See * some duration and then later disabled, this breakpoint should be split instead. See
* {@link #splitWithEnabled(long,boolean)}. * {@link #splitAndSet(long,boolean, Collection)}.
* *
* @param enabled true if enabled, false if disabled * @param enabled true if enabled, false if disabled
*/ */
@ -173,12 +175,25 @@ public interface TraceBreakpoint {
*/ */
boolean isEnabled(); boolean isEnabled();
/**
* Set the kinds included in this breakpoint
*
* <p>
* See {@link #getKinds()}. Note that it is unusual for a breakpoint to change kinds during its
* life. Nevertheless, in the course of recording a trace, it may happen, or at least appear to
* happen. Rather than require the client to delete and re-create the breakpoint, this allows
* the record to be updated. See also {@link #splitAndSet(long, boolean, Collection)}.
*
* @param kinds the set of kinds
*/
void setKinds(Collection<TraceBreakpointKind> kinds);
/** /**
* Get the kinds included in this breakpoint * Get the kinds included in this breakpoint
* *
* <p> * <p>
* For example, an "access breakpoint" or "access watchpoint," depending on terminology, would * For example, an "access breakpoint" or "access watchpoint," depending on terminology, would
* have include both {@link TraceBreakpointKind#READ} and {@link TraceBreakpointKind#WRITE}. * include both {@link TraceBreakpointKind#READ} and {@link TraceBreakpointKind#WRITE}.
* *
* @return the set of kinds * @return the set of kinds
*/ */