From 4aa54dd1f913fcc57da9dbb0ee01f6421e52ac10 Mon Sep 17 00:00:00 2001 From: Dan <46821332+nsadeveloper789@users.noreply.github.com> Date: Wed, 29 Mar 2023 16:36:17 -0400 Subject: [PATCH] GP-1529: Interrupt emulator when decoding uninitialized memory --- ...stractRWTargetPcodeExecutorStatePiece.java | 24 +++---- ...RWTargetMemoryPcodeExecutorStatePiece.java | 24 +++---- ...argetRegistersPcodeExecutorStatePiece.java | 14 ++--- .../core/debug/stack/UnwindAnalysis.java | 4 +- .../DebuggerEmulationServiceTest.java | 49 +++++++++++++-- .../BytesTracePcodeExecutorStatePiece.java | 37 +++++++---- ...chedWriteBytesPcodeExecutorStatePiece.java | 10 +-- .../data/AbstractPcodeTraceDataAccess.java | 17 +++++ .../data/DefaultPcodeTraceThreadAccess.java | 5 ++ .../exec/trace/data/PcodeTraceDataAccess.java | 9 +++ .../exec/trace/TraceSleighUtilsTest.java | 2 +- .../trace/model/time/schedule/TestThread.java | 2 +- .../AbstractEmuUnixSyscallUseropLibrary.java | 2 +- .../Emulation/src/main/java/generic/Span.java | 9 +-- .../ghidra/app/emulator/AdaptedEmulator.java | 7 ++- .../ghidra/pcode/emu/DefaultPcodeThread.java | 3 +- .../ghidra/pcode/emu/ModifiedPcodeThread.java | 2 +- .../pcode/emu/SleighInstructionDecoder.java | 3 +- .../AbstractBytesPcodeExecutorStatePiece.java | 15 +++-- .../exec/BytesPcodeExecutorStateSpace.java | 62 ++++++++++++++++--- .../exec/DecodePcodeExecutionException.java | 31 ++++++++++ .../ghidra/pcode/exec/PcodeArithmetic.java | 27 +++++--- .../pcode/exec/PcodeExecutorStatePiece.java | 8 ++- .../exec/AnnotatedPcodeUseropLibraryTest.java | 2 +- .../program/disassemble/Disassembler.java | 6 +- 25 files changed, 274 insertions(+), 100 deletions(-) create mode 100644 Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/DecodePcodeExecutionException.java diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/AbstractRWTargetPcodeExecutorStatePiece.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/AbstractRWTargetPcodeExecutorStatePiece.java index 4aedcbd732..463d0c3eaf 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/AbstractRWTargetPcodeExecutorStatePiece.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/AbstractRWTargetPcodeExecutorStatePiece.java @@ -18,6 +18,7 @@ package ghidra.app.plugin.core.debug.service.emulation; import java.util.Map; import java.util.concurrent.*; +import generic.ULongSpan.ULongSpanSet; import ghidra.app.plugin.core.debug.service.emulation.data.PcodeDebuggerDataAccess; import ghidra.generic.util.datastruct.SemisparseByteArray; import ghidra.pcode.exec.AccessPcodeExecutionException; @@ -52,28 +53,17 @@ public abstract class AbstractRWTargetPcodeExecutorStatePiece super(language, space, backing, bytes, written); } - protected abstract void fillUninitialized(AddressSet uninitialized); + protected abstract ULongSpanSet readUninitializedFromTarget(ULongSpanSet uninitialized); @Override - public byte[] read(long offset, int size, Reason reason) { - if (backing != null) { - AddressSet uninitialized = - addrSet(bytes.getUninitialized(offset, offset + size - 1)); - if (uninitialized.isEmpty()) { - return super.read(offset, size, reason); - } - - fillUninitialized(uninitialized); - - AddressSetView unknown = backing.intersectUnknown( - addrSet(bytes.getUninitialized(offset, offset + size - 1))); - if (!unknown.isEmpty() && reason == Reason.EXECUTE) { - warnUnknown(unknown); - } + protected ULongSpanSet readUninitializedFromBacking(ULongSpanSet uninitialized) { + uninitialized = readUninitializedFromTarget(uninitialized); + if (uninitialized.isEmpty()) { + return uninitialized; } + return super.readUninitializedFromBacking(uninitialized); // TODO: What to flush when bytes in the trace change? - return super.read(offset, size, reason); } protected T waitTimeout(CompletableFuture future) { diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/RWTargetMemoryPcodeExecutorStatePiece.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/RWTargetMemoryPcodeExecutorStatePiece.java index e80f37baad..b7c20a682b 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/RWTargetMemoryPcodeExecutorStatePiece.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/RWTargetMemoryPcodeExecutorStatePiece.java @@ -18,6 +18,8 @@ package ghidra.app.plugin.core.debug.service.emulation; import java.util.Map; import java.util.concurrent.CompletableFuture; +import generic.ULongSpan; +import generic.ULongSpan.ULongSpanSet; import ghidra.app.plugin.core.debug.service.emulation.data.PcodeDebuggerDataAccess; import ghidra.app.plugin.core.debug.service.emulation.data.PcodeDebuggerMemoryAccess; import ghidra.generic.util.datastruct.SemisparseByteArray; @@ -81,27 +83,27 @@ public class RWTargetMemoryPcodeExecutorStatePiece } @Override - protected void fillUninitialized(AddressSet uninitialized) { + protected ULongSpanSet readUninitializedFromTarget(ULongSpanSet uninitialized) { if (space.isUniqueSpace()) { - return; + return uninitialized; } AddressSetView unknown; - unknown = backing.intersectUnknown(uninitialized); + AddressSet addrsUninit = addrSet(uninitialized); + unknown = backing.intersectUnknown(addrsUninit); if (unknown.isEmpty()) { - return; + return uninitialized; } - if (waitTimeout(backing.readFromTargetMemory(unknown))) { - unknown = backing.intersectUnknown(uninitialized); + if (backing.isLive() && waitTimeout(backing.readFromTargetMemory(unknown))) { + unknown = backing.intersectUnknown(addrsUninit); if (unknown.isEmpty()) { - return; + return uninitialized; } } if (backing.readFromStaticImages(bytes, unknown)) { - unknown = backing.intersectUnknown(uninitialized); - if (unknown.isEmpty()) { - return; - } + ULongSpan bound = uninitialized.bound(); + return bytes.getUninitialized(bound.min(), bound.max()); } + return uninitialized; } @Override diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/RWTargetRegistersPcodeExecutorStatePiece.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/RWTargetRegistersPcodeExecutorStatePiece.java index f09212652a..5832ec8882 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/RWTargetRegistersPcodeExecutorStatePiece.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/RWTargetRegistersPcodeExecutorStatePiece.java @@ -18,6 +18,7 @@ package ghidra.app.plugin.core.debug.service.emulation; import java.util.Map; import java.util.concurrent.CompletableFuture; +import generic.ULongSpan.ULongSpanSet; import ghidra.app.plugin.core.debug.service.emulation.data.PcodeDebuggerDataAccess; import ghidra.app.plugin.core.debug.service.emulation.data.PcodeDebuggerRegistersAccess; import ghidra.generic.util.datastruct.SemisparseByteArray; @@ -78,15 +79,14 @@ public class RWTargetRegistersPcodeExecutorStatePiece } @Override - protected void fillUninitialized(AddressSet uninitialized) { - if (space.isUniqueSpace()) { - return; + protected ULongSpanSet readUninitializedFromTarget(ULongSpanSet uninitialized) { + if (space.isUniqueSpace() || !backing.isLive()) { + return uninitialized; } - if (!backing.isLive()) { - return; - } - AddressSetView unknown = backing.intersectUnknown(uninitialized); + AddressSet addrsUninit = addrSet(uninitialized); + AddressSetView unknown = backing.intersectUnknown(addrsUninit); waitTimeout(backing.readFromTargetRegisters(unknown)); + return uninitialized; } @Override diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/stack/UnwindAnalysis.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/stack/UnwindAnalysis.java index b9763d2e2d..ee3a056d77 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/stack/UnwindAnalysis.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/stack/UnwindAnalysis.java @@ -353,7 +353,7 @@ public class UnwindAnalysis { public SymPcodeExecutorState executeToPc(Deque to) throws CancelledException { SymPcodeExecutorState state = new SymPcodeExecutorState(program); SymPcodeExecutor exec = - SymPcodeExecutor.forProgram(program, state, Reason.EXECUTE, warnings, monitor); + SymPcodeExecutor.forProgram(program, state, Reason.EXECUTE_READ, warnings, monitor); executePathTo(exec, to); executeBlockTo(exec, pcBlock.block, pc); return state; @@ -375,7 +375,7 @@ public class UnwindAnalysis { public SymPcodeExecutorState executeFromPc(SymPcodeExecutorState state, Deque from) throws CancelledException { SymPcodeExecutor exec = - SymPcodeExecutor.forProgram(program, state, Reason.EXECUTE, warnings, monitor); + SymPcodeExecutor.forProgram(program, state, Reason.EXECUTE_READ, warnings, monitor); executeBlockFrom(exec, pcBlock.block, pc); executePathFrom(exec, from); return state; diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/emulation/DebuggerEmulationServiceTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/emulation/DebuggerEmulationServiceTest.java index 55256e1e8c..a981816957 100644 --- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/emulation/DebuggerEmulationServiceTest.java +++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/emulation/DebuggerEmulationServiceTest.java @@ -39,6 +39,7 @@ import ghidra.app.plugin.core.debug.service.platform.DebuggerPlatformServicePlug import ghidra.app.services.DebuggerEmulationService.EmulationResult; import ghidra.app.services.DebuggerStaticMappingService; import ghidra.app.services.DebuggerTraceManagerService.ActivationCause; +import ghidra.pcode.exec.DecodePcodeExecutionException; import ghidra.pcode.exec.InterruptPcodeExecutionException; import ghidra.pcode.utils.Utils; import ghidra.program.model.address.Address; @@ -343,13 +344,53 @@ public class DebuggerEmulationServiceTest extends AbstractGhidraHeadedDebuggerGU .toString(16)); } + @Test + public void testInterruptOnDecodeUninitialized() throws Exception { + createProgram(); + intoProject(program); + Assembler asm = Assemblers.getAssembler(program); + Memory memory = program.getMemory(); + Address addrText = addr(program, 0x00400000); + Register regPC = program.getRegister("pc"); + + try (Transaction tx = program.openTransaction("Initialize")) { + MemoryBlock blockText = memory.createInitializedBlock(".text", addrText, 0x1000, + (byte) 0, TaskMonitor.DUMMY, false); + blockText.setExecute(true); + asm.assemble(addrText, + "br 0x003ffffe"); + } + + programManager.openProgram(program); + waitForSwing(); + codeBrowser.goTo(new ProgramLocation(program, addrText)); + waitForSwing(); + + performEnabledAction(codeBrowser.getProvider(), emulationPlugin.actionEmulateProgram, true); + + Trace trace = traceManager.getCurrentTrace(); + assertNotNull(trace); + + TraceThread thread = Unique.assertOne(trace.getThreadManager().getAllThreads()); + TraceMemorySpace regs = trace.getMemoryManager().getMemoryRegisterSpace(thread, false); + + EmulationResult result = emulationPlugin.run(trace.getPlatformManager().getHostPlatform(), + TraceSchedule.snap(0), TaskMonitor.DUMMY, Scheduler.oneThread(thread)); + + assertEquals(TraceSchedule.parse("0:t0-1"), result.schedule()); + assertTrue(result.error() instanceof DecodePcodeExecutionException); + + long scratch = result.snapshot(); + assertEquals(new BigInteger("003ffffe", 16), regs.getViewValue(scratch, regPC).getUnsignedValue()); + } + @Test public void testExecutionBreakpoint() throws Exception { createProgram(); intoProject(program); Assembler asm = Assemblers.getAssembler(program); Memory memory = program.getMemory(); - Address addrText = addr(program, 0x000400000); + Address addrText = addr(program, 0x00400000); Register regPC = program.getRegister("pc"); Register regR0 = program.getRegister("r0"); Register regR1 = program.getRegister("r1"); @@ -411,7 +452,7 @@ public class DebuggerEmulationServiceTest extends AbstractGhidraHeadedDebuggerGU intoProject(program); Assembler asm = Assemblers.getAssembler(program); Memory memory = program.getMemory(); - Address addrText = addr(program, 0x000400000); + Address addrText = addr(program, 0x00400000); Address addrI1; Address addrI2; try (Transaction tx = program.openTransaction("Initialize")) { @@ -470,7 +511,7 @@ public class DebuggerEmulationServiceTest extends AbstractGhidraHeadedDebuggerGU intoProject(program); Assembler asm = Assemblers.getAssembler(program); Memory memory = program.getMemory(); - Address addrText = addr(program, 0x000400000); + Address addrText = addr(program, 0x00400000); Register regPC = program.getRegister("pc"); Register regR0 = program.getRegister("r0"); Register regR1 = program.getRegister("r1"); @@ -537,7 +578,7 @@ public class DebuggerEmulationServiceTest extends AbstractGhidraHeadedDebuggerGU intoProject(program); Assembler asm = Assemblers.getAssembler(program); Memory memory = program.getMemory(); - Address addrText = addr(program, 0x000400000); + Address addrText = addr(program, 0x00400000); Register regPC = program.getRegister("pc"); Register regR0 = program.getRegister("r0"); Register regR1 = program.getRegister("r1"); diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/BytesTracePcodeExecutorStatePiece.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/BytesTracePcodeExecutorStatePiece.java index 0bd21e49ab..1d1710bd72 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/BytesTracePcodeExecutorStatePiece.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/BytesTracePcodeExecutorStatePiece.java @@ -21,7 +21,8 @@ import java.util.Map; import generic.ULongSpan; import generic.ULongSpan.ULongSpanSet; import ghidra.generic.util.datastruct.SemisparseByteArray; -import ghidra.pcode.exec.*; +import ghidra.pcode.exec.AbstractBytesPcodeExecutorStatePiece; +import ghidra.pcode.exec.BytesPcodeExecutorStateSpace; import ghidra.pcode.exec.trace.BytesTracePcodeExecutorStatePiece.CachedSpace; import ghidra.pcode.exec.trace.data.PcodeTraceDataAccess; import ghidra.program.model.address.*; @@ -76,19 +77,29 @@ public class BytesTracePcodeExecutorStatePiece } @Override - protected void readUninitializedFromBacking(ULongSpanSet uninitialized) { - if (!uninitialized.isEmpty()) { - // TODO: Warn or bail when reading UNKNOWN bytes - // NOTE: Read without regard to gaps - // NOTE: Cannot write those gaps, though!!! - ULongSpan bound = uninitialized.bound(); - ByteBuffer buf = ByteBuffer.allocate((int) bound.length()); - backing.getBytes(space.getAddress(bound.min()), buf); - for (ULongSpan span : uninitialized.spans()) { - bytes.putData(span.min(), buf.array(), (int) (span.min() - bound.min()), - (int) span.length()); - } + protected ULongSpanSet readUninitializedFromBacking(ULongSpanSet uninitialized) { + if (uninitialized.isEmpty()) { + return uninitialized; } + // TODO: Warn or bail when reading UNKNOWN bytes + // NOTE: Read without regard to gaps + // NOTE: Cannot write those gaps, though!!! + AddressSetView knownButUninit = backing.intersectViewKnown(addrSet(uninitialized)); + if (knownButUninit.isEmpty()) { + return uninitialized; + } + AddressRange knownBound = new AddressRangeImpl( + knownButUninit.getMinAddress(), + knownButUninit.getMaxAddress()); + ByteBuffer buf = ByteBuffer.allocate((int) knownBound.getLength()); + backing.getBytes(knownBound.getMinAddress(), buf); + for (AddressRange range : knownButUninit) { + bytes.putData(range.getMinAddress().getOffset(), buf.array(), + (int) (range.getMinAddress().subtract(knownBound.getMinAddress())), + (int) range.getLength()); + } + ULongSpan uninitBound = uninitialized.bound(); + return bytes.getUninitialized(uninitBound.min(), uninitBound.max()); } protected void warnUnknown(AddressSetView unknown) { diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/RequireIsKnownTraceCachedWriteBytesPcodeExecutorStatePiece.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/RequireIsKnownTraceCachedWriteBytesPcodeExecutorStatePiece.java index ed32bfc313..e487911a18 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/RequireIsKnownTraceCachedWriteBytesPcodeExecutorStatePiece.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/RequireIsKnownTraceCachedWriteBytesPcodeExecutorStatePiece.java @@ -30,6 +30,11 @@ import ghidra.trace.model.memory.TraceMemorySpace; public class RequireIsKnownTraceCachedWriteBytesPcodeExecutorStatePiece extends AbstractCheckedTraceCachedWriteBytesPcodeExecutorStatePiece { + /** + * Construct a piece + * + * @param data the trace-data access shim + */ public RequireIsKnownTraceCachedWriteBytesPcodeExecutorStatePiece(PcodeTraceDataAccess data) { super(data); } @@ -45,11 +50,6 @@ public class RequireIsKnownTraceCachedWriteBytesPcodeExecutorStatePiece spaceMap.fork()); } - /** - * Construct a piece - * - * @param data the trace-data access shim - */ protected AddressSetView getKnown(PcodeTraceDataAccess backing) { return backing.getKnownNow(); } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/data/AbstractPcodeTraceDataAccess.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/data/AbstractPcodeTraceDataAccess.java index a7332e8ae9..ff0c8a9452 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/data/AbstractPcodeTraceDataAccess.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/data/AbstractPcodeTraceDataAccess.java @@ -149,6 +149,23 @@ public abstract class AbstractPcodeTraceDataAccess implements InternalPcodeTrace return doGetKnown(Lifespan.since(snap)); } + @Override + public AddressSetView intersectViewKnown(AddressSetView guestView) { + TraceMemoryOperations ops = getMemoryOps(false); + if (ops == null) { + return new AddressSet(); + } + + AddressSetView hostView = toOverlay(platform.mapGuestToHost(guestView)); + AddressSet hostKnown = new AddressSet(); + for (long sn : viewport.getOrderedSnaps()) { + hostKnown.add(ops.getAddressesWithState(sn, hostView, + st -> st != null && st != TraceMemoryState.UNKNOWN)); + } + AddressSetView hostResult = hostView.intersect(hostKnown); + return platform.mapHostToGuest(hostResult); + } + @Override public AddressSetView intersectUnknown(AddressSetView guestView) { TraceMemoryOperations ops = getMemoryOps(false); diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/data/DefaultPcodeTraceThreadAccess.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/data/DefaultPcodeTraceThreadAccess.java index ab04af2030..2456b8027f 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/data/DefaultPcodeTraceThreadAccess.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/data/DefaultPcodeTraceThreadAccess.java @@ -80,6 +80,11 @@ public class DefaultPcodeTraceThreadAccess return memory.getKnownBefore().union(registers.getKnownBefore()); } + @Override + public AddressSetView intersectViewKnown(AddressSetView view) { + return memory.intersectViewKnown(view).union(registers.intersectViewKnown(view)); + } + @Override public AddressSetView intersectUnknown(AddressSetView view) { return memory.intersectUnknown(view).union(registers.intersectUnknown(view)); diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/data/PcodeTraceDataAccess.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/data/PcodeTraceDataAccess.java index 0c9820544e..71f68a4d49 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/data/PcodeTraceDataAccess.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/data/PcodeTraceDataAccess.java @@ -92,6 +92,15 @@ public interface PcodeTraceDataAccess { */ AddressSetView getKnownBefore(); + /** + * Compute the intersection of the given address set and the set of + * {@link TraceMemoryState#KNOWN} or (@link {@link TraceMemoryState#ERROR} memory + * + * @param view the address set + * @return the intersection + */ + AddressSetView intersectViewKnown(AddressSetView view); + /** * Compute the intersection of the given address set and the set of * {@link TraceMemoryState#UNKNOWN} memory diff --git a/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/pcode/exec/trace/TraceSleighUtilsTest.java b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/pcode/exec/trace/TraceSleighUtilsTest.java index e59c14d6b5..9c6b3660ed 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/pcode/exec/trace/TraceSleighUtilsTest.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/pcode/exec/trace/TraceSleighUtilsTest.java @@ -221,7 +221,7 @@ public class TraceSleighUtilsTest extends AbstractGhidraHeadlessIntegrationTest new PcodeExecutor<>(sp.getLanguage(), BytesPcodeArithmetic.forLanguage(b.language), new DirectBytesTracePcodeExecutorState(b.host, 0, thread, 0), - Reason.EXECUTE); + Reason.EXECUTE_READ); sp.execute(executor, PcodeUseropLibrary.nil()); } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/model/time/schedule/TestThread.java b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/model/time/schedule/TestThread.java index 5ed403b1f3..186b1b5bf7 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/model/time/schedule/TestThread.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/model/time/schedule/TestThread.java @@ -62,7 +62,7 @@ class TestThread implements PcodeThread { @Override public PcodeExecutor getExecutor() { return new PcodeExecutor<>(TraceScheduleTest.TOY_BE_64_LANG, machine.getArithmetic(), - getState(), Reason.EXECUTE) { + getState(), Reason.EXECUTE_READ) { public PcodeFrame execute(PcodeProgram program, PcodeUseropLibrary library) { machine.record.add("x:" + name); // TODO: Verify the actual effect diff --git a/Ghidra/Features/SystemEmulation/src/main/java/ghidra/pcode/emu/unix/AbstractEmuUnixSyscallUseropLibrary.java b/Ghidra/Features/SystemEmulation/src/main/java/ghidra/pcode/emu/unix/AbstractEmuUnixSyscallUseropLibrary.java index 11a205130e..08055867ad 100644 --- a/Ghidra/Features/SystemEmulation/src/main/java/ghidra/pcode/emu/unix/AbstractEmuUnixSyscallUseropLibrary.java +++ b/Ghidra/Features/SystemEmulation/src/main/java/ghidra/pcode/emu/unix/AbstractEmuUnixSyscallUseropLibrary.java @@ -287,7 +287,7 @@ public abstract class AbstractEmuUnixSyscallUseropLibrary // TODO: Not ideal to require concrete size. What are the alternatives, though? // TODO: size should actually be long (size_t) int size = (int) arithmetic.toLong(count, Purpose.OTHER); - T buf = state.getVar(space, bufPtr, size, true, Reason.EXECUTE); + T buf = state.getVar(space, bufPtr, size, true, Reason.EXECUTE_READ); // TODO: Write back into state? "write" shouldn't touch the buffer.... return desc.write(buf); } diff --git a/Ghidra/Framework/Emulation/src/main/java/generic/Span.java b/Ghidra/Framework/Emulation/src/main/java/generic/Span.java index 160a989c89..6d896683e6 100644 --- a/Ghidra/Framework/Emulation/src/main/java/generic/Span.java +++ b/Ghidra/Framework/Emulation/src/main/java/generic/Span.java @@ -869,11 +869,12 @@ public interface Span> extends Comparable { @Override public S bound() { - S result = domain.empty(); - for (Entry entry : spanTree.values()) { - result = result.bound(entry.getKey()); + if (spanTree.isEmpty()) { + return domain.empty(); } - return result; + S first = spanTree.firstEntry().getValue().getKey(); + S last = spanTree.lastEntry().getValue().getKey(); + return first.bound(last); } @Override diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/app/emulator/AdaptedEmulator.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/app/emulator/AdaptedEmulator.java index 25cc825f2f..cd41d92d4b 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/app/emulator/AdaptedEmulator.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/app/emulator/AdaptedEmulator.java @@ -165,16 +165,16 @@ public class AdaptedEmulator implements Emulator { } @Override - protected void readUninitializedFromBacking(ULongSpanSet uninitialized) { + protected ULongSpanSet readUninitializedFromBacking(ULongSpanSet uninitialized) { if (uninitialized.isEmpty()) { - return; + return uninitialized; } if (backing.loadImage == null) { if (space.isUniqueSpace()) { throw new AccessPcodeExecutionException( "Attempted to read from uninitialized unique: " + uninitialized); } - return; + return uninitialized; } ULongSpan bound = uninitialized.bound(); byte[] data = new byte[(int) bound.length()]; @@ -184,6 +184,7 @@ public class AdaptedEmulator implements Emulator { bytes.putData(span.min(), data, (int) (span.min() - bound.min()), (int) span.length()); } + return bytes.getUninitialized(bound.min(), bound.max()); } @Override diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/DefaultPcodeThread.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/DefaultPcodeThread.java index f544127f41..4557e2e1e8 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/DefaultPcodeThread.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/DefaultPcodeThread.java @@ -159,7 +159,8 @@ public class DefaultPcodeThread implements PcodeThread { * @param state the composite state assigned to the thread */ public PcodeThreadExecutor(DefaultPcodeThread thread) { - super(thread.language, thread.arithmetic, thread.state, Reason.EXECUTE); + // NB. The executor itself is not decoding. So reads are in fact data reads. + super(thread.language, thread.arithmetic, thread.state, Reason.EXECUTE_READ); this.thread = thread; } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/ModifiedPcodeThread.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/ModifiedPcodeThread.java index 29965f9dfc..ef808c7d87 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/ModifiedPcodeThread.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/ModifiedPcodeThread.java @@ -112,7 +112,7 @@ public class ModifiedPcodeThread extends DefaultPcodeThread { * These two exist as a way to integrate the language-specific injects that are already * written for {@link Emulator}. */ - emulate = new GlueEmulate(language, new AdaptedMemoryState<>(state, Reason.EXECUTE) { + emulate = new GlueEmulate(language, new AdaptedMemoryState<>(state, Reason.EXECUTE_READ) { @Override public void setMemoryBank(MemoryBank bank) { // Ignore diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/SleighInstructionDecoder.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/SleighInstructionDecoder.java index c613dffac2..c494c90121 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/SleighInstructionDecoder.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/SleighInstructionDecoder.java @@ -17,6 +17,7 @@ package ghidra.pcode.emu; import ghidra.app.util.PseudoInstruction; import ghidra.pcode.emulate.InstructionDecodeException; +import ghidra.pcode.exec.DecodePcodeExecutionException; import ghidra.pcode.exec.PcodeArithmetic.Purpose; import ghidra.pcode.exec.PcodeExecutorState; import ghidra.program.disassemble.Disassembler; @@ -87,7 +88,7 @@ public class SleighInstructionDecoder implements InstructionDecoder { block = disassembler.pseudoDisassembleBlock( state.getConcreteBuffer(address, Purpose.DECODE), context, 1); if (block == null || block.isEmpty()) { - throw new InstructionDecodeException(lastMsg, address); + throw new DecodePcodeExecutionException(lastMsg, address); } instruction = (PseudoInstruction) block.getInstructionAt(address); lengthWithDelays = computeLength(); diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/AbstractBytesPcodeExecutorStatePiece.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/AbstractBytesPcodeExecutorStatePiece.java index 275c3eb502..576aff6abd 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/AbstractBytesPcodeExecutorStatePiece.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/AbstractBytesPcodeExecutorStatePiece.java @@ -25,6 +25,7 @@ import ghidra.program.model.lang.Language; import ghidra.program.model.lang.Register; import ghidra.program.model.mem.*; import ghidra.program.model.pcode.PcodeOp; +import ghidra.program.model.pcode.Varnode; import ghidra.util.Msg; /** @@ -41,16 +42,21 @@ public abstract class AbstractBytesPcodeExecutorStatePiece source; + protected final Reason reason; /** * Construct a buffer bound to the given space, at the given address * * @param address the address * @param source the space + * @param reason the reason this buffer reads from the state, as in + * {@link PcodeExecutorStatePiece#getVar(Varnode, Reason)} */ - public StateMemBuffer(Address address, BytesPcodeExecutorStateSpace source) { + public StateMemBuffer(Address address, BytesPcodeExecutorStateSpace source, + Reason reason) { this.address = address; this.source = source; + this.reason = reason; } @Override @@ -70,8 +76,8 @@ public abstract class AbstractBytesPcodeExecutorStatePiece { * Extension point: Read from backing into this space, when acting as a cache. * * @param uninitialized the ranges which need to be read. + * @return the ranges which remain uninitialized */ - protected void readUninitializedFromBacking(ULongSpanSet uninitialized) { + protected ULongSpanSet readUninitializedFromBacking(ULongSpanSet uninitialized) { + return uninitialized; } /** @@ -95,9 +97,15 @@ public class BytesPcodeExecutorStateSpace { } protected AddressRange addrRng(ULongSpan span) { - Address start = space.getAddress(span.min()); - Address end = space.getAddress(span.max()); - return new AddressRangeImpl(start, end); + return new AddressRangeImpl( + space.getAddress(span.min()), + space.getAddress(span.max())); + } + + protected ULongSpan spanRng(AddressRange range) { + return ULongSpan.span( + range.getMinAddress().getOffset(), + range.getMaxAddress().getOffset()); } protected AddressSet addrSet(ULongSpanSet set) { @@ -108,6 +116,20 @@ public class BytesPcodeExecutorStateSpace { return result; } + /** + * This assumes without assertion that the set is contained in this space + * + * @param set the address set + * @return the unsigned long span set + */ + protected ULongSpanSet spanSet(AddressSetView set) { + MutableULongSpanSet result = new DefaultULongSpanSet(); + for (AddressRange range : set) { + result.add(spanRng(range)); + } + return result; + } + protected Set getRegs(AddressSetView set) { Set regs = new TreeSet<>(); for (AddressRange rng : set) { @@ -151,12 +173,32 @@ public class BytesPcodeExecutorStateSpace { * @return the bytes read */ public byte[] read(long offset, int size, Reason reason) { - if (backing != null) { - readUninitializedFromBacking(bytes.getUninitialized(offset, offset + size - 1)); + ULongSpanSet uninitialized = bytes.getUninitialized(offset, offset + size - 1); + if (uninitialized.isEmpty()) { + return readBytes(offset, size, reason); } - ULongSpanSet stillUninit = bytes.getUninitialized(offset, offset + size - 1); - if (!stillUninit.isEmpty() && reason == Reason.EXECUTE) { - warnUninit(stillUninit); + if (backing != null) { + uninitialized = readUninitializedFromBacking(uninitialized); + if (uninitialized.isEmpty()) { + return readBytes(offset, size, reason); + } + } + + Iterator it = + uninitialized.complement(ULongSpan.extent(offset, size)).iterator(); + if (it.hasNext()) { + ULongSpan init = it.next(); + if (init.min().longValue() == offset) { + return readBytes(offset, (int) init.length(), reason); + } + } + + if (reason == Reason.EXECUTE_READ) { + warnUninit(uninitialized); + } + else if (reason == Reason.EXECUTE_DECODE) { + throw new DecodePcodeExecutionException("Cannot decode uninitialized memory", + space.getAddress(offset)); } return readBytes(offset, size, reason); } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/DecodePcodeExecutionException.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/DecodePcodeExecutionException.java new file mode 100644 index 0000000000..2be36d8aa6 --- /dev/null +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/DecodePcodeExecutionException.java @@ -0,0 +1,31 @@ +/* ### + * 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.pcode.exec; + +import ghidra.program.model.address.Address; + +public class DecodePcodeExecutionException extends PcodeExecutionException { + private final Address pc; + + public DecodePcodeExecutionException(String message, Address pc) { + super(message + ", PC=" + pc); + this.pc = pc; + } + + public Address getProgramCounter() { + return pc; + } +} diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/PcodeArithmetic.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/PcodeArithmetic.java index eb6aef9f5c..99f4adb035 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/PcodeArithmetic.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/PcodeArithmetic.java @@ -17,6 +17,7 @@ package ghidra.pcode.exec; import java.math.BigInteger; +import ghidra.pcode.exec.PcodeExecutorStatePiece.Reason; import ghidra.pcode.opbehavior.*; import ghidra.pcode.utils.Utils; import ghidra.program.model.lang.Endian; @@ -49,21 +50,31 @@ public interface PcodeArithmetic { */ enum Purpose { /** The value is needed to parse an instruction */ - DECODE, + DECODE(Reason.EXECUTE_DECODE), /** The value is needed for disassembly context */ - CONTEXT, + CONTEXT(Reason.EXECUTE_READ), /** The value is needed to decide a conditional branch */ - CONDITION, + CONDITION(Reason.EXECUTE_READ), /** The value will be used as the address of an indirect branch */ - BRANCH, + BRANCH(Reason.EXECUTE_READ), /** The value will be used as the address of a value to load */ - LOAD, + LOAD(Reason.EXECUTE_READ), /** The value will be used as the address of a value to store */ - STORE, + STORE(Reason.EXECUTE_READ), /** Some other reason, perhaps for userop library use */ - OTHER, + OTHER(Reason.EXECUTE_READ), /** The user or a tool is inspecting the value */ - INSPECT + INSPECT(Reason.INSPECT); + + private final Reason reason; + + private Purpose(Reason reason) { + this.reason = reason; + } + + public Reason reason() { + return reason; + } } /** diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/PcodeExecutorStatePiece.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/PcodeExecutorStatePiece.java index 510326d4b8..cc47f67073 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/PcodeExecutorStatePiece.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/PcodeExecutorStatePiece.java @@ -47,9 +47,11 @@ public interface PcodeExecutorStatePiece { enum Reason { /** The value is needed as the default program counter or disassembly context */ RE_INIT, - /** The value is needed by the emulator in the course of execution */ - EXECUTE, - /** The value is being inspected */ + /** The value is being read by the emulator as data in the course of execution */ + EXECUTE_READ, + /** The value is being decoded by the emulator as an instruction for execution */ + EXECUTE_DECODE, + /** The value is being inspected by something other than an emulator */ INSPECT } diff --git a/Ghidra/Framework/Emulation/src/test/java/ghidra/pcode/exec/AnnotatedPcodeUseropLibraryTest.java b/Ghidra/Framework/Emulation/src/test/java/ghidra/pcode/exec/AnnotatedPcodeUseropLibraryTest.java index 230fbeb7c0..b14d97043f 100644 --- a/Ghidra/Framework/Emulation/src/test/java/ghidra/pcode/exec/AnnotatedPcodeUseropLibraryTest.java +++ b/Ghidra/Framework/Emulation/src/test/java/ghidra/pcode/exec/AnnotatedPcodeUseropLibraryTest.java @@ -59,7 +59,7 @@ public class AnnotatedPcodeUseropLibraryTest extends AbstractGTest { protected PcodeExecutor createBytesExecutor(SleighLanguage language) throws Exception { PcodeExecutorState state = new BytesPcodeExecutorState(language); PcodeArithmetic arithmetic = BytesPcodeArithmetic.forLanguage(language); - return new PcodeExecutor<>(language, arithmetic, state, Reason.EXECUTE); + return new PcodeExecutor<>(language, arithmetic, state, Reason.EXECUTE_READ); } protected void executeSleigh(PcodeExecutor executor, PcodeUseropLibrary library, diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/disassemble/Disassembler.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/disassemble/Disassembler.java index 4a1634271e..56335ab029 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/disassemble/Disassembler.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/disassemble/Disassembler.java @@ -802,8 +802,10 @@ public class Disassembler implements DisassemblerConflictHandler { disassembleInstructionBlock(block, blockMemBuffer, null, limit, null, false); } catch (Exception e) { - Msg.error(this, "Pseudo block disassembly failure at " + blockMemBuffer.getAddress() + - ": " + e.getMessage(), e); + String message = "Pseudo block disassembly failure at " + blockMemBuffer.getAddress() + + ": " + e.getMessage(); + Msg.error(this, message, e); + reportMessage(message); } finally {