diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/impl/DbgModelImpl.java b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/impl/DbgModelImpl.java index 6cdff2e25a..8fad13fd68 100644 --- a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/impl/DbgModelImpl.java +++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/impl/DbgModelImpl.java @@ -91,6 +91,11 @@ public class DbgModelImpl extends AbstractDbgModel { dbg.terminate(); } + @Override + public TargetObjectSchema getRootSchema() { + return root.getSchema(); + } + @Override public CompletableFuture fetchModelRoot() { return completedRoot; diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/impl/DbgModelTargetBreakpointSpecImpl.java b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/impl/DbgModelTargetBreakpointSpecImpl.java index a15b5fcfa8..018f9fd66b 100644 --- a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/impl/DbgModelTargetBreakpointSpecImpl.java +++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/impl/DbgModelTargetBreakpointSpecImpl.java @@ -21,13 +21,19 @@ import java.util.concurrent.CompletableFuture; import agent.dbgeng.manager.breakpoint.DbgBreakpointInfo; import agent.dbgeng.model.iface2.DbgModelTargetBreakpointContainer; import agent.dbgeng.model.iface2.DbgModelTargetBreakpointSpec; -import ghidra.dbg.target.TargetObject; +import ghidra.dbg.target.*; import ghidra.dbg.target.schema.TargetAttributeType; import ghidra.dbg.target.schema.TargetObjectSchemaInfo; import ghidra.dbg.util.PathUtils; import ghidra.util.datastruct.ListenerSet; @TargetObjectSchemaInfo(name = "BreakpointSpec", attributes = { // + @TargetAttributeType( // + name = TargetBreakpointSpec.CONTAINER_ATTRIBUTE_NAME, // + type = DbgModelTargetBreakpointContainerImpl.class), // + @TargetAttributeType( // + name = TargetBreakpointLocation.SPEC_ATTRIBUTE_NAME, // + type = DbgModelTargetBreakpointSpecImpl.class), // @TargetAttributeType(type = Void.class) // }, canonicalContainer = true) public class DbgModelTargetBreakpointSpecImpl extends DbgModelTargetObjectImpl diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/impl/DbgModelTargetConnectorContainerImpl.java b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/impl/DbgModelTargetConnectorContainerImpl.java index 28f154ae66..3d947ca429 100644 --- a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/impl/DbgModelTargetConnectorContainerImpl.java +++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/impl/DbgModelTargetConnectorContainerImpl.java @@ -42,7 +42,7 @@ public class DbgModelTargetConnectorContainerImpl extends DbgModelTargetObjectIm protected final DbgModelTargetKernelConnectorImpl kernelAttacher; public DbgModelTargetConnectorContainerImpl(DbgModelTargetRoot root) { - super(root.getModel(), root, "Connectors", "ConnectorsContainer"); + super(root.getModel(), root, "Connectors", "ConnectorsContainer", null); this.root = root; this.processLauncher = new DbgModelTargetProcessLaunchConnectorImpl(this, "Launch process"); diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/impl/DbgModelTargetMemoryRegionImpl.java b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/impl/DbgModelTargetMemoryRegionImpl.java index b093d993a6..0629696c15 100644 --- a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/impl/DbgModelTargetMemoryRegionImpl.java +++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/impl/DbgModelTargetMemoryRegionImpl.java @@ -21,6 +21,7 @@ import java.util.Map; import agent.dbgeng.manager.DbgModuleMemory; import agent.dbgeng.model.iface2.DbgModelTargetMemoryContainer; import agent.dbgeng.model.iface2.DbgModelTargetMemoryRegion; +import ghidra.dbg.target.TargetMemoryRegion; import ghidra.dbg.target.schema.*; import ghidra.dbg.util.PathUtils; import ghidra.program.model.address.*; @@ -28,6 +29,9 @@ import ghidra.program.model.address.*; @TargetObjectSchemaInfo(name = "MemoryRegion", elements = { // @TargetElementType(type = Void.class) // }, attributes = { // + @TargetAttributeType( // + name = TargetMemoryRegion.MEMORY_ATTRIBUTE_NAME, // + type = DbgModelTargetMemoryContainerImpl.class), // @TargetAttributeType(name = "BaseAddress", type = Address.class), // @TargetAttributeType(name = "EndAddress", type = Address.class), // @TargetAttributeType(name = "RegionSize", type = String.class), // diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/impl/DbgModelTargetObjectImpl.java b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/impl/DbgModelTargetObjectImpl.java index 13cab1ef6f..c35c00600e 100644 --- a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/impl/DbgModelTargetObjectImpl.java +++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/impl/DbgModelTargetObjectImpl.java @@ -40,7 +40,7 @@ public class DbgModelTargetObjectImpl extends DefaultTargetObject this.typeHint = typeHint; this.schema = schema; - schema.validateTypeAndInterfaces(getProxy(), null, enforcesStrictSchema()); + if (schema != null) { + schema.validateTypeAndInterfaces(getProxy(), null, enforcesStrictSchema()); + } } /** @@ -124,8 +126,12 @@ public abstract class AbstractTargetObject

@Override public String toString() { - return String.format("<%s: path=%s model=%s schema=%s>", - getClass().getSimpleName(), path, getModel(), schema.getName()); + if (schema == null) { + return String.format("<%s: path=%s model=%s schema=NULL>", getClass().getSimpleName(), + path, getModel()); + } + return String.format("<%s: path=%s model=%s schema=%s>", getClass().getSimpleName(), path, + getModel(), schema.getName()); } @Override diff --git a/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/agent/DefaultTargetObject.java b/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/agent/DefaultTargetObject.java index 7732d88226..11b7162daa 100644 --- a/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/agent/DefaultTargetObject.java +++ b/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/agent/DefaultTargetObject.java @@ -237,7 +237,10 @@ public class DefaultTargetObject synchronized (this.elements) { delta = Delta.computeAndSet(this.elements, elements, Delta.SAME); } - getSchema().validateElementDelta(getProxy(), delta, enforcesStrictSchema()); + TargetObjectSchema schemax = getSchema(); + if (schemax != null) { + schemax.validateElementDelta(getProxy(), delta, enforcesStrictSchema()); + } doInvalidateElements(delta.removed.values(), reason); if (!delta.isEmpty()) { listeners.fire.elementsChanged(getProxy(), delta.getKeysRemoved(), delta.added); @@ -279,7 +282,10 @@ public class DefaultTargetObject synchronized (elements) { delta = Delta.apply(this.elements, remove, add, Delta.SAME); } - getSchema().validateElementDelta(getProxy(), delta, enforcesStrictSchema()); + TargetObjectSchema schemax = getSchema(); + if (schemax != null) { + schemax.validateElementDelta(getProxy(), delta, enforcesStrictSchema()); + } doInvalidateElements(delta.removed.values(), reason); if (!delta.isEmpty()) { listeners.fire.elementsChanged(getProxy(), delta.getKeysRemoved(), delta.added); @@ -395,7 +401,10 @@ public class DefaultTargetObject synchronized (this.attributes) { delta = Delta.computeAndSet(this.attributes, attributes, Delta.EQUAL); } - getSchema().validateAttributeDelta(getProxy(), delta, enforcesStrictSchema()); + TargetObjectSchema schemax = getSchema(); + if (schemax != null) { + schemax.validateAttributeDelta(getProxy(), delta, enforcesStrictSchema()); + } doInvalidateAttributes(delta.removed, reason); if (!delta.isEmpty()) { listeners.fire.attributesChanged(getProxy(), delta.getKeysRemoved(), delta.added); @@ -437,7 +446,10 @@ public class DefaultTargetObject synchronized (attributes) { delta = Delta.apply(this.attributes, remove, add, Delta.EQUAL); } - getSchema().validateAttributeDelta(getProxy(), delta, enforcesStrictSchema()); + TargetObjectSchema schemax = getSchema(); + if (schemax != null) { + schemax.validateAttributeDelta(getProxy(), delta, enforcesStrictSchema()); + } doInvalidateAttributes(delta.removed, reason); if (!delta.isEmpty()) { listeners.fire.attributesChanged(getProxy(), delta.getKeysRemoved(), delta.added); diff --git a/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/target/TargetObject.java b/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/target/TargetObject.java index 7c91985907..f78494ed22 100644 --- a/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/target/TargetObject.java +++ b/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/target/TargetObject.java @@ -27,7 +27,6 @@ import ghidra.dbg.attributes.TypedTargetObjectRef; import ghidra.dbg.error.DebuggerModelTypeException; import ghidra.dbg.target.TargetExecutionStateful.TargetExecutionState; import ghidra.dbg.target.schema.*; -import ghidra.dbg.target.schema.TargetObjectSchema.AttributeSchema; import ghidra.dbg.util.PathUtils; import ghidra.dbg.util.PathUtils.TargetObjectKeyComparator; import ghidra.dbg.util.ValueUtils; @@ -161,44 +160,18 @@ import ghidra.util.Msg; */ public interface TargetObject extends TargetObjectRef { - Set> ALL_INTERFACES = Set.of( - TargetAccessConditioned.class, - TargetAggregate.class, - TargetAttachable.class, - TargetAttacher.class, - TargetBreakpointContainer.class, - TargetBreakpointSpec.class, - TargetDataTypeMember.class, - TargetDataTypeNamespace.class, - TargetDeletable.class, - TargetDetachable.class, - TargetBreakpointLocation.class, - TargetEnvironment.class, - TargetEventScope.class, - TargetExecutionStateful.class, - TargetFocusScope.class, - TargetInterpreter.class, - TargetInterruptible.class, - TargetKillable.class, - TargetLauncher.class, - TargetMethod.class, - TargetMemory.class, - TargetMemoryRegion.class, - TargetModule.class, - TargetModuleContainer.class, - TargetNamedDataType.class, - TargetProcess.class, - TargetRegister.class, - TargetRegisterBank.class, - TargetRegisterContainer.class, - TargetResumable.class, - TargetSection.class, - TargetStack.class, - TargetStackFrame.class, - TargetSteppable.class, - TargetSymbol.class, - TargetSymbolNamespace.class, - TargetThread.class); + Set> ALL_INTERFACES = Set.of(TargetAccessConditioned.class, + TargetAggregate.class, TargetAttachable.class, TargetAttacher.class, + TargetBreakpointContainer.class, TargetBreakpointSpec.class, TargetDataTypeMember.class, + TargetDataTypeNamespace.class, TargetDeletable.class, TargetDetachable.class, + TargetBreakpointLocation.class, TargetEnvironment.class, TargetEventScope.class, + TargetExecutionStateful.class, TargetFocusScope.class, TargetInterpreter.class, + TargetInterruptible.class, TargetKillable.class, TargetLauncher.class, TargetMethod.class, + TargetMemory.class, TargetMemoryRegion.class, TargetModule.class, + TargetModuleContainer.class, TargetNamedDataType.class, TargetProcess.class, + TargetRegister.class, TargetRegisterBank.class, TargetRegisterContainer.class, + TargetResumable.class, TargetSection.class, TargetStack.class, TargetStackFrame.class, + TargetSteppable.class, TargetSymbol.class, TargetSymbolNamespace.class, TargetThread.class); Map> INTERFACES_BY_NAME = initInterfacesByName(); /** @@ -209,12 +182,11 @@ public interface TargetObject extends TargetObjectRef { @Internal static Map> initInterfacesByName() { return ALL_INTERFACES.stream() - .collect(Collectors.toUnmodifiableMap( - DebuggerObjectModel::requireIfaceName, i -> i)); + .collect( + Collectors.toUnmodifiableMap(DebuggerObjectModel::requireIfaceName, i -> i)); } - static List> getInterfacesByName( - Collection names) { + static List> getInterfacesByName(Collection names) { return names.stream() .filter(INTERFACES_BY_NAME::containsKey) .map(INTERFACES_BY_NAME::get) @@ -627,9 +599,10 @@ public interface TargetObject extends TargetObjectRef { * @return the value casted to the expected type, or the fallback value */ public default T getTypedAttributeNowByName(String name, Class cls, T fallback) { - AttributeSchema as = getSchema().getAttributeSchema(name); Object obj = getCachedAttribute(name); - return ValueUtils.expectType(obj, cls, this, name, fallback, as.isRequired()); + TargetObjectSchema schema = getSchema(); + boolean required = schema == null ? false : schema.getAttributeSchema(name).isRequired(); + return ValueUtils.expectType(obj, cls, this, name, fallback, required); } /** diff --git a/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/target/schema/AnnotatedSchemaContext.java b/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/target/schema/AnnotatedSchemaContext.java index c89b801d5d..af4f85ab97 100644 --- a/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/target/schema/AnnotatedSchemaContext.java +++ b/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/target/schema/AnnotatedSchemaContext.java @@ -36,6 +36,27 @@ import utilities.util.reflection.ReflectionUtilities; public class AnnotatedSchemaContext extends DefaultSchemaContext { + public static class AnnotatedAttributeSchema extends DefaultAttributeSchema { + protected final Class javaClass; + + public AnnotatedAttributeSchema(String name, SchemaName schema, boolean isRequired, + boolean isFixed, boolean isHidden, Class javaClass) { + super(name, schema, isRequired, isFixed, isHidden); + this.javaClass = javaClass; + } + + public AnnotatedAttributeSchema lower(AnnotatedAttributeSchema that) { + if (this.javaClass.isAssignableFrom(that.javaClass)) { + return that; + } + if (that.javaClass.isAssignableFrom(this.javaClass)) { + return this; + } + throw new IllegalArgumentException("Cannot find lower of " + this.javaClass + " and " + + that.javaClass + ". They are unrelated."); + } + } + static Stream> filterBounds(Class base, Stream> bounds) { return bounds.filter(base::isAssignableFrom).map(c -> c.asSubclass(base)); } @@ -129,8 +150,12 @@ public class AnnotatedSchemaContext extends DefaultSchemaContext { TargetObjectSchemaInfo info = cls.getAnnotation(TargetObjectSchemaInfo.class); if (info == null) { // TODO: Compile-time validation? - Msg.warn(this, "Class " + cls + " is not annotated with @" + - TargetObjectSchemaInfo.class.getSimpleName()); + DebuggerTargetObjectIface iface = + cls.getAnnotation(DebuggerTargetObjectIface.class); + if (iface == null) { + Msg.warn(this, "Class " + cls + " is not annotated with @" + + TargetObjectSchemaInfo.class.getSimpleName()); + } return EnumerableTargetObjectSchema.OBJECT.getName(); } return namesByClass.computeIfAbsent(cls, c -> { @@ -235,8 +260,12 @@ public class AnnotatedSchemaContext extends DefaultSchemaContext { } } for (TargetAttributeType at : info.attributes()) { - AttributeSchema attrSchema = attributeSchemaFromAnnotation(at); - builder.addAttributeSchema(attrSchema, at); + AnnotatedAttributeSchema attrSchema = attributeSchemaFromAnnotation(at); + AttributeSchema exists = builder.getAttributeSchema(attrSchema.getName()); + if (exists != null) { + attrSchema = attrSchema.lower((AnnotatedAttributeSchema) exists); + } + builder.replaceAttributeSchema(attrSchema, at); } return builder.buildAndAdd(); @@ -255,9 +284,9 @@ public class AnnotatedSchemaContext extends DefaultSchemaContext { .toLowerCase(); } - protected AttributeSchema attributeSchemaFromAnnotation(TargetAttributeType at) { - return new DefaultAttributeSchema(at.name(), nameFromClass(at.type()), at.required(), - at.fixed(), at.hidden()); + protected AnnotatedAttributeSchema attributeSchemaFromAnnotation(TargetAttributeType at) { + return new AnnotatedAttributeSchema(at.name(), nameFromClass(at.type()), at.required(), + at.fixed(), at.hidden(), at.type()); } protected AttributeSchema attributeSchemaFromAnnotatedMethod(Class cls, @@ -275,8 +304,8 @@ public class AnnotatedSchemaContext extends DefaultSchemaContext { } SchemaName primitiveName = EnumerableTargetObjectSchema.nameForPrimitive(ret); if (primitiveName != null) { - return new DefaultAttributeSchema(name, primitiveName, at.required(), at.fixed(), - at.hidden()); + return new AnnotatedAttributeSchema(name, primitiveName, at.required(), at.fixed(), + at.hidden(), ret); } Set> bounds = getBoundsOfObjectAttributeGetter(cls, method); if (bounds.size() != 1) { @@ -284,8 +313,9 @@ public class AnnotatedSchemaContext extends DefaultSchemaContext { throw new IllegalArgumentException( "Could not identify unique attribute class for method " + method + ": " + bounds); } - return new DefaultAttributeSchema(name, nameFromClass(bounds.iterator().next()), - at.required(), at.fixed(), at.hidden()); + Class bound = bounds.iterator().next(); + return new AnnotatedAttributeSchema(name, nameFromClass(bound), + at.required(), at.fixed(), at.hidden(), bound); } protected SchemaName nameFromClass(Class cls) { diff --git a/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/target/schema/SchemaBuilder.java b/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/target/schema/SchemaBuilder.java index 5c70d929b3..eb7a705f5b 100644 --- a/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/target/schema/SchemaBuilder.java +++ b/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/target/schema/SchemaBuilder.java @@ -148,6 +148,19 @@ public class SchemaBuilder { return Map.copyOf(attributeSchemas); } + public AttributeSchema getAttributeSchema(String name) { + return attributeSchemas.get(name); + } + + public SchemaBuilder replaceAttributeSchema(AttributeSchema schema, Object origin) { + if (schema.getName().equals("")) { + return setDefaultAttributeSchema(schema); + } + attributeSchemas.put(schema.getName(), schema); + attributeOrigins.put(schema.getName(), origin); + return this; + } + public SchemaBuilder setDefaultAttributeSchema(AttributeSchema defaultAttributeSchema) { this.defaultAttributeSchema = defaultAttributeSchema; return this;