mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-03 09:49:23 +02:00
GP-2068: Indexing, caching, other optimizations for TraceRmi.
This commit is contained in:
parent
9c6eabfbb3
commit
a41c4ca5f7
63 changed files with 3529 additions and 1167 deletions
|
@ -98,39 +98,39 @@ public class DBTraceTimeViewport implements TraceTimeViewport {
|
|||
if (range == null) {
|
||||
return false;
|
||||
}
|
||||
try (LockHold hold = trace.lockRead()) {
|
||||
synchronized (ordered) {
|
||||
for (Lifespan rng : ordered) {
|
||||
if (lifespan.contains(rng.lmax())) {
|
||||
return true;
|
||||
}
|
||||
if (occlusion.occluded(object, range, rng)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* NB. Because occlusion.occluded can be expensive, we should only keep the lock long enough
|
||||
* to copy the spans.
|
||||
*/
|
||||
for (Lifespan rng : getOrderedSpans()) {
|
||||
if (lifespan.contains(rng.lmax())) {
|
||||
return true;
|
||||
}
|
||||
if (occlusion.occluded(object, range, rng)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> AddressSet computeVisibleParts(AddressSetView set, Lifespan lifespan, T object,
|
||||
Occlusion<T> occlusion) {
|
||||
List<Lifespan> spans;
|
||||
try (LockHold hold = trace.lockRead()) {
|
||||
if (!containsAnyUpper(lifespan)) {
|
||||
return new AddressSet();
|
||||
}
|
||||
AddressSet remains = new AddressSet(set);
|
||||
synchronized (ordered) {
|
||||
for (Lifespan rng : ordered) {
|
||||
if (lifespan.contains(rng.lmax())) {
|
||||
return remains;
|
||||
}
|
||||
occlusion.remove(object, remains, rng);
|
||||
if (remains.isEmpty()) {
|
||||
return remains;
|
||||
}
|
||||
}
|
||||
spans = getOrderedSpans();
|
||||
}
|
||||
AddressSet remains = new AddressSet(set);
|
||||
for (Lifespan rng : spans) {
|
||||
if (lifespan.contains(rng.lmax())) {
|
||||
return remains;
|
||||
}
|
||||
occlusion.remove(object, remains, rng);
|
||||
if (remains.isEmpty()) {
|
||||
return remains;
|
||||
}
|
||||
}
|
||||
// This condition should have been detected by !containsAnyUpper
|
||||
|
@ -308,73 +308,62 @@ public class DBTraceTimeViewport implements TraceTimeViewport {
|
|||
|
||||
@Override
|
||||
public List<Long> getOrderedSnaps() {
|
||||
ArrayList<Long> result = new ArrayList<>();
|
||||
try (LockHold hold = trace.lockRead()) {
|
||||
synchronized (ordered) {
|
||||
return ordered
|
||||
.stream()
|
||||
.map(Lifespan::lmax)
|
||||
.collect(Collectors.toList());
|
||||
for (Lifespan span : ordered) {
|
||||
result.add(span.lmax());
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Long> getReversedSnaps() {
|
||||
ArrayList<Long> result = new ArrayList<>();
|
||||
try (LockHold hold = trace.lockRead()) {
|
||||
synchronized (ordered) {
|
||||
List<Long> reversed =
|
||||
ordered.stream().map(Lifespan::lmax).collect(Collectors.toList());
|
||||
Collections.reverse(reversed);
|
||||
return reversed;
|
||||
ListIterator<Lifespan> it = ordered.listIterator(ordered.size());
|
||||
while (it.hasPrevious()) {
|
||||
result.add(it.previous().lmax());
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T getTop(Function<Long, T> func) {
|
||||
try (LockHold hold = trace.lockRead()) {
|
||||
synchronized (ordered) {
|
||||
for (Lifespan rng : ordered) {
|
||||
T t = func.apply(rng.lmax());
|
||||
if (t != null) {
|
||||
return t;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
for (Lifespan rng : getOrderedSpans()) {
|
||||
T t = func.apply(rng.lmax());
|
||||
if (t != null) {
|
||||
return t;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Iterator<T> mergedIterator(Function<Long, Iterator<T>> iterFunc,
|
||||
Comparator<? super T> comparator) {
|
||||
List<Iterator<T>> iters;
|
||||
try (LockHold hold = trace.lockRead()) {
|
||||
synchronized (ordered) {
|
||||
if (!isForked()) {
|
||||
return iterFunc.apply(snap);
|
||||
}
|
||||
iters = ordered.stream()
|
||||
.map(rng -> iterFunc.apply(rng.lmax()))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
if (!isForked()) {
|
||||
return iterFunc.apply(snap);
|
||||
}
|
||||
List<Iterator<T>> iters = getOrderedSpans().stream()
|
||||
.map(rng -> iterFunc.apply(rng.lmax()))
|
||||
.collect(Collectors.toList());
|
||||
return new UniqIterator<>(new MergeSortingIterator<>(iters, comparator));
|
||||
}
|
||||
|
||||
@Override
|
||||
public AddressSetView unionedAddresses(Function<Long, AddressSetView> viewFunc) {
|
||||
List<AddressSetView> views;
|
||||
try (LockHold hold = trace.lockRead()) {
|
||||
synchronized (ordered) {
|
||||
if (!isForked()) {
|
||||
return viewFunc.apply(snap);
|
||||
}
|
||||
views = ordered.stream()
|
||||
.map(rng -> viewFunc.apply(rng.lmax()))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
if (!isForked()) {
|
||||
return viewFunc.apply(snap);
|
||||
}
|
||||
List<AddressSetView> views = getOrderedSpans().stream()
|
||||
.map(rng -> viewFunc.apply(rng.lmax()))
|
||||
.collect(Collectors.toList());
|
||||
return new UnionAddressSetView(views);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -118,7 +118,7 @@ public class DBTraceAddressSnapRangePropertyMapTree<T, DR extends AbstractDBTrac
|
|||
|
||||
@Override
|
||||
protected NodeType getType() {
|
||||
return NodeType.values()[(typeAndChildCount >> NODE_TYPE_SHIFT) & NODE_TYPE_MASK];
|
||||
return NodeType.VALUES.get((typeAndChildCount >> NODE_TYPE_SHIFT) & NODE_TYPE_MASK);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -26,12 +26,29 @@ import ghidra.util.LockHold;
|
|||
import ghidra.util.datastruct.WeakValueHashMap;
|
||||
|
||||
public class DBTraceProgramViewMemory extends AbstractDBTraceProgramViewMemory {
|
||||
// I think size should be about how many instructions may appear on screen at once.
|
||||
// Double for good measure (in case windows are cloned, maximized, etc.)
|
||||
private static final int REGION_CACHE_BY_ADDRESS_SIZE = 300;
|
||||
|
||||
// Size should be about how many distinct regions are involved in displayed instructions
|
||||
// Probably only about 5, but cost of 30 is still small.
|
||||
private static final int REGION_CACHE_BY_NAME_SIZE = 30;
|
||||
|
||||
// NB. Keep both per-region and force-full (per-space) block sets ready
|
||||
private final Map<TraceMemoryRegion, DBTraceProgramViewMemoryRegionBlock> regionBlocks =
|
||||
new WeakValueHashMap<>();
|
||||
private final Map<AddressSpace, DBTraceProgramViewMemorySpaceBlock> spaceBlocks =
|
||||
new WeakValueHashMap<>();
|
||||
private final Map<Address, TraceMemoryRegion> regionCacheByAddress = new LinkedHashMap<>() {
|
||||
protected boolean removeEldestEntry(Map.Entry<Address, TraceMemoryRegion> eldest) {
|
||||
return this.size() > REGION_CACHE_BY_ADDRESS_SIZE;
|
||||
}
|
||||
};
|
||||
private final Map<String, TraceMemoryRegion> regionCacheByName = new LinkedHashMap<>() {
|
||||
protected boolean removeEldestEntry(Map.Entry<String, TraceMemoryRegion> eldest) {
|
||||
return this.size() > REGION_CACHE_BY_NAME_SIZE;
|
||||
}
|
||||
};
|
||||
|
||||
public DBTraceProgramViewMemory(DBTraceProgramView program) {
|
||||
super(program);
|
||||
|
@ -90,8 +107,24 @@ public class DBTraceProgramViewMemory extends AbstractDBTraceProgramViewMemory {
|
|||
if (forceFullView) {
|
||||
return getSpaceBlock(addr.getAddressSpace());
|
||||
}
|
||||
TraceMemoryRegion region = getTopRegion(s -> memoryManager.getRegionContaining(s, addr));
|
||||
return region == null ? null : getRegionBlock(region);
|
||||
TraceMemoryRegion region = regionCacheByAddress.get(addr);
|
||||
if (region != null && !region.isDeleted()) {
|
||||
/**
|
||||
* TODO: This is assuming: 1) We never fork in non-scratch space. 2) Regions are not
|
||||
* created in scratch space. These are convention, but weren't originally intended to be
|
||||
* rules. This makes them rules.
|
||||
*/
|
||||
long s = program.viewport.getReversedSnaps().get(0);
|
||||
if (region.getLifespan().contains(s)) {
|
||||
return getRegionBlock(region);
|
||||
}
|
||||
}
|
||||
region = getTopRegion(s -> memoryManager.getRegionContaining(s, addr));
|
||||
if (region != null) {
|
||||
regionCacheByAddress.put(addr, region);
|
||||
return getRegionBlock(region);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -100,9 +133,19 @@ public class DBTraceProgramViewMemory extends AbstractDBTraceProgramViewMemory {
|
|||
AddressSpace space = program.getAddressFactory().getAddressSpace(blockName);
|
||||
return space == null ? null : getSpaceBlock(space);
|
||||
}
|
||||
TraceMemoryRegion region =
|
||||
getTopRegion(s -> memoryManager.getLiveRegionByPath(s, blockName));
|
||||
return region == null ? null : getRegionBlock(region);
|
||||
TraceMemoryRegion region = regionCacheByName.get(blockName);
|
||||
if (region != null && !region.isDeleted()) {
|
||||
long s = program.viewport.getReversedSnaps().get(0);
|
||||
if (region.getLifespan().contains(s)) {
|
||||
return getRegionBlock(region);
|
||||
}
|
||||
}
|
||||
region = getTopRegion(s -> memoryManager.getLiveRegionByPath(s, blockName));
|
||||
if (region != null) {
|
||||
regionCacheByName.put(blockName, region);
|
||||
return getRegionBlock(region);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -19,26 +19,24 @@ import java.io.IOException;
|
|||
import java.lang.reflect.Field;
|
||||
import java.util.*;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.*;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import db.DBRecord;
|
||||
import db.StringField;
|
||||
import ghidra.dbg.target.TargetObject;
|
||||
import ghidra.dbg.target.schema.TargetObjectSchema;
|
||||
import ghidra.dbg.util.*;
|
||||
import ghidra.program.model.address.AddressSpace;
|
||||
import ghidra.trace.database.DBTrace;
|
||||
import ghidra.trace.database.breakpoint.DBTraceObjectBreakpointLocation;
|
||||
import ghidra.trace.database.breakpoint.DBTraceObjectBreakpointSpec;
|
||||
import ghidra.trace.database.map.DBTraceAddressSnapRangePropertyMapSpace;
|
||||
import ghidra.trace.database.map.DBTraceAddressSnapRangePropertyMapTree.TraceAddressSnapRangeQuery;
|
||||
import ghidra.trace.database.memory.DBTraceObjectMemoryRegion;
|
||||
import ghidra.trace.database.memory.DBTraceObjectRegister;
|
||||
import ghidra.trace.database.module.*;
|
||||
import ghidra.trace.database.stack.DBTraceObjectStack;
|
||||
import ghidra.trace.database.stack.DBTraceObjectStackFrame;
|
||||
import ghidra.trace.database.target.DBTraceObjectValue.PrimaryTriple;
|
||||
import ghidra.trace.database.target.InternalTraceObjectValue.ValueLifespanSetter;
|
||||
import ghidra.trace.database.target.ValueSpace.SnapDimension;
|
||||
import ghidra.trace.database.target.visitors.*;
|
||||
import ghidra.trace.database.target.visitors.TreeTraversal.Visitor;
|
||||
import ghidra.trace.database.thread.DBTraceObjectThread;
|
||||
|
@ -56,16 +54,18 @@ import ghidra.trace.model.target.*;
|
|||
import ghidra.trace.model.target.annot.TraceObjectInterfaceUtils;
|
||||
import ghidra.trace.model.thread.TraceObjectThread;
|
||||
import ghidra.trace.util.TraceChangeRecord;
|
||||
import ghidra.util.*;
|
||||
import ghidra.util.LockHold;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.database.*;
|
||||
import ghidra.util.database.DBCachedObjectStoreFactory.AbstractDBFieldCodec;
|
||||
import ghidra.util.database.annot.*;
|
||||
import ghidra.util.database.spatial.rect.Rectangle2DDirection;
|
||||
|
||||
@DBAnnotatedObjectInfo(version = 0)
|
||||
public class DBTraceObject extends DBAnnotatedObject implements TraceObject {
|
||||
protected static final String TABLE_NAME = "Objects";
|
||||
|
||||
private static final int VALUE_CACHE_SIZE = 50;
|
||||
|
||||
protected static <T extends TraceObjectInterface> //
|
||||
Map.Entry<Class<? extends T>, Function<DBTraceObject, ? extends T>> safeEntry(
|
||||
Class<T> cls, Function<DBTraceObject, ? extends T> ctor) {
|
||||
|
@ -119,6 +119,9 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject {
|
|||
}
|
||||
}
|
||||
|
||||
record CachedLifespanValues(Lifespan span, Set<InternalTraceObjectValue> values) {
|
||||
}
|
||||
|
||||
// Canonical path
|
||||
static final String PATH_COLUMN_NAME = "Path";
|
||||
|
||||
|
@ -135,6 +138,19 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject {
|
|||
|
||||
private Map<Class<? extends TraceObjectInterface>, TraceObjectInterface> ifaces;
|
||||
|
||||
private final Map<String, InternalTraceObjectValue> valueCache = new LinkedHashMap<>() {
|
||||
protected boolean removeEldestEntry(Map.Entry<String, InternalTraceObjectValue> eldest) {
|
||||
return size() > VALUE_CACHE_SIZE;
|
||||
}
|
||||
};
|
||||
private final Map<String, Long> nullCache = new LinkedHashMap<>() {
|
||||
protected boolean removeEldestEntry(Map.Entry<String, Long> eldest) {
|
||||
return size() > VALUE_CACHE_SIZE;
|
||||
}
|
||||
};
|
||||
private CachedLifespanValues cachedLifespanValues = null;
|
||||
private MutableLifeSet cachedLife = null;
|
||||
|
||||
public DBTraceObject(DBTraceObjectManager manager, DBCachedObjectStore<?> store,
|
||||
DBRecord record) {
|
||||
super(store, record);
|
||||
|
@ -199,12 +215,17 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject {
|
|||
|
||||
@Override
|
||||
public LifeSet getLife() {
|
||||
// TODO: This should really be cached
|
||||
try (LockHold hold = manager.trace.lockRead()) {
|
||||
if (cachedLife != null) {
|
||||
synchronized (cachedLife) {
|
||||
return DefaultLifeSet.copyOf(cachedLife);
|
||||
}
|
||||
}
|
||||
MutableLifeSet result = new DefaultLifeSet();
|
||||
// NOTE: connected ranges should already be coalesced
|
||||
// No need to apply discreet domain
|
||||
getCanonicalParents(Lifespan.ALL).forEach(v -> result.add(v.getLifespan()));
|
||||
cachedLife = result;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
@ -253,10 +274,10 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject {
|
|||
}
|
||||
|
||||
protected void doRemoveTree(Lifespan span) {
|
||||
for (DBTraceObjectValue parent : getParents()) {
|
||||
for (InternalTraceObjectValue parent : getParents(span)) {
|
||||
parent.doTruncateOrDeleteAndEmitLifeChange(span);
|
||||
}
|
||||
for (InternalTraceObjectValue value : getValues()) {
|
||||
for (InternalTraceObjectValue value : getValues(span)) {
|
||||
value.doTruncateOrDeleteAndEmitLifeChange(span);
|
||||
if (value.isCanonical()) {
|
||||
value.getChild().doRemoveTree(span);
|
||||
|
@ -275,26 +296,24 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject {
|
|||
public TraceObjectValue getCanonicalParent(long snap) {
|
||||
try (LockHold hold = manager.trace.lockRead()) {
|
||||
if (isRoot()) {
|
||||
return manager.valueStore.getObjectAt(0);
|
||||
return manager.getRootValue();
|
||||
}
|
||||
return getCanonicalParents(Lifespan.at(snap)).findAny().orElse(null);
|
||||
return manager.valueMap
|
||||
.reduce(TraceObjectValueQuery.canonicalParents(this, Lifespan.at(snap)))
|
||||
.firstValue();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<? extends DBTraceObjectValue> getCanonicalParents(Lifespan lifespan) {
|
||||
// TODO: If this is invoked often, perhaps index
|
||||
public Stream<? extends InternalTraceObjectValue> getCanonicalParents(Lifespan lifespan) {
|
||||
try (LockHold hold = manager.trace.lockRead()) {
|
||||
if (isRoot()) {
|
||||
return Stream.of(manager.valueStore.getObjectAt(0));
|
||||
return Stream.of(manager.getRootValue());
|
||||
}
|
||||
String canonicalKey = path.key();
|
||||
TraceObjectKeyPath canonicalTail = path.parent();
|
||||
return manager.valuesByChild.getLazily(this)
|
||||
.stream()
|
||||
.filter(v -> canonicalKey.equals(v.getEntryKey()))
|
||||
.filter(v -> v.getLifespan().intersects(lifespan))
|
||||
.filter(v -> canonicalTail.equals(v.getParent().getCanonicalPath()));
|
||||
List<InternalTraceObjectValue> list = List.copyOf(
|
||||
manager.valueMap.reduce(TraceObjectValueQuery.canonicalParents(this, lifespan))
|
||||
.values());
|
||||
return list.stream();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -329,135 +348,53 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject {
|
|||
return ifCls.cast(ifaces.get(ifCls));
|
||||
}
|
||||
|
||||
protected Collection<? extends DBTraceObjectValue> doGetParents() {
|
||||
return manager.valuesByChild.get(this);
|
||||
protected Collection<? extends InternalTraceObjectValue> doGetParents(Lifespan lifespan) {
|
||||
return List.copyOf(
|
||||
manager.valueMap.reduce(TraceObjectValueQuery.parents(this, lifespan)).values());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<? extends DBTraceObjectValue> getParents() {
|
||||
public Collection<? extends InternalTraceObjectValue> getParents(Lifespan lifespan) {
|
||||
try (LockHold hold = manager.trace.lockRead()) {
|
||||
return doGetParents();
|
||||
return doGetParents(lifespan);
|
||||
}
|
||||
}
|
||||
|
||||
protected void collectNonRangedValues(Collection<? super DBTraceObjectValue> result) {
|
||||
for (DBTraceObjectValue val : manager.valuesByTriple
|
||||
.tail(new PrimaryTriple(this, "", Long.MIN_VALUE), true)
|
||||
.values()) {
|
||||
if (val.getParent() != this) {
|
||||
break;
|
||||
}
|
||||
result.add(val);
|
||||
}
|
||||
}
|
||||
|
||||
protected void collectNonRangedAttributes(List<? super DBTraceObjectValue> result) {
|
||||
for (DBTraceObjectValue val : manager.valuesByTriple
|
||||
.sub(new PrimaryTriple(this, "", Long.MIN_VALUE), true,
|
||||
new PrimaryTriple(this, "[", Long.MIN_VALUE), false)
|
||||
.values()) {
|
||||
result.add(val);
|
||||
}
|
||||
for (DBTraceObjectValue val : manager.valuesByTriple
|
||||
.tail(new PrimaryTriple(this, "\\", Long.MIN_VALUE), true)
|
||||
.values()) {
|
||||
if (val.getParent() != this) {
|
||||
break;
|
||||
}
|
||||
result.add(val);
|
||||
}
|
||||
}
|
||||
|
||||
protected void collectNonRangedElements(List<? super DBTraceObjectValue> result) {
|
||||
for (DBTraceObjectValue val : manager.valuesByTriple
|
||||
.sub(new PrimaryTriple(this, "[", Long.MIN_VALUE), true,
|
||||
new PrimaryTriple(this, "\\", Long.MIN_VALUE), false)
|
||||
.values()) {
|
||||
result.add(val);
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean doHasAnyNonRangedValues() {
|
||||
for (DBTraceObjectValue val : manager.valuesByTriple
|
||||
.tail(new PrimaryTriple(this, "", Long.MIN_VALUE), true)
|
||||
.values()) {
|
||||
if (val.getParent() != this) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
protected void collectRangedValues(Collection<? super DBTraceObjectAddressRangeValue> result) {
|
||||
for (DBTraceAddressSnapRangePropertyMapSpace<DBTraceObjectAddressRangeValue, ?> space //
|
||||
: manager.rangeValueMap.getActiveMemorySpaces()) {
|
||||
for (DBTraceObjectAddressRangeValue val : space.values()) {
|
||||
if (val.getParent() != this) {
|
||||
continue;
|
||||
}
|
||||
result.add(val);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void collectRangedAttributes(
|
||||
Collection<? super DBTraceObjectAddressRangeValue> result) {
|
||||
for (DBTraceAddressSnapRangePropertyMapSpace<DBTraceObjectAddressRangeValue, ?> space //
|
||||
: manager.rangeValueMap.getActiveMemorySpaces()) {
|
||||
for (DBTraceObjectAddressRangeValue val : space.values()) {
|
||||
if (val.getParent() != this) {
|
||||
continue;
|
||||
}
|
||||
if (!PathUtils.isName(val.getEntryKey())) {
|
||||
continue;
|
||||
}
|
||||
result.add(val);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void collectRangedElements(
|
||||
Collection<? super DBTraceObjectAddressRangeValue> result) {
|
||||
for (DBTraceAddressSnapRangePropertyMapSpace<DBTraceObjectAddressRangeValue, ?> space //
|
||||
: manager.rangeValueMap.getActiveMemorySpaces()) {
|
||||
for (DBTraceObjectAddressRangeValue val : space.values()) {
|
||||
if (val.getParent() != this) {
|
||||
continue;
|
||||
}
|
||||
if (!PathUtils.isIndex(val.getEntryKey())) {
|
||||
continue;
|
||||
}
|
||||
result.add(val);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean doHasAnyRangedValues() {
|
||||
for (DBTraceAddressSnapRangePropertyMapSpace<DBTraceObjectAddressRangeValue, ?> space //
|
||||
: manager.rangeValueMap.getActiveMemorySpaces()) {
|
||||
for (DBTraceObjectAddressRangeValue val : space.values()) {
|
||||
if (val.getParent() == this) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
protected Collection<? extends InternalTraceObjectValue> doGetValues() {
|
||||
List<InternalTraceObjectValue> result = new ArrayList<>();
|
||||
collectNonRangedValues(result);
|
||||
collectRangedValues(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
protected boolean doHasAnyValues() {
|
||||
return doHasAnyNonRangedValues() || doHasAnyRangedValues();
|
||||
return !manager.valueMap.reduce(TraceObjectValueQuery.values(this, Lifespan.ALL))
|
||||
.isEmpty();
|
||||
}
|
||||
|
||||
protected Collection<? extends InternalTraceObjectValue> doGetValues(Lifespan lifespan) {
|
||||
return manager.valueMap.reduce(TraceObjectValueQuery.values(this, lifespan)).values();
|
||||
}
|
||||
|
||||
protected Collection<? extends InternalTraceObjectValue> cachedDoGetValues(Lifespan lifespan) {
|
||||
if (Long.compareUnsigned(lifespan.lmax() - lifespan.lmin(), 10) > 0) {
|
||||
return List.copyOf(doGetValues(lifespan));
|
||||
}
|
||||
if (cachedLifespanValues == null || !cachedLifespanValues.span.encloses(lifespan)) {
|
||||
// Expand the query to take advantage of spatial locality (in the time dimension)
|
||||
long min = lifespan.lmin() - 10;
|
||||
if (min > lifespan.lmin()) {
|
||||
min = Lifespan.ALL.lmin();
|
||||
}
|
||||
long max = lifespan.lmax() + 10;
|
||||
if (max < lifespan.lmax()) {
|
||||
max = Lifespan.ALL.lmax();
|
||||
}
|
||||
Lifespan expanded = Lifespan.span(min, max);
|
||||
cachedLifespanValues =
|
||||
new CachedLifespanValues(expanded, new HashSet<>(doGetValues(expanded)));
|
||||
}
|
||||
return cachedLifespanValues.values.stream()
|
||||
.filter(v -> v.getLifespan().intersects(lifespan))
|
||||
.toList();
|
||||
}
|
||||
|
||||
protected boolean doHasAnyParents() {
|
||||
return manager.valuesByChild.containsKey(this);
|
||||
return !manager.valueMap.reduce(TraceObjectValueQuery.parents(this, Lifespan.ALL))
|
||||
.isEmpty();
|
||||
}
|
||||
|
||||
protected boolean doIsConnected() {
|
||||
|
@ -465,42 +402,28 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Collection<? extends InternalTraceObjectValue> getValues() {
|
||||
public Collection<? extends InternalTraceObjectValue> getValues(Lifespan lifespan) {
|
||||
try (LockHold hold = manager.trace.lockRead()) {
|
||||
return doGetValues();
|
||||
return cachedDoGetValues(lifespan);
|
||||
}
|
||||
}
|
||||
|
||||
protected Collection<? extends InternalTraceObjectValue> doGetElements() {
|
||||
List<InternalTraceObjectValue> result = new ArrayList<>();
|
||||
collectNonRangedElements(result);
|
||||
collectRangedElements(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<? extends InternalTraceObjectValue> getElements() {
|
||||
try (LockHold hold = manager.trace.lockRead()) {
|
||||
return doGetElements();
|
||||
}
|
||||
}
|
||||
|
||||
protected Collection<? extends InternalTraceObjectValue> doGetAttributes() {
|
||||
List<InternalTraceObjectValue> result = new ArrayList<>();
|
||||
collectNonRangedAttributes(result);
|
||||
collectRangedAttributes(result);
|
||||
return result;
|
||||
public Collection<? extends InternalTraceObjectValue> getElements(Lifespan lifespan) {
|
||||
return getValues(lifespan).stream()
|
||||
.filter(v -> PathUtils.isIndex(v.getEntryKey()))
|
||||
.toList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<? extends InternalTraceObjectValue> getAttributes() {
|
||||
try (LockHold hold = manager.trace.lockRead()) {
|
||||
return doGetAttributes();
|
||||
}
|
||||
public Collection<? extends InternalTraceObjectValue> getAttributes(Lifespan lifespan) {
|
||||
return getValues(lifespan).stream()
|
||||
.filter(v -> PathUtils.isName(v.getEntryKey()))
|
||||
.toList();
|
||||
}
|
||||
|
||||
protected void doCheckConflicts(Lifespan span, String key, Object value) {
|
||||
for (InternalTraceObjectValue val : doGetValues(span, key)) {
|
||||
for (InternalTraceObjectValue val : doGetValues(span, key, true)) {
|
||||
if (!Objects.equals(value, val.getValue())) {
|
||||
throw new DuplicateKeyException(key);
|
||||
}
|
||||
|
@ -510,7 +433,7 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject {
|
|||
protected Lifespan doAdjust(Lifespan span, String key, Object value) {
|
||||
// Ordered by min, so I only need to consider the first conflict
|
||||
// If start is contained in an entry, assume the user means to overwrite it.
|
||||
for (InternalTraceObjectValue val : doGetValues(span, key)) {
|
||||
for (InternalTraceObjectValue val : doGetValues(span, key, true)) {
|
||||
if (Objects.equals(value, val.getValue())) {
|
||||
continue; // not a conflict
|
||||
}
|
||||
|
@ -523,154 +446,50 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject {
|
|||
return span;
|
||||
}
|
||||
|
||||
// TODO: Could/should this return Stream instead?
|
||||
protected Collection<? extends InternalTraceObjectValue> doGetValues(Lifespan span,
|
||||
String key) {
|
||||
return doGetValues(span.lmin(), span.lmax(), key);
|
||||
}
|
||||
|
||||
/**
|
||||
* The implementation of {@link #getValues(Lifespan, String)}
|
||||
*
|
||||
* <p>
|
||||
* This collects entries ordered by min snap
|
||||
*
|
||||
* @param lower the lower snap
|
||||
* @param upper the upper snap
|
||||
* @param key the key
|
||||
* @return the collection of values
|
||||
*/
|
||||
protected Collection<? extends InternalTraceObjectValue> doGetValues(long lower, long upper,
|
||||
String key) {
|
||||
// Collect triplet-indexed values
|
||||
Set<InternalTraceObjectValue> result = new LinkedHashSet<>();
|
||||
PrimaryTriple min = new PrimaryTriple(this, key, lower);
|
||||
PrimaryTriple max = new PrimaryTriple(this, key, upper);
|
||||
DBTraceObjectValue floor = manager.valuesByTriple.floorValue(min);
|
||||
if (floor != null && floor.getParent() == this && key.equals(floor.getEntryKey()) &&
|
||||
floor.getLifespan().contains(lower)) {
|
||||
result.add(floor);
|
||||
}
|
||||
for (DBTraceObjectValue val : manager.valuesByTriple.sub(min, true, max, true)
|
||||
.values()) {
|
||||
result.add(val);
|
||||
}
|
||||
|
||||
// Collect R*-Tree-indexed values
|
||||
for (DBTraceAddressSnapRangePropertyMapSpace<DBTraceObjectAddressRangeValue, DBTraceObjectAddressRangeValue> space : manager.rangeValueMap
|
||||
.getActiveMemorySpaces()) {
|
||||
AddressSpace as = space.getAddressSpace();
|
||||
|
||||
for (DBTraceObjectAddressRangeValue val : manager.rangeValueMap
|
||||
.reduce(TraceAddressSnapRangeQuery
|
||||
.intersecting(as.getMinAddress(), as.getMaxAddress(), lower, upper))
|
||||
.values()) {
|
||||
if (val.getParent() != this) {
|
||||
continue;
|
||||
}
|
||||
if (!key.equals(val.getEntryKey())) {
|
||||
continue;
|
||||
}
|
||||
result.add(val);
|
||||
}
|
||||
}
|
||||
|
||||
return result.stream()
|
||||
.sorted(Comparator.comparing(v -> v.getMinSnap()))
|
||||
.collect(Collectors.toList());
|
||||
String key, boolean forward) {
|
||||
return manager.valueMap
|
||||
.reduce(TraceObjectValueQuery.values(this, key, key, span)
|
||||
.starting(forward ? SnapDimension.FORWARD : SnapDimension.BACKWARD))
|
||||
.orderedValues();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<? extends InternalTraceObjectValue> getValues(Lifespan span, String key) {
|
||||
try (LockHold hold = manager.trace.lockRead()) {
|
||||
return doGetValues(span, key);
|
||||
return doGetValues(span, key, true);
|
||||
}
|
||||
}
|
||||
|
||||
protected DBTraceObjectValue doGetNonRangedValue(long snap, String key) {
|
||||
DBTraceObjectValue floor =
|
||||
manager.valuesByTriple.floorValue(new PrimaryTriple(this, key, snap));
|
||||
if (floor == null || floor.getParent() != this || !key.equals(floor.getEntryKey()) ||
|
||||
!floor.getLifespan().contains(snap)) {
|
||||
return null;
|
||||
}
|
||||
return floor;
|
||||
}
|
||||
|
||||
protected Stream<DBTraceObjectValue> doGetOrderedNonRangedValues(Lifespan span, String key,
|
||||
boolean forward) {
|
||||
DBCachedObjectIndex<PrimaryTriple, DBTraceObjectValue> sub = manager.valuesByTriple.sub(
|
||||
new PrimaryTriple(this, key, span.lmin()), true,
|
||||
new PrimaryTriple(this, key, span.lmax()), true);
|
||||
Spliterator<DBTraceObjectValue> spliterator = (forward ? sub : sub.descending())
|
||||
.values()
|
||||
.spliterator();
|
||||
return StreamSupport.stream(spliterator, false);
|
||||
}
|
||||
|
||||
protected DBTraceObjectAddressRangeValue doGetRangedValue(long snap, String key) {
|
||||
for (DBTraceAddressSnapRangePropertyMapSpace<DBTraceObjectAddressRangeValue, //
|
||||
DBTraceObjectAddressRangeValue> space : manager.rangeValueMap
|
||||
.getActiveMemorySpaces()) {
|
||||
AddressSpace as = space.getAddressSpace();
|
||||
for (DBTraceObjectAddressRangeValue val : space
|
||||
.reduce(TraceAddressSnapRangeQuery.atSnap(snap, as))
|
||||
.values()) {
|
||||
if (val.getParent() == this && key.equals(val.getEntryKey())) {
|
||||
return val;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
protected Stream<DBTraceObjectAddressRangeValue> doGetOrderedRangedValues(Lifespan span,
|
||||
String key, boolean forward) {
|
||||
Rectangle2DDirection dir = forward
|
||||
? Rectangle2DDirection.BOTTOMMOST
|
||||
: Rectangle2DDirection.TOPMOST;
|
||||
List<Stream<DBTraceObjectAddressRangeValue>> streams = manager.rangeValueMap
|
||||
.getActiveMemorySpaces()
|
||||
.stream()
|
||||
.map(s -> StreamSupport.stream(s
|
||||
.reduce(TraceAddressSnapRangeQuery.intersecting(span, s.getAddressSpace())
|
||||
.starting(dir))
|
||||
.orderedValues()
|
||||
.spliterator(),
|
||||
false).filter(v -> key.equals(v.getEntryKey()) && this == v.getParent()))
|
||||
.toList();
|
||||
Comparator<Long> order = forward ? Comparator.naturalOrder() : Comparator.reverseOrder();
|
||||
Comparator<DBTraceObjectAddressRangeValue> comparator =
|
||||
Comparator.comparing(v -> v.getMinSnap(), order);
|
||||
return StreamUtils.merge(streams, comparator);
|
||||
}
|
||||
|
||||
@Override
|
||||
public InternalTraceObjectValue getValue(long snap, String key) {
|
||||
try (LockHold hold = manager.trace.lockRead()) {
|
||||
DBTraceObjectValue nrVal = doGetNonRangedValue(snap, key);
|
||||
if (nrVal != null) {
|
||||
return nrVal;
|
||||
InternalTraceObjectValue cached = valueCache.get(key);
|
||||
if (cached != null && !cached.isDeleted() && cached.getLifespan().contains(snap)) {
|
||||
return cached;
|
||||
}
|
||||
return doGetRangedValue(snap, key);
|
||||
Long nullSnap = nullCache.get(key);
|
||||
if (nullSnap != null && nullSnap.longValue() == snap) {
|
||||
return null;
|
||||
}
|
||||
InternalTraceObjectValue found = manager.valueMap
|
||||
.reduce(TraceObjectValueQuery.values(this, key, key, Lifespan.at(snap)))
|
||||
.firstValue();
|
||||
if (found == null) {
|
||||
nullCache.put(key, snap);
|
||||
}
|
||||
else {
|
||||
valueCache.put(key, found);
|
||||
}
|
||||
return found;
|
||||
}
|
||||
}
|
||||
|
||||
protected Stream<InternalTraceObjectValue> doGetOrderedValues(Lifespan span, String key,
|
||||
boolean forward) {
|
||||
Stream<DBTraceObjectValue> nrVals = doGetOrderedNonRangedValues(span, key, forward);
|
||||
Stream<DBTraceObjectAddressRangeValue> rVals = doGetOrderedRangedValues(span, key, forward);
|
||||
Comparator<Long> order = forward ? Comparator.naturalOrder() : Comparator.reverseOrder();
|
||||
Comparator<InternalTraceObjectValue> comparator =
|
||||
Comparator.comparing(v -> v.getMinSnap(), order);
|
||||
return StreamUtils.merge(List.of(nrVals, rVals), comparator);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<? extends InternalTraceObjectValue> getOrderedValues(Lifespan span, String key,
|
||||
boolean forward) {
|
||||
try (LockHold hold = manager.trace.lockRead()) {
|
||||
return doGetOrderedValues(span, key, forward);
|
||||
return doGetValues(span, key, forward).stream();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -762,6 +581,10 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject {
|
|||
|
||||
protected InternalTraceObjectValue doCreateValue(Lifespan lifespan, String key,
|
||||
Object value) {
|
||||
Long nullSnap = nullCache.get(key);
|
||||
if (nullSnap != null && lifespan.contains(nullSnap)) {
|
||||
nullCache.remove(key);
|
||||
}
|
||||
return manager.doCreateValue(lifespan, this, key, value);
|
||||
}
|
||||
|
||||
|
@ -784,7 +607,8 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject {
|
|||
@Override
|
||||
protected Iterable<InternalTraceObjectValue> getIntersecting(Long lower,
|
||||
Long upper) {
|
||||
return Collections.unmodifiableCollection(doGetValues(lower, upper, key));
|
||||
return Collections.unmodifiableCollection(
|
||||
doGetValues(Lifespan.span(lower, upper), key, true));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -930,10 +754,10 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject {
|
|||
}
|
||||
|
||||
protected void doDeleteReferringValues() {
|
||||
for (InternalTraceObjectValue child : getValues()) {
|
||||
for (InternalTraceObjectValue child : getValues(Lifespan.ALL)) {
|
||||
child.doDeleteAndEmit();
|
||||
}
|
||||
for (DBTraceObjectValue parent : getParents()) {
|
||||
for (InternalTraceObjectValue parent : getParents(Lifespan.ALL)) {
|
||||
parent.doDeleteAndEmit();
|
||||
}
|
||||
}
|
||||
|
@ -957,7 +781,38 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject {
|
|||
}
|
||||
}
|
||||
catch (Throwable t) {
|
||||
Msg.error(this, "Error while translating event " + rec + " for interface " + iface);
|
||||
Msg.error(this,
|
||||
"Error while translating event " + rec + " for interface " + iface + ":" + t);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void notifyValueCreated(InternalTraceObjectValue value) {
|
||||
if (cachedLifespanValues != null) {
|
||||
if (cachedLifespanValues.span.intersects(value.getLifespan())) {
|
||||
cachedLifespanValues.values.add(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void notifyValueDeleted(InternalTraceObjectValue value) {
|
||||
if (cachedLifespanValues != null) {
|
||||
cachedLifespanValues.values.remove(value);
|
||||
}
|
||||
}
|
||||
|
||||
protected void notifyParentValueCreated(InternalTraceObjectValue parent) {
|
||||
if (cachedLife != null && parent.isCanonical()) {
|
||||
synchronized (cachedLife) {
|
||||
cachedLife.add(parent.getLifespan());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void notifyParentValueDeleted(InternalTraceObjectValue parent) {
|
||||
if (cachedLife != null && parent.isCanonical()) {
|
||||
synchronized (cachedLife) {
|
||||
cachedLife.remove(parent.getLifespan());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,202 +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.trace.database.target;
|
||||
|
||||
import db.DBRecord;
|
||||
import ghidra.trace.database.map.DBTraceAddressSnapRangePropertyMapTree;
|
||||
import ghidra.trace.database.map.DBTraceAddressSnapRangePropertyMapTree.AbstractDBTraceAddressSnapRangePropertyMapData;
|
||||
import ghidra.trace.database.target.DBTraceObjectValue.DBTraceObjectDBFieldCodec;
|
||||
import ghidra.trace.model.Lifespan;
|
||||
import ghidra.trace.model.Trace;
|
||||
import ghidra.trace.model.target.TraceObjectKeyPath;
|
||||
import ghidra.trace.model.target.TraceObjectValue;
|
||||
import ghidra.trace.util.TraceAddressSpace;
|
||||
import ghidra.util.LockHold;
|
||||
import ghidra.util.database.DBCachedObjectStore;
|
||||
import ghidra.util.database.DBObjectColumn;
|
||||
import ghidra.util.database.annot.*;
|
||||
|
||||
@DBAnnotatedObjectInfo(version = 0)
|
||||
public class DBTraceObjectAddressRangeValue
|
||||
extends AbstractDBTraceAddressSnapRangePropertyMapData<DBTraceObjectAddressRangeValue>
|
||||
implements InternalTraceObjectValue {
|
||||
public static final String TABLE_NAME = "ObjectRangeValue";
|
||||
|
||||
static final String PARENT_COLUMN_NAME = "ValueParent";
|
||||
static final String KEY_COLUMN_NAME = "ValueKey";
|
||||
static final String TYPE_COLUMN_NAME = "IsAddress";
|
||||
|
||||
@DBAnnotatedColumn(PARENT_COLUMN_NAME)
|
||||
static DBObjectColumn PARENT_COLUMN;
|
||||
@DBAnnotatedColumn(KEY_COLUMN_NAME)
|
||||
static DBObjectColumn KEY_COLUMN;
|
||||
@DBAnnotatedColumn(TYPE_COLUMN_NAME)
|
||||
static DBObjectColumn TYPE_COLUMN;
|
||||
|
||||
@DBAnnotatedField(
|
||||
column = PARENT_COLUMN_NAME,
|
||||
indexed = true,
|
||||
codec = DBTraceObjectDBFieldCodec.class)
|
||||
private DBTraceObject parent;
|
||||
@DBAnnotatedField(column = KEY_COLUMN_NAME)
|
||||
private String entryKey;
|
||||
@DBAnnotatedField(column = TYPE_COLUMN_NAME)
|
||||
private boolean isAddress;
|
||||
|
||||
protected final DBTraceObjectManager manager;
|
||||
|
||||
public DBTraceObjectAddressRangeValue(DBTraceObjectManager manager,
|
||||
DBTraceAddressSnapRangePropertyMapTree<DBTraceObjectAddressRangeValue, ?> tree,
|
||||
DBCachedObjectStore<?> store, DBRecord record) {
|
||||
super(tree, store, record);
|
||||
this.manager = manager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getClass().getSimpleName() + ": parent=" + parent + ", key=" + entryKey +
|
||||
", lifespan=" + getLifespan() + ", value=" + getValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setRecordValue(DBTraceObjectAddressRangeValue value) {
|
||||
// Nothing to do. I am the value
|
||||
assert value == null;
|
||||
}
|
||||
|
||||
public TraceAddressSpace getTraceAddressSpace() {
|
||||
return tree.getMapSpace();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected DBTraceObjectAddressRangeValue getRecordValue() {
|
||||
return this;
|
||||
}
|
||||
|
||||
void set(DBTraceObject parent, String key, boolean isAddress) {
|
||||
this.parent = parent;
|
||||
this.entryKey = key;
|
||||
this.isAddress = isAddress;
|
||||
update(PARENT_COLUMN, KEY_COLUMN, TYPE_COLUMN);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Trace getTrace() {
|
||||
return manager.trace;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DBTraceObjectManager getManager() {
|
||||
return manager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DBTraceObject getParent() {
|
||||
return parent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getEntryKey() {
|
||||
return entryKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getValue() {
|
||||
if (isAddress) {
|
||||
return getRange().getMinAddress();
|
||||
}
|
||||
return getRange();
|
||||
}
|
||||
|
||||
@Override
|
||||
public DBTraceObject getChild() {
|
||||
throw new ClassCastException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isObject() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DBTraceObject getChildOrNull() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TraceObjectKeyPath getCanonicalPath() {
|
||||
try (LockHold hold = manager.trace.lockRead()) {
|
||||
return parent.getCanonicalPath().extend(entryKey);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCanonical() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doSetLifespan(Lifespan lifespan) {
|
||||
super.doSetLifespan(lifespan);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMinSnap(long minSnap) {
|
||||
try (LockHold hold = manager.trace.lockWrite()) {
|
||||
setLifespan(Lifespan.span(minSnap, getY2()));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getMinSnap() {
|
||||
try (LockHold hold = manager.trace.lockRead()) {
|
||||
return getY1();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMaxSnap(long maxSnap) {
|
||||
try (LockHold hold = manager.trace.lockWrite()) {
|
||||
setLifespan(Lifespan.span(getY1(), maxSnap));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getMaxSnap() {
|
||||
try (LockHold hold = manager.trace.lockRead()) {
|
||||
return getY2();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doDelete() {
|
||||
manager.rangeValueMap.deleteData(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete() {
|
||||
try (LockHold hold = LockHold.lock(manager.lock.writeLock())) {
|
||||
doDeleteAndEmit();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public TraceObjectValue truncateOrDelete(Lifespan span) {
|
||||
try (LockHold hold = LockHold.lock(manager.lock.writeLock())) {
|
||||
return doTruncateOrDeleteAndEmitLifeChange(span);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
/* ###
|
||||
* 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.trace.database.target;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
import db.DBRecord;
|
||||
import db.LongField;
|
||||
import ghidra.util.database.DBAnnotatedObject;
|
||||
import ghidra.util.database.DBCachedObjectStoreFactory.AbstractDBFieldCodec;
|
||||
|
||||
public class DBTraceObjectDBFieldCodec<OV extends DBAnnotatedObject & InternalTraceObjectValue>
|
||||
extends AbstractDBFieldCodec<DBTraceObject, OV, LongField> {
|
||||
public DBTraceObjectDBFieldCodec(Class<OV> objectType, Field field, int column) {
|
||||
super(DBTraceObject.class, objectType, LongField.class, field, column);
|
||||
}
|
||||
|
||||
protected static long encode(DBTraceObject value) {
|
||||
return value == null ? -1 : value.getKey();
|
||||
}
|
||||
|
||||
protected static DBTraceObject decode(InternalTraceObjectValue ent, long enc) {
|
||||
return enc == -1 ? null : ent.getManager().getObjectById(enc);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void store(DBTraceObject value, LongField f) {
|
||||
f.setLongValue(encode(value));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doStore(OV obj, DBRecord record)
|
||||
throws IllegalArgumentException, IllegalAccessException {
|
||||
record.setLongValue(column, encode(getValue(obj)));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doLoad(OV obj, DBRecord record)
|
||||
throws IllegalArgumentException, IllegalAccessException {
|
||||
setValue(obj, decode(obj, record.getLongValue(column)));
|
||||
}
|
||||
}
|
|
@ -15,8 +15,12 @@
|
|||
*/
|
||||
package ghidra.trace.database.target;
|
||||
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.model.address.AddressRange;
|
||||
import ghidra.trace.database.space.DBTraceSpaceKey.DefaultDBTraceSpaceKey;
|
||||
import ghidra.trace.model.Lifespan;
|
||||
import ghidra.trace.model.Lifespan.*;
|
||||
import ghidra.trace.model.Lifespan.DefaultLifeSet;
|
||||
import ghidra.trace.model.Lifespan.LifeSet;
|
||||
import ghidra.trace.model.Trace.TraceObjectChangeType;
|
||||
import ghidra.trace.model.TraceUniqueObject;
|
||||
import ghidra.trace.model.target.*;
|
||||
|
@ -173,9 +177,14 @@ public interface DBTraceObjectInterface extends TraceObjectInterface, TraceUniqu
|
|||
|
||||
static TraceAddressSpace spaceForValue(TraceObject object, long snap, String key) {
|
||||
TraceObjectValue val = object.getAttribute(snap, key);
|
||||
if (val instanceof DBTraceObjectAddressRangeValue) {
|
||||
DBTraceObjectAddressRangeValue addrVal = (DBTraceObjectAddressRangeValue) val;
|
||||
return addrVal.getTraceAddressSpace();
|
||||
if (val == null) {
|
||||
return null;
|
||||
}
|
||||
if (val.getValue() instanceof Address address) {
|
||||
return new DefaultDBTraceSpaceKey(null, address.getAddressSpace(), 0);
|
||||
}
|
||||
if (val.getValue() instanceof AddressRange range) {
|
||||
return new DefaultDBTraceSpaceKey(null, range.getAddressSpace(), 0);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -23,7 +23,6 @@ import java.util.function.Predicate;
|
|||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.apache.commons.collections4.IteratorUtils;
|
||||
import org.jdom.JDOMException;
|
||||
|
||||
import db.*;
|
||||
|
@ -38,13 +37,13 @@ import ghidra.program.model.lang.Language;
|
|||
import ghidra.trace.database.DBTrace;
|
||||
import ghidra.trace.database.DBTraceManager;
|
||||
import ghidra.trace.database.breakpoint.DBTraceObjectBreakpointLocation;
|
||||
import ghidra.trace.database.map.DBTraceAddressSnapRangePropertyMap;
|
||||
import ghidra.trace.database.map.DBTraceAddressSnapRangePropertyMapTree.TraceAddressSnapRangeQuery;
|
||||
import ghidra.trace.database.module.TraceObjectSection;
|
||||
import ghidra.trace.database.target.DBTraceObjectValue.PrimaryTriple;
|
||||
import ghidra.trace.database.target.DBTraceObjectValueRStarTree.DBTraceObjectValueMap;
|
||||
import ghidra.trace.database.target.ValueSpace.EntryKeyDimension;
|
||||
import ghidra.trace.database.target.visitors.SuccessorsRelativeVisitor;
|
||||
import ghidra.trace.database.thread.DBTraceObjectThread;
|
||||
import ghidra.trace.model.*;
|
||||
import ghidra.trace.model.Lifespan;
|
||||
import ghidra.trace.model.Trace;
|
||||
import ghidra.trace.model.Trace.TraceObjectChangeType;
|
||||
import ghidra.trace.model.breakpoint.*;
|
||||
import ghidra.trace.model.memory.*;
|
||||
|
@ -69,6 +68,7 @@ import ghidra.util.exception.VersionException;
|
|||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager {
|
||||
private static final int OBJECTS_CONTAINING_CACHE_SIZE = 100;
|
||||
|
||||
public static class DBTraceObjectSchemaDBFieldCodec extends
|
||||
AbstractDBFieldCodec<SchemaContext, DBTraceObjectSchemaEntry, StringField> {
|
||||
|
@ -148,24 +148,33 @@ public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager
|
|||
}
|
||||
}
|
||||
|
||||
record ObjectsContainingKey(long snap, Address address, String key,
|
||||
Class<? extends TraceObjectInterface> iface) {
|
||||
}
|
||||
|
||||
protected final ReadWriteLock lock;
|
||||
protected final DBTrace trace;
|
||||
|
||||
protected final DBCachedObjectStore<DBTraceObjectSchemaEntry> schemaStore;
|
||||
protected final DBCachedObjectStore<DBTraceObject> objectStore;
|
||||
protected final DBCachedObjectStore<DBTraceObjectValue> valueStore;
|
||||
|
||||
protected final DBTraceAddressSnapRangePropertyMap<DBTraceObjectAddressRangeValue, DBTraceObjectAddressRangeValue> rangeValueMap;
|
||||
protected final DBTraceObjectValueRStarTree valueTree;
|
||||
protected final DBTraceObjectValueMap valueMap;
|
||||
|
||||
protected final DBCachedObjectIndex<TraceObjectKeyPath, DBTraceObject> objectsByPath;
|
||||
protected final DBCachedObjectIndex<PrimaryTriple, DBTraceObjectValue> valuesByTriple;
|
||||
protected final DBCachedObjectIndex<DBTraceObject, DBTraceObjectValue> valuesByChild;
|
||||
|
||||
protected final Collection<TraceObject> objectsView;
|
||||
protected final Collection<TraceObjectValue> valuesView;
|
||||
|
||||
protected TargetObjectSchema rootSchema;
|
||||
|
||||
protected final Map<ObjectsContainingKey, Collection<?>> objectsContainingCache =
|
||||
new LinkedHashMap<>() {
|
||||
protected boolean removeEldestEntry(
|
||||
Map.Entry<ObjectsContainingKey, Collection<?>> eldest) {
|
||||
return size() > OBJECTS_CONTAINING_CACHE_SIZE;
|
||||
}
|
||||
};
|
||||
|
||||
public DBTraceObjectManager(DBHandle dbh, DBOpenMode openMode, ReadWriteLock lock,
|
||||
TaskMonitor monitor, Language baseLanguage, DBTrace trace)
|
||||
throws IOException, VersionException {
|
||||
|
@ -178,33 +187,17 @@ public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager
|
|||
loadRootSchema();
|
||||
objectStore = factory.getOrCreateCachedStore(DBTraceObject.TABLE_NAME,
|
||||
DBTraceObject.class, (s, r) -> new DBTraceObject(this, s, r), true);
|
||||
valueStore = factory.getOrCreateCachedStore(DBTraceObjectValue.TABLE_NAME,
|
||||
DBTraceObjectValue.class, (s, r) -> new DBTraceObjectValue(this, s, r), true);
|
||||
rangeValueMap = new DBTraceAddressSnapRangePropertyMap<>(
|
||||
DBTraceObjectAddressRangeValue.TABLE_NAME, dbh, openMode, lock, monitor, baseLanguage,
|
||||
trace, null, DBTraceObjectAddressRangeValue.class,
|
||||
(t, s, r) -> new DBTraceObjectAddressRangeValue(this, t, s, r));
|
||||
|
||||
valueTree = new DBTraceObjectValueRStarTree(this, factory,
|
||||
DBTraceObjectValueData.TABLE_NAME, ValueSpace.INSTANCE, DBTraceObjectValueData.class,
|
||||
DBTraceObjectValueNode.class, false, 50);
|
||||
valueMap = valueTree.asSpatialMap();
|
||||
|
||||
objectsByPath =
|
||||
objectStore.getIndex(TraceObjectKeyPath.class, DBTraceObject.PATH_COLUMN);
|
||||
valuesByTriple =
|
||||
valueStore.getIndex(PrimaryTriple.class, DBTraceObjectValue.TRIPLE_COLUMN);
|
||||
valuesByChild =
|
||||
valueStore.getIndex(DBTraceObject.class, DBTraceObjectValue.CHILD_COLUMN);
|
||||
|
||||
objectsView = Collections.unmodifiableCollection(objectStore.asMap().values());
|
||||
valuesView = new AbstractCollection<>() {
|
||||
@Override
|
||||
public Iterator<TraceObjectValue> iterator() {
|
||||
return IteratorUtils.chainedIterator(valueStore.asMap().values().iterator(),
|
||||
rangeValueMap.values().iterator());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return objectStore.getRecordCount() + rangeValueMap.size();
|
||||
}
|
||||
};
|
||||
valuesView = Collections.unmodifiableCollection(valueMap.values());
|
||||
}
|
||||
|
||||
protected void loadRootSchema() {
|
||||
|
@ -225,8 +218,7 @@ public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager
|
|||
@Override
|
||||
public void invalidateCache(boolean all) {
|
||||
objectStore.invalidateCache();
|
||||
valueStore.invalidateCache();
|
||||
rangeValueMap.invalidateCache(all);
|
||||
valueTree.invalidateCache();
|
||||
schemaStore.invalidateCache();
|
||||
loadRootSchema();
|
||||
}
|
||||
|
@ -281,24 +273,22 @@ public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager
|
|||
|
||||
protected InternalTraceObjectValue doCreateValue(Lifespan lifespan,
|
||||
DBTraceObject parent, String key, Object value) {
|
||||
if (value instanceof AddressRange) {
|
||||
DBTraceObjectAddressRangeValue entry = rangeValueMap
|
||||
.put(new ImmutableTraceAddressSnapRange((AddressRange) value, lifespan), null);
|
||||
entry.set(parent, key, false);
|
||||
emitValueCreated(parent, entry);
|
||||
return entry;
|
||||
InternalTraceObjectValue entry = valueTree.asSpatialMap()
|
||||
.put(new ImmutableValueShape(parent, value, key, lifespan), null);
|
||||
if (value instanceof DBTraceObject child) {
|
||||
child.notifyParentValueCreated(entry);
|
||||
}
|
||||
else if (value instanceof Address) {
|
||||
Address address = (Address) value;
|
||||
AddressRange singleton = new AddressRangeImpl(address, address);
|
||||
DBTraceObjectAddressRangeValue entry = rangeValueMap
|
||||
.put(new ImmutableTraceAddressSnapRange(singleton, lifespan), null);
|
||||
entry.set(parent, key, true);
|
||||
emitValueCreated(parent, entry);
|
||||
return entry;
|
||||
else {
|
||||
entry.doSetPrimitive(value);
|
||||
}
|
||||
DBTraceObjectValue entry = valueStore.create();
|
||||
entry.set(lifespan, parent, key, value);
|
||||
|
||||
if (parent != null) { // Root
|
||||
parent.notifyValueCreated(entry);
|
||||
}
|
||||
|
||||
// TODO: Perhaps a little drastic
|
||||
invalidateObjectsContainingCache();
|
||||
|
||||
emitValueCreated(parent, entry);
|
||||
return entry;
|
||||
}
|
||||
|
@ -350,6 +340,12 @@ public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager
|
|||
}
|
||||
}
|
||||
|
||||
public DBTraceObjectValueData getRootValue() {
|
||||
try (LockHold hold = trace.lockRead()) {
|
||||
return valueTree.getDataStore().getObjectAt(0);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public DBTraceObject getRootObject() {
|
||||
return getObjectById(0);
|
||||
|
@ -381,7 +377,7 @@ public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager
|
|||
public Stream<? extends TraceObjectValPath> getValuePaths(Lifespan span,
|
||||
PathPredicates predicates) {
|
||||
try (LockHold hold = trace.lockRead()) {
|
||||
DBTraceObjectValue rootVal = valueStore.getObjectAt(0);
|
||||
DBTraceObjectValueData rootVal = getRootValue();
|
||||
if (rootVal == null) {
|
||||
return Stream.of();
|
||||
}
|
||||
|
@ -401,14 +397,18 @@ public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager
|
|||
|
||||
@Override
|
||||
public Collection<? extends TraceObjectValue> getValuesIntersecting(Lifespan span,
|
||||
AddressRange range) {
|
||||
return Collections.unmodifiableCollection(
|
||||
rangeValueMap.reduce(TraceAddressSnapRangeQuery.intersecting(range, span)).values());
|
||||
AddressRange range, String entryKey) {
|
||||
return Collections
|
||||
.unmodifiableCollection(valueMap.reduce(TraceObjectValueQuery.intersecting(
|
||||
entryKey != null ? entryKey : EntryKeyDimension.INSTANCE.absoluteMin(),
|
||||
entryKey != null ? entryKey : EntryKeyDimension.INSTANCE.absoluteMax(),
|
||||
span, range)).values());
|
||||
}
|
||||
|
||||
public Collection<? extends TraceObjectValue> getValuesAt(long snap, Address address) {
|
||||
public Collection<? extends TraceObjectValue> getValuesAt(long snap, Address address,
|
||||
String entryKey) {
|
||||
return Collections.unmodifiableCollection(
|
||||
rangeValueMap.reduce(TraceAddressSnapRangeQuery.at(address, snap)).values());
|
||||
valueMap.reduce(TraceObjectValueQuery.at(entryKey, snap, address)).values());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -445,8 +445,7 @@ public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager
|
|||
@Override
|
||||
public void clear() {
|
||||
try (LockHold hold = trace.lockWrite()) {
|
||||
valueStore.deleteAll();
|
||||
rangeValueMap.clear();
|
||||
valueMap.clear();
|
||||
objectStore.deleteAll();
|
||||
schemaStore.deleteAll();
|
||||
rootSchema = null;
|
||||
|
@ -458,8 +457,16 @@ public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager
|
|||
object.emitEvents(new TraceChangeRecord<>(TraceObjectChangeType.DELETED, null, object));
|
||||
}
|
||||
|
||||
protected void doDeleteEdge(DBTraceObjectValue edge) {
|
||||
valueStore.delete(edge);
|
||||
protected void doDeleteEdge(DBTraceObjectValueData edge) {
|
||||
valueTree.doDeleteEntry(edge);
|
||||
|
||||
// TODO: Perhaps a little drastic....
|
||||
/**
|
||||
* NB. An object in one of these queries had to have an edge. Deleting that object will also
|
||||
* delete referring edges, so the cache will get invalidated. No need to repeat in
|
||||
* doDeleteObject.
|
||||
*/
|
||||
invalidateObjectsContainingCache();
|
||||
}
|
||||
|
||||
public boolean hasSchema() {
|
||||
|
@ -508,35 +515,51 @@ public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager
|
|||
}
|
||||
}
|
||||
|
||||
protected <I extends TraceObjectInterface> Stream<I> doParentsWithKeyHaving(
|
||||
Stream<? extends TraceObjectValue> values, String key, Class<I> iface) {
|
||||
return values.filter(v -> key.equals(v.getEntryKey()))
|
||||
.map(v -> v.getParent())
|
||||
protected <I extends TraceObjectInterface> Stream<I> doParentsHaving(
|
||||
Stream<? extends TraceObjectValue> values, Class<I> iface) {
|
||||
return values.map(v -> v.getParent())
|
||||
.map(o -> o.queryInterface(iface))
|
||||
.filter(i -> i != null);
|
||||
}
|
||||
|
||||
protected void invalidateObjectsContainingCache() {
|
||||
synchronized (objectsContainingCache) {
|
||||
objectsContainingCache.clear();
|
||||
}
|
||||
}
|
||||
|
||||
protected Collection<? extends TraceObjectInterface> doGetObjectsContaining(
|
||||
ObjectsContainingKey key) {
|
||||
return doParentsHaving(getValuesAt(key.snap, key.address, key.key).stream(), key.iface)
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <I extends TraceObjectInterface> Collection<I> getObjectsContaining(long snap,
|
||||
Address address, String key, Class<I> iface) {
|
||||
try (LockHold hold = trace.lockRead()) {
|
||||
return doParentsWithKeyHaving(getValuesAt(snap, address).stream(), key,
|
||||
iface).collect(Collectors.toSet());
|
||||
synchronized (objectsContainingCache) {
|
||||
return (Collection<I>) objectsContainingCache.computeIfAbsent(
|
||||
new ObjectsContainingKey(snap, address, key, iface),
|
||||
this::doGetObjectsContaining);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public <I extends TraceObjectInterface> I getObjectContaining(long snap, Address address,
|
||||
String key, Class<I> iface) {
|
||||
try (LockHold hold = trace.lockRead()) {
|
||||
return doParentsWithKeyHaving(getValuesAt(snap, address).stream(), key,
|
||||
iface).findAny().orElse(null);
|
||||
Collection<I> col = getObjectsContaining(snap, address, key, iface);
|
||||
if (col.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return col.iterator().next();
|
||||
}
|
||||
|
||||
public <I extends TraceObjectInterface> Collection<I> getObjectsIntersecting(
|
||||
Lifespan lifespan, AddressRange range, String key, Class<I> iface) {
|
||||
try (LockHold hold = trace.lockRead()) {
|
||||
return doParentsWithKeyHaving(getValuesIntersecting(lifespan, range).stream(), key,
|
||||
iface).collect(Collectors.toSet());
|
||||
return doParentsHaving(getValuesIntersecting(lifespan, range, key).stream(), iface)
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -549,7 +572,7 @@ public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager
|
|||
|
||||
public <I extends TraceObjectInterface> AddressSetView getObjectsAddressSet(long snap,
|
||||
String key, Class<I> ifaceCls, Predicate<? super I> predicate) {
|
||||
return rangeValueMap.getAddressSetView(Lifespan.at(snap), v -> {
|
||||
return valueMap.getAddressSetView(Lifespan.at(snap), v -> {
|
||||
if (!key.equals(v.getEntryKey())) {
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -74,20 +74,33 @@ public class DBTraceObjectValPath implements TraceObjectValPath {
|
|||
|
||||
@Override
|
||||
public DBTraceObjectValPath prepend(TraceObjectValue entry) {
|
||||
if (!entryList.isEmpty() && entry.getTrace() != entryList.get(0).getTrace()) {
|
||||
throw new IllegalArgumentException("All values in path must be from the same trace");
|
||||
}
|
||||
if (!(entry instanceof InternalTraceObjectValue val)) {
|
||||
throw new IllegalArgumentException("Value must be in the database");
|
||||
}
|
||||
InternalTraceObjectValue[] arr = new InternalTraceObjectValue[1 + entryList.size()];
|
||||
arr[0] = (DBTraceObjectValue) entry;
|
||||
arr[0] = val;
|
||||
for (int i = 1; i < arr.length; i++) {
|
||||
arr[i] = entryList.get(i - 1);
|
||||
}
|
||||
return new DBTraceObjectValPath(Collections.unmodifiableList(Arrays.asList(arr)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public DBTraceObjectValPath append(TraceObjectValue entry) {
|
||||
if (!entryList.isEmpty() && entry.getTrace() != entryList.get(0).getTrace()) {
|
||||
throw new IllegalArgumentException("All values in path must be from the same trace");
|
||||
}
|
||||
if (!(entry instanceof InternalTraceObjectValue val)) {
|
||||
throw new IllegalArgumentException("Value must be in the database");
|
||||
}
|
||||
InternalTraceObjectValue[] arr = new InternalTraceObjectValue[1 + entryList.size()];
|
||||
for (int i = 0; i < arr.length - 1; i++) {
|
||||
arr[i] = entryList.get(i);
|
||||
}
|
||||
arr[arr.length - 1] = (InternalTraceObjectValue) entry;
|
||||
arr[arr.length - 1] = val;
|
||||
return new DBTraceObjectValPath(Collections.unmodifiableList(Arrays.asList(arr)));
|
||||
}
|
||||
|
||||
|
|
|
@ -1,382 +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.trace.database.target;
|
||||
|
||||
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;
|
||||
|
||||
import db.*;
|
||||
import ghidra.trace.database.target.visitors.TreeTraversal;
|
||||
import ghidra.trace.database.target.visitors.TreeTraversal.Visitor;
|
||||
import ghidra.trace.model.Lifespan;
|
||||
import ghidra.trace.model.Trace;
|
||||
import ghidra.trace.model.target.*;
|
||||
import ghidra.util.LockHold;
|
||||
import ghidra.util.database.*;
|
||||
import ghidra.util.database.DBCachedObjectStoreFactory.AbstractDBFieldCodec;
|
||||
import ghidra.util.database.DBCachedObjectStoreFactory.VariantDBFieldCodec;
|
||||
import ghidra.util.database.annot.*;
|
||||
|
||||
@DBAnnotatedObjectInfo(version = 0)
|
||||
public class DBTraceObjectValue extends DBAnnotatedObject implements InternalTraceObjectValue {
|
||||
protected static final String TABLE_NAME = "ObjectValue";
|
||||
|
||||
protected static class PrimaryTriple {
|
||||
private final DBTraceObject parent;
|
||||
private final String key;
|
||||
private final long minSnap;
|
||||
|
||||
protected PrimaryTriple(DBTraceObject parent, String key, long minSnap) {
|
||||
this.parent = parent;
|
||||
this.key = Objects.requireNonNull(key);
|
||||
this.minSnap = minSnap;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "<parent=" + parent + ",key=" + key + ",minSnap=" + minSnap + ">";
|
||||
}
|
||||
|
||||
public PrimaryTriple withMinSnap(long minSnap) {
|
||||
return new PrimaryTriple(parent, key, minSnap);
|
||||
}
|
||||
}
|
||||
|
||||
public static class PrimaryTripleDBFieldCodec
|
||||
extends AbstractDBFieldCodec<PrimaryTriple, DBTraceObjectValue, BinaryField> {
|
||||
static final Charset cs = Charset.forName("UTF-8");
|
||||
|
||||
public PrimaryTripleDBFieldCodec(Class<DBTraceObjectValue> objectType, Field field,
|
||||
int column) {
|
||||
super(PrimaryTriple.class, objectType, BinaryField.class, field, column);
|
||||
}
|
||||
|
||||
protected static byte[] encode(PrimaryTriple value) {
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
protected static PrimaryTriple decode(DBTraceObjectValue ent, byte[] enc) {
|
||||
if (enc == null) {
|
||||
return null;
|
||||
}
|
||||
ByteBuffer buf = ByteBuffer.wrap(enc);
|
||||
|
||||
DBTraceObject parent =
|
||||
DBTraceObjectDBFieldCodec.decode(ent, buf.getLong() ^ Long.MIN_VALUE);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void store(PrimaryTriple value, BinaryField f) {
|
||||
f.setBinaryData(encode(value));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doStore(DBTraceObjectValue obj, DBRecord record)
|
||||
throws IllegalArgumentException, IllegalAccessException {
|
||||
record.setBinaryData(column, encode(getValue(obj)));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doLoad(DBTraceObjectValue obj, DBRecord record)
|
||||
throws IllegalArgumentException, IllegalAccessException {
|
||||
setValue(obj, decode(obj, record.getBinaryData(column)));
|
||||
}
|
||||
}
|
||||
|
||||
public static class DBTraceObjectDBFieldCodec<OV extends DBAnnotatedObject & InternalTraceObjectValue>
|
||||
extends AbstractDBFieldCodec<DBTraceObject, OV, LongField> {
|
||||
public DBTraceObjectDBFieldCodec(Class<OV> objectType, Field field,
|
||||
int column) {
|
||||
super(DBTraceObject.class, objectType, LongField.class, field, column);
|
||||
}
|
||||
|
||||
protected static long encode(DBTraceObject value) {
|
||||
return value == null ? -1 : value.getKey();
|
||||
}
|
||||
|
||||
protected static DBTraceObject decode(InternalTraceObjectValue ent, long enc) {
|
||||
return enc == -1 ? null : ent.getManager().getObjectById(enc);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void store(DBTraceObject value, LongField f) {
|
||||
f.setLongValue(encode(value));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doStore(OV obj, DBRecord record)
|
||||
throws IllegalArgumentException, IllegalAccessException {
|
||||
record.setLongValue(column, encode(getValue(obj)));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doLoad(OV obj, DBRecord record)
|
||||
throws IllegalArgumentException, IllegalAccessException {
|
||||
setValue(obj, decode(obj, record.getLongValue(column)));
|
||||
}
|
||||
}
|
||||
|
||||
static final String TRIPLE_COLUMN_NAME = "Triple";
|
||||
static final String MAX_SNAP_COLUMN_NAME = "MaxSnap";
|
||||
static final String CHILD_COLUMN_NAME = "Child";
|
||||
static final String PRIMITIVE_COLUMN_NAME = "Primitive";
|
||||
|
||||
@DBAnnotatedColumn(TRIPLE_COLUMN_NAME)
|
||||
static DBObjectColumn TRIPLE_COLUMN;
|
||||
@DBAnnotatedColumn(MAX_SNAP_COLUMN_NAME)
|
||||
static DBObjectColumn MAX_SNAP_COLUMN;
|
||||
@DBAnnotatedColumn(CHILD_COLUMN_NAME)
|
||||
static DBObjectColumn CHILD_COLUMN;
|
||||
@DBAnnotatedColumn(PRIMITIVE_COLUMN_NAME)
|
||||
static DBObjectColumn PRIMITIVE_COLUMN;
|
||||
|
||||
@DBAnnotatedField(
|
||||
column = TRIPLE_COLUMN_NAME,
|
||||
indexed = true,
|
||||
codec = PrimaryTripleDBFieldCodec.class)
|
||||
private PrimaryTriple triple;
|
||||
@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)
|
||||
private Object primitive;
|
||||
|
||||
protected final DBTraceObjectManager manager;
|
||||
|
||||
private Lifespan lifespan;
|
||||
|
||||
public DBTraceObjectValue(DBTraceObjectManager manager, DBCachedObjectStore<?> store,
|
||||
DBRecord record) {
|
||||
super(store, record);
|
||||
this.manager = manager;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void fresh(boolean created) throws IOException {
|
||||
if (created) {
|
||||
return;
|
||||
}
|
||||
lifespan = Lifespan.span(triple.minSnap, maxSnap);
|
||||
}
|
||||
|
||||
protected void set(Lifespan lifespan, DBTraceObject parent, String key, Object value) {
|
||||
this.triple = new PrimaryTriple(parent, key, lifespan.lmin());
|
||||
this.maxSnap = lifespan.lmax();
|
||||
this.lifespan = Lifespan.span(triple.minSnap, maxSnap);
|
||||
if (value instanceof TraceObject) {
|
||||
DBTraceObject child = manager.assertIsMine((TraceObject) value);
|
||||
this.child = child;
|
||||
this.primitive = null;
|
||||
}
|
||||
else {
|
||||
this.primitive = manager.validatePrimitive(value);
|
||||
this.child = null;
|
||||
}
|
||||
update(TRIPLE_COLUMN, MAX_SNAP_COLUMN, CHILD_COLUMN, PRIMITIVE_COLUMN);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getClass().getSimpleName() + ": parent=" + triple.parent + ", key=" + triple.key +
|
||||
", lifespan=" + getLifespan() + ", value=" + getValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doSetLifespan(Lifespan lifespan) {
|
||||
long minSnap = lifespan.lmin();
|
||||
if (this.triple.minSnap != minSnap) {
|
||||
this.triple = triple.withMinSnap(minSnap);
|
||||
update(TRIPLE_COLUMN);
|
||||
}
|
||||
this.maxSnap = lifespan.lmax();
|
||||
update(MAX_SNAP_COLUMN);
|
||||
this.lifespan = Lifespan.span(minSnap, maxSnap);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Trace getTrace() {
|
||||
return manager.trace;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DBTraceObjectManager getManager() {
|
||||
return manager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DBTraceObject getParent() {
|
||||
return triple == null ? null : triple.parent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getEntryKey() {
|
||||
return triple == null ? null : triple.key;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getValue() {
|
||||
try (LockHold hold = manager.trace.lockRead()) {
|
||||
return child != null ? child : primitive;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public DBTraceObject getChild() {
|
||||
return (DBTraceObject) getValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isObject() {
|
||||
return child != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DBTraceObject getChildOrNull() {
|
||||
return child;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Lifespan getLifespan() {
|
||||
try (LockHold hold = manager.trace.lockRead()) {
|
||||
return lifespan;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMinSnap(long minSnap) {
|
||||
try (LockHold hold = manager.trace.lockWrite()) {
|
||||
setLifespan(Lifespan.span(minSnap, maxSnap));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getMinSnap() {
|
||||
try (LockHold hold = manager.trace.lockRead()) {
|
||||
return triple.minSnap;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMaxSnap(long maxSnap) {
|
||||
try (LockHold hold = manager.trace.lockWrite()) {
|
||||
setLifespan(Lifespan.span(triple.minSnap, maxSnap));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getMaxSnap() {
|
||||
try (LockHold hold = manager.trace.lockRead()) {
|
||||
return maxSnap;
|
||||
}
|
||||
}
|
||||
|
||||
protected Stream<? extends TraceObjectValPath> doStreamVisitor(Lifespan span,
|
||||
Visitor visitor) {
|
||||
return TreeTraversal.INSTANCE.walkValue(visitor, this, span, null);
|
||||
}
|
||||
|
||||
protected TraceObjectKeyPath doGetCanonicalPath() {
|
||||
if (triple == null || triple.parent == null) {
|
||||
return TraceObjectKeyPath.of();
|
||||
}
|
||||
return triple.parent.getCanonicalPath().extend(triple.key);
|
||||
}
|
||||
|
||||
protected boolean doIsCanonical() {
|
||||
if (child == null) {
|
||||
return false;
|
||||
}
|
||||
if (triple.parent == null) {
|
||||
return true;
|
||||
}
|
||||
return doGetCanonicalPath().equals(child.getCanonicalPath());
|
||||
}
|
||||
|
||||
@Override
|
||||
public TraceObjectKeyPath getCanonicalPath() {
|
||||
try (LockHold hold = LockHold.lock(manager.lock.readLock())) {
|
||||
return doGetCanonicalPath();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCanonical() {
|
||||
try (LockHold hold = LockHold.lock(manager.lock.readLock())) {
|
||||
return doIsCanonical();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doDelete() {
|
||||
manager.doDeleteEdge(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete() {
|
||||
try (LockHold hold = LockHold.lock(manager.lock.writeLock())) {
|
||||
if (triple.parent == null) {
|
||||
throw new IllegalArgumentException("Cannot delete root value");
|
||||
}
|
||||
doDeleteAndEmit();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public InternalTraceObjectValue truncateOrDelete(Lifespan span) {
|
||||
try (LockHold hold = LockHold.lock(manager.lock.writeLock())) {
|
||||
if (triple.parent == null) {
|
||||
throw new IllegalArgumentException("Cannot truncate or delete root value");
|
||||
}
|
||||
return doTruncateOrDeleteAndEmitLifeChange(span);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,433 @@
|
|||
/* ###
|
||||
* 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.trace.database.target;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import db.DBRecord;
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.model.address.AddressRange;
|
||||
import ghidra.trace.database.target.visitors.TreeTraversal;
|
||||
import ghidra.trace.database.target.visitors.TreeTraversal.Visitor;
|
||||
import ghidra.trace.model.Lifespan;
|
||||
import ghidra.trace.model.Trace;
|
||||
import ghidra.trace.model.target.*;
|
||||
import ghidra.util.LockHold;
|
||||
import ghidra.util.database.DBCachedObjectStore;
|
||||
import ghidra.util.database.DBCachedObjectStoreFactory.*;
|
||||
import ghidra.util.database.DBObjectColumn;
|
||||
import ghidra.util.database.annot.*;
|
||||
import ghidra.util.database.spatial.DBTreeDataRecord;
|
||||
|
||||
@DBAnnotatedObjectInfo(version = 1)
|
||||
public class DBTraceObjectValueData
|
||||
extends DBTreeDataRecord<ValueShape, ValueBox, InternalTraceObjectValue>
|
||||
implements InternalTraceObjectValue, ValueShape {
|
||||
static final String TABLE_NAME = "ObjectValue";
|
||||
|
||||
static final String PARENT_COLUMN_NAME = "Parent"; // R*-Tree parent
|
||||
static final String OBJ_PARENT_COLUMN_NAME = "ObjParent"; // Object-Tree parent
|
||||
static final String ENTRY_KEY_COLUMN_NAME = "EntryKey";
|
||||
static final String MIN_SNAP_COLUMN_NAME = "MinSnap";
|
||||
static final String MAX_SNAP_COLUMN_NAME = "MaxSnap";
|
||||
static final String CHILD_COLUMN_NAME = "Child";
|
||||
static final String PRIMITIVE_COLUMN_NAME = "Primitive";
|
||||
|
||||
@DBAnnotatedColumn(PARENT_COLUMN_NAME)
|
||||
static DBObjectColumn PARENT_COLUMN;
|
||||
@DBAnnotatedColumn(OBJ_PARENT_COLUMN_NAME)
|
||||
static DBObjectColumn OBJ_PARENT_COLUMN;
|
||||
@DBAnnotatedColumn(ENTRY_KEY_COLUMN_NAME)
|
||||
static DBObjectColumn ENTRY_KEY_COLUMN;
|
||||
@DBAnnotatedColumn(MIN_SNAP_COLUMN_NAME)
|
||||
static DBObjectColumn MIN_SNAP_COLUMN;
|
||||
@DBAnnotatedColumn(MAX_SNAP_COLUMN_NAME)
|
||||
static DBObjectColumn MAX_SNAP_COLUMN;
|
||||
@DBAnnotatedColumn(CHILD_COLUMN_NAME)
|
||||
static DBObjectColumn CHILD_COLUMN;
|
||||
@DBAnnotatedColumn(PRIMITIVE_COLUMN_NAME)
|
||||
static DBObjectColumn PRIMITIVE_COLUMN;
|
||||
|
||||
@DBAnnotatedField(column = PARENT_COLUMN_NAME, indexed = true)
|
||||
private long parentKey;
|
||||
@DBAnnotatedField(column = OBJ_PARENT_COLUMN_NAME, codec = DBTraceObjectDBFieldCodec.class)
|
||||
private DBTraceObject objParent;
|
||||
@DBAnnotatedField(column = ENTRY_KEY_COLUMN_NAME)
|
||||
private String entryKey;
|
||||
@DBAnnotatedField(column = MIN_SNAP_COLUMN_NAME)
|
||||
private long minSnap;
|
||||
@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)
|
||||
private Object primitive;
|
||||
|
||||
protected final DBTraceObjectManager manager;
|
||||
protected final DBTraceObjectValueRStarTree tree;
|
||||
|
||||
protected ValueBox bounds;
|
||||
protected Lifespan lifespan;
|
||||
protected Address address;
|
||||
protected AddressRange range;
|
||||
|
||||
public DBTraceObjectValueData(DBTraceObjectManager manager, DBTraceObjectValueRStarTree tree,
|
||||
DBCachedObjectStore<?> store, DBRecord record) {
|
||||
super(store, record);
|
||||
this.manager = manager;
|
||||
this.tree = tree;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doSetPrimitive(Object primitive) {
|
||||
if (primitive instanceof TraceObject) {
|
||||
throw new AssertionError();
|
||||
}
|
||||
else if (primitive instanceof Address address) {
|
||||
this.address = address;
|
||||
this.range = null;
|
||||
this.primitive = RecAddress.fromAddress(address);
|
||||
}
|
||||
else if (primitive instanceof AddressRange range) {
|
||||
this.address = null;
|
||||
this.range = range;
|
||||
this.primitive = RecRange.fromRange(range);
|
||||
}
|
||||
else {
|
||||
this.address = null;
|
||||
this.range = null;
|
||||
this.primitive = primitive;
|
||||
}
|
||||
update(PRIMITIVE_COLUMN);
|
||||
}
|
||||
|
||||
protected long getObjParentKey() {
|
||||
return objParent == null ? -1 : objParent.getKey();
|
||||
}
|
||||
|
||||
protected long getObjChildKey() {
|
||||
return child == null ? -1 : child.getKey();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getAddressSpaceId() {
|
||||
if (primitive instanceof RecAddress addr) {
|
||||
return addr.spaceId();
|
||||
}
|
||||
if (primitive instanceof RecRange rng) {
|
||||
return rng.spaceId();
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getMinAddressOffset() {
|
||||
if (primitive instanceof RecAddress addr) {
|
||||
return addr.offset();
|
||||
}
|
||||
if (primitive instanceof RecRange rng) {
|
||||
return rng.min();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getMaxAddressOffset() {
|
||||
if (primitive instanceof RecAddress addr) {
|
||||
return addr.offset();
|
||||
}
|
||||
if (primitive instanceof RecRange rng) {
|
||||
return rng.max();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
protected void updateBounds() {
|
||||
long objParentKey = getObjParentKey();
|
||||
long objChildKey = getObjChildKey();
|
||||
int spaceId = getAddressSpaceId();
|
||||
bounds = new ImmutableValueBox(
|
||||
new ValueTriple(objParentKey, objChildKey, entryKey, minSnap,
|
||||
new RecAddress(spaceId, getMinAddressOffset())),
|
||||
new ValueTriple(objParentKey, objChildKey, entryKey, maxSnap,
|
||||
new RecAddress(spaceId, getMaxAddressOffset())));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void fresh(boolean created) throws IOException {
|
||||
super.fresh(created);
|
||||
if (created) {
|
||||
return;
|
||||
}
|
||||
updateBounds();
|
||||
lifespan = Lifespan.span(minSnap, maxSnap);
|
||||
if (primitive instanceof RecAddress address) {
|
||||
this.address = address.toAddress(manager.trace.getBaseAddressFactory());
|
||||
this.range = null;
|
||||
}
|
||||
else if (primitive instanceof RecRange range) {
|
||||
this.address = null;
|
||||
this.range = range.toRange(manager.trace.getBaseAddressFactory());
|
||||
}
|
||||
else {
|
||||
this.address = null;
|
||||
this.range = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Trace getTrace() {
|
||||
return manager.trace;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DBTraceObject getParent() {
|
||||
return objParent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getEntryKey() {
|
||||
return entryKey;
|
||||
}
|
||||
|
||||
protected TraceObjectKeyPath doGetCanonicalPath() {
|
||||
if (objParent == null) {
|
||||
return TraceObjectKeyPath.of();
|
||||
}
|
||||
return objParent.getCanonicalPath().extend(entryKey);
|
||||
}
|
||||
|
||||
protected boolean doIsCanonical() {
|
||||
if (child == null) {
|
||||
return false;
|
||||
}
|
||||
if (objParent == null) { // We're the root
|
||||
return true;
|
||||
}
|
||||
return doGetCanonicalPath().equals(child.getCanonicalPath());
|
||||
}
|
||||
|
||||
@Override
|
||||
public TraceObjectKeyPath getCanonicalPath() {
|
||||
try (LockHold hold = LockHold.lock(manager.lock.readLock())) {
|
||||
return doGetCanonicalPath();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCanonical() {
|
||||
try (LockHold hold = LockHold.lock(manager.lock.readLock())) {
|
||||
return doIsCanonical();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getValue() {
|
||||
try (LockHold hold = manager.trace.lockRead()) {
|
||||
if (child != null) {
|
||||
return child;
|
||||
}
|
||||
if (address != null) {
|
||||
return address;
|
||||
}
|
||||
if (range != null) {
|
||||
return range;
|
||||
}
|
||||
return child != null ? child : primitive;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public DBTraceObject getChild() {
|
||||
return (DBTraceObject) getValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isObject() {
|
||||
return child != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Lifespan getLifespan() {
|
||||
try (LockHold hold = manager.trace.lockRead()) {
|
||||
return lifespan;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMinSnap(long minSnap) {
|
||||
try (LockHold hold = manager.trace.lockWrite()) {
|
||||
setLifespan(Lifespan.span(minSnap, maxSnap));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getMinSnap() {
|
||||
try (LockHold hold = manager.trace.lockRead()) {
|
||||
return minSnap;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMaxSnap(long maxSnap) {
|
||||
try (LockHold hold = manager.trace.lockWrite()) {
|
||||
setLifespan(Lifespan.span(minSnap, maxSnap));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getMaxSnap() {
|
||||
try (LockHold hold = manager.trace.lockRead()) {
|
||||
return maxSnap;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete() {
|
||||
try (LockHold hold = LockHold.lock(manager.lock.writeLock())) {
|
||||
if (objParent == null) {
|
||||
throw new IllegalArgumentException("Cannot delete root value");
|
||||
}
|
||||
doDeleteAndEmit();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public TraceObjectValue truncateOrDelete(Lifespan span) {
|
||||
try (LockHold hold = LockHold.lock(manager.lock.writeLock())) {
|
||||
if (objParent == null) {
|
||||
throw new IllegalArgumentException("Cannot truncate or delete root value");
|
||||
}
|
||||
return doTruncateOrDeleteAndEmitLifeChange(span);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean shapeEquals(ValueShape shape) {
|
||||
if (objParent != shape.getParent()) {
|
||||
return false;
|
||||
}
|
||||
if (!Objects.equals(entryKey, shape.getEntryKey())) {
|
||||
return false;
|
||||
}
|
||||
if (!Objects.equals(lifespan, shape.getLifespan())) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setRecordValue(InternalTraceObjectValue value) {
|
||||
// Nothing. Entry is the value
|
||||
}
|
||||
|
||||
@Override
|
||||
protected InternalTraceObjectValue getRecordValue() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ValueShape getShape() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ValueBox getBounds() {
|
||||
return bounds;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setShape(ValueShape shape) {
|
||||
objParent = shape.getParent();
|
||||
child = shape.getChild();
|
||||
entryKey = shape.getEntryKey();
|
||||
minSnap = shape.getLifespan().lmin();
|
||||
maxSnap = shape.getLifespan().lmax();
|
||||
// Space/Address/Range will be set by doSetPrimitive
|
||||
update(OBJ_PARENT_COLUMN, CHILD_COLUMN, ENTRY_KEY_COLUMN, MIN_SNAP_COLUMN, MAX_SNAP_COLUMN);
|
||||
|
||||
lifespan = shape.getLifespan();
|
||||
bounds = shape.getBounds();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getParentKey() {
|
||||
return parentKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setParentKey(long parentKey) {
|
||||
this.parentKey = parentKey;
|
||||
update(PARENT_COLUMN);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String description() {
|
||||
return new ImmutableValueShape(getShape()).toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public DBTraceObjectManager getManager() {
|
||||
return manager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DBTraceObject getChildOrNull() {
|
||||
return child;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doSetLifespan(Lifespan lifespan) {
|
||||
if (minSnap == lifespan.lmin() && maxSnap == lifespan.lmax()) {
|
||||
return;
|
||||
}
|
||||
DBTraceObjectValueRStarTree tree = this.tree;
|
||||
tree.doUnparentEntry(this);
|
||||
objParent.notifyValueDeleted(this);
|
||||
if (child != null) {
|
||||
child.notifyParentValueDeleted(this);
|
||||
}
|
||||
minSnap = lifespan.lmin();
|
||||
maxSnap = lifespan.lmax();
|
||||
update(MIN_SNAP_COLUMN, MAX_SNAP_COLUMN);
|
||||
this.lifespan = lifespan;
|
||||
updateBounds();
|
||||
tree.doInsertDataEntry(this);
|
||||
objParent.notifyValueCreated(this);
|
||||
if (child != null) {
|
||||
child.notifyParentValueCreated(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doDelete() {
|
||||
objParent.notifyValueDeleted(this);
|
||||
if (child != null) {
|
||||
child.notifyParentValueDeleted(this);
|
||||
}
|
||||
manager.doDeleteEdge(this);
|
||||
}
|
||||
|
||||
protected Stream<? extends TraceObjectValPath> doStreamVisitor(Lifespan span,
|
||||
Visitor visitor) {
|
||||
return TreeTraversal.INSTANCE.walkValue(visitor, this, span, null);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,286 @@
|
|||
/* ###
|
||||
* 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.trace.database.target;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.concurrent.locks.ReadWriteLock;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import org.apache.commons.collections4.IteratorUtils;
|
||||
|
||||
import ghidra.program.model.address.*;
|
||||
import ghidra.trace.database.target.ValueSpace.AddressDimension;
|
||||
import ghidra.trace.database.target.ValueSpace.EntryKeyDimension;
|
||||
import ghidra.trace.model.Lifespan;
|
||||
import ghidra.util.*;
|
||||
import ghidra.util.database.DBCachedObjectStoreFactory.RecAddress;
|
||||
import ghidra.util.database.spatial.SpatialMap;
|
||||
|
||||
public class DBTraceObjectValueMapAddressSetView extends AbstractAddressSetView {
|
||||
|
||||
private final AddressFactory factory;
|
||||
private final ReadWriteLock lock;
|
||||
private final SpatialMap<ValueShape, InternalTraceObjectValue, TraceObjectValueQuery> map;
|
||||
private final Predicate<? super InternalTraceObjectValue> predicate;
|
||||
|
||||
/**
|
||||
* An address set view that unions all addresses where an entry satisfying the given predicate
|
||||
* exists.
|
||||
*
|
||||
* <p>
|
||||
* The caller may reduce the map given to this view. Reduction is preferable to using a
|
||||
* predicate, where possible, because reduction benefits from the index.
|
||||
*
|
||||
* @param factory the trace's address factory
|
||||
* @param lock the lock on the database
|
||||
* @param map the map
|
||||
* @param predicate a predicate to further filter entries
|
||||
*/
|
||||
public DBTraceObjectValueMapAddressSetView(AddressFactory factory, ReadWriteLock lock,
|
||||
SpatialMap<ValueShape, InternalTraceObjectValue, TraceObjectValueQuery> map,
|
||||
Predicate<? super InternalTraceObjectValue> predicate) {
|
||||
this.factory = factory;
|
||||
this.lock = lock;
|
||||
this.map = map;
|
||||
this.predicate = predicate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(Address addr) {
|
||||
try (LockHold hold = LockHold.lock(lock.readLock())) {
|
||||
for (InternalTraceObjectValue value : map
|
||||
.reduce(TraceObjectValueQuery.intersecting(Lifespan.ALL,
|
||||
new AddressRangeImpl(addr, addr)))
|
||||
.values()) {
|
||||
if (predicate.test(value)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
catch (NoSuchElementException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(Address start, Address end) {
|
||||
try (LockHold hold = LockHold.lock(lock.readLock())) {
|
||||
return super.contains(start, end);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(AddressSetView rangeSet) {
|
||||
try (LockHold hold = LockHold.lock(lock.readLock())) {
|
||||
return super.contains(rangeSet);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
try (LockHold hold = LockHold.lock(lock.readLock())) {
|
||||
for (InternalTraceObjectValue value : map.values()) {
|
||||
if (predicate.test(value)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Address getMinAddress() {
|
||||
try (LockHold hold = LockHold.lock(lock.readLock())) {
|
||||
for (Entry<ValueShape, InternalTraceObjectValue> entry : map
|
||||
.reduce(TraceObjectValueQuery.all().starting(AddressDimension.FORWARD))
|
||||
.orderedEntries()) {
|
||||
if (predicate.test(entry.getValue())) {
|
||||
return entry.getKey().getMinAddress(factory);
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Address getMaxAddress() {
|
||||
try (LockHold hold = LockHold.lock(lock.readLock())) {
|
||||
for (Entry<ValueShape, InternalTraceObjectValue> entry : map
|
||||
.reduce(TraceObjectValueQuery.all().starting(AddressDimension.BACKWARD))
|
||||
.orderedEntries()) {
|
||||
if (predicate.test(entry.getValue())) {
|
||||
return entry.getKey().getMaxAddress(factory);
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getNumAddressRanges() {
|
||||
try (LockHold hold = LockHold.lock(lock.readLock())) {
|
||||
return super.getNumAddressRanges();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public AddressRangeIterator getAddressRanges() {
|
||||
return doGetAddressRanges(AddressDimension.INSTANCE.absoluteMin(),
|
||||
AddressDimension.INSTANCE.absoluteMax(), true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AddressRangeIterator getAddressRanges(boolean forward) {
|
||||
return doGetAddressRanges(AddressDimension.INSTANCE.absoluteMin(),
|
||||
AddressDimension.INSTANCE.absoluteMax(), forward);
|
||||
}
|
||||
|
||||
protected AddressRangeIterator doGetAddressRanges(RecAddress start, RecAddress end,
|
||||
boolean forward) {
|
||||
Iterator<Entry<ValueShape, InternalTraceObjectValue>> mapIt = map
|
||||
.reduce(TraceObjectValueQuery
|
||||
.intersecting(EntryKeyDimension.INSTANCE.absoluteMin(),
|
||||
EntryKeyDimension.INSTANCE.absoluteMax(), Lifespan.ALL, start, end)
|
||||
.starting(forward ? AddressDimension.FORWARD : AddressDimension.BACKWARD))
|
||||
.orderedEntries()
|
||||
.iterator();
|
||||
Iterator<Entry<ValueShape, InternalTraceObjectValue>> fltIt =
|
||||
IteratorUtils.filteredIterator(mapIt, e -> predicate.test(e.getValue()));
|
||||
Iterator<AddressRange> rawIt =
|
||||
IteratorUtils.transformedIterator(fltIt, e -> e.getKey().getRange(factory));
|
||||
return new UnionAddressRangeIterator(rawIt, forward);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* <p>
|
||||
* Note the first range may be incomplete up when composed of connected entries, but it will at
|
||||
* least include all the ranges ahead of the given start -- or behind the given start if forward
|
||||
* is false. TODO: Fix that, just like {@link UnionAddressSetView}?
|
||||
*/
|
||||
@Override
|
||||
public AddressRangeIterator getAddressRanges(Address start, boolean forward) {
|
||||
RecAddress min = forward
|
||||
? RecAddress.fromAddress(start)
|
||||
: AddressDimension.INSTANCE.absoluteMin();
|
||||
RecAddress max = forward
|
||||
? AddressDimension.INSTANCE.absoluteMax()
|
||||
: RecAddress.fromAddress(start);
|
||||
return doGetAddressRanges(min, max, forward);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getNumAddresses() {
|
||||
try (LockHold hold = LockHold.lock(lock.readLock())) {
|
||||
return super.getNumAddresses();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean intersects(AddressSetView addrSet) {
|
||||
try (LockHold hold = LockHold.lock(lock.readLock())) {
|
||||
return super.intersects(addrSet);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean intersects(Address start, Address end) {
|
||||
try (LockHold hold = LockHold.lock(lock.readLock())) {
|
||||
return super.intersects(start, end);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public AddressSet intersect(AddressSetView view) {
|
||||
try (LockHold hold = LockHold.lock(lock.readLock())) {
|
||||
return super.intersect(view);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public AddressSet intersectRange(Address start, Address end) {
|
||||
try (LockHold hold = LockHold.lock(lock.readLock())) {
|
||||
return super.intersectRange(start, end);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public AddressSet union(AddressSetView addrSet) {
|
||||
try (LockHold hold = LockHold.lock(lock.readLock())) {
|
||||
return super.union(addrSet);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public AddressSet subtract(AddressSetView addrSet) {
|
||||
try (LockHold hold = LockHold.lock(lock.readLock())) {
|
||||
return super.subtract(addrSet);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public AddressSet xor(AddressSetView addrSet) {
|
||||
try (LockHold hold = LockHold.lock(lock.readLock())) {
|
||||
return super.xor(addrSet);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasSameAddresses(AddressSetView view) {
|
||||
try (LockHold hold = LockHold.lock(lock.readLock())) {
|
||||
return super.hasSameAddresses(view);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public AddressRange getFirstRange() {
|
||||
try (LockHold hold = LockHold.lock(lock.readLock())) {
|
||||
return super.getFirstRange();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public AddressRange getLastRange() {
|
||||
try (LockHold hold = LockHold.lock(lock.readLock())) {
|
||||
return super.getLastRange();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* <p>
|
||||
* Note that adjacent or overlapping ranges may be omitted if they don't also contain the
|
||||
* address.
|
||||
*/
|
||||
@Override
|
||||
public AddressRange getRangeContaining(Address address) {
|
||||
try (LockHold hold = LockHold.lock(lock.readLock())) {
|
||||
return super.getRangeContaining(address);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Address findFirstAddressInCommon(AddressSetView set) {
|
||||
try (LockHold hold = LockHold.lock(lock.readLock())) {
|
||||
return super.findFirstAddressInCommon(set);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,232 @@
|
|||
/* ###
|
||||
* 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.trace.database.target;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Objects;
|
||||
|
||||
import db.DBRecord;
|
||||
import ghidra.util.database.DBCachedObjectStore;
|
||||
import ghidra.util.database.DBCachedObjectStoreFactory.RecAddress;
|
||||
import ghidra.util.database.DBObjectColumn;
|
||||
import ghidra.util.database.annot.*;
|
||||
import ghidra.util.database.spatial.DBTreeNodeRecord;
|
||||
|
||||
@DBAnnotatedObjectInfo(version = 0)
|
||||
public class DBTraceObjectValueNode extends DBTreeNodeRecord<ValueBox> implements ValueBox {
|
||||
protected static final byte NODE_TYPE_MASK = 3;
|
||||
protected static final int NODE_TYPE_SHIFT = 6;
|
||||
protected static final byte NODE_TYPE_CLEAR = (byte) ~(NODE_TYPE_MASK << NODE_TYPE_SHIFT);
|
||||
|
||||
protected static final byte CHILD_COUNT_MASK = 0x3f;
|
||||
protected static final int CHILD_COUNT_SHIFT = 0;
|
||||
protected static final byte CHILD_COUNT_CLEAR = (byte) ~(CHILD_COUNT_MASK << CHILD_COUNT_SHIFT);
|
||||
|
||||
// Please note the overloaded use of "parent" here:
|
||||
static final String PARENT_COLUMN_NAME = "Parent"; // parent in the R*-Tree
|
||||
static final String MIN_PARENT_KEY_COLUMN_NAME = "MinParentKey"; // parent in the object tree
|
||||
static final String MAX_PARENT_KEY_COLUMN_NAME = "MaxParentKey"; // parent in the object tree
|
||||
static final String MIN_CHILD_KEY_COLUMN_NAME = "MinChildKey";
|
||||
static final String MAX_CHILD_KEY_COLUMN_NAME = "MaxChildKey";
|
||||
static final String MIN_ENTRY_KEY_COLUMN_NAME = "MinEntryKey";
|
||||
static final String MAX_ENTRY_KEY_COLUMN_NAME = "MaxEntryKey";
|
||||
static final String MIN_SNAP_COLUMN_NAME = "MinSnap";
|
||||
static final String MAX_SNAP_COLUMN_NAME = "MaxSnap";
|
||||
static final String MIN_SPACE_COLUMN_NAME = "MinSpace";
|
||||
static final String MAX_SPACE_COLUMN_NAME = "MaxSpace";
|
||||
static final String MIN_ADDRESS_COLUMN_NAME = "MinAddress";
|
||||
static final String MAX_ADDRESS_COLUMN_NAME = "MaxAddress";
|
||||
static final String TYPE_AND_CHILD_COUNT_COLUMN_NAME = "Type/ChildCount";
|
||||
static final String DATA_COUNT_COLUMN_NAME = "DataCount";
|
||||
|
||||
@DBAnnotatedColumn(PARENT_COLUMN_NAME)
|
||||
static DBObjectColumn PARENT_COLUMN;
|
||||
@DBAnnotatedColumn(MIN_PARENT_KEY_COLUMN_NAME)
|
||||
static DBObjectColumn MIN_PARENT_KEY_COLUMN;
|
||||
@DBAnnotatedColumn(MAX_PARENT_KEY_COLUMN_NAME)
|
||||
static DBObjectColumn MAX_PARENT_KEY_COLUMN;
|
||||
@DBAnnotatedColumn(MIN_CHILD_KEY_COLUMN_NAME)
|
||||
static DBObjectColumn MIN_CHILD_KEY_COLUMN;
|
||||
@DBAnnotatedColumn(MAX_CHILD_KEY_COLUMN_NAME)
|
||||
static DBObjectColumn MAX_CHILD_KEY_COLUMN;
|
||||
@DBAnnotatedColumn(MIN_ENTRY_KEY_COLUMN_NAME)
|
||||
static DBObjectColumn MIN_ENTRY_KEY_COLUMN;
|
||||
@DBAnnotatedColumn(MAX_ENTRY_KEY_COLUMN_NAME)
|
||||
static DBObjectColumn MAX_ENTRY_KEY_COLUMN;
|
||||
@DBAnnotatedColumn(MIN_SNAP_COLUMN_NAME)
|
||||
static DBObjectColumn MIN_SNAP_COLUMN;
|
||||
@DBAnnotatedColumn(MAX_SNAP_COLUMN_NAME)
|
||||
static DBObjectColumn MAX_SNAP_COLUMN;
|
||||
@DBAnnotatedColumn(MIN_SPACE_COLUMN_NAME)
|
||||
static DBObjectColumn MIN_SPACE_COLUMN;
|
||||
@DBAnnotatedColumn(MAX_SPACE_COLUMN_NAME)
|
||||
static DBObjectColumn MAX_SPACE_COLUMN;
|
||||
@DBAnnotatedColumn(MIN_ADDRESS_COLUMN_NAME)
|
||||
static DBObjectColumn MIN_ADDRESS_COLUMN;
|
||||
@DBAnnotatedColumn(MAX_ADDRESS_COLUMN_NAME)
|
||||
static DBObjectColumn MAX_ADDRESS_COLUMN;
|
||||
@DBAnnotatedColumn(TYPE_AND_CHILD_COUNT_COLUMN_NAME)
|
||||
static DBObjectColumn TYPE_AND_CHILD_COUNT_COLUMN;
|
||||
@DBAnnotatedColumn(DATA_COUNT_COLUMN_NAME)
|
||||
static DBObjectColumn DATA_COUNT_COLUMN;
|
||||
|
||||
@DBAnnotatedField(column = PARENT_COLUMN_NAME, indexed = true)
|
||||
private long parentKey;
|
||||
@DBAnnotatedField(column = MIN_PARENT_KEY_COLUMN_NAME)
|
||||
private long minObjParentKey;
|
||||
@DBAnnotatedField(column = MAX_PARENT_KEY_COLUMN_NAME)
|
||||
private long maxObjParentKey;
|
||||
@DBAnnotatedField(column = MIN_CHILD_KEY_COLUMN_NAME)
|
||||
private long minObjChildKey;
|
||||
@DBAnnotatedField(column = MAX_CHILD_KEY_COLUMN_NAME)
|
||||
private long maxObjChildKey;
|
||||
@DBAnnotatedField(column = MIN_ENTRY_KEY_COLUMN_NAME)
|
||||
private String minEntryKey;
|
||||
@DBAnnotatedField(column = MAX_ENTRY_KEY_COLUMN_NAME)
|
||||
private String maxEntryKey;
|
||||
@DBAnnotatedField(column = MIN_SNAP_COLUMN_NAME)
|
||||
private long minSnap;
|
||||
@DBAnnotatedField(column = MAX_SNAP_COLUMN_NAME)
|
||||
private long maxSnap;
|
||||
@DBAnnotatedField(column = MIN_SPACE_COLUMN_NAME)
|
||||
private int minSpace;
|
||||
@DBAnnotatedField(column = MAX_SPACE_COLUMN_NAME)
|
||||
private int maxSpace;
|
||||
@DBAnnotatedField(column = MIN_ADDRESS_COLUMN_NAME)
|
||||
private long minAddress;
|
||||
@DBAnnotatedField(column = MAX_ADDRESS_COLUMN_NAME)
|
||||
private long maxAddress;
|
||||
@DBAnnotatedField(column = TYPE_AND_CHILD_COUNT_COLUMN_NAME)
|
||||
private byte typeAndChildCount;
|
||||
@DBAnnotatedField(column = DATA_COUNT_COLUMN_NAME)
|
||||
private int dataCount;
|
||||
|
||||
protected final DBTraceObjectValueRStarTree tree;
|
||||
|
||||
private ValueTriple lCorner;
|
||||
private ValueTriple uCorner;
|
||||
|
||||
public DBTraceObjectValueNode(DBTraceObjectValueRStarTree tree, DBCachedObjectStore<?> store,
|
||||
DBRecord record) {
|
||||
super(store, record);
|
||||
this.tree = tree;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void fresh(boolean created) throws IOException {
|
||||
super.fresh(created);
|
||||
if (created) {
|
||||
return;
|
||||
}
|
||||
lCorner = new ValueTriple(minObjParentKey, minObjChildKey, minEntryKey, minSnap,
|
||||
new RecAddress(minSpace, minAddress));
|
||||
uCorner = new ValueTriple(maxObjParentKey, maxObjChildKey, maxEntryKey, maxSnap,
|
||||
new RecAddress(maxSpace, maxAddress));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ValueTriple lCorner() {
|
||||
return Objects.requireNonNull(lCorner);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ValueTriple uCorner() {
|
||||
return Objects.requireNonNull(uCorner);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected NodeType getType() {
|
||||
return NodeType.VALUES.get((typeAndChildCount >> NODE_TYPE_SHIFT) & NODE_TYPE_MASK);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setType(NodeType type) {
|
||||
typeAndChildCount =
|
||||
(byte) (typeAndChildCount & NODE_TYPE_CLEAR | (type.ordinal() << NODE_TYPE_SHIFT));
|
||||
update(TYPE_AND_CHILD_COUNT_COLUMN);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getChildCount() {
|
||||
return (typeAndChildCount >> CHILD_COUNT_SHIFT) & CHILD_COUNT_MASK;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setChildCount(int childCount) {
|
||||
assert (childCount & CHILD_COUNT_MASK) == childCount;
|
||||
typeAndChildCount =
|
||||
(byte) (typeAndChildCount & CHILD_COUNT_CLEAR | (childCount << CHILD_COUNT_SHIFT));
|
||||
update(TYPE_AND_CHILD_COUNT_COLUMN);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getDataCount() {
|
||||
return dataCount;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setDataCount(int dataCount) {
|
||||
this.dataCount = dataCount;
|
||||
update(DATA_COUNT_COLUMN);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ValueBox getShape() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setShape(ValueBox shape) {
|
||||
minObjParentKey = shape.lCorner().parentKey();
|
||||
maxObjParentKey = shape.uCorner().parentKey();
|
||||
minObjChildKey = shape.lCorner().childKey();
|
||||
maxObjChildKey = shape.uCorner().childKey();
|
||||
minEntryKey = shape.lCorner().entryKey();
|
||||
maxEntryKey = shape.uCorner().entryKey();
|
||||
minSnap = shape.lCorner().snap();
|
||||
maxSnap = shape.uCorner().snap();
|
||||
minSpace = shape.lCorner().address().spaceId();
|
||||
maxSpace = shape.uCorner().address().spaceId();
|
||||
minAddress = shape.lCorner().address().offset();
|
||||
maxAddress = shape.uCorner().address().offset();
|
||||
update(
|
||||
MIN_PARENT_KEY_COLUMN, MAX_PARENT_KEY_COLUMN,
|
||||
MIN_CHILD_KEY_COLUMN, MAX_CHILD_KEY_COLUMN,
|
||||
MIN_ENTRY_KEY_COLUMN, MAX_ENTRY_KEY_COLUMN,
|
||||
MIN_SNAP_COLUMN, MAX_SNAP_COLUMN,
|
||||
MIN_SPACE_COLUMN, MAX_SPACE_COLUMN,
|
||||
MIN_ADDRESS_COLUMN, MAX_ADDRESS_COLUMN);
|
||||
|
||||
lCorner = shape.lCorner();
|
||||
uCorner = shape.uCorner();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ValueBox getBounds() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getParentKey() {
|
||||
return parentKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setParentKey(long parentKey) {
|
||||
this.parentKey = parentKey;
|
||||
update(PARENT_COLUMN);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,127 @@
|
|||
/* ###
|
||||
* 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.trace.database.target;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
import java.util.concurrent.locks.ReadWriteLock;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import db.DBRecord;
|
||||
import ghidra.program.model.address.AddressFactory;
|
||||
import ghidra.program.model.address.AddressSetView;
|
||||
import ghidra.trace.model.Lifespan;
|
||||
import ghidra.util.database.*;
|
||||
import ghidra.util.database.spatial.AbstractConstraintsTree;
|
||||
import ghidra.util.database.spatial.hyper.AbstractHyperRStarTree;
|
||||
import ghidra.util.database.spatial.hyper.EuclideanHyperSpace;
|
||||
import ghidra.util.exception.VersionException;
|
||||
|
||||
public class DBTraceObjectValueRStarTree extends AbstractHyperRStarTree< //
|
||||
ValueTriple, //
|
||||
ValueShape, DBTraceObjectValueData, //
|
||||
ValueBox, DBTraceObjectValueNode, //
|
||||
InternalTraceObjectValue, TraceObjectValueQuery> {
|
||||
|
||||
public static class DBTraceObjectValueMap extends AsSpatialMap<ValueShape, //
|
||||
DBTraceObjectValueData, ValueBox, InternalTraceObjectValue, TraceObjectValueQuery> {
|
||||
|
||||
private final AddressFactory factory;
|
||||
private final ReadWriteLock lock;
|
||||
|
||||
public DBTraceObjectValueMap(AbstractConstraintsTree<ValueShape, DBTraceObjectValueData, //
|
||||
ValueBox, ?, InternalTraceObjectValue, TraceObjectValueQuery> tree,
|
||||
TraceObjectValueQuery query, AddressFactory factory, ReadWriteLock lock) {
|
||||
super(tree, query);
|
||||
this.factory = factory;
|
||||
this.lock = lock;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DBTraceObjectValueMap reduce(TraceObjectValueQuery andQuery) {
|
||||
return new DBTraceObjectValueMap(this.tree,
|
||||
this.query == null ? andQuery : this.query.and(andQuery), this.factory, this.lock);
|
||||
}
|
||||
|
||||
public AddressSetView getAddressSetView(Lifespan at,
|
||||
Predicate<? super InternalTraceObjectValue> predicate) {
|
||||
return new DBTraceObjectValueMapAddressSetView(factory, lock, this, predicate);
|
||||
}
|
||||
}
|
||||
|
||||
protected final DBTraceObjectManager manager;
|
||||
protected final DBCachedObjectIndex<Long, DBTraceObjectValueNode> nodesByParent;
|
||||
protected final DBCachedObjectIndex<Long, DBTraceObjectValueData> dataByParent;
|
||||
|
||||
public DBTraceObjectValueRStarTree(DBTraceObjectManager manager,
|
||||
DBCachedObjectStoreFactory storeFactory, String tableName,
|
||||
EuclideanHyperSpace<ValueTriple, ValueBox> space,
|
||||
Class<DBTraceObjectValueData> dataType, Class<DBTraceObjectValueNode> nodeType,
|
||||
boolean upgradeable, int maxChildren) throws VersionException, IOException {
|
||||
super(storeFactory, tableName, space, dataType, nodeType, upgradeable, maxChildren);
|
||||
this.manager = manager;
|
||||
this.nodesByParent = nodeStore.getIndex(long.class, DBTraceObjectValueNode.PARENT_COLUMN);
|
||||
this.dataByParent = dataStore.getIndex(long.class, DBTraceObjectValueNode.PARENT_COLUMN);
|
||||
|
||||
init();
|
||||
}
|
||||
|
||||
protected DBCachedObjectStore<DBTraceObjectValueData> getDataStore() {
|
||||
return dataStore;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doUnparentEntry(DBTraceObjectValueData data) {
|
||||
super.doUnparentEntry(data);
|
||||
}
|
||||
|
||||
protected void doInsertDataEntry(DBTraceObjectValueData entry) {
|
||||
super.doInsert(entry, new LevelInfo(leafLevel));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doDeleteEntry(DBTraceObjectValueData data) {
|
||||
super.doDeleteEntry(data);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected DBTraceObjectValueData createDataEntry(
|
||||
DBCachedObjectStore<DBTraceObjectValueData> store, DBRecord record) {
|
||||
return new DBTraceObjectValueData(manager, this, store, record);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected DBTraceObjectValueNode createNodeEntry(
|
||||
DBCachedObjectStore<DBTraceObjectValueNode> store, DBRecord record) {
|
||||
return new DBTraceObjectValueNode(this, store, record);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Collection<DBTraceObjectValueNode> getNodeChildrenOf(long parentKey) {
|
||||
return nodesByParent.get(parentKey);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Collection<DBTraceObjectValueData> getDataChildrenOf(long parentKey) {
|
||||
return dataByParent.get(parentKey);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DBTraceObjectValueMap asSpatialMap() {
|
||||
return new DBTraceObjectValueMap(this, null, manager.trace.getBaseAddressFactory(),
|
||||
manager.lock);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
/* ###
|
||||
* 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.trace.database.target;
|
||||
|
||||
record ImmutableValueBox(ValueTriple lCorner, ValueTriple uCorner) implements ValueBox {
|
||||
public ImmutableValueBox(ValueBox box) {
|
||||
this(box.lCorner(), box.uCorner());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,123 @@
|
|||
/* ###
|
||||
* 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.trace.database.target;
|
||||
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.model.address.AddressRange;
|
||||
import ghidra.trace.model.Lifespan;
|
||||
import ghidra.util.database.DBCachedObjectStoreFactory.RecAddress;
|
||||
|
||||
public record ImmutableValueShape(DBTraceObject parent, DBTraceObject child, String entryKey,
|
||||
Lifespan lifespan, int addressSpaceId, long minAddressOffset, long maxAddressOffset)
|
||||
implements ValueShape {
|
||||
|
||||
public static int getAddressSpaceId(Object value) {
|
||||
if (value instanceof Address address) {
|
||||
return address.getAddressSpace().getSpaceID();
|
||||
}
|
||||
if (value instanceof AddressRange range) {
|
||||
return range.getAddressSpace().getSpaceID();
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
public static long getMinAddressOffset(Object value) {
|
||||
if (value instanceof Address address) {
|
||||
return address.getOffset();
|
||||
}
|
||||
if (value instanceof AddressRange range) {
|
||||
return range.getMinAddress().getOffset();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static long getMaxAddressOffset(Object value) {
|
||||
if (value instanceof Address address) {
|
||||
return address.getOffset();
|
||||
}
|
||||
if (value instanceof AddressRange range) {
|
||||
return range.getMaxAddress().getOffset();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
public ImmutableValueShape(DBTraceObject parent, Object value, String entryKey,
|
||||
Lifespan lifespan) {
|
||||
this(parent, value instanceof DBTraceObject child ? child : null, entryKey, lifespan,
|
||||
getAddressSpaceId(value), getMinAddressOffset(value), getMaxAddressOffset(value));
|
||||
}
|
||||
|
||||
public ImmutableValueShape(ValueShape shape) {
|
||||
this(shape.getParent(), shape.getChildOrNull(), shape.getEntryKey(), shape.getLifespan(),
|
||||
shape.getAddressSpaceId(), shape.getMinAddressOffset(), shape.getMaxAddressOffset());
|
||||
}
|
||||
|
||||
@Override
|
||||
public ValueBox getBounds() {
|
||||
long parentKey = parent == null ? -1 : parent.getKey();
|
||||
long childKey = child == null ? -1 : child.getKey();
|
||||
return new ImmutableValueBox(
|
||||
new ValueTriple(parentKey, childKey, entryKey, lifespan.lmin(),
|
||||
new RecAddress(addressSpaceId, minAddressOffset)),
|
||||
new ValueTriple(parentKey, childKey, entryKey, lifespan.lmax(),
|
||||
new RecAddress(addressSpaceId, maxAddressOffset)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String description() {
|
||||
return toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public DBTraceObject getParent() {
|
||||
return parent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DBTraceObject getChild() {
|
||||
return child;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DBTraceObject getChildOrNull() {
|
||||
return child;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getEntryKey() {
|
||||
return entryKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Lifespan getLifespan() {
|
||||
return lifespan;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getAddressSpaceId() {
|
||||
return addressSpaceId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getMinAddressOffset() {
|
||||
return minAddressOffset;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getMaxAddressOffset() {
|
||||
return maxAddressOffset;
|
||||
}
|
||||
}
|
|
@ -98,6 +98,8 @@ interface InternalTraceObjectValue extends TraceObjectValue {
|
|||
protected abstract InternalTraceObjectValue create(Lifespan range, Object value);
|
||||
}
|
||||
|
||||
void doSetPrimitive(Object primitive);
|
||||
|
||||
DBTraceObjectManager getManager();
|
||||
|
||||
/**
|
||||
|
@ -143,7 +145,7 @@ interface InternalTraceObjectValue extends TraceObjectValue {
|
|||
protected Iterable<InternalTraceObjectValue> getIntersecting(Long lower,
|
||||
Long upper) {
|
||||
Collection<InternalTraceObjectValue> col = Collections.unmodifiableCollection(
|
||||
getParent().doGetValues(lower, upper, getEntryKey()));
|
||||
getParent().doGetValues(Lifespan.span(lower, upper), getEntryKey(), true));
|
||||
return IterableUtils.filteredIterable(col, v -> v != keep);
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,182 @@
|
|||
/* ###
|
||||
* 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.trace.database.target;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.model.address.AddressRange;
|
||||
import ghidra.trace.database.target.ValueSpace.*;
|
||||
import ghidra.trace.model.Lifespan;
|
||||
import ghidra.trace.model.target.TraceObjectKeyPath;
|
||||
import ghidra.util.database.DBCachedObjectStoreFactory.RecAddress;
|
||||
import ghidra.util.database.spatial.hyper.AbstractHyperBoxQuery;
|
||||
import ghidra.util.database.spatial.hyper.HyperDirection;
|
||||
|
||||
public class TraceObjectValueQuery
|
||||
extends AbstractHyperBoxQuery<ValueTriple, ValueShape, ValueBox, TraceObjectValueQuery> {
|
||||
|
||||
public TraceObjectValueQuery(ValueBox ls, ValueBox us, HyperDirection direction) {
|
||||
super(ls, us, ls.space(), direction);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean testData(ValueShape shape) {
|
||||
ValueBox bounds = shape.getBounds();
|
||||
if (!ls.contains(bounds.lCorner())) {
|
||||
return false;
|
||||
}
|
||||
if (!us.contains(bounds.uCorner())) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected TraceObjectValueQuery create(ValueBox ir1, ValueBox ir2,
|
||||
HyperDirection newDirection) {
|
||||
return new TraceObjectValueQuery(ir1, ir2, newDirection);
|
||||
}
|
||||
|
||||
public static TraceObjectValueQuery all() {
|
||||
return AbstractHyperBoxQuery.intersecting(ValueSpace.FULL, HyperDirection.DEFAULT,
|
||||
TraceObjectValueQuery::new);
|
||||
}
|
||||
|
||||
public static TraceObjectValueQuery canonicalParents(DBTraceObject child, Lifespan lifespan) {
|
||||
Objects.requireNonNull(child);
|
||||
TraceObjectKeyPath path = child.getCanonicalPath();
|
||||
String entryKey = path.isRoot() ? "" : path.key();
|
||||
long childKey = child.getKey();
|
||||
return AbstractHyperBoxQuery.intersecting(
|
||||
new ImmutableValueBox(
|
||||
new ValueTriple(
|
||||
ParentKeyDimension.INSTANCE.absoluteMin(),
|
||||
childKey, entryKey, lifespan.lmin(),
|
||||
AddressDimension.INSTANCE.absoluteMin()),
|
||||
new ValueTriple(
|
||||
ParentKeyDimension.INSTANCE.absoluteMax(),
|
||||
childKey, entryKey, lifespan.lmax(),
|
||||
AddressDimension.INSTANCE.absoluteMax())),
|
||||
HyperDirection.DEFAULT, TraceObjectValueQuery::new);
|
||||
}
|
||||
|
||||
public static TraceObjectValueQuery parents(DBTraceObject child, Lifespan lifespan) {
|
||||
Objects.requireNonNull(child);
|
||||
long childKey = child.getKey();
|
||||
return AbstractHyperBoxQuery.intersecting(
|
||||
new ImmutableValueBox(
|
||||
new ValueTriple(
|
||||
ParentKeyDimension.INSTANCE.absoluteMin(),
|
||||
childKey,
|
||||
EntryKeyDimension.INSTANCE.absoluteMin(),
|
||||
lifespan.lmin(),
|
||||
AddressDimension.INSTANCE.absoluteMin()),
|
||||
new ValueTriple(
|
||||
ParentKeyDimension.INSTANCE.absoluteMax(),
|
||||
childKey,
|
||||
EntryKeyDimension.INSTANCE.absoluteMax(),
|
||||
lifespan.lmax(),
|
||||
AddressDimension.INSTANCE.absoluteMax())),
|
||||
HyperDirection.DEFAULT, TraceObjectValueQuery::new);
|
||||
}
|
||||
|
||||
public static TraceObjectValueQuery values(DBTraceObject parent, Lifespan lifespan) {
|
||||
Objects.requireNonNull(parent);
|
||||
long parentKey = parent.getKey();
|
||||
return AbstractHyperBoxQuery.intersecting(
|
||||
new ImmutableValueBox(
|
||||
new ValueTriple(
|
||||
parentKey,
|
||||
ChildKeyDimension.INSTANCE.absoluteMin(),
|
||||
EntryKeyDimension.INSTANCE.absoluteMin(),
|
||||
lifespan.lmin(),
|
||||
AddressDimension.INSTANCE.absoluteMin()),
|
||||
new ValueTriple(
|
||||
parentKey,
|
||||
ChildKeyDimension.INSTANCE.absoluteMax(),
|
||||
EntryKeyDimension.INSTANCE.absoluteMax(),
|
||||
lifespan.lmax(),
|
||||
AddressDimension.INSTANCE.absoluteMax())),
|
||||
HyperDirection.DEFAULT, TraceObjectValueQuery::new);
|
||||
}
|
||||
|
||||
public static TraceObjectValueQuery values(DBTraceObject parent, String minKey, String maxKey,
|
||||
Lifespan lifespan) {
|
||||
Objects.requireNonNull(parent);
|
||||
long parentKey = parent.getKey();
|
||||
return AbstractHyperBoxQuery.intersecting(
|
||||
new ImmutableValueBox(
|
||||
new ValueTriple(
|
||||
parentKey,
|
||||
ChildKeyDimension.INSTANCE.absoluteMin(),
|
||||
minKey,
|
||||
lifespan.lmin(),
|
||||
AddressDimension.INSTANCE.absoluteMin()),
|
||||
new ValueTriple(
|
||||
parentKey,
|
||||
ChildKeyDimension.INSTANCE.absoluteMax(),
|
||||
maxKey,
|
||||
lifespan.lmax(),
|
||||
AddressDimension.INSTANCE.absoluteMax())),
|
||||
HyperDirection.DEFAULT, TraceObjectValueQuery::new);
|
||||
}
|
||||
|
||||
public static TraceObjectValueQuery intersecting(String minKey, String maxKey,
|
||||
Lifespan lifespan, RecAddress minAddress, RecAddress maxAddress) {
|
||||
return AbstractHyperBoxQuery.intersecting(
|
||||
new ImmutableValueBox(
|
||||
new ValueTriple(
|
||||
ParentKeyDimension.INSTANCE.absoluteMin(),
|
||||
ChildKeyDimension.INSTANCE.absoluteMin(),
|
||||
minKey, lifespan.lmin(), minAddress),
|
||||
new ValueTriple(
|
||||
ParentKeyDimension.INSTANCE.absoluteMax(),
|
||||
ChildKeyDimension.INSTANCE.absoluteMax(),
|
||||
maxKey, lifespan.lmax(), maxAddress)),
|
||||
HyperDirection.DEFAULT, TraceObjectValueQuery::new);
|
||||
}
|
||||
|
||||
public static TraceObjectValueQuery intersecting(String minKey, String maxKey,
|
||||
Lifespan lifespan, AddressRange range) {
|
||||
return intersecting(minKey, maxKey, lifespan,
|
||||
RecAddress.fromAddress(range.getMinAddress()),
|
||||
RecAddress.fromAddress(range.getMaxAddress()));
|
||||
}
|
||||
|
||||
public static TraceObjectValueQuery intersecting(Lifespan lifespan, AddressRange range) {
|
||||
return intersecting(EntryKeyDimension.INSTANCE.absoluteMin(),
|
||||
EntryKeyDimension.INSTANCE.absoluteMax(), lifespan, range);
|
||||
}
|
||||
|
||||
public static TraceObjectValueQuery at(String entryKey, long snap, Address address) {
|
||||
return AbstractHyperBoxQuery.intersecting(
|
||||
new ImmutableValueBox(
|
||||
new ValueTriple(
|
||||
ParentKeyDimension.INSTANCE.absoluteMin(),
|
||||
ChildKeyDimension.INSTANCE.absoluteMin(),
|
||||
entryKey,
|
||||
snap,
|
||||
RecAddress.fromAddress(address)),
|
||||
new ValueTriple(
|
||||
ParentKeyDimension.INSTANCE.absoluteMax(),
|
||||
ChildKeyDimension.INSTANCE.absoluteMax(),
|
||||
entryKey,
|
||||
snap,
|
||||
RecAddress.fromAddress(address))),
|
||||
HyperDirection.DEFAULT, TraceObjectValueQuery::new);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
/* ###
|
||||
* 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.trace.database.target;
|
||||
|
||||
import ghidra.util.database.spatial.hyper.HyperBox;
|
||||
|
||||
public interface ValueBox extends HyperBox<ValueTriple, ValueBox> {
|
||||
@Override
|
||||
default ValueBox immutable(ValueTriple lCorner, ValueTriple uCorner) {
|
||||
return new ImmutableValueBox(lCorner, uCorner);
|
||||
}
|
||||
|
||||
@Override
|
||||
default ValueBox getBounds() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
default ValueSpace space() {
|
||||
return ValueSpace.INSTANCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
default String description() {
|
||||
return new ImmutableValueBox(this).toString();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
/* ###
|
||||
* 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.trace.database.target;
|
||||
|
||||
import ghidra.program.model.address.*;
|
||||
import ghidra.trace.model.Lifespan;
|
||||
import ghidra.util.database.spatial.BoundedShape;
|
||||
|
||||
public interface ValueShape extends BoundedShape<ValueBox> {
|
||||
DBTraceObject getParent();
|
||||
|
||||
DBTraceObject getChild();
|
||||
|
||||
DBTraceObject getChildOrNull();
|
||||
|
||||
String getEntryKey();
|
||||
|
||||
Lifespan getLifespan();
|
||||
|
||||
/**
|
||||
* If the value is an address or range, the id of the address space
|
||||
*
|
||||
* @return the space id, or -1 for non-address value
|
||||
*/
|
||||
int getAddressSpaceId();
|
||||
|
||||
long getMinAddressOffset();
|
||||
|
||||
long getMaxAddressOffset();
|
||||
|
||||
default Address getMinAddress(AddressFactory factory) {
|
||||
int spaceId = getAddressSpaceId();
|
||||
if (spaceId == -1) {
|
||||
return null;
|
||||
}
|
||||
AddressSpace space = factory.getAddressSpace(spaceId);
|
||||
return space.getAddress(getMinAddressOffset());
|
||||
}
|
||||
|
||||
default Address getMaxAddress(AddressFactory factory) {
|
||||
int spaceId = getAddressSpaceId();
|
||||
if (spaceId == -1) {
|
||||
return null;
|
||||
}
|
||||
AddressSpace space = factory.getAddressSpace(spaceId);
|
||||
return space.getAddress(getMaxAddressOffset());
|
||||
}
|
||||
|
||||
default AddressRange getRange(AddressFactory factory) {
|
||||
Address min = getMinAddress(factory);
|
||||
if (min == null) {
|
||||
return null;
|
||||
}
|
||||
return new AddressRangeImpl(min, getMaxAddress(factory));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,213 @@
|
|||
/* ###
|
||||
* 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.trace.database.target;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.util.List;
|
||||
|
||||
import ghidra.util.database.DBCachedObjectStoreFactory.RecAddress;
|
||||
import ghidra.util.database.spatial.hyper.*;
|
||||
|
||||
enum ValueSpace implements EuclideanHyperSpace<ValueTriple, ValueBox> {
|
||||
INSTANCE;
|
||||
|
||||
enum ParentKeyDimension implements ULongDimension<ValueTriple, ValueBox> {
|
||||
INSTANCE;
|
||||
|
||||
public static final HyperDirection FORWARD = new HyperDirection(0, true);
|
||||
public static final HyperDirection BACKWARD = new HyperDirection(0, false);
|
||||
|
||||
@Override
|
||||
public Long value(ValueTriple point) {
|
||||
return point.parentKey();
|
||||
}
|
||||
}
|
||||
|
||||
enum ChildKeyDimension implements ULongDimension<ValueTriple, ValueBox> {
|
||||
INSTANCE;
|
||||
|
||||
public static final HyperDirection FORWARD = new HyperDirection(1, true);
|
||||
public static final HyperDirection BACKWARD = new HyperDirection(1, false);
|
||||
|
||||
@Override
|
||||
public Long value(ValueTriple point) {
|
||||
return point.childKey();
|
||||
}
|
||||
}
|
||||
|
||||
enum EntryKeyDimension implements StringDimension<ValueTriple, ValueBox> {
|
||||
INSTANCE;
|
||||
|
||||
public static final HyperDirection FORWARD = new HyperDirection(2, true);
|
||||
public static final HyperDirection BACKWARD = new HyperDirection(2, false);
|
||||
|
||||
@Override
|
||||
public String value(ValueTriple point) {
|
||||
return point.entryKey();
|
||||
}
|
||||
}
|
||||
|
||||
enum SnapDimension implements LongDimension<ValueTriple, ValueBox> {
|
||||
INSTANCE;
|
||||
|
||||
public static final HyperDirection FORWARD = new HyperDirection(3, true);
|
||||
public static final HyperDirection BACKWARD = new HyperDirection(3, false);
|
||||
|
||||
@Override
|
||||
public Long value(ValueTriple point) {
|
||||
return point.snap();
|
||||
}
|
||||
}
|
||||
|
||||
enum AddressDimension implements Dimension<RecAddress, ValueTriple, ValueBox> {
|
||||
INSTANCE;
|
||||
|
||||
static final BigInteger MASK_32 = BigInteger.ONE.shiftLeft(64).subtract(BigInteger.ONE);
|
||||
static final BigInteger MASK_64 = BigInteger.ONE.shiftLeft(64).subtract(BigInteger.ONE);
|
||||
|
||||
public static final HyperDirection FORWARD = new HyperDirection(4, true);
|
||||
public static final HyperDirection BACKWARD = new HyperDirection(4, false);
|
||||
|
||||
@Override
|
||||
public RecAddress value(ValueTriple point) {
|
||||
return point.address();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compare(RecAddress a, RecAddress b) {
|
||||
int c = Integer.compareUnsigned(a.spaceId(), b.spaceId());
|
||||
if (c != 0) {
|
||||
return c;
|
||||
}
|
||||
return Long.compareUnsigned(a.offset(), b.offset());
|
||||
}
|
||||
|
||||
@Override
|
||||
public double distance(RecAddress a, RecAddress b) {
|
||||
double result = b.spaceId() - a.spaceId();
|
||||
result *= Math.pow(2, 64);
|
||||
result += (b.offset() - a.offset());
|
||||
return result;
|
||||
}
|
||||
|
||||
static BigInteger addrToBigInt(RecAddress a) {
|
||||
return BigInteger.valueOf(a.spaceId())
|
||||
.and(MASK_32)
|
||||
.shiftLeft(64)
|
||||
.add(BigInteger.valueOf(a.offset()).and(MASK_64));
|
||||
}
|
||||
|
||||
static RecAddress bigIntToAddr(BigInteger i) {
|
||||
return new RecAddress(i.shiftRight(64).intValue(), i.longValue());
|
||||
}
|
||||
|
||||
@Override
|
||||
public RecAddress mid(RecAddress a, RecAddress b) {
|
||||
BigInteger biA = addrToBigInt(a);
|
||||
BigInteger biB = addrToBigInt(b);
|
||||
BigInteger biMid = biA.add((biB.subtract(biA)).shiftRight(1));
|
||||
return bigIntToAddr(biMid);
|
||||
}
|
||||
|
||||
private static final RecAddress MIN = new RecAddress(0, 0);
|
||||
private static final RecAddress MAX = new RecAddress(-1, -1);
|
||||
|
||||
@Override
|
||||
public RecAddress absoluteMin() {
|
||||
return MIN;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RecAddress absoluteMax() {
|
||||
return MAX;
|
||||
}
|
||||
}
|
||||
|
||||
static final List<Dimension<?, ValueTriple, ValueBox>> DIMENSIONS = List.of(
|
||||
ParentKeyDimension.INSTANCE,
|
||||
ChildKeyDimension.INSTANCE,
|
||||
EntryKeyDimension.INSTANCE,
|
||||
SnapDimension.INSTANCE,
|
||||
AddressDimension.INSTANCE);
|
||||
|
||||
static final ValueBox FULL = new ImmutableValueBox(
|
||||
new ValueTriple(
|
||||
ParentKeyDimension.INSTANCE.absoluteMin(),
|
||||
ChildKeyDimension.INSTANCE.absoluteMin(),
|
||||
EntryKeyDimension.INSTANCE.absoluteMin(),
|
||||
SnapDimension.INSTANCE.absoluteMin(),
|
||||
AddressDimension.INSTANCE.absoluteMin()),
|
||||
new ValueTriple(
|
||||
ParentKeyDimension.INSTANCE.absoluteMax(),
|
||||
ChildKeyDimension.INSTANCE.absoluteMax(),
|
||||
EntryKeyDimension.INSTANCE.absoluteMax(),
|
||||
SnapDimension.INSTANCE.absoluteMax(),
|
||||
AddressDimension.INSTANCE.absoluteMax()));
|
||||
|
||||
@Override
|
||||
public List<Dimension<?, ValueTriple, ValueBox>> getDimensions() {
|
||||
return DIMENSIONS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ValueBox getFull() {
|
||||
return FULL;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ValueTriple boxCenter(ValueBox box) {
|
||||
return new ValueTriple(
|
||||
ParentKeyDimension.INSTANCE.boxMid(box),
|
||||
ChildKeyDimension.INSTANCE.boxMid(box),
|
||||
EntryKeyDimension.INSTANCE.boxMid(box),
|
||||
SnapDimension.INSTANCE.boxMid(box),
|
||||
AddressDimension.INSTANCE.boxMid(box));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ValueBox boxUnionBounds(ValueBox a, ValueBox b) {
|
||||
ValueTriple lc = new ValueTriple(
|
||||
ParentKeyDimension.INSTANCE.unionLower(a, b),
|
||||
ChildKeyDimension.INSTANCE.unionLower(a, b),
|
||||
EntryKeyDimension.INSTANCE.unionLower(a, b),
|
||||
SnapDimension.INSTANCE.unionLower(a, b),
|
||||
AddressDimension.INSTANCE.unionLower(a, b));
|
||||
ValueTriple uc = new ValueTriple(
|
||||
ParentKeyDimension.INSTANCE.unionUpper(a, b),
|
||||
ChildKeyDimension.INSTANCE.unionUpper(a, b),
|
||||
EntryKeyDimension.INSTANCE.unionUpper(a, b),
|
||||
SnapDimension.INSTANCE.unionUpper(a, b),
|
||||
AddressDimension.INSTANCE.unionUpper(a, b));
|
||||
return new ImmutableValueBox(lc, uc);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ValueBox boxIntersection(ValueBox a, ValueBox b) {
|
||||
ValueTriple lc = new ValueTriple(
|
||||
ParentKeyDimension.INSTANCE.intersectionLower(a, b),
|
||||
ChildKeyDimension.INSTANCE.intersectionLower(a, b),
|
||||
EntryKeyDimension.INSTANCE.intersectionLower(a, b),
|
||||
SnapDimension.INSTANCE.intersectionLower(a, b),
|
||||
AddressDimension.INSTANCE.intersectionLower(a, b));
|
||||
ValueTriple uc = new ValueTriple(
|
||||
ParentKeyDimension.INSTANCE.intersectionUpper(a, b),
|
||||
ChildKeyDimension.INSTANCE.intersectionUpper(a, b),
|
||||
EntryKeyDimension.INSTANCE.intersectionUpper(a, b),
|
||||
SnapDimension.INSTANCE.intersectionUpper(a, b),
|
||||
AddressDimension.INSTANCE.intersectionUpper(a, b));
|
||||
return new ImmutableValueBox(lc, uc);
|
||||
}
|
||||
}
|
|
@ -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.trace.database.target;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import ghidra.util.database.DBCachedObjectStoreFactory.RecAddress;
|
||||
import ghidra.util.database.spatial.hyper.HyperPoint;
|
||||
|
||||
record ValueTriple(long parentKey, long childKey, String entryKey, long snap, RecAddress address)
|
||||
implements HyperPoint {
|
||||
public ValueTriple(long parentKey, long childKey, String entryKey, long snap,
|
||||
RecAddress address) {
|
||||
this.parentKey = parentKey;
|
||||
this.childKey = childKey;
|
||||
this.entryKey = entryKey;
|
||||
this.snap = snap;
|
||||
this.address = Objects.requireNonNull(address);
|
||||
}
|
||||
}
|
|
@ -50,6 +50,6 @@ public enum AllPathsVisitor implements SpanIntersectingVisitor {
|
|||
@Override
|
||||
public Stream<? extends TraceObjectValue> continueValues(TraceObject object,
|
||||
Lifespan span, TraceObjectValPath path) {
|
||||
return object.getParents().stream().filter(v -> !path.contains(v));
|
||||
return object.getParents(span).stream().filter(v -> !path.contains(v));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -59,7 +59,7 @@ public class AncestorsRelativeVisitor implements SpanIntersectingVisitor {
|
|||
return Stream.empty();
|
||||
}
|
||||
|
||||
return object.getParents()
|
||||
return object.getParents(span)
|
||||
.stream()
|
||||
.filter(v -> PathPredicates.anyMatches(prevKeys, v.getEntryKey()));
|
||||
}
|
||||
|
|
|
@ -58,6 +58,6 @@ public class AncestorsRootVisitor implements SpanIntersectingVisitor {
|
|||
* Can't really filter the parent values by predicates here, since the predicates are not
|
||||
* matching relative paths, but canonical paths.
|
||||
*/
|
||||
return object.getParents().stream().filter(v -> !path.contains(v));
|
||||
return object.getParents(span).stream().filter(v -> !path.contains(v));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -73,7 +73,7 @@ public class CanonicalSuccessorsRelativeVisitor implements Visitor {
|
|||
|
||||
Stream<? extends TraceObjectValue> attrStream;
|
||||
if (nextKeys.contains("")) {
|
||||
attrStream = object.getAttributes().stream().filter(TraceObjectValue::isCanonical);
|
||||
attrStream = object.getAttributes(span).stream().filter(TraceObjectValue::isCanonical);
|
||||
}
|
||||
else {
|
||||
attrStream = Stream.empty();
|
||||
|
@ -81,7 +81,7 @@ public class CanonicalSuccessorsRelativeVisitor implements Visitor {
|
|||
|
||||
Stream<? extends TraceObjectValue> elemStream;
|
||||
if (nextKeys.contains("[]")) {
|
||||
elemStream = object.getElements().stream().filter(TraceObjectValue::isCanonical);
|
||||
elemStream = object.getElements(span).stream().filter(TraceObjectValue::isCanonical);
|
||||
}
|
||||
else {
|
||||
elemStream = Stream.empty();
|
||||
|
|
|
@ -61,9 +61,7 @@ public class SuccessorsRelativeVisitor implements SpanIntersectingVisitor {
|
|||
|
||||
Stream<? extends TraceObjectValue> attrStream;
|
||||
if (nextKeys.contains("")) {
|
||||
attrStream = object.getAttributes()
|
||||
.stream()
|
||||
.filter(v -> span.intersects(v.getLifespan()));
|
||||
attrStream = object.getAttributes(span).stream();
|
||||
}
|
||||
else {
|
||||
attrStream = Stream.empty();
|
||||
|
@ -71,9 +69,7 @@ public class SuccessorsRelativeVisitor implements SpanIntersectingVisitor {
|
|||
|
||||
Stream<? extends TraceObjectValue> elemStream;
|
||||
if (nextKeys.contains("[]")) {
|
||||
elemStream = object.getElements()
|
||||
.stream()
|
||||
.filter(v -> span.intersects(v.getLifespan()));
|
||||
elemStream = object.getElements(span).stream();
|
||||
}
|
||||
else {
|
||||
elemStream = Stream.empty();
|
||||
|
|
|
@ -395,6 +395,12 @@ public sealed interface Lifespan extends Span<Long, Lifespan>, Iterable<Long> {
|
|||
* An interval tree implementing {@link MutableLifeSet}
|
||||
*/
|
||||
public class DefaultLifeSet extends DefaultSpanSet<Long, Lifespan> implements MutableLifeSet {
|
||||
public static DefaultLifeSet copyOf(LifeSet set) {
|
||||
DefaultLifeSet copy = new DefaultLifeSet();
|
||||
copy.addAll(set);
|
||||
return copy;
|
||||
}
|
||||
|
||||
public DefaultLifeSet() {
|
||||
super(Lifespan.DOMAIN);
|
||||
}
|
||||
|
|
|
@ -16,28 +16,23 @@
|
|||
package ghidra.trace.model;
|
||||
|
||||
import ghidra.framework.model.*;
|
||||
import ghidra.util.TimedMsg;
|
||||
|
||||
public class TraceDomainObjectListener extends TypedEventDispatcher
|
||||
implements DomainObjectListener {
|
||||
|
||||
@Override
|
||||
public void domainObjectChanged(DomainObjectChangedEvent ev) {
|
||||
//TimedMsg.info(this, "Handing (" + this + "): " + ev);
|
||||
if (restoredHandler != null && ev.containsEvent(DomainObject.DO_OBJECT_RESTORED)) {
|
||||
for (DomainObjectChangeRecord rec : ev) {
|
||||
if (rec.getEventType() == DomainObject.DO_OBJECT_RESTORED) {
|
||||
restoredHandler.accept(rec);
|
||||
TimedMsg.debug(this, " Done: OBJECT_RESTORED");
|
||||
return;
|
||||
}
|
||||
}
|
||||
throw new AssertionError();
|
||||
}
|
||||
//Map<String, Integer> CountsByType = new TreeMap<>();
|
||||
for (DomainObjectChangeRecord rec : ev) {
|
||||
handleChangeRecord(rec);
|
||||
}
|
||||
//TimedMsg.info(this, " Done: " + CountsByType);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -202,18 +202,19 @@ public interface TraceObject extends TraceUniqueObject {
|
|||
<I extends TraceObjectInterface> I queryInterface(Class<I> ifClass);
|
||||
|
||||
/**
|
||||
* Get all values whose child is this object
|
||||
* Get all values intersecting the given span and whose child is this object
|
||||
*
|
||||
* @param span the span
|
||||
* @return the parent values
|
||||
*/
|
||||
Collection<? extends TraceObjectValue> getParents();
|
||||
Collection<? extends TraceObjectValue> getParents(Lifespan span);
|
||||
|
||||
/**
|
||||
* Get all values (elements and attributes) of this object
|
||||
* Get all values (elements and attributes) of this object intersecting the given span
|
||||
*
|
||||
* @return the values
|
||||
*/
|
||||
Collection<? extends TraceObjectValue> getValues();
|
||||
Collection<? extends TraceObjectValue> getValues(Lifespan span);
|
||||
|
||||
/**
|
||||
* Get values with the given key intersecting the given span
|
||||
|
@ -236,18 +237,18 @@ public interface TraceObject extends TraceUniqueObject {
|
|||
boolean forward);
|
||||
|
||||
/**
|
||||
* Get all elements of this object
|
||||
* Get all elements of this object intersecting the given span
|
||||
*
|
||||
* @return the element values
|
||||
*/
|
||||
Collection<? extends TraceObjectValue> getElements();
|
||||
Collection<? extends TraceObjectValue> getElements(Lifespan span);
|
||||
|
||||
/**
|
||||
* Get all attributes of this object
|
||||
* Get all attributes of this object intersecting the given span
|
||||
*
|
||||
* @return the attribute values
|
||||
*/
|
||||
Collection<? extends TraceObjectValue> getAttributes();
|
||||
Collection<? extends TraceObjectValue> getAttributes(Lifespan span);
|
||||
|
||||
/**
|
||||
* Get the value for the given snap and key
|
||||
|
|
|
@ -139,10 +139,23 @@ public interface TraceObjectManager {
|
|||
*
|
||||
* @param span the span that desired values lifespans must intersect
|
||||
* @param range the range that desired address-ranged values must intersect
|
||||
* @param entryKey the entry key if a single one should be matched, or null for any
|
||||
* @return the collection of values
|
||||
*/
|
||||
Collection<? extends TraceObjectValue> getValuesIntersecting(Lifespan span,
|
||||
AddressRange range);
|
||||
AddressRange range, String entryKey);
|
||||
|
||||
/**
|
||||
* Get all address-ranged values intersecting the given span and address range
|
||||
*
|
||||
* @param span the span that desired values lifespans must intersect
|
||||
* @param range the range that desired address-ranged values must intersect
|
||||
* @return the collection of values
|
||||
*/
|
||||
default Collection<? extends TraceObjectValue> getValuesIntersecting(Lifespan span,
|
||||
AddressRange range) {
|
||||
return getValuesIntersecting(span, range, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all interfaces of the given type in the database
|
||||
|
|
|
@ -54,7 +54,7 @@ public enum TraceObjectInterfaceUtils {
|
|||
public static void setLifespan(Class<? extends TraceObjectInterface> traceIf,
|
||||
TraceObject object, Lifespan lifespan) throws DuplicateNameException {
|
||||
try (LockHold hold = object.getTrace().lockWrite()) {
|
||||
for (TraceObjectValue val : object.getParents()) {
|
||||
for (TraceObjectValue val : object.getParents(Lifespan.ALL)) {
|
||||
if (val.isCanonical() && !val.isDeleted()) {
|
||||
val.setLifespan(lifespan, ConflictResolution.DENY);
|
||||
}
|
||||
|
|
|
@ -18,8 +18,10 @@ package ghidra.trace.database.target;
|
|||
import static org.junit.Assert.*;
|
||||
|
||||
import java.io.File;
|
||||
import java.math.BigInteger;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
@ -31,6 +33,7 @@ import ghidra.dbg.target.schema.TargetObjectSchema.SchemaName;
|
|||
import ghidra.dbg.target.schema.XmlSchemaContext;
|
||||
import ghidra.dbg.util.PathPredicates;
|
||||
import ghidra.dbg.util.PathUtils;
|
||||
import ghidra.program.model.address.*;
|
||||
import ghidra.test.AbstractGhidraHeadlessIntegrationTest;
|
||||
import ghidra.trace.database.ToyDBTraceBuilder;
|
||||
import ghidra.trace.model.Lifespan;
|
||||
|
@ -111,22 +114,22 @@ public class DBTraceObjectManagerTest extends AbstractGhidraHeadlessIntegrationT
|
|||
TraceObjectKeyPath pathTargets = TraceObjectKeyPath.of("Targets");
|
||||
targetContainer = manager.createObject(pathTargets);
|
||||
root.setAttribute(Lifespan.nowOn(0), "Targets", targetContainer);
|
||||
dumpStore(manager.valueStore);
|
||||
dumpStore(manager.valueTree.getDataStore());
|
||||
|
||||
for (int i = 0; i < targetCount; i++) {
|
||||
Lifespan lifespan = Lifespan.nowOn(i);
|
||||
TraceObject target = manager.createObject(pathTargets.index(i));
|
||||
target.setAttribute(Lifespan.ALL, "self", target);
|
||||
dumpStore(manager.valueStore);
|
||||
dumpStore(manager.valueTree.getDataStore());
|
||||
targetContainer.setElement(lifespan, i, target);
|
||||
dumpStore(manager.valueStore);
|
||||
dumpStore(manager.valueTree.getDataStore());
|
||||
targets.add(target);
|
||||
root.setAttribute(lifespan, "curTarget", target);
|
||||
dumpStore(manager.valueStore);
|
||||
dumpStore(manager.valueTree.getDataStore());
|
||||
}
|
||||
|
||||
root.setValue(Lifespan.ALL, "anAttribute", "A primitive string");
|
||||
dumpStore(manager.valueStore);
|
||||
dumpStore(manager.valueTree.getDataStore());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -268,8 +271,8 @@ public class DBTraceObjectManagerTest extends AbstractGhidraHeadlessIntegrationT
|
|||
TraceObjectValue rangeVal =
|
||||
root.setValue(Lifespan.nowOn(0), "a", b.range(0x1000, 0x1fff));
|
||||
|
||||
assertTrue(root.getValues().contains(rangeVal));
|
||||
assertFalse(targetContainer.getValues().contains(rangeVal));
|
||||
assertTrue(root.getValues(Lifespan.at(0)).contains(rangeVal));
|
||||
assertFalse(targetContainer.getValues(Lifespan.ALL).contains(rangeVal));
|
||||
assertEquals(rangeVal, root.getValue(0, "a"));
|
||||
assertNull(root.getValue(0, "b"));
|
||||
|
||||
|
@ -292,10 +295,14 @@ public class DBTraceObjectManagerTest extends AbstractGhidraHeadlessIntegrationT
|
|||
|
||||
assertEquals(Set.of(rangeVal),
|
||||
Set.copyOf(manager.getValuesIntersecting(Lifespan.ALL, b.range(0, -1))));
|
||||
assertEquals(Set.of(rangeVal),
|
||||
Set.copyOf(manager.getValuesIntersecting(Lifespan.ALL, b.range(0, -1), "a")));
|
||||
assertEquals(Set.of(),
|
||||
Set.copyOf(manager.getValuesIntersecting(Lifespan.toNow(-1), b.range(0, -1))));
|
||||
assertEquals(Set.of(),
|
||||
Set.copyOf(manager.getValuesIntersecting(Lifespan.ALL, b.range(0, 0xfff))));
|
||||
assertEquals(Set.of(),
|
||||
Set.copyOf(manager.getValuesIntersecting(Lifespan.ALL, b.range(0, -1), "b")));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -444,32 +451,33 @@ public class DBTraceObjectManagerTest extends AbstractGhidraHeadlessIntegrationT
|
|||
public void testGetParents() {
|
||||
populateModel(3);
|
||||
|
||||
assertEquals(1, root.getParents().size());
|
||||
assertEquals(root, Unique.assertOne(targetContainer.getParents()).getParent());
|
||||
assertEquals(3, targets.get(0).getParents().size()); // curTarget, targetContainer, self
|
||||
assertEquals(1, root.getParents(Lifespan.ALL).size());
|
||||
assertEquals(root, Unique.assertOne(targetContainer.getParents(Lifespan.ALL)).getParent());
|
||||
assertEquals(3, targets.get(0).getParents(Lifespan.ALL).size());
|
||||
// curTarget, targetContainer, self
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetValues() {
|
||||
populateModel(3);
|
||||
|
||||
assertEquals(3, targetContainer.getValues().size());
|
||||
assertEquals(3, targetContainer.getValues(Lifespan.ALL).size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetElements() {
|
||||
populateModel(3);
|
||||
|
||||
assertEquals(0, root.getElements().size());
|
||||
assertEquals(3, targetContainer.getElements().size());
|
||||
assertEquals(0, root.getElements(Lifespan.ALL).size());
|
||||
assertEquals(3, targetContainer.getElements(Lifespan.ALL).size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetAttributes() {
|
||||
populateModel(3);
|
||||
|
||||
assertEquals(5, root.getAttributes().size()); // Targets, curTarget(x3), string
|
||||
assertEquals(0, targetContainer.getAttributes().size());
|
||||
assertEquals(5, root.getAttributes(Lifespan.ALL).size()); // Targets, curTarget(x3), string
|
||||
assertEquals(0, targetContainer.getAttributes(Lifespan.ALL).size());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -610,7 +618,7 @@ public class DBTraceObjectManagerTest extends AbstractGhidraHeadlessIntegrationT
|
|||
|
||||
assertEquals(valA, root.setValue(Lifespan.span(-10, -1), "a", 1));
|
||||
assertEquals(Lifespan.span(-10, 9), valA.getLifespan());
|
||||
assertEquals(1, root.getValues().size());
|
||||
assertEquals(1, root.getValues(Lifespan.ALL).size());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -624,7 +632,7 @@ public class DBTraceObjectManagerTest extends AbstractGhidraHeadlessIntegrationT
|
|||
assertEquals(valA,
|
||||
root.setValue(Lifespan.span(-10, -1), "a", b.range(0x1000, 0x1fff)));
|
||||
assertEquals(Lifespan.span(-10, 9), valA.getLifespan());
|
||||
assertEquals(1, root.getValues().size());
|
||||
assertEquals(1, root.getValues(Lifespan.ALL).size());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -636,7 +644,7 @@ public class DBTraceObjectManagerTest extends AbstractGhidraHeadlessIntegrationT
|
|||
|
||||
assertEquals(valA, root.setValue(Lifespan.span(10, 19), "a", 1));
|
||||
assertEquals(Lifespan.span(0, 19), valA.getLifespan());
|
||||
assertEquals(1, root.getValues().size());
|
||||
assertEquals(1, root.getValues(Lifespan.ALL).size());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -648,7 +656,7 @@ public class DBTraceObjectManagerTest extends AbstractGhidraHeadlessIntegrationT
|
|||
|
||||
assertEquals(valA, root.setValue(Lifespan.span(-5, 4), "a", 1));
|
||||
assertEquals(Lifespan.span(-5, 9), valA.getLifespan());
|
||||
assertEquals(1, root.getValues().size());
|
||||
assertEquals(1, root.getValues(Lifespan.ALL).size());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -660,7 +668,7 @@ public class DBTraceObjectManagerTest extends AbstractGhidraHeadlessIntegrationT
|
|||
|
||||
assertEquals(valA, root.setValue(Lifespan.span(5, 14), "a", 1));
|
||||
assertEquals(Lifespan.span(0, 14), valA.getLifespan());
|
||||
assertEquals(1, root.getValues().size());
|
||||
assertEquals(1, root.getValues(Lifespan.ALL).size());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -672,7 +680,7 @@ public class DBTraceObjectManagerTest extends AbstractGhidraHeadlessIntegrationT
|
|||
|
||||
assertEquals(valA, root.setValue(Lifespan.span(0, 9), "a", 1));
|
||||
assertEquals(Lifespan.span(0, 9), valA.getLifespan());
|
||||
assertEquals(1, root.getValues().size());
|
||||
assertEquals(1, root.getValues(Lifespan.ALL).size());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -684,11 +692,11 @@ public class DBTraceObjectManagerTest extends AbstractGhidraHeadlessIntegrationT
|
|||
|
||||
assertEquals(valA, root.setValue(Lifespan.at(5), "a", 1));
|
||||
assertEquals(Lifespan.span(0, 9), valA.getLifespan());
|
||||
assertEquals(1, root.getValues().size());
|
||||
assertEquals(1, root.getValues(Lifespan.ALL).size());
|
||||
|
||||
assertEquals(valA, root.setValue(Lifespan.span(-5, 14), "a", 1));
|
||||
assertEquals(Lifespan.span(-5, 14), valA.getLifespan());
|
||||
assertEquals(1, root.getValues().size());
|
||||
assertEquals(1, root.getValues(Lifespan.ALL).size());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -700,11 +708,11 @@ public class DBTraceObjectManagerTest extends AbstractGhidraHeadlessIntegrationT
|
|||
|
||||
assertEquals(valA, root.setValue(Lifespan.span(0, 5), "a", 1));
|
||||
assertEquals(Lifespan.span(0, 9), valA.getLifespan());
|
||||
assertEquals(1, root.getValues().size());
|
||||
assertEquals(1, root.getValues(Lifespan.ALL).size());
|
||||
|
||||
assertEquals(valA, root.setValue(Lifespan.span(0, 14), "a", 1));
|
||||
assertEquals(Lifespan.span(0, 14), valA.getLifespan());
|
||||
assertEquals(1, root.getValues().size());
|
||||
assertEquals(1, root.getValues(Lifespan.ALL).size());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -716,11 +724,11 @@ public class DBTraceObjectManagerTest extends AbstractGhidraHeadlessIntegrationT
|
|||
|
||||
assertEquals(valA, root.setValue(Lifespan.span(5, 9), "a", 1));
|
||||
assertEquals(Lifespan.span(0, 9), valA.getLifespan());
|
||||
assertEquals(1, root.getValues().size());
|
||||
assertEquals(1, root.getValues(Lifespan.ALL).size());
|
||||
|
||||
assertEquals(valA, root.setValue(Lifespan.span(-5, 9), "a", 1));
|
||||
assertEquals(Lifespan.span(-5, 9), valA.getLifespan());
|
||||
assertEquals(1, root.getValues().size());
|
||||
assertEquals(1, root.getValues(Lifespan.ALL).size());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -731,12 +739,12 @@ public class DBTraceObjectManagerTest extends AbstractGhidraHeadlessIntegrationT
|
|||
TraceObjectValue valA = root.setValue(Lifespan.span(0, 9), "a", 1);
|
||||
TraceObjectValue valB = root.setValue(Lifespan.span(20, 29), "a", 1);
|
||||
assertNotSame(valA, valB);
|
||||
assertEquals(2, root.getValues().size());
|
||||
assertEquals(2, root.getValues(Lifespan.ALL).size());
|
||||
|
||||
assertEquals(valA, root.setValue(Lifespan.span(10, 19), "a", 1));
|
||||
assertEquals(Lifespan.span(0, 29), valA.getLifespan());
|
||||
assertTrue(valB.isDeleted());
|
||||
assertEquals(1, root.getValues().size());
|
||||
assertEquals(1, root.getValues(Lifespan.ALL).size());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -825,13 +833,13 @@ public class DBTraceObjectManagerTest extends AbstractGhidraHeadlessIntegrationT
|
|||
try (Transaction tx = b.startTransaction()) {
|
||||
root = manager.createRootObject(ctx.getSchema(new SchemaName("Session"))).getChild();
|
||||
assertNull(root.setValue(Lifespan.span(0, 9), "a", null));
|
||||
assertEquals(0, root.getValues().size());
|
||||
assertEquals(0, root.getValues(Lifespan.ALL).size());
|
||||
|
||||
assertNotNull(root.setValue(Lifespan.span(0, 9), "a", 1));
|
||||
assertEquals(1, root.getValues().size());
|
||||
assertEquals(1, root.getValues(Lifespan.ALL).size());
|
||||
|
||||
assertNull(root.setValue(Lifespan.at(5), "a", null));
|
||||
assertEquals(2, root.getValues().size());
|
||||
assertEquals(2, root.getValues(Lifespan.ALL).size());
|
||||
|
||||
assertEquals(List.of(Lifespan.span(0, 4), Lifespan.span(6, 9)),
|
||||
root.getOrderedValues(Lifespan.ALL, "a", true)
|
||||
|
@ -845,10 +853,10 @@ public class DBTraceObjectManagerTest extends AbstractGhidraHeadlessIntegrationT
|
|||
try (Transaction tx = b.startTransaction()) {
|
||||
root = manager.createRootObject(ctx.getSchema(new SchemaName("Session"))).getChild();
|
||||
assertNotNull(root.setValue(Lifespan.span(0, 9), "a", 1));
|
||||
assertEquals(1, root.getValues().size());
|
||||
assertEquals(1, root.getValues(Lifespan.ALL).size());
|
||||
|
||||
assertNull(root.setValue(Lifespan.span(0, 9), "a", null));
|
||||
assertEquals(0, root.getValues().size());
|
||||
assertEquals(0, root.getValues(Lifespan.ALL).size());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -879,7 +887,7 @@ public class DBTraceObjectManagerTest extends AbstractGhidraHeadlessIntegrationT
|
|||
// Delete a leaf
|
||||
TraceObject t1 = targets.get(1);
|
||||
assertFalse(t1.isDeleted());
|
||||
assertEquals(3, targetContainer.getValues().size());
|
||||
assertEquals(3, targetContainer.getValues(Lifespan.ALL).size());
|
||||
assertEquals(t1, Unique.assertOne(
|
||||
manager.getObjectsByPath(Lifespan.ALL, TraceObjectKeyPath.parse("Targets[1]"))));
|
||||
assertEquals(t1, t1.getAttribute(1, "self").getValue());
|
||||
|
@ -890,8 +898,8 @@ public class DBTraceObjectManagerTest extends AbstractGhidraHeadlessIntegrationT
|
|||
}
|
||||
|
||||
assertTrue(t1.isDeleted());
|
||||
assertTrue(t1.getParents().isEmpty());
|
||||
assertEquals(2, targetContainer.getValues().size());
|
||||
assertTrue(t1.getParents(Lifespan.ALL).isEmpty());
|
||||
assertEquals(2, targetContainer.getValues(Lifespan.ALL).size());
|
||||
assertEquals(0,
|
||||
manager.getObjectsByPath(Lifespan.ALL, TraceObjectKeyPath.parse("Targets[1]")).count());
|
||||
assertNull(t1.getAttribute(2, "self"));
|
||||
|
@ -901,13 +909,14 @@ public class DBTraceObjectManagerTest extends AbstractGhidraHeadlessIntegrationT
|
|||
TraceObject t0 = targets.get(0);
|
||||
assertEquals(2,
|
||||
manager.getObjectsByPath(Lifespan.ALL, TraceObjectKeyPath.parse("Targets[]")).count());
|
||||
assertTrue(t0.getParents().stream().anyMatch(v -> v.getParent() == targetContainer));
|
||||
assertEquals(2, targetContainer.getValues().size());
|
||||
assertTrue(
|
||||
t0.getParents(Lifespan.ALL).stream().anyMatch(v -> v.getParent() == targetContainer));
|
||||
assertEquals(2, targetContainer.getValues(Lifespan.ALL).size());
|
||||
|
||||
b.trace.undo();
|
||||
b.trace.redo();
|
||||
|
||||
assertEquals(2, targetContainer.getValues().size());
|
||||
assertEquals(2, targetContainer.getValues(Lifespan.ALL).size());
|
||||
|
||||
try (Transaction tx = b.startTransaction()) {
|
||||
targetContainer.delete();
|
||||
|
@ -916,7 +925,8 @@ public class DBTraceObjectManagerTest extends AbstractGhidraHeadlessIntegrationT
|
|||
assertEquals(0,
|
||||
manager.getObjectsByPath(Lifespan.ALL, TraceObjectKeyPath.parse("Targets[]")).count());
|
||||
assertFalse(t0.isDeleted());
|
||||
assertFalse(t0.getParents().stream().anyMatch(v -> v.getParent() == targetContainer));
|
||||
assertFalse(
|
||||
t0.getParents(Lifespan.ALL).stream().anyMatch(v -> v.getParent() == targetContainer));
|
||||
// A little odd, but allows branch to be replaced and successors restored later
|
||||
assertEquals(t0, root.getValue(0, "curTarget").getValue());
|
||||
}
|
||||
|
@ -1093,4 +1103,76 @@ public class DBTraceObjectManagerTest extends AbstractGhidraHeadlessIntegrationT
|
|||
assertTrue(hiddenOutside.getCanonicalParent(0).isHidden());
|
||||
}
|
||||
}
|
||||
|
||||
protected String randomIdentifier(Random random, int length) {
|
||||
StringBuilder sb = new StringBuilder(length);
|
||||
while (sb.length() < length) {
|
||||
char c = (char) random.nextInt();
|
||||
boolean isValid = sb.isEmpty()
|
||||
? Character.isJavaIdentifierStart(c)
|
||||
: Character.isJavaIdentifierPart(c);
|
||||
if (isValid) {
|
||||
sb.append(c);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
protected Address randomAddress(Random random) {
|
||||
List<AddressSpace> spaces = Stream.of(b.trace.getBaseAddressFactory().getAllAddressSpaces())
|
||||
.filter(s -> s.isMemorySpace() || s.isRegisterSpace())
|
||||
.toList();
|
||||
AddressSpace space = spaces.get(random.nextInt(spaces.size()));
|
||||
while (true) {
|
||||
BigInteger offset = new BigInteger(space.getSize(), random);
|
||||
try {
|
||||
return space.getAddress(offset.longValue());
|
||||
}
|
||||
catch (AddressOutOfBoundsException e) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected Lifespan randomLifespan(Random random) {
|
||||
boolean isNowOn = random.nextInt(4) < 3;
|
||||
if (isNowOn) {
|
||||
return Lifespan.nowOn(random.nextLong(10000));
|
||||
}
|
||||
int length = random.nextInt(10000);
|
||||
long start = random.nextLong(10000);
|
||||
return Lifespan.span(start, start + length);
|
||||
}
|
||||
|
||||
protected void assertSameResult(Collection<TraceObjectValue> values, Lifespan span,
|
||||
AddressRange range) {
|
||||
List<TraceObjectValue> expected = values.stream()
|
||||
.filter(v -> v.getLifespan().intersects(span) && range.contains(v.castValue()))
|
||||
.toList();
|
||||
List<TraceObjectValue> actual =
|
||||
List.copyOf(b.trace.getObjectManager().getValuesIntersecting(span, range));
|
||||
assertEquals(expected, actual);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testManyAddressValuesAcrossSpaces() {
|
||||
Random random = new Random();
|
||||
List<TraceObjectValue> values = new ArrayList<>();
|
||||
try (Transaction tx = b.startTransaction()) {
|
||||
TraceObjectValue rootVal =
|
||||
manager.createRootObject(ctx.getSchema(new SchemaName("Session")));
|
||||
root = rootVal.getChild();
|
||||
|
||||
for (int i = 0; i < 1000; i++) {
|
||||
String key = randomIdentifier(random, 6);
|
||||
Address addr = randomAddress(random);
|
||||
Lifespan lifespan = randomLifespan(random);
|
||||
|
||||
values.add(root.setAttribute(lifespan, key, addr));
|
||||
}
|
||||
}
|
||||
|
||||
b.trace.getObjectManager().getValuesIntersecting(Lifespan.ALL, b.range(0, -1));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,140 @@
|
|||
/* ###
|
||||
* 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.trace.database.target;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import ghidra.trace.database.target.ValueSpace.AddressDimension;
|
||||
import ghidra.trace.database.target.ValueSpace.EntryKeyDimension;
|
||||
import ghidra.util.database.DBCachedObjectStoreFactory.RecAddress;
|
||||
|
||||
public class ValueSpaceTest {
|
||||
@Test
|
||||
public void testAddressDistance() {
|
||||
AddressDimension dim = AddressDimension.INSTANCE;
|
||||
|
||||
assertEquals(0.0, dim.distance(new RecAddress(0, 0), new RecAddress(0, 0)), 0.0);
|
||||
|
||||
assertEquals(100.0, dim.distance(new RecAddress(0, 0), new RecAddress(0, 100)), 0.0);
|
||||
assertEquals(-100.0, dim.distance(new RecAddress(0, 100), new RecAddress(0, 0)), 0.0);
|
||||
assertEquals(Math.pow(2, 65), dim.distance(new RecAddress(0, 0), new RecAddress(2, 0)),
|
||||
0.0);
|
||||
assertEquals(-Math.pow(2, 65), dim.distance(new RecAddress(2, 0), new RecAddress(0, 0)),
|
||||
0.0);
|
||||
|
||||
// 10000 instead of 100, because double precision will not detect just 100
|
||||
assertEquals(Math.pow(2, 65) + 10000,
|
||||
dim.distance(new RecAddress(0, 0), new RecAddress(2, 10000)), 0.0);
|
||||
assertEquals(-Math.pow(2, 65) - 10000,
|
||||
dim.distance(new RecAddress(2, 10000), new RecAddress(0, 0)), 0.0);
|
||||
assertEquals(Math.pow(2, 65) - 10000,
|
||||
dim.distance(new RecAddress(0, 10000), new RecAddress(2, 0)), 0.0);
|
||||
assertEquals(-Math.pow(2, 65) + 10000,
|
||||
dim.distance(new RecAddress(2, 0), new RecAddress(0, 10000)), 0.0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddressMid() {
|
||||
AddressDimension dim = AddressDimension.INSTANCE;
|
||||
|
||||
assertEquals(new RecAddress(0, 100),
|
||||
dim.mid(new RecAddress(0, 100), new RecAddress(0, 100)));
|
||||
|
||||
assertEquals(new RecAddress(0, 50), dim.mid(new RecAddress(0, 0), new RecAddress(0, 100)));
|
||||
assertEquals(new RecAddress(0, 50), dim.mid(new RecAddress(0, 100), new RecAddress(0, 0)));
|
||||
|
||||
assertEquals(new RecAddress(0, 49), dim.mid(new RecAddress(0, 0), new RecAddress(0, 99)));
|
||||
assertEquals(new RecAddress(0, 49), dim.mid(new RecAddress(0, 99), new RecAddress(0, 0)));
|
||||
|
||||
assertEquals(new RecAddress(1, 0), dim.mid(new RecAddress(0, 0), new RecAddress(2, 0)));
|
||||
assertEquals(new RecAddress(1, 0), dim.mid(new RecAddress(2, 0), new RecAddress(0, 0)));
|
||||
|
||||
assertEquals(new RecAddress(1, 50), dim.mid(new RecAddress(0, 0), new RecAddress(2, 100)));
|
||||
assertEquals(new RecAddress(1, 50), dim.mid(new RecAddress(2, 100), new RecAddress(0, 0)));
|
||||
assertEquals(new RecAddress(1, 50), dim.mid(new RecAddress(0, 100), new RecAddress(2, 0)));
|
||||
assertEquals(new RecAddress(1, 50), dim.mid(new RecAddress(2, 0), new RecAddress(0, 100)));
|
||||
|
||||
assertEquals(new RecAddress(0, Long.MIN_VALUE),
|
||||
dim.mid(new RecAddress(0, 0), new RecAddress(1, 0)));
|
||||
assertEquals(new RecAddress(0, Long.MIN_VALUE),
|
||||
dim.mid(new RecAddress(1, 0), new RecAddress(0, 0)));
|
||||
|
||||
assertEquals(new RecAddress(0, Long.MAX_VALUE),
|
||||
dim.mid(new RecAddress(0, 0), new RecAddress(0, -1)));
|
||||
assertEquals(new RecAddress(0, Long.MAX_VALUE),
|
||||
dim.mid(new RecAddress(0, -1), new RecAddress(0, 0)));
|
||||
|
||||
assertEquals(new RecAddress(0, Long.MIN_VALUE + 50),
|
||||
dim.mid(new RecAddress(0, 0), new RecAddress(1, 100)));
|
||||
assertEquals(new RecAddress(0, Long.MIN_VALUE + 50),
|
||||
dim.mid(new RecAddress(1, 100), new RecAddress(0, 0)));
|
||||
assertEquals(new RecAddress(0, Long.MIN_VALUE + 50),
|
||||
dim.mid(new RecAddress(0, 100), new RecAddress(1, 0)));
|
||||
assertEquals(new RecAddress(0, Long.MIN_VALUE + 50),
|
||||
dim.mid(new RecAddress(1, 0), new RecAddress(0, 100)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEntryKeyDistance() {
|
||||
EntryKeyDimension dim = EntryKeyDimension.INSTANCE;
|
||||
|
||||
assertEquals(0.0, dim.distance("", ""), 0.0);
|
||||
assertEquals(0.0, dim.distance("A", "A"), 0.0);
|
||||
// null is not a valid key, but because it's the "absolute max", the tree may ask
|
||||
assertEquals(0.0, dim.distance(null, null), 0.0);
|
||||
|
||||
assertEquals(25.0 * (Double.MAX_VALUE / 128), dim.distance("A", "Z"), 0.0);
|
||||
assertEquals(-1.0 * (Double.MAX_VALUE / 128), dim.distance("B", "A"), 0.0);
|
||||
assertEquals(1.0 * (Double.MAX_VALUE / 128), dim.distance("AA", "BA"), 0.0);
|
||||
assertEquals(-1.0 * (Double.MAX_VALUE / 128), dim.distance("BA", "AA"), 0.0);
|
||||
|
||||
assertEquals(1.0 * (Double.MAX_VALUE / 128) + 1.0 * (Double.MAX_VALUE / 128 / 128),
|
||||
dim.distance("AA", "BB"), 0.0);
|
||||
assertEquals(-1.0 * (Double.MAX_VALUE / 128) - 1.0 * (Double.MAX_VALUE / 128 / 128),
|
||||
dim.distance("BB", "AA"), 0.0);
|
||||
assertEquals(1.0 * (Double.MAX_VALUE / 128) - 1.0 * (Double.MAX_VALUE / 128 / 128),
|
||||
dim.distance("AB", "BA"), 0.0);
|
||||
assertEquals(-1.0 * (Double.MAX_VALUE / 128) + 1.0 * (Double.MAX_VALUE / 128 / 128),
|
||||
dim.distance("BA", "AB"), 0.0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEntryKeyMid() {
|
||||
EntryKeyDimension dim = EntryKeyDimension.INSTANCE;
|
||||
|
||||
assertEquals("A", dim.mid("A", "A"));
|
||||
assertEquals(null, dim.mid(null, null));
|
||||
|
||||
assertEquals("@", dim.mid("", null));
|
||||
assertEquals("@", dim.mid(null, ""));
|
||||
|
||||
assertEquals("M@", dim.mid("A", "Z"));
|
||||
assertEquals("M@", dim.mid("Z", "A"));
|
||||
assertEquals("M@", dim.mid("A\0", "Z\0"));
|
||||
|
||||
assertEquals("M", dim.mid("A", "Y"));
|
||||
assertEquals("M", dim.mid("Y", "A"));
|
||||
|
||||
assertEquals("MA", dim.mid("AA", "YA"));
|
||||
assertEquals("MA", dim.mid("YA", "AA"));
|
||||
|
||||
assertEquals("N\1", dim.mid("AA", "ZA"));
|
||||
assertEquals("N\1@", dim.mid("AA", "ZB"));
|
||||
assertEquals("N\1@", dim.mid("ZB", "AA"));
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue