From aefb7f2aede00255bdcf90f370cbd6e37df43923 Mon Sep 17 00:00:00 2001 From: ghidra1 Date: Mon, 28 Aug 2023 09:17:44 -0400 Subject: [PATCH] GP-3256 Added support for Instruction length-override --- .../debug/gui/copying/DebuggerCopyPlan.java | 14 +- .../listing/DBTraceCodeUnitAdapter.java | 7 +- .../database/listing/DBTraceInstruction.java | 155 +++++++-- .../DBTraceInstructionsMemoryView.java | 5 +- .../listing/DBTraceInstructionsView.java | 51 ++- .../AbstractDBTraceProgramViewListing.java | 9 +- .../database/program/DBTraceProgramView.java | 14 +- .../main/java/ghidra/trace/model/Trace.java | 2 + .../model/listing/TraceInstructionsView.java | 7 +- .../trace/database/ToyDBTraceBuilder.java | 3 +- .../database/listing/DBTraceCodeUnitTest.java | 3 - Ghidra/Features/Base/certification.manifest | 2 + .../topics/DisassemblerPlugin/Disassembly.htm | 139 +++++--- .../LengthOverrideLockPrefixExample.png | Bin 0 -> 13380 bytes .../LengthOverrideLockPrefixExample2.png | Bin 0 -> 23146 bytes .../app/merge/listing/CodeUnitDetails.java | 39 +-- .../app/merge/listing/CodeUnitMerger.java | 13 +- .../merge/listing/ListingMergeManager.java | 1 - .../core/analysis/AutoAnalysisManager.java | 1 + .../core/disassembler/DisassemblerPlugin.java | 3 + .../disassembler/SetLengthOverrideAction.java | 137 ++++++++ .../core/fallthrough/FallThroughPlugin.java | 12 +- .../ui/InsertBytesWidget.java | 14 +- .../plugin/core/reloc/InstructionStasher.java | 25 +- .../app/util/exporter/ProgramTextWriter.java | 13 +- .../util/viewer/field/BytesFieldFactory.java | 13 +- .../viewer/field/MnemonicFieldFactory.java | 3 +- .../viewer/field/PostCommentFieldFactory.java | 14 +- .../java/ghidra/program/util/DiffUtility.java | 37 ++- .../java/ghidra/program/util/ProgramDiff.java | 5 +- .../program/util/ProgramDiffDetails.java | 25 +- .../program/util/ProgramMemoryUtil.java | 19 +- .../ghidra/program/util/ProgramMerge.java | 43 +-- .../program/util/SymbolicPropogator.java | 3 +- .../util/table/field/BytesTableColumn.java | 162 +++++----- .../AbstractListingMergeManagerTest.java | 2 +- .../listing/CodeUnitMergeManager6Test.java | 261 +++++++++++++++ .../database/code/CodeUnitIteratorTest.java | 56 +++- .../model/symbol/SymbolUtilities1Test.java | 2 +- .../model/symbol/SymbolUtilities2Test.java | 2 +- .../database/code/CodeManagerTest.java | 2 +- .../markupitem/DataTypeMarkupItemTest.java | 9 +- .../plugin/processors/sleigh/PcodeEmit.java | 3 +- .../java/ghidra/app/util/PseudoCodeUnit.java | 13 - .../ghidra/app/util/PseudoInstruction.java | 22 ++ .../ghidra/program/database/ListingDB.java | 18 +- .../ghidra/program/database/ProgramDB.java | 4 +- .../program/database/code/CodeManager.java | 109 +++++-- .../program/database/code/CodeUnitDB.java | 10 +- .../program/database/code/InstructionDB.java | 245 ++++++++++++-- .../database/external/ExternalManagerDB.java | 33 +- .../program/database/mem/MemoryMapDB.java | 2 +- .../references/ReferenceDBManager.java | 49 ++- .../correlate/AllBytesHashCalculator.java | 2 +- .../program/model/listing/CodeUnit.java | 16 +- .../program/model/listing/DataStub.java | 5 - .../program/model/listing/Instruction.java | 84 ++++- .../listing/InstructionPcodeOverride.java | 5 +- .../model/listing/InstructionStub.java | 25 +- .../ghidra/program/model/listing/Listing.java | 24 +- .../program/model/listing/StubListing.java | 5 +- .../program/model/symbol/ExternalManager.java | 88 +++-- .../ghidra/program/model/symbol/RefType.java | 301 +++++++++++++++--- .../model/symbol/ReferenceManager.java | 52 ++- .../ghidra/program/util/ChangeManager.java | 15 +- .../ghidra/program/util/InstructionUtils.java | 10 +- .../core/clipboard/ClipboardPluginTest.java | 2 +- 67 files changed, 1895 insertions(+), 574 deletions(-) create mode 100644 Ghidra/Features/Base/src/main/help/help/topics/DisassemblerPlugin/images/LengthOverrideLockPrefixExample.png create mode 100644 Ghidra/Features/Base/src/main/help/help/topics/DisassemblerPlugin/images/LengthOverrideLockPrefixExample2.png create mode 100644 Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/disassembler/SetLengthOverrideAction.java create mode 100644 Ghidra/Features/Base/src/test.slow/java/ghidra/app/merge/listing/CodeUnitMergeManager6Test.java diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/copying/DebuggerCopyPlan.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/copying/DebuggerCopyPlan.java index 818067afff..c1ba706387 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/copying/DebuggerCopyPlan.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/copying/DebuggerCopyPlan.java @@ -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(); }); } } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/listing/DBTraceCodeUnitAdapter.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/listing/DBTraceCodeUnitAdapter.java index 08f680b1e3..d96aae386c 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/listing/DBTraceCodeUnitAdapter.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/listing/DBTraceCodeUnitAdapter.java @@ -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; diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/listing/DBTraceInstruction.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/listing/DBTraceInstruction.java index 9ba646ac6f..347bec2e49 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/listing/DBTraceInstruction.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/listing/DBTraceInstruction.java @@ -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 implements - TraceInstruction, InstructionAdapterFromPrototype, InstructionContext { +public class DBTraceInstruction extends AbstractDBTraceCodeUnit + 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> 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(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(TraceInstructionChangeType.FALL_THROUGH_OVERRIDE_CHANGED, - space, this, !overridden, overridden)); + new TraceChangeRecord<>(TraceInstructionChangeType.FALL_THROUGH_OVERRIDE_CHANGED, space, + this, !overridden, overridden)); } @Override diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/listing/DBTraceInstructionsMemoryView.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/listing/DBTraceInstructionsMemoryView.java index 3f5d315004..a55df5600b 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/listing/DBTraceInstructionsMemoryView.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/listing/DBTraceInstructionsMemoryView.java @@ -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 diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/listing/DBTraceInstructionsView.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/listing/DBTraceInstructionsView.java index 676dccd17b..95fd050ab0 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/listing/DBTraceInstructionsView.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/listing/DBTraceInstructionsView.java @@ -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; diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/program/AbstractDBTraceProgramViewListing.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/program/AbstractDBTraceProgramViewListing.java index 22b316fd77..a3263acf40 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/program/AbstractDBTraceProgramViewListing.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/program/AbstractDBTraceProgramViewListing.java @@ -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 diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/program/DBTraceProgramView.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/program/DBTraceProgramView.java index 1bc046f38c..a6e1ae917b 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/program/DBTraceProgramView.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/program/DBTraceProgramView.java @@ -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); diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/Trace.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/Trace.java index f3ec5693bf..9300b72886 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/Trace.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/Trace.java @@ -235,6 +235,8 @@ public interface Trace extends DataTypeManagerDomainObject { new TraceInstructionChangeType<>(); public static final TraceInstructionChangeType FALL_THROUGH_OVERRIDE_CHANGED = new TraceInstructionChangeType<>(); + public static final TraceInstructionChangeType LENGTH_OVERRIDE_CHANGED = + new TraceInstructionChangeType<>(); } public static final class TraceMemoryBytesChangeType diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/listing/TraceInstructionsView.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/listing/TraceInstructionsView.java index 34e2c03259..c8028156e6 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/listing/TraceInstructionsView.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/listing/TraceInstructionsView.java @@ -37,11 +37,12 @@ public interface TraceInstructionsView extends TraceBaseDefinedUnitsViewStatic Disassembly
  • Restricted Disassembly
  • + +
  • Disassemble ARM / Thumb
  • +
  • Processor Options
  • + +
  • Modify Instruction Flow
  • + +
  • Modify Instruction Length
  • +

    Disassembly

    @@ -131,7 +139,7 @@ undefined bytes.

    -

    Disassembly (Restricted)

    +

    Restricted Disassembly

    Restricted Disassembly is similar to Disassembly, except that only bytes in @@ -157,13 +165,11 @@ undefined bytes.

    -

     

    - -

    Disassemble ARM / Disassemble ThumbDisassemble ARM / Thumb

    -

    Disassemble ARM and Disassemble Thumb will only be available if the program you are +

    Disassemble ARM and Disassemble Thumb actions will only be available if the program you are working with is an ARM based processor.  ARM processors have two states, ARM and Thumb mode.  The instructions available in ARM mode are 4 bytes long.  In Thumb mode, the instructions are "generally" 2 bytes long.  ARM and Thumb mode are mutually exclusive, @@ -199,38 +205,6 @@ undefined bytes.

    -

    Modify Instruction Flow

    - -
    -

    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.

    -

    The following basic flow types may be imposed upon the default flow of an instruction:

    -
      -
    • BRANCH
    • -
    • CALL
    • -
    • CALL_RETURN
    • -
    • RETURN
    • -
    -

    In all situations the conditional nature of the original flow is perserved.

    -

    To Modify Instruction Flow:

    -
      -
    1. Place the cursor on an instruction within the Code Browser. Note that - instructions which are purely fall-through can not be modified.
    2. -
    3. Right-mouse-click
    4. -
    5. Select Modify Instruction Flow... menu item. -
    6. Within the Modify Instruction Flow dialog select the desired basic flow - behavior.
    7. -
    8. Click OK in the dialog
    9. -
    -

    An instruction whose flow has been - modified will have its' mnemonic color modified.

    - -
    - -

    Processor Options

    @@ -244,11 +218,6 @@


    - - -
    -
    -

    The options are @@ -282,8 +251,90 @@

  • Languages
  • - -

     


    + +

    Modify Instruction Flow

    + +
    +

    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.

    +

    The following basic flow types may be imposed upon the default flow of an instruction:

    +
      +
    • BRANCH
    • +
    • CALL
    • +
    • CALL_RETURN
    • +
    • RETURN
    • +
    +

    In all situations the conditional nature of the original flow is perserved.

    +

    To Modify Instruction Flow:

    +
      +
    1. Place the cursor on an instruction within the Code Browser. Note that + instructions which are purely fall-through can not be modified.
    2. +
    3. Right-mouse-click
    4. +
    5. Select Modify Instruction Flow... menu item. +
    6. Within the Modify Instruction Flow dialog select the desired basic flow + behavior.
    7. +
    8. Click OK in the dialog
    9. +
    +

    An instruction whose flow has been + modified will have its' mnemonic color modified.

    + +
    + +

    Modify Instruction Length

    + +
    +

    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 + JZ 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.

    +
    + +
    +
    +
    +
    + +

    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.

    + +
      +
    1. Place the cursor on an instruction within the Code Browser (e.g., + CMPXCHG instruction).
    2. +
    3. Right-mouse-click
    4. +
    5. Select Modify Instruction Length... menu item. +
    6. Within the Modify Instruction Length 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.
    7. +
    8. Click OK in the dialog
    9. +
    10. 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., D key binding).
    11. +
    12. You may also delete the error bookmark which should no longer be relavent. +
    + +

    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 JNZ + instruction. +

    +
    + +
    +
    +
    +
    + +
    + diff --git a/Ghidra/Features/Base/src/main/help/help/topics/DisassemblerPlugin/images/LengthOverrideLockPrefixExample.png b/Ghidra/Features/Base/src/main/help/help/topics/DisassemblerPlugin/images/LengthOverrideLockPrefixExample.png new file mode 100644 index 0000000000000000000000000000000000000000..5e946f93770ea50d01b73b9469f918a0f9bbaf67 GIT binary patch literal 13380 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

    nOWcU#>W>w7O36FhsUWU)Wev< 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 F_#Xz9C{q9c literal 0 HcmV?d00001 diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/merge/listing/CodeUnitDetails.java b/Ghidra/Features/Base/src/main/java/ghidra/app/merge/listing/CodeUnitDetails.java index dd19ed023c..0ed26ebce3 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/merge/listing/CodeUnitDetails.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/merge/listing/CodeUnitDetails.java @@ -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(); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/merge/listing/CodeUnitMerger.java b/Ghidra/Features/Base/src/main/java/ghidra/app/merge/listing/CodeUnitMerger.java index 10766f8427..8236586996 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/merge/listing/CodeUnitMerger.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/merge/listing/CodeUnitMerger.java @@ -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()) { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/merge/listing/ListingMergeManager.java b/Ghidra/Features/Base/src/main/java/ghidra/app/merge/listing/ListingMergeManager.java index 213722ba38..e309e77518 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/merge/listing/ListingMergeManager.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/merge/listing/ListingMergeManager.java @@ -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. */ diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/AutoAnalysisManager.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/AutoAnalysisManager.java index b6d0f672f5..22b14ced8e 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/AutoAnalysisManager.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/AutoAnalysisManager.java @@ -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) diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/disassembler/DisassemblerPlugin.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/disassembler/DisassemblerPlugin.java index bfca604ced..3866d49904 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/disassembler/DisassemblerPlugin.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/disassembler/DisassemblerPlugin.java @@ -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) { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/disassembler/SetLengthOverrideAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/disassembler/SetLengthOverrideAction.java new file mode 100644 index 0000000000..9a6b1fed36 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/disassembler/SetLengthOverrideAction.java @@ -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; + } + +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/fallthrough/FallThroughPlugin.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/fallthrough/FallThroughPlugin.java index fba2516dc8..5ce8294a76 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/fallthrough/FallThroughPlugin.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/fallthrough/FallThroughPlugin.java @@ -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); } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/instructionsearch/ui/InsertBytesWidget.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/instructionsearch/ui/InsertBytesWidget.java index d878427944..84d922f04b 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/instructionsearch/ui/InsertBytesWidget.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/instructionsearch/ui/InsertBytesWidget.java @@ -326,8 +326,9 @@ public class InsertBytesWidget extends ReusableDialogComponentProvider implement private List createOperandMetadata(PseudoInstruction instruction) throws MemoryAccessException { - List operands = new ArrayList<>(); + byte[] bytes = instruction.getParsedBytes(); + List 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); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/reloc/InstructionStasher.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/reloc/InstructionStasher.java index 9abc626868..729029258a 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/reloc/InstructionStasher.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/reloc/InstructionStasher.java @@ -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) { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/exporter/ProgramTextWriter.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/exporter/ProgramTextWriter.java index 04a4fe2231..e8ea62ffca 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/exporter/ProgramTextWriter.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/exporter/ProgramTextWriter.java @@ -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 { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/BytesFieldFactory.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/BytesFieldFactory.java index bad3ebf9ba..03f19213b4 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/BytesFieldFactory.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/BytesFieldFactory.java @@ -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 { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/MnemonicFieldFactory.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/MnemonicFieldFactory.java index ceb054f35d..612f3a2bf5 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/MnemonicFieldFactory.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/MnemonicFieldFactory.java @@ -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; } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/PostCommentFieldFactory.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/PostCommentFieldFactory.java index 7ed5a334c2..aeb541f8b0 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/PostCommentFieldFactory.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/PostCommentFieldFactory.java @@ -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 = diff --git a/Ghidra/Features/Base/src/main/java/ghidra/program/util/DiffUtility.java b/Ghidra/Features/Base/src/main/java/ghidra/program/util/DiffUtility.java index f4fe11379e..4ec68b1072 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/program/util/DiffUtility.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/program/util/DiffUtility.java @@ -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) diff --git a/Ghidra/Features/Base/src/main/java/ghidra/program/util/ProgramDiff.java b/Ghidra/Features/Base/src/main/java/ghidra/program/util/ProgramDiff.java index 9521ddb31e..2885f22926 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/program/util/ProgramDiff.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/program/util/ProgramDiff.java @@ -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 } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/program/util/ProgramDiffDetails.java b/Ghidra/Features/Base/src/main/java/ghidra/program/util/ProgramDiffDetails.java index c13f49a2a6..23547e268e 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/program/util/ProgramDiffDetails.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/program/util/ProgramDiffDetails.java @@ -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; diff --git a/Ghidra/Features/Base/src/main/java/ghidra/program/util/ProgramMemoryUtil.java b/Ghidra/Features/Base/src/main/java/ghidra/program/util/ProgramMemoryUtil.java index 6c7e2d7b99..65e2f7d2fa 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/program/util/ProgramMemoryUtil.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/program/util/ProgramMemoryUtil.java @@ -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); } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/program/util/ProgramMerge.java b/Ghidra/Features/Base/src/main/java/ghidra/program/util/ProgramMerge.java index 5b9cd7a7cf..8e8c7d12f7 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/program/util/ProgramMerge.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/program/util/ProgramMerge.java @@ -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 tagNames = getTagNames(discardTags); - Iterator 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); } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/program/util/SymbolicPropogator.java b/Ghidra/Features/Base/src/main/java/ghidra/program/util/SymbolicPropogator.java index 49bc2bece1..52404311c1 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/program/util/SymbolicPropogator.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/program/util/SymbolicPropogator.java @@ -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... diff --git a/Ghidra/Features/Base/src/main/java/ghidra/util/table/field/BytesTableColumn.java b/Ghidra/Features/Base/src/main/java/ghidra/util/table/field/BytesTableColumn.java index 5512615617..e2704f8e1e 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/util/table/field/BytesTableColumn.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/util/table/field/BytesTableColumn.java @@ -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 monospacedRenderer = - new AbstractGColumnRenderer<>() { - @Override - protected void configureFont(JTable table, TableModel model, int column) { - setFont(getFixedWidthFont()); + private final GColumnRenderer 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 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 diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/ProgramDB.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/ProgramDB.java index ef4eed5a3c..4540f3ebf9 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/ProgramDB.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/ProgramDB.java @@ -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 diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/code/CodeManager.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/code/CodeManager.java index 4ab40ba900..f938ca15f3 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/code/CodeManager.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/code/CodeManager.java @@ -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} *

    {@literal
    -	 * instruction.minAddress() <= addr <= instruction.maxAddress()
    +	 * instruction.getMinAddress() <= addr <= 
    +	 *    instruction.getMinAddress().add(instruction.getPrototype().getLength() - 1)
     	 * }
    - * + * If {@code usePrototypeLength==false} + *
    {@literal
    +	 *    instruction.getMinAddress() <= addr <= instruction.getMaxAddress()
    +	 * }
    + * 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(); diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/code/CodeUnitDB.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/code/CodeUnitDB.java index 81b923fb91..bcefe9f714 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/code/CodeUnitDB.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/code/CodeUnitDB.java @@ -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 propertyNames() { PropertyMapManager upm = codeMgr.getPropertyMapManager(); diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/code/InstructionDB.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/code/InstructionDB.java index 193feeb680..c8e56fe794 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/code/InstructionDB.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/code/InstructionDB.java @@ -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
    list = new ArrayList<>(); + Set
    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) { diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/external/ExternalManagerDB.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/external/ExternalManagerDB.java index a84d788322..8cc33b1a30 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/external/ExternalManagerDB.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/external/ExternalManagerDB.java @@ -18,6 +18,8 @@ package ghidra.program.database.external; import java.io.IOException; import java.util.*; +import org.apache.commons.lang3.StringUtils; + import db.*; import ghidra.framework.store.FileSystem; import ghidra.program.database.ManagerDB; @@ -90,8 +92,8 @@ public class ExternalManagerDB implements ManagerDB, ExternalManager { @Override public void setProgram(ProgramDB program) { this.program = program; - symbolMgr = (SymbolManager) program.getSymbolTable(); - functionMgr = (FunctionManagerDB) program.getFunctionManager(); + symbolMgr = program.getSymbolTable(); + functionMgr = program.getFunctionManager(); scopeMgr = program.getNamespaceManager(); } @@ -153,8 +155,8 @@ public class ExternalManagerDB implements ManagerDB, ExternalManager { oldAddrMap.decodeAddress(rec.getLongValue(OldExtRefAdapter.FROM_ADDR_COL)); int opIndex = rec.getShortValue(OldExtRefAdapter.OP_INDEX_COL); boolean userDefined = rec.getBooleanValue(OldExtRefAdapter.USER_DEFINED_COL); - String name = nameMap.get(rec.getLongValue(OldExtRefAdapter.EXT_NAME_ID_COL)); - if (name == null) { + String extLibraryName = nameMap.get(rec.getLongValue(OldExtRefAdapter.EXT_NAME_ID_COL)); + if (extLibraryName == null) { continue; // should not happen } String label = rec.getString(OldExtRefAdapter.LABEL_COL); @@ -163,14 +165,11 @@ public class ExternalManagerDB implements ManagerDB, ExternalManager { : null; try { - refMgr.addExternalReference(fromAddr, name, label, addr, + refMgr.addExternalReference(fromAddr, extLibraryName, label, addr, userDefined ? SourceType.USER_DEFINED : SourceType.IMPORTED, opIndex, RefType.DATA); } - catch (DuplicateNameException e) { - Msg.error(this, "Unexpected Exception: " + e.getMessage(), e); - } - catch (InvalidInputException e) { + catch (InvalidInputException | DuplicateNameException e) { Msg.error(this, "Unexpected Exception: " + e.getMessage(), e); } monitor.setProgress(++cnt); @@ -283,7 +282,8 @@ public class ExternalManagerDB implements ManagerDB, ExternalManager { private SourceType checkExternalLabel(String extLabel, Address extAddr, SourceType source) throws InvalidInputException { - if (extLabel == null || extLabel.length() == 0) { + if (extLabel != null && (StringUtils.isBlank(extLabel) || + SymbolUtilities.isReservedExternalDefaultName(extLabel, addrMap.getAddressFactory()))) { extLabel = null; } if (extLabel == null && extAddr == null) { @@ -312,8 +312,15 @@ public class ExternalManagerDB implements ManagerDB, ExternalManager { throw new InvalidInputException("The namespace must be an external namespace."); } sourceType = checkExternalLabel(extLabel, extAddr, sourceType); - if (extAddr != null && !extAddr.isLoadedMemoryAddress()) { - throw new InvalidInputException("Invalid memory address"); + if (extAddr != null) { + if (!extAddr.isLoadedMemoryAddress()) { + throw new InvalidInputException("Invalid memory address: " + extAddr); + } + AddressSpace space = extAddr.getAddressSpace(); + if (!space.equals(program.getAddressFactory().getAddressSpace(space.getName()))) { + throw new InvalidInputException( + "Memory address not defined for program: " + extAddr); + } } lock.acquire(); try { @@ -359,7 +366,7 @@ public class ExternalManagerDB implements ManagerDB, ExternalManager { /** * Get the external location which best matches the specified parameters. * Preference is given to extLabel over extAddr - * @param libScope, the library namespace containing this external location. + * @param library the library namespace containing this external location. * @param extLabel the name of the external location. Can be null. * @param extAddr the address of function in the external program. Can be null * @return the best matching ExternalLocation or null. diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/mem/MemoryMapDB.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/mem/MemoryMapDB.java index 980c10c5d8..bbaac650ba 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/mem/MemoryMapDB.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/mem/MemoryMapDB.java @@ -2297,7 +2297,7 @@ public class MemoryMapDB implements Memory, ManagerDB, LiveMemoryListener { void checkRangeForInstructions(Address start, Address end) throws MemoryAccessException { CodeManager codeManager = program.getCodeManager(); - Instruction instr = codeManager.getInstructionContaining(start); + Instruction instr = codeManager.getInstructionContaining(start, true); if (instr != null) { throw new MemoryAccessException( "Memory change conflicts with instruction at " + instr.getMinAddress()); diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/references/ReferenceDBManager.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/references/ReferenceDBManager.java index 67d268a99c..7af458de41 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/references/ReferenceDBManager.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/references/ReferenceDBManager.java @@ -449,6 +449,9 @@ public class ReferenceDBManager implements ReferenceManager, ManagerDB, ErrorHan if (!fromAddr.isMemoryAddress()) { throw new IllegalArgumentException("From address must be memory address"); } + if (!type.isData()) { + throw new IllegalArgumentException("Invalid stack reference type: " + type); + } Function function = program.getFunctionManager().getFunctionContaining(fromAddr); if (function == null) { throw new IllegalArgumentException( @@ -488,6 +491,9 @@ public class ReferenceDBManager implements ReferenceManager, ManagerDB, ErrorHan if (!fromAddr.isMemoryAddress()) { throw new IllegalArgumentException("From address must be memory address"); } + if (!type.isData()) { + throw new IllegalArgumentException("Invalid register reference type: " + type); + } removeAllFrom(fromAddr, opIndex); try { return addRef(fromAddr, register.getAddress(), type, sourceType, opIndex, false, false, @@ -573,9 +579,9 @@ public class ReferenceDBManager implements ReferenceManager, ManagerDB, ErrorHan if (!fromAddr.isMemoryAddress()) { throw new IllegalArgumentException("From address must be memory addresses"); } - removeAllFrom(fromAddr, opIndex); try { if (symbolMgr.getPrimarySymbol(location.getExternalSpaceAddress()) != null) { + removeAllFrom(fromAddr, opIndex); return addRef(fromAddr, location.getExternalSpaceAddress(), type, sourceType, opIndex, false, false, 0); } @@ -598,24 +604,17 @@ public class ReferenceDBManager implements ReferenceManager, ManagerDB, ErrorHan public Reference addExternalReference(Address fromAddr, String libraryName, String extLabel, Address extAddr, SourceType sourceType, int opIndex, RefType type) throws InvalidInputException, DuplicateNameException { - if (libraryName == null || libraryName.length() == 0) { - throw new InvalidInputException("A valid library name must be specified."); + + if (!fromAddr.isMemoryAddress()) { + throw new IllegalArgumentException("From addresses must be memory addresses"); } - if (extLabel != null && extLabel.length() == 0) { - extLabel = null; - } - if (extLabel == null && extAddr == null) { - throw new InvalidInputException("Either an external label or address is required"); - } - if (!fromAddr.isMemoryAddress() || (extAddr != null && !extAddr.isMemoryAddress())) { - throw new IllegalArgumentException( - "From and extAddr addresses must be memory addresses"); - } - removeAllFrom(fromAddr, opIndex); try { - ExternalManagerDB extMgr = (ExternalManagerDB) program.getExternalManager(); + ExternalManagerDB extMgr = program.getExternalManager(); ExternalLocation extLoc = extMgr.addExtLocation(libraryName, extLabel, extAddr, sourceType); + + removeAllFrom(fromAddr, opIndex); + Address toAddr = extLoc.getExternalSpaceAddress(); return addRef(fromAddr, toAddr, type, sourceType, opIndex, false, false, 0); @@ -630,24 +629,16 @@ public class ReferenceDBManager implements ReferenceManager, ManagerDB, ErrorHan public Reference addExternalReference(Address fromAddr, Namespace extNamespace, String extLabel, Address extAddr, SourceType sourceType, int opIndex, RefType type) throws InvalidInputException, DuplicateNameException { - if (extNamespace == null || !extNamespace.isExternal()) { - throw new InvalidInputException("The namespace must be an external namespace."); + if (!fromAddr.isMemoryAddress()) { + throw new IllegalArgumentException("From addresses must be memory addresses"); } - if (extLabel != null && extLabel.length() == 0) { - extLabel = null; - } - if (extLabel == null && extAddr == null) { - throw new InvalidInputException("Either an external label or address is required"); - } - if (!fromAddr.isMemoryAddress() || (extAddr != null && !extAddr.isMemoryAddress())) { - throw new IllegalArgumentException( - "From and extAddr addresses must be memory addresses"); - } - removeAllFrom(fromAddr, opIndex); try { - ExternalManagerDB extMgr = (ExternalManagerDB) program.getExternalManager(); + ExternalManagerDB extMgr = program.getExternalManager(); ExternalLocation extLoc = extMgr.addExtLocation(extNamespace, extLabel, extAddr, sourceType); + + removeAllFrom(fromAddr, opIndex); + Address toAddr = extLoc.getExternalSpaceAddress(); return addRef(fromAddr, toAddr, type, sourceType, opIndex, false, false, 0); } diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/correlate/AllBytesHashCalculator.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/correlate/AllBytesHashCalculator.java index 625204a4d5..48732de696 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/correlate/AllBytesHashCalculator.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/correlate/AllBytesHashCalculator.java @@ -26,7 +26,7 @@ import ghidra.program.model.mem.MemoryAccessException; public class AllBytesHashCalculator implements HashCalculator { @Override public int calcHash(int startHash, Instruction inst) throws MemoryAccessException { - byte[] bytes = inst.getBytes(); + byte[] bytes = inst.getParsedBytes(); for(int i=0;i + * NOTE: Use of the feature with a delay slot instruction is discouraged. + * @param length effective instruction code unit length. + * @throws CodeUnitInsertionException if expanding instruction length conflicts with another + * instruction or length is not a multiple of the language specified instruction alignment. + */ + public void setLengthOverride(int length) throws CodeUnitInsertionException; + + /** + * Determine if an instruction length override has been set. + * @return true if length override has been set else false. + */ + public boolean isLengthOverridden(); + + /** + * Get the actual number of bytes parsed when forming this instruction. While this method + * will generally return the same value as {@link #getLength()}, its value will differ when + * {@link #setLengthOverride(int)} has been used. In addition, it is important to note that + * {@link #getMaxAddress()} will always reflect a non-overlapping address which reflects + * {@link #getLength()}. + * This method is equivalent to the following code for a given instruction: + *
    + *
    +	 * {@link InstructionPrototype} proto = instruction.{@link #getPrototype()};
    +	 * int length = proto.{@link InstructionPrototype#getLength() getLength()};
    +	 * 
    + * @return the actual number of bytes parsed when forming this instruction + */ + public int getParsedLength(); + + /** + * Get the actual bytes parsed when forming this instruction. While this method + * will generally return the same value as {@link #getBytes()}, it will return more bytes when + * {@link #setLengthOverride(int)} has been used. In this override situation, the bytes + * returned will generally duplicate some of the parsed bytes associated with the next + * instruction that this instruction overlaps. + * This method is equivalent to the following code for a given instruction: + *
    + *
    +	 * {@link InstructionPrototype} proto = instruction.{@link #getPrototype()};
    +	 * {@link Memory} mem = instruction.{@link #getMemory()};
    +	 * byte[] bytes = mem.getBytes(instruction.{@link #getAddress()}, proto.getLength());
    +	 * int length = proto.{@link InstructionPrototype#getLength() getLength()};
    +	 * 
    + * @return the actual number of bytes parsed when forming this instruction + * @throws MemoryAccessException if the full number of bytes could not be read + */ + public byte[] getParsedBytes() throws MemoryAccessException; + /** * Get an array of PCode operations (micro code) that this instruction * performs. Flow overrides are not factored into pcode. @@ -223,21 +282,22 @@ public interface Instruction extends CodeUnit, ProcessorContext { * some RISC processors such as SPARC and the PA-RISC. This * returns an integer instead of a boolean in case some other * processor executes more than one instruction from a delay slot. + * @return delay slot depth (number of instructions) */ public int getDelaySlotDepth(); /** - * Return true if this instruction was disassembled in a delay slot + * @return true if this instruction was disassembled in a delay slot */ public boolean isInDelaySlot(); /** - * Get the instruction following this one in address order. + * @return the instruction following this one in address order or null if none found. */ public Instruction getNext(); /** - * Get the instruction before this one in address order. + * @return the instruction before this one in address order or null if none found. */ public Instruction getPrevious(); @@ -256,7 +316,7 @@ public interface Instruction extends CodeUnit, ProcessorContext { public void clearFallThroughOverride(); /** - * Returns true if this instructions fallthrough has been overriden. + * @return true if this instructions fallthrough has been overriden. */ public boolean isFallThroughOverridden(); diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/listing/InstructionPcodeOverride.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/listing/InstructionPcodeOverride.java index d2647b5ed3..db1c8535a6 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/listing/InstructionPcodeOverride.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/listing/InstructionPcodeOverride.java @@ -28,7 +28,8 @@ import ghidra.util.Msg; public class InstructionPcodeOverride implements PcodeOverride { - protected Instruction instr; + protected final Instruction instr; + private boolean callOverrideApplied = false; private boolean jumpOverrideApplied = false; private boolean callOtherCallOverrideApplied = false; @@ -64,7 +65,7 @@ public class InstructionPcodeOverride implements PcodeOverride { public Address getFallThroughOverride() { Address defaultFallAddr = instr.getDefaultFallThrough(); Address fallAddr = instr.getFallThrough(); - if (fallAddr != null && !fallAddr.equals(defaultFallAddr)) { + if (fallAddr != null && (instr.isLengthOverridden() || !fallAddr.equals(defaultFallAddr))) { return fallAddr; } return null; diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/listing/InstructionStub.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/listing/InstructionStub.java index 855caa133f..7961ec2852 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/listing/InstructionStub.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/listing/InstructionStub.java @@ -146,21 +146,26 @@ public class InstructionStub implements Instruction { throw new UnsupportedOperationException(); } - @Override - public boolean isSuccessor(CodeUnit codeUnit) { - throw new UnsupportedOperationException(); - } - @Override public int getLength() { throw new UnsupportedOperationException(); } + @Override + public int getParsedLength() { + throw new UnsupportedOperationException(); + } + @Override public byte[] getBytes() throws MemoryAccessException { throw new UnsupportedOperationException(); } + @Override + public byte[] getParsedBytes() throws MemoryAccessException { + throw new UnsupportedOperationException(); + } + @Override public void getBytesInCodeUnit(byte[] buffer, int bufferOffset) throws MemoryAccessException { throw new UnsupportedOperationException(); @@ -458,6 +463,16 @@ public class InstructionStub implements Instruction { throw new UnsupportedOperationException(); } + @Override + public void setLengthOverride(int length) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isLengthOverridden() { + throw new UnsupportedOperationException(); + } + @Override public void setFlowOverride(FlowOverride flowOverride) { throw new UnsupportedOperationException(); diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/listing/Listing.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/listing/Listing.java index 4e0362b861..ac5407e6a8 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/listing/Listing.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/listing/Listing.java @@ -602,17 +602,22 @@ public interface Listing { * changes will result in a ContextChangeException * * @param addr the address at which to create an instruction - * @param prototype the InstructionPrototype the describes the type of - * instruction to create. - * @param memBuf buffer that provides the bytes that make up the - * instruction. + * @param prototype the InstructionPrototype that describes the type of instruction to create. + * @param memBuf buffer that provides the bytes that make up the instruction. * @param context the processor context at this location. - * @return the newly created instruction. - * @exception CodeUnitInsertionException thrown if the new Instruction would - * overlap and existing Instruction or defined data. + * @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 createInstruction(Address addr, InstructionPrototype prototype, - MemBuffer memBuf, ProcessorContextView context) throws CodeUnitInsertionException; + MemBuffer memBuf, ProcessorContextView context, int length) + throws CodeUnitInsertionException; /** * Creates a complete set of instructions. A preliminary pass will be made @@ -659,8 +664,7 @@ public interface Listing { * @exception CodeUnitInsertionException thrown if the new Instruction would * overlap and existing Instruction or defined data. */ - public Data createData(Address addr, DataType dataType) - throws CodeUnitInsertionException; + public Data createData(Address addr, DataType dataType) throws CodeUnitInsertionException; /** * Clears any code units in the given range returning everything to "db"s, diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/listing/StubListing.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/listing/StubListing.java index eef4a44ea0..eb1a5846ee 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/listing/StubListing.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/listing/StubListing.java @@ -282,13 +282,14 @@ public class StubListing implements Listing { } @Override - public PropertyMap getPropertyMap(String propertyName) { + public PropertyMap getPropertyMap(String propertyName) { throw new UnsupportedOperationException(); } @Override public Instruction createInstruction(Address addr, InstructionPrototype prototype, - MemBuffer memBuf, ProcessorContextView context) throws CodeUnitInsertionException { + MemBuffer memBuf, ProcessorContextView context, int forcedLengthOverride) + throws CodeUnitInsertionException { throw new UnsupportedOperationException(); } diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/symbol/ExternalManager.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/symbol/ExternalManager.java index 8dfa7c6c9a..6027628c81 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/symbol/ExternalManager.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/symbol/ExternalManager.java @@ -18,6 +18,7 @@ package ghidra.program.model.symbol; import java.util.List; import ghidra.program.model.address.Address; +import ghidra.program.model.listing.Function; import ghidra.program.model.listing.Library; import ghidra.util.exception.DuplicateNameException; import ghidra.util.exception.InvalidInputException; @@ -167,92 +168,121 @@ public interface ExternalManager { * Adds a new external library name * @param libraryName the new external library name to add. * @param source the source of this external library - * @return library - * @throws InvalidInputException + * @return library external {@link Library namespace} + * @throws InvalidInputException if {@code libraryName} is invalid or null. A library name + * with spaces or the empty string are not permitted. + * Neither {@code extLabel} nor {@code extAddr} was specified properly. * @throws DuplicateNameException if another non-Library namespace has the same name */ public Library addExternalLibraryName(String libraryName, SourceType source) throws InvalidInputException, DuplicateNameException; /** - * Get or create an external location associated with an library/file named extName - * and the label within that file specified by extLabel + * Get or create an external location associated with a library/file named {@code libraryName} + * and the location within that file identified by {@code extLabel} and/or its memory address + * {@code extAddr}. Either or both {@code extLabel} or {@code extAddr} must be specified. * @param libraryName the external library name - * @param extLabel the external label - * @param extAddr the external address + * @param extLabel the external label or null + * @param extAddr the external memory address or null * @param sourceType the source type of this external library's symbol * @return external location - * @throws InvalidInputException + * @throws InvalidInputException if {@code libraryName} is invalid or null, or an invalid + * {@code extlabel} is specified. Names with spaces or the empty string are not permitted. + * Neither {@code extLabel} nor {@code extAddr} was specified properly. * @throws DuplicateNameException if another non-Library namespace has the same name + * @throws IllegalArgumentException if an invalid {@code extAddr} was specified. */ public ExternalLocation addExtLocation(String libraryName, String extLabel, Address extAddr, SourceType sourceType) throws InvalidInputException, DuplicateNameException; /** - * Get or create an external location in the indicated parent namespace with the specified name. + * Create an external location in the indicated external parent namespace + * and identified by {@code extLabel} and/or its memory address + * {@code extAddr}. Either or both {@code extLabel} or {@code extAddr} must be specified. * @param extNamespace the external namespace - * @param extLabel the external label - * @param extAddr the external address + * @param extLabel the external label or null + * @param extAddr the external memory address or null * @param sourceType the source type of this external library's symbol * @return external location - * @throws InvalidInputException + * @throws InvalidInputException if an invalid {@code extlabel} is specified. + * Names with spaces or the empty string are not permitted. + * Neither {@code extLabel} nor {@code extAddr} was specified properly. + * @throws IllegalArgumentException if an invalid {@code extAddr} was specified. */ public ExternalLocation addExtLocation(Namespace extNamespace, String extLabel, Address extAddr, SourceType sourceType) throws InvalidInputException; /** - * Get or create an external location in the indicated parent namespace with the specified name. + * Get or create an external location in the indicated external parent namespace + * and identified by {@code extLabel} and/or its memory address + * {@code extAddr}. Either or both {@code extLabel} or {@code extAddr} must be specified. * @param extNamespace the external namespace - * @param extLabel the external label - * @param extAddr the external address + * @param extLabel the external label or null + * @param extAddr the external memory address or null * @param sourceType the source type of this external library's symbol * @param reuseExisting if true, this will return an existing matching external * location instead of creating a new one. * @return external location - * @throws InvalidInputException + * @throws InvalidInputException if an invalid {@code extlabel} is specified. + * Names with spaces or the empty string are not permitted. + * Neither {@code extLabel} nor {@code extAddr} was specified properly. + * @throws IllegalArgumentException if an invalid {@code extAddr} was specified. */ public ExternalLocation addExtLocation(Namespace extNamespace, String extLabel, Address extAddr, SourceType sourceType, boolean reuseExisting) throws InvalidInputException; /** - * Get or create an external location associated with an library/file named extName - * and the label within that file specified by extLabel + * Create an external {@link Function} in the external {@link Library} namespace + * {@code libararyName} and identified by {@code extLabel} and/or its memory address + * {@code extAddr}. Either or both {@code extLabel} or {@code extAddr} must be specified. * @param libraryName the external library name - * @param extLabel the external label - * @param extAddr the external address + * @param extLabel label within the external program, may be null if extAddr is not null + * @param extAddr memory address within the external program, may be null * @param sourceType the source type of this external library's symbol * @return external location - * @throws InvalidInputException + * @throws InvalidInputException if {@code libraryName} is invalid or null, or an invalid + * {@code extlabel} is specified. Names with spaces or the empty string are not permitted. + * Neither {@code extLabel} nor {@code extAddr} was specified properly. * @throws DuplicateNameException if another non-Library namespace has the same name + * @throws IllegalArgumentException if an invalid {@code extAddr} was specified. */ public ExternalLocation addExtFunction(String libraryName, String extLabel, Address extAddr, SourceType sourceType) throws InvalidInputException, DuplicateNameException; /** - * Get or create an external function location associated with an library/file named extName - * and the label within that file specified by extLabel + * Create an external {@link Function} in the indicated external parent namespace + * and identified by {@code extLabel} and/or its memory address + * {@code extAddr}. Either or both {@code extLabel} or {@code extAddr} must be specified. * @param extNamespace the external namespace - * @param extLabel the external label - * @param extAddr the external address + * @param extLabel the external label or null + * @param extAddr the external memory address or null * @param sourceType the source type of this external library's symbol * @return external location - * @throws InvalidInputException + * @throws InvalidInputException if an invalid {@code extlabel} is specified. + * Names with spaces or the empty string are not permitted. + * Neither {@code extLabel} nor {@code extAddr} was specified properly. + * @throws IllegalArgumentException if an invalid {@code extAddr} was specified. */ public ExternalLocation addExtFunction(Namespace extNamespace, String extLabel, Address extAddr, SourceType sourceType) throws InvalidInputException; /** - * Get or create an external function location associated with an library/file named extName - * and the label within that file specified by extLabel + * Get or create an external {@link Function} in the indicated external parent namespace + * and identified by {@code extLabel} and/or its memory address + * {@code extAddr}. Either or both {@code extLabel} or {@code extAddr} must be specified. * @param extNamespace the external namespace - * @param extLabel the external label + * @param extLabel the external label or null + * @param extAddr the external memory address or null * @param sourceType the source type of this external library's symbol * @param reuseExisting if true, will return any existing matching location instead of * creating a new one. If false, will prefer to create a new one as long as the specified * address is not null and not used in an existing location. * @return external location - * @throws InvalidInputException + * @throws InvalidInputException if an invalid {@code extlabel} is specified. + * Names with spaces or the empty string are not permitted. + * Neither {@code extLabel} nor {@code extAddr} was specified properly. + * @throws IllegalArgumentException if an invalid {@code extAddr} was specified. */ public ExternalLocation addExtFunction(Namespace extNamespace, String extLabel, Address extAddr, SourceType sourceType, boolean reuseExisting) diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/symbol/RefType.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/symbol/RefType.java index 310450c589..3c59d6ea70 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/symbol/RefType.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/symbol/RefType.java @@ -17,13 +17,24 @@ package ghidra.program.model.symbol; +import ghidra.program.model.address.Address; +import ghidra.program.model.block.CodeBlock; +import ghidra.program.model.listing.CodeUnit; +import ghidra.program.model.listing.Instruction; +import ghidra.program.model.pcode.PcodeOp; + /** - * Class to define reference types. + * {@link RefType} defines reference types used to specify the nature of a directional + * relationship between a source-location and a destination-location where a "location" + * may correspond to a {@link Address}, {@link CodeUnit}, {@link CodeBlock} or other + * code related objects. Reference types are generally identified as either + * {@link #isData() data} (see {@link DataRefType}) or {@link #isFlow() flow} + * (see {@link FlowType}). */ public abstract class RefType { // - // NOTE: + // IMPORTANT: // - When creating a new flow type, be sure to add code to the RefTypeFactory // - Once a RefType value is defined it must be maintained for upgrade use // @@ -70,86 +81,181 @@ public abstract class RefType { static final byte __UNKNOWNPARAM = 107; @Deprecated - static final byte __STACK_READ = 110; // Use __READ instead - required for upgrade use + static final byte __STACK_READ = 110; // Use __READ instead - retained for upgrade use @Deprecated - static final byte __STACK_WRITE = 111; // Use __WRITE instead - required for upgrade use + static final byte __STACK_WRITE = 111; // Use __WRITE instead - retained for upgrade use static final byte __EXTERNAL_REF = 113; static final byte __UNKNOWNDATA_IND = 114; static final byte __DYNAMICDATA = 127; + /** + * {@link #INVALID} corresponds to an unknown {@link FlowType} which encountered an error + * when determining the flow-type of the instruction at the from address. + */ public static final FlowType INVALID = new FlowType.Builder(__INVALID, "INVALID") .setHasFall() .build(); + /** + * {@link #FLOW} corresponds to a complex or generic {@link FlowType}. This may be used + * to describe the flow-type of an instruction or code-block which contains multiple outbound + * flows of differing types. This should not be used for a specific flow {@link Reference}. + */ public static final FlowType FLOW = new FlowType.Builder(__UNKNOWNFLOW, "FLOW") .setHasFall() .build(); + + /** + * {@link #FALL_THROUGH} corresponds to an instruction fall-through override where modeling + * requires a fall-through instruction to convey a branch around other {@link CodeUnit}s. + * While this may be freely used to describe the flow-type of a code-block or its relationship + * to another code-block, its use with a {@link Reference} is reserved for internal use + * to reflect an {@link Instruction} fall-through-override or length-override condition. + */ public static final FlowType FALL_THROUGH = new FlowType.Builder(__FALL_THROUGH, "FALL_THROUGH") .setHasFall() .build(); + + /** + * {@link #UNCONDITIONAL_JUMP} corresponds to an unconditional jump/branch {@link FlowType}. + * This may be used to describe the flow-type of an instruction or code-block, or + * {@link Reference} to another instruction or code-block. + */ public static final FlowType UNCONDITIONAL_JUMP = new FlowType.Builder(__UNCONDITIONAL_JUMP, "UNCONDITIONAL_JUMP") .setIsJump() .build(); + + /** + * {@link #CONDITIONAL_JUMP} corresponds to a conditional jump/branch {@link FlowType}. + * This may be used to describe the flow-type of an instruction or code-block, or + * {@link Reference} to another instruction or code-block. + */ public static final FlowType CONDITIONAL_JUMP = new FlowType.Builder(__CONDITIONAL_JUMP, "CONDITIONAL_JUMP") .setHasFall() .setIsJump() .setIsConditional() .build(); + + /** + * {@link #UNCONDITIONAL_CALL} corresponds to an unconditional call {@link FlowType} with fall-through. + * This may be used to describe the flow-type of an instruction or code-block, or + * call {@link Reference} to another instruction or code-block. + */ public static final FlowType UNCONDITIONAL_CALL = new FlowType.Builder(__UNCONDITIONAL_CALL, "UNCONDITIONAL_CALL") .setHasFall() .setIsCall() .build(); + + /** + * {@link #CONDITIONAL_CALL} corresponds to a conditional call {@link FlowType} with fall-through. + * This may be used to describe the flow-type of an instruction or code-block, or + * call {@link Reference} to another instruction or code-block. + */ public static final FlowType CONDITIONAL_CALL = new FlowType.Builder(__CONDITIONAL_CALL, "CONDITIONAL_CALL") .setHasFall() .setIsCall() .setIsConditional() .build(); + + /** + * {@link #TERMINATOR} corresponds to a terminal {@link FlowType} (e.g., return from a + * function). This may be used to describe the flow-type of an instruction or code-block + * but should generally not be used with a {@link Reference}. + */ public static final FlowType TERMINATOR = new FlowType.Builder(__TERMINATOR, "TERMINATOR") .setIsTerminal() .build(); + + /** + * {@link #COMPUTED_JUMP} corresponds to a computed jump/branch {@link FlowType}. + * This may be used to describe the flow-type of an instruction or code-block, or + * {@link Reference} to another instruction or code-block. + */ public static final FlowType COMPUTED_JUMP = new FlowType.Builder(__COMPUTED_JUMP, "COMPUTED_JUMP") .setIsJump() .setIsComputed() .build(); + + /** + * {@link #TERMINATOR} corresponds to a terminal {@link FlowType} (e.g., conditional return + * from a function). This may be used to describe the flow-type of an instruction or code-block + * but should generally not be used with a {@link Reference}. + */ public static final FlowType CONDITIONAL_TERMINATOR = new FlowType.Builder(__CONDITIONAL_TERMINATOR, "CONDITIONAL_TERMINATOR") .setHasFall() .setIsTerminal() .setIsConditional() .build(); + + /** + * {@link #COMPUTED_CALL} corresponds to a computed call {@link FlowType} with fall-through. + * This may be used to describe the flow-type of an instruction or code-block, or + * call {@link Reference} to another instruction or code-block. + */ public static final FlowType COMPUTED_CALL = new FlowType.Builder(__COMPUTED_CALL, "COMPUTED_CALL") .setHasFall() .setIsCall() .setIsComputed() .build(); + + /** + * {@link #CALL_TERMINATOR} corresponds to an unconditional call {@link FlowType} + * followed by a terminal without fall-through (e.g., unconditional return from a function). + * This may be used to describe the flow-type of an instruction or code-block but + * should generally not be used with a {@link Reference}. A corresponding {@link Reference} + * should generally specify {@link #__UNCONDITIONAL_CALL}. + */ public static final FlowType CALL_TERMINATOR = new FlowType.Builder(__CALL_TERMINATOR, "CALL_TERMINATOR") .setIsCall() .setIsTerminal() .build(); + + /** + * {@link #COMPUTED_CALL_TERMINATOR} corresponds to an unconditional call {@link FlowType} + * followed by a terminal without fall-through (e.g., unconditional return from a function). + * This may be used to describe the flow-type of an instruction or code-block but + * should generally not be used with a {@link Reference}. A corresponding {@link Reference} + * should generally specify {@link #COMPUTED_CALL}. + */ public static final FlowType COMPUTED_CALL_TERMINATOR = new FlowType.Builder(__COMPUTED_CALL_TERMINATOR, "COMPUTED_CALL_TERMINATOR") .setIsCall() .setIsTerminal() .setIsComputed() .build(); + + /** + * {@link #CONDITIONAL_CALL_TERMINATOR} corresponds to a conditional call {@link FlowType} + * followed by a terminal without fall-through (e.g., unconditional return from a function). + * This may be used to describe the flow-type of an instruction or code-block but + * should generally not be used with a {@link Reference}. A corresponding {@link Reference} + * should generally specify {@link #CONDITIONAL_CALL}. + */ public static final FlowType CONDITIONAL_CALL_TERMINATOR = new FlowType.Builder(__CONDITIONAL_CALL_TERMINATOR, "CONDITIONAL_CALL_TERMINATOR") .setIsCall() .setIsTerminal() .setIsConditional() .build(); + + /** + * {@link #CONDITIONAL_COMPUTED_CALL} corresponds to a conditional computed call {@link FlowType} + * with fall-through. This may be used to describe the flow-type of an instruction or + * code-block, or call {@link Reference} to another instruction or code-block. + */ public static final FlowType CONDITIONAL_COMPUTED_CALL = new FlowType.Builder( __CONDITIONAL_COMPUTED_CALL, "CONDITIONAL_COMPUTED_CALL") .setHasFall() @@ -157,6 +263,12 @@ public abstract class RefType { .setIsComputed() .setIsConditional() .build(); + + /** + * {@link #CONDITIONAL_COMPUTED_JUMP} corresponds to a conditional computed jump/branch + * {@link FlowType}. This may be used to describe the flow-type of an instruction or + * code-block, or {@link Reference} to another instruction or code-block. + */ public static final FlowType CONDITIONAL_COMPUTED_JUMP = new FlowType.Builder(__CONDITIONAL_COMPUTED_JUMP, "CONDITIONAL_COMPUTED_JUMP") .setHasFall() @@ -164,31 +276,89 @@ public abstract class RefType { .setIsComputed() .setIsConditional() .build(); + + /** + * {@link #JUMP_TERMINATOR} corresponds to a conditional jump/branch {@link FlowType} + * followed by a terminal without fall-through (e.g., unconditional return from a function). + * This may be used to describe the flow-type of an instruction or code-block but + * should generally not be used with a {@link Reference}. A corresponding {@link Reference} + * should generally specify {@link #CONDITIONAL_JUMP}. + */ public static final FlowType JUMP_TERMINATOR = new FlowType.Builder(__JUMP_TERMINATOR, "JUMP_TERMINATOR") .setIsJump() .setIsTerminal() .build(); + + /** + * {@link #INDIRECTION} corresponds to a flow {@link Reference} placed on a pointer data location + * that is utilized indirectly by a computed jump/branch or call instruction. + */ public static final FlowType INDIRECTION = new FlowType.Builder(__INDIRECTION, "INDIRECTION") .build(); + + /** + * {@link #__CALL_OVERRIDE_UNCONDITIONAL} is used with a memory {@link Reference} to + * override the destination of an instruction {@link PcodeOp#CALL} or {@link PcodeOp#CALLIND} + * pcode operation. {@link PcodeOp#CALLIND} operations are changed to {@link PcodeOp#CALL} + * operations. The new call target is the "to" address of the {@link Reference}. The override + * only takes effect when the {@link Reference} is primary, and only when there is exactly + * one {@link #__CALL_OVERRIDE_UNCONDITIONAL} {@link Reference} at the "from" address of + * the reference. + */ public static final FlowType CALL_OVERRIDE_UNCONDITIONAL = new FlowType.Builder(__CALL_OVERRIDE_UNCONDITIONAL, "CALL_OVERRIDE_UNCONDITIONAL") .setHasFall() .setIsCall() .setIsOverride() .build(); + + /** + * {@link #JUMP_OVERRIDE_UNCONDITIONAL} is used with a memory {@link Reference} to + * override the destination of an instruction {@link PcodeOp#BRANCH} or {@link PcodeOp#CBRANCH} + * pcode operation. {@link PcodeOp#CBRANCH} operations are changed to {@link PcodeOp#BRANCH} + * operations. The new jump target is the "to" address of the {@link Reference}. The override + * only takes effect when the {@link Reference} is primary, and only when there is exactly + * one {@link #JUMP_OVERRIDE_UNCONDITIONAL} reference at the "from" address of + * the reference. + */ public static final FlowType JUMP_OVERRIDE_UNCONDITIONAL = new FlowType.Builder(__JUMP_OVERRIDE_UNCONDITIONAL, "JUMP_OVERRIDE_UNCONDITIONAL") .setIsJump() .setIsOverride() .build(); + + /** + * {@link #CALLOTHER_OVERRIDE_CALL} is used to change a {@link PcodeOp#CALLOTHER} pcode operation + * to a {@link PcodeOp#CALL} operation. The new call target is the "to" address of the + * {@link Reference}. Any inputs to the original {@link PcodeOp#CALLOTHER} are discarded; + * the new {@link PcodeOp#CALL} may have inputs assigned to it during decompilation. The + * override only takes effect when the {@link Reference} is primary, and only when there is + * exactly one {@link #CALLOTHER_OVERRIDE_CALL} reference at the "from" address of the + * reference. Only the first {@link PcodeOp#CALLOTHER} operation at the "from" address of the + * reference is changed. Applying this override to instances of a {@link PcodeOp#CALLOTHER} + * that have an output is not recommended and can adversely affect decompilation + * (e.g., the decompiler may throw an exception). Note that this reference override takes + * precedence over {@link #CALLOTHER_OVERRIDE_JUMP} references. + */ public static final FlowType CALLOTHER_OVERRIDE_CALL = new FlowType.Builder(__CALLOTHER_OVERRIDE_CALL, "CALLOTHER_OVERRIDE_CALL") .setHasFall() .setIsCall() .setIsOverride() .build(); + + /** + * {@link #CALLOTHER_OVERRIDE_CALL} is used to change a {@link PcodeOp#CALLOTHER} pcode + * operation to a {@link PcodeOp#BRANCH} operation. The new jump target is the "to" address + * of the {@link Reference}. The override only takes effect when the {@link Reference} is + * primary, and only when there is exactly one {@link #CALLOTHER_OVERRIDE_CALL} reference at + * the "from" address of the reference. Only the first {@link PcodeOp#CALLOTHER} operation + * at the "from" address of the reference is changed. Applying this override to an instance + * of a {@link PcodeOp#CALLOTHER} with output is not recommended + * (see {@link #CALLOTHER_OVERRIDE_CALL}). + */ public static final FlowType CALLOTHER_OVERRIDE_JUMP = new FlowType.Builder(__CALLOTHER_OVERRIDE_JUMP, "CALLOTHER_OVERRIDE_JUMP") .setIsJump() @@ -196,61 +366,80 @@ public abstract class RefType { .build(); /** - * Reference type is unknown. + * {@link #THUNK} type identifies the relationship between a thunk-function and its + * corresponding thunked-function which do not rely on a stored {@link Reference}. */ - public static final RefType THUNK = new DataRefType(__DYNAMICDATA, "THUNK", 0); /** - * Reference type assigned when data access is unknown. + * {@link #DATA} type identifies a generic reference from either an instruction, + * when the read/write data access is unknown, or from pointer data when it refers to + * data or it's unknown if it refers to code. A pointer that is known to refer to code + * should generally have a {@link #INDIRECTION} type if used for by a computed + * jump/branch or call. */ public static final RefType DATA = new DataRefType(__UNKNOWNDATA, "DATA", 0); /** - * Reference type assigned when data (constant or pointer) is passed to a function + * {@link #PARAM} type is used to identify data (constant or pointer) that is passed + * to a function. */ public static final RefType PARAM = new DataRefType(__UNKNOWNPARAM, "PARAM", 0); + /** + * {@link #DATA_IND} corresponds to a data {@link Reference} placed on a pointer data location + * that is utilized indirectly to access a data location. + * @deprecated use of this type is discouraged and may be eliminated in a future release. + * The type {@link #DATA} should generally be used in place of this type. + */ public static final RefType DATA_IND = new DataRefType(__UNKNOWNDATA_IND, "DATA_IND", DataRefType.INDX); /** - * Reference type assigned when data is being read. + * {@link #READ} type identifies an instruction reference to a data location that is directly + * read. */ public static final RefType READ = new DataRefType(__READ, "READ", DataRefType.READX); /** - * Reference type assigned when data is being written. + * {@link #WRITE} type identifies an instruction reference to a data location that is directly + * written. */ public static final RefType WRITE = new DataRefType(__WRITE, "WRITE", DataRefType.WRITEX); /** - * Reference type assigned when data is read and written. + * {@link #READ_WRITE} type identifies an instruction reference to a data location that is + * both directly read and written. */ public static final RefType READ_WRITE = new DataRefType(__READ_WRITE, "READ_WRITE", DataRefType.READX | DataRefType.WRITEX); /** - * Reference type assigned when data is being read. + * {@link #READ_IND} type identifies an instruction reference to a data location that is + * indirectly read using a stored pointer or computed value. */ public static final RefType READ_IND = new DataRefType(__READ_IND, "READ_IND", DataRefType.READX | DataRefType.INDX); /** - * Reference type assigned when data is being written. + * {@link #WRITE_IND} type identifies an instruction reference to a data location that is + * indirectly written using a stored pointer or computed value. */ public static final RefType WRITE_IND = new DataRefType(__WRITE_IND, "WRITE_IND", DataRefType.WRITEX | DataRefType.INDX); /** - * Reference type assigned when data is read and written. + * {@link #READ_WRITE_IND} type identifies an instruction reference to a data location that is + * both indirectly read and written using a stored pointer or computed value. */ public static final RefType READ_WRITE_IND = new DataRefType(__READ_WRITE_IND, "READ_WRITE_IND", DataRefType.READX | DataRefType.WRITEX | DataRefType.INDX); /** - * Reference type used internally to identify external entry points. - * The use of this RefType for references to external library data or functions + * {@link #EXTERNAL_REF} type is used internally to identify external entry point locations + * using a from address of {@link Address#NO_ADDRESS}. + *

    + * NOTE: The use of this type for references to external library data or functions * is deprecated and should not be used for that purpose. */ public static final RefType EXTERNAL_REF = new DataRefType(__EXTERNAL_REF, "EXTERNAL", 0); @@ -266,6 +455,9 @@ public abstract class RefType { if (this == RefType.THUNK) { return "Thunk"; } + if (this == RefType.FALL_THROUGH) { + return "FallThrough"; + } if (isRead() && isWrite()) { return "RW"; @@ -301,6 +493,37 @@ public abstract class RefType { return type; } + /** + * Returns name of ref-type + * @return the name + */ + public String getName() { + return name; + } + + @Override + public boolean equals(Object obj) { + if (obj == null || !getClass().equals(obj.getClass())) { + return false; + } + RefType other = (RefType) obj; + return type == other.type; + } + + @Override + public int hashCode() { + return type; + } + + @Override + public String toString() { + return name; + } + + // + // Data related methods + // + /** * Returns true if the reference is to data * @return true if the reference is to data @@ -325,6 +548,18 @@ public abstract class RefType { return false; } + // + // Flow related methods + // + + /** + * Returns true if the reference is an instruction flow reference + * @return true if the reference is an instruction flow reference + */ + public boolean isFlow() { + return false; + } + /** * Returns true if the reference is indirect * @return true if the reference is indirect @@ -336,14 +571,6 @@ public abstract class RefType { return false; } - /** - * Returns true if the reference is an instruction flow reference - * @return true if the reference is an instruction flow reference - */ - public boolean isFlow() { - return false; - } - /** * Return true if this flow type is one that does not cause a break in control flow * @return if this flow type is one that does not cause a break in control flow @@ -416,30 +643,4 @@ public abstract class RefType { return false; } - /** - * Returns name of ref-type - * @return the name - */ - public String getName() { - return name; - } - - @Override - public boolean equals(Object obj) { - if (obj == null || !getClass().equals(obj.getClass())) { - return false; - } - RefType other = (RefType) obj; - return type == other.type; - } - - @Override - public int hashCode() { - return type; - } - - @Override - public String toString() { - return name; - } } diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/symbol/ReferenceManager.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/symbol/ReferenceManager.java index bc2643a7c4..caa0a1293d 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/symbol/ReferenceManager.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/symbol/ReferenceManager.java @@ -17,6 +17,7 @@ package ghidra.program.model.symbol; import ghidra.program.model.address.*; import ghidra.program.model.lang.Register; +import ghidra.program.model.listing.Library; import ghidra.program.model.listing.Variable; import ghidra.program.model.mem.MemoryBlock; import ghidra.util.exception.DuplicateNameException; @@ -69,7 +70,8 @@ public interface ReferenceManager { /** * Adds a memory reference. The first memory reference placed on * an operand will be made primary by default. All non-memory references - * will be removed from the specified operand. + * will be removed from the specified operand. Certain reference {@link RefType types} + * may not be specified (e.g., {@link RefType#FALL_THROUGH}). * @param fromAddr address of the codeunit where the reference occurs * @param toAddr address of the location being referenced. * Memory, stack, and register addresses are all permitted. @@ -78,6 +80,7 @@ public interface ReferenceManager { * @param opIndex the operand index * display of the operand making this reference * @return new memory reference + * @throws IllegalArgumentException if unsupported {@link RefType type} is specified */ public Reference addMemoryReference(Address fromAddr, Address toAddr, RefType type, SourceType source, int opIndex); @@ -109,10 +112,14 @@ public interface ReferenceManager { * shiftValue parameter. The first memory reference placed on * an operand will be made primary by default. All non-memory references * will be removed from the specified operand. - * @param fromAddr address for the "from" - * @param toAddr computed as the value of the operand at opIndex shifted - * by the number of bits specified by shiftValue - * @param shiftValue shifted value + * + * @param fromAddr source/from memory address + * @param toAddr destination/to memory address computed as some + * {@link ShiftedReference#getValue() base offset value} shifted left + * by the number of bits specified by shiftValue. The least-significant bits of toAddr + * offset should be 0's based upon the specified shiftValue since this value is shifted + * right to calculate the base offset value. + * @param shiftValue number of bits to shift * @param type reference type - how the location is being referenced * @param source the source of this reference * @param opIndex the operand index @@ -122,19 +129,27 @@ public interface ReferenceManager { RefType type, SourceType source, int opIndex); /** - * Adds an external reference. If a reference already - * exists for the fromAddr and opIndex, the existing reference is replaced - * with the new reference. - * @param fromAddr from address (source of the reference) + * Adds an external reference to an external symbol. If a reference already + * exists at {@code fromAddr} and {@code opIndex} the existing reference is replaced + * with a new reference. If the external symbol cannot be found, a new {@link Library} + * and/or {@link ExternalLocation} symbol will be created which corresponds to the specified + * library/file named {@code libraryName} + * and the location within that file identified by {@code extLabel} and/or its memory address + * {@code extAddr}. Either or both {@code extLabel} or {@code extAddr} must be specified. + * + * @param fromAddr from memory address (source of the reference) * @param libraryName name of external program * @param extLabel label within the external program, may be null if extAddr is not null - * @param extAddr address within the external program, may be null + * @param extAddr memory address within the external program, may be null * @param source the source of this reference * @param opIndex operand index * @param type reference type - how the location is being referenced * @return new external space reference - * @throws InvalidInputException - * @throws DuplicateNameException + * @throws InvalidInputException if {@code libraryName} is invalid or null, or an invalid + * {@code extlabel} is specified. Names with spaces or the empty string are not permitted. + * Neither {@code extLabel} nor {@code extAddr} was specified properly. + * @throws DuplicateNameException if another non-Library namespace has the same name + * @throws IllegalArgumentException if an invalid {@code extAddr} was specified. */ public Reference addExternalReference(Address fromAddr, String libraryName, String extLabel, Address extAddr, SourceType source, int opIndex, RefType type) @@ -144,7 +159,8 @@ public interface ReferenceManager { * Adds an external reference. If a reference already * exists for the fromAddr and opIndex, the existing reference is replaced * with the new reference. - * @param fromAddr from address (source of the reference) + * + * @param fromAddr from memory address (source of the reference) * @param extNamespace external namespace containing the named external label. * @param extLabel label within the external program, may be null if extAddr is not null * @param extAddr address within the external program, may be null @@ -152,8 +168,11 @@ public interface ReferenceManager { * @param opIndex operand index * @param type reference type - how the location is being referenced * @return new external space reference - * @throws InvalidInputException - * @throws DuplicateNameException + * @throws InvalidInputException if an invalid {@code extlabel} is specified. + * Names with spaces or the empty string are not permitted. + * Neither {@code extLabel} nor {@code extAddr} was specified properly. + * @throws DuplicateNameException if another non-Library namespace has the same name + * @throws IllegalArgumentException if an invalid {@code extAddr} was specified. */ public Reference addExternalReference(Address fromAddr, Namespace extNamespace, String extLabel, Address extAddr, SourceType source, int opIndex, RefType type) @@ -163,7 +182,8 @@ public interface ReferenceManager { * Adds an external reference. If a reference already * exists for the fromAddr and opIndex, the existing reference is replaced * with the new reference. - * @param fromAddr from address (source of the reference) + * + * @param fromAddr from memory address (source of the reference) * @param opIndex operand index * @param location external location * @param source the source of this reference diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/util/ChangeManager.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/util/ChangeManager.java index 66ba56455e..4f99369021 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/util/ChangeManager.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/util/ChangeManager.java @@ -661,17 +661,22 @@ public interface ChangeManager { */ public final static int DOCR_FLOWOVERRIDE_CHANGED = 163; + /** + * An instruction length override was changed for an instruction. + */ + public final static int DOCR_LENGTH_OVERRIDE_CHANGED = 164; + //////////////////////////////////////////////////////////////////////////// /** * A custom format for a data type was added. */ - public final static int DOCR_CUSTOM_FORMAT_ADDED = 164; + public final static int DOCR_CUSTOM_FORMAT_ADDED = 165; /** * A custom format for a data type was removed. */ - public final static int DOCR_CUSTOM_FORMAT_REMOVED = 165; + public final static int DOCR_CUSTOM_FORMAT_REMOVED = 166; //////////////////////////////////////////////////////////////////////////// // @@ -682,17 +687,17 @@ public interface ChangeManager { /** * An AddressSetPropertyMap was added. */ - public final static int DOCR_ADDRESS_SET_PROPERTY_MAP_ADDED = 166; + public final static int DOCR_ADDRESS_SET_PROPERTY_MAP_ADDED = 167; /** * An AddressSetPropertyMap was removed. */ - public final static int DOCR_ADDRESS_SET_PROPERTY_MAP_REMOVED = 167; + public final static int DOCR_ADDRESS_SET_PROPERTY_MAP_REMOVED = 168; /** * An AddressSetPropertyMap was changed. */ - public final static int DOCR_ADDRESS_SET_PROPERTY_MAP_CHANGED = 168; + public final static int DOCR_ADDRESS_SET_PROPERTY_MAP_CHANGED = 169; /** * An IntAddressSetPropertyMap was added. diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/util/InstructionUtils.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/util/InstructionUtils.java index 9a7aa409ec..aede0f409d 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/util/InstructionUtils.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/util/InstructionUtils.java @@ -72,10 +72,16 @@ public class InstructionUtils { textBuf.append( "\nConstructor Line #'s:\n" + getString(debug.getConstructorLineNumbers(), true)) .append('\n'); - textBuf.append("\nByte Length : " + instruction.getLength()); + int len = instruction.getLength(); + textBuf.append("\nByte Length : " + len); + if (instruction.isLengthOverridden()) { + textBuf.append("\n >>> reflects length override, actual length is " + + instruction.getParsedLength()); + } try { + byte[] bytes = instruction.getParsedBytes(); textBuf.append( - "\nInstr Bytes : " + SleighDebugLogger.getFormattedBytes(instruction.getBytes())); + "\nInstr Bytes : " + SleighDebugLogger.getFormattedBytes(bytes)); textBuf.append("\nMask : " + debug.getFormattedInstructionMask(-1)); textBuf.append("\nMasked Bytes: " + debug.getFormattedMaskedValue(-1)).append('\n'); } diff --git a/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/app/plugin/core/clipboard/ClipboardPluginTest.java b/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/app/plugin/core/clipboard/ClipboardPluginTest.java index 373817f152..3c94ccee40 100644 --- a/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/app/plugin/core/clipboard/ClipboardPluginTest.java +++ b/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/app/plugin/core/clipboard/ClipboardPluginTest.java @@ -162,7 +162,7 @@ public class ClipboardPluginTest extends AbstractGhidraHeadedIntegrationTest { new ClassicSampleX86ProgramBuilder("notepad", false, this); // need a default label at 01002cf0, so make up a reference - builder.createMemoryReference("01002ce5", "01002cf0", RefType.FALL_THROUGH, + builder.createMemoryReference("01002ce5", "01002cf0", RefType.DATA, SourceType.ANALYSIS); return builder.getProgram();