diff --git a/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/service/rmi/trace/TraceRmiHandler.java b/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/service/rmi/trace/TraceRmiHandler.java index 4b7c95bbad..7ed4932b07 100644 --- a/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/service/rmi/trace/TraceRmiHandler.java +++ b/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/service/rmi/trace/TraceRmiHandler.java @@ -356,6 +356,7 @@ public class TraceRmiHandler implements TraceRmiConnection { name = object.getName() + "." + nextId; } } + name = object.getName() + "." + System.currentTimeMillis(); // Don't catch it this last time return parent.createFile(name, object, monitor); } @@ -948,9 +949,12 @@ public class TraceRmiHandler implements TraceRmiConnection { tx.tx.abortOnClose(); } tx.tx.close(); + OpenTrace open = requireOpenTrace(tx.txId.doId); 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(); } @@ -976,10 +980,11 @@ public class TraceRmiHandler implements TraceRmiConnection { RequestGetValuesIntersecting req) throws AddressOverflowException { OpenTrace open = requireOpenTrace(req.getOid()); AddressRange range = open.toRange(req.getBox().getRange(), false); + String key = req.getKey() == "" ? null : req.getKey(); Collection col = range == null ? List.of() : open.trace.getObjectManager() - .getValuesIntersecting(toLifespan(req.getBox().getSpan()), range); + .getValuesIntersecting(toLifespan(req.getBox().getSpan()), range, key); return ReplyGetValues.newBuilder() .addAllValues(col.stream().map(TraceRmiHandler::makeValDesc).toList()) .build(); @@ -1074,16 +1079,15 @@ public class TraceRmiHandler implements TraceRmiConnection { if (object == null) { return ReplyRetainValues.getDefaultInstance(); } + Lifespan span = toLifespan(req.getSpan()); Collection values = switch (req.getKinds()) { - case VK_ELEMENTS -> object.getElements(); - case VK_ATTRIBUTES -> object.getAttributes(); - case VK_BOTH -> object.getValues(); + case VK_ELEMENTS -> object.getElements(span); + case VK_ATTRIBUTES -> object.getAttributes(span); + case VK_BOTH -> object.getValues(span); default -> throw new TraceRmiError("Protocol error: Invalid value kinds"); }; - Lifespan span = toLifespan(req.getSpan()); Set keysToKeep = Set.copyOf(req.getKeysList()); List keysToDelete = values.stream() - .filter(v -> v.getLifespan().intersects(span)) .map(v -> v.getEntryKey()) .filter(k -> !keysToKeep.contains(k)) .distinct() @@ -1123,6 +1127,7 @@ public class TraceRmiHandler implements TraceRmiConnection { // Implies request was to set value to null return ReplySetValue.newBuilder().setSpan(makeSpan(Lifespan.EMPTY)).build(); } + TraceObjectValue val = object.setValue(toLifespan(value.getSpan()), value.getKey(), objVal, toResolution(req.getResolution())); return ReplySetValue.newBuilder() @@ -1144,6 +1149,7 @@ public class TraceRmiHandler implements TraceRmiConnection { protected ReplyStartTx handleStartTx(RequestStartTx req) { OpenTrace open = requireOpenTrace(req.getOid()); Tid tid = requireAvailableTid(open, req.getTxid()); + open.trace.setEventsEnabled(false); @SuppressWarnings("resource") OpenTx tx = new OpenTx(tid, open.trace.openTransaction(req.getDescription()), req.getUndoable()); diff --git a/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/service/rmi/trace/TraceRmiTarget.java b/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/service/rmi/trace/TraceRmiTarget.java index f85884fbdb..cc50c3d321 100644 --- a/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/service/rmi/trace/TraceRmiTarget.java +++ b/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/service/rmi/trace/TraceRmiTarget.java @@ -847,11 +847,10 @@ public class TraceRmiTarget extends AbstractTarget { protected TraceObject getProcessForSpace(AddressSpace space) { for (TraceObjectValue objVal : trace.getObjectManager() - .getValuesIntersecting(Lifespan.at(getSnap()), - new AddressRangeImpl(space.getMinAddress(), space.getMaxAddress()))) { - if (!TargetMemoryRegion.RANGE_ATTRIBUTE_NAME.equals(objVal.getEntryKey())) { - continue; - } + .getValuesIntersecting( + Lifespan.at(getSnap()), + new AddressRangeImpl(space.getMinAddress(), space.getMaxAddress()), + TargetMemoryRegion.RANGE_ATTRIBUTE_NAME)) { TraceObject obj = objVal.getParent(); if (!obj.getInterfaces().contains(TraceObjectMemoryRegion.class)) { continue; diff --git a/Ghidra/Debug/Debugger-rmi-trace/src/main/proto/trace-rmi.proto b/Ghidra/Debug/Debugger-rmi-trace/src/main/proto/trace-rmi.proto index 10326ab452..21220af85e 100644 --- a/Ghidra/Debug/Debugger-rmi-trace/src/main/proto/trace-rmi.proto +++ b/Ghidra/Debug/Debugger-rmi-trace/src/main/proto/trace-rmi.proto @@ -361,6 +361,7 @@ message ReplyGetValues { message RequestGetValuesIntersecting { DomObjId oid = 1; Box box = 2; + string key = 3; } // Analysis operations diff --git a/Ghidra/Debug/Debugger-rmi-trace/src/main/py/src/ghidratrace/client.py b/Ghidra/Debug/Debugger-rmi-trace/src/main/py/src/ghidratrace/client.py index 6f3187727f..acc1eb4da2 100644 --- a/Ghidra/Debug/Debugger-rmi-trace/src/main/py/src/ghidratrace/client.py +++ b/Ghidra/Debug/Debugger-rmi-trace/src/main/py/src/ghidratrace/client.py @@ -389,10 +389,10 @@ class Trace(object): span = Lifespan(self.snap(), self.snap()) 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: 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): self.client._activate_object(self.id, object) @@ -1053,11 +1053,12 @@ class Client(object): return self._read_values(reply) 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.request_get_values_intersecting.oid.id = id self._write_span(root.request_get_values_intersecting.box.span, span) self._write_range(root.request_get_values_intersecting.box.range, rng) + root.request_get_values_intersecting.key = key def _handle(reply): return self._read_values(reply) diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/DebuggerLocationLabel.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/DebuggerLocationLabel.java index 7925e3aab9..55da69bf86 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/DebuggerLocationLabel.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/DebuggerLocationLabel.java @@ -136,7 +136,10 @@ public class DebuggerLocationLabel extends JLabel { if (sections.isEmpty()) { 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( Comparator.comparing(s -> s.getRange().getMinAddress()), Comparator.comparing(s -> -s.getRange().getLength())))); diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingPlugin.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingPlugin.java index 75923b2ebd..15a8d43fef 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingPlugin.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingPlugin.java @@ -32,7 +32,8 @@ import ghidra.app.plugin.core.codebrowser.CodeViewerProvider; import ghidra.app.plugin.core.debug.DebuggerPluginPackage; import ghidra.app.plugin.core.debug.event.*; 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.util.viewer.format.FormatManager; import ghidra.app.util.viewer.listingpanel.ListingPanel; diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memview/DebuggerMemviewTraceListener.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memview/DebuggerMemviewTraceListener.java index 9e5dfab453..e1be8a5c81 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memview/DebuggerMemviewTraceListener.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memview/DebuggerMemviewTraceListener.java @@ -228,6 +228,9 @@ public class DebuggerMemviewTraceListener extends TraceDomainObjectListener { private void processTrace(Trace trace) { updateList.clear(); provider.reset(); + if (!provider.isVisible()) { + return; + } TraceThreadManager threadManager = trace.getThreadManager(); for (TraceThread thread : threadManager.getAllThreads()) { threadChanged(thread); diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/model/AbstractQueryTableModel.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/model/AbstractQueryTableModel.java index a2d9d32a43..e6c1ca4aac 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/model/AbstractQueryTableModel.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/model/AbstractQueryTableModel.java @@ -16,6 +16,7 @@ package ghidra.app.plugin.core.debug.gui.model; import java.awt.Color; +import java.util.ArrayList; import java.util.Objects; import java.util.stream.Stream; @@ -303,9 +304,17 @@ public abstract class AbstractQueryTableModel extends ThreadedTableModel batch = new ArrayList<>(100); for (T t : (Iterable) streamRows(trace, query, span)::iterator) { - accumulator.add(t); - monitor.checkCancelled(); + batch.add(t); + if (batch.size() >= 100) { + accumulator.addAll(batch); + monitor.checkCancelled(); + batch.clear(); + } + } + if (batch.size() > 0) { + accumulator.addAll(batch); } } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/model/DebuggerModelProvider.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/model/DebuggerModelProvider.java index 2e4d654697..e2b01972db 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/model/DebuggerModelProvider.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/model/DebuggerModelProvider.java @@ -453,7 +453,8 @@ public class DebuggerModelProvider extends ComponentProvider implements Saveable if (parent == 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())) { return value.getCanonicalPath(); } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/model/ObjectTreeModel.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/model/ObjectTreeModel.java index 15af719001..7b2d34d53c 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/model/ObjectTreeModel.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/model/ObjectTreeModel.java @@ -605,9 +605,6 @@ public class ObjectTreeModel implements DisplaysModified { if (!showMethods && value.isObject() && value.getChild().isMethod(snap)) { return false; } - if (!value.getLifespan().intersects(span)) { - return false; - } return true; } @@ -624,7 +621,9 @@ public class ObjectTreeModel implements DisplaysModified { protected List generateObjectChildren(TraceObject object) { List result = ObjectTableModel - .distinctCanonical(object.getValues().stream().filter(this::isValueVisible)) + .distinctCanonical(object.getValues(span) + .stream() + .filter(this::isValueVisible)) .map(v -> nodeCache.getOrCreateNode(v)) .sorted() .collect(Collectors.toList()); diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/modules/DebuggerModulesPanel.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/modules/DebuggerModulesPanel.java index 4955351619..3e1f671cc9 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/modules/DebuggerModulesPanel.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/modules/DebuggerModulesPanel.java @@ -101,6 +101,9 @@ public class DebuggerModulesPanel extends AbstractObjectsTableBasedPanel" + // - " " + // - " " + // - " " + // - " " + // - " " + // - " " + // - " " + // - " " + // - " " + // - " " + // - " " + // - " " + // - " " + // - " " + // - " " + // - " " + // - " " + // - " " + // - " " + // - " " + // - " " + // - " " + // - " " + // - " " + // - " " + // - " " + // - " " + // - " " + // - " " + // - " " + // - " " + // - " " + // - " " + // - " " + // - " " + // - " " + // - " " + // - ""); + ctx = XmlSchemaContext.deserialize(""" + + + + + + + + + + + + + + + " + + + + " + + + + " + + + + + + + + + + + + + + + + """); addPlugin(tool, DebuggerListingPlugin.class); platformService = addPlugin(tool, DebuggerPlatformServicePlugin.class); diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/model/record/ObjectBasedTraceRecorderTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/model/record/ObjectBasedTraceRecorderTest.java index 80c1ef1977..f70822a219 100644 --- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/model/record/ObjectBasedTraceRecorderTest.java +++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/model/record/ObjectBasedTraceRecorderTest.java @@ -26,7 +26,7 @@ import org.junit.Test; import generic.Unique; 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.model.*; import ghidra.dbg.target.*; @@ -77,7 +77,7 @@ public class ObjectBasedTraceRecorderTest extends AbstractGhidraHeadedDebuggerTe protected void dumpValues(TraceObject 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()); } } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/DBTraceTimeViewport.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/DBTraceTimeViewport.java index 0427a684ea..03270ab921 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/DBTraceTimeViewport.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/DBTraceTimeViewport.java @@ -98,39 +98,39 @@ public class DBTraceTimeViewport implements TraceTimeViewport { if (range == null) { return false; } - try (LockHold hold = trace.lockRead()) { - synchronized (ordered) { - for (Lifespan rng : ordered) { - if (lifespan.contains(rng.lmax())) { - return true; - } - if (occlusion.occluded(object, range, rng)) { - return false; - } - } + /** + * NB. Because occlusion.occluded can be expensive, we should only keep the lock long enough + * to copy the spans. + */ + for (Lifespan rng : getOrderedSpans()) { + if (lifespan.contains(rng.lmax())) { + return true; + } + if (occlusion.occluded(object, range, rng)) { return false; } } + return false; } @Override public AddressSet computeVisibleParts(AddressSetView set, Lifespan lifespan, T object, Occlusion occlusion) { + List spans; try (LockHold hold = trace.lockRead()) { if (!containsAnyUpper(lifespan)) { return new AddressSet(); } - AddressSet remains = new AddressSet(set); - synchronized (ordered) { - for (Lifespan rng : ordered) { - if (lifespan.contains(rng.lmax())) { - return remains; - } - occlusion.remove(object, remains, rng); - if (remains.isEmpty()) { - return remains; - } - } + spans = getOrderedSpans(); + } + AddressSet remains = new AddressSet(set); + for (Lifespan rng : spans) { + if (lifespan.contains(rng.lmax())) { + return remains; + } + occlusion.remove(object, remains, rng); + if (remains.isEmpty()) { + return remains; } } // This condition should have been detected by !containsAnyUpper @@ -308,73 +308,62 @@ public class DBTraceTimeViewport implements TraceTimeViewport { @Override public List getOrderedSnaps() { + ArrayList result = new ArrayList<>(); try (LockHold hold = trace.lockRead()) { synchronized (ordered) { - return ordered - .stream() - .map(Lifespan::lmax) - .collect(Collectors.toList()); + for (Lifespan span : ordered) { + result.add(span.lmax()); + } } } + return result; } @Override public List getReversedSnaps() { + ArrayList result = new ArrayList<>(); try (LockHold hold = trace.lockRead()) { synchronized (ordered) { - List reversed = - ordered.stream().map(Lifespan::lmax).collect(Collectors.toList()); - Collections.reverse(reversed); - return reversed; + ListIterator it = ordered.listIterator(ordered.size()); + while (it.hasPrevious()) { + result.add(it.previous().lmax()); + } } } + return result; } @Override public T getTop(Function func) { - try (LockHold hold = trace.lockRead()) { - synchronized (ordered) { - for (Lifespan rng : ordered) { - T t = func.apply(rng.lmax()); - if (t != null) { - return t; - } - } - return null; + for (Lifespan rng : getOrderedSpans()) { + T t = func.apply(rng.lmax()); + if (t != null) { + return t; } } + return null; } @Override public Iterator mergedIterator(Function> iterFunc, Comparator comparator) { - List> iters; - try (LockHold hold = trace.lockRead()) { - synchronized (ordered) { - if (!isForked()) { - return iterFunc.apply(snap); - } - iters = ordered.stream() - .map(rng -> iterFunc.apply(rng.lmax())) - .collect(Collectors.toList()); - } + if (!isForked()) { + return iterFunc.apply(snap); } + List> iters = getOrderedSpans().stream() + .map(rng -> iterFunc.apply(rng.lmax())) + .collect(Collectors.toList()); return new UniqIterator<>(new MergeSortingIterator<>(iters, comparator)); } @Override public AddressSetView unionedAddresses(Function viewFunc) { - List views; - try (LockHold hold = trace.lockRead()) { - synchronized (ordered) { - if (!isForked()) { - return viewFunc.apply(snap); - } - views = ordered.stream() - .map(rng -> viewFunc.apply(rng.lmax())) - .collect(Collectors.toList()); - } + if (!isForked()) { + return viewFunc.apply(snap); } + List views = getOrderedSpans().stream() + .map(rng -> viewFunc.apply(rng.lmax())) + .collect(Collectors.toList()); return new UnionAddressSetView(views); } } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/map/DBTraceAddressSnapRangePropertyMapTree.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/map/DBTraceAddressSnapRangePropertyMapTree.java index 6cef794dda..203a813459 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/map/DBTraceAddressSnapRangePropertyMapTree.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/map/DBTraceAddressSnapRangePropertyMapTree.java @@ -118,7 +118,7 @@ public class DBTraceAddressSnapRangePropertyMapTree> NODE_TYPE_SHIFT) & NODE_TYPE_MASK]; + return NodeType.VALUES.get((typeAndChildCount >> NODE_TYPE_SHIFT) & NODE_TYPE_MASK); } @Override diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/program/DBTraceProgramViewMemory.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/program/DBTraceProgramViewMemory.java index 00d2b5f6db..a7cfcdb076 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/program/DBTraceProgramViewMemory.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/program/DBTraceProgramViewMemory.java @@ -26,12 +26,29 @@ import ghidra.util.LockHold; import ghidra.util.datastruct.WeakValueHashMap; public class DBTraceProgramViewMemory extends AbstractDBTraceProgramViewMemory { + // I think size should be about how many instructions may appear on screen at once. + // Double for good measure (in case windows are cloned, maximized, etc.) + private static final int REGION_CACHE_BY_ADDRESS_SIZE = 300; + + // Size should be about how many distinct regions are involved in displayed instructions + // Probably only about 5, but cost of 30 is still small. + private static final int REGION_CACHE_BY_NAME_SIZE = 30; // NB. Keep both per-region and force-full (per-space) block sets ready private final Map regionBlocks = new WeakValueHashMap<>(); private final Map spaceBlocks = new WeakValueHashMap<>(); + private final Map regionCacheByAddress = new LinkedHashMap<>() { + protected boolean removeEldestEntry(Map.Entry eldest) { + return this.size() > REGION_CACHE_BY_ADDRESS_SIZE; + } + }; + private final Map regionCacheByName = new LinkedHashMap<>() { + protected boolean removeEldestEntry(Map.Entry eldest) { + return this.size() > REGION_CACHE_BY_NAME_SIZE; + } + }; public DBTraceProgramViewMemory(DBTraceProgramView program) { super(program); @@ -90,8 +107,24 @@ public class DBTraceProgramViewMemory extends AbstractDBTraceProgramViewMemory { if (forceFullView) { return getSpaceBlock(addr.getAddressSpace()); } - TraceMemoryRegion region = getTopRegion(s -> memoryManager.getRegionContaining(s, addr)); - return region == null ? null : getRegionBlock(region); + TraceMemoryRegion region = regionCacheByAddress.get(addr); + if (region != null && !region.isDeleted()) { + /** + * TODO: This is assuming: 1) We never fork in non-scratch space. 2) Regions are not + * created in scratch space. These are convention, but weren't originally intended to be + * rules. This makes them rules. + */ + long s = program.viewport.getReversedSnaps().get(0); + if (region.getLifespan().contains(s)) { + return getRegionBlock(region); + } + } + region = getTopRegion(s -> memoryManager.getRegionContaining(s, addr)); + if (region != null) { + regionCacheByAddress.put(addr, region); + return getRegionBlock(region); + } + return null; } @Override @@ -100,9 +133,19 @@ public class DBTraceProgramViewMemory extends AbstractDBTraceProgramViewMemory { AddressSpace space = program.getAddressFactory().getAddressSpace(blockName); return space == null ? null : getSpaceBlock(space); } - TraceMemoryRegion region = - getTopRegion(s -> memoryManager.getLiveRegionByPath(s, blockName)); - return region == null ? null : getRegionBlock(region); + TraceMemoryRegion region = regionCacheByName.get(blockName); + if (region != null && !region.isDeleted()) { + long s = program.viewport.getReversedSnaps().get(0); + if (region.getLifespan().contains(s)) { + return getRegionBlock(region); + } + } + region = getTopRegion(s -> memoryManager.getLiveRegionByPath(s, blockName)); + if (region != null) { + regionCacheByName.put(blockName, region); + return getRegionBlock(region); + } + return null; } @Override diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/DBTraceObject.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/DBTraceObject.java index 3062e1f78b..02681d0040 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/DBTraceObject.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/DBTraceObject.java @@ -19,26 +19,24 @@ import java.io.IOException; import java.lang.reflect.Field; import java.util.*; import java.util.function.Function; -import java.util.stream.*; +import java.util.stream.Collectors; +import java.util.stream.Stream; import db.DBRecord; import db.StringField; import ghidra.dbg.target.TargetObject; import ghidra.dbg.target.schema.TargetObjectSchema; import ghidra.dbg.util.*; -import ghidra.program.model.address.AddressSpace; import ghidra.trace.database.DBTrace; import ghidra.trace.database.breakpoint.DBTraceObjectBreakpointLocation; import ghidra.trace.database.breakpoint.DBTraceObjectBreakpointSpec; -import ghidra.trace.database.map.DBTraceAddressSnapRangePropertyMapSpace; -import ghidra.trace.database.map.DBTraceAddressSnapRangePropertyMapTree.TraceAddressSnapRangeQuery; import ghidra.trace.database.memory.DBTraceObjectMemoryRegion; import ghidra.trace.database.memory.DBTraceObjectRegister; import ghidra.trace.database.module.*; import ghidra.trace.database.stack.DBTraceObjectStack; import ghidra.trace.database.stack.DBTraceObjectStackFrame; -import ghidra.trace.database.target.DBTraceObjectValue.PrimaryTriple; import ghidra.trace.database.target.InternalTraceObjectValue.ValueLifespanSetter; +import ghidra.trace.database.target.ValueSpace.SnapDimension; import ghidra.trace.database.target.visitors.*; import ghidra.trace.database.target.visitors.TreeTraversal.Visitor; import ghidra.trace.database.thread.DBTraceObjectThread; @@ -56,16 +54,18 @@ import ghidra.trace.model.target.*; import ghidra.trace.model.target.annot.TraceObjectInterfaceUtils; import ghidra.trace.model.thread.TraceObjectThread; import ghidra.trace.util.TraceChangeRecord; -import ghidra.util.*; +import ghidra.util.LockHold; +import ghidra.util.Msg; import ghidra.util.database.*; import ghidra.util.database.DBCachedObjectStoreFactory.AbstractDBFieldCodec; import ghidra.util.database.annot.*; -import ghidra.util.database.spatial.rect.Rectangle2DDirection; @DBAnnotatedObjectInfo(version = 0) public class DBTraceObject extends DBAnnotatedObject implements TraceObject { protected static final String TABLE_NAME = "Objects"; + private static final int VALUE_CACHE_SIZE = 50; + protected static // Map.Entry, Function> safeEntry( Class cls, Function ctor) { @@ -119,6 +119,9 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject { } } + record CachedLifespanValues(Lifespan span, Set values) { + } + // Canonical path static final String PATH_COLUMN_NAME = "Path"; @@ -135,6 +138,19 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject { private Map, TraceObjectInterface> ifaces; + private final Map valueCache = new LinkedHashMap<>() { + protected boolean removeEldestEntry(Map.Entry eldest) { + return size() > VALUE_CACHE_SIZE; + } + }; + private final Map nullCache = new LinkedHashMap<>() { + protected boolean removeEldestEntry(Map.Entry eldest) { + return size() > VALUE_CACHE_SIZE; + } + }; + private CachedLifespanValues cachedLifespanValues = null; + private MutableLifeSet cachedLife = null; + public DBTraceObject(DBTraceObjectManager manager, DBCachedObjectStore store, DBRecord record) { super(store, record); @@ -199,12 +215,17 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject { @Override public LifeSet getLife() { - // TODO: This should really be cached try (LockHold hold = manager.trace.lockRead()) { + if (cachedLife != null) { + synchronized (cachedLife) { + return DefaultLifeSet.copyOf(cachedLife); + } + } MutableLifeSet result = new DefaultLifeSet(); // NOTE: connected ranges should already be coalesced // No need to apply discreet domain getCanonicalParents(Lifespan.ALL).forEach(v -> result.add(v.getLifespan())); + cachedLife = result; return result; } } @@ -253,10 +274,10 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject { } protected void doRemoveTree(Lifespan span) { - for (DBTraceObjectValue parent : getParents()) { + for (InternalTraceObjectValue parent : getParents(span)) { parent.doTruncateOrDeleteAndEmitLifeChange(span); } - for (InternalTraceObjectValue value : getValues()) { + for (InternalTraceObjectValue value : getValues(span)) { value.doTruncateOrDeleteAndEmitLifeChange(span); if (value.isCanonical()) { value.getChild().doRemoveTree(span); @@ -275,26 +296,24 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject { public TraceObjectValue getCanonicalParent(long snap) { try (LockHold hold = manager.trace.lockRead()) { if (isRoot()) { - return manager.valueStore.getObjectAt(0); + return manager.getRootValue(); } - return getCanonicalParents(Lifespan.at(snap)).findAny().orElse(null); + return manager.valueMap + .reduce(TraceObjectValueQuery.canonicalParents(this, Lifespan.at(snap))) + .firstValue(); } } @Override - public Stream getCanonicalParents(Lifespan lifespan) { - // TODO: If this is invoked often, perhaps index + public Stream getCanonicalParents(Lifespan lifespan) { try (LockHold hold = manager.trace.lockRead()) { if (isRoot()) { - return Stream.of(manager.valueStore.getObjectAt(0)); + return Stream.of(manager.getRootValue()); } - String canonicalKey = path.key(); - TraceObjectKeyPath canonicalTail = path.parent(); - return manager.valuesByChild.getLazily(this) - .stream() - .filter(v -> canonicalKey.equals(v.getEntryKey())) - .filter(v -> v.getLifespan().intersects(lifespan)) - .filter(v -> canonicalTail.equals(v.getParent().getCanonicalPath())); + List list = List.copyOf( + manager.valueMap.reduce(TraceObjectValueQuery.canonicalParents(this, lifespan)) + .values()); + return list.stream(); } } @@ -329,135 +348,53 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject { return ifCls.cast(ifaces.get(ifCls)); } - protected Collection doGetParents() { - return manager.valuesByChild.get(this); + protected Collection doGetParents(Lifespan lifespan) { + return List.copyOf( + manager.valueMap.reduce(TraceObjectValueQuery.parents(this, lifespan)).values()); } @Override - public Collection getParents() { + public Collection getParents(Lifespan lifespan) { try (LockHold hold = manager.trace.lockRead()) { - return doGetParents(); + return doGetParents(lifespan); } } - protected void collectNonRangedValues(Collection 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 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 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 result) { - for (DBTraceAddressSnapRangePropertyMapSpace space // - : manager.rangeValueMap.getActiveMemorySpaces()) { - for (DBTraceObjectAddressRangeValue val : space.values()) { - if (val.getParent() != this) { - continue; - } - result.add(val); - } - } - } - - protected void collectRangedAttributes( - Collection result) { - for (DBTraceAddressSnapRangePropertyMapSpace 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 result) { - for (DBTraceAddressSnapRangePropertyMapSpace 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 space // - : manager.rangeValueMap.getActiveMemorySpaces()) { - for (DBTraceObjectAddressRangeValue val : space.values()) { - if (val.getParent() == this) { - return true; - } - } - } - return false; - } - - protected Collection doGetValues() { - List result = new ArrayList<>(); - collectNonRangedValues(result); - collectRangedValues(result); - return result; - } - protected boolean doHasAnyValues() { - return doHasAnyNonRangedValues() || doHasAnyRangedValues(); + return !manager.valueMap.reduce(TraceObjectValueQuery.values(this, Lifespan.ALL)) + .isEmpty(); + } + + protected Collection doGetValues(Lifespan lifespan) { + return manager.valueMap.reduce(TraceObjectValueQuery.values(this, lifespan)).values(); + } + + protected Collection cachedDoGetValues(Lifespan lifespan) { + if (Long.compareUnsigned(lifespan.lmax() - lifespan.lmin(), 10) > 0) { + return List.copyOf(doGetValues(lifespan)); + } + if (cachedLifespanValues == null || !cachedLifespanValues.span.encloses(lifespan)) { + // Expand the query to take advantage of spatial locality (in the time dimension) + long min = lifespan.lmin() - 10; + if (min > lifespan.lmin()) { + min = Lifespan.ALL.lmin(); + } + long max = lifespan.lmax() + 10; + if (max < lifespan.lmax()) { + max = Lifespan.ALL.lmax(); + } + Lifespan expanded = Lifespan.span(min, max); + cachedLifespanValues = + new CachedLifespanValues(expanded, new HashSet<>(doGetValues(expanded))); + } + return cachedLifespanValues.values.stream() + .filter(v -> v.getLifespan().intersects(lifespan)) + .toList(); } protected boolean doHasAnyParents() { - return manager.valuesByChild.containsKey(this); + return !manager.valueMap.reduce(TraceObjectValueQuery.parents(this, Lifespan.ALL)) + .isEmpty(); } protected boolean doIsConnected() { @@ -465,42 +402,28 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject { } @Override - public Collection getValues() { + public Collection getValues(Lifespan lifespan) { try (LockHold hold = manager.trace.lockRead()) { - return doGetValues(); + return cachedDoGetValues(lifespan); } } - protected Collection doGetElements() { - List result = new ArrayList<>(); - collectNonRangedElements(result); - collectRangedElements(result); - return result; - } - @Override - public Collection getElements() { - try (LockHold hold = manager.trace.lockRead()) { - return doGetElements(); - } - } - - protected Collection doGetAttributes() { - List result = new ArrayList<>(); - collectNonRangedAttributes(result); - collectRangedAttributes(result); - return result; + public Collection getElements(Lifespan lifespan) { + return getValues(lifespan).stream() + .filter(v -> PathUtils.isIndex(v.getEntryKey())) + .toList(); } @Override - public Collection getAttributes() { - try (LockHold hold = manager.trace.lockRead()) { - return doGetAttributes(); - } + public Collection getAttributes(Lifespan lifespan) { + return getValues(lifespan).stream() + .filter(v -> PathUtils.isName(v.getEntryKey())) + .toList(); } protected void doCheckConflicts(Lifespan span, String key, Object value) { - for (InternalTraceObjectValue val : doGetValues(span, key)) { + for (InternalTraceObjectValue val : doGetValues(span, key, true)) { if (!Objects.equals(value, val.getValue())) { throw new DuplicateKeyException(key); } @@ -510,7 +433,7 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject { protected Lifespan doAdjust(Lifespan span, String key, Object value) { // Ordered by min, so I only need to consider the first conflict // If start is contained in an entry, assume the user means to overwrite it. - for (InternalTraceObjectValue val : doGetValues(span, key)) { + for (InternalTraceObjectValue val : doGetValues(span, key, true)) { if (Objects.equals(value, val.getValue())) { continue; // not a conflict } @@ -523,154 +446,50 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject { return span; } - // TODO: Could/should this return Stream instead? protected Collection doGetValues(Lifespan span, - String key) { - return doGetValues(span.lmin(), span.lmax(), key); - } - - /** - * The implementation of {@link #getValues(Lifespan, String)} - * - *

