mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-03 09:49:23 +02:00
Merge remote-tracking branch 'origin/GP-4143_Dan_schemaAttributeAliasing--SQUASHED'
This commit is contained in:
commit
a416caf911
21 changed files with 584 additions and 158 deletions
|
@ -70,12 +70,10 @@ public class DefaultSchemaContext implements SchemaContext {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object obj) {
|
public boolean equals(Object obj) {
|
||||||
if (obj instanceof DefaultSchemaContext) {
|
if (obj instanceof DefaultSchemaContext that) {
|
||||||
DefaultSchemaContext that = (DefaultSchemaContext) obj;
|
|
||||||
return Objects.equals(this.schemas, that.schemas);
|
return Objects.equals(this.schemas, that.schemas);
|
||||||
}
|
}
|
||||||
if (obj instanceof SchemaContext) {
|
if (obj instanceof SchemaContext that) {
|
||||||
SchemaContext that = (SchemaContext) obj;
|
|
||||||
return this.schemas.values().equals(that.getAllSchemas());
|
return this.schemas.values().equals(that.getAllSchemas());
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
|
|
@ -61,10 +61,9 @@ public class DefaultTargetObjectSchema
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object obj) {
|
public boolean equals(Object obj) {
|
||||||
if (!(obj instanceof DefaultAttributeSchema)) {
|
if (!(obj instanceof DefaultAttributeSchema that)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
DefaultAttributeSchema that = (DefaultAttributeSchema) obj;
|
|
||||||
if (!Objects.equals(this.name, that.name)) {
|
if (!Objects.equals(this.name, that.name)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -119,6 +118,64 @@ public class DefaultTargetObjectSchema
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected static class AliasResolver {
|
||||||
|
private final Map<String, AttributeSchema> schemas;
|
||||||
|
private final Map<String, String> aliases;
|
||||||
|
private final AttributeSchema defaultSchema;
|
||||||
|
private Map<String, String> resolvedAliases;
|
||||||
|
|
||||||
|
public AliasResolver(Map<String, AttributeSchema> schemas, Map<String, String> aliases,
|
||||||
|
AttributeSchema defaultSchema) {
|
||||||
|
this.schemas = schemas;
|
||||||
|
this.aliases = aliases;
|
||||||
|
this.defaultSchema = defaultSchema;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, String> resolveAliases() {
|
||||||
|
this.resolvedAliases = new LinkedHashMap<>();
|
||||||
|
for (String alias : aliases.keySet()) {
|
||||||
|
if (alias.equals("")) {
|
||||||
|
throw new IllegalArgumentException("Key '' cannot be an alias");
|
||||||
|
}
|
||||||
|
if (schemas.containsKey(alias)) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"Key '%s' cannot be both an attribute and an alias".formatted(alias));
|
||||||
|
}
|
||||||
|
resolveAlias(alias, new LinkedHashSet<>());
|
||||||
|
}
|
||||||
|
return resolvedAliases;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected String resolveAlias(String alias, LinkedHashSet<String> visited) {
|
||||||
|
String already = resolvedAliases.get(alias);
|
||||||
|
if (already != null) {
|
||||||
|
return already;
|
||||||
|
}
|
||||||
|
if (!visited.add(alias)) {
|
||||||
|
throw new IllegalArgumentException("Cycle of aliases: " + visited);
|
||||||
|
}
|
||||||
|
String to = aliases.get(alias);
|
||||||
|
if (to == null) {
|
||||||
|
return alias;
|
||||||
|
}
|
||||||
|
if (to.equals("")) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"Cannot alias to key '' (from %s)".formatted(alias));
|
||||||
|
}
|
||||||
|
String result = resolveAlias(to, visited);
|
||||||
|
resolvedAliases.put(alias, result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, AttributeSchema> resolveSchemas() {
|
||||||
|
Map<String, AttributeSchema> resolved = new LinkedHashMap<>(schemas);
|
||||||
|
for (Map.Entry<String, String> ent : resolvedAliases.entrySet()) {
|
||||||
|
resolved.put(ent.getKey(), schemas.getOrDefault(ent.getValue(), defaultSchema));
|
||||||
|
}
|
||||||
|
return resolved;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private final SchemaContext context;
|
private final SchemaContext context;
|
||||||
private final SchemaName name;
|
private final SchemaName name;
|
||||||
private final Class<?> type;
|
private final Class<?> type;
|
||||||
|
@ -130,6 +187,7 @@ public class DefaultTargetObjectSchema
|
||||||
private final ResyncMode elementResync;
|
private final ResyncMode elementResync;
|
||||||
|
|
||||||
private final Map<String, AttributeSchema> attributeSchemas;
|
private final Map<String, AttributeSchema> attributeSchemas;
|
||||||
|
private final Map<String, String> attributeAliases;
|
||||||
private final AttributeSchema defaultAttributeSchema;
|
private final AttributeSchema defaultAttributeSchema;
|
||||||
private final ResyncMode attributeResync;
|
private final ResyncMode attributeResync;
|
||||||
|
|
||||||
|
@ -137,7 +195,8 @@ public class DefaultTargetObjectSchema
|
||||||
Set<Class<? extends TargetObject>> interfaces, boolean isCanonicalContainer,
|
Set<Class<? extends TargetObject>> interfaces, boolean isCanonicalContainer,
|
||||||
Map<String, SchemaName> elementSchemas, SchemaName defaultElementSchema,
|
Map<String, SchemaName> elementSchemas, SchemaName defaultElementSchema,
|
||||||
ResyncMode elementResync,
|
ResyncMode elementResync,
|
||||||
Map<String, AttributeSchema> attributeSchemas, AttributeSchema defaultAttributeSchema,
|
Map<String, AttributeSchema> attributeSchemas, Map<String, String> attributeAliases,
|
||||||
|
AttributeSchema defaultAttributeSchema,
|
||||||
ResyncMode attributeResync) {
|
ResyncMode attributeResync) {
|
||||||
this.context = context;
|
this.context = context;
|
||||||
this.name = name;
|
this.name = name;
|
||||||
|
@ -149,7 +208,10 @@ public class DefaultTargetObjectSchema
|
||||||
this.defaultElementSchema = defaultElementSchema;
|
this.defaultElementSchema = defaultElementSchema;
|
||||||
this.elementResync = elementResync;
|
this.elementResync = elementResync;
|
||||||
|
|
||||||
this.attributeSchemas = Collections.unmodifiableMap(new LinkedHashMap<>(attributeSchemas));
|
AliasResolver resolver =
|
||||||
|
new AliasResolver(attributeSchemas, attributeAliases, defaultAttributeSchema);
|
||||||
|
this.attributeAliases = Collections.unmodifiableMap(resolver.resolveAliases());
|
||||||
|
this.attributeSchemas = Collections.unmodifiableMap(resolver.resolveSchemas());
|
||||||
this.defaultAttributeSchema = defaultAttributeSchema;
|
this.defaultAttributeSchema = defaultAttributeSchema;
|
||||||
this.attributeResync = attributeResync;
|
this.attributeResync = attributeResync;
|
||||||
}
|
}
|
||||||
|
@ -199,6 +261,11 @@ public class DefaultTargetObjectSchema
|
||||||
return attributeSchemas;
|
return attributeSchemas;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, String> getAttributeAliases() {
|
||||||
|
return attributeAliases;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public AttributeSchema getDefaultAttributeSchema() {
|
public AttributeSchema getDefaultAttributeSchema() {
|
||||||
return defaultAttributeSchema;
|
return defaultAttributeSchema;
|
||||||
|
@ -236,6 +303,7 @@ public class DefaultTargetObjectSchema
|
||||||
sb.append("attributes(resync " + attributeResync + ") = ");
|
sb.append("attributes(resync " + attributeResync + ") = ");
|
||||||
sb.append(attributeSchemas);
|
sb.append(attributeSchemas);
|
||||||
sb.append(" default " + defaultAttributeSchema);
|
sb.append(" default " + defaultAttributeSchema);
|
||||||
|
sb.append(" aliases " + attributeAliases);
|
||||||
sb.append("\n}");
|
sb.append("\n}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -255,10 +323,9 @@ public class DefaultTargetObjectSchema
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object obj) {
|
public boolean equals(Object obj) {
|
||||||
if (!(obj instanceof DefaultTargetObjectSchema)) {
|
if (!(obj instanceof DefaultTargetObjectSchema that)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
DefaultTargetObjectSchema that = (DefaultTargetObjectSchema) obj;
|
|
||||||
if (!Objects.equals(this.name, that.name)) {
|
if (!Objects.equals(this.name, that.name)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -283,6 +350,9 @@ public class DefaultTargetObjectSchema
|
||||||
if (!Objects.equals(this.attributeSchemas, that.attributeSchemas)) {
|
if (!Objects.equals(this.attributeSchemas, that.attributeSchemas)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (!Objects.equals(this.attributeAliases, that.attributeAliases)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
if (!Objects.equals(this.defaultAttributeSchema, that.defaultAttributeSchema)) {
|
if (!Objects.equals(this.defaultAttributeSchema, that.defaultAttributeSchema)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -191,6 +191,11 @@ public enum EnumerableTargetObjectSchema implements TargetObjectSchema {
|
||||||
return Map.of();
|
return Map.of();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, String> getAttributeAliases() {
|
||||||
|
return Map.of();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public AttributeSchema getDefaultAttributeSchema() {
|
public AttributeSchema getDefaultAttributeSchema() {
|
||||||
return AttributeSchema.DEFAULT_VOID;
|
return AttributeSchema.DEFAULT_VOID;
|
||||||
|
|
|
@ -33,6 +33,7 @@ public class SchemaBuilder {
|
||||||
private ResyncMode elementResync = TargetObjectSchema.DEFAULT_ELEMENT_RESYNC;
|
private ResyncMode elementResync = TargetObjectSchema.DEFAULT_ELEMENT_RESYNC;
|
||||||
|
|
||||||
private Map<String, AttributeSchema> attributeSchemas = new LinkedHashMap<>();
|
private Map<String, AttributeSchema> attributeSchemas = new LinkedHashMap<>();
|
||||||
|
private Map<String, String> attributeAliases = new LinkedHashMap<>();
|
||||||
private AttributeSchema defaultAttributeSchema = AttributeSchema.DEFAULT_ANY;
|
private AttributeSchema defaultAttributeSchema = AttributeSchema.DEFAULT_ANY;
|
||||||
private ResyncMode attributeResync = TargetObjectSchema.DEFAULT_ATTRIBUTE_RESYNC;
|
private ResyncMode attributeResync = TargetObjectSchema.DEFAULT_ATTRIBUTE_RESYNC;
|
||||||
|
|
||||||
|
@ -163,10 +164,10 @@ public class SchemaBuilder {
|
||||||
if (schema.getName().equals("")) {
|
if (schema.getName().equals("")) {
|
||||||
return setDefaultAttributeSchema(schema);
|
return setDefaultAttributeSchema(schema);
|
||||||
}
|
}
|
||||||
if (attributeSchemas.containsKey(schema.getName())) {
|
if (attributeOrigins.containsKey(schema.getName())) {
|
||||||
throw new IllegalArgumentException("Duplicate attribute name '" + schema.getName() +
|
throw new IllegalArgumentException(
|
||||||
"' origin1=" + attributeOrigins.get(schema.getName()) +
|
"Duplicate attribute name '%s' adding schema origin1=%s origin2=%s".formatted(
|
||||||
" origin2=" + origin);
|
schema.getName(), attributeOrigins.get(schema.getName()), origin));
|
||||||
}
|
}
|
||||||
attributeSchemas.put(schema.getName(), schema);
|
attributeSchemas.put(schema.getName(), schema);
|
||||||
attributeOrigins.put(schema.getName(), origin);
|
attributeOrigins.put(schema.getName(), origin);
|
||||||
|
@ -178,6 +179,7 @@ public class SchemaBuilder {
|
||||||
return setDefaultAttributeSchema(AttributeSchema.DEFAULT_ANY);
|
return setDefaultAttributeSchema(AttributeSchema.DEFAULT_ANY);
|
||||||
}
|
}
|
||||||
attributeSchemas.remove(name);
|
attributeSchemas.remove(name);
|
||||||
|
attributeAliases.remove(name);
|
||||||
attributeOrigins.remove(name);
|
attributeOrigins.remove(name);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
@ -194,11 +196,41 @@ public class SchemaBuilder {
|
||||||
if (schema.getName().equals("")) {
|
if (schema.getName().equals("")) {
|
||||||
return setDefaultAttributeSchema(schema);
|
return setDefaultAttributeSchema(schema);
|
||||||
}
|
}
|
||||||
|
attributeAliases.remove(schema.getName());
|
||||||
attributeSchemas.put(schema.getName(), schema);
|
attributeSchemas.put(schema.getName(), schema);
|
||||||
attributeOrigins.put(schema.getName(), origin);
|
attributeOrigins.put(schema.getName(), origin);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected void validateAlias(String from, String to) {
|
||||||
|
if (from.equals("")) {
|
||||||
|
throw new IllegalArgumentException("Key '' cannot be an alias");
|
||||||
|
}
|
||||||
|
if (to.equals("")) {
|
||||||
|
throw new IllegalArgumentException("Cannot alias to key '' (from %s)".formatted(from));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public SchemaBuilder addAttributeAlias(String from, String to, Object origin) {
|
||||||
|
validateAlias(from, to);
|
||||||
|
if (attributeOrigins.containsKey(from)) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"Duplicate attribute name '%s' adding alias origin1=%s origin2=%s".formatted(
|
||||||
|
from, attributeOrigins.get(from), origin));
|
||||||
|
}
|
||||||
|
attributeAliases.put(from, to);
|
||||||
|
attributeOrigins.put(from, origin);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SchemaBuilder replaceAttributeAlias(String from, String to, Object origin) {
|
||||||
|
validateAlias(from, to);
|
||||||
|
attributeSchemas.remove(from);
|
||||||
|
attributeAliases.put(from, to);
|
||||||
|
attributeOrigins.put(from, origin);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public SchemaBuilder setDefaultAttributeSchema(AttributeSchema defaultAttributeSchema) {
|
public SchemaBuilder setDefaultAttributeSchema(AttributeSchema defaultAttributeSchema) {
|
||||||
this.defaultAttributeSchema = defaultAttributeSchema;
|
this.defaultAttributeSchema = defaultAttributeSchema;
|
||||||
return this;
|
return this;
|
||||||
|
@ -227,6 +259,6 @@ public class SchemaBuilder {
|
||||||
return new DefaultTargetObjectSchema(
|
return new DefaultTargetObjectSchema(
|
||||||
context, name, type, interfaces, isCanonicalContainer,
|
context, name, type, interfaces, isCanonicalContainer,
|
||||||
elementSchemas, defaultElementSchema, elementResync,
|
elementSchemas, defaultElementSchema, elementResync,
|
||||||
attributeSchemas, defaultAttributeSchema, attributeResync);
|
attributeSchemas, attributeAliases, defaultAttributeSchema, attributeResync);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,7 @@ import java.util.concurrent.CompletableFuture;
|
||||||
import java.util.function.Predicate;
|
import java.util.function.Predicate;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import ghidra.dbg.DebuggerObjectModel.RefreshBehavior;
|
||||||
import ghidra.dbg.agent.DefaultTargetObject;
|
import ghidra.dbg.agent.DefaultTargetObject;
|
||||||
import ghidra.dbg.target.*;
|
import ghidra.dbg.target.*;
|
||||||
import ghidra.dbg.target.schema.DefaultTargetObjectSchema.DefaultAttributeSchema;
|
import ghidra.dbg.target.schema.DefaultTargetObjectSchema.DefaultAttributeSchema;
|
||||||
|
@ -42,6 +43,11 @@ import ghidra.util.Msg;
|
||||||
* by matching on the keys (indices and names), the result being a subordinate
|
* by matching on the keys (indices and names), the result being a subordinate
|
||||||
* {@link TargetObjectSchema}. Keys must match exactly, unless the "pattern" is the empty string,
|
* {@link TargetObjectSchema}. Keys must match exactly, unless the "pattern" is the empty string,
|
||||||
* which matches any key. Similarly, the wild-card index is {@code []}.
|
* which matches any key. Similarly, the wild-card index is {@code []}.
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* The schema can specify attribute aliases, which implies that a particular key ("from") will
|
||||||
|
* always have the same value as another ("to"). As a result, the schemas of aliased keys will also
|
||||||
|
* implicitly match.
|
||||||
*/
|
*/
|
||||||
public interface TargetObjectSchema {
|
public interface TargetObjectSchema {
|
||||||
public static final ResyncMode DEFAULT_ELEMENT_RESYNC = ResyncMode.NEVER;
|
public static final ResyncMode DEFAULT_ELEMENT_RESYNC = ResyncMode.NEVER;
|
||||||
|
@ -107,9 +113,9 @@ public interface TargetObjectSchema {
|
||||||
*
|
*
|
||||||
* <p>
|
* <p>
|
||||||
* Each object specifies a element sync mode, and an attribute sync mode. These describe when
|
* Each object specifies a element sync mode, and an attribute sync mode. These describe when
|
||||||
* the client must call {@link TargetObject#resync(boolean, boolean)} to refresh/resync to
|
* the client must call {@link TargetObject#resync(RefreshBehavior, RefreshBehavior)} to
|
||||||
* ensure it has a fresh cache of elements and/or attributes. Note that any client requesting a
|
* refresh/resync to ensure it has a fresh cache of elements and/or attributes. Note that any
|
||||||
* resync will cause all clients to receive the updates.
|
* client requesting a resync will cause all clients to receive the updates.
|
||||||
*/
|
*/
|
||||||
enum ResyncMode {
|
enum ResyncMode {
|
||||||
/**
|
/**
|
||||||
|
@ -332,10 +338,42 @@ public interface TargetObjectSchema {
|
||||||
/**
|
/**
|
||||||
* Get the map of attribute names to named schemas
|
* Get the map of attribute names to named schemas
|
||||||
*
|
*
|
||||||
|
* <p>
|
||||||
|
* The returned map will include aliases. To determine whether or not an attribute key is an
|
||||||
|
* alias, check whether the entry's key matches the name of the attribute (see
|
||||||
|
* {@link AttributeSchema#getName()}). It is possible the schema's name is empty, i.e., the
|
||||||
|
* default schema. This indicates an alias to a key that was not named in the schema. Use
|
||||||
|
* {@link #getAttributeAliases()} to determine the name of that key.
|
||||||
|
*
|
||||||
* @return the map
|
* @return the map
|
||||||
*/
|
*/
|
||||||
Map<String, AttributeSchema> getAttributeSchemas();
|
Map<String, AttributeSchema> getAttributeSchemas();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the map of attribute name aliases
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* The returned map must provide the <em>direct</em> alias names. For any given key, the client
|
||||||
|
* need only query the map once to determine the name of the attribute to which the alias
|
||||||
|
* refers. Consequently, the map also cannot indicate a cycle.
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* An aliased attribute takes the value of its target implicitly.
|
||||||
|
*
|
||||||
|
* @return the map
|
||||||
|
*/
|
||||||
|
Map<String, String> getAttributeAliases();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the given name is an alias and get the target attribute name
|
||||||
|
*
|
||||||
|
* @param name the name
|
||||||
|
* @return the alias' target, or the given name if not an alias
|
||||||
|
*/
|
||||||
|
default String checkAliasedAttribute(String name) {
|
||||||
|
return getAttributeAliases().getOrDefault(name, name);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the default schema for attributes
|
* Get the default schema for attributes
|
||||||
*
|
*
|
||||||
|
@ -355,8 +393,8 @@ public interface TargetObjectSchema {
|
||||||
* Get the attribute schema for a given attribute name
|
* Get the attribute schema for a given attribute name
|
||||||
*
|
*
|
||||||
* <p>
|
* <p>
|
||||||
* If there's a schema specified for the given name, that schema is taken. Otherwise, the
|
* If there's a schema specified for the given name, that schema is taken. If the name refers to
|
||||||
* default attribute schema is taken.
|
* an alias, its schema is taken. Otherwise, the default attribute schema is taken.
|
||||||
*
|
*
|
||||||
* @param name the name
|
* @param name the name
|
||||||
* @return the attribute schema
|
* @return the attribute schema
|
||||||
|
@ -824,7 +862,7 @@ public interface TargetObjectSchema {
|
||||||
*
|
*
|
||||||
* @param type
|
* @param type
|
||||||
* @param path
|
* @param path
|
||||||
* @return
|
* @return the predicates for finding objects
|
||||||
*/
|
*/
|
||||||
default PathPredicates matcherForSuitable(Class<? extends TargetObject> type,
|
default PathPredicates matcherForSuitable(Class<? extends TargetObject> type,
|
||||||
List<String> path) {
|
List<String> path) {
|
||||||
|
@ -947,7 +985,10 @@ public interface TargetObjectSchema {
|
||||||
* Verify that the given value is of this schema's required type and, if applicable, implements
|
* Verify that the given value is of this schema's required type and, if applicable, implements
|
||||||
* the required interfaces
|
* the required interfaces
|
||||||
*
|
*
|
||||||
* @param value the value
|
* @param value the value being assigned to the key
|
||||||
|
* @param parentPath the path of the object whose key is being assigned, for diagnostics
|
||||||
|
* @param key the key that is being assigned
|
||||||
|
* @param strict true to throw an exception upon violation; false to just log and continue
|
||||||
*/
|
*/
|
||||||
default void validateTypeAndInterfaces(Object value, List<String> parentPath, String key,
|
default void validateTypeAndInterfaces(Object value, List<String> parentPath, String key,
|
||||||
boolean strict) {
|
boolean strict) {
|
||||||
|
|
|
@ -29,7 +29,25 @@ import ghidra.util.Msg;
|
||||||
import ghidra.util.xml.XmlUtilities;
|
import ghidra.util.xml.XmlUtilities;
|
||||||
|
|
||||||
public class XmlSchemaContext extends DefaultSchemaContext {
|
public class XmlSchemaContext extends DefaultSchemaContext {
|
||||||
protected static final Set<String> TRUES = Set.of("true", "yes", "y", "1");
|
protected static final String ELEM_CONTEXT = "context";
|
||||||
|
protected static final String ATTR_CANONICAL = "canonical";
|
||||||
|
protected static final String ELEM_SCHEMA = "schema";
|
||||||
|
protected static final String ATTR_ELEMENT_RESYNC = "elementResync";
|
||||||
|
protected static final String ATTR_ATTRIBUTE_RESYNC = "attributeResync";
|
||||||
|
protected static final String ELEM_INTERFACE = "interface";
|
||||||
|
protected static final String ELEM_ELEMENT = "element";
|
||||||
|
protected static final String ATTR_INDEX = "index";
|
||||||
|
protected static final String ELEM_ATTRIBUTE = "attribute";
|
||||||
|
protected static final String ATTR_NAME = "name";
|
||||||
|
protected static final String ATTR_SCHEMA = "schema";
|
||||||
|
protected static final String ATTR_REQUIRED = "required";
|
||||||
|
protected static final String ATTR_FIXED = "fixed";
|
||||||
|
protected static final String ATTR_HIDDEN = "hidden";
|
||||||
|
protected static final String ELEM_ATTRIBUTE_ALIAS = "attribute-alias";
|
||||||
|
protected static final String ATTR_FROM = "from";
|
||||||
|
protected static final String ATTR_TO = "to";
|
||||||
|
protected static final String YES = "yes";
|
||||||
|
protected static final Set<String> TRUES = Set.of("true", YES, "y", "1");
|
||||||
|
|
||||||
protected static boolean parseBoolean(Element ele, String attrName) {
|
protected static boolean parseBoolean(Element ele, String attrName) {
|
||||||
return TRUES.contains(ele.getAttributeValue(attrName, "no").toLowerCase());
|
return TRUES.contains(ele.getAttributeValue(attrName, "no").toLowerCase());
|
||||||
|
@ -40,7 +58,7 @@ public class XmlSchemaContext extends DefaultSchemaContext {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Element contextToXml(SchemaContext ctx) {
|
public static Element contextToXml(SchemaContext ctx) {
|
||||||
Element result = new Element("context");
|
Element result = new Element(ELEM_CONTEXT);
|
||||||
for (TargetObjectSchema schema : ctx.getAllSchemas()) {
|
for (TargetObjectSchema schema : ctx.getAllSchemas()) {
|
||||||
Element schemaElem = schemaToXml(schema);
|
Element schemaElem = schemaToXml(schema);
|
||||||
if (schemaElem != null) {
|
if (schemaElem != null) {
|
||||||
|
@ -51,23 +69,30 @@ public class XmlSchemaContext extends DefaultSchemaContext {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Element attributeSchemaToXml(AttributeSchema as) {
|
public static Element attributeSchemaToXml(AttributeSchema as) {
|
||||||
Element attrElem = new Element("attribute");
|
Element attrElem = new Element(ELEM_ATTRIBUTE);
|
||||||
if (!as.getName().equals("")) {
|
if (!as.getName().equals("")) {
|
||||||
XmlUtilities.setStringAttr(attrElem, "name", as.getName());
|
XmlUtilities.setStringAttr(attrElem, ATTR_NAME, as.getName());
|
||||||
}
|
}
|
||||||
XmlUtilities.setStringAttr(attrElem, "schema", as.getSchema().toString());
|
XmlUtilities.setStringAttr(attrElem, ATTR_SCHEMA, as.getSchema().toString());
|
||||||
if (as.isRequired()) {
|
if (as.isRequired()) {
|
||||||
XmlUtilities.setStringAttr(attrElem, "required", "yes");
|
XmlUtilities.setStringAttr(attrElem, ATTR_REQUIRED, YES);
|
||||||
}
|
}
|
||||||
if (as.isFixed()) {
|
if (as.isFixed()) {
|
||||||
XmlUtilities.setStringAttr(attrElem, "fixed", "yes");
|
XmlUtilities.setStringAttr(attrElem, ATTR_FIXED, YES);
|
||||||
}
|
}
|
||||||
if (as.isHidden()) {
|
if (as.isHidden()) {
|
||||||
XmlUtilities.setStringAttr(attrElem, "hidden", "yes");
|
XmlUtilities.setStringAttr(attrElem, ATTR_HIDDEN, YES);
|
||||||
}
|
}
|
||||||
return attrElem;
|
return attrElem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Element aliasToXml(Map.Entry<String, String> alias) {
|
||||||
|
Element aliasElem = new Element(ELEM_ATTRIBUTE_ALIAS);
|
||||||
|
XmlUtilities.setStringAttr(aliasElem, ATTR_FROM, alias.getKey());
|
||||||
|
XmlUtilities.setStringAttr(aliasElem, ATTR_TO, alias.getValue());
|
||||||
|
return aliasElem;
|
||||||
|
}
|
||||||
|
|
||||||
public static Element schemaToXml(TargetObjectSchema schema) {
|
public static Element schemaToXml(TargetObjectSchema schema) {
|
||||||
if (!TargetObject.class.isAssignableFrom(schema.getType())) {
|
if (!TargetObject.class.isAssignableFrom(schema.getType())) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -76,33 +101,40 @@ public class XmlSchemaContext extends DefaultSchemaContext {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
Element result = new Element("schema");
|
Element result = new Element(ELEM_SCHEMA);
|
||||||
XmlUtilities.setStringAttr(result, "name", schema.getName().toString());
|
XmlUtilities.setStringAttr(result, ATTR_NAME, schema.getName().toString());
|
||||||
for (Class<? extends TargetObject> iface : schema.getInterfaces()) {
|
for (Class<? extends TargetObject> iface : schema.getInterfaces()) {
|
||||||
Element ifElem = new Element("interface");
|
Element ifElem = new Element(ELEM_INTERFACE);
|
||||||
XmlUtilities.setStringAttr(ifElem, "name", DebuggerObjectModel.requireIfaceName(iface));
|
XmlUtilities.setStringAttr(ifElem, ATTR_NAME,
|
||||||
|
DebuggerObjectModel.requireIfaceName(iface));
|
||||||
result.addContent(ifElem);
|
result.addContent(ifElem);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (schema.isCanonicalContainer()) {
|
if (schema.isCanonicalContainer()) {
|
||||||
XmlUtilities.setStringAttr(result, "canonical", "yes");
|
XmlUtilities.setStringAttr(result, ATTR_CANONICAL, YES);
|
||||||
}
|
}
|
||||||
XmlUtilities.setStringAttr(result, "elementResync",
|
XmlUtilities.setStringAttr(result, ATTR_ELEMENT_RESYNC,
|
||||||
schema.getElementResyncMode().name());
|
schema.getElementResyncMode().name());
|
||||||
XmlUtilities.setStringAttr(result, "attributeResync",
|
XmlUtilities.setStringAttr(result, ATTR_ATTRIBUTE_RESYNC,
|
||||||
schema.getAttributeResyncMode().name());
|
schema.getAttributeResyncMode().name());
|
||||||
|
|
||||||
for (Map.Entry<String, SchemaName> ent : schema.getElementSchemas().entrySet()) {
|
for (Map.Entry<String, SchemaName> ent : schema.getElementSchemas().entrySet()) {
|
||||||
Element elemElem = new Element("element");
|
Element elemElem = new Element(ELEM_ELEMENT);
|
||||||
XmlUtilities.setStringAttr(elemElem, "index", ent.getKey());
|
XmlUtilities.setStringAttr(elemElem, ATTR_INDEX, ent.getKey());
|
||||||
XmlUtilities.setStringAttr(elemElem, "schema", ent.getValue().toString());
|
XmlUtilities.setStringAttr(elemElem, ATTR_SCHEMA, ent.getValue().toString());
|
||||||
result.addContent(elemElem);
|
result.addContent(elemElem);
|
||||||
}
|
}
|
||||||
Element deElem = new Element("element");
|
Element deElem = new Element(ELEM_ELEMENT);
|
||||||
XmlUtilities.setStringAttr(deElem, "schema", schema.getDefaultElementSchema().toString());
|
XmlUtilities.setStringAttr(deElem, ATTR_SCHEMA,
|
||||||
|
schema.getDefaultElementSchema().toString());
|
||||||
result.addContent(deElem);
|
result.addContent(deElem);
|
||||||
|
|
||||||
for (AttributeSchema as : schema.getAttributeSchemas().values()) {
|
for (Map.Entry<String, AttributeSchema> ent : schema.getAttributeSchemas().entrySet()) {
|
||||||
|
AttributeSchema as = ent.getValue();
|
||||||
|
if (!ent.getKey().equals(as.getName())) {
|
||||||
|
// Exclude aliases here
|
||||||
|
continue;
|
||||||
|
}
|
||||||
Element attrElem = attributeSchemaToXml(as);
|
Element attrElem = attributeSchemaToXml(as);
|
||||||
result.addContent(attrElem);
|
result.addContent(attrElem);
|
||||||
}
|
}
|
||||||
|
@ -110,6 +142,12 @@ public class XmlSchemaContext extends DefaultSchemaContext {
|
||||||
Element daElem = attributeSchemaToXml(das);
|
Element daElem = attributeSchemaToXml(das);
|
||||||
result.addContent(daElem);
|
result.addContent(daElem);
|
||||||
|
|
||||||
|
// Yes, these will be the "resolved" aliases, but I think that's okay.
|
||||||
|
for (Map.Entry<String, String> alias : schema.getAttributeAliases().entrySet()) {
|
||||||
|
Element aliasElem = aliasToXml(alias);
|
||||||
|
result.addContent(aliasElem);
|
||||||
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -138,7 +176,7 @@ public class XmlSchemaContext extends DefaultSchemaContext {
|
||||||
|
|
||||||
public static XmlSchemaContext contextFromXml(Element contextElem) {
|
public static XmlSchemaContext contextFromXml(Element contextElem) {
|
||||||
XmlSchemaContext ctx = new XmlSchemaContext();
|
XmlSchemaContext ctx = new XmlSchemaContext();
|
||||||
for (Element schemaElem : XmlUtilities.getChildren(contextElem, "schema")) {
|
for (Element schemaElem : XmlUtilities.getChildren(contextElem, ELEM_SCHEMA)) {
|
||||||
ctx.schemaFromXml(schemaElem);
|
ctx.schemaFromXml(schemaElem);
|
||||||
}
|
}
|
||||||
return ctx;
|
return ctx;
|
||||||
|
@ -153,16 +191,17 @@ public class XmlSchemaContext extends DefaultSchemaContext {
|
||||||
private String requireAttributeValue(Element elem, String name) {
|
private String requireAttributeValue(Element elem, String name) {
|
||||||
String value = elem.getAttributeValue(name);
|
String value = elem.getAttributeValue(name);
|
||||||
if (value == null) {
|
if (value == null) {
|
||||||
throw new IllegalArgumentException("Missing attribute " + name + " in " + elem);
|
throw new IllegalArgumentException(
|
||||||
|
"Missing attribute '%s' in %s".formatted(name, elem));
|
||||||
}
|
}
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public TargetObjectSchema schemaFromXml(Element schemaElem) {
|
public TargetObjectSchema schemaFromXml(Element schemaElem) {
|
||||||
SchemaBuilder builder = builder(name(schemaElem.getAttributeValue("name", "")));
|
SchemaBuilder builder = builder(name(schemaElem.getAttributeValue(ATTR_NAME, "")));
|
||||||
|
|
||||||
for (Element ifaceElem : XmlUtilities.getChildren(schemaElem, "interface")) {
|
for (Element ifaceElem : XmlUtilities.getChildren(schemaElem, ELEM_INTERFACE)) {
|
||||||
String ifaceName = requireAttributeValue(ifaceElem, "name");
|
String ifaceName = requireAttributeValue(ifaceElem, ATTR_NAME);
|
||||||
Class<? extends TargetObject> iface = TargetObject.INTERFACES_BY_NAME.get(ifaceName);
|
Class<? extends TargetObject> iface = TargetObject.INTERFACES_BY_NAME.get(ifaceName);
|
||||||
if (iface == null) {
|
if (iface == null) {
|
||||||
Msg.warn(this, "Unknown interface name: '" + ifaceName + "'");
|
Msg.warn(this, "Unknown interface name: '" + ifaceName + "'");
|
||||||
|
@ -172,29 +211,35 @@ public class XmlSchemaContext extends DefaultSchemaContext {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
builder.setCanonicalContainer(parseBoolean(schemaElem, "canonical"));
|
builder.setCanonicalContainer(parseBoolean(schemaElem, ATTR_CANONICAL));
|
||||||
builder.setElementResyncMode(
|
builder.setElementResyncMode(
|
||||||
ResyncMode.valueOf(requireAttributeValue(schemaElem, "elementResync")));
|
ResyncMode.valueOf(requireAttributeValue(schemaElem, ATTR_ELEMENT_RESYNC)));
|
||||||
builder.setAttributeResyncMode(
|
builder.setAttributeResyncMode(
|
||||||
ResyncMode.valueOf(requireAttributeValue(schemaElem, "attributeResync")));
|
ResyncMode.valueOf(requireAttributeValue(schemaElem, ATTR_ATTRIBUTE_RESYNC)));
|
||||||
|
|
||||||
for (Element elemElem : XmlUtilities.getChildren(schemaElem, "element")) {
|
for (Element elemElem : XmlUtilities.getChildren(schemaElem, ELEM_ELEMENT)) {
|
||||||
SchemaName schema = name(requireAttributeValue(elemElem, "schema"));
|
SchemaName schema = name(requireAttributeValue(elemElem, ATTR_SCHEMA));
|
||||||
String index = elemElem.getAttributeValue("index", "");
|
String index = elemElem.getAttributeValue(ATTR_INDEX, "");
|
||||||
builder.addElementSchema(index, schema, elemElem);
|
builder.addElementSchema(index, schema, elemElem);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (Element attrElem : XmlUtilities.getChildren(schemaElem, "attribute")) {
|
for (Element attrElem : XmlUtilities.getChildren(schemaElem, ELEM_ATTRIBUTE)) {
|
||||||
SchemaName schema = name(requireAttributeValue(attrElem, "schema"));
|
SchemaName schema = name(requireAttributeValue(attrElem, ATTR_SCHEMA));
|
||||||
boolean required = parseBoolean(attrElem, "required");
|
boolean required = parseBoolean(attrElem, ATTR_REQUIRED);
|
||||||
boolean fixed = parseBoolean(attrElem, "fixed");
|
boolean fixed = parseBoolean(attrElem, ATTR_FIXED);
|
||||||
boolean hidden = parseBoolean(attrElem, "hidden");
|
boolean hidden = parseBoolean(attrElem, ATTR_HIDDEN);
|
||||||
|
|
||||||
String name = attrElem.getAttributeValue("name", "");
|
String name = attrElem.getAttributeValue(ATTR_NAME, "");
|
||||||
builder.addAttributeSchema(
|
builder.addAttributeSchema(
|
||||||
new DefaultAttributeSchema(name, schema, required, fixed, hidden), attrElem);
|
new DefaultAttributeSchema(name, schema, required, fixed, hidden), attrElem);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (Element aliasElem : XmlUtilities.getChildren(schemaElem, ELEM_ATTRIBUTE_ALIAS)) {
|
||||||
|
String from = requireAttributeValue(aliasElem, ATTR_FROM);
|
||||||
|
String to = requireAttributeValue(aliasElem, ATTR_TO);
|
||||||
|
builder.addAttributeAlias(from, to, aliasElem);
|
||||||
|
}
|
||||||
|
|
||||||
return builder.buildAndAdd();
|
return builder.buildAndAdd();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,22 +28,25 @@ import ghidra.dbg.target.schema.DefaultTargetObjectSchema.DefaultAttributeSchema
|
||||||
import ghidra.dbg.target.schema.TargetObjectSchema.*;
|
import ghidra.dbg.target.schema.TargetObjectSchema.*;
|
||||||
|
|
||||||
public class XmlTargetObjectSchemaTest {
|
public class XmlTargetObjectSchemaTest {
|
||||||
protected static final String SCHEMA_XML = "" +
|
protected static final String SCHEMA_XML =
|
||||||
"<context>\n" +
|
// Do not line-wrap or serialize test will fail
|
||||||
" <schema name=\"root\" canonical=\"yes\" elementResync=\"NEVER\" attributeResync=\"ONCE\">\n" +
|
"""
|
||||||
" <interface name=\"Process\" />\n" +
|
<context>
|
||||||
" <interface name=\"Interpreter\" />\n" +
|
<schema name="root" canonical="yes" elementResync="NEVER" attributeResync="ONCE">
|
||||||
" <element index=\"reserved\" schema=\"VOID\" />\n" +
|
<interface name="Process" />
|
||||||
" <element schema=\"down1\" />\n" +
|
<interface name="Interpreter" />
|
||||||
" <attribute name=\"some_int\" schema=\"INT\" />\n" +
|
<element index="reserved" schema="VOID" />
|
||||||
" <attribute name=\"some_object\" schema=\"OBJECT\" required=\"yes\" fixed=\"yes\" hidden=\"yes\" />\n" +
|
<element schema="down1" />
|
||||||
" <attribute schema=\"ANY\" hidden=\"yes\" />\n" +
|
<attribute name="some_int" schema="INT" />
|
||||||
" </schema>\n" +
|
<attribute name="some_object" schema="OBJECT" required="yes" fixed="yes" hidden="yes" />
|
||||||
" <schema name=\"down1\" elementResync=\"ALWAYS\" attributeResync=\"ALWAYS\">\n" +
|
<attribute schema="ANY" hidden="yes" />
|
||||||
" <element schema=\"OBJECT\" />\n" +
|
<attribute-alias from="_int" to="some_int" />
|
||||||
" <attribute schema=\"VOID\" fixed=\"yes\" hidden=\"yes\" />\n" +
|
</schema>
|
||||||
" </schema>\n" +
|
<schema name="down1" elementResync="ALWAYS" attributeResync="ALWAYS">
|
||||||
"</context>";
|
<element schema="OBJECT" />
|
||||||
|
<attribute schema="VOID" fixed="yes" hidden="yes" />
|
||||||
|
</schema>
|
||||||
|
</context>"""; // Cannot have final final new-line or serialize test will fail
|
||||||
|
|
||||||
protected static final DefaultSchemaContext CTX = new DefaultSchemaContext();
|
protected static final DefaultSchemaContext CTX = new DefaultSchemaContext();
|
||||||
protected static final SchemaName NAME_ROOT = new SchemaName("root");
|
protected static final SchemaName NAME_ROOT = new SchemaName("root");
|
||||||
|
@ -59,6 +62,7 @@ public class XmlTargetObjectSchemaTest {
|
||||||
EnumerableTargetObjectSchema.INT.getName(), false, false, false), null)
|
EnumerableTargetObjectSchema.INT.getName(), false, false, false), null)
|
||||||
.addAttributeSchema(new DefaultAttributeSchema("some_object",
|
.addAttributeSchema(new DefaultAttributeSchema("some_object",
|
||||||
EnumerableTargetObjectSchema.OBJECT.getName(), true, true, true), null)
|
EnumerableTargetObjectSchema.OBJECT.getName(), true, true, true), null)
|
||||||
|
.addAttributeAlias("_int", "some_int", null)
|
||||||
.setAttributeResyncMode(ResyncMode.ONCE)
|
.setAttributeResyncMode(ResyncMode.ONCE)
|
||||||
.buildAndAdd();
|
.buildAndAdd();
|
||||||
protected static final TargetObjectSchema SCHEMA_DOWN1 = CTX.builder(NAME_DOWN1)
|
protected static final TargetObjectSchema SCHEMA_DOWN1 = CTX.builder(NAME_DOWN1)
|
||||||
|
|
|
@ -40,9 +40,22 @@ import ghidra.util.exception.DuplicateNameException;
|
||||||
public class DBTraceObjectBreakpointLocation
|
public class DBTraceObjectBreakpointLocation
|
||||||
implements TraceObjectBreakpointLocation, DBTraceObjectInterface {
|
implements TraceObjectBreakpointLocation, DBTraceObjectInterface {
|
||||||
|
|
||||||
protected class BreakpointChangeTranslator extends Translator<TraceBreakpoint> {
|
protected static class BreakpointChangeTranslator extends Translator<TraceBreakpoint> {
|
||||||
|
private static final Map<TargetObjectSchema, Set<String>> KEYS_BY_SCHEMA =
|
||||||
|
new WeakHashMap<>();
|
||||||
|
|
||||||
|
private final Set<String> keys;
|
||||||
|
|
||||||
protected BreakpointChangeTranslator(DBTraceObject object, TraceBreakpoint iface) {
|
protected BreakpointChangeTranslator(DBTraceObject object, TraceBreakpoint iface) {
|
||||||
super(TargetBreakpointLocation.RANGE_ATTRIBUTE_NAME, object, iface);
|
super(TargetBreakpointLocation.RANGE_ATTRIBUTE_NAME, object, iface);
|
||||||
|
TargetObjectSchema schema = object.getTargetSchema();
|
||||||
|
synchronized (KEYS_BY_SCHEMA) {
|
||||||
|
keys = KEYS_BY_SCHEMA.computeIfAbsent(schema, s -> Set.of(
|
||||||
|
schema.checkAliasedAttribute(TargetBreakpointLocation.RANGE_ATTRIBUTE_NAME),
|
||||||
|
schema.checkAliasedAttribute(TargetObject.DISPLAY_ATTRIBUTE_NAME),
|
||||||
|
schema.checkAliasedAttribute(TargetTogglable.ENABLED_ATTRIBUTE_NAME),
|
||||||
|
schema.checkAliasedAttribute(KEY_COMMENT)));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -62,10 +75,7 @@ public class DBTraceObjectBreakpointLocation
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean appliesToKey(String key) {
|
protected boolean appliesToKey(String key) {
|
||||||
return TargetBreakpointLocation.RANGE_ATTRIBUTE_NAME.equals(key) ||
|
return keys.contains(key);
|
||||||
TargetObject.DISPLAY_ATTRIBUTE_NAME.equals(key) ||
|
|
||||||
TargetBreakpointSpec.ENABLED_ATTRIBUTE_NAME.equals(key) ||
|
|
||||||
KEY_COMMENT.equals(key);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -15,12 +15,11 @@
|
||||||
*/
|
*/
|
||||||
package ghidra.trace.database.breakpoint;
|
package ghidra.trace.database.breakpoint;
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.*;
|
||||||
import java.util.Set;
|
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import ghidra.dbg.target.TargetBreakpointSpec;
|
import ghidra.dbg.target.*;
|
||||||
import ghidra.dbg.target.TargetObject;
|
import ghidra.dbg.target.schema.TargetObjectSchema;
|
||||||
import ghidra.program.model.address.Address;
|
import ghidra.program.model.address.Address;
|
||||||
import ghidra.program.model.address.AddressRange;
|
import ghidra.program.model.address.AddressRange;
|
||||||
import ghidra.trace.database.target.DBTraceObject;
|
import ghidra.trace.database.target.DBTraceObject;
|
||||||
|
@ -44,12 +43,21 @@ import ghidra.util.exception.DuplicateNameException;
|
||||||
|
|
||||||
public class DBTraceObjectBreakpointSpec
|
public class DBTraceObjectBreakpointSpec
|
||||||
implements TraceObjectBreakpointSpec, DBTraceObjectInterface {
|
implements TraceObjectBreakpointSpec, DBTraceObjectInterface {
|
||||||
|
private static final Map<TargetObjectSchema, Set<String>> KEYS_BY_SCHEMA = new WeakHashMap<>();
|
||||||
|
|
||||||
private final DBTraceObject object;
|
private final DBTraceObject object;
|
||||||
|
private final Set<String> keys;
|
||||||
|
|
||||||
private TraceBreakpointKindSet kinds = TraceBreakpointKindSet.of();
|
private TraceBreakpointKindSet kinds = TraceBreakpointKindSet.of();
|
||||||
|
|
||||||
public DBTraceObjectBreakpointSpec(DBTraceObject object) {
|
public DBTraceObjectBreakpointSpec(DBTraceObject object) {
|
||||||
this.object = object;
|
this.object = object;
|
||||||
|
TargetObjectSchema schema = object.getTargetSchema();
|
||||||
|
synchronized (KEYS_BY_SCHEMA) {
|
||||||
|
keys = KEYS_BY_SCHEMA.computeIfAbsent(schema, s -> Set.of(
|
||||||
|
schema.checkAliasedAttribute(TargetBreakpointSpec.KINDS_ATTRIBUTE_NAME),
|
||||||
|
schema.checkAliasedAttribute(TargetTogglable.ENABLED_ATTRIBUTE_NAME)));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -238,8 +246,7 @@ public class DBTraceObjectBreakpointSpec
|
||||||
TraceObjectChangeType.VALUE_CREATED.cast(rec);
|
TraceObjectChangeType.VALUE_CREATED.cast(rec);
|
||||||
TraceObjectValue affected = cast.getAffectedObject();
|
TraceObjectValue affected = cast.getAffectedObject();
|
||||||
String key = affected.getEntryKey();
|
String key = affected.getEntryKey();
|
||||||
boolean applies = TargetBreakpointSpec.KINDS_ATTRIBUTE_NAME.equals(key) ||
|
boolean applies = keys.contains(key);
|
||||||
TargetBreakpointSpec.ENABLED_ATTRIBUTE_NAME.equals(key);
|
|
||||||
if (!applies) {
|
if (!applies) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,7 @@ import java.util.*;
|
||||||
|
|
||||||
import ghidra.dbg.target.TargetMemoryRegion;
|
import ghidra.dbg.target.TargetMemoryRegion;
|
||||||
import ghidra.dbg.target.TargetObject;
|
import ghidra.dbg.target.TargetObject;
|
||||||
|
import ghidra.dbg.target.schema.TargetObjectSchema;
|
||||||
import ghidra.program.model.address.*;
|
import ghidra.program.model.address.*;
|
||||||
import ghidra.trace.database.DBTrace;
|
import ghidra.trace.database.DBTrace;
|
||||||
import ghidra.trace.database.DBTraceUtils;
|
import ghidra.trace.database.DBTraceUtils;
|
||||||
|
@ -38,9 +39,46 @@ import ghidra.util.exception.DuplicateNameException;
|
||||||
|
|
||||||
public class DBTraceObjectMemoryRegion implements TraceObjectMemoryRegion, DBTraceObjectInterface {
|
public class DBTraceObjectMemoryRegion implements TraceObjectMemoryRegion, DBTraceObjectInterface {
|
||||||
|
|
||||||
|
protected record Keys(Set<String> all, String range, String display,
|
||||||
|
Set<String> flags) {
|
||||||
|
static Keys fromSchema(TargetObjectSchema schema) {
|
||||||
|
String keyRange = schema.checkAliasedAttribute(TargetMemoryRegion.RANGE_ATTRIBUTE_NAME);
|
||||||
|
String keyDisplay = schema.checkAliasedAttribute(TargetObject.DISPLAY_ATTRIBUTE_NAME);
|
||||||
|
String keyReadable =
|
||||||
|
schema.checkAliasedAttribute(TargetMemoryRegion.READABLE_ATTRIBUTE_NAME);
|
||||||
|
String keyWritable =
|
||||||
|
schema.checkAliasedAttribute(TargetMemoryRegion.WRITABLE_ATTRIBUTE_NAME);
|
||||||
|
String keyExecutable =
|
||||||
|
schema.checkAliasedAttribute(TargetMemoryRegion.EXECUTABLE_ATTRIBUTE_NAME);
|
||||||
|
return new Keys(Set.of(keyRange, keyDisplay, keyReadable, keyWritable, keyExecutable),
|
||||||
|
keyRange, keyDisplay, Set.of(keyReadable, keyWritable, keyExecutable));
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isRange(String key) {
|
||||||
|
return range.equals(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isDisplay(String key) {
|
||||||
|
return display.equals(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isFlag(String key) {
|
||||||
|
return flags.contains(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected class RegionChangeTranslator extends Translator<TraceMemoryRegion> {
|
protected class RegionChangeTranslator extends Translator<TraceMemoryRegion> {
|
||||||
|
private static final Map<TargetObjectSchema, Keys> KEYS_BY_SCHEMA =
|
||||||
|
new WeakHashMap<>();
|
||||||
|
|
||||||
|
private final Keys keys;
|
||||||
|
|
||||||
protected RegionChangeTranslator(DBTraceObject object, TraceMemoryRegion iface) {
|
protected RegionChangeTranslator(DBTraceObject object, TraceMemoryRegion iface) {
|
||||||
super(TargetMemoryRegion.RANGE_ATTRIBUTE_NAME, object, iface);
|
super(TargetMemoryRegion.RANGE_ATTRIBUTE_NAME, object, iface);
|
||||||
|
TargetObjectSchema schema = object.getTargetSchema();
|
||||||
|
synchronized (KEYS_BY_SCHEMA) {
|
||||||
|
keys = KEYS_BY_SCHEMA.computeIfAbsent(schema, Keys::fromSchema);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -60,11 +98,7 @@ public class DBTraceObjectMemoryRegion implements TraceObjectMemoryRegion, DBTra
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean appliesToKey(String key) {
|
protected boolean appliesToKey(String key) {
|
||||||
return TargetMemoryRegion.RANGE_ATTRIBUTE_NAME.equals(key) ||
|
return keys.all.contains(key);
|
||||||
TargetObject.DISPLAY_ATTRIBUTE_NAME.equals(key) ||
|
|
||||||
TargetMemoryRegion.READABLE_ATTRIBUTE_NAME.equals(key) ||
|
|
||||||
TargetMemoryRegion.WRITABLE_ATTRIBUTE_NAME.equals(key) ||
|
|
||||||
TargetMemoryRegion.EXECUTABLE_ATTRIBUTE_NAME.equals(key);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -375,19 +409,15 @@ public class DBTraceObjectMemoryRegion implements TraceObjectMemoryRegion, DBTra
|
||||||
protected void updateViewsValueChanged(Lifespan lifespan, String key, Object oldValue,
|
protected void updateViewsValueChanged(Lifespan lifespan, String key, Object oldValue,
|
||||||
Object newValue) {
|
Object newValue) {
|
||||||
DBTrace trace = object.getTrace();
|
DBTrace trace = object.getTrace();
|
||||||
switch (key) {
|
if (translator.keys.isRange(key)) {
|
||||||
case TargetMemoryRegion.RANGE_ATTRIBUTE_NAME:
|
// NB. old/newValue are null here. The CREATED event just has the new entry.
|
||||||
// NB. old/newValue are null here. The CREATED event just has the new entry.
|
trace.updateViewsRefreshBlocks();
|
||||||
trace.updateViewsRefreshBlocks();
|
}
|
||||||
return;
|
else if (translator.keys.isDisplay(key)) {
|
||||||
case TargetObject.DISPLAY_ATTRIBUTE_NAME:
|
trace.updateViewsChangeRegionBlockName(this);
|
||||||
trace.updateViewsChangeRegionBlockName(this);
|
}
|
||||||
return;
|
else if (translator.keys.isFlag(key)) {
|
||||||
case TargetMemoryRegion.READABLE_ATTRIBUTE_NAME:
|
trace.updateViewsChangeRegionBlockFlags(this, lifespan);
|
||||||
case TargetMemoryRegion.WRITABLE_ATTRIBUTE_NAME:
|
|
||||||
case TargetMemoryRegion.EXECUTABLE_ATTRIBUTE_NAME:
|
|
||||||
trace.updateViewsChangeRegionBlockFlags(this, lifespan);
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -15,11 +15,11 @@
|
||||||
*/
|
*/
|
||||||
package ghidra.trace.database.module;
|
package ghidra.trace.database.module;
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.*;
|
||||||
import java.util.List;
|
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import ghidra.dbg.target.*;
|
import ghidra.dbg.target.*;
|
||||||
|
import ghidra.dbg.target.schema.TargetObjectSchema;
|
||||||
import ghidra.dbg.util.PathMatcher;
|
import ghidra.dbg.util.PathMatcher;
|
||||||
import ghidra.dbg.util.PathPredicates.Align;
|
import ghidra.dbg.util.PathPredicates.Align;
|
||||||
import ghidra.dbg.util.PathUtils;
|
import ghidra.dbg.util.PathUtils;
|
||||||
|
@ -40,8 +40,19 @@ import ghidra.util.exception.DuplicateNameException;
|
||||||
public class DBTraceObjectModule implements TraceObjectModule, DBTraceObjectInterface {
|
public class DBTraceObjectModule implements TraceObjectModule, DBTraceObjectInterface {
|
||||||
|
|
||||||
protected class ModuleChangeTranslator extends Translator<TraceModule> {
|
protected class ModuleChangeTranslator extends Translator<TraceModule> {
|
||||||
|
private static final Map<TargetObjectSchema, Set<String>> KEYS_BY_SCHEMA =
|
||||||
|
new WeakHashMap<>();
|
||||||
|
|
||||||
|
private final Set<String> keys;
|
||||||
|
|
||||||
protected ModuleChangeTranslator(DBTraceObject object, TraceModule iface) {
|
protected ModuleChangeTranslator(DBTraceObject object, TraceModule iface) {
|
||||||
super(TargetModule.RANGE_ATTRIBUTE_NAME, object, iface);
|
super(TargetModule.RANGE_ATTRIBUTE_NAME, object, iface);
|
||||||
|
TargetObjectSchema schema = object.getTargetSchema();
|
||||||
|
synchronized (KEYS_BY_SCHEMA) {
|
||||||
|
keys = KEYS_BY_SCHEMA.computeIfAbsent(schema, s -> Set.of(
|
||||||
|
s.checkAliasedAttribute(TargetModule.RANGE_ATTRIBUTE_NAME),
|
||||||
|
s.checkAliasedAttribute(TargetObject.DISPLAY_ATTRIBUTE_NAME)));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -61,8 +72,7 @@ public class DBTraceObjectModule implements TraceObjectModule, DBTraceObjectInte
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean appliesToKey(String key) {
|
protected boolean appliesToKey(String key) {
|
||||||
return TargetModule.RANGE_ATTRIBUTE_NAME.equals(key) ||
|
return keys.contains(key);
|
||||||
TargetObject.DISPLAY_ATTRIBUTE_NAME.equals(key);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -15,7 +15,10 @@
|
||||||
*/
|
*/
|
||||||
package ghidra.trace.database.module;
|
package ghidra.trace.database.module;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
import ghidra.dbg.target.*;
|
import ghidra.dbg.target.*;
|
||||||
|
import ghidra.dbg.target.schema.TargetObjectSchema;
|
||||||
import ghidra.dbg.util.PathUtils;
|
import ghidra.dbg.util.PathUtils;
|
||||||
import ghidra.program.model.address.AddressRange;
|
import ghidra.program.model.address.AddressRange;
|
||||||
import ghidra.trace.database.target.DBTraceObject;
|
import ghidra.trace.database.target.DBTraceObject;
|
||||||
|
@ -34,8 +37,19 @@ import ghidra.util.LockHold;
|
||||||
public class DBTraceObjectSection implements TraceObjectSection, DBTraceObjectInterface {
|
public class DBTraceObjectSection implements TraceObjectSection, DBTraceObjectInterface {
|
||||||
|
|
||||||
protected class SectionTranslator extends Translator<TraceSection> {
|
protected class SectionTranslator extends Translator<TraceSection> {
|
||||||
|
private static final Map<TargetObjectSchema, Set<String>> KEYS_BY_SCHEMA =
|
||||||
|
new WeakHashMap<>();
|
||||||
|
|
||||||
|
private final Set<String> keys;
|
||||||
|
|
||||||
protected SectionTranslator(DBTraceObject object, TraceSection iface) {
|
protected SectionTranslator(DBTraceObject object, TraceSection iface) {
|
||||||
super(TargetSection.RANGE_ATTRIBUTE_NAME, object, iface);
|
super(TargetSection.RANGE_ATTRIBUTE_NAME, object, iface);
|
||||||
|
TargetObjectSchema schema = object.getTargetSchema();
|
||||||
|
synchronized (KEYS_BY_SCHEMA) {
|
||||||
|
keys = KEYS_BY_SCHEMA.computeIfAbsent(schema, s -> Set.of(
|
||||||
|
s.checkAliasedAttribute(TargetSection.RANGE_ATTRIBUTE_NAME),
|
||||||
|
s.checkAliasedAttribute(TargetObject.DISPLAY_ATTRIBUTE_NAME)));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -55,8 +69,7 @@ public class DBTraceObjectSection implements TraceObjectSection, DBTraceObjectIn
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean appliesToKey(String key) {
|
protected boolean appliesToKey(String key) {
|
||||||
return TargetSection.RANGE_ATTRIBUTE_NAME.equals(key) ||
|
return keys.contains(key);
|
||||||
TargetObject.DISPLAY_ATTRIBUTE_NAME.equals(key);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -15,9 +15,10 @@
|
||||||
*/
|
*/
|
||||||
package ghidra.trace.database.stack;
|
package ghidra.trace.database.stack;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.*;
|
||||||
|
|
||||||
import ghidra.dbg.target.TargetStackFrame;
|
import ghidra.dbg.target.*;
|
||||||
|
import ghidra.dbg.target.schema.TargetObjectSchema;
|
||||||
import ghidra.dbg.util.PathUtils;
|
import ghidra.dbg.util.PathUtils;
|
||||||
import ghidra.program.model.address.Address;
|
import ghidra.program.model.address.Address;
|
||||||
import ghidra.program.model.listing.CodeUnit;
|
import ghidra.program.model.listing.CodeUnit;
|
||||||
|
@ -37,13 +38,23 @@ import ghidra.trace.util.TraceChangeRecord;
|
||||||
import ghidra.util.LockHold;
|
import ghidra.util.LockHold;
|
||||||
|
|
||||||
public class DBTraceObjectStackFrame implements TraceObjectStackFrame, DBTraceObjectInterface {
|
public class DBTraceObjectStackFrame implements TraceObjectStackFrame, DBTraceObjectInterface {
|
||||||
|
private static final Map<TargetObjectSchema, Set<String>> KEYS_BY_SCHEMA = new WeakHashMap<>();
|
||||||
|
|
||||||
private final DBTraceObject object;
|
private final DBTraceObject object;
|
||||||
|
private final Set<String> keys;
|
||||||
|
|
||||||
// TODO: Memorizing life is not optimal.
|
// TODO: Memorizing life is not optimal.
|
||||||
// GP-1887 means to expose multiple lifespans in, e.g., TraceThread
|
// GP-1887 means to expose multiple lifespans in, e.g., TraceThread
|
||||||
private LifeSet life = new DefaultLifeSet();
|
private LifeSet life = new DefaultLifeSet();
|
||||||
|
|
||||||
public DBTraceObjectStackFrame(DBTraceObject object) {
|
public DBTraceObjectStackFrame(DBTraceObject object) {
|
||||||
this.object = object;
|
this.object = object;
|
||||||
|
|
||||||
|
TargetObjectSchema schema = object.getTargetSchema();
|
||||||
|
synchronized (KEYS_BY_SCHEMA) {
|
||||||
|
keys = KEYS_BY_SCHEMA.computeIfAbsent(schema, s -> Set.of(
|
||||||
|
schema.checkAliasedAttribute(TargetStackFrame.PC_ATTRIBUTE_NAME)));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -136,7 +147,7 @@ public class DBTraceObjectStackFrame implements TraceObjectStackFrame, DBTraceOb
|
||||||
TraceObjectChangeType.VALUE_CREATED.cast(rec);
|
TraceObjectChangeType.VALUE_CREATED.cast(rec);
|
||||||
TraceObjectValue affected = cast.getAffectedObject();
|
TraceObjectValue affected = cast.getAffectedObject();
|
||||||
assert affected.getParent() == object;
|
assert affected.getParent() == object;
|
||||||
if (!TargetStackFrame.PC_ATTRIBUTE_NAME.equals(affected.getEntryKey())) {
|
if (!keys.contains(affected.getEntryKey())) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (object.getCanonicalParent(affected.getMaxSnap()) == null) {
|
if (object.getCanonicalParent(affected.getMaxSnap()) == null) {
|
||||||
|
|
|
@ -137,6 +137,7 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject {
|
||||||
|
|
||||||
protected final DBTraceObjectManager manager;
|
protected final DBTraceObjectManager manager;
|
||||||
|
|
||||||
|
private TargetObjectSchema targetSchema;
|
||||||
private Map<Class<? extends TraceObjectInterface>, TraceObjectInterface> ifaces;
|
private Map<Class<? extends TraceObjectInterface>, TraceObjectInterface> ifaces;
|
||||||
|
|
||||||
private final Map<String, InternalTraceObjectValue> valueCache = new LinkedHashMap<>() {
|
private final Map<String, InternalTraceObjectValue> valueCache = new LinkedHashMap<>() {
|
||||||
|
@ -461,29 +462,31 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject {
|
||||||
@Override
|
@Override
|
||||||
public Collection<? extends InternalTraceObjectValue> getValues(Lifespan span, String key) {
|
public Collection<? extends InternalTraceObjectValue> getValues(Lifespan span, String key) {
|
||||||
try (LockHold hold = manager.trace.lockRead()) {
|
try (LockHold hold = manager.trace.lockRead()) {
|
||||||
return doGetValues(span, key, true);
|
String k = getTargetSchema().checkAliasedAttribute(key);
|
||||||
|
return doGetValues(span, k, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public InternalTraceObjectValue getValue(long snap, String key) {
|
public InternalTraceObjectValue getValue(long snap, String key) {
|
||||||
try (LockHold hold = manager.trace.lockRead()) {
|
try (LockHold hold = manager.trace.lockRead()) {
|
||||||
InternalTraceObjectValue cached = valueCache.get(key);
|
String k = getTargetSchema().checkAliasedAttribute(key);
|
||||||
|
InternalTraceObjectValue cached = valueCache.get(k);
|
||||||
if (cached != null && !cached.isDeleted() && cached.getLifespan().contains(snap)) {
|
if (cached != null && !cached.isDeleted() && cached.getLifespan().contains(snap)) {
|
||||||
return cached;
|
return cached;
|
||||||
}
|
}
|
||||||
Long nullSnap = nullCache.get(key);
|
Long nullSnap = nullCache.get(k);
|
||||||
if (nullSnap != null && nullSnap.longValue() == snap) {
|
if (nullSnap != null && nullSnap.longValue() == snap) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
InternalTraceObjectValue found = manager.valueMap
|
InternalTraceObjectValue found = manager.valueMap
|
||||||
.reduce(TraceObjectValueQuery.values(this, key, key, Lifespan.at(snap)))
|
.reduce(TraceObjectValueQuery.values(this, k, k, Lifespan.at(snap)))
|
||||||
.firstValue();
|
.firstValue();
|
||||||
if (found == null) {
|
if (found == null) {
|
||||||
nullCache.put(key, snap);
|
nullCache.put(k, snap);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
valueCache.put(key, found);
|
valueCache.put(k, found);
|
||||||
}
|
}
|
||||||
return found;
|
return found;
|
||||||
}
|
}
|
||||||
|
@ -493,7 +496,8 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject {
|
||||||
public Stream<? extends InternalTraceObjectValue> getOrderedValues(Lifespan span, String key,
|
public Stream<? extends InternalTraceObjectValue> getOrderedValues(Lifespan span, String key,
|
||||||
boolean forward) {
|
boolean forward) {
|
||||||
try (LockHold hold = manager.trace.lockRead()) {
|
try (LockHold hold = manager.trace.lockRead()) {
|
||||||
return doGetValues(span, key, forward).stream();
|
String k = getTargetSchema().checkAliasedAttribute(key);
|
||||||
|
return doGetValues(span, k, forward).stream();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -599,11 +603,12 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject {
|
||||||
if (isDeleted()) {
|
if (isDeleted()) {
|
||||||
throw new IllegalStateException("Cannot set value on deleted object.");
|
throw new IllegalStateException("Cannot set value on deleted object.");
|
||||||
}
|
}
|
||||||
|
String k = getTargetSchema().checkAliasedAttribute(key);
|
||||||
if (resolution == ConflictResolution.DENY) {
|
if (resolution == ConflictResolution.DENY) {
|
||||||
doCheckConflicts(lifespan, key, value);
|
doCheckConflicts(lifespan, k, value);
|
||||||
}
|
}
|
||||||
else if (resolution == ConflictResolution.ADJUST) {
|
else if (resolution == ConflictResolution.ADJUST) {
|
||||||
lifespan = doAdjust(lifespan, key, value);
|
lifespan = doAdjust(lifespan, k, value);
|
||||||
}
|
}
|
||||||
var setter = new ValueLifespanSetter(lifespan, value) {
|
var setter = new ValueLifespanSetter(lifespan, value) {
|
||||||
DBTraceObject canonicalLifeChanged = null;
|
DBTraceObject canonicalLifeChanged = null;
|
||||||
|
@ -612,7 +617,7 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject {
|
||||||
protected Iterable<InternalTraceObjectValue> getIntersecting(Long lower,
|
protected Iterable<InternalTraceObjectValue> getIntersecting(Long lower,
|
||||||
Long upper) {
|
Long upper) {
|
||||||
return Collections.unmodifiableCollection(
|
return Collections.unmodifiableCollection(
|
||||||
doGetValues(Lifespan.span(lower, upper), key, true));
|
doGetValues(Lifespan.span(lower, upper), k, true));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -634,7 +639,7 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected InternalTraceObjectValue create(Lifespan range, Object value) {
|
protected InternalTraceObjectValue create(Lifespan range, Object value) {
|
||||||
return doCreateValue(range, key, value);
|
return doCreateValue(range, k, value);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
InternalTraceObjectValue result = setter.set(lifespan, value);
|
InternalTraceObjectValue result = setter.set(lifespan, value);
|
||||||
|
@ -673,7 +678,11 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public TargetObjectSchema getTargetSchema() {
|
public TargetObjectSchema getTargetSchema() {
|
||||||
return manager.rootSchema.getSuccessorSchema(path.getKeyList());
|
// NOTE: No need to synchronize. Schema is immutable.
|
||||||
|
if (targetSchema == null) {
|
||||||
|
targetSchema = manager.rootSchema.getSuccessorSchema(path.getKeyList());
|
||||||
|
}
|
||||||
|
return targetSchema;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
*/
|
*/
|
||||||
package ghidra.trace.database.target;
|
package ghidra.trace.database.target;
|
||||||
|
|
||||||
|
import ghidra.dbg.target.schema.TargetObjectSchema;
|
||||||
import ghidra.program.model.address.Address;
|
import ghidra.program.model.address.Address;
|
||||||
import ghidra.program.model.address.AddressRange;
|
import ghidra.program.model.address.AddressRange;
|
||||||
import ghidra.trace.database.space.DBTraceSpaceKey.DefaultDBTraceSpaceKey;
|
import ghidra.trace.database.space.DBTraceSpaceKey.DefaultDBTraceSpaceKey;
|
||||||
|
@ -38,7 +39,13 @@ public interface DBTraceObjectInterface extends TraceObjectInterface, TraceUniqu
|
||||||
private LifeSet life = new DefaultLifeSet();
|
private LifeSet life = new DefaultLifeSet();
|
||||||
|
|
||||||
public Translator(String spaceValueKey, DBTraceObject object, T iface) {
|
public Translator(String spaceValueKey, DBTraceObject object, T iface) {
|
||||||
this.spaceValueKey = spaceValueKey;
|
if (spaceValueKey == null) {
|
||||||
|
this.spaceValueKey = null;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
TargetObjectSchema schema = object.getTargetSchema();
|
||||||
|
this.spaceValueKey = schema.checkAliasedAttribute(spaceValueKey);
|
||||||
|
}
|
||||||
this.object = object;
|
this.object = object;
|
||||||
this.iface = iface;
|
this.iface = iface;
|
||||||
}
|
}
|
||||||
|
|
|
@ -174,6 +174,8 @@ public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager
|
||||||
return size() > OBJECTS_CONTAINING_CACHE_SIZE;
|
return size() > OBJECTS_CONTAINING_CACHE_SIZE;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
protected final Map<Class<? extends TraceObjectInterface>, Set<TargetObjectSchema>> //
|
||||||
|
schemasByInterface = new HashMap<>();
|
||||||
|
|
||||||
public DBTraceObjectManager(DBHandle dbh, DBOpenMode openMode, ReadWriteLock lock,
|
public DBTraceObjectManager(DBHandle dbh, DBOpenMode openMode, ReadWriteLock lock,
|
||||||
TaskMonitor monitor, Language baseLanguage, DBTrace trace)
|
TaskMonitor monitor, Language baseLanguage, DBTrace trace)
|
||||||
|
@ -221,6 +223,9 @@ public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager
|
||||||
valueTree.invalidateCache();
|
valueTree.invalidateCache();
|
||||||
schemaStore.invalidateCache();
|
schemaStore.invalidateCache();
|
||||||
loadRootSchema();
|
loadRootSchema();
|
||||||
|
objectsContainingCache.clear();
|
||||||
|
// Though rare, the root schema could change
|
||||||
|
schemasByInterface.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Internal
|
@Internal
|
||||||
|
@ -449,6 +454,8 @@ public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager
|
||||||
objectStore.deleteAll();
|
objectStore.deleteAll();
|
||||||
schemaStore.deleteAll();
|
schemaStore.deleteAll();
|
||||||
rootSchema = null;
|
rootSchema = null;
|
||||||
|
objectsContainingCache.clear();
|
||||||
|
schemasByInterface.clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -515,13 +522,6 @@ public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected <I extends TraceObjectInterface> Stream<I> doParentsHaving(
|
|
||||||
Stream<? extends TraceObjectValue> values, Class<I> iface) {
|
|
||||||
return values.map(v -> v.getParent())
|
|
||||||
.map(o -> o.queryInterface(iface))
|
|
||||||
.filter(i -> i != null);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void invalidateObjectsContainingCache() {
|
protected void invalidateObjectsContainingCache() {
|
||||||
synchronized (objectsContainingCache) {
|
synchronized (objectsContainingCache) {
|
||||||
objectsContainingCache.clear();
|
objectsContainingCache.clear();
|
||||||
|
@ -530,8 +530,8 @@ public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager
|
||||||
|
|
||||||
protected Collection<? extends TraceObjectInterface> doGetObjectsContaining(
|
protected Collection<? extends TraceObjectInterface> doGetObjectsContaining(
|
||||||
ObjectsContainingKey key) {
|
ObjectsContainingKey key) {
|
||||||
return doParentsHaving(getValuesAt(key.snap, key.address, key.key).stream(), key.iface)
|
return getObjectsIntersecting(Lifespan.at(key.snap),
|
||||||
.collect(Collectors.toSet());
|
new AddressRangeImpl(key.address, key.address), key.key, key.iface);
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
|
@ -555,11 +555,36 @@ public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager
|
||||||
return col.iterator().next();
|
return col.iterator().next();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected Set<TargetObjectSchema> collectSchemasForInterface(
|
||||||
|
Class<? extends TraceObjectInterface> iface) {
|
||||||
|
if (rootSchema == null) {
|
||||||
|
return Set.of();
|
||||||
|
}
|
||||||
|
Class<? extends TargetObject> targetIf = TraceObjectInterfaceUtils.toTargetIf(iface);
|
||||||
|
Set<TargetObjectSchema> result = new HashSet<>();
|
||||||
|
for (TargetObjectSchema schema : rootSchema.getContext().getAllSchemas()) {
|
||||||
|
if (schema.getInterfaces().contains(targetIf)) {
|
||||||
|
result.add(schema);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Set.copyOf(result);
|
||||||
|
}
|
||||||
|
|
||||||
public <I extends TraceObjectInterface> Collection<I> getObjectsIntersecting(
|
public <I extends TraceObjectInterface> Collection<I> getObjectsIntersecting(
|
||||||
Lifespan lifespan, AddressRange range, String key, Class<I> iface) {
|
Lifespan lifespan, AddressRange range, String key, Class<I> iface) {
|
||||||
try (LockHold hold = trace.lockRead()) {
|
try (LockHold hold = trace.lockRead()) {
|
||||||
return doParentsHaving(getValuesIntersecting(lifespan, range, key).stream(), iface)
|
Set<TargetObjectSchema> schemas;
|
||||||
.collect(Collectors.toSet());
|
synchronized (schemasByInterface) {
|
||||||
|
schemas =
|
||||||
|
schemasByInterface.computeIfAbsent(iface, this::collectSchemasForInterface);
|
||||||
|
}
|
||||||
|
Map<String, List<TargetObjectSchema>> schemasByAliasTo =
|
||||||
|
schemas.stream().collect(Collectors.groupingBy(s -> s.checkAliasedAttribute(key)));
|
||||||
|
return schemasByAliasTo.entrySet().stream().flatMap(ent -> {
|
||||||
|
return getValuesIntersecting(lifespan, range, ent.getKey()).stream()
|
||||||
|
.map(v -> v.getParent())
|
||||||
|
.filter(o -> ent.getValue().contains(o.getTargetSchema()));
|
||||||
|
}).map(o -> o.queryInterface(iface)).collect(Collectors.toSet());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -573,7 +598,7 @@ public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager
|
||||||
public <I extends TraceObjectInterface> AddressSetView getObjectsAddressSet(long snap,
|
public <I extends TraceObjectInterface> AddressSetView getObjectsAddressSet(long snap,
|
||||||
String key, Class<I> ifaceCls, Predicate<? super I> predicate) {
|
String key, Class<I> ifaceCls, Predicate<? super I> predicate) {
|
||||||
return valueMap.getAddressSetView(Lifespan.at(snap), v -> {
|
return valueMap.getAddressSetView(Lifespan.at(snap), v -> {
|
||||||
if (!key.equals(v.getEntryKey())) {
|
if (!v.hasEntryKey(key)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
TraceObject parent = v.getParent();
|
TraceObject parent = v.getParent();
|
||||||
|
|
|
@ -15,7 +15,10 @@
|
||||||
*/
|
*/
|
||||||
package ghidra.trace.database.thread;
|
package ghidra.trace.database.thread;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
import ghidra.dbg.target.TargetObject;
|
import ghidra.dbg.target.TargetObject;
|
||||||
|
import ghidra.dbg.target.schema.TargetObjectSchema;
|
||||||
import ghidra.trace.database.target.DBTraceObject;
|
import ghidra.trace.database.target.DBTraceObject;
|
||||||
import ghidra.trace.database.target.DBTraceObjectInterface;
|
import ghidra.trace.database.target.DBTraceObjectInterface;
|
||||||
import ghidra.trace.model.Lifespan;
|
import ghidra.trace.model.Lifespan;
|
||||||
|
@ -32,8 +35,19 @@ import ghidra.util.exception.DuplicateNameException;
|
||||||
public class DBTraceObjectThread implements TraceObjectThread, DBTraceObjectInterface {
|
public class DBTraceObjectThread implements TraceObjectThread, DBTraceObjectInterface {
|
||||||
|
|
||||||
protected class ThreadChangeTranslator extends Translator<TraceThread> {
|
protected class ThreadChangeTranslator extends Translator<TraceThread> {
|
||||||
|
private static final Map<TargetObjectSchema, Set<String>> KEYS_BY_SCHEMA =
|
||||||
|
new WeakHashMap<>();
|
||||||
|
|
||||||
|
private final Set<String> keys;
|
||||||
|
|
||||||
protected ThreadChangeTranslator(DBTraceObject object, TraceThread iface) {
|
protected ThreadChangeTranslator(DBTraceObject object, TraceThread iface) {
|
||||||
super(null, object, iface);
|
super(null, object, iface);
|
||||||
|
TargetObjectSchema schema = object.getTargetSchema();
|
||||||
|
synchronized (KEYS_BY_SCHEMA) {
|
||||||
|
keys = KEYS_BY_SCHEMA.computeIfAbsent(schema, s -> Set.of(
|
||||||
|
s.checkAliasedAttribute(KEY_COMMENT),
|
||||||
|
s.checkAliasedAttribute(TargetObject.DISPLAY_ATTRIBUTE_NAME)));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -53,8 +67,7 @@ public class DBTraceObjectThread implements TraceObjectThread, DBTraceObjectInte
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean appliesToKey(String key) {
|
protected boolean appliesToKey(String key) {
|
||||||
return KEY_COMMENT.equals(key) ||
|
return keys.contains(key);
|
||||||
TargetObject.DISPLAY_ATTRIBUTE_NAME.equals(key);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -86,7 +86,7 @@ public interface TraceObject extends TraceUniqueObject {
|
||||||
* contain the given lifespan. Only the canonical path is considered when looking for existing
|
* contain the given lifespan. Only the canonical path is considered when looking for existing
|
||||||
* ancestry.
|
* ancestry.
|
||||||
*
|
*
|
||||||
* @param the minimum lifespan of edges from the root to this object
|
* @param lifespan the minimum lifespan of edges from the root to this object
|
||||||
* @param resolution the rule for handling duplicate keys when setting values.
|
* @param resolution the rule for handling duplicate keys when setting values.
|
||||||
* @return the value path from root to the newly inserted object
|
* @return the value path from root to the newly inserted object
|
||||||
*/
|
*/
|
||||||
|
@ -155,6 +155,9 @@ public interface TraceObject extends TraceUniqueObject {
|
||||||
/**
|
/**
|
||||||
* Get all paths actually leading to this object, from the root, within the given span
|
* Get all paths actually leading to this object, from the root, within the given span
|
||||||
*
|
*
|
||||||
|
* <p>
|
||||||
|
* Aliased keys are excluded.
|
||||||
|
*
|
||||||
* @param span the span which every value entry on each path must intersect
|
* @param span the span which every value entry on each path must intersect
|
||||||
* @return the paths
|
* @return the paths
|
||||||
*/
|
*/
|
||||||
|
@ -204,6 +207,9 @@ public interface TraceObject extends TraceUniqueObject {
|
||||||
/**
|
/**
|
||||||
* Get all values intersecting the given span and whose child is this object
|
* Get all values intersecting the given span and whose child is this object
|
||||||
*
|
*
|
||||||
|
* <p>
|
||||||
|
* Aliased keys are excluded.
|
||||||
|
*
|
||||||
* @param span the span
|
* @param span the span
|
||||||
* @return the parent values
|
* @return the parent values
|
||||||
*/
|
*/
|
||||||
|
@ -212,6 +218,10 @@ public interface TraceObject extends TraceUniqueObject {
|
||||||
/**
|
/**
|
||||||
* Get all values (elements and attributes) of this object intersecting the given span
|
* Get all values (elements and attributes) of this object intersecting the given span
|
||||||
*
|
*
|
||||||
|
* <p>
|
||||||
|
* Aliased keys are excluded.
|
||||||
|
*
|
||||||
|
* @param span the span
|
||||||
* @return the values
|
* @return the values
|
||||||
*/
|
*/
|
||||||
Collection<? extends TraceObjectValue> getValues(Lifespan span);
|
Collection<? extends TraceObjectValue> getValues(Lifespan span);
|
||||||
|
@ -219,6 +229,9 @@ public interface TraceObject extends TraceUniqueObject {
|
||||||
/**
|
/**
|
||||||
* Get values with the given key intersecting the given span
|
* Get values with the given key intersecting the given span
|
||||||
*
|
*
|
||||||
|
* <p>
|
||||||
|
* If the key is an alias, the target key's values are retrieved instead.
|
||||||
|
*
|
||||||
* @param span the span
|
* @param span the span
|
||||||
* @param key the key
|
* @param key the key
|
||||||
* @return the collection of values
|
* @return the collection of values
|
||||||
|
@ -228,6 +241,9 @@ public interface TraceObject extends TraceUniqueObject {
|
||||||
/**
|
/**
|
||||||
* Get values with the given key intersecting the given span ordered by time
|
* Get values with the given key intersecting the given span ordered by time
|
||||||
*
|
*
|
||||||
|
* <p>
|
||||||
|
* If the key is an alias, the target key's values are retrieved instead.
|
||||||
|
*
|
||||||
* @param span the span
|
* @param span the span
|
||||||
* @param key the key
|
* @param key the key
|
||||||
* @param forward true to order from least- to most-recent, false for most- to least-recent
|
* @param forward true to order from least- to most-recent, false for most- to least-recent
|
||||||
|
@ -239,6 +255,7 @@ public interface TraceObject extends TraceUniqueObject {
|
||||||
/**
|
/**
|
||||||
* Get all elements of this object intersecting the given span
|
* Get all elements of this object intersecting the given span
|
||||||
*
|
*
|
||||||
|
* @param span the span
|
||||||
* @return the element values
|
* @return the element values
|
||||||
*/
|
*/
|
||||||
Collection<? extends TraceObjectValue> getElements(Lifespan span);
|
Collection<? extends TraceObjectValue> getElements(Lifespan span);
|
||||||
|
@ -246,6 +263,10 @@ public interface TraceObject extends TraceUniqueObject {
|
||||||
/**
|
/**
|
||||||
* Get all attributes of this object intersecting the given span
|
* Get all attributes of this object intersecting the given span
|
||||||
*
|
*
|
||||||
|
* <p>
|
||||||
|
* Aliased keys are excluded.
|
||||||
|
*
|
||||||
|
* @param span the span
|
||||||
* @return the attribute values
|
* @return the attribute values
|
||||||
*/
|
*/
|
||||||
Collection<? extends TraceObjectValue> getAttributes(Lifespan span);
|
Collection<? extends TraceObjectValue> getAttributes(Lifespan span);
|
||||||
|
@ -253,6 +274,9 @@ public interface TraceObject extends TraceUniqueObject {
|
||||||
/**
|
/**
|
||||||
* Get the value for the given snap and key
|
* Get the value for the given snap and key
|
||||||
*
|
*
|
||||||
|
* <p>
|
||||||
|
* If the key is an alias, the target key's value is retrieved instead.
|
||||||
|
*
|
||||||
* @param snap the snap
|
* @param snap the snap
|
||||||
* @param key the key
|
* @param key the key
|
||||||
* @return the value entry
|
* @return the value entry
|
||||||
|
@ -302,6 +326,10 @@ public interface TraceObject extends TraceUniqueObject {
|
||||||
* Stream all ancestor values of this object matching the given predicates, intersecting the
|
* Stream all ancestor values of this object matching the given predicates, intersecting the
|
||||||
* given span
|
* given span
|
||||||
*
|
*
|
||||||
|
* <p>
|
||||||
|
* Aliased keys are excluded. The predicates should be formulated to use the aliases' target
|
||||||
|
* attributes.
|
||||||
|
*
|
||||||
* @param span a span which values along the path must intersect
|
* @param span a span which values along the path must intersect
|
||||||
* @param rootPredicates the predicates for matching path keys, relative to the root
|
* @param rootPredicates the predicates for matching path keys, relative to the root
|
||||||
* @return the stream of matching paths to values
|
* @return the stream of matching paths to values
|
||||||
|
@ -313,6 +341,10 @@ public interface TraceObject extends TraceUniqueObject {
|
||||||
* Stream all ancestor values of this object matching the given predicates, intersecting the
|
* Stream all ancestor values of this object matching the given predicates, intersecting the
|
||||||
* given span
|
* given span
|
||||||
*
|
*
|
||||||
|
* <p>
|
||||||
|
* Aliased keys are excluded. The predicates should be formulated to use the aliases' target
|
||||||
|
* attributes.
|
||||||
|
*
|
||||||
* @param span a span which values along the path must intersect
|
* @param span a span which values along the path must intersect
|
||||||
* @param relativePredicates the predicates for matching path keys, relative to this object
|
* @param relativePredicates the predicates for matching path keys, relative to this object
|
||||||
* @return the stream of matching paths to values
|
* @return the stream of matching paths to values
|
||||||
|
@ -324,6 +356,10 @@ public interface TraceObject extends TraceUniqueObject {
|
||||||
* Stream all successor values of this object matching the given predicates, intersecting the
|
* Stream all successor values of this object matching the given predicates, intersecting the
|
||||||
* given span
|
* given span
|
||||||
*
|
*
|
||||||
|
* <p>
|
||||||
|
* Aliased keys are excluded. The predicates should be formulated to use the aliases' target
|
||||||
|
* attributes.
|
||||||
|
*
|
||||||
* @param span a span which values along the path must intersect
|
* @param span a span which values along the path must intersect
|
||||||
* @param relativePredicates the predicates for matching path keys, relative to this object
|
* @param relativePredicates the predicates for matching path keys, relative to this object
|
||||||
* @return the stream of matching paths to values
|
* @return the stream of matching paths to values
|
||||||
|
@ -335,6 +371,10 @@ public interface TraceObject extends TraceUniqueObject {
|
||||||
* Stream all successor values of this object at the given relative path, intersecting the given
|
* Stream all successor values of this object at the given relative path, intersecting the given
|
||||||
* span, ordered by time.
|
* span, ordered by time.
|
||||||
*
|
*
|
||||||
|
* <p>
|
||||||
|
* Aliased keys are excluded. The predicates should be formulated to use the aliases' target
|
||||||
|
* attributes.
|
||||||
|
*
|
||||||
* @param span the span which values along the path must intersect
|
* @param span the span which values along the path must intersect
|
||||||
* @param relativePath the path relative to this object
|
* @param relativePath the path relative to this object
|
||||||
* @param forward true to order from least- to most-recent, false for most- to least-recent
|
* @param forward true to order from least- to most-recent, false for most- to least-recent
|
||||||
|
@ -348,9 +388,11 @@ public interface TraceObject extends TraceUniqueObject {
|
||||||
*
|
*
|
||||||
* <p>
|
* <p>
|
||||||
* If an object has a disjoint life, i.e., multiple canonical parents, then only the
|
* If an object has a disjoint life, i.e., multiple canonical parents, then only the
|
||||||
* least-recent of those is traversed.
|
* least-recent of those is traversed. Aliased keys are excluded; those can't be canonical
|
||||||
|
* anyway.
|
||||||
*
|
*
|
||||||
* @param relativePath the path relative to this object
|
* @param relativePredicates predicates on the relative path from this object to desired
|
||||||
|
* successors
|
||||||
* @return the stream of value paths
|
* @return the stream of value paths
|
||||||
*/
|
*/
|
||||||
Stream<? extends TraceObjectValPath> getCanonicalSuccessors(PathPredicates relativePredicates);
|
Stream<? extends TraceObjectValPath> getCanonicalSuccessors(PathPredicates relativePredicates);
|
||||||
|
@ -358,6 +400,9 @@ public interface TraceObject extends TraceUniqueObject {
|
||||||
/**
|
/**
|
||||||
* Set a value for the given lifespan
|
* Set a value for the given lifespan
|
||||||
*
|
*
|
||||||
|
* <p>
|
||||||
|
* If the key is an alias, the target key's value is set instead.
|
||||||
|
*
|
||||||
* @param lifespan the lifespan of the value
|
* @param lifespan the lifespan of the value
|
||||||
* @param key the key to set
|
* @param key the key to set
|
||||||
* @param value the new value
|
* @param value the new value
|
||||||
|
@ -374,7 +419,8 @@ public interface TraceObject extends TraceUniqueObject {
|
||||||
* <p>
|
* <p>
|
||||||
* Setting a value of {@code null} effectively deletes the value for the given lifespan and
|
* Setting a value of {@code null} effectively deletes the value for the given lifespan and
|
||||||
* returns {@code null}. Values of the same key intersecting the given lifespan or either
|
* returns {@code null}. Values of the same key intersecting the given lifespan or either
|
||||||
* truncated or deleted.
|
* truncated or deleted. If the key is an alias, the target key's value is set instead.
|
||||||
|
*
|
||||||
*
|
*
|
||||||
* @param lifespan the lifespan of the value
|
* @param lifespan the lifespan of the value
|
||||||
* @param key the key to set
|
* @param key the key to set
|
||||||
|
@ -453,7 +499,7 @@ public interface TraceObject extends TraceUniqueObject {
|
||||||
* Search for ancestors on the canonical path having the given target interface
|
* Search for ancestors on the canonical path having the given target interface
|
||||||
*
|
*
|
||||||
* <p>
|
* <p>
|
||||||
* The object may not yet be inserted at its canonical path
|
* The object may not yet be inserted at its canonical path.
|
||||||
*
|
*
|
||||||
* @param targetIf the interface class
|
* @param targetIf the interface class
|
||||||
* @return the stream of objects
|
* @return the stream of objects
|
||||||
|
@ -465,7 +511,7 @@ public interface TraceObject extends TraceUniqueObject {
|
||||||
* Search for ancestors on the canonical path providing the given interface
|
* Search for ancestors on the canonical path providing the given interface
|
||||||
*
|
*
|
||||||
* <p>
|
* <p>
|
||||||
* The object may not yet be inserted at its canonical path
|
* The object may not yet be inserted at its canonical path.
|
||||||
*
|
*
|
||||||
* @param <I> the interface type
|
* @param <I> the interface type
|
||||||
* @param ifClass the interface class
|
* @param ifClass the interface class
|
||||||
|
|
|
@ -43,6 +43,20 @@ public interface TraceObjectValue {
|
||||||
*/
|
*/
|
||||||
String getEntryKey();
|
String getEntryKey();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the given key (or alias) matches this entry's key
|
||||||
|
*
|
||||||
|
* @param keyOrAlias the key or alias
|
||||||
|
* @return true if the key matches this entry's key, or it is an alias for it
|
||||||
|
*/
|
||||||
|
default boolean hasEntryKey(String keyOrAlias) {
|
||||||
|
TraceObject parent = getParent();
|
||||||
|
if (parent == null) {
|
||||||
|
return getEntryKey().equals(keyOrAlias);
|
||||||
|
}
|
||||||
|
return getEntryKey().equals(parent.getTargetSchema().checkAliasedAttribute(keyOrAlias));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the "canonical path" of this value
|
* Get the "canonical path" of this value
|
||||||
*
|
*
|
||||||
|
@ -50,7 +64,7 @@ public interface TraceObjectValue {
|
||||||
* This is the parent's canonical path extended by this value's entry key. Note, in the case
|
* 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.
|
* this value has a child object, this is not necessarily its canonical path.
|
||||||
*
|
*
|
||||||
* @return
|
* @return the canonical path
|
||||||
*/
|
*/
|
||||||
TraceObjectKeyPath getCanonicalPath();
|
TraceObjectKeyPath getCanonicalPath();
|
||||||
|
|
||||||
|
@ -127,7 +141,7 @@ public interface TraceObjectValue {
|
||||||
* uniquely determined at a given snap. Thus, when lifespans are being adjusted, such conflicts
|
* uniquely determined at a given snap. Thus, when lifespans are being adjusted, such conflicts
|
||||||
* must be resolved.
|
* must be resolved.
|
||||||
*
|
*
|
||||||
* @param lifespan the new lifespan
|
* @param span the new lifespan
|
||||||
* @param resolution specifies how to resolve duplicate keys with intersecting lifespans
|
* @param resolution specifies how to resolve duplicate keys with intersecting lifespans
|
||||||
* @throws DuplicateKeyException if there are denied duplicate keys
|
* @throws DuplicateKeyException if there are denied duplicate keys
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -88,6 +88,7 @@ public class DBTraceObjectManagerTest extends AbstractGhidraHeadlessIntegrationT
|
||||||
</schema>
|
</schema>
|
||||||
<schema name='Region' elementResync='NEVER' attributeResync='NEVER'>
|
<schema name='Region' elementResync='NEVER' attributeResync='NEVER'>
|
||||||
<interface name='MemoryRegion' />
|
<interface name='MemoryRegion' />
|
||||||
|
<attribute-alias from="_range" to="Range" />
|
||||||
</schema>
|
</schema>
|
||||||
</context>
|
</context>
|
||||||
""";
|
""";
|
||||||
|
@ -1175,4 +1176,30 @@ public class DBTraceObjectManagerTest extends AbstractGhidraHeadlessIntegrationT
|
||||||
|
|
||||||
b.trace.getObjectManager().getValuesIntersecting(Lifespan.ALL, b.range(0, -1));
|
b.trace.getObjectManager().getValuesIntersecting(Lifespan.ALL, b.range(0, -1));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAttributeAliasing() {
|
||||||
|
TraceObject regionText;
|
||||||
|
try (Transaction tx = b.startTransaction()) {
|
||||||
|
TraceObjectValue rootVal =
|
||||||
|
manager.createRootObject(ctx.getSchema(new SchemaName("Session")));
|
||||||
|
root = rootVal.getChild();
|
||||||
|
|
||||||
|
regionText =
|
||||||
|
manager.createObject(TraceObjectKeyPath.parse("Targets[0].Memory[bin:.text]"));
|
||||||
|
regionText.insert(Lifespan.nowOn(0), ConflictResolution.DENY);
|
||||||
|
regionText.setAttribute(Lifespan.nowOn(0), "_range", b.range(0x00400000, 0x00401000));
|
||||||
|
regionText.setAttribute(Lifespan.nowOn(0), "Range", b.range(0x00400000, 0x00402000));
|
||||||
|
}
|
||||||
|
|
||||||
|
assertEquals(ctx.getSchema(new SchemaName("Region")), regionText.getTargetSchema());
|
||||||
|
assertEquals(Set.of(
|
||||||
|
regionText.getAttribute(0, "Range")),
|
||||||
|
Set.copyOf(regionText.getAttributes(Lifespan.ALL)));
|
||||||
|
assertEquals(b.range(0x00400000, 0x00402000),
|
||||||
|
regionText.getAttribute(0, "_range").getValue());
|
||||||
|
assertEquals(b.range(0x00400000, 0x00402000),
|
||||||
|
regionText.getAttribute(0, "Range").getValue());
|
||||||
|
assertEquals("Range", regionText.getAttribute(0, "_range").getEntryKey());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,7 +49,8 @@ public class AbstractGhidraHeadedDebuggerIntegrationTest
|
||||||
<attribute name='Threads' schema='ThreadContainer' />
|
<attribute name='Threads' schema='ThreadContainer' />
|
||||||
<attribute name='Memory' schema='Memory' />
|
<attribute name='Memory' schema='Memory' />
|
||||||
<attribute name='Breakpoints' schema='BreakpointContainer' />
|
<attribute name='Breakpoints' schema='BreakpointContainer' />
|
||||||
<attribute name='_state' schema='EXECUTION_STATE' hidden='yes' />
|
<attribute name='State' schema='EXECUTION_STATE' />
|
||||||
|
<attribute-alias from="_state" to="State" />
|
||||||
</schema>
|
</schema>
|
||||||
<schema name='ThreadContainer' canonical='yes' elementResync='NEVER'
|
<schema name='ThreadContainer' canonical='yes' elementResync='NEVER'
|
||||||
attributeResync='ONCE'>
|
attributeResync='ONCE'>
|
||||||
|
@ -86,10 +87,14 @@ public class AbstractGhidraHeadedDebuggerIntegrationTest
|
||||||
</schema>
|
</schema>
|
||||||
<schema name='MemoryRegion' elementResync='NEVER' attributeResync='ONCE'>
|
<schema name='MemoryRegion' elementResync='NEVER' attributeResync='ONCE'>
|
||||||
<interface name='MemoryRegion' />
|
<interface name='MemoryRegion' />
|
||||||
<attribute name='_range' schema='RANGE' hidden='yes' />
|
<attribute name='Range' schema='RANGE' />
|
||||||
<attribute name='_readable' schema='BOOL' hidden='yes' />
|
<attribute-alias from='_range' to='Range' />
|
||||||
<attribute name='_writable' schema='BOOL' hidden='yes' />
|
<attribute name='R' schema='BOOL' />
|
||||||
<attribute name='_executable' schema='BOOL' hidden='yes' />
|
<attribute-alias from='_readable' to='R' />
|
||||||
|
<attribute name='W' schema='BOOL' />
|
||||||
|
<attribute-alias from='_writable' to='W' />
|
||||||
|
<attribute name='X' schema='BOOL' />
|
||||||
|
<attribute-alias from='_executable' to='X' />
|
||||||
</schema>
|
</schema>
|
||||||
<schema name='BreakpointContainer' canonical='yes' elementResync='NEVER'
|
<schema name='BreakpointContainer' canonical='yes' elementResync='NEVER'
|
||||||
attributeResync='ONCE'>
|
attributeResync='ONCE'>
|
||||||
|
@ -102,14 +107,18 @@ public class AbstractGhidraHeadedDebuggerIntegrationTest
|
||||||
<interface name='BreakpointLocationContainer' />
|
<interface name='BreakpointLocationContainer' />
|
||||||
<interface name='Togglable' />
|
<interface name='Togglable' />
|
||||||
<element schema='BreakpointLoc' />
|
<element schema='BreakpointLoc' />
|
||||||
<attribute name='_kinds' schema='SET_BREAKPOINT_KIND' hidden='yes' />
|
<attribute name='Kinds' schema='SET_BREAKPOINT_KIND' />
|
||||||
<attribute name='_expr' schema='STRING' hidden='yes' />
|
<attribute-alias from='_kinds' to='Kinds' />
|
||||||
<attribute name='_enabled' schema='BOOL' hidden='yes' />
|
<attribute name='Expression' schema='STRING' />
|
||||||
|
<attribute-alias from='_expr' to='Expression' />
|
||||||
|
<attribute name='Enabled' schema='BOOL' />
|
||||||
|
<attribute-alias from='_enabled' to='Enabled' />
|
||||||
</schema>
|
</schema>
|
||||||
<schema name='BreakpointLoc' canonical='yes' elementResync='NEVER'
|
<schema name='BreakpointLoc' canonical='yes' elementResync='NEVER'
|
||||||
attributeResync='ONCE'>
|
attributeResync='ONCE'>
|
||||||
<interface name='BreakpointLocation' />
|
<interface name='BreakpointLocation' />
|
||||||
<attribute name='_range' schema='RANGE' hidden='yes' />
|
<attribute name='Range' schema='RANGE' />
|
||||||
|
<attribute-alias from='_range' to='Range' />
|
||||||
</schema>
|
</schema>
|
||||||
</context>
|
</context>
|
||||||
""");
|
""");
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue