GP-77: Model schema specification, and GDB's implementation.

This commit is contained in:
Dan 2020-12-28 09:55:03 -05:00
parent c90e12a78b
commit eb66a90f6c
110 changed files with 4207 additions and 354 deletions

View file

@ -26,6 +26,8 @@ import ghidra.dbg.error.DebuggerModelNoSuchPathException;
import ghidra.dbg.error.DebuggerModelTypeException;
import ghidra.dbg.target.TargetMemory;
import ghidra.dbg.target.TargetObject;
import ghidra.dbg.target.schema.EnumerableTargetObjectSchema;
import ghidra.dbg.target.schema.TargetObjectSchema;
import ghidra.dbg.util.PathUtils;
import ghidra.program.model.address.*;
@ -165,6 +167,21 @@ public interface DebuggerObjectModel {
*/
public boolean isAlive();
/**
* Get the schema of this model, i.e., the schema of its root object.
*
* <p>
* The schema may not be known until the model has been successfully opened. Some factories will
* ensure success before providing the model, but this may not always be the case. Callers
* should listen for {@link DebuggerModelListener#modelOpened()} or retrieve the root object
* first.
*
* @return the root schema
*/
public default TargetObjectSchema getRootSchema() {
return EnumerableTargetObjectSchema.OBJECT;
}
/**
* Check if the debugger agent is alive (optional operation)
*

View file

@ -21,6 +21,8 @@ import java.util.concurrent.CompletableFuture;
import ghidra.dbg.DebuggerObjectModel;
import ghidra.dbg.target.TargetObject;
import ghidra.dbg.target.schema.EnumerableTargetObjectSchema;
import ghidra.dbg.target.schema.TargetObjectSchema;
import ghidra.dbg.util.PathUtils;
import ghidra.util.datastruct.ListenerSet;
@ -49,13 +51,15 @@ public abstract class AbstractTargetObject<P extends TargetObject>
protected final List<String> path;
protected final int hash;
protected final String typeHint;
protected final TargetObjectSchema schema;
protected boolean valid = true;
protected final ListenerSet<TargetObjectListener> listeners =
new ListenerSet<>(TargetObjectListener.class);
public AbstractTargetObject(DebuggerObjectModel model, P parent, String key, String typeHint) {
public AbstractTargetObject(DebuggerObjectModel model, P parent, String key, String typeHint,
TargetObjectSchema schema) {
this.model = model;
this.parent = parent;
this.completedParent = CompletableFuture.completedFuture(parent);
@ -67,6 +71,45 @@ public abstract class AbstractTargetObject<P extends TargetObject>
}
this.hash = computeHashCode();
this.typeHint = typeHint;
this.schema = schema;
schema.validateTypeAndInterfaces(getProxy(), null, enforcesStrictSchema());
}
/**
* Get an alternative for {@code this} when invoking the listeners.
*
* <p>
* Some implementations may use on a proxy-delegate pattern to implement target objects with
* various combinations of supported interfaces. When this pattern is employed, the delegate
* will extend {@link DefaultTargetObject}, causing {@code this} to refer to the delegate rather
* than the proxy. When invoking listeners, the proxy given by this method is used instead. By
* default, it simply returns {@code this}, providing the expected behavior for typical
* implementations. The proxy is also used for schema interface validation.
*
* @return the proxy or this
*/
public TargetObject getProxy() {
return this;
}
/**
* Check if this object strictly conforms to the schema
*
* <p>
* This method exists to support the transition to schemas. If this method returns false, schema
* violations are logged, but whatever changes were requested that caused the violation are
* still allowed to occur. If it returns true, then any schema violation will cause an
* {@link AssertionError}. Because schema violations are presumed to be programming errors,
* there is no guarantee of consistency after an exception is thrown. In general, models without
* explicit schemas should not fail validation, since objects will likely be assigned
* {@link EnumerableTargetObjectSchema#ANY}. When developing a schema for an existing model, it
* may be useful to override this to return false in the interim.
*
* @return true to throw exceptions on schema violations.
*/
protected boolean enforcesStrictSchema() {
return true;
}
@Override
@ -81,8 +124,8 @@ public abstract class AbstractTargetObject<P extends TargetObject>
@Override
public String toString() {
return "<Local " + getClass().getSimpleName() + ": " + path + " in " +
getModel() + ">";
return String.format("<%s: path=%s model=%s schema=%s>",
getClass().getSimpleName(), path, getModel(), schema.getName());
}
@Override
@ -90,6 +133,11 @@ public abstract class AbstractTargetObject<P extends TargetObject>
return typeHint;
}
@Override
public TargetObjectSchema getSchema() {
return schema;
}
@Override
public boolean isValid() {
return valid;

View file

@ -18,11 +18,18 @@ package ghidra.dbg.agent;
import ghidra.dbg.DebuggerObjectModel;
import ghidra.dbg.target.TargetAggregate;
import ghidra.dbg.target.TargetObject;
import ghidra.dbg.target.schema.EnumerableTargetObjectSchema;
import ghidra.dbg.target.schema.TargetObjectSchema;
public class DefaultTargetModelRoot extends DefaultTargetObject<TargetObject, TargetObject>
implements TargetAggregate {
public DefaultTargetModelRoot(DebuggerObjectModel model, String typeHint) {
super(model, null, null, typeHint);
this(model, typeHint, EnumerableTargetObjectSchema.OBJECT);
}
public DefaultTargetModelRoot(DebuggerObjectModel model, String typeHint,
TargetObjectSchema schema) {
super(model, null, null, typeHint, schema);
}
}

View file

@ -22,6 +22,7 @@ import ghidra.async.AsyncUtils;
import ghidra.dbg.DebuggerObjectModel;
import ghidra.dbg.attributes.TargetObjectRef;
import ghidra.dbg.target.TargetObject;
import ghidra.dbg.target.schema.TargetObjectSchema;
import ghidra.dbg.util.CollectionUtils.Delta;
import ghidra.dbg.util.PathUtils;
import ghidra.dbg.util.PathUtils.TargetObjectKeyComparator;
@ -48,6 +49,20 @@ public class DefaultTargetObject<E extends TargetObject, P extends TargetObject>
new TreeMap<>(TargetObjectKeyComparator.ATTRIBUTE);
protected CompletableFuture<Void> curAttrsRequest;
/**
* Construct a new default target object whose schema is derived from the parent
*
* @see #DefaultTargetObject(DebuggerObjectModel, TargetObject, String, String,
* TargetObjectSchema)
* @param model the model to which the object belongs
* @param parent the (non-null) parent of this object
* @param key the key (attribute name or element index) of this object
* @param typeHint the type hint for this object
*/
public DefaultTargetObject(DebuggerObjectModel model, P parent, String key, String typeHint) {
this(model, parent, key, typeHint, parent.getSchema().getChildSchema(key));
}
/**
* Construct a new default target object
*
@ -71,31 +86,16 @@ public class DefaultTargetObject<E extends TargetObject, P extends TargetObject>
* @param parent the parent of this object
* @param key the key (attribute name or element index) of this object
* @param typeHint the type hint for this object
* @param schema the schema of this object
*/
public DefaultTargetObject(DebuggerObjectModel model, P parent, String key, String typeHint) {
super(model, parent, key, typeHint);
public DefaultTargetObject(DebuggerObjectModel model, P parent, String key, String typeHint,
TargetObjectSchema schema) {
super(model, parent, key, typeHint, schema);
changeAttributes(List.of(), List.of(), Map.of(DISPLAY_ATTRIBUTE_NAME,
key == null ? "<root>" : key, UPDATE_MODE_ATTRIBUTE_NAME, TargetUpdateMode.UNSOLICITED),
"Initialized");
}
/**
* Get an alternative for {@code this} when invoking the listeners.
*
* <p>
* Some implementations may use on a proxy-delegate pattern to implement target objects with
* various combinations of supported interfaces. When this pattern is employed, the delegate
* will extend {@link DefaultTargetObject}, causing {@code this} to refer to the delegate rather
* than the proxy. When invoking listeners, the proxy given by this method is used instead. By
* default, it simply returns {@code this}, providing the expected behavior for typical
* implementations.
*
* @return
*/
public TargetObject getProxy() {
return this;
}
/**
* Check if this object is being observed
*
@ -237,6 +237,7 @@ public class DefaultTargetObject<E extends TargetObject, P extends TargetObject>
synchronized (this.elements) {
delta = Delta.computeAndSet(this.elements, elements, Delta.SAME);
}
getSchema().validateElementDelta(getProxy(), delta, enforcesStrictSchema());
doInvalidateElements(delta.removed.values(), reason);
if (!delta.isEmpty()) {
listeners.fire.elementsChanged(getProxy(), delta.getKeysRemoved(), delta.added);
@ -278,6 +279,7 @@ public class DefaultTargetObject<E extends TargetObject, P extends TargetObject>
synchronized (elements) {
delta = Delta.apply(this.elements, remove, add, Delta.SAME);
}
getSchema().validateElementDelta(getProxy(), delta, enforcesStrictSchema());
doInvalidateElements(delta.removed.values(), reason);
if (!delta.isEmpty()) {
listeners.fire.elementsChanged(getProxy(), delta.getKeysRemoved(), delta.added);
@ -393,6 +395,7 @@ public class DefaultTargetObject<E extends TargetObject, P extends TargetObject>
synchronized (this.attributes) {
delta = Delta.computeAndSet(this.attributes, attributes, Delta.EQUAL);
}
getSchema().validateAttributeDelta(getProxy(), delta, enforcesStrictSchema());
doInvalidateAttributes(delta.removed, reason);
if (!delta.isEmpty()) {
listeners.fire.attributesChanged(getProxy(), delta.getKeysRemoved(), delta.added);
@ -434,6 +437,7 @@ public class DefaultTargetObject<E extends TargetObject, P extends TargetObject>
synchronized (attributes) {
delta = Delta.apply(this.attributes, remove, add, Delta.EQUAL);
}
getSchema().validateAttributeDelta(getProxy(), delta, enforcesStrictSchema());
doInvalidateAttributes(delta.removed, reason);
if (!delta.isEmpty()) {
listeners.fire.attributesChanged(getProxy(), delta.getKeysRemoved(), delta.added);

View file

@ -0,0 +1,44 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.dbg.error;
import ghidra.async.AsyncUtils;
/**
* An exception which should not alert the user
*
* <p>
* At most, it can be logged, probably without a stack trace. These are sorts of soft warnings,
* which might be issued when an exception is a normal occurrence. One example is when a model is
* shutting down. It's common for requests in the queue to be rejected once the model beings
* shutting down. Exceptions raised by those requests can likely be ignored. Please note, clients
* will likely need to apply {@link AsyncUtils#unwrapThrowable(Throwable)} in order to determine
* whether the exception is ignorable. Alternatively, use {@link #isIgnorable(Throwable)}.
*/
public class DebuggerIgnorableException extends DebuggerRuntimeException {
public static boolean isIgnorable(Throwable ex) {
Throwable u = AsyncUtils.unwrapThrowable(ex);
return u instanceof DebuggerIgnorableException;
}
public DebuggerIgnorableException(String message, Throwable cause) {
super(message, cause);
}
public DebuggerIgnorableException(String message) {
super(message);
}
}

View file

@ -0,0 +1,29 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.dbg.error;
/**
* A request was rejected, because the model is shutting down
*/
public class DebuggerModelTerminatingException extends DebuggerIgnorableException {
public DebuggerModelTerminatingException(String message, Throwable cause) {
super(message, cause);
}
public DebuggerModelTerminatingException(String message) {
super(message);
}
}

View file

@ -18,12 +18,14 @@ package ghidra.dbg.target;
import org.apache.commons.lang3.reflect.TypeLiteral;
import ghidra.dbg.DebuggerTargetObjectIface;
import ghidra.dbg.target.schema.TargetAttributeType;
import ghidra.dbg.util.ValueUtils;
import ghidra.lifecycle.Internal;
/**
* A target object which may not be accessible
*
* <p>
* Depending on the state of the debugger, it may not be able to process commands for certain target
* objects. Objects which may not be accessible should support this interface. Note, that the
* granularity of accessibility is the entire object, including its children (excluding links). If,
@ -45,6 +47,9 @@ public interface TargetAccessConditioned<T extends TargetAccessConditioned<T>>
TypeLiteral<TargetAccessConditioned<?>> type = new TypeLiteral<>() {};
String ACCESSIBLE_ATTRIBUTE_NAME = PREFIX_INVISIBLE + "accessible";
/**
* TODO: I'm seriously considering removing this
*/
public enum TargetAccessibility {
ACCESSIBLE, INACCESSIBLE;
@ -59,7 +64,13 @@ public interface TargetAccessConditioned<T extends TargetAccessConditioned<T>>
return TargetAccessibility.ACCESSIBLE;
}
return TargetAccessibility
.fromBool(ValueUtils.expectBoolean(obj, this, ACCESSIBLE_ATTRIBUTE_NAME, true));
.fromBool(
ValueUtils.expectBoolean(obj, this, ACCESSIBLE_ATTRIBUTE_NAME, true, true));
}
@TargetAttributeType(name = ACCESSIBLE_ATTRIBUTE_NAME, required = true, hidden = true)
public default Boolean isAccessible() {
return getTypedAttributeNowByName(ACCESSIBLE_ATTRIBUTE_NAME, Boolean.class, true);
}
public default TargetAccessibility getAccessibility() {
@ -69,7 +80,6 @@ public interface TargetAccessConditioned<T extends TargetAccessConditioned<T>>
public interface TargetAccessibilityListener extends TargetObjectListener {
default void accessibilityChanged(TargetAccessConditioned<?> object,
TargetAccessibility accessibility) {
System.err.println("default");
}
}
}

View file

@ -20,6 +20,7 @@ import ghidra.dbg.DebuggerTargetObjectIface;
/**
* A marker interface which indicates its attributes represent the object as a whole
*
* <p>
* Often applied to processes and sessions, this causes ancestry traversals to include this object's
* children when visited.
*/

View file

@ -20,12 +20,13 @@ import java.util.concurrent.CompletableFuture;
import ghidra.dbg.DebuggerTargetObjectIface;
import ghidra.dbg.attributes.TypedTargetObjectRef;
import ghidra.dbg.target.TargetSteppable.TargetStepKind;
import ghidra.dbg.target.TargetSteppable.TargetStepKindSet;
import ghidra.dbg.target.TargetSteppable.TargetStepKindSet.ImmutableTargetStepKindSet;
import ghidra.dbg.target.schema.TargetAttributeType;
import ghidra.dbg.util.CollectionUtils;
import ghidra.dbg.util.CollectionUtils.AbstractEmptySet;
/**
* An object which is capable of attaching to a {@link TargetAttachable}
*/
@DebuggerTargetObjectIface("Attacher")
public interface TargetAttacher<T extends TargetAttacher<T>> extends TypedTargetObject<T> {
enum Private {
@ -62,18 +63,18 @@ public interface TargetAttacher<T extends TargetAttacher<T>> extends TypedTarget
return EMPTY;
}
public static TargetStepKindSet of(TargetStepKind... kinds) {
return new ImmutableTargetStepKindSet(kinds);
public static TargetAttachKindSet of(TargetAttachKind... kinds) {
return new ImmutableTargetAttachKindSet(kinds);
}
public static TargetStepKindSet copyOf(Set<TargetStepKind> set) {
return new ImmutableTargetStepKindSet(set);
public static TargetAttachKindSet copyOf(Set<TargetAttachKind> set) {
return new ImmutableTargetAttachKindSet(set);
}
}
enum TargetAttachKind {
/**
* Use an "attachable" object
* Use a {@link TargetAttachable} object
*/
BY_OBJECT_REF,
/**
@ -87,11 +88,13 @@ public interface TargetAttacher<T extends TargetAttacher<T>> extends TypedTarget
/**
* Get the kinds of multi-stepping implemented by the debugger
*
* Different debuggers may provide similar, but slightly different vocabularies of stepping.
* This method queries the connected debugger for its supported step kinds.
* <p>
* Different debuggers provide varying methods of attaching. This attribute describes which are
* supported. NOTE: This should be replaced by generic method invocation.
*
* @return the set of supported multi-step operations
* @return the set of supported attach operations
*/
@TargetAttributeType(name = SUPPORTED_ATTACH_KINDS_ATTRIBUTE_NAME, required = true, hidden = true)
public default TargetAttachKindSet getSupportedAttachKinds() {
return getTypedAttributeNowByName(SUPPORTED_ATTACH_KINDS_ATTRIBUTE_NAME,
TargetAttachKindSet.class, TargetAttachKindSet.of());
@ -100,6 +103,7 @@ public interface TargetAttacher<T extends TargetAttacher<T>> extends TypedTarget
/**
* Attach to the given {@link TargetAttachable} or reference
*
* <p>
* This is mostly applicable to user-space contexts, in which case, this usually means to attach
* to a process.
*
@ -111,6 +115,7 @@ public interface TargetAttacher<T extends TargetAttacher<T>> extends TypedTarget
/**
* Attach to the given id
*
* <p>
* This is mostly applicable to user-space contexts, in which case, this usually means to attach
* to a process using its pid.
*
@ -118,5 +123,4 @@ public interface TargetAttacher<T extends TargetAttacher<T>> extends TypedTarget
* @return a future which completes when the command is confirmed
*/
public CompletableFuture<Void> attach(long id);
}

View file

@ -22,10 +22,18 @@ import ghidra.dbg.DebuggerTargetObjectIface;
import ghidra.dbg.attributes.TargetObjectRef;
import ghidra.dbg.attributes.TypedTargetObjectRef;
import ghidra.dbg.target.TargetBreakpointSpec.TargetBreakpointKind;
import ghidra.dbg.target.schema.TargetAttributeType;
import ghidra.dbg.util.CollectionUtils.AbstractEmptySet;
import ghidra.dbg.util.CollectionUtils.AbstractNSet;
import ghidra.program.model.address.*;
/**
* A container for breakpoint specifications and/or locations
*
* <p>
* This interface provides for the placment (creation) of breakpoints and as a listening point for
* breakpoint events. Typically, it is implemented by an object whose elements are breakpoints.
*/
@DebuggerTargetObjectIface("BreakpointContainer")
public interface TargetBreakpointContainer<T extends TargetBreakpointContainer<T>>
extends TypedTargetObject<T> {
@ -74,17 +82,60 @@ public interface TargetBreakpointContainer<T extends TargetBreakpointContainer<T
}
}
/**
* Get the kinds of supported breakpoints
*
* <p>
* Different debuggers have differing vocabularies of breakpoints, and may only support a subset
* of those recognized by Ghidra. This attribute describes those supported.
*
* @return the set of supported kinds
*/
@TargetAttributeType(name = SUPPORTED_BREAK_KINDS_ATTRIBUTE_NAME, required = true, hidden = true)
public default TargetBreakpointKindSet getSupportedBreakpointKinds() {
return getTypedAttributeNowByName(SUPPORTED_BREAK_KINDS_ATTRIBUTE_NAME,
TargetBreakpointKindSet.class, TargetBreakpointKindSet.of());
}
/**
* Specify a breakpoint having the given expression and kinds
*
* <p>
* Certain combinations of kinds and expression may not be reasonable. In those cases, the
* debugger may choose to reject, split, and/or adjust the request.
*
* @param expression the expression, in the native debugger's syntax
* @param kinds the desired set of kinds
* @return a future which completes when the request is processed
*/
public CompletableFuture<Void> placeBreakpoint(String expression,
Set<TargetBreakpointKind> kinds);
/**
* Specify a breakpoint having the given range and kinds
*
* <p>
* Certain combinations of kinds and range may not be reasonable. In those cases, the debugger
* may choose to reject, split, and/or adjust the request.
*
* @param range the range of addresses for the breakpoint
* @param kinds the desired set of kinds
* @return a future which completes when the request is processed
*/
public CompletableFuture<Void> placeBreakpoint(AddressRange range,
Set<TargetBreakpointKind> kinds);
/**
* Specify a breakpoint having the given address and kinds
*
* <p>
* Certain combinations of kinds may not be reasonable. In those cases, the debugger may choose
* to reject, split, and/or adjust the request.
*
* @param expression the expression, in the native debugger's syntax
* @param kinds the desired set of kinds
* @return a future which completes when the request is processed
*/
public default CompletableFuture<Void> placeBreakpoint(Address address,
Set<TargetBreakpointKind> kinds) {
return placeBreakpoint(new AddressRangeImpl(address, address), kinds);

View file

@ -18,8 +18,16 @@ package ghidra.dbg.target;
import ghidra.dbg.DebuggerTargetObjectIface;
import ghidra.dbg.attributes.TargetObjectRefList;
import ghidra.dbg.attributes.TypedTargetObjectRef;
import ghidra.dbg.target.schema.TargetAttributeType;
import ghidra.program.model.address.Address;
/**
* The location of a breakpoint
*
* <p>
* If the native debugger does not separate the concepts of specification and location, then
* breakpoint objects should implement both the specification and location interfaces.
*/
@DebuggerTargetObjectIface("BreakpointLocation")
public interface TargetBreakpointLocation<T extends TargetBreakpointLocation<T>>
extends TypedTargetObject<T> {
@ -39,26 +47,43 @@ public interface TargetBreakpointLocation<T extends TargetBreakpointLocation<T>>
String LENGTH_ATTRIBUTE_NAME = PREFIX_INVISIBLE + "length";
String SPEC_ATTRIBUTE_NAME = PREFIX_INVISIBLE + "spec";
/**
* The minimum address of this location
*
* @return the address
*/
@TargetAttributeType(name = ADDRESS_ATTRIBUTE_NAME, required = true, hidden = true)
public default Address getAddress() {
return getTypedAttributeNowByName(ADDRESS_ATTRIBUTE_NAME, Address.class, null);
}
/**
* A list of object to which this breakpoint applies
*
* <p>
* This list may be empty, in which case, this location is conventionally assumed to apply
* everywhere its container's location/scope suggests.
*
* @return the list of affected objects' references
*/
@TargetAttributeType(name = AFFECTS_ATTRIBUTE_NAME, hidden = true)
public default TargetObjectRefList<?> getAffects() {
return getTypedAttributeNowByName(AFFECTS_ATTRIBUTE_NAME, TargetObjectRefList.class,
TargetObjectRefList.of());
}
/**
* If available, get the length in bytes, of the range covered by the
* breakpoint.
* If available, get the length in bytes, of the range covered by the breakpoint.
*
* In most cases, where the length is not available, a length of 1 should be
* presumed.
* <p>
* In most cases, where the length is not available, a length of 1 should be presumed.
*
* <p>
* TODO: Should this be Long?
*
* @return the length, or {@code null} if not known
*/
@TargetAttributeType(name = LENGTH_ATTRIBUTE_NAME, hidden = true)
public default Integer getLength() {
return getTypedAttributeNowByName(LENGTH_ATTRIBUTE_NAME, Integer.class, null);
}
@ -70,14 +95,13 @@ public interface TargetBreakpointLocation<T extends TargetBreakpointLocation<T>>
/**
* Get a reference to the specification which generated this breakpoint.
*
* If the debugger does not separate specifications from actual breakpoints,
* then the "specification" is this breakpoint. Otherwise, this
* specification is the parent. The default implementation distinguishes the
* cases by examining the implemented interfaces. Implementors may slightly
* increase efficiency by overriding this method.
* <p>
* If the debugger does not separate specifications from actual breakpoints, then the
* "specification" is this breakpoint. Otherwise, the specification is the parent.
*
* @return the reference to the specification
*/
@TargetAttributeType(name = SPEC_ATTRIBUTE_NAME, required = true, hidden = true)
public default TypedTargetObjectRef<? extends TargetBreakpointSpec<?>> getSpecification() {
return getTypedRefAttributeNowByName(SPEC_ATTRIBUTE_NAME, TargetBreakpointSpec.tclass,
null);

View file

@ -24,6 +24,7 @@ import ghidra.dbg.DebuggerTargetObjectIface;
import ghidra.dbg.attributes.TargetObjectRef;
import ghidra.dbg.attributes.TypedTargetObjectRef;
import ghidra.dbg.target.TargetBreakpointContainer.TargetBreakpointKindSet;
import ghidra.dbg.target.schema.TargetAttributeType;
/**
* The specification of a breakpoint applied to a target object
@ -68,6 +69,7 @@ public interface TargetBreakpointSpec<T extends TargetBreakpointSpec<T>>
*
* @return a reference to the container
*/
@TargetAttributeType(name = CONTAINER_ATTRIBUTE_NAME, required = true, hidden = true)
public default TypedTargetObjectRef<? extends TargetBreakpointContainer<?>> getContainer() {
return getTypedRefAttributeNowByName(CONTAINER_ATTRIBUTE_NAME,
TargetBreakpointContainer.tclass, null);
@ -82,6 +84,7 @@ public interface TargetBreakpointSpec<T extends TargetBreakpointSpec<T>>
*
* @return the expression
*/
@TargetAttributeType(name = EXPRESSION_ATTRIBUTE_NAME, required = true, hidden = true)
public default String getExpression() {
return getTypedAttributeNowByName(EXPRESSION_ATTRIBUTE_NAME, String.class, "");
}
@ -91,6 +94,7 @@ public interface TargetBreakpointSpec<T extends TargetBreakpointSpec<T>>
*
* @return the kinds
*/
@TargetAttributeType(name = KINDS_ATTRIBUTE_NAME, required = true, hidden = true)
public default TargetBreakpointKindSet getKinds() {
return getTypedAttributeNowByName(KINDS_ATTRIBUTE_NAME, TargetBreakpointKindSet.class,
TargetBreakpointKindSet.EMPTY);
@ -101,6 +105,7 @@ public interface TargetBreakpointSpec<T extends TargetBreakpointSpec<T>>
*
* @return true if enabled, false otherwise
*/
@TargetAttributeType(name = ENABLED_ATTRIBUTE_NAME, required = true, hidden = true)
public default boolean isEnabled() {
return getTypedAttributeNowByName(ENABLED_ATTRIBUTE_NAME, Boolean.class, false);
}

View file

@ -19,7 +19,23 @@ import java.nio.charset.Charset;
import java.util.concurrent.CompletableFuture;
import ghidra.dbg.DebuggerTargetObjectIface;
import ghidra.lifecycle.Experimental;
/**
* A user-facing console
*
* <p>
* This could be a CLI for the native debugger, or I/O for a target, or anything else the model
* might like to expose in terminal-like fashion.
*
* <p>
* This is still an experimental concept and has not been implemented in any model. While it seems
* like an abstract case of {@link TargetInterpreter}, their specifications don't seem to line up.
* E.g., implementing the CLI as a {@link TargetConsole} requires the server to buffer and parse
* line input; whereas, implementing the CLI as a {@link TargetInterpreter} requires the client to
* parse line input.
*/
@Experimental
@DebuggerTargetObjectIface("Console")
public interface TargetConsole<T extends TargetConsole<T>> extends TypedTargetObject<T> {
Charset CHARSET = Charset.forName("utf-8");

View file

@ -17,7 +17,20 @@ package ghidra.dbg.target;
import ghidra.dbg.DebuggerTargetObjectIface;
import ghidra.dbg.attributes.TargetDataType;
import ghidra.dbg.target.schema.TargetAttributeType;
import ghidra.dbg.util.TargetDataTypeConverter;
/**
* A member of another data type
*
* <p>
* This is usually the child of a {@link TargetNamedDataType}, and its role in determined
* conventionally by the actual type of the parent, and of this member's key.
*
* <p>
* TODO: Document the conventions. Most, if not all, are implemented in
* {@link TargetDataTypeConverter}.
*/
@DebuggerTargetObjectIface("TypeMember")
public interface TargetDataTypeMember<T extends TargetDataTypeMember<T>>
extends TypedTargetObject<T> {
@ -38,20 +51,47 @@ public interface TargetDataTypeMember<T extends TargetDataTypeMember<T>>
/**
* The position of the member in the composite
*
* <p>
* A position of -1 implies the parent is not a composite or this member's role is special,
* e.g., the return type of a function.
*
* @return the position
*/
@TargetAttributeType(name = POSITION_ATTRIBUTE_NAME, hidden = true)
default int getPosition() {
return getTypedAttributeNowByName(POSITION_ATTRIBUTE_NAME, Integer.class, -1);
}
/**
* The name of the member in the composite
*
* @return the name
*/
@TargetAttributeType(name = MEMBER_NAME_ATTRIBUTE_NAME, required = true, hidden = true)
default String getMemberName() {
return getTypedAttributeNowByName(MEMBER_NAME_ATTRIBUTE_NAME, String.class, "");
}
/**
* The offset of the member in the composite
*
* <p>
* For structs, this should be the offset in bytes from the base of the struct. For unions, this
* should likely be 0. For others, this should be absent or -1.
*
* @return the offset
*/
@TargetAttributeType(name = OFFSET_ATTRIBUTE_NAME, hidden = true)
default long getOffset() {
return getTypedAttributeNowByName(OFFSET_ATTRIBUTE_NAME, Long.class, -1L);
}
/**
* The type of this member
*
* @return the type
*/
@TargetAttributeType(name = DATA_TYPE_ATTRIBUTE_NAME, required = true, hidden = true)
default TargetDataType getDataType() {
return getTypedAttributeNowByName(DATA_TYPE_ATTRIBUTE_NAME, TargetDataType.class, null);
}

View file

@ -24,7 +24,8 @@ import ghidra.dbg.DebuggerTargetObjectIface;
/**
* A container of data types
*
* The debugger should present these in as granular of unit as possible. Consider a desktop
* <p>
* The debugger should present these in as granular of units as possible. Consider a desktop
* application, for example. The debugger should present each module as a namespace rather than the
* entire target (or worse, the entire session) as a single namespace.
*/
@ -43,6 +44,7 @@ public interface TargetDataTypeNamespace<T extends TargetDataTypeNamespace<T>>
/**
* Get the types in this namespace
*
* <p>
* While it is most common for types to be immediate children of the namespace, that is not
* necessarily the case.
*

View file

@ -19,6 +19,9 @@ import java.util.concurrent.CompletableFuture;
import ghidra.dbg.DebuggerTargetObjectIface;
/**
* An object which can be removed by the user
*/
@DebuggerTargetObjectIface("Deletable")
public interface TargetDeletable<T extends TargetDeletable<T>> extends TypedTargetObject<T> {
enum Private {
@ -30,5 +33,10 @@ public interface TargetDeletable<T extends TargetDeletable<T>> extends TypedTarg
@SuppressWarnings({ "unchecked", "rawtypes" })
Class<Private.Cls> tclass = (Class) TargetDeletable.class;
/**
* Remove the object
*
* @return a future which completes when the request is processed
*/
public CompletableFuture<Void> delete();
}

View file

@ -19,6 +19,9 @@ import java.util.concurrent.CompletableFuture;
import ghidra.dbg.DebuggerTargetObjectIface;
/**
* A target which can be detached from the debugger
*/
@DebuggerTargetObjectIface("Detachable")
public interface TargetDetachable<T extends TargetDetachable<T>> extends TypedTargetObject<T> {
enum Private {
@ -33,7 +36,7 @@ public interface TargetDetachable<T extends TargetDetachable<T>> extends TypedTa
/**
* Detach from this target
*
* @return a future which completes upon successfully detaching
* @return a future which completes when the request is processed
*/
public CompletableFuture<Void> detach();
}

View file

@ -16,6 +16,7 @@
package ghidra.dbg.target;
import ghidra.dbg.DebuggerTargetObjectIface;
import ghidra.dbg.target.schema.TargetAttributeType;
/**
* Provides information about a given target object
@ -65,8 +66,9 @@ public interface TargetEnvironment<T extends TargetEnvironment<T>> extends Typed
*
* @return the target architecture
*/
@TargetAttributeType(name = VISIBLE_ARCH_ATTRIBUTE_NAME)
default String getArchitecture() {
return getTypedAttributeNowByName(ARCH_ATTRIBUTE_NAME, String.class, "");
return getTypedAttributeNowByName(VISIBLE_ARCH_ATTRIBUTE_NAME, String.class, "");
}
/**
@ -82,6 +84,7 @@ public interface TargetEnvironment<T extends TargetEnvironment<T>> extends Typed
*
* @return the host debugger
*/
@TargetAttributeType(name = DEBUGGER_ATTRIBUTE_NAME, hidden = true)
default String getDebugger() {
return getTypedAttributeNowByName(DEBUGGER_ATTRIBUTE_NAME, String.class, "");
}
@ -98,8 +101,9 @@ public interface TargetEnvironment<T extends TargetEnvironment<T>> extends Typed
*
* @return the target operating system
*/
@TargetAttributeType(name = VISIBLE_OS_ATTRIBUTE_NAME)
default String getOperatingSystem() {
return getTypedAttributeNowByName(OS_ATTRIBUTE_NAME, String.class, "");
return getTypedAttributeNowByName(VISIBLE_OS_ATTRIBUTE_NAME, String.class, "");
}
/**
@ -112,8 +116,9 @@ public interface TargetEnvironment<T extends TargetEnvironment<T>> extends Typed
*
* @return the target endianness
*/
@TargetAttributeType(name = VISIBLE_ENDIAN_ATTRIBUTE_NAME)
default String getEndian() {
return getTypedAttributeNowByName(ENDIAN_ATTRIBUTE_NAME, String.class, "");
return getTypedAttributeNowByName(VISIBLE_ENDIAN_ATTRIBUTE_NAME, String.class, "");
}
// TODO: Devices? File System?

View file

@ -19,9 +19,10 @@ import java.util.List;
import ghidra.dbg.DebuggerTargetObjectIface;
import ghidra.dbg.attributes.TypedTargetObjectRef;
import ghidra.dbg.target.schema.TargetAttributeType;
/**
* The object can emit events affecting itself and its successors
* An object that can emit events affecting itself and its successors
*
* <p>
* Most often, this interface is supported by the (root) session.
@ -120,6 +121,36 @@ public interface TargetEventScope<T extends TargetEventScope<T>> extends TypedTa
SIGNAL,
}
/**
* If applicable, get the process producing the last reported event
*
* <p>
* TODO: This is currently the hexadecimal PID. It should really be a ref to the process object.
*
* <p>
* TODO: Since the event thread will be a successor of the event process, this may not be
* needed, but perhaps keep it for convenience.
*
* @return the process or reference
*/
@TargetAttributeType(name = EVENT_PROCESS_ATTRIBUTE_NAME, hidden = true)
public default /*TODO: TypedTargetObjectRef<? extends TargetProcess<?>>*/ String getEventProcess() {
return getTypedAttributeNowByName(EVENT_PROCESS_ATTRIBUTE_NAME, String.class, null);
}
/**
* If applicable, get the thread producing the last reported event
*
* <p>
* TODO: This is currently the hexadecimal TID. It should really be a ref to the thread object.
*
* @return the thread or reference
*/
@TargetAttributeType(name = EVENT_THREAD_ATTRIBUTE_NAME, hidden = true)
public default /*TODO: TypedTargetObjectRef<? extends TargetThread<?>>*/ String getEventThread() {
return getTypedAttributeNowByName(EVENT_THREAD_ATTRIBUTE_NAME, String.class, null);
}
public interface TargetEventScopeListener extends TargetObjectListener {
/**
* An event affecting a target in this scope has occurred

View file

@ -16,7 +16,11 @@
package ghidra.dbg.target;
import ghidra.dbg.DebuggerTargetObjectIface;
import ghidra.dbg.target.schema.TargetAttributeType;
/**
* An object which has an execution life cycle
*/
@DebuggerTargetObjectIface("ExecutionStateful")
public interface TargetExecutionStateful<T extends TargetExecutionStateful<T>>
extends TypedTargetObject<T> {
@ -101,6 +105,7 @@ public interface TargetExecutionStateful<T extends TargetExecutionStateful<T>>
/**
* The object is alive and executing
*
* <p>
* "Running" is loosely defined. For example, with respect to a thread, it may indicate the
* thread is currently executing, waiting on an event, or scheduled for execution. It does
* not necessarily mean it is executing on a CPU at this exact moment.
@ -125,6 +130,7 @@ public interface TargetExecutionStateful<T extends TargetExecutionStateful<T>>
/**
* The object is no longer alive
*
* <p>
* The object still exists but no longer represents something alive. This could be used for
* stale handles to objects which may still be queried (e.g., for a process exit code), or
* e.g., a GDB "Inferior" which could be re-used to launch or attach to another process.
@ -146,19 +152,46 @@ public interface TargetExecutionStateful<T extends TargetExecutionStateful<T>>
}
};
/**
* Check if this state implies the object is alive
*
* @return true if alive
*/
public abstract boolean isAlive();
/**
* Check if this state implies the object is running
*
* @return true if running
*/
public abstract boolean isRunning();
/**
* Check if this state implies the object is stopped
*
* @return true if stopped
*/
public abstract boolean isStopped();
}
/**
* Get the current execution state of this object
*
* @return the state
*/
@TargetAttributeType(name = STATE_ATTRIBUTE_NAME, required = true, hidden = true)
public default TargetExecutionState getExecutionState() {
return getTypedAttributeNowByName(STATE_ATTRIBUTE_NAME, TargetExecutionState.class,
TargetExecutionState.STOPPED);
}
public interface TargetExecutionStateListener extends TargetObjectListener {
/**
* The object has entered a different execution state
*
* @param object the object
* @param state the new state
*/
default void executionStateChanged(TargetExecutionStateful<?> object,
TargetExecutionState state) {
}

View file

@ -20,7 +20,16 @@ import java.util.concurrent.CompletableFuture;
import ghidra.dbg.DebugModelConventions;
import ghidra.dbg.DebuggerTargetObjectIface;
import ghidra.dbg.attributes.TargetObjectRef;
import ghidra.dbg.target.schema.TargetAttributeType;
/**
* An object having a designated "focus"
*
* <p>
* Focus is usually communicated via various UI hints, but also semantically implies that actions
* taken within this scope apply to the focused object. The least confusing option is to implement
* this at the root, but that need not always be the case.
*/
@DebuggerTargetObjectIface("FocusScope")
public interface TargetFocusScope<T extends TargetFocusScope<T>> extends TypedTargetObject<T> {
enum Private {
@ -37,9 +46,10 @@ public interface TargetFocusScope<T extends TargetFocusScope<T>> extends TypedTa
/**
* Focus on the given object
*
* -obj- must be successor of this scope. The debugger may reject or ignore the request for any
* reason. If the debugger cannot focus the given object, it should attempt to do so for each
* ancestor until it succeeds or reaches this focus scope.
* <p>
* {@code obj} must be successor of this scope. The debugger may reject or ignore the request
* for any reason. If the debugger cannot focus the given object, it should attempt to do so for
* each ancestor until it succeeds or reaches this focus scope.
*
* @param obj the object to receive focus
* @return a future which completes upon successfully changing focus.
@ -51,6 +61,7 @@ public interface TargetFocusScope<T extends TargetFocusScope<T>> extends TypedTa
*
* @return a reference to the focused object or {@code null} if no object is focused.
*/
@TargetAttributeType(name = FOCUS_ATTRIBUTE_NAME, required = true, hidden = true)
default TargetObjectRef getFocus() {
return getTypedAttributeNowByName(FOCUS_ATTRIBUTE_NAME, TargetObjectRef.class, null);
}

View file

@ -20,9 +20,10 @@ import java.util.concurrent.CompletableFuture;
import ghidra.dbg.DebuggerTargetObjectIface;
import ghidra.dbg.target.TargetConsole.Channel;
import ghidra.dbg.target.TargetConsole.TargetTextConsoleListener;
import ghidra.dbg.target.schema.TargetAttributeType;
/**
* A command interpreter, usually that of an external debugger
* A command interpreter, usually that of a native debugger
*/
@DebuggerTargetObjectIface("Interpreter")
public interface TargetInterpreter<T extends TargetInterpreter<T>> extends TypedTargetObject<T> {
@ -76,6 +77,7 @@ public interface TargetInterpreter<T extends TargetInterpreter<T>> extends Typed
*
* @return the current prompt
*/
@TargetAttributeType(name = PROMPT_ATTRIBUTE_NAME, required = true, hidden = true)
public default String getPrompt() {
return getTypedAttributeNowByName(PROMPT_ATTRIBUTE_NAME, String.class, ">");
}

View file

@ -20,6 +20,9 @@ import java.util.concurrent.CompletableFuture;
import ghidra.dbg.DebuggerTargetObjectIface;
import ghidra.dbg.target.TargetExecutionStateful.TargetExecutionState;
/**
* A target that can be interrupted
*/
@DebuggerTargetObjectIface("Interruptible")
public interface TargetInterruptible<T extends TargetInterruptible<T>>
extends TypedTargetObject<T> {
@ -35,6 +38,7 @@ public interface TargetInterruptible<T extends TargetInterruptible<T>>
/**
* Interrupt the target object
*
* <p>
* Typically, this breaks, i.e., stops, all target objects in scope of the receiver. Note the
* command completes when the interrupt has been sent, whether or not it actually stopped
* anything. Users wishing to confirm execution has stopped should wait for the target object to

View file

@ -19,6 +19,9 @@ import java.util.concurrent.CompletableFuture;
import ghidra.dbg.DebuggerTargetObjectIface;
/**
* A target which can be killed (terminated)
*/
@DebuggerTargetObjectIface("Killable")
public interface TargetKillable<T extends TargetKillable<T>> extends TypedTargetObject<T> {
enum Private {

View file

@ -22,9 +22,10 @@ import java.util.concurrent.CompletableFuture;
import ghidra.dbg.DebuggerTargetObjectIface;
import ghidra.dbg.target.TargetMethod.ParameterDescription;
import ghidra.dbg.target.TargetMethod.TargetParameterMap;
import ghidra.dbg.target.schema.TargetAttributeType;
/**
* An interface which indicates this object is capable of launching targets.
* An interface which indicates this object is capable of launching targets
*
* <p>
* The targets this launcher creates ought to appear in its successors.
@ -130,6 +131,7 @@ public interface TargetLauncher<T extends TargetLauncher<T>> extends TypedTarget
}
}
@TargetAttributeType(name = TargetMethod.PARAMETERS_ATTRIBUTE_NAME, required = true, fixed = true, hidden = true)
default public TargetParameterMap getParameters() {
return TargetMethod.getParameters(this);
}

View file

@ -17,6 +17,7 @@ package ghidra.dbg.target;
import ghidra.dbg.DebuggerTargetObjectIface;
import ghidra.dbg.attributes.TypedTargetObjectRef;
import ghidra.dbg.target.schema.TargetAttributeType;
import ghidra.program.model.address.AddressRange;
@DebuggerTargetObjectIface("MemoryRegion")
@ -36,18 +37,44 @@ public interface TargetMemoryRegion<T extends TargetMemoryRegion<T>> extends Typ
String EXECUTABLE_ATTRIBUTE_NAME = PREFIX_INVISIBLE + "executable";
String MEMORY_ATTRIBUTE_NAME = PREFIX_INVISIBLE + "memory";
/**
* Get the address range representing this region
*
* @return the range
*/
@TargetAttributeType(name = RANGE_ATTRIBUTE_NAME, required = true, hidden = true)
public default AddressRange getRange() {
return getTypedAttributeNowByName(RANGE_ATTRIBUTE_NAME, AddressRange.class, null);
}
/**
* Check if this region is readable
*
* @return true if read is permitted
*/
@TargetAttributeType(name = READABLE_ATTRIBUTE_NAME, required = true, hidden = true)
public default boolean isReadable() {
return getTypedAttributeNowByName(READABLE_ATTRIBUTE_NAME, Boolean.class, false);
}
/**
* Check if this region is writable
*
* @return true if write is permitted
*/
@TargetAttributeType(name = WRITABLE_ATTRIBUTE_NAME, required = true, hidden = true)
public default boolean isWritable() {
return getTypedAttributeNowByName(WRITABLE_ATTRIBUTE_NAME, Boolean.class, false);
}
/**
* Check if this region is executable
*
* @return true if execute is permitted
*/
@TargetAttributeType(name = EXECUTABLE_ATTRIBUTE_NAME, required = true, hidden = true)
public default boolean isExecutable() {
return getTypedAttributeNowByName(EXECUTABLE_ATTRIBUTE_NAME, Boolean.class, false);
}
@ -66,6 +93,7 @@ public interface TargetMemoryRegion<T extends TargetMemoryRegion<T>> extends Typ
*
* @return a reference to the memory
*/
@TargetAttributeType(name = MEMORY_ATTRIBUTE_NAME, required = true, fixed = true, hidden = true)
public default TypedTargetObjectRef<? extends TargetMemory<?>> getMemory() {
return getTypedRefAttributeNowByName(MEMORY_ATTRIBUTE_NAME, TargetMemory.tclass, null);
}

View file

@ -22,12 +22,14 @@ import java.util.stream.Stream;
import ghidra.dbg.DebuggerTargetObjectIface;
import ghidra.dbg.error.DebuggerIllegalArgumentException;
import ghidra.dbg.target.schema.TargetAttributeType;
import ghidra.dbg.util.CollectionUtils.AbstractEmptyMap;
import ghidra.dbg.util.CollectionUtils.AbstractNMap;
/**
* A marker interface which indicates a method on an object
* An object which can be invoked as a method
*
* TODO: Should parameters and return type be something incorporated into Schemas?
*/
@DebuggerTargetObjectIface("Method")
public interface TargetMethod<T extends TargetMethod<T>> extends TypedTargetObject<T> {
@ -47,19 +49,42 @@ public interface TargetMethod<T extends TargetMethod<T>> extends TypedTargetObje
* A description of a method parameter
*
* <p>
* TODO: Most of this should probably eventually go into {@link TargetMethod}
* <p>
* TODO: For convenience, these should be programmable via annotations.
* <P>
* TODO: Should this be incorporated into schemas?
*
* @param <T> the type of the parameter
*/
class ParameterDescription<T> {
/**
* Create a parameter
*
* @param <T> the type of the parameter
* @param type the class representing the type of the parameter
* @param name the name of the parameter
* @param required true if this parameter must be provided
* @param defaultValue the default value of this parameter
* @param display the human-readable name of this parameter
* @param description the human-readable description of this parameter
* @return the new parameter description
*/
public static <T> ParameterDescription<T> create(Class<T> type, String name,
boolean required, T defaultValue, String display, String description) {
return new ParameterDescription<>(type, name, required, defaultValue, display,
description, List.of());
}
/**
* Create a parameter having enumerated choices
*
* @param <T> the type of the parameter
* @param type the class representing the type of the parameter
* @param name the name of the parameter
* @param choices the non-empty set of choices
* @param display the human-readable name of this parameter
* @param description the human-readable description of this parameter
* @return the new parameter description
*/
public static <T> ParameterDescription<T> choices(Class<T> type, String name,
Collection<T> choices, String display, String description) {
T defaultValue = choices.iterator().next();
@ -266,6 +291,13 @@ public interface TargetMethod<T extends TargetMethod<T>> extends TypedTargetObje
return valid;
}
/**
* A convenience method used by {@link TargetLauncher} as a stopgap until "launch" becomes a
* {@link TargetMethod}.
*
* @param obj the object having a "parameters" attribute.
* @return the parameter map
*/
static TargetParameterMap getParameters(TargetObject obj) {
return obj.getTypedAttributeNowByName(PARAMETERS_ATTRIBUTE_NAME,
TargetParameterMap.class, TargetParameterMap.of());
@ -274,11 +306,9 @@ public interface TargetMethod<T extends TargetMethod<T>> extends TypedTargetObje
/**
* Get the parameter descriptions of this method
*
* <p>
* TODO: This attribute needs better type checking. Probably delay for schemas.
*
* @return the name-description map of parameters
*/
@TargetAttributeType(name = PARAMETERS_ATTRIBUTE_NAME, required = true, fixed = true, hidden = true)
default public TargetParameterMap getParameters() {
return getParameters(this);
}
@ -293,22 +323,13 @@ public interface TargetMethod<T extends TargetMethod<T>> extends TypedTargetObje
*
* @return the return type
*/
@TargetAttributeType(name = RETURN_TYPE_ATTRIBUTE_NAME, required = true, fixed = true, hidden = true)
default public Class<?> getReturnType() {
return getTypedAttributeNowByName(RETURN_TYPE_ATTRIBUTE_NAME, Class.class,
Object.class);
}
/**
* Check if extra parameters are allowed
*
* <p>
* If not allowed, any named parameter not in the descriptions is considered an error.
*
* @return true if extras allowed, false if not
*/
default public boolean allowsExtra() {
return false;
}
// TODO: Allow extra parameters, i.e., varargs?
/**
* Invoke the method with the given arguments

View file

@ -17,10 +17,15 @@ package ghidra.dbg.target;
import ghidra.async.TypeSpec;
import ghidra.dbg.DebuggerTargetObjectIface;
import ghidra.dbg.target.schema.TargetAttributeType;
import ghidra.program.model.address.AddressRange;
/**
* A binary module loaded by the debugger
* A binary module loaded by the target and/or debugger
*
* <p>
* If the debugger cares to parse the modules for section information, those sections should be
* presented as successors to the module.
*/
@DebuggerTargetObjectIface("Module")
public interface TargetModule<T extends TargetModule<T>> extends TypedTargetObject<T> {
@ -49,8 +54,9 @@ public interface TargetModule<T extends TargetModule<T>> extends TypedTargetObje
*
* @return the base address, or {@code null}
*/
@TargetAttributeType(name = VISIBLE_RANGE_ATTRIBUTE_NAME, required = true)
public default AddressRange getRange() {
return getTypedAttributeNowByName(RANGE_ATTRIBUTE_NAME, AddressRange.class, null);
return getTypedAttributeNowByName(VISIBLE_RANGE_ATTRIBUTE_NAME, AddressRange.class, null);
}
/**
@ -58,6 +64,7 @@ public interface TargetModule<T extends TargetModule<T>> extends TypedTargetObje
*
* @return the module name
*/
@TargetAttributeType(name = MODULE_NAME_ATTRIBUTE_NAME, required = true, hidden = true)
public default String getModuleName() {
return getTypedAttributeNowByName(MODULE_NAME_ATTRIBUTE_NAME, String.class, null);
}

View file

@ -18,17 +18,20 @@ package ghidra.dbg.target;
import java.util.concurrent.CompletableFuture;
import ghidra.dbg.DebuggerTargetObjectIface;
import ghidra.dbg.target.schema.TargetAttributeType;
import ghidra.lifecycle.Experimental;
/**
* A place for modules to reside
*
* <p>
* Also a hint interface which helps the user of the client locate modules which apply to a given
* target object
*
* <p>
* TODO: Experiment with the idea of "synthetic modules" as presented by {@code dbgeng.dll}. Is
* there a similar idea in GDB? This could allow us to expose Ghidra's symbol table to the connected
* debugger.
* there a similar idea in GDB? This could allow us to expose Ghidra's symbol table and types to the
* native debugger.
*/
@DebuggerTargetObjectIface("ModuleContainer")
public interface TargetModuleContainer<T extends TargetModuleContainer<T>>
@ -45,6 +48,7 @@ public interface TargetModuleContainer<T extends TargetModuleContainer<T>>
String SUPPORTS_SYNTHETIC_MODULES_ATTRIBUTE_NAME =
PREFIX_INVISIBLE + "supports_synthetic_modules";
@TargetAttributeType(name = SUPPORTS_SYNTHETIC_MODULES_ATTRIBUTE_NAME, fixed = true, hidden = true)
@Experimental
public default boolean supportsSyntheticModules() {
return getTypedAttributeNowByName(SUPPORTS_SYNTHETIC_MODULES_ATTRIBUTE_NAME, Boolean.class,

View file

@ -24,6 +24,7 @@ import ghidra.dbg.DebugModelConventions;
import ghidra.dbg.DebuggerTargetObjectIface;
import ghidra.dbg.attributes.TargetNamedDataTypeRef;
import ghidra.dbg.attributes.TypedTargetObjectRef;
import ghidra.dbg.target.schema.TargetAttributeType;
import ghidra.dbg.util.TargetDataTypeConverter;
/**
@ -37,8 +38,10 @@ import ghidra.dbg.util.TargetDataTypeConverter;
* <li>{@code union}</li>
* </ul>
*
* <p>
* Other types, e.g., pointers, arrays, are modeled as attributes.
*
* <p>
* See {@link TargetDataTypeConverter} to get a grasp of the conventions
*
* @param <T> the type of this object
@ -61,7 +64,7 @@ public interface TargetNamedDataType<T extends TargetNamedDataType<T>>
ENUM, FUNCTION, STRUCT, TYPEDEF, UNION;
}
String NAMED_DATA_TYPE_KIND_ATTRIBUTE_NAME = PREFIX_INVISIBLE + "kind";
String NAMED_DATA_TYPE_KIND_ATTRIBUTE_NAME = PREFIX_INVISIBLE + "type_kind";
String ENUM_BYTE_LENGTH_ATTRIBUTE_NAME = PREFIX_INVISIBLE + "byte_length";
String NAMESPACE_ATTRIBUTE_NAME = PREFIX_INVISIBLE + "namespace";
@ -71,6 +74,7 @@ public interface TargetNamedDataType<T extends TargetNamedDataType<T>>
/**
* Get the members of this data type in order.
*
* <p>
* While it is most common for members to be immediate children of the type, that is not
* necessarily the case.
*
@ -85,12 +89,14 @@ public interface TargetNamedDataType<T extends TargetNamedDataType<T>>
/**
* Get the namespace for this data type.
*
* <p>
* While it is most common for a data type to be an immediate child of its namespace, that is
* not necessarily the case. This method is a reliable and type-safe means of obtaining that
* namespace.
*
* @return a reference to the namespace
*/
@TargetAttributeType(name = NAMESPACE_ATTRIBUTE_NAME, required = true, fixed = true, hidden = true)
default TypedTargetObjectRef<? extends TargetDataTypeNamespace<?>> getNamespace() {
return getTypedRefAttributeNowByName(NAMESPACE_ATTRIBUTE_NAME,
TargetDataTypeNamespace.tclass, null);
@ -101,8 +107,14 @@ public interface TargetNamedDataType<T extends TargetNamedDataType<T>>
*
* @return the kind
*/
default NamedDataTypeKind getKind() {
@TargetAttributeType(name = NAMED_DATA_TYPE_KIND_ATTRIBUTE_NAME, required = true, fixed = true, hidden = true)
default NamedDataTypeKind getTypeKind() {
return getTypedAttributeNowByName(NAMED_DATA_TYPE_KIND_ATTRIBUTE_NAME,
NamedDataTypeKind.class, null);
}
@TargetAttributeType(name = ENUM_BYTE_LENGTH_ATTRIBUTE_NAME, fixed = true, hidden = true)
default Integer getEnumByteLength() {
return getTypedAttributeNowByName(ENUM_BYTE_LENGTH_ATTRIBUTE_NAME, Integer.class, null);
}
}

View file

@ -17,6 +17,7 @@ package ghidra.dbg.target;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
import ghidra.async.AsyncFence;
import ghidra.async.AsyncUtils;
@ -25,9 +26,12 @@ import ghidra.dbg.attributes.TargetObjectRef;
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;
import ghidra.lifecycle.Internal;
import ghidra.util.Msg;
/**
@ -157,15 +161,79 @@ import ghidra.util.Msg;
*/
public interface TargetObject extends TargetObjectRef {
Set<Class<? extends TargetObject>> 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<String, Class<? extends TargetObject>> INTERFACES_BY_NAME = initInterfacesByName();
/**
* Initializer for {@link #INTERFACES_BY_NAME}
*
* @return interfaces indexed by name
*/
@Internal
static Map<String, Class<? extends TargetObject>> initInterfacesByName() {
return ALL_INTERFACES.stream()
.collect(Collectors.toUnmodifiableMap(
DebuggerObjectModel::requireIfaceName, i -> i));
}
static List<Class<? extends TargetObject>> getInterfacesByName(
Collection<String> names) {
return names.stream()
.filter(INTERFACES_BY_NAME::containsKey)
.map(INTERFACES_BY_NAME::get)
.collect(Collectors.toList());
}
@Deprecated
String PREFIX_INVISIBLE = "_";
String DISPLAY_ATTRIBUTE_NAME = PREFIX_INVISIBLE + "display";
String SHORT_DISPLAY_ATTRIBUTE_NAME = PREFIX_INVISIBLE + "short_display";
String KIND_ATTRIBUTE_NAME = PREFIX_INVISIBLE + "kind";
String UPDATE_MODE_ATTRIBUTE_NAME = PREFIX_INVISIBLE + "update_mode";
String ORDER_ATTRIBUTE_NAME = PREFIX_INVISIBLE + "order";
// TODO: Should these belong to a new TargetValue interface?
String MODIFIED_ATTRIBUTE_NAME = PREFIX_INVISIBLE + "modified";
String TYPE_ATTRIBUTE_NAME = PREFIX_INVISIBLE + "type";
String UPDATE_MODE_ATTRIBUTE_NAME = PREFIX_INVISIBLE + "update_mode";
String VALUE_ATTRIBUTE_NAME = PREFIX_INVISIBLE + "value";
String ORDER_ATTRIBUTE_NAME = PREFIX_INVISIBLE + "order";
enum Protected {
;
@ -270,6 +338,15 @@ public interface TargetObject extends TargetObjectRef {
*/
public String getTypeHint();
/**
* Get this object's schema.
*
* @return the schema
*/
public default TargetObjectSchema getSchema() {
return EnumerableTargetObjectSchema.OBJECT;
}
/**
* Get the interfaces this object actually supports, and that the client recognizes.
*
@ -349,6 +426,7 @@ public interface TargetObject extends TargetObjectRef {
*
* @return the display description
*/
@TargetAttributeType(name = DISPLAY_ATTRIBUTE_NAME, hidden = true)
public default String getDisplay() {
return getTypedAttributeNowByName(DISPLAY_ATTRIBUTE_NAME, String.class, getName());
}
@ -358,10 +436,26 @@ public interface TargetObject extends TargetObjectRef {
*
* @return the display description
*/
@TargetAttributeType(name = SHORT_DISPLAY_ATTRIBUTE_NAME, hidden = true)
public default String getShortDisplay() {
return getTypedAttributeNowByName(SHORT_DISPLAY_ATTRIBUTE_NAME, String.class, getDisplay());
}
/**
* Get a hint to the "kind" of object this represents.
*
* <p>
* This is useful when the native debugger presents a comparable tree-like model. If this object
* is simply proxying an object from that model, and that model provides additional type
* information that would not otherwise be encoded in this model.
*
* @return the kind of the object
*/
@TargetAttributeType(name = KIND_ATTRIBUTE_NAME, fixed = true, hidden = true)
public default String getKind() {
return getTypedAttributeNowByName(KIND_ATTRIBUTE_NAME, String.class, getDisplay());
}
/**
* Get the element update mode for this object
*
@ -382,11 +476,77 @@ public interface TargetObject extends TargetObjectRef {
*
* @return the update mode
*/
@TargetAttributeType(name = UPDATE_MODE_ATTRIBUTE_NAME, hidden = true)
public default TargetUpdateMode getUpdateMode() {
return getTypedAttributeNowByName(UPDATE_MODE_ATTRIBUTE_NAME, TargetUpdateMode.class,
TargetUpdateMode.UNSOLICITED);
}
/**
* A custom ordinal for positioning this item on screen
*
* <p>
* Ordinarily, children are ordered by key, attributes followed by elements. The built-in
* comparator does a decent job ordering them, so long as indices keep a consistent format among
* siblings. In some cases, however, especially with query-style methods, the same objects (and
* thus keys) need to be presented with an alternative ordering. This attribute can be used by
* model implementations to recommend an alternative ordering, where siblings are instead sorted
* according to this ordinal.
*
* @return the recommended display position for this element
*/
@TargetAttributeType(name = ORDER_ATTRIBUTE_NAME, hidden = true)
public default Integer getOrder() {
return getTypedAttributeNowByName(ORDER_ATTRIBUTE_NAME, Integer.class, null);
}
/**
* For values, check if it was "recently" modified
*
* <p>
* TODO: This should probably be moved to a new {@code TargetType} interface.
*
* <p>
* "Recently" generally means since (or as a result of ) the last event affecting a target in
* the same scope. This is mostly used as a UI hint, to bring the user's attention to modified
* values.
*
* @return true if modified, false if not
*/
@TargetAttributeType(name = MODIFIED_ATTRIBUTE_NAME, hidden = true)
public default Boolean isModified() {
return getTypedAttributeNowByName(MODIFIED_ATTRIBUTE_NAME, Boolean.class, null);
}
/**
* For values, get the type of the value
*
* <p>
* TODO: This should probably be moved to a new {@code TargetType} interface. How does this
* differ from "kind" when both are present? Can this be a {@link TargetNamedDataType} instead?
* Though, I suppose that would imply the object is a value in the target execution state, e.g.,
* a register, variable, field, etc.
*
* @return the name of the type
*/
@TargetAttributeType(name = TYPE_ATTRIBUTE_NAME, hidden = true)
public default String getType() {
return getTypedAttributeNowByName(TYPE_ATTRIBUTE_NAME, String.class, null);
}
/**
* For values, get the actual value
*
* <p>
* TODO: This should probably be moved to a new {@code TargetType} interface.
*
* @return the value
*/
@TargetAttributeType(name = VALUE_ATTRIBUTE_NAME, hidden = true)
public default Object getValue() {
return getCachedAttribute(VALUE_ATTRIBUTE_NAME);
}
@Override
default CompletableFuture<? extends TargetObject> fetch() {
return CompletableFuture.completedFuture(this);
@ -452,8 +612,8 @@ public interface TargetObject extends TargetObjectRef {
* Cast the named attribute to the given type, if possible
*
* <p>
* If the attribute value is {@code null} or cannot be cast to the given type, an error message
* is printed, and the fallback value is returned.
* If the attribute value is {@code null} or cannot be cast to the given type, the fallback
* value is returned.
*
* @param <T> the expected type of the attribute
* @param name the name of the attribute
@ -462,8 +622,9 @@ public interface TargetObject extends TargetObjectRef {
* @return the value casted to the expected type, or the fallback value
*/
public default <T> T getTypedAttributeNowByName(String name, Class<T> cls, T fallback) {
AttributeSchema as = getSchema().getAttributeSchema(name);
Object obj = getCachedAttribute(name);
return ValueUtils.expectType(obj, cls, this, name, fallback);
return ValueUtils.expectType(obj, cls, this, name, fallback, as.isRequired());
}
/**

View file

@ -17,13 +17,16 @@ package ghidra.dbg.target;
import ghidra.dbg.DebuggerTargetObjectIface;
import ghidra.dbg.target.TargetExecutionStateful.TargetExecutionState;
import ghidra.dbg.target.schema.TargetAttributeType;
/**
* A marker interface which indicates a process, usually on a host operating system
*
* <p>
* If this object does not support {@link TargetExecutionStateful}, then its mere existence in the
* model implies that it is {@link TargetExecutionState#ALIVE}. TODO: Should allow association, but
* that may have to wait until schemas are introduced.
* model implies that it is {@link TargetExecutionState#ALIVE}. TODO: Should allow association via
* convention to a different {@link TargetExecutionStateful}, but that may have to wait until
* schemas are introduced.
*/
@DebuggerTargetObjectIface("Process")
public interface TargetProcess<T extends TargetProcess<T>> extends TypedTargetObject<T> {
@ -37,4 +40,9 @@ public interface TargetProcess<T extends TargetProcess<T>> extends TypedTargetOb
@SuppressWarnings({ "unchecked", "rawtypes" })
Class<Private.Cls> tclass = (Class) TargetProcess.class;
@TargetAttributeType(name = PID_ATTRIBUTE_NAME, hidden = true)
public default Long getPid() {
return getTypedAttributeNowByName(PID_ATTRIBUTE_NAME, Long.class, null);
}
}

View file

@ -18,11 +18,13 @@ package ghidra.dbg.target;
import ghidra.async.TypeSpec;
import ghidra.dbg.DebuggerTargetObjectIface;
import ghidra.dbg.attributes.TypedTargetObjectRef;
import ghidra.dbg.target.schema.TargetAttributeType;
import ghidra.dbg.util.PathUtils;
/**
* This is a description of a register
*
* <p>
* This describes a register abstractly. It does not represent the actual value of a register. For
* values, see {@link TargetRegisterBank}. The description and values are separated, since the
* descriptions typically apply to the entire platform, and so can be presented just once.
@ -45,6 +47,7 @@ public interface TargetRegister<T extends TargetRegister<T>> extends TypedTarget
/**
* Get the container of this register.
*
* <p>
* While it is most common for a register descriptor to be an immediate child of its container,
* that is not necessarily the case. In fact, some models may present sub-registers as children
* of another register. This method is a reliable and type-safe means of obtaining the
@ -52,6 +55,7 @@ public interface TargetRegister<T extends TargetRegister<T>> extends TypedTarget
*
* @return a reference to the container
*/
@TargetAttributeType(name = CONTAINER_ATTRIBUTE_NAME, required = true, fixed = true, hidden = true)
default TypedTargetObjectRef<? extends TargetRegisterContainer<?>> getContainer() {
return getTypedRefAttributeNowByName(CONTAINER_ATTRIBUTE_NAME,
TargetRegisterContainer.tclass, null);
@ -62,6 +66,7 @@ public interface TargetRegister<T extends TargetRegister<T>> extends TypedTarget
*
* @return the length of the register
*/
@TargetAttributeType(name = LENGTH_ATTRIBUTE_NAME, required = true, fixed = true, hidden = true)
default int getBitLength() {
return getTypedAttributeNowByName(LENGTH_ATTRIBUTE_NAME, Integer.class, 0);
}
@ -70,11 +75,10 @@ public interface TargetRegister<T extends TargetRegister<T>> extends TypedTarget
public default String getIndex() {
return PathUtils.isIndex(getPath()) ? PathUtils.getIndex(getPath())
: PathUtils.getKey(getPath());
//return PathUtils.getIndex(getPath());
}
// TODO: Any typical type assignment or structure definition?
// TODO: (Related) Should describe if typically a pointer?
// TODO: What if the register is memory-mapped?
// TODO: What if the register is memory-mapped? Probably map client-side.
}

View file

@ -23,10 +23,15 @@ import java.util.stream.Collectors;
import ghidra.dbg.DebuggerTargetObjectIface;
import ghidra.dbg.attributes.TypedTargetObjectRef;
import ghidra.dbg.error.DebuggerRegisterAccessException;
import ghidra.dbg.target.schema.TargetAttributeType;
import ghidra.util.Msg;
/**
* A bank of registers on the debug target
*
* <p>
* The bank allows access to registers' <em>values</em>; whereas, a {@link TargetRegisterContainer}
* allows reflection of the registers' names and structures.
*/
@DebuggerTargetObjectIface("RegisterBank")
public interface TargetRegisterBank<T extends TargetRegisterBank<T>> extends TypedTargetObject<T> {
@ -46,6 +51,7 @@ public interface TargetRegisterBank<T extends TargetRegisterBank<T>> extends Typ
*
* @return a future which completes with object
*/
@TargetAttributeType(name = DESCRIPTIONS_ATTRIBUTE_NAME)
@SuppressWarnings("unchecked")
public default TypedTargetObjectRef<? extends TargetRegisterContainer<?>> getDescriptions() {
return getTypedRefAttributeNowByName(DESCRIPTIONS_ATTRIBUTE_NAME,
@ -216,6 +222,6 @@ public interface TargetRegisterBank<T extends TargetRegisterBank<T>> extends Typ
* @param updates a name-value map of updated registers
*/
default void registersUpdated(TargetRegisterBank<?> bank, Map<String, byte[]> updates) {
};
}
}
}

View file

@ -41,6 +41,7 @@ public interface TargetRegisterContainer<T extends TargetRegisterContainer<T>>
/**
* Get the register descriptions in this container
*
* <p>
* While it is most common for registers to be immediate children of the container, that is not
* necessarily the case. In fact, some models may present sub-registers as children of another
* register. This method must return all registers (including sub-registers, if applicable) in

View file

@ -36,6 +36,7 @@ public interface TargetResumable<T extends TargetResumable<T>> extends TypedTarg
/**
* Resume execution of this object
*
* <p>
* Note, this would be called "continue" if it weren't a Java reserved word :( .
*
* @return a future which completes upon successful resumption

View file

@ -17,17 +17,20 @@ package ghidra.dbg.target;
import ghidra.dbg.DebuggerTargetObjectIface;
import ghidra.dbg.attributes.TypedTargetObjectRef;
import ghidra.dbg.target.schema.TargetAttributeType;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressRange;
/**
* An allocated section of a binary module
*
* <p>
* Note that the model should only present those sections which are allocated in memory. Otherwise
* strange things may happen, such as zero-length ranges (which AddressRange hates), or overlapping
* ranges (which Trace hates).
*
* TODO: Present all sections, but include start, length, isAllocated instead?
* <p>
* TODO: Present all sections, but include isAllocated?
*/
@DebuggerTargetObjectIface("Section")
public interface TargetSection<T extends TargetSection<T>> extends TypedTargetObject<T> {
@ -42,13 +45,14 @@ public interface TargetSection<T extends TargetSection<T>> extends TypedTargetOb
String MODULE_ATTRIBUTE_NAME = PREFIX_INVISIBLE + "module";
String RANGE_ATTRIBUTE_NAME = PREFIX_INVISIBLE + "range";
String VISIBLE_RANGE_ATTRIBUTE_NAME = "range";
String VISIBLE_RANGE_ATTRIBUTE_NAME = "range";
/**
* Get the module to which this section belongs
*
* @return the owning module
*/
@TargetAttributeType(name = MODULE_ATTRIBUTE_NAME, required = true, fixed = true, hidden = true)
@SuppressWarnings("unchecked")
public default TypedTargetObjectRef<? extends TargetModule<?>> getModule() {
return getTypedRefAttributeNowByName(MODULE_ATTRIBUTE_NAME, TargetModule.class, null);
@ -62,8 +66,9 @@ public interface TargetSection<T extends TargetSection<T>> extends TypedTargetOb
*
* @return the range
*/
@TargetAttributeType(name = VISIBLE_RANGE_ATTRIBUTE_NAME, required = true, fixed = true)
public default AddressRange getRange() {
return getTypedAttributeNowByName(RANGE_ATTRIBUTE_NAME, AddressRange.class, null);
return getTypedAttributeNowByName(VISIBLE_RANGE_ATTRIBUTE_NAME, AddressRange.class, null);
}
/**

View file

@ -24,6 +24,7 @@ import ghidra.dbg.DebuggerTargetObjectIface;
/**
* Represents the execution stack, as unwound into frames by the debugger
*
* <p>
* Conventionally, if the debugger can also unwind register values, then each frame should present a
* register bank. Otherwise, the same object presenting this stack should present the register bank.
*/
@ -41,6 +42,7 @@ public interface TargetStack<T extends TargetStack<T>> extends TypedTargetObject
/**
* Get the frames in this stack
*
* <p>
* While it is most common for frames to be immediate children of the stack, that is not
* necessarily the case.
*

View file

@ -16,10 +16,11 @@
package ghidra.dbg.target;
import ghidra.dbg.DebuggerTargetObjectIface;
import ghidra.dbg.target.schema.TargetAttributeType;
import ghidra.program.model.address.Address;
/**
* One frame of an execution stack
* One frame (call record) of an execution stack
*/
@DebuggerTargetObjectIface("StackFrame")
public interface TargetStackFrame<T extends TargetStackFrame<T>> extends TypedTargetObject<T> {
@ -37,10 +38,12 @@ public interface TargetStackFrame<T extends TargetStackFrame<T>> extends TypedTa
/**
* Get the program counter for the frame
*
* <p>
* Note for some platforms, this may differ from the value in the program counter register.
*
* @return a future completing with the address of the executing (or next) instruction.
*/
@TargetAttributeType(name = PC_ATTRIBUTE_NAME, required = true, hidden = true)
public default Address getProgramCounter() {
return getTypedAttributeNowByName(PC_ATTRIBUTE_NAME, Address.class, Address.NO_ADDRESS);
}

View file

@ -22,10 +22,14 @@ import java.util.concurrent.CompletableFuture;
import ghidra.dbg.DebuggerTargetObjectIface;
import ghidra.dbg.target.TargetExecutionStateful.TargetExecutionState;
import ghidra.dbg.target.schema.TargetAttributeType;
import ghidra.dbg.util.CollectionUtils;
import ghidra.dbg.util.CollectionUtils.AbstractEmptySet;
import ghidra.lifecycle.Experimental;
/**
* A target whose execution can be single stepped
*/
@DebuggerTargetObjectIface("Steppable")
public interface TargetSteppable<T extends TargetSteppable<T>> extends TypedTargetObject<T> {
enum Private {
@ -202,6 +206,7 @@ public interface TargetSteppable<T extends TargetSteppable<T>> extends TypedTarg
*
* @return the set of supported multi-step operations
*/
@TargetAttributeType(name = SUPPORTED_STEP_KINDS_ATTRIBUTE_NAME, required = true, fixed = true, hidden = true)
public default TargetStepKindSet getSupportedStepKinds() {
return getTypedAttributeNowByName(SUPPORTED_STEP_KINDS_ATTRIBUTE_NAME,
TargetStepKindSet.class, TargetStepKindSet.of());

View file

@ -21,6 +21,7 @@ import ghidra.async.TypeSpec;
import ghidra.dbg.DebuggerTargetObjectIface;
import ghidra.dbg.attributes.TargetDataType;
import ghidra.dbg.attributes.TypedTargetObjectRef;
import ghidra.dbg.target.schema.TargetAttributeType;
import ghidra.dbg.util.TargetDataTypeConverter;
import ghidra.program.model.address.Address;
import ghidra.program.model.data.DataType;
@ -52,6 +53,7 @@ public interface TargetSymbol<T extends TargetSymbol<T>> extends TypedTargetObje
*
* @return a future completing with the type
*/
@TargetAttributeType(name = DATA_TYPE_ATTRIBUTE_NAME, fixed = true, hidden = true)
public default TargetDataType getDataType() {
return getTypedAttributeNowByName(DATA_TYPE_ATTRIBUTE_NAME, TargetDataType.class,
TargetDataType.UNDEFINED1);
@ -70,8 +72,9 @@ public interface TargetSymbol<T extends TargetSymbol<T>> extends TypedTargetObje
/**
* Get the type of this symbol converted to a Ghidra data type
*
* Each call to this variant creates a new {@link TargetDataTypeConverter}, and so does not take
* full advantage of its internal cache.
* <p>
* WARNING: Each call to this variant creates a new {@link TargetDataTypeConverter}, and so does
* not take full advantage of its internal cache.
*
* @see #getGhidraDataType(TargetDataTypeConverter)
*/
@ -83,6 +86,7 @@ public interface TargetSymbol<T extends TargetSymbol<T>> extends TypedTargetObje
* Get the type of this symbol converted to a Ghidra data type, without using a
* {@link DataTypeManager}
*
* <p>
* It is better to use variants with a {@link DataTypeManager} directly, rather than using no
* manager and cloning to one later. The former will select types suited to the data
* organization of the destination manager. Using no manager and cloning later will use
@ -97,6 +101,7 @@ public interface TargetSymbol<T extends TargetSymbol<T>> extends TypedTargetObje
/**
* Determine whether the symbol has a constant value
*
* <p>
* Constant symbols include but are not limited to C enumeration constants. Otherwise, the
* symbol's value refers to an address, which stores a presumably non-constant value.
*
@ -109,11 +114,14 @@ public interface TargetSymbol<T extends TargetSymbol<T>> extends TypedTargetObje
/**
* Get the value of the symbol
*
* <p>
* If the symbol is a constant, then the returned address will be in the constant space.
*
* @return the address or constant value of the symbol, or {@link Address#NO_ADDRESS} if
* unspecified
*/
@Override
// NB. TargetObject defines this attribute
public default Address getValue() {
return getTypedAttributeNowByName(VALUE_ATTRIBUTE_NAME, Address.class, Address.NO_ADDRESS);
}
@ -121,11 +129,13 @@ public interface TargetSymbol<T extends TargetSymbol<T>> extends TypedTargetObje
/**
* If known, get the size of the symbol in bytes
*
* <p>
* The size of a symbol is usually not required at runtime, so a user should be grateful if this
* is known. If it is not known, or the symbol does not have a size, this method returns 0.
*
* @return the size of the symbol, or 0 if unspecified
*/
@TargetAttributeType(name = SIZE_ATTRIBUTE_NAME, fixed = true, hidden = true)
public default long getSize() {
return getTypedAttributeNowByName(SIZE_ATTRIBUTE_NAME, Long.class, 0L);
}
@ -133,12 +143,14 @@ public interface TargetSymbol<T extends TargetSymbol<T>> extends TypedTargetObje
/**
* Get the namespace for this symbol.
*
* <p>
* While it is most common for a symbol to be an immediate child of its namespace, that is not
* necessarily the case. This method is a reliable and type-safe means of obtaining that
* namespace.
*
* @return a reference to the namespace
*/
@TargetAttributeType(name = NAMESPACE_ATTRIBUTE_NAME, required = true, fixed = true, hidden = true)
public default TypedTargetObjectRef<? extends TargetSymbolNamespace<?>> getNamespace() {
return getTypedRefAttributeNowByName(NAMESPACE_ATTRIBUTE_NAME, TargetSymbolNamespace.tclass,
null);

View file

@ -24,7 +24,8 @@ import ghidra.dbg.DebuggerTargetObjectIface;
/**
* A container of symbols
*
* The debugger should present these in as granular of unit as possible. Consider a desktop
* <p>
* The debugger should present these in as granular of units as possible. Consider a desktop
* application, for example. The debugger should present each module as a namespace rather than the
* entire target (or worse, the entire session) as a single namespace.
*/
@ -43,6 +44,7 @@ public interface TargetSymbolNamespace<T extends TargetSymbolNamespace<T>>
/**
* Get the symbols in this namespace.
*
* <p>
* While it is most common for symbols to be immediate children of the namespace, that is not
* necessarily the case.
*

View file

@ -16,10 +16,12 @@
package ghidra.dbg.target;
import ghidra.dbg.DebuggerTargetObjectIface;
import ghidra.dbg.target.schema.TargetAttributeType;
/**
* A marker interface which indicates a thread, usually within a process
*
* <p>
* This object must be associated with a suitable {@link TargetExecutionStateful}. In most cases,
* the object should just implement it.
*/
@ -35,4 +37,9 @@ public interface TargetThread<T extends TargetThread<T>> extends TypedTargetObje
@SuppressWarnings({ "unchecked", "rawtypes" })
Class<Private.Cls> tclass = (Class) TargetThread.class;
@TargetAttributeType(name = TID_ATTRIBUTE_NAME, hidden = true)
public default Integer getTid() {
return getTypedAttributeNowByName(TID_ATTRIBUTE_NAME, Integer.class, null);
}
}

View file

@ -19,6 +19,13 @@ import java.util.concurrent.CompletableFuture;
import ghidra.dbg.attributes.TypedTargetObjectRef;
/**
* A common interface for all {@link TargetObject} interfaces, as well as implementations of
* {@link TargetObject}, so that {@link #fetch()} simply returns this object.
*
* @param <T> the type of this object. It should be a type variable extending this object's type if
* the class is meant to be further extended.
*/
public interface TypedTargetObject<T extends TypedTargetObject<T>>
extends TargetObject, TypedTargetObjectRef<T> {
@Override

View file

@ -0,0 +1,329 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.dbg.target.schema;
import java.lang.reflect.*;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.reflect.TypeUtils;
import ghidra.dbg.DebuggerTargetObjectIface;
import ghidra.dbg.attributes.TargetObjectRef;
import ghidra.dbg.attributes.TypedTargetObjectRef;
import ghidra.dbg.target.TargetObject;
import ghidra.dbg.target.schema.DefaultTargetObjectSchema.DefaultAttributeSchema;
import ghidra.dbg.target.schema.TargetObjectSchema.AttributeSchema;
import ghidra.dbg.target.schema.TargetObjectSchema.SchemaName;
import utilities.util.reflection.ReflectionUtilities;
public class AnnotatedSchemaContext extends DefaultSchemaContext {
static <T> Stream<Class<? extends T>> filterBounds(Class<T> base, Stream<Class<?>> bounds) {
return bounds.filter(base::isAssignableFrom).map(c -> c.asSubclass(base));
}
static Stream<Class<?>> resolveUpperBounds(Class<? extends TargetObjectRef> cls, Type type) {
if (type == null) {
return Stream.empty();
}
if (type instanceof Class<?>) {
return Stream.of((Class<?>) type);
}
if (type instanceof ParameterizedType) {
ParameterizedType pt = (ParameterizedType) type;
return resolveUpperBounds(cls, pt.getRawType());
}
if (type instanceof WildcardType) {
WildcardType wt = (WildcardType) type;
return Stream.of(TypeUtils.getImplicitUpperBounds(wt))
.flatMap(t -> resolveUpperBounds(cls, t));
}
if (type instanceof TypeVariable) {
TypeVariable<?> tv = (TypeVariable<?>) type;
Object decl = tv.getGenericDeclaration();
if (decl instanceof Class<?>) {
Class<?> declCls = (Class<?>) decl;
Map<TypeVariable<?>, Type> args = TypeUtils.getTypeArguments(cls, declCls);
Type argTv = args.get(tv);
if (argTv != null) {
return resolveUpperBounds(cls, argTv);
}
}
return Stream.of(TypeUtils.getImplicitBounds(tv))
.flatMap(t -> resolveUpperBounds(cls, t));
}
/**
* NB. This method is always called with a type taken from "T extends TargetObject" So, an
* array should never be possible.
*/
throw new AssertionError("Cannot handle type: " + type);
}
static Set<Class<? extends TargetObject>> getBoundsOfFetchElements(
Class<? extends TargetObjectRef> cls) {
try {
Method method = cls.getMethod("fetchElements", new Class<?>[] { boolean.class });
Type ret = method.getGenericReturnType();
Map<TypeVariable<?>, Type> argsCf =
TypeUtils.getTypeArguments(ret, CompletableFuture.class);
Type typeCfT = argsCf.get(CompletableFuture.class.getTypeParameters()[0]);
Map<TypeVariable<?>, Type> argsMap = TypeUtils.getTypeArguments(typeCfT, Map.class);
Type typeCfMapV = argsMap.get(Map.class.getTypeParameters()[1]);
return filterBounds(TargetObject.class, resolveUpperBounds(cls, typeCfMapV))
.collect(Collectors.toSet());
}
catch (NoSuchMethodException | SecurityException e) {
throw new AssertionError(e);
}
}
static Set<Class<? extends TargetObject>> getBoundsOfObjectAttributeGetter(
Class<? extends TargetObject> cls, Method getter) {
Class<?> retCls = getter.getReturnType();
if (TargetObject.class.isAssignableFrom(retCls)) {
return Set.of(retCls.asSubclass(TargetObject.class));
}
Type ret = getter.getGenericReturnType();
Map<TypeVariable<?>, Type> argsTtor =
TypeUtils.getTypeArguments(ret, TypedTargetObjectRef.class);
if (argsTtor != null) {
Type typeTtorT = argsTtor.get(TypedTargetObjectRef.class.getTypeParameters()[0]);
return filterBounds(TargetObject.class, resolveUpperBounds(cls, typeTtorT))
.collect(Collectors.toSet());
}
if (TargetObjectRef.class.isAssignableFrom(retCls)) {
return Set.of(TargetObject.class);
}
throw new IllegalArgumentException("Getter " + getter +
" for attribute must return primitive or subclass of " +
TargetObjectRef.class);
}
protected final Map<Class<? extends TargetObject>, SchemaName> namesByClass =
new LinkedHashMap<>();
protected final Map<Class<? extends TargetObject>, TargetObjectSchema> schemasByClass =
new LinkedHashMap<>();
protected SchemaName nameFromAnnotatedClass(Class<? extends TargetObject> cls) {
synchronized (namesByClass) {
return namesByClass.computeIfAbsent(cls, c -> {
TargetObjectSchemaInfo info = cls.getAnnotation(TargetObjectSchemaInfo.class);
if (info == null) {
// TODO: Compile-time validation?
throw new IllegalArgumentException("Class " + cls + " is not annotated with @" +
TargetObjectSchemaInfo.class.getSimpleName());
}
String name = info.name();
if (name.equals("")) {
return new SchemaName(cls.getSimpleName());
}
return new SchemaName(name);
});
}
}
protected void addPublicMethodsFromClass(SchemaBuilder builder,
Class<? extends TargetObject> declCls, Class<? extends TargetObject> cls) {
for (Method declMethod : declCls.getDeclaredMethods()) {
int mod = declMethod.getModifiers();
if (!Modifier.isPublic(mod)) {
continue;
}
TargetAttributeType at = declMethod.getAnnotation(TargetAttributeType.class);
if (at == null) {
continue;
}
// In case it was overridden with a more-specific return type
Method method;
try {
method = cls.getMethod(declMethod.getName(), declMethod.getParameterTypes());
}
catch (NoSuchMethodException | SecurityException e) {
throw new AssertionError(e);
}
AttributeSchema attrSchema;
try {
attrSchema =
attributeSchemaFromAnnotatedMethod(declCls, method, at);
}
catch (IllegalArgumentException e) {
throw new IllegalArgumentException(
"Could not get schema name for attribute accessor " + method + " in " + cls, e);
}
if (attrSchema != null) {
builder.addAttributeSchema(attrSchema, declMethod);
}
}
}
protected TargetObjectSchema fromAnnotatedClass(Class<? extends TargetObject> cls) {
synchronized (namesByClass) {
SchemaName name = nameFromAnnotatedClass(cls);
return schemasByClass.computeIfAbsent(cls, c -> {
TargetObjectSchemaInfo info = cls.getAnnotation(TargetObjectSchemaInfo.class);
SchemaBuilder builder = builder(name);
Set<Class<?>> allParents = ReflectionUtilities.getAllParents(cls);
for (Class<?> parent : allParents) {
DebuggerTargetObjectIface ifaceAnnot =
parent.getAnnotation(DebuggerTargetObjectIface.class);
if (ifaceAnnot != null) {
builder.addInterface(parent.asSubclass(TargetObject.class));
}
}
builder.setCanonicalContainer(info.canonicalContainer());
boolean sawDefaultElementType = false;
for (TargetElementType et : info.elements()) {
if (et.index().equals("")) {
sawDefaultElementType = true;
}
builder.addElementSchema(et.index(), nameFromClass(et.type()), et);
}
if (!sawDefaultElementType) {
Set<Class<? extends TargetObject>> bounds = getBoundsOfFetchElements(cls);
if (bounds.size() != 1) {
// TODO: Compile-time validation?
throw new IllegalArgumentException(
"Could not identify unique element class: " + bounds);
}
else {
Class<? extends TargetObject> bound = bounds.iterator().next();
SchemaName schemaName;
try {
schemaName = nameFromClass(bound);
}
catch (IllegalArgumentException e) {
throw new IllegalArgumentException(
"Could not get schema name from bound " + bound + " of " +
cls + ".fetchElements()",
e);
}
builder.setDefaultElementSchema(schemaName);
}
}
addPublicMethodsFromClass(builder, cls, cls);
for (Class<?> parent : allParents) {
if (TargetObject.class.isAssignableFrom(parent)) {
addPublicMethodsFromClass(builder, parent.asSubclass(TargetObject.class),
cls);
}
}
for (TargetAttributeType at : info.attributes()) {
AttributeSchema attrSchema = attributeSchemaFromAnnotation(at);
builder.addAttributeSchema(attrSchema, at);
}
return builder.buildAndAdd();
});
}
}
protected String attributeNameFromBean(String beanName, boolean isBool) {
beanName = isBool
? StringUtils.removeStartIgnoreCase(beanName, "is")
: StringUtils.removeStartIgnoreCase(beanName, "get");
if (beanName.equals("")) {
throw new IllegalArgumentException("Attribute getter must have a name");
}
return beanName
.replaceAll("([A-Z]+)([A-Z][a-z])", "$1_$2")
.replaceAll("([a-z])([A-Z])", "$1_$2")
.toLowerCase();
}
protected AttributeSchema attributeSchemaFromAnnotation(TargetAttributeType at) {
return new DefaultAttributeSchema(at.name(), nameFromClass(at.type()),
at.required(), at.fixed(), at.hidden());
}
protected AttributeSchema attributeSchemaFromAnnotatedMethod(Class<? extends TargetObject> cls,
Method method, TargetAttributeType at) {
if (method.getParameterCount() != 0) {
// TODO: Compile-time validation?
throw new IllegalArgumentException(
"Non-getter method " + method + " is annotated with @" +
TargetAttributeType.class.getSimpleName());
}
String name = at.name();
Class<?> ret = method.getReturnType();
if (name.equals("")) {
name = attributeNameFromBean(method.getName(),
EnumerableTargetObjectSchema.BOOL.getTypes().contains(ret));
}
SchemaName primitiveName =
EnumerableTargetObjectSchema.nameForPrimitive(ret);
if (primitiveName != null) {
return new DefaultAttributeSchema(name, primitiveName,
at.required(), at.fixed(), at.hidden());
}
Set<Class<? extends TargetObject>> bounds = getBoundsOfObjectAttributeGetter(cls, method);
if (bounds.size() != 1) {
// TODO: Compile-time validation?
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());
}
protected SchemaName nameFromClass(Class<?> cls) {
SchemaName name = EnumerableTargetObjectSchema.nameForPrimitive(cls);
if (name != null) {
return name;
}
if (TargetObject.class.isAssignableFrom(cls)) {
return nameFromAnnotatedClass(cls.asSubclass(TargetObject.class));
}
throw new IllegalArgumentException("Cannot figure schema from class: " + cls);
}
protected void fillDependencies() {
while (fillDependenciesRound()) {
// Action is side-effect of predicate
}
}
protected boolean fillDependenciesRound() {
Set<Class<? extends TargetObject>> classes = new HashSet<>(namesByClass.keySet());
classes.removeAll(schemasByClass.keySet());
if (classes.isEmpty()) {
return false;
}
for (Class<? extends TargetObject> cls : classes) {
fromAnnotatedClass(cls);
}
return true;
}
public TargetObjectSchema getSchemaForClass(Class<? extends TargetObject> cls) {
TargetObjectSchema schema = fromAnnotatedClass(cls);
fillDependencies();
return schema;
}
}

View file

@ -0,0 +1,79 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.dbg.target.schema;
import java.util.*;
import ghidra.dbg.target.schema.TargetObjectSchema.SchemaName;
public class DefaultSchemaContext implements SchemaContext {
private final Map<SchemaName, TargetObjectSchema> schemas = new LinkedHashMap<>();
public DefaultSchemaContext() {
for (EnumerableTargetObjectSchema schema : EnumerableTargetObjectSchema.values()) {
schemas.put(schema.getName(), schema);
}
}
public SchemaBuilder builder(SchemaName name) {
return new SchemaBuilder(this, name);
}
public synchronized void putSchema(TargetObjectSchema schema) {
if (schemas.containsKey(schema.getName())) {
throw new IllegalArgumentException("Name already in context: " + schema.getName());
}
schemas.put(schema.getName(), schema);
}
@Override
public synchronized TargetObjectSchema getSchema(SchemaName name) {
return Objects.requireNonNull(schemas.get(name), "No such schema name: " + name);
}
@Override
public synchronized Set<TargetObjectSchema> getAllSchemas() {
// Set.copyOf does not preserve iteration order
return Collections.unmodifiableSet(new LinkedHashSet<>(schemas.values()));
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
for (TargetObjectSchema s : schemas.values()) {
sb.append(s + "\n");
}
return sb.toString();
}
@Override
public boolean equals(Object obj) {
if (obj instanceof DefaultSchemaContext) {
DefaultSchemaContext that = (DefaultSchemaContext) obj;
return Objects.equals(this.schemas, that.schemas);
}
if (obj instanceof SchemaContext) {
SchemaContext that = (SchemaContext) obj;
return this.schemas.values().equals(that.getAllSchemas());
}
return false;
}
@Override
public int hashCode() {
return schemas.hashCode();
}
}

View file

@ -0,0 +1,278 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.dbg.target.schema;
import java.util.*;
import ghidra.dbg.DebuggerObjectModel;
import ghidra.dbg.target.TargetObject;
public class DefaultTargetObjectSchema
implements TargetObjectSchema, Comparable<DefaultTargetObjectSchema> {
private static final String INDENT = " ";
protected static class DefaultAttributeSchema
implements AttributeSchema, Comparable<DefaultAttributeSchema> {
private final String name;
private final SchemaName schema;
private final boolean isRequired;
private final boolean isFixed;
private final boolean isHidden;
public DefaultAttributeSchema(String name, SchemaName schema, boolean isRequired,
boolean isFixed, boolean isHidden) {
if (name.equals("") && isRequired) {
throw new IllegalArgumentException(
"The default attribute schema cannot be required");
}
this.name = name;
this.schema = schema;
this.isRequired = isRequired;
this.isFixed = isFixed;
this.isHidden = isHidden;
}
@Override
public String toString() {
return String.format("<attr name=%s schema=%s required=%s fixed=%s hidden=%s>",
name, schema, isRequired, isFixed, isHidden);
}
/**
* {@inheritDoc}
*
* <p>
* Generally speaking, object identity is sufficient for checking equality in production;
* however, this method is provided for testing equality between an actual and expected
* attribute schema.
*/
@Override
public boolean equals(Object obj) {
if (!(obj instanceof DefaultAttributeSchema)) {
return false;
}
DefaultAttributeSchema that = (DefaultAttributeSchema) obj;
if (!Objects.equals(this.name, that.name)) {
return false;
}
if (!Objects.equals(this.schema, that.schema)) {
return false;
}
if (this.isRequired != that.isRequired) {
return false;
}
if (this.isFixed != that.isFixed) {
return false;
}
if (this.isHidden != that.isHidden) {
return false;
}
return true;
}
@Override
public int hashCode() {
return name.hashCode();
}
@Override
public int compareTo(DefaultAttributeSchema o) {
return name.compareTo(o.name);
}
@Override
public String getName() {
return name;
}
@Override
public SchemaName getSchema() {
return schema;
}
@Override
public boolean isRequired() {
return isRequired;
}
@Override
public boolean isFixed() {
return isFixed;
}
@Override
public boolean isHidden() {
return isHidden;
}
}
private final SchemaContext context;
private final SchemaName name;
private final Class<?> type;
private final Set<Class<? extends TargetObject>> interfaces;
private final boolean isCanonicalContainer;
private final Map<String, SchemaName> elementSchemas;
private final SchemaName defaultElementSchema;
private final Map<String, AttributeSchema> attributeSchemas;
private final AttributeSchema defaultAttributeSchema;
DefaultTargetObjectSchema(SchemaContext context, SchemaName name, Class<?> type,
Set<Class<? extends TargetObject>> interfaces, boolean isCanonicalContainer,
Map<String, SchemaName> elementSchemas, SchemaName defaultElementSchema,
Map<String, AttributeSchema> attributeSchemas, AttributeSchema defaultAttributeSchema) {
this.context = context;
this.name = name;
this.type = type;
this.interfaces = Collections.unmodifiableSet(new LinkedHashSet<>(interfaces));
this.isCanonicalContainer = isCanonicalContainer;
this.elementSchemas = Collections.unmodifiableMap(new LinkedHashMap<>(elementSchemas));
this.defaultElementSchema = defaultElementSchema;
this.attributeSchemas = Collections.unmodifiableMap(new LinkedHashMap<>(attributeSchemas));
this.defaultAttributeSchema = defaultAttributeSchema;
}
@Override
public SchemaContext getContext() {
return context;
}
@Override
public SchemaName getName() {
return name;
}
@Override
public Class<?> getType() {
return type;
}
@Override
public Set<Class<? extends TargetObject>> getInterfaces() {
return interfaces;
}
@Override
public boolean isCanonicalContainer() {
return isCanonicalContainer;
}
@Override
public Map<String, SchemaName> getElementSchemas() {
return elementSchemas;
}
@Override
public SchemaName getDefaultElementSchema() {
return defaultElementSchema;
}
@Override
public Map<String, AttributeSchema> getAttributeSchemas() {
return attributeSchemas;
}
@Override
public AttributeSchema getDefaultAttributeSchema() {
return defaultAttributeSchema;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
toString(sb);
return sb.toString();
}
protected void toString(StringBuilder sb) {
sb.append("schema ");
sb.append(name);
if (isCanonicalContainer) {
sb.append("*");
}
sb.append(" {\n" + INDENT);
sb.append("ifaces = [");
for (Class<? extends TargetObject> iface : interfaces) {
sb.append(DebuggerObjectModel.requireIfaceName(iface));
sb.append(" ");
}
sb.append("]\n" + INDENT);
sb.append("elements = ");
sb.append(elementSchemas);
sb.append(" default " + defaultElementSchema);
sb.append("\n" + INDENT);
sb.append("attributes = ");
sb.append(attributeSchemas);
sb.append(" default " + defaultAttributeSchema);
sb.append("\n}");
}
/**
* {@inheritDoc}
*
* <p>
* Generally speaking, object identity is sufficient for checking equality in production;
* however, this method is provided for testing equality between an actual and expected schema.
* Furthermore, this tests more for "content equality" than it does schema equivalence. In
* particular, if the two entries being compared come from different contexts, then, even though
* they may refer to child schemas by the same name, those child schemas may not be equivalent.
* This test will consider them "equal," even though they specify different overall schemas.
* Testing for true equivalence has too many nuances to consider here: What if they come from
* different contexts? What if they refer to different schemas, but those schemas are
* equivalent? etc.
*/
@Override
public boolean equals(Object obj) {
if (!(obj instanceof DefaultTargetObjectSchema)) {
return false;
}
DefaultTargetObjectSchema that = (DefaultTargetObjectSchema) obj;
if (!Objects.equals(this.name, that.name)) {
return false;
}
if (!Objects.equals(this.type, that.type)) {
return false;
}
if (!Objects.equals(this.interfaces, that.interfaces)) {
return false;
}
if (this.isCanonicalContainer != that.isCanonicalContainer) {
return false;
}
if (!Objects.equals(this.elementSchemas, that.elementSchemas)) {
return false;
}
if (!Objects.equals(this.defaultElementSchema, that.defaultElementSchema)) {
return false;
}
if (!Objects.equals(this.attributeSchemas, that.attributeSchemas)) {
return false;
}
if (!Objects.equals(this.defaultAttributeSchema, that.defaultAttributeSchema)) {
return false;
}
return true;
}
@Override
public int hashCode() {
return name.hashCode();
}
@Override
public int compareTo(DefaultTargetObjectSchema o) {
return name.compareTo(o.name);
}
}

View file

@ -0,0 +1,192 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.dbg.target.schema;
import java.util.*;
import ghidra.dbg.attributes.TargetDataType;
import ghidra.dbg.attributes.TargetObjectRefList;
import ghidra.dbg.target.TargetAccessConditioned.TargetAccessibility;
import ghidra.dbg.target.TargetAttacher.TargetAttachKindSet;
import ghidra.dbg.target.TargetBreakpointContainer.TargetBreakpointKindSet;
import ghidra.dbg.target.TargetExecutionStateful.TargetExecutionState;
import ghidra.dbg.target.TargetMethod.TargetParameterMap;
import ghidra.dbg.target.TargetObject;
import ghidra.dbg.target.TargetObject.TargetUpdateMode;
import ghidra.dbg.target.TargetSteppable.TargetStepKindSet;
import ghidra.dbg.util.PathPattern;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressRange;
public enum EnumerableTargetObjectSchema implements TargetObjectSchema {
/**
* The top-most type descriptor
*
* <p>
* The described value can be any primitive or a {@link TargetObject}.
*/
ANY("ANY", Object.class) {
@Override
public SchemaName getDefaultElementSchema() {
return OBJECT.getName();
}
@Override
public AttributeSchema getDefaultAttributeSchema() {
return AttributeSchema.DEFAULT_ANY;
}
},
/**
* The least restrictive, but least informative object schema.
*
* <p>
* This requires nothing more than the described value to be a {@link TargetObject}.
*/
OBJECT("OBJECT", TargetObject.class) {
@Override
public SchemaName getDefaultElementSchema() {
return OBJECT.getName();
}
@Override
public AttributeSchema getDefaultAttributeSchema() {
return AttributeSchema.DEFAULT_ANY;
}
},
/**
* A type so restrictive nothing can satisfy it.
*
* <p>
* This is how a schema specifies that a particular key is not allowed. It is commonly used as
* the default attribute when only certain enumerated attributes are allowed. It is also used as
* the type for the children of primitives, since primitives cannot have successors.
*/
VOID("VOID", Void.class, void.class),
BOOL("BOOL", Boolean.class, boolean.class),
BYTE("BYTE", Byte.class, byte.class),
SHORT("SHORT", Short.class, short.class),
INT("INT", Integer.class, int.class),
LONG("LONG", Long.class, long.class),
STRING("STRING", String.class),
ADDRESS("ADDRESS", Address.class),
RANGE("RANGE", AddressRange.class),
DATA_TYPE("DATA_TYPE", TargetDataType.class),
LIST_OBJECT("LIST_OBJECT", TargetObjectRefList.class),
MAP_PARAMETERS("MAP_PARAMETERS", TargetParameterMap.class),
SET_ATTACH_KIND("SET_ATTACH_KIND", TargetAttachKindSet.class), // TODO: Limited built-in generics
SET_BREAKPOINT_KIND("SET_BREAKPOINT_KIND", TargetBreakpointKindSet.class),
SET_STEP_KIND("SET_STEP_KIND", TargetStepKindSet.class),
ACCESSIBILITY("ACCESSIBILITY", TargetAccessibility.class),
EXECUTION_STATE("EXECUTION_STATE", TargetExecutionState.class),
UPDATE_MODE("UPDATE_MODE", TargetUpdateMode.class);
private static class MinimalSchemaContext extends DefaultSchemaContext {
private static final SchemaContext INSTANCE = new MinimalSchemaContext();
}
/**
* Get a suitable schema for a given Java primitive class
*
* <p>
* The term "primitive" here is used in terms of object schemas, not in terms of Java types.
*
* @param cls the class, which may or may not be the boxed form
* @return the schema or null if no schema is suitable
*/
public static EnumerableTargetObjectSchema schemaForPrimitive(Class<?> cls) {
for (EnumerableTargetObjectSchema schema : EnumerableTargetObjectSchema.values()) {
if (schema.getTypes().contains(cls)) {
return schema;
}
}
return null;
}
/**
* Get the name of a suitable enumerable schema for a given Java class
*
* @see #schemaForPrimitive(Class)
* @param cls the class, which may or may no be the boxed form
* @return the name or null if no schema is suitable
*/
public static SchemaName nameForPrimitive(Class<?> cls) {
EnumerableTargetObjectSchema schema = schemaForPrimitive(cls);
return schema == null ? null : schema.getName();
}
private final SchemaName name;
private final List<Class<?>> types;
private EnumerableTargetObjectSchema(String name, Class<?>... types) {
this.name = new SchemaName(name);
this.types = List.of(types);
}
@Override
public SchemaContext getContext() {
return MinimalSchemaContext.INSTANCE;
}
@Override
public SchemaName getName() {
return name;
}
@Override
public Class<?> getType() {
return types.get(0);
}
public List<Class<?>> getTypes() {
return types;
}
@Override
public Set<Class<? extends TargetObject>> getInterfaces() {
return Set.of();
}
@Override
public boolean isCanonicalContainer() {
return false;
}
@Override
public Map<String, SchemaName> getElementSchemas() {
return Map.of();
}
@Override
public SchemaName getDefaultElementSchema() {
return VOID.getName();
}
@Override
public Map<String, AttributeSchema> getAttributeSchemas() {
return Map.of();
}
@Override
public AttributeSchema getDefaultAttributeSchema() {
return AttributeSchema.DEFAULT_VOID;
}
@Override
public void searchFor(Set<PathPattern> result, List<String> prefix, boolean parentIsCanonical,
Class<? extends TargetObject> type, boolean requireCanonical) {
return;
}
}

View file

@ -0,0 +1,170 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.dbg.target.schema;
import java.util.*;
import ghidra.dbg.target.TargetObject;
import ghidra.dbg.target.schema.TargetObjectSchema.AttributeSchema;
import ghidra.dbg.target.schema.TargetObjectSchema.SchemaName;
public class SchemaBuilder {
private final DefaultSchemaContext context;
private final SchemaName name;
private Class<?> type = TargetObject.class;
private Set<Class<? extends TargetObject>> interfaces = new LinkedHashSet<>();
private boolean isCanonicalContainer = false;
private Map<String, SchemaName> elementSchemas = new LinkedHashMap<>();
private SchemaName defaultElementSchema = EnumerableTargetObjectSchema.OBJECT.getName();
private Map<String, AttributeSchema> attributeSchemas = new LinkedHashMap<>();
private AttributeSchema defaultAttributeSchema = AttributeSchema.DEFAULT_ANY;
private Map<String, Object> elementOrigins = new LinkedHashMap<>();
private Map<String, Object> attributeOrigins = new LinkedHashMap<>();
public SchemaBuilder(DefaultSchemaContext context, SchemaName name) {
this.context = context;
this.name = name;
}
public SchemaBuilder setType(Class<?> type) {
this.type = type;
return this;
}
public Class<?> getType() {
return type;
}
public SchemaBuilder setInterfaces(Set<Class<? extends TargetObject>> interfaces) {
this.interfaces.clear();
this.interfaces.addAll(interfaces);
return this;
}
public Set<Class<? extends TargetObject>> getInterfaces() {
return Set.copyOf(interfaces);
}
public SchemaBuilder addInterface(Class<? extends TargetObject> iface) {
this.interfaces.add(iface);
return this;
}
public SchemaBuilder setCanonicalContainer(boolean isCanonicalContainer) {
this.isCanonicalContainer = isCanonicalContainer;
return this;
}
public boolean isCanonicalContaineration() {
return isCanonicalContainer;
}
public SchemaBuilder setElementSchemas(Map<String, SchemaName> elementSchemas) {
this.elementSchemas.clear();
this.elementSchemas.putAll(elementSchemas);
return this;
}
/**
* Define the schema for a child element
*
* @param index the index whose schema to define, or "" for the default
* @param schema the schema defining the element
* @return this builder
*/
public SchemaBuilder addElementSchema(String index, SchemaName schema, Object origin) {
if (index.equals("")) {
return setDefaultElementSchema(schema);
}
if (elementSchemas.containsKey(index)) {
throw new IllegalArgumentException("Duplicate element index '" + index +
"' origin1=" + elementOrigins.get(index) +
" origin2=" + origin);
}
elementSchemas.put(index, schema);
elementOrigins.put(index, origin);
return this;
}
public Map<String, SchemaName> getElementSchemas() {
return Map.copyOf(elementSchemas);
}
public SchemaBuilder setDefaultElementSchema(SchemaName defaultElementSchema) {
this.defaultElementSchema = defaultElementSchema;
return this;
}
public SchemaName getDefaultElementSchema() {
return defaultElementSchema;
}
public SchemaBuilder setAttributeSchemas(Map<String, AttributeSchema> attributeSchemas) {
this.attributeSchemas.clear();
this.attributeSchemas.putAll(attributeSchemas);
return this;
}
/**
* Define the schema for a child attribute.
*
* <p>
* If the attribute schema's name is empty, the given schema becomes the default attribute
* schema.
*
* @param schema the attribute schema to add to the definition
* @return this builder
*/
public SchemaBuilder addAttributeSchema(AttributeSchema schema, Object origin) {
if (schema.getName().equals("")) {
return setDefaultAttributeSchema(schema);
}
if (attributeSchemas.containsKey(schema.getName())) {
throw new IllegalArgumentException("Duplicate attribute name '" + schema.getName() +
"' origin1=" + attributeOrigins.get(schema.getName()) +
" origin2=" + origin);
}
attributeSchemas.put(schema.getName(), schema);
attributeOrigins.put(schema.getName(), origin);
return this;
}
public Map<String, AttributeSchema> getAttributeSchemas() {
return Map.copyOf(attributeSchemas);
}
public SchemaBuilder setDefaultAttributeSchema(AttributeSchema defaultAttributeSchema) {
this.defaultAttributeSchema = defaultAttributeSchema;
return this;
}
public AttributeSchema getDefaultAttributeSchema() {
return defaultAttributeSchema;
}
public TargetObjectSchema buildAndAdd() {
TargetObjectSchema schema = build();
context.putSchema(schema);
return schema;
}
public TargetObjectSchema build() {
return new DefaultTargetObjectSchema(context, name, type, interfaces, isCanonicalContainer,
elementSchemas, defaultElementSchema, attributeSchemas, defaultAttributeSchema);
}
}

View file

@ -0,0 +1,46 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.dbg.target.schema;
import java.util.Set;
import ghidra.dbg.target.schema.TargetObjectSchema.SchemaName;
/**
* A collection of related schemas
*/
public interface SchemaContext {
/**
* Resolve a schema in this context by name
*
* <p>
* Note that resolving a name generated outside of this context may have undefined results. In
* most cases, it will resolve to the schema whose name has the same string representation, but
* it might instead throw a {@link NullPointerException}.
*
* @param name the schema's name
* @return the schema
* @throws NullPointerException if no schema by the given name exists
*/
TargetObjectSchema getSchema(SchemaName name);
/**
* Collect all schemas in this context
*
* @return the set of all schemas
*/
Set<TargetObjectSchema> getAllSchemas();
}

View file

@ -0,0 +1,79 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.dbg.target.schema;
import java.lang.annotation.*;
import ghidra.dbg.target.TargetObject;
/**
* A schema annotation to describe a model attribute.
*
* <p>
* It can be used in {@link TargetObjectSchemaInfo#attributes()} or be applied to a public, possibly
* inherited, getter method for the attribute.
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface TargetAttributeType {
/**
* The name of the attribute
*
* <p>
* When used in {@link TargetObjectSchemaInfo#attributes()}, the default {@code ""} matches any
* attribute name and should rarely, if ever, be used. When applied to a getter, the default
* indicates the name should be derived from the method name, by removing {@code get}, and
* converting from {@code CamelCase} to {@code lower_case_with_underscores}.
*
* @return the attribute name
*/
String name() default "";
/**
* The Java class best representing the attribute's type.
*
* <p>
* When applied to a getter, {@code type} can be omitted. In that case, the getter's return type
* will be used to derive the attribute's schema instead.
*
* @return the type
*/
Class<?> type() default TargetObject.class;
/**
* True if the attribute must be set before the object exists.
*
* @return true if required, false if optional
*/
boolean required() default false;
/**
* True if the attribute can only be set once.
*
* @return true if fixed/final/immutable, false if mutable
*/
boolean fixed() default false;
/**
* Whether or not this attribute should be displayed by default
*
* <p>
* This is purely a UI hint and has no other semantic consequences.
*
* @return true if hidden, false if visible
*/
boolean hidden() default false;
}

View file

@ -0,0 +1,28 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.dbg.target.schema;
import java.lang.annotation.*;
import ghidra.dbg.target.TargetObject;
@Target({})
@Retention(RetentionPolicy.RUNTIME)
public @interface TargetElementType {
String index() default "";
Class<?> type() default TargetObject.class;
}

View file

@ -0,0 +1,573 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.dbg.target.schema;
import java.util.*;
import java.util.Map.Entry;
import java.util.stream.Collectors;
import ghidra.dbg.DebugModelConventions;
import ghidra.dbg.agent.DefaultTargetObject;
import ghidra.dbg.target.TargetObject;
import ghidra.dbg.target.schema.DefaultTargetObjectSchema.DefaultAttributeSchema;
import ghidra.dbg.util.CollectionUtils.Delta;
import ghidra.dbg.util.PathPattern;
import ghidra.dbg.util.PathUtils;
import ghidra.lifecycle.Internal;
import ghidra.util.Msg;
/**
* Type information for a particular value or {@link TargetObject}
*
* <p>
* This allows a client to inspect predictable aspects of a model before fetching any actual
* objects. This also helps a client understand where to listen for particular types of objects and
* comprehend the model's structure in general.
*
* <p>
* For a primitive type, the type is given by {@link #getType()}. For {@link TargetObject}s,
* supported interfaces are given by {@link #getInterfaces()}. The types of children are determined
* 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,
* which matches any key. Similarly, the wild-card index is {@code []}.
*/
public interface TargetObjectSchema {
/**
* An identifier for schemas within a context.
*
* This is essentially a wrapper on {@link String}, but typed so that strings and names cannot
* be accidentally interchanged.
*/
class SchemaName implements Comparable<SchemaName> {
private final String name;
/**
* Create an identifier with the given name
*
* <p>
* In most cases, this constructor should always be wrapped in a cache, e.g.,
* {@link Map#computeIfAbsent(Object, java.util.function.Function)}.
*
* @param name the name
*/
public SchemaName(String name) {
this.name = Objects.requireNonNull(name);
}
/**
* {@inheritDoc}
*
* @return the name
*/
@Override
public String toString() {
return name;
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof SchemaName)) {
return false;
}
SchemaName that = (SchemaName) obj;
if (!this.name.equals(that.name)) {
return false;
}
return true;
}
@Override
public int hashCode() {
return name.hashCode();
}
@Override
public int compareTo(SchemaName o) {
return this.name.compareTo(o.name);
}
}
/**
* Schema descriptor for a child attribute.
*/
interface AttributeSchema {
/**
* A descriptor suitable as a default that imposes no restrictions.
*/
AttributeSchema DEFAULT_ANY = new DefaultAttributeSchema("",
EnumerableTargetObjectSchema.ANY.getName(), false, false, true);
/**
* A descriptor suitable as a default that requires an object
*/
AttributeSchema DEFAULT_OBJECT = new DefaultAttributeSchema("",
EnumerableTargetObjectSchema.OBJECT.getName(), false, false, true);
/**
* A descriptor suitable as a default that forbids an attribute name
*/
AttributeSchema DEFAULT_VOID = new DefaultAttributeSchema("",
EnumerableTargetObjectSchema.VOID.getName(), false, true, true);
/**
* Get the name of the attribute
*
* @return the name of the attribute
*/
String getName();
/**
* Get the schema name for the named attribute
*
* @return the schema name
*/
SchemaName getSchema();
/**
* Check if the named attribute must always be present
*
* @return true if required, false if optional
*/
boolean isRequired();
/**
* Check if the named attribute can be modified
*
* @return true if immutable, false if mutable
*/
boolean isFixed();
/**
* Check if the named attribute should be displayed be default
*
* <p>
* This is purely a UI hint. It has no other semantic consequence.
*
* @return true if hidden, false if visible
*/
boolean isHidden();
}
/**
* Get the context of which this schema is a member
*
* <p>
* All schema names are resolved in this same context
*
* @return the context
*/
SchemaContext getContext();
/**
* Get the name of this schema
*
* @return the name
*/
SchemaName getName();
/**
* Get the Java class that best represents this type.
*
* <p>
* Note that this is either a primitive, or {@link TargetObject}. Even though an object
* implementation is necessarily a sub-type of {@link TargetObject}, for any object schema, this
* return {@link TargetObject}. Information about a "sub-type" of object is communicated via
* interfaces, element schemas, and attribute schemas.
*
* @return the Java class for this type
*/
Class<?> getType();
/**
* Get the minimum interfaces supported by a conforming object
*
* @return the set of required interfaces
*/
Set<Class<? extends TargetObject>> getInterfaces();
/**
* Check if this object is the canonical container for its elements
*
* <p>
* This is generally in reference to the default type of this object's elements. For example, if
* elements of this object are all expected to support the "Process" interface, then this is the
* canonical Process container. Any Process ought to have a (canonical) path in this container.
* Any other path referring to such a Process ought to be a link.
*
* <p>
* NOTE: the concept of links is still in incubation, as some native debugging APIs seem to have
* made it difficult to detect object identity. Additionally, it's possible a caller's first
* encounter with an object is not via its canonical path, and it may be difficult to assign a
* path having only the native-API-given object in hand.
*
* @return true if this is a canonical container, false otherwise
*/
boolean isCanonicalContainer();
/**
* Get the map of element indices to named schemas
*
* <p>
* It is uncommon for this map to be populated, since the elements of a given container are
* typically uniform in type. Nevertheless, there can be restrictions imposed on -- and
* information provided for -- specific indices.
*
* @return the map
*/
Map<String, SchemaName> getElementSchemas();
/**
* Get the default schema for elements
*
* <p>
* Since elements of a given container are typically uniform in type, this is the primary means
* of specifying element schemas.
*
* @return the default named schema
*/
default SchemaName getDefaultElementSchema() {
return EnumerableTargetObjectSchema.OBJECT.getName();
}
/**
* Get the named schema for a given element index
*
* <p>
* If there's a schema specified for the given index, that schema is taken. Otherwise, the
* default element schema is taken.
*
* @param index the index
* @return the named schema
*/
default SchemaName getElementSchema(String index) {
for (Entry<String, SchemaName> ent : getElementSchemas().entrySet()) {
if (ent.getKey().equals(index)) {
return ent.getValue();
}
}
return getDefaultElementSchema();
}
/**
* Get the map of attribute names to named schemas
*
* @return the map
*/
Map<String, AttributeSchema> getAttributeSchemas();
/**
* Get the default schema for attributes
*
* <p>
* Since the expected attributes and their respective schemas are generally enumerated, this
* most commonly returns {@link AttributeSchema#DEFAULT_ANY}, to allow unrestricted use of
* additional attributes, or {@link AttributeSchema#DEFAULT_VOID}, to forbid any additional
* attributes.
*
* @return the default attribute schema
*/
default AttributeSchema getDefaultAttributeSchema() {
return AttributeSchema.DEFAULT_ANY;
}
/**
* Get the attribute schema for a given attribute name
*
* <p>
* If there's a schema specified for the given name, that schema is taken. Otherwise, the
* default attribute schema is taken.
*
* @param name the name
* @return the attribute schema
*/
default AttributeSchema getAttributeSchema(String name) {
for (Entry<String, AttributeSchema> ent : getAttributeSchemas().entrySet()) {
if (ent.getKey().equals(name)) {
return ent.getValue();
}
}
return getDefaultAttributeSchema();
}
/**
* Get the named schema for a child having the given key
*
* @param key the key
* @return the named schema
*/
default SchemaName getChildSchemaName(String key) {
if (PathUtils.isIndex(key)) {
return getElementSchema(PathUtils.parseIndex(key));
}
return getAttributeSchema(key).getSchema();
}
/**
* Get the schema for a child having the given key
*
* <p>
* This is the preferred method for navigating a schema and computing the expected type of a
* child.
*
* @param key the key
* @return the schema
*/
default TargetObjectSchema getChildSchema(String key) {
SchemaName name = getChildSchemaName(key);
return getContext().getSchema(name);
}
/**
* Get the schema for a successor at the given (sub) path
*
* <p>
* If this is the schema of the root object, then this gives the schema of the object at the
* given path in the model.
*
* @param path the relative path from an object having this schema to the desired successor
* @return the schema for the successor
*/
default TargetObjectSchema getSuccessorSchema(List<String> path) {
if (path.isEmpty()) {
return this;
}
TargetObjectSchema childSchema = getChildSchema(path.get(0));
return childSchema.getSuccessorSchema(path.subList(1, path.size()));
}
/**
* Find (sub) path patterns that match objects implementing a given interface
*
* <p>
* Each returned path pattern accepts relative paths from an object having this schema to a
* successor implementing the interface.
*
* @param type the sub-type of {@link TargetObject} to search for
* @param requireCanonical only return patterns matching a canonical location for the type
* @return a set of patterns where such objects could be found
*/
default Set<PathPattern> searchFor(Class<? extends TargetObject> type,
boolean requireCanonical) {
if (type == TargetObject.class) {
throw new IllegalArgumentException("Must provide a specific interface");
}
Set<PathPattern> result = new LinkedHashSet<>();
searchFor(result, List.of(), false, type, requireCanonical);
return result;
}
@Internal // TODO: Make a separate internal interface?
default void searchFor(Set<PathPattern> result, List<String> prefix, boolean parentIsCanonical,
Class<? extends TargetObject> type, boolean requireCanonical) {
if (getInterfaces().contains(type) && parentIsCanonical) {
result.add(new PathPattern(prefix));
}
for (Entry<String, SchemaName> ent : getElementSchemas().entrySet()) {
List<String> extended = PathUtils.index(prefix, ent.getKey());
TargetObjectSchema elemSchema = getContext().getSchema(ent.getValue());
elemSchema.searchFor(result, extended, isCanonicalContainer(), type, requireCanonical);
}
List<String> deExtended = PathUtils.extend(prefix, "[]");
TargetObjectSchema deSchema = getContext().getSchema(getDefaultElementSchema());
deSchema.searchFor(result, deExtended, isCanonicalContainer(), type, requireCanonical);
for (Entry<String, AttributeSchema> ent : getAttributeSchemas().entrySet()) {
List<String> extended = PathUtils.extend(prefix, ent.getKey());
TargetObjectSchema attrSchema = getContext().getSchema(ent.getValue().getSchema());
attrSchema.searchFor(result, extended, isCanonicalContainer(), type, requireCanonical);
}
List<String> daExtended = PathUtils.extend(prefix, "");
TargetObjectSchema daSchema =
getContext().getSchema(getDefaultAttributeSchema().getSchema());
daSchema.searchFor(result, daExtended, isCanonicalContainer(), type, requireCanonical);
}
/**
* Check if the given key should be hidden for an object having this schema
*
* <p>
* Elements ought never to be hidden. Otherwise, this defers to the attribute schema. As a
* special case, if the attribute schema is {@link AttributeSchema#DEFAULT_ANY} or
* {@link AttributeSchema#DEFAULT_OBJECT}, then it checks if the attribute starts with
* {@link TargetObject#PREFIX_INVISIBLE}. That convention is deprecated, and no new code should
* rely on that prefix. The special case provides a transition point for client-side code that
* would like to use the schema's definition for controlling visibility, but still support
* models which have not implemented schemas.
*
* @param key the child key to check
* @return true if hidden
*/
default boolean isHidden(String key) {
if (PathUtils.isIndex(key)) {
return false;
}
AttributeSchema schema = getAttributeSchema(key);
if (schema == AttributeSchema.DEFAULT_ANY || schema == AttributeSchema.DEFAULT_OBJECT) {
// FIXME: Remove this hack once we stop depending on this prefix
return key.startsWith(TargetObject.PREFIX_INVISIBLE);
}
return schema.isHidden();
}
/**
* Verify that the given value is of this schema's required type and, if applicable, implements
* the required interfaces
*
* @param value the value
*/
default void validateTypeAndInterfaces(Object value, String key, boolean strict) {
Class<?> cls = value.getClass();
if (!getType().isAssignableFrom(cls)) {
String msg = key == null
? "Value " + value + " does not conform to required type " +
getType() + " of schema " + this
: "Value " + value + " for " + key + " does not conform to required type " +
getType() + " of schema " + this;
if (strict) {
throw new AssertionError(msg);
}
Msg.error(this, msg);
}
for (Class<? extends TargetObject> iface : getInterfaces()) {
if (!iface.isAssignableFrom(cls)) {
// TODO: Should this throw an exception, eventually?
String msg = "Value " + value + " does not implement required interface " +
iface + " of schema " + this;
if (strict) {
throw new AssertionError(msg);
}
Msg.error(this, msg);
}
}
}
/**
* Verify that all required attributes are present
*
* <p>
* Note: this should be called not at construction, but when the object is actually added to the
* model, e.g., when it appears in the "added" set of
* {@link DefaultTargetObject#setAttributes(Map, String)} called on its parent.
*/
default void validateRequiredAttributes(TargetObject object, boolean strict) {
Set<String> present = object.getCachedAttributes().keySet();
Set<String> missing = getAttributeSchemas()
.values()
.stream()
.filter(AttributeSchema::isRequired)
.map(AttributeSchema::getName)
.filter(a -> !present.contains(a))
.collect(Collectors.toSet());
if (!missing.isEmpty()) {
String msg = "Object " + object + " is missing required attributes " + missing +
" of schema " + this;
if (strict) {
throw new AssertionError(msg);
}
Msg.error(this, msg);
}
}
/**
* Verify that the given change does not cause a violation of the attribute schema
*
* <p>
* For attributes, there are multiple possibilities of violation:
* </p>
*
* <ul>
* <li>The type of an added attribute does not conform</li>
* <li>A required attribute is removed</li>
* <li>A fixed attribute is changed</li>
* </ul>
*
* @param delta the delta, before or after the fact
*/
default void validateAttributeDelta(TargetObject object, Delta<?, ?> delta, boolean strict) {
for (Map.Entry<String, ?> ent : delta.added.entrySet()) {
String key = ent.getKey();
Object value = ent.getValue();
AttributeSchema as = getAttributeSchema(key);
TargetObjectSchema schema = getContext().getSchema(as.getSchema());
/**
* TODO: There's some duplication of effort here, since canonical attributes will
* already have been checked at construction.
*/
schema.validateTypeAndInterfaces(value, key, strict);
if (value instanceof TargetObject) {
TargetObject ov = (TargetObject) value;
if (!PathUtils.isLink(object.getPath(), ent.getKey(), ov.getPath())) {
schema.validateRequiredAttributes(ov, strict);
}
}
}
// TODO: Creating these sets *every* change could be costly
// NB. "keysRemoved" does not include changed things, just removed
Set<String> violatesRequired = getAttributeSchemas().values()
.stream()
.filter(AttributeSchema::isRequired)
.map(AttributeSchema::getName)
.filter(delta.getKeysRemoved()::contains)
.collect(Collectors.toSet());
if (!violatesRequired.isEmpty()) {
String msg = "Object " + object + " removed required attributes " +
violatesRequired + " of schema " + this;
if (strict) {
throw new AssertionError(msg);
}
Msg.error(this, msg);
}
// TODO: Another set....
// NB. "removed" includes changed things
// NB. I don't care about "new" attributes, since those don't violate "fixed"
// TODO: Should "new, fixed" attributes be allowed after the object enters the model
Set<String> violatesFixed = getAttributeSchemas().values()
.stream()
.filter(AttributeSchema::isFixed)
.map(AttributeSchema::getName)
.filter(delta.removed::containsKey)
.collect(Collectors.toSet());
if (!violatesFixed.isEmpty()) {
if (strict) {
}
String msg = "Object " + object + " modified or removed fixed attributes " +
violatesFixed + " of schema " + this;
if (strict) {
throw new AssertionError(msg);
}
Msg.error(this, msg);
}
}
/**
* Verify that the given change does not cause a violation of the element schema
*
* <p>
* For elements, we can only check whether the type conforms. Important within that, however, is
* that we verify the elements all have their required attributes.
*
* @param delta the delta, before or after the fact
*/
default void validateElementDelta(TargetObject object, Delta<?, ? extends TargetObject> delta,
boolean strict) {
for (Map.Entry<String, ? extends TargetObject> ent : delta.added.entrySet()) {
TargetObjectSchema schema = getContext().getSchema(getElementSchema(ent.getKey()));
schema.validateRequiredAttributes(ent.getValue(), strict);
}
}
}

View file

@ -0,0 +1,30 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.dbg.target.schema;
import java.lang.annotation.*;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface TargetObjectSchemaInfo {
String name() default "";
boolean canonicalContainer() default false;
TargetElementType[] elements() default {};
TargetAttributeType[] attributes() default {};
}

View file

@ -0,0 +1,180 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.dbg.target.schema;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.*;
import org.jdom.*;
import org.jdom.input.SAXBuilder;
import ghidra.dbg.DebuggerObjectModel;
import ghidra.dbg.target.TargetObject;
import ghidra.dbg.target.schema.DefaultTargetObjectSchema.DefaultAttributeSchema;
import ghidra.dbg.target.schema.TargetObjectSchema.AttributeSchema;
import ghidra.dbg.target.schema.TargetObjectSchema.SchemaName;
import ghidra.util.Msg;
import ghidra.util.xml.XmlUtilities;
public class XmlSchemaContext extends DefaultSchemaContext {
protected static final Set<String> TRUES = Set.of("true", "yes", "y", "1");
protected static boolean parseBoolean(Element ele, String attrName) {
return TRUES.contains(ele.getAttributeValue(attrName, "no").toLowerCase());
}
public static String serialize(SchemaContext ctx) {
return XmlUtilities.toString(contextToXml(ctx));
}
public static Element contextToXml(SchemaContext ctx) {
Element result = new Element("context");
for (TargetObjectSchema schema : ctx.getAllSchemas()) {
Element schemaElem = schemaToXml(schema);
if (schemaElem != null) {
result.addContent(schemaElem);
}
}
return result;
}
public static Element attributeSchemaToXml(AttributeSchema as) {
Element attrElem = new Element("attribute");
if (!as.getName().equals("")) {
XmlUtilities.setStringAttr(attrElem, "name", as.getName());
}
XmlUtilities.setStringAttr(attrElem, "schema", as.getSchema().toString());
if (as.isRequired()) {
XmlUtilities.setStringAttr(attrElem, "required", "yes");
}
if (as.isFixed()) {
XmlUtilities.setStringAttr(attrElem, "fixed", "yes");
}
if (as.isHidden()) {
XmlUtilities.setStringAttr(attrElem, "hidden", "yes");
}
return attrElem;
}
public static Element schemaToXml(TargetObjectSchema schema) {
if (!TargetObject.class.isAssignableFrom(schema.getType())) {
return null;
}
if (schema == EnumerableTargetObjectSchema.OBJECT) {
return null;
}
Element result = new Element("schema");
XmlUtilities.setStringAttr(result, "name", schema.getName().toString());
for (Class<? extends TargetObject> iface : schema.getInterfaces()) {
Element ifElem = new Element("interface");
XmlUtilities.setStringAttr(ifElem, "name",
DebuggerObjectModel.requireIfaceName(iface));
result.addContent(ifElem);
}
if (schema.isCanonicalContainer()) {
XmlUtilities.setStringAttr(result, "canonical", "yes");
}
for (Map.Entry<String, SchemaName> ent : schema.getElementSchemas().entrySet()) {
Element elemElem = new Element("element");
XmlUtilities.setStringAttr(elemElem, "index", ent.getKey());
XmlUtilities.setStringAttr(elemElem, "schema", ent.getValue().toString());
result.addContent(elemElem);
}
Element deElem = new Element("element");
XmlUtilities.setStringAttr(deElem, "schema",
schema.getDefaultElementSchema().toString());
result.addContent(deElem);
for (AttributeSchema as : schema.getAttributeSchemas().values()) {
Element attrElem = attributeSchemaToXml(as);
result.addContent(attrElem);
}
AttributeSchema das = schema.getDefaultAttributeSchema();
Element daElem = attributeSchemaToXml(das);
result.addContent(daElem);
return result;
}
public static XmlSchemaContext deserialize(String xml) throws JDOMException {
return deserialize(xml.getBytes());
}
public static XmlSchemaContext deserialize(byte[] xml) throws JDOMException {
try {
SAXBuilder sb = XmlUtilities.createSecureSAXBuilder(false, false);
Document doc = sb.build(new ByteArrayInputStream(xml));
return contextFromXml(doc.getRootElement());
}
catch (IOException e) {
throw new AssertionError(e);
}
}
public static XmlSchemaContext contextFromXml(Element contextElem) {
XmlSchemaContext ctx = new XmlSchemaContext();
for (Element schemaElem : XmlUtilities.getChildren(contextElem, "schema")) {
ctx.schemaFromXml(schemaElem);
}
return ctx;
}
protected final Map<String, SchemaName> names = new HashMap<>();
public synchronized SchemaName name(String name) {
return names.computeIfAbsent(name, SchemaName::new);
}
public TargetObjectSchema schemaFromXml(Element schemaElem) {
SchemaBuilder builder = builder(name(schemaElem.getAttributeValue("name", "")));
for (Element ifaceElem : XmlUtilities.getChildren(schemaElem, "interface")) {
String ifaceName = ifaceElem.getAttributeValue("name");
Class<? extends TargetObject> iface = TargetObject.INTERFACES_BY_NAME.get(ifaceName);
if (iface == null) {
Msg.warn(this, "Unknown interface name: '" + ifaceName + "'");
}
else {
builder.addInterface(iface);
}
}
builder.setCanonicalContainer(parseBoolean(schemaElem, "canonical"));
for (Element elemElem : XmlUtilities.getChildren(schemaElem, "element")) {
SchemaName schema = name(elemElem.getAttributeValue("schema"));
String index = elemElem.getAttributeValue("index", "");
builder.addElementSchema(index, schema, elemElem);
}
for (Element attrElem : XmlUtilities.getChildren(schemaElem, "attribute")) {
SchemaName schema = name(attrElem.getAttributeValue("schema"));
boolean required = parseBoolean(attrElem, "required");
boolean fixed = parseBoolean(attrElem, "fixed");
boolean hidden = parseBoolean(attrElem, "hidden");
String name = attrElem.getAttributeValue("name", "");
builder.addAttributeSchema(
new DefaultAttributeSchema(name, schema, required, fixed, hidden), attrElem);
}
return builder.buildAndAdd();
}
}

View file

@ -25,12 +25,15 @@ public class PathPattern implements PathPredicates {
* TODO: This can get more sophisticated if needed, but for now, I don't think we even need
* regular expressions. Either we care about a path element, or we don't.
*
* <p>
* This takes a list of path keys as a means of matching paths. The empty key serves as a
* wildcard accepting all keys in that position, e.g., the following matches all elements within
* {@code Processes}:
*
* <p>
* {@code List.of("Processes", "[]")}
*
* <p>
* This should still be compatible with {@link PathUtils#parse(String)} and
* {@link PathUtils#toString(List)} allowing the last example to be expressed as
* {@code PathUtils.parse("Processes[]")}.
@ -55,7 +58,7 @@ public class PathPattern implements PathPredicates {
return pattern.hashCode();
}
protected boolean keyMatches(String pat, String key) {
public static boolean keyMatches(String pat, String key) {
if (key.equals(pat)) {
return true;
}

View file

@ -329,7 +329,7 @@ public class TargetDataTypeConverter {
*/
String parts[] = type.getIndex().split("\\s+");
String name = parts[parts.length - 1];
switch (type.getKind()) {
switch (type.getTypeKind()) {
case ENUM:
return convertTargetEnumDataType(name, type);
case FUNCTION:

View file

@ -17,8 +17,6 @@ package ghidra.dbg.util;
import java.util.Collection;
import ghidra.dbg.attributes.TargetObjectRefList;
import ghidra.dbg.target.TargetBreakpointContainer.TargetBreakpointKindSet;
import ghidra.util.Msg;
public enum ValueUtils {
@ -37,17 +35,19 @@ public enum ValueUtils {
}
public static <T> T expectType(Object val, Class<T> cls, Object logObj, String attributeName,
T fallback) {
T fallback, boolean required) {
if (val == null || !cls.isAssignableFrom(val.getClass())) {
reportErr(val, cls, logObj, attributeName);
if (val != null || required) {
reportErr(val, cls, logObj, attributeName);
}
return fallback;
}
return cls.cast(val);
}
public static boolean expectBoolean(Object val, Object logObj, String attributeName,
boolean fallback) {
Boolean exp = expectType(val, Boolean.class, logObj, attributeName, null);
boolean fallback, boolean required) {
Boolean exp = expectType(val, Boolean.class, logObj, attributeName, null, required);
if (exp == null) {
return fallback;
}
@ -59,31 +59,4 @@ public enum ValueUtils {
Class<E> elemType) {
return (Class<T>) colType;
}
public static <T extends Collection<E>, E> T expectCollectionOf(Object val, Class<T> colType,
Class<E> elemType, Object logObj,
String attributeName, T fallback) {
if (!colType.isAssignableFrom(val.getClass())) {
reportErr(val, colType, logObj, attributeName);
return fallback;
}
T col = colType.cast(val);
for (E e : col) {
if (!elemType.isAssignableFrom(e.getClass())) {
reportErr(e, elemType, logObj, "element of " + attributeName);
return fallback;
}
}
return col;
}
public static TargetBreakpointKindSet expectBreakKindSet(Object val, Object logObj,
String attributeName, TargetBreakpointKindSet fallback) {
return expectType(val, TargetBreakpointKindSet.class, logObj, attributeName, fallback);
}
public static TargetObjectRefList<?> expectTargetObjectRefList(Object val,
Object logObj, String attributeName, TargetObjectRefList<?> fallback) {
return expectType(val, TargetObjectRefList.class, logObj, attributeName, fallback);
}
}

View file

@ -0,0 +1,345 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.dbg.target.schema;
import static org.junit.Assert.assertEquals;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import org.junit.Test;
import ghidra.dbg.DebuggerObjectModel;
import ghidra.dbg.agent.DefaultTargetModelRoot;
import ghidra.dbg.agent.DefaultTargetObject;
import ghidra.dbg.target.*;
import ghidra.dbg.target.schema.DefaultTargetObjectSchema.DefaultAttributeSchema;
import ghidra.dbg.target.schema.TargetObjectSchema.SchemaName;
public class AnnotatedTargetObjectSchemaTest {
@TargetObjectSchemaInfo
static class TestAnnotatedTargetRootPlain extends DefaultTargetModelRoot {
public TestAnnotatedTargetRootPlain(DebuggerObjectModel model, String typeHint) {
super(model, typeHint);
}
}
@Test
public void testAnnotatedRootSchemaPlain() {
AnnotatedSchemaContext ctx = new AnnotatedSchemaContext();
TargetObjectSchema schema = ctx.getSchemaForClass(TestAnnotatedTargetRootPlain.class);
TargetObjectSchema exp = ctx.builder(schema.getName())
.addInterface(TargetAggregate.class) // Inherited from root
.build();
assertEquals(exp, schema);
}
@TargetObjectSchemaInfo(elements = @TargetElementType(type = Void.class))
static class TestAnnotatedTargetRootNoElems extends DefaultTargetModelRoot {
public TestAnnotatedTargetRootNoElems(DebuggerObjectModel model, String typeHint) {
super(model, typeHint);
}
}
@Test
public void testAnnotatedRootSchemaNoElems() {
AnnotatedSchemaContext ctx = new AnnotatedSchemaContext();
TargetObjectSchema schema = ctx.getSchemaForClass(TestAnnotatedTargetRootNoElems.class);
TargetObjectSchema exp = ctx.builder(schema.getName())
.addInterface(TargetAggregate.class) // Inherited from root
.setDefaultElementSchema(EnumerableTargetObjectSchema.VOID.getName())
.build();
assertEquals(exp, schema);
}
@TargetObjectSchemaInfo(name = "Process")
static class TestAnnotatedTargetProcessStub
extends DefaultTargetObject<TargetObject, TargetObject>
implements TargetProcess<TestAnnotatedTargetProcessStub> {
public TestAnnotatedTargetProcessStub(DebuggerObjectModel model, TargetObject parent,
String key, String typeHint) {
super(model, parent, key, typeHint);
}
}
@TargetObjectSchemaInfo(name = "Root")
static class TestAnnotatedTargetRootOverriddenFetchElems extends DefaultTargetModelRoot {
public TestAnnotatedTargetRootOverriddenFetchElems(DebuggerObjectModel model,
String typeHint) {
super(model, typeHint);
}
@Override
public CompletableFuture<? extends Map<String, TestAnnotatedTargetProcessStub>> fetchElements(
boolean refresh) {
return null; // Doesn't matter
}
}
@Test
public void testAnnotatedRootSchemaOverridenFetchElems() {
AnnotatedSchemaContext ctx = new AnnotatedSchemaContext();
TargetObjectSchema schema =
ctx.getSchemaForClass(TestAnnotatedTargetRootOverriddenFetchElems.class);
SchemaName schemaProc = ctx.nameFromClass(TestAnnotatedTargetProcessStub.class);
TargetObjectSchema exp = ctx.builder(schema.getName())
.addInterface(TargetAggregate.class)
.setDefaultElementSchema(schemaProc)
.build();
assertEquals(exp, schema);
assertEquals("Root", schema.getName().toString());
}
@TargetObjectSchemaInfo(name = "ProcessContainer")
static class TestAnnotatedProcessContainer
extends DefaultTargetObject<TestAnnotatedTargetProcessStub, TargetObject> {
public TestAnnotatedProcessContainer(DebuggerObjectModel model, TargetObject parent,
String key, String typeHint) {
super(model, parent, key, typeHint);
}
}
@Test
public void testAnnotatedSubSchemaElemsByParam() {
AnnotatedSchemaContext ctx = new AnnotatedSchemaContext();
TargetObjectSchema schema = ctx.getSchemaForClass(TestAnnotatedProcessContainer.class);
SchemaName schemaProc = ctx.nameFromClass(TestAnnotatedTargetProcessStub.class);
TargetObjectSchema exp = ctx.builder(schema.getName())
.setDefaultElementSchema(schemaProc)
.addAttributeSchema(new DefaultAttributeSchema("_display",
EnumerableTargetObjectSchema.STRING.getName(), false, false, true), null)
.addAttributeSchema(new DefaultAttributeSchema("_short_display",
EnumerableTargetObjectSchema.STRING.getName(), false, false, true), null)
.addAttributeSchema(new DefaultAttributeSchema("_update_mode",
EnumerableTargetObjectSchema.UPDATE_MODE.getName(), false, false, true), null)
.build();
assertEquals(exp, schema);
}
@TargetObjectSchemaInfo(name = "Process")
static class TestAnnotatedTargetProcessParam<T>
extends DefaultTargetObject<TargetObject, TargetObject>
implements TargetProcess<TestAnnotatedTargetProcessParam<T>> {
public TestAnnotatedTargetProcessParam(DebuggerObjectModel model, TargetObject parent,
String key, String typeHint) {
super(model, parent, key, typeHint);
}
}
@TargetObjectSchemaInfo
static class TestAnnotatedTargetRootWithAnnotatedAttrs extends DefaultTargetModelRoot {
public TestAnnotatedTargetRootWithAnnotatedAttrs(DebuggerObjectModel model,
String typeHint) {
super(model, typeHint);
}
@TargetAttributeType(name = "int_attribute")
public int getSomeIntAttribute() {
return 0; // Doesn't matter
}
@TargetAttributeType
public TestAnnotatedTargetProcessParam<?> getSomeObjectAttribute() {
return null; // Doesn't matter
}
}
@Test
public void testAnnotatedRootSchemaWithAnnotatedAttrs() {
AnnotatedSchemaContext ctx = new AnnotatedSchemaContext();
TargetObjectSchema schema =
ctx.getSchemaForClass(TestAnnotatedTargetRootWithAnnotatedAttrs.class);
SchemaName schemaProc = ctx.nameFromClass(TestAnnotatedTargetProcessParam.class);
TargetObjectSchema exp = ctx.builder(schema.getName())
.addInterface(TargetAggregate.class)
.addAttributeSchema(new DefaultAttributeSchema("int_attribute",
EnumerableTargetObjectSchema.INT.getName(), false, false, false), null)
.addAttributeSchema(new DefaultAttributeSchema("some_object_attribute",
schemaProc, false, false, false), null)
.build();
assertEquals(exp, schema);
assertEquals("TestAnnotatedTargetRootWithAnnotatedAttrs", schema.getName().toString());
}
@TargetObjectSchemaInfo(attributes = {
@TargetAttributeType(type = Void.class),
@TargetAttributeType(name = "some_int_attribute", type = Integer.class),
@TargetAttributeType(name = "some_object_attribute", type = TestAnnotatedTargetProcessStub.class)
}, elements = {
@TargetElementType(index = "reserved", type = Void.class)
})
static class TestAnnotatedTargetRootWithListedAttrs extends DefaultTargetModelRoot {
public TestAnnotatedTargetRootWithListedAttrs(DebuggerObjectModel model,
String typeHint) {
super(model, typeHint);
}
}
@Test
public void testAnnotatedRootSchemaWithListedAttrs() {
AnnotatedSchemaContext ctx = new AnnotatedSchemaContext();
TargetObjectSchema schema =
ctx.getSchemaForClass(TestAnnotatedTargetRootWithListedAttrs.class);
SchemaName schemaProc = ctx.nameFromClass(TestAnnotatedTargetProcessStub.class);
TargetObjectSchema exp = ctx.builder(schema.getName())
.addInterface(TargetAggregate.class)
.setDefaultAttributeSchema(new DefaultAttributeSchema("",
EnumerableTargetObjectSchema.VOID.getName(), false, false, false))
.addAttributeSchema(new DefaultAttributeSchema("some_int_attribute",
EnumerableTargetObjectSchema.INT.getName(), false, false, false), null)
.addAttributeSchema(new DefaultAttributeSchema("some_object_attribute",
schemaProc, false, false, false), null)
.addElementSchema("reserved", EnumerableTargetObjectSchema.VOID.getName(), null)
.build();
assertEquals(exp, schema);
assertEquals("TestAnnotatedTargetRootWithListedAttrs", schema.getName().toString());
}
static class NotAPrimitive {
}
@TargetObjectSchemaInfo
static class TestAnnotatedTargetRootWithAnnotatedAttrsBadType extends DefaultTargetModelRoot {
public TestAnnotatedTargetRootWithAnnotatedAttrsBadType(DebuggerObjectModel model,
String typeHint) {
super(model, typeHint);
}
@TargetAttributeType
public NotAPrimitive getSomeErrAttribute() {
return null; // Doesn't matter
}
}
@Test(expected = IllegalArgumentException.class)
public void testAnnotatedRootSchemaWithAnnotatedAttrsBadType() {
AnnotatedSchemaContext ctx = new AnnotatedSchemaContext();
ctx.getSchemaForClass(TestAnnotatedTargetRootWithAnnotatedAttrsBadType.class);
}
@Test(expected = IllegalArgumentException.class)
public void testNotAnnotated() {
AnnotatedSchemaContext ctx = new AnnotatedSchemaContext();
ctx.getSchemaForClass(DefaultTargetObject.class);
}
@TargetObjectSchemaInfo
static class TestAnnotatedTargetRootWithAnnotatedAttrsNonUnique<T extends TargetProcess<T> & TargetInterpreter<T>>
extends DefaultTargetModelRoot {
public TestAnnotatedTargetRootWithAnnotatedAttrsNonUnique(DebuggerObjectModel model,
String typeHint) {
super(model, typeHint);
}
@TargetAttributeType
public T getSomeErrAttribute() {
return null; // Doesn't matter
}
}
@Test(expected = IllegalArgumentException.class)
public void testAnnotatedRootWithAnnotatedAttrsNonUnique() {
AnnotatedSchemaContext ctx = new AnnotatedSchemaContext();
ctx.getSchemaForClass(TestAnnotatedTargetRootWithAnnotatedAttrsNonUnique.class);
}
@TargetObjectSchemaInfo
static class TestAnnotatedTargetRootWithElemsNonUnique<T extends TargetProcess<T> & TargetInterpreter<T>>
extends DefaultTargetModelRoot {
public TestAnnotatedTargetRootWithElemsNonUnique(DebuggerObjectModel model,
String typeHint) {
super(model, typeHint);
}
@Override
public CompletableFuture<? extends Map<String, ? extends T>> fetchElements(
boolean refresh) {
return null; // Doesn't matter
}
}
@Test(expected = IllegalArgumentException.class)
public void testAnnotatedRootWithElemsNonUnique() {
AnnotatedSchemaContext ctx = new AnnotatedSchemaContext();
ctx.getSchemaForClass(TestAnnotatedTargetRootWithElemsNonUnique.class);
}
@TargetObjectSchemaInfo
static class TestAnnotatedTargetRootWithAnnotatedAttrsBadName extends DefaultTargetModelRoot {
public TestAnnotatedTargetRootWithAnnotatedAttrsBadName(DebuggerObjectModel model,
String typeHint) {
super(model, typeHint);
}
@TargetAttributeType
public int get() {
return 0; // Doesn't matter
}
}
@Test(expected = IllegalArgumentException.class)
public void testAnnotatedRootSchemaWithAnnotatedAttrsBadName() {
AnnotatedSchemaContext ctx = new AnnotatedSchemaContext();
ctx.getSchemaForClass(TestAnnotatedTargetRootWithAnnotatedAttrsBadName.class);
}
@TargetObjectSchemaInfo
static class TestAnnotatedTargetRootWithAnnotatedAttrsBadGetter extends DefaultTargetModelRoot {
public TestAnnotatedTargetRootWithAnnotatedAttrsBadGetter(DebuggerObjectModel model,
String typeHint) {
super(model, typeHint);
}
@TargetAttributeType
public int getSomeIntAttribute(boolean bogus) {
return 0; // Doesn't matter
}
}
@Test(expected = IllegalArgumentException.class)
public void testAnnotatedRootSchemaWithAnnotatedAttrsBadGetter() {
AnnotatedSchemaContext ctx = new AnnotatedSchemaContext();
ctx.getSchemaForClass(TestAnnotatedTargetRootWithAnnotatedAttrsBadGetter.class);
}
@TargetObjectSchemaInfo(attributes = @TargetAttributeType(name = "some_attr", type = NotAPrimitive.class))
static class TestAnnotatedTargetRootWithListedAttrsBadType extends DefaultTargetModelRoot {
public TestAnnotatedTargetRootWithListedAttrsBadType(DebuggerObjectModel model,
String typeHint) {
super(model, typeHint);
}
}
@Test(expected = IllegalArgumentException.class)
public void testAnnotatedRootSchemaWithListAttrsBadType() {
AnnotatedSchemaContext ctx = new AnnotatedSchemaContext();
ctx.getSchemaForClass(TestAnnotatedTargetRootWithListedAttrsBadType.class);
}
}

View file

@ -0,0 +1,336 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.dbg.target.schema;
import static org.junit.Assert.*;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import org.junit.Test;
import ghidra.dbg.DebuggerObjectModel;
import ghidra.dbg.agent.*;
import ghidra.dbg.target.*;
import ghidra.dbg.target.TargetObject.TargetUpdateMode;
import ghidra.dbg.target.schema.DefaultTargetObjectSchema.DefaultAttributeSchema;
import ghidra.dbg.target.schema.TargetObjectSchema.AttributeSchema;
import ghidra.dbg.target.schema.TargetObjectSchema.SchemaName;
import ghidra.program.model.address.AddressFactory;
public class TargetObjectSchemaValidationTest {
protected DefaultSchemaContext ctx = new DefaultSchemaContext();
protected SchemaName nameRoot = new SchemaName("Root");
protected SchemaName nameDown1 = new SchemaName("Down1");
protected TargetObjectSchema schemaDown1 = ctx.builder(nameDown1)
.buildAndAdd();
protected SchemaName nameWReq = new SchemaName("WithRequired");
protected TargetObjectSchema schemaWReq = ctx.builder(nameWReq)
.addAttributeSchema(new DefaultAttributeSchema("req",
EnumerableTargetObjectSchema.ANY.getName(), true, false, false), null)
.buildAndAdd();
protected AbstractDebuggerObjectModel model = new AbstractDebuggerObjectModel() {
@Override
public CompletableFuture<? extends TargetObject> fetchModelRoot() {
fail();
return null;
}
@Override
public AddressFactory getAddressFactory() {
fail();
return null;
}
@Override
public CompletableFuture<Void> close() {
fail();
return null;
}
};
@Test
public void testAttributeSchemaAssignment() {
TargetObjectSchema schemaRoot = ctx.builder(nameRoot)
.addAttributeSchema(new DefaultTargetObjectSchema.DefaultAttributeSchema("my_attr",
nameDown1, false, false, false), null)
.buildAndAdd();
DefaultTargetModelRoot root = new DefaultTargetModelRoot(model, "Root", schemaRoot);
DefaultTargetObject<?, ?> control =
new DefaultTargetObject<>(model, root, "control", "Default");
assertNotEquals(schemaDown1, control.getSchema());
DefaultTargetObject<?, ?> down1 =
new DefaultTargetObject<>(model, root, "my_attr", "Default");
assertEquals(schemaDown1, down1.getSchema());
}
@Test
public void testElementSchemaAssignment() {
TargetObjectSchema schemaRoot = ctx.builder(nameRoot)
.addElementSchema("1", nameDown1, null)
.buildAndAdd();
DefaultTargetModelRoot root = new DefaultTargetModelRoot(model, "Root", schemaRoot);
DefaultTargetObject<?, ?> control =
new DefaultTargetObject<>(model, root, "[0]", "Default");
assertNotEquals(schemaDown1, control.getSchema());
DefaultTargetObject<TargetObject, TargetObject> down1 =
new DefaultTargetObject<>(model, root, "[1]", "Default");
assertEquals(schemaDown1, down1.getSchema());
}
static class ValidatedModelRoot extends DefaultTargetModelRoot {
public ValidatedModelRoot(DebuggerObjectModel model, String typeHint,
TargetObjectSchema schema) {
super(model, typeHint, schema);
}
@Override
protected boolean enforcesStrictSchema() {
return true;
}
}
static class ValidatedObject extends DefaultTargetObject<TargetObject, TargetObject> {
public ValidatedObject(DebuggerObjectModel model, String key, TargetObjectSchema schema) {
super(model, null, key, "Object", schema);
}
@Override
protected boolean enforcesStrictSchema() {
return true;
}
}
@Test
public void testInterfaceValidation() {
TargetObjectSchema schemaRoot = ctx.builder(nameRoot)
.addInterface(TargetAggregate.class)
.buildAndAdd();
new ValidatedModelRoot(model, "Root", schemaRoot);
// pass
}
@Test(expected = AssertionError.class)
public void testInterfaceValidationErr() {
TargetObjectSchema schemaRoot = ctx.builder(nameRoot)
.addInterface(TargetProcess.class)
.buildAndAdd();
new ValidatedModelRoot(model, "Root", schemaRoot);
}
protected ValidatedModelRoot createRootAttrWReq() {
TargetObjectSchema schemaRoot = ctx.builder(nameRoot)
.addAttributeSchema(new DefaultTargetObjectSchema.DefaultAttributeSchema("my_attr",
nameWReq, false, false, false), null)
.buildAndAdd();
return new ValidatedModelRoot(model, "Root", schemaRoot);
}
protected ValidatedModelRoot createRootElemWReq() {
TargetObjectSchema schemaRoot = ctx.builder(nameRoot)
.addElementSchema("1", nameWReq, null)
.buildAndAdd();
return new ValidatedModelRoot(model, "Root", schemaRoot);
}
protected DefaultTargetObject<?, ?> createWReqCorrect(TargetObject root, String name) {
DefaultTargetObject<?, ?> wreq =
new DefaultTargetObject<>(model, root, name, "Default");
wreq.changeAttributes(List.of(), Map.of("req", "Hello!"), "Initialized");
return wreq;
}
protected DefaultTargetObject<?, ?> createWReqIncorrect(TargetObject root, String name) {
DefaultTargetObject<?, ?> wreq =
new DefaultTargetObject<>(model, root, name, "Default");
return wreq;
}
@Test
public void testAttributeValidationAtInsertViaSetAttributes() {
DefaultTargetModelRoot root = createRootAttrWReq();
DefaultTargetObject<?, ?> wreq = createWReqCorrect(root, "my_attr");
root.setAttributes(List.of(wreq), Map.of(), "Initialized");
}
@Test(expected = AssertionError.class)
public void testAttributeValidationAtInsertViaSetAttributesErr() {
DefaultTargetModelRoot root = createRootAttrWReq();
DefaultTargetObject<?, ?> wreq = createWReqIncorrect(root, "my_attr");
root.setAttributes(List.of(wreq), Map.of(), "Initialized");
}
@Test
public void testAttributeValidationAtInsertViaChangeAttributes() {
DefaultTargetModelRoot root = createRootAttrWReq();
DefaultTargetObject<?, ?> wreq = createWReqCorrect(root, "my_attr");
root.changeAttributes(List.of(), List.of(wreq), Map.of(), "Initialized");
}
@Test(expected = AssertionError.class)
public void testAttributeValidationAtInsertViaChangeAttributesErr() {
DefaultTargetModelRoot root = createRootAttrWReq();
DefaultTargetObject<?, ?> wreq = createWReqIncorrect(root, "my_attr");
root.changeAttributes(List.of(), List.of(wreq), Map.of(), "Initialized");
}
@Test
public void testAttributeValidationAtInsertViaSetElements() {
DefaultTargetModelRoot root = createRootElemWReq();
DefaultTargetObject<?, ?> wreq = createWReqCorrect(root, "[1]");
root.setElements(List.of(wreq), Map.of(), "Initialized");
}
@Test(expected = AssertionError.class)
public void testAttributeValidationAtInsertViaSetElementsErr() {
DefaultTargetModelRoot root = createRootElemWReq();
DefaultTargetObject<?, ?> wreq = createWReqIncorrect(root, "[1]");
root.setElements(List.of(wreq), Map.of(), "Initialized");
}
@Test
public void testAttributeValidationAtInsertViaChangeElements() {
DefaultTargetModelRoot root = createRootElemWReq();
DefaultTargetObject<?, ?> wreq = createWReqCorrect(root, "[1]");
root.changeElements(List.of(), List.of(wreq), Map.of(), "Initialized");
}
@Test(expected = AssertionError.class)
public void testAttributeValidationAtInsertViaChangeElementsErr() {
DefaultTargetModelRoot root = createRootElemWReq();
DefaultTargetObject<?, ?> wreq = createWReqIncorrect(root, "[1]");
root.changeElements(List.of(), List.of(wreq), Map.of(), "Initialized");
}
@Test(expected = AssertionError.class)
public void testValidateRequiredAttributeViaSetErr() {
TargetObjectSchema schema = ctx.builder(new SchemaName("test"))
.addAttributeSchema(new DefaultAttributeSchema("req",
EnumerableTargetObjectSchema.ANY.getName(), true, false, false), null)
.buildAndAdd();
ValidatedObject obj = new ValidatedObject(model, "Test", schema);
obj.setAttributes(List.of(), Map.of("req", "Hello"), "Initialized");
obj.setAttributes(List.of(), Map.of(), "Test");
}
@Test(expected = AssertionError.class)
public void testValidateRequiredAttributeViaChangeErr() {
TargetObjectSchema schema = ctx.builder(new SchemaName("test"))
.addAttributeSchema(new DefaultAttributeSchema("req",
EnumerableTargetObjectSchema.ANY.getName(), true, false, false), null)
.buildAndAdd();
ValidatedObject obj = new ValidatedObject(model, "Test", schema);
obj.setAttributes(List.of(), Map.of("req", "Hello"), "Initialized");
obj.changeAttributes(List.of("req"), List.of(), Map.of(), "Test");
}
@Test(expected = AssertionError.class)
public void testValidateFixedAttributeViaSetErr() {
TargetObjectSchema schema = ctx.builder(new SchemaName("test"))
.addAttributeSchema(new DefaultAttributeSchema("fix",
EnumerableTargetObjectSchema.ANY.getName(), false, true, false), null)
.buildAndAdd();
ValidatedObject obj = new ValidatedObject(model, "Test", schema);
obj.setAttributes(List.of(), Map.of("fix", "Hello"), "Initialized");
obj.setAttributes(List.of(), Map.of("fix", "World"), "Test");
}
@Test(expected = AssertionError.class)
public void testValidateFixedAttributeViaChangeErr() {
TargetObjectSchema schema = ctx.builder(new SchemaName("test"))
.addAttributeSchema(new DefaultAttributeSchema("fix",
EnumerableTargetObjectSchema.ANY.getName(), false, true, false), null)
.buildAndAdd();
ValidatedObject obj = new ValidatedObject(model, "Test", schema);
obj.setAttributes(List.of(), Map.of("fix", "Hello"), "Initialized");
// Removal of fixed attr also forbidden after it's set
obj.changeAttributes(List.of("fix"), List.of(), Map.of(), "Test");
}
ValidatedObject createRepleteValidatedObject() {
TargetObjectSchema schema = ctx.builder(new SchemaName("test"))
.addAttributeSchema(new DefaultAttributeSchema("_update_mode",
EnumerableTargetObjectSchema.UPDATE_MODE.getName(), true, false, false), null)
.addAttributeSchema(new DefaultAttributeSchema("_display",
EnumerableTargetObjectSchema.STRING.getName(), true, false, false), null)
.addAttributeSchema(new DefaultAttributeSchema("int",
EnumerableTargetObjectSchema.INT.getName(), false, false, false), null)
.addAttributeSchema(new DefaultAttributeSchema("obj",
EnumerableTargetObjectSchema.OBJECT.getName(), false, false, false), null)
.setDefaultAttributeSchema(AttributeSchema.DEFAULT_VOID)
.buildAndAdd();
ValidatedObject obj = new ValidatedObject(model, "Test", schema);
return obj;
}
@Test
public void testValidateAttributeTypesViaSet() {
ValidatedObject obj = createRepleteValidatedObject();
obj.setAttributes(List.of(), Map.of(
"_display", "Hello",
"_update_mode", TargetUpdateMode.SOLICITED,
"int", 5),
"Test");
obj.setAttributes(List.of(), Map.of(
"_display", "World",
"_update_mode", TargetUpdateMode.FIXED,
"int", 6),
"Test");
}
@Test
public void testValidateAttributeTypesViaChange() {
ValidatedObject obj = createRepleteValidatedObject();
obj.changeAttributes(List.of(), Map.of("int", 5), "Test");
obj.changeAttributes(List.of(), Map.of("int", 6), "Test");
}
@Test(expected = AssertionError.class)
public void testValidateAttributeTypesViaSetErr() {
ValidatedObject obj = createRepleteValidatedObject();
obj.setAttributes(List.of(), Map.of(
"_display", "World",
"_update_mode", TargetUpdateMode.UNSOLICITED,
"int", 7.0),
"Test");
}
@Test(expected = AssertionError.class)
public void testValidateAttributeTypesViaChangeErr() {
ValidatedObject obj = createRepleteValidatedObject();
obj.changeAttributes(List.of(), Map.of("int", 7.0), "Test");
}
}

View file

@ -0,0 +1,79 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.dbg.target.schema;
import static org.junit.Assert.assertEquals;
import java.io.IOException;
import org.jdom.JDOMException;
import org.junit.Test;
import ghidra.dbg.target.TargetInterpreter;
import ghidra.dbg.target.TargetProcess;
import ghidra.dbg.target.schema.DefaultTargetObjectSchema.DefaultAttributeSchema;
import ghidra.dbg.target.schema.TargetObjectSchema.AttributeSchema;
import ghidra.dbg.target.schema.TargetObjectSchema.SchemaName;
public class XmlTargetObjectSchemaTest {
protected static final String SCHEMA_XML = "" +
"<context>\n" +
" <schema name=\"root\" canonical=\"yes\">\n" +
" <interface name=\"Process\" />\n" +
" <interface name=\"Interpreter\" />\n" +
" <element index=\"reserved\" schema=\"VOID\" />\n" +
" <element schema=\"down1\" />\n" +
" <attribute name=\"some_int\" schema=\"INT\" />\n" +
" <attribute name=\"some_object\" schema=\"OBJECT\" required=\"yes\" fixed=\"yes\" hidden=\"yes\" />\n" +
" <attribute schema=\"ANY\" hidden=\"yes\" />\n" +
" </schema>\n" +
" <schema name=\"down1\">\n" +
" <element schema=\"OBJECT\" />\n" +
" <attribute schema=\"VOID\" fixed=\"yes\" hidden=\"yes\" />\n" +
" </schema>\n" +
"</context>";
protected static final DefaultSchemaContext CTX = new DefaultSchemaContext();
protected static final SchemaName NAME_ROOT = new SchemaName("root");
protected static final SchemaName NAME_DOWN1 = new SchemaName("down1");
protected static final TargetObjectSchema SCHEMA_ROOT = CTX.builder(NAME_ROOT)
.addInterface(TargetProcess.class)
.addInterface(TargetInterpreter.class)
.setCanonicalContainer(true)
.addElementSchema("reserved", EnumerableTargetObjectSchema.VOID.getName(), null)
.addElementSchema("", NAME_DOWN1, null)
.addAttributeSchema(new DefaultAttributeSchema("some_int",
EnumerableTargetObjectSchema.INT.getName(), false, false, false), null)
.addAttributeSchema(new DefaultAttributeSchema("some_object",
EnumerableTargetObjectSchema.OBJECT.getName(), true, true, true), null)
.buildAndAdd();
protected static final TargetObjectSchema SCHEMA_DOWN1 = CTX.builder(NAME_DOWN1)
.setDefaultAttributeSchema(AttributeSchema.DEFAULT_VOID)
.buildAndAdd();
@Test
public void testSerialize() {
String serialized =
XmlSchemaContext.serialize(CTX).replace("\t", " ").replace("\r", "").trim();
assertEquals(SCHEMA_XML, serialized);
}
@Test
public void testDeserialize() throws JDOMException, IOException {
SchemaContext result = XmlSchemaContext.deserialize(SCHEMA_XML);
assertEquals(CTX, result);
}
}