mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-04 18:29:37 +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;
|
||||
}
|
||||
|
||||
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);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -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()));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue