GP-3256 Added support for Instruction length-override

This commit is contained in:
ghidra1 2023-08-28 09:17:44 -04:00
parent a820a36e94
commit aefb7f2aed
67 changed files with 1895 additions and 574 deletions

View file

@ -15,7 +15,7 @@
*/
package ghidra.trace.database.listing;
import static ghidra.lifecycle.Unfinished.TODO;
import static ghidra.lifecycle.Unfinished.*;
import java.nio.ByteBuffer;
import java.util.*;
@ -278,11 +278,6 @@ public interface DBTraceCodeUnitAdapter extends TraceCodeUnit, MemBufferMixin {
return DBTraceCommentAdapter.arrayFromComment(getComment(commentType));
}
@Override
default boolean isSuccessor(CodeUnit codeUnit) {
return getMaxAddress().isSuccessor(codeUnit.getMinAddress());
}
@Override
default boolean contains(Address testAddr) {
return getMinAddress().compareTo(testAddr) <= 0 && testAddr.compareTo(getMaxAddress()) <= 0;

View file

@ -21,12 +21,14 @@ import java.nio.ByteBuffer;
import java.util.*;
import db.DBRecord;
import ghidra.program.database.code.InstructionDB;
import ghidra.program.model.address.*;
import ghidra.program.model.lang.*;
import ghidra.program.model.listing.ContextChangeException;
import ghidra.program.model.listing.FlowOverride;
import ghidra.program.model.mem.*;
import ghidra.program.model.symbol.*;
import ghidra.program.model.util.CodeUnitInsertionException;
import ghidra.trace.database.DBTrace;
import ghidra.trace.database.DBTraceUtils;
import ghidra.trace.database.context.DBTraceRegisterContextManager;
@ -36,7 +38,7 @@ import ghidra.trace.database.guest.InternalTracePlatform;
import ghidra.trace.database.map.DBTraceAddressSnapRangePropertyMapTree;
import ghidra.trace.database.symbol.DBTraceReference;
import ghidra.trace.database.symbol.DBTraceReferenceSpace;
import ghidra.trace.model.Lifespan;
import ghidra.trace.model.*;
import ghidra.trace.model.Trace.TraceInstructionChangeType;
import ghidra.trace.model.guest.TracePlatform;
import ghidra.trace.model.listing.TraceInstruction;
@ -52,17 +54,21 @@ import ghidra.util.database.annot.*;
* The implementation of {@link TraceInstruction} for {@link DBTrace}
*/
@DBAnnotatedObjectInfo(version = 0)
public class DBTraceInstruction extends AbstractDBTraceCodeUnit<DBTraceInstruction> implements
TraceInstruction, InstructionAdapterFromPrototype, InstructionContext {
public class DBTraceInstruction extends AbstractDBTraceCodeUnit<DBTraceInstruction>
implements TraceInstruction, InstructionAdapterFromPrototype, InstructionContext {
private static final Address[] EMPTY_ADDRESS_ARRAY = new Address[] {};
private static final String TABLE_NAME = "Instructions";
private static final byte FALLTHROUGH_SET_MASK = 0x01;
private static final byte FALLTHROUGH_CLEAR_MASK = ~FALLTHROUGH_SET_MASK;
private static final byte FLOWOVERRIDE_SET_MASK = 0x0e;
private static final byte FLOWOVERRIDE_CLEAR_MASK = ~FLOWOVERRIDE_SET_MASK;
private static final int FLOWOVERRIDE_SHIFT = 1;
private static final byte FLOW_OVERRIDE_SET_MASK = 0x0e;
private static final byte FLOW_OVERRIDE_CLEAR_MASK = ~FLOW_OVERRIDE_SET_MASK;
private static final int FLOW_OVERRIDE_SHIFT = 1;
private static final byte LENGTH_OVERRIDE_SET_MASK = 0x70;
private static final byte LENGTH_OVERRIDE_CLEAR_MASK = ~LENGTH_OVERRIDE_SET_MASK;
private static final int LENGTH_OVERRIDE_SHIFT = 4;
static final String PLATFORM_COLUMN_NAME = "Platform";
static final String PROTOTYPE_COLUMN_NAME = "Prototype";
@ -143,6 +149,7 @@ public class DBTraceInstruction extends AbstractDBTraceCodeUnit<DBTraceInstructi
protected InstructionPrototype prototype;
protected FlowOverride flowOverride;
protected int lengthOverride;
protected ParserContext parserContext;
protected InternalTracePlatform platform;
@ -186,18 +193,24 @@ public class DBTraceInstruction extends AbstractDBTraceCodeUnit<DBTraceInstructi
* @param platform the platform
* @param prototype the instruction prototype
* @param context the context for locating or creating the prototype entry
* @param forcedLengthOverride reduced instruction byte-length (1..7) or 0 to use default length
*/
protected void set(InternalTracePlatform platform, InstructionPrototype prototype,
ProcessorContextView context) {
ProcessorContextView context, int forcedLengthOverride) {
this.platformKey = platform.getIntKey();
// NOTE: Using "this" for the MemBuffer seems a bit precarious.
DBTraceGuestLanguage languageEntry = platform == null ? null : platform.getLanguageEntry();
this.prototypeKey = (int) space.manager
.findOrRecordPrototype(prototype, languageEntry, this, context)
.getKey();
DBTraceGuestLanguage languageEntry = platform.getLanguageEntry();
this.prototypeKey =
(int) space.manager.findOrRecordPrototype(prototype, languageEntry, this, context)
.getKey();
this.flowOverride = FlowOverride.NONE; // flags field is already consistent
this.lengthOverride = 0;
update(PLATFORM_COLUMN, PROTOTYPE_COLUMN, FLAGS_COLUMN);
if (forcedLengthOverride != 0) {
updateLengthOverride(forcedLengthOverride);
}
// TODO: Can there be more in this context than the context register???
doSetPlatformMapping(platform);
this.prototype = prototype;
@ -216,12 +229,19 @@ public class DBTraceInstruction extends AbstractDBTraceCodeUnit<DBTraceInstructi
}
prototype = space.manager.getPrototypeByKey(prototypeKey);
if (prototype == null) {
Msg.error(this,
"Instruction table is corrupt for address " + getMinAddress() +
". Missing prototype " + prototypeKey);
Msg.error(this, "Instruction table is corrupt for address " + getMinAddress() +
". Missing prototype " + prototypeKey);
prototype = new InvalidPrototype(getTrace().getBaseLanguage());
}
flowOverride = FlowOverride.values()[(flags & FLOWOVERRIDE_SET_MASK) >> FLOWOVERRIDE_SHIFT];
flowOverride =
FlowOverride.values()[(flags & FLOW_OVERRIDE_SET_MASK) >> FLOW_OVERRIDE_SHIFT];
lengthOverride = (flags & LENGTH_OVERRIDE_SET_MASK) >> LENGTH_OVERRIDE_SHIFT;
if (lengthOverride != 0 && lengthOverride < prototype.getLength()) {
Address minAddr = getMinAddress();
Address newEndAddr = minAddr.add(lengthOverride - 1);
doSetRange(new AddressRangeImpl(minAddr, newEndAddr));
}
doSetPlatformMapping(platform);
}
@ -334,8 +354,7 @@ public class DBTraceInstruction extends AbstractDBTraceCodeUnit<DBTraceInstructi
if (flowType.hasFallthrough()) {
try {
return instructionContext.getAddress()
.addNoWrap(
prototype.getFallThroughOffset(instructionContext));
.addNoWrap(prototype.getFallThroughOffset(instructionContext));
}
catch (AddressOverflowException e) {
return null;
@ -364,6 +383,9 @@ public class DBTraceInstruction extends AbstractDBTraceCodeUnit<DBTraceInstructi
}
return null;
}
if (lengthOverride != 0 && getFlowType().hasFallthrough()) {
return getMinAddress().add(lengthOverride);
}
return getDefaultFallThrough();
}
}
@ -433,6 +455,10 @@ public class DBTraceInstruction extends AbstractDBTraceCodeUnit<DBTraceInstructi
return EMPTY_ADDRESS_ARRAY;
}
if (lengthOverride != 0 && getFlowType().hasFallthrough()) {
list.add(getMinAddress().add(lengthOverride));
}
return list.toArray(new Address[list.size()]);
}
}
@ -526,8 +552,8 @@ public class DBTraceInstruction extends AbstractDBTraceCodeUnit<DBTraceInstructi
}
FlowType origFlowType = getFlowType();
flags &= FLOWOVERRIDE_CLEAR_MASK;
flags |= (flowOverride.ordinal() << FLOWOVERRIDE_SHIFT) & FLOWOVERRIDE_SET_MASK;
flags &= FLOW_OVERRIDE_CLEAR_MASK;
flags |= (flowOverride.ordinal() << FLOW_OVERRIDE_SHIFT) & FLOW_OVERRIDE_SET_MASK;
this.flowOverride = flowOverride;
update(FLAGS_COLUMN);
@ -545,8 +571,91 @@ public class DBTraceInstruction extends AbstractDBTraceCodeUnit<DBTraceInstructi
}
}
space.trace.setChanged(
new TraceChangeRecord<>(TraceInstructionChangeType.FLOW_OVERRIDE_CHANGED,
space, this, oldFlowOverride, flowOverride));
new TraceChangeRecord<>(TraceInstructionChangeType.FLOW_OVERRIDE_CHANGED, space, this,
oldFlowOverride, flowOverride));
}
@Override
public void setLengthOverride(int length) throws CodeUnitInsertionException {
int oldLengthOverride = this.lengthOverride;
try (LockHold hold = space.trace.lockWrite()) {
checkDeleted();
InstructionPrototype proto = getPrototype();
length = InstructionDB.checkLengthOverride(length, proto);
if (length == lengthOverride) {
return; // no change
}
int newLength = length != 0 ? length : proto.getLength();
if (newLength > getLength()) {
Address minAddr = getMinAddress();
Address newEndAddr = minAddr.add(newLength - 1);
TraceAddressSnapRange tasr = new ImmutableTraceAddressSnapRange(
new AddressRangeImpl(minAddr.next(), newEndAddr), getLifespan());
for (AbstractDBTraceCodeUnit<?> cu : space.definedUnits.getIntersecting(tasr)) {
if (cu != this) {
throw new CodeUnitInsertionException(
"Length override of " + newLength + " conflicts with code unit at " +
cu.getMinAddress() + ", lifespan=" + cu.getLifespan());
}
}
}
updateLengthOverride(length);
}
space.trace.setChanged(
new TraceChangeRecord<>(TraceInstructionChangeType.LENGTH_OVERRIDE_CHANGED, space, this,
oldLengthOverride, length));
}
private void updateLengthOverride(int length) {
flags &= LENGTH_OVERRIDE_CLEAR_MASK;
flags |= (length << LENGTH_OVERRIDE_SHIFT);
lengthOverride = length;
update(FLAGS_COLUMN);
int newLength = length != 0 ? length : getPrototype().getLength();
Address minAddr = getMinAddress();
Address newEndAddr = minAddr.add(newLength - 1);
doSetRange(new AddressRangeImpl(minAddr, newEndAddr));
}
@Override
public boolean isLengthOverridden() {
return lengthOverride != 0;
}
@Override
public int getLength() {
if (lengthOverride != 0) {
return lengthOverride;
}
return super.getLength();
}
@Override
public int getParsedLength() {
if (lengthOverride == 0) {
return super.getLength();
}
return getPrototype().getLength();
}
@Override
public byte[] getParsedBytes() throws MemoryAccessException {
if (!isLengthOverridden()) {
return getBytes();
}
try (LockHold hold = LockHold.lock(space.lock.readLock())) {
checkIsValid();
int len = getPrototype().getLength();
byte[] b = new byte[len];
Address addr = getAddress();
if (len != getMemory().getBytes(addr, b)) {
throw new MemoryAccessException("Failed to read " + len + " bytes at " + addr);
}
return b;
}
}
@Override
@ -622,8 +731,8 @@ public class DBTraceInstruction extends AbstractDBTraceCodeUnit<DBTraceInstructi
}
update(FLAGS_COLUMN);
space.trace.setChanged(
new TraceChangeRecord<>(TraceInstructionChangeType.FALL_THROUGH_OVERRIDE_CHANGED,
space, this, !overridden, overridden));
new TraceChangeRecord<>(TraceInstructionChangeType.FALL_THROUGH_OVERRIDE_CHANGED, space,
this, !overridden, overridden));
}
@Override

