Merge remote-tracking branch

'origin/GP-1678_Dan_objectRecorder--SQUASHED'

Conflicts:
	Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/copying/DebuggerCopyIntoProgramDialog.java
This commit is contained in:
Ryan Kurtz 2022-04-29 10:10:33 -04:00
commit 0e8e418bfa
110 changed files with 3191 additions and 497 deletions

View file

@ -15,6 +15,8 @@
*/
package ghidra.pcode.exec.trace;
import com.google.common.collect.Range;
import ghidra.app.plugin.processors.sleigh.SleighLanguage;
import ghidra.pcode.emu.AbstractPcodeEmulator;
import ghidra.pcode.emu.PcodeThread;
@ -96,7 +98,8 @@ public class TracePcodeEmulator extends AbstractPcodeEmulator {
ls.writeCacheDown(trace, destSnap, traceThread, 0);
if (synthesizeStacks) {
TraceStack stack = trace.getStackManager().getStack(traceThread, destSnap, true);
stack.getFrame(0, true).setProgramCounter(emuThread.getCounter());
stack.getFrame(0, true)
.setProgramCounter(Range.atLeast(destSnap), emuThread.getCounter());
}
}
}

View file

@ -268,7 +268,9 @@ public class DBTraceObjectBreakpointLocation
@Override
public TraceObjectBreakpointSpec getSpecification() {
try (LockHold hold = object.getTrace().lockRead()) {
return object.queryAncestorsInterface(getLifespan(), TraceObjectBreakpointSpec.class)
return object
.queryCanonicalAncestorsInterface(getLifespan(),
TraceObjectBreakpointSpec.class)
.findAny()
.orElseThrow();
}

View file

@ -15,7 +15,8 @@
*/
package ghidra.trace.database.breakpoint;
import java.util.*;
import java.util.Collection;
import java.util.Set;
import java.util.stream.Collectors;
import com.google.common.collect.Range;
@ -31,6 +32,7 @@ import ghidra.trace.model.Trace;
import ghidra.trace.model.Trace.TraceBreakpointChangeType;
import ghidra.trace.model.Trace.TraceObjectChangeType;
import ghidra.trace.model.breakpoint.*;
import ghidra.trace.model.breakpoint.TraceBreakpointKind.TraceBreakpointKindSet;
import ghidra.trace.model.target.TraceObject;
import ghidra.trace.model.target.TraceObjectValue;
import ghidra.trace.model.target.annot.TraceObjectInterfaceUtils;
@ -46,7 +48,7 @@ public class DBTraceObjectBreakpointSpec
implements TraceObjectBreakpointSpec, DBTraceObjectInterface {
private final DBTraceObject object;
private Set<TraceBreakpointKind> kinds;
private TraceBreakpointKindSet kinds;
public DBTraceObjectBreakpointSpec(DBTraceObject object) {
this.object = object;
@ -147,28 +149,25 @@ public class DBTraceObjectBreakpointSpec
// TODO: Target-Trace mapping is implied by encoded name. Seems bad.
try (LockHold hold = object.getTrace().lockWrite()) {
object.setValue(getLifespan(), TargetBreakpointSpec.KINDS_ATTRIBUTE_NAME,
kinds.stream().map(k -> k.name()).collect(Collectors.joining(",")));
this.kinds = Set.copyOf(kinds);
TraceBreakpointKindSet.encode(kinds));
this.kinds = TraceBreakpointKindSet.copyOf(kinds);
}
}
@Override
public Set<TraceBreakpointKind> getKinds() {
Set<TraceBreakpointKind> result = new HashSet<>();
String kindsStr = TraceObjectInterfaceUtils.getValue(object, getPlacedSnap(),
TargetBreakpointSpec.KINDS_ATTRIBUTE_NAME, String.class, null);
if (kindsStr == null) {
return kinds;
}
for (String name : kindsStr.split(",")) {
try {
result.add(TraceBreakpointKind.valueOf(name));
}
catch (IllegalArgumentException e) {
Msg.warn(this, "Could not decode breakpoint kind from trace database: " + name);
}
try {
return kinds = TraceBreakpointKindSet.decode(kindsStr, true);
}
catch (IllegalArgumentException e) {
Msg.warn(this, "Unrecognized breakpoint kind(s) in trace database: " + e);
return kinds = TraceBreakpointKindSet.decode(kindsStr, false);
}
return kinds = result;
}
@Override
@ -210,13 +209,17 @@ public class DBTraceObjectBreakpointSpec
if (rec.getEventType() == TraceObjectChangeType.VALUE_CHANGED.getType()) {
TraceChangeRecord<TraceObjectValue, Object> cast =
TraceObjectChangeType.VALUE_CHANGED.cast(rec);
String key = cast.getAffectedObject().getEntryKey();
TraceObjectValue affected = cast.getAffectedObject();
String key = affected.getEntryKey();
boolean applies = TargetBreakpointSpec.KINDS_ATTRIBUTE_NAME.equals(key) ||
TargetBreakpointSpec.ENABLED_ATTRIBUTE_NAME.equals(key);
if (!applies) {
return null;
}
assert cast.getAffectedObject().getParent() == object;
assert affected.getParent() == object;
if (object.getCanonicalParent(affected.getMaxSnap()) == null) {
return null; // Incomplete object
}
for (TraceObjectBreakpointLocation loc : getLocations()) {
DBTraceObjectBreakpointLocation dbLoc = (DBTraceObjectBreakpointLocation) loc;
TraceAddressSpace space = dbLoc.getTraceAddressSpace();

View file

@ -22,6 +22,7 @@ import com.google.common.collect.Range;
import ghidra.dbg.target.TargetMemoryRegion;
import ghidra.dbg.target.TargetObject;
import ghidra.program.model.address.*;
import ghidra.trace.database.DBTrace;
import ghidra.trace.database.DBTraceUtils;
import ghidra.trace.database.target.DBTraceObject;
import ghidra.trace.database.target.DBTraceObjectInterface;
@ -71,6 +72,27 @@ public class DBTraceObjectMemoryRegion implements TraceObjectMemoryRegion, DBTra
protected TraceChangeType<TraceMemoryRegion, Void> getDeletedType() {
return TraceMemoryRegionChangeType.DELETED;
}
@Override
protected void emitExtraAdded() {
updateViewsAdded();
}
@Override
protected void emitExtraLifespanChanged(Range<Long> oldLifespan, Range<Long> newLifespan) {
updateViewsLifespanChanged(oldLifespan, newLifespan);
}
@Override
protected void emitExtraValueChanged(Range<Long> lifespan, String key, Object oldValue,
Object newValue) {
updateViewsValueChanged(lifespan, key, oldValue, newValue);
}
@Override
protected void emitExtraDeleted() {
updateViewsDeleted();
}
}
private final DBTraceObject object;
@ -96,7 +118,6 @@ public class DBTraceObjectMemoryRegion implements TraceObjectMemoryRegion, DBTra
public void setName(String name) {
try (LockHold hold = object.getTrace().lockWrite()) {
object.setValue(getLifespan(), TargetObject.DISPLAY_ATTRIBUTE_NAME, name);
object.getTrace().updateViewsChangeRegionBlockName(this);
}
}
@ -116,7 +137,6 @@ public class DBTraceObjectMemoryRegion implements TraceObjectMemoryRegion, DBTra
}
TraceObjectInterfaceUtils.setLifespan(TraceObjectMemoryRegion.class, object,
newLifespan);
object.getTrace().updateViewsChangeRegionBlockLifespan(this, oldLifespan, newLifespan);
}
}
@ -157,7 +177,6 @@ public class DBTraceObjectMemoryRegion implements TraceObjectMemoryRegion, DBTra
return;
}
object.setValue(getLifespan(), TargetMemoryRegion.RANGE_ATTRIBUTE_NAME, newRange);
object.getTrace().updateViewsChangeRegionBlockRange(this, oldRange, newRange);
}
}
@ -229,7 +248,6 @@ public class DBTraceObjectMemoryRegion implements TraceObjectMemoryRegion, DBTra
Boolean val = flags.contains(flag) ? true : null;
object.setValue(lifespan, keyForFlag(flag), val);
}
object.getTrace().updateViewsChangeRegionBlockFlags(this, lifespan);
}
}
@ -239,7 +257,6 @@ public class DBTraceObjectMemoryRegion implements TraceObjectMemoryRegion, DBTra
for (TraceMemoryFlag flag : flags) {
object.setValue(lifespan, keyForFlag(flag), true);
}
object.getTrace().updateViewsChangeRegionBlockFlags(this, lifespan);
}
}
@ -249,7 +266,6 @@ public class DBTraceObjectMemoryRegion implements TraceObjectMemoryRegion, DBTra
for (TraceMemoryFlag flag : flags) {
object.setValue(lifespan, keyForFlag(flag), null);
}
object.getTrace().updateViewsChangeRegionBlockFlags(this, lifespan);
}
}
@ -296,7 +312,6 @@ public class DBTraceObjectMemoryRegion implements TraceObjectMemoryRegion, DBTra
public void delete() {
try (LockHold hold = object.getTrace().lockWrite()) {
object.deleteTree();
object.getTrace().updateViewsDeleteRegionBlock(this);
}
}
@ -309,4 +324,35 @@ public class DBTraceObjectMemoryRegion implements TraceObjectMemoryRegion, DBTra
public TraceChangeRecord<?, ?> translateEvent(TraceChangeRecord<?, ?> rec) {
return translator.translate(rec);
}
protected void updateViewsAdded() {
object.getTrace().updateViewsAddRegionBlock(this);
}
protected void updateViewsLifespanChanged(Range<Long> oldLifespan, Range<Long> newLifespan) {
object.getTrace().updateViewsChangeRegionBlockLifespan(this, oldLifespan, newLifespan);
}
protected void updateViewsValueChanged(Range<Long> lifespan, String key, Object oldValue,
Object newValue) {
DBTrace trace = object.getTrace();
switch (key) {
case TargetMemoryRegion.RANGE_ATTRIBUTE_NAME:
trace.updateViewsChangeRegionBlockRange(this,
(AddressRange) oldValue, (AddressRange) newValue);
return;
case TargetObject.DISPLAY_ATTRIBUTE_NAME:
trace.updateViewsChangeRegionBlockName(this);
return;
case TargetMemoryRegion.READABLE_ATTRIBUTE_NAME:
case TargetMemoryRegion.WRITABLE_ATTRIBUTE_NAME:
case TargetMemoryRegion.EXECUTABLE_ATTRIBUTE_NAME:
trace.updateViewsChangeRegionBlockFlags(this, lifespan);
return;
}
}
protected void updateViewsDeleted() {
object.getTrace().updateViewsDeleteRegionBlock(this);
}
}

