GP-1969: Add 'Model' provider for inspecting object-based traces.

This commit is contained in:
Dan 2022-06-15 15:41:38 -04:00
parent eb0a23aecc
commit 2a4b4f9bcf
59 changed files with 6131 additions and 269 deletions

View file

@ -277,7 +277,7 @@ public class DBTraceObjectBreakpointLocation
}
PathMatcher procMatcher = schema.searchFor(TargetProcess.class, false);
return object.getAncestors(getLifespan(), procMatcher)
return object.getAncestorsRoot(getLifespan(), procMatcher)
.flatMap(proc -> proc.getSource(object)
.querySuccessorsInterface(getLifespan(),
TraceObjectThread.class))

View file

@ -203,6 +203,7 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject {
@Override
public RangeSet<Long> getLife() {
// TODO: This should really be cached
try (LockHold hold = manager.trace.lockRead()) {
RangeSet<Long> result = TreeRangeSet.create();
// NOTE: connected ranges should already be coalesced
@ -220,19 +221,20 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject {
return manager.doGetObject(path.parent());
}
protected void doInsert(Range<Long> lifespan, ConflictResolution resolution) {
protected DBTraceObjectValPath doInsert(Range<Long> lifespan, ConflictResolution resolution) {
if (path.isRoot()) {
return;
return DBTraceObjectValPath.of();
}
DBTraceObject parent = doCreateCanonicalParentObject();
parent.setValue(lifespan, path.key(), this, resolution);
parent.doInsert(lifespan, resolution);
InternalTraceObjectValue value = parent.setValue(lifespan, path.key(), this, resolution);
DBTraceObjectValPath path = parent.doInsert(lifespan, resolution);
return path.append(value);
}
@Override
public void insert(Range<Long> lifespan, ConflictResolution resolution) {
public DBTraceObjectValPath insert(Range<Long> lifespan, ConflictResolution resolution) {
try (LockHold hold = manager.trace.lockWrite()) {
doInsert(lifespan, resolution);
return doInsert(lifespan, resolution);
}
}
@ -253,6 +255,9 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject {
}
protected void doRemoveTree(Range<Long> span) {
for (DBTraceObjectValue parent : getParents()) {
parent.doTruncateOrDeleteAndEmitLifeChange(span);
}
for (InternalTraceObjectValue value : getValues()) {
value.doTruncateOrDeleteAndEmitLifeChange(span);
if (value.isCanonical()) {
@ -264,7 +269,6 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject {
@Override
public void removeTree(Range<Long> span) {
try (LockHold hold = manager.trace.lockWrite()) {
getCanonicalParents(span).forEach(v -> v.doTruncateOrDeleteAndEmitLifeChange(span));
doRemoveTree(span);
}
}
@ -327,10 +331,14 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject {
return ifCls.cast(ifaces.get(ifCls));
}
protected Collection<? extends DBTraceObjectValue> doGetParents() {
return manager.valuesByChild.get(this);
}
@Override
public Collection<? extends DBTraceObjectValue> getParents() {
try (LockHold hold = manager.trace.lockRead()) {
return manager.valuesByChild.get(this);
return doGetParents();
}
}
@ -626,23 +634,35 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject {
}
@Override
public Stream<? extends DBTraceObjectValPath> getAncestors(
public Stream<? extends TraceObjectValPath> getAncestors(Range<Long> span,
PathPredicates relativePredicates) {
try (LockHold hold = manager.trace.lockRead()) {
Stream<? extends DBTraceObjectValPath> ancestors =
doStreamVisitor(span, new InternalAncestorsRelativeVisitor(relativePredicates));
if (relativePredicates.matches(List.of())) {
return Stream.concat(Stream.of(DBTraceObjectValPath.of()), ancestors);
}
return ancestors;
}
}
@Override
public Stream<? extends DBTraceObjectValPath> getAncestorsRoot(
Range<Long> span, PathPredicates rootPredicates) {
try (LockHold hold = manager.trace.lockRead()) {
return doStreamVisitor(span, new InternalAncestorsVisitor(rootPredicates));
return doStreamVisitor(span, new InternalAncestorsRootVisitor(rootPredicates));
}
}
@Override
public Stream<? extends DBTraceObjectValPath> getSuccessors(
Range<Long> span, PathPredicates relativePredicates) {
DBTraceObjectValPath empty = DBTraceObjectValPath.of();
try (LockHold hold = manager.trace.lockRead()) {
Stream<? extends DBTraceObjectValPath> succcessors =
doStreamVisitor(span, new InternalSuccessorsVisitor(relativePredicates));
doStreamVisitor(span, new InternalSuccessorsRelativeVisitor(relativePredicates));
if (relativePredicates.matches(List.of())) {
// Pre-cat the empty path (not the empty stream)
return Stream.concat(Stream.of(empty), succcessors);
return Stream.concat(Stream.of(DBTraceObjectValPath.of()), succcessors);
}
return succcessors;
}
@ -794,7 +814,7 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject {
Class<? extends TargetObject> targetIf) {
// This is a sort of meet-in-the-middle. The type search must originate from the root
PathMatcher matcher = getManager().getRootSchema().searchFor(targetIf, false);
return getAncestors(span, matcher);
return getAncestorsRoot(span, matcher);
}
@Override

View file

@ -23,6 +23,7 @@ import ghidra.trace.database.map.DBTraceAddressSnapRangePropertyMapTree;
import ghidra.trace.database.map.DBTraceAddressSnapRangePropertyMapTree.AbstractDBTraceAddressSnapRangePropertyMapData;
import ghidra.trace.database.target.DBTraceObjectValue.DBTraceObjectDBFieldCodec;
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;
@ -66,6 +67,12 @@ public class DBTraceObjectAddressRangeValue
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
@ -121,11 +128,23 @@ public class DBTraceObjectAddressRangeValue
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;

View file

@ -368,7 +368,7 @@ public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager
if (rootVal == null) {
return Stream.of();
}
return rootVal.doStreamVisitor(span, new InternalSuccessorsVisitor(predicates));
return rootVal.doStreamVisitor(span, new InternalSuccessorsRelativeVisitor(predicates));
}
}

View file

@ -31,6 +31,7 @@ import ghidra.trace.database.DBTraceUtils;
import ghidra.trace.database.target.InternalTreeTraversal.Visitor;
import ghidra.trace.model.Trace;
import ghidra.trace.model.target.TraceObject;
import ghidra.trace.model.target.TraceObjectKeyPath;
import ghidra.util.LockHold;
import ghidra.util.database.*;
import ghidra.util.database.DBCachedObjectStoreFactory.AbstractDBFieldCodec;
@ -275,6 +276,11 @@ public class DBTraceObjectValue extends DBAnnotatedObject implements InternalTra
return (DBTraceObject) getValue();
}
@Override
public boolean isObject() {
return child != null;
}
@Override
public DBTraceObject getChildOrNull() {
return child;
@ -320,6 +326,10 @@ public class DBTraceObjectValue extends DBAnnotatedObject implements InternalTra
return InternalTreeTraversal.INSTANCE.walkValue(visitor, this, span, null);
}
protected TraceObjectKeyPath doGetCanonicalPath() {
return triple.parent.getCanonicalPath().extend(triple.key);
}
protected boolean doIsCanonical() {
if (child == null) {
return false;
@ -327,7 +337,14 @@ public class DBTraceObjectValue extends DBAnnotatedObject implements InternalTra
if (triple.parent == null) {
return true;
}
return triple.parent.getCanonicalPath().extend(triple.key).equals(child.getCanonicalPath());
return doGetCanonicalPath().equals(child.getCanonicalPath());
}
@Override
public TraceObjectKeyPath getCanonicalPath() {
try (LockHold hold = LockHold.lock(manager.lock.readLock())) {
return doGetCanonicalPath();
}
}
@Override

View file

@ -0,0 +1,66 @@
/* ###
* 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.List;
import java.util.Set;
import java.util.stream.Stream;
import com.google.common.collect.Range;
import ghidra.dbg.util.PathPredicates;
import ghidra.trace.database.target.InternalTreeTraversal.SpanIntersectingVisitor;
import ghidra.trace.database.target.InternalTreeTraversal.VisitResult;
public class InternalAncestorsRelativeVisitor implements SpanIntersectingVisitor {
protected final PathPredicates predicates;
public InternalAncestorsRelativeVisitor(PathPredicates predicates) {
this.predicates = predicates;
}
@Override
public DBTraceObjectValPath composePath(DBTraceObjectValPath pre,
InternalTraceObjectValue value) {
return pre == null ? DBTraceObjectValPath.of() : pre.prepend(value);
}
@Override
public VisitResult visitValue(InternalTraceObjectValue value, DBTraceObjectValPath path) {
List<String> keyList = path.getKeyList();
return VisitResult.result(predicates.matches(keyList),
predicates.ancestorCouldMatchRight(keyList, true) && value.getChildOrNull() != null);
}
@Override
public DBTraceObject continueObject(InternalTraceObjectValue value) {
return value.getParent();
}
@Override
public Stream<? extends InternalTraceObjectValue> continueValues(DBTraceObject object,
Range<Long> span, DBTraceObjectValPath pre) {
Set<String> prevKeys = predicates.getPrevKeys(pre.getKeyList());
if (prevKeys.isEmpty()) {
return Stream.empty();
}
return object.doGetParents()
.stream()
.filter(v -> PathPredicates.anyMatches(prevKeys, v.getEntryKey()));
}
}

View file

@ -23,11 +23,11 @@ import ghidra.dbg.util.PathPredicates;
import ghidra.trace.database.target.InternalTreeTraversal.SpanIntersectingVisitor;
import ghidra.trace.database.target.InternalTreeTraversal.VisitResult;
public class InternalAncestorsVisitor implements SpanIntersectingVisitor {
public class InternalAncestorsRootVisitor implements SpanIntersectingVisitor {
protected final PathPredicates predicates;
public InternalAncestorsVisitor(PathPredicates predicates) {
public InternalAncestorsRootVisitor(PathPredicates predicates) {
this.predicates = predicates;
}

View file

@ -26,11 +26,11 @@ import ghidra.trace.database.DBTraceUtils;
import ghidra.trace.database.target.InternalTreeTraversal.SpanIntersectingVisitor;
import ghidra.trace.database.target.InternalTreeTraversal.VisitResult;
public class InternalSuccessorsVisitor implements SpanIntersectingVisitor {
public class InternalSuccessorsRelativeVisitor implements SpanIntersectingVisitor {
protected final PathPredicates predicates;
public InternalSuccessorsVisitor(PathPredicates predicates) {
public InternalSuccessorsRelativeVisitor(PathPredicates predicates) {
this.predicates = predicates;
}

View file

@ -125,6 +125,14 @@ public class TraceDomainObjectListener implements DomainObjectListener {
typedMap.put(type, handler);
}
/**
* Listen for the given event, taking the affected object, the old value, and the new value
*
* @param <T> the type of the affected object
* @param <U> the type of the values
* @param type the event type
* @param handler the handler
*/
protected <T, U> void listenFor(TraceChangeType<T, U> type,
AffectedAndValuesOnlyHandler<? super T, ? super U> handler) {
typedMap.put(type, handler);

View file

@ -21,6 +21,7 @@ import java.util.stream.Stream;
import com.google.common.collect.Range;
import com.google.common.collect.RangeSet;
import ghidra.dbg.target.TargetMethod;
import ghidra.dbg.target.TargetObject;
import ghidra.dbg.target.schema.TargetObjectSchema;
import ghidra.dbg.util.PathPredicates;
@ -38,6 +39,8 @@ import ghidra.trace.model.TraceUniqueObject;
* In many cases, such interfaces are just wrappers.
*/
public interface TraceObject extends TraceUniqueObject {
String EXTRA_INTERFACES_ATTRIBUTE_NAME = "_extra_ifs";
/**
* Get the trace containing this object
*
@ -79,8 +82,9 @@ public interface TraceObject extends TraceUniqueObject {
*
* @param the minimum lifespan of edges from the root to this object
* @param resolution the rule for handling duplicate keys when setting values.
* @return the value path from root to the newly inserted object
*/
void insert(Range<Long> lifespan, ConflictResolution resolution);
TraceObjectValPath insert(Range<Long> lifespan, ConflictResolution resolution);
/**
* Remove this object from its canonical path for the given lifespan
@ -97,9 +101,9 @@ public interface TraceObject extends TraceUniqueObject {
* Remove this object and its successors from their canonical paths for the given span
*
* <p>
* Truncate the lifespans of this object's canonical parent value and all canonical values
* succeeding this object. If a truncated value's lifespan is contained in the given span, the
* value will be deleted.
* Truncate the lifespans of this object's parent values and all canonical values succeeding
* this object. If a truncated value's lifespan is contained in the given span, the value will
* be deleted.
*
* @param span the span during which this object and its canonical successors should be removed
*/
@ -282,9 +286,20 @@ public interface TraceObject extends TraceUniqueObject {
* @param rootPredicates the predicates for matching path keys, relative to the root
* @return the stream of matching paths to values
*/
Stream<? extends TraceObjectValPath> getAncestors(Range<Long> span,
Stream<? extends TraceObjectValPath> getAncestorsRoot(Range<Long> span,
PathPredicates rootPredicates);
/**
* Stream all ancestor values of this object matching the given predicates, intersecting the
* given span
*
* @param span a span which values along the path must intersect
* @param relativePredicates the predicates for matching path keys, relative to this object
* @return the stream of matching paths to values
*/
Stream<? extends TraceObjectValPath> getAncestors(Range<Long> span,
PathPredicates relativePredicates);
/**
* Stream all successor values of this object matching the given predicates, intersecting the
* given span
@ -466,4 +481,30 @@ public interface TraceObject extends TraceUniqueObject {
*/
@Override
boolean isDeleted();
/**
* Check if the child represents a method at the given snap
*
* @param snap the snap
* @return true if a method
*/
default boolean isMethod(long snap) {
if (getTargetSchema().getInterfaces().contains(TargetMethod.class)) {
return true;
}
TraceObjectValue extras = getAttribute(snap, TraceObject.EXTRA_INTERFACES_ATTRIBUTE_NAME);
if (extras == null) {
return false;
}
Object val = extras.getValue();
if (!(val instanceof String)) {
return false;
}
String valStr = (String) val;
// Not ideal, but it's not a substring of any other schema interface....
if (valStr.contains("Method")) {
return true;
}
return false;
}
}

View file

@ -225,9 +225,26 @@ public final class TraceObjectKeyPath implements Comparable<TraceObjectKeyPath>
if (!predicates.ancestorMatches(keyList, false)) {
return Stream.of();
}
Stream<TraceObjectKeyPath> ancestry =
isRoot() ? Stream.of() : parent().streamMatchingAncestry(predicates);
if (predicates.matches(keyList)) {
return Stream.concat(Stream.of(this), parent().streamMatchingAncestry(predicates));
return Stream.concat(Stream.of(this), ancestry);
}
return parent().streamMatchingAncestry(predicates);
return ancestry;
}
/**
* Check if this path is an ancestor of the given path
*
* <p>
* Equivalently, check if the given path is a successor of this path. A path is considered an
* ancestor of itself. To check for a strict ancestor, use
* {@code this.isAncestor(that) && !this.equals(that)}.
*
* @param that the supposed successor to this path
* @return true if the given path is in fact a successor
*/
public boolean isAncestor(TraceObjectKeyPath that) {
return PathUtils.isAncestor(keyList, that.keyList);
}
}

View file

@ -17,6 +17,7 @@ package ghidra.trace.model.target;
import com.google.common.collect.Range;
import ghidra.dbg.target.schema.TargetObjectSchema;
import ghidra.trace.model.Trace;
import ghidra.trace.model.target.TraceObject.ConflictResolution;
@ -43,6 +44,17 @@ public interface TraceObjectValue {
*/
String getEntryKey();
/**
* Get the "canonical path" of this value
*
* <p>
* This is the parent's canonical path extended by this value's entry key. Note, in the case
* this value has a child object, this is not necessarily its canonical path.
*
* @return
*/
TraceObjectKeyPath getCanonicalPath();
/**
* Get the value
*
@ -58,6 +70,13 @@ public interface TraceObjectValue {
*/
TraceObject getChild();
/**
* Check if the value is an object (i.e., {@link TraceObject})
*
* @return true if an object, false otherwise
*/
boolean isObject();
/**
* Check if this value represents its child's canonical location
*
@ -69,6 +88,15 @@ public interface TraceObjectValue {
*/
boolean isCanonical();
/**
* Get the (target) schema for the value
*
* @return the schema
*/
default TargetObjectSchema getTargetSchema() {
return getParent().getTargetSchema().getChildSchema(getEntryKey());
}
/**
* Set the lifespan of this entry, truncating duplicates
*
@ -157,4 +185,17 @@ public interface TraceObjectValue {
* if a second is created.
*/
TraceObjectValue truncateOrDelete(Range<Long> span);
/**
* Check if the schema designates this value as hidden
*
* @return true if hidden
*/
default boolean isHidden() {
TraceObject parent = getParent();
if (parent == null) {
return false;
}
return parent.getTargetSchema().isHidden(getEntryKey());
}
}