From 0ff904081f66c8cd29e8ece3233ca013db1e0d0b Mon Sep 17 00:00:00 2001 From: Dan <46821332+nsadeveloper789@users.noreply.github.com> Date: Mon, 23 Aug 2021 15:47:03 -0400 Subject: [PATCH] GP-1206: Factored TraceCachedWriteBytesPcodeExecutorState and added two checked variants. --- ...aceCachedWriteBytesPcodeExecutorState.java | 76 +++++++++++ ...aceCachedWriteBytesPcodeExecutorState.java | 45 +++++++ ...aceCachedWriteBytesPcodeExecutorState.java | 60 +++++++++ ...aceCachedWriteBytesPcodeExecutorState.java | 48 ++++--- .../pcode/exec/trace/TracePcodeEmulator.java | 10 +- .../exec/trace/TracePcodeEmulatorTest.java | 119 +++++++++++++++++- 6 files changed, 334 insertions(+), 24 deletions(-) create mode 100644 Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/AbstractCheckedTraceCachedWriteBytesPcodeExecutorState.java create mode 100644 Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/RequireHasKnownTraceCachedWriteBytesPcodeExecutorState.java create mode 100644 Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/RequireIsKnownTraceCachedWriteBytesPcodeExecutorState.java diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/AbstractCheckedTraceCachedWriteBytesPcodeExecutorState.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/AbstractCheckedTraceCachedWriteBytesPcodeExecutorState.java new file mode 100644 index 0000000000..09156e0ce8 --- /dev/null +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/AbstractCheckedTraceCachedWriteBytesPcodeExecutorState.java @@ -0,0 +1,76 @@ +/* ### + * 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.trace; + +import com.google.common.collect.Range; +import com.google.common.collect.RangeSet; +import com.google.common.primitives.UnsignedLong; + +import ghidra.program.model.address.*; +import ghidra.trace.model.Trace; +import ghidra.trace.model.memory.TraceMemorySpace; +import ghidra.trace.model.thread.TraceThread; + +public abstract class AbstractCheckedTraceCachedWriteBytesPcodeExecutorState + extends TraceCachedWriteBytesPcodeExecutorState { + + protected class CheckedCachedSpace extends CachedSpace { + public CheckedCachedSpace(AddressSpace space, TraceMemorySpace source, long snap) { + super(space, source, snap); + } + + protected AddressRange addrRng(Range rng) { + Address start = space.getAddress(lower(rng)); + Address end = space.getAddress(upper(rng)); + return new AddressRangeImpl(start, end); + } + + protected AddressSet addrSet(RangeSet set) { + AddressSet result = new AddressSet(); + for (Range rng : set.asRanges()) { + result.add(addrRng(rng)); + } + return result; + } + + @Override + public byte[] read(long offset, int size) { + RangeSet uninitialized = cache.getUninitialized(offset, offset + size); + + if (!uninitialized.isEmpty()) { + size = checkUninitialized(source, space.getAddress(offset), size, + addrSet(uninitialized)); + if (source != null) { + readUninitializedFromSource(uninitialized); + } + } + return readCached(offset, size); + } + } + + public AbstractCheckedTraceCachedWriteBytesPcodeExecutorState(Trace trace, long snap, + TraceThread thread, int frame) { + super(trace, snap, thread, frame); + } + + @Override + protected CachedSpace newSpace(AddressSpace space, TraceMemorySpace source, long snap) { + return new CheckedCachedSpace(space, source, snap); + } + + protected abstract int checkUninitialized(TraceMemorySpace source, Address start, int size, + AddressSet uninitialized); +} diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/RequireHasKnownTraceCachedWriteBytesPcodeExecutorState.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/RequireHasKnownTraceCachedWriteBytesPcodeExecutorState.java new file mode 100644 index 0000000000..fea268383a --- /dev/null +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/RequireHasKnownTraceCachedWriteBytesPcodeExecutorState.java @@ -0,0 +1,45 @@ +/* ### + * 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.trace; + +import com.google.common.collect.Range; + +import ghidra.pcode.exec.AccessPcodeExecutionException; +import ghidra.program.model.address.AddressSetView; +import ghidra.trace.model.Trace; +import ghidra.trace.model.memory.TraceMemorySpace; +import ghidra.trace.model.memory.TraceMemoryState; +import ghidra.trace.model.thread.TraceThread; + +public class RequireHasKnownTraceCachedWriteBytesPcodeExecutorState + extends RequireIsKnownTraceCachedWriteBytesPcodeExecutorState { + + public RequireHasKnownTraceCachedWriteBytesPcodeExecutorState(Trace trace, long snap, + TraceThread thread, int frame) { + super(trace, snap, thread, frame); + } + + @Override + protected AddressSetView getKnown(TraceMemorySpace source) { + return source.getAddressesWithState(Range.closed(0L, snap), + s -> s == TraceMemoryState.KNOWN); + } + + @Override + protected AccessPcodeExecutionException excFor(AddressSetView unknown) { + throw new AccessPcodeExecutionException("Memory at " + unknown + " has never been known."); + } +} diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/RequireIsKnownTraceCachedWriteBytesPcodeExecutorState.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/RequireIsKnownTraceCachedWriteBytesPcodeExecutorState.java new file mode 100644 index 0000000000..3a1f69bc16 --- /dev/null +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/RequireIsKnownTraceCachedWriteBytesPcodeExecutorState.java @@ -0,0 +1,60 @@ +/* ### + * 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.trace; + +import ghidra.pcode.exec.AccessPcodeExecutionException; +import ghidra.program.model.address.*; +import ghidra.trace.model.Trace; +import ghidra.trace.model.memory.TraceMemorySpace; +import ghidra.trace.model.memory.TraceMemoryState; +import ghidra.trace.model.thread.TraceThread; + +public class RequireIsKnownTraceCachedWriteBytesPcodeExecutorState + extends AbstractCheckedTraceCachedWriteBytesPcodeExecutorState { + + public RequireIsKnownTraceCachedWriteBytesPcodeExecutorState(Trace trace, long snap, + TraceThread thread, int frame) { + super(trace, snap, thread, frame); + } + + protected AddressSetView getKnown(TraceMemorySpace source) { + return source.getAddressesWithState(snap, s -> s == TraceMemoryState.KNOWN); + } + + protected AccessPcodeExecutionException excFor(AddressSetView unknown) { + return new AccessPcodeExecutionException("Memory at " + unknown + " is unknown."); + } + + @Override + protected int checkUninitialized(TraceMemorySpace source, Address start, int size, + AddressSet uninitialized) { + if (source == null) { + if (!uninitialized.contains(start)) { + return (int) uninitialized.getMinAddress().subtract(start); + } + throw excFor(uninitialized); + } + // TODO: Could find first instead? + AddressSetView unknown = uninitialized.subtract(getKnown(source)); + if (unknown.isEmpty()) { + return size; + } + if (!unknown.contains(start)) { + return (int) unknown.getMinAddress().subtract(start); + } + throw excFor(unknown); + } +} diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/TraceCachedWriteBytesPcodeExecutorState.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/TraceCachedWriteBytesPcodeExecutorState.java index f40028c6ea..62b525eaed 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/TraceCachedWriteBytesPcodeExecutorState.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/TraceCachedWriteBytesPcodeExecutorState.java @@ -128,30 +128,36 @@ public class TraceCachedWriteBytesPcodeExecutorState : rng.upperEndpoint().longValue() - 1; } + protected void readUninitializedFromSource(RangeSet uninitialized) { + if (!uninitialized.isEmpty()) { + Range toRead = uninitialized.span(); + assert toRead.hasUpperBound() && toRead.hasLowerBound(); + long lower = lower(toRead); + long upper = upper(toRead); + ByteBuffer buf = ByteBuffer.allocate((int) (upper - lower + 1)); + source.getBytes(snap, space.getAddress(lower), buf); + for (Range rng : uninitialized.asRanges()) { + long l = lower(rng); + long u = upper(rng); + cache.putData(l, buf.array(), (int) (l - lower), (int) (u - l + 1)); + } + } + } + + protected byte[] readCached(long offset, int size) { + byte[] data = new byte[size]; + cache.getData(offset, data); + return data; + } + public byte[] read(long offset, int size) { if (source != null) { // TODO: Warn or bail when reading UNKNOWN bytes // NOTE: Read without regard to gaps // NOTE: Cannot write those gaps, though!!! - RangeSet uninitialized = - cache.getUninitialized(offset, offset + size); - if (!uninitialized.isEmpty()) { - Range toRead = uninitialized.span(); - assert toRead.hasUpperBound() && toRead.hasLowerBound(); - long lower = lower(toRead); - long upper = upper(toRead); - ByteBuffer buf = ByteBuffer.allocate((int) (upper - lower + 1)); - source.getBytes(snap, space.getAddress(lower), buf); - for (Range rng : uninitialized.asRanges()) { - long l = lower(rng); - long u = upper(rng); - cache.putData(l, buf.array(), (int) (l - lower), (int) (u - l + 1)); - } - } + readUninitializedFromSource(cache.getUninitialized(offset, offset + size)); } - byte[] data = new byte[size]; - cache.getData(offset, data); - return data; + return readCached(offset, size); } // Must already have started a transaction @@ -229,12 +235,16 @@ public class TraceCachedWriteBytesPcodeExecutorState return arithmetic.fromConst(l, space.getPointerSize()); } + protected CachedSpace newSpace(AddressSpace space, TraceMemorySpace source, long snap) { + return new CachedSpace(space, source, snap); + } + @Override protected CachedSpace getForSpace(AddressSpace space, boolean toWrite) { return spaces.computeIfAbsent(space, s -> { TraceMemorySpace tms = s.isUniqueSpace() ? null : TraceSleighUtils.getSpaceForExecution(s, trace, thread, frame, false); - return new CachedSpace(s, tms, snap); + return newSpace(s, tms, snap); }); } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/TracePcodeEmulator.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/TracePcodeEmulator.java index 207fbf82d9..02ea8060b0 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/TracePcodeEmulator.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/TracePcodeEmulator.java @@ -50,16 +50,18 @@ public class TracePcodeEmulator extends AbstractPcodeEmulator { this(trace, snap, SleighUseropLibrary.nil()); } + protected PcodeExecutorState newState(TraceThread thread) { + return new TraceCachedWriteBytesPcodeExecutorState(trace, snap, thread, 0); + } + @Override protected PcodeExecutorState createMemoryState() { - return new TraceCachedWriteBytesPcodeExecutorState(trace, snap, null, 0); + return newState(null); } @Override protected PcodeExecutorState createRegisterState(PcodeThread emuThread) { - TraceThread traceThread = - trace.getThreadManager().getLiveThreadByPath(snap, emuThread.getName()); - return new TraceCachedWriteBytesPcodeExecutorState(trace, snap, traceThread, 0); + return newState(trace.getThreadManager().getLiveThreadByPath(snap, emuThread.getName())); } /** diff --git a/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/pcode/exec/trace/TracePcodeEmulatorTest.java b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/pcode/exec/trace/TracePcodeEmulatorTest.java index 3d7da631ad..c347ce1c27 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/pcode/exec/trace/TracePcodeEmulatorTest.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/pcode/exec/trace/TracePcodeEmulatorTest.java @@ -669,7 +669,7 @@ public class TracePcodeEmulatorTest extends AbstractGhidraHeadlessIntegrationTes } /** - * ( Test x86's SAR instruction + * Test x86's SAR instruction * *

* This test hits an INT_SRIGHT p-code op where the two input operands have differing sizes. @@ -732,4 +732,121 @@ public class TracePcodeEmulatorTest extends AbstractGhidraHeadlessIntegrationTes TraceSleighUtils.evaluate("RAX", tb.trace, 1, thread, 0)); } } + + @Test(expected = AccessPcodeExecutionException.class) + public void testCheckedMOV_err() throws Throwable { + try (ToyDBTraceBuilder tb = new ToyDBTraceBuilder("Test", "x86:LE:64:default")) { + TraceThread thread = initTrace(tb, + List.of( + "RIP = 0x00400000;"), + List.of( + "MOV RCX,RAX")); + + TracePcodeEmulator emu = new TracePcodeEmulator(tb.trace, 0) { + @Override + protected PcodeExecutorState newState(TraceThread thread) { + return new RequireIsKnownTraceCachedWriteBytesPcodeExecutorState(trace, snap, + thread, 0); + } + }; + PcodeThread emuThread = emu.newThread(thread.getPath()); + emuThread.overrideContextWithDefault(); + emuThread.stepInstruction(); + } + } + + @Test + public void testCheckedMOV_known() throws Throwable { + try (ToyDBTraceBuilder tb = new ToyDBTraceBuilder("Test", "x86:LE:64:default")) { + TraceThread thread = initTrace(tb, + List.of( + "RIP = 0x00400000;", + "RAX = 0x1234;"), // Make it known in the trace + List.of( + "MOV RCX,RAX")); + + TracePcodeEmulator emu = new TracePcodeEmulator(tb.trace, 0) { + @Override + protected PcodeExecutorState newState(TraceThread thread) { + return new RequireIsKnownTraceCachedWriteBytesPcodeExecutorState(trace, snap, + thread, 0); + } + }; + PcodeThread emuThread = emu.newThread(thread.getPath()); + emuThread.overrideContextWithDefault(); + emuThread.stepInstruction(); + } + } + + @Test(expected = AccessPcodeExecutionException.class) + public void testCheckedMOV_knownPast_err() throws Throwable { + try (ToyDBTraceBuilder tb = new ToyDBTraceBuilder("Test", "x86:LE:64:default")) { + TraceThread thread = initTrace(tb, + List.of( + "RIP = 0x00400000;", + "RAX = 0x1234;"), // Make it known in the trace + List.of( + "MOV RCX,RAX")); + + // Start emulator one snap later + TracePcodeEmulator emu = new TracePcodeEmulator(tb.trace, 1) { + @Override + protected PcodeExecutorState newState(TraceThread thread) { + return new RequireIsKnownTraceCachedWriteBytesPcodeExecutorState(trace, snap, + thread, 0); + } + }; + PcodeThread emuThread = emu.newThread(thread.getPath()); + emuThread.overrideContextWithDefault(); + emuThread.stepInstruction(); + } + } + + @Test + public void testCheckedMOV_knownPast_has() throws Throwable { + try (ToyDBTraceBuilder tb = new ToyDBTraceBuilder("Test", "x86:LE:64:default")) { + TraceThread thread = initTrace(tb, + List.of( + "RIP = 0x00400000;", + "RAX = 0x1234;"), // Make it known in the trace + List.of( + "MOV RCX,RAX")); + + // Start emulator one snap later, but with "has-known" checks + TracePcodeEmulator emu = new TracePcodeEmulator(tb.trace, 1) { + @Override + protected PcodeExecutorState newState(TraceThread thread) { + return new RequireHasKnownTraceCachedWriteBytesPcodeExecutorState(trace, snap, + thread, 0); + } + }; + PcodeThread emuThread = emu.newThread(thread.getPath()); + emuThread.overrideContextWithDefault(); + emuThread.stepInstruction(); + } + } + + @Test + public void testCheckedMOV_initialized() throws Throwable { + try (ToyDBTraceBuilder tb = new ToyDBTraceBuilder("Test", "x86:LE:64:default")) { + TraceThread thread = initTrace(tb, + List.of( + "RIP = 0x00400000;"), + List.of( + "MOV RAX,0", // Have the program initialize it + "MOV RCX,RAX")); + + TracePcodeEmulator emu = new TracePcodeEmulator(tb.trace, 0) { + @Override + protected PcodeExecutorState newState(TraceThread thread) { + return new RequireIsKnownTraceCachedWriteBytesPcodeExecutorState(trace, snap, + thread, 0); + } + }; + PcodeThread emuThread = emu.newThread(thread.getPath()); + emuThread.overrideContextWithDefault(); + emuThread.stepInstruction(); + emuThread.stepInstruction(); + } + } }