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
@@ -131,7 +131,8 @@ public class DebuggerCopyPlan {
}
long off = ins.getMinAddress().subtract(fromRange.getMinAddress());
Address dest = intoAddress.add(off);
intoListing.createInstruction(dest, ins.getPrototype(), ins, ins);
intoListing.createInstruction(dest, ins.getPrototype(), ins, ins,
ins.getLength());
}
}
},
@@ -143,8 +144,7 @@ public class DebuggerCopyPlan {
@Override
public void copy(TraceProgramView from, AddressRange fromRange, Program into,
Address intoAddress, TaskMonitor monitor)
throws Exception {
Address intoAddress, TaskMonitor monitor) throws Exception {
Listing intoListing = into.getListing();
for (Data data : from.getListing()
.getDefinedData(new AddressSet(fromRange), true)) {
@@ -204,8 +204,8 @@ public class DebuggerCopyPlan {
}
}
private Namespace findOrCopyNamespace(Namespace ns, SymbolTable intoTable,
Program into) throws Exception {
private Namespace findOrCopyNamespace(Namespace ns, SymbolTable intoTable, Program into)
throws Exception {
if (ns.isGlobal()) {
return into.getGlobalNamespace();
}
@@ -442,8 +442,8 @@ public class DebuggerCopyPlan {
public boolean isRequiresInitializedMemory(TraceProgramView from, Program dest) {
return checkBoxes.entrySet().stream().anyMatch(ent -> {
Copier copier = ent.getKey();
return copier.isRequiresInitializedMemory() &&
copier.isAvailable(from, dest) && ent.getValue().isSelected();
return copier.isRequiresInitializedMemory() && copier.isAvailable(from, dest) &&
ent.getValue().isSelected();
});
}
}
@@ -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;
@@ -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
@@ -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
@@ -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;
@@ -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
@@ -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);
@@ -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
@@ -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);
}
/**
@@ -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);
}
/**
@@ -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)));
@@ -220,6 +220,8 @@ src/main/help/help/topics/DbViewerPlugin/images/DatabaseViewer.png||GHIDRA||||EN
src/main/help/help/topics/DisassembledViewPlugin/DisassembledViewPlugin.htm||GHIDRA||||END|
src/main/help/help/topics/DisassembledViewPlugin/images/DisassembledViewPluginMain.png||GHIDRA||||END|
src/main/help/help/topics/DisassemblerPlugin/Disassembly.htm||GHIDRA||||END|
src/main/help/help/topics/DisassemblerPlugin/images/LengthOverrideLockPrefixExample.png||GHIDRA||||END|
src/main/help/help/topics/DisassemblerPlugin/images/LengthOverrideLockPrefixExample2.png||GHIDRA||||END|
src/main/help/help/topics/DisassemblerPlugin/images/ProcessorOptions.png||GHIDRA||||END|
src/main/help/help/topics/DockingWindows/Docking_Windows.htm||GHIDRA||||END|
src/main/help/help/topics/DockingWindows/images/Tool.png||GHIDRA||||END|
@@ -24,7 +24,15 @@
<LI><A href="#Disassemble_Static">Static Disassembly</A></LI>
<LI><A href="#Disassemble_Restricted">Restricted Disassembly</A></LI>
<LI><A href="#Disassemble_Arm">Disassemble ARM / Thumb</A></LI>
<LI><A href="#ProcessorOptions">Processor Options</A></LI>
<LI><A href="#Modify_Instruction_Flow">Modify Instruction Flow</A></LI>
<LI><A href="#Modify_Instruction_Length">Modify Instruction Length</A></LI>
</UL>
<H2><A name="Disassemble"></A>Disassembly</H2>
@@ -131,7 +139,7 @@
undefined bytes.</I></P>
</BLOCKQUOTE>
<H2><A name="Disassemble_Restricted"></A>Disassembly (Restricted)</H2>
<H2><A name="Disassemble_Restricted"></A>Restricted Disassembly</H2>
<BLOCKQUOTE>
<P><I>Restricted Disassembly</I> is similar to <I>Disassembly</I>, except that only bytes in
@@ -157,13 +165,11 @@
undefined bytes.</I></P>
</BLOCKQUOTE>
<P class="providedbyplugin">&nbsp;</P>
<H2><A name="Disassemble_Arm"></A>Disassemble ARM / Disassemble Thumb<A name=
<H2><A name="Disassemble_Arm"></A>Disassemble ARM / Thumb<A name=
"Disassemble_Thumb"></A></H2>
<BLOCKQUOTE>
<P><I>Disassemble ARM and Disassemble Thumb</I> will only be available if the program you are
<P><I>Disassemble ARM and Disassemble Thumb actions</I> will only be available if the program you are
working with is an ARM based processor.&nbsp; ARM processors have two states, ARM and Thumb
mode.&nbsp; The instructions available in ARM mode are 4 bytes long.&nbsp; In Thumb mode, the
instructions are "generally" 2 bytes long.&nbsp; ARM and Thumb mode are mutually exclusive,
@@ -199,38 +205,6 @@
undefined bytes.</I></P>
</BLOCKQUOTE>
<H2><A name="Modify_Instruction_Flow"></A>Modify Instruction Flow</H2>
<BLOCKQUOTE>
<P>With certain processors and situations it may be desirable to modify the default
flow of an instruction to better reflect the nature of its flow. For example a jump may
actually be performing a call type operation, a call may be performing a long-jump.
This distinction primarily affects the subroutine models and flow analysis performed
within Ghidra.</P>
<P>The following basic flow types may be imposed upon the default flow of an instruction:</P>
<UL>
<LI>BRANCH</LI>
<LI>CALL</LI>
<LI>CALL_RETURN</LI>
<LI>RETURN</LI>
</UL>
<P>In all situations the conditional nature of the original flow is perserved.</P>
<P>To Modify Instruction Flow:</P>
<OL>
<LI>Place the cursor on an instruction within the Code Browser. Note that
instructions which are purely fall-through can not be modified.</LI>
<LI>Right-mouse-click</LI>
<LI>Select <I>Modify Instruction Flow...</I> menu item.
<LI>Within the <I>Modify Instruction Flow</I> dialog select the desired basic flow
behavior.</LI>
<LI>Click OK in the dialog</LI>
</OL>
<P align="left"><I><IMG src="help/shared/tip.png">An instruction whose flow has been
modified will have its' mnemonic color modified.</I></P>
</BLOCKQUOTE>
<H2><A name="ProcessorOptions"></A>Processor Options</H2>
<BLOCKQUOTE>
@@ -244,11 +218,6 @@
<DIV align="center">
<IMG src="images/ProcessorOptions.png"><BR>
<BR>
<DIV align="left">
<BR>
</DIV>
</DIV>
<P>The options are
@@ -282,8 +251,90 @@
<LI><A href="help/topics/LanguageProviderPlugin/Languages.htm">Languages</A></LI>
</UL>
<P>&nbsp;</P>
<BR>
<H2><A name="Modify_Instruction_Flow"></A>Modify Instruction Flow</H2>
<BLOCKQUOTE>
<P>With certain processors and situations it may be desirable to modify the default
flow of an instruction to better reflect the nature of its flow. For example a jump may
actually be performing a call type operation, a call may be performing a long-jump.
This distinction primarily affects the subroutine models and flow analysis performed
within Ghidra.</P>
<P>The following basic flow types may be imposed upon the default flow of an instruction:</P>
<UL>
<LI>BRANCH</LI>
<LI>CALL</LI>
<LI>CALL_RETURN</LI>
<LI>RETURN</LI>
</UL>
<P>In all situations the conditional nature of the original flow is perserved.</P>
<P>To Modify Instruction Flow:</P>
<OL>
<LI>Place the cursor on an instruction within the Code Browser. Note that
instructions which are purely fall-through can not be modified.</LI>
<LI>Right-mouse-click</LI>
<LI>Select <I>Modify Instruction Flow...</I> menu item.
<LI>Within the <I>Modify Instruction Flow</I> dialog select the desired basic flow
behavior.</LI>
<LI>Click OK in the dialog</LI>
</OL>
<P align="left"><I><IMG src="help/shared/tip.png">An instruction whose flow has been
modified will have its' mnemonic color modified.</I></P>
</BLOCKQUOTE>
<H2><A name="Modify_Instruction_Length"></A>Modify Instruction Length</H2>
<BLOCKQUOTE>
<P>There are certain situations where code may flow into an offcut location within the
middle of another instruction where the bytes happen to form a different instruction.
While this generally indicates a bad flow, this can also be a legitimate situation
of an overlapping instruction. For example, with x86 instructions where a flow
may bypass a LOCK prefix byte. Depending on which flow disassembles first, the situation
may manifest differently. Below is an example of the x86 LOCK prefix case. This situation can be
quickly identified by the error bookmark along with the offcut reference. In this case the
<code>JZ</code> instruction has two flows: 1) one falls-through and 2) conditionally jumps
around the LOCK prefix byte resulting in an offcut flow and disassembly conflict.</P>
<BR>
<DIV align="center">
<IMG src="images/LengthOverrideLockPrefixExample.png"><BR>
<BR>
</DIV>
<P>The above case can be resolved by overriding the code-unit length of the first
instruction to 1-byte allowing the incomplete disassembly of the offcut instruction
to be repaired.</P>
<OL>
<LI>Place the cursor on an instruction within the Code Browser (e.g.,
<code>CMPXCHG</code> instruction).</LI>
<LI>Right-mouse-click</LI>
<LI>Select <I>Modify Instruction Length...</I> menu item.
<LI>Within the <I>Modify Instruction Length</I> dialog enter the reduced instruction
length (e.g., 1 in this case). Note that this does not impact the number of bytes
actually parsed, but only the affective code unit length.</LI>
<LI>Click OK in the dialog</LI>
<LI>This should result in the subsequent locations becoming undefined code units
which can know be disassembled. Click on the first undefined location and disassemble
(i.e., <B>D</B> key binding).</LI>
<LI>You may also delete the error bookmark which should no longer be relavent.
</OL>
<P>The image below shows this same code after these length override steps have been
performed. The fallthrough of the first instruction, whose length was overriden from four
to one, is preserved and both instructions fallthrough to the same <code>JNZ</code>
instruction.
</P>
<BR>
<DIV align="center">
<IMG src="images/LengthOverrideLockPrefixExample2.png"><BR>
<BR>
</DIV>
</BLOCKQUOTE>
</BODY>
</HTML>
Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

@@ -68,28 +68,31 @@ public abstract class CodeUnitDetails {
boolean removedFallThrough =
inst.isFallThroughOverridden() && (inst.getFallThrough() == null);
boolean hasFlowOverride = inst.getFlowOverride() != FlowOverride.NONE;
boolean hasLengthOverride = inst.isLengthOverridden();
cuRep = cu.toString();
if (removedFallThrough) {
cuRep +=
NEW_LINE + indent + getSpaces(addrRangeStr.length()) + " " +
"Removed FallThrough";
cuRep += NEW_LINE + indent + getSpaces(addrRangeStr.length()) + " " +
"Removed FallThrough";
}
else if (inst.isFallThroughOverridden()) {
Reference[] refs = cu.getReferencesFrom();
// Show the fallthrough override.
for (int i = 0; i < refs.length; i++) {
if (refs[i].getReferenceType().isFallthrough()) {
cuRep +=
NEW_LINE + indent + getSpaces(addrRangeStr.length()) + " " +
"FallThrough Override: " +
DiffUtility.getUserToAddressString(inst.getProgram(), refs[i]);
for (Reference ref : refs) {
if (ref.getReferenceType().isFallthrough()) {
cuRep += NEW_LINE + indent + getSpaces(addrRangeStr.length()) + " " +
"FallThrough Override: " +
DiffUtility.getUserToAddressString(inst.getProgram(), ref);
}
}
}
if (hasFlowOverride) {
cuRep +=
NEW_LINE + indent + getSpaces(addrRangeStr.length()) + " " +
"Flow Override: " + inst.getFlowOverride();
cuRep += NEW_LINE + indent + getSpaces(addrRangeStr.length()) + " " +
"Flow Override: " + inst.getFlowOverride();
}
if (hasLengthOverride) {
cuRep += NEW_LINE + indent + getSpaces(addrRangeStr.length()) + " " +
"Length Override: " + inst.getLength() + " (actual length is " +
inst.getParsedLength() + ")";
}
// Commented the following out, since we may want the hash code in the future.
// cuRep +=
@@ -146,15 +149,15 @@ public abstract class CodeUnitDetails {
return indent + "None";
}
StringBuffer buf = new StringBuffer();
for (int i = 0; i < refs.length; i++) {
if (refs[i].isExternalReference()) {
buf.append(indent + "External Reference " + getRefInfo(pgm, refs[i]) + NEW_LINE);
for (Reference ref : refs) {
if (ref.isExternalReference()) {
buf.append(indent + "External Reference " + getRefInfo(pgm, ref) + NEW_LINE);
}
else if (refs[i].isStackReference()) {
buf.append(indent + "Stack Reference " + getRefInfo(pgm, refs[i]) + NEW_LINE);
else if (ref.isStackReference()) {
buf.append(indent + "Stack Reference " + getRefInfo(pgm, ref) + NEW_LINE);
}
else {
buf.append(indent + "Reference " + getRefInfo(pgm, refs[i]) + NEW_LINE);
buf.append(indent + "Reference " + getRefInfo(pgm, ref) + NEW_LINE);
}
}
return buf.toString();
@@ -858,7 +858,13 @@ class CodeUnitMerger extends AbstractListingMerger {
private void performMergeInstruction(Instruction instruction, boolean copyBytes)
throws CodeUnitInsertionException, MemoryAccessException {
Address minAddress = instruction.getMinAddress();
Address maxAddress = instruction.getMaxAddress();
Address maxAddress;
if (instruction.isLengthOverridden()) {
maxAddress = minAddress.add(instruction.getParsedLength() - 1);
}
else {
maxAddress = instruction.getMaxAddress();
}
Program fromPgm = instruction.getProgram();
// Code unit should already be cleared where this instruction needs to go.
Listing resultListing = resultPgm.getListing();
@@ -868,9 +874,12 @@ class CodeUnitMerger extends AbstractListingMerger {
ProgramMemoryUtil.copyBytesInRanges(resultPgm, fromPgm, minAddress, maxAddress);
}
// avoid forcing length of new instruction if old instruction length was not overriden
int lengthOverride = instruction.isLengthOverridden() ? instruction.getLength() : 0;
Instruction inst = resultListing.createInstruction(minAddress, instruction.getPrototype(),
new DumbMemBufferImpl(resultPgm.getMemory(), minAddress),
new ProgramProcessorContext(resultPgm.getProgramContext(), minAddress));
new ProgramProcessorContext(resultPgm.getProgramContext(), minAddress), lengthOverride);
// Set the fallthrough override if necessary.
if (instruction.isFallThroughOverridden()) {
@@ -570,7 +570,6 @@ public class ListingMergeManager implements MergeResolver, ListingMergeConstants
* determine the conflicts.
* @param mergers the listing mergers whose conflicts are to be merged.
* @param monitor indicates progress to user and allows cancel.
* @throws ProgramConflictException if programs can't be compared using Diff.
* @throws MemoryAccessException if bytes can't be merged.
* @throws CancelledException if the user cancels the merge.
*/
@@ -445,6 +445,7 @@ public class AutoAnalysisManager implements DomainObjectListener {
break;
case ChangeManager.DOCR_FALLTHROUGH_CHANGED:
case ChangeManager.DOCR_FLOWOVERRIDE_CHANGED:
case ChangeManager.DOCR_LENGTH_OVERRIDE_CHANGED:
// TODO: not sure if this should be done this way or explicitly
// via the application commands (this is inconsistent with other
// codeDefined cases which do not rely on change events (e.g., disassembly)
@@ -88,6 +88,7 @@ public class DisassemblerPlugin extends Plugin {
private DockingAction x86_64DisassembleAction;
private DockingAction x86_32DisassembleAction;
private DockingAction setFlowOverrideAction;
private DockingAction setLengthOverrideAction;
/** Dialog for obtaining the processor state to be used for disassembling. */
// private ProcessorStateDialog processorStateDialog;
@@ -177,6 +178,7 @@ public class DisassemblerPlugin extends Plugin {
x86_64DisassembleAction = new X86_64DisassembleAction(this, GROUP_NAME, false);
x86_32DisassembleAction = new X86_64DisassembleAction(this, GROUP_NAME, true);
setFlowOverrideAction = new SetFlowOverrideAction(this, GROUP_NAME);
setLengthOverrideAction = new SetLengthOverrideAction(this, GROUP_NAME);
tool.addAction(disassembleAction);
tool.addAction(disassembleRestrictedAction);
@@ -193,6 +195,7 @@ public class DisassemblerPlugin extends Plugin {
tool.addAction(x86_32DisassembleAction);
tool.addAction(contextAction);
tool.addAction(setFlowOverrideAction);
tool.addAction(setLengthOverrideAction);
}
void disassembleRestrictedCallback(ListingActionContext context) {
@@ -0,0 +1,137 @@
/* ###
* 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.app.plugin.core.disassembler;
import db.Transaction;
import docking.action.MenuData;
import docking.widgets.dialogs.NumberInputDialog;
import ghidra.app.context.ListingActionContext;
import ghidra.app.context.ListingContextAction;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.address.Address;
import ghidra.program.model.listing.*;
import ghidra.program.model.util.CodeUnitInsertionException;
import ghidra.util.Msg;
class SetLengthOverrideAction extends ListingContextAction {
private DisassemblerPlugin plugin;
public SetLengthOverrideAction(DisassemblerPlugin plugin, String groupName) {
super("Modify Instruction Length", plugin.getName());
this.plugin = plugin;
setPopupMenuData(
new MenuData(new String[] { "Modify Instruction Length..." }, null, groupName));
}
@Override
public void actionPerformed(ListingActionContext context) {
PluginTool tool = plugin.getTool();
Address address = context.getAddress();
if (address == null) {
return;
}
Program program = context.getProgram();
Listing listing = program.getListing();
Instruction instr = listing.getInstructionAt(address);
if (instr == null) {
return;
}
int protoLen = instr.getPrototype().getLength();
if (protoLen == 1) {
Msg.showError(this, null, "Length Override Error",
"Length override for 1-byte instruction not allowed");
return;
}
String restoreTip = ", 0=restore";
String alignTip = "";
int align = program.getLanguage().getInstructionAlignment();
if (align != 1) {
alignTip = ", must be multiple of " + align;
}
int minLength = 0;
long maxLength = Math.min(Instruction.MAX_LENGTH_OVERRIDE, protoLen - 1);
Instruction nextInstr = listing.getInstructionAfter(address);
if (nextInstr != null &&
nextInstr.getAddress().getAddressSpace().equals(address.getAddressSpace())) {
long limit = nextInstr.getAddress().subtract(address);
maxLength = Math.min(limit, maxLength);
if (limit < instr.getParsedLength()) {
minLength = 1; // unable to restore to default length using 0 value
restoreTip = "";
}
}
if (maxLength == 1) {
// Assume we have an instruction whose length can't be changed
Msg.showError(this, null, "Length Override Error",
"Insufficient space to alter current length override of 1-byte");
return;
}
int currentLengthOverride = 0;
if (instr.isLengthOverridden()) {
currentLengthOverride = instr.getLength();
}
NumberInputDialog dialog = new NumberInputDialog("Override/Restore Instruction Length",
"Enter byte-length [" + minLength + ".." + maxLength + restoreTip + alignTip + "]",
currentLengthOverride, minLength, (int) maxLength, false);
tool.showDialog(dialog);
if (dialog.wasCancelled()) {
return;
}
String kind = "Set";
int lengthOverride = dialog.getIntValue();
if (lengthOverride == 0) {
if (!instr.isLengthOverridden()) {
return; // no change
}
kind = "Clear";
}
try (Transaction tx = instr.getProgram().openTransaction(kind + " Length Override")) {
instr.setLengthOverride(lengthOverride);
}
catch (CodeUnitInsertionException e) {
Msg.showError(this, null, "Length Override Error", e.getMessage());
}
}
@Override
public boolean isEnabledForContext(ListingActionContext context) {
Address address = context.getAddress();
if (address == null) {
return false;
}
Program program = context.getProgram();
Instruction instr = program.getListing().getInstructionAt(address);
if (instr == null) {
return false;
}
int alignment = program.getLanguage().getInstructionAlignment();
return instr.getParsedLength() > alignment;
}
}
@@ -91,8 +91,8 @@ public class FallThroughPlugin extends Plugin {
return instruction != null && !instruction.isFallThroughOverridden();
}
};
autoFallthroughAction.setPopupMenuData(new MenuData(new String[] { "Fallthrough",
"Auto Override" }, null, "Fallthrough"));
autoFallthroughAction.setPopupMenuData(
new MenuData(new String[] { "Fallthrough", "Auto Override" }, null, "Fallthrough"));
tool.addAction(autoFallthroughAction);
@@ -111,8 +111,8 @@ public class FallThroughPlugin extends Plugin {
return instruction != null && instruction.isFallThroughOverridden();
}
};
clearFallthroughAction.setPopupMenuData(new MenuData(new String[] { "Fallthrough",
"Clear Overrides" }, null, "Fallthrough"));
clearFallthroughAction.setPopupMenuData(
new MenuData(new String[] { "Fallthrough", "Clear Overrides" }, null, "Fallthrough"));
tool.addAction(clearFallthroughAction);
@@ -128,8 +128,8 @@ public class FallThroughPlugin extends Plugin {
return instruction != null;
}
};
setFallthroughAction.setPopupMenuData(new MenuData(
new String[] { "Fallthrough", "Set..." }, null, "Fallthrough"));
setFallthroughAction.setPopupMenuData(
new MenuData(new String[] { "Fallthrough", "Set..." }, null, "Fallthrough"));
tool.addAction(setFallthroughAction);
}
@@ -326,8 +326,9 @@ public class InsertBytesWidget extends ReusableDialogComponentProvider implement
private List<OperandMetadata> createOperandMetadata(PseudoInstruction instruction)
throws MemoryAccessException {
List<OperandMetadata> operands = new ArrayList<>();
byte[] bytes = instruction.getParsedBytes();
List<OperandMetadata> operands = new ArrayList<>();
for (int i = 0; i < instruction.getNumOperands(); i++) {
OperandMetadata operandMD = new OperandMetadata();
operandMD.setOpType(instruction.getOperandType(i));
@@ -337,8 +338,9 @@ public class InsertBytesWidget extends ReusableDialogComponentProvider implement
// prototype object in the pseudo instruction. For the value string we have to do
// a bit of calculating: we know the entire instruction byte string and we know
// this operand mask, so AND them together and we get the operand bytes.
byte[] mask = instruction.getPrototype().getOperandValueMask(i).getBytes();
byte[] value = InstructionSearchUtils.byteArrayAnd(mask, instruction.getBytes());
InstructionPrototype prototype = instruction.getPrototype();
byte[] mask = prototype.getOperandValueMask(i).getBytes();
byte[] value = InstructionSearchUtils.byteArrayAnd(mask, bytes);
MaskContainer maskContainer = new MaskContainer(mask, value);
operandMD.setMaskContainer(maskContainer);
@@ -362,8 +364,10 @@ public class InsertBytesWidget extends ReusableDialogComponentProvider implement
// The mask array we can get directly from the prototype. For the value array we
// have to figure out which bits pertain to operands and just zero them out, so we're
// just left with the instruction (mnemonic) bits.
byte[] mask = instruction.getPrototype().getInstructionMask().getBytes();
byte[] value = clearOperandBits(mask, instruction.getBytes());
InstructionPrototype prototype = instruction.getPrototype();
byte[] mask = prototype.getInstructionMask().getBytes();
byte[] value = instruction.getParsedBytes();
value = clearOperandBits(mask, value);
MaskContainer mnemonicMask = new MaskContainer(mask, value);
InstructionMetadata instructionMD = new InstructionMetadata(mnemonicMask);
@@ -1,6 +1,5 @@
/* ###
* IP: GHIDRA
* REVIEWED: YES
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,8 +17,7 @@ package ghidra.app.plugin.core.reloc;
import ghidra.program.model.address.Address;
import ghidra.program.model.lang.*;
import ghidra.program.model.listing.Instruction;
import ghidra.program.model.listing.Program;
import ghidra.program.model.listing.*;
import ghidra.program.model.mem.DumbMemBufferImpl;
import ghidra.program.model.mem.MemBuffer;
import ghidra.program.model.symbol.Reference;
@@ -31,6 +29,10 @@ public class InstructionStasher {
private Address address;
private InstructionPrototype prototype;
private Reference[] referencesFrom;
private FlowOverride flowOverride;
private Address fallthroughOverride;
private int lengthOverride;
private Address minAddress;
public InstructionStasher(Program program, Address address) {
@@ -47,6 +49,12 @@ public class InstructionStasher {
minAddress = instruction.getMinAddress();
prototype = instruction.getPrototype();
referencesFrom = instruction.getReferencesFrom();
flowOverride = instruction.getFlowOverride();
fallthroughOverride =
instruction.isFallThroughOverridden() ? instruction.getFallThrough() : null;
// Relocation data change may mutate instruction. Do not force length of instruction
// unless it was previously overriden. A value of 0 allows length to match prototoype.
lengthOverride = instruction.isLengthOverridden() ? instruction.getLength() : 0;
program.getListing().clearCodeUnits(minAddress, instruction.getMaxAddress(), false);
}
@@ -57,7 +65,16 @@ public class InstructionStasher {
MemBuffer buf = new DumbMemBufferImpl(program.getMemory(), minAddress);
ProcessorContext context =
new ProgramProcessorContext(program.getProgramContext(), minAddress);
program.getListing().createInstruction(minAddress, prototype, buf, context);
Instruction instr = program.getListing()
.createInstruction(minAddress, prototype, buf, context, lengthOverride);
if (flowOverride != FlowOverride.NONE) {
instr.setFlowOverride(flowOverride);
}
if (fallthroughOverride != null) {
instr.setFallThrough(fallthroughOverride);
}
for (Reference reference : referencesFrom) {
if (reference.getSource() != SourceType.DEFAULT) {
@@ -568,7 +568,13 @@ class ProgramTextWriter {
}
try {
byte[] bytes = cu.getBytes();
byte[] bytes;
if (cu instanceof Instruction instr) {
bytes = instr.getParsedBytes();
}
else {
bytes = cu.getBytes();
}
StringBuffer bytesbuf = new StringBuffer();
for (int i = 0; i < bytes.length; ++i) {
if (i > 0) {
@@ -630,10 +636,7 @@ class ProgramTextWriter {
if (options.isHTML()) {
Reference ref =
cu.getProgram()
.getReferenceManager()
.getPrimaryReferenceFrom(cuAddress,
i);
cu.getProgram().getReferenceManager().getPrimaryReferenceFrom(cuAddress, i);
addReferenceLinkedText(ref, opReps[i], true);
}
else {
@@ -174,7 +174,18 @@ public class BytesFieldFactory extends FieldFactory {
}
CodeUnit cu = (CodeUnit) obj;
int length = Math.min(cu.getLength(), 100);
int length;
// Instructions: use prototype length so we display all bytes for length-override case
if (cu instanceof Instruction) {
// Consider all parsed bytes even if the overlap the next instruction due to the
// use of an instruction length-override.
length = ((Instruction) cu).getParsedLength();
}
else {
length = Math.min(cu.getLength(), 100);
}
byte[] bytes = new byte[length];
Memory memory = cu.getProgram().getMemory();
try {
@@ -120,7 +120,8 @@ public class MnemonicFieldFactory extends FieldFactory {
}
else if (cu instanceof Instruction) {
Instruction instr = (Instruction) cu;
if (instr.getFlowOverride() != FlowOverride.NONE || instr.isFallThroughOverridden()) {
if (instr.getFlowOverride() != FlowOverride.NONE || instr.isFallThroughOverridden() ||
instr.isLengthOverridden()) {
c = MnemonicColors.OVERRIDE;
}
}
@@ -177,12 +177,20 @@ public class PostCommentFieldFactory extends FieldFactory {
}
}
if (instr.isFallThroughOverridden()) {
if (instr.isLengthOverridden() || instr.isFallThroughOverridden()) {
Address fallThrough = instr.getFallThrough();
String fallthroughComment = "-- Fallthrough Override: " +
(fallThrough != null ? fallThrough.toString() : "NO-FALLTHROUGH");
String fallthroughComment =
"-- Fallthrough" + (instr.isFallThroughOverridden() ? " Override" : "") + ": " +
(fallThrough != null ? fallThrough.toString() : "NO-FALLTHROUGH");
comments.addFirst(fallthroughComment);
}
if (instr.isLengthOverridden()) {
String lengthOverrideComment = "-- Length Override: " + instr.getLength() +
" (actual length is " + instr.getParsedLength() + ")";
comments.addFirst(lengthOverrideComment);
}
FlowOverride flowOverride = instr.getFlowOverride();
if (flowOverride != FlowOverride.NONE) {
String flowOverrideComment =
@@ -194,10 +194,10 @@ public class DiffUtility extends SimpleDiffUtility {
}
else if (namespace instanceof GhidraClass) {
return otherProgram.getSymbolTable()
.createClass(otherParentNamespace, namespace.getName(), source);
.createClass(otherParentNamespace, namespace.getName(), source);
}
return otherProgram.getSymbolTable()
.createNameSpace(otherParentNamespace, namespace.getName(), source);
.createNameSpace(otherParentNamespace, namespace.getName(), source);
}
// /**
@@ -330,10 +330,10 @@ public class DiffUtility extends SimpleDiffUtility {
return null;
}
return otherProgram.getReferenceManager()
.getReference(fromAddr, toAddr, ref.getOperandIndex());
.getReference(fromAddr, toAddr, ref.getOperandIndex());
}
Reference otherRef = otherProgram.getReferenceManager()
.getPrimaryReferenceFrom(fromAddr, ref.getOperandIndex());
.getPrimaryReferenceFrom(fromAddr, ref.getOperandIndex());
if (otherRef != null && ref.getToAddress().hasSameAddressSpace(otherRef.getToAddress())) {
return otherRef;
}
@@ -358,10 +358,10 @@ public class DiffUtility extends SimpleDiffUtility {
return null;
}
return program.getReferenceManager()
.getReference(fromAddr1, toAddr1, p2Ref.getOperandIndex());
.getReference(fromAddr1, toAddr1, p2Ref.getOperandIndex());
}
Reference p1Ref = program.getReferenceManager()
.getPrimaryReferenceFrom(fromAddr1, p2Ref.getOperandIndex());
.getPrimaryReferenceFrom(fromAddr1, p2Ref.getOperandIndex());
if (p1Ref != null && p1Ref.getToAddress().hasSameAddressSpace(p2Ref.getToAddress())) {
return p1Ref;
}
@@ -386,8 +386,8 @@ public class DiffUtility extends SimpleDiffUtility {
}
// FIXME Should this be passing the Namespace?
return otherProgram.getExternalManager()
.addExtLocation(extLoc.getLibraryName(), extLoc.getLabel(), otherAddr,
extLoc.getSource());
.addExtLocation(extLoc.getLibraryName(), extLoc.getLabel(), otherAddr,
extLoc.getSource());
}
/**
@@ -571,15 +571,18 @@ public class DiffUtility extends SimpleDiffUtility {
Address rangeMin = range.getMinAddress();
Address rangeMax = range.getMaxAddress();
CodeUnit minCu = listing.getCodeUnitContaining(rangeMin);
// NOTE: minCu does not consider prior code unit which may share bytes due to
// possible instruction length-override. Max address of code unit will factor-in
// full length of instruction prototype.
if (minCu != null) {
Address minCuMinAddr = minCu.getMinAddress();
if (minCuMinAddr.compareTo(rangeMin) != 0) {
addrs.addRange(minCuMinAddr, minCu.getMaxAddress());
addrs.addRange(minCuMinAddr, getMaxAddress(minCu));
}
}
CodeUnit maxCu = listing.getCodeUnitContaining(rangeMax);
if (maxCu != null) {
Address maxCuMaxAddr = maxCu.getMaxAddress();
Address maxCuMaxAddr = getMaxAddress(maxCu);
if (maxCuMaxAddr.compareTo(rangeMax) != 0) {
addrs.addRange(maxCu.getMinAddress(), maxCuMaxAddr);
}
@@ -588,6 +591,20 @@ public class DiffUtility extends SimpleDiffUtility {
return addrs;
}
/**
* Get the code unit max address. Instruction max address will be based upon prototype
* length which may be longer than code unit length if length-override is in effect.
* @param cu code unit
* @return effective code unit length
*/
private static Address getMaxAddress(CodeUnit cu) {
if (cu instanceof Instruction inst && inst.isLengthOverridden()) {
// factor in prototype length when length-override applies
return cu.getMinAddress().add(inst.getParsedLength() - 1);
}
return cu.getMaxAddress();
}
/**
* Returns the signed hex string representing the int value.
* Positive values are represented beginning with 0x. (i.e. value of 12 would be 0xc)
@@ -2871,6 +2871,7 @@ public class ProgramDiff {
if (i1 == i2) {
return true;
}
// Checking length and prototype will handle use of length-override
if (i1.getLength() != i2.getLength()) {
return false;
}
@@ -2888,7 +2889,9 @@ public class ProgramDiff {
}
try {
if (!Arrays.equals(i1.getBytes(), i2.getBytes())) {
byte[] bytes1 = i1.getParsedBytes();
byte[] bytes2 = i2.getParsedBytes();
if (!Arrays.equals(bytes1, bytes2)) {
return false; // bytes differ
}
}
@@ -906,8 +906,7 @@ public class ProgramDiffDetails {
ordinal + " " + fieldName + " " + actualDt.getMnemonic(actualDt.getDefaultSettings()) +
" " + getCategoryName(actualDt) + " " + "DataTypeSize=" +
(actualDt.isZeroLength() ? 0 : actualDt.getLength()) + " " + "ComponentSize=" +
dtc.getLength() + " " + ((comment != null) ? comment : "") +
" " + newLine);
dtc.getLength() + " " + ((comment != null) ? comment : "") + " " + newLine);
return actualDt;
}
@@ -958,8 +957,7 @@ public class ProgramDiffDetails {
fieldName = "field" + offset;
}
buf.append(newIndent + min.add(offset) + " " + dtc.getFieldName() + " " +
dtc.getDataType().getName() + " " + "length=" +
dtc.getLength() + " " +
dtc.getDataType().getName() + " " + "length=" + dtc.getLength() + " " +
((comment != null) ? comment : "") + " " + newLine);
}
}
@@ -987,6 +985,7 @@ public class ProgramDiffDetails {
boolean removedFallThrough =
inst.isFallThroughOverridden() && (inst.getFallThrough() == null);
boolean hasFlowOverride = inst.getFlowOverride() != FlowOverride.NONE;
boolean hasLengthOverride = inst.isLengthOverridden();
cuRep = cu.toString();
if (removedFallThrough) {
cuRep += newLine + indent + getSpaces(addrRangeStr.length()) + " " +
@@ -1011,6 +1010,11 @@ public class ProgramDiffDetails {
cuRep += newLine + indent + getSpaces(addrRangeStr.length()) + " " +
"Flow Override: " + inst.getFlowOverride();
}
if (hasLengthOverride) {
cuRep += newLine + indent + getSpaces(addrRangeStr.length()) + " " +
"Length Override: " + inst.getLength() + " (actual length is " +
inst.getParsedLength() + ")";
}
cuRep += newLine + indent + getSpaces(addrRangeStr.length()) + " " +
"Instruction Prototype hash = " +
Integer.toHexString(inst.getPrototype().hashCode());
@@ -1463,8 +1467,8 @@ public class ProgramDiffDetails {
private boolean addSpecificCommentDetails(int commentType, String commentName) {
boolean hasCommentDiff = false;
try {
for (Address p1Address = minP1Address; p1Address.compareTo(
maxP1Address) <= 0; p1Address = p1Address.add(1L)) {
for (Address p1Address = minP1Address; p1Address
.compareTo(maxP1Address) <= 0; p1Address = p1Address.add(1L)) {
Address p2Address = SimpleDiffUtility.getCompatibleAddress(p1, p1Address, p2);
String noComment = "No " + commentName + ".";
String cmt1 = l1.getComment(commentType, p1Address);
@@ -2169,8 +2173,7 @@ public class ProgramDiffDetails {
// Handle case where the class for a Saveable property is missing (unsupported).
if (cu.getProgram()
.getListing()
.getPropertyMap(
propertyName) instanceof UnsupportedMapDB) {
.getPropertyMap(propertyName) instanceof UnsupportedMapDB) {
buf.append(
indent2 + propertyName + " is an unsupported property." + newLine);
continue;
@@ -2241,8 +2244,8 @@ public class ProgramDiffDetails {
BookmarkManager bmm1 = p1.getBookmarkManager();
BookmarkManager bmm2 = p2.getBookmarkManager();
try {
for (Address p1Address = minP1Address; p1Address.compareTo(
maxP1Address) <= 0; p1Address = p1Address.add(1)) {
for (Address p1Address = minP1Address; p1Address
.compareTo(maxP1Address) <= 0; p1Address = p1Address.add(1)) {
Address p2Address = SimpleDiffUtility.getCompatibleAddress(p1, p1Address, p2);
Bookmark[] marks1 = bmm1.getBookmarks(p1Address);
Arrays.sort(marks1, BOOKMARK_COMPARATOR);
@@ -2341,7 +2344,7 @@ public class ProgramDiffDetails {
private boolean isSameInstruction(Instruction i1, Instruction i2) {
boolean samePrototypes = i1.getPrototype().equals(i2.getPrototype());
boolean sameInstructionLength = i1.getLength() == i2.getLength();
boolean sameInstructionLength = i1.getLength() == i2.getLength(); // factors length override
boolean sameFallthrough = ProgramDiff.isSameFallthrough(p1, i1, p2, i2);
boolean sameFlowOverride = i1.getFlowOverride() == i2.getFlowOverride();
return samePrototypes && sameInstructionLength && sameFallthrough && sameFlowOverride;
@@ -29,7 +29,6 @@ import ghidra.util.datastruct.Accumulator;
import ghidra.util.datastruct.ListAccumulator;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;
import ghidra.util.task.TaskMonitorAdapter;
import utility.function.TerminatingConsumer;
/**
@@ -151,14 +150,26 @@ public class ProgramMemoryUtil {
*/
private static void copyByteRange(Memory toMem, Memory fromMem, AddressRange range)
throws MemoryAccessException {
// TODO: Addresses from one program cannot be used with another program (e.g., overlays)
// TODO: Should be using AddressTranslator
// TODO: Method relies too heavily on caller limiting range to valid initialized memory in both programs
// Copy the bytes for this range
int length = 0;
Address writeAddress = range.getMinAddress();
for (long len = range.getLength(); len > 0; len -= length) {
length = (int) Math.min(len, Integer.MAX_VALUE);
byte[] bytes = new byte[length];
fromMem.getBytes(writeAddress, bytes);
toMem.setBytes(writeAddress, bytes);
byte[] srcBytes = new byte[length];
// NOTE: problems will arise if srcLen != length
int srcLen = fromMem.getBytes(writeAddress, srcBytes);
byte[] destBytes = new byte[srcLen];
if (toMem.getBytes(writeAddress, destBytes) != srcLen ||
!Arrays.equals(destBytes, 0, srcLen, srcBytes, 0, srcLen)) {
// only copy bytes if they differ
toMem.setBytes(writeAddress, srcBytes, 0, srcLen);
}
if (len > length) {
writeAddress = writeAddress.add(length);
}
@@ -628,7 +628,9 @@ public class ProgramMerge {
return true;
}
try {
if (!Arrays.equals(instruction.getBytes(), resultInstruction.getBytes())) {
byte[] bytes = instruction.getParsedBytes();
byte[] resultBytes = resultInstruction.getParsedBytes();
if (!Arrays.equals(bytes, resultBytes)) {
return true; // bytes differ
}
}
@@ -682,8 +684,8 @@ public class ProgramMerge {
bytesLength = (int) originMax.subtract(originMin);
}
else {
originMax = originInstruction.getMaxAddress();
bytesLength = originInstruction.getLength();
bytesLength = originInstruction.getParsedLength();
originMax = originMin.add(bytesLength - 1);
}
Address resultMax = originToResultTranslator.getAddress(originMax);
@@ -699,7 +701,7 @@ public class ProgramMerge {
// If there are byte differences for this instruction then the
// bytes need to get copied even though the user did not indicate to.
if (bytesAreDifferent(originByteDiffs, originMin, resultMin, bytesLength)) { // FIXME
if (bytesMayDiffer(originByteDiffs, originMin, resultMin, bytesLength)) {
// Copy all the bytes for the instruction if any bytes differ.
ProgramMemoryUtil.copyBytesInRanges(resultProgram, originProgram, resultMin, resultMax);
}
@@ -709,7 +711,8 @@ public class ProgramMerge {
newInst = disassembleDelaySlottedInstruction(resultProgram, resultMin);
}
else {
newInst = disassembleNonDelaySlotInstruction(resultProgram, resultMin);
newInst = disassembleNonDelaySlotInstruction(resultProgram, resultMin,
originInstruction.isLengthOverridden() ? originInstruction.getLength() : 0);
}
if (newInst == null) {
return;
@@ -740,24 +743,29 @@ public class ProgramMerge {
}
private Instruction disassembleDelaySlottedInstruction(Program program, Address addr) {
// WARNING: does not support instruction length override use
// Use heavyweight disassembler for delay slotted instruction
AddressSet restrictedSet = new AddressSet(addr);
Disassembler disassembler =
Disassembler.getDisassembler(program, TaskMonitor.DUMMY, null);
Disassembler disassembler = Disassembler.getDisassembler(program, TaskMonitor.DUMMY, null);
disassembler.disassemble(addr, restrictedSet, false);
return program.getListing().getInstructionAt(addr);
}
private Instruction disassembleNonDelaySlotInstruction(Program program, Address addr) {
private Instruction disassembleNonDelaySlotInstruction(Program program, Address addr,
int lengthOverride) {
// Use lightweight disassembler for simple case
DisassemblerContextImpl context = new DisassemblerContextImpl(program.getProgramContext());
context.flowStart(addr);
try {
InstructionPrototype proto = program.getLanguage()
.parse(new DumbMemBufferImpl(program.getMemory(), addr), context, false);
if (lengthOverride > proto.getLength()) {
lengthOverride = 0;
}
return resultListing.createInstruction(addr, proto,
new DumbMemBufferImpl(program.getMemory(), addr),
new ProgramProcessorContext(program.getProgramContext(), addr));
new ProgramProcessorContext(program.getProgramContext(), addr),
Math.min(lengthOverride, proto.getLength()));
}
catch (Exception e) {
program.getBookmarkManager()
@@ -767,17 +775,13 @@ public class ProgramMerge {
return null;
}
private boolean bytesAreDifferent(AddressSetView originByteDiffs, Address originMin,
private boolean bytesMayDiffer(AddressSetView originByteDiffs, Address originMin,
Address resultMin, int byteCnt) throws MemoryAccessException {
if (originByteDiffs != null) {
AddressSet resultByteDiffs = originToResultTranslator.getAddressSet(originByteDiffs);
return resultByteDiffs.intersects(new AddressSet(resultMin, resultMin.add(byteCnt)));
}
byte[] originBytes = new byte[byteCnt];
originProgram.getMemory().getBytes(originMin, originBytes);
byte[] resultBytes = new byte[byteCnt];
resultProgram.getMemory().getBytes(resultMin, resultBytes);
return !Arrays.equals(originBytes, resultBytes);
return true;
}
/**
@@ -808,7 +812,7 @@ public class ProgramMerge {
// If there are byte differences for this instruction then the
// bytes need to get copied even though the user did not indicate to.
if (copyBytes &&
bytesAreDifferent(originByteDiffs, originMin, resultMin, originData.getLength())) {
bytesMayDiffer(originByteDiffs, originMin, resultMin, originData.getLength())) {
// Copy all the bytes for the instruction if any bytes differ.
ProgramMemoryUtil.copyBytesInRanges(resultProgram, originProgram, resultMin, resultMax);
}
@@ -1717,9 +1721,7 @@ public class ProgramMerge {
// Now discard any tags we've been told to remove.
if (discardTags != null) {
Set<String> tagNames = getTagNames(discardTags);
Iterator<FunctionTag> iter = resultTags.iterator();
while (iter.hasNext()) {
FunctionTag tag = iter.next();
for (FunctionTag tag : resultTags) {
if (tagNames.contains(tag.getName())) {
resultFunction.removeTag(tag.getName());
@@ -3934,8 +3936,7 @@ public class ProgramMerge {
if (originObject != null) {
try {
if (resultOpm == null) {
resultOpm =
createPropertyMap(userPropertyName, resultProgram, originOpm);
resultOpm = createPropertyMap(userPropertyName, resultProgram, originOpm);
}
resultOpm.add(resultAddress, originObject);
}
@@ -669,7 +669,8 @@ public class SymbolicPropogator {
else {
int instrByteHashCode = -1;
try {
instrByteHashCode = Arrays.hashCode(instr.getBytes());
byte[] bytes = instr.getParsedBytes();
instrByteHashCode = Arrays.hashCode(bytes);
}
catch (MemoryAccessException e) {
// this should NEVER happen, should always be able to get the bytes...
@@ -26,8 +26,7 @@ import ghidra.docking.settings.*;
import ghidra.framework.plugintool.ServiceProvider;
import ghidra.program.model.address.*;
import ghidra.program.model.data.EndianSettingsDefinition;
import ghidra.program.model.listing.CodeUnit;
import ghidra.program.model.listing.Program;
import ghidra.program.model.listing.*;
import ghidra.program.model.mem.MemoryAccessException;
import ghidra.program.util.BytesFieldLocation;
import ghidra.program.util.ProgramLocation;
@@ -50,100 +49,98 @@ public class BytesTableColumn extends ProgramLocationTableColumnExtensionPoint<A
private static SettingsDefinition[] SETTINGS_DEFS =
{ BYTE_COUNT, MEMORY_OFFSET, ENDIANESS, FORMAT };
private final GColumnRenderer<Byte[]> monospacedRenderer =
new AbstractGColumnRenderer<>() {
@Override
protected void configureFont(JTable table, TableModel model, int column) {
setFont(getFixedWidthFont());
private final GColumnRenderer<Byte[]> monospacedRenderer = new AbstractGColumnRenderer<>() {
@Override
protected void configureFont(JTable table, TableModel model, int column) {
setFont(getFixedWidthFont());
}
private String formatBytes(Byte[] bytes, Settings settings) {
boolean bigEndian = (ENDIANESS.getChoice(settings) != EndianSettingsDefinition.LITTLE);
int startIx = 0;
int endIx = bytes.length;
int inc = 1;
if (!bigEndian) {
startIx = bytes.length - 1;
endIx = -1;
inc = -1;
}
private String formatBytes(Byte[] bytes, Settings settings) {
boolean bigEndian =
(ENDIANESS.getChoice(settings) != EndianSettingsDefinition.LITTLE);
int format = FORMAT.getChoice(settings);
if (format == FormatSettingsDefinition.CHAR) {
return bytesToString(bytes);
}
int startIx = 0;
int endIx = bytes.length;
int inc = 1;
if (!bigEndian) {
startIx = bytes.length - 1;
endIx = -1;
inc = -1;
StringBuilder buffer = new StringBuilder();
for (int i = startIx; i != endIx; i += inc) {
if (buffer.length() != 0) {
buffer.append(' ');
}
buffer.append(getByteString(bytes[i], format));
}
return buffer.toString();
}
int format = FORMAT.getChoice(settings);
if (format == FormatSettingsDefinition.CHAR) {
return bytesToString(bytes);
private String bytesToString(Byte[] bytes) {
StringBuilder buf = new StringBuilder();
for (byte b : bytes) {
char c = (char) (b & 0xff);
if (c > 32 && c < 128) {
buf.append((char) (b & 0xff));
}
StringBuilder buffer = new StringBuilder();
for (int i = startIx; i != endIx; i += inc) {
if (buffer.length() != 0) {
buffer.append(' ');
}
buffer.append(getByteString(bytes[i], format));
else {
buf.append('.');
}
return buffer.toString();
}
return buf.toString();
}
private String bytesToString(Byte[] bytes) {
StringBuilder buf = new StringBuilder();
for (byte b : bytes) {
char c = (char) (b & 0xff);
if (c > 32 && c < 128) {
buf.append((char) (b & 0xff));
}
else {
buf.append('.');
}
}
return buf.toString();
private String getByteString(Byte b, int format) {
String val;
switch (format) {
case FormatSettingsDefinition.DECIMAL:
val = Integer.toString(b);
break;
case FormatSettingsDefinition.BINARY:
val = Integer.toBinaryString(b & 0x0ff);
val = StringFormat.padIt(val, 8, (char) 0, true);
break;
case FormatSettingsDefinition.OCTAL:
val = Integer.toOctalString(b & 0x0ff);
val = StringFormat.padIt(val, 3, (char) 0, true);
break;
default:
case FormatSettingsDefinition.HEX:
val = Integer.toHexString(b & 0x0ff).toUpperCase();
val = StringFormat.padIt(val, 2, (char) 0, true);
break;
}
return val;
}
private String getByteString(Byte b, int format) {
@Override
public Component getTableCellRendererComponent(GTableCellRenderingData data) {
String val;
switch (format) {
case FormatSettingsDefinition.DECIMAL:
val = Integer.toString(b);
break;
case FormatSettingsDefinition.BINARY:
val = Integer.toBinaryString(b & 0x0ff);
val = StringFormat.padIt(val, 8, (char) 0, true);
break;
case FormatSettingsDefinition.OCTAL:
val = Integer.toOctalString(b & 0x0ff);
val = StringFormat.padIt(val, 3, (char) 0, true);
break;
default:
case FormatSettingsDefinition.HEX:
val = Integer.toHexString(b & 0x0ff).toUpperCase();
val = StringFormat.padIt(val, 2, (char) 0, true);
break;
}
return val;
}
JLabel label = (JLabel) super.getTableCellRendererComponent(data);
@Override
public Component getTableCellRendererComponent(GTableCellRenderingData data) {
Object value = data.getValue();
Settings settings = data.getColumnSettings();
JLabel label = (JLabel) super.getTableCellRendererComponent(data);
Byte[] bytes = (Byte[]) value;
Object value = data.getValue();
Settings settings = data.getColumnSettings();
setText(formatBytes(bytes, settings));
Byte[] bytes = (Byte[]) value;
return label;
}
setText(formatBytes(bytes, settings));
return label;
}
@Override
public String getFilterString(Byte[] t, Settings settings) {
String formatted = formatBytes(t, settings);
return formatted;
}
};
@Override
public String getFilterString(Byte[] t, Settings settings) {
String formatted = formatBytes(t, settings);
return formatted;
}
};
/**
* Default Constructor
@@ -194,7 +191,12 @@ public class BytesTableColumn extends ProgramLocationTableColumnExtensionPoint<A
if (cu == null) { // can happen for 'SpecialAddress'es
return new Byte[0];
}
bytes = cu.getBytes();
if (cu instanceof Instruction instr) {
bytes = instr.getParsedBytes();
}
else {
bytes = cu.getBytes();
}
}
}
@@ -94,7 +94,7 @@ public abstract class AbstractListingMergeManagerTest extends AbstractMergeTest
new ProgramProcessorContext(program.getProgramContext(), atAddress);
InstructionPrototype proto = program.getLanguage().parse(buf, context, false);
Instruction createdInstruction =
listing.createInstruction(atAddress, proto, buf, context);
listing.createInstruction(atAddress, proto, buf, context, 0);
commit = true;
return createdInstruction;
}
@@ -0,0 +1,261 @@
/* ###
* 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.app.merge.listing;
import static org.junit.Assert.*;
import org.junit.Test;
import ghidra.program.database.OriginalProgramModifierListener;
import ghidra.program.database.ProgramDB;
import ghidra.program.model.address.AddressSet;
import ghidra.program.model.listing.Instruction;
import ghidra.program.model.listing.Listing;
import ghidra.program.model.symbol.*;
import ghidra.program.model.util.CodeUnitInsertionException;
/**
* Test the merge of the versioned program's listing.
* Provides tests that create instructions with overrides in various combinations.
* For tests with conflicts, checks selection of latest, my, or original code unit(s).
*/
public class CodeUnitMergeManager6Test extends AbstractListingMergeManagerTest {
public CodeUnitMergeManager6Test() {
super();
}
@Test
public void testAddLengthOverrideMyInstrPickMyInstr() throws Exception {
mtf.initialize("DiffTestPgm1", new OriginalProgramModifierListener() {
@Override
public void modifyOriginal(ProgramDB program) throws Exception {
int txId = program.startTransaction("Modify Original Program");
boolean commit = false;
try {
// nop #0x3 11011001 00110001 00000000
// imm r1,#0x300 00110001 00000000
// imm r0,#0x0 00000000 00000000
setBytes(program, "0x10013d9", new byte[] { (byte) 0xd9, 0x31, 0, 0, 0 });
commit = true;
}
finally {
program.endTransaction(txId, commit);
}
}
@Override
public void modifyLatest(ProgramDB program) {
int txId = program.startTransaction("Modify Latest Program");
boolean commit = false;
try {
disassemble(program, "0x10013d9", "0x10013dc");
commit = true;
}
finally {
program.endTransaction(txId, commit);
}
}
@Override
public void modifyPrivate(ProgramDB program) {
int txId = program.startTransaction("Modify My Program");
boolean commit = false;
try {
disassemble(program, "0x10013d9", "0x10013dc");
Listing listing = program.getListing();
Instruction instr = listing.getInstructionAt(addr(program, "0x10013d9"));
try {
instr.setLengthOverride(1);
}
catch (CodeUnitInsertionException e) {
failWithException("Unexpected exception", e);
}
disassemble(program, "0x10013da", "0x10013db");
instr = listing.getInstructionAt(addr(program, "0x10013da"));
assertNotNull("Failed to create overlapped instruction", instr);
commit = true;
}
finally {
program.endTransaction(txId, commit);
}
}
});
executeMerge(ASK_USER);
chooseCodeUnit("0x10013d9", "0x10013db", KEEP_MY);
waitForMergeCompletion();
assertSameCodeUnits(resultProgram, myProgram, new AddressSet(addr("0x10013d9"),
addr("0x10013db")));
ReferenceManager refMgr = resultProgram.getReferenceManager();
Reference[] refs = refMgr.getReferencesFrom(addr("0x10013d9"));
assertEquals(1, refs.length);
assertEquals(RefType.FALL_THROUGH, refs[0].getReferenceType());
assertEquals(addr("0x10013dc"), refs[0].getToAddress());
}
@Test
public void testModifyLengthOverrideMyInstrPickOriginalInstr() throws Exception {
mtf.initialize("DiffTestPgm1", new OriginalProgramModifierListener() {
@Override
public void modifyOriginal(ProgramDB program) throws Exception {
int txId = program.startTransaction("Modify Original Program");
boolean commit = false;
try {
// nop #0x3 11011001 00110001 00000000
// imm r1,#0x300 00110001 00000000
// imm r0,#0x0 00000000 00000000
setBytes(program, "0x10013d9", new byte[] { (byte) 0xd9, 0x31, 0, 0, 0 });
disassemble(program, "0x10013d9", "0x10013dc");
Listing listing = program.getListing();
Instruction instr = listing.getInstructionAt(addr(program, "0x10013d9"));
try {
instr.setLengthOverride(1);
}
catch (CodeUnitInsertionException e) {
failWithException("Unexpected exception", e);
}
commit = true;
}
finally {
program.endTransaction(txId, commit);
}
}
@Override
public void modifyLatest(ProgramDB program) {
int txId = program.startTransaction("Modify Latest Program");
boolean commit = false;
try {
disassemble(program, "0x10013da", "0x10013db");
Listing listing = program.getListing();
Instruction instr = listing.getInstructionAt(addr(program, "0x10013da"));
assertNotNull("Failed to create overlapped instruction", instr);
commit = true;
}
finally {
program.endTransaction(txId, commit);
}
}
@Override
public void modifyPrivate(ProgramDB program) {
int txId = program.startTransaction("Modify My Program");
boolean commit = false;
try {
clear(program, "0x10013d9", "0x10013d9");
disassemble(program, "0x10013d9", "0x10013dc");
commit = true;
}
finally {
program.endTransaction(txId, commit);
}
}
});
executeMerge(ASK_USER);
chooseCodeUnit("0x10013d9", "0x10013db", KEEP_ORIGINAL);
waitForMergeCompletion();
assertSameCodeUnits(resultProgram, originalProgram, new AddressSet(addr("0x10013d9"),
addr("0x10013db")));
ReferenceManager refMgr = resultProgram.getReferenceManager();
Reference[] refs = refMgr.getReferencesFrom(addr("0x10013d9"));
assertEquals(1, refs.length);
assertEquals(RefType.FALL_THROUGH, refs[0].getReferenceType());
assertEquals(addr("0x10013dc"), refs[0].getToAddress());
}
@Test
public void testAddLengthAndFallthroughOverrideMyInstrPickMyInstr() throws Exception {
mtf.initialize("DiffTestPgm1", new OriginalProgramModifierListener() {
@Override
public void modifyOriginal(ProgramDB program) throws Exception {
int txId = program.startTransaction("Modify Original Program");
boolean commit = false;
try {
// nop #0x3 11011001 00110001 00000000
// imm r1,#0x300 00110001 00000000
// imm r0,#0x0 00000000 00000000
setBytes(program, "0x10013d9", new byte[] { (byte) 0xd9, 0x31, 0, 0, 0 });
commit = true;
}
finally {
program.endTransaction(txId, commit);
}
}
@Override
public void modifyLatest(ProgramDB program) {
int txId = program.startTransaction("Modify Latest Program");
boolean commit = false;
try {
disassemble(program, "0x10013d9", "0x10013dc");
commit = true;
}
finally {
program.endTransaction(txId, commit);
}
}
@Override
public void modifyPrivate(ProgramDB program) {
int txId = program.startTransaction("Modify My Program");
boolean commit = false;
try {
disassemble(program, "0x10013d9", "0x10013dc");
Listing listing = program.getListing();
Instruction instr = listing.getInstructionAt(addr(program, "0x10013d9"));
try {
instr.setLengthOverride(1);
}
catch (CodeUnitInsertionException e) {
failWithException("Unexpected exception", e);
}
instr.setFallThrough(addr(program, "0x10013de"));
disassemble(program, "0x10013da", "0x10013db");
instr = listing.getInstructionAt(addr(program, "0x10013da"));
assertNotNull("Failed to create overlapped instruction", instr);
commit = true;
}
finally {
program.endTransaction(txId, commit);
}
}
});
executeMerge(ASK_USER);
chooseCodeUnit("0x10013d9", "0x10013db", KEEP_MY);
waitForMergeCompletion();
assertSameCodeUnits(resultProgram, myProgram, new AddressSet(addr("0x10013d9"),
addr("0x10013db")));
ReferenceManager refMgr = resultProgram.getReferenceManager();
Reference[] refs = refMgr.getReferencesFrom(addr("0x10013d9"));
assertEquals(1, refs.length);
assertEquals(RefType.FALL_THROUGH, refs[0].getReferenceType());
assertEquals(addr("0x10013de"), refs[0].getToAddress());
}
}
@@ -19,6 +19,7 @@ import static org.junit.Assert.*;
import org.junit.*;
import db.Transaction;
import ghidra.app.plugin.core.bookmark.BookmarkPlugin;
import ghidra.app.plugin.core.clear.ClearPlugin;
import ghidra.app.plugin.core.codebrowser.CodeBrowserPlugin;
@@ -1678,6 +1679,59 @@ public class CodeUnitIteratorTest extends AbstractGhidraHeadedIntegrationTest {
assertEquals(10, cnt);
}
@Test
public void testGetCodeUnitsSetWithOverride() throws Exception {
// set length-override on 0x3f4
try (Transaction tx = program.openTransaction("Set Length Override")) {
program.getListing().getInstructionAt(addr(0x3f4)).setLengthOverride(1);
}
AddressSet set = new AddressSet();
set.addRange(addr(0x3f0), addr(0x3f9));
CodeUnitIterator it = listing.getCodeUnits(set, true);
CodeUnit cu = it.next();
assertEquals(addr(0x3f1), cu.getMinAddress());
assertTrue(cu instanceof Data);
assertFalse(((Data) cu).isDefined());
cu = it.next();
assertEquals(addr(0x3f2), cu.getMinAddress());
assertTrue(cu instanceof Instruction);
assertEquals(2, cu.getLength());
cu = it.next();
assertEquals(addr(0x3f4), cu.getMinAddress());
assertTrue(cu instanceof Instruction);
assertEquals(1, cu.getLength()); // length overriden
assertEquals("imm r0,#0x0", cu.toString());
cu = it.next();
assertEquals(addr(0x3f5), cu.getMinAddress());
assertTrue(cu instanceof Data);
assertFalse(((Data) cu).isDefined());
cu = it.next();
assertEquals(addr(0x3f6), cu.getMinAddress());
assertTrue(cu instanceof Instruction);
assertEquals(2, cu.getLength());
cu = it.next();
assertEquals(addr(0x3f8), cu.getMinAddress());
assertTrue(cu instanceof Data);
assertFalse(((Data) cu).isDefined());
cu = it.next();
assertEquals(addr(0x3f9), cu.getMinAddress());
assertTrue(cu instanceof Data);
assertFalse(((Data) cu).isDefined());
assertFalse(it.hasNext());
assertNull(it.next());
}
private Address addr(long l) {
return space.getAddress(l);
}
@@ -1704,7 +1758,7 @@ public class CodeUnitIteratorTest extends AbstractGhidraHeadedIntegrationTest {
ProcessorContext context = new ProgramProcessorContext(program.getProgramContext(), atAddr);
InstructionPrototype proto = program.getLanguage().parse(buf, context, false);
listing.createInstruction(atAddr, proto, buf, context);
listing.createInstruction(atAddr, proto, buf, context, 0);
}
private void startTransaction() {
@@ -99,7 +99,7 @@ public class SymbolUtilities1Test extends AbstractGhidraHeadedIntegrationTest {
new ProgramProcessorContext(program.getProgramContext(), addr(0x200));
DumbMemBufferImpl membuf = new DumbMemBufferImpl(program.getMemory(), addr(0x200));
InstructionPrototype proto = program.getLanguage().parse(membuf, context, false);
listing.createInstruction(addr(0x200), proto, membuf, context);
listing.createInstruction(addr(0x200), proto, membuf, context, 0);
Symbol symbol = symbolTable.getPrimarySymbol(addr(0x200));
assertEquals("LAB_00000200", symbol.getName());
@@ -97,7 +97,7 @@ public class SymbolUtilities2Test extends AbstractGhidraHeadedIntegrationTest {
new ProgramProcessorContext(program.getProgramContext(), addr(0x200));
DumbMemBufferImpl membuf = new DumbMemBufferImpl(program.getMemory(), addr(0x200));
InstructionPrototype proto = program.getLanguage().parse(membuf, context, false);
listing.createInstruction(addr(0x200), proto, membuf, context);
listing.createInstruction(addr(0x200), proto, membuf, context, 0);
Symbol symbol = symbolTable.getPrimarySymbol(addr(0x200));
assertEquals("LAB_CODE_0200", symbol.getName());
@@ -1063,7 +1063,7 @@ public class CodeManagerTest extends AbstractGenericTest {
MemBuffer buf = new DumbMemBufferImpl(mem, atAddr);
ProcessorContext context = new ProgramProcessorContext(program.getProgramContext(), atAddr);
InstructionPrototype proto = program.getLanguage().parse(buf, context, false);
listing.createInstruction(atAddr, proto, buf, context);
listing.createInstruction(atAddr, proto, buf, context, 0);
}
}
@@ -15,10 +15,9 @@
*/
package ghidra.feature.vt.api.markupitem;
import static ghidra.feature.vt.api.main.VTMarkupItemApplyActionType.REPLACE;
import static ghidra.feature.vt.db.VTTestUtils.addr;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static ghidra.feature.vt.api.main.VTMarkupItemApplyActionType.*;
import static ghidra.feature.vt.db.VTTestUtils.*;
import static org.junit.Assert.*;
import java.util.List;
@@ -379,7 +378,7 @@ public class DataTypeMarkupItemTest extends AbstractVTMarkupItemTest {
new ProgramProcessorContext(program.getProgramContext(), atAddress);
InstructionPrototype proto = program.getLanguage().parse(buf, context, false);
Instruction createdInstruction =
listing.createInstruction(atAddress, proto, buf, context);
listing.createInstruction(atAddress, proto, buf, context, 0);
commit = true;
return createdInstruction;
}
@@ -182,7 +182,8 @@ public abstract class PcodeEmit {
*/
void resolveFinalFallthrough() throws IOException {
try {
if (fallOverride == null || fallOverride.equals(getStartAddress().add(fallOffset))) {
if (fallOverride == null) {
// handles both length-override and fallthrough override cases
return;
}
}
@@ -505,19 +505,6 @@ abstract class PseudoCodeUnit implements CodeUnit {
return program.getListing().getCodeUnitBefore(address);
}
/**
* Return true if the given CodeUnit follows directly after this code unit.
*
* @throws ConcurrentModificationException
* if this object is no longer valid.
*/
@Override
public boolean isSuccessor(CodeUnit codeUnit) {
Address min = codeUnit.getMinAddress();
return this.getMaxAddress().isSuccessor(min);
}
@Override
public String getComment(int commentType) {
return comments.get(commentType);
@@ -146,6 +146,18 @@ public class PseudoInstruction extends PseudoCodeUnit implements Instruction, In
return b0;
}
@Override
public int getParsedLength() {
// length-override is not supported
return getLength();
}
@Override
public byte[] getParsedBytes() throws MemoryAccessException {
// length-override is not supported
return getBytes();
}
@Override
public Register getBaseContextRegister() {
return procContext.getBaseContextRegister();
@@ -505,6 +517,16 @@ public class PseudoInstruction extends PseudoCodeUnit implements Instruction, In
this.flowOverride = flowOverride != null ? flowOverride : FlowOverride.NONE;
}
@Override
public void setLengthOverride(int length) {
throw new UnsupportedOperationException();
}
@Override
public boolean isLengthOverridden() {
return false;
}
@Override
public boolean isFallThroughOverridden() {
return fallThroughOverride != null;
@@ -22,7 +22,8 @@ import ghidra.program.database.function.OverlappingFunctionException;
import ghidra.program.database.module.TreeManager;
import ghidra.program.database.symbol.FunctionSymbol;
import ghidra.program.model.address.*;
import ghidra.program.model.data.*;
import ghidra.program.model.data.DataType;
import ghidra.program.model.data.DataTypeManager;
import ghidra.program.model.lang.*;
import ghidra.program.model.listing.*;
import ghidra.program.model.mem.MemBuffer;
@@ -107,7 +108,7 @@ class ListingDB implements Listing {
@Override
public Instruction getInstructionContaining(Address addr) {
return codeMgr.getInstructionContaining(addr);
return codeMgr.getInstructionContaining(addr, false);
}
@Override
@@ -261,14 +262,15 @@ class ListingDB implements Listing {
}
@Override
public PropertyMap getPropertyMap(String propertyName) {
public PropertyMap<?> getPropertyMap(String propertyName) {
return codeMgr.getPropertyMap(propertyName);
}
@Override
public Instruction createInstruction(Address addr, InstructionPrototype prototype,
MemBuffer memBuf, ProcessorContextView context) throws CodeUnitInsertionException {
return codeMgr.createCodeUnit(addr, prototype, memBuf, context);
MemBuffer memBuf, ProcessorContextView context, int length)
throws CodeUnitInsertionException {
return codeMgr.createCodeUnit(addr, prototype, memBuf, context, length);
}
@Override
@@ -278,8 +280,7 @@ class ListingDB implements Listing {
}
@Override
public Data createData(Address addr, DataType dataType)
throws CodeUnitInsertionException {
public Data createData(Address addr, DataType dataType) throws CodeUnitInsertionException {
return codeMgr.createCodeUnit(addr, dataType, dataType.getLength());
}
@@ -292,8 +293,7 @@ class ListingDB implements Listing {
@Override
public void clearCodeUnits(Address startAddr, Address endAddr, boolean clearContext) {
try {
codeMgr.clearCodeUnits(startAddr, endAddr, clearContext,
TaskMonitor.DUMMY);
codeMgr.clearCodeUnits(startAddr, endAddr, clearContext, TaskMonitor.DUMMY);
}
catch (CancelledException e) {
// can't happen with dummy monitor
@@ -109,8 +109,10 @@ public class ProgramDB extends DomainObjectAdapterDB implements Program, ChangeM
* property map (StringTranslations).
* 19-Jan-2023 - version 26 Improved relocation data records to incorporate status and
* byte-length when original FileBytes should be used.
* 10-Jul-2023 - VERSION 27 Add support for Instruction length override which utilizes
* unused flag bits.
*/
static final int DB_VERSION = 26;
static final int DB_VERSION = 27;
/**
* UPGRADE_REQUIRED_BFORE_VERSION should be changed to DB_VERSION anytime the
@@ -155,11 +155,16 @@ public class CodeManager implements ErrorHandler, ManagerDB {
while (addrIter.hasNext()) {
monitor.checkCancelled();
Address addr = addrIter.next();
Instruction instr = getInstructionAt(addr);
if (instr == null) {
continue;
}
try {
long offset = oldFallThroughs.getLong(addr);
Address toAddr = addr.getNewAddress(offset);
refMgr.addMemoryReference(addr, toAddr, RefType.FALL_THROUGH,
SourceType.USER_DEFINED, Reference.MNEMONIC);
instr.setFallThrough(toAddr);
}
catch (NoValueException e) {
// skip
@@ -531,6 +536,16 @@ public class CodeManager implements ErrorHandler, ManagerDB {
if (flowOverride != FlowOverride.NONE) {
lastInstruction.setFlowOverride(flowOverride);
}
try {
if (protoInstr.isLengthOverridden()) {
lastInstruction.setLengthOverride(protoInstr.getLength());
}
}
catch (CodeUnitInsertionException e) {
// unexpected - should have been caught previously
throw new RuntimeException(e);
}
}
if (errorAddr != null && conflictCodeUnit == null &&
@@ -581,21 +596,34 @@ public class CodeManager implements ErrorHandler, ManagerDB {
* @param prototype instruction definition object
* @param memBuf the MemBuffer to use to get the bytes from memory
* @param context object that has the state of all the registers.
* @return the new instruction
* @exception CodeUnitInsertionException thrown if code unit
* overlaps with an existing code 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.
*/
public Instruction createCodeUnit(Address address, InstructionPrototype prototype,
MemBuffer memBuf, ProcessorContextView context) throws CodeUnitInsertionException {
MemBuffer memBuf, ProcessorContextView context, int length)
throws CodeUnitInsertionException {
lock.acquire();
creatingInstruction = true;
try {
Address endAddr = address.addNoWrap(prototype.getLength() - 1);
int forcedLengthOverride = InstructionDB.checkLengthOverride(length, prototype);
if (length == 0) {
length = prototype.getLength();
}
Address endAddr = address.addNoWrap(length - 1);
checkValidAddressRange(address, endAddr);
InstructionDB inst = addInstruction(address, endAddr, prototype, memBuf, context);
if (forcedLengthOverride != 0) {
inst.doSetLengthOverride(forcedLengthOverride);
}
// fire event
program.setChanged(ChangeManager.DOCR_CODE_ADDED, address, endAddr, null, inst);
@@ -1370,29 +1398,48 @@ public class CodeManager implements ErrorHandler, ManagerDB {
}
/**
* Returns the instruction whose min address is less than or equal to the specified address and
* Returns an instruction whose min address is less than or equal to the specified address and
* whose max address is greater than or equal to the specified address.
* If {@code usePrototypeLength==true}
* <pre>{@literal
* instruction.minAddress() <= addr <= instruction.maxAddress()
* instruction.getMinAddress() <= addr <=
* instruction.getMinAddress().add(instruction.getPrototype().getLength() - 1)
* }</pre>
*
* If {@code usePrototypeLength==false}
* <pre>{@literal
* instruction.getMinAddress() <= addr <= instruction.getMaxAddress()
* }</pre>
* The use of the prototype length is required when guarding against memory modifications. If
* a length-override is present only one of the entangled instructions will be returned and is
* intended to simply indicate the presence of a conflict.
* @param address the address to be contained
* @param usePrototypeLength if actual prototype length should be considered when identifying a
* conflict (required when checking for memory modification conflicts), otherwise code unit
* length is used. These lengths can vary when a
* {@link Instruction#setLengthOverride(int) length-override} is in affect for an instruction.
* @return the instruction containing the specified address, or null if a
* instruction does not exist
*/
public Instruction getInstructionContaining(Address address) {
public Instruction getInstructionContaining(Address address, boolean usePrototypeLength) {
lock.acquire();
try {
Instruction instr = getInstructionAt(address);
if (instr != null) {
return instr;
}
instr = this.getInstructionBefore(address);
if (instr != null && instr.contains(address)) {
return instr;
if (instr != null) {
Address endAddr;
if (usePrototypeLength && instr.isLengthOverridden()) {
endAddr = instr.getMinAddress().add(instr.getParsedLength() - 1);
}
else {
endAddr = instr.getMaxAddress();
}
if (address.compareTo(endAddr) <= 0) {
return instr;
}
}
return null;
}
@@ -1639,8 +1686,14 @@ public class CodeManager implements ErrorHandler, ManagerDB {
nextInstEndAddr = nextInstAddr;
int protoID = nextInstRec.getIntValue(InstDBAdapter.PROTO_ID_COL);
InstructionPrototype proto = protoMgr.getPrototype(protoID);
int len = proto != null ? proto.getLength()
: nextInstAddr.getAddressSpace().getAddressableUnitSize();
int len;
if (proto != null) {
len = InstructionDB.getLength(proto,
nextInstRec.getByteValue(InstDBAdapter.FLAGS_COL));
}
else {
len = nextInstAddr.getAddressSpace().getAddressableUnitSize();
}
if (len > 1) {
try {
nextInstEndAddr = nextInstAddr.addNoWrap(len - 1);
@@ -1730,7 +1783,7 @@ public class CodeManager implements ErrorHandler, ManagerDB {
if (addr != AddressMap.INVALID_ADDRESS_KEY) {
lock.acquire();
try {
Instruction inst = getInstructionContaining(address);
Instruction inst = getInstructionContaining(address, false);
if (inst != null) {
return null;
}
@@ -2538,7 +2591,7 @@ public class CodeManager implements ErrorHandler, ManagerDB {
return;
}
boolean fail = false;
if (getInstructionContaining(start) != null) {
if (getInstructionContaining(start, false) != null) {
fail = true;
}
else {
@@ -2574,7 +2627,7 @@ public class CodeManager implements ErrorHandler, ManagerDB {
if (!program.getMemory().contains(start, end)) {
return false;
}
if (getInstructionContaining(start) != null) {
if (getInstructionContaining(start, false) != null) {
return false;
}
if (getDefinedDataContaining(start) != null) {
@@ -3168,13 +3221,21 @@ public class CodeManager implements ErrorHandler, ManagerDB {
* @param newFallThroughRef new fallthrough reference or null if removed
*/
public void fallThroughChanged(Address fromAddr, Reference newFallThroughRef) {
if (newFallThroughRef != null &&
newFallThroughRef.getReferenceType() != RefType.FALL_THROUGH) {
throw new IllegalArgumentException("invalid reftype");
}
lock.acquire();
try {
InstructionDB instr = getInstructionAt(addrMap.getKey(fromAddr, false));
// TODO: Should prevent this if instruction is null or isInDelaySlot
if (instr != null) {
instr.fallThroughChanged(newFallThroughRef);
if (instr == null) {
// Do not allow fallthrough ref without instruction
if (newFallThroughRef != null) {
refManager.delete(newFallThroughRef);
}
return;
}
instr.fallThroughChanged(newFallThroughRef);
}
finally {
lock.release();
@@ -23,6 +23,7 @@ import org.apache.commons.lang3.StringUtils;
import db.DBRecord;
import ghidra.program.database.*;
import ghidra.program.database.references.ReferenceDBManager;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressOutOfBoundsException;
import ghidra.program.model.lang.*;
@@ -48,7 +49,7 @@ abstract class CodeUnitDB extends DatabaseObject implements CodeUnit, ProcessorC
protected long addr;
protected Address endAddr;
protected int length;
protected ReferenceManager refMgr;
protected ReferenceDBManager refMgr;
protected ProgramDB program;
private DBRecord commentRec;
@@ -407,13 +408,6 @@ abstract class CodeUnitDB extends DatabaseObject implements CodeUnit, ProcessorC
return false;
}
@Override
public boolean isSuccessor(CodeUnit codeUnit) {
Address min = codeUnit.getMinAddress();
return this.getMaxAddress().isSuccessor(min);
}
@Override
public Iterator<String> propertyNames() {
PropertyMapManager upm = codeMgr.getPropertyMapManager();
@@ -15,8 +15,7 @@
*/
package ghidra.program.database.code;
import java.util.ArrayList;
import java.util.List;
import java.util.*;
import db.DBRecord;
import ghidra.program.database.DBObjectCache;
@@ -29,6 +28,7 @@ import ghidra.program.model.mem.MemoryAccessException;
import ghidra.program.model.pcode.PcodeOp;
import ghidra.program.model.scalar.Scalar;
import ghidra.program.model.symbol.*;
import ghidra.program.model.util.CodeUnitInsertionException;
import ghidra.program.util.ChangeManager;
import ghidra.util.Msg;
import ghidra.util.exception.NoValueException;
@@ -38,16 +38,21 @@ import ghidra.util.exception.NoValueException;
*/
public class InstructionDB extends CodeUnitDB implements Instruction, InstructionContext {
private static byte FALLTHROUGH_SET_MASK = (byte) 0x01;
private static byte FALLTHROUGH_CLEAR_MASK = (byte) 0xfe;
private static final byte FALLTHROUGH_SET_MASK = 0x01;
private static final byte FALLTHROUGH_CLEAR_MASK = ~FALLTHROUGH_SET_MASK;
private static byte FLOWOVERRIDE_MASK = (byte) 0x0e;
private static byte FLOWOVERRIDE_CLEAR_MASK = (byte) 0xf1;
private static 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;
private InstructionPrototype proto;
private byte flags;
private FlowOverride flowOverride;
private int lengthOverride;
private final static Address[] EMPTY_ADDR_ARRAY = new Address[0];
private volatile boolean clearingFallThroughs = false;
@@ -68,7 +73,8 @@ public class InstructionDB extends CodeUnitDB implements Instruction, Instructio
this.proto = proto;
this.flags = flags;
flowOverride =
FlowOverride.getFlowOverride((flags & FLOWOVERRIDE_MASK) >> FLOWOVERRIDE_SHIFT);
FlowOverride.getFlowOverride((flags & FLOW_OVERRIDE_SET_MASK) >> FLOW_OVERRIDE_SHIFT);
refreshLength();
}
@Override
@@ -83,6 +89,36 @@ public class InstructionDB extends CodeUnitDB implements Instruction, Instructio
return proto.hasDelaySlots() ? (length * 2) : length;
}
private void refreshLength() {
length = proto.getLength();
lengthOverride = (flags & LENGTH_OVERRIDE_SET_MASK) >> LENGTH_OVERRIDE_SHIFT;
if (lengthOverride != 0 && lengthOverride < length) {
length = lengthOverride;
}
else {
lengthOverride = 0;
}
}
/**
* Get the instruction code unit length based upon its prototype and flags
* which will be used to check for a length-override condition.
* @param proto instruction prototype
* @param flags instruction flags
* @return instruction code unit length
*/
static int getLength(InstructionPrototype proto, byte flags) {
int length = proto.getLength();
int lengthOverride = (flags & LENGTH_OVERRIDE_SET_MASK) >> LENGTH_OVERRIDE_SHIFT;
if (lengthOverride != 0 && lengthOverride < length) {
length = lengthOverride;
}
else {
lengthOverride = 0;
}
return length;
}
@Override
protected boolean hasBeenDeleted(DBRecord rec) {
if (rec == null) {
@@ -106,10 +142,11 @@ public class InstructionDB extends CodeUnitDB implements Instruction, Instructio
if (!newProto.equals(proto)) {
return true;
}
length = proto.getLength();
flags = rec.getByteValue(InstDBAdapter.FLAGS_COL);
flowOverride =
FlowOverride.getFlowOverride((flags & FLOWOVERRIDE_MASK) >> FLOWOVERRIDE_SHIFT);
FlowOverride.getFlowOverride((flags & FLOW_OVERRIDE_SET_MASK) >> FLOW_OVERRIDE_SHIFT);
refreshLength();
return false;
}
@@ -142,6 +179,31 @@ public class InstructionDB extends CodeUnitDB implements Instruction, Instructio
return null;
}
@Override
public int getParsedLength() {
return isLengthOverridden() ? proto.getLength() : getLength();
}
@Override
public byte[] getParsedBytes() throws MemoryAccessException {
if (!isLengthOverridden()) {
return getBytes();
}
lock.acquire();
try {
checkIsValid();
int len = proto.getLength();
byte[] b = new byte[len];
if (len != getMemory().getBytes(address, b)) {
throw new MemoryAccessException("Failed to read " + len + " bytes at " + address);
}
return b;
}
finally {
lock.release();
}
}
@Override
public Address getFallFrom() {
lock.acquire();
@@ -192,19 +254,22 @@ public class InstructionDB extends CodeUnitDB implements Instruction, Instructio
}
}
private Address getFallThroughReference() {
for (Reference ref : refMgr.getReferencesFrom(address)) {
if (ref.getReferenceType().isFallthrough() && ref.getToAddress().isMemoryAddress()) {
return ref.getToAddress();
}
}
return null;
}
@Override
public Address getFallThrough() {
lock.acquire();
try {
checkIsValid();
if (isFallThroughOverridden()) {
for (Reference ref : refMgr.getReferencesFrom(address)) {
if (ref.getReferenceType().isFallthrough() &&
ref.getToAddress().isMemoryAddress()) {
return ref.getToAddress();
}
}
return null;
return getFallThroughReference();
}
return getDefaultFallThrough();
}
@@ -221,7 +286,7 @@ public class InstructionDB extends CodeUnitDB implements Instruction, Instructio
return EMPTY_ADDR_ARRAY;
}
ArrayList<Address> list = new ArrayList<>();
Set<Address> list = new HashSet<>();
for (Reference ref : refs) {
if (!ref.getReferenceType().isIndirect()) {
list.add(ref.getToAddress());
@@ -232,8 +297,7 @@ public class InstructionDB extends CodeUnitDB implements Instruction, Instructio
return EMPTY_ADDR_ARRAY;
}
Address[] addrs = new Address[list.size()];
return list.toArray(addrs);
return list.toArray(new Address[list.size()]);
}
@Override
@@ -537,8 +601,8 @@ public class InstructionDB extends CodeUnitDB implements Instruction, Instructio
}
FlowType origFlowType = getFlowType();
flags &= FLOWOVERRIDE_CLEAR_MASK;
flags |= (flow.ordinal() << FLOWOVERRIDE_SHIFT);
flags &= FLOW_OVERRIDE_CLEAR_MASK;
flags |= (flow.ordinal() << FLOW_OVERRIDE_SHIFT);
codeMgr.setFlags(addr, flags);
flowOverride = flow;
@@ -623,19 +687,25 @@ public class InstructionDB extends CodeUnitDB implements Instruction, Instructio
/**
* Clear all existing fall-through references from address.
* @param keepFallThroughRef if not null, corresponding fall-through reference will be preserved
* @param keepFallThroughAddr if not null, corresponding fall-through reference will be
* preserved.
*/
private void clearFallThroughRefs(Reference keepFallThroughRef) {
private void clearFallThroughRefs(Address keepFallThroughAddr) {
if (clearingFallThroughs) {
return;
}
refreshIfNeeded();
clearingFallThroughs = true;
try {
boolean fallThroughPreserved = false;
for (Reference ref : refMgr.getReferencesFrom(address)) {
if (ref.getReferenceType() == RefType.FALL_THROUGH &&
!ref.equals(keepFallThroughRef)) {
refMgr.delete(ref);
if (ref.getReferenceType() == RefType.FALL_THROUGH) {
if (!fallThroughPreserved && ref.getToAddress().equals(keepFallThroughAddr)) {
fallThroughPreserved = true; // only preserve one
}
else {
refMgr.delete(ref);
}
}
}
}
@@ -646,9 +716,16 @@ public class InstructionDB extends CodeUnitDB implements Instruction, Instructio
void fallThroughChanged(Reference fallThroughRef) {
if (!clearingFallThroughs) {
clearFallThroughRefs(fallThroughRef);
setFallthroughOverride(fallThroughRef != null &&
fallThroughRef.getReferenceType() == RefType.FALL_THROUGH);
Address fallThroughAddr = fallThroughRef != null ? fallThroughRef.getToAddress() : null;
clearFallThroughRefs(fallThroughAddr); // ensure there is only one fallthrough ref
if (fallThroughAddr == null) { // fallthrough ref removed
setFallthroughOverride(false);
addLengthOverrideFallthroughRef(); // restore length-override fallthrough if needed
}
else {
// enable fallthrough-override if fallThroughRef does not match length-override fallthrough
setFallthroughOverride(!fallThroughAddr.equals(getLengthOverrideFallThrough()));
}
}
}
@@ -661,8 +738,9 @@ public class InstructionDB extends CodeUnitDB implements Instruction, Instructio
flags &= FALLTHROUGH_CLEAR_MASK;
}
codeMgr.setFlags(addr, flags);
program.setChanged(ChangeManager.DOCR_FALLTHROUGH_CHANGED, address, address, null,
null);
}
program.setChanged(ChangeManager.DOCR_FALLTHROUGH_CHANGED, address, address, null, null);
}
@Override
@@ -673,8 +751,10 @@ public class InstructionDB extends CodeUnitDB implements Instruction, Instructio
if (!isFallThroughOverridden()) {
return;
}
// clear fall-through override
clearFallThroughRefs(null);
setFallthroughOverride(false);
addLengthOverrideFallthroughRef(); // restore length-override fallthrough if needed
}
finally {
lock.release();
@@ -693,11 +773,12 @@ public class InstructionDB extends CodeUnitDB implements Instruction, Instructio
return;
}
if (fallThroughAddr == null) {
// Fall-through eliminated - no reference added
// Fall-through eliminated (i.e., terminal flow) - no reference added
clearFallThroughRefs(null);
setFallthroughOverride(true);
}
else {
// Adding fallthough ref will trigger override flag on callback
refMgr.addMemoryReference(address, fallThroughAddr, RefType.FALL_THROUGH,
SourceType.USER_DEFINED, Reference.MNEMONIC);
}
@@ -705,7 +786,107 @@ public class InstructionDB extends CodeUnitDB implements Instruction, Instructio
finally {
lock.release();
}
}
@Override
public void setLengthOverride(int len) throws CodeUnitInsertionException {
lock.acquire();
try {
checkDeleted();
if (doSetLengthOverride(len)) {
program.setChanged(ChangeManager.DOCR_LENGTH_OVERRIDE_CHANGED, address, address,
null, null);
}
}
finally {
lock.release();
}
}
/**
* Check and revise a specified {@code length} to arrive at a suitable length-override value.
* @param length instruction byte-length (must be in the range 0..{@code prototype-length}).
* 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} .
* @param prototype instruction prototype
* @return length-override value (0 = disable length-override)
* @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.
*/
public static int checkLengthOverride(int length, InstructionPrototype prototype)
throws IllegalArgumentException, CodeUnitInsertionException {
if (length < 0) {
throw new IllegalArgumentException("Negative length not permitted");
}
int instrProtoLength = prototype.getLength();
if (length == 0 || length == instrProtoLength) {
return 0;
}
if (length > instrProtoLength) {
return 0;
}
int align = prototype.getLanguage().getInstructionAlignment();
if (length % align != 0) {
throw new CodeUnitInsertionException(
"Length(" + length + ") override must be a multiple of " + align + " bytes");
}
if (length > MAX_LENGTH_OVERRIDE) {
throw new CodeUnitInsertionException("Unsupported length override: " + length);
}
return length;
}
boolean doSetLengthOverride(int len) throws CodeUnitInsertionException {
int protoLength = proto.getLength();
len = checkLengthOverride(len, proto);
if (len == lengthOverride) {
return false; // no change
}
int instrLength = len != 0 ? len : protoLength;
if (instrLength > getLength()) {
Address newEndAddr = address.add(instrLength - 1);
Address nextCodeUnitAddr = codeMgr.getDefinedAddressAfter(address);
if (nextCodeUnitAddr != null && nextCodeUnitAddr.compareTo(newEndAddr) <= 0) {
throw new CodeUnitInsertionException("Length override of " + instrLength +
" conflicts with code unit at " + nextCodeUnitAddr);
}
}
flags &= LENGTH_OVERRIDE_CLEAR_MASK;
flags |= (len << LENGTH_OVERRIDE_SHIFT);
codeMgr.setFlags(addr, flags);
endAddr = null;
refreshLength();
addLengthOverrideFallthroughRef();
return true;
}
private void addLengthOverrideFallthroughRef() {
if (isLengthOverridden() && !isFallThroughOverridden()) {
// length-override always uses default fall-through address
refMgr.addMemoryReference(address, getDefaultFallThrough(), RefType.FALL_THROUGH,
SourceType.USER_DEFINED, Reference.MNEMONIC);
}
}
@Override
public boolean isLengthOverridden() {
refreshIfNeeded();
return lengthOverride != 0;
}
private Address getLengthOverrideFallThrough() {
return isLengthOverridden() ? getDefaultFallThrough() : null;
}
private boolean addrsEqual(Address addr1, Address addr2) {

Some files were not shown because too many files have changed in this diff Show More