GP-569: Added trace interpolation and extrapolation (emulation)

This commit is contained in:
Dan 2021-01-06 16:04:02 -05:00
parent 57b69005c7
commit fcc0d97ae0
173 changed files with 10969 additions and 1424 deletions

View file

@ -23,11 +23,14 @@ import org.apache.commons.lang3.tuple.Pair;
import ghidra.generic.util.datastruct.SemisparseByteArray;
import ghidra.pcode.exec.*;
import ghidra.pcode.utils.Utils;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSpace;
import ghidra.program.model.mem.MemBuffer;
import ghidra.trace.model.Trace;
import ghidra.trace.model.memory.TraceMemorySpace;
import ghidra.trace.model.memory.TraceMemoryState;
import ghidra.trace.model.thread.TraceThread;
import ghidra.trace.util.DefaultTraceTimeViewport;
public class TraceBytesPcodeExecutorState
extends AbstractLongOffsetPcodeExecutorState<byte[], TraceMemorySpace> {
@ -38,12 +41,17 @@ public class TraceBytesPcodeExecutorState
private TraceThread thread;
private int frame;
private final DefaultTraceTimeViewport viewport;
public TraceBytesPcodeExecutorState(Trace trace, long snap, TraceThread thread, int frame) {
super(trace.getBaseLanguage(), BytesPcodeArithmetic.forLanguage(trace.getBaseLanguage()));
this.trace = trace;
this.snap = snap;
this.thread = thread;
this.frame = frame;
this.viewport = new DefaultTraceTimeViewport(trace);
this.viewport.setSnap(snap);
}
public PcodeExecutorState<Pair<byte[], TraceMemoryState>> withMemoryState() {
@ -81,6 +89,7 @@ public class TraceBytesPcodeExecutorState
public void setSnap(long snap) {
this.snap = snap;
this.viewport.setSnap(snap);
}
public long getSnap() {
@ -147,10 +156,15 @@ public class TraceBytesPcodeExecutorState
@Override
protected byte[] getFromSpace(TraceMemorySpace space, long offset, int size) {
ByteBuffer buf = ByteBuffer.allocate(size);
int read = space.getBytes(snap, space.getAddressSpace().getAddress(offset), buf);
int read = space.getViewBytes(snap, space.getAddressSpace().getAddress(offset), buf);
if (read != size) {
throw new RuntimeException("Could not read full value from trace");
}
return buf.array();
}
@Override
public MemBuffer getConcreteBuffer(Address address) {
return trace.getMemoryManager().getBufferAt(snap, address);
}
}

View file

@ -0,0 +1,257 @@
/* ###
* 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 java.nio.ByteBuffer;
import java.util.HashMap;
import java.util.Map;
import com.google.common.collect.*;
import com.google.common.primitives.UnsignedLong;
import ghidra.generic.util.datastruct.SemisparseByteArray;
import ghidra.pcode.exec.AbstractLongOffsetPcodeExecutorState;
import ghidra.pcode.exec.BytesPcodeArithmetic;
import ghidra.pcode.exec.trace.TraceCachedWriteBytesPcodeExecutorState.CachedSpace;
import ghidra.pcode.utils.Utils;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSpace;
import ghidra.program.model.mem.MemBuffer;
import ghidra.program.model.mem.Memory;
import ghidra.trace.model.Trace;
import ghidra.trace.model.memory.TraceMemorySpace;
import ghidra.trace.model.thread.TraceThread;
import ghidra.trace.util.MemBufferAdapter;
import ghidra.util.MathUtilities;
/**
* A state which reads bytes from a trace, but caches writes internally.
*
* <p>
* This provides for "read-only" emulation on a trace. Writes do not affect the source trace, but
* rather are cached in this state. If desired, those cached writes can be written back out at a
* later time.
*/
public class TraceCachedWriteBytesPcodeExecutorState
extends AbstractLongOffsetPcodeExecutorState<byte[], CachedSpace> {
protected class StateMemBuffer implements MemBufferAdapter {
protected final Address address;
protected final CachedSpace source;
public StateMemBuffer(Address address, CachedSpace source) {
this.address = address;
this.source = source;
}
@Override
public Address getAddress() {
return address;
}
@Override
public Memory getMemory() {
return null;
}
@Override
public boolean isBigEndian() {
return trace.getBaseLanguage().isBigEndian();
}
@Override
public int getBytes(ByteBuffer buffer, int addressOffset) {
byte[] data = source.read(address.getOffset() + addressOffset, buffer.remaining());
buffer.put(data);
return data.length;
}
}
protected final Map<AddressSpace, CachedSpace> spaces = new HashMap<>();
protected final Trace trace;
protected final long snap;
protected final TraceThread thread;
protected final int frame;
public TraceCachedWriteBytesPcodeExecutorState(Trace trace, long snap, TraceThread thread,
int frame) {
super(trace.getBaseLanguage(), BytesPcodeArithmetic.forLanguage(trace.getBaseLanguage()));
this.trace = trace;
this.snap = snap;
this.thread = thread;
this.frame = frame;
}
protected static class CachedSpace {
protected final SemisparseByteArray cache = new SemisparseByteArray();
protected final RangeSet<UnsignedLong> written = TreeRangeSet.create();
protected final AddressSpace space;
protected final TraceMemorySpace source;
protected final long snap;
public CachedSpace(AddressSpace space, TraceMemorySpace source, long snap) {
this.space = space;
this.source = source;
this.snap = snap;
}
public void write(long offset, byte[] val) {
cache.putData(offset, val);
UnsignedLong uLoc = UnsignedLong.fromLongBits(offset);
UnsignedLong uEnd = UnsignedLong.fromLongBits(offset + val.length);
written.add(Range.closedOpen(uLoc, uEnd));
}
public static long lower(Range<UnsignedLong> rng) {
return rng.lowerBoundType() == BoundType.CLOSED
? rng.lowerEndpoint().longValue()
: rng.lowerEndpoint().longValue() + 1;
}
public static long upper(Range<UnsignedLong> rng) {
return rng.upperBoundType() == BoundType.CLOSED
? rng.upperEndpoint().longValue()
: rng.upperEndpoint().longValue() - 1;
}
public byte[] read(long offset, int size) {
if (source != null) {
// TODO: Warn or bail when reading UNKNOWN bytes
// NOTE: Not going to worry about gaps here:
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);
cache.putData(lower, buf.array());
}
}
byte[] data = new byte[size];
cache.getData(offset, data);
return data;
}
// Must already have started a transaction
protected void writeDown(Trace trace, long snap, TraceThread thread, int frame) {
if (space.isUniqueSpace()) {
return;
}
byte[] data = new byte[4096];
ByteBuffer buf = ByteBuffer.wrap(data);
TraceMemorySpace mem =
TraceSleighUtils.getSpaceForExecution(space, trace, thread, frame, true);
for (Range<UnsignedLong> range : written.asRanges()) {
assert range.lowerBoundType() == BoundType.CLOSED;
assert range.upperBoundType() == BoundType.OPEN;
long lower = range.lowerEndpoint().longValue();
long fullLen = range.upperEndpoint().longValue() - lower;
while (fullLen > 0) {
int len = MathUtilities.unsignedMin(data.length, fullLen);
cache.getData(lower, data, 0, len);
buf.position(0);
buf.limit(len);
mem.putBytes(snap, space.getAddress(lower), buf);
lower += len;
fullLen -= len;
}
}
}
}
public Trace getTrace() {
return trace;
}
public long getSnap() {
return snap;
}
public TraceThread getThread() {
return thread;
}
public int getFrame() {
return frame;
}
/**
* Write the accumulated writes into the given trace
*
* <p>
* NOTE: This method requires a transaction to have already been started on the destination
* trace.
*
* @param trace the trace to modify
* @param snap the snap within the trace
* @param thread the thread to take register writes
* @param frame the frame for register writes
*/
public void writeCacheDown(Trace trace, long snap, TraceThread thread, int frame) {
if (trace.getBaseLanguage() != language) {
throw new IllegalArgumentException("Destination trace must be same language as source");
}
for (CachedSpace cached : spaces.values()) {
cached.writeDown(trace, snap, thread, frame);
}
}
@Override
protected long offsetToLong(byte[] offset) {
return Utils.bytesToLong(offset, offset.length, language.isBigEndian());
}
@Override
public byte[] longToOffset(AddressSpace space, long l) {
return arithmetic.fromConst(l, space.getPointerSize());
}
@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);
});
}
@Override
protected void setInSpace(CachedSpace space, long offset, int size, byte[] val) {
assert size == val.length;
space.write(offset, val);
}
@Override
protected byte[] getFromSpace(CachedSpace space, long offset, int size) {
byte[] read = space.read(offset, size);
if (read.length != size) {
Address addr = space.space.getAddress(offset);
throw new UnknownStatePcodeExecutionException("Incomplete read (" + read.length +
" of " + size + " bytes)", language, addr.add(read.length), size - read.length);
}
return read;
}
@Override
public MemBuffer getConcreteBuffer(Address address) {
return new StateMemBuffer(address, getForSpace(address.getAddressSpace(), false));
}
}

View file

@ -15,6 +15,8 @@
*/
package ghidra.pcode.exec.trace;
import java.math.BigInteger;
import ghidra.pcode.exec.PcodeArithmetic;
import ghidra.pcode.opbehavior.BinaryOpBehavior;
import ghidra.pcode.opbehavior.UnaryOpBehavior;
@ -43,8 +45,18 @@ public enum TraceMemoryStatePcodeArithmetic implements PcodeArithmetic<TraceMemo
return TraceMemoryState.KNOWN;
}
@Override
public TraceMemoryState fromConst(BigInteger value, int size) {
return TraceMemoryState.KNOWN;
}
@Override
public boolean isTrue(TraceMemoryState cond) {
throw new AssertionError("Cannot decide branches using TraceMemoryState");
}
@Override
public BigInteger toConcrete(TraceMemoryState value) {
throw new AssertionError("Cannot make TraceMemoryState a 'concrete value'");
}
}

View file

@ -23,10 +23,12 @@ import com.google.common.primitives.UnsignedLong;
import ghidra.pcode.exec.AbstractLongOffsetPcodeExecutorStatePiece;
import ghidra.pcode.utils.Utils;
import ghidra.program.model.address.*;
import ghidra.program.model.mem.MemBuffer;
import ghidra.trace.model.Trace;
import ghidra.trace.model.memory.TraceMemorySpace;
import ghidra.trace.model.memory.TraceMemoryState;
import ghidra.trace.model.thread.TraceThread;
import ghidra.trace.util.DefaultTraceTimeViewport;
public class TraceMemoryStatePcodeExecutorStatePiece extends
AbstractLongOffsetPcodeExecutorStatePiece<byte[], TraceMemoryState, TraceMemorySpace> {
@ -37,6 +39,8 @@ public class TraceMemoryStatePcodeExecutorStatePiece extends
private TraceThread thread;
private int frame;
private final DefaultTraceTimeViewport viewport;
public TraceMemoryStatePcodeExecutorStatePiece(Trace trace, long snap, TraceThread thread,
int frame) {
super(trace.getBaseLanguage(), TraceMemoryStatePcodeArithmetic.INSTANCE);
@ -44,6 +48,9 @@ public class TraceMemoryStatePcodeExecutorStatePiece extends
this.snap = snap;
this.thread = thread;
this.frame = frame;
this.viewport = new DefaultTraceTimeViewport(trace);
this.viewport.setSnap(snap);
}
public Trace getTrace() {
@ -52,6 +59,7 @@ public class TraceMemoryStatePcodeExecutorStatePiece extends
public void setSnap(long snap) {
this.snap = snap;
this.viewport.setSnap(snap);
}
public long getSnap() {
@ -137,7 +145,15 @@ public class TraceMemoryStatePcodeExecutorStatePiece extends
@Override
protected TraceMemoryState getFromSpace(TraceMemorySpace space, long offset, int size) {
AddressSet set = new AddressSet(range(space.getAddressSpace(), offset, size));
set.delete(space.getAddressesWithState(snap, set, s -> s == TraceMemoryState.KNOWN));
for (long snap : viewport.getOrderedSnaps()) {
set.delete(
space.getAddressesWithState(snap, set, state -> state == TraceMemoryState.KNOWN));
}
return set.isEmpty() ? TraceMemoryState.KNOWN : TraceMemoryState.UNKNOWN;
}
@Override
public MemBuffer getConcreteBuffer(Address address) {
throw new AssertionError("Cannot make TraceMemoryState into a concrete buffer");
}
}

View file

@ -0,0 +1,101 @@
/* ###
* 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.app.plugin.processors.sleigh.SleighLanguage;
import ghidra.pcode.emu.AbstractPcodeEmulator;
import ghidra.pcode.emu.PcodeThread;
import ghidra.pcode.exec.PcodeExecutorState;
import ghidra.pcode.exec.SleighUseropLibrary;
import ghidra.program.model.lang.Language;
import ghidra.trace.model.Trace;
import ghidra.trace.model.stack.TraceStack;
import ghidra.trace.model.thread.TraceThread;
import ghidra.trace.model.thread.TraceThreadManager;
/**
* An emulator that can read initial state from a trace
*/
public class TracePcodeEmulator extends AbstractPcodeEmulator {
private static SleighLanguage assertSleigh(Language language) {
if (!(language instanceof SleighLanguage)) {
throw new IllegalArgumentException("Emulation requires a sleigh language");
}
return (SleighLanguage) language;
}
protected final Trace trace;
protected final long snap;
public TracePcodeEmulator(Trace trace, long snap, SleighUseropLibrary<byte[]> library) {
super(assertSleigh(trace.getBaseLanguage()), library);
this.trace = trace;
this.snap = snap;
}
public TracePcodeEmulator(Trace trace, long snap) {
this(trace, snap, SleighUseropLibrary.nil());
}
@Override
protected PcodeExecutorState<byte[]> createMemoryState() {
return new TraceCachedWriteBytesPcodeExecutorState(trace, snap, null, 0);
}
@Override
protected PcodeExecutorState<byte[]> createRegisterState(PcodeThread<byte[]> emuThread) {
TraceThread traceThread =
trace.getThreadManager().getLiveThreadByPath(snap, emuThread.getName());
return new TraceCachedWriteBytesPcodeExecutorState(trace, snap, traceThread, 0);
}
/**
* Write the accumulated writes into the given trace at the given snap
*
* <p>
* NOTE: This method requires a transaction to have already been started on the destination
* trace. The destination threads must have equal names/paths at the given threadsSnap. When
* using scratch space, threadsSnap should be the source snap. If populating a new trace,
* threadsSnap should probably be the destination snap.
*
* @param trace the trace to modify
* @param destSnap the destination snap within the trace
* @param threadsSnap the snap at which to find corresponding threads
* @param synthesizeStacks true to synthesize the innermost stack frame of each thread
*/
public void writeDown(Trace trace, long destSnap, long threadsSnap, boolean synthesizeStacks) {
TraceCachedWriteBytesPcodeExecutorState ms =
(TraceCachedWriteBytesPcodeExecutorState) getMemoryState();
ms.writeCacheDown(trace, destSnap, null, 0);
TraceThreadManager threadManager = trace.getThreadManager();
for (PcodeThread<byte[]> emuThread : threads.values()) {
TraceCachedWriteBytesPcodeExecutorState rs =
(TraceCachedWriteBytesPcodeExecutorState) emuThread.getState().getRegisterState();
TraceThread traceThread = threadManager.getLiveThreadByPath(
threadsSnap, emuThread.getName());
if (traceThread == null) {
throw new IllegalArgumentException(
"Given trace does not have thread with name/path '" + emuThread.getName() +
"' at snap " + destSnap);
}
rs.writeCacheDown(trace, destSnap, traceThread, 0);
if (synthesizeStacks) {
TraceStack stack = trace.getStackManager().getStack(traceThread, destSnap, true);
stack.getFrame(0, true).setProgramCounter(emuThread.getCounter());
}
}
}
}

View file

@ -39,7 +39,7 @@ public enum TraceSleighUtils {
if (space.isRegisterSpace()) {
if (thread == null) {
throw new IllegalArgumentException(
"Cannot execute with register context unless a thread is given.");
"Cannot access register unless a thread is given.");
}
return trace.getMemoryManager().getMemoryRegisterSpace(thread, frame, toWrite);
}

View file

@ -0,0 +1,52 @@
/* ###
* 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 java.util.Arrays;
import ghidra.pcode.exec.AccessPcodeExecutionException;
import ghidra.program.model.address.*;
import ghidra.program.model.lang.Language;
import ghidra.program.model.lang.Register;
public class UnknownStatePcodeExecutionException extends AccessPcodeExecutionException {
public static String getMessage(Language language, Address address, int size) {
if (address.getAddressSpace().isRegisterSpace()) {
Register reg = language.getRegister(address, size);
if (reg != null) {
return "No recorded value for register " + reg;
}
return "No recorded value for register(s) " +
Arrays.asList(language.getRegisters(address));
}
try {
return "No recorded value for memory at " + new AddressRangeImpl(address, size);
}
catch (AddressOverflowException e) {
throw new AssertionError(e);
}
}
public UnknownStatePcodeExecutionException(Language language, Address address, int size) {
super(getMessage(language, address, size));
}
public UnknownStatePcodeExecutionException(String message, Language language, Address address,
int size) {
super(message + ": " + getMessage(language, address, size));
}
}

View file

@ -185,7 +185,8 @@ public class DBTraceRegisterContextManager extends
public RegisterValue getValueWithDefault(Language language, Register register, long snap,
Address address) {
return delegateRead(address.getAddressSpace(),
m -> m.getValueWithDefault(language, register, snap, address));
m -> m.getValueWithDefault(language, register, snap, address),
() -> getDefaultValue(language, register, address));
}
@Override

View file

@ -29,10 +29,10 @@ import ghidra.trace.database.DBTraceUtils;
import ghidra.trace.database.data.DBTraceDataSettingsAdapter.DBTraceSettingsEntry;
import ghidra.trace.database.map.*;
import ghidra.trace.database.map.DBTraceAddressSnapRangePropertyMapTree.AbstractDBTraceAddressSnapRangePropertyMapData;
import ghidra.trace.database.space.DBTraceSpaceKey;
import ghidra.trace.database.thread.DBTraceThread;
import ghidra.trace.database.thread.DBTraceThreadManager;
import ghidra.trace.model.thread.TraceThread;
import ghidra.trace.util.TraceAddressSpace;
import ghidra.util.database.*;
import ghidra.util.database.annot.*;
import ghidra.util.exception.VersionException;
@ -226,8 +226,8 @@ public class DBTraceDataSettingsAdapter
}
@Override
public DBTraceDataSettingsSpace get(DBTraceSpaceKey key, boolean createIfAbsent) {
return (DBTraceDataSettingsSpace) super.get(key, createIfAbsent);
public DBTraceDataSettingsSpace get(TraceAddressSpace space, boolean createIfAbsent) {
return (DBTraceDataSettingsSpace) super.get(space, createIfAbsent);
}
@Override

View file

@ -25,6 +25,7 @@ import ghidra.trace.database.map.DBTraceAddressSnapRangePropertyMapTree;
import ghidra.trace.database.map.DBTraceAddressSnapRangePropertyMapTree.AbstractDBTraceAddressSnapRangePropertyMapData;
import ghidra.trace.database.memory.DBTraceMemorySpace;
import ghidra.trace.model.thread.TraceThread;
import ghidra.trace.util.TraceAddressSpace;
import ghidra.util.LockHold;
import ghidra.util.database.DBCachedObjectStore;
@ -42,6 +43,11 @@ public abstract class AbstractDBTraceCodeUnit<T extends AbstractDBTraceCodeUnit<
this.space = space;
}
@Override
public TraceAddressSpace getTraceSpace() {
return space;
}
@Override
public Address getAddress() {
return getX1();
@ -96,7 +102,7 @@ public abstract class AbstractDBTraceCodeUnit<T extends AbstractDBTraceCodeUnit<
byteCache.limit(Math.min(byteCache.capacity(), end));
// TODO: Retrieve the memory space at code space construction time
DBTraceMemorySpace mem = space.trace.getMemoryManager().get(space, false);
mem.getBytes(getStartSnap(), address.add(byteCache.position()), byteCache);
mem.getViewBytes(getStartSnap(), address.add(byteCache.position()), byteCache);
}
// Copy from the cache
int toCopyFromCache =
@ -110,7 +116,7 @@ public abstract class AbstractDBTraceCodeUnit<T extends AbstractDBTraceCodeUnit<
int startRemains = Math.max(addressOffset, byteCache.position());
DBTraceMemorySpace mem = space.trace.getMemoryManager().get(space, false);
return toCopyFromCache +
mem.getBytes(getStartSnap(), address.add(startRemains), buffer);
mem.getViewBytes(getStartSnap(), address.add(startRemains), buffer);
}
}
}

View file

@ -26,11 +26,9 @@ import ghidra.program.model.lang.Language;
import ghidra.trace.database.DBTrace;
import ghidra.trace.database.data.DBTraceDataSettingsAdapter.DBTraceDataSettingsSpace;
import ghidra.trace.model.thread.TraceThread;
import ghidra.trace.util.DataAdapterFromDataType;
import ghidra.util.LockHold;
public abstract class AbstractDBTraceDataComponent
implements DBTraceDefinedDataAdapter, DataAdapterFromDataType {
public abstract class AbstractDBTraceDataComponent implements DBTraceDefinedDataAdapter {
protected final DBTraceData root;
protected final DBTraceDefinedDataAdapter parent;

View file

@ -50,8 +50,10 @@ import ghidra.trace.database.thread.DBTraceThreadManager;
import ghidra.trace.model.AddressSnap;
import ghidra.trace.model.DefaultAddressSnap;
import ghidra.trace.model.listing.TraceCodeManager;
import ghidra.trace.model.listing.TraceCodeSpace;
import ghidra.trace.model.stack.TraceStackFrame;
import ghidra.trace.model.thread.TraceThread;
import ghidra.trace.util.TraceAddressSpace;
import ghidra.util.*;
import ghidra.util.database.*;
import ghidra.util.database.annot.*;
@ -295,6 +297,11 @@ public class DBTraceCodeManager
return spaceStore.writeLock();
}
@Override
public TraceCodeSpace getCodeSpace(TraceAddressSpace space, boolean createIfAbsent) {
return get(space, createIfAbsent);
}
@Override
public DBTraceCodeSpace getCodeSpace(AddressSpace space, boolean createIfAbsent) {
return getForSpace(space, createIfAbsent);

View file

@ -27,7 +27,6 @@ import ghidra.program.model.lang.Language;
import ghidra.trace.database.DBTraceUtils;
import ghidra.trace.database.data.DBTraceDataSettingsAdapter.DBTraceDataSettingsSpace;
import ghidra.trace.database.map.DBTraceAddressSnapRangePropertyMapTree;
import ghidra.trace.util.DataAdapterFromDataType;
import ghidra.util.LockHold;
import ghidra.util.database.DBCachedObjectStore;
import ghidra.util.database.DBObjectColumn;
@ -35,8 +34,7 @@ import ghidra.util.database.annot.*;
@DBAnnotatedObjectInfo(version = 0)
public class DBTraceData extends AbstractDBTraceCodeUnit<DBTraceData>
implements DBTraceDefinedDataAdapter, DataAdapterFromDataType {
static final int[] EMPTY_INT_ARRAY = new int[0];
implements DBTraceDefinedDataAdapter {
private static final String TABLE_NAME = "Data";
static final String LANGUAGE_COLUMN_NAME = "Langauge";

View file

@ -20,66 +20,39 @@ import java.util.Collection;
import ghidra.docking.settings.Settings;
import ghidra.docking.settings.SettingsDefinition;
import ghidra.program.model.address.Address;
import ghidra.program.model.data.DataType;
import ghidra.program.model.data.MutabilitySettingsDefinition;
import ghidra.program.model.symbol.RefType;
import ghidra.program.model.symbol.SourceType;
import ghidra.trace.database.data.DBTraceDataSettingsOperations;
import ghidra.trace.database.symbol.DBTraceReference;
import ghidra.trace.model.listing.TraceCodeManager;
import ghidra.trace.model.listing.TraceData;
import ghidra.trace.model.symbol.TraceReference;
import ghidra.trace.util.*;
import ghidra.util.LockHold;
public interface DBTraceDataAdapter extends DBTraceCodeUnitAdapter, TraceData {
public interface DBTraceDataAdapter extends DBTraceCodeUnitAdapter, DataAdapterMinimal,
DataAdapterFromDataType, DataAdapterFromSettings, TraceData {
static String[] EMPTY_STRING_ARRAY = new String[] {};
default String doToString() {
StringBuilder builder = new StringBuilder();
builder.append(getMnemonicString());
String valueRepresentation = getDefaultValueRepresentation();
if (valueRepresentation != null) {
builder.append(' ');
builder.append(valueRepresentation);
}
return builder.toString();
}
@Override
default String getMnemonicString() {
return getDataType().getMnemonic(this);
}
@Override
DBTraceDataAdapter getRoot();
default String getPrimarySymbolOrDynamicName() {
/** TODO: Use primary symbol or dynamic name as in {@link DataDB#getPathName()} */
return "DAT_" + getAddressString(false, false);
}
@Override
default int getNumOperands() {
return 1;
}
@Override
default TraceReference[] getValueReferences() {
return getOperandReferences(TraceCodeManager.DATA_OP_INDEX);
return (TraceReference[]) DataAdapterMinimal.super.getValueReferences();
}
@Override
default void addValueReference(Address refAddr, RefType type) {
getTrace().getReferenceManager()
.addMemoryReference(getLifespan(), getAddress(), refAddr,
type, SourceType.USER_DEFINED, TraceCodeManager.DATA_OP_INDEX);
type, SourceType.USER_DEFINED, DATA_OP_INDEX);
}
@Override
default void removeValueReference(Address refAddr) {
DBTraceReference ref = getTrace().getReferenceManager()
.getReference(getStartSnap(),
getAddress(), refAddr, TraceCodeManager.DATA_OP_INDEX);
getAddress(), refAddr, DATA_OP_INDEX);
if (ref == null) {
return;
}
@ -198,38 +171,21 @@ public interface DBTraceDataAdapter extends DBTraceCodeUnitAdapter, TraceData {
return space.isEmpty(getLifespan(), getAddress());
}
@Override
default <T extends SettingsDefinition> T getSettingsDefinition(
Class<T> settingsDefinitionClass) {
DataType dt = getBaseDataType();
for (SettingsDefinition def : dt.getSettingsDefinitions()) {
if (settingsDefinitionClass.isAssignableFrom(def.getClass())) {
return settingsDefinitionClass.cast(def);
}
try (LockHold hold = LockHold.lock(getTrace().getReadWriteLock().readLock())) {
return DataAdapterFromSettings.super.getSettingsDefinition(settingsDefinitionClass);
}
return null;
}
@Override
default boolean hasMutability(int mutabilityType) {
try (LockHold hold = LockHold.lock(getTrace().getReadWriteLock().readLock())) {
MutabilitySettingsDefinition def =
getSettingsDefinition(MutabilitySettingsDefinition.class);
if (def != null) {
return def.getChoice(this) == mutabilityType;
}
return false;
return DataAdapterFromSettings.super.hasMutability(mutabilityType);
}
}
@Override
default boolean isConstant() {
return hasMutability(MutabilitySettingsDefinition.CONSTANT);
}
@Override
default boolean isVolatile() {
return hasMutability(MutabilitySettingsDefinition.VOLATILE);
}
@Override
DBTraceDataAdapter getPrimitiveAt(int offset);
}

View file

