mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-05 02:39:44 +02:00
GP-1206: Factored TraceCachedWriteBytesPcodeExecutorState and added two checked variants.
This commit is contained in:
parent
888f2635b4
commit
0ff904081f
6 changed files with 334 additions and 24 deletions
|
@ -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);
|
||||||
|
}
|
|
@ -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.");
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -128,30 +128,36 @@ public class TraceCachedWriteBytesPcodeExecutorState
|
||||||
: rng.upperEndpoint().longValue() - 1;
|
: 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) {
|
public byte[] read(long offset, int size) {
|
||||||
if (source != null) {
|
if (source != null) {
|
||||||
// TODO: Warn or bail when reading UNKNOWN bytes
|
// TODO: Warn or bail when reading UNKNOWN bytes
|
||||||
// NOTE: Read without regard to gaps
|
// NOTE: Read without regard to gaps
|
||||||
// NOTE: Cannot write those gaps, though!!!
|
// NOTE: Cannot write those gaps, though!!!
|
||||||
RangeSet<UnsignedLong> uninitialized =
|
readUninitializedFromSource(cache.getUninitialized(offset, offset + size));
|
||||||
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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
byte[] data = new byte[size];
|
return readCached(offset, size);
|
||||||
cache.getData(offset, data);
|
|
||||||
return data;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Must already have started a transaction
|
// Must already have started a transaction
|
||||||
|
@ -229,12 +235,16 @@ public class TraceCachedWriteBytesPcodeExecutorState
|
||||||
return arithmetic.fromConst(l, space.getPointerSize());
|
return arithmetic.fromConst(l, space.getPointerSize());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected CachedSpace newSpace(AddressSpace space, TraceMemorySpace source, long snap) {
|
||||||
|
return new CachedSpace(space, source, snap);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected CachedSpace getForSpace(AddressSpace space, boolean toWrite) {
|
protected CachedSpace getForSpace(AddressSpace space, boolean toWrite) {
|
||||||
return spaces.computeIfAbsent(space, s -> {
|
return spaces.computeIfAbsent(space, s -> {
|
||||||
TraceMemorySpace tms = s.isUniqueSpace() ? null
|
TraceMemorySpace tms = s.isUniqueSpace() ? null
|
||||||
: TraceSleighUtils.getSpaceForExecution(s, trace, thread, frame, false);
|
: TraceSleighUtils.getSpaceForExecution(s, trace, thread, frame, false);
|
||||||
return new CachedSpace(s, tms, snap);
|
return newSpace(s, tms, snap);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -50,16 +50,18 @@ public class TracePcodeEmulator extends AbstractPcodeEmulator {
|
||||||
this(trace, snap, SleighUseropLibrary.nil());
|
this(trace, snap, SleighUseropLibrary.nil());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected PcodeExecutorState<byte[]> newState(TraceThread thread) {
|
||||||
|
return new TraceCachedWriteBytesPcodeExecutorState(trace, snap, thread, 0);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected PcodeExecutorState<byte[]> createMemoryState() {
|
protected PcodeExecutorState<byte[]> createMemoryState() {
|
||||||
return new TraceCachedWriteBytesPcodeExecutorState(trace, snap, null, 0);
|
return newState(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected PcodeExecutorState<byte[]> createRegisterState(PcodeThread<byte[]> emuThread) {
|
protected PcodeExecutorState<byte[]> createRegisterState(PcodeThread<byte[]> emuThread) {
|
||||||
TraceThread traceThread =
|
return newState(trace.getThreadManager().getLiveThreadByPath(snap, emuThread.getName()));
|
||||||
trace.getThreadManager().getLiveThreadByPath(snap, emuThread.getName());
|
|
||||||
return new TraceCachedWriteBytesPcodeExecutorState(trace, snap, traceThread, 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -669,7 +669,7 @@ public class TracePcodeEmulatorTest extends AbstractGhidraHeadlessIntegrationTes
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ( Test x86's SAR instruction
|
* Test x86's SAR instruction
|
||||||
*
|
*
|
||||||
* <p>
|
* <p>
|
||||||
* This test hits an INT_SRIGHT p-code op where the two input operands have differing sizes.
|
* 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));
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue