GP-0: Fixes post GP-1529

This commit is contained in:
Dan 2023-03-31 10:10:00 -04:00
parent 3bbcc56886
commit 2fb2902721
14 changed files with 129 additions and 93 deletions

View file

@ -76,6 +76,10 @@ public class BytesTracePcodeExecutorStatePiece
} }
} }
protected AddressSetView intersectViewKnown(AddressSetView set) {
return backing.intersectViewKnown(set, true);
}
@Override @Override
protected ULongSpanSet readUninitializedFromBacking(ULongSpanSet uninitialized) { protected ULongSpanSet readUninitializedFromBacking(ULongSpanSet uninitialized) {
if (uninitialized.isEmpty()) { if (uninitialized.isEmpty()) {
@ -84,7 +88,7 @@ public class BytesTracePcodeExecutorStatePiece
// 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!!!
AddressSetView knownButUninit = backing.intersectViewKnown(addrSet(uninitialized)); AddressSetView knownButUninit = intersectViewKnown(addrSet(uninitialized));
if (knownButUninit.isEmpty()) { if (knownButUninit.isEmpty()) {
return uninitialized; return uninitialized;
} }

View file

@ -52,8 +52,8 @@ public class RequireHasKnownTraceCachedWriteBytesPcodeExecutorStatePiece
} }
@Override @Override
protected AddressSetView getKnown(PcodeTraceDataAccess backing) { protected AddressSetView getKnown(PcodeTraceDataAccess backing, AddressSetView set) {
return backing.getKnownBefore(); return backing.intersectViewKnown(set, true);
} }
@Override @Override

View file

@ -50,8 +50,8 @@ public class RequireIsKnownTraceCachedWriteBytesPcodeExecutorStatePiece
spaceMap.fork()); spaceMap.fork());
} }
protected AddressSetView getKnown(PcodeTraceDataAccess backing) { protected AddressSetView getKnown(PcodeTraceDataAccess backing, AddressSetView set) {
return backing.getKnownNow(); return backing.intersectViewKnown(set, false);
} }
protected AccessPcodeExecutionException excFor(AddressSetView unknown) { protected AccessPcodeExecutionException excFor(AddressSetView unknown) {
@ -68,7 +68,7 @@ public class RequireIsKnownTraceCachedWriteBytesPcodeExecutorStatePiece
throw excFor(uninitialized); throw excFor(uninitialized);
} }
// TODO: Could find first instead? // TODO: Could find first instead?
AddressSetView unknown = uninitialized.subtract(getKnown(backing)); AddressSetView unknown = uninitialized.subtract(getKnown(backing, uninitialized));
if (unknown.isEmpty()) { if (unknown.isEmpty()) {
return size; return size;
} }

View file