@ -15,8 +15,11 @@
*/
package ghidra.trace.database.listing;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.*;
import ghidra.program.model.data.DataType;
import ghidra.trace.model.ImmutableTraceAddressSnapRange;
import ghidra.trace.model.TraceAddressSnapRange;
import ghidra.trace.util.TraceAddressSpace;
public class DBTraceDataArrayElementComponent extends AbstractDBTraceDataComponent {
public DBTraceDataArrayElementComponent(DBTraceData root, DBTraceDefinedDataAdapter parent,
@ -24,6 +27,11 @@ public class DBTraceDataArrayElementComponent extends AbstractDBTraceDataCompone
super(root, parent, index, address, dataType, length);
}
@Override
public TraceAddressSpace getTraceSpace() {
return parent.getTraceSpace();
}
@Override
public String getFieldName() {
return "[" + index + "]";
@ -33,4 +41,16 @@ public class DBTraceDataArrayElementComponent extends AbstractDBTraceDataCompone
public String getFieldSyntax() {
return getFieldName();
}
@Override
public AddressRange getRange() {
// TODO: Cache this?
return new AddressRangeImpl(getMinAddress(), getMaxAddress());
}
@Override
public TraceAddressSnapRange getBounds() {
// TODO: Cache this?
return new ImmutableTraceAddressSnapRange(getMinAddress(), getMaxAddress(), getLifespan());
}
}

View file

@ -15,8 +15,11 @@
*/
package ghidra.trace.database.listing;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.*;
import ghidra.program.model.data.DataTypeComponent;
import ghidra.trace.model.ImmutableTraceAddressSnapRange;
import ghidra.trace.model.TraceAddressSnapRange;
import ghidra.trace.util.TraceAddressSpace;
public class DBTraceDataCompositeFieldComponent extends AbstractDBTraceDataComponent {
protected final DataTypeComponent dtc;
@ -27,6 +30,11 @@ public class DBTraceDataCompositeFieldComponent extends AbstractDBTraceDataCompo
this.dtc = dtc;
}
@Override
public TraceAddressSpace getTraceSpace() {
return parent.getTraceSpace();
}
@Override
public String getFieldName() {
String fieldName = dtc.getFieldName();
@ -40,4 +48,16 @@ public class DBTraceDataCompositeFieldComponent extends AbstractDBTraceDataCompo
public String getFieldSyntax() {
return "." + getFieldName();
}
@Override
public AddressRange getRange() {
// TODO: Cache this?
return new AddressRangeImpl(getMinAddress(), getMaxAddress());
}
@Override
public TraceAddressSnapRange getBounds() {
// TODO: Cache this?
return new ImmutableTraceAddressSnapRange(getMinAddress(), getMaxAddress(), getLifespan());
}
}

View file

@ -22,7 +22,6 @@ import java.util.*;
import com.google.common.collect.Range;
import db.DBRecord;
import ghidra.lifecycle.Unfinished;
import ghidra.program.model.address.*;
import ghidra.program.model.lang.*;
import ghidra.program.model.listing.ContextChangeException;
@ -47,7 +46,7 @@ import ghidra.util.database.annot.*;
@DBAnnotatedObjectInfo(version = 0)
public class DBTraceInstruction extends AbstractDBTraceCodeUnit<DBTraceInstruction> implements
TraceInstruction, InstructionAdapterFromPrototype, InstructionContext, Unfinished {
TraceInstruction, InstructionAdapterFromPrototype, InstructionContext {
private static final Address[] EMPTY_ADDRESS_ARRAY = new Address[] {};
private static final String TABLE_NAME = "Instructions";
@ -347,7 +346,7 @@ public class DBTraceInstruction extends AbstractDBTraceCodeUnit<DBTraceInstructi
return EMPTY_ADDRESS_ARRAY;
}
Collection<? extends DBTraceReference> refs =
refSpace.getFlowRefrencesFrom(getStartSnap(), getAddress());
refSpace.getFlowReferencesFrom(getStartSnap(), getAddress());
if (refs.isEmpty()) {
return EMPTY_ADDRESS_ARRAY;
}
@ -461,7 +460,7 @@ public class DBTraceInstruction extends AbstractDBTraceCodeUnit<DBTraceInstructi
update(FLAGS_COLUMN);
DBTraceReferenceSpace refSpace = space.referenceManager.get(space, true);
for (DBTraceReference ref : refSpace.getFlowRefrencesFrom(getStartSnap(), getX1())) {
for (DBTraceReference ref : refSpace.getFlowReferencesFrom(getStartSnap(), getX1())) {
if (!isSameFlowType(origFlowType, ref.getReferenceType())) {
continue;
}

View file

@ -22,8 +22,7 @@ import java.util.List;
import com.google.common.collect.Range;
import ghidra.docking.settings.Settings;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSpace;
import ghidra.program.model.address.*;
import ghidra.program.model.data.DataType;
import ghidra.program.model.lang.Language;
import ghidra.program.model.listing.Data;
@ -33,11 +32,12 @@ import ghidra.trace.database.data.DBTraceDataSettingsOperations;
import ghidra.trace.database.memory.DBTraceMemorySpace;
import ghidra.trace.database.space.DBTraceSpaceKey;
import ghidra.trace.database.thread.DBTraceThread;
import ghidra.trace.model.ImmutableTraceAddressSnapRange;
import ghidra.trace.model.TraceAddressSnapRange;
import ghidra.trace.model.listing.TraceData;
import ghidra.trace.util.DataAdapterFromDataType;
import ghidra.trace.util.TraceAddressSpace;
public class UndefinedDBTraceData
implements DBTraceDataAdapter, DataAdapterFromDataType, DBTraceSpaceKey {
public class UndefinedDBTraceData implements DBTraceDataAdapter, DBTraceSpaceKey {
protected final DBTrace trace;
protected final long snap;
protected final Range<Long> lifespan;
@ -55,6 +55,11 @@ public class UndefinedDBTraceData
this.frameLevel = frameLevel;
}
@Override
public TraceAddressSpace getTraceSpace() {
return this;
}
@Override
public AddressSpace getAddressSpace() {
return address.getAddressSpace();
@ -75,6 +80,18 @@ public class UndefinedDBTraceData
return trace.getBaseLanguage();
}
@Override
public AddressRange getRange() {
// TODO: Cache this?
return new AddressRangeImpl(getMinAddress(), getMaxAddress());
}
@Override
public TraceAddressSnapRange getBounds() {
// TODO: Cache this?
return new ImmutableTraceAddressSnapRange(getMinAddress(), getMaxAddress(), getLifespan());
}
@Override
public Range<Long> getLifespan() {
return lifespan;
@ -207,7 +224,7 @@ public class UndefinedDBTraceData
@Override
public int[] getComponentPath() {
return DBTraceData.EMPTY_INT_ARRAY;
return EMPTY_INT_ARRAY;
}
@Override

View file

@ -165,7 +165,8 @@ public class DBTraceAddressSnapRangePropertyMapAddressSetView<T> extends Abstrac
: new AddressRangeImpl(fullSpace.getMinAddress(), start);
Iterator<Entry<TraceAddressSnapRange, T>> mapIt = map
.reduce(TraceAddressSnapRangeQuery.intersecting(within, Range.all())
.starting(forward ? Rectangle2DDirection.LEFTMOST
.starting(forward
? Rectangle2DDirection.LEFTMOST
: Rectangle2DDirection.RIGHTMOST))
.orderedEntries()
.iterator();

View file

@ -505,6 +505,12 @@ public class DBTraceAddressSnapRangePropertyMapTree<T, DR extends AbstractDBTrac
Rectangle2DDirection.TOPMOST, TraceAddressSnapRangeQuery::new);
}
public static TraceAddressSnapRangeQuery mostRecent(Address address, Range<Long> span) {
return intersecting(
new ImmutableTraceAddressSnapRange(address, span),
Rectangle2DDirection.TOPMOST, TraceAddressSnapRangeQuery::new);
}
public static TraceAddressSnapRangeQuery equalTo(TraceAddressSnapRange shape) {
return equalTo(shape, null, TraceAddressSnapRangeQuery::new);
}

View file

@ -56,7 +56,7 @@ public class DBTraceMemBuffer implements MemBufferAdapter {
@Override
public int getBytes(ByteBuffer buffer, int offset) {
try {
return space.getBytes(snap, start.addNoWrap(offset), buffer);
return space.getViewBytes(snap, start.addNoWrap(offset), buffer);
}
catch (AddressOverflowException e) {
return 0;

View file

@ -211,6 +211,11 @@ public class DBTraceMemoryManager
return delegateRead(address.getAddressSpace(), m -> m.getState(snap, address));
}
@Override
public Entry<Long, TraceMemoryState> getViewState(long snap, Address address) {
return delegateRead(address.getAddressSpace(), m -> m.getViewState(snap, address));
}
@Override
public Entry<TraceAddressSnapRange, TraceMemoryState> getMostRecentStateEntry(long snap,
Address address) {
@ -218,6 +223,13 @@ public class DBTraceMemoryManager
m -> m.getMostRecentStateEntry(snap, address));
}
@Override
public Entry<TraceAddressSnapRange, TraceMemoryState> getViewMostRecentStateEntry(long snap,
Address address) {
return delegateRead(address.getAddressSpace(),
m -> m.getViewMostRecentStateEntry(snap, address));
}
@Override
public AddressSetView getAddressesWithState(long snap, AddressSetView set,
Predicate<TraceMemoryState> predicate) {
@ -267,6 +279,16 @@ public class DBTraceMemoryManager
});
}
@Override
public int getViewBytes(long snap, Address start, ByteBuffer buf) {
return delegateReadI(start.getAddressSpace(), m -> m.getViewBytes(snap, start, buf), () -> {
Address max = start.getAddressSpace().getMaxAddress();
int len = MathUtilities.unsignedMin(buf.remaining(), max.subtract(start));
buf.position(buf.position() + len);
return len;
});
}
@Override
public void removeBytes(long snap, Address start, int len) {
delegateDeleteV(start.getAddressSpace(), m -> m.removeBytes(snap, start, len));

View file

@ -46,7 +46,9 @@ import ghidra.trace.model.*;
import ghidra.trace.model.Trace.*;
import ghidra.trace.model.memory.*;
import ghidra.trace.util.TraceChangeRecord;
import ghidra.trace.util.TraceViewportSpanIterator;
import ghidra.util.LockHold;
import ghidra.util.MathUtilities;
import ghidra.util.database.*;
import ghidra.util.database.spatial.rect.Rectangle2DDirection;
import ghidra.util.exception.DuplicateNameException;
@ -329,8 +331,7 @@ public class DBTraceMemorySpace implements Unfinished, TraceMemorySpace, DBTrace
protected void checkState(TraceMemoryState state) {
if (state == null || state == TraceMemoryState.UNKNOWN) {
throw new IllegalArgumentException(
"User cannot erase memory state without removing bytes");
throw new IllegalArgumentException("Cannot erase memory state without removing bytes");
}
}
@ -376,6 +377,26 @@ public class DBTraceMemorySpace implements Unfinished, TraceMemorySpace, DBTrace
return state == null ? TraceMemoryState.UNKNOWN : state;
}
@Override
public Entry<Long, TraceMemoryState> getViewState(long snap, Address address) {
TraceViewportSpanIterator spit = new TraceViewportSpanIterator(trace, snap);
while (spit.hasNext()) {
Range<Long> span = spit.next();
TraceMemoryState state = getState(span.upperEndpoint(), address);
switch (state) {
case KNOWN:
case ERROR:
return Map.entry(span.upperEndpoint(), state);
default: // fall through
}
// Only the snap with the schedule specified gets the source snap's states
if (span.upperEndpoint() - span.lowerEndpoint() > 0) {
return Map.entry(snap, TraceMemoryState.UNKNOWN);
}
}
return Map.entry(snap, TraceMemoryState.UNKNOWN);
}
@Override
public Entry<TraceAddressSnapRange, TraceMemoryState> getMostRecentStateEntry(long snap,
Address address) {
@ -383,6 +404,22 @@ public class DBTraceMemorySpace implements Unfinished, TraceMemorySpace, DBTrace
TraceAddressSnapRangeQuery.mostRecent(address, snap)).firstEntry();
}
@Override
public Entry<TraceAddressSnapRange, TraceMemoryState> getViewMostRecentStateEntry(long snap,
Address address) {
TraceViewportSpanIterator spit = new TraceViewportSpanIterator(trace, snap);
while (spit.hasNext()) {
Range<Long> span = spit.next();
Entry<TraceAddressSnapRange, TraceMemoryState> entry =
stateMapSpace.reduce(TraceAddressSnapRangeQuery.mostRecent(address, span))
.firstEntry();
if (entry != null) {
return entry;
}
}
return null;
}
@Override
public AddressSetView getAddressesWithState(long snap, Predicate<TraceMemoryState> predicate) {
return new DBTraceAddressSnapRangePropertyMapAddressSetView<>(space, lock,
@ -658,6 +695,58 @@ public class DBTraceMemorySpace implements Unfinished, TraceMemorySpace, DBTrace
return result;
}
@Override
public int getViewBytes(long snap, Address start, ByteBuffer buf) {
AddressRange toRead;
int len = MathUtilities.unsignedMin(buf.remaining(),
start.getAddressSpace().getMaxAddress().subtract(start) + 1);
try {
toRead = new AddressRangeImpl(start, len);
}
catch (AddressOverflowException e) {
throw new AssertionError(e);
}
Map<AddressRange, Long> sources = new TreeMap<>();
AddressSet remains = new AddressSet(toRead);
TraceViewportSpanIterator spit = new TraceViewportSpanIterator(trace, snap);
spans: while (spit.hasNext()) {
Range<Long> span = spit.next();
Iterator<AddressRange> arit =
getAddressesWithState(span, s -> s == TraceMemoryState.KNOWN).iterator(start, true);
while (arit.hasNext()) {
AddressRange rng = arit.next();
if (rng.getMinAddress().compareTo(toRead.getMaxAddress()) > 0) {
break;
}
for (AddressRange sub : remains.intersectRange(rng.getMinAddress(),
rng.getMaxAddress())) {
sources.put(sub, span.upperEndpoint());
}
remains.delete(rng);
if (remains.isEmpty()) {
break spans;
}
}
}
int lim = buf.limit();
int pos = buf.position();
for (Map.Entry<AddressRange, Long> ent : sources.entrySet()) {
AddressRange rng = ent.getKey();
int offset = (int) rng.getMinAddress().subtract(toRead.getMinAddress());
int length = (int) rng.getLength();
buf.position(pos + offset);
buf.limit(pos + offset + length);
int read = getBytes(ent.getValue(), rng.getMinAddress(), buf);
if (read < length) {
break;
}
}
// We "got it all", even if there were gaps in "KNOWN"
buf.limit(lim);
buf.position(pos + len);
return len;
}
@Override
public Address findBytes(long snap, AddressRange range, ByteBuffer data, ByteBuffer mask,
boolean forward, TaskMonitor monitor) {

View file

@ -15,8 +15,12 @@
*/
package ghidra.trace.database.program;
import java.nio.ByteBuffer;
import java.util.*;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.RemovalNotification;
import com.google.common.collect.Iterators;
import com.google.common.collect.Range;
import generic.NestedIterator;
@ -32,17 +36,21 @@ import ghidra.program.model.symbol.Namespace;
import ghidra.program.model.symbol.SourceType;
import ghidra.program.model.util.CodeUnitInsertionException;
import ghidra.program.model.util.PropertyMap;
import ghidra.trace.database.DBTrace;
import ghidra.trace.database.listing.UndefinedDBTraceData;
import ghidra.trace.database.memory.DBTraceMemoryRegion;
import ghidra.trace.database.memory.DBTraceMemorySpace;
import ghidra.trace.database.symbol.DBTraceFunctionSymbol;
import ghidra.trace.model.Trace;
import ghidra.trace.model.listing.TraceCodeOperations;
import ghidra.trace.model.listing.TraceData;
import ghidra.trace.database.thread.DBTraceThread;
import ghidra.trace.model.*;
import ghidra.trace.model.listing.*;
import ghidra.trace.model.map.TracePropertyMap;
import ghidra.trace.model.program.TraceProgramView;
import ghidra.trace.model.program.TraceProgramViewListing;
import ghidra.trace.model.symbol.TraceFunctionSymbol;
import ghidra.util.IntersectionAddressSetView;
import ghidra.util.LockHold;
import ghidra.trace.util.*;
import ghidra.util.*;
import ghidra.util.AddressIteratorAdapter;
import ghidra.util.exception.*;
import ghidra.util.task.TaskMonitor;
@ -50,72 +58,19 @@ public abstract class AbstractDBTraceProgramViewListing implements TraceProgramV
public static final String[] EMPTY_STRING_ARRAY = new String[] {};
public static final String TREE_NAME = "Trace Tree";
protected static class WrappingCodeUnitIterator implements CodeUnitIterator {
protected final Iterator<? extends CodeUnit> it;
public WrappingCodeUnitIterator(Iterator<? extends CodeUnit> it) {
this.it = it;
protected class DBTraceProgramViewUndefinedData extends UndefinedDBTraceData {
public DBTraceProgramViewUndefinedData(DBTrace trace, long snap, Address address,
DBTraceThread thread, int frameLevel) {
super(trace, snap, address, thread, frameLevel);
}
@Override
public Iterator<CodeUnit> iterator() {
return this;
}
@Override
public boolean hasNext() {
return it.hasNext();
}
@Override
public CodeUnit next() {
return it.next();
}
}
protected static class WrappingInstructionIterator implements InstructionIterator {
protected final Iterator<? extends Instruction> it;
public WrappingInstructionIterator(Iterator<? extends Instruction> it) {
this.it = it;
}
@Override
public Iterator<Instruction> iterator() {
return this;
}
@Override
public boolean hasNext() {
return it.hasNext();
}
@Override
public Instruction next() {
return it.next();
}
}
protected static class WrappingDataIterator implements DataIterator {
protected final Iterator<? extends Data> it;
public WrappingDataIterator(Iterator<? extends Data> it) {
this.it = it;
}
@Override
public Iterator<Data> iterator() {
return this;
}
@Override
public boolean hasNext() {
return it.hasNext();
}
@Override
public Data next() {
return it.next();
public int getBytes(ByteBuffer buffer, int addressOffset) {
DBTraceMemorySpace mem = trace.getMemoryManager().get(this, false);
if (mem == null) {
// TODO: 0-fill instead? Will need to check memory space bounds.
}
return mem.getViewBytes(program.snap, address.add(addressOffset), buffer);
}
}
@ -126,6 +81,14 @@ public abstract class AbstractDBTraceProgramViewListing implements TraceProgramV
protected final Map<DBTraceMemoryRegion, DBTraceProgramViewFragment> fragmentsByRegion =
new HashMap<>();
protected final Map<AddressSnap, UndefinedDBTraceData> undefinedCache =
CacheBuilder.newBuilder()
.removalListener(
this::undefinedRemovedFromCache)
.weakValues()
.build()
.asMap();
public AbstractDBTraceProgramViewListing(DBTraceProgramView program,
TraceCodeOperations codeOperations) {
this.program = program;
@ -134,6 +97,11 @@ public abstract class AbstractDBTraceProgramViewListing implements TraceProgramV
this.rootModule = new DBTraceProgramViewRootModule(this);
}
private void undefinedRemovedFromCache(
RemovalNotification<AddressSnap, UndefinedDBTraceData> rn) {
// Do nothing
}
@Override
public TraceProgramView getProgram() {
return program;
@ -149,32 +117,241 @@ public abstract class AbstractDBTraceProgramViewListing implements TraceProgramV
return program.snap;
}
protected <T extends TraceCodeUnit> T getTopCode(
java.util.function.Function<Long, T> codeFunc) {
return program.viewport.getTop(s -> {
T cu = codeFunc.apply(s);
if (cu != null && program.isCodeVisible(cu, cu.getLifespan())) {
return cu;
}
return null;
});
}
protected TraceCodeUnit orUndef(TraceCodeUnit cu, Address address) {
if (cu != null) {
return cu;
}
return doCreateUndefinedUnit(address);
}
protected TraceData orUndefData(TraceData data, Address address) {
return (TraceData) orUndef(data, address);
}
protected TraceData reqUndef(TraceCodeUnit cu, Address address) {
if (cu != null) {
return null;
}
return doCreateUndefinedUnit(address);
}
protected <T> T next(Iterator<T> it) {
if (it.hasNext()) {
return it.next();
}
return null;
}
protected Comparator<CodeUnit> getUnitComparator(boolean forward) {
return forward
? (u1, u2) -> u1.getMinAddress().compareTo(u2.getMinAddress())
: (u1, u2) -> -u1.getMinAddress().compareTo(u2.getMinAddress());
}
protected <T extends TraceCodeUnit> Iterator<T> getTopCodeIterator(
java.util.function.Function<Long, Iterator<T>> iterFunc, boolean forward) {
return Iterators.filter(
program.viewport.mergedIterator(iterFunc, getUnitComparator(forward)),
cu -> program.isCodeVisible(cu, cu.getLifespan()));
}
protected AddressSet getAddressSet(Address start, boolean forward) {
AddressFactory factory = program.getAddressFactory();
AddressSet all = program.allAddresses;
return forward
? factory.getAddressSet(start, all.getMaxAddress())
: factory.getAddressSet(all.getMinAddress(), start);
}
protected UndefinedDBTraceData doCreateUndefinedUnit(Address address) {
return undefinedCache.computeIfAbsent(new DefaultAddressSnap(address, program.snap),
ot -> new DBTraceProgramViewUndefinedData(program.trace, program.snap, address, null,
0));
}
protected Iterator<? extends TraceInstruction> getInstructionIterator(Address start,
boolean forward) {
return getTopCodeIterator(
s -> codeOperations.instructions().get(s, start, forward).iterator(), forward);
}
protected Iterator<? extends TraceInstruction> getInstructionIterator(AddressSetView set,
boolean forward) {
return getTopCodeIterator(
s -> codeOperations.instructions().get(s, set, forward).iterator(), forward);
}
protected Iterator<? extends TraceInstruction> getInstructionIterator(boolean forward) {
return getTopCodeIterator(
s -> codeOperations.instructions().get(s, forward).iterator(), forward);
}
protected Iterator<? extends TraceData> getDefinedDataIterator(Address start, boolean forward) {
return getTopCodeIterator(
s -> codeOperations.definedData().get(s, start, forward).iterator(), forward);
}
protected Iterator<? extends TraceData> getDefinedDataIterator(AddressSetView set,
boolean forward) {
return getTopCodeIterator(
s -> codeOperations.definedData().get(s, set, forward).iterator(), forward);
}
protected Iterator<? extends TraceData> getDefinedDataIterator(boolean forward) {
return getTopCodeIterator(
s -> codeOperations.definedData().get(s, forward).iterator(), forward);
}
protected Iterator<? extends TraceCodeUnit> getDefinedUnitIterator(Address start,
boolean forward) {
return getTopCodeIterator(
s -> codeOperations.definedUnits().get(s, start, forward).iterator(), forward);
}
protected Iterator<? extends TraceCodeUnit> getDefinedUnitIterator(AddressSetView set,
boolean forward) {
return getTopCodeIterator(
s -> codeOperations.definedUnits().get(s, set, forward).iterator(), forward);
}
protected Iterator<TraceData> getUndefinedDataIterator(Address start, boolean forward) {
AddressSet set = getAddressSet(start, forward);
Address defStart = start;
if (forward) {
CodeUnit defUnit =
getTopCode(s -> codeOperations.definedUnits().getContaining(s, start));
if (defUnit != null) {
defStart = defUnit.getMinAddress();
}
}
Iterator<AddressRange> defIter = Iterators.transform(
getDefinedUnitIterator(defStart, forward), u -> u.getRange());
AddressRangeIterator undefIter =
AddressRangeIterators.subtract(set.iterator(forward), defIter, start, forward);
AddressIteratorAdapter undefAddrIter = new AddressIteratorAdapter(undefIter, forward);
return Iterators.transform(undefAddrIter.iterator(), a -> doCreateUndefinedUnit(a));
}
protected AddressRangeIterator getUndefinedRangeIterator(AddressSetView set, boolean forward) {
Iterator<AddressRange> defIter = Iterators.transform(
getDefinedUnitIterator(set, forward), u -> u.getRange());
return AddressRangeIterators.subtract(set.iterator(forward), defIter,
forward ? set.getMinAddress() : set.getMaxAddress(), forward);
}
protected boolean isUndefinedRange(long snap, AddressRange range) {
if (codeOperations.undefinedData().coversRange(Range.singleton(snap), range)) {
return true;
}
TraceCodeUnit minUnit =
codeOperations.definedUnits().getContaining(snap, range.getMinAddress());
if (minUnit != null && program.isCodeVisible(minUnit, minUnit.getLifespan())) {
return false;
}
TraceCodeUnit maxUnit =
codeOperations.definedUnits().getContaining(snap, range.getMaxAddress());
if (maxUnit != null && program.isCodeVisible(maxUnit, maxUnit.getLifespan())) {
return false;
}
return true;
}
protected Iterator<TraceData> getUndefinedDataIterator(AddressSetView set, boolean forward) {
AddressRangeIterator undefIter = getUndefinedRangeIterator(set, forward);
AddressIteratorAdapter undefAddrIter = new AddressIteratorAdapter(undefIter, forward);
return Iterators.transform(undefAddrIter.iterator(), a -> doCreateUndefinedUnit(a));
}
protected Iterator<TraceCodeUnit> getCodeUnitIterator(AddressSetView set, boolean forward) {
return new MergeSortingIterator<>(List.of(
getDefinedUnitIterator(set, forward),
getUndefinedDataIterator(set, forward)),
getUnitComparator(forward));
}
protected Iterator<TraceCodeUnit> getCodeUnitIterator(Address start, boolean forward) {
return new MergeSortingIterator<>(List.of(
getDefinedUnitIterator(start, forward),
getUndefinedDataIterator(start, forward)),
getUnitComparator(forward));
}
protected Iterator<TraceCodeUnit> getCodeUnitIterator(boolean forward) {
AddressSetView set = program.allAddresses;
return getCodeUnitIterator(forward ? set.getMinAddress() : set.getMaxAddress(), forward);
}
protected Iterator<TraceData> getDataIterator(AddressSetView set, boolean forward) {
return new MergeSortingIterator<>(List.of(
getDefinedDataIterator(set, forward),
getUndefinedDataIterator(set, forward)),
getUnitComparator(forward));
}
protected Iterator<TraceData> getDataIterator(Address start, boolean forward) {
return new MergeSortingIterator<>(List.of(
getDefinedDataIterator(start, forward),
getUndefinedDataIterator(start, forward)),
getUnitComparator(forward));
}
protected Iterator<TraceData> getDataIterator(boolean forward) {
AddressSetView set = program.allAddresses;
return getDataIterator(forward ? set.getMinAddress() : set.getMaxAddress(), forward);
}
@Override
public CodeUnit getCodeUnitAt(Address addr) {
return codeOperations.codeUnits().getAt(program.snap, addr);
CodeUnit containing = getCodeUnitContaining(addr);
if (containing == null) {
return doCreateUndefinedUnit(addr);
}
if (!containing.getMinAddress().equals(addr)) {
return null;
}
return containing;
}
@Override
public CodeUnit getCodeUnitContaining(Address addr) {
return codeOperations.codeUnits().getContaining(program.snap, addr);
try (LockHold hold = program.trace.lockRead()) {
return orUndef(getTopCode(s -> codeOperations.definedUnits().getContaining(s, addr)),
addr);
}
}
@Override
public CodeUnit getCodeUnitAfter(Address addr) {
return codeOperations.codeUnits().getAfter(program.snap, addr);
addr = addr.next();
try (LockHold hold = program.trace.lockRead()) {
return addr == null ? null : next(getCodeUnitIterator(addr, true));
}
}
@Override
public CodeUnit getCodeUnitBefore(Address addr) {
return codeOperations.codeUnits().getBefore(program.snap, addr);
addr = addr.previous();
try (LockHold hold = program.trace.lockRead()) {
return addr == null ? null : next(getCodeUnitIterator(addr, false));
}
}
@Override
public CodeUnitIterator getCodeUnitIterator(String property, boolean forward) {
// HACK
if (CodeUnit.INSTRUCTION_PROPERTY.equals(property)) {
return new WrappingCodeUnitIterator(
codeOperations.instructions().get(program.snap, forward).iterator());
return new WrappingCodeUnitIterator(getInstructionIterator(forward));
}
// TODO: Other "special" property types
@ -184,28 +361,19 @@ public abstract class AbstractDBTraceProgramViewListing implements TraceProgramV
if (map == null) {
return new WrappingCodeUnitIterator(Collections.emptyIterator());
}
// TODO: The property map doesn't heed forking.
return new WrappingCodeUnitIterator(NestedIterator.start(
map.getAddressSetView(Range.singleton(program.snap)).iterator(forward),
rng -> program.trace.getCodeManager()
.codeUnits()
.get(program.snap, rng, forward)
.iterator()));
}
protected static AddressRange fixRange(AddressRange range, Address start, boolean forward) {
if (!range.contains(start)) {
return range;
}
return forward ? new AddressRangeImpl(start, range.getMaxAddress())
: new AddressRangeImpl(range.getMinAddress(), start);
rng -> getTopCodeIterator(
s -> codeOperations.codeUnits().get(s, rng, forward).iterator(),
forward)));
}
@Override
public CodeUnitIterator getCodeUnitIterator(String property, Address addr, boolean forward) {
// HACK
if (CodeUnit.INSTRUCTION_PROPERTY.equals(property)) {
return new WrappingCodeUnitIterator(
codeOperations.instructions().get(program.snap, addr, forward).iterator());
return new WrappingCodeUnitIterator(getInstructionIterator(addr, forward));
}
// TODO: Other "special" property types
@ -215,12 +383,12 @@ public abstract class AbstractDBTraceProgramViewListing implements TraceProgramV
if (map == null) {
return new WrappingCodeUnitIterator(Collections.emptyIterator());
}
// TODO: The property map doesn't heed forking.
return new WrappingCodeUnitIterator(NestedIterator.start(
map.getAddressSetView(Range.singleton(program.snap)).iterator(addr, forward),
rng -> program.trace.getCodeManager()
.codeUnits()
.get(program.snap, fixRange(rng, addr, forward), forward)
.iterator()));
rng -> getTopCodeIterator(
s -> codeOperations.codeUnits().get(s, rng, forward).iterator(),
forward)));
}
@Override
@ -228,8 +396,7 @@ public abstract class AbstractDBTraceProgramViewListing implements TraceProgramV
boolean forward) {
// HACK
if (CodeUnit.INSTRUCTION_PROPERTY.equals(property)) {
return new WrappingCodeUnitIterator(
codeOperations.instructions().get(program.snap, addrSet, forward).iterator());
return new WrappingCodeUnitIterator(getInstructionIterator(addrSet, forward));
}
// TODO: Other "special" property types
@ -239,13 +406,13 @@ public abstract class AbstractDBTraceProgramViewListing implements TraceProgramV
if (map == null) {
return new WrappingCodeUnitIterator(Collections.emptyIterator());
}
// TODO: The property map doesn't heed forking.
return new WrappingCodeUnitIterator(NestedIterator.start(
new IntersectionAddressSetView(map.getAddressSetView(Range.singleton(program.snap)),
addrSet).iterator(forward),
rng -> program.trace.getCodeManager()
.codeUnits()
.get(program.snap, rng, forward)
.iterator()));
rng -> getTopCodeIterator(
s -> codeOperations.codeUnits().get(s, rng, forward).iterator(),
forward)));
}
@Override
@ -269,7 +436,10 @@ public abstract class AbstractDBTraceProgramViewListing implements TraceProgramV
@Override
public String getComment(int commentType, Address address) {
return program.trace.getCommentAdapter().getComment(program.snap, address, commentType);
try (LockHold hold = program.trace.lockRead()) {
return program.viewport.getTop(
s -> program.trace.getCommentAdapter().getComment(s, address, commentType));
}
}
@Override
@ -281,190 +451,227 @@ public abstract class AbstractDBTraceProgramViewListing implements TraceProgramV
@Override
public CodeUnitIterator getCodeUnits(boolean forward) {
return new WrappingCodeUnitIterator(
codeOperations.codeUnits().get(program.snap, forward).iterator());
return new WrappingCodeUnitIterator(getCodeUnitIterator(forward));
}
@Override
public CodeUnitIterator getCodeUnits(Address start, boolean forward) {
return new WrappingCodeUnitIterator(
codeOperations.codeUnits().get(program.snap, start, forward).iterator());
return new WrappingCodeUnitIterator(getCodeUnitIterator(start, forward));
}
@Override
public CodeUnitIterator getCodeUnits(AddressSetView addressSet, boolean forward) {
return new WrappingCodeUnitIterator(
codeOperations.codeUnits().get(program.snap, addressSet, forward).iterator());
return new WrappingCodeUnitIterator(getCodeUnitIterator(addressSet, forward));
}
@Override
public Instruction getInstructionAt(Address addr) {
return codeOperations.instructions().getAt(program.snap, addr);
try (LockHold hold = program.trace.lockRead()) {
return getTopCode(s -> codeOperations.instructions().getAt(s, addr));
}
}
@Override
public Instruction getInstructionContaining(Address addr) {
return codeOperations.instructions().getContaining(program.snap, addr);
try (LockHold hold = program.trace.lockRead()) {
return getTopCode(s -> codeOperations.instructions().getContaining(s, addr));
}
}
@Override
public Instruction getInstructionAfter(Address addr) {
return codeOperations.instructions().getAfter(program.snap, addr);
addr = addr.next();
try (LockHold hold = program.trace.lockRead()) {
return addr == null ? null : next(getInstructionIterator(addr, true));
}
}
@Override
public Instruction getInstructionBefore(Address addr) {
return codeOperations.instructions().getBefore(program.snap, addr);
addr = addr.previous();
try (LockHold hold = program.trace.lockRead()) {
return addr == null ? null : next(getInstructionIterator(addr, false));
}
}
@Override
public InstructionIterator getInstructions(boolean forward) {
return new WrappingInstructionIterator(
codeOperations.instructions().get(program.snap, forward).iterator());
return new WrappingInstructionIterator(getInstructionIterator(forward));
}
@Override
public InstructionIterator getInstructions(Address start, boolean forward) {
return new WrappingInstructionIterator(
codeOperations.instructions().get(program.snap, start, forward).iterator());
return new WrappingInstructionIterator(getInstructionIterator(start, forward));
}
@Override
public InstructionIterator getInstructions(AddressSetView addressSet, boolean forward) {
return new WrappingInstructionIterator(
codeOperations.instructions().get(program.snap, addressSet, forward).iterator());
return new WrappingInstructionIterator(getInstructionIterator(addressSet, forward));
}
@Override
public Data getDataAt(Address addr) {
return codeOperations.data().getAt(program.snap, addr);
CodeUnit containing = getCodeUnitContaining(addr);
if (containing == null) {
return doCreateUndefinedUnit(addr);
}
if (!(containing instanceof Data)) {
return null;
}
if (!containing.getMinAddress().equals(addr)) {
return null;
}
return (Data) containing;
}
@Override
public Data getDataContaining(Address addr) {
return codeOperations.data().getContaining(program.snap, addr);
CodeUnit cu = getCodeUnitContaining(addr);
if (cu instanceof Data) {
return (Data) cu;
}
return null;
}
@Override
public Data getDataAfter(Address addr) {
return codeOperations.data().getAfter(program.snap, addr);
addr = addr.next();
try (LockHold hold = program.trace.lockRead()) {
return addr == null ? null : next(getDataIterator(addr, true));
}
}
@Override
public Data getDataBefore(Address addr) {
return codeOperations.data().getBefore(program.snap, addr);
addr = addr.previous();
try (LockHold hold = program.trace.lockRead()) {
return addr == null ? null : next(getDataIterator(addr, false));
}
}
@Override
public DataIterator getData(boolean forward) {
return new WrappingDataIterator(
codeOperations.data().get(program.snap, forward).iterator());
return new WrappingDataIterator(getDataIterator(forward));
}
@Override
public DataIterator getData(Address start, boolean forward) {
return new WrappingDataIterator(
codeOperations.data().get(program.snap, start, forward).iterator());
return new WrappingDataIterator(getDataIterator(start, forward));
}
@Override
public DataIterator getData(AddressSetView addressSet, boolean forward) {
return new WrappingDataIterator(
codeOperations.data().get(program.snap, addressSet, forward).iterator());
return new WrappingDataIterator(getDataIterator(addressSet, forward));
}
@Override
public Data getDefinedDataAt(Address addr) {
return codeOperations.definedData().getAt(program.snap, addr);
try (LockHold hold = program.trace.lockRead()) {
return getTopCode(s -> codeOperations.definedData().getAt(s, addr));
}
}
@Override
public Data getDefinedDataContaining(Address addr) {
return codeOperations.definedData().getContaining(program.snap, addr);
try (LockHold hold = program.trace.lockRead()) {
return getTopCode(s -> codeOperations.definedData().getContaining(s, addr));
}
}
@Override
public Data getDefinedDataAfter(Address addr) {
return codeOperations.definedData().getAfter(program.snap, addr);
addr = addr.next();
try (LockHold hold = program.trace.lockRead()) {
return addr == null ? null : next(getDefinedDataIterator(addr, true));
}
}
@Override
public Data getDefinedDataBefore(Address addr) {
return codeOperations.definedData().getBefore(program.snap, addr);
addr = addr.previous();
try (LockHold hold = program.trace.lockRead()) {
return addr == null ? null : next(getDefinedDataIterator(addr, false));
}
}
@Override
public DataIterator getDefinedData(boolean forward) {
return new WrappingDataIterator(
codeOperations.definedData().get(program.snap, forward).iterator());
return new WrappingDataIterator(getDefinedDataIterator(forward));
}
@Override
public DataIterator getDefinedData(Address start, boolean forward) {
return new WrappingDataIterator(
codeOperations.definedData().get(program.snap, start, forward).iterator());
return new WrappingDataIterator(getDefinedDataIterator(start, forward));
}
@Override
public DataIterator getDefinedData(AddressSetView addressSet, boolean forward) {
return new WrappingDataIterator(
codeOperations.definedData().get(program.snap, addressSet, forward).iterator());
return new WrappingDataIterator(getDefinedDataIterator(addressSet, forward));
}
@Override
public Data getUndefinedDataAt(Address addr) {
return codeOperations.undefinedData().getAt(program.snap, addr);
try (LockHold hold = program.trace.lockRead()) {
return reqUndef(getTopCode(s -> codeOperations.definedUnits().getContaining(s, addr)),
addr);
}
}
@Override
public Data getUndefinedDataAfter(Address addr, TaskMonitor monitor) {
return codeOperations.undefinedData().getAfter(program.snap, addr);
addr = addr.next();
try (LockHold hold = program.trace.lockRead()) {
return addr == null ? null : next(getUndefinedDataIterator(addr, true));
}
}
@Override
public Data getUndefinedDataBefore(Address addr, TaskMonitor monitor) {
return codeOperations.undefinedData().getBefore(program.snap, addr);
addr = addr.previous();
try (LockHold hold = program.trace.lockRead()) {
return addr == null ? null : next(getUndefinedDataIterator(addr, false));
}
}
@Override
public Data getFirstUndefinedData(AddressSetView addressSet, TaskMonitor monitor) {
try (LockHold hold = program.trace.lockRead()) {
for (TraceData u : codeOperations.undefinedData().get(program.snap, addressSet, true)) {
return u;
}
return null;
return next(getUndefinedDataIterator(addressSet, true));
}
}
/**
* {@inheritDoc}
*
* @implNote This could technically use a (lazy) view; however, to be consistent with
* expectations established by {@link ProgramDB}, it constructs the actual set, and
* permits cancellation by the monitor.
* @implNote This could maybe use a (lazy) view; however, to be consistent with expectations
* established by {@link ProgramDB}, it constructs the actual set, and permits
* cancellation by the monitor.
*/
@Override
public AddressSet getUndefinedRanges(AddressSetView set, boolean initializedMemoryOnly,
TaskMonitor monitor) throws CancelledException {
AddressSet result = new AddressSet();
for (AddressRange range : set) {
for (AddressRange und : codeOperations.undefinedData()
.getAddressSetView(program.snap, range)) {
monitor.checkCanceled();
result.add(und.intersect(range));
}
for (AddressRange range : getUndefinedRangeIterator(set, true)) {
result.add(range);
monitor.checkCanceled();
}
return result;
}
@Override
public CodeUnit getDefinedCodeUnitAfter(Address addr) {
return codeOperations.definedUnits().getAfter(program.snap, addr);
addr = addr.next();
try (LockHold hold = program.trace.lockRead()) {
return next(getDefinedUnitIterator(addr, true));
}
}
@Override
public CodeUnit getDefinedCodeUnitBefore(Address addr) {
return codeOperations.definedUnits().getBefore(program.snap, addr);
addr = addr.previous();
try (LockHold hold = program.trace.lockRead()) {
return next(getDefinedUnitIterator(addr, false));
}
}
@Override
@ -561,8 +768,8 @@ public abstract class AbstractDBTraceProgramViewListing implements TraceProgramV
@Override
public ProgramFragment getFragment(String treeName, Address addr) {
DBTraceMemoryRegion region =
program.trace.getMemoryManager().getRegionContaining(program.snap, addr);
DBTraceMemoryRegion region = program.memory.getTopRegion(
s -> program.trace.getMemoryManager().getRegionContaining(s, addr));
if (region == null) {
return null;
}
@ -580,8 +787,8 @@ public abstract class AbstractDBTraceProgramViewListing implements TraceProgramV
@Override
public ProgramFragment getFragment(String treeName, String name) {
DBTraceMemoryRegion region =
program.trace.getMemoryManager().getLiveRegionByPath(program.snap, name);
DBTraceMemoryRegion region = program.memory.getTopRegion(
s -> program.trace.getMemoryManager().getLiveRegionByPath(s, name));
if (region == null) {
return null;
}
@ -648,8 +855,8 @@ public abstract class AbstractDBTraceProgramViewListing implements TraceProgramV
@Override
public long getNumInstructions() {
// TODO: See getNumCodeUnits
return Long.MAX_VALUE;
// TODO: See getNumCodeUnits... Why was this Long.MAX_VALUE before?
return codeOperations.instructions().size();
}
@Override

View file

@ -18,7 +18,6 @@ package ghidra.trace.database.program;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.*;
import com.google.common.cache.RemovalNotification;
@ -31,12 +30,14 @@ import ghidra.trace.database.memory.*;
import ghidra.trace.model.Trace;
import ghidra.trace.model.program.TraceProgramView;
import ghidra.trace.model.program.TraceProgramViewMemory;
import ghidra.trace.util.MemoryAdapter;
import ghidra.util.MathUtilities;
import ghidra.util.exception.CancelledException;
import ghidra.util.exception.NotFoundException;
import ghidra.util.task.TaskMonitor;
public abstract class AbstractDBTraceProgramViewMemory implements TraceProgramViewMemory {
public abstract class AbstractDBTraceProgramViewMemory
implements TraceProgramViewMemory, MemoryAdapter {
protected final DBTraceProgramView program;
protected final DBTraceMemoryManager memoryManager;
@ -95,7 +96,7 @@ public abstract class AbstractDBTraceProgramViewMemory implements TraceProgramVi
public AddressSetView getExecuteSet() {
AddressSet result = new AddressSet();
for (DBTraceMemoryRegion region : memoryManager.getRegionsInternal()) {
if (!region.isExecute()) {
if (!region.isExecute() || !program.isRegionVisible(region, region.getLifespan())) {
continue;
}
result.add(region.getRange());
@ -247,6 +248,8 @@ public abstract class AbstractDBTraceProgramViewMemory implements TraceProgramVi
if (space == null) {
continue;
}
// TODO: findBytes must heed fork, or there should exist a variant that does....
// Lest I have to implement the forked search here.
Address found =
space.findBytes(snap, range, bufBytes, bufMasks, forward, monitor);
if (found != null) {
@ -265,11 +268,6 @@ public abstract class AbstractDBTraceProgramViewMemory implements TraceProgramVi
return block.getByte(addr);
}
@Override
public int getBytes(Address addr, byte[] dest) throws MemoryAccessException {
return getBytes(addr, dest, 0, dest.length);
}
@Override
public int getBytes(Address addr, byte[] dest, int destIndex, int size)
throws MemoryAccessException {
@ -283,109 +281,6 @@ public abstract class AbstractDBTraceProgramViewMemory implements TraceProgramVi
return block.getBytes(addr, dest, destIndex, size);
}
protected ByteBuffer mustRead(Address addr, int length, boolean bigEndian)
throws MemoryAccessException {
ByteBuffer buf = ByteBuffer.allocate(length);
if (getBytes(addr, buf.array()) != length) {
throw new MemoryAccessException();
}
buf.order(bigEndian ? ByteOrder.BIG_ENDIAN : ByteOrder.LITTLE_ENDIAN);
return buf;
}
@Override
public short getShort(Address addr) throws MemoryAccessException {
return mustRead(addr, Short.BYTES, true).getShort(0);
}
@Override
public short getShort(Address addr, boolean bigEndian) throws MemoryAccessException {
return mustRead(addr, Short.BYTES, bigEndian).getShort(0);
}
@Override
public int getShorts(Address addr, short[] dest) throws MemoryAccessException {
return getShorts(addr, dest, 0, dest.length, true);
}
@Override
public int getShorts(Address addr, short[] dest, int dIndex, int nElem)
throws MemoryAccessException {
return getShorts(addr, dest, dIndex, nElem, true);
}
@Override
public int getShorts(Address addr, short[] dest, int dIndex, int nElem, boolean bigEndian)
throws MemoryAccessException {
ByteBuffer buf = ByteBuffer.allocate(Short.BYTES * nElem);
int got = getBytes(addr, buf.array()) / Short.BYTES;
buf.order(bigEndian ? ByteOrder.BIG_ENDIAN : ByteOrder.LITTLE_ENDIAN);
buf.asShortBuffer().get(dest, dIndex, got);
return got;
}
@Override
public int getInt(Address addr) throws MemoryAccessException {
return mustRead(addr, Integer.BYTES, true).getInt(0);
}
@Override
public int getInt(Address addr, boolean bigEndian) throws MemoryAccessException {
return mustRead(addr, Integer.BYTES, bigEndian).getInt(0);
}
@Override
public int getInts(Address addr, int[] dest) throws MemoryAccessException {
return getInts(addr, dest, 0, dest.length, true);
}
@Override
public int getInts(Address addr, int[] dest, int dIndex, int nElem)
throws MemoryAccessException {
return getInts(addr, dest, dIndex, nElem, true);
}
@Override
public int getInts(Address addr, int[] dest, int dIndex, int nElem, boolean bigEndian)
throws MemoryAccessException {
ByteBuffer buf = ByteBuffer.allocate(Integer.BYTES * nElem);
int got = getBytes(addr, buf.array()) / Integer.BYTES;
buf.order(bigEndian ? ByteOrder.BIG_ENDIAN : ByteOrder.LITTLE_ENDIAN);
buf.asIntBuffer().get(dest, dIndex, got);
return got;
}
@Override
public long getLong(Address addr) throws MemoryAccessException {
return mustRead(addr, Long.BYTES, true).getLong(0);
}
@Override
public long getLong(Address addr, boolean bigEndian) throws MemoryAccessException {
return mustRead(addr, Long.BYTES, bigEndian).getLong(0);
}
@Override
public int getLongs(Address addr, long[] dest) throws MemoryAccessException {
return getLongs(addr, dest, 0, dest.length, true);
}
@Override
public int getLongs(Address addr, long[] dest, int dIndex, int nElem)
throws MemoryAccessException {
return getLongs(addr, dest, dIndex, nElem, true);
}
@Override
public int getLongs(Address addr, long[] dest, int dIndex, int nElem, boolean bigEndian)
throws MemoryAccessException {
ByteBuffer buf = ByteBuffer.allocate(Long.BYTES * nElem);
int got = getBytes(addr, buf.array()) / Long.BYTES;
buf.order(bigEndian ? ByteOrder.BIG_ENDIAN : ByteOrder.LITTLE_ENDIAN);
buf.asLongBuffer().get(dest, dIndex, got);
return got;
}
@Override
public void setByte(Address addr, byte value) throws MemoryAccessException {
DBTraceMemorySpace space = memoryManager.getMemorySpace(addr.getAddressSpace(), true);
@ -394,11 +289,6 @@ public abstract class AbstractDBTraceProgramViewMemory implements TraceProgramVi
}
}
@Override
public void setBytes(Address addr, byte[] source) throws MemoryAccessException {
setBytes(addr, source, 0, source.length);
}
@Override
public void setBytes(Address addr, byte[] source, int sIndex, int size)
throws MemoryAccessException {
@ -408,46 +298,6 @@ public abstract class AbstractDBTraceProgramViewMemory implements TraceProgramVi
}
}
@Override
public void setShort(Address addr, short value) throws MemoryAccessException {
setShort(addr, value, true);
}
@Override
public void setShort(Address addr, short value, boolean bigEndian)
throws MemoryAccessException {
ByteBuffer buf = ByteBuffer.allocate(Short.BYTES);
buf.order(bigEndian ? ByteOrder.BIG_ENDIAN : ByteOrder.LITTLE_ENDIAN);
buf.putShort(value);
setBytes(addr, buf.array());
}
@Override
public void setInt(Address addr, int value) throws MemoryAccessException {
setInt(addr, value, true);
}
@Override
public void setInt(Address addr, int value, boolean bigEndian) throws MemoryAccessException {
ByteBuffer buf = ByteBuffer.allocate(Integer.BYTES);
buf.order(bigEndian ? ByteOrder.BIG_ENDIAN : ByteOrder.LITTLE_ENDIAN);
buf.putInt(value);
setBytes(addr, buf.array());
}
@Override
public void setLong(Address addr, long value) throws MemoryAccessException {
setLong(addr, value, true);
}
@Override
public void setLong(Address addr, long value, boolean bigEndian) throws MemoryAccessException {
ByteBuffer buf = ByteBuffer.allocate(Long.BYTES);
buf.order(bigEndian ? ByteOrder.BIG_ENDIAN : ByteOrder.LITTLE_ENDIAN);
buf.putLong(value);
setBytes(addr, buf.array());
}
@Override
public FileBytes createFileBytes(String filename, long offset, long size, InputStream is,
TaskMonitor monitor) throws IOException, CancelledException {

View file

@ -17,8 +17,9 @@ package ghidra.trace.database.program;
import static ghidra.lifecycle.Unfinished.TODO;
import java.util.Collection;
import java.util.Collections;
import java.util.*;
import java.util.function.Function;
import java.util.function.Predicate;
import javax.help.UnsupportedOperationException;
@ -175,21 +176,42 @@ public abstract class AbstractDBTraceProgramViewReferenceManager implements Refe
dbRef.setPrimary(isPrimary);
}
protected boolean any(boolean noSpace, Predicate<Long> predicate) {
if (refs(false) == null) {
return noSpace;
}
for (long s : program.viewport.getOrderedSnaps()) {
if (predicate.test(s)) {
return true;
}
}
return false;
}
protected Collection<Reference> collect(
Function<Long, Collection<? extends Reference>> refFunc) {
if (refs(false) == null) {
return Collections.emptyList();
}
Set<Reference> result = new LinkedHashSet<>();
for (long s : program.viewport.getOrderedSnaps()) {
Collection<? extends Reference> from = refFunc.apply(s);
if (from != null) {
result.addAll(from);
}
}
return result;
}
@Override
public boolean hasFlowReferencesFrom(Address addr) {
if (refs(false) == null) {
return false;
}
return !refs.getFlowRefrencesFrom(program.snap, addr).isEmpty();
return any(false, s -> !refs.getFlowReferencesFrom(s, addr).isEmpty());
}
@Override
public Reference[] getFlowReferencesFrom(Address addr) {
Collection<? extends TraceReference> from = refs(false) == null
? Collections.emptyList()
: refs.getFlowRefrencesFrom(program.snap, addr);
// TODO: Requires two traversals. Not terrible for this size....
return from.toArray(new Reference[from.size()]);
Collection<Reference> result = collect(s -> refs.getFlowReferencesFrom(s, addr));
return result.toArray(new Reference[result.size()]);
}
@Override
@ -199,138 +221,151 @@ public abstract class AbstractDBTraceProgramViewReferenceManager implements Refe
@Override
public ReferenceIterator getReferencesTo(Address addr) {
Collection<? extends TraceReference> to = refs(false) == null
? Collections.emptyList()
: refs.getReferencesTo(program.snap, addr);
return new ReferenceIteratorAdapter(to.iterator());
Collection<Reference> result = collect(s -> refs.getReferencesTo(s, addr));
return new ReferenceIteratorAdapter(result.iterator());
}
protected Comparator<Reference> getReferenceFromComparator(boolean forward) {
return forward
? (r1, r2) -> r1.getFromAddress().compareTo(r2.getFromAddress())
: (r1, r2) -> -r1.getFromAddress().compareTo(r2.getFromAddress());
}
@Override
public ReferenceIterator getReferenceIterator(Address startAddr) {
Collection<? extends TraceReference> from = refs(false) == null
? Collections.emptyList()
: refs.getReferencesFrom(program.snap, startAddr);
return new ReferenceIteratorAdapter(from.iterator());
if (refs(false) == null) {
return new ReferenceIteratorAdapter(Collections.emptyIterator());
}
return new ReferenceIteratorAdapter(
program.viewport.mergedIterator(s -> refs.getReferencesFrom(s, startAddr).iterator(),
getReferenceFromComparator(true)));
}
@Override
public Reference getReference(Address fromAddr, Address toAddr, int opIndex) {
return refs(false) == null
? null
: refs.getReference(program.snap, fromAddr, toAddr, opIndex);
if (refs(false) == null) {
return null;
}
return program.viewport.getTop(s -> refs.getReference(s, fromAddr, toAddr, opIndex));
}
@Override
public Reference[] getReferencesFrom(Address addr) {
Collection<? extends TraceReference> from = refs(false) == null
? Collections.emptyList()
: refs.getReferencesFrom(program.snap, addr);
return from.toArray(new Reference[from.size()]);
Collection<Reference> result = collect(s -> refs.getReferencesFrom(s, addr));
return result.toArray(new Reference[result.size()]);
}
@Override
public Reference[] getReferencesFrom(Address fromAddr, int opIndex) {
Collection<? extends TraceReference> from = refs(false) == null
? Collections.emptyList()
: refs.getReferencesFrom(program.snap, fromAddr, opIndex);
return from.toArray(new Reference[from.size()]);
Collection<Reference> result = collect(s -> refs.getReferencesFrom(s, fromAddr, opIndex));
return result.toArray(new Reference[result.size()]);
}
@Override
public boolean hasReferencesFrom(Address fromAddr, int opIndex) {
return refs(false) == null
? false
: !refs.getReferencesFrom(program.snap, fromAddr, opIndex).isEmpty();
return any(false, s -> !refs.getReferencesFrom(s, fromAddr, opIndex).isEmpty());
}
@Override
public boolean hasReferencesFrom(Address fromAddr) {
return refs(false) == null
? false
: !refs.getReferencesFrom(program.snap, fromAddr).isEmpty();
return any(false, s -> !refs.getReferencesFrom(s, fromAddr).isEmpty());
}
@Override
public Reference getPrimaryReferenceFrom(Address addr, int opIndex) {
return refs(false) == null
? null
: refs.getPrimaryReferenceFrom(program.snap, addr, opIndex);
if (refs(false) == null) {
return null;
}
return program.viewport.getTop(s -> refs.getPrimaryReferenceFrom(s, addr, opIndex));
}
@Override
public AddressIterator getReferenceSourceIterator(Address startAddr, boolean forward) {
return refs(false) == null
? new EmptyAddressIterator()
: refs.getReferenceSources(Range.closed(program.snap, program.snap))
.getAddresses(startAddr, forward);
if (refs(false) == null) {
return new EmptyAddressIterator();
}
return program.viewport.unionedAddresses(
s -> refs.getReferenceSources(Range.singleton(s))).getAddresses(startAddr, forward);
}
@Override
public AddressIterator getReferenceSourceIterator(AddressSetView addrSet, boolean forward) {
return refs(false) == null
? new EmptyAddressIterator()
: new IntersectionAddressSetView(
refs.getReferenceSources(Range.closed(program.snap, program.snap)), addrSet)
.getAddresses(forward);
if (refs(false) == null) {
return new EmptyAddressIterator();
}
return new IntersectionAddressSetView(addrSet, program.viewport.unionedAddresses(
s -> refs.getReferenceSources(Range.singleton(s)))).getAddresses(forward);
}
@Override
public AddressIterator getReferenceDestinationIterator(Address startAddr, boolean forward) {
return refs(false) == null
? new EmptyAddressIterator()
: refs.getReferenceDestinations(Range.closed(program.snap, program.snap))
.getAddresses(startAddr, forward);
if (refs(false) == null) {
return new EmptyAddressIterator();
}
return program.viewport.unionedAddresses(
s -> refs.getReferenceDestinations(Range.singleton(s)))
.getAddresses(startAddr, forward);
}
@Override
public AddressIterator getReferenceDestinationIterator(AddressSetView addrSet,
boolean forward) {
return refs(false) == null
? new EmptyAddressIterator()
: new IntersectionAddressSetView(
refs.getReferenceDestinations(Range.closed(program.snap, program.snap)),
addrSet).getAddresses(forward);
if (refs(false) == null) {
return new EmptyAddressIterator();
}
return new IntersectionAddressSetView(addrSet, program.viewport.unionedAddresses(
s -> refs.getReferenceDestinations(Range.singleton(s)))).getAddresses(forward);
}
@Override
public int getReferenceCountTo(Address toAddr) {
return refs(false) == null
? 0
: refs.getReferenceCountTo(program.snap, toAddr);
if (refs(false) == null) {
return 0;
}
if (!program.viewport.isForked()) {
return refs.getReferenceCountTo(program.snap, toAddr);
}
return collect(s -> refs.getReferencesTo(s, toAddr)).size();
}
@Override
public int getReferenceCountFrom(Address fromAddr) {
return refs(false) == null
? 0
: refs.getReferenceCountFrom(program.snap, fromAddr);
if (refs(false) == null) {
return 0;
}
if (!program.viewport.isForked()) {
return refs.getReferenceCountFrom(program.snap, fromAddr);
}
return collect(s -> refs.getReferencesFrom(s, fromAddr)).size();
}
@Override
public int getReferenceDestinationCount() {
// TODO: It is unclear if the interface definition means to include unique addresses
// or also unique references
return refs(false) == null
? 0
: (int) refs.getReferenceDestinations(Range.closed(program.snap, program.snap))
.getNumAddresses();
if (refs(false) == null) {
return 0;
}
return (int) program.viewport
.unionedAddresses(s -> refs.getReferenceDestinations(Range.singleton(s)))
.getNumAddresses();
}
@Override
public int getReferenceSourceCount() {
// TODO: It is unclear if the interface definition means to include unique addresses
// or also unique references
return refs(false) == null
? 0
: (int) refs.getReferenceSources(Range.closed(program.snap, program.snap))
.getNumAddresses();
if (refs(false) == null) {
return 0;
}
return (int) program.viewport
.unionedAddresses(s -> refs.getReferenceSources(Range.singleton(s)))
.getNumAddresses();
}
@Override
public boolean hasReferencesTo(Address toAddr) {
return refs(false) == null
? false
: !refs.getReferencesTo(program.snap, toAddr).isEmpty();
return any(false, s -> !refs.getReferencesTo(s, toAddr).isEmpty());
}
@Override
@ -361,9 +396,11 @@ public abstract class AbstractDBTraceProgramViewReferenceManager implements Refe
/**
* Get the reference level for a given reference type
*
* <p>
* TODO: Why is this not a property of {@link RefType}, or a static method of
* {@link SymbolUtilities}?
*
* <p>
* Note that this was copy-pasted from {@code BigRefListV0}, and there's an exact copy also in
* {@code RefListV0}.
*
@ -389,11 +426,13 @@ public abstract class AbstractDBTraceProgramViewReferenceManager implements Refe
/**
* {@inheritDoc}
*
* <p>
* To clarify, "reference level" is a sort of priority assigned to each reference type. See,
* e.g., {@link SymbolUtilities#SUB_LEVEL}. Each is a byte constant, and greater values imply
* higher priority. This method returns the highest priority of any reference to the given
* address.
*
* <p>
* TODO: Track this in the database?
*/
@Override
@ -402,8 +441,10 @@ public abstract class AbstractDBTraceProgramViewReferenceManager implements Refe
return SymbolUtilities.UNK_LEVEL;
}
byte highest = SymbolUtilities.UNK_LEVEL;
for (TraceReference ref : refs.getReferencesTo(program.snap, toAddr)) {
highest = (byte) Math.max(highest, getRefLevel(ref.getReferenceType()));
for (long s : program.viewport.getOrderedSnaps()) {
for (TraceReference ref : refs.getReferencesTo(s, toAddr)) {
highest = (byte) Math.max(highest, getRefLevel(ref.getReferenceType()));
}
}
return highest;
}

View file

@ -17,8 +17,10 @@ package ghidra.trace.database.program;
import java.io.File;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.*;
import java.util.Map.Entry;
import java.util.function.BiFunction;
import org.apache.commons.lang3.tuple.Pair;
@ -34,6 +36,7 @@ import ghidra.program.model.address.*;
import ghidra.program.model.data.*;
import ghidra.program.model.lang.*;
import ghidra.program.model.listing.*;
import ghidra.program.model.mem.MemoryAccessException;
import ghidra.program.model.pcode.Varnode;
import ghidra.program.model.reloc.RelocationTable;
import ghidra.program.model.symbol.*;
@ -42,9 +45,9 @@ import ghidra.program.model.util.PropertyMapManager;
import ghidra.program.util.ChangeManager;
import ghidra.program.util.ProgramChangeRecord;
import ghidra.trace.database.DBTrace;
import ghidra.trace.database.listing.DBTraceCodeRegisterSpace;
import ghidra.trace.database.memory.DBTraceMemoryRegion;
import ghidra.trace.database.memory.DBTraceMemoryRegisterSpace;
import ghidra.trace.database.listing.*;
import ghidra.trace.database.memory.*;
import ghidra.trace.database.symbol.DBTraceFunctionSymbolView;
import ghidra.trace.model.Trace.*;
import ghidra.trace.model.TraceAddressSnapRange;
import ghidra.trace.model.TraceDomainObjectListener;
@ -53,26 +56,29 @@ import ghidra.trace.model.bookmark.TraceBookmarkType;
import ghidra.trace.model.data.TraceBasedDataTypeManager;
import ghidra.trace.model.listing.*;
import ghidra.trace.model.memory.TraceMemoryRegion;
import ghidra.trace.model.memory.TraceMemoryState;
import ghidra.trace.model.program.TraceProgramView;
import ghidra.trace.model.program.TraceProgramViewMemory;
import ghidra.trace.model.symbol.*;
import ghidra.trace.model.thread.TraceThread;
import ghidra.trace.util.TraceAddressSpace;
import ghidra.util.PairingIteratorMerger;
import ghidra.util.UniversalID;
import ghidra.trace.util.*;
import ghidra.trace.util.TraceTimeViewport.*;
import ghidra.util.*;
import ghidra.util.datastruct.WeakValueHashMap;
import ghidra.util.exception.CancelledException;
import ghidra.util.exception.DuplicateNameException;
import ghidra.util.task.TaskMonitor;
/**
* TODO
* A wrapper on a trace, which given a snap, implements the {@link Program} interface
*
* <p>
* NOTE: Calling {@link CodeUnit#getProgram()} from units contained in this view may not necessarily
* return this same view. If the code unit comes from a less-recent snap than the snap associated
* with this view, the view for that snap is returned instead.
*
* TODO: Unit tests for all of this
* <p>
* TODO: Unit tests for all of this.
*/
public class DBTraceProgramView implements TraceProgramView {
public static final int TIME_INTERVAL = 100;
@ -160,58 +166,6 @@ public class DBTraceProgramView implements TraceProgramView {
listenFor(TraceSymbolChangeType.DELETED, this::symbolDeleted);
}
protected DomainObjectEventQueues getEventQueues(TraceAddressSpace space) {
// TODO: Should there be views on other frames?
// IIRC, this was an abandoned experiment for "register listings"
TraceThread thread = space == null ? null : space.getThread();
if (thread == null) {
return eventQueues;
}
DBTraceProgramViewRegisters viewRegisters;
synchronized (regViewsByThread) {
viewRegisters = regViewsByThread.get(thread);
}
return viewRegisters == null ? null : viewRegisters.eventQueues;
}
protected DomainObjectEventQueues isVisible(TraceAddressSpace space,
TraceAddressSnapRange range) {
if (!range.getLifespan().contains(snap)) {
return null;
}
return getEventQueues(space);
}
protected DomainObjectEventQueues isVisible(TraceAddressSpace space, TraceCodeUnit cu) {
if (!cu.getLifespan().contains(snap)) {
return null;
}
return getEventQueues(space);
}
protected DomainObjectEventQueues isVisible(TraceAddressSpace space, TraceSymbol symbol) {
DomainObjectEventQueues queues = getEventQueues(space);
if (queues == null) {
return null;
}
if (symbol instanceof TraceVariableSymbol) {
TraceVariableSymbol var = (TraceVariableSymbol) symbol;
TraceFunctionSymbol func = var.getFunction();
if (func == null) {
return queues;
}
return func.getLifespan().contains(snap) ? queues : null;
}
if (!(symbol instanceof TraceSymbolWithLifespan)) {
return queues;
}
TraceSymbolWithLifespan symWl = (TraceSymbolWithLifespan) symbol;
if (!symWl.getLifespan().contains(snap)) {
return null;
}
return queues;
}
private void eventPassthrough(DomainObjectChangeRecord rec) {
fireEventAllViews(rec);
}
@ -227,13 +181,10 @@ public class DBTraceProgramView implements TraceProgramView {
}
private void bookmarkAdded(TraceAddressSpace space, TraceBookmark bm) {
DomainObjectEventQueues queues = getEventQueues(space);
DomainObjectEventQueues queues = isBookmarkVisible(space, bm);
if (queues == null) {
return;
}
if (!bm.getLifespan().contains(snap)) {
return;
}
fireBookmarkAdded(queues, bm);
}
@ -243,13 +194,10 @@ public class DBTraceProgramView implements TraceProgramView {
}
private void bookmarkChanged(TraceAddressSpace space, TraceBookmark bm) {
DomainObjectEventQueues queues = getEventQueues(space);
DomainObjectEventQueues queues = isBookmarkVisible(space, bm);
if (queues == null) {
return;
}
if (!bm.getLifespan().contains(snap)) {
return;
}
fireBookmarkChanged(queues, bm);
}
@ -259,14 +207,13 @@ public class DBTraceProgramView implements TraceProgramView {
}
private void bookmarkLifespanChanged(TraceAddressSpace space, TraceBookmark bm,
Range<Long> oldSpan,
Range<Long> newSpan) {
Range<Long> oldSpan, Range<Long> newSpan) {
DomainObjectEventQueues queues = getEventQueues(space);
if (queues == null) {
return;
}
boolean inOld = oldSpan.contains(snap);
boolean inNew = newSpan.contains(snap);
boolean inOld = isBookmarkVisible(bm, oldSpan);
boolean inNew = isBookmarkVisible(bm, newSpan);
if (inOld && !inNew) {
fireBookmarkRemoved(queues, bm);
}
@ -276,13 +223,10 @@ public class DBTraceProgramView implements TraceProgramView {
}
private void bookmarkDeleted(TraceAddressSpace space, TraceBookmark bm) {
DomainObjectEventQueues queues = getEventQueues(space);
DomainObjectEventQueues queues = isBookmarkVisible(space, bm);
if (queues == null) {
return;
}
if (!bm.getLifespan().contains(snap)) {
return;
}
fireBookmarkRemoved(queues, bm);
}
@ -317,7 +261,9 @@ public class DBTraceProgramView implements TraceProgramView {
private void codeAdded(TraceAddressSpace space, TraceAddressSnapRange range,
TraceCodeUnit oldIsNull, TraceCodeUnit added) {
// NOTE: Added code may be coalesced range. -added- is just first unit.
DomainObjectEventQueues queues = isVisible(space, range);
// TODO: The range may contain many units, so this could be broken down
DomainObjectEventQueues queues =
isCodeVisible(space, range) ? getEventQueues(space) : null;
if (queues == null) {
return;
}
@ -335,8 +281,8 @@ public class DBTraceProgramView implements TraceProgramView {
if (queues == null) {
return;
}
boolean inOld = oldSpan.contains(snap);
boolean inNew = newSpan.contains(snap);
boolean inOld = isCodeVisible(unit, oldSpan);
boolean inNew = isCodeVisible(unit, newSpan);
if (inOld && !inNew) {
fireCodeRemoved(queues, unit.getMinAddress(), unit.getMaxAddress(), unit);
}
@ -348,7 +294,7 @@ public class DBTraceProgramView implements TraceProgramView {
private void codeRemoved(TraceAddressSpace space, TraceAddressSnapRange range,
TraceCodeUnit removed, TraceCodeUnit newIsNull) {
// NOTE: Removed code may be coalesced range. -removed- is just first unit.
DomainObjectEventQueues queues = isVisible(space, range);
DomainObjectEventQueues queues = isCodeVisible(space, removed);
if (queues == null) {
return;
}
@ -373,6 +319,7 @@ public class DBTraceProgramView implements TraceProgramView {
private void codeDataTypeReplaced(TraceAddressSpace space, TraceAddressSnapRange range,
Long oldDataTypeID, Long newDataTypeID) {
// TODO??: "code" visibility check may not be necessary or advantageous
DomainObjectEventQueues queues = isVisible(space, range);
if (queues == null) {
return;
@ -383,7 +330,7 @@ public class DBTraceProgramView implements TraceProgramView {
private void compositeDataAdded(TraceAddressSpace space, TraceAddressSnapRange range,
TraceData oldIsNull, TraceData added) {
DomainObjectEventQueues queues = isVisible(space, range);
DomainObjectEventQueues queues = isCodeVisible(space, added);
if (queues == null) {
return;
}
@ -397,8 +344,8 @@ public class DBTraceProgramView implements TraceProgramView {
if (queues == null) {
return;
}
boolean inOld = oldSpan.contains(snap);
boolean inNew = newSpan.contains(snap);
boolean inOld = isCodeVisible(data, oldSpan);
boolean inNew = isCodeVisible(data, newSpan);
if (inOld && !inNew) {
queues.fireEvent(new ProgramChangeRecord(ChangeManager.DOCR_COMPOSITE_REMOVED,
data.getMinAddress(), data.getMaxAddress(), null, data, null));
@ -411,7 +358,7 @@ public class DBTraceProgramView implements TraceProgramView {
private void compositeDataRemoved(TraceAddressSpace space, TraceAddressSnapRange range,
TraceData removed, TraceData newIsNull) {
DomainObjectEventQueues queues = isVisible(space, range);
DomainObjectEventQueues queues = isCodeVisible(space, removed);
if (queues == null) {
return;
}
@ -473,7 +420,7 @@ public class DBTraceProgramView implements TraceProgramView {
private void functionChangedGeneric(TraceAddressSpace space, TraceFunctionSymbol function,
int type, int subType) {
DomainObjectEventQueues queues = isVisible(space, function);
DomainObjectEventQueues queues = isFunctionVisible(space, function);
if (queues == null) {
return;
}
@ -557,7 +504,7 @@ public class DBTraceProgramView implements TraceProgramView {
private void instructionFlowOverrideChanged(TraceAddressSpace space,
TraceInstruction instruction, FlowOverride oldOverride, FlowOverride newOverride) {
DomainObjectEventQueues queues = isVisible(space, instruction);
DomainObjectEventQueues queues = isCodeVisible(space, instruction);
if (queues == null) {
return;
}
@ -567,7 +514,7 @@ public class DBTraceProgramView implements TraceProgramView {
private void instructionFallThroughChanged(TraceAddressSpace space,
TraceInstruction instruction, boolean oldFallThrough, boolean newFallThrough) {
DomainObjectEventQueues queues = isVisible(space, instruction);
DomainObjectEventQueues queues = isCodeVisible(space, instruction);
if (queues == null) {
return;
}
@ -577,7 +524,7 @@ public class DBTraceProgramView implements TraceProgramView {
private void memoryBytesChanged(TraceAddressSpace space, TraceAddressSnapRange range,
byte[] oldIsNull, byte[] bytes) {
DomainObjectEventQueues queues = isVisible(space, range);
DomainObjectEventQueues queues = isBytesVisible(space, range);
if (queues == null) {
return;
}
@ -591,7 +538,7 @@ public class DBTraceProgramView implements TraceProgramView {
}
private void memoryRegionAdded(TraceAddressSpace space, TraceMemoryRegion region) {
if (!region.getLifespan().contains(snap)) {
if (!isRegionVisible(region)) {
return;
}
// NOTE: Register view regions are fixed
@ -602,7 +549,7 @@ public class DBTraceProgramView implements TraceProgramView {
}
private void memoryRegionChanged(TraceAddressSpace space, TraceMemoryRegion region) {
if (!region.getLifespan().contains(snap)) {
if (!isRegionVisible(region)) {
return;
}
eventQueues.fireEvent(new ProgramChangeRecord(ChangeManager.DOCR_MEMORY_BLOCK_CHANGED,
@ -614,8 +561,8 @@ public class DBTraceProgramView implements TraceProgramView {
private void memoryRegionLifespanChanged(TraceAddressSpace space, TraceMemoryRegion region,
Range<Long> oldSpan, Range<Long> newSpan) {
boolean inOld = oldSpan.contains(snap);
boolean inNew = newSpan.contains(snap);
boolean inOld = isRegionVisible(region, oldSpan);
boolean inNew = isRegionVisible(region, newSpan);
if (inOld && !inNew) {
eventQueues.fireEvent(
new ProgramChangeRecord(ChangeManager.DOCR_MEMORY_BLOCK_REMOVED,
@ -637,7 +584,7 @@ public class DBTraceProgramView implements TraceProgramView {
// HACK
listing.fragmentsByRegion.remove(region);
// END HACK
if (!region.getLifespan().contains(snap)) {
if (!isRegionVisible(region)) {
return;
}
eventQueues.fireEvent(new ProgramChangeRecord(ChangeManager.DOCR_MEMORY_BLOCK_REMOVED,
@ -678,7 +625,7 @@ public class DBTraceProgramView implements TraceProgramView {
}
private void symbolAdded(TraceAddressSpace space, TraceSymbol symbol) {
DomainObjectEventQueues queues = isVisible(space, symbol);
DomainObjectEventQueues queues = isSymbolVisible(space, symbol);
if (queues == null) {
return;
}
@ -697,7 +644,7 @@ public class DBTraceProgramView implements TraceProgramView {
}
private void symbolSourceChanged(TraceAddressSpace space, TraceSymbol symbol) {
DomainObjectEventQueues queues = isVisible(space, symbol);
DomainObjectEventQueues queues = isSymbolVisible(space, symbol);
if (queues == null) {
return;
}
@ -709,11 +656,11 @@ public class DBTraceProgramView implements TraceProgramView {
private void symbolSetAsPrimary(TraceAddressSpace space, TraceSymbol symbol,
TraceSymbol oldPrimary, TraceSymbol newPrimary) {
// NOTE symbol == newPrimary
DomainObjectEventQueues newQueues = isVisible(space, symbol);
DomainObjectEventQueues newQueues = isSymbolVisible(space, symbol);
if (newQueues == null) {
return;
}
DomainObjectEventQueues oldQueues = isVisible(space, oldPrimary);
DomainObjectEventQueues oldQueues = isSymbolVisible(space, oldPrimary);
if (oldPrimary != null && oldQueues == null) {
oldPrimary = null;
}
@ -725,7 +672,7 @@ public class DBTraceProgramView implements TraceProgramView {
private void symbolRenamed(TraceAddressSpace space, TraceSymbol symbol, String oldName,
String newName) {
DomainObjectEventQueues queues = isVisible(space, symbol);
DomainObjectEventQueues queues = isSymbolVisible(space, symbol);
if (queues == null) {
return;
}
@ -736,7 +683,7 @@ public class DBTraceProgramView implements TraceProgramView {
private void symbolParentChanged(TraceAddressSpace space, TraceSymbol symbol,
TraceNamespaceSymbol oldParent, TraceNamespaceSymbol newParent) {
DomainObjectEventQueues queues = isVisible(space, symbol);
DomainObjectEventQueues queues = isSymbolVisible(space, symbol);
if (queues == null) {
return;
}
@ -747,7 +694,7 @@ public class DBTraceProgramView implements TraceProgramView {
private void symbolAssociationAdded(TraceAddressSpace space, TraceSymbol symbol,
TraceReference oldRefIsNull, TraceReference newRef) {
DomainObjectEventQueues queues = isVisible(space, symbol);
DomainObjectEventQueues queues = isSymbolVisible(space, symbol);
if (queues == null) {
return;
}
@ -759,7 +706,7 @@ public class DBTraceProgramView implements TraceProgramView {
private void symbolAssociationRemoved(TraceAddressSpace space, TraceSymbol symbol,
TraceReference oldRef, TraceReference newRefIsNull) {
DomainObjectEventQueues queues = isVisible(space, symbol);
DomainObjectEventQueues queues = isSymbolVisible(space, symbol);
if (queues == null) {
return;
}
@ -770,7 +717,7 @@ public class DBTraceProgramView implements TraceProgramView {
private void symbolAddressChanged(TraceAddressSpace space, TraceSymbol symbol,
Address oldAddress, Address newAddress) {
DomainObjectEventQueues queues = isVisible(space, symbol);
DomainObjectEventQueues queues = isSymbolVisible(space, symbol);
if (queues == null) {
return;
}
@ -779,14 +726,14 @@ public class DBTraceProgramView implements TraceProgramView {
checkVariableFunctionChanged(space, symbol);
}
private void symbolLifespanChanged(TraceAddressSpace space, TraceSymbol symbol,
private void symbolLifespanChanged(TraceAddressSpace space, TraceSymbolWithLifespan symbol,
Range<Long> oldSpan, Range<Long> newSpan) {
DomainObjectEventQueues queues = getEventQueues(space);
if (queues == null) {
return;
}
boolean inOld = oldSpan.contains(snap);
boolean inNew = newSpan.contains(snap);
boolean inOld = isSymbolWithLifespanVisible(symbol, oldSpan);
boolean inNew = isSymbolWithLifespanVisible(symbol, newSpan);
if (inOld && !inNew) {
fireSymbolRemoved(queues, symbol);
if (symbol instanceof TraceFunctionSymbol) {
@ -807,7 +754,7 @@ public class DBTraceProgramView implements TraceProgramView {
}
private void symbolDeleted(TraceAddressSpace space, TraceSymbol symbol) {
DomainObjectEventQueues queues = isVisible(space, symbol);
DomainObjectEventQueues queues = isSymbolVisible(space, symbol);
if (queues == null) {
return;
}
@ -830,6 +777,7 @@ public class DBTraceProgramView implements TraceProgramView {
protected static class OverlappingAddressRangeKeyIteratorMerger<T> extends
PairingIteratorMerger<Entry<AddressRange, T>, Entry<AddressRange, T>, Entry<AddressRange, T>> {
protected static <T> Iterable<Pair<Entry<AddressRange, T>, Entry<AddressRange, T>>> iter(
Iterable<Entry<AddressRange, T>> left, Iterable<Entry<AddressRange, T>> right) {
return new Iterable<>() {
@ -864,6 +812,7 @@ public class DBTraceProgramView implements TraceProgramView {
protected final DomainObjectEventQueues eventQueues;
protected EventTranslator eventTranslator;
protected final AddressSet allAddresses = new AddressSet();
protected final DBTraceProgramViewBookmarkManager bookmarkManager;
protected final DBTraceProgramViewEquateTable equateTable;
@ -881,17 +830,28 @@ public class DBTraceProgramView implements TraceProgramView {
protected final Map<TraceThread, DBTraceProgramViewRegisters> regViewsByThread;
protected long snap;
protected final DefaultTraceTimeViewport viewport;
protected final Runnable viewportChangeListener = this::viewportChanged;
// This is a strange thing
Long versionTag = 0L;
public DBTraceProgramView(DBTrace trace, long snap, CompilerSpec compilerSpec) {
for (AddressSpace space : trace.getBaseAddressFactory().getPhysicalSpaces()) {
if (space.getType() == AddressSpace.TYPE_OTHER) {
continue;
}
allAddresses.add(space.getMinAddress(), space.getMaxAddress());
}
this.trace = trace;
this.snap = snap;
this.languageID = compilerSpec.getLanguage().getLanguageID();
this.language = compilerSpec.getLanguage();
this.compilerSpec = compilerSpec;
this.viewport = new DefaultTraceTimeViewport(trace);
this.viewport.setSnap(snap);
this.eventQueues =
new DomainObjectEventQueues(this, TIME_INTERVAL, BUF_SIZE, trace.getLock());
@ -911,6 +871,10 @@ public class DBTraceProgramView implements TraceProgramView {
}
protected void viewportChanged() {
eventQueues.fireEvent(new DomainObjectChangeRecord(DomainObject.DO_OBJECT_RESTORED));
}
protected void fireEventAllViews(DomainObjectChangeRecord ev) {
// TODO: Do I need to make copies?
eventQueues.fireEvent(ev);
@ -934,6 +898,11 @@ public class DBTraceProgramView implements TraceProgramView {
return snap;
}
@Override
public TraceTimeViewport getViewport() {
return viewport;
}
@Override
public Long getMaxSnap() {
return trace.getTimeManager().getMaxSnap();
@ -1527,21 +1496,21 @@ public class DBTraceProgramView implements TraceProgramView {
}
public void updateMemoryAddBlock(DBTraceMemoryRegion region) {
if (!region.getLifespan().contains(snap)) {
if (!isRegionVisible(region)) {
return;
}
memory.updateAddBlock(region);
}
public void updateMemoryChangeBlockName(DBTraceMemoryRegion region) {
if (!region.getLifespan().contains(snap)) {
if (!isRegionVisible(region)) {
return;
}
memory.updateChangeBlockName(region);
}
public void updateMemoryChangeBlockFlags(DBTraceMemoryRegion region) {
if (!region.getLifespan().contains(snap)) {
if (!isRegionVisible(region)) {
return;
}
memory.updateChangeBlockFlags(region);
@ -1549,7 +1518,7 @@ public class DBTraceProgramView implements TraceProgramView {
public void updateMemoryChangeBlockRange(DBTraceMemoryRegion region, AddressRange oldRange,
AddressRange newRange) {
if (!region.getLifespan().contains(snap)) {
if (!isRegionVisible(region)) {
return;
}
memory.updateChangeBlockRange(region, oldRange, newRange);
@ -1557,8 +1526,8 @@ public class DBTraceProgramView implements TraceProgramView {
public void updateMemoryChangeBlockLifespan(DBTraceMemoryRegion region,
Range<Long> oldLifespan, Range<Long> newLifespan) {
boolean inOld = oldLifespan.contains(snap);
boolean inNew = newLifespan.contains(snap);
boolean inOld = isRegionVisible(region, oldLifespan);
boolean inNew = isRegionVisible(region, newLifespan);
if (inOld && !inNew) {
memory.updateDeleteBlock(region);
}
@ -1568,7 +1537,7 @@ public class DBTraceProgramView implements TraceProgramView {
}
public void updateMemoryDeleteBlock(DBTraceMemoryRegion region) {
if (!region.getLifespan().contains(snap)) {
if (!isRegionVisible(region)) {
return;
}
memory.updateAddBlock(region);
@ -1577,4 +1546,234 @@ public class DBTraceProgramView implements TraceProgramView {
public void updateMemoryRefreshBlocks() {
memory.updateRefreshBlocks();
}
protected DomainObjectEventQueues getEventQueues(TraceAddressSpace space) {
// TODO: Should there be views on other frames?
// IIRC, this was an abandoned experiment for "register listings"
TraceThread thread = space == null ? null : space.getThread();
if (thread == null) {
return eventQueues;
}
DBTraceProgramViewRegisters viewRegisters;
synchronized (regViewsByThread) {
viewRegisters = regViewsByThread.get(thread);
}
return viewRegisters == null ? null : viewRegisters.eventQueues;
}
protected DomainObjectEventQueues isVisible(TraceAddressSpace space,
TraceAddressSnapRange range) {
return viewport.containsAnyUpper(range.getLifespan()) ? getEventQueues(space) : null;
}
protected boolean isBookmarkVisible(TraceBookmark bm, Range<Long> lifespan) {
return viewport.containsAnyUpper(lifespan);
}
protected DomainObjectEventQueues isBookmarkVisible(TraceAddressSpace space, TraceBookmark bm) {
return isBookmarkVisible(bm, bm.getLifespan()) ? getEventQueues(space) : null;
}
protected boolean bytesDifferForSet(byte[] b1, byte[] b2, AddressSetView set) {
Address min = set.getMinAddress();
for (AddressRange rng : set) {
int beg = (int) rng.getMinAddress().subtract(min);
int end = beg + (int) rng.getLength();
if (!Arrays.equals(b1, beg, end, b2, beg, end)) {
return true;
}
}
return false;
}
protected Occlusion<TraceCodeUnit> getCodeOcclusion(TraceAddressSpace space) {
return new RangeQueryOcclusion<>() {
final DBTraceCodeSpace codeSpace = trace.getCodeManager().get(space, false);
final DBTraceMemorySpace memSpace = trace.getMemoryManager().get(space, false);
final DBTraceDefinedUnitsView definedUnits =
codeSpace == null ? null : codeSpace.definedUnits();
public boolean occluded(TraceCodeUnit cu, AddressRange range, Range<Long> span) {
if (cu == null) {
return RangeQueryOcclusion.super.occluded(cu, range, span);
}
AddressSetView known =
memSpace.getAddressesWithState(span, s -> s == TraceMemoryState.KNOWN);
if (!known.intersects(range.getMinAddress(), range.getMaxAddress())) {
return RangeQueryOcclusion.super.occluded(cu, range, span);
}
byte[] memBytes = new byte[cu.getLength()];
memSpace.getBytes(span.upperEndpoint(), cu.getMinAddress(),
ByteBuffer.wrap(memBytes));
byte[] cuBytes;
try {
cuBytes = cu.getBytes();
}
catch (MemoryAccessException e) {
throw new AssertionError(e);
}
AddressSetView intersectKnown =
new IntersectionAddressSetView(new AddressSet(range), known);
if (bytesDifferForSet(memBytes, cuBytes, intersectKnown)) {
return true;
}
return RangeQueryOcclusion.super.occluded(cu, range, span);
}
@Override
public Iterable<? extends TraceCodeUnit> query(AddressRange range, Range<Long> span) {
return definedUnits == null
? Collections.emptyList()
: definedUnits.get(span.upperEndpoint(), range, true);
}
@Override
public AddressRange range(TraceCodeUnit cu) {
return cu.getRange();
}
};
}
protected <T extends TraceCodeUnit> T getTopCode(Address address,
BiFunction<TraceCodeSpace, Long, T> codeFunc) {
DBTraceCodeSpace codeSpace =
trace.getCodeManager().getCodeSpace(address.getAddressSpace(), false);
if (codeSpace == null) {
return null;
}
return viewport.getTop(s -> {
T t = codeFunc.apply(codeSpace, s);
if (t != null && isCodeVisible(t, t.getLifespan())) {
return t;
}
return null;
});
}
protected boolean isCodeVisible(TraceCodeUnit cu, Range<Long> lifespan) {
return viewport.isCompletelyVisible(cu.getRange(), lifespan, cu,
getCodeOcclusion(cu.getTraceSpace()));
}
protected boolean isCodeVisible(TraceAddressSpace space, TraceAddressSnapRange range) {
return viewport.isCompletelyVisible(range.getRange(), range.getLifespan(), null,
getCodeOcclusion(space));
}
protected DomainObjectEventQueues isCodeVisible(TraceAddressSpace space, TraceCodeUnit cu) {
if (!isCodeVisible(cu, cu.getLifespan())) {
return null;
}
return getEventQueues(space);
}
protected Occlusion<TraceFunctionSymbol> getFunctionOcclusion(TraceFunctionSymbol func) {
return new QueryOcclusion<>() {
DBTraceFunctionSymbolView functions = trace.getSymbolManager().functions();
AddressSetView body = func.getBody();
@Override
public Iterable<? extends TraceFunctionSymbol> query(AddressRange range,
Range<Long> span) {
// NB. No functions in register space!
return functions.getIntersecting(Range.singleton(span.upperEndpoint()), null, range,
false);
}
public boolean itemOccludes(AddressRange range, TraceFunctionSymbol f) {
return body.intersects(f.getBody());
}
@Override
public void removeItem(AddressSet remains, TraceFunctionSymbol t) {
remains.delete(t.getBody());
}
};
}
protected boolean isFunctionVisible(TraceFunctionSymbol function, Range<Long> lifespan) {
AddressSetView body = function.getBody();
AddressRange bodySpan =
new AddressRangeImpl(body.getMinAddress(), body.getMaxAddress());
return viewport.isCompletelyVisible(bodySpan, function.getLifespan(), function,
getFunctionOcclusion(function));
}
protected DomainObjectEventQueues isFunctionVisible(TraceAddressSpace space,
TraceFunctionSymbol function) {
DomainObjectEventQueues queues = getEventQueues(space);
if (queues == null) {
return null;
}
return isFunctionVisible(function, function.getLifespan()) ? queues : null;
}
protected boolean isSymbolWithLifespanVisible(TraceSymbolWithLifespan symbol,
Range<Long> lifespan) {
if (symbol instanceof TraceFunctionSymbol) {
TraceFunctionSymbol func = (TraceFunctionSymbol) symbol;
return isFunctionVisible(func, lifespan);
}
if (!viewport.containsAnyUpper(lifespan)) {
return false;
}
return true;
}
protected DomainObjectEventQueues isSymbolVisible(TraceAddressSpace space,
TraceSymbol symbol) {
// NB. Most symbols do not occlude each other
DomainObjectEventQueues queues = getEventQueues(space);
if (queues == null) {
return null;
}
if (symbol instanceof TraceVariableSymbol) {
TraceVariableSymbol var = (TraceVariableSymbol) symbol;
TraceFunctionSymbol func = var.getFunction();
if (func == null) {
return queues;
}
return isFunctionVisible(space, func);
}
if (!(symbol instanceof TraceSymbolWithLifespan)) {
return queues;
}
TraceSymbolWithLifespan symWl = (TraceSymbolWithLifespan) symbol;
return isSymbolWithLifespanVisible(symWl, symWl.getLifespan()) ? queues : null;
}
protected DomainObjectEventQueues isBytesVisible(TraceAddressSpace space,
TraceAddressSnapRange range) {
// NB. This need not be precise....
DomainObjectEventQueues queues = getEventQueues(space);
if (queues == null) {
return null;
}
if (!viewport.containsAnyUpper(range.getLifespan())) {
return null;
}
return queues;
}
protected Occlusion<TraceMemoryRegion> regionOcclusion = new RangeQueryOcclusion<>() {
@Override
public Iterable<? extends TraceMemoryRegion> query(AddressRange range, Range<Long> span) {
return trace.getMemoryManager()
.getRegionsIntersecting(Range.singleton(span.upperEndpoint()), range);
}
@Override
public AddressRange range(TraceMemoryRegion r) {
return r.getRange();
}
};
protected boolean isRegionVisible(TraceMemoryRegion reg) {
return isRegionVisible(reg, reg.getLifespan());
}
protected boolean isRegionVisible(TraceMemoryRegion reg, Range<Long> lifespan) {
return viewport.isCompletelyVisible(reg.getRange(), lifespan, reg,
regionOcclusion);
}
}

View file

@ -23,6 +23,7 @@ import javax.swing.ImageIcon;
import org.apache.commons.collections4.IteratorUtils;
import com.google.common.collect.Iterators;
import com.google.common.collect.Range;
import generic.NestedIterator;
@ -96,14 +97,16 @@ public class DBTraceProgramViewBookmarkManager implements TraceProgramViewBookma
if (space == null) {
return null;
}
for (TraceBookmark bm : space.getBookmarksAt(program.snap, addr)) {
if (!type.equals(bm.getTypeString())) {
continue;
for (long s : program.viewport.getOrderedSnaps()) {
for (TraceBookmark bm : space.getBookmarksAt(s, addr)) {
if (!type.equals(bm.getTypeString())) {
continue;
}
if (!category.equals(bm.getCategory())) {
continue;
}
return bm;
}
if (!category.equals(bm.getCategory())) {
continue;
}
return bm;
}
return null;
}
@ -222,11 +225,13 @@ public class DBTraceProgramViewBookmarkManager implements TraceProgramViewBookma
return EMPTY_BOOKMARK_ARRAY;
}
List<Bookmark> list = new ArrayList<>();
for (TraceBookmark bm : space.getBookmarksAt(program.snap, addr)) {
if (!bm.getLifespan().contains(program.snap)) {
continue;
for (long s : program.viewport.getOrderedSnaps()) {
for (TraceBookmark bm : space.getBookmarksAt(s, addr)) {
if (!bm.getLifespan().contains(program.snap)) {
continue;
}
list.add(bm);
}
list.add(bm);
}
return list.toArray(new Bookmark[list.size()]);
}
@ -241,11 +246,13 @@ public class DBTraceProgramViewBookmarkManager implements TraceProgramViewBookma
return EMPTY_BOOKMARK_ARRAY;
}
List<Bookmark> list = new ArrayList<>();
for (TraceBookmark bm : space.getBookmarksAt(program.snap, address)) {
if (!type.equals(bm.getTypeString())) {
continue;
for (long s : program.viewport.getOrderedSnaps()) {
for (TraceBookmark bm : space.getBookmarksAt(s, address)) {
if (!type.equals(bm.getTypeString())) {
continue;
}
list.add(bm);
}
list.add(bm);
}
return list.toArray(new Bookmark[list.size()]);
}
@ -261,10 +268,10 @@ public class DBTraceProgramViewBookmarkManager implements TraceProgramViewBookma
return result;
}
for (TraceBookmark bm : bmt.getBookmarks()) {
if (bm.getAddress().isRegisterAddress()) {
if (bm.getAddress().getAddressSpace().isRegisterSpace()) {
continue;
}
if (!bm.getLifespan().contains(program.snap)) {
if (!program.viewport.containsAnyUpper(bm.getLifespan())) {
continue;
}
result.add(bm.getAddress());
@ -287,7 +294,7 @@ public class DBTraceProgramViewBookmarkManager implements TraceProgramViewBookma
@SuppressWarnings("unchecked")
protected static <T, U extends T> Iterator<T> filteredIterator(Iterator<U> it,
Predicate<? super U> predicate) {
return IteratorUtils.filteredIterator(it, e -> predicate.test((U) e));
return (Iterator<T>) Iterators.filter(it, e -> predicate.test(e));
}
@Override
@ -298,14 +305,22 @@ public class DBTraceProgramViewBookmarkManager implements TraceProgramViewBookma
}
// TODO: May want to offer memory-only and/or register-only bookmark iterators
return filteredIterator(bmt.getBookmarks().iterator(),
bm -> !bm.getAddress().isRegisterAddress() && bm.getLifespan().contains(program.snap));
bm -> !bm.getAddress().getAddressSpace().isRegisterSpace() &&
program.viewport.containsAnyUpper(bm.getLifespan()));
}
@Override
public Iterator<Bookmark> getBookmarksIterator() {
// TODO: This seems terribly inefficient. We'll have to see how/when it's used.
return NestedIterator.start(bookmarkManager.getActiveMemorySpaces().iterator(),
space -> filteredIterator(space.getAllBookmarks().iterator(),
bm -> bm.getLifespan().contains(program.snap)));
bm -> program.viewport.containsAnyUpper(bm.getLifespan())));
}
protected Comparator<Bookmark> getBookmarkComparator(boolean forward) {
return forward
? (b1, b2) -> b1.getAddress().compareTo(b2.getAddress())
: (b1, b2) -> -b1.getAddress().compareTo(b2.getAddress());
}
@Override
@ -320,8 +335,9 @@ public class DBTraceProgramViewBookmarkManager implements TraceProgramViewBookma
if (space == null) {
return Collections.emptyIterator();
}
return space.getBookmarksIntersecting(Range.closed(program.snap, program.snap),
rng).iterator();
return program.viewport.mergedIterator(
s -> space.getBookmarksIntersecting(Range.closed(s, s), rng).iterator(),
getBookmarkComparator(forward));
});
}

View file

@ -27,6 +27,7 @@ import ghidra.program.model.symbol.Equate;
import ghidra.program.model.symbol.EquateTable;
import ghidra.trace.database.symbol.DBTraceEquate;
import ghidra.trace.database.symbol.DBTraceEquateManager;
import ghidra.trace.model.listing.TraceCodeUnit;
import ghidra.util.IntersectionAddressSetView;
import ghidra.util.LockHold;
import ghidra.util.exception.*;
@ -97,8 +98,13 @@ public class DBTraceProgramViewEquateTable implements EquateTable {
@Override
public Equate getEquate(Address reference, int opndPosition, long value) {
try (LockHold hold = program.trace.lockRead()) {
return doGetViewEquate(
equateManager.getReferencedByValue(program.snap, reference, opndPosition, value));
TraceCodeUnit cu = program.getTopCode(reference,
(space, s) -> space.definedUnits().getContaining(s, reference));
if (cu == null) {
return null;
}
return doGetViewEquate(equateManager.getReferencedByValue(cu.getStartSnap(), reference,
opndPosition, value));
}
}
@ -106,7 +112,12 @@ public class DBTraceProgramViewEquateTable implements EquateTable {
public List<Equate> getEquates(Address reference, int opndPosition) {
try (LockHold hold = program.trace.lockRead()) {
List<Equate> result = new ArrayList<>();
for (DBTraceEquate equate : equateManager.getReferenced(program.snap, reference,
TraceCodeUnit cu = program.getTopCode(reference,
(space, s) -> space.definedUnits().getContaining(s, reference));
if (cu == null) {
return result;
}
for (DBTraceEquate equate : equateManager.getReferenced(cu.getStartSnap(), reference,
opndPosition)) {
result.add(doGetViewEquate(equate));
}
@ -118,7 +129,12 @@ public class DBTraceProgramViewEquateTable implements EquateTable {
public List<Equate> getEquates(Address reference) {
try (LockHold hold = program.trace.lockRead()) {
List<Equate> result = new ArrayList<>();
for (DBTraceEquate equate : equateManager.getReferenced(program.snap, reference)) {
TraceCodeUnit cu = program.getTopCode(reference,
(space, s) -> space.definedUnits().getContaining(s, reference));
if (cu == null) {
return result;
}
for (DBTraceEquate equate : equateManager.getReferenced(cu.getStartSnap(), reference)) {
result.add(doGetViewEquate(equate));
}
return result;
@ -127,9 +143,9 @@ public class DBTraceProgramViewEquateTable implements EquateTable {
@Override
public AddressIterator getEquateAddresses() {
return equateManager.getReferringAddresses(Range.singleton(program.snap))
.getAddresses(
true);
return program.viewport
.unionedAddresses(s -> equateManager.getReferringAddresses(Range.singleton(s)))
.getAddresses(true);
}
@Override
@ -150,14 +166,15 @@ public class DBTraceProgramViewEquateTable implements EquateTable {
@Override
public AddressIterator getEquateAddresses(Address start) {
return equateManager.getReferringAddresses(Range.singleton(program.snap))
.getAddresses(
start, true);
return program.viewport
.unionedAddresses(s -> equateManager.getReferringAddresses(Range.singleton(s)))
.getAddresses(start, true);
}
@Override
public AddressIterator getEquateAddresses(AddressSetView asv) {
return new IntersectionAddressSetView(asv,
equateManager.getReferringAddresses(Range.singleton(program.snap))).getAddresses(true);
return new IntersectionAddressSetView(asv, program.viewport
.unionedAddresses(s -> equateManager.getReferringAddresses(Range.singleton(s))))
.getAddresses(true);
}
}

View file

@ -21,8 +21,6 @@ import java.io.IOException;
import java.util.Iterator;
import java.util.List;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterators;
import com.google.common.collect.Range;
import generic.NestedIterator;
@ -35,8 +33,10 @@ import ghidra.program.model.symbol.Namespace;
import ghidra.program.model.symbol.SourceType;
import ghidra.trace.database.DBTraceUtils;
import ghidra.trace.database.symbol.*;
import ghidra.trace.model.listing.TraceData;
import ghidra.trace.model.symbol.TraceFunctionSymbol;
import ghidra.trace.util.EmptyFunctionIterator;
import ghidra.trace.util.WrappingFunctionIterator;
import ghidra.util.LockHold;
import ghidra.util.exception.CancelledException;
import ghidra.util.exception.InvalidInputException;
@ -44,34 +44,6 @@ import ghidra.util.task.TaskMonitor;
public class DBTraceProgramViewFunctionManager implements FunctionManager {
public static class FunctionIteratorAdapter implements FunctionIterator {
private Iterator<? extends Function> it;
public FunctionIteratorAdapter(Iterator<? extends Function> it) {
this.it = it;
}
public <T extends Function> FunctionIteratorAdapter(Iterator<T> it,
Predicate<? super T> filter) {
this.it = Iterators.filter(it, filter);
}
@Override
public boolean hasNext() {
return it.hasNext();
}
@Override
public Function next() {
return it.next();
}
@Override
public Iterator<Function> iterator() {
return this;
}
}
protected final DBTraceProgramView program;
protected final DBTraceFunctionSymbolView functions;
protected final DBTraceNamespaceSymbol global;
@ -176,10 +148,16 @@ public class DBTraceProgramViewFunctionManager implements FunctionManager {
if (!entryPoint.getAddressSpace().isMemorySpace()) {
return null;
}
// NOTE: There ought only to be one, since no overlaps allowed.
for (TraceFunctionSymbol at : functions.getAt(program.snap, null, entryPoint, false)) {
if (entryPoint.equals(at.getEntryPoint())) {
return at;
for (long s : program.viewport.getOrderedSnaps()) {
// NOTE: There ought only to be one, since no overlaps allowed.
for (TraceFunctionSymbol at : functions.getAt(s, null, entryPoint, false)) {
if (entryPoint.equals(at.getEntryPoint())) {
return at;
}
else {
return null; // Anything below is occluded by the found function
}
}
}
return null;
@ -187,16 +165,20 @@ public class DBTraceProgramViewFunctionManager implements FunctionManager {
@Override
public TraceFunctionSymbol getReferencedFunction(Address address) {
if (!address.getAddressSpace().isMemorySpace()) {
return null;
}
TraceFunctionSymbol found = getFunctionAt(address);
if (found != null) {
return found;
}
// We're assuming a data reference
if (program.trace.getCodeManager().data().getContaining(program.snap, address) == null) {
TraceData data =
program.getTopCode(address, (space, s) -> space.data().getContaining(s, address));
if (data == null) {
return null;
}
DBTraceReference ref =
program.trace.getReferenceManager().getPrimaryReferenceFrom(program.snap, address, 0);
DBTraceReference ref = program.trace.getReferenceManager()
.getPrimaryReferenceFrom(data.getStartSnap(), address, 0);
return ref == null ? null : getFunctionAt(ref.getToAddress());
}
@ -228,7 +210,7 @@ public class DBTraceProgramViewFunctionManager implements FunctionManager {
@Override
public FunctionIterator getFunctions(AddressSetView asv, boolean forward) {
return new FunctionIteratorAdapter(
return new WrappingFunctionIterator(
NestedIterator.start(asv.iterator(forward), rng -> getFunctionsInRange(rng, forward)),
f -> {
if (!asv.contains(f.getEntryPoint())) {
@ -251,7 +233,7 @@ public class DBTraceProgramViewFunctionManager implements FunctionManager {
@Override
public FunctionIterator getFunctionsNoStubs(AddressSetView asv, boolean forward) {
return new FunctionIteratorAdapter(
return new WrappingFunctionIterator(
NestedIterator.start(asv.iterator(forward), rng -> getFunctionsInRange(rng, forward)),
f -> {
if (f.isThunk()) {
@ -316,7 +298,7 @@ public class DBTraceProgramViewFunctionManager implements FunctionManager {
@Override
public Iterator<Function> getFunctionsOverlapping(AddressSetView set) {
return new FunctionIteratorAdapter(
return new WrappingFunctionIterator(
NestedIterator.start(set.iterator(true), rng -> getFunctionsInRange(rng, true)));
}

View file

@ -22,8 +22,6 @@ import ghidra.util.LockHold;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;
// TODO: Make at/since configurable?
// NOTE: Probably not, esp., if I get the coloring right.
public class DBTraceProgramViewListing extends AbstractDBTraceProgramViewListing {
protected final AddressSet allMemory;
@ -36,10 +34,10 @@ public class DBTraceProgramViewListing extends AbstractDBTraceProgramViewListing
public boolean isUndefined(Address start, Address end) {
try (LockHold hold = program.trace.lockRead()) {
for (AddressRange range : program.getAddressFactory().getAddressSet(start, end)) {
if (!codeOperations.undefinedData()
.coversRange(
Range.closed(program.snap, program.snap), range)) {
return false;
for (long s : program.viewport.getOrderedSnaps()) {
if (!isUndefinedRange(s, range)) {
return false;
}
}
}
return true;

View file

@ -16,6 +16,8 @@
package ghidra.trace.database.program;
import java.util.*;
import java.util.function.Consumer;
import java.util.function.Function;
import com.google.common.cache.CacheBuilder;
@ -32,13 +34,33 @@ public class DBTraceProgramViewMemory extends AbstractDBTraceProgramViewMemory {
super(program);
}
protected DBTraceMemoryRegion getTopRegion(Function<Long, DBTraceMemoryRegion> regFunc) {
return program.viewport.getTop(s -> {
// TODO: There is probably an early-bail condition I can check for.
DBTraceMemoryRegion reg = regFunc.apply(s);
if (reg != null && program.isRegionVisible(reg)) {
return reg;
}
return null;
});
}
protected void forVisibleRegions(Consumer<? super DBTraceMemoryRegion> action) {
for (long s : program.viewport.getOrderedSnaps()) {
// NOTE: This is slightly faster than new AddressSet(mm.getRegionsAddressSet(snap))
for (DBTraceMemoryRegion reg : memoryManager.getRegionsAtSnap(s)) {
if (program.isRegionVisible(reg)) {
action.accept(reg);
}
}
}
}
@Override
protected void recomputeAddressSet() {
AddressSet temp = new AddressSet();
// NOTE: This is slightly faster than new AddressSet(mm.getRegionsAddressSet(snap))
for (DBTraceMemoryRegion reg : memoryManager.getRegionsAtSnap(snap)) {
temp.add(reg.getRange());
}
// TODO: Performance test this
forVisibleRegions(reg -> temp.add(reg.getRange()));
addressSet = temp;
}
@ -49,26 +71,21 @@ public class DBTraceProgramViewMemory extends AbstractDBTraceProgramViewMemory {
@Override
public MemoryBlock getBlock(Address addr) {
DBTraceMemoryRegion region = memoryManager.getRegionContaining(snap, addr);
DBTraceMemoryRegion region = getTopRegion(s -> memoryManager.getRegionContaining(s, addr));
return region == null ? null : getBlock(region);
}
@Override
public MemoryBlock getBlock(String blockName) {
DBTraceMemoryRegion region = memoryManager.getLiveRegionByPath(snap, blockName);
DBTraceMemoryRegion region =
getTopRegion(s -> memoryManager.getLiveRegionByPath(s, blockName));
return region == null ? null : getBlock(region);
}
@Override
public MemoryBlock[] getBlocks() {
List<MemoryBlock> result = new ArrayList<>();
for (DBTraceMemoryRegion region : memoryManager.getRegionsInternal()) {
MemoryBlock block = getBlock(region);
if (block == null) {
continue;
}
result.add(block);
}
forVisibleRegions(reg -> result.add(getBlock(reg)));
return result.toArray(new MemoryBlock[result.size()]);
}

View file

@ -241,7 +241,7 @@ public class DBTraceProgramViewMemoryBlock implements MemoryBlock {
throw new MemoryAccessException("Space does not exist");
}
ByteBuffer buf = ByteBuffer.allocate(1);
if (space.getBytes(program.snap, addr, buf) != 1) {
if (space.getViewBytes(program.snap, addr, buf) != 1) {
throw new MemoryAccessException();
}
return buf.get(0);
@ -264,7 +264,7 @@ public class DBTraceProgramViewMemoryBlock implements MemoryBlock {
throw new MemoryAccessException("Space does not exist");
}
len = (int) Math.min(len, range.getMaxAddress().subtract(addr) + 1);
return space.getBytes(program.snap, addr, ByteBuffer.wrap(b, off, len));
return space.getViewBytes(program.snap, addr, ByteBuffer.wrap(b, off, len));
}
@Override

View file

@ -53,8 +53,11 @@ public class DBTraceProgramViewProgramContext extends AbstractProgramContext {
List<Register> registers = language.getRegisters();
List<Register> result = new ArrayList<>(registers.size());
for (Register register : registers) {
if (registerContextManager.hasRegisterValue(language, register, program.snap)) {
result.add(register);
for (long s : program.viewport.getReversedSnaps()) {
if (registerContextManager.hasRegisterValue(language, register, s)) {
result.add(register);
break;
}
}
}
return result.toArray(new Register[result.size()]);
@ -66,10 +69,27 @@ public class DBTraceProgramViewProgramContext extends AbstractProgramContext {
return value == null ? null : signed ? value.getSignedValue() : value.getUnsignedValue();
}
protected RegisterValue combine(RegisterValue v1, RegisterValue v2) {
if (v1 == null) {
return v2;
}
else if (v2 == null) {
return v1;
}
return v1.combineValues(v2);
}
protected RegisterValue stack(RegisterValue value, Register register, Address address) {
for (long s : program.viewport.getReversedSnaps()) {
value = combine(value, registerContextManager.getValue(language, register, s, address));
}
return value;
}
@Override
public RegisterValue getRegisterValue(Register register, Address address) {
return registerContextManager.getValueWithDefault(language, register, program.snap,
address);
RegisterValue value = registerContextManager.getDefaultValue(language, register, address);
return stack(value, register, address);
}
@Override
@ -81,7 +101,8 @@ public class DBTraceProgramViewProgramContext extends AbstractProgramContext {
@Override
public RegisterValue getNonDefaultValue(Register register, Address address) {
return registerContextManager.getValue(language, register, program.snap, address);
RegisterValue value = new RegisterValue(register);
return stack(value, register, address);
}
@Override
@ -105,22 +126,24 @@ public class DBTraceProgramViewProgramContext extends AbstractProgramContext {
@Override
public AddressRangeIterator getRegisterValueAddressRanges(Register register) {
return registerContextManager.getRegisterValueAddressRanges(language, register,
program.snap).getAddressRanges();
return program.viewport.unionedAddresses(
s -> registerContextManager.getRegisterValueAddressRanges(language, register,
s)).getAddressRanges();
}
@Override
public AddressRangeIterator getRegisterValueAddressRanges(Register register, Address start,
Address end) {
return new NestedAddressRangeIterator<>(
language.getAddressFactory().getAddressSet(start, end).iterator(), range -> {
return registerContextManager.getRegisterValueAddressRanges(language, register,
program.snap, range).iterator();
});
language.getAddressFactory().getAddressSet(start, end).iterator(),
range -> program.viewport.unionedAddresses(
s -> registerContextManager.getRegisterValueAddressRanges(language, register,
s, range)).iterator());
}
@Override
public AddressRange getRegisterValueRangeContaining(Register register, Address address) {
// TODO: I don't know the value of making this work through the viewport.
Entry<TraceAddressSnapRange, RegisterValue> entry =
registerContextManager.getEntry(language, register, program.snap, address);
if (entry != null) {
@ -164,6 +187,7 @@ public class DBTraceProgramViewProgramContext extends AbstractProgramContext {
@Override
public boolean hasValueOverRange(Register register, BigInteger value,
AddressSetView addressSet) {
// TODO: Not sure the value of making this use the viewport
RegisterValue regVal = new RegisterValue(register, value);
try (LockHold hold = program.trace.lockRead()) {
AddressSet remains = new AddressSet(addressSet);

View file

@ -19,6 +19,7 @@ import com.google.common.collect.Range;
import ghidra.program.model.address.*;
import ghidra.trace.database.listing.DBTraceCodeRegisterSpace;
import ghidra.trace.database.listing.UndefinedDBTraceData;
import ghidra.trace.database.thread.DBTraceThread;
import ghidra.trace.model.program.TraceProgramViewRegisterListing;
import ghidra.util.exception.CancelledException;
@ -45,6 +46,11 @@ public class DBTraceProgramViewRegisterListing extends AbstractDBTraceProgramVie
return thread;
}
@Override
public UndefinedDBTraceData doCreateUndefinedUnit(Address address) {
throw new UnsupportedOperationException();
}
@Override
public boolean isUndefined(Address start, Address end) {
return codeOperations.undefinedData()

View file

@ -225,7 +225,7 @@ public class DBTraceProgramViewRegisterMemoryBlock implements MemoryBlock {
throw new MemoryAccessException();
}
ByteBuffer buf = ByteBuffer.allocate(1);
if (space.getBytes(program.snap, addr, buf) != 1) {
if (space.getViewBytes(program.snap, addr, buf) != 1) {
throw new MemoryAccessException();
}
return buf.get(0);
@ -242,7 +242,7 @@ public class DBTraceProgramViewRegisterMemoryBlock implements MemoryBlock {
throw new MemoryAccessException();
}
len = (int) Math.min(len, range.getMaxAddress().subtract(addr) + 1);
return space.getBytes(program.snap, addr, ByteBuffer.wrap(b, off, len));
return space.getViewBytes(program.snap, addr, ByteBuffer.wrap(b, off, len));
}
@Override

View file

@ -41,6 +41,7 @@ import ghidra.trace.model.Trace;
import ghidra.trace.model.data.TraceBasedDataTypeManager;
import ghidra.trace.model.program.TraceProgramView;
import ghidra.trace.model.thread.TraceThread;
import ghidra.trace.util.TraceTimeViewport;
import ghidra.util.exception.CancelledException;
import ghidra.util.exception.DuplicateNameException;
import ghidra.util.task.TaskMonitor;
@ -616,6 +617,11 @@ public class DBTraceProgramViewRegisters implements TraceProgramView {
return view.getSnap();
}
@Override
public TraceTimeViewport getViewport() {
return view.getViewport();
}
@Override
public Long getMaxSnap() {
return view.getMaxSnap();

View file

@ -17,11 +17,14 @@ package ghidra.trace.database.program;
import java.util.ArrayList;
import java.util.List;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSetView;
import ghidra.program.model.listing.*;
import ghidra.trace.database.memory.DBTraceMemoryRegion;
import ghidra.trace.model.memory.TraceMemoryRegion;
import ghidra.util.ComparatorMath;
import ghidra.util.LockHold;
import ghidra.util.exception.*;
@ -102,11 +105,10 @@ public class DBTraceProgramViewRootModule implements ProgramModule {
// NOTE: Would flush on snap change
try (LockHold hold = LockHold.lock(program.trace.getReadWriteLock().readLock())) {
List<DBTraceProgramViewFragment> frags = new ArrayList<>();
for (DBTraceMemoryRegion region : program.trace.getMemoryManager()
.getRegionsAtSnap(program.snap)) {
program.memory.forVisibleRegions(region -> {
frags.add(listing.fragmentsByRegion.computeIfAbsent(region,
r -> new DBTraceProgramViewFragment(listing, r)));
}
});
return frags.toArray(new DBTraceProgramViewFragment[frags.size()]);
}
}
@ -114,17 +116,11 @@ public class DBTraceProgramViewRootModule implements ProgramModule {
@Override
public int getIndex(String name) {
// TODO: This isn't pretty at all. Really should database these.
List<String> names = new ArrayList<>();
try (LockHold hold = LockHold.lock(program.trace.getReadWriteLock().readLock())) {
int i = 0;
for (DBTraceMemoryRegion region : program.trace.getMemoryManager()
.getRegionsAtSnap(program.snap)) {
if (name.equals(region.getName())) {
return i;
}
i++;
}
program.memory.forVisibleRegions(region -> names.add(region.getName()));
}
return -1;
return names.indexOf(names);
}
@Override
@ -173,14 +169,44 @@ public class DBTraceProgramViewRootModule implements ProgramModule {
return true;
}
protected <T> T reduceRegions(java.util.function.Function<TraceMemoryRegion, T> func,
BiFunction<T, T, T> reducer) {
var action = new Consumer<TraceMemoryRegion>() {
public T cur;
@Override
public void accept(TraceMemoryRegion region) {
if (cur == null) {
cur = func.apply(region);
}
else {
cur = reducer.apply(cur, func.apply(region));
}
}
};
return action.cur;
}
@Override
public Address getMinAddress() {
return program.trace.getMemoryManager().getRegionsAddressSet(program.snap).getMinAddress();
if (!program.viewport.isForked()) {
return program.trace.getMemoryManager()
.getRegionsAddressSet(program.snap)
.getMinAddress();
}
// TODO: There has got to be a better way
return reduceRegions(TraceMemoryRegion::getMinAddress, ComparatorMath::cmin);
}
@Override
public Address getMaxAddress() {
return program.trace.getMemoryManager().getRegionsAddressSet(program.snap).getMaxAddress();
if (!program.viewport.isForked()) {
return program.trace.getMemoryManager()
.getRegionsAddressSet(program.snap)
.getMaxAddress();
}
// TODO: There has got to be a better way
return reduceRegions(TraceMemoryRegion::getMaxAddress, ComparatorMath::cmax);
}
@Override
@ -195,7 +221,8 @@ public class DBTraceProgramViewRootModule implements ProgramModule {
@Override
public AddressSetView getAddressSet() {
return program.trace.getMemoryManager().getRegionsAddressSet(program.snap);
return program.viewport
.unionedAddresses(s -> program.trace.getMemoryManager().getRegionsAddressSet(s));
}
@Override

View file

@ -153,12 +153,16 @@ public class DBTraceProgramViewSymbolTable implements SymbolTable {
}
}
protected <T extends TraceSymbol> T requireInLifespan(T sym) {
protected <T extends TraceSymbol> T requireVisible(T sym) {
if (!(sym instanceof TraceSymbolWithLifespan)) {
return sym;
}
if (sym instanceof TraceFunctionSymbol) {
TraceFunctionSymbol function = (TraceFunctionSymbol) sym;
return program.isFunctionVisible(function, function.getLifespan()) ? sym : null;
}
TraceSymbolWithLifespan wl = (TraceSymbolWithLifespan) sym;
if (wl.getLifespan().contains(program.snap)) {
if (program.viewport.containsAnyUpper(wl.getLifespan())) {
return sym;
}
return null;
@ -166,7 +170,7 @@ public class DBTraceProgramViewSymbolTable implements SymbolTable {
@Override
public Symbol getSymbol(long symbolID) {
return requireInLifespan(symbolManager.getSymbolByID(symbolID));
return requireVisible(symbolManager.getSymbolByID(symbolID));
}
@Override
@ -178,7 +182,7 @@ public class DBTraceProgramViewSymbolTable implements SymbolTable {
if (!addr.equals(sym.getAddress())) {
continue;
}
if (requireInLifespan(sym) == null) {
if (requireVisible(sym) == null) {
continue;
}
return sym;
@ -198,7 +202,7 @@ public class DBTraceProgramViewSymbolTable implements SymbolTable {
for (TraceSymbol sym : symbolManager.allSymbols()
.getChildrenNamed(name,
assertTraceNamespace(namespace))) {
if (requireInLifespan(sym) == null) {
if (requireVisible(sym) == null) {
continue;
}
return sym;
@ -218,7 +222,7 @@ public class DBTraceProgramViewSymbolTable implements SymbolTable {
try (LockHold hold = program.trace.lockRead()) {
List<Symbol> result = new ArrayList<>();
for (TraceSymbol sym : symbolManager.allSymbols().getChildrenNamed(name, parent)) {
if (requireInLifespan(sym) != null) {
if (requireVisible(sym) != null) {
result.add(sym);
}
}
@ -239,7 +243,7 @@ public class DBTraceProgramViewSymbolTable implements SymbolTable {
for (TraceSymbol sym : symbolManager.labelsAndFunctions()
.getChildrenNamed(name,
parent)) {
if (requireInLifespan(sym) != null) {
if (requireVisible(sym) != null) {
result.add(sym);
}
}
@ -352,7 +356,7 @@ public class DBTraceProgramViewSymbolTable implements SymbolTable {
if (addr.isMemoryAddress()) {
return symbolManager.labelsAndFunctions().hasAt(program.snap, null, addr, true);
}
if (addr.isRegisterAddress() || addr.isStackAddress()) {
if (addr.getAddressSpace().isRegisterSpace() || addr.isStackAddress()) {
return symbolManager.allVariables().hasAt(addr, true);
}
return false;
@ -527,7 +531,7 @@ public class DBTraceProgramViewSymbolTable implements SymbolTable {
return sym.getParentNamespace();
}
}
if (addr.isRegisterAddress() || addr.isStackAddress()) {
if (addr.getAddressSpace().isRegisterSpace() || addr.isStackAddress()) {
for (TraceSymbol sym : symbolManager.allVariables().getAt(addr, true)) {
return sym.getParentNamespace();
}

View file

@ -30,6 +30,7 @@ import ghidra.trace.model.program.TraceVariableSnapProgramView;
*/
public class DBTraceVariableSnapProgramView extends DBTraceProgramView
implements TraceVariableSnapProgramView {
//private static final int SNAP_CHANGE_EVENT_THRESHHOLD = 100;
public DBTraceVariableSnapProgramView(DBTrace trace, long snap, CompilerSpec compilerSpec) {
@ -50,6 +51,7 @@ public class DBTraceVariableSnapProgramView extends DBTraceProgramView
}
//long oldSnap = this.snap;
this.snap = newSnap;
viewport.setSnap(newSnap);
memory.setSnap(newSnap);
// TODO: I could be more particular, but this seems to work fast enough, now.

View file

@ -33,7 +33,9 @@ import ghidra.trace.database.thread.DBTraceThread;
import ghidra.trace.database.thread.DBTraceThreadManager;
import ghidra.trace.model.stack.TraceStackFrame;
import ghidra.trace.model.thread.TraceThread;
import ghidra.trace.util.TraceAddressSpace;
import ghidra.util.LockHold;
import ghidra.util.Msg;
import ghidra.util.database.*;
import ghidra.util.database.annot.*;
import ghidra.util.exception.VersionException;
@ -127,7 +129,11 @@ public abstract class AbstractDBTraceSpaceBasedManager<M extends DBTraceSpaceBas
for (DBTraceSpaceEntry ent : spaceStore.asMap().values()) {
AddressFactory addressFactory = baseLanguage.getAddressFactory();
AddressSpace space = addressFactory.getAddressSpace(ent.spaceName);
if (space.isRegisterSpace()) {
if (space == null) {
Msg.error(this, "Space " + ent.spaceName + " does not exist in " + baseLanguage +
". Perhaps the language changed.");
}
else if (space.isRegisterSpace()) {
DBTraceThread thread = threadManager.getThread(ent.threadKey);
R regSpace;
if (ent.space == null) {
@ -227,10 +233,10 @@ public abstract class AbstractDBTraceSpaceBasedManager<M extends DBTraceSpaceBas
return baseLanguage;
}
public M get(DBTraceSpaceKey key, boolean createIfAbsent) {
AddressSpace addressSpace = key.getAddressSpace();
public M get(TraceAddressSpace space, boolean createIfAbsent) {
AddressSpace addressSpace = space.getAddressSpace();
if (addressSpace.isRegisterSpace()) {
return getForRegisterSpace(key.getThread(), key.getFrameLevel(), createIfAbsent);
return getForRegisterSpace(space.getThread(), space.getFrameLevel(), createIfAbsent);
}
return getForSpace(addressSpace, createIfAbsent);
}

View file

@ -32,6 +32,10 @@ public interface DBTraceDelegatingManager<M> {
void accept(T t) throws E1, E2;
}
interface ExcSupplier<T, E1 extends Throwable, E2 extends Throwable> {
T get() throws E1, E2;
}
interface ExcPredicate<T, E1 extends Throwable, E2 extends Throwable> {
boolean test(T t) throws E1, E2;
}
@ -79,7 +83,7 @@ public interface DBTraceDelegatingManager<M> {
default <T, E1 extends Throwable, E2 extends Throwable> T delegateRead(AddressSpace space,
ExcFunction<M, T, E1, E2> func) throws E1, E2 {
return delegateRead(space, func, null);
return delegateRead(space, func, (T) null);
}
default <T, E1 extends Throwable, E2 extends Throwable> T delegateRead(AddressSpace space,
@ -94,6 +98,18 @@ public interface DBTraceDelegatingManager<M> {
}
}
default <T, E1 extends Throwable, E2 extends Throwable> T delegateRead(AddressSpace space,
ExcFunction<M, T, E1, E2> func, ExcSupplier<T, E1, E2> ifNull) throws E1, E2 {
checkIsInMemory(space);
try (LockHold hold = LockHold.lock(readLock())) {
M m = getForSpace(space, false);
if (m == null) {
return ifNull.get();
}
return func.apply(m);
}
}
default int delegateReadI(AddressSpace space, ToIntFunction<M> func, int ifNull) {
checkIsInMemory(space);
try (LockHold hold = LockHold.lock(readLock())) {

View file

@ -115,7 +115,9 @@ public class DBTraceStackManager implements TraceStackManager, DBTraceManager {
if (found == null) {
return null;
}
if (found.getThread() != thread) {
if (found.getThread() != thread || found.getSnap() > snap) {
// Encoded <thread,snap> field results in unsigned index
// NB. Conventionally, a search should never traverse 0 (real to scratch space)
return null;
}
return found;

View file

@ -230,10 +230,10 @@ public class DBTraceReferenceManager extends
}
@Override
public Collection<? extends DBTraceReference> getFlowRefrencesFrom(long snap,
public Collection<? extends DBTraceReference> getFlowReferencesFrom(long snap,
Address fromAddress) {
return delegateRead(fromAddress.getAddressSpace(),
s -> s.getFlowRefrencesFrom(snap, fromAddress), Collections.emptyList());
s -> s.getFlowReferencesFrom(snap, fromAddress), Collections.emptyList());
}
@Override

View file

@ -564,7 +564,7 @@ public class DBTraceReferenceSpace implements DBTraceSpaceBased, TraceReferenceS
}
@Override
public Collection<? extends DBTraceReference> getFlowRefrencesFrom(long snap,
public Collection<? extends DBTraceReference> getFlowReferencesFrom(long snap,
Address fromAddress) {
return Collections2.filter(getReferencesFrom(snap, fromAddress),
r -> r.getReferenceType().isFlow());

View file

@ -22,9 +22,11 @@ import ghidra.trace.database.thread.DBTraceThread;
import ghidra.trace.model.Trace;
import ghidra.trace.model.Trace.TraceSnapshotChangeType;
import ghidra.trace.model.thread.TraceThread;
import ghidra.trace.model.time.TraceSchedule;
import ghidra.trace.model.time.TraceSnapshot;
import ghidra.trace.util.TraceChangeRecord;
import ghidra.util.LockHold;
import ghidra.util.Msg;
import ghidra.util.database.*;
import ghidra.util.database.annot.*;
@ -33,14 +35,14 @@ public class DBTraceSnapshot extends DBAnnotatedObject implements TraceSnapshot
protected static final String TABLE_NAME = "Snapshots";
protected static final String REAL_TIME_COLUMN_NAME = "RealTime";
protected static final String TICKS_COLUMN_NAME = "Ticks";
protected static final String SCHEDULE_COLUMN_NAME = "Schedule";
protected static final String DESCRIPTION_COLUMN_NAME = "Description";
protected static final String THREAD_COLUMN_NAME = "Thread";
@DBAnnotatedColumn(REAL_TIME_COLUMN_NAME)
static DBObjectColumn REAL_TIME_COLUMN;
@DBAnnotatedColumn(TICKS_COLUMN_NAME)
static DBObjectColumn TICKS_COLUMN;
@DBAnnotatedColumn(SCHEDULE_COLUMN_NAME)
static DBObjectColumn SCHEDULE_COLUMN;
@DBAnnotatedColumn(DESCRIPTION_COLUMN_NAME)
static DBObjectColumn DESCRIPTION_COLUMN;
@DBAnnotatedColumn(THREAD_COLUMN_NAME)
@ -48,8 +50,8 @@ public class DBTraceSnapshot extends DBAnnotatedObject implements TraceSnapshot
@DBAnnotatedField(column = REAL_TIME_COLUMN_NAME)
long realTime; // milliseconds
@DBAnnotatedField(column = TICKS_COLUMN_NAME)
long ticks;
@DBAnnotatedField(column = SCHEDULE_COLUMN_NAME, indexed = true)
String scheduleStr = "";
@DBAnnotatedField(column = DESCRIPTION_COLUMN_NAME)
String description;
@DBAnnotatedField(column = THREAD_COLUMN_NAME)
@ -58,6 +60,7 @@ public class DBTraceSnapshot extends DBAnnotatedObject implements TraceSnapshot
public final DBTraceTimeManager manager;
private DBTraceThread eventThread;
private TraceSchedule schedule;
public DBTraceSnapshot(DBTraceTimeManager manager, DBCachedObjectStore<?> store,
DBRecord record) {
@ -69,23 +72,33 @@ public class DBTraceSnapshot extends DBAnnotatedObject implements TraceSnapshot
protected void fresh(boolean created) throws IOException {
if (created) {
threadKey = -1;
scheduleStr = "";
}
else {
eventThread = manager.threadManager.getThread(threadKey);
if (!"".equals(scheduleStr)) {
try {
schedule = TraceSchedule.parse(scheduleStr);
}
catch (IllegalArgumentException e) {
Msg.error(this, "Could not parse schedule: " + schedule, e);
// Leave as null (or previous value?)
}
}
}
}
@Override
public String toString() {
return String.format("<DBTraceSnapshot realTime=%d, ticks=%d, description='%s'>", realTime,
ticks, description);
return String.format(
"<DBTraceSnapshot key=%d, realTime=%d, schedule='%s', description='%s'>",
key, realTime, scheduleStr, description);
}
protected void set(long realTime, String description, long ticks) {
protected void set(long realTime, String description) {
this.realTime = realTime;
this.description = description;
this.ticks = ticks;
update(REAL_TIME_COLUMN, DESCRIPTION_COLUMN, TICKS_COLUMN);
update(REAL_TIME_COLUMN, DESCRIPTION_COLUMN);
}
@Override
@ -146,15 +159,21 @@ public class DBTraceSnapshot extends DBAnnotatedObject implements TraceSnapshot
}
@Override
public long getTicks() {
return ticks;
public TraceSchedule getSchedule() {
return schedule;
}
@Override
public void setTicks(long ticks) {
public String getScheduleString() {
return scheduleStr;
}
@Override
public void setSchedule(TraceSchedule schedule) {
try (LockHold hold = LockHold.lock(manager.lock.writeLock())) {
this.ticks = ticks;
update(TICKS_COLUMN);
this.schedule = schedule;
this.scheduleStr = schedule == null ? "" : schedule.toString();
update(SCHEDULE_COLUMN);
}
manager.trace.setChanged(
new TraceChangeRecord<>(TraceSnapshotChangeType.CHANGED, null, this));

View file

@ -18,6 +18,7 @@ package ghidra.trace.database.time;
import java.io.IOException;
import java.util.Collection;
import java.util.Collections;
import java.util.Map.Entry;
import java.util.concurrent.locks.ReadWriteLock;
import db.DBHandle;
@ -25,8 +26,7 @@ import ghidra.trace.database.DBTrace;
import ghidra.trace.database.DBTraceManager;
import ghidra.trace.database.thread.DBTraceThreadManager;
import ghidra.trace.model.Trace.TraceSnapshotChangeType;
import ghidra.trace.model.time.TraceSnapshot;
import ghidra.trace.model.time.TraceTimeManager;
import ghidra.trace.model.time.*;
import ghidra.trace.util.TraceChangeRecord;
import ghidra.util.LockHold;
import ghidra.util.database.*;
@ -40,6 +40,7 @@ public class DBTraceTimeManager implements TraceTimeManager, DBTraceManager {
protected final DBTraceThreadManager threadManager;
protected final DBCachedObjectStore<DBTraceSnapshot> snapshotStore;
protected final DBCachedObjectIndex<String, DBTraceSnapshot> snapshotsBySchedule;
public DBTraceTimeManager(DBHandle dbh, DBOpenMode openMode, ReadWriteLock lock,
TaskMonitor monitor, DBTrace trace, DBTraceThreadManager threadManager)
@ -52,6 +53,7 @@ public class DBTraceTimeManager implements TraceTimeManager, DBTraceManager {
snapshotStore = factory.getOrCreateCachedStore(DBTraceSnapshot.TABLE_NAME,
DBTraceSnapshot.class, (s, r) -> new DBTraceSnapshot(this, s, r), true);
snapshotsBySchedule = snapshotStore.getIndex(String.class, DBTraceSnapshot.SCHEDULE_COLUMN);
}
@Override
@ -68,7 +70,11 @@ public class DBTraceTimeManager implements TraceTimeManager, DBTraceManager {
public DBTraceSnapshot createSnapshot(String description) {
try (LockHold hold = LockHold.lock(lock.writeLock())) {
DBTraceSnapshot snapshot = snapshotStore.create();
snapshot.set(System.currentTimeMillis(), description, 0);
snapshot.set(System.currentTimeMillis(), description);
if (snapshot.getKey() == 0) {
// Convention for first snap
snapshot.setSchedule(TraceSchedule.snap(0));
}
trace.setChanged(
new TraceChangeRecord<>(TraceSnapshotChangeType.ADDED, null, snapshot));
return snapshot;
@ -76,7 +82,7 @@ public class DBTraceTimeManager implements TraceTimeManager, DBTraceManager {
}
@Override
public TraceSnapshot getSnapshot(long snap, boolean createIfAbsent) {
public DBTraceSnapshot getSnapshot(long snap, boolean createIfAbsent) {
if (!createIfAbsent) {
try (LockHold hold = LockHold.lock(lock.readLock())) {
return snapshotStore.getObjectAt(snap);
@ -86,7 +92,11 @@ public class DBTraceTimeManager implements TraceTimeManager, DBTraceManager {
DBTraceSnapshot snapshot = snapshotStore.getObjectAt(snap);
if (snapshot == null) {
snapshot = snapshotStore.create(snap);
snapshot.set(System.currentTimeMillis(), "", 0);
snapshot.set(System.currentTimeMillis(), "");
if (snapshot.getKey() == 0) {
// Convention for first snap
snapshot.setSchedule(TraceSchedule.snap(0));
}
trace.setChanged(
new TraceChangeRecord<>(TraceSnapshotChangeType.ADDED, null, snapshot));
}
@ -94,11 +104,31 @@ public class DBTraceTimeManager implements TraceTimeManager, DBTraceManager {
}
}
@Override
public DBTraceSnapshot getMostRecentSnapshot(long snap) {
try (LockHold hold = LockHold.lock(lock.readLock())) {
Entry<Long, DBTraceSnapshot> ent = snapshotStore.asMap().floorEntry(snap);
return ent == null ? null : ent.getValue();
}
}
@Override
public Collection<? extends TraceSnapshot> getSnapshotsWithSchedule(TraceSchedule schedule) {
return snapshotsBySchedule.get(schedule.toString());
}
@Override
public Collection<? extends DBTraceSnapshot> getAllSnapshots() {
return Collections.unmodifiableCollection(snapshotStore.asMap().values());
}
@Override
public Collection<? extends DBTraceSnapshot> getSnapshots(long fromSnap, boolean fromInclusive,
long toSnap, boolean toInclusive) {
return Collections.unmodifiableCollection(
snapshotStore.asMap().subMap(fromSnap, fromInclusive, toSnap, toInclusive).values());
}
@Override
public Long getMaxSnap() {
return snapshotStore.getMaxKey();

View file

@ -273,8 +273,8 @@ public interface Trace extends DataTypeManagerDomainObject {
new TraceSymbolChangeType<>();
public static final TraceSymbolChangeType<Address> ADDRESS_CHANGED =
new TraceSymbolChangeType<>();
public static final TraceSymbolChangeType<Range<Long>> LIFESPAN_CHANGED =
new TraceSymbolChangeType<>();
public static final DefaultTraceChangeType<TraceSymbolWithLifespan, Range<Long>> LIFESPAN_CHANGED =
new DefaultTraceChangeType<>();
public static final TraceSymbolChangeType<Void> DELETED = new TraceSymbolChangeType<>();
// Other changes not captured above
public static final TraceSymbolChangeType<Void> CHANGED = new TraceSymbolChangeType<>();

View file

@ -15,25 +15,57 @@
*/
package ghidra.trace.model.listing;
import ghidra.lifecycle.Experimental;
import ghidra.program.model.address.AddressSetView;
import ghidra.program.model.address.AddressSpace;
import ghidra.trace.model.stack.TraceStackFrame;
import ghidra.trace.model.thread.TraceThread;
import ghidra.trace.util.TraceAddressSpace;
public interface TraceCodeManager extends TraceCodeOperations {
/** Operand index for data. Will always be zero */
int DATA_OP_INDEX = 0;
/**
* Get the code space for the memory or registers of the given trace address space
*
* @param space the trace address space (thread, stack frame, address space)
* @param createIfAbsent true to create the space if it's not already present
* @return the space, of {@code null} if absent and not created
*/
TraceCodeSpace getCodeSpace(TraceAddressSpace space, boolean createIfAbsent);
/**
* Get the code space for the memory of the given address space
*
* @param space the address space
* @param createIfAbsent true to create the space if it's not already present
* @return the space, of {@code null} if absent and not created
*/
TraceCodeSpace getCodeSpace(AddressSpace space, boolean createIfAbsent);
/**
* Get the code space for registers of the given thread's innermost frame
*
* @param thread the thread
* @param createIfAbsent true to create the space if it's not already present
* @return the space, of {@code null} if absent and not created
*/
TraceCodeRegisterSpace getCodeRegisterSpace(TraceThread thread, boolean createIfAbsent);
/**
* Get the code space for registers of the given thread and frame
*
* @param thread the thread
* @param frameLevel the frame (0 for innermost)
* @param createIfAbsent true to create the space if it's not already present
* @return the space, of {@code null} if absent and not created
*/
TraceCodeRegisterSpace getCodeRegisterSpace(TraceThread thread, int frameLevel,
boolean createIfAbsent);
/**
* Get the code space for registers of the given stack frame
*
* <p>
* Note this is simply a shortcut for {@link #getCodeRegisterSpace(TraceThread, int, boolean)},
* and does not in any way bind the space to the lifetime of the given frame. Nor, if the frame
* is moved, will this space move with it.
@ -44,8 +76,23 @@ public interface TraceCodeManager extends TraceCodeOperations {
*/
TraceCodeRegisterSpace getCodeRegisterSpace(TraceStackFrame frame, boolean createIfAbsent);
/**
* Query for the address set where code units have been added between the two given snaps
*
* @param from the beginning snap
* @param to the ending snap
* @return the view of addresses where units have been added
*/
@Experimental
AddressSetView getCodeAdded(long from, long to);
/**
* Query for the address set where code units have been removed between the two given snaps
*
* @param from the beginning snap
* @param to the ending snap
* @return the view of addresses where units have been removed
*/
@Experimental
AddressSetView getCodeRemoved(long from, long to);
}

View file

@ -19,15 +19,21 @@ import java.nio.ByteBuffer;
import com.google.common.collect.Range;
import ghidra.program.model.address.AddressRange;
import ghidra.program.model.lang.Language;
import ghidra.program.model.listing.CodeUnit;
import ghidra.program.model.util.TypeMismatchException;
import ghidra.trace.model.Trace;
import ghidra.trace.model.TraceAddressSnapRange;
import ghidra.trace.model.program.TraceProgramView;
import ghidra.trace.model.symbol.TraceReference;
import ghidra.trace.model.thread.TraceThread;
import ghidra.trace.util.TraceAddressSpace;
import ghidra.util.Saveable;
/**
* A code unit in a {@link Trace}
*/
public interface TraceCodeUnit extends CodeUnit {
/**
* Get the trace in which this code unit exists
@ -39,9 +45,12 @@ public interface TraceCodeUnit extends CodeUnit {
@Override
TraceProgramView getProgram();
TraceAddressSpace getTraceSpace();
/**
* Get the thread associated with this code unit
*
* <p>
* A thread is associated with a code unit if it exists in a register space
*
* @return the thread
@ -51,6 +60,7 @@ public interface TraceCodeUnit extends CodeUnit {
/**
* Get the language of this code unit
*
* <p>
* Currently, for data units, this is always the base or "host" language of the trace. For
* instructions, this may be a guest language.
*
@ -58,6 +68,20 @@ public interface TraceCodeUnit extends CodeUnit {
*/
Language getLanguage();
/**
* Get the bounds of this unit in space and time
*
* @return the bounds
*/
TraceAddressSnapRange getBounds();
/**
* Get the address range covered by this unit
*
* @return the range
*/
AddressRange getRange();
/**
* Get the lifespan of this code unit
*
@ -95,6 +119,7 @@ public interface TraceCodeUnit extends CodeUnit {
/**
* Read bytes starting at this unit's address plus the given offset into the given buffer
*
* <p>
* This method honors the markers (position and limit) of the destination buffer. Use those
* markers to control the destination offset and maximum length.
*
@ -107,15 +132,19 @@ public interface TraceCodeUnit extends CodeUnit {
/**
* Set a property of the given type to the given value
*
* <p>
* This method is preferred to {@link #setTypedProperty(String, Object)}, because in the case
* the property map does not already exist, the desired type is given explicitly.
*
* While it is best practice to match -valueClass- exactly with the type of the map, this method
* will work so long as the given -valueClass- is a subtype of the map's type. If the property
* map does not already exist, it is created with the given -valueClass-. Note that there is no
* established mechanism for restoring values of a subtype from the underlying database.
* <p>
* While it is best practice to match {@code valueClass} exactly with the type of the map, this
* method will work so long as the given {@code valueClass} is a subtype of the map's type. If
* the property map does not already exist, it is created with the given {@code valueClass}.
* Note that there is no established mechanism for restoring values of a subtype from the
* underlying database.
*
* Currently, the only supported types are {@link Integer},{@link String}, {@link Void}, and
* <p>
* Currently, the only supported types are {@link Integer}, {@link String}, {@link Void}, and
* subtypes of {@link Saveable}.
*
* @param name the name of the property
@ -127,10 +156,12 @@ public interface TraceCodeUnit extends CodeUnit {
/**
* Set a property having the same type as the given value
*
* <p>
* If the named property has a super-type of the value's type, the value is accepted. If not, a
* {@link TypeMismatchException} is thrown. If the property map does not already exist, it is
* created having <em>exactly</em> the type of the given value.
*
* <p>
* This method exists for two reasons: 1) To introduce the type variable U, which is more
* existential, and 2) to remove the requirement to subtype {@link Saveable}. Otherwise, this
* method is identical in operation to {@link #setProperty(String, Saveable)}.
@ -143,11 +174,13 @@ public interface TraceCodeUnit extends CodeUnit {
/**
* Get a property having the given type
*
* If the named property has a sub-type of the given -valueClass-, the value (possibly
* <p>
* If the named property has a sub-type of the given {@code valueClass}, the value (possibly
* {@code null}) is returned. If the property does not exist, {@code null} is returned.
* Otherwise {@link TypeMismatchException} is thrown, even if the property is not set at this
* unit's address.
*
* <p>
* Note that getting a {@link Void} property will always return {@code null}. Use
* {@link #getVoidProperty(String)} instead to detect if the property is set.
* {@link #hasProperty(String)} will also work, but it does not verify that the property's type

View file

@ -27,6 +27,7 @@ import com.google.common.collect.Range;
import ghidra.program.model.address.*;
import ghidra.program.model.mem.MemBuffer;
import ghidra.trace.model.*;
import ghidra.trace.model.time.TraceSnapshot;
import ghidra.util.exception.DuplicateNameException;
import ghidra.util.task.TaskMonitor;
@ -45,6 +46,19 @@ import ghidra.util.task.TaskMonitor;
* states can be manipulated directly; however, this is recommended only to record read failures,
* using the state {@link TraceMemoryState#ERROR}. A state of {@code null} is equivalent to
* {@link TraceMemoryState#UNKNOWN} and indicates no observation has been made.
*
* <p>
* Negative snaps may have different semantics than positive, since negative snaps are used as
* "scratch space". These snaps are not presumed to have any temporal relation to their neighbors,
* or any other snap for that matter. Clients may use the description field of the
* {@link TraceSnapshot} to indicate a relationship to another snap. Operations which seek the
* "most-recent" data might not retrieve anything from scratch snaps, and writing to a scratch snap
* might not cause any changes to others. Note the "integrity" of data where the memory state is not
* {@link TraceMemoryState#KNOWN} may be neglected to some extent. For example, writing bytes to
* snap -10 may cause bytes in snap -9 to change, where the effected range at snap -9 has state
* {@link TraceMemoryState#UNKNOWN}. The time semantics are not necessarily prohibited in scratch
* space, but implementations may choose cheaper semantics if desired. Clients should be wary not to
* accidentally rely on implied temporal relationships in scratch space.
*/
public interface TraceMemoryOperations {
/**
@ -219,6 +233,15 @@ public interface TraceMemoryOperations {
*/
TraceMemoryState getState(long snap, Address address);
/**
* Get the state of memory at a given snap and address, following schedule forks
*
* @param snap the time
* @param address the location
* @return the state, and the snap where it was found
*/
Entry<Long, TraceMemoryState> getViewState(long snap, Address address);
/**
* Get the entry recording the most recent state at the given snap and address
*
@ -233,6 +256,17 @@ public interface TraceMemoryOperations {
Entry<TraceAddressSnapRange, TraceMemoryState> getMostRecentStateEntry(long snap,
Address address);
/**
* Get the entry recording the most recent state at the given snap and address, following
* schedule forks
*
* @param snap the time
* @param address the location
* @return the state
*/
Entry<TraceAddressSnapRange, TraceMemoryState> getViewMostRecentStateEntry(long snap,
Address address);
/**
* Get at least the subset of addresses having state satisfying the given predicate
*
@ -359,6 +393,22 @@ public interface TraceMemoryOperations {
*/
int getBytes(long snap, Address start, ByteBuffer buf);
/**
* Read the most recent bytes from the given snap and address, following schedule forks
*
* <p>
* This behaves similarly to {@link #getBytes(long, Address, ByteBuffer)}, except it checks for
* the {@link TraceMemoryState#KNOWN} state among each involved snap range and reads the
* applicable address ranges, preferring the most recent. Where memory is never known the buffer
* is left unmodified.
*
* @param snap the time
* @param start the location
* @param buf the destination buffer of bytes
* @return the number of bytes read
*/
int getViewBytes(long snap, Address start, ByteBuffer buf);
/**
* Search the given address range at the given snap for a given byte pattern
*

View file

@ -19,8 +19,6 @@ import java.nio.ByteBuffer;
import java.util.*;
import java.util.Map.Entry;
import org.apache.commons.lang3.ArrayUtils;
import ghidra.program.model.lang.Register;
import ghidra.program.model.lang.RegisterValue;
import ghidra.trace.model.TraceAddressSnapRange;
@ -71,20 +69,13 @@ public interface TraceMemoryRegisterSpace extends TraceMemorySpace {
}
default RegisterValue getValue(long snap, Register register) {
int byteLength = TraceRegisterUtils.byteLengthOf(register);
byte[] mask = register.getBaseMask();
ByteBuffer buf = ByteBuffer.allocate(mask.length * 2);
buf.put(mask);
int maskOffset = TraceRegisterUtils.computeMaskOffset(mask);
int startVal = buf.position() + maskOffset;
buf.position(startVal);
buf.limit(buf.position() + byteLength);
getBytes(snap, register.getAddress(), buf);
byte[] arr = buf.array();
if (!register.isBigEndian()) {
ArrayUtils.reverse(arr, startVal, startVal + byteLength);
}
return new RegisterValue(register, arr);
return TraceRegisterUtils.getRegisterValue(register,
(a, buf) -> getBytes(snap, a, buf));
}
default RegisterValue getViewValue(long snap, Register register) {
return TraceRegisterUtils.getRegisterValue(register,
(a, buf) -> getViewBytes(snap, a, buf));
}
default int getBytes(long snap, Register register, ByteBuffer buf) {

View file

@ -18,9 +18,10 @@ package ghidra.trace.model.program;
import ghidra.program.model.listing.Program;
import ghidra.trace.model.Trace;
import ghidra.trace.model.thread.TraceThread;
import ghidra.trace.util.TraceTimeViewport;
/**
* View of a trace at a particular time,as a program
* View of a trace at a particular time, as a program
*/
public interface TraceProgramView extends Program {
/**
@ -37,6 +38,13 @@ public interface TraceProgramView extends Program {
*/
long getSnap();
/**
* Get the viewport this view is using for forked queries
*
* @return the viewport
*/
TraceTimeViewport getViewport();
/**
* Get the latest snap
*

View file

@ -66,7 +66,7 @@ public interface TraceReferenceOperations {
TraceReference getPrimaryReferenceFrom(long snap, Address fromAddress, int operandIndex);
Collection<? extends TraceReference> getFlowRefrencesFrom(long snap, Address fromAddress);
Collection<? extends TraceReference> getFlowReferencesFrom(long snap, Address fromAddress);
void clearReferencesFrom(Range<Long> span, AddressRange range);
@ -94,7 +94,7 @@ public interface TraceReferenceOperations {
}
default boolean hasFlowReferencesFrom(long snap, Address fromAddress) {
return !getFlowRefrencesFrom(snap, fromAddress).isEmpty();
return !getFlowReferencesFrom(snap, fromAddress).isEmpty();
}
default boolean hasReferencesTo(long snap, Address toAddress) {

View file

@ -82,20 +82,37 @@ public interface TraceSnapshot {
void setEventThread(TraceThread thread);
/**
* Get the number of ticks, if known, between this snapshot and the next
* Get the schedule, if applicable and known, relating this snapshot to a previous one
*
* @return the (unsigned) number of ticks, or 0 for unspecified.
* <p>
* This information is not always known, or even applicable. If recording a single step,
* ideally, this is simply the previous snap plus one step of the event thread, e.g., for snap
* 6, the schedule would be "5:1". For an emulated machine cached in scratch space, this should
* be the schedule that would recover the same machine state.
*
* <p>
* The object managers in the trace pay no heed to this schedule. In particular, when retrieving
* the "most-recent" information from a snapshot with a known schedule, the "previous snap" part
* of that schedule is <em>not</em> taken into account. In other words, the managers still
* interpret time linearly, even though this schedule field might imply built-in forking.
*
* @return the (possibly null) schedule
*/
long getTicks();
TraceSchedule getSchedule();
/**
* Set the number of ticks between this snapshot and the next
* Get the string representation of the schedule
*
* Conventionally, 0 indicates unknown or unspecified
*
* @param ticks the number of ticks
* @return the (possibly empty) string representation of the schedule
*/
void setTicks(long ticks);
String getScheduleString();
/**
* Set the schedule from some previous snapshot to this one
*
* @param schedule the schedule
*/
void setSchedule(TraceSchedule schedule);
/**
* Delete this snapshot

View file

@ -34,6 +34,26 @@ public interface TraceTimeManager {
*/
TraceSnapshot getSnapshot(long snap, boolean createIfAbsent);
/**
* Get the most recent snapshot since a given key
*
* @param snap the snapshot key
* @return the snapshot or {@code null}
*/
TraceSnapshot getMostRecentSnapshot(long snap);
/**
* Get all snapshots with the given schedule
*
* <p>
* Ideally, the snapshot schedules should be managed such that the returned collection contains
* at most one snapshot.
*
* @param schedule the schedule to find
* @return the snapshot, or {@code null} if no such snapshot exists
*/
Collection<? extends TraceSnapshot> getSnapshotsWithSchedule(TraceSchedule schedule);
/**
* List all snapshots in the trace
*
@ -41,6 +61,18 @@ public interface TraceTimeManager {
*/
Collection<? extends TraceSnapshot> getAllSnapshots();
/**
* List all snapshots between two given snaps in the trace
*
* @param fromSnap the starting snap
* @param fromInclusive whether to include the from snap
* @param toSnap the ending snap
* @param toInclusive when to include the to snap
* @return the set of snapshots
*/
Collection<? extends TraceSnapshot> getSnapshots(long fromSnap, boolean fromInclusive,
long toSnap, boolean toInclusive);
/**
* Get maximum snapshot key that has ever existed, usually that of the latest snapshot
*

View file

@ -21,6 +21,23 @@ import ghidra.program.model.listing.Data;
import ghidra.program.model.scalar.Scalar;
public interface DataAdapterFromDataType extends Data {
default String doToString() {
StringBuilder builder = new StringBuilder();
builder.append(getMnemonicString());
String valueRepresentation = getDefaultValueRepresentation();
if (valueRepresentation != null) {
builder.append(' ');
builder.append(valueRepresentation);
}
return builder.toString();
}
@Override
default String getMnemonicString() {
return getDataType().getMnemonic(this);
}
@Override
default Address getAddress(int opIndex) {
if (opIndex != 0) {

View file

@ -0,0 +1,54 @@
/* ###
* 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.trace.util;
import ghidra.docking.settings.SettingsDefinition;
import ghidra.program.model.data.DataType;
import ghidra.program.model.data.MutabilitySettingsDefinition;
import ghidra.program.model.listing.Data;
public interface DataAdapterFromSettings extends Data {
default <T extends SettingsDefinition> T getSettingsDefinition(
Class<T> settingsDefinitionClass) {
DataType dt = getBaseDataType();
for (SettingsDefinition def : dt.getSettingsDefinitions()) {
if (settingsDefinitionClass.isAssignableFrom(def.getClass())) {
return settingsDefinitionClass.cast(def);
}
}
return null;
}
default boolean hasMutability(int mutabilityType) {
MutabilitySettingsDefinition def =
getSettingsDefinition(MutabilitySettingsDefinition.class);
if (def != null) {
return def.getChoice(this) == mutabilityType;
}
return false;
}
@Override
default boolean isConstant() {
return hasMutability(MutabilitySettingsDefinition.CONSTANT);
}
@Override
default boolean isVolatile() {
return hasMutability(MutabilitySettingsDefinition.VOLATILE);
}
}

View file

@ -0,0 +1,40 @@
/* ###
* 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.trace.util;
import ghidra.program.model.listing.Data;
import ghidra.program.model.symbol.Reference;
public interface DataAdapterMinimal extends Data {
/** Operand index for data. Will always be zero */
int DATA_OP_INDEX = 0;
int[] EMPTY_INT_ARRAY = new int[0];
default String getPrimarySymbolOrDynamicName() {
/** TODO: Use primary symbol or dynamic name as in {@link DataDB#getPathName()} */
return "DAT_" + getAddressString(false, false);
}
@Override
default int getNumOperands() {
return 1;
}
@Override
default Reference[] getValueReferences() {
return getOperandReferences(DATA_OP_INDEX);
}
}

View file

@ -0,0 +1,277 @@
/* ###
* 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.trace.util;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
import com.google.common.collect.*;
import ghidra.program.model.address.*;
import ghidra.trace.model.Trace;
import ghidra.trace.model.Trace.TraceSnapshotChangeType;
import ghidra.trace.model.TraceDomainObjectListener;
import ghidra.trace.model.program.TraceProgramView;
import ghidra.trace.model.time.*;
import ghidra.util.*;
import ghidra.util.datastruct.ListenerSet;
/**
* Computes and tracks the "viewport" resulting from forking patterns encoded in snapshot schedules
*
* <p>
* This is used primarily by the {@link TraceProgramView} implementation to resolve most-recent
* objects according to a layering or forking structure given in snapshot schedules. This listens on
* the given trace for changes in snapshot schedules and keeps an up-to-date set of visible (or
* potentially-visible) ranges from the given snap.
*
* <p>
* TODO: Because complicated forking structures are not anticipated, some minimal effort is given to
* cull meaningless changes, but in general, changes cause a complete re-computation of the
* viewport. If complex, deep forking structures prove to be desirable, then this is an area for
* optimization.
*/
public class DefaultTraceTimeViewport implements TraceTimeViewport {
protected class ForSnapshotsListener extends TraceDomainObjectListener {
{
listenFor(TraceSnapshotChangeType.ADDED, this::snapshotAdded);
listenFor(TraceSnapshotChangeType.CHANGED, this::snapshotChanged);
listenFor(TraceSnapshotChangeType.DELETED, this::snapshotDeleted);
}
private void snapshotAdded(TraceSnapshot snapshot) {
if (snapshot.getSchedule() == null) {
return;
}
if (spanSet.contains(snapshot.getKey())) {
recomputeSnapRanges();
return;
}
}
private void snapshotChanged(TraceSnapshot snapshot) {
if (isLower(snapshot.getKey())) {
recomputeSnapRanges();
return;
}
if (spanSet.contains(snapshot.getKey()) && snapshot.getSchedule() != null) {
recomputeSnapRanges();
return;
}
}
private void snapshotDeleted(TraceSnapshot snapshot) {
if (isLower(snapshot.getKey())) {
recomputeSnapRanges();
return;
}
}
}
protected final TraceTimeManager timeManager;
protected final List<Range<Long>> ordered = new ArrayList<>();
protected final RangeSet<Long> spanSet = TreeRangeSet.create();
protected final ForSnapshotsListener listener = new ForSnapshotsListener();
protected final ListenerSet<Runnable> changeListeners = new ListenerSet<>(Runnable.class);
protected long snap;
public DefaultTraceTimeViewport(Trace trace) {
this.timeManager = trace.getTimeManager();
trace.addListener(listener);
}
@Override
public void addChangeListener(Runnable l) {
changeListeners.add(l);
}
@Override
public void removeChangeListener(Runnable l) {
changeListeners.remove(l);
}
@Override
public boolean containsAnyUpper(Range<Long> range) {
// NB. This should only ever visit the first range intersecting that given
for (Range<Long> intersecting : spanSet.subRangeSet(range).asRanges()) {
if (range.contains(intersecting.upperEndpoint())) {
return true;
}
}
return false;
}
@Override
public <T> boolean isCompletelyVisible(AddressRange range, Range<Long> lifespan, T object,
Occlusion<T> occlusion) {
for (Range<Long> rng : ordered) {
if (lifespan.contains(rng.upperEndpoint())) {
return true;
}
if (occlusion.occluded(object, range, rng)) {
return false;
}
}
return false;
}
@Override
public <T> AddressSet computeVisibleParts(AddressSetView set, Range<Long> lifespan, T object,
Occlusion<T> occlusion) {
if (!containsAnyUpper(lifespan)) {
return new AddressSet();
}
AddressSet remains = new AddressSet(set);
for (Range<Long> rng : ordered) {
if (lifespan.contains(rng.upperEndpoint())) {
return remains;
}
occlusion.remove(object, remains, rng);
if (remains.isEmpty()) {
return remains;
}
}
// This condition should have been detected by !containsAnyUpper
throw new AssertionError();
}
protected boolean isLower(long lower) {
Range<Long> range = spanSet.rangeContaining(lower);
if (range == null) {
return false;
}
return range.lowerEndpoint().longValue() == lower;
}
protected boolean addSnapRange(long lower, long upper) {
if (spanSet.contains(lower)) {
return false;
}
Range<Long> range = Range.closed(lower, upper);
spanSet.add(range);
ordered.add(range);
return true;
}
protected TraceSnapshot locateMostRecentFork(long from) {
while (true) {
TraceSnapshot prev = timeManager.getMostRecentSnapshot(from);
if (prev == null) {
return null;
}
TraceSchedule prevSched = prev.getSchedule();
long prevKey = prev.getKey();
if (prevSched == null) {
if (prevKey == Long.MIN_VALUE) {
return null;
}
from = prevKey - 1;
continue;
}
long forkedSnap = prevSched.getSnap();
if (forkedSnap == prevKey - 1) {
// Schedule is notational without forking
from--;
continue;
}
return prev;
}
}
protected void traverseAndAddForkRanges(long curSnap) {
while (true) {
TraceSnapshot fork = locateMostRecentFork(curSnap);
long prevSnap = fork == null ? Long.MIN_VALUE : fork.getKey();
if (!addSnapRange(prevSnap, curSnap)) {
return;
}
if (fork == null) {
return;
}
curSnap = fork.getSchedule().getSnap();
}
}
protected void recomputeSnapRanges() {
spanSet.clear();
ordered.clear();
traverseAndAddForkRanges(snap);
assert !ordered.isEmpty();
changeListeners.fire.run();
}
public void setSnap(long snap) {
this.snap = snap;
recomputeSnapRanges();
}
@Override
public boolean isForked() {
return ordered.size() > 1;
}
@Override
public List<Long> getOrderedSnaps() {
return ordered
.stream()
.map(Range::upperEndpoint)
.collect(Collectors.toList());
}
@Override
public List<Long> getReversedSnaps() {
return Lists.reverse(ordered)
.stream()
.map(Range::upperEndpoint)
.collect(Collectors.toList());
}
@Override
public <T> T getTop(Function<Long, T> func) {
for (Range<Long> rng : ordered) {
T t = func.apply(rng.upperEndpoint());
if (t != null) {
return t;
}
}
return null;
}
@Override
public <T> Iterator<T> mergedIterator(Function<Long, Iterator<T>> iterFunc,
Comparator<? super T> comparator) {
if (!isForked()) {
return iterFunc.apply(snap);
}
List<Iterator<T>> iters = ordered.stream()
.map(rng -> iterFunc.apply(rng.upperEndpoint()))
.collect(Collectors.toList());
return new UniqIterator<>(new MergeSortingIterator<>(iters, comparator));
}
@Override
public AddressSetView unionedAddresses(Function<Long, AddressSetView> viewFunc) {
if (!isForked()) {
return viewFunc.apply(snap);
}
List<AddressSetView> views = ordered.stream()
.map(rng -> viewFunc.apply(rng.upperEndpoint()))
.collect(Collectors.toList());
return new UnionAddressSetView(views);
}
}

View file

@ -0,0 +1,189 @@
/* ###
* 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.trace.util;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import ghidra.program.model.address.Address;
import ghidra.program.model.mem.Memory;
import ghidra.program.model.mem.MemoryAccessException;
public interface MemoryAdapter extends Memory {
default ByteBuffer mustRead(Address addr, int length, boolean bigEndian)
throws MemoryAccessException {
ByteBuffer buf = ByteBuffer.allocate(length);
if (getBytes(addr, buf.array()) != length) {
throw new MemoryAccessException();
}
buf.order(bigEndian ? ByteOrder.BIG_ENDIAN : ByteOrder.LITTLE_ENDIAN);
return buf;
}
@Override
default int getBytes(Address addr, byte[] dest) throws MemoryAccessException {
return getBytes(addr, dest, 0, dest.length);
}
@Override
default byte getByte(Address addr) throws MemoryAccessException {
return mustRead(addr, Byte.BYTES, true).get(0);
}
@Override
default short getShort(Address addr) throws MemoryAccessException {
return mustRead(addr, Short.BYTES, true).getShort(0);
}
@Override
default short getShort(Address addr, boolean bigEndian) throws MemoryAccessException {
return mustRead(addr, Short.BYTES, bigEndian).getShort(0);
}
@Override
default int getShorts(Address addr, short[] dest) throws MemoryAccessException {
return getShorts(addr, dest, 0, dest.length, true);
}
@Override
default int getShorts(Address addr, short[] dest, int dIndex, int nElem)
throws MemoryAccessException {
return getShorts(addr, dest, dIndex, nElem, true);
}
@Override
default int getShorts(Address addr, short[] dest, int dIndex, int nElem, boolean bigEndian)
throws MemoryAccessException {
ByteBuffer buf = ByteBuffer.allocate(Short.BYTES * nElem);
int got = getBytes(addr, buf.array()) / Short.BYTES;
buf.order(bigEndian ? ByteOrder.BIG_ENDIAN : ByteOrder.LITTLE_ENDIAN);
buf.asShortBuffer().get(dest, dIndex, got);
return got;
}
@Override
default int getInt(Address addr) throws MemoryAccessException {
return mustRead(addr, Integer.BYTES, true).getInt(0);
}
@Override
default int getInt(Address addr, boolean bigEndian) throws MemoryAccessException {
return mustRead(addr, Integer.BYTES, bigEndian).getInt(0);
}
@Override
default int getInts(Address addr, int[] dest) throws MemoryAccessException {
return getInts(addr, dest, 0, dest.length, true);
}
@Override
default int getInts(Address addr, int[] dest, int dIndex, int nElem)
throws MemoryAccessException {
return getInts(addr, dest, dIndex, nElem, true);
}
@Override
default int getInts(Address addr, int[] dest, int dIndex, int nElem, boolean bigEndian)
throws MemoryAccessException {
ByteBuffer buf = ByteBuffer.allocate(Integer.BYTES * nElem);
int got = getBytes(addr, buf.array()) / Integer.BYTES;
buf.order(bigEndian ? ByteOrder.BIG_ENDIAN : ByteOrder.LITTLE_ENDIAN);
buf.asIntBuffer().get(dest, dIndex, got);
return got;
}
@Override
default long getLong(Address addr) throws MemoryAccessException {
return mustRead(addr, Long.BYTES, true).getLong(0);
}
@Override
default long getLong(Address addr, boolean bigEndian) throws MemoryAccessException {
return mustRead(addr, Long.BYTES, bigEndian).getLong(0);
}
@Override
default int getLongs(Address addr, long[] dest) throws MemoryAccessException {
return getLongs(addr, dest, 0, dest.length, true);
}
@Override
default int getLongs(Address addr, long[] dest, int dIndex, int nElem)
throws MemoryAccessException {
return getLongs(addr, dest, dIndex, nElem, true);
}
@Override
default int getLongs(Address addr, long[] dest, int dIndex, int nElem, boolean bigEndian)
throws MemoryAccessException {
ByteBuffer buf = ByteBuffer.allocate(Long.BYTES * nElem);
int got = getBytes(addr, buf.array()) / Long.BYTES;
buf.order(bigEndian ? ByteOrder.BIG_ENDIAN : ByteOrder.LITTLE_ENDIAN);
buf.asLongBuffer().get(dest, dIndex, got);
return got;
}
@Override
default void setBytes(Address addr, byte[] source) throws MemoryAccessException {
setBytes(addr, source, 0, source.length);
}
@Override
default void setByte(Address addr, byte value) throws MemoryAccessException {
setBytes(addr, new byte[] { value });
}
@Override
default void setShort(Address addr, short value) throws MemoryAccessException {
setShort(addr, value, true);
}
@Override
default void setShort(Address addr, short value, boolean bigEndian)
throws MemoryAccessException {
ByteBuffer buf = ByteBuffer.allocate(Short.BYTES);
buf.order(bigEndian ? ByteOrder.BIG_ENDIAN : ByteOrder.LITTLE_ENDIAN);
buf.putShort(value);
setBytes(addr, buf.array());
}
@Override
default void setInt(Address addr, int value) throws MemoryAccessException {
setInt(addr, value, true);
}
@Override
default void setInt(Address addr, int value, boolean bigEndian) throws MemoryAccessException {
ByteBuffer buf = ByteBuffer.allocate(Integer.BYTES);
buf.order(bigEndian ? ByteOrder.BIG_ENDIAN : ByteOrder.LITTLE_ENDIAN);
buf.putInt(value);
setBytes(addr, buf.array());
}
@Override
default void setLong(Address addr, long value) throws MemoryAccessException {
setLong(addr, value, true);
}
@Override
default void setLong(Address addr, long value, boolean bigEndian) throws MemoryAccessException {
ByteBuffer buf = ByteBuffer.allocate(Long.BYTES);
buf.order(bigEndian ? ByteOrder.BIG_ENDIAN : ByteOrder.LITTLE_ENDIAN);
buf.putLong(value);
setBytes(addr, buf.array());
}
}

View file

@ -17,6 +17,7 @@ package ghidra.trace.util;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.function.BiConsumer;
import org.apache.commons.lang3.ArrayUtils;
@ -160,4 +161,22 @@ public enum TraceRegisterUtils {
}
return regs.getValue(snap, reg.getBaseRegister()).combineValues(rv);
}
public static RegisterValue getRegisterValue(Register register,
BiConsumer<Address, ByteBuffer> readAction) {
int byteLength = TraceRegisterUtils.byteLengthOf(register);
byte[] mask = register.getBaseMask();
ByteBuffer buf = ByteBuffer.allocate(mask.length * 2);
buf.put(mask);
int maskOffset = TraceRegisterUtils.computeMaskOffset(mask);
int startVal = buf.position() + maskOffset;
buf.position(startVal);
buf.limit(buf.position() + byteLength);
readAction.accept(register.getAddress(), buf);
byte[] arr = buf.array();
if (!register.isBigEndian()) {
ArrayUtils.reverse(arr, startVal, startVal + byteLength);
}
return new RegisterValue(register, arr);
}
}

View file

@ -0,0 +1,211 @@
/* ###
* 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.trace.util;
import java.util.*;
import java.util.function.Function;
import com.google.common.collect.Range;
import ghidra.program.model.address.*;
/**
* A convenience for tracking the time structure of a trace and querying the trace accordingly.
*/
public interface TraceTimeViewport {
public interface Occlusion<T> {
boolean occluded(T object, AddressRange range, Range<Long> span);
void remove(T object, AddressSet remains, Range<Long> span);
}
public interface QueryOcclusion<T> extends Occlusion<T> {
@Override
default boolean occluded(T object, AddressRange range, Range<Long> span) {
for (T found : query(range, span)) {
if (found == object) {
continue;
}
if (itemOccludes(range, found)) {
return true;
}
}
return false;
}
@Override
default void remove(T object, AddressSet remains, Range<Long> span) {
// TODO: Split query by parts of remains? Probably not worth it.
for (T found : query(
new AddressRangeImpl(remains.getMinAddress(), remains.getMaxAddress()), span)) {
if (found == object) {
continue;
}
removeItem(remains, found);
if (remains.isEmpty()) {
return;
}
}
}
Iterable<? extends T> query(AddressRange range, Range<Long> span);
boolean itemOccludes(AddressRange range, T t);
void removeItem(AddressSet remains, T t);
}
public interface RangeQueryOcclusion<T> extends QueryOcclusion<T> {
@Override
default boolean itemOccludes(AddressRange range, T t) {
return range(t).intersects(range);
}
@Override
default void removeItem(AddressSet remains, T t) {
remains.delete(range(t));
}
AddressRange range(T t);
}
public interface SetQueryOcclusion<T> extends QueryOcclusion<T> {
@Override
default boolean itemOccludes(AddressRange range, T t) {
return set(t).intersects(range.getMinAddress(), range.getMaxAddress());
}
@Override
default void removeItem(AddressSet remains, T t) {
for (AddressRange range : set(t)) {
remains.delete(range);
if (remains.isEmpty()) {
return;
}
}
}
AddressSetView set(T t);
}
void addChangeListener(Runnable l);
void removeChangeListener(Runnable l);
/**
* Check if this view is forked
*
* <p>
* The view is considered forked if any snap previous to this has a schedule with an initial
* snap other than the immediately-preceding one. Such forks "break" the linearity of the
* trace's usual time line.
*
* @return true if forked, false otherwise
*/
boolean isForked();
/**
* Check if the given lifespan contains any upper snap among the involved spans
*
* @param lifespan the lifespan to consider
* @return true if it contains any upper snap, false otherwise.
*/
boolean containsAnyUpper(Range<Long> lifespan);
/**
* Check if any part of the given object is occluded by more-recent objects
*
* @param <T> the type of the object
* @param range the address range of the object
* @param lifespan the lifespan of the object
* @param object optionally, the object to examine. Used to avoid "self occlusion"
* @param occlusion a mechanism for querying other like objects and checking for occlusion
* @return true if completely visible, false if even partially occluded
*/
<T> boolean isCompletelyVisible(AddressRange range, Range<Long> lifespan, T object,
Occlusion<T> occlusion);
/**
* Compute the parts of a given object that are visible past more-recent objects
*
* @param <T> the type of the object
* @param set the addresses comprising the object
* @param lifespan the lifespan of the object
* @param object the object to examine
* @param occlusion a mechanism for query other like objects and removing occluded parts
* @return the set of visible addresses
*/
<T> AddressSet computeVisibleParts(AddressSetView set, Range<Long> lifespan, T object,
Occlusion<T> occlusion);
/**
* Get the snaps involved in the view in most-recent-first order
*
* <p>
* The first is always this view's snap. Following are the source snaps of each previous
* snapshot's schedule where applicable.
*
* @return the list of snaps
*/
List<Long> getOrderedSnaps();
/**
* Get the snaps involved in the view in least-recent-first order
*
* @return the list of snaps
*/
List<Long> getReversedSnaps();
/**
* Get the first non-null result of the function, applied to the most-recent snaps first
*
* <p>
* Typically, func both retrieves an object and tests for its suitability.
*
* @param <T> the type of object to retrieve
* @param func the function on a snap to retrieve an object
* @return the first non-null result
*/
<T> T getTop(Function<Long, T> func);
/**
* Merge iterators from each involved snap into a single iterator
*
* <p>
* Typically, the resulting iterator is passed through a filter to test each objects
* suitability.
*
* @param <T> the type of objects in each iterator
* @param iterFunc a function on a snap to retrieve each iterator
* @param comparator the comparator for merging, which must yield the same order as each
* iterator
* @return the merged iterator
*/
<T> Iterator<T> mergedIterator(Function<Long, Iterator<T>> iterFunc,
Comparator<? super T> comparator);
/**
* Union address sets from each involved snap
*
* <p>
* The returned union is computed lazily.
*
* @param setFunc a function on a snap to retrieve the address set
* @return the union
*/
AddressSetView unionedAddresses(Function<Long, AddressSetView> setFunc);
}

View file

@ -0,0 +1,82 @@
/* ###
* 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.trace.util;
import com.google.common.collect.*;
import ghidra.trace.model.Trace;
import ghidra.trace.model.time.*;
import ghidra.util.AbstractPeekableIterator;
public class TraceViewportSpanIterator extends AbstractPeekableIterator<Range<Long>> {
private final TraceTimeManager timeManager;
private final RangeSet<Long> set = TreeRangeSet.create();
private long snap;
private boolean done = false;
public TraceViewportSpanIterator(Trace trace, long snap) {
this.timeManager = trace.getTimeManager();
this.snap = snap;
}
protected TraceSnapshot locateMostRecentFork(long from) {
while (true) {
TraceSnapshot prev = timeManager.getMostRecentSnapshot(from);
if (prev == null) {
return null;
}
TraceSchedule prevSched = prev.getSchedule();
long prevKey = prev.getKey();
if (prevSched == null) {
if (prevKey == Long.MIN_VALUE) {
return null;
}
from = prevKey - 1;
continue;
}
long forkedSnap = prevSched.getSnap();
if (forkedSnap == prevKey - 1) {
// Schedule is notational without forking
from--;
continue;
}
return prev;
}
}
@Override
protected Range<Long> seekNext() {
if (done) {
return null;
}
long curSnap = snap;
TraceSnapshot fork = locateMostRecentFork(snap);
long prevSnap = fork == null ? Long.MIN_VALUE : fork.getKey();
if (fork == null) {
done = true;
}
else if (set.contains(prevSnap)) {
done = true;
return null;
}
else {
snap = fork.getSchedule().getSnap();
}
Range<Long> range = Range.closed(prevSnap, curSnap);
set.add(range);
return range;
}
}

View file

@ -0,0 +1,44 @@
/* ###
* 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.trace.util;
import java.util.Iterator;
import ghidra.program.model.listing.CodeUnit;
import ghidra.program.model.listing.CodeUnitIterator;
public class WrappingCodeUnitIterator implements CodeUnitIterator {
protected final Iterator<? extends CodeUnit> it;
public WrappingCodeUnitIterator(Iterator<? extends CodeUnit> it) {
this.it = it;
}
@Override
public Iterator<CodeUnit> iterator() {
return this;
}
@Override
public boolean hasNext() {
return it.hasNext();
}
@Override
public CodeUnit next() {
return it.next();
}
}

View file

@ -0,0 +1,44 @@
/* ###
* 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.trace.util;
import java.util.Iterator;
import ghidra.program.model.listing.Data;
import ghidra.program.model.listing.DataIterator;
public class WrappingDataIterator implements DataIterator {
protected final Iterator<? extends Data> it;
public WrappingDataIterator(Iterator<? extends Data> it) {
this.it = it;
}
@Override
public Iterator<Data> iterator() {
return this;
}
@Override
public boolean hasNext() {
return it.hasNext();
}
@Override
public Data next() {
return it.next();
}
}

View file

@ -0,0 +1,52 @@
/* ###
* 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.trace.util;
import java.util.Iterator;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterators;
import ghidra.program.model.listing.Function;
import ghidra.program.model.listing.FunctionIterator;
public class WrappingFunctionIterator implements FunctionIterator {
private Iterator<? extends Function> it;
public WrappingFunctionIterator(Iterator<? extends Function> it) {
this.it = it;
}
public <T extends Function> WrappingFunctionIterator(Iterator<T> it,
Predicate<? super T> filter) {
this.it = Iterators.filter(it, filter);
}
@Override
public boolean hasNext() {
return it.hasNext();
}
@Override
public Function next() {
return it.next();
}
@Override
public Iterator<Function> iterator() {
return this;
}
}

View file

@ -0,0 +1,44 @@
/* ###
* 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.trace.util;
import java.util.Iterator;
import ghidra.program.model.listing.Instruction;
import ghidra.program.model.listing.InstructionIterator;
public class WrappingInstructionIterator implements InstructionIterator {
protected final Iterator<? extends Instruction> it;
public WrappingInstructionIterator(Iterator<? extends Instruction> it) {
this.it = it;
}
@Override
public Iterator<Instruction> iterator() {
return this;
}
@Override
public boolean hasNext() {
return it.hasNext();
}
@Override
public Instruction next() {
return it.next();
}
}

View file

@ -0,0 +1,641 @@
/* ###
* 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 static org.junit.Assert.*;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodHandles.Lookup;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.util.List;
import org.junit.Test;
import com.google.common.collect.Range;
import ghidra.app.plugin.assembler.Assembler;
import ghidra.app.plugin.assembler.Assemblers;
import ghidra.app.plugin.assembler.sleigh.sem.AssemblyPatternBlock;
import ghidra.app.plugin.processors.sleigh.SleighLanguage;
import ghidra.pcode.emu.PcodeThread;
import ghidra.pcode.exec.*;
import ghidra.program.model.lang.*;
import ghidra.test.AbstractGhidraHeadlessIntegrationTest;
import ghidra.trace.database.ToyDBTraceBuilder;
import ghidra.trace.model.memory.TraceMemoryFlag;
import ghidra.trace.model.memory.TraceMemoryManager;
import ghidra.trace.model.thread.TraceThread;
import ghidra.util.Msg;
import ghidra.util.NumericUtilities;
import ghidra.util.database.UndoableTransaction;
public class TracePcodeEmulatorTest extends AbstractGhidraHeadlessIntegrationTest {
/**
* Build a trace with a program ready for emulation
*
* <p>
* This creates a relatively bare-bones trace with initial state for testing trace
* emulation/interpolation. It adds ".text" and "stack" regions, creates a thread, assembles
* given instructions, and then executes the given SLEIGH source (in the context of the new
* thread) to finish initializing the trace. Note, though given first, the SLEIGH is executed
* after assembly. Thus, it can be used to modify the resulting machine code by modifying the
* memory where it was assembled.
*
* @param tb the trace builder
* @param stateInit SLEIGH source lines to execute to initialize the trace state before
* emulation. Each line must end with ";"
* @param assembly lines of assembly to place starting at {@code 0x00400000}
* @return a new trace thread, whose register state is initialized as specified
* @throws Throwable if anything goes wrong
*/
public TraceThread initTrace(ToyDBTraceBuilder tb, List<String> stateInit,
List<String> assembly) throws Throwable {
TraceMemoryManager mm = tb.trace.getMemoryManager();
TraceThread thread;
try (UndoableTransaction tid = tb.startTransaction()) {
thread = tb.getOrAddThread("Thread1", 0);
mm.addRegion("Regions[bin:.text]",
Range.atLeast(0L), tb.range(0x00400000, 0x0040ffff),
TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE);
mm.addRegion("Regions[stack1]",
Range.atLeast(0L), tb.range(0x00100000, 0x0010ffff),
TraceMemoryFlag.READ, TraceMemoryFlag.WRITE);
Assembler asm = Assemblers.getAssembler(tb.trace.getFixedProgramView(0));
InstructionBlock block =
asm.assemble(tb.addr(0x00400000), assembly.toArray(String[]::new));
Msg.info(this, "Assembly ended at: " + block.getMaxAddress());
PcodeExecutor<byte[]> exec =
TraceSleighUtils.buildByteExecutor(tb.trace, 0, thread, 0);
PcodeProgram initProg = SleighProgramCompiler.compileProgram(
(SleighLanguage) tb.language, "test", stateInit,
SleighUseropLibrary.nil());
exec.execute(initProg, SleighUseropLibrary.nil());
}
return thread;
}
/**
* Test a single instruction
*
* <p>
* This tests that the internal p-code execution is working, that intermediate writes do not
* affect the trace, and that the write-down method works. That written state is also verified
* against the expected instruction behavior.
*/
@Test
public void testSinglePUSH() throws Throwable {
try (ToyDBTraceBuilder tb = new ToyDBTraceBuilder("Test", "x86:LE:64:default")) {
TraceThread thread = initTrace(tb,
List.of(
"RIP = 0x00400000;",
"RSP = 0x00110000;"),
List.of(
"PUSH 0xdeadbeef"));
TracePcodeEmulator emu = new TracePcodeEmulator(tb.trace, 0);
PcodeThread<byte[]> emuThread = emu.newThread(thread.getPath());
emuThread.overrideContextWithDefault();
emuThread.stepInstruction();
// Verify no changes to trace
assertEquals(BigInteger.valueOf(0x00110000),
TraceSleighUtils.evaluate("RSP", tb.trace, 0, thread, 0));
assertEquals(BigInteger.valueOf(0),
TraceSleighUtils.evaluate("*:4 0x0010fffc:8", tb.trace, 0, thread, 0));
try (UndoableTransaction tid = tb.startTransaction()) {
emu.writeDown(tb.trace, 1, 1, true);
}
// 4, not 8 bytes pushed?
assertEquals(BigInteger.valueOf(0x0010fffc),
TraceSleighUtils.evaluate("RSP", tb.trace, 1, thread, 0));
assertEquals(BigInteger.valueOf(0xdeadbeefL),
TraceSleighUtils.evaluate("*:4 RSP", tb.trace, 1, thread, 0));
assertEquals(tb.addr(0x00400006),
tb.trace.getStackManager()
.getStack(thread, 1, false)
.getFrame(0, false)
.getProgramCounter());
}
}
/**
* Test two consecutive instructions
*
* <p>
* This tests both the fall-through case, and that the emulator is using the cached intermediate
* register state, rather than reading through to the trace, again.
*/
@Test
public void testDoublePUSH() throws Throwable {
try (ToyDBTraceBuilder tb = new ToyDBTraceBuilder("Test", "x86:LE:64:default")) {
TraceThread thread = initTrace(tb,
List.of(
"RIP = 0x00400000;",
"RSP = 0x00110000;"),
List.of(
"PUSH 0xdeadbeef",
"PUSH 0xbaadf00d"));
TracePcodeEmulator emu = new TracePcodeEmulator(tb.trace, 0);
PcodeThread<byte[]> emuThread = emu.newThread(thread.getPath());
emuThread.overrideContextWithDefault();
emuThread.stepInstruction();
emuThread.stepInstruction();
try (UndoableTransaction tid = tb.startTransaction()) {
emu.writeDown(tb.trace, 1, 1, false);
}
assertEquals(BigInteger.valueOf(0x0010fff8),
TraceSleighUtils.evaluate("RSP", tb.trace, 1, thread, 0));
assertEquals(BigInteger.valueOf(0xdeadbeefL),
TraceSleighUtils.evaluate("*:4 (RSP + 4)", tb.trace, 1, thread, 0));
assertEquals(BigInteger.valueOf(0xbaadf00dL),
TraceSleighUtils.evaluate("*:4 RSP", tb.trace, 1, thread, 0));
}
}
/**
* Test the branch case
*
* <p>
* This tests that branch instructions function. Both the emulator's counter and the PC of the
* machine state are verified after the JMP.
*/
@Test
public void testJMP() throws Throwable {
try (ToyDBTraceBuilder tb = new ToyDBTraceBuilder("Test", "x86:LE:64:default")) {
Register pc = tb.language.getProgramCounter();
TraceThread thread = initTrace(tb,
List.of(
"RIP = 0x00400000;",
"RSP = 0x00110000;",
"RAX = 0x12345678;"),
List.of(
"JMP 0x00400007", // 2 bytes
"MOV EAX,0xdeadbeef", // 5 bytes
"MOV ECX,0xbaadf00d")); // 5 bytes
TracePcodeEmulator emu = new TracePcodeEmulator(tb.trace, 0);
PcodeThread<byte[]> emuThread = emu.newThread(thread.getPath());
emuThread.overrideContextWithDefault();
emuThread.stepInstruction();
assertEquals(tb.addr(0x00400007), emuThread.getCounter());
assertArrayEquals(tb.arr(0x07, 0, 0x40, 0, 0, 0, 0, 0),
emuThread.getState().getVar(pc));
emuThread.stepInstruction();
try (UndoableTransaction tid = tb.startTransaction()) {
emu.writeDown(tb.trace, 1, 1, false);
}
assertEquals(BigInteger.valueOf(0x00110000),
TraceSleighUtils.evaluate("RSP", tb.trace, 1, thread, 0));
assertEquals(BigInteger.valueOf(0x0040000c),
TraceSleighUtils.evaluate("RIP", tb.trace, 1, thread, 0));
assertEquals(BigInteger.valueOf(0x12345678),
TraceSleighUtils.evaluate("RAX", tb.trace, 1, thread, 0));
assertEquals(BigInteger.valueOf(0xbaadf00dL),
TraceSleighUtils.evaluate("RCX", tb.trace, 1, thread, 0));
}
}
/**
* Test branch with flow
*
* <p>
* This will test both context flow and some language-specific state modifiers, since ARM needs
* to truncate the last bit when jumping into THUMB mode.
*/
@Test
public void testBX() throws Throwable {
try (ToyDBTraceBuilder tb = new ToyDBTraceBuilder("Test", "ARM:LE:32:v8")) {
Register pc = tb.language.getProgramCounter();
Register ctxreg = tb.language.getContextBaseRegister();
Register tmode = tb.language.getRegister("TMode");
Assembler asm = Assemblers.getAssembler(tb.trace.getFixedProgramView(0));
RegisterValue thumbCtx =
new RegisterValue(ctxreg, BigInteger.ZERO).assign(tmode, BigInteger.ONE);
AssemblyPatternBlock thumbPat = AssemblyPatternBlock.fromRegisterValue(thumbCtx);
// NOTE: Assemble the thumb section separately
TraceThread thread = initTrace(tb,
List.of(
"pc = 0x00400000;",
"sp = 0x00110000;",
"*:4 0x00400008:4 = 0x00401001;"), // immediately after bx
List.of(
"ldr r6, [pc,#0]!", // 4 bytes, pc+4 should be 00400008
"bx r6")); // 4 bytes
byte[] mov = asm.assembleLine(tb.addr(0x00401000),
"mov r0, #123", thumbPat); // #123 is decimal
try (UndoableTransaction tid = tb.startTransaction()) {
asm.patchProgram(mov, tb.addr(0x00401000));
}
TracePcodeEmulator emu = new TracePcodeEmulator(tb.trace, 0);
PcodeThread<byte[]> emuThread = emu.newThread(thread.getPath());
emuThread.overrideContextWithDefault();
emuThread.stepInstruction();
emuThread.stepInstruction();
assertEquals(tb.addr(0x00401000), emuThread.getCounter());
assertArrayEquals(tb.arr(0, 0x10, 0x40, 0),
emuThread.getState().getVar(pc));
assertEquals(new RegisterValue(ctxreg, BigInteger.valueOf(0x8000_0000_0000_0000L)),
emuThread.getContext());
assertArrayEquals(tb.arr(0, 0, 0, 0, 0, 0, 0, 0x80),
emuThread.getState().getVar(ctxreg));
emuThread.stepInstruction();
try (UndoableTransaction tid = tb.startTransaction()) {
emu.writeDown(tb.trace, 1, 1, false);
}
assertEquals(BigInteger.valueOf(0x00110000),
TraceSleighUtils.evaluate("sp", tb.trace, 1, thread, 0));
assertEquals(BigInteger.valueOf(0x00401002),
TraceSleighUtils.evaluate("pc", tb.trace, 1, thread, 0));
assertEquals(BigInteger.valueOf(123),
TraceSleighUtils.evaluate("r0", tb.trace, 1, thread, 0));
}
}
/**
* This tests an language without a contextreg
*/
@Test
public void testIMM() throws Throwable {
try (ToyDBTraceBuilder tb = new ToyDBTraceBuilder("Test", "Toy:BE:64:default")) {
assertNull(tb.language.getContextBaseRegister());
TraceThread thread = initTrace(tb,
List.of(
"pc = 0x00400000;",
"sp = 0x00110000;"),
List.of(
"imm r0, #1234")); // decimal
TracePcodeEmulator emu = new TracePcodeEmulator(tb.trace, 0);
PcodeThread<byte[]> emuThread = emu.newThread(thread.getPath());
emuThread.overrideContextWithDefault();
emuThread.stepInstruction();
try (UndoableTransaction tid = tb.startTransaction()) {
emu.writeDown(tb.trace, 1, 1, false);
}
assertEquals(BigInteger.valueOf(0x00110000),
TraceSleighUtils.evaluate("sp", tb.trace, 1, thread, 0));
assertEquals(BigInteger.valueOf(0x00400002),
TraceSleighUtils.evaluate("pc", tb.trace, 1, thread, 0));
assertEquals(BigInteger.valueOf(1234),
TraceSleighUtils.evaluate("r0", tb.trace, 1, thread, 0));
}
}
/**
* This tests the delay-slot semantics of the emulator
*/
@Test
public void testBRDS() throws Throwable {
try (ToyDBTraceBuilder tb = new ToyDBTraceBuilder("Test", "Toy:BE:64:default")) {
// TODO: Seems traces do not take delay-slotted instructions well...
// Assemble to the side and just write bytes in until that's fixed
Assembler asm = Assemblers.getAssembler(tb.trace.getFixedProgramView(0));
TraceThread thread = initTrace(tb,
List.of(
"pc = 0x00400000;",
"sp = 0x00110000;"),
List.of());
try (UndoableTransaction tid = tb.startTransaction()) {
tb.trace.getMemoryManager()
.putBytes(0, tb.addr(0x00400000), ByteBuffer.wrap(
asm.assembleLine(tb.addr(0x00400000), "brds 0x00400006")));
tb.trace.getMemoryManager()
.putBytes(0, tb.addr(0x00400002), ByteBuffer.wrap(
asm.assembleLine(tb.addr(0x00400002), "imm r0, #1234"))); // decimal
tb.trace.getMemoryManager()
.putBytes(0, tb.addr(0x00400004), ByteBuffer.wrap(
asm.assembleLine(tb.addr(0x00400004), "imm r0, #2020")));
tb.trace.getMemoryManager()
.putBytes(0, tb.addr(0x00400006), ByteBuffer.wrap(
asm.assembleLine(tb.addr(0x00400006), "imm r1, #2021")));
}
TracePcodeEmulator emu = new TracePcodeEmulator(tb.trace, 0);
PcodeThread<byte[]> emuThread = emu.newThread(thread.getPath());
emuThread.overrideContextWithDefault();
emuThread.stepInstruction(); // brds and 1st imm executed
emuThread.stepInstruction(); // 3rd imm executed
try (UndoableTransaction tid = tb.startTransaction()) {
emu.writeDown(tb.trace, 1, 1, false);
}
assertEquals(BigInteger.valueOf(0x00400008),
TraceSleighUtils.evaluate("pc", tb.trace, 1, thread, 0));
assertEquals(BigInteger.valueOf(1234),
TraceSleighUtils.evaluate("r0", tb.trace, 1, thread, 0));
assertEquals(BigInteger.valueOf(2021),
TraceSleighUtils.evaluate("r1", tb.trace, 1, thread, 0));
}
}
/**
* Test the instruction decoder considers the cached state
*
* <p>
* This may not reflect the semantics of an actual processor in these situations, since they may
* have instruction caching. Emulating such semantics is TODO, if at all. NB. This also tests
* that PC-relative addressing works, since internally the emulator advances the counter after
* execution of each instruction. Addressing is computed by the SLEIGH instruction parser and
* encoded as a constant deref in the p-code.
*/
@Test
public void testSelfModifyingX86() throws Throwable {
try (ToyDBTraceBuilder tb = new ToyDBTraceBuilder("Test", "x86:LE:64:default")) {
TraceThread thread = initTrace(tb,
List.of(
"RIP = 0x00400000;",
"RSP = 0x00110000;",
"RAX = 0x12345678;",
// NB. Assembly actually happens first, so this is modifying
"*:1 0x00400007:8 = *0x00400007:8 ^ 0xcc;"),
List.of(
// First instruction undoes the modification above
"XOR byte ptr [0x00400007], 0xcc", // 7 bytes
"MOV EAX,0xdeadbeef")); // 5 bytes
TracePcodeEmulator emu = new TracePcodeEmulator(tb.trace, 0);
PcodeThread<byte[]> emuThread = emu.newThread(thread.getPath());
emuThread.overrideContextWithDefault();
emuThread.stepInstruction();
emuThread.stepInstruction();
try (UndoableTransaction tid = tb.startTransaction()) {
emu.writeDown(tb.trace, 1, 1, false);
}
assertEquals(BigInteger.valueOf(0x00110000),
TraceSleighUtils.evaluate("RSP", tb.trace, 1, thread, 0));
assertEquals(BigInteger.valueOf(0x0040000c),
TraceSleighUtils.evaluate("RIP", tb.trace, 1, thread, 0));
assertEquals(BigInteger.valueOf(0xdeadbeefL),
TraceSleighUtils.evaluate("RAX", tb.trace, 1, thread, 0));
}
}
/**
* Test a two-instruction sample with p-code stepping
*
* <p>
* Two instructions are used here to ensure that stepping will proceed to the next instruction.
* This will also serve as an evaluation of the API.
*/
@Test
public void testDoublePUSH_pCode() throws Throwable {
try (ToyDBTraceBuilder tb = new ToyDBTraceBuilder("Test", "x86:LE:64:default")) {
TraceThread thread = initTrace(tb,
List.of(
"RIP = 0x00400000;",
"RSP = 0x00110000;"),
List.of(
"PUSH 0xdeadbeef",
"PUSH 0xbaadf00d"));
TracePcodeEmulator emu = new TracePcodeEmulator(tb.trace, 0);
PcodeThread<byte[]> emuThread = emu.newThread(thread.getPath());
emuThread.overrideContextWithDefault();
assertNull(emuThread.getFrame());
emuThread.stepPcodeOp();
for (int i = 0; !emuThread.getFrame().isFinished(); i++) {
assertEquals(i, emuThread.getFrame().index());
emuThread.stepPcodeOp();
}
assertTrue(emuThread.getFrame().isFallThrough());
assertEquals(tb.addr(0x00400000), emuThread.getCounter());
emuThread.stepPcodeOp();
assertNull(emuThread.getFrame());
assertEquals(tb.addr(0x00400006), emuThread.getCounter());
emuThread.stepPcodeOp();
assertEquals(0, emuThread.getFrame().index());
emuThread.finishInstruction();
assertNull(emuThread.getFrame());
try (UndoableTransaction tid = tb.startTransaction()) {
emu.writeDown(tb.trace, 1, 1, false);
}
assertEquals(BigInteger.valueOf(0x0040000c),
TraceSleighUtils.evaluate("RIP", tb.trace, 1, thread, 0));
assertEquals(BigInteger.valueOf(0x0010fff8),
TraceSleighUtils.evaluate("RSP", tb.trace, 1, thread, 0));
assertEquals(BigInteger.valueOf(0xdeadbeefL),
TraceSleighUtils.evaluate("*:4 (RSP + 4)", tb.trace, 1, thread, 0));
assertEquals(BigInteger.valueOf(0xbaadf00dL),
TraceSleighUtils.evaluate("*:4 RSP", tb.trace, 1, thread, 0));
}
}
/**
* Test the inject method
*
* <p>
* This tests that injects work, and that they can invoke a userop from a client-provided
* library
*/
@Test
public void testInject() throws Throwable {
try (ToyDBTraceBuilder tb = new ToyDBTraceBuilder("Test", "x86:LE:64:default")) {
final StringBuilder dumped = new StringBuilder();
SleighUseropLibrary<byte[]> library = new AnnotatedSleighUseropLibrary<byte[]>() {
@Override
protected Lookup getMethodLookup() {
return MethodHandles.lookup();
}
@SleighUserop
public void hexdump(byte[] in) {
dumped.append(NumericUtilities.convertBytesToString(in));
}
};
TraceThread thread = initTrace(tb,
List.of(
"RIP = 0x00400000;",
"RSP = 0x00110000;"),
List.of(
"PUSH 0xdeadbeef",
"PUSH 0xbaadf00d"));
TracePcodeEmulator emu = new TracePcodeEmulator(tb.trace, 0, library);
emu.inject(tb.addr(0x00400006), List.of("hexdump(RSP);"));
PcodeThread<byte[]> emuThread = emu.newThread(thread.getPath());
emuThread.overrideContextWithDefault();
emuThread.stepInstruction();
assertEquals("", dumped.toString());
emuThread.stepInstruction();
assertEquals("fcff100000000000", dumped.toString()); // LE
}
}
/**
* Test that injects and interrupts work
*
* <p>
* We'll put the interrupt within a more involved inject, so we can single-step the remainder of
* the inject, too. This will check the semantics of stepping over the interrupt.
*/
@Test
public void testInjectedInterrupt() throws Throwable {
try (ToyDBTraceBuilder tb = new ToyDBTraceBuilder("Test", "x86:LE:64:default")) {
final StringBuilder dumped = new StringBuilder();
SleighUseropLibrary<byte[]> library = new AnnotatedSleighUseropLibrary<byte[]>() {
@Override
protected Lookup getMethodLookup() {
return MethodHandles.lookup();
}
@SleighUserop
public void hexdump(byte[] in) {
dumped.append(NumericUtilities.convertBytesToString(in));
}
};
TraceThread thread = initTrace(tb,
List.of(
"RIP = 0x00400000;",
"RSP = 0x00110000;"),
List.of(
"PUSH 0xdeadbeef",
"PUSH 0xbaadf00d"));
TracePcodeEmulator emu = new TracePcodeEmulator(tb.trace, 0, library);
emu.inject(tb.addr(0x00400006), List.of(
"hexdump(RSP);",
"emu_swi();",
"hexdump(RIP);",
"emu_exec_decoded();",
"hexdump(RIP);"));
PcodeThread<byte[]> emuThread = emu.newThread(thread.getPath());
emuThread.overrideContextWithDefault();
try {
emuThread.run();
}
catch (InterruptPcodeExecutionException e) {
assertEquals(e.getFrame(), emuThread.getFrame());
}
assertEquals("fcff100000000000", dumped.toString()); // LE
dumped.delete(0, dumped.length());
emuThread.stepPcodeOp();
assertEquals("0600400000000000", dumped.toString());
dumped.delete(0, dumped.length());
emuThread.finishInstruction();
assertEquals("0c00400000000000", dumped.toString());
dumped.delete(0, dumped.length());
try (UndoableTransaction tid = tb.startTransaction()) {
emu.writeDown(tb.trace, 1, 1, false);
}
assertEquals(BigInteger.valueOf(0xbaadf00dL),
TraceSleighUtils.evaluate("*:4 RSP", tb.trace, 1, thread, 0));
}
}
/**
* Test that conditional breakpoints work
*/
@Test
public void testBreakpoints() throws Throwable {
try (ToyDBTraceBuilder tb = new ToyDBTraceBuilder("Test", "x86:LE:64:default")) {
TraceThread thread = initTrace(tb,
List.of(
"RIP = 0x00400000;",
"RSP = 0x00110000;",
"RAX = 0;"),
List.of(
"PUSH 0xdeadbeef",
"PUSH 0xbaadf00d"));
TracePcodeEmulator emu = new TracePcodeEmulator(tb.trace, 0);
emu.addBreakpoint(tb.addr(0x00400000), "RAX == 1");
emu.addBreakpoint(tb.addr(0x00400006), "RAX == 0");
PcodeThread<byte[]> emuThread = emu.newThread(thread.getPath());
emuThread.overrideContextWithDefault();
try {
emuThread.run();
}
catch (InterruptPcodeExecutionException e) {
assertEquals(e.getFrame(), emuThread.getFrame());
}
assertEquals(tb.addr(0x00400006), emuThread.getCounter());
}
}
/**
* Test ARM's CLZ instruction
*
* <p>
* This tests that the state modifiers are properly invoked on CALLOTHER.
*/
@Test
public void testCLZ() throws Throwable {
try (ToyDBTraceBuilder tb = new ToyDBTraceBuilder("Test", "ARM:LE:32:v8")) {
TraceThread thread = initTrace(tb,
List.of(
"pc = 0x00400000;",
"sp = 0x00110000;",
"r0 = 0x00008000;"),
List.of(
"clz r1, r0"));
TracePcodeEmulator emu = new TracePcodeEmulator(tb.trace, 0);
PcodeThread<byte[]> emuThread = emu.newThread(thread.getPath());
emuThread.overrideContextWithDefault();
emuThread.stepInstruction();
try (UndoableTransaction tid = tb.startTransaction()) {
emu.writeDown(tb.trace, 1, 1, false);
}
assertEquals(BigInteger.valueOf(16),
TraceSleighUtils.evaluate("r1", tb.trace, 1, thread, 0));
}
}
}

View file

@ -206,7 +206,7 @@ public class TraceSleighUtilsTest extends AbstractGhidraHeadlessIntegrationTest
@Test
public void testCompileSleighProgram() throws Exception {
try (ToyDBTraceBuilder b = new ToyDBTraceBuilder("test", TOY_BE_64_HARVARD)) {
SleighProgram sp = SleighProgramCompiler.compileProgram((SleighLanguage) b.language,
PcodeProgram sp = SleighProgramCompiler.compileProgram((SleighLanguage) b.language,
"test", List.of(
"if (r0) goto <else>;",
" r1 = 6;",

View file

@ -403,7 +403,7 @@ public class DBTraceReferenceManagerTest extends AbstractGhidraHeadlessIntegrati
}
assertEquals(Set.of(flowRef),
new HashSet<>(manager.getFlowRefrencesFrom(0, b.addr(0x4000))));
new HashSet<>(manager.getFlowReferencesFrom(0, b.addr(0x4000))));
}
@Test

View file

@ -0,0 +1,519 @@
/* ###
* 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.trace.model.time;
import static org.junit.Assert.*;
import java.util.ArrayList;
import java.util.List;
import org.junit.Test;
import ghidra.pcode.emu.AbstractPcodeMachine;
import ghidra.pcode.emu.AbstractPcodeMachine.ThreadPcodeExecutorState;
import ghidra.pcode.emu.PcodeThread;
import ghidra.pcode.exec.*;
import ghidra.program.model.address.Address;
import ghidra.program.model.lang.RegisterValue;
import ghidra.test.AbstractGhidraHeadlessIntegrationTest;
import ghidra.trace.database.ToyDBTraceBuilder;
import ghidra.trace.model.thread.TraceThread;
import ghidra.trace.model.time.TraceSchedule.*;
import ghidra.util.database.UndoableTransaction;
import ghidra.util.task.TaskMonitor;
public class TraceScheduleTest extends AbstractGhidraHeadlessIntegrationTest {
@Test
public void testParseZero() {
TraceSchedule time = TraceSchedule.parse("0:0");
assertEquals(new TraceSchedule(0, TickSequence.of(), TickSequence.of()), time);
}
@Test
public void testParseSimple() {
TraceSchedule time = TraceSchedule.parse("0:100");
assertEquals(
new TraceSchedule(0, TickSequence.of(new TickStep(-1, 100)), TickSequence.of()), time);
}
@Test
public void testToStringSimple() {
assertEquals("0:100",
new TraceSchedule(0, TickSequence.of(new TickStep(-1, 100)), TickSequence.of())
.toString());
}
@Test
public void testParseWithPcodeSteps() {
TraceSchedule time = TraceSchedule.parse("0:100.5");
assertEquals(new TraceSchedule(0, TickSequence.of(new TickStep(-1, 100)),
TickSequence.of(new TickStep(-1, 5))), time);
}
@Test
public void testToStringWithPcodeSteps() {
assertEquals("0:100.5", new TraceSchedule(0, TickSequence.of(new TickStep(-1, 100)),
TickSequence.of(new TickStep(-1, 5))).toString());
}
@Test
public void testParseWithThread() {
TraceSchedule time = TraceSchedule.parse("1:t3-100");
assertEquals(new TraceSchedule(1, TickSequence.of(new TickStep(3, 100)), TickSequence.of()),
time);
}
@Test
public void testToStringWithThread() {
assertEquals("1:t3-100",
new TraceSchedule(1, TickSequence.of(new TickStep(3, 100)), TickSequence.of())
.toString());
}
@Test
public void testParseMultipleSteps() {
TraceSchedule time = TraceSchedule.parse("1:50,t3-50");
assertEquals(new TraceSchedule(1,
TickSequence.of(new TickStep(-1, 50), new TickStep(3, 50)), TickSequence.of()), time);
}
@Test
public void testToStringMultipleSteps() {
assertEquals("1:50,t3-50",
new TraceSchedule(1, TickSequence.of(new TickStep(-1, 50), new TickStep(3, 50)),
TickSequence.of()).toString());
}
@Test(expected = IllegalArgumentException.class)
public void testParseNegativeStepErr() {
TraceSchedule.parse("0:-100");
}
@Test(expected = IllegalArgumentException.class)
public void testNegativeStepErr() {
new TickStep(0, -100);
}
@Test(expected = IllegalArgumentException.class)
public void testParseBadStepForm3Parts() {
TraceSchedule.parse("0:t1-10-10");
}
@Test(expected = IllegalArgumentException.class)
public void testParseBadStepFormMissingT() {
TraceSchedule.parse("0:1-10");
}
@Test
public void testAdvance() {
TickSequence seq = new TickSequence();
seq.advance(new TickStep(-1, 0));
assertEquals(TickSequence.of(), seq);
seq.advance(new TickStep(-1, 10));
assertEquals(TickSequence.of(new TickStep(-1, 10)), seq);
seq.advance(new TickStep(-1, 10));
assertEquals(TickSequence.of(new TickStep(-1, 20)), seq);
seq.advance(new TickStep(1, 10));
assertEquals(TickSequence.of(new TickStep(-1, 20), new TickStep(1, 10)), seq);
seq.advance(new TickStep(-1, 10));
assertEquals(TickSequence.of(new TickStep(-1, 20), new TickStep(1, 20)), seq);
seq.advance(seq);
assertEquals(TickSequence.of(new TickStep(-1, 20), new TickStep(1, 60)), seq);
}
@Test(expected = IllegalArgumentException.class)
public void testAdvanceNegativeErr() {
new TickStep(-1, 10).advance(-10);
}
@Test(expected = IllegalArgumentException.class)
public void testAdvanceOverflowErr() {
new TickStep(-1, Long.MAX_VALUE).advance(Long.MAX_VALUE);
}
@Test
public void testRewind() {
TickSequence seq = TickSequence.parse("10,t1-20,t2-30");
assertEquals(0, seq.rewind(5));
assertEquals("10,t1-20,t2-25", seq.toString());
assertEquals(0, seq.rewind(25));
assertEquals("10,t1-20", seq.toString());
assertEquals(0, seq.rewind(27));
assertEquals("3", seq.toString());
assertEquals(7, seq.rewind(10));
assertEquals("", seq.toString());
assertEquals(10, seq.rewind(10));
assertEquals("", seq.toString());
}
@Test(expected = IllegalArgumentException.class)
public void testRewindNegativeErr() {
TickSequence seq = TickSequence.parse("10,t1-20,t2-30");
seq.rewind(-1);
}
@Test
public void testEquals() {
TraceSchedule time = TraceSchedule.parse("0:10");
assertTrue(time.equals(time));
assertFalse(TraceSchedule.parse("0:10").equals(null));
assertFalse(TraceSchedule.parse("0:10").equals("Hello"));
assertFalse(TraceSchedule.parse("0:t0-10").equals(TraceSchedule.parse("1:t0-10")));
assertFalse(TraceSchedule.parse("0:t0-10").equals(TraceSchedule.parse("0:t1-10")));
assertFalse(TraceSchedule.parse("0:t0-10").equals(TraceSchedule.parse("0:t0-11")));
assertFalse(TraceSchedule.parse("0:t0-10").equals(TraceSchedule.parse("0:t0-10.1")));
assertTrue(TraceSchedule.parse("0:t0-10").equals(TraceSchedule.parse("0:t0-10")));
}
protected void expectU(String specL, String specG) {
TraceSchedule timeL = TraceSchedule.parse(specL);
TraceSchedule timeG = TraceSchedule.parse(specG);
assertEquals(CompareResult.UNREL_LT, timeL.compareSchedule(timeG));
assertEquals(CompareResult.UNREL_GT, timeG.compareSchedule(timeL));
}
protected void expectR(String specL, String specG) {
TraceSchedule timeL = TraceSchedule.parse(specL);
TraceSchedule timeG = TraceSchedule.parse(specG);
assertEquals(CompareResult.REL_LT, timeL.compareSchedule(timeG));
assertEquals(CompareResult.REL_GT, timeG.compareSchedule(timeL));
}
protected void expectE(String specL, String specG) {
TraceSchedule timeL = TraceSchedule.parse(specL);
TraceSchedule timeG = TraceSchedule.parse(specG);
assertEquals(CompareResult.EQUALS, timeL.compareSchedule(timeG));
assertEquals(CompareResult.EQUALS, timeG.compareSchedule(timeL));
}
@Test
public void testCompare() {
expectU("0:10", "1:10");
expectU("0:t0-10", "0:t1-10");
// We don't know how many p-code steps complete an instruction step
expectU("0:t0-10.1", "0:t0-11");
expectU("0:t0-10,t1-5", "0:t0-11,t1-5");
expectR("0:t0-10", "0:t0-11");
expectR("0:t0-10", "0:t0-10,t1-5");
expectR("0:t0-10", "0:t0-11,t1-5");
expectR("0:t0-10", "0:t0-10.1");
expectR("0:t0-10", "0:t0-11.1");
expectR("0:t0-10", "0:t0-10,t1-5.1");
expectR("0:t0-10", "0:t0-11,t1-5.1");
expectE("0:t0-10", "0:t0-10");
expectE("0:t0-10.1", "0:t0-10.1");
}
public String strRelativize(String fromSpec, String toSpec) {
TickSequence seq = TickSequence.parse(toSpec).relativize(TickSequence.parse(fromSpec));
return seq == null ? null : seq.toString();
}
@Test
public void testRelativize() {
assertEquals("10", strRelativize("", "10"));
assertEquals("", strRelativize("10", "10"));
assertEquals("9", strRelativize("1", "10"));
assertEquals("t1-9", strRelativize("t1-1", "t1-10"));
assertEquals("t1-10", strRelativize("5", "5,t1-10"));
}
@Test(expected = IllegalArgumentException.class)
public void testRelativizeNotPrefixErr() {
strRelativize("t1-5", "5");
}
@Test
public void testTotalStepCount() {
assertEquals(15, TraceSchedule.parse("0:4,t1-5.6").totalTickCount());
}
protected static class TestThread implements PcodeThread<Void> {
protected final String name;
protected final TestMachine machine;
public TestThread(String name, TestMachine machine) {
this.name = name;
this.machine = machine;
}
@Override
public String getName() {
return name;
}
@Override
public TestMachine getMachine() {
return machine;
}
@Override
public void setCounter(Address counter) {
}
@Override
public Address getCounter() {
return null;
}
@Override
public void overrideCounter(Address counter) {
}
@Override
public void assignContext(RegisterValue context) {
}
@Override
public RegisterValue getContext() {
return null;
}
@Override
public void overrideContext(RegisterValue context) {
}
@Override
public void overrideContextWithDefault() {
}
@Override
public void reInitialize() {
}
@Override
public void stepInstruction() {
machine.record.add("s:" + name);
}
@Override
public void stepPcodeOp() {
machine.record.add("p:" + name);
}
@Override
public PcodeFrame getFrame() {
return null;
}
@Override
public void executeInstruction() {
}
@Override
public void finishInstruction() {
}
@Override
public void skipInstruction() {
}
@Override
public void dropInstruction() {
}
@Override
public void run() {
}
@Override
public void setSuspended(boolean suspended) {
}
@Override
public PcodeExecutor<Void> getExecutor() {
return null;
}
@Override
public SleighUseropLibrary<Void> getUseropLibrary() {
return null;
}
@Override
public ThreadPcodeExecutorState<Void> getState() {
return null;
}
@Override
public void inject(Address address, List<String> sleigh) {
}
@Override
public void clearInject(Address address) {
}
@Override
public void clearAllInjects() {
}
}
protected static class TestMachine extends AbstractPcodeMachine<Void> {
protected final List<String> record = new ArrayList<>();
public TestMachine() {
super(null, null, null);
}
@Override
protected PcodeThread<Void> createThread(String name) {
return new TestThread(name, this);
}
@Override
protected PcodeExecutorState<Void> createMemoryState() {
return null;
}
@Override
protected PcodeExecutorState<Void> createRegisterState(PcodeThread<Void> thread) {
return null;
}
}
@Test
public void testExecute() throws Exception {
TestMachine machine = new TestMachine();
TraceSchedule time = TraceSchedule.parse("1:4,t0-3,t1-2.1");
try (ToyDBTraceBuilder tb = new ToyDBTraceBuilder("test", "Toy:BE:64:default")) {
TraceThread t2;
try (UndoableTransaction tid = tb.startTransaction()) {
tb.trace.getThreadManager().createThread("Threads[0]", 0);
tb.trace.getThreadManager().createThread("Threads[1]", 0);
t2 = tb.trace.getThreadManager().createThread("Threads[2]", 0);
tb.trace.getTimeManager().getSnapshot(1, true).setEventThread(t2);
}
time.execute(tb.trace, machine, TaskMonitor.DUMMY);
}
assertEquals(List.of(
"s:Threads[2]",
"s:Threads[2]",
"s:Threads[2]",
"s:Threads[2]",
"s:Threads[0]",
"s:Threads[0]",
"s:Threads[0]",
"s:Threads[1]",
"s:Threads[1]",
"p:Threads[1]"),
machine.record);
}
@Test(expected = IllegalArgumentException.class)
public void testExecuteNoEventThreadErr() throws Exception {
TestMachine machine = new TestMachine();
TraceSchedule time = TraceSchedule.parse("1:4,t0-3,t1-2.1");
try (ToyDBTraceBuilder tb = new ToyDBTraceBuilder("test", "Toy:BE:64:default")) {
try (UndoableTransaction tid = tb.startTransaction()) {
tb.trace.getThreadManager().createThread("Threads[0]", 0);
tb.trace.getThreadManager().createThread("Threads[1]", 0);
tb.trace.getThreadManager().createThread("Threads[2]", 0);
tb.trace.getTimeManager().getSnapshot(1, true);
}
time.execute(tb.trace, machine, TaskMonitor.DUMMY);
}
}
@Test(expected = IllegalArgumentException.class)
public void testExecuteBadThreadKeyErr() throws Exception {
TestMachine machine = new TestMachine();
TraceSchedule time = TraceSchedule.parse("1:4,t0-3,t5-2.1");
try (ToyDBTraceBuilder tb = new ToyDBTraceBuilder("test", "Toy:BE:64:default")) {
TraceThread t2;
try (UndoableTransaction tid = tb.startTransaction()) {
tb.trace.getThreadManager().createThread("Threads[0]", 0);
tb.trace.getThreadManager().createThread("Threads[1]", 0);
t2 = tb.trace.getThreadManager().createThread("Threads[2]", 0);
tb.trace.getTimeManager().getSnapshot(1, true).setEventThread(t2);
}
time.execute(tb.trace, machine, TaskMonitor.DUMMY);
}
}
@Test
public void testFinish() throws Exception {
TestMachine machine = new TestMachine();
TraceSchedule time = TraceSchedule.parse("1:4,t0-3,t1-2.1");
try (ToyDBTraceBuilder tb = new ToyDBTraceBuilder("test", "Toy:BE:64:default")) {
TraceThread t2;
try (UndoableTransaction tid = tb.startTransaction()) {
tb.trace.getThreadManager().createThread("Threads[0]", 0);
tb.trace.getThreadManager().createThread("Threads[1]", 0);
t2 = tb.trace.getThreadManager().createThread("Threads[2]", 0);
tb.trace.getTimeManager().getSnapshot(1, true).setEventThread(t2);
}
time.finish(tb.trace, TraceSchedule.parse("1:4,t0-2"), machine, TaskMonitor.DUMMY);
}
assertEquals(List.of(
"s:Threads[0]",
"s:Threads[1]",
"s:Threads[1]",
"p:Threads[1]"),
machine.record);
}
@Test
public void testFinishPcode() throws Exception {
TestMachine machine = new TestMachine();
TraceSchedule time = TraceSchedule.parse("1:4,t0-3,t1-2.1");
try (ToyDBTraceBuilder tb = new ToyDBTraceBuilder("test", "Toy:BE:64:default")) {
TraceThread t2;
try (UndoableTransaction tid = tb.startTransaction()) {
tb.trace.getThreadManager().createThread("Threads[0]", 0);
tb.trace.getThreadManager().createThread("Threads[1]", 0);
t2 = tb.trace.getThreadManager().createThread("Threads[2]", 0);
tb.trace.getTimeManager().getSnapshot(1, true).setEventThread(t2);
}
time.finish(tb.trace, TraceSchedule.parse("1:4,t0-3,t1-2"), machine,
TaskMonitor.DUMMY);
}
assertEquals(List.of(
"p:Threads[1]"),
machine.record);
}
@Test(expected = IllegalArgumentException.class)
public void testFinishUnrelatedErr() throws Exception {
TestMachine machine = new TestMachine();
TraceSchedule time = TraceSchedule.parse("1:4,t0-3,t1-2.1");
try (ToyDBTraceBuilder tb = new ToyDBTraceBuilder("test", "Toy:BE:64:default")) {
TraceThread t2;
try (UndoableTransaction tid = tb.startTransaction()) {
tb.trace.getThreadManager().createThread("Threads[0]", 0);
tb.trace.getThreadManager().createThread("Threads[1]", 0);
t2 = tb.trace.getThreadManager().createThread("Threads[2]", 0);
tb.trace.getTimeManager().getSnapshot(1, true).setEventThread(t2);
}
time.finish(tb.trace, TraceSchedule.parse("1:4,t0-4"), machine, TaskMonitor.DUMMY);
}
}
}

View file

@ -0,0 +1,106 @@
/* ###
* 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.trace.util;
import static org.junit.Assert.assertEquals;
import java.util.List;
import org.junit.Test;
import com.google.common.collect.*;
import ghidra.test.AbstractGhidraHeadlessIntegrationTest;
import ghidra.trace.database.ToyDBTraceBuilder;
import ghidra.trace.database.time.DBTraceTimeManager;
import ghidra.trace.model.time.TraceSchedule;
import ghidra.util.database.UndoableTransaction;
public class DefaultTraceTimeViewportTest extends AbstractGhidraHeadlessIntegrationTest {
public static <C extends Comparable<C>> RangeSet<C> rangeSetOf(List<Range<C>> ranges) {
RangeSet<C> result = TreeRangeSet.create();
ranges.forEach(result::add);
return result;
}
@Test
public void testEmptyTime() throws Exception {
try (ToyDBTraceBuilder tb = new ToyDBTraceBuilder("test", "Toy:BE:64:default")) {
DefaultTraceTimeViewport viewport = new DefaultTraceTimeViewport(tb.trace);
viewport.setSnap(10);
assertEquals(rangeSetOf(List.of(Range.closed(Long.MIN_VALUE, 10L))), viewport.spanSet);
}
}
@Test
public void testSelfScheduleSnapshot0RemovesScratch() throws Exception {
try (ToyDBTraceBuilder tb = new ToyDBTraceBuilder("test", "Toy:BE:64:default")) {
try (UndoableTransaction tid = tb.startTransaction()) {
tb.trace.getTimeManager().getSnapshot(0, true).setSchedule(TraceSchedule.snap(0));
}
DefaultTraceTimeViewport viewport = new DefaultTraceTimeViewport(tb.trace);
viewport.setSnap(10);
assertEquals(rangeSetOf(List.of(Range.closed(0L, 10L))), viewport.spanSet);
}
}
@Test
public void testNotationalSchedulesDontFork() throws Exception {
try (ToyDBTraceBuilder tb = new ToyDBTraceBuilder("test", "Toy:BE:64:default")) {
try (UndoableTransaction tid = tb.startTransaction()) {
DBTraceTimeManager tm = tb.trace.getTimeManager();
tm.getSnapshot(0, true).setSchedule(TraceSchedule.snap(0));
tm.getSnapshot(5, true).setSchedule(TraceSchedule.parse("4:1"));
}
DefaultTraceTimeViewport viewport = new DefaultTraceTimeViewport(tb.trace);
viewport.setSnap(10);
assertEquals(rangeSetOf(List.of(Range.closed(0L, 10L))), viewport.spanSet);
}
}
@Test
public void testForkFromScratch() throws Exception {
try (ToyDBTraceBuilder tb = new ToyDBTraceBuilder("test", "Toy:BE:64:default")) {
try (UndoableTransaction tid = tb.startTransaction()) {
DBTraceTimeManager tm = tb.trace.getTimeManager();
tm.getSnapshot(0, true).setSchedule(TraceSchedule.snap(0));
tm.getSnapshot(Long.MIN_VALUE, true).setSchedule(TraceSchedule.parse("10:4"));
}
DefaultTraceTimeViewport viewport = new DefaultTraceTimeViewport(tb.trace);
viewport.setSnap(Long.MIN_VALUE);
assertEquals(
rangeSetOf(List.of(Range.singleton(Long.MIN_VALUE), Range.closed(0L, 10L))),
viewport.spanSet);
}
}
@Test
public void testCyclesIgnored() throws Exception {
try (ToyDBTraceBuilder tb = new ToyDBTraceBuilder("test", "Toy:BE:64:default")) {
try (UndoableTransaction tid = tb.startTransaction()) {
DBTraceTimeManager tm = tb.trace.getTimeManager();
tm.getSnapshot(Long.MIN_VALUE, true).setSchedule(TraceSchedule.parse("10:4"));
}
DefaultTraceTimeViewport viewport = new DefaultTraceTimeViewport(tb.trace);
viewport.setSnap(Long.MIN_VALUE);
assertEquals(rangeSetOf(List.of(Range.singleton(Long.MIN_VALUE))), viewport.spanSet);
}
}
}