GP-1206: Factored TraceCachedWriteBytesPcodeExecutorState and added two checked variants.

This commit is contained in:
Dan 2021-08-23 15:47:03 -04:00
parent 888f2635b4
commit 0ff904081f
6 changed files with 334 additions and 24 deletions

View file

@ -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<UnsignedLong> rng) {
Address start = space.getAddress(lower(rng));
Address end = space.getAddress(upper(rng));
return new AddressRangeImpl(start, end);
}
protected AddressSet addrSet(RangeSet<UnsignedLong> set) {
AddressSet result = new AddressSet();
for (Range<UnsignedLong> rng : set.asRanges()) {
result.add(addrRng(rng));
}
return result;
}
@Override
public byte[] read(long offset, int size) {
RangeSet<UnsignedLong> 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);
}

View file

@ -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.");
}
}

View file

@ -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);
}
}

View file

@ -128,30 +128,36 @@ public class TraceCachedWriteBytesPcodeExecutorState
: rng.upperEndpoint().longValue() - 1;
}
protected void readUninitializedFromSource(RangeSet<UnsignedLong> uninitialized) {
if (!uninitialized.isEmpty()) {
Range<UnsignedLong> 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<UnsignedLong> 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<UnsignedLong> uninitialized =
cache.getUninitialized(offset, offset + size);
if (!uninitialized.isEmpty()) {
Range<UnsignedLong> 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<UnsignedLong> 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);
});
}

View file

@ -50,16 +50,18 @@ public class TracePcodeEmulator extends AbstractPcodeEmulator {
this(trace, snap, SleighUseropLibrary.nil());
}
protected PcodeExecutorState<byte[]> newState(TraceThread thread) {
return new TraceCachedWriteBytesPcodeExecutorState(trace, snap, thread, 0);
}
@Override
protected PcodeExecutorState<byte[]> createMemoryState() {
return new TraceCachedWriteBytesPcodeExecutorState(trace, snap, null, 0);
return newState(null);
}
@Override
protected PcodeExecutorState<byte[]> createRegisterState(PcodeThread<byte[]> emuThread) {
TraceThread traceThread =
trace.getThreadManager().getLiveThreadByPath(snap, emuThread.getName());
return new TraceCachedWriteBytesPcodeExecutorState(trace, snap, traceThread, 0);
return newState(trace.getThreadManager().getLiveThreadByPath(snap, emuThread.getName()));
}
/**

View file

@ -669,7 +669,7 @@ public class TracePcodeEmulatorTest extends AbstractGhidraHeadlessIntegrationTes
}
/**
* ( Test x86's SAR instruction
* Test x86's SAR instruction
*
* <p>
* 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<byte[]> newState(TraceThread thread) {
return new RequireIsKnownTraceCachedWriteBytesPcodeExecutorState(trace, snap,
thread, 0);
}
};
PcodeThread<byte[]> 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<byte[]> newState(TraceThread thread) {
return new RequireIsKnownTraceCachedWriteBytesPcodeExecutorState(trace, snap,
thread, 0);
}
};
PcodeThread<byte[]> 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<byte[]> newState(TraceThread thread) {
return new RequireIsKnownTraceCachedWriteBytesPcodeExecutorState(trace, snap,
thread, 0);
}
};
PcodeThread<byte[]> 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<byte[]> newState(TraceThread thread) {
return new RequireHasKnownTraceCachedWriteBytesPcodeExecutorState(trace, snap,
thread, 0);
}
};
PcodeThread<byte[]> 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<byte[]> newState(TraceThread thread) {
return new RequireIsKnownTraceCachedWriteBytesPcodeExecutorState(trace, snap,
thread, 0);
}
};
PcodeThread<byte[]> emuThread = emu.newThread(thread.getPath());
emuThread.overrideContextWithDefault();
emuThread.stepInstruction();
emuThread.stepInstruction();
}
}
}