GP-2551: Fix RegistersProvider for new trace conventions

This commit is contained in:
Dan 2022-09-15 11:53:24 -04:00
parent e89b86a66f
commit bc2ba594b4
86 changed files with 1743 additions and 708 deletions

View file

@ -31,7 +31,7 @@ import ghidra.dbg.util.PathUtils;
public interface TargetRegister extends TargetObject {
String CONTAINER_ATTRIBUTE_NAME = PREFIX_INVISIBLE + "container";
String LENGTH_ATTRIBUTE_NAME = PREFIX_INVISIBLE + "length";
String BIT_LENGTH_ATTRIBUTE_NAME = PREFIX_INVISIBLE + "length";
/**
* Get the container of this register.
@ -60,12 +60,25 @@ public interface TargetRegister extends TargetObject {
* @return the length of the register
*/
@TargetAttributeType(
name = LENGTH_ATTRIBUTE_NAME,
name = BIT_LENGTH_ATTRIBUTE_NAME,
required = true,
fixed = true,
hidden = true)
default int getBitLength() {
return getTypedAttributeNowByName(LENGTH_ATTRIBUTE_NAME, Integer.class, 0);
return getTypedAttributeNowByName(BIT_LENGTH_ATTRIBUTE_NAME, Integer.class, 0);
}
/**
* Get the length, in bytes, of the register
*
* <p>
* For registers whose bit lengths are not a multiple of 8, this should be the minimum number of
* bytes required to bit all the bits, i.e., it should divide by 8 rounding up.
*
* @return the length of the register
*/
default int getByteLength() {
return (getBitLength() + 7) / 8;
}
/**

View file

@ -317,12 +317,8 @@ public interface TargetObjectSchema {
* @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();
SchemaName schemaName = getElementSchemas().get(index);
return schemaName == null ? getDefaultElementSchema() : schemaName;
}
/**
@ -365,12 +361,8 @@ public interface TargetObjectSchema {
* @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();
AttributeSchema attributeSchema = getAttributeSchemas().get(name);
return attributeSchema == null ? getDefaultAttributeSchema() : attributeSchema;
}
/**
@ -935,46 +927,46 @@ public interface TargetObjectSchema {
* This places some conventional restrictions / expectations on models where registers are given
* on a frame-by-frame basis. The schema should present the {@link TargetRegisterContainer} as
* the same object or a successor to {@link TargetStackFrame}, which must in turn be a successor
* to {@link TargetThread}. The frame level (usually an index) must be in the path from thread
* to stack frame. There can be no wild cards between the frame and the register container. For
* example, the container for {@code Threads[1]} may be {@code Threads[1].Stack[n].Registers},
* where {@code n} is the frame level. {@code Threads[1]} would have the {@link TargetThread}
* to {@link TargetStack}. The frame level (an index) must be in the path from stack to frame.
* There can be no wild cards between the frame and the register container. For example, the
* container for {@code Threads[1]} may be {@code Threads[1].Stack[n].Registers}, where
* {@code n} is the frame level. {@code Threads[1].Stack} would have the {@link TargetStack}
* interface, {@code Threads[1].Stack[0]} would have the {@link TargetStackFrame} interface, and
* {@code Threads[1].Stack[0].Registers} would have the {@link TargetRegisterContainer}
* interface. Note it is not sufficient for {@link TargetRegisterContainer} to be a successor of
* {@link TargetThread} with a single index between. There <em>must</em> be an intervening
* {@link TargetStack} with a single index between. There <em>must</em> be an intervening
* {@link TargetStackFrame}, and the frame level (index) must precede it.
*
* @param frameLevel the frameLevel, must be 0 if not applicable
* @param frameLevel the frame level. May be ignored if not applicable
* @path the path of the seed object relative to the root
* @return the predicates where the register container should be found, possibly empty
*/
default PathPredicates searchForRegisterContainer(int frameLevel, List<String> path) {
List<String> simple = searchForSuitable(TargetRegisterContainer.class, path);
if (simple != null) {
return frameLevel == 0 ? PathPredicates.pattern(simple) : PathPredicates.EMPTY;
return PathPredicates.pattern(simple);
}
List<String> threadPath = searchForAncestor(TargetThread.class, path);
if (threadPath == null) {
List<String> stackPath = searchForSuitable(TargetStack.class, path);
if (stackPath == null) {
return PathPredicates.EMPTY;
}
PathPattern framePatternRelThread =
getSuccessorSchema(threadPath).searchFor(TargetStackFrame.class, false)
PathPattern framePatternRelStack =
getSuccessorSchema(stackPath).searchFor(TargetStackFrame.class, false)
.getSingletonPattern();
if (framePatternRelThread == null) {
if (framePatternRelStack == null) {
return PathPredicates.EMPTY;
}
if (framePatternRelThread.countWildcards() != 1) {
if (framePatternRelStack.countWildcards() != 1) {
return null;
}
PathMatcher result = new PathMatcher();
for (String index : List.of(Integer.toString(frameLevel),
"0x" + Integer.toHexString(frameLevel))) {
List<String> framePathRelThread =
framePatternRelThread.applyKeys(index).getSingletonPath();
List<String> framePath = PathUtils.extend(threadPath, framePathRelThread);
List<String> framePathRelStack =
framePatternRelStack.applyKeys(index).getSingletonPath();
List<String> framePath = PathUtils.extend(stackPath, framePathRelStack);
List<String> regsPath =
searchForSuitable(TargetRegisterContainer.class, framePath);
if (regsPath != null) {
@ -983,4 +975,36 @@ public interface TargetObjectSchema {
}
return result;
}
/**
* Compute the frame level of the object at the given path relative to this schema
*
* <p>
* If there is no {@link TargetStackFrame} in the path, this will return 0 since it is not
* applicable to the object. If there is a stack frame in the path, this will examine its
* ancestry, up to and excluding the {@link TargetStack} for an index. If there isn't a stack in
* the path, it is assumed to be an ancestor of this schema, meaning the examination will
* exhaust the ancestry provided in the path. If no index is found, an exception is thrown,
* because the frame level is applicable, but couldn't be computed from the path given. In that
* case, the client should include more ancestry in the path. Ideally, this is invoked relative
* to the root schema.
*
* @param path the path
* @return the frame level, or 0 if not applicable
* @throws IllegalArgumentException if frame level is applicable but not given in the path
*/
default int computeFrameLevel(List<String> path) {
List<String> framePath = searchForAncestor(TargetStackFrame.class, path);
if (framePath == null) {
return 0;
}
List<String> stackPath = searchForAncestor(TargetStack.class, framePath);
for (int i = stackPath == null ? 0 : stackPath.size(); i < framePath.size(); i++) {
String key = framePath.get(i);
if (PathUtils.isIndex(key)) {
return Integer.decode(PathUtils.parseIndex(key));
}
}
throw new IllegalArgumentException("No index between stack and frame");
}
}

View file

@ -15,27 +15,35 @@
*/
package ghidra.dbg.model;
import java.math.BigInteger;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;
import ghidra.dbg.error.DebuggerRegisterAccessException;
import ghidra.dbg.target.TargetRegisterBank;
import ghidra.dbg.target.TargetRegisterContainer;
import ghidra.dbg.util.PathUtils;
import ghidra.pcode.utils.Utils;
import ghidra.program.model.address.Address;
public abstract class AbstractTestTargetRegisterBank<P extends TestTargetObject>
extends DefaultTestTargetObject<TestTargetObject, P> implements TargetRegisterBank {
extends DefaultTestTargetObject<TestTargetObject, P>
implements TargetRegisterBank, TargetRegisterContainer {
protected final TestTargetRegisterContainer regs;
// TODO: Remove the separate descriptors idea
protected final TestTargetRegisterContainer descs;
public final Map<String, byte[]> regVals = new HashMap<>();
public AbstractTestTargetRegisterBank(P parent, String name, String typeHint,
TestTargetRegisterContainer regs) {
super(parent, name, typeHint);
this.regs = regs;
this.descs = regs;
this.descs.addBank(this);
changeAttributes(List.of(), Map.of(
DESCRIPTIONS_ATTRIBUTE_NAME, regs //
), "Initialized");
DESCRIPTIONS_ATTRIBUTE_NAME, this),
"Initialized");
initializeValues();
}
public abstract TestTargetThread getThread();
@ -43,18 +51,19 @@ public abstract class AbstractTestTargetRegisterBank<P extends TestTargetObject>
@Override
public CompletableFuture<? extends Map<String, byte[]>> readRegistersNamed(
Collection<String> names) {
if (!regs.getDescs().keySet().containsAll(names)) {
if (!descs.getDescs().keySet().containsAll(names)) {
throw new DebuggerRegisterAccessException("No such register");
}
Map<String, byte[]> result = new LinkedHashMap<>();
for (String n : names) {
byte[] v = regVals.get(n);
if (v == null) {
v = regs.getDescs().get(n).defaultValue();
v = descs.getDescs().get(n).defaultValue();
}
result.put(n, v);
}
return model.gateFuture(regs.getModel().future(result).thenApply(__ -> {
populateObjectValues(result, "Read registers");
return model.gateFuture(descs.getModel().future(result).thenApply(__ -> {
listeners.fire.registersUpdated(this, result);
return result;
}));
@ -62,14 +71,14 @@ public abstract class AbstractTestTargetRegisterBank<P extends TestTargetObject>
protected CompletableFuture<Void> writeRegs(Map<String, byte[]> values,
Consumer<Address> setPC) {
if (!regs.getDescs().keySet().containsAll(values.keySet())) {
if (!descs.getDescs().keySet().containsAll(values.keySet())) {
throw new DebuggerRegisterAccessException("No such register");
}
Map<String, byte[]> updates = new LinkedHashMap<>();
CompletableFuture<Void> future = regs.getModel().future(null);
CompletableFuture<Void> future = descs.getModel().future(null);
for (Map.Entry<String, byte[]> ent : values.entrySet()) {
String n = ent.getKey();
TestTargetRegister desc = regs.getDescs().get(n);
TestTargetRegister desc = descs.getDescs().get(n);
byte[] v = desc.normalizeValue(ent.getValue());
regVals.put(n, v);
updates.put(n, v);
@ -79,12 +88,53 @@ public abstract class AbstractTestTargetRegisterBank<P extends TestTargetObject>
});
}
}
populateObjectValues(updates, "Write registers");
future.thenAccept(__ -> {
listeners.fire.registersUpdated(this, updates);
});
return model.gateFuture(future);
}
protected void addObjectValues(Collection<TestTargetRegister> descs, String reason) {
Set<TestTargetRegisterValue> objVals = new HashSet<>();
for (TestTargetRegister rd : descs) {
if (attributes.containsKey(rd.getName())) {
continue;
}
TestTargetRegisterValue tv = new TestTargetRegisterValue(this, rd, (BigInteger) null);
objVals.add(tv);
}
changeAttributes(List.of(), objVals, Map.of(), reason);
}
protected void removeObjectValues(Collection<TestTargetRegister> descs, String reason) {
List<String> toRemove = new ArrayList<>();
for (TestTargetRegister rd : descs) {
toRemove.add(PathUtils.parseIndex(rd.getName()));
}
changeAttributes(toRemove, Map.of(), reason);
}
protected void populateObjectValues(Map<String, byte[]> values, String reason) {
Set<TestTargetRegisterValue> objVals = new HashSet<>();
for (Map.Entry<String, byte[]> ent : values.entrySet()) {
TestTargetRegister rd = descs.getDescs().get(ent.getKey());
byte[] value = ent.getValue();
TestTargetRegisterValue tv = new TestTargetRegisterValue(this, rd,
value == null ? null : Utils.bytesToBigInteger(value, rd.byteLength, true, false));
objVals.add(tv);
}
changeAttributes(List.of(), objVals, Map.of(), reason);
}
protected void initializeValues() {
Map<String, byte[]> values = new HashMap<>();
for (TestTargetRegister desc : descs.getDescs().values()) {
values.put(desc.getIndex(), null);
}
populateObjectValues(values, "Populate");
}
public void setFromBank(AbstractTestTargetRegisterBank<?> bank) {
//Map<String, byte[]> updates = new HashMap<>();
//updates.putAll(bank.regVals);
@ -98,4 +148,12 @@ public abstract class AbstractTestTargetRegisterBank<P extends TestTargetObject>
kit.remove();
}
}
public void addRegisterDescs(Collection<TestTargetRegister> added, String reason) {
addObjectValues(added, reason);
}
public void removeRegisterDescs(Collection<TestTargetRegister> removed, String reason) {
removeObjectValues(removed, reason);
}
}

View file

@ -44,8 +44,8 @@ public class TestTargetRegister
changeAttributes(List.of(), Map.of(
CONTAINER_ATTRIBUTE_NAME, parent,
LENGTH_ATTRIBUTE_NAME, byteLength //
), "Initialized");
BIT_LENGTH_ATTRIBUTE_NAME, byteLength * 8),
"Initialized");
}
public byte[] normalizeValue(byte[] value) {

View file

@ -19,6 +19,7 @@ import java.util.*;
import java.util.function.Predicate;
import ghidra.dbg.target.TargetRegisterContainer;
import ghidra.dbg.util.CollectionUtils.Delta;
import ghidra.program.model.lang.Language;
import ghidra.program.model.lang.Register;
@ -26,6 +27,8 @@ public class TestTargetRegisterContainer
extends DefaultTestTargetObject<TestTargetRegister, TestTargetProcess>
implements TargetRegisterContainer {
private final Set<AbstractTestTargetRegisterBank<?>> banks = new HashSet<>();
public TestTargetRegisterContainer(TestTargetProcess parent) {
super(parent, "Registers", "RegisterContainer");
}
@ -43,13 +46,50 @@ public class TestTargetRegisterContainer
}
add.add(getModel().newTestTargetRegister(this, register));
}
changeElements(List.of(), add, "Added registers from Ghidra language: " + language);
String reason = "Added registers from Ghidra language: " + language;
changeElements(List.of(), add, reason);
List<AbstractTestTargetRegisterBank<?>> banks;
synchronized (this.banks) {
banks = List.copyOf(this.banks);
}
for (AbstractTestTargetRegisterBank<?> bank : banks) {
bank.addRegisterDescs(add, reason);
}
return add;
}
public TestTargetRegister addRegister(Register register) {
TestTargetRegister tr = getModel().newTestTargetRegister(this, register);
changeElements(List.of(), List.of(tr), "Added " + register + " from Ghidra language");
TestTargetRegister tr =
getModel().newTestTargetRegister(this, Objects.requireNonNull(register));
String reason = "Added " + register + " from Ghidra language";
changeElements(List.of(), List.of(tr), reason);
List<AbstractTestTargetRegisterBank<?>> banks;
synchronized (this.banks) {
banks = List.copyOf(this.banks);
}
for (AbstractTestTargetRegisterBank<?> bank : banks) {
bank.addRegisterDescs(List.of(tr), reason);
}
return tr;
}
public Delta<TestTargetRegister, TestTargetRegister> removeRegister(Register register,
String reason) {
Delta<TestTargetRegister, TestTargetRegister> result =
changeElements(List.of(register.getName()), List.of(), reason);
List<AbstractTestTargetRegisterBank<?>> banks;
synchronized (this.banks) {
banks = List.copyOf(this.banks);
}
for (AbstractTestTargetRegisterBank<?> bank : banks) {
bank.removeRegisterDescs(result.removed.values(), reason);
}
return result;
}
public void addBank(AbstractTestTargetRegisterBank<?> bank) {
synchronized (this.banks) {
this.banks.add(bank);
}
}
}

View file

@ -22,44 +22,42 @@ import java.util.Map;
import ghidra.dbg.target.TargetRegister;
import ghidra.dbg.util.PathUtils;
import ghidra.pcode.utils.Utils;
import ghidra.program.model.lang.Register;
import ghidra.program.model.lang.RegisterValue;
public class TestTargetRegisterValue
extends DefaultTestTargetObject<TestTargetObject, AbstractTestTargetRegisterBank<?>>
implements TargetRegister {
public static TestTargetRegisterValue fromRegisterValue(
AbstractTestTargetRegisterBank<?> parent, RegisterValue rv) {
Register register = rv.getRegister();
return new TestTargetRegisterValue(parent, PathUtils.makeKey(register.getName()),
register.isProgramCounter(), rv.getUnsignedValue(), register.getBitLength() + 7 / 8);
public final TestTargetRegister desc;
public TestTargetRegisterValue(AbstractTestTargetRegisterBank<?> parent,
TestTargetRegister desc, BigInteger value) {
this(parent, desc,
value == null ? null : Utils.bigIntegerToBytes(value, desc.byteLength, true));
}
protected final int byteLength;
protected final boolean isPC;
public TestTargetRegisterValue(AbstractTestTargetRegisterBank<?> parent,
TestTargetRegister desc, byte[] value) {
super(parent, PathUtils.parseIndex(desc.getName()), "Register");
this.desc = desc;
public TestTargetRegisterValue(AbstractTestTargetRegisterBank<?> parent, String name,
boolean isPC, BigInteger value, int byteLength) {
this(parent, name, isPC, Utils.bigIntegerToBytes(value, byteLength, true));
}
public TestTargetRegisterValue(AbstractTestTargetRegisterBank<?> parent, String name,
boolean isPC, byte[] value) {
super(parent, name, "Register");
this.byteLength = value.length;
this.isPC = isPC;
changeAttributes(List.of(), Map.of(
CONTAINER_ATTRIBUTE_NAME, parent,
LENGTH_ATTRIBUTE_NAME, byteLength,
VALUE_ATTRIBUTE_NAME, value //
), "Initialized");
if (value == null) {
changeAttributes(List.of(), Map.of(
CONTAINER_ATTRIBUTE_NAME, parent,
BIT_LENGTH_ATTRIBUTE_NAME, desc.byteLength * 8),
"Populated");
}
else {
changeAttributes(List.of(), Map.of(
CONTAINER_ATTRIBUTE_NAME, parent,
BIT_LENGTH_ATTRIBUTE_NAME, desc.byteLength * 8,
VALUE_ATTRIBUTE_NAME, value),
"Initialized");
}
}
public void setValue(BigInteger value) {
changeAttributes(List.of(), Map.of(
VALUE_ATTRIBUTE_NAME, Utils.bigIntegerToBytes(value, byteLength, true) //
), "Set value");
VALUE_ATTRIBUTE_NAME, Utils.bigIntegerToBytes(value, desc.byteLength, true)),
"Set value");
}
}

View file

@ -25,8 +25,8 @@ import ghidra.dbg.util.PathUtils;
public class TestTargetThread
extends DefaultTestTargetObject<TestTargetObject, TestTargetThreadContainer>
implements TargetThread, TargetExecutionStateful, TargetSteppable, TargetResumable,
TargetInterruptible, TargetKillable {
implements TargetThread, TargetAggregate, TargetExecutionStateful, TargetSteppable,
TargetResumable, TargetInterruptible, TargetKillable {
public static final TargetStepKindSet SUPPORTED_KINDS =
TargetStepKindSet.of(TargetStepKind.values());

View file

@ -94,7 +94,7 @@
<attribute name="Breakpoints" schema="BreakpointContainer" required="yes" fixed="yes" />
<attribute name="Memory" schema="Memory" required="yes" fixed="yes" />
<attribute name="Modules" schema="ModuleContainer" required="yes" fixed="yes" />
<attribute name="Registers" schema="RegisterContainer" required="yes" fixed="yes" />
<!-- <attribute name="Registers" schema="RegisterContainer" required="yes" fixed="yes" /> -->
<attribute name="Threads" schema="ThreadContainer" required="yes" fixed="yes" />
<attribute name="Devices" schema="OBJECT" />
<attribute name="Environment" schema="OBJECT" />
@ -171,6 +171,7 @@
</schema>
<schema name="Thread" elementResync="NEVER" attributeResync="NEVER">
<interface name="Thread" />
<interface name="Aggregate" />
<interface name="ExecutionStateful" />
<interface name="Steppable" />
<interface name="Resumable" />
@ -269,7 +270,7 @@
<attribute name="_order" schema="INT" hidden="yes" />
<attribute schema="ANY" />
</schema>
<schema name="RegisterContainer" canonical="yes" elementResync="NEVER" attributeResync="NEVER">
<schema name="RegisterContainer" elementResync="NEVER" attributeResync="NEVER">
<interface name="RegisterContainer" />
<element schema="RegisterDescriptor" />
<attribute name="_descriptions" schema="RegisterContainer" />
@ -280,12 +281,13 @@
<attribute name="_value" schema="ANY" hidden="yes" />
<attribute name="_type" schema="STRING" hidden="yes" />
<attribute name="_order" schema="INT" hidden="yes" />
<attribute schema="ANY" />
</schema>
<schema name="RegisterBank" elementResync="NEVER" attributeResync="NEVER">
<schema name="RegisterBank" canonical="yes" elementResync="NEVER" attributeResync="NEVER">
<interface name="RegisterBank" />
<interface name="RegisterContainer" />
<!-- NB: registers are attributes, not elements here -->
<element schema="RegisterDescriptor" />
<element schema="VOID" />
<attribute schema="RegisterValue" />
<attribute name="_descriptions" schema="RegisterContainer" />
<attribute name="_modified" schema="BOOL" hidden="yes" />
<attribute name="_display" schema="STRING" hidden="yes" />
@ -294,9 +296,9 @@
<attribute name="_value" schema="ANY" hidden="yes" />
<attribute name="_type" schema="STRING" hidden="yes" />
<attribute name="_order" schema="INT" hidden="yes" />
<attribute schema="OBJECT" />
</schema>
<schema name="Stack" canonical="yes" elementResync="NEVER" attributeResync="NEVER">
<interface name="Stack" />
<element schema="StackFrame" />
<attribute name="_modified" schema="BOOL" hidden="yes" />
<attribute name="_display" schema="STRING" hidden="yes" />
@ -394,4 +396,18 @@
<attribute name="_order" schema="INT" hidden="yes" />
<attribute schema="VOID" />
</schema>
</context>
<schema name="RegisterValue" elementResync="NEVER" attributeResync="NEVER">
<interface name="Register" />
<element schema="VOID" />
<attribute name="_length" schema="INT" fixed="yes" hidden="yes" />
<attribute name="_container" schema="RegisterContainer" />
<attribute name="_modified" schema="BOOL" hidden="yes" />
<attribute name="_display" schema="STRING" hidden="yes" />
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
<attribute name="_short_display" schema="STRING" hidden="yes" />
<attribute name="_value" schema="ANY" required="yes" hidden="yes" />
<attribute name="_type" schema="STRING" hidden="yes" />
<attribute name="_order" schema="INT" hidden="yes" />
<attribute schema="VOID" />
</schema>
</context>