View file

@ -23,13 +23,15 @@ import ghidra.dbg.target.TargetRegister;
import ghidra.dbg.util.PathUtils;
import ghidra.pcode.utils.Utils;
import ghidra.trace.database.target.DBTraceObject;
import ghidra.trace.database.target.DBTraceObjectInterface;
import ghidra.trace.model.memory.TraceMemoryState;
import ghidra.trace.model.memory.TraceObjectRegister;
import ghidra.trace.model.target.*;
import ghidra.trace.model.target.annot.TraceObjectInterfaceUtils;
import ghidra.trace.model.thread.TraceObjectThread;
import ghidra.trace.util.TraceChangeRecord;
public class DBTraceObjectRegister implements TraceObjectRegister {
public class DBTraceObjectRegister implements TraceObjectRegister, DBTraceObjectInterface {
private final DBTraceObject object;
public DBTraceObjectRegister(DBTraceObject object) {
@ -102,4 +104,10 @@ public class DBTraceObjectRegister implements TraceObjectRegister {
return TraceMemoryState.values()[TraceObjectInterfaceUtils.getValue(object, snap, KEY_STATE,
Integer.class, TraceMemoryState.UNKNOWN.ordinal())];
}
@Override
public TraceChangeRecord<?, ?> translateEvent(TraceChangeRecord<?, ?> rec) {
// TODO: Once we decide how to map registers....
return null;
}
}

View file

@ -107,14 +107,14 @@ public class DBTraceObjectModule implements TraceObjectModule, DBTraceObjectInte
@Override
public void setName(String name) {
try (LockHold hold = object.getTrace().lockWrite()) {
object.setValue(getLifespan(), TargetObject.DISPLAY_ATTRIBUTE_NAME, name);
object.setValue(getLifespan(), TargetModule.MODULE_NAME_ATTRIBUTE_NAME, name);
}
}
@Override
public String getName() {
return TraceObjectInterfaceUtils.getValue(object, getLoadedSnap(),
TargetObject.DISPLAY_ATTRIBUTE_NAME, String.class, "");
TargetModule.MODULE_NAME_ATTRIBUTE_NAME, String.class, "");
}
@Override

View file

@ -24,7 +24,8 @@ import com.google.common.collect.Range;
import ghidra.dbg.target.TargetStackFrame;
import ghidra.dbg.target.schema.TargetObjectSchema;
import ghidra.dbg.util.*;
import ghidra.trace.database.target.*;
import ghidra.trace.database.target.DBTraceObject;
import ghidra.trace.database.target.DBTraceObjectInterface;
import ghidra.trace.model.Trace.TraceStackChangeType;
import ghidra.trace.model.stack.*;
import ghidra.trace.model.target.TraceObject;
@ -52,7 +53,7 @@ public class DBTraceObjectStack implements TraceObjectStack, DBTraceObjectInterf
}
@Override
protected TraceChangeType<TraceStack, Void> getChangedType() {
protected TraceChangeType<TraceStack, ?> getChangedType() {
return TraceStackChangeType.CHANGED;
}
@ -106,7 +107,7 @@ public class DBTraceObjectStack implements TraceObjectStack, DBTraceObjectInterf
try (LockHold hold = object.getTrace().lockWrite()) {
PathMatcher matcher = object.getTargetSchema().searchFor(TargetStackFrame.class, true);
List<String> relKeyList =
matcher.applyIndices(PathUtils.makeIndex(level)).getSingletonPath();
matcher.applyKeys(PathUtils.makeIndex(level)).getSingletonPath();
if (relKeyList == null) {
throw new IllegalStateException("Could not determine where to create new frame");
}
@ -117,8 +118,9 @@ public class DBTraceObjectStack implements TraceObjectStack, DBTraceObjectInterf
}
protected void copyFrameAttributes(TraceObjectStackFrame from, TraceObjectStackFrame to) {
// TODO: All attributes or just those known to StackFrame?
to.setProgramCounter(from.getProgramCounter());
// TODO: All attributes within a given span, intersected to that span?
to.setProgramCounter(to.getObject().getLifespan(),
from.getProgramCounter(from.getObject().getMaxSnap()));
}
protected void shiftFrameAttributes(int from, int to, int count,
@ -141,12 +143,13 @@ public class DBTraceObjectStack implements TraceObjectStack, DBTraceObjectInterf
protected void clearFrameAttributes(int start, int end, List<TraceObjectStackFrame> frames) {
for (int i = start; i < end; i++) {
TraceObjectStackFrame frame = frames.get(i);
frame.setProgramCounter(null);
frame.setProgramCounter(frame.getObject().getLifespan(), null);
}
}
@Override
public void setDepth(int depth, boolean atInner) {
// TODO: Need a span parameter
try (LockHold hold = object.getTrace().lockWrite()) {
List<TraceObjectStackFrame> frames = // Want mutable list
doGetFrames().collect(Collectors.toCollection(ArrayList::new));
@ -179,7 +182,7 @@ public class DBTraceObjectStack implements TraceObjectStack, DBTraceObjectInterf
protected TraceStackFrame doGetFrame(int level) {
TargetObjectSchema schema = object.getTargetSchema();
PathPredicates matcher = schema.searchFor(TargetStackFrame.class, true);
matcher = matcher.applyIndices(PathUtils.makeIndex(level));
matcher = matcher.applyKeys(PathUtils.makeIndex(level));
return object.getSuccessors(object.getLifespan(), matcher)
.findAny()
.map(p -> p.getLastChild(object).queryInterface(TraceObjectStackFrame.class))

View file

@ -17,6 +17,8 @@ package ghidra.trace.database.stack;
import java.util.List;
import com.google.common.collect.Range;
import ghidra.dbg.target.TargetStackFrame;
import ghidra.dbg.util.PathUtils;
import ghidra.program.model.address.Address;
@ -72,23 +74,25 @@ public class DBTraceObjectStackFrame implements TraceObjectStackFrame, DBTraceOb
}
@Override
public Address getProgramCounter() {
return TraceObjectInterfaceUtils.getValue(object, object.getMaxSnap(),
public Address getProgramCounter(long snap) {
return TraceObjectInterfaceUtils.getValue(object, snap,
TargetStackFrame.PC_ATTRIBUTE_NAME, Address.class, null);
}
@Override
public void setProgramCounter(Address pc) {
public void setProgramCounter(Range<Long> span, Address pc) {
try (LockHold hold = object.getTrace().lockWrite()) {
if (pc == Address.NO_ADDRESS) {
pc = null;
}
object.setValue(object.getLifespan(), TargetStackFrame.PC_ATTRIBUTE_NAME, pc);
object.setValue(object.getLifespan().intersection(span),
TargetStackFrame.PC_ATTRIBUTE_NAME, pc);
}
}
@Override
public String getComment() {
// TODO: Do I need to add a snap argument?
// TODO: One day, we'll have dynamic columns in the debugger
/**
* I don't use an attribute for this, because there's not a nice way track the "identity" of
@ -101,7 +105,7 @@ public class DBTraceObjectStackFrame implements TraceObjectStackFrame, DBTraceOb
* follow the "same frame" as its level changes.
*/
try (LockHold hold = object.getTrace().lockRead()) {
Address pc = getProgramCounter();
Address pc = getProgramCounter(object.getMaxSnap());
return pc == null ? null
: object.getTrace()
.getCommentAdapter()
@ -111,11 +115,12 @@ public class DBTraceObjectStackFrame implements TraceObjectStackFrame, DBTraceOb
@Override
public void setComment(String comment) {
// TODO: Do I need to add a span argument?
try (LockHold hold = object.getTrace().lockWrite()) {
object.getTrace()
.getCommentAdapter()
.setComment(object.getLifespan(), getProgramCounter(), CodeUnit.EOL_COMMENT,
comment);
.setComment(object.getLifespan(), getProgramCounter(object.getMaxSnap()),
CodeUnit.EOL_COMMENT, comment);
}
}
@ -124,23 +129,38 @@ public class DBTraceObjectStackFrame implements TraceObjectStackFrame, DBTraceOb
return object;
}
protected boolean isPcChange(TraceChangeRecord<?, ?> rec) {
protected boolean changeApplies(TraceChangeRecord<?, ?> rec) {
TraceChangeRecord<TraceObjectValue, Object> cast =
TraceObjectChangeType.VALUE_CHANGED.cast(rec);
return TargetStackFrame.PC_ATTRIBUTE_NAME.equals(cast.getAffectedObject().getEntryKey());
TraceObjectValue affected = cast.getAffectedObject();
assert affected.getParent() == object;
if (!TargetStackFrame.PC_ATTRIBUTE_NAME.equals(affected.getEntryKey())) {
return false;
}
if (object.getCanonicalParent(affected.getMaxSnap()) == null) {
return false;
}
return true;
}
protected long snapFor(TraceChangeRecord<?, ?> rec) {
if (rec.getEventType() == TraceObjectChangeType.VALUE_CHANGED.getType()) {
return TraceObjectChangeType.VALUE_CHANGED.cast(rec).getAffectedObject().getMinSnap();
}
return object.getMinSnap();
}
@Override
public TraceChangeRecord<?, ?> translateEvent(TraceChangeRecord<?, ?> rec) {
if (rec.getEventType() == TraceObjectChangeType.CREATED.getType() ||
if (rec.getEventType() == TraceObjectChangeType.INSERTED.getType() ||
rec.getEventType() == TraceObjectChangeType.DELETED.getType() ||
rec.getEventType() == TraceObjectChangeType.VALUE_CHANGED.getType() &&
isPcChange(rec)) {
// NB. Affected object may be the wrapped object, or the value entry
changeApplies(rec)) {
TraceAddressSpace space =
spaceForValue(object.getMinSnap(), TargetStackFrame.PC_ATTRIBUTE_NAME);
return new TraceChangeRecord<>(TraceStackChangeType.CHANGED, space, getStack(), null,
null);
TraceObjectStack stack = getStack();
return new TraceChangeRecord<>(TraceStackChangeType.CHANGED, space, stack,
0L, snapFor(rec));
}
return null;
}

View file

@ -212,8 +212,8 @@ public class DBTraceStack extends DBAnnotatedObject implements TraceStack {
}
doUpdateFrameKeys();
}
manager.trace
.setChanged(new TraceChangeRecord<>(TraceStackChangeType.CHANGED, null, this));
manager.trace.setChanged(
new TraceChangeRecord<>(TraceStackChangeType.CHANGED, null, this, 0L, getSnap()));
}
@Override

View file

@ -18,6 +18,8 @@ package ghidra.trace.database.stack;
import java.io.IOException;
import java.util.Objects;
import com.google.common.collect.Range;
import db.DBRecord;
import ghidra.lifecycle.Internal;
import ghidra.program.model.address.Address;
@ -98,12 +100,12 @@ public class DBTraceStackFrame extends DBAnnotatedObject
}
@Override
public Address getProgramCounter() {
public Address getProgramCounter(long snap) {
return pc;
}
@Override
public void setProgramCounter(Address pc) {
public void setProgramCounter(Range<Long> span, Address pc) {
//System.err.println("setPC(threadKey=" + stack.getThread().getKey() + ",snap=" +
// stack.getSnap() + ",level=" + level + ",pc=" + pc + ");");
manager.trace.assertValidAddress(pc);
@ -115,7 +117,8 @@ public class DBTraceStackFrame extends DBAnnotatedObject
update(PC_COLUMN);
}
manager.trace.setChanged(
new TraceChangeRecord<>(TraceStackChangeType.CHANGED, null, stack));
new TraceChangeRecord<>(TraceStackChangeType.CHANGED, null, stack, 0L,
stack.getSnap()));
}
@Override
@ -130,7 +133,8 @@ public class DBTraceStackFrame extends DBAnnotatedObject
update(COMMENT_COLUMN);
}
manager.trace.setChanged(
new TraceChangeRecord<>(TraceStackChangeType.CHANGED, null, stack));
new TraceChangeRecord<>(TraceStackChangeType.CHANGED, null, stack, 0L,
stack.getSnap()));
}
@Internal

View file

@ -205,6 +205,13 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject {
this.lifespan = DBTraceUtils.toRange(minSnap, maxSnap);
}
protected void doSetLifespanAndEmit(Range<Long> lifespan) {
Range<Long> oldLifespan = getLifespan();
doSetLifespan(lifespan);
emitEvents(new TraceChangeRecord<>(TraceObjectChangeType.LIFESPAN_CHANGED, null, this,
oldLifespan, lifespan));
}
@Override
public DBTrace getTrace() {
return manager.trace;
@ -247,6 +254,25 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject {
}
}
@Override
public TraceObjectValue getCanonicalParent(long snap) {
// TODO: If this is invoked often, perhaps keep as field
try (LockHold hold = manager.trace.lockRead()) {
if (isRoot()) {
return manager.valueStore.getObjectAt(0);
}
String canonicalKey = path.key();
TraceObjectKeyPath canonicalTail = path.parent();
return manager.valuesByChild.getLazily(this)
.stream()
.filter(v -> canonicalKey.equals(v.getEntryKey()))
.filter(v -> v.getLifespan().contains(snap))
.filter(v -> canonicalTail.equals(v.getParent().getCanonicalPath()))
.findAny()
.orElse(null);
}
}
@Override
public boolean isRoot() {
try (LockHold hold = manager.trace.lockRead()) {
@ -275,10 +301,7 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject {
public void setLifespan(Range<Long> lifespan) {
// TODO: Could derive fixed attributes from schema and set their lifespans, too....
try (LockHold hold = manager.trace.lockWrite()) {
Range<Long> oldLifespan = getLifespan();
doSetLifespan(lifespan);
emitEvents(new TraceChangeRecord<>(TraceObjectChangeType.LIFESPAN_CHANGED, null, this,
oldLifespan, lifespan));
doSetLifespanAndEmit(lifespan);
}
}
@ -335,7 +358,9 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject {
@Override
public Collection<? extends DBTraceObjectValue> getParents() {
return manager.valuesByChild.get(this);
try (LockHold hold = manager.trace.lockRead()) {
return manager.valuesByChild.get(this);
}
}
protected void collectNonRangedValues(Collection<? super DBTraceObjectValue> result) {
@ -422,6 +447,7 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject {
}
}
// TODO: Could/should this return Stream instead?
protected Collection<? extends InternalTraceObjectValue> doGetValues(Range<Long> span,
String key) {
return doGetValues(DBTraceUtils.lowerEndpoint(span), DBTraceUtils.upperEndpoint(span), key);
@ -638,8 +664,15 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject {
@Override
public Stream<? extends DBTraceObjectValPath> getSuccessors(
Range<Long> span, PathPredicates relativePredicates) {
DBTraceObjectValPath empty = DBTraceObjectValPath.of();
try (LockHold hold = manager.trace.lockRead()) {
return doGetSuccessors(span, DBTraceObjectValPath.of(), relativePredicates);
Stream<? extends DBTraceObjectValPath> succcessors =
doGetSuccessors(span, empty, relativePredicates);
if (relativePredicates.matches(List.of())) {
// Pre-cat the empty path (not the empty stream)
return Stream.concat(Stream.of(empty), succcessors);
}
return succcessors;
}
}
@ -647,7 +680,7 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject {
DBTraceObjectValPath pre, PathPredicates predicates, boolean forward) {
Set<String> nextKeys = predicates.getNextKeys(pre.getKeyList());
if (nextKeys.isEmpty()) {
return null;
return Stream.empty();
}
if (nextKeys.size() != 1) {
throw new IllegalArgumentException("predicates must be a singleton");
@ -663,8 +696,12 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject {
@Override
public Stream<? extends DBTraceObjectValPath> getOrderedSuccessors(Range<Long> span,
TraceObjectKeyPath relativePath, boolean forward) {
DBTraceObjectValPath empty = DBTraceObjectValPath.of();
try (LockHold hold = manager.trace.lockRead()) {
return doGetOrderedSuccessors(span, DBTraceObjectValPath.of(),
if (relativePath.isRoot()) {
return Stream.of(empty); // Not the empty stream
}
return doGetOrderedSuccessors(span, empty,
new PathPattern(relativePath.getKeyList()), forward);
}
}
@ -686,22 +723,22 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject {
if (key == TargetBreakpointLocation.ADDRESS_ATTRIBUTE_NAME &&
value instanceof Address) {
address = (Address) value;
Object lengthObj = getValue(DBTraceUtils.lowerEndpoint(lifespan),
InternalTraceObjectValue lengthObj = getValue(DBTraceUtils.lowerEndpoint(lifespan),
TargetBreakpointLocation.LENGTH_ATTRIBUTE_NAME);
if (!(lengthObj instanceof Integer)) {
if (lengthObj == null || !(lengthObj.getValue() instanceof Integer)) {
return;
}
length = (Integer) lengthObj;
length = (Integer) lengthObj.getValue();
}
else if (key == TargetBreakpointLocation.LENGTH_ATTRIBUTE_NAME &&
value instanceof Integer) {
length = (Integer) value;
Object addressObj = getValue(DBTraceUtils.lowerEndpoint(lifespan),
InternalTraceObjectValue addressObj = getValue(DBTraceUtils.lowerEndpoint(lifespan),
TargetBreakpointLocation.ADDRESS_ATTRIBUTE_NAME);
if (!(addressObj instanceof Address)) {
if (addressObj == null || !(addressObj.getValue() instanceof Address)) {
return;
}
address = (Address) addressObj;
address = (Address) addressObj.getValue();
}
else {
return;
@ -715,6 +752,17 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject {
}
}
protected void emitIfCanonicalInsertion(InternalTraceObjectValue value) {
if (value == null || !(value.getValue() instanceof DBTraceObject)) {
return;
}
DBTraceObject child = (DBTraceObject) value.getValue();
if (this.path.extend(value.getEntryKey()).equals(child.getCanonicalPath())) {
child.emitEvents(
new TraceChangeRecord<>(TraceObjectChangeType.INSERTED, null, child, value));
}
}
@Override
public InternalTraceObjectValue setValue(Range<Long> lifespan, String key, Object value,
ConflictResolution resolution) {
@ -750,6 +798,7 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject {
// NB. It will cause another event. good.
applyBreakpointRangeHack(lifespan, key, value, resolution);
emitIfCanonicalInsertion(result);
return result;
}
}
@ -783,18 +832,23 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject {
}
@Override
public <I extends TraceObjectInterface> Stream<I> queryAncestorsInterface(Range<Long> span,
Class<I> ifClass) {
Class<? extends TargetObject> targetIf = TraceObjectInterfaceUtils.toTargetIf(ifClass);
public Stream<? extends DBTraceObjectValPath> queryAncestorsTargetInterface(Range<Long> span,
Class<? extends TargetObject> targetIf) {
// This is a sort of meet-in-the-middle. The type search must originate from the root
PathMatcher matcher = getManager().getRootSchema().searchFor(targetIf, false);
return getAncestors(span, matcher).map(p -> p.getFirstParent(this).queryInterface(ifClass));
return getAncestors(span, matcher);
}
@Override
public <I extends TraceObjectInterface> Stream<I> queryCanonicalAncestorsInterface(
Range<Long> span, Class<I> ifClass) {
Class<? extends TargetObject> targetIf = TraceObjectInterfaceUtils.toTargetIf(ifClass);
public <I extends TraceObjectInterface> Stream<I> queryAncestorsInterface(Range<Long> span,
Class<I> ifClass) {
return queryAncestorsTargetInterface(span, TraceObjectInterfaceUtils.toTargetIf(ifClass))
.map(p -> p.getFirstParent(this).queryInterface(ifClass));
}
@Override
public Stream<? extends TraceObject> queryCanonicalAncestorsTargetInterface(Range<Long> span,
Class<? extends TargetObject> targetIf) {
// This is a sort of meet-in-the-middle. The type search must originate from the root
PathMatcher matcher = getManager().getRootSchema().searchFor(targetIf, false);
List<String> parentPath = getCanonicalPath().getKeyList();
@ -806,20 +860,35 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject {
return manager.getObjectsByCanonicalPath(TraceObjectKeyPath.of(parentPath))
.stream()
.filter(o -> DBTraceUtils.intersect(span, o.getLifespan()))
.map(o -> o.queryInterface(ifClass))
.filter(i -> i != null);
// TODO: Post filter until GP-1301
.filter(o -> o.getTargetSchema().getInterfaces().contains(targetIf));
}
}
return Stream.of();
}
@Override
public <I extends TraceObjectInterface> Stream<I> queryCanonicalAncestorsInterface(
Range<Long> span, Class<I> ifClass) {
return queryCanonicalAncestorsTargetInterface(span,
TraceObjectInterfaceUtils.toTargetIf(ifClass))
.map(o -> o.queryInterface(ifClass));
}
@Override
public Stream<? extends DBTraceObjectValPath> querySuccessorsTargetInterface(Range<Long> span,
Class<? extends TargetObject> targetIf) {
PathMatcher matcher = getTargetSchema().searchFor(targetIf, true);
// TODO: Post filter until GP-1301
return getSuccessors(span, matcher).filter(
p -> p.getLastChild(this).getTargetSchema().getInterfaces().contains(targetIf));
}
@Override
public <I extends TraceObjectInterface> Stream<I> querySuccessorsInterface(Range<Long> span,
Class<I> ifClass) {
Class<? extends TargetObject> targetIf = TraceObjectInterfaceUtils.toTargetIf(ifClass);
PathMatcher matcher = getTargetSchema().searchFor(targetIf, true);
return getSuccessors(span, matcher).map(p -> p.getLastChild(this).queryInterface(ifClass))
.filter(i -> i != null); // because GP-1301
return querySuccessorsTargetInterface(span, TraceObjectInterfaceUtils.toTargetIf(ifClass))
.map(p -> p.getLastChild(this).queryInterface(ifClass));
}
protected void doDelete() {
@ -869,13 +938,14 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject {
try (LockHold hold = LockHold.lock(manager.lock.writeLock())) {
List<Range<Long>> removed = DBTraceUtils.subtract(lifespan, span);
if (removed.isEmpty()) {
doDeleteReferringValues();
doDelete();
return null;
}
if (removed.size() == 2) {
throw new IllegalArgumentException("Cannot create a gap in an object's lifespan");
}
doSetLifespan(removed.get(0));
doSetLifespanAndEmit(removed.get(0));
return this;
}
}

View file

@ -40,21 +40,39 @@ public interface DBTraceObjectInterface extends TraceObjectInterface, TraceUniqu
protected abstract TraceChangeType<T, Range<Long>> getLifespanChangedType();
protected abstract TraceChangeType<T, Void> getChangedType();
protected abstract TraceChangeType<T, ?> getChangedType();
protected abstract boolean appliesToKey(String key);
protected abstract TraceChangeType<T, Void> getDeletedType();
protected void emitExtraAdded() {
// Extension point
}
protected void emitExtraLifespanChanged(Range<Long> oldLifespan, Range<Long> newLifespan) {
// Extension point
}
protected void emitExtraValueChanged(Range<Long> lifespan, String key, Object oldValue,
Object newValue) {
// Extension point
}
protected void emitExtraDeleted() {
// Extension point
}
public TraceChangeRecord<?, ?> translate(TraceChangeRecord<?, ?> rec) {
TraceAddressSpace space = spaceValueKey == null ? null
: spaceForValue(object, object.getMinSnap(), spaceValueKey);
if (rec.getEventType() == TraceObjectChangeType.CREATED.getType()) {
if (rec.getEventType() == TraceObjectChangeType.INSERTED.getType()) {
TraceChangeType<T, Void> type = getAddedType();
if (type == null) {
return null;
}
assert rec.getAffectedObject() == object;
emitExtraAdded();
return new TraceChangeRecord<>(type, space, iface, null,
null);
}
@ -69,6 +87,7 @@ public interface DBTraceObjectInterface extends TraceObjectInterface, TraceUniqu
assert rec.getAffectedObject() == object;
TraceChangeRecord<TraceObject, Range<Long>> cast =
TraceObjectChangeType.LIFESPAN_CHANGED.cast(rec);
emitExtraLifespanChanged(cast.getOldValue(), cast.getNewValue());
return new TraceChangeRecord<>(type, space, iface,
cast.getOldValue(), cast.getNewValue());
}
@ -76,17 +95,23 @@ public interface DBTraceObjectInterface extends TraceObjectInterface, TraceUniqu
if (object.isDeleted()) {
return null;
}
TraceChangeType<T, Void> type = getChangedType();
TraceChangeType<T, ?> type = getChangedType();
if (type == null) {
return null;
}
TraceChangeRecord<TraceObjectValue, Object> cast =
TraceObjectChangeType.VALUE_CHANGED.cast(rec);
String key = cast.getAffectedObject().getEntryKey();
TraceObjectValue affected = cast.getAffectedObject();
String key = affected.getEntryKey();
if (!appliesToKey(key)) {
return null;
}
assert cast.getAffectedObject().getParent() == object;
assert affected.getParent() == object;
if (object.getCanonicalParent(affected.getMaxSnap()) == null) {
return null; // Object is not complete
}
emitExtraValueChanged(affected.getLifespan(), key, cast.getOldValue(),
cast.getNewValue());
return new TraceChangeRecord<>(type, space, iface, null, null);
}
if (rec.getEventType() == TraceObjectChangeType.DELETED.getType()) {
@ -95,6 +120,7 @@ public interface DBTraceObjectInterface extends TraceObjectInterface, TraceUniqu
return null;
}
assert rec.getAffectedObject() == object;
emitExtraDeleted();
return new TraceChangeRecord<>(type, space, iface, null, null);
}
return null;

View file

@ -43,7 +43,6 @@ import ghidra.trace.database.map.DBTraceAddressSnapRangePropertyMapTree.TraceAdd
import ghidra.trace.database.module.TraceObjectSection;
import ghidra.trace.database.target.DBTraceObjectValue.PrimaryTriple;
import ghidra.trace.database.thread.DBTraceObjectThread;
import ghidra.trace.database.thread.DBTraceThreadManager;
import ghidra.trace.model.ImmutableTraceAddressSnapRange;
import ghidra.trace.model.Trace;
import ghidra.trace.model.Trace.TraceObjectChangeType;
@ -61,6 +60,7 @@ import ghidra.trace.model.thread.TraceObjectThread;
import ghidra.trace.model.thread.TraceThread;
import ghidra.trace.util.TraceChangeRecord;
import ghidra.util.LockHold;
import ghidra.util.Msg;
import ghidra.util.database.*;
import ghidra.util.database.DBCachedObjectStoreFactory.AbstractDBFieldCodec;
import ghidra.util.database.DBCachedObjectStoreFactory.PrimitiveCodec;
@ -235,7 +235,12 @@ public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager
}
protected Object validatePrimitive(Object child) {
PrimitiveCodec.getCodec(child.getClass());
try {
PrimitiveCodec.getCodec(child.getClass());
}
catch (IllegalArgumentException e) {
throw new IllegalArgumentException("Cannot encode " + child, e);
}
return child;
}
@ -300,7 +305,7 @@ public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager
setSchema(schema);
DBTraceObject root = doCreateObject(TraceObjectKeyPath.of(), Range.all());
assert root.getKey() == 0;
InternalTraceObjectValue val = doCreateValue(Range.all(), null, null, root);
InternalTraceObjectValue val = doCreateValue(Range.all(), null, "", root);
assert val.getKey() == 0;
return val;
}
@ -378,6 +383,14 @@ public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager
Class<? extends TargetObject> targetIf = TraceObjectInterfaceUtils.toTargetIf(ifClass);
PathMatcher matcher = rootSchema.searchFor(targetIf, true);
return getValuePaths(span, matcher)
.filter(p -> {
TraceObject object = p.getLastChild(getRootObject());
if (object == null) {
Msg.error(this, "NULL VALUE! " + p.getLastEntry());
return false;
}
return true;
})
.map(p -> p.getLastChild(getRootObject()).queryInterface(ifClass));
}
@ -561,15 +574,9 @@ public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager
try (LockHold hold = trace.lockWrite()) {
TraceObjectMemoryRegion region = doAddWithInterface(path, lifespan,
TraceObjectMemoryRegion.class, ConflictResolution.TRUNCATE);
/**
* TODO: Test that when the ADDED events hits, that it actually appears in queries. I
* suspect this will work since the events and/or event processors should be delayed
* until the write lock is released. Certainly, a query would require the read lock.
*/
region.setName(path);
region.setRange(range);
region.setFlags(flags);
trace.updateViewsAddRegionBlock(region);
return region;
}
}

View file

@ -19,6 +19,7 @@ import java.io.IOException;
import java.lang.reflect.Field;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.util.Objects;
import java.util.stream.Stream;
import org.apache.commons.lang3.ArrayUtils;
@ -48,7 +49,7 @@ public class DBTraceObjectValue extends DBAnnotatedObject implements InternalTra
protected PrimaryTriple(DBTraceObject parent, String key, long minSnap) {
this.parent = parent;
this.key = key;
this.key = Objects.requireNonNull(key);
this.minSnap = minSnap;
}
@ -75,19 +76,15 @@ public class DBTraceObjectValue extends DBAnnotatedObject implements InternalTra
if (value == null) {
return null;
}
if (value.key == null) {
ByteBuffer buf = ByteBuffer.allocate(Long.BYTES * 2);
buf.putLong(DBTraceObjectDBFieldCodec.encode(value.parent) ^ Long.MIN_VALUE);
buf.putLong(value.minSnap ^ Long.MIN_VALUE);
return buf.array();
}
byte[] keyBytes = value.key.getBytes(cs);
ByteBuffer buf = ByteBuffer.allocate(keyBytes.length + 1 + Long.BYTES * 2);
buf.putLong(DBTraceObjectDBFieldCodec.encode(value.parent) ^ Long.MIN_VALUE);
buf.put(keyBytes);
buf.put((byte) 0);
buf.putLong(value.minSnap ^ Long.MIN_VALUE);
return buf.array();
@ -101,16 +98,12 @@ public class DBTraceObjectValue extends DBAnnotatedObject implements InternalTra
DBTraceObject parent =
DBTraceObjectDBFieldCodec.decode(ent, buf.getLong() ^ Long.MIN_VALUE);
String key;
if (enc.length > Long.BYTES * 2) {
int nullPos = ArrayUtils.indexOf(enc, (byte) 0, buf.position());
assert nullPos != -1;
key = new String(enc, buf.position(), nullPos - buf.position(), cs);
buf.position(nullPos + 1);
}
else {
key = null;
}
int nullPos = ArrayUtils.indexOf(enc, (byte) 0, buf.position());
assert nullPos != -1;
String key = new String(enc, buf.position(), nullPos - buf.position(), cs);
buf.position(nullPos + 1);
long minSnap = buf.getLong() ^ Long.MIN_VALUE;
return new PrimaryTriple(parent, key, minSnap);
@ -186,14 +179,17 @@ public class DBTraceObjectValue extends DBAnnotatedObject implements InternalTra
indexed = true,
codec = PrimaryTripleDBFieldCodec.class)
private PrimaryTriple triple;
@DBAnnotatedField(column = MAX_SNAP_COLUMN_NAME)
@DBAnnotatedField(
column = MAX_SNAP_COLUMN_NAME)
private long maxSnap;
@DBAnnotatedField(
column = CHILD_COLUMN_NAME,
indexed = true,
codec = DBTraceObjectDBFieldCodec.class)
private DBTraceObject child;
@DBAnnotatedField(column = PRIMITIVE_COLUMN_NAME, codec = VariantDBFieldCodec.class)
@DBAnnotatedField(
column = PRIMITIVE_COLUMN_NAME,
codec = VariantDBFieldCodec.class)
private Object primitive;
protected final DBTraceObjectManager manager;
@ -260,12 +256,12 @@ public class DBTraceObjectValue extends DBAnnotatedObject implements InternalTra
@Override
public DBTraceObject getParent() {
return triple.parent;
return triple == null ? null : triple.parent;
}
@Override
public String getEntryKey() {
return triple.key;
return triple == null ? null : triple.key;
}
@Override

View file

@ -71,10 +71,13 @@ interface InternalTraceObjectValue extends TraceObjectValue {
}
}
else {
entry.doTruncateOrDelete(range);
InternalTraceObjectValue created = entry.doTruncateOrDelete(range);
if (!entry.isDeleted()) {
kept.add(entry);
}
if (created != null) {
kept.add(created);
}
}
}

View file

@ -57,10 +57,23 @@ public interface Trace extends DataTypeManagerDomainObject {
public static final class TraceObjectChangeType<T, U>
extends DefaultTraceChangeType<T, U> {
/**
* An object was created, but not necessarily inserted.
* An object was created, but not yet inserted.
*
* <p>
* Between the {@link #CREATED} and {@link #INSERTED} events, an object is considered
* "incomplete," because it is likely missing its attributes. Thus, a trace client must take
* care to ensure all attributes, especially fixed attributes, are added to the object
* before it is inserted at its canonical path. Listeners may use
* {@link TraceObject#getCanonicalParent(long)} to check if an object is complete for a
* given snapshot.
*/
public static final TraceObjectChangeType<TraceObject, Void> CREATED =
new TraceObjectChangeType<>();
/**
* An object was inserted at its canonical path.
*/
public static final TraceObjectChangeType<TraceObject, TraceObjectValue> INSERTED =
new TraceObjectChangeType<>();
/**
* An object's lifespan changed.
*/
@ -75,9 +88,9 @@ public interface Trace extends DataTypeManagerDomainObject {
* An object's value changed.
*
* <p>
* If the old value is uniform for the new value's lifespan, that value is passed as the old
* value. Otherwise, {@code null} is passed for the old value. If the value was cleared,
* {@code null} is passed for the new value.
* If the old value was equal for the entirety of the new value's lifespan, that old value
* is passed as the old value. Otherwise, {@code null} is passed for the old value. If the
* value was cleared, {@code null} is passed for the new value.
*/
public static final TraceObjectChangeType<TraceObjectValue, Object> VALUE_CHANGED =
new TraceObjectChangeType<>();
@ -307,7 +320,12 @@ public interface Trace extends DataTypeManagerDomainObject {
public static final class TraceStackChangeType<U>
extends DefaultTraceChangeType<TraceStack, U> {
public static final TraceStackChangeType<Void> ADDED = new TraceStackChangeType<>();
public static final TraceStackChangeType<Void> CHANGED = new TraceStackChangeType<>();
/**
* NOTE: The "new value" is the (min) snap where the change occurred. For StackFrame, it's
* Stack.getSnap(), for ObjectStackFrame, the min snap of the value entry. The "old value"
* is always 0.
*/
public static final TraceStackChangeType<Long> CHANGED = new TraceStackChangeType<>();
public static final TraceStackChangeType<Void> DELETED = new TraceStackChangeType<>();
}

View file

@ -15,6 +15,8 @@
*/
package ghidra.trace.model.stack;
import com.google.common.collect.Range;
import ghidra.program.model.address.Address;
public interface TraceStackFrame {
@ -22,9 +24,9 @@ public interface TraceStackFrame {
int getLevel();
Address getProgramCounter();
Address getProgramCounter(long snap);
void setProgramCounter(Address pc);
void setProgramCounter(Range<Long> span, Address pc);
String getComment();

View file

@ -72,6 +72,20 @@ public interface TraceObject extends TraceUniqueObject {
*/
void insert(ConflictResolution resolution);
/**
* Get the parent value along this object's canonical path for a given snapshot
*
* <p>
* To be the canonical parent value at a given snapshot, three things must be true: 1) The
* parent object must have this object's path with the last key removed. 2) The parent value's
* entry key must be equal to the final key of this object's path. 3) The value's lifespan must
* contain the given snapshot. If no value satisfies these, null is returned.
*
* @param snap the snapshot key
* @return the canonical parent value, or null
*/
TraceObjectValue getCanonicalParent(long snap);
/**
* Check if this object is the root
*
@ -309,10 +323,15 @@ public interface TraceObject extends TraceUniqueObject {
/**
* Set a value for the given lifespan, truncating existing entries
*
* <p>
* Setting a value of {@code null} effectively deletes the value for the given lifespan and
* returns {@code null}. Values of the same key intersecting the given lifespan or either
* truncated or deleted.
*
* @param lifespan the lifespan of the value
* @param key the key to set
* @param value the new value
* @return the created value entry
* @return the created value entry, or null
*/
TraceObjectValue setValue(Range<Long> lifespan, String key, Object value);
@ -361,6 +380,16 @@ public interface TraceObject extends TraceUniqueObject {
*/
TargetObjectSchema getTargetSchema();
/**
* Search for ancestors having the given target interface
*
* @param span the span which the found objects must intersect
* @param targetIf the interface class
* @return the stream of found paths to values
*/
Stream<? extends TraceObjectValPath> queryAncestorsTargetInterface(Range<Long> span,
Class<? extends TargetObject> targetIf);
/**
* Search for ancestors providing the given interface and retrieve those interfaces
*
@ -372,6 +401,19 @@ public interface TraceObject extends TraceUniqueObject {
<I extends TraceObjectInterface> Stream<I> queryAncestorsInterface(Range<Long> span,
Class<I> ifClass);
/**
* Search for ancestors on the canonical path having the given target interface
*
* <p>
* The object may not yet be inserted at its canonical path
*
* @param span the span which the found objects must intersect
* @param targetIf the interface class
* @return the stream of objects
*/
Stream<? extends TraceObject> queryCanonicalAncestorsTargetInterface(Range<Long> span,
Class<? extends TargetObject> targetIf);
/**
* Search for ancestors on the canonical path providing the given interface
*
@ -386,6 +428,9 @@ public interface TraceObject extends TraceUniqueObject {
<I extends TraceObjectInterface> Stream<I> queryCanonicalAncestorsInterface(
Range<Long> span, Class<I> ifClass);
Stream<? extends TraceObjectValPath> querySuccessorsTargetInterface(Range<Long> span,
Class<? extends TargetObject> targetIf);
/**
* Search for successors providing the given interface and retrieve those interfaces
*

View file

@ -28,7 +28,8 @@ import ghidra.trace.model.Trace;
import ghidra.trace.model.Trace.TraceSnapshotChangeType;
import ghidra.trace.model.TraceDomainObjectListener;
import ghidra.trace.model.program.TraceProgramView;
import ghidra.trace.model.time.*;
import ghidra.trace.model.time.TraceSnapshot;
import ghidra.trace.model.time.TraceTimeManager;
import ghidra.trace.model.time.schedule.TraceSchedule;
import ghidra.util.*;
import ghidra.util.datastruct.ListenerSet;

View file

@ -143,7 +143,7 @@ public class TracePcodeEmulatorTest extends AbstractGhidraHeadlessIntegrationTes
tb.trace.getStackManager()
.getStack(thread, 1, false)
.getFrame(0, false)
.getProgramCounter());
.getProgramCounter(1));
}
}

View file

@ -24,6 +24,8 @@ import java.util.stream.StreamSupport;
import org.junit.*;
import com.google.common.collect.Range;
import ghidra.test.AbstractGhidraHeadlessIntegrationTest;
import ghidra.trace.database.ToyDBTraceBuilder;
import ghidra.trace.model.stack.TraceStack;
@ -137,13 +139,13 @@ public class DBTraceStackManagerTest extends AbstractGhidraHeadlessIntegrationTe
TraceStack stack1 = stackManager.getStack(thread, 0, true);
stack1.setDepth(2, true);
(frame1a = stack1.getFrame(0, false)).setProgramCounter(b.addr(0x0040100));
(frame1b = stack1.getFrame(1, false)).setProgramCounter(b.addr(0x0040300));
(frame1a = stack1.getFrame(0, false)).setProgramCounter(Range.all(), b.addr(0x0040100));
(frame1b = stack1.getFrame(1, false)).setProgramCounter(Range.all(), b.addr(0x0040300));
TraceStack stack2 = stackManager.getStack(thread, 1, true);
stack2.setDepth(2, true);
(frame2a = stack2.getFrame(0, false)).setProgramCounter(b.addr(0x0040200));
(frame2b = stack2.getFrame(1, false)).setProgramCounter(b.addr(0x0040400));
(frame2a = stack2.getFrame(0, false)).setProgramCounter(Range.all(), b.addr(0x0040200));
(frame2b = stack2.getFrame(1, false)).setProgramCounter(Range.all(), b.addr(0x0040400));
}
assertEquals(Set.of(frame1a, frame2a, frame1b, frame2b), toSet(stackManager
@ -271,11 +273,11 @@ public class DBTraceStackManagerTest extends AbstractGhidraHeadlessIntegrationTe
stack.setDepth(1, true);
frame = stack.getFrame(0, false);
assertNull(frame.getProgramCounter());
frame.setProgramCounter(b.addr(0x00400123));
assertNull(frame.getProgramCounter(Long.MAX_VALUE));
frame.setProgramCounter(Range.all(), b.addr(0x00400123));
}
assertEquals(b.addr(0x00400123), frame.getProgramCounter());
assertEquals(b.addr(0x00400123), frame.getProgramCounter(0));
}
@Test
@ -289,7 +291,7 @@ public class DBTraceStackManagerTest extends AbstractGhidraHeadlessIntegrationTe
stack.setDepth(1, true);
frame = stack.getFrame(0, false);
// NB. Object-mode sets comment at pc in listing, not on frame itself
frame.setProgramCounter(b.addr(0x00400123));
frame.setProgramCounter(Range.all(), b.addr(0x00400123));
assertNull(frame.getComment());
frame.setComment("Hello, World!");

View file

@ -507,6 +507,8 @@ public class DBTraceObjectManagerTest extends AbstractGhidraHeadlessIntegrationT
public void testGetSuccessors() {
populateModel(3);
assertEquals(1, root.getSuccessors(Range.all(), PathPredicates.parse("")).count());
assertEquals(1, root.getSuccessors(Range.all(), PathPredicates.parse("Targets")).count());
assertEquals(1,
@ -531,6 +533,29 @@ public class DBTraceObjectManagerTest extends AbstractGhidraHeadlessIntegrationT
root.getSuccessors(Range.all(), PathPredicates.parse("anAttribute.nope")).count());
}
@Test
public void testGetOrderedSuccessors() {
populateModel(3);
assertEquals(List.of(root),
root.getOrderedSuccessors(Range.all(), TraceObjectKeyPath.parse(""), true)
.map(p -> p.getLastChild(root))
.collect(Collectors.toList()));
assertEquals(List.of(root),
root.getOrderedSuccessors(Range.all(), TraceObjectKeyPath.parse(""), false)
.map(p -> p.getLastChild(root))
.collect(Collectors.toList()));
assertEquals(List.of(targets.get(0), targets.get(1), targets.get(2)),
root.getOrderedSuccessors(Range.all(), TraceObjectKeyPath.parse("curTarget"), true)
.map(p -> p.getLastChild(root))
.collect(Collectors.toList()));
assertEquals(List.of(targets.get(2), targets.get(1), targets.get(0)),
root.getOrderedSuccessors(Range.all(), TraceObjectKeyPath.parse("curTarget"), false)
.map(p -> p.getLastChild(root))
.collect(Collectors.toList()));
}
@Test
public void testSetValue_TruncatesOrDeletes() {
try (UndoableTransaction tid = b.startTransaction()) {
@ -794,6 +819,38 @@ public class DBTraceObjectManagerTest extends AbstractGhidraHeadlessIntegrationT
}
}
@Test
public void testSetValue_NullContainedTruncates() {
try (UndoableTransaction tid = b.startTransaction()) {
root = manager.createRootObject(ctx.getSchema(new SchemaName("Session"))).getChild();
assertNull(root.setValue(Range.closed(0L, 9L), "a", null));
assertEquals(0, root.getValues().size());
assertNotNull(root.setValue(Range.closed(0L, 9L), "a", 1));
assertEquals(1, root.getValues().size());
assertNull(root.setValue(Range.singleton(5L), "a", null));
assertEquals(2, root.getValues().size());
assertEquals(List.of(Range.closed(0L, 4L), Range.closed(6L, 9L)),
root.getOrderedValues(Range.all(), "a", true)
.map(v -> v.getLifespan())
.collect(Collectors.toList()));
}
}
@Test
public void testSetValue_NullSameDeletes() {
try (UndoableTransaction tid = b.startTransaction()) {
root = manager.createRootObject(ctx.getSchema(new SchemaName("Session"))).getChild();
assertNotNull(root.setValue(Range.closed(0L, 9L), "a", 1));
assertEquals(1, root.getValues().size());
assertNull(root.setValue(Range.closed(0L, 9L), "a", null));
assertEquals(0, root.getValues().size());
}
}
@Test
public void testSetAttribute() {
try (UndoableTransaction tid = b.startTransaction()) {
@ -815,7 +872,7 @@ public class DBTraceObjectManagerTest extends AbstractGhidraHeadlessIntegrationT
}
@Test
public void testObjectDelete() {
public void testObjectDelete() throws Exception {
populateModel(3);
// Delete a leaf
@ -844,6 +901,12 @@ public class DBTraceObjectManagerTest extends AbstractGhidraHeadlessIntegrationT
assertEquals(2,
manager.getObjectsByPath(Range.all(), TraceObjectKeyPath.parse("Targets[]")).count());
assertTrue(t0.getParents().stream().anyMatch(v -> v.getParent() == targetContainer));
assertEquals(2, targetContainer.getValues().size());
b.trace.undo();
b.trace.redo();
assertEquals(2, targetContainer.getValues().size());
try (UndoableTransaction tid = b.startTransaction()) {
targetContainer.delete();
@ -876,7 +939,7 @@ public class DBTraceObjectManagerTest extends AbstractGhidraHeadlessIntegrationT
}
@Test
public void testObjectTruncateOrDelete() {
public void testRootObjectTruncateOrDelete() {
try (UndoableTransaction tid = b.startTransaction()) {
root = manager.createRootObject(ctx.getSchema(new SchemaName("Session"))).getChild();