GP-2068: Indexing, caching, other optimizations for TraceRmi.

This commit is contained in:
Dan 2023-11-03 10:34:31 -04:00
parent 9c6eabfbb3
commit a41c4ca5f7
63 changed files with 3529 additions and 1167 deletions

View file

@ -356,6 +356,7 @@ public class TraceRmiHandler implements TraceRmiConnection {
name = object.getName() + "." + nextId; name = object.getName() + "." + nextId;
} }
} }
name = object.getName() + "." + System.currentTimeMillis();
// Don't catch it this last time // Don't catch it this last time
return parent.createFile(name, object, monitor); return parent.createFile(name, object, monitor);
} }
@ -948,9 +949,12 @@ public class TraceRmiHandler implements TraceRmiConnection {
tx.tx.abortOnClose(); tx.tx.abortOnClose();
} }
tx.tx.close(); tx.tx.close();
OpenTrace open = requireOpenTrace(tx.txId.doId);
if (!tx.undoable) { if (!tx.undoable) {
requireOpenTrace(tx.txId.doId).trace.clearUndo(); open.trace.clearUndo();
} }
// TODO: Check for other transactions on the same trace?
open.trace.setEventsEnabled(true);
return ReplyEndTx.getDefaultInstance(); return ReplyEndTx.getDefaultInstance();
} }
@ -976,10 +980,11 @@ public class TraceRmiHandler implements TraceRmiConnection {
RequestGetValuesIntersecting req) throws AddressOverflowException { RequestGetValuesIntersecting req) throws AddressOverflowException {
OpenTrace open = requireOpenTrace(req.getOid()); OpenTrace open = requireOpenTrace(req.getOid());
AddressRange range = open.toRange(req.getBox().getRange(), false); AddressRange range = open.toRange(req.getBox().getRange(), false);
String key = req.getKey() == "" ? null : req.getKey();
Collection<? extends TraceObjectValue> col = range == null Collection<? extends TraceObjectValue> col = range == null
? List.of() ? List.of()
: open.trace.getObjectManager() : open.trace.getObjectManager()
.getValuesIntersecting(toLifespan(req.getBox().getSpan()), range); .getValuesIntersecting(toLifespan(req.getBox().getSpan()), range, key);
return ReplyGetValues.newBuilder() return ReplyGetValues.newBuilder()
.addAllValues(col.stream().map(TraceRmiHandler::makeValDesc).toList()) .addAllValues(col.stream().map(TraceRmiHandler::makeValDesc).toList())
.build(); .build();
@ -1074,16 +1079,15 @@ public class TraceRmiHandler implements TraceRmiConnection {
if (object == null) { if (object == null) {
return ReplyRetainValues.getDefaultInstance(); return ReplyRetainValues.getDefaultInstance();
} }
Lifespan span = toLifespan(req.getSpan());
Collection<? extends TraceObjectValue> values = switch (req.getKinds()) { Collection<? extends TraceObjectValue> values = switch (req.getKinds()) {
case VK_ELEMENTS -> object.getElements(); case VK_ELEMENTS -> object.getElements(span);
case VK_ATTRIBUTES -> object.getAttributes(); case VK_ATTRIBUTES -> object.getAttributes(span);
case VK_BOTH -> object.getValues(); case VK_BOTH -> object.getValues(span);
default -> throw new TraceRmiError("Protocol error: Invalid value kinds"); default -> throw new TraceRmiError("Protocol error: Invalid value kinds");
}; };
Lifespan span = toLifespan(req.getSpan());
Set<String> keysToKeep = Set.copyOf(req.getKeysList()); Set<String> keysToKeep = Set.copyOf(req.getKeysList());
List<String> keysToDelete = values.stream() List<String> keysToDelete = values.stream()
.filter(v -> v.getLifespan().intersects(span))
.map(v -> v.getEntryKey()) .map(v -> v.getEntryKey())
.filter(k -> !keysToKeep.contains(k)) .filter(k -> !keysToKeep.contains(k))
.distinct() .distinct()
@ -1123,6 +1127,7 @@ public class TraceRmiHandler implements TraceRmiConnection {
// Implies request was to set value to null // Implies request was to set value to null
return ReplySetValue.newBuilder().setSpan(makeSpan(Lifespan.EMPTY)).build(); return ReplySetValue.newBuilder().setSpan(makeSpan(Lifespan.EMPTY)).build();
} }
TraceObjectValue val = object.setValue(toLifespan(value.getSpan()), value.getKey(), TraceObjectValue val = object.setValue(toLifespan(value.getSpan()), value.getKey(),
objVal, toResolution(req.getResolution())); objVal, toResolution(req.getResolution()));
return ReplySetValue.newBuilder() return ReplySetValue.newBuilder()
@ -1144,6 +1149,7 @@ public class TraceRmiHandler implements TraceRmiConnection {
protected ReplyStartTx handleStartTx(RequestStartTx req) { protected ReplyStartTx handleStartTx(RequestStartTx req) {
OpenTrace open = requireOpenTrace(req.getOid()); OpenTrace open = requireOpenTrace(req.getOid());
Tid tid = requireAvailableTid(open, req.getTxid()); Tid tid = requireAvailableTid(open, req.getTxid());
open.trace.setEventsEnabled(false);
@SuppressWarnings("resource") @SuppressWarnings("resource")
OpenTx tx = OpenTx tx =
new OpenTx(tid, open.trace.openTransaction(req.getDescription()), req.getUndoable()); new OpenTx(tid, open.trace.openTransaction(req.getDescription()), req.getUndoable());

View file

@ -847,11 +847,10 @@ public class TraceRmiTarget extends AbstractTarget {
protected TraceObject getProcessForSpace(AddressSpace space) { protected TraceObject getProcessForSpace(AddressSpace space) {
for (TraceObjectValue objVal : trace.getObjectManager() for (TraceObjectValue objVal : trace.getObjectManager()
.getValuesIntersecting(Lifespan.at(getSnap()), .getValuesIntersecting(
new AddressRangeImpl(space.getMinAddress(), space.getMaxAddress()))) { Lifespan.at(getSnap()),
if (!TargetMemoryRegion.RANGE_ATTRIBUTE_NAME.equals(objVal.getEntryKey())) { new AddressRangeImpl(space.getMinAddress(), space.getMaxAddress()),
continue; TargetMemoryRegion.RANGE_ATTRIBUTE_NAME)) {
}
TraceObject obj = objVal.getParent(); TraceObject obj = objVal.getParent();
if (!obj.getInterfaces().contains(TraceObjectMemoryRegion.class)) { if (!obj.getInterfaces().contains(TraceObjectMemoryRegion.class)) {
continue; continue;

View file

@ -361,6 +361,7 @@ message ReplyGetValues {
message RequestGetValuesIntersecting { message RequestGetValuesIntersecting {
DomObjId oid = 1; DomObjId oid = 1;
Box box = 2; Box box = 2;
string key = 3;
} }
// Analysis operations // Analysis operations

View file

@ -389,10 +389,10 @@ class Trace(object):
span = Lifespan(self.snap(), self.snap()) span = Lifespan(self.snap(), self.snap())
return self._make_values(self.client._get_values(self.id, span, pattern)) return self._make_values(self.client._get_values(self.id, span, pattern))
def get_values_intersecting(self, rng, span=None): def get_values_intersecting(self, rng, span=None, key=""):
if span is None: if span is None:
span = Lifespan(self.snap(), self.snap()) span = Lifespan(self.snap(), self.snap())
return self._make_values(self.client._get_values_intersecting(self.id, span, rng)) return self._make_values(self.client._get_values_intersecting(self.id, span, rng, key))
def _activate_object(self, object): def _activate_object(self, object):
self.client._activate_object(self.id, object) self.client._activate_object(self.id, object)
@ -1053,11 +1053,12 @@ class Client(object):
return self._read_values(reply) return self._read_values(reply)
return self._batch_or_now(root, 'reply_get_values', _handle) return self._batch_or_now(root, 'reply_get_values', _handle)
def _get_values_intersecting(self, id, span, rng): def _get_values_intersecting(self, id, span, rng, key):
root = bufs.RootMessage() root = bufs.RootMessage()
root.request_get_values_intersecting.oid.id = id root.request_get_values_intersecting.oid.id = id
self._write_span(root.request_get_values_intersecting.box.span, span) self._write_span(root.request_get_values_intersecting.box.span, span)
self._write_range(root.request_get_values_intersecting.box.range, rng) self._write_range(root.request_get_values_intersecting.box.range, rng)
root.request_get_values_intersecting.key = key
def _handle(reply): def _handle(reply):
return self._read_values(reply) return self._read_values(reply)

View file

@ -136,7 +136,10 @@ public class DebuggerLocationLabel extends JLabel {
if (sections.isEmpty()) { if (sections.isEmpty()) {
return null; return null;
} }
// TODO: DB's R-Tree could probably do this natively /**
* TODO: DB's R-Tree could probably do this natively. Not sure it's an optimization, though,
* since few, if any, overlapping sections are expected.
*/
sections.sort(ComparatorUtils.chainedComparator(List.of( sections.sort(ComparatorUtils.chainedComparator(List.of(
Comparator.comparing(s -> s.getRange().getMinAddress()), Comparator.comparing(s -> s.getRange().getMinAddress()),
Comparator.comparing(s -> -s.getRange().getLength())))); Comparator.comparing(s -> -s.getRange().getLength()))));

View file

@ -32,7 +32,8 @@ import ghidra.app.plugin.core.codebrowser.CodeViewerProvider;
import ghidra.app.plugin.core.debug.DebuggerPluginPackage; import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
import ghidra.app.plugin.core.debug.event.*; import ghidra.app.plugin.core.debug.event.*;
import ghidra.app.plugin.core.debug.gui.DebuggerResources.AbstractNewListingAction; import ghidra.app.plugin.core.debug.gui.DebuggerResources.AbstractNewListingAction;
import ghidra.app.plugin.core.debug.gui.action.*; import ghidra.app.plugin.core.debug.gui.action.DebuggerProgramLocationActionContext;
import ghidra.app.plugin.core.debug.gui.action.NoneLocationTrackingSpec;
import ghidra.app.services.*; import ghidra.app.services.*;
import ghidra.app.util.viewer.format.FormatManager; import ghidra.app.util.viewer.format.FormatManager;
import ghidra.app.util.viewer.listingpanel.ListingPanel; import ghidra.app.util.viewer.listingpanel.ListingPanel;

View file

@ -228,6 +228,9 @@ public class DebuggerMemviewTraceListener extends TraceDomainObjectListener {
private void processTrace(Trace trace) { private void processTrace(Trace trace) {
updateList.clear(); updateList.clear();
provider.reset(); provider.reset();
if (!provider.isVisible()) {
return;
}
TraceThreadManager threadManager = trace.getThreadManager(); TraceThreadManager threadManager = trace.getThreadManager();
for (TraceThread thread : threadManager.getAllThreads()) { for (TraceThread thread : threadManager.getAllThreads()) {
threadChanged(thread); threadChanged(thread);

View file

@ -16,6 +16,7 @@
package ghidra.app.plugin.core.debug.gui.model; package ghidra.app.plugin.core.debug.gui.model;
import java.awt.Color; import java.awt.Color;
import java.util.ArrayList;
import java.util.Objects; import java.util.Objects;
import java.util.stream.Stream; import java.util.stream.Stream;
@ -303,9 +304,17 @@ public abstract class AbstractQueryTableModel<T> extends ThreadedTableModel<T, T
if (trace == null || query == null || trace.getObjectManager().getRootSchema() == null) { if (trace == null || query == null || trace.getObjectManager().getRootSchema() == null) {
return; return;
} }
ArrayList<T> batch = new ArrayList<>(100);
for (T t : (Iterable<T>) streamRows(trace, query, span)::iterator) { for (T t : (Iterable<T>) streamRows(trace, query, span)::iterator) {
accumulator.add(t); batch.add(t);
monitor.checkCancelled(); if (batch.size() >= 100) {
accumulator.addAll(batch);
monitor.checkCancelled();
batch.clear();
}
}
if (batch.size() > 0) {
accumulator.addAll(batch);
} }
} }

View file

@ -453,7 +453,8 @@ public class DebuggerModelProvider extends ComponentProvider implements Saveable
if (parent == null) { if (parent == null) {
return null; return null;
} }
for (TraceObjectValue value : parent.getValues()) { for (TraceObjectValue value : parent.getValues(
isLimitToCurrentSnap() ? Lifespan.at(current.getSnap()) : Lifespan.ALL)) {
if (Objects.equals(object, value.getValue())) { if (Objects.equals(object, value.getValue())) {
return value.getCanonicalPath(); return value.getCanonicalPath();
} }

View file

@ -605,9 +605,6 @@ public class ObjectTreeModel implements DisplaysModified {
if (!showMethods && value.isObject() && value.getChild().isMethod(snap)) { if (!showMethods && value.isObject() && value.getChild().isMethod(snap)) {
return false; return false;
} }
if (!value.getLifespan().intersects(span)) {
return false;
}
return true; return true;
} }
@ -624,7 +621,9 @@ public class ObjectTreeModel implements DisplaysModified {
protected List<GTreeNode> generateObjectChildren(TraceObject object) { protected List<GTreeNode> generateObjectChildren(TraceObject object) {
List<GTreeNode> result = ObjectTableModel List<GTreeNode> result = ObjectTableModel
.distinctCanonical(object.getValues().stream().filter(this::isValueVisible)) .distinctCanonical(object.getValues(span)
.stream()
.filter(this::isValueVisible))
.map(v -> nodeCache.getOrCreateNode(v)) .map(v -> nodeCache.getOrCreateNode(v))
.sorted() .sorted()
.collect(Collectors.toList()); .collect(Collectors.toList());

View file

@ -101,6 +101,9 @@ public class DebuggerModulesPanel extends AbstractObjectsTableBasedPanel<TraceOb
return ""; return "";
} }
AddressRange range = attr.getValue(); AddressRange range = attr.getValue();
if (range == null) {
return "";
}
// TODO: Cache this? Would flush on: // TODO: Cache this? Would flush on:
// 1. Mapping changes // 1. Mapping changes

View file

@ -75,51 +75,51 @@ public class DebuggerDisassemblyTest extends AbstractGhidraHeadedDebuggerTest {
@Before @Before
public void setUpDisassemblyTest() throws Exception { public void setUpDisassemblyTest() throws Exception {
ctx = XmlSchemaContext.deserialize("" + // ctx = XmlSchemaContext.deserialize("""
"<context>" + // <context>
" <schema name='Session' elementResync='NEVER' attributeResync='ONCE'>" + // <schema name='Session' elementResync='NEVER' attributeResync='ONCE'>
" <attribute name='Targets' schema='TargetContainer' />" + // <attribute name='Targets' schema='TargetContainer' />
" </schema>" + // </schema>
" <schema name='TargetContainer' canonical='yes' elementResync='NEVER' " + // <schema name='TargetContainer' canonical='yes' elementResync='NEVER'
" attributeResync='ONCE'>" + // attributeResync='ONCE'>
" <element schema='Target' />" + // <element schema='Target' />
" </schema>" + // </schema>
" <schema name='Target' elementResync='NEVER' attributeResync='NEVER'>" + // <schema name='Target' elementResync='NEVER' attributeResync='NEVER'>
" <interface name='Process' />" + // <interface name='Process' />
" <interface name='Aggregate' />" + // <interface name='Aggregate' />
" <attribute name='Environment' schema='Environment' />" + // <attribute name='Environment' schema='Environment' />
" <attribute name='Memory' schema='Memory' />" + // <attribute name='Memory' schema='Memory' />
" <attribute name='Threads' schema='ThreadContainer' />" + // <attribute name='Threads' schema='ThreadContainer' />
" </schema>" + // </schema>
" <schema name='Environment' elementResync='NEVER' " + // <schema name='Environment' elementResync='NEVER'
" attributeResync='NEVER'>" + // attributeResync='NEVER'>"
" <interface name='Environment' />" + // <interface name='Environment' />
" </schema>" + // </schema>
" <schema name='Memory' canonical='yes' elementResync='NEVER' " + // <schema name='Memory' canonical='yes' elementResync='NEVER'
" attributeResync='NEVER'>" + // attributeResync='NEVER'>
" <element schema='MemoryRegion' />" + // <element schema='MemoryRegion' />"
" </schema>" + // </schema>
" <schema name='MemoryRegion' elementResync='NEVER' attributeResync='NEVER'>" + // <schema name='MemoryRegion' elementResync='NEVER' attributeResync='NEVER'>
" <interface name='MemoryRegion' />" + // <interface name='MemoryRegion' />
" </schema>" + // </schema>"
" <schema name='ThreadContainer' canonical='yes' elementResync='NEVER' " + // <schema name='ThreadContainer' canonical='yes' elementResync='NEVER'
" attributeResync='NEVER'>" + // attributeResync='NEVER'>
" <element schema='Thread' />" + // <element schema='Thread' />
" </schema>" + // </schema>
" <schema name='Thread' elementResync='NEVER' attributeResync='NEVER'>" + // <schema name='Thread' elementResync='NEVER' attributeResync='NEVER'>
" <interface name='Thread' />" + // <interface name='Thread' />
" <interface name='Aggregate' />" + // <interface name='Aggregate' />
" <attribute name='Stack' schema='Stack' />" + // <attribute name='Stack' schema='Stack' />
" </schema>" + // </schema>
" <schema name='Stack' canonical='yes' elementResync='NEVER' " + // <schema name='Stack' canonical='yes' elementResync='NEVER'
" attributeResync='NEVER'>" + // attributeResync='NEVER'>
" <interface name='Stack' />" + // <interface name='Stack' />
" <element schema='Frame' />" + // <element schema='Frame' />
" </schema>" + // </schema>
" <schema name='Frame' elementResync='NEVER' attributeResync='NEVER'>" + // <schema name='Frame' elementResync='NEVER' attributeResync='NEVER'>
" <interface name='StackFrame' />" + // <interface name='StackFrame' />
" </schema>" + // </schema>
"</context>"); </context>""");
addPlugin(tool, DebuggerListingPlugin.class); addPlugin(tool, DebuggerListingPlugin.class);
platformService = addPlugin(tool, DebuggerPlatformServicePlugin.class); platformService = addPlugin(tool, DebuggerPlatformServicePlugin.class);

View file

@ -26,7 +26,7 @@ import org.junit.Test;
import generic.Unique; import generic.Unique;
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerTest; import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerTest;
import ghidra.app.plugin.core.debug.mapping.*; import ghidra.app.plugin.core.debug.mapping.ObjectBasedDebuggerMappingOpinion;
import ghidra.dbg.error.DebuggerMemoryAccessException; import ghidra.dbg.error.DebuggerMemoryAccessException;
import ghidra.dbg.model.*; import ghidra.dbg.model.*;
import ghidra.dbg.target.*; import ghidra.dbg.target.*;
@ -77,7 +77,7 @@ public class ObjectBasedTraceRecorderTest extends AbstractGhidraHeadedDebuggerTe
protected void dumpValues(TraceObject obj) { protected void dumpValues(TraceObject obj) {
System.err.println("Values of " + obj); System.err.println("Values of " + obj);
for (TraceObjectValue val : obj.getValues()) { for (TraceObjectValue val : obj.getValues(Lifespan.ALL)) {
System.err.println(" " + val.getEntryKey() + " = " + val.getValue()); System.err.println(" " + val.getEntryKey() + " = " + val.getValue());
} }
} }

View file

@ -98,39 +98,39 @@ public class DBTraceTimeViewport implements TraceTimeViewport {
if (range == null) { if (range == null) {
return false; return false;
} }
try (LockHold hold = trace.lockRead()) { /**
synchronized (ordered) { * NB. Because occlusion.occluded can be expensive, we should only keep the lock long enough
for (Lifespan rng : ordered) { * to copy the spans.
if (lifespan.contains(rng.lmax())) { */
return true; for (Lifespan rng : getOrderedSpans()) {
} if (lifespan.contains(rng.lmax())) {
if (occlusion.occluded(object, range, rng)) { return true;
return false; }
} if (occlusion.occluded(object, range, rng)) {
}
return false; return false;
} }
} }
return false;
} }
@Override @Override
public <T> AddressSet computeVisibleParts(AddressSetView set, Lifespan lifespan, T object, public <T> AddressSet computeVisibleParts(AddressSetView set, Lifespan lifespan, T object,
Occlusion<T> occlusion) { Occlusion<T> occlusion) {
List<Lifespan> spans;
try (LockHold hold = trace.lockRead()) { try (LockHold hold = trace.lockRead()) {
if (!containsAnyUpper(lifespan)) { if (!containsAnyUpper(lifespan)) {
return new AddressSet(); return new AddressSet();
} }
AddressSet remains = new AddressSet(set); spans = getOrderedSpans();
synchronized (ordered) { }
for (Lifespan rng : ordered) { AddressSet remains = new AddressSet(set);
if (lifespan.contains(rng.lmax())) { for (Lifespan rng : spans) {
return remains; if (lifespan.contains(rng.lmax())) {
} return remains;
occlusion.remove(object, remains, rng); }
if (remains.isEmpty()) { occlusion.remove(object, remains, rng);
return remains; if (remains.isEmpty()) {
} return remains;
}
} }
} }
// This condition should have been detected by !containsAnyUpper // This condition should have been detected by !containsAnyUpper
@ -308,73 +308,62 @@ public class DBTraceTimeViewport implements TraceTimeViewport {
@Override @Override
public List<Long> getOrderedSnaps() { public List<Long> getOrderedSnaps() {
ArrayList<Long> result = new ArrayList<>();
try (LockHold hold = trace.lockRead()) { try (LockHold hold = trace.lockRead()) {
synchronized (ordered) { synchronized (ordered) {
return ordered for (Lifespan span : ordered) {
.stream() result.add(span.lmax());
.map(Lifespan::lmax) }
.collect(Collectors.toList());
} }
} }
return result;
} }
@Override @Override
public List<Long> getReversedSnaps() { public List<Long> getReversedSnaps() {
ArrayList<Long> result = new ArrayList<>();
try (LockHold hold = trace.lockRead()) { try (LockHold hold = trace.lockRead()) {
synchronized (ordered) { synchronized (ordered) {
List<Long> reversed = ListIterator<Lifespan> it = ordered.listIterator(ordered.size());
ordered.stream().map(Lifespan::lmax).collect(Collectors.toList()); while (it.hasPrevious()) {
Collections.reverse(reversed); result.add(it.previous().lmax());
return reversed; }
} }
} }
return result;
} }
@Override @Override
public <T> T getTop(Function<Long, T> func) { public <T> T getTop(Function<Long, T> func) {
try (LockHold hold = trace.lockRead()) { for (Lifespan rng : getOrderedSpans()) {
synchronized (ordered) { T t = func.apply(rng.lmax());
for (Lifespan rng : ordered) { if (t != null) {
T t = func.apply(rng.lmax()); return t;
if (t != null) {
return t;
}
}
return null;
} }
} }
return null;
} }
@Override @Override
public <T> Iterator<T> mergedIterator(Function<Long, Iterator<T>> iterFunc, public <T> Iterator<T> mergedIterator(Function<Long, Iterator<T>> iterFunc,
Comparator<? super T> comparator) { Comparator<? super T> comparator) {
List<Iterator<T>> iters; if (!isForked()) {
try (LockHold hold = trace.lockRead()) { return iterFunc.apply(snap);
synchronized (ordered) {
if (!isForked()) {
return iterFunc.apply(snap);
}
iters = ordered.stream()
.map(rng -> iterFunc.apply(rng.lmax()))
.collect(Collectors.toList());
}
} }
List<Iterator<T>> iters = getOrderedSpans().stream()
.map(rng -> iterFunc.apply(rng.lmax()))
.collect(Collectors.toList());
return new UniqIterator<>(new MergeSortingIterator<>(iters, comparator)); return new UniqIterator<>(new MergeSortingIterator<>(iters, comparator));
} }
@Override @Override
public AddressSetView unionedAddresses(Function<Long, AddressSetView> viewFunc) { public AddressSetView unionedAddresses(Function<Long, AddressSetView> viewFunc) {
List<AddressSetView> views; if (!isForked()) {
try (LockHold hold = trace.lockRead()) { return viewFunc.apply(snap);
synchronized (ordered) {
if (!isForked()) {
return viewFunc.apply(snap);
}
views = ordered.stream()
.map(rng -> viewFunc.apply(rng.lmax()))
.collect(Collectors.toList());
}
} }
List<AddressSetView> views = getOrderedSpans().stream()
.map(rng -> viewFunc.apply(rng.lmax()))
.collect(Collectors.toList());
return new UnionAddressSetView(views); return new UnionAddressSetView(views);
} }
} }

View file

@ -118,7 +118,7 @@ public class DBTraceAddressSnapRangePropertyMapTree<T, DR extends AbstractDBTrac
@Override @Override
protected NodeType getType() { 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 @Override

View file

@ -26,12 +26,29 @@ import ghidra.util.LockHold;
import ghidra.util.datastruct.WeakValueHashMap; import ghidra.util.datastruct.WeakValueHashMap;
public class DBTraceProgramViewMemory extends AbstractDBTraceProgramViewMemory { 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 // NB. Keep both per-region and force-full (per-space) block sets ready
private final Map<TraceMemoryRegion, DBTraceProgramViewMemoryRegionBlock> regionBlocks = private final Map<TraceMemoryRegion, DBTraceProgramViewMemoryRegionBlock> regionBlocks =
new WeakValueHashMap<>(); new WeakValueHashMap<>();
private final Map<AddressSpace, DBTraceProgramViewMemorySpaceBlock> spaceBlocks = private final Map<AddressSpace, DBTraceProgramViewMemorySpaceBlock> spaceBlocks =
new WeakValueHashMap<>(); 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) { public DBTraceProgramViewMemory(DBTraceProgramView program) {
super(program); super(program);
@ -90,8 +107,24 @@ public class DBTraceProgramViewMemory extends AbstractDBTraceProgramViewMemory {
if (forceFullView) { if (forceFullView) {
return getSpaceBlock(addr.getAddressSpace()); return getSpaceBlock(addr.getAddressSpace());
} }
TraceMemoryRegion region = getTopRegion(s -> memoryManager.getRegionContaining(s, addr)); TraceMemoryRegion region = regionCacheByAddress.get(addr);
return region == null ? null : getRegionBlock(region); 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 @Override
@ -100,9 +133,19 @@ public class DBTraceProgramViewMemory extends AbstractDBTraceProgramViewMemory {
AddressSpace space = program.getAddressFactory().getAddressSpace(blockName); AddressSpace space = program.getAddressFactory().getAddressSpace(blockName);
return space == null ? null : getSpaceBlock(space); return space == null ? null : getSpaceBlock(space);
} }
TraceMemoryRegion region = TraceMemoryRegion region = regionCacheByName.get(blockName);
getTopRegion(s -> memoryManager.getLiveRegionByPath(s, blockName)); if (region != null && !region.isDeleted()) {
return region == null ? null : getRegionBlock(region); 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 @Override

View file

@ -19,26 +19,24 @@ import java.io.IOException;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.util.*; import java.util.*;
import java.util.function.Function; import java.util.function.Function;
import java.util.stream.*; import java.util.stream.Collectors;
import java.util.stream.Stream;
import db.DBRecord; import db.DBRecord;
import db.StringField; import db.StringField;
import ghidra.dbg.target.TargetObject; import ghidra.dbg.target.TargetObject;
import ghidra.dbg.target.schema.TargetObjectSchema; import ghidra.dbg.target.schema.TargetObjectSchema;
import ghidra.dbg.util.*; import ghidra.dbg.util.*;
import ghidra.program.model.address.AddressSpace;
import ghidra.trace.database.DBTrace; import ghidra.trace.database.DBTrace;
import ghidra.trace.database.breakpoint.DBTraceObjectBreakpointLocation; import ghidra.trace.database.breakpoint.DBTraceObjectBreakpointLocation;
import ghidra.trace.database.breakpoint.DBTraceObjectBreakpointSpec; 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.DBTraceObjectMemoryRegion;
import ghidra.trace.database.memory.DBTraceObjectRegister; import ghidra.trace.database.memory.DBTraceObjectRegister;
import ghidra.trace.database.module.*; import ghidra.trace.database.module.*;
import ghidra.trace.database.stack.DBTraceObjectStack; import ghidra.trace.database.stack.DBTraceObjectStack;
import ghidra.trace.database.stack.DBTraceObjectStackFrame; 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.InternalTraceObjectValue.ValueLifespanSetter;
import ghidra.trace.database.target.ValueSpace.SnapDimension;
import ghidra.trace.database.target.visitors.*; import ghidra.trace.database.target.visitors.*;
import ghidra.trace.database.target.visitors.TreeTraversal.Visitor; import ghidra.trace.database.target.visitors.TreeTraversal.Visitor;
import ghidra.trace.database.thread.DBTraceObjectThread; 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.target.annot.TraceObjectInterfaceUtils;
import ghidra.trace.model.thread.TraceObjectThread; import ghidra.trace.model.thread.TraceObjectThread;
import ghidra.trace.util.TraceChangeRecord; import ghidra.trace.util.TraceChangeRecord;
import ghidra.util.*; import ghidra.util.LockHold;
import ghidra.util.Msg;
import ghidra.util.database.*; import ghidra.util.database.*;
import ghidra.util.database.DBCachedObjectStoreFactory.AbstractDBFieldCodec; import ghidra.util.database.DBCachedObjectStoreFactory.AbstractDBFieldCodec;
import ghidra.util.database.annot.*; import ghidra.util.database.annot.*;
import ghidra.util.database.spatial.rect.Rectangle2DDirection;
@DBAnnotatedObjectInfo(version = 0) @DBAnnotatedObjectInfo(version = 0)
public class DBTraceObject extends DBAnnotatedObject implements TraceObject { public class DBTraceObject extends DBAnnotatedObject implements TraceObject {
protected static final String TABLE_NAME = "Objects"; protected static final String TABLE_NAME = "Objects";
private static final int VALUE_CACHE_SIZE = 50;
protected static <T extends TraceObjectInterface> // protected static <T extends TraceObjectInterface> //
Map.Entry<Class<? extends T>, Function<DBTraceObject, ? extends T>> safeEntry( Map.Entry<Class<? extends T>, Function<DBTraceObject, ? extends T>> safeEntry(
Class<T> cls, Function<DBTraceObject, ? extends T> ctor) { 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 // Canonical path
static final String PATH_COLUMN_NAME = "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 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, public DBTraceObject(DBTraceObjectManager manager, DBCachedObjectStore<?> store,
DBRecord record) { DBRecord record) {
super(store, record); super(store, record);
@ -199,12 +215,17 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject {
@Override @Override
public LifeSet getLife() { public LifeSet getLife() {
// TODO: This should really be cached
try (LockHold hold = manager.trace.lockRead()) { try (LockHold hold = manager.trace.lockRead()) {
if (cachedLife != null) {
synchronized (cachedLife) {
return DefaultLifeSet.copyOf(cachedLife);
}
}
MutableLifeSet result = new DefaultLifeSet(); MutableLifeSet result = new DefaultLifeSet();
// NOTE: connected ranges should already be coalesced // NOTE: connected ranges should already be coalesced
// No need to apply discreet domain // No need to apply discreet domain
getCanonicalParents(Lifespan.ALL).forEach(v -> result.add(v.getLifespan())); getCanonicalParents(Lifespan.ALL).forEach(v -> result.add(v.getLifespan()));
cachedLife = result;
return result; return result;
} }
} }
@ -253,10 +274,10 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject {
} }
protected void doRemoveTree(Lifespan span) { protected void doRemoveTree(Lifespan span) {
for (DBTraceObjectValue parent : getParents()) { for (InternalTraceObjectValue parent : getParents(span)) {
parent.doTruncateOrDeleteAndEmitLifeChange(span); parent.doTruncateOrDeleteAndEmitLifeChange(span);
} }
for (InternalTraceObjectValue value : getValues()) { for (InternalTraceObjectValue value : getValues(span)) {
value.doTruncateOrDeleteAndEmitLifeChange(span); value.doTruncateOrDeleteAndEmitLifeChange(span);
if (value.isCanonical()) { if (value.isCanonical()) {
value.getChild().doRemoveTree(span); value.getChild().doRemoveTree(span);
@ -275,26 +296,24 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject {
public TraceObjectValue getCanonicalParent(long snap) { public TraceObjectValue getCanonicalParent(long snap) {
try (LockHold hold = manager.trace.lockRead()) { try (LockHold hold = manager.trace.lockRead()) {
if (isRoot()) { 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 @Override
public Stream<? extends DBTraceObjectValue> getCanonicalParents(Lifespan lifespan) { public Stream<? extends InternalTraceObjectValue> getCanonicalParents(Lifespan lifespan) {
// TODO: If this is invoked often, perhaps index
try (LockHold hold = manager.trace.lockRead()) { try (LockHold hold = manager.trace.lockRead()) {
if (isRoot()) { if (isRoot()) {
return Stream.of(manager.valueStore.getObjectAt(0)); return Stream.of(manager.getRootValue());
} }
String canonicalKey = path.key(); List<InternalTraceObjectValue> list = List.copyOf(
TraceObjectKeyPath canonicalTail = path.parent(); manager.valueMap.reduce(TraceObjectValueQuery.canonicalParents(this, lifespan))
return manager.valuesByChild.getLazily(this) .values());
.stream() return list.stream();
.filter(v -> canonicalKey.equals(v.getEntryKey()))
.filter(v -> v.getLifespan().intersects(lifespan))
.filter(v -> canonicalTail.equals(v.getParent().getCanonicalPath()));
} }
} }
@ -329,135 +348,53 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject {
return ifCls.cast(ifaces.get(ifCls)); return ifCls.cast(ifaces.get(ifCls));
} }
protected Collection<? extends DBTraceObjectValue> doGetParents() { protected Collection<? extends InternalTraceObjectValue> doGetParents(Lifespan lifespan) {
return manager.valuesByChild.get(this); return List.copyOf(
manager.valueMap.reduce(TraceObjectValueQuery.parents(this, lifespan)).values());
} }
@Override @Override
public Collection<? extends DBTraceObjectValue> getParents() { public Collection<? extends InternalTraceObjectValue> getParents(Lifespan lifespan) {
try (LockHold hold = manager.trace.lockRead()) { 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() { 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() { protected boolean doHasAnyParents() {
return manager.valuesByChild.containsKey(this); return !manager.valueMap.reduce(TraceObjectValueQuery.parents(this, Lifespan.ALL))
.isEmpty();
} }
protected boolean doIsConnected() { protected boolean doIsConnected() {
@ -465,42 +402,28 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject {
} }
@Override @Override
public Collection<? extends InternalTraceObjectValue> getValues() { public Collection<? extends InternalTraceObjectValue> getValues(Lifespan lifespan) {
try (LockHold hold = manager.trace.lockRead()) { 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 @Override
public Collection<? extends InternalTraceObjectValue> getElements() { public Collection<? extends InternalTraceObjectValue> getElements(Lifespan lifespan) {
try (LockHold hold = manager.trace.lockRead()) { return getValues(lifespan).stream()
return doGetElements(); .filter(v -> PathUtils.isIndex(v.getEntryKey()))
} .toList();
}
protected Collection<? extends InternalTraceObjectValue> doGetAttributes() {
List<InternalTraceObjectValue> result = new ArrayList<>();
collectNonRangedAttributes(result);
collectRangedAttributes(result);
return result;
} }
@Override @Override
public Collection<? extends InternalTraceObjectValue> getAttributes() { public Collection<? extends InternalTraceObjectValue> getAttributes(Lifespan lifespan) {
try (LockHold hold = manager.trace.lockRead()) { return getValues(lifespan).stream()
return doGetAttributes(); .filter(v -> PathUtils.isName(v.getEntryKey()))
} .toList();
} }
protected void doCheckConflicts(Lifespan span, String key, Object value) { 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())) { if (!Objects.equals(value, val.getValue())) {
throw new DuplicateKeyException(key); throw new DuplicateKeyException(key);
} }
@ -510,7 +433,7 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject {
protected Lifespan doAdjust(Lifespan span, String key, Object value) { protected Lifespan doAdjust(Lifespan span, String key, Object value) {
// Ordered by min, so I only need to consider the first conflict // 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. // 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())) { if (Objects.equals(value, val.getValue())) {
continue; // not a conflict continue; // not a conflict
} }
@ -523,154 +446,50 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject {
return span; return span;
} }
// TODO: Could/should this return Stream instead?
protected Collection<? extends InternalTraceObjectValue> doGetValues(Lifespan span, protected Collection<? extends InternalTraceObjectValue> doGetValues(Lifespan span,
String key) { String key, boolean forward) {
return doGetValues(span.lmin(), span.lmax(), key); return manager.valueMap
} .reduce(TraceObjectValueQuery.values(this, key, key, span)
.starting(forward ? SnapDimension.FORWARD : SnapDimension.BACKWARD))
/** .orderedValues();
* 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());
} }
@Override @Override
public Collection<? extends InternalTraceObjectValue> getValues(Lifespan span, String key) { public Collection<? extends InternalTraceObjectValue> getValues(Lifespan span, String key) {
try (LockHold hold = manager.trace.lockRead()) { 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 @Override
public InternalTraceObjectValue getValue(long snap, String key) { public InternalTraceObjectValue getValue(long snap, String key) {
try (LockHold hold = manager.trace.lockRead()) { try (LockHold hold = manager.trace.lockRead()) {
DBTraceObjectValue nrVal = doGetNonRangedValue(snap, key); InternalTraceObjectValue cached = valueCache.get(key);
if (nrVal != null) { if (cached != null && !cached.isDeleted() && cached.getLifespan().contains(snap)) {
return nrVal; 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 @Override
public Stream<? extends InternalTraceObjectValue> getOrderedValues(Lifespan span, String key, public Stream<? extends InternalTraceObjectValue> getOrderedValues(Lifespan span, String key,
boolean forward) { boolean forward) {
try (LockHold hold = manager.trace.lockRead()) { 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, protected InternalTraceObjectValue doCreateValue(Lifespan lifespan, String key,
Object value) { Object value) {
Long nullSnap = nullCache.get(key);
if (nullSnap != null && lifespan.contains(nullSnap)) {
nullCache.remove(key);
}
return manager.doCreateValue(lifespan, this, key, value); return manager.doCreateValue(lifespan, this, key, value);
} }
@ -784,7 +607,8 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject {
@Override @Override
protected Iterable<InternalTraceObjectValue> getIntersecting(Long lower, protected Iterable<InternalTraceObjectValue> getIntersecting(Long lower,
Long upper) { Long upper) {
return Collections.unmodifiableCollection(doGetValues(lower, upper, key)); return Collections.unmodifiableCollection(
doGetValues(Lifespan.span(lower, upper), key, true));
} }
@Override @Override
@ -930,10 +754,10 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject {
} }
protected void doDeleteReferringValues() { protected void doDeleteReferringValues() {
for (InternalTraceObjectValue child : getValues()) { for (InternalTraceObjectValue child : getValues(Lifespan.ALL)) {
child.doDeleteAndEmit(); child.doDeleteAndEmit();
} }
for (DBTraceObjectValue parent : getParents()) { for (InternalTraceObjectValue parent : getParents(Lifespan.ALL)) {
parent.doDeleteAndEmit(); parent.doDeleteAndEmit();
} }
} }
@ -957,7 +781,38 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject {
} }
} }
catch (Throwable t) { 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());
} }
} }
} }

View file

@ -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);
}
}
}

View file

@ -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)));
}
}

View file

@ -15,8 +15,12 @@
*/ */
package ghidra.trace.database.target; 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.*; import ghidra.trace.model.Lifespan.DefaultLifeSet;
import ghidra.trace.model.Lifespan.LifeSet;
import ghidra.trace.model.Trace.TraceObjectChangeType; import ghidra.trace.model.Trace.TraceObjectChangeType;
import ghidra.trace.model.TraceUniqueObject; import ghidra.trace.model.TraceUniqueObject;
import ghidra.trace.model.target.*; import ghidra.trace.model.target.*;
@ -173,9 +177,14 @@ public interface DBTraceObjectInterface extends TraceObjectInterface, TraceUniqu
static TraceAddressSpace spaceForValue(TraceObject object, long snap, String key) { static TraceAddressSpace spaceForValue(TraceObject object, long snap, String key) {
TraceObjectValue val = object.getAttribute(snap, key); TraceObjectValue val = object.getAttribute(snap, key);
if (val instanceof DBTraceObjectAddressRangeValue) { if (val == null) {
DBTraceObjectAddressRangeValue addrVal = (DBTraceObjectAddressRangeValue) val; return null;
return addrVal.getTraceAddressSpace(); }
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; return null;
} }

View file

@ -23,7 +23,6 @@ import java.util.function.Predicate;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
import org.apache.commons.collections4.IteratorUtils;
import org.jdom.JDOMException; import org.jdom.JDOMException;
import db.*; import db.*;
@ -38,13 +37,13 @@ import ghidra.program.model.lang.Language;
import ghidra.trace.database.DBTrace; import ghidra.trace.database.DBTrace;
import ghidra.trace.database.DBTraceManager; import ghidra.trace.database.DBTraceManager;
import ghidra.trace.database.breakpoint.DBTraceObjectBreakpointLocation; 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.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.target.visitors.SuccessorsRelativeVisitor;
import ghidra.trace.database.thread.DBTraceObjectThread; 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.Trace.TraceObjectChangeType;
import ghidra.trace.model.breakpoint.*; import ghidra.trace.model.breakpoint.*;
import ghidra.trace.model.memory.*; import ghidra.trace.model.memory.*;
@ -69,6 +68,7 @@ import ghidra.util.exception.VersionException;
import ghidra.util.task.TaskMonitor; import ghidra.util.task.TaskMonitor;
public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager { public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager {
private static final int OBJECTS_CONTAINING_CACHE_SIZE = 100;
public static class DBTraceObjectSchemaDBFieldCodec extends public static class DBTraceObjectSchemaDBFieldCodec extends
AbstractDBFieldCodec<SchemaContext, DBTraceObjectSchemaEntry, StringField> { 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 ReadWriteLock lock;
protected final DBTrace trace; protected final DBTrace trace;
protected final DBCachedObjectStore<DBTraceObjectSchemaEntry> schemaStore; protected final DBCachedObjectStore<DBTraceObjectSchemaEntry> schemaStore;
protected final DBCachedObjectStore<DBTraceObject> objectStore; protected final DBCachedObjectStore<DBTraceObject> objectStore;
protected final DBCachedObjectStore<DBTraceObjectValue> valueStore; protected final DBTraceObjectValueRStarTree valueTree;
protected final DBTraceObjectValueMap valueMap;
protected final DBTraceAddressSnapRangePropertyMap<DBTraceObjectAddressRangeValue, DBTraceObjectAddressRangeValue> rangeValueMap;
protected final DBCachedObjectIndex<TraceObjectKeyPath, DBTraceObject> objectsByPath; 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<TraceObject> objectsView;
protected final Collection<TraceObjectValue> valuesView; protected final Collection<TraceObjectValue> valuesView;
protected TargetObjectSchema rootSchema; 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, public DBTraceObjectManager(DBHandle dbh, DBOpenMode openMode, ReadWriteLock lock,
TaskMonitor monitor, Language baseLanguage, DBTrace trace) TaskMonitor monitor, Language baseLanguage, DBTrace trace)
throws IOException, VersionException { throws IOException, VersionException {
@ -178,33 +187,17 @@ public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager
loadRootSchema(); loadRootSchema();
objectStore = factory.getOrCreateCachedStore(DBTraceObject.TABLE_NAME, objectStore = factory.getOrCreateCachedStore(DBTraceObject.TABLE_NAME,
DBTraceObject.class, (s, r) -> new DBTraceObject(this, s, r), true); 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); valueTree = new DBTraceObjectValueRStarTree(this, factory,
rangeValueMap = new DBTraceAddressSnapRangePropertyMap<>( DBTraceObjectValueData.TABLE_NAME, ValueSpace.INSTANCE, DBTraceObjectValueData.class,
DBTraceObjectAddressRangeValue.TABLE_NAME, dbh, openMode, lock, monitor, baseLanguage, DBTraceObjectValueNode.class, false, 50);
trace, null, DBTraceObjectAddressRangeValue.class, valueMap = valueTree.asSpatialMap();
(t, s, r) -> new DBTraceObjectAddressRangeValue(this, t, s, r));
objectsByPath = objectsByPath =
objectStore.getIndex(TraceObjectKeyPath.class, DBTraceObject.PATH_COLUMN); 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()); objectsView = Collections.unmodifiableCollection(objectStore.asMap().values());
valuesView = new AbstractCollection<>() { valuesView = Collections.unmodifiableCollection(valueMap.values());
@Override
public Iterator<TraceObjectValue> iterator() {
return IteratorUtils.chainedIterator(valueStore.asMap().values().iterator(),
rangeValueMap.values().iterator());
}
@Override
public int size() {
return objectStore.getRecordCount() + rangeValueMap.size();
}
};
} }
protected void loadRootSchema() { protected void loadRootSchema() {
@ -225,8 +218,7 @@ public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager
@Override @Override
public void invalidateCache(boolean all) { public void invalidateCache(boolean all) {
objectStore.invalidateCache(); objectStore.invalidateCache();
valueStore.invalidateCache(); valueTree.invalidateCache();
rangeValueMap.invalidateCache(all);
schemaStore.invalidateCache(); schemaStore.invalidateCache();
loadRootSchema(); loadRootSchema();
} }
@ -281,24 +273,22 @@ public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager
protected InternalTraceObjectValue doCreateValue(Lifespan lifespan, protected InternalTraceObjectValue doCreateValue(Lifespan lifespan,
DBTraceObject parent, String key, Object value) { DBTraceObject parent, String key, Object value) {
if (value instanceof AddressRange) { InternalTraceObjectValue entry = valueTree.asSpatialMap()
DBTraceObjectAddressRangeValue entry = rangeValueMap .put(new ImmutableValueShape(parent, value, key, lifespan), null);
.put(new ImmutableTraceAddressSnapRange((AddressRange) value, lifespan), null); if (value instanceof DBTraceObject child) {
entry.set(parent, key, false); child.notifyParentValueCreated(entry);
emitValueCreated(parent, entry);
return entry;
} }
else if (value instanceof Address) { else {
Address address = (Address) value; entry.doSetPrimitive(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;
} }
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); emitValueCreated(parent, entry);
return 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 @Override
public DBTraceObject getRootObject() { public DBTraceObject getRootObject() {
return getObjectById(0); return getObjectById(0);
@ -381,7 +377,7 @@ public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager
public Stream<? extends TraceObjectValPath> getValuePaths(Lifespan span, public Stream<? extends TraceObjectValPath> getValuePaths(Lifespan span,
PathPredicates predicates) { PathPredicates predicates) {
try (LockHold hold = trace.lockRead()) { try (LockHold hold = trace.lockRead()) {
DBTraceObjectValue rootVal = valueStore.getObjectAt(0); DBTraceObjectValueData rootVal = getRootValue();
if (rootVal == null) { if (rootVal == null) {
return Stream.of(); return Stream.of();
} }
@ -401,14 +397,18 @@ public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager
@Override @Override
public Collection<? extends TraceObjectValue> getValuesIntersecting(Lifespan span, public Collection<? extends TraceObjectValue> getValuesIntersecting(Lifespan span,
AddressRange range) { AddressRange range, String entryKey) {
return Collections.unmodifiableCollection( return Collections
rangeValueMap.reduce(TraceAddressSnapRangeQuery.intersecting(range, span)).values()); .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( return Collections.unmodifiableCollection(
rangeValueMap.reduce(TraceAddressSnapRangeQuery.at(address, snap)).values()); valueMap.reduce(TraceObjectValueQuery.at(entryKey, snap, address)).values());
} }
@Override @Override
@ -445,8 +445,7 @@ public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager
@Override @Override
public void clear() { public void clear() {
try (LockHold hold = trace.lockWrite()) { try (LockHold hold = trace.lockWrite()) {
valueStore.deleteAll(); valueMap.clear();
rangeValueMap.clear();
objectStore.deleteAll(); objectStore.deleteAll();
schemaStore.deleteAll(); schemaStore.deleteAll();
rootSchema = null; rootSchema = null;
@ -458,8 +457,16 @@ public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager
object.emitEvents(new TraceChangeRecord<>(TraceObjectChangeType.DELETED, null, object)); object.emitEvents(new TraceChangeRecord<>(TraceObjectChangeType.DELETED, null, object));
} }
protected void doDeleteEdge(DBTraceObjectValue edge) { protected void doDeleteEdge(DBTraceObjectValueData edge) {
valueStore.delete(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() { public boolean hasSchema() {
@ -508,35 +515,51 @@ public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager
} }
} }
protected <I extends TraceObjectInterface> Stream<I> doParentsWithKeyHaving( protected <I extends TraceObjectInterface> Stream<I> doParentsHaving(
Stream<? extends TraceObjectValue> values, String key, Class<I> iface) { Stream<? extends TraceObjectValue> values, Class<I> iface) {
return values.filter(v -> key.equals(v.getEntryKey())) return values.map(v -> v.getParent())
.map(v -> v.getParent())
.map(o -> o.queryInterface(iface)) .map(o -> o.queryInterface(iface))
.filter(i -> i != null); .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, public <I extends TraceObjectInterface> Collection<I> getObjectsContaining(long snap,
Address address, String key, Class<I> iface) { Address address, String key, Class<I> iface) {
try (LockHold hold = trace.lockRead()) { try (LockHold hold = trace.lockRead()) {
return doParentsWithKeyHaving(getValuesAt(snap, address).stream(), key, synchronized (objectsContainingCache) {
iface).collect(Collectors.toSet()); return (Collection<I>) objectsContainingCache.computeIfAbsent(
new ObjectsContainingKey(snap, address, key, iface),
this::doGetObjectsContaining);
}
} }
} }
public <I extends TraceObjectInterface> I getObjectContaining(long snap, Address address, public <I extends TraceObjectInterface> I getObjectContaining(long snap, Address address,
String key, Class<I> iface) { String key, Class<I> iface) {
try (LockHold hold = trace.lockRead()) { Collection<I> col = getObjectsContaining(snap, address, key, iface);
return doParentsWithKeyHaving(getValuesAt(snap, address).stream(), key, if (col.isEmpty()) {
iface).findAny().orElse(null); return null;
} }
return col.iterator().next();
} }
public <I extends TraceObjectInterface> Collection<I> getObjectsIntersecting( public <I extends TraceObjectInterface> Collection<I> getObjectsIntersecting(
Lifespan lifespan, AddressRange range, String key, Class<I> iface) { Lifespan lifespan, AddressRange range, String key, Class<I> iface) {
try (LockHold hold = trace.lockRead()) { try (LockHold hold = trace.lockRead()) {
return doParentsWithKeyHaving(getValuesIntersecting(lifespan, range).stream(), key, return doParentsHaving(getValuesIntersecting(lifespan, range, key).stream(), iface)
iface).collect(Collectors.toSet()); .collect(Collectors.toSet());
} }
} }
@ -549,7 +572,7 @@ public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager
public <I extends TraceObjectInterface> AddressSetView getObjectsAddressSet(long snap, public <I extends TraceObjectInterface> AddressSetView getObjectsAddressSet(long snap,
String key, Class<I> ifaceCls, Predicate<? super I> predicate) { 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())) { if (!key.equals(v.getEntryKey())) {
return false; return false;
} }

View file

@ -74,20 +74,33 @@ public class DBTraceObjectValPath implements TraceObjectValPath {
@Override @Override
public DBTraceObjectValPath prepend(TraceObjectValue entry) { 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()]; InternalTraceObjectValue[] arr = new InternalTraceObjectValue[1 + entryList.size()];
arr[0] = (DBTraceObjectValue) entry; arr[0] = val;
for (int i = 1; i < arr.length; i++) { for (int i = 1; i < arr.length; i++) {
arr[i] = entryList.get(i - 1); arr[i] = entryList.get(i - 1);
} }
return new DBTraceObjectValPath(Collections.unmodifiableList(Arrays.asList(arr))); return new DBTraceObjectValPath(Collections.unmodifiableList(Arrays.asList(arr)));
} }
@Override
public DBTraceObjectValPath append(TraceObjectValue entry) { 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()]; InternalTraceObjectValue[] arr = new InternalTraceObjectValue[1 + entryList.size()];
for (int i = 0; i < arr.length - 1; i++) { for (int i = 0; i < arr.length - 1; i++) {
arr[i] = entryList.get(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))); return new DBTraceObjectValPath(Collections.unmodifiableList(Arrays.asList(arr)));
} }

View file

@ -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);
}
}
}

View file

@ -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);
}
}

View file

@ -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);
}
}
}

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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());
}
}

View file

@ -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;
}
}

View file

@ -98,6 +98,8 @@ interface InternalTraceObjectValue extends TraceObjectValue {
protected abstract InternalTraceObjectValue create(Lifespan range, Object value); protected abstract InternalTraceObjectValue create(Lifespan range, Object value);
} }
void doSetPrimitive(Object primitive);
DBTraceObjectManager getManager(); DBTraceObjectManager getManager();
/** /**
@ -143,7 +145,7 @@ interface InternalTraceObjectValue extends TraceObjectValue {
protected Iterable<InternalTraceObjectValue> getIntersecting(Long lower, protected Iterable<InternalTraceObjectValue> getIntersecting(Long lower,
Long upper) { Long upper) {
Collection<InternalTraceObjectValue> col = Collections.unmodifiableCollection( 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); return IterableUtils.filteredIterable(col, v -> v != keep);
} }

View file

@ -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);
}
}

View file

@ -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();
}
}

View file

@ -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));
}
}

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -50,6 +50,6 @@ public enum AllPathsVisitor implements SpanIntersectingVisitor {
@Override @Override
public Stream<? extends TraceObjectValue> continueValues(TraceObject object, public Stream<? extends TraceObjectValue> continueValues(TraceObject object,
Lifespan span, TraceObjectValPath path) { Lifespan span, TraceObjectValPath path) {
return object.getParents().stream().filter(v -> !path.contains(v)); return object.getParents(span).stream().filter(v -> !path.contains(v));
} }
} }

View file

@ -59,7 +59,7 @@ public class AncestorsRelativeVisitor implements SpanIntersectingVisitor {
return Stream.empty(); return Stream.empty();
} }
return object.getParents() return object.getParents(span)
.stream() .stream()
.filter(v -> PathPredicates.anyMatches(prevKeys, v.getEntryKey())); .filter(v -> PathPredicates.anyMatches(prevKeys, v.getEntryKey()));
} }

View file

@ -58,6 +58,6 @@ public class AncestorsRootVisitor implements SpanIntersectingVisitor {
* Can't really filter the parent values by predicates here, since the predicates are not * Can't really filter the parent values by predicates here, since the predicates are not
* matching relative paths, but canonical paths. * 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));
} }
} }

View file

@ -73,7 +73,7 @@ public class CanonicalSuccessorsRelativeVisitor implements Visitor {
Stream<? extends TraceObjectValue> attrStream; Stream<? extends TraceObjectValue> attrStream;
if (nextKeys.contains("")) { if (nextKeys.contains("")) {
attrStream = object.getAttributes().stream().filter(TraceObjectValue::isCanonical); attrStream = object.getAttributes(span).stream().filter(TraceObjectValue::isCanonical);
} }
else { else {
attrStream = Stream.empty(); attrStream = Stream.empty();
@ -81,7 +81,7 @@ public class CanonicalSuccessorsRelativeVisitor implements Visitor {
Stream<? extends TraceObjectValue> elemStream; Stream<? extends TraceObjectValue> elemStream;
if (nextKeys.contains("[]")) { if (nextKeys.contains("[]")) {
elemStream = object.getElements().stream().filter(TraceObjectValue::isCanonical); elemStream = object.getElements(span).stream().filter(TraceObjectValue::isCanonical);
} }
else { else {
elemStream = Stream.empty(); elemStream = Stream.empty();

View file

@ -61,9 +61,7 @@ public class SuccessorsRelativeVisitor implements SpanIntersectingVisitor {
Stream<? extends TraceObjectValue> attrStream; Stream<? extends TraceObjectValue> attrStream;
if (nextKeys.contains("")) { if (nextKeys.contains("")) {
attrStream = object.getAttributes() attrStream = object.getAttributes(span).stream();
.stream()
.filter(v -> span.intersects(v.getLifespan()));
} }
else { else {
attrStream = Stream.empty(); attrStream = Stream.empty();
@ -71,9 +69,7 @@ public class SuccessorsRelativeVisitor implements SpanIntersectingVisitor {
Stream<? extends TraceObjectValue> elemStream; Stream<? extends TraceObjectValue> elemStream;
if (nextKeys.contains("[]")) { if (nextKeys.contains("[]")) {
elemStream = object.getElements() elemStream = object.getElements(span).stream();
.stream()
.filter(v -> span.intersects(v.getLifespan()));
} }
else { else {
elemStream = Stream.empty(); elemStream = Stream.empty();

View file

@ -395,6 +395,12 @@ public sealed interface Lifespan extends Span<Long, Lifespan>, Iterable<Long> {
* An interval tree implementing {@link MutableLifeSet} * An interval tree implementing {@link MutableLifeSet}
*/ */
public class DefaultLifeSet extends DefaultSpanSet<Long, Lifespan> implements 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() { public DefaultLifeSet() {
super(Lifespan.DOMAIN); super(Lifespan.DOMAIN);
} }

View file

@ -16,28 +16,23 @@
package ghidra.trace.model; package ghidra.trace.model;
import ghidra.framework.model.*; import ghidra.framework.model.*;
import ghidra.util.TimedMsg;
public class TraceDomainObjectListener extends TypedEventDispatcher public class TraceDomainObjectListener extends TypedEventDispatcher
implements DomainObjectListener { implements DomainObjectListener {
@Override @Override
public void domainObjectChanged(DomainObjectChangedEvent ev) { public void domainObjectChanged(DomainObjectChangedEvent ev) {
//TimedMsg.info(this, "Handing (" + this + "): " + ev);
if (restoredHandler != null && ev.containsEvent(DomainObject.DO_OBJECT_RESTORED)) { if (restoredHandler != null && ev.containsEvent(DomainObject.DO_OBJECT_RESTORED)) {
for (DomainObjectChangeRecord rec : ev) { for (DomainObjectChangeRecord rec : ev) {
if (rec.getEventType() == DomainObject.DO_OBJECT_RESTORED) { if (rec.getEventType() == DomainObject.DO_OBJECT_RESTORED) {
restoredHandler.accept(rec); restoredHandler.accept(rec);
TimedMsg.debug(this, " Done: OBJECT_RESTORED");
return; return;
} }
} }
throw new AssertionError(); throw new AssertionError();
} }
//Map<String, Integer> CountsByType = new TreeMap<>();
for (DomainObjectChangeRecord rec : ev) { for (DomainObjectChangeRecord rec : ev) {
handleChangeRecord(rec); handleChangeRecord(rec);
} }
//TimedMsg.info(this, " Done: " + CountsByType);
} }
} }

View file

@ -202,18 +202,19 @@ public interface TraceObject extends TraceUniqueObject {
<I extends TraceObjectInterface> I queryInterface(Class<I> ifClass); <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 * @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 * @return the values
*/ */
Collection<? extends TraceObjectValue> getValues(); Collection<? extends TraceObjectValue> getValues(Lifespan span);
/** /**
* Get values with the given key intersecting the given span * Get values with the given key intersecting the given span
@ -236,18 +237,18 @@ public interface TraceObject extends TraceUniqueObject {
boolean forward); boolean forward);
/** /**
* Get all elements of this object * Get all elements of this object intersecting the given span
* *
* @return the element values * @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 * @return the attribute values
*/ */
Collection<? extends TraceObjectValue> getAttributes(); Collection<? extends TraceObjectValue> getAttributes(Lifespan span);
/** /**
* Get the value for the given snap and key * Get the value for the given snap and key

View file

@ -139,10 +139,23 @@ public interface TraceObjectManager {
* *
* @param span the span that desired values lifespans must intersect * @param span the span that desired values lifespans must intersect
* @param range the range that desired address-ranged values 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 * @return the collection of values
*/ */
Collection<? extends TraceObjectValue> getValuesIntersecting(Lifespan span, 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 * Get all interfaces of the given type in the database

View file

@ -54,7 +54,7 @@ public enum TraceObjectInterfaceUtils {
public static void setLifespan(Class<? extends TraceObjectInterface> traceIf, public static void setLifespan(Class<? extends TraceObjectInterface> traceIf,
TraceObject object, Lifespan lifespan) throws DuplicateNameException { TraceObject object, Lifespan lifespan) throws DuplicateNameException {
try (LockHold hold = object.getTrace().lockWrite()) { try (LockHold hold = object.getTrace().lockWrite()) {
for (TraceObjectValue val : object.getParents()) { for (TraceObjectValue val : object.getParents(Lifespan.ALL)) {
if (val.isCanonical() && !val.isDeleted()) { if (val.isCanonical() && !val.isDeleted()) {
val.setLifespan(lifespan, ConflictResolution.DENY); val.setLifespan(lifespan, ConflictResolution.DENY);
} }

View file

@ -18,8 +18,10 @@ package ghidra.trace.database.target;
import static org.junit.Assert.*; import static org.junit.Assert.*;
import java.io.File; import java.io.File;
import java.math.BigInteger;
import java.util.*; import java.util.*;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
@ -31,6 +33,7 @@ import ghidra.dbg.target.schema.TargetObjectSchema.SchemaName;
import ghidra.dbg.target.schema.XmlSchemaContext; import ghidra.dbg.target.schema.XmlSchemaContext;
import ghidra.dbg.util.PathPredicates; import ghidra.dbg.util.PathPredicates;
import ghidra.dbg.util.PathUtils; import ghidra.dbg.util.PathUtils;
import ghidra.program.model.address.*;
import ghidra.test.AbstractGhidraHeadlessIntegrationTest; import ghidra.test.AbstractGhidraHeadlessIntegrationTest;
import ghidra.trace.database.ToyDBTraceBuilder; import ghidra.trace.database.ToyDBTraceBuilder;
import ghidra.trace.model.Lifespan; import ghidra.trace.model.Lifespan;
@ -111,22 +114,22 @@ public class DBTraceObjectManagerTest extends AbstractGhidraHeadlessIntegrationT
TraceObjectKeyPath pathTargets = TraceObjectKeyPath.of("Targets"); TraceObjectKeyPath pathTargets = TraceObjectKeyPath.of("Targets");
targetContainer = manager.createObject(pathTargets); targetContainer = manager.createObject(pathTargets);
root.setAttribute(Lifespan.nowOn(0), "Targets", targetContainer); root.setAttribute(Lifespan.nowOn(0), "Targets", targetContainer);
dumpStore(manager.valueStore); dumpStore(manager.valueTree.getDataStore());
for (int i = 0; i < targetCount; i++) { for (int i = 0; i < targetCount; i++) {
Lifespan lifespan = Lifespan.nowOn(i); Lifespan lifespan = Lifespan.nowOn(i);
TraceObject target = manager.createObject(pathTargets.index(i)); TraceObject target = manager.createObject(pathTargets.index(i));
target.setAttribute(Lifespan.ALL, "self", target); target.setAttribute(Lifespan.ALL, "self", target);
dumpStore(manager.valueStore); dumpStore(manager.valueTree.getDataStore());
targetContainer.setElement(lifespan, i, target); targetContainer.setElement(lifespan, i, target);
dumpStore(manager.valueStore); dumpStore(manager.valueTree.getDataStore());
targets.add(target); targets.add(target);
root.setAttribute(lifespan, "curTarget", target); root.setAttribute(lifespan, "curTarget", target);
dumpStore(manager.valueStore); dumpStore(manager.valueTree.getDataStore());
} }
root.setValue(Lifespan.ALL, "anAttribute", "A primitive string"); 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 = TraceObjectValue rangeVal =
root.setValue(Lifespan.nowOn(0), "a", b.range(0x1000, 0x1fff)); root.setValue(Lifespan.nowOn(0), "a", b.range(0x1000, 0x1fff));
assertTrue(root.getValues().contains(rangeVal)); assertTrue(root.getValues(Lifespan.at(0)).contains(rangeVal));
assertFalse(targetContainer.getValues().contains(rangeVal)); assertFalse(targetContainer.getValues(Lifespan.ALL).contains(rangeVal));
assertEquals(rangeVal, root.getValue(0, "a")); assertEquals(rangeVal, root.getValue(0, "a"));
assertNull(root.getValue(0, "b")); assertNull(root.getValue(0, "b"));
@ -292,10 +295,14 @@ public class DBTraceObjectManagerTest extends AbstractGhidraHeadlessIntegrationT
assertEquals(Set.of(rangeVal), assertEquals(Set.of(rangeVal),
Set.copyOf(manager.getValuesIntersecting(Lifespan.ALL, b.range(0, -1)))); 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(), assertEquals(Set.of(),
Set.copyOf(manager.getValuesIntersecting(Lifespan.toNow(-1), b.range(0, -1)))); Set.copyOf(manager.getValuesIntersecting(Lifespan.toNow(-1), b.range(0, -1))));
assertEquals(Set.of(), assertEquals(Set.of(),
Set.copyOf(manager.getValuesIntersecting(Lifespan.ALL, b.range(0, 0xfff)))); 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() { public void testGetParents() {
populateModel(3); populateModel(3);
assertEquals(1, root.getParents().size()); assertEquals(1, root.getParents(Lifespan.ALL).size());
assertEquals(root, Unique.assertOne(targetContainer.getParents()).getParent()); assertEquals(root, Unique.assertOne(targetContainer.getParents(Lifespan.ALL)).getParent());
assertEquals(3, targets.get(0).getParents().size()); // curTarget, targetContainer, self assertEquals(3, targets.get(0).getParents(Lifespan.ALL).size());
// curTarget, targetContainer, self
} }
@Test @Test
public void testGetValues() { public void testGetValues() {
populateModel(3); populateModel(3);
assertEquals(3, targetContainer.getValues().size()); assertEquals(3, targetContainer.getValues(Lifespan.ALL).size());
} }
@Test @Test
public void testGetElements() { public void testGetElements() {
populateModel(3); populateModel(3);
assertEquals(0, root.getElements().size()); assertEquals(0, root.getElements(Lifespan.ALL).size());
assertEquals(3, targetContainer.getElements().size()); assertEquals(3, targetContainer.getElements(Lifespan.ALL).size());
} }
@Test @Test
public void testGetAttributes() { public void testGetAttributes() {
populateModel(3); populateModel(3);
assertEquals(5, root.getAttributes().size()); // Targets, curTarget(x3), string assertEquals(5, root.getAttributes(Lifespan.ALL).size()); // Targets, curTarget(x3), string
assertEquals(0, targetContainer.getAttributes().size()); assertEquals(0, targetContainer.getAttributes(Lifespan.ALL).size());
} }
@Test @Test
@ -610,7 +618,7 @@ public class DBTraceObjectManagerTest extends AbstractGhidraHeadlessIntegrationT
assertEquals(valA, root.setValue(Lifespan.span(-10, -1), "a", 1)); assertEquals(valA, root.setValue(Lifespan.span(-10, -1), "a", 1));
assertEquals(Lifespan.span(-10, 9), valA.getLifespan()); 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, assertEquals(valA,
root.setValue(Lifespan.span(-10, -1), "a", b.range(0x1000, 0x1fff))); root.setValue(Lifespan.span(-10, -1), "a", b.range(0x1000, 0x1fff)));
assertEquals(Lifespan.span(-10, 9), valA.getLifespan()); 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(valA, root.setValue(Lifespan.span(10, 19), "a", 1));
assertEquals(Lifespan.span(0, 19), valA.getLifespan()); 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(valA, root.setValue(Lifespan.span(-5, 4), "a", 1));
assertEquals(Lifespan.span(-5, 9), valA.getLifespan()); 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(valA, root.setValue(Lifespan.span(5, 14), "a", 1));
assertEquals(Lifespan.span(0, 14), valA.getLifespan()); 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(valA, root.setValue(Lifespan.span(0, 9), "a", 1));
assertEquals(Lifespan.span(0, 9), valA.getLifespan()); 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(valA, root.setValue(Lifespan.at(5), "a", 1));
assertEquals(Lifespan.span(0, 9), valA.getLifespan()); 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(valA, root.setValue(Lifespan.span(-5, 14), "a", 1));
assertEquals(Lifespan.span(-5, 14), valA.getLifespan()); 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(valA, root.setValue(Lifespan.span(0, 5), "a", 1));
assertEquals(Lifespan.span(0, 9), valA.getLifespan()); 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(valA, root.setValue(Lifespan.span(0, 14), "a", 1));
assertEquals(Lifespan.span(0, 14), valA.getLifespan()); 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(valA, root.setValue(Lifespan.span(5, 9), "a", 1));
assertEquals(Lifespan.span(0, 9), valA.getLifespan()); 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(valA, root.setValue(Lifespan.span(-5, 9), "a", 1));
assertEquals(Lifespan.span(-5, 9), valA.getLifespan()); 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 valA = root.setValue(Lifespan.span(0, 9), "a", 1);
TraceObjectValue valB = root.setValue(Lifespan.span(20, 29), "a", 1); TraceObjectValue valB = root.setValue(Lifespan.span(20, 29), "a", 1);
assertNotSame(valA, valB); 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(valA, root.setValue(Lifespan.span(10, 19), "a", 1));
assertEquals(Lifespan.span(0, 29), valA.getLifespan()); assertEquals(Lifespan.span(0, 29), valA.getLifespan());
assertTrue(valB.isDeleted()); 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()) { try (Transaction tx = b.startTransaction()) {
root = manager.createRootObject(ctx.getSchema(new SchemaName("Session"))).getChild(); root = manager.createRootObject(ctx.getSchema(new SchemaName("Session"))).getChild();
assertNull(root.setValue(Lifespan.span(0, 9), "a", null)); 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)); 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)); 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)), assertEquals(List.of(Lifespan.span(0, 4), Lifespan.span(6, 9)),
root.getOrderedValues(Lifespan.ALL, "a", true) root.getOrderedValues(Lifespan.ALL, "a", true)
@ -845,10 +853,10 @@ public class DBTraceObjectManagerTest extends AbstractGhidraHeadlessIntegrationT
try (Transaction tx = b.startTransaction()) { try (Transaction tx = b.startTransaction()) {
root = manager.createRootObject(ctx.getSchema(new SchemaName("Session"))).getChild(); root = manager.createRootObject(ctx.getSchema(new SchemaName("Session"))).getChild();
assertNotNull(root.setValue(Lifespan.span(0, 9), "a", 1)); 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)); 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 // Delete a leaf
TraceObject t1 = targets.get(1); TraceObject t1 = targets.get(1);
assertFalse(t1.isDeleted()); assertFalse(t1.isDeleted());
assertEquals(3, targetContainer.getValues().size()); assertEquals(3, targetContainer.getValues(Lifespan.ALL).size());
assertEquals(t1, Unique.assertOne( assertEquals(t1, Unique.assertOne(
manager.getObjectsByPath(Lifespan.ALL, TraceObjectKeyPath.parse("Targets[1]")))); manager.getObjectsByPath(Lifespan.ALL, TraceObjectKeyPath.parse("Targets[1]"))));
assertEquals(t1, t1.getAttribute(1, "self").getValue()); assertEquals(t1, t1.getAttribute(1, "self").getValue());
@ -890,8 +898,8 @@ public class DBTraceObjectManagerTest extends AbstractGhidraHeadlessIntegrationT
} }
assertTrue(t1.isDeleted()); assertTrue(t1.isDeleted());
assertTrue(t1.getParents().isEmpty()); assertTrue(t1.getParents(Lifespan.ALL).isEmpty());
assertEquals(2, targetContainer.getValues().size()); assertEquals(2, targetContainer.getValues(Lifespan.ALL).size());
assertEquals(0, assertEquals(0,
manager.getObjectsByPath(Lifespan.ALL, TraceObjectKeyPath.parse("Targets[1]")).count()); manager.getObjectsByPath(Lifespan.ALL, TraceObjectKeyPath.parse("Targets[1]")).count());
assertNull(t1.getAttribute(2, "self")); assertNull(t1.getAttribute(2, "self"));
@ -901,13 +909,14 @@ public class DBTraceObjectManagerTest extends AbstractGhidraHeadlessIntegrationT
TraceObject t0 = targets.get(0); TraceObject t0 = targets.get(0);
assertEquals(2, assertEquals(2,
manager.getObjectsByPath(Lifespan.ALL, TraceObjectKeyPath.parse("Targets[]")).count()); manager.getObjectsByPath(Lifespan.ALL, TraceObjectKeyPath.parse("Targets[]")).count());
assertTrue(t0.getParents().stream().anyMatch(v -> v.getParent() == targetContainer)); assertTrue(
assertEquals(2, targetContainer.getValues().size()); t0.getParents(Lifespan.ALL).stream().anyMatch(v -> v.getParent() == targetContainer));
assertEquals(2, targetContainer.getValues(Lifespan.ALL).size());
b.trace.undo(); b.trace.undo();
b.trace.redo(); b.trace.redo();
assertEquals(2, targetContainer.getValues().size()); assertEquals(2, targetContainer.getValues(Lifespan.ALL).size());
try (Transaction tx = b.startTransaction()) { try (Transaction tx = b.startTransaction()) {
targetContainer.delete(); targetContainer.delete();
@ -916,7 +925,8 @@ public class DBTraceObjectManagerTest extends AbstractGhidraHeadlessIntegrationT
assertEquals(0, assertEquals(0,
manager.getObjectsByPath(Lifespan.ALL, TraceObjectKeyPath.parse("Targets[]")).count()); manager.getObjectsByPath(Lifespan.ALL, TraceObjectKeyPath.parse("Targets[]")).count());
assertFalse(t0.isDeleted()); 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 // A little odd, but allows branch to be replaced and successors restored later
assertEquals(t0, root.getValue(0, "curTarget").getValue()); assertEquals(t0, root.getValue(0, "curTarget").getValue());
} }
@ -1093,4 +1103,76 @@ public class DBTraceObjectManagerTest extends AbstractGhidraHeadlessIntegrationT
assertTrue(hiddenOutside.getCanonicalParent(0).isHidden()); 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));
}
} }

View file

@ -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"));
}
}

View file

@ -27,6 +27,7 @@ import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
import db.*; import db.*;
import ghidra.program.model.address.*;
import ghidra.util.Msg; import ghidra.util.Msg;
import ghidra.util.database.annot.*; import ghidra.util.database.annot.*;
import ghidra.util.database.annot.DBAnnotatedField.DefaultCodec; import ghidra.util.database.annot.DBAnnotatedField.DefaultCodec;
@ -110,6 +111,32 @@ import ghidra.util.exception.VersionException;
*/ */
public class DBCachedObjectStoreFactory { public class DBCachedObjectStoreFactory {
public record RecAddress(int spaceId, long offset) {
public static RecAddress fromAddress(Address address) {
return new RecAddress(address.getAddressSpace().getSpaceID(), address.getOffset());
}
public long offset() {
return offset;
}
public Address toAddress(AddressFactory factory) {
return factory.getAddressSpace(spaceId).getAddress(offset);
}
}
public record RecRange(int spaceId, long min, long max) {
public static RecRange fromRange(AddressRange range) {
return new RecRange(range.getAddressSpace().getSpaceID(),
range.getMinAddress().getOffset(), range.getMaxAddress().getOffset());
}
public AddressRange toRange(AddressFactory factory) {
AddressSpace space = factory.getAddressSpace(spaceId);
return new AddressRangeImpl(space.getAddress(min), space.getAddress(max));
}
}
/** /**
* A codec for encoding alternative data types * A codec for encoding alternative data types
* *
@ -928,11 +955,41 @@ public class DBCachedObjectStoreFactory {
/** Codec for {@code String[]} */ /** Codec for {@code String[]} */
PrimitiveCodec<String[]> STRING_ARR = PrimitiveCodec<String[]> STRING_ARR =
new ArrayObjectCodec<>(new LengthBoundCodec<>(STRING)); new ArrayObjectCodec<>(new LengthBoundCodec<>(STRING));
PrimitiveCodec<RecAddress> ADDRESS = new AbstractPrimitiveCodec<>(RecAddress.class) {
@Override
public RecAddress decode(ByteBuffer buffer) {
int spaceId = buffer.getInt();
long offset = buffer.getLong();
return new RecAddress(spaceId, offset);
}
@Override
public void encode(ByteBuffer buffer, RecAddress value) {
buffer.putInt(value.spaceId);
buffer.putLong(value.offset);
}
};
PrimitiveCodec<RecRange> RANGE = new AbstractPrimitiveCodec<>(RecRange.class) {
@Override
public RecRange decode(ByteBuffer buffer) {
int spaceId = buffer.getInt();
long min = buffer.getLong();
long max = buffer.getLong();
return new RecRange(spaceId, min, max);
}
@Override
public void encode(ByteBuffer buffer, RecRange value) {
buffer.putInt(value.spaceId);
buffer.putLong(value.min);
buffer.putLong(value.max);
}
};
// TODO: No floats? // TODO: No floats?
Map<Byte, PrimitiveCodec<?>> CODECS_BY_SELECTOR = Stream Map<Byte, PrimitiveCodec<?>> CODECS_BY_SELECTOR = Stream
.of(BOOL, BYTE, CHAR, SHORT, INT, LONG, STRING, BOOL_ARR, BYTE_ARR, CHAR_ARR, .of(BOOL, BYTE, CHAR, SHORT, INT, LONG, STRING, BOOL_ARR, BYTE_ARR, CHAR_ARR,
SHORT_ARR, INT_ARR, LONG_ARR, STRING_ARR) SHORT_ARR, INT_ARR, LONG_ARR, STRING_ARR, ADDRESS, RANGE)
.collect(Collectors.toMap(c -> c.getSelector(), c -> c)); .collect(Collectors.toMap(c -> c.getSelector(), c -> c));
Map<Class<?>, PrimitiveCodec<?>> CODECS_BY_CLASS = CODECS_BY_SELECTOR.values() Map<Class<?>, PrimitiveCodec<?>> CODECS_BY_CLASS = CODECS_BY_SELECTOR.values()
.stream() .stream()

View file

@ -15,6 +15,8 @@
*/ */
package ghidra.util.database.spatial; package ghidra.util.database.spatial;
import java.util.List;
import db.DBRecord; import db.DBRecord;
import ghidra.util.database.DBCachedObjectStore; import ghidra.util.database.DBCachedObjectStore;
@ -39,6 +41,8 @@ public abstract class DBTreeNodeRecord<NS extends BoundingShape<NS>> extends DBT
} }
}; };
public static final List<NodeType> VALUES = List.of(values());
private final boolean directory; private final boolean directory;
private final boolean leafParent; private final boolean leafParent;

View file

@ -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.util.database.spatial.hyper;
import java.util.Comparator;
import ghidra.util.database.spatial.BoundedShape;
import ghidra.util.database.spatial.Query;
public abstract class AbstractHyperBoxQuery< //
P extends HyperPoint, //
DS extends BoundedShape<NS>, //
NS extends HyperBox<P, NS>, //
Q extends AbstractHyperBoxQuery<P, DS, NS, Q>> //
implements Query<DS, NS> {
public interface QueryFactory<NS extends HyperBox<?, NS>, Q extends AbstractHyperBoxQuery<?, ?, NS, Q>> {
Q create(NS ls, NS us, HyperDirection direction);
}
protected static <P extends HyperPoint, NS extends HyperBox<P, NS>, //
Q extends AbstractHyperBoxQuery<P, ?, NS, Q>> Q intersecting(NS shape,
HyperDirection direction, QueryFactory<NS, Q> factory) {
HyperBox<P, ?> full = shape.space().getFull();
NS ls = shape.immutable(full.lCorner(), shape.uCorner());
NS us = shape.immutable(shape.lCorner(), full.uCorner());
return factory.create(ls, us, direction);
}
protected static <P extends HyperPoint, NS extends HyperBox<P, NS>, //
Q extends AbstractHyperBoxQuery<P, ?, NS, Q>> Q enclosing(NS shape,
HyperDirection direction, QueryFactory<NS, Q> factory) {
HyperBox<P, ?> full = shape.space().getFull();
NS ls = shape.immutable(full.lCorner(), shape.lCorner());
NS us = shape.immutable(shape.uCorner(), full.uCorner());
return factory.create(ls, us, direction);
}
protected static <P extends HyperPoint, NS extends HyperBox<P, NS>, //
Q extends AbstractHyperBoxQuery<P, ?, NS, Q>> Q enclosed(NS shape,
HyperDirection direction, QueryFactory<NS, Q> factory) {
HyperBox<P, ?> full = shape.space().getFull();
NS ls = shape.immutable(shape.lCorner(), full.uCorner());
NS us = shape.immutable(full.lCorner(), shape.uCorner());
return factory.create(ls, us, direction);
}
protected static <P extends HyperPoint, NS extends HyperBox<P, NS>, //
Q extends AbstractHyperBoxQuery<P, ?, NS, Q>> Q equalTo(NS shape,
HyperDirection direction, QueryFactory<NS, Q> factory) {
NS ls = shape.immutable(shape.lCorner(), shape.lCorner());
NS us = shape.immutable(shape.uCorner(), shape.uCorner());
return factory.create(ls, us, direction);
}
protected final NS ls;
protected final NS us;
protected final EuclideanHyperSpace<P, NS> space;
protected final HyperDirection direction;
protected Comparator<NS> comparator;
public AbstractHyperBoxQuery(NS ls, NS us, EuclideanHyperSpace<P, NS> space,
HyperDirection direction) {
this.ls = ls;
this.us = us;
this.space = space;
this.direction = direction;
}
@Override
public boolean terminateEarlyData(DS shape) {
return terminateEarlyNode(shape.getBounds());
}
private <T> boolean dimTerminateEarlyNode(Dimension<T, P, NS> dim, NS shape) {
return direction.forward()
? dim.compare(dim.lower(shape), dim.upper(us)) > 0
: dim.compare(dim.upper(shape), dim.lower(ls)) < 0;
}
@Override
public boolean terminateEarlyNode(NS shape) {
Dimension<?, P, NS> dim = space.getDimensions().get(direction.dimension());
return dimTerminateEarlyNode(dim, shape);
}
@Override
public Comparator<NS> getBoundsComparator() {
if (comparator == null) {
Dimension<?, P, NS> dim = space.getDimensions().get(direction.dimension());
comparator = createBoundsComparator(dim);
}
return comparator;
}
protected <T> Comparator<NS> createBoundsComparator(Dimension<T, P, NS> dim) {
if (direction.forward()) {
return Comparator.comparing(dim::lower, dim::compare);
}
return Comparator.comparing(dim::upper, (a, b) -> dim.compare(b, a));
}
private <T> boolean isNone(Dimension<T, P, NS> dim, NS shape) {
if (dim.compare(dim.lower(shape), dim.upper(ls)) > 0) {
return true;
}
if (dim.compare(dim.lower(shape), dim.upper(us)) > 0) {
return true;
}
if (dim.compare(dim.upper(shape), dim.lower(us)) < 0) {
return true;
}
if (dim.compare(dim.upper(shape), dim.lower(ls)) < 0) {
return true;
}
return false;
}
private <T> boolean isSome(Dimension<T, P, NS> dim, NS shape) {
if (dim.compare(dim.lower(shape), dim.lower(ls)) < 0) {
return true;
}
if (dim.compare(dim.lower(shape), dim.lower(us)) < 0) {
return true;
}
if (dim.compare(dim.upper(shape), dim.upper(us)) > 0) {
return true;
}
if (dim.compare(dim.upper(shape), dim.upper(ls)) > 0) {
return true;
}
return false;
}
@Override
public QueryInclusion testNode(NS shape) {
for (Dimension<?, P, NS> dim : space.getDimensions()) {
if (isNone(dim, shape)) {
return QueryInclusion.NONE;
}
}
for (Dimension<?, P, NS> dim : space.getDimensions()) {
if (isSome(dim, shape)) {
return QueryInclusion.SOME;
}
}
return QueryInclusion.ALL;
}
protected abstract Q create(NS ir1, NS ir2, HyperDirection newDirection);
public Q and(Q query) {
NS ir1 = ls.intersection(query.ls);
NS ir2 = us.intersection(query.us);
return create(ir1, ir2, query.direction != null ? query.direction : this.direction);
}
public HyperDirection getDirection() {
if (direction == null) {
return new HyperDirection(0, true);
}
return direction;
}
public Q starting(HyperDirection newDirection) {
return create(ls, us, newDirection);
}
}

View file

@ -0,0 +1,81 @@
/* ###
* 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.util.database.spatial.hyper;
import java.io.IOException;
import java.util.Comparator;
import java.util.List;
import ghidra.util.database.DBCachedObjectStoreFactory;
import ghidra.util.database.spatial.*;
import ghidra.util.exception.VersionException;
public abstract class AbstractHyperRStarTree< //
P extends HyperPoint, //
DS extends BoundedShape<NS>, //
DR extends DBTreeDataRecord<DS, NS, T>, //
NS extends HyperBox<P, NS>, //
NR extends DBTreeNodeRecord<NS>, //
T, //
Q extends AbstractHyperBoxQuery<P, DS, NS, Q>> //
extends AbstractRStarConstraintsTree<DS, DR, NS, NR, T, Q> {
protected static class AsSpatialMap< //
DS extends BoundedShape<NS>, //
DR extends DBTreeDataRecord<DS, NS, T>, //
NS extends HyperBox<?, NS>, T, Q extends AbstractHyperBoxQuery<?, DS, NS, Q>>
extends AbstractConstraintsTreeSpatialMap<DS, DR, NS, T, Q> {
public AsSpatialMap(AbstractConstraintsTree<DS, DR, NS, ?, T, Q> tree, Q query) {
super(tree, query);
}
@Override
public AsSpatialMap<DS, DR, NS, T, Q> reduce(Q andQuery) {
return new AsSpatialMap<>(this.tree,
this.query == null ? andQuery : this.query.and(andQuery));
}
}
protected final EuclideanHyperSpace<P, NS> space;
protected final List<Comparator<NS>> axes;
protected <V> Comparator<NS> dimComparator(Dimension<V, P, NS> dim) {
return Comparator.comparing(dim::lower, dim::compare);
}
public AbstractHyperRStarTree(DBCachedObjectStoreFactory storeFactory, String tableName,
EuclideanHyperSpace<P, NS> space, Class<DR> dataType, Class<NR> nodeType,
boolean upgradeable, int maxChildren) throws VersionException, IOException {
super(storeFactory, tableName, dataType, nodeType, upgradeable, maxChildren);
this.space = space;
this.axes = space.getDimensions().stream().map(this::dimComparator).toList();
}
@Override
protected List<Comparator<NS>> getSplitAxes() {
return axes;
}
@Override
protected Comparator<NS> getDefaultBoundsComparator() {
return axes.get(0);
}
@Override
public AbstractConstraintsTreeSpatialMap<DS, DR, NS, T, Q> asSpatialMap() {
return new AsSpatialMap<>(this, null);
}
}

View file

@ -0,0 +1,105 @@
/* ###
* 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.util.database.spatial.hyper;
public interface Dimension<T, P extends HyperPoint, B extends HyperBox<P, B>> {
T value(P point);
default T lower(B box) {
return value(box.lCorner());
}
default T upper(B box) {
return value(box.uCorner());
}
int compare(T a, T b);
double distance(T a, T b);
T mid(T a, T b);
default T boxMid(B box) {
return mid(lower(box), upper(box));
}
default T min(T a, T b) {
return compare(a, b) < 0 ? a : b;
}
default T max(T a, T b) {
return compare(a, b) > 0 ? a : b;
}
T absoluteMin();
T absoluteMax();
default double pointDistance(P a, P b) {
return distance(value(a), value(b));
}
default boolean contains(B box, P point) {
T value = value(point);
if (compare(value, lower(box)) < 0) {
return false;
}
if (compare(value, upper(box)) > 0) {
return false;
}
return true;
}
default boolean intersect(B a, B b) {
if (compare(lower(a), upper(b)) > 0) {
return false;
}
if (compare(upper(a), lower(b)) < 0) {
return false;
}
return true;
}
default boolean encloses(B outer, B inner) {
if (compare(lower(outer), lower(inner)) > 0) {
return false;
}
if (compare(upper(outer), upper(inner)) < 0) {
return false;
}
return true;
}
default T intersectionLower(B a, B b) {
return max(lower(a), lower(b));
}
default T intersectionUpper(B a, B b) {
return min(upper(a), upper(b));
}
default T unionLower(B a, B b) {
return min(lower(a), lower(b));
}
default T unionUpper(B a, B b) {
return max(upper(a), upper(b));
}
default double measure(B box) {
return distance(upper(box), lower(box));
}
}

View file

@ -0,0 +1,132 @@
/* ###
* 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.util.database.spatial.hyper;
import java.util.List;
import java.util.Objects;
public interface EuclideanHyperSpace<P extends HyperPoint, B extends HyperBox<P, B>> {
List<Dimension<?, P, B>> getDimensions();
B getFull();
default boolean boxesEqual(B a, B b) {
for (Dimension<?, P, B> dim : getDimensions()) {
if (!Objects.equals(dim.lower(a), dim.lower(b))) {
return false;
}
if (!Objects.equals(dim.upper(a), dim.upper(b))) {
return false;
}
}
return true;
}
default Object[] collectBounds(B box) {
List<Dimension<?, P, B>> dims = getDimensions();
Object[] result = new Object[dims.size() * 2];
for (int i = 0; i < dims.size(); i++) {
Dimension<?, P, B> d = dims.get(i);
result[i * 2] = d.lower(box);
result[i * 2 + 1] = d.upper(box);
}
return result;
}
default boolean boxContains(B box, P point) {
for (Dimension<?, P, B> dim : getDimensions()) {
if (!dim.contains(box, point)) {
return false;
}
}
return true;
}
default double boxArea(B box) {
double result = 1;
for (Dimension<?, P, B> dim : getDimensions()) {
result *= 1 + dim.measure(box);
}
return result;
}
default double boxMargin(B box) {
double result = 0;
for (Dimension<?, P, B> dim : getDimensions()) {
result += 1 + dim.measure(box);
}
return result;
}
P boxCenter(B box);
default <T> double measureUnion(Dimension<T, P, B> dim, B a, B b) {
T unionLower = dim.unionLower(a, b);
T unionUpper = dim.unionUpper(a, b);
return dim.distance(unionUpper, unionLower);
}
default double computeAreaUnionBounds(B a, B b) {
double result = 1;
for (Dimension<?, P, B> dim : getDimensions()) {
result *= 1 + measureUnion(dim, a, b);
}
return result;
}
default <T> double measureIntersection(Dimension<T, P, B> dim, B a, B b) {
T intLower = dim.intersectionLower(a, b);
T intUpper = dim.intersectionUpper(a, b);
if (dim.compare(intLower, intUpper) > 0) {
return 0;
}
return dim.distance(intUpper, intLower);
}
default double computeAreaIntersection(B a, B b) {
double result = 1;
for (Dimension<?, P, B> dim : getDimensions()) {
double measure = measureIntersection(dim, a, b);
if (measure == 0) {
return 0;
}
result *= 1 + measure;
}
return result;
}
default double sqDistance(P a, P b) {
double result = 0;
for (Dimension<?, P, B> dim : getDimensions()) {
double dist = dim.pointDistance(a, b);
result += dist * dist;
}
return result;
}
B boxUnionBounds(B a, B b);
B boxIntersection(B b, B shape);
default boolean boxEncloses(B outer, B inner) {
for (Dimension<?, P, B> dim : getDimensions()) {
if (!dim.encloses(outer, inner)) {
return false;
}
}
return true;
}
}

View file

@ -0,0 +1,102 @@
/* ###
* 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.util.database.spatial.hyper;
import java.util.Objects;
import ghidra.util.database.spatial.BoundingShape;
public interface HyperBox<P extends HyperPoint, B extends HyperBox<P, B>> extends BoundingShape<B> {
EuclideanHyperSpace<P, B> space();
@SuppressWarnings("unchecked")
default boolean doEquals(Object obj) {
if (!(obj instanceof HyperBox<?, ?> that)) {
return false;
}
if (this.space() != that.space()) {
return false;
}
return space().boxesEqual((B) this, (B) that);
}
@SuppressWarnings("unchecked")
default int doHashCode() {
return Objects.hash(space().collectBounds((B) this));
}
@SuppressWarnings("unchecked")
default boolean contains(P p) {
return space().boxContains((B) this, p);
}
@Override
@SuppressWarnings("unchecked")
default double getArea() {
return space().boxArea((B) this);
}
@Override
@SuppressWarnings("unchecked")
default double getMargin() {
return space().boxMargin((B) this);
}
@SuppressWarnings("unchecked")
default P getCenter() {
return space().boxCenter((B) this);
}
@Override
@SuppressWarnings("unchecked")
default double computeAreaUnionBounds(B shape) {
return space().computeAreaUnionBounds((B) this, shape);
}
@Override
@SuppressWarnings("unchecked")
default double computeAreaIntersection(B shape) {
return space().computeAreaIntersection((B) this, shape);
}
@Override
default double computeCentroidDistance(B shape) {
return space().sqDistance(this.getCenter(), shape.getCenter());
}
@Override
@SuppressWarnings("unchecked")
default B unionBounds(B shape) {
return space().boxUnionBounds((B) this, shape);
}
@Override
@SuppressWarnings("unchecked")
default boolean encloses(B shape) {
return space().boxEncloses((B) this, shape);
}
P lCorner();
P uCorner();
B immutable(P lCorner, P uCorner);
@SuppressWarnings("unchecked")
default B intersection(B shape) {
return space().boxIntersection((B) this, shape);
}
}

View file

@ -0,0 +1,20 @@
/* ###
* 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.util.database.spatial.hyper;
public record HyperDirection(int dimension, boolean forward) {
public static final HyperDirection DEFAULT = new HyperDirection(0, true);
}

View file

@ -0,0 +1,19 @@
/* ###
* 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.util.database.spatial.hyper;
public interface HyperPoint {
}

View file

@ -0,0 +1,45 @@
/* ###
* 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.util.database.spatial.hyper;
public interface LongDimension<P extends HyperPoint, B extends HyperBox<P, B>>
extends Dimension<Long, P, B> {
@Override
default int compare(Long a, Long b) {
return Long.compare(a, b);
}
@Override
default double distance(Long a, Long b) {
return a - b;
}
@Override
default Long mid(Long a, Long b) {
return a + (b - a) / 2;
}
@Override
default Long absoluteMin() {
return Long.MIN_VALUE;
}
@Override
default Long absoluteMax() {
return Long.MAX_VALUE;
}
}

View file

@ -0,0 +1,145 @@
/* ###
* 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.util.database.spatial.hyper;
import java.math.BigInteger;
import java.util.Objects;
public interface StringDimension<P extends HyperPoint, B extends HyperBox<P, B>>
extends Dimension<String, P, B> {
@Override
default int compare(String a, String b) {
if (a == null && b == null) {
return 0;
}
// Treat null as the absolute max value
if (a == null) {
return 1;
}
if (b == null) {
return -1;
}
return a.compareTo(b);
}
static int charAt(String s, int i) {
if (s == null) {
if (i == 0) {
return 128;
}
return 0;
}
if (i < s.length()) {
return Math.min(127, s.charAt(i));
}
return 0;
}
static int lenStrings(String a, String b) {
if (a == null) {
return b.length();
}
if (b == null) {
return a.length();
}
return Math.max(a.length(), b.length());
}
@Override
default double distance(String a, String b) {
if (Objects.equals(a, b)) {
return 0;
}
// TODO: May revisit the starting place value, to scale this dimension down in importance.
double result = 0;
double placeVal = Double.MAX_VALUE / 128;
int len = lenStrings(a, b);
for (int i = 0; i < len; i++) {
int ca = charAt(a, i);
int cb = charAt(b, i);
double oldResult = result;
result += placeVal * (cb - ca);
if (oldResult == result) {
// Can't capture any more precision, so we're done
return result;
}
if (placeVal == Double.MIN_VALUE || placeVal == 0) {
return result;
}
placeVal /= 128;
}
return result;
}
static BigInteger subtractExact(String a, String b) {
int len = lenStrings(a, b);
BigInteger result = BigInteger.ZERO;
for (int i = 0; i < len; i++) {
int ca = charAt(a, i);
int cb = charAt(b, i);
result = result.shiftLeft(7).add(BigInteger.valueOf(ca - cb));
}
return result;
}
static String add(String a, BigInteger d, int len) {
char[] cb = new char[len];
boolean carry = false;
for (int i = len - 1; i >= 0; i--) {
int tc = charAt(a, i) + (d.intValue() % 128) + (carry ? 1 : 0);
d = d.shiftRight(7);
carry = tc >= 128;
cb[i] = (char) (tc % 128);
}
return new String(cb);
}
@Override
default String mid(String a, String b) {
if (Objects.equals(a, b)) {
return a;
}
if (a == null && b.isEmpty()) {
return "" + ((char) 64);
}
if (b == null && a.isEmpty()) {
return "" + ((char) 64);
}
if (a.compareTo(b) > 0) {
// I'll cheat a bit here to avoid carries with negative operand
String c = a;
a = b;
b = c;
}
BigInteger diff = subtractExact(b, a);
String maybeTrunc = add(a, diff.shiftRight(1), lenStrings(a, b));
if (diff.testBit(0)) {
return maybeTrunc + ((char) 64);
}
return maybeTrunc;
}
@Override
default String absoluteMin() {
return "";
}
@Override
default String absoluteMax() {
return null;
}
}

View file

@ -0,0 +1,45 @@
/* ###
* 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.util.database.spatial.hyper;
public interface ULongDimension<P extends HyperPoint, B extends HyperBox<P, B>>
extends Dimension<Long, P, B> {
@Override
default int compare(Long a, Long b) {
return Long.compareUnsigned(a, b);
}
@Override
default double distance(Long a, Long b) {
return a - b;
}
@Override
default Long mid(Long a, Long b) {
return a + Long.divideUnsigned(b - a, 2);
}
@Override
default Long absoluteMin() {
return 0L;
}
@Override
default Long absoluteMax() {
return -1L;
}
}

View file

@ -25,6 +25,7 @@ public interface Point2D<X, Y> {
default double computeDistance(Point2D<X, Y> point) { default double computeDistance(Point2D<X, Y> point) {
double distX = getSpace().distX(getX(), point.getX()); double distX = getSpace().distX(getX(), point.getX());
double distY = getSpace().distY(getY(), point.getY()); double distY = getSpace().distY(getY(), point.getY());
// NB. Square root is unnecessary, if this is just for comparison
return distX * distX + distY * distY; return distX * distX + distY * distY;
} }
} }

View file

@ -761,7 +761,7 @@ public class DbgEngCommandsTest extends AbstractDbgEngTraceRmiTest {
Map.entry("[1]", Lifespan.nowOn(0)), Map.entry("[1]", Lifespan.nowOn(0)),
Map.entry("[2]", Lifespan.span(0, 9)), Map.entry("[2]", Lifespan.span(0, 9)),
Map.entry("[3]", Lifespan.nowOn(0))), Map.entry("[3]", Lifespan.nowOn(0))),
object.getValues() object.getValues(Lifespan.ALL)
.stream() .stream()
.collect(Collectors.toMap(v -> v.getEntryKey(), v -> v.getLifespan()))); .collect(Collectors.toMap(v -> v.getEntryKey(), v -> v.getLifespan())));
} }

View file

@ -786,7 +786,7 @@ public class GdbCommandsTest extends AbstractGdbTraceRmiTest {
Map.entry("[1]", Lifespan.nowOn(0)), Map.entry("[1]", Lifespan.nowOn(0)),
Map.entry("[2]", Lifespan.span(0, 9)), Map.entry("[2]", Lifespan.span(0, 9)),
Map.entry("[3]", Lifespan.nowOn(0))), Map.entry("[3]", Lifespan.nowOn(0))),
object.getValues() object.getValues(Lifespan.ALL)
.stream() .stream()
.collect(Collectors.toMap(v -> v.getEntryKey(), v -> v.getLifespan()))); .collect(Collectors.toMap(v -> v.getEntryKey(), v -> v.getLifespan())));
} }

View file

@ -810,7 +810,7 @@ public class LldbCommandsTest extends AbstractLldbTraceRmiTest {
Map.entry("[1]", Lifespan.nowOn(0)), Map.entry("[1]", Lifespan.nowOn(0)),
Map.entry("[2]", Lifespan.span(0, 9)), Map.entry("[2]", Lifespan.span(0, 9)),
Map.entry("[3]", Lifespan.nowOn(0))), Map.entry("[3]", Lifespan.nowOn(0))),
object.getValues() object.getValues(Lifespan.ALL)
.stream() .stream()
.collect(Collectors.toMap(v -> v.getEntryKey(), v -> v.getLifespan()))); .collect(Collectors.toMap(v -> v.getEntryKey(), v -> v.getLifespan())));
} }