- * 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 doGetValues(long lower, long upper, - String key) { - // Collect triplet-indexed values - Set 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 space : manager.rangeValueMap - .getActiveMemorySpaces()) { - AddressSpace as = space.getAddressSpace(); - - for (DBTraceObjectAddressRangeValue val : manager.rangeValueMap - .reduce(TraceAddressSnapRangeQuery - .intersecting(as.getMinAddress(), as.getMaxAddress(), lower, upper)) - .values()) { - if (val.getParent() != this) { - continue; - } - if (!key.equals(val.getEntryKey())) { - continue; - } - result.add(val); - } - } - - return result.stream() - .sorted(Comparator.comparing(v -> v.getMinSnap())) - .collect(Collectors.toList()); + String key, boolean forward) { + return manager.valueMap + .reduce(TraceObjectValueQuery.values(this, key, key, span) + .starting(forward ? SnapDimension.FORWARD : SnapDimension.BACKWARD)) + .orderedValues(); } @Override public Collection getValues(Lifespan span, String key) { try (LockHold hold = manager.trace.lockRead()) { - return doGetValues(span, key); + return doGetValues(span, key, true); } } - protected DBTraceObjectValue doGetNonRangedValue(long snap, String key) { - DBTraceObjectValue floor = - manager.valuesByTriple.floorValue(new PrimaryTriple(this, key, snap)); - if (floor == null || floor.getParent() != this || !key.equals(floor.getEntryKey()) || - !floor.getLifespan().contains(snap)) { - return null; - } - return floor; - } - - protected Stream doGetOrderedNonRangedValues(Lifespan span, String key, - boolean forward) { - DBCachedObjectIndex sub = manager.valuesByTriple.sub( - new PrimaryTriple(this, key, span.lmin()), true, - new PrimaryTriple(this, key, span.lmax()), true); - Spliterator spliterator = (forward ? sub : sub.descending()) - .values() - .spliterator(); - return StreamSupport.stream(spliterator, false); - } - - protected DBTraceObjectAddressRangeValue doGetRangedValue(long snap, String key) { - for (DBTraceAddressSnapRangePropertyMapSpace 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 doGetOrderedRangedValues(Lifespan span, - String key, boolean forward) { - Rectangle2DDirection dir = forward - ? Rectangle2DDirection.BOTTOMMOST - : Rectangle2DDirection.TOPMOST; - List> 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 order = forward ? Comparator.naturalOrder() : Comparator.reverseOrder(); - Comparator comparator = - Comparator.comparing(v -> v.getMinSnap(), order); - return StreamUtils.merge(streams, comparator); - } - @Override public InternalTraceObjectValue getValue(long snap, String key) { try (LockHold hold = manager.trace.lockRead()) { - DBTraceObjectValue nrVal = doGetNonRangedValue(snap, key); - if (nrVal != null) { - return nrVal; + InternalTraceObjectValue cached = valueCache.get(key); + if (cached != null && !cached.isDeleted() && cached.getLifespan().contains(snap)) { + return cached; } - return doGetRangedValue(snap, key); + Long nullSnap = nullCache.get(key); + if (nullSnap != null && nullSnap.longValue() == snap) { + return null; + } + InternalTraceObjectValue found = manager.valueMap + .reduce(TraceObjectValueQuery.values(this, key, key, Lifespan.at(snap))) + .firstValue(); + if (found == null) { + nullCache.put(key, snap); + } + else { + valueCache.put(key, found); + } + return found; } } - protected Stream doGetOrderedValues(Lifespan span, String key, - boolean forward) { - Stream nrVals = doGetOrderedNonRangedValues(span, key, forward); - Stream rVals = doGetOrderedRangedValues(span, key, forward); - Comparator order = forward ? Comparator.naturalOrder() : Comparator.reverseOrder(); - Comparator comparator = - Comparator.comparing(v -> v.getMinSnap(), order); - return StreamUtils.merge(List.of(nrVals, rVals), comparator); - } - @Override public Stream getOrderedValues(Lifespan span, String key, boolean forward) { try (LockHold hold = manager.trace.lockRead()) { - return doGetOrderedValues(span, key, forward); + return doGetValues(span, key, forward).stream(); } } @@ -762,6 +581,10 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject { protected InternalTraceObjectValue doCreateValue(Lifespan lifespan, String key, Object value) { + Long nullSnap = nullCache.get(key); + if (nullSnap != null && lifespan.contains(nullSnap)) { + nullCache.remove(key); + } return manager.doCreateValue(lifespan, this, key, value); } @@ -784,7 +607,8 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject { @Override protected Iterable getIntersecting(Long lower, Long upper) { - return Collections.unmodifiableCollection(doGetValues(lower, upper, key)); + return Collections.unmodifiableCollection( + doGetValues(Lifespan.span(lower, upper), key, true)); } @Override @@ -930,10 +754,10 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject { } protected void doDeleteReferringValues() { - for (InternalTraceObjectValue child : getValues()) { + for (InternalTraceObjectValue child : getValues(Lifespan.ALL)) { child.doDeleteAndEmit(); } - for (DBTraceObjectValue parent : getParents()) { + for (InternalTraceObjectValue parent : getParents(Lifespan.ALL)) { parent.doDeleteAndEmit(); } } @@ -957,7 +781,38 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject { } } catch (Throwable t) { - Msg.error(this, "Error while translating event " + rec + " for interface " + iface); + Msg.error(this, + "Error while translating event " + rec + " for interface " + iface + ":" + t); + } + } + } + + protected void notifyValueCreated(InternalTraceObjectValue value) { + if (cachedLifespanValues != null) { + if (cachedLifespanValues.span.intersects(value.getLifespan())) { + cachedLifespanValues.values.add(value); + } + } + } + + protected void notifyValueDeleted(InternalTraceObjectValue value) { + if (cachedLifespanValues != null) { + cachedLifespanValues.values.remove(value); + } + } + + protected void notifyParentValueCreated(InternalTraceObjectValue parent) { + if (cachedLife != null && parent.isCanonical()) { + synchronized (cachedLife) { + cachedLife.add(parent.getLifespan()); + } + } + } + + protected void notifyParentValueDeleted(InternalTraceObjectValue parent) { + if (cachedLife != null && parent.isCanonical()) { + synchronized (cachedLife) { + cachedLife.remove(parent.getLifespan()); } } } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/DBTraceObjectAddressRangeValue.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/DBTraceObjectAddressRangeValue.java deleted file mode 100644 index fd49db401c..0000000000 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/DBTraceObjectAddressRangeValue.java +++ /dev/null @@ -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 - 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 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); - } - } -} diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/DBTraceObjectDBFieldCodec.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/DBTraceObjectDBFieldCodec.java new file mode 100644 index 0000000000..8a5526f9ea --- /dev/null +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/DBTraceObjectDBFieldCodec.java @@ -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 + extends AbstractDBFieldCodec { + public DBTraceObjectDBFieldCodec(Class 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))); + } +} diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/DBTraceObjectInterface.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/DBTraceObjectInterface.java index 71e266c603..e4ada7ef1b 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/DBTraceObjectInterface.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/DBTraceObjectInterface.java @@ -15,8 +15,12 @@ */ package ghidra.trace.database.target; +import ghidra.program.model.address.Address; +import ghidra.program.model.address.AddressRange; +import ghidra.trace.database.space.DBTraceSpaceKey.DefaultDBTraceSpaceKey; import ghidra.trace.model.Lifespan; -import ghidra.trace.model.Lifespan.*; +import ghidra.trace.model.Lifespan.DefaultLifeSet; +import ghidra.trace.model.Lifespan.LifeSet; import ghidra.trace.model.Trace.TraceObjectChangeType; import ghidra.trace.model.TraceUniqueObject; import ghidra.trace.model.target.*; @@ -173,9 +177,14 @@ public interface DBTraceObjectInterface extends TraceObjectInterface, TraceUniqu static TraceAddressSpace spaceForValue(TraceObject object, long snap, String key) { TraceObjectValue val = object.getAttribute(snap, key); - if (val instanceof DBTraceObjectAddressRangeValue) { - DBTraceObjectAddressRangeValue addrVal = (DBTraceObjectAddressRangeValue) val; - return addrVal.getTraceAddressSpace(); + if (val == null) { + return null; + } + if (val.getValue() instanceof Address address) { + return new DefaultDBTraceSpaceKey(null, address.getAddressSpace(), 0); + } + if (val.getValue() instanceof AddressRange range) { + return new DefaultDBTraceSpaceKey(null, range.getAddressSpace(), 0); } return null; } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/DBTraceObjectManager.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/DBTraceObjectManager.java index 49ce50eabb..03d1bd275b 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/DBTraceObjectManager.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/DBTraceObjectManager.java @@ -23,7 +23,6 @@ import java.util.function.Predicate; import java.util.stream.Collectors; import java.util.stream.Stream; -import org.apache.commons.collections4.IteratorUtils; import org.jdom.JDOMException; import db.*; @@ -38,13 +37,13 @@ import ghidra.program.model.lang.Language; import ghidra.trace.database.DBTrace; import ghidra.trace.database.DBTraceManager; import ghidra.trace.database.breakpoint.DBTraceObjectBreakpointLocation; -import ghidra.trace.database.map.DBTraceAddressSnapRangePropertyMap; -import ghidra.trace.database.map.DBTraceAddressSnapRangePropertyMapTree.TraceAddressSnapRangeQuery; import ghidra.trace.database.module.TraceObjectSection; -import ghidra.trace.database.target.DBTraceObjectValue.PrimaryTriple; +import ghidra.trace.database.target.DBTraceObjectValueRStarTree.DBTraceObjectValueMap; +import ghidra.trace.database.target.ValueSpace.EntryKeyDimension; import ghidra.trace.database.target.visitors.SuccessorsRelativeVisitor; import ghidra.trace.database.thread.DBTraceObjectThread; -import ghidra.trace.model.*; +import ghidra.trace.model.Lifespan; +import ghidra.trace.model.Trace; import ghidra.trace.model.Trace.TraceObjectChangeType; import ghidra.trace.model.breakpoint.*; import ghidra.trace.model.memory.*; @@ -69,6 +68,7 @@ import ghidra.util.exception.VersionException; import ghidra.util.task.TaskMonitor; public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager { + private static final int OBJECTS_CONTAINING_CACHE_SIZE = 100; public static class DBTraceObjectSchemaDBFieldCodec extends AbstractDBFieldCodec { @@ -148,24 +148,33 @@ public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager } } + record ObjectsContainingKey(long snap, Address address, String key, + Class iface) { + } + protected final ReadWriteLock lock; protected final DBTrace trace; protected final DBCachedObjectStore schemaStore; protected final DBCachedObjectStore objectStore; - protected final DBCachedObjectStore valueStore; - - protected final DBTraceAddressSnapRangePropertyMap rangeValueMap; + protected final DBTraceObjectValueRStarTree valueTree; + protected final DBTraceObjectValueMap valueMap; protected final DBCachedObjectIndex objectsByPath; - protected final DBCachedObjectIndex valuesByTriple; - protected final DBCachedObjectIndex valuesByChild; protected final Collection objectsView; protected final Collection valuesView; protected TargetObjectSchema rootSchema; + protected final Map> objectsContainingCache = + new LinkedHashMap<>() { + protected boolean removeEldestEntry( + Map.Entry> eldest) { + return size() > OBJECTS_CONTAINING_CACHE_SIZE; + } + }; + public DBTraceObjectManager(DBHandle dbh, DBOpenMode openMode, ReadWriteLock lock, TaskMonitor monitor, Language baseLanguage, DBTrace trace) throws IOException, VersionException { @@ -178,33 +187,17 @@ public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager loadRootSchema(); objectStore = factory.getOrCreateCachedStore(DBTraceObject.TABLE_NAME, DBTraceObject.class, (s, r) -> new DBTraceObject(this, s, r), true); - valueStore = factory.getOrCreateCachedStore(DBTraceObjectValue.TABLE_NAME, - DBTraceObjectValue.class, (s, r) -> new DBTraceObjectValue(this, s, r), true); - rangeValueMap = new DBTraceAddressSnapRangePropertyMap<>( - DBTraceObjectAddressRangeValue.TABLE_NAME, dbh, openMode, lock, monitor, baseLanguage, - trace, null, DBTraceObjectAddressRangeValue.class, - (t, s, r) -> new DBTraceObjectAddressRangeValue(this, t, s, r)); + + valueTree = new DBTraceObjectValueRStarTree(this, factory, + DBTraceObjectValueData.TABLE_NAME, ValueSpace.INSTANCE, DBTraceObjectValueData.class, + DBTraceObjectValueNode.class, false, 50); + valueMap = valueTree.asSpatialMap(); objectsByPath = objectStore.getIndex(TraceObjectKeyPath.class, DBTraceObject.PATH_COLUMN); - valuesByTriple = - valueStore.getIndex(PrimaryTriple.class, DBTraceObjectValue.TRIPLE_COLUMN); - valuesByChild = - valueStore.getIndex(DBTraceObject.class, DBTraceObjectValue.CHILD_COLUMN); objectsView = Collections.unmodifiableCollection(objectStore.asMap().values()); - valuesView = new AbstractCollection<>() { - @Override - public Iterator iterator() { - return IteratorUtils.chainedIterator(valueStore.asMap().values().iterator(), - rangeValueMap.values().iterator()); - } - - @Override - public int size() { - return objectStore.getRecordCount() + rangeValueMap.size(); - } - }; + valuesView = Collections.unmodifiableCollection(valueMap.values()); } protected void loadRootSchema() { @@ -225,8 +218,7 @@ public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager @Override public void invalidateCache(boolean all) { objectStore.invalidateCache(); - valueStore.invalidateCache(); - rangeValueMap.invalidateCache(all); + valueTree.invalidateCache(); schemaStore.invalidateCache(); loadRootSchema(); } @@ -281,24 +273,22 @@ public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager protected InternalTraceObjectValue doCreateValue(Lifespan lifespan, DBTraceObject parent, String key, Object value) { - if (value instanceof AddressRange) { - DBTraceObjectAddressRangeValue entry = rangeValueMap - .put(new ImmutableTraceAddressSnapRange((AddressRange) value, lifespan), null); - entry.set(parent, key, false); - emitValueCreated(parent, entry); - return entry; + InternalTraceObjectValue entry = valueTree.asSpatialMap() + .put(new ImmutableValueShape(parent, value, key, lifespan), null); + if (value instanceof DBTraceObject child) { + child.notifyParentValueCreated(entry); } - else if (value instanceof Address) { - Address address = (Address) value; - AddressRange singleton = new AddressRangeImpl(address, address); - DBTraceObjectAddressRangeValue entry = rangeValueMap - .put(new ImmutableTraceAddressSnapRange(singleton, lifespan), null); - entry.set(parent, key, true); - emitValueCreated(parent, entry); - return entry; + else { + entry.doSetPrimitive(value); } - DBTraceObjectValue entry = valueStore.create(); - entry.set(lifespan, parent, key, value); + + if (parent != null) { // Root + parent.notifyValueCreated(entry); + } + + // TODO: Perhaps a little drastic + invalidateObjectsContainingCache(); + emitValueCreated(parent, entry); return entry; } @@ -350,6 +340,12 @@ public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager } } + public DBTraceObjectValueData getRootValue() { + try (LockHold hold = trace.lockRead()) { + return valueTree.getDataStore().getObjectAt(0); + } + } + @Override public DBTraceObject getRootObject() { return getObjectById(0); @@ -381,7 +377,7 @@ public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager public Stream getValuePaths(Lifespan span, PathPredicates predicates) { try (LockHold hold = trace.lockRead()) { - DBTraceObjectValue rootVal = valueStore.getObjectAt(0); + DBTraceObjectValueData rootVal = getRootValue(); if (rootVal == null) { return Stream.of(); } @@ -401,14 +397,18 @@ public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager @Override public Collection getValuesIntersecting(Lifespan span, - AddressRange range) { - return Collections.unmodifiableCollection( - rangeValueMap.reduce(TraceAddressSnapRangeQuery.intersecting(range, span)).values()); + AddressRange range, String entryKey) { + return Collections + .unmodifiableCollection(valueMap.reduce(TraceObjectValueQuery.intersecting( + entryKey != null ? entryKey : EntryKeyDimension.INSTANCE.absoluteMin(), + entryKey != null ? entryKey : EntryKeyDimension.INSTANCE.absoluteMax(), + span, range)).values()); } - public Collection getValuesAt(long snap, Address address) { + public Collection getValuesAt(long snap, Address address, + String entryKey) { return Collections.unmodifiableCollection( - rangeValueMap.reduce(TraceAddressSnapRangeQuery.at(address, snap)).values()); + valueMap.reduce(TraceObjectValueQuery.at(entryKey, snap, address)).values()); } @Override @@ -445,8 +445,7 @@ public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager @Override public void clear() { try (LockHold hold = trace.lockWrite()) { - valueStore.deleteAll(); - rangeValueMap.clear(); + valueMap.clear(); objectStore.deleteAll(); schemaStore.deleteAll(); rootSchema = null; @@ -458,8 +457,16 @@ public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager object.emitEvents(new TraceChangeRecord<>(TraceObjectChangeType.DELETED, null, object)); } - protected void doDeleteEdge(DBTraceObjectValue edge) { - valueStore.delete(edge); + protected void doDeleteEdge(DBTraceObjectValueData edge) { + valueTree.doDeleteEntry(edge); + + // TODO: Perhaps a little drastic.... + /** + * NB. An object in one of these queries had to have an edge. Deleting that object will also + * delete referring edges, so the cache will get invalidated. No need to repeat in + * doDeleteObject. + */ + invalidateObjectsContainingCache(); } public boolean hasSchema() { @@ -508,35 +515,51 @@ public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager } } - protected Stream doParentsWithKeyHaving( - Stream values, String key, Class iface) { - return values.filter(v -> key.equals(v.getEntryKey())) - .map(v -> v.getParent()) + protected Stream doParentsHaving( + Stream values, Class iface) { + return values.map(v -> v.getParent()) .map(o -> o.queryInterface(iface)) .filter(i -> i != null); } + protected void invalidateObjectsContainingCache() { + synchronized (objectsContainingCache) { + objectsContainingCache.clear(); + } + } + + protected Collection doGetObjectsContaining( + ObjectsContainingKey key) { + return doParentsHaving(getValuesAt(key.snap, key.address, key.key).stream(), key.iface) + .collect(Collectors.toSet()); + } + + @SuppressWarnings("unchecked") public Collection getObjectsContaining(long snap, Address address, String key, Class iface) { try (LockHold hold = trace.lockRead()) { - return doParentsWithKeyHaving(getValuesAt(snap, address).stream(), key, - iface).collect(Collectors.toSet()); + synchronized (objectsContainingCache) { + return (Collection) objectsContainingCache.computeIfAbsent( + new ObjectsContainingKey(snap, address, key, iface), + this::doGetObjectsContaining); + } } } public I getObjectContaining(long snap, Address address, String key, Class iface) { - try (LockHold hold = trace.lockRead()) { - return doParentsWithKeyHaving(getValuesAt(snap, address).stream(), key, - iface).findAny().orElse(null); + Collection col = getObjectsContaining(snap, address, key, iface); + if (col.isEmpty()) { + return null; } + return col.iterator().next(); } public Collection getObjectsIntersecting( Lifespan lifespan, AddressRange range, String key, Class iface) { try (LockHold hold = trace.lockRead()) { - return doParentsWithKeyHaving(getValuesIntersecting(lifespan, range).stream(), key, - iface).collect(Collectors.toSet()); + return doParentsHaving(getValuesIntersecting(lifespan, range, key).stream(), iface) + .collect(Collectors.toSet()); } } @@ -549,7 +572,7 @@ public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager public AddressSetView getObjectsAddressSet(long snap, String key, Class ifaceCls, Predicate predicate) { - return rangeValueMap.getAddressSetView(Lifespan.at(snap), v -> { + return valueMap.getAddressSetView(Lifespan.at(snap), v -> { if (!key.equals(v.getEntryKey())) { return false; } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/DBTraceObjectValPath.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/DBTraceObjectValPath.java index 71b86adb8b..e1b45389a6 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/DBTraceObjectValPath.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/DBTraceObjectValPath.java @@ -74,20 +74,33 @@ public class DBTraceObjectValPath implements TraceObjectValPath { @Override public DBTraceObjectValPath prepend(TraceObjectValue entry) { + if (!entryList.isEmpty() && entry.getTrace() != entryList.get(0).getTrace()) { + throw new IllegalArgumentException("All values in path must be from the same trace"); + } + if (!(entry instanceof InternalTraceObjectValue val)) { + throw new IllegalArgumentException("Value must be in the database"); + } InternalTraceObjectValue[] arr = new InternalTraceObjectValue[1 + entryList.size()]; - arr[0] = (DBTraceObjectValue) entry; + arr[0] = val; for (int i = 1; i < arr.length; i++) { arr[i] = entryList.get(i - 1); } return new DBTraceObjectValPath(Collections.unmodifiableList(Arrays.asList(arr))); } + @Override public DBTraceObjectValPath append(TraceObjectValue entry) { + if (!entryList.isEmpty() && entry.getTrace() != entryList.get(0).getTrace()) { + throw new IllegalArgumentException("All values in path must be from the same trace"); + } + if (!(entry instanceof InternalTraceObjectValue val)) { + throw new IllegalArgumentException("Value must be in the database"); + } InternalTraceObjectValue[] arr = new InternalTraceObjectValue[1 + entryList.size()]; for (int i = 0; i < arr.length - 1; i++) { arr[i] = entryList.get(i); } - arr[arr.length - 1] = (InternalTraceObjectValue) entry; + arr[arr.length - 1] = val; return new DBTraceObjectValPath(Collections.unmodifiableList(Arrays.asList(arr))); } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/DBTraceObjectValue.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/DBTraceObjectValue.java deleted file mode 100644 index 70dfb21662..0000000000 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/DBTraceObjectValue.java +++ /dev/null @@ -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 ""; - } - - public PrimaryTriple withMinSnap(long minSnap) { - return new PrimaryTriple(parent, key, minSnap); - } - } - - public static class PrimaryTripleDBFieldCodec - extends AbstractDBFieldCodec { - static final Charset cs = Charset.forName("UTF-8"); - - public PrimaryTripleDBFieldCodec(Class 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 - extends AbstractDBFieldCodec { - public DBTraceObjectDBFieldCodec(Class 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 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); - } - } -} diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/DBTraceObjectValueData.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/DBTraceObjectValueData.java new file mode 100644 index 0000000000..b2423263cd --- /dev/null +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/DBTraceObjectValueData.java @@ -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 + 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 doStreamVisitor(Lifespan span, + Visitor visitor) { + return TreeTraversal.INSTANCE.walkValue(visitor, this, span, null); + } +} diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/DBTraceObjectValueMapAddressSetView.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/DBTraceObjectValueMapAddressSetView.java new file mode 100644 index 0000000000..6f55468f27 --- /dev/null +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/DBTraceObjectValueMapAddressSetView.java @@ -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 map; + private final Predicate predicate; + + /** + * An address set view that unions all addresses where an entry satisfying the given predicate + * exists. + * + *

+ * 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 map, + Predicate 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 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 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> mapIt = map + .reduce(TraceObjectValueQuery + .intersecting(EntryKeyDimension.INSTANCE.absoluteMin(), + EntryKeyDimension.INSTANCE.absoluteMax(), Lifespan.ALL, start, end) + .starting(forward ? AddressDimension.FORWARD : AddressDimension.BACKWARD)) + .orderedEntries() + .iterator(); + Iterator> fltIt = + IteratorUtils.filteredIterator(mapIt, e -> predicate.test(e.getValue())); + Iterator rawIt = + IteratorUtils.transformedIterator(fltIt, e -> e.getKey().getRange(factory)); + return new UnionAddressRangeIterator(rawIt, forward); + } + + /** + * {@inheritDoc} + * + *

+ * 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} + * + *

+ * 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); + } + } +} diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/DBTraceObjectValueNode.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/DBTraceObjectValueNode.java new file mode 100644 index 0000000000..1eae1f78c2 --- /dev/null +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/DBTraceObjectValueNode.java @@ -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 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); + } +} diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/DBTraceObjectValueRStarTree.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/DBTraceObjectValueRStarTree.java new file mode 100644 index 0000000000..b6acb4af3a --- /dev/null +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/DBTraceObjectValueRStarTree.java @@ -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 { + + private final AddressFactory factory; + private final ReadWriteLock lock; + + public DBTraceObjectValueMap(AbstractConstraintsTree 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 predicate) { + return new DBTraceObjectValueMapAddressSetView(factory, lock, this, predicate); + } + } + + protected final DBTraceObjectManager manager; + protected final DBCachedObjectIndex nodesByParent; + protected final DBCachedObjectIndex dataByParent; + + public DBTraceObjectValueRStarTree(DBTraceObjectManager manager, + DBCachedObjectStoreFactory storeFactory, String tableName, + EuclideanHyperSpace space, + Class dataType, Class 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 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 store, DBRecord record) { + return new DBTraceObjectValueData(manager, this, store, record); + } + + @Override + protected DBTraceObjectValueNode createNodeEntry( + DBCachedObjectStore store, DBRecord record) { + return new DBTraceObjectValueNode(this, store, record); + } + + @Override + protected Collection getNodeChildrenOf(long parentKey) { + return nodesByParent.get(parentKey); + } + + @Override + protected Collection getDataChildrenOf(long parentKey) { + return dataByParent.get(parentKey); + } + + @Override + public DBTraceObjectValueMap asSpatialMap() { + return new DBTraceObjectValueMap(this, null, manager.trace.getBaseAddressFactory(), + manager.lock); + } +} diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/ImmutableValueBox.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/ImmutableValueBox.java new file mode 100644 index 0000000000..f00e6ff44b --- /dev/null +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/ImmutableValueBox.java @@ -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()); + } +} diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/ImmutableValueShape.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/ImmutableValueShape.java new file mode 100644 index 0000000000..9de775a214 --- /dev/null +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/ImmutableValueShape.java @@ -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; + } +} diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/InternalTraceObjectValue.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/InternalTraceObjectValue.java index 1a014bd46f..60b524e0eb 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/InternalTraceObjectValue.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/InternalTraceObjectValue.java @@ -98,6 +98,8 @@ interface InternalTraceObjectValue extends TraceObjectValue { protected abstract InternalTraceObjectValue create(Lifespan range, Object value); } + void doSetPrimitive(Object primitive); + DBTraceObjectManager getManager(); /** @@ -143,7 +145,7 @@ interface InternalTraceObjectValue extends TraceObjectValue { protected Iterable getIntersecting(Long lower, Long upper) { Collection col = Collections.unmodifiableCollection( - getParent().doGetValues(lower, upper, getEntryKey())); + getParent().doGetValues(Lifespan.span(lower, upper), getEntryKey(), true)); return IterableUtils.filteredIterable(col, v -> v != keep); } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/TraceObjectValueQuery.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/TraceObjectValueQuery.java new file mode 100644 index 0000000000..ad2c2218cc --- /dev/null +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/TraceObjectValueQuery.java @@ -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 { + + 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); + } +} diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/ValueBox.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/ValueBox.java new file mode 100644 index 0000000000..aba0003837 --- /dev/null +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/ValueBox.java @@ -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 { + @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(); + } +} diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/ValueShape.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/ValueShape.java new file mode 100644 index 0000000000..c5cdc89b42 --- /dev/null +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/ValueShape.java @@ -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 { + 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)); + } +} diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/ValueSpace.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/ValueSpace.java new file mode 100644 index 0000000000..861c9a7a3e --- /dev/null +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/ValueSpace.java @@ -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 { + INSTANCE; + + enum ParentKeyDimension implements ULongDimension { + 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 { + 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 { + 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 { + 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 { + 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> 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> 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); + } +} diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/ValueTriple.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/ValueTriple.java new file mode 100644 index 0000000000..090f4e086d --- /dev/null +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/ValueTriple.java @@ -0,0 +1,33 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.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); + } +} diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/visitors/AllPathsVisitor.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/visitors/AllPathsVisitor.java index 0f77b3ef32..1cd88b62db 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/visitors/AllPathsVisitor.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/visitors/AllPathsVisitor.java @@ -50,6 +50,6 @@ public enum AllPathsVisitor implements SpanIntersectingVisitor { @Override public Stream continueValues(TraceObject object, Lifespan span, TraceObjectValPath path) { - return object.getParents().stream().filter(v -> !path.contains(v)); + return object.getParents(span).stream().filter(v -> !path.contains(v)); } } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/visitors/AncestorsRelativeVisitor.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/visitors/AncestorsRelativeVisitor.java index 61703ba4ad..3866596980 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/visitors/AncestorsRelativeVisitor.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/visitors/AncestorsRelativeVisitor.java @@ -59,7 +59,7 @@ public class AncestorsRelativeVisitor implements SpanIntersectingVisitor { return Stream.empty(); } - return object.getParents() + return object.getParents(span) .stream() .filter(v -> PathPredicates.anyMatches(prevKeys, v.getEntryKey())); } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/visitors/AncestorsRootVisitor.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/visitors/AncestorsRootVisitor.java index 10ec538914..df281b4f9e 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/visitors/AncestorsRootVisitor.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/visitors/AncestorsRootVisitor.java @@ -58,6 +58,6 @@ public class AncestorsRootVisitor implements SpanIntersectingVisitor { * Can't really filter the parent values by predicates here, since the predicates are not * matching relative paths, but canonical paths. */ - return object.getParents().stream().filter(v -> !path.contains(v)); + return object.getParents(span).stream().filter(v -> !path.contains(v)); } } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/visitors/CanonicalSuccessorsRelativeVisitor.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/visitors/CanonicalSuccessorsRelativeVisitor.java index 4a3720ab84..36bbb54f7b 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/visitors/CanonicalSuccessorsRelativeVisitor.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/visitors/CanonicalSuccessorsRelativeVisitor.java @@ -73,7 +73,7 @@ public class CanonicalSuccessorsRelativeVisitor implements Visitor { Stream attrStream; if (nextKeys.contains("")) { - attrStream = object.getAttributes().stream().filter(TraceObjectValue::isCanonical); + attrStream = object.getAttributes(span).stream().filter(TraceObjectValue::isCanonical); } else { attrStream = Stream.empty(); @@ -81,7 +81,7 @@ public class CanonicalSuccessorsRelativeVisitor implements Visitor { Stream elemStream; if (nextKeys.contains("[]")) { - elemStream = object.getElements().stream().filter(TraceObjectValue::isCanonical); + elemStream = object.getElements(span).stream().filter(TraceObjectValue::isCanonical); } else { elemStream = Stream.empty(); diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/visitors/SuccessorsRelativeVisitor.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/visitors/SuccessorsRelativeVisitor.java index 7605863ba0..425e226510 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/visitors/SuccessorsRelativeVisitor.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/visitors/SuccessorsRelativeVisitor.java @@ -61,9 +61,7 @@ public class SuccessorsRelativeVisitor implements SpanIntersectingVisitor { Stream attrStream; if (nextKeys.contains("")) { - attrStream = object.getAttributes() - .stream() - .filter(v -> span.intersects(v.getLifespan())); + attrStream = object.getAttributes(span).stream(); } else { attrStream = Stream.empty(); @@ -71,9 +69,7 @@ public class SuccessorsRelativeVisitor implements SpanIntersectingVisitor { Stream elemStream; if (nextKeys.contains("[]")) { - elemStream = object.getElements() - .stream() - .filter(v -> span.intersects(v.getLifespan())); + elemStream = object.getElements(span).stream(); } else { elemStream = Stream.empty(); diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/Lifespan.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/Lifespan.java index 0afb877370..965ef07542 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/Lifespan.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/Lifespan.java @@ -395,6 +395,12 @@ public sealed interface Lifespan extends Span, Iterable { * An interval tree implementing {@link MutableLifeSet} */ public class DefaultLifeSet extends DefaultSpanSet implements MutableLifeSet { + public static DefaultLifeSet copyOf(LifeSet set) { + DefaultLifeSet copy = new DefaultLifeSet(); + copy.addAll(set); + return copy; + } + public DefaultLifeSet() { super(Lifespan.DOMAIN); } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/TraceDomainObjectListener.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/TraceDomainObjectListener.java index fd859a47fe..4c8dbaa1da 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/TraceDomainObjectListener.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/TraceDomainObjectListener.java @@ -16,28 +16,23 @@ package ghidra.trace.model; import ghidra.framework.model.*; -import ghidra.util.TimedMsg; public class TraceDomainObjectListener extends TypedEventDispatcher implements DomainObjectListener { @Override public void domainObjectChanged(DomainObjectChangedEvent ev) { - //TimedMsg.info(this, "Handing (" + this + "): " + ev); if (restoredHandler != null && ev.containsEvent(DomainObject.DO_OBJECT_RESTORED)) { for (DomainObjectChangeRecord rec : ev) { if (rec.getEventType() == DomainObject.DO_OBJECT_RESTORED) { restoredHandler.accept(rec); - TimedMsg.debug(this, " Done: OBJECT_RESTORED"); return; } } throw new AssertionError(); } - //Map CountsByType = new TreeMap<>(); for (DomainObjectChangeRecord rec : ev) { handleChangeRecord(rec); } - //TimedMsg.info(this, " Done: " + CountsByType); } } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/target/TraceObject.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/target/TraceObject.java index 98f320159e..fac5a58824 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/target/TraceObject.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/target/TraceObject.java @@ -202,18 +202,19 @@ public interface TraceObject extends TraceUniqueObject { I queryInterface(Class ifClass); /** - * Get all values whose child is this object + * Get all values intersecting the given span and whose child is this object * + * @param span the span * @return the parent values */ - Collection getParents(); + Collection getParents(Lifespan span); /** - * Get all values (elements and attributes) of this object + * Get all values (elements and attributes) of this object intersecting the given span * * @return the values */ - Collection getValues(); + Collection getValues(Lifespan span); /** * Get values with the given key intersecting the given span @@ -236,18 +237,18 @@ public interface TraceObject extends TraceUniqueObject { boolean forward); /** - * Get all elements of this object + * Get all elements of this object intersecting the given span * * @return the element values */ - Collection getElements(); + Collection getElements(Lifespan span); /** - * Get all attributes of this object + * Get all attributes of this object intersecting the given span * * @return the attribute values */ - Collection getAttributes(); + Collection getAttributes(Lifespan span); /** * Get the value for the given snap and key diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/target/TraceObjectManager.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/target/TraceObjectManager.java index 5f965c35fd..6da8c977eb 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/target/TraceObjectManager.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/target/TraceObjectManager.java @@ -139,10 +139,23 @@ public interface TraceObjectManager { * * @param span the span that desired values lifespans must intersect * @param range the range that desired address-ranged values must intersect + * @param entryKey the entry key if a single one should be matched, or null for any * @return the collection of values */ Collection 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 getValuesIntersecting(Lifespan span, + AddressRange range) { + return getValuesIntersecting(span, range, null); + } /** * Get all interfaces of the given type in the database diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/target/annot/TraceObjectInterfaceUtils.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/target/annot/TraceObjectInterfaceUtils.java index 34f5ca40ef..712a049fa9 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/target/annot/TraceObjectInterfaceUtils.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/target/annot/TraceObjectInterfaceUtils.java @@ -54,7 +54,7 @@ public enum TraceObjectInterfaceUtils { public static void setLifespan(Class traceIf, TraceObject object, Lifespan lifespan) throws DuplicateNameException { try (LockHold hold = object.getTrace().lockWrite()) { - for (TraceObjectValue val : object.getParents()) { + for (TraceObjectValue val : object.getParents(Lifespan.ALL)) { if (val.isCanonical() && !val.isDeleted()) { val.setLifespan(lifespan, ConflictResolution.DENY); } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/target/DBTraceObjectManagerTest.java b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/target/DBTraceObjectManagerTest.java index e46af9b1d2..c98568d2f1 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/target/DBTraceObjectManagerTest.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/target/DBTraceObjectManagerTest.java @@ -18,8 +18,10 @@ package ghidra.trace.database.target; import static org.junit.Assert.*; import java.io.File; +import java.math.BigInteger; import java.util.*; import java.util.stream.Collectors; +import java.util.stream.Stream; import org.junit.Before; import org.junit.Test; @@ -31,6 +33,7 @@ import ghidra.dbg.target.schema.TargetObjectSchema.SchemaName; import ghidra.dbg.target.schema.XmlSchemaContext; import ghidra.dbg.util.PathPredicates; import ghidra.dbg.util.PathUtils; +import ghidra.program.model.address.*; import ghidra.test.AbstractGhidraHeadlessIntegrationTest; import ghidra.trace.database.ToyDBTraceBuilder; import ghidra.trace.model.Lifespan; @@ -111,22 +114,22 @@ public class DBTraceObjectManagerTest extends AbstractGhidraHeadlessIntegrationT TraceObjectKeyPath pathTargets = TraceObjectKeyPath.of("Targets"); targetContainer = manager.createObject(pathTargets); root.setAttribute(Lifespan.nowOn(0), "Targets", targetContainer); - dumpStore(manager.valueStore); + dumpStore(manager.valueTree.getDataStore()); for (int i = 0; i < targetCount; i++) { Lifespan lifespan = Lifespan.nowOn(i); TraceObject target = manager.createObject(pathTargets.index(i)); target.setAttribute(Lifespan.ALL, "self", target); - dumpStore(manager.valueStore); + dumpStore(manager.valueTree.getDataStore()); targetContainer.setElement(lifespan, i, target); - dumpStore(manager.valueStore); + dumpStore(manager.valueTree.getDataStore()); targets.add(target); root.setAttribute(lifespan, "curTarget", target); - dumpStore(manager.valueStore); + dumpStore(manager.valueTree.getDataStore()); } root.setValue(Lifespan.ALL, "anAttribute", "A primitive string"); - dumpStore(manager.valueStore); + dumpStore(manager.valueTree.getDataStore()); } } @@ -268,8 +271,8 @@ public class DBTraceObjectManagerTest extends AbstractGhidraHeadlessIntegrationT TraceObjectValue rangeVal = root.setValue(Lifespan.nowOn(0), "a", b.range(0x1000, 0x1fff)); - assertTrue(root.getValues().contains(rangeVal)); - assertFalse(targetContainer.getValues().contains(rangeVal)); + assertTrue(root.getValues(Lifespan.at(0)).contains(rangeVal)); + assertFalse(targetContainer.getValues(Lifespan.ALL).contains(rangeVal)); assertEquals(rangeVal, root.getValue(0, "a")); assertNull(root.getValue(0, "b")); @@ -292,10 +295,14 @@ public class DBTraceObjectManagerTest extends AbstractGhidraHeadlessIntegrationT assertEquals(Set.of(rangeVal), Set.copyOf(manager.getValuesIntersecting(Lifespan.ALL, b.range(0, -1)))); + assertEquals(Set.of(rangeVal), + Set.copyOf(manager.getValuesIntersecting(Lifespan.ALL, b.range(0, -1), "a"))); assertEquals(Set.of(), Set.copyOf(manager.getValuesIntersecting(Lifespan.toNow(-1), b.range(0, -1)))); assertEquals(Set.of(), Set.copyOf(manager.getValuesIntersecting(Lifespan.ALL, b.range(0, 0xfff)))); + assertEquals(Set.of(), + Set.copyOf(manager.getValuesIntersecting(Lifespan.ALL, b.range(0, -1), "b"))); } } @@ -444,32 +451,33 @@ public class DBTraceObjectManagerTest extends AbstractGhidraHeadlessIntegrationT public void testGetParents() { populateModel(3); - assertEquals(1, root.getParents().size()); - assertEquals(root, Unique.assertOne(targetContainer.getParents()).getParent()); - assertEquals(3, targets.get(0).getParents().size()); // curTarget, targetContainer, self + assertEquals(1, root.getParents(Lifespan.ALL).size()); + assertEquals(root, Unique.assertOne(targetContainer.getParents(Lifespan.ALL)).getParent()); + assertEquals(3, targets.get(0).getParents(Lifespan.ALL).size()); + // curTarget, targetContainer, self } @Test public void testGetValues() { populateModel(3); - assertEquals(3, targetContainer.getValues().size()); + assertEquals(3, targetContainer.getValues(Lifespan.ALL).size()); } @Test public void testGetElements() { populateModel(3); - assertEquals(0, root.getElements().size()); - assertEquals(3, targetContainer.getElements().size()); + assertEquals(0, root.getElements(Lifespan.ALL).size()); + assertEquals(3, targetContainer.getElements(Lifespan.ALL).size()); } @Test public void testGetAttributes() { populateModel(3); - assertEquals(5, root.getAttributes().size()); // Targets, curTarget(x3), string - assertEquals(0, targetContainer.getAttributes().size()); + assertEquals(5, root.getAttributes(Lifespan.ALL).size()); // Targets, curTarget(x3), string + assertEquals(0, targetContainer.getAttributes(Lifespan.ALL).size()); } @Test @@ -610,7 +618,7 @@ public class DBTraceObjectManagerTest extends AbstractGhidraHeadlessIntegrationT assertEquals(valA, root.setValue(Lifespan.span(-10, -1), "a", 1)); assertEquals(Lifespan.span(-10, 9), valA.getLifespan()); - assertEquals(1, root.getValues().size()); + assertEquals(1, root.getValues(Lifespan.ALL).size()); } } @@ -624,7 +632,7 @@ public class DBTraceObjectManagerTest extends AbstractGhidraHeadlessIntegrationT assertEquals(valA, root.setValue(Lifespan.span(-10, -1), "a", b.range(0x1000, 0x1fff))); assertEquals(Lifespan.span(-10, 9), valA.getLifespan()); - assertEquals(1, root.getValues().size()); + assertEquals(1, root.getValues(Lifespan.ALL).size()); } } @@ -636,7 +644,7 @@ public class DBTraceObjectManagerTest extends AbstractGhidraHeadlessIntegrationT assertEquals(valA, root.setValue(Lifespan.span(10, 19), "a", 1)); assertEquals(Lifespan.span(0, 19), valA.getLifespan()); - assertEquals(1, root.getValues().size()); + assertEquals(1, root.getValues(Lifespan.ALL).size()); } } @@ -648,7 +656,7 @@ public class DBTraceObjectManagerTest extends AbstractGhidraHeadlessIntegrationT assertEquals(valA, root.setValue(Lifespan.span(-5, 4), "a", 1)); assertEquals(Lifespan.span(-5, 9), valA.getLifespan()); - assertEquals(1, root.getValues().size()); + assertEquals(1, root.getValues(Lifespan.ALL).size()); } } @@ -660,7 +668,7 @@ public class DBTraceObjectManagerTest extends AbstractGhidraHeadlessIntegrationT assertEquals(valA, root.setValue(Lifespan.span(5, 14), "a", 1)); assertEquals(Lifespan.span(0, 14), valA.getLifespan()); - assertEquals(1, root.getValues().size()); + assertEquals(1, root.getValues(Lifespan.ALL).size()); } } @@ -672,7 +680,7 @@ public class DBTraceObjectManagerTest extends AbstractGhidraHeadlessIntegrationT assertEquals(valA, root.setValue(Lifespan.span(0, 9), "a", 1)); assertEquals(Lifespan.span(0, 9), valA.getLifespan()); - assertEquals(1, root.getValues().size()); + assertEquals(1, root.getValues(Lifespan.ALL).size()); } } @@ -684,11 +692,11 @@ public class DBTraceObjectManagerTest extends AbstractGhidraHeadlessIntegrationT assertEquals(valA, root.setValue(Lifespan.at(5), "a", 1)); assertEquals(Lifespan.span(0, 9), valA.getLifespan()); - assertEquals(1, root.getValues().size()); + assertEquals(1, root.getValues(Lifespan.ALL).size()); assertEquals(valA, root.setValue(Lifespan.span(-5, 14), "a", 1)); assertEquals(Lifespan.span(-5, 14), valA.getLifespan()); - assertEquals(1, root.getValues().size()); + assertEquals(1, root.getValues(Lifespan.ALL).size()); } } @@ -700,11 +708,11 @@ public class DBTraceObjectManagerTest extends AbstractGhidraHeadlessIntegrationT assertEquals(valA, root.setValue(Lifespan.span(0, 5), "a", 1)); assertEquals(Lifespan.span(0, 9), valA.getLifespan()); - assertEquals(1, root.getValues().size()); + assertEquals(1, root.getValues(Lifespan.ALL).size()); assertEquals(valA, root.setValue(Lifespan.span(0, 14), "a", 1)); assertEquals(Lifespan.span(0, 14), valA.getLifespan()); - assertEquals(1, root.getValues().size()); + assertEquals(1, root.getValues(Lifespan.ALL).size()); } } @@ -716,11 +724,11 @@ public class DBTraceObjectManagerTest extends AbstractGhidraHeadlessIntegrationT assertEquals(valA, root.setValue(Lifespan.span(5, 9), "a", 1)); assertEquals(Lifespan.span(0, 9), valA.getLifespan()); - assertEquals(1, root.getValues().size()); + assertEquals(1, root.getValues(Lifespan.ALL).size()); assertEquals(valA, root.setValue(Lifespan.span(-5, 9), "a", 1)); assertEquals(Lifespan.span(-5, 9), valA.getLifespan()); - assertEquals(1, root.getValues().size()); + assertEquals(1, root.getValues(Lifespan.ALL).size()); } } @@ -731,12 +739,12 @@ public class DBTraceObjectManagerTest extends AbstractGhidraHeadlessIntegrationT TraceObjectValue valA = root.setValue(Lifespan.span(0, 9), "a", 1); TraceObjectValue valB = root.setValue(Lifespan.span(20, 29), "a", 1); assertNotSame(valA, valB); - assertEquals(2, root.getValues().size()); + assertEquals(2, root.getValues(Lifespan.ALL).size()); assertEquals(valA, root.setValue(Lifespan.span(10, 19), "a", 1)); assertEquals(Lifespan.span(0, 29), valA.getLifespan()); assertTrue(valB.isDeleted()); - assertEquals(1, root.getValues().size()); + assertEquals(1, root.getValues(Lifespan.ALL).size()); } } @@ -825,13 +833,13 @@ public class DBTraceObjectManagerTest extends AbstractGhidraHeadlessIntegrationT try (Transaction tx = b.startTransaction()) { root = manager.createRootObject(ctx.getSchema(new SchemaName("Session"))).getChild(); assertNull(root.setValue(Lifespan.span(0, 9), "a", null)); - assertEquals(0, root.getValues().size()); + assertEquals(0, root.getValues(Lifespan.ALL).size()); assertNotNull(root.setValue(Lifespan.span(0, 9), "a", 1)); - assertEquals(1, root.getValues().size()); + assertEquals(1, root.getValues(Lifespan.ALL).size()); assertNull(root.setValue(Lifespan.at(5), "a", null)); - assertEquals(2, root.getValues().size()); + assertEquals(2, root.getValues(Lifespan.ALL).size()); assertEquals(List.of(Lifespan.span(0, 4), Lifespan.span(6, 9)), root.getOrderedValues(Lifespan.ALL, "a", true) @@ -845,10 +853,10 @@ public class DBTraceObjectManagerTest extends AbstractGhidraHeadlessIntegrationT try (Transaction tx = b.startTransaction()) { root = manager.createRootObject(ctx.getSchema(new SchemaName("Session"))).getChild(); assertNotNull(root.setValue(Lifespan.span(0, 9), "a", 1)); - assertEquals(1, root.getValues().size()); + assertEquals(1, root.getValues(Lifespan.ALL).size()); assertNull(root.setValue(Lifespan.span(0, 9), "a", null)); - assertEquals(0, root.getValues().size()); + assertEquals(0, root.getValues(Lifespan.ALL).size()); } } @@ -879,7 +887,7 @@ public class DBTraceObjectManagerTest extends AbstractGhidraHeadlessIntegrationT // Delete a leaf TraceObject t1 = targets.get(1); assertFalse(t1.isDeleted()); - assertEquals(3, targetContainer.getValues().size()); + assertEquals(3, targetContainer.getValues(Lifespan.ALL).size()); assertEquals(t1, Unique.assertOne( manager.getObjectsByPath(Lifespan.ALL, TraceObjectKeyPath.parse("Targets[1]")))); assertEquals(t1, t1.getAttribute(1, "self").getValue()); @@ -890,8 +898,8 @@ public class DBTraceObjectManagerTest extends AbstractGhidraHeadlessIntegrationT } assertTrue(t1.isDeleted()); - assertTrue(t1.getParents().isEmpty()); - assertEquals(2, targetContainer.getValues().size()); + assertTrue(t1.getParents(Lifespan.ALL).isEmpty()); + assertEquals(2, targetContainer.getValues(Lifespan.ALL).size()); assertEquals(0, manager.getObjectsByPath(Lifespan.ALL, TraceObjectKeyPath.parse("Targets[1]")).count()); assertNull(t1.getAttribute(2, "self")); @@ -901,13 +909,14 @@ public class DBTraceObjectManagerTest extends AbstractGhidraHeadlessIntegrationT TraceObject t0 = targets.get(0); assertEquals(2, manager.getObjectsByPath(Lifespan.ALL, TraceObjectKeyPath.parse("Targets[]")).count()); - assertTrue(t0.getParents().stream().anyMatch(v -> v.getParent() == targetContainer)); - assertEquals(2, targetContainer.getValues().size()); + assertTrue( + t0.getParents(Lifespan.ALL).stream().anyMatch(v -> v.getParent() == targetContainer)); + assertEquals(2, targetContainer.getValues(Lifespan.ALL).size()); b.trace.undo(); b.trace.redo(); - assertEquals(2, targetContainer.getValues().size()); + assertEquals(2, targetContainer.getValues(Lifespan.ALL).size()); try (Transaction tx = b.startTransaction()) { targetContainer.delete(); @@ -916,7 +925,8 @@ public class DBTraceObjectManagerTest extends AbstractGhidraHeadlessIntegrationT assertEquals(0, manager.getObjectsByPath(Lifespan.ALL, TraceObjectKeyPath.parse("Targets[]")).count()); assertFalse(t0.isDeleted()); - assertFalse(t0.getParents().stream().anyMatch(v -> v.getParent() == targetContainer)); + assertFalse( + t0.getParents(Lifespan.ALL).stream().anyMatch(v -> v.getParent() == targetContainer)); // A little odd, but allows branch to be replaced and successors restored later assertEquals(t0, root.getValue(0, "curTarget").getValue()); } @@ -1093,4 +1103,76 @@ public class DBTraceObjectManagerTest extends AbstractGhidraHeadlessIntegrationT assertTrue(hiddenOutside.getCanonicalParent(0).isHidden()); } } + + protected String randomIdentifier(Random random, int length) { + StringBuilder sb = new StringBuilder(length); + while (sb.length() < length) { + char c = (char) random.nextInt(); + boolean isValid = sb.isEmpty() + ? Character.isJavaIdentifierStart(c) + : Character.isJavaIdentifierPart(c); + if (isValid) { + sb.append(c); + continue; + } + } + return sb.toString(); + } + + protected Address randomAddress(Random random) { + List 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 values, Lifespan span, + AddressRange range) { + List expected = values.stream() + .filter(v -> v.getLifespan().intersects(span) && range.contains(v.castValue())) + .toList(); + List actual = + List.copyOf(b.trace.getObjectManager().getValuesIntersecting(span, range)); + assertEquals(expected, actual); + } + + @Test + public void testManyAddressValuesAcrossSpaces() { + Random random = new Random(); + List 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)); + } } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/target/ValueSpaceTest.java b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/target/ValueSpaceTest.java new file mode 100644 index 0000000000..92a7d00088 --- /dev/null +++ b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/target/ValueSpaceTest.java @@ -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")); + } +} diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/util/database/DBCachedObjectStoreFactory.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/util/database/DBCachedObjectStoreFactory.java index 3e1c7f8208..9d1ecfe2b3 100644 --- a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/util/database/DBCachedObjectStoreFactory.java +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/util/database/DBCachedObjectStoreFactory.java @@ -27,6 +27,7 @@ import java.util.stream.Collectors; import java.util.stream.Stream; import db.*; +import ghidra.program.model.address.*; import ghidra.util.Msg; import ghidra.util.database.annot.*; import ghidra.util.database.annot.DBAnnotatedField.DefaultCodec; @@ -110,6 +111,32 @@ import ghidra.util.exception.VersionException; */ 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 * @@ -928,11 +955,41 @@ public class DBCachedObjectStoreFactory { /** Codec for {@code String[]} */ PrimitiveCodec STRING_ARR = new ArrayObjectCodec<>(new LengthBoundCodec<>(STRING)); + PrimitiveCodec 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 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? Map> CODECS_BY_SELECTOR = Stream .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)); Map, PrimitiveCodec> CODECS_BY_CLASS = CODECS_BY_SELECTOR.values() .stream() diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/util/database/spatial/DBTreeNodeRecord.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/util/database/spatial/DBTreeNodeRecord.java index 949b29a463..5f3cfd3f92 100644 --- a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/util/database/spatial/DBTreeNodeRecord.java +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/util/database/spatial/DBTreeNodeRecord.java @@ -15,6 +15,8 @@ */ package ghidra.util.database.spatial; +import java.util.List; + import db.DBRecord; import ghidra.util.database.DBCachedObjectStore; @@ -39,6 +41,8 @@ public abstract class DBTreeNodeRecord> extends DBT } }; + public static final List VALUES = List.of(values()); + private final boolean directory; private final boolean leafParent; diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/util/database/spatial/hyper/AbstractHyperBoxQuery.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/util/database/spatial/hyper/AbstractHyperBoxQuery.java new file mode 100644 index 0000000000..13ac4e8bd6 --- /dev/null +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/util/database/spatial/hyper/AbstractHyperBoxQuery.java @@ -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 extends HyperBox, // + Q extends AbstractHyperBoxQuery> // + implements Query { + + public interface QueryFactory, Q extends AbstractHyperBoxQuery> { + Q create(NS ls, NS us, HyperDirection direction); + } + + protected static

, // + Q extends AbstractHyperBoxQuery> Q intersecting(NS shape, + HyperDirection direction, QueryFactory factory) { + HyperBox 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

, // + Q extends AbstractHyperBoxQuery> Q enclosing(NS shape, + HyperDirection direction, QueryFactory factory) { + HyperBox 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

, // + Q extends AbstractHyperBoxQuery> Q enclosed(NS shape, + HyperDirection direction, QueryFactory factory) { + HyperBox 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

, // + Q extends AbstractHyperBoxQuery> Q equalTo(NS shape, + HyperDirection direction, QueryFactory 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 space; + protected final HyperDirection direction; + + protected Comparator comparator; + + public AbstractHyperBoxQuery(NS ls, NS us, EuclideanHyperSpace 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 boolean dimTerminateEarlyNode(Dimension 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 dim = space.getDimensions().get(direction.dimension()); + return dimTerminateEarlyNode(dim, shape); + } + + @Override + public Comparator getBoundsComparator() { + if (comparator == null) { + Dimension dim = space.getDimensions().get(direction.dimension()); + comparator = createBoundsComparator(dim); + } + return comparator; + } + + protected Comparator createBoundsComparator(Dimension dim) { + if (direction.forward()) { + return Comparator.comparing(dim::lower, dim::compare); + } + return Comparator.comparing(dim::upper, (a, b) -> dim.compare(b, a)); + } + + private boolean isNone(Dimension 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 boolean isSome(Dimension 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 dim : space.getDimensions()) { + if (isNone(dim, shape)) { + return QueryInclusion.NONE; + } + } + for (Dimension 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); + } +} diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/util/database/spatial/hyper/AbstractHyperRStarTree.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/util/database/spatial/hyper/AbstractHyperRStarTree.java new file mode 100644 index 0000000000..d8dfadf2e4 --- /dev/null +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/util/database/spatial/hyper/AbstractHyperRStarTree.java @@ -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, // + DR extends DBTreeDataRecord, // + NS extends HyperBox, // + NR extends DBTreeNodeRecord, // + T, // + Q extends AbstractHyperBoxQuery> // + extends AbstractRStarConstraintsTree { + + protected static class AsSpatialMap< // + DS extends BoundedShape, // + DR extends DBTreeDataRecord, // + NS extends HyperBox, T, Q extends AbstractHyperBoxQuery> + extends AbstractConstraintsTreeSpatialMap { + public AsSpatialMap(AbstractConstraintsTree tree, Q query) { + super(tree, query); + } + + @Override + public AsSpatialMap reduce(Q andQuery) { + return new AsSpatialMap<>(this.tree, + this.query == null ? andQuery : this.query.and(andQuery)); + } + } + + protected final EuclideanHyperSpace space; + protected final List> axes; + + protected Comparator dimComparator(Dimension dim) { + return Comparator.comparing(dim::lower, dim::compare); + } + + public AbstractHyperRStarTree(DBCachedObjectStoreFactory storeFactory, String tableName, + EuclideanHyperSpace space, Class dataType, Class 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> getSplitAxes() { + return axes; + } + + @Override + protected Comparator getDefaultBoundsComparator() { + return axes.get(0); + } + + @Override + public AbstractConstraintsTreeSpatialMap asSpatialMap() { + return new AsSpatialMap<>(this, null); + } +} diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/util/database/spatial/hyper/Dimension.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/util/database/spatial/hyper/Dimension.java new file mode 100644 index 0000000000..26c2271468 --- /dev/null +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/util/database/spatial/hyper/Dimension.java @@ -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 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)); + } +} diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/util/database/spatial/hyper/EuclideanHyperSpace.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/util/database/spatial/hyper/EuclideanHyperSpace.java new file mode 100644 index 0000000000..62dd0c3a92 --- /dev/null +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/util/database/spatial/hyper/EuclideanHyperSpace.java @@ -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

> { + List> getDimensions(); + + B getFull(); + + default boolean boxesEqual(B a, B b) { + for (Dimension 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> dims = getDimensions(); + Object[] result = new Object[dims.size() * 2]; + for (int i = 0; i < dims.size(); i++) { + Dimension 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 dim : getDimensions()) { + if (!dim.contains(box, point)) { + return false; + } + } + return true; + } + + default double boxArea(B box) { + double result = 1; + for (Dimension dim : getDimensions()) { + result *= 1 + dim.measure(box); + } + return result; + } + + default double boxMargin(B box) { + double result = 0; + for (Dimension dim : getDimensions()) { + result += 1 + dim.measure(box); + } + return result; + } + + P boxCenter(B box); + + default double measureUnion(Dimension 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 dim : getDimensions()) { + result *= 1 + measureUnion(dim, a, b); + } + return result; + } + + default double measureIntersection(Dimension 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 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 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 dim : getDimensions()) { + if (!dim.encloses(outer, inner)) { + return false; + } + } + return true; + } +} diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/util/database/spatial/hyper/HyperBox.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/util/database/spatial/hyper/HyperBox.java new file mode 100644 index 0000000000..cdd8313792 --- /dev/null +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/util/database/spatial/hyper/HyperBox.java @@ -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

> extends BoundingShape { + EuclideanHyperSpace 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); + } +} diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/util/database/spatial/hyper/HyperDirection.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/util/database/spatial/hyper/HyperDirection.java new file mode 100644 index 0000000000..34816b4e8c --- /dev/null +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/util/database/spatial/hyper/HyperDirection.java @@ -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); +} diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/util/database/spatial/hyper/HyperPoint.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/util/database/spatial/hyper/HyperPoint.java new file mode 100644 index 0000000000..682331f8d8 --- /dev/null +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/util/database/spatial/hyper/HyperPoint.java @@ -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 { +} diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/util/database/spatial/hyper/LongDimension.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/util/database/spatial/hyper/LongDimension.java new file mode 100644 index 0000000000..e04cc4cd3b --- /dev/null +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/util/database/spatial/hyper/LongDimension.java @@ -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

> + extends Dimension { + + @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; + } +} diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/util/database/spatial/hyper/StringDimension.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/util/database/spatial/hyper/StringDimension.java new file mode 100644 index 0000000000..bf49232bbf --- /dev/null +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/util/database/spatial/hyper/StringDimension.java @@ -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

> + extends Dimension { + + @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; + } +} diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/util/database/spatial/hyper/ULongDimension.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/util/database/spatial/hyper/ULongDimension.java new file mode 100644 index 0000000000..456b966224 --- /dev/null +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/util/database/spatial/hyper/ULongDimension.java @@ -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

> + extends Dimension { + + @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; + } +} diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/util/database/spatial/rect/Point2D.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/util/database/spatial/rect/Point2D.java index 30c68add13..f289e2b84d 100644 --- a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/util/database/spatial/rect/Point2D.java +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/util/database/spatial/rect/Point2D.java @@ -25,6 +25,7 @@ public interface Point2D { default double computeDistance(Point2D point) { double distX = getSpace().distX(getX(), point.getX()); double distY = getSpace().distY(getY(), point.getY()); + // NB. Square root is unnecessary, if this is just for comparison return distX * distX + distY * distY; } } diff --git a/Ghidra/Test/DebuggerIntegrationTest/src/test.slow/java/agent/dbgeng/rmi/DbgEngCommandsTest.java b/Ghidra/Test/DebuggerIntegrationTest/src/test.slow/java/agent/dbgeng/rmi/DbgEngCommandsTest.java index 1f1725cfe2..1fa3e2419f 100644 --- a/Ghidra/Test/DebuggerIntegrationTest/src/test.slow/java/agent/dbgeng/rmi/DbgEngCommandsTest.java +++ b/Ghidra/Test/DebuggerIntegrationTest/src/test.slow/java/agent/dbgeng/rmi/DbgEngCommandsTest.java @@ -761,7 +761,7 @@ public class DbgEngCommandsTest extends AbstractDbgEngTraceRmiTest { Map.entry("[1]", Lifespan.nowOn(0)), Map.entry("[2]", Lifespan.span(0, 9)), Map.entry("[3]", Lifespan.nowOn(0))), - object.getValues() + object.getValues(Lifespan.ALL) .stream() .collect(Collectors.toMap(v -> v.getEntryKey(), v -> v.getLifespan()))); } diff --git a/Ghidra/Test/DebuggerIntegrationTest/src/test.slow/java/agent/gdb/rmi/GdbCommandsTest.java b/Ghidra/Test/DebuggerIntegrationTest/src/test.slow/java/agent/gdb/rmi/GdbCommandsTest.java index 588c28ff55..173b4104af 100644 --- a/Ghidra/Test/DebuggerIntegrationTest/src/test.slow/java/agent/gdb/rmi/GdbCommandsTest.java +++ b/Ghidra/Test/DebuggerIntegrationTest/src/test.slow/java/agent/gdb/rmi/GdbCommandsTest.java @@ -786,7 +786,7 @@ public class GdbCommandsTest extends AbstractGdbTraceRmiTest { Map.entry("[1]", Lifespan.nowOn(0)), Map.entry("[2]", Lifespan.span(0, 9)), Map.entry("[3]", Lifespan.nowOn(0))), - object.getValues() + object.getValues(Lifespan.ALL) .stream() .collect(Collectors.toMap(v -> v.getEntryKey(), v -> v.getLifespan()))); } diff --git a/Ghidra/Test/DebuggerIntegrationTest/src/test.slow/java/agent/lldb/rmi/LldbCommandsTest.java b/Ghidra/Test/DebuggerIntegrationTest/src/test.slow/java/agent/lldb/rmi/LldbCommandsTest.java index aa93892ffc..af98e3291e 100644 --- a/Ghidra/Test/DebuggerIntegrationTest/src/test.slow/java/agent/lldb/rmi/LldbCommandsTest.java +++ b/Ghidra/Test/DebuggerIntegrationTest/src/test.slow/java/agent/lldb/rmi/LldbCommandsTest.java @@ -810,7 +810,7 @@ public class LldbCommandsTest extends AbstractLldbTraceRmiTest { Map.entry("[1]", Lifespan.nowOn(0)), Map.entry("[2]", Lifespan.span(0, 9)), Map.entry("[3]", Lifespan.nowOn(0))), - object.getValues() + object.getValues(Lifespan.ALL) .stream() .collect(Collectors.toMap(v -> v.getEntryKey(), v -> v.getLifespan()))); }