@ -23,6 +23,7 @@ import ghidra.trace.model.Lifespan;
import ghidra.trace.model.TraceTimeViewport; import ghidra.trace.model.TraceTimeViewport;
import ghidra.trace.model.guest.TracePlatform; import ghidra.trace.model.guest.TracePlatform;
import ghidra.trace.model.memory.*; import ghidra.trace.model.memory.*;
import ghidra.trace.util.TraceRegisterUtils;
/** /**
* An abstract data-access shim, for either memory or registers * An abstract data-access shim, for either memory or registers
@ -130,27 +131,8 @@ public abstract class AbstractPcodeTraceDataAccess implements InternalPcodeTrace
return hostSet.isEmpty() ? TraceMemoryState.KNOWN : TraceMemoryState.UNKNOWN; return hostSet.isEmpty() ? TraceMemoryState.KNOWN : TraceMemoryState.UNKNOWN;
} }
protected AddressSetView doGetKnown(Lifespan span) {
TraceMemoryOperations ops = getMemoryOps(false);
if (ops == null) {
return new AddressSet();
}
return platform.mapHostToGuest(ops.getAddressesWithState(span,
s -> s == TraceMemoryState.KNOWN));
}
@Override @Override
public AddressSetView getKnownNow() { public AddressSetView intersectViewKnown(AddressSetView guestView, boolean useFullSpans) {
return doGetKnown(Lifespan.at(snap));
}
@Override
public AddressSetView getKnownBefore() {
return doGetKnown(Lifespan.since(snap));
}
@Override
public AddressSetView intersectViewKnown(AddressSetView guestView) {
TraceMemoryOperations ops = getMemoryOps(false); TraceMemoryOperations ops = getMemoryOps(false);
if (ops == null) { if (ops == null) {
return new AddressSet(); return new AddressSet();
@ -158,11 +140,20 @@ public abstract class AbstractPcodeTraceDataAccess implements InternalPcodeTrace
AddressSetView hostView = toOverlay(platform.mapGuestToHost(guestView)); AddressSetView hostView = toOverlay(platform.mapGuestToHost(guestView));
AddressSet hostKnown = new AddressSet(); AddressSet hostKnown = new AddressSet();
for (long sn : viewport.getOrderedSnaps()) { if (useFullSpans) {
hostKnown.add(ops.getAddressesWithState(sn, hostView, for (Lifespan span : viewport.getOrderedSpans()) {
st -> st != null && st != TraceMemoryState.UNKNOWN)); hostKnown.add(ops.getAddressesWithState(span, hostView,
st -> st != null && st != TraceMemoryState.UNKNOWN));
}
} }
AddressSetView hostResult = hostView.intersect(hostKnown); else {
for (long snap : viewport.getOrderedSnaps()) {
hostKnown.add(ops.getAddressesWithState(snap, hostView,
st -> st != null && st != TraceMemoryState.UNKNOWN));
}
}
AddressSetView hostResult =
TraceRegisterUtils.getPhysicalSet(hostView.intersect(hostKnown));
return platform.mapHostToGuest(hostResult); return platform.mapHostToGuest(hostResult);
} }
@ -176,7 +167,7 @@ public abstract class AbstractPcodeTraceDataAccess implements InternalPcodeTrace
AddressSetView hostView = toOverlay(platform.mapGuestToHost(guestView)); AddressSetView hostView = toOverlay(platform.mapGuestToHost(guestView));
AddressSetView hostKnown = ops.getAddressesWithState(snap, hostView, AddressSetView hostKnown = ops.getAddressesWithState(snap, hostView,
s -> s != null && s != TraceMemoryState.UNKNOWN); s -> s != null && s != TraceMemoryState.UNKNOWN);
AddressSetView hostResult = hostView.subtract(hostKnown); AddressSetView hostResult = TraceRegisterUtils.getPhysicalSet(hostView.subtract(hostKnown));
return platform.mapHostToGuest(hostResult); return platform.mapHostToGuest(hostResult);
} }

View file

@ -71,18 +71,9 @@ public class DefaultPcodeTraceThreadAccess
} }
@Override @Override
public AddressSetView getKnownNow() { public AddressSetView intersectViewKnown(AddressSetView view, boolean useFullSpans) {
return memory.getKnownNow().union(registers.getKnownNow()); return memory.intersectViewKnown(view, useFullSpans)
} .union(registers.intersectViewKnown(view, useFullSpans));
@Override
public AddressSetView getKnownBefore() {
return memory.getKnownBefore().union(registers.getKnownBefore());
}
@Override
public AddressSetView intersectViewKnown(AddressSetView view) {
return memory.intersectViewKnown(view).union(registers.intersectViewKnown(view));
} }
@Override @Override

View file

@ -63,43 +63,15 @@ public interface PcodeTraceDataAccess {
*/ */
TraceMemoryState getViewportState(AddressRange range); TraceMemoryState getViewportState(AddressRange range);
/**
* Get the address set of {@link TraceMemoryState#KNOWN} memory in the source snapshot
*
* <p>
* Note, this does not consider the snapshot's viewport.
*
* @implNote This can be an expensive operation when the platform is a guest, since what would
* ordinarily be a lazy address set must be computed and translated to the guest
* address spaces.
*
* @return the address set
*/
AddressSetView getKnownNow();
/**
* Get the address set of {@link TraceMemoryState#KNOWN} memory among all snapshots from 0 to
* the source snapshot
*
* <p>
* Note, this does not consider the snapshot's viewport.
*
* @implNote This can be an expensive operation when the platform is a guest, since what would
* ordinarily be a lazy address set must be computed and translated to the guest
* address spaces.
*
* @return the address set
*/
AddressSetView getKnownBefore();
/** /**
* Compute the intersection of the given address set and the set of * Compute the intersection of the given address set and the set of
* {@link TraceMemoryState#KNOWN} or (@link {@link TraceMemoryState#ERROR} memory * {@link TraceMemoryState#KNOWN} or (@link {@link TraceMemoryState#ERROR} memory
* *
* @param view the address set * @param view the address set
* @param useFullSpans how to treat the viewport; true for ever known, false for known now.
* @return the intersection * @return the intersection
*/ */
AddressSetView intersectViewKnown(AddressSetView view); AddressSetView intersectViewKnown(AddressSetView view, boolean useFullSpans);
/** /**
* Compute the intersection of the given address set and the set of * Compute the intersection of the given address set and the set of

View file

@ -290,7 +290,7 @@ public class DBTraceMemoryManager extends AbstractDBTraceSpaceBasedManager<DBTra
} }
@Override @Override
public AddressSetView getAddressesWithState(long snap, AddressSetView set, public AddressSetView getAddressesWithState(Lifespan snap, AddressSetView set,
Predicate<TraceMemoryState> predicate) { Predicate<TraceMemoryState> predicate) {
return delegateAddressSet(getActiveMemorySpaces(), return delegateAddressSet(getActiveMemorySpaces(),
m -> m.getAddressesWithState(snap, set, predicate)); m -> m.getAddressesWithState(snap, set, predicate));
@ -311,6 +311,11 @@ public class DBTraceMemoryManager extends AbstractDBTraceSpaceBasedManager<DBTra
.toList()); .toList());
} }
protected Collection<Entry<TraceAddressSnapRange, TraceMemoryState>> doGetStates(Lifespan span,
AddressRange range) {
return delegateRead(range.getAddressSpace(), m -> m.doGetStates(span, range));
}
@Override @Override
public Collection<Entry<TraceAddressSnapRange, TraceMemoryState>> getStates(long snap, public Collection<Entry<TraceAddressSnapRange, TraceMemoryState>> getStates(long snap,
AddressRange range) { AddressRange range) {

View file

@ -434,7 +434,7 @@ public class DBTraceMemorySpace
} }
@Override @Override
public AddressSetView getAddressesWithState(long snap, AddressSetView set, public AddressSetView getAddressesWithState(Lifespan span, AddressSetView set,
Predicate<TraceMemoryState> predicate) { Predicate<TraceMemoryState> predicate) {
try (LockHold hold = LockHold.lock(lock.readLock())) { try (LockHold hold = LockHold.lock(lock.readLock())) {
AddressSet remains = new AddressSet(set); AddressSet remains = new AddressSet(set);
@ -442,7 +442,7 @@ public class DBTraceMemorySpace
while (!remains.isEmpty()) { while (!remains.isEmpty()) {
AddressRange range = remains.getFirstRange(); AddressRange range = remains.getFirstRange();
remains.delete(range); remains.delete(range);
for (Entry<TraceAddressSnapRange, TraceMemoryState> entry : doGetStates(snap, for (Entry<TraceAddressSnapRange, TraceMemoryState> entry : doGetStates(span,
range)) { range)) {
AddressRange foundRange = entry.getKey().getRange(); AddressRange foundRange = entry.getKey().getRange();
remains.delete(foundRange); remains.delete(foundRange);
@ -455,21 +455,20 @@ public class DBTraceMemorySpace
} }
} }
protected Collection<Entry<TraceAddressSnapRange, TraceMemoryState>> doGetStates(long snap, protected Collection<Entry<TraceAddressSnapRange, TraceMemoryState>> doGetStates(Lifespan span,
AddressRange range) { AddressRange range) {
// TODO: A better way to handle memory-mapped registers? // TODO: A better way to handle memory-mapped registers?
if (getAddressSpace().isRegisterSpace() && !range.getAddressSpace().isRegisterSpace()) { if (getAddressSpace().isRegisterSpace() && !range.getAddressSpace().isRegisterSpace()) {
return trace.getMemoryManager().getStates(snap, range); return trace.getMemoryManager().doGetStates(span, range);
} }
return stateMapSpace.reduce(TraceAddressSnapRangeQuery.intersecting(range.getMinAddress(), return stateMapSpace.reduce(TraceAddressSnapRangeQuery.intersecting(range, span)).entries();
range.getMaxAddress(), snap, snap)).entries();
} }
@Override @Override
public Collection<Entry<TraceAddressSnapRange, TraceMemoryState>> getStates(long snap, public Collection<Entry<TraceAddressSnapRange, TraceMemoryState>> getStates(long snap,
AddressRange range) { AddressRange range) {
assertInSpace(range); assertInSpace(range);
return doGetStates(snap, range); return doGetStates(Lifespan.at(snap), range);
} }
@Override @Override

View file

@ -268,6 +268,20 @@ public interface TraceMemoryOperations {
Entry<TraceAddressSnapRange, TraceMemoryState> getViewMostRecentStateEntry(long snap, Entry<TraceAddressSnapRange, TraceMemoryState> getViewMostRecentStateEntry(long snap,
Address address); Address address);
/**
* Get at least the subset of addresses having state satisfying the given predicate
*
* @param snap the time
* @param set the set to examine
* @param predicate a predicate on state to search for
* @return the address set
* @see #getAddressesWithState(Lifespan, AddressSetView, Predicate)
*/
default AddressSetView getAddressesWithState(long snap, AddressSetView set,
Predicate<TraceMemoryState> predicate) {
return getAddressesWithState(Lifespan.at(snap), set, predicate);
}
/** /**
* Get at least the subset of addresses having state satisfying the given predicate * Get at least the subset of addresses having state satisfying the given predicate
* *
@ -288,7 +302,7 @@ public interface TraceMemoryOperations {
* @param predicate a predicate on state to search for * @param predicate a predicate on state to search for
* @return the address set * @return the address set
*/ */
AddressSetView getAddressesWithState(long snap, AddressSetView set, AddressSetView getAddressesWithState(Lifespan span, AddressSetView set,
Predicate<TraceMemoryState> predicate); Predicate<TraceMemoryState> predicate);
/** /**

View file

@ -60,6 +60,34 @@ public enum TraceRegisterUtils {
return result; return result;
} }
public static AddressRange getPhysicalRange(AddressRange range) {
AddressSpace space = range.getAddressSpace();
AddressSpace physical = space.getPhysicalSpace();
if (space == physical) {
return range;
}
return new AddressRangeImpl(
physical.getAddress(range.getMinAddress().getOffset()),
physical.getAddress(range.getMaxAddress().getOffset()));
}
/**
* Convert a set in an overlay space to the corresponding set in its physical space
*
* @param set a set contained entirely in one space
* @return the physical set
*/
public static AddressSetView getPhysicalSet(AddressSetView set) {
if (set.isEmpty() || !set.getMinAddress().getAddressSpace().isOverlaySpace()) {
return set;
}
AddressSet result = new AddressSet();
for (AddressRange rng : set) {
result.add(getPhysicalRange(rng));
}
return result;
}
public static byte[] padOrTruncate(byte[] arr, int length) { public static byte[] padOrTruncate(byte[] arr, int length) {
if (arr.length == length) { if (arr.length == length) {
return arr; return arr;

View file

@ -942,6 +942,23 @@ public class BytesTracePcodeEmulatorTest extends AbstractTracePcodeEmulatorTest
} }
} }
@Test(expected = DecodePcodeExecutionException.class)
public void testUninitialized() throws Throwable {
try (ToyDBTraceBuilder tb = new ToyDBTraceBuilder("Test", "Toy:BE:64:default")) {
assertEquals(Register.NO_CONTEXT, tb.language.getContextBaseRegister());
TraceThread thread = initTrace(tb, """
pc = 0x00400000;
sp = 0x00110000;
""",
List.of()); // An empty, uninitialized program
BytesTracePcodeEmulator emu = new BytesTracePcodeEmulator(tb.host, 0);
PcodeThread<byte[]> emuThread = emu.newThread(thread.getPath());
emuThread.stepInstruction();
}
}
@Test @Test
public void testMov_w_mW1_W0() throws Throwable { public void testMov_w_mW1_W0() throws Throwable {
try (ToyDBTraceBuilder tb = new ToyDBTraceBuilder("Test", "dsPIC33F:LE:24:default")) { try (ToyDBTraceBuilder tb = new ToyDBTraceBuilder("Test", "dsPIC33F:LE:24:default")) {

View file

@ -15,7 +15,8 @@
*/ */
package ghidra.pcode.emu.taint.full; package ghidra.pcode.emu.taint.full;
import static org.junit.Assert.*; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import java.util.Set; import java.util.Set;
@ -30,18 +31,18 @@ import ghidra.app.plugin.core.debug.service.emulation.DebuggerEmulationServicePl
import ghidra.app.plugin.core.debug.service.emulation.DebuggerPcodeMachine; import ghidra.app.plugin.core.debug.service.emulation.DebuggerPcodeMachine;
import ghidra.app.plugin.core.debug.service.modules.DebuggerStaticMappingServicePlugin; import ghidra.app.plugin.core.debug.service.modules.DebuggerStaticMappingServicePlugin;
import ghidra.app.services.DebuggerEmulationService; import ghidra.app.services.DebuggerEmulationService;
import ghidra.app.services.DebuggerEmulationService.EmulationResult;
import ghidra.app.services.DebuggerStaticMappingService; import ghidra.app.services.DebuggerStaticMappingService;
import ghidra.pcode.emu.taint.trace.TaintTracePcodeEmulatorTest; import ghidra.pcode.emu.taint.trace.TaintTracePcodeEmulatorTest;
import ghidra.pcode.emu.taint.trace.TaintTracePcodeExecutorStatePiece; import ghidra.pcode.emu.taint.trace.TaintTracePcodeExecutorStatePiece;
import ghidra.program.model.address.AddressSpace; import ghidra.program.model.address.AddressSpace;
import ghidra.program.model.util.StringPropertyMap; import ghidra.program.model.util.StringPropertyMap;
import ghidra.program.util.ProgramLocation; import ghidra.program.util.ProgramLocation;
import ghidra.trace.model.DefaultTraceLocation; import ghidra.trace.model.*;
import ghidra.trace.model.Lifespan;
import ghidra.trace.model.property.TracePropertyMap; import ghidra.trace.model.property.TracePropertyMap;
import ghidra.trace.model.property.TracePropertyMapSpace; import ghidra.trace.model.property.TracePropertyMapSpace;
import ghidra.trace.model.thread.TraceThread; import ghidra.trace.model.thread.TraceThread;
import ghidra.trace.model.time.schedule.TraceSchedule; import ghidra.trace.model.time.schedule.*;
import ghidra.util.task.TaskMonitor; import ghidra.util.task.TaskMonitor;
public class TaintDebuggerPcodeEmulatorTest extends AbstractGhidraHeadedDebuggerGUITest { public class TaintDebuggerPcodeEmulatorTest extends AbstractGhidraHeadedDebuggerGUITest {
@ -75,11 +76,19 @@ public class TaintDebuggerPcodeEmulatorTest extends AbstractGhidraHeadedDebugger
traceManager.activateTrace(tb.trace); traceManager.activateTrace(tb.trace);
TraceSchedule time = TraceSchedule.parse("0:t0-1"); EmulationResult result =
emuService.emulate(tb.trace, time, TaskMonitor.DUMMY); emuService.run(tb.host, TraceSchedule.snap(0), monitor, new Scheduler() {
traceManager.activateTime(time); int calls = 0;
DebuggerPcodeMachine<?> emu = emuService.getCachedEmulator(tb.trace, time); @Override
public TickStep nextSlice(Trace trace) {
// Expect decode of uninitialized memory immediately
assertEquals(0, calls++);
return new TickStep(0, 1);
}
});
DebuggerPcodeMachine<?> emu = emuService.getCachedEmulator(tb.trace, result.schedule());
assertTrue(emu instanceof TaintDebuggerPcodeEmulator); assertTrue(emu instanceof TaintDebuggerPcodeEmulator);
} }

View file

@ -32,6 +32,7 @@ import ghidra.util.Msg;
* @param <B> if this space is a cache, the type of object backing this space * @param <B> if this space is a cache, the type of object backing this space
*/ */
public class BytesPcodeExecutorStateSpace<B> { public class BytesPcodeExecutorStateSpace<B> {
protected final static byte[] EMPTY = new byte[] {};
protected final SemisparseByteArray bytes; protected final SemisparseByteArray bytes;
protected final Language language; // for logging diagnostics protected final Language language; // for logging diagnostics
protected final AddressSpace space; protected final AddressSpace space;
@ -197,8 +198,13 @@ public class BytesPcodeExecutorStateSpace<B> {
warnUninit(uninitialized); warnUninit(uninitialized);
} }
else if (reason == Reason.EXECUTE_DECODE) { else if (reason == Reason.EXECUTE_DECODE) {
throw new DecodePcodeExecutionException("Cannot decode uninitialized memory", /**
space.getAddress(offset)); * The callers may be reading ahead, so it's not appropriate to throw an exception here.
* Instead, communicate there's no more. If the buffer's empty on their end, they'll
* handle the error as appropriate. If it's in the emulator, the instruction decoder
* should eventually throw the decode exception.
*/
return EMPTY;
} }
return readBytes(offset, size, reason); return readBytes(offset, size, reason);
} }

View file

@ -21,7 +21,7 @@ public class DecodePcodeExecutionException extends PcodeExecutionException {
private final Address pc; private final Address pc;
public DecodePcodeExecutionException(String message, Address pc) { public DecodePcodeExecutionException(String message, Address pc) {
super(message + ", PC=" + pc); super(message.contains("PC=") ? message : "%s (PC=%s)".formatted(message, pc));
this.pc = pc; this.pc = pc;
} }