View file

@ -59,9 +59,10 @@ public class DBTraceInstructionsMemoryView
@Override
public DBTraceInstruction create(Lifespan lifespan, Address address,
TracePlatform platform, InstructionPrototype prototype,
ProcessorContextView context) throws CodeUnitInsertionException {
ProcessorContextView context, int forcedLengthOverride)
throws CodeUnitInsertionException {
return delegateWrite(address.getAddressSpace(),
m -> m.create(lifespan, address, platform, prototype, context));
m -> m.create(lifespan, address, platform, prototype, context, forcedLengthOverride));
}
@Override

View file

@ -19,6 +19,7 @@ import java.util.*;
import org.apache.commons.lang3.tuple.Pair;
import ghidra.program.database.code.InstructionDB;
import ghidra.program.model.address.*;
import ghidra.program.model.lang.*;
import ghidra.program.model.lang.InstructionError.InstructionErrorType;
@ -103,16 +104,22 @@ public class DBTraceInstructionsView extends AbstractBaseDBTraceDefinedUnitsView
protected boolean isSuitable(Instruction candidate, Instruction protoInstr) {
try {
return candidate.getPrototype().equals(protoInstr.getPrototype()) &&
Arrays.equals(candidate.getBytes(), protoInstr.getBytes()) &&
candidate.isFallThroughOverridden() == protoInstr.isFallThroughOverridden() &&
Objects.equals(candidate.getFallThrough(), protoInstr.getFallThrough()) &&
candidate.getFlowOverride() == protoInstr.getFlowOverride();
candidate.getFlowOverride() == protoInstr.getFlowOverride() &&
candidate.getLength() == protoInstr.getLength() && // handles potential length override
hasSameBytes(candidate, protoInstr);
}
catch (MemoryAccessException e) {
throw new AssertionError(e);
}
}
private boolean hasSameBytes(Instruction instr1, Instruction instr2)
throws MemoryAccessException {
return Arrays.equals(instr1.getParsedBytes(), instr2.getParsedBytes());
}
protected Instruction doAdjustExisting(Address address, Instruction protoInstr)
throws AddressOverflowException, CancelledException, CodeUnitInsertionException {
DBTraceInstruction exists = getAt(lifespan.lmin(), address);
@ -193,8 +200,8 @@ public class DBTraceInstructionsView extends AbstractBaseDBTraceDefinedUnitsView
return prec;
}
Instruction created =
doCreate(lifespan, address, platform, protoInstr.getPrototype(), protoInstr);
Instruction created = doCreate(lifespan, address, platform,
protoInstr.getPrototype(), protoInstr, protoInstr.getLength());
// copy override settings to replacement instruction
if (protoInstr.isFallThroughOverridden()) {
created.setFallThrough(protoInstr.getFallThrough());
@ -336,20 +343,30 @@ public class DBTraceInstructionsView extends AbstractBaseDBTraceDefinedUnitsView
* @param platform the platform (language, compiler) for the instruction
* @param prototype the instruction's prototype
* @param context the initial context for parsing the instruction
* @return the new instructions
* @throws CodeUnitInsertionException if the instruction cannot be created due to an existing
* unit
* @param length instruction byte-length (must be in the range 0..prototype.getLength()).
* If smaller than the prototype length it must have a value no greater than 7, otherwise
* an error will be thrown. A value of 0 or greater-than-or-equal the prototype length
* will be ignored and not impose and override length. The length value must be a multiple
* of the {@link Language#getInstructionAlignment() instruction alignment} .
* @return the newly created instruction.
* @throws CodeUnitInsertionException thrown if the new Instruction would overlap and
* existing {@link CodeUnit} or the specified {@code length} is unsupported.
* @throws IllegalArgumentException if a negative {@code length} is specified.
* @throws AddressOverflowException if the instruction would fall off the address space
*/
protected DBTraceInstruction doCreate(Lifespan lifespan, Address address,
InternalTracePlatform platform, InstructionPrototype prototype,
ProcessorContextView context)
ProcessorContextView context, int length)
throws CodeUnitInsertionException, AddressOverflowException {
if (platform.getLanguage() != prototype.getLanguage()) {
throw new IllegalArgumentException("Platform and prototype disagree in language");
}
Address endAddress = address.addNoWrap(prototype.getLength() - 1);
int forcedLengthOverride = InstructionDB.checkLengthOverride(length, prototype);
if (length == 0) {
length = prototype.getLength();
}
Address endAddress = address.addNoWrap(length - 1);
AddressRangeImpl createdRange = new AddressRangeImpl(address, endAddress);
// Truncate, then check that against existing code units.
@ -365,7 +382,7 @@ public class DBTraceInstructionsView extends AbstractBaseDBTraceDefinedUnitsView
doSetContext(tasr, prototype.getLanguage(), context);
DBTraceInstruction created = space.instructionMapSpace.put(tasr, null);
created.set(platform, prototype, context);
created.set(platform, prototype, context, forcedLengthOverride);
cacheForContaining.notifyNewEntry(tasr.getLifespan(), createdRange, created);
cacheForSequence.notifyNewEntry(tasr.getLifespan(), createdRange, created);
@ -379,14 +396,14 @@ public class DBTraceInstructionsView extends AbstractBaseDBTraceDefinedUnitsView
@Override
public DBTraceInstruction create(Lifespan lifespan, Address address, TracePlatform platform,
InstructionPrototype prototype, ProcessorContextView context)
InstructionPrototype prototype, ProcessorContextView context, int forcedLengthOverride)
throws CodeUnitInsertionException {
InternalTracePlatform dbPlatform = space.manager.platformManager.assertMine(platform);
try (LockHold hold = LockHold.lock(space.lock.writeLock())) {
DBTraceInstruction created =
doCreate(lifespan, address, dbPlatform, prototype, context);
space.trace.setChanged(new TraceChangeRecord<>(TraceCodeChangeType.ADDED,
space, created, created));
doCreate(lifespan, address, dbPlatform, prototype, context, forcedLengthOverride);
space.trace.setChanged(
new TraceChangeRecord<>(TraceCodeChangeType.ADDED, space, created, created));
return created;
}
catch (AddressOverflowException e) {
@ -567,9 +584,9 @@ public class DBTraceInstructionsView extends AbstractBaseDBTraceDefinedUnitsView
if (lastInstruction != null) {
Address maxAddress = DBTraceCodeManager.instructionMax(lastInstruction, true);
result.addRange(block.getStartAddress(), maxAddress);
space.trace.setChanged(new TraceChangeRecord<>(TraceCodeChangeType.ADDED,
space, new ImmutableTraceAddressSnapRange(
block.getStartAddress(), maxAddress, lifespan)));
space.trace.setChanged(new TraceChangeRecord<>(TraceCodeChangeType.ADDED, space,
new ImmutableTraceAddressSnapRange(block.getStartAddress(), maxAddress,
lifespan)));
}
}
return result;

View file

@ -708,18 +708,19 @@ public abstract class AbstractDBTraceProgramViewListing implements TraceProgramV
}
@Override
@SuppressWarnings("rawtypes")
public PropertyMap getPropertyMap(String propertyName) {
public PropertyMap<?> getPropertyMap(String propertyName) {
// TODO Auto-generated method stub
return null;
}
@Override
public Instruction createInstruction(Address addr, InstructionPrototype prototype,
MemBuffer memBuf, ProcessorContextView context) throws CodeUnitInsertionException {
MemBuffer memBuf, ProcessorContextView context, int forcedLengthOverride)
throws CodeUnitInsertionException {
// TODO: Why memBuf? Can it vary from program memory?
return codeOperations.instructions()
.create(Lifespan.nowOn(program.snap), addr, platform, prototype, context);
.create(Lifespan.nowOn(program.snap), addr, platform, prototype, context,
forcedLengthOverride);
}
@Override

View file

@ -129,6 +129,8 @@ public class DBTraceProgramView implements TraceProgramView {
listenFor(TraceDataTypeChangeType.RENAMED, this::dataTypeRenamed);
listenFor(TraceDataTypeChangeType.DELETED, this::dataTypeDeleted);
listenFor(TraceInstructionChangeType.LENGTH_OVERRIDE_CHANGED,
this::instructionLengthOverrideChanged);
listenFor(TraceInstructionChangeType.FLOW_OVERRIDE_CHANGED,
this::instructionFlowOverrideChanged);
listenFor(TraceInstructionChangeType.FALL_THROUGH_OVERRIDE_CHANGED,
@ -466,9 +468,19 @@ public class DBTraceProgramView implements TraceProgramView {
return;
}
queues.fireEvent(new ProgramChangeRecord(ChangeManager.DOCR_FALLTHROUGH_CHANGED,
instruction.getMinAddress(), instruction.getMaxAddress(), null, null, null));
instruction.getMinAddress(), instruction.getMinAddress(), null, null, null));
}
private void instructionLengthOverrideChanged(TraceAddressSpace space,
TraceInstruction instruction, int oldLengthOverride, int newLengthOverride) {
DomainObjectEventQueues queues = isCodeVisible(space, instruction);
if (queues == null) {
return;
}
queues.fireEvent(new ProgramChangeRecord(ChangeManager.DOCR_LENGTH_OVERRIDE_CHANGED,
instruction.getMinAddress(), instruction.getMinAddress(), null, null, null));
}
private void memoryBytesChanged(TraceAddressSpace space, TraceAddressSnapRange range,
byte[] oldIsNull, byte[] bytes) {
DomainObjectEventQueues queues = isBytesVisible(space, range);

View file

@ -235,6 +235,8 @@ public interface Trace extends DataTypeManagerDomainObject {
new TraceInstructionChangeType<>();
public static final TraceInstructionChangeType<Boolean> FALL_THROUGH_OVERRIDE_CHANGED =
new TraceInstructionChangeType<>();
public static final TraceInstructionChangeType<Integer> LENGTH_OVERRIDE_CHANGED =
new TraceInstructionChangeType<>();
}
public static final class TraceMemoryBytesChangeType

View file

@ -37,11 +37,12 @@ public interface TraceInstructionsView extends TraceBaseDefinedUnitsView<TraceIn
* @param platform the platform
* @param prototype the instruction prototype
* @param context the input disassembly context for the instruction
* @param forcedLengthOverride reduced instruction byte-length (1..7) or 0 to use default length
* @return the new instruction
* @throws CodeUnitInsertionException if the instruction cannot be created
*/
TraceInstruction create(Lifespan lifespan, Address address, TracePlatform platform,
InstructionPrototype prototype, ProcessorContextView context)
InstructionPrototype prototype, ProcessorContextView context, int forcedLengthOverride)
throws CodeUnitInsertionException;
/**
@ -50,10 +51,10 @@ public interface TraceInstructionsView extends TraceBaseDefinedUnitsView<TraceIn
* @see #create(Lifespan, Address, TracePlatform, InstructionPrototype, ProcessorContextView)
*/
default TraceInstruction create(Lifespan lifespan, Address address,
InstructionPrototype prototype, ProcessorContextView context)
InstructionPrototype prototype, ProcessorContextView context, int forcedLengthOverride)
throws CodeUnitInsertionException {
return create(lifespan, address, getTrace().getPlatformManager().getHostPlatform(),
prototype, context);
prototype, context, forcedLengthOverride);
}
/**

View file

@ -564,7 +564,8 @@ public class ToyDBTraceBuilder implements AutoCloseable {
InstructionBlock block = dis.pseudoDisassembleBlock(memBuf, defaultContextValue, 1);
Instruction pseudoIns = block.iterator().next();
return code.instructions()
.create(Lifespan.nowOn(snap), start, platform, pseudoIns.getPrototype(), pseudoIns);
.create(Lifespan.nowOn(snap), start, platform, pseudoIns.getPrototype(), pseudoIns,
0);
}
/**

View file

@ -468,9 +468,6 @@ public class DBTraceCodeUnitTest extends AbstractGhidraHeadlessIntegrationTest
d4008 = b.addData(0, b.addr(0x4008), LongDataType.dataType, b.buf(1, 2, 3, 4));
}
assertTrue(i4004.isSuccessor(i4006));
assertFalse(i4004.isSuccessor(d4008));
assertFalse(i4004.contains(b.addr(0x4003)));
assertTrue(i4004.contains(b.addr(0x4004)));
assertTrue(i4004.contains(b.addr(0x4005)));