mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-04 18:29:37 +02:00
GP-569: Added trace interpolation and extrapolation (emulation)
This commit is contained in:
parent
57b69005c7
commit
fcc0d97ae0
173 changed files with 10969 additions and 1424 deletions
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -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'");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)));
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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()]);
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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())) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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<>();
|
||||
|
|
|
@ -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);
|
||||
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
*
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
*
|
||||
|
|
|
@ -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) {
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -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
|
||||
|
|
|
@ -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
|
||||
*
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;",
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue