mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-04 18:29:37 +02:00
GP-1230: Add Taint Analysis prototype and emulator framework support
This commit is contained in:
parent
4bfd8d1112
commit
51a1933ab3
205 changed files with 11214 additions and 3714 deletions
|
@ -24,8 +24,17 @@ import ghidra.trace.model.Trace;
|
|||
import ghidra.trace.model.memory.TraceMemorySpace;
|
||||
import ghidra.trace.model.thread.TraceThread;
|
||||
|
||||
public abstract class AbstractCheckedTraceCachedWriteBytesPcodeExecutorState
|
||||
extends TraceCachedWriteBytesPcodeExecutorState {
|
||||
/**
|
||||
* A state piece which can check for uninitialized reads
|
||||
*
|
||||
* <p>
|
||||
* Depending on the use case, it may be desirable to ensure all reads through the course of
|
||||
* emulation are from initialized parts of memory. For traces, there's an additional consideration
|
||||
* as to whether the values are present, but state. Again, depending on the use case, that may be
|
||||
* acceptable. See the extensions of this class for "stock" implementations.
|
||||
*/
|
||||
public abstract class AbstractCheckedTraceCachedWriteBytesPcodeExecutorStatePiece
|
||||
extends BytesTracePcodeExecutorStatePiece {
|
||||
|
||||
protected class CheckedCachedSpace extends CachedSpace {
|
||||
public CheckedCachedSpace(Language language, AddressSpace space, TraceMemorySpace source,
|
||||
|
@ -45,16 +54,31 @@ public abstract class AbstractCheckedTraceCachedWriteBytesPcodeExecutorState
|
|||
}
|
||||
}
|
||||
|
||||
public AbstractCheckedTraceCachedWriteBytesPcodeExecutorState(Trace trace, long snap,
|
||||
public AbstractCheckedTraceCachedWriteBytesPcodeExecutorStatePiece(Trace trace, long snap,
|
||||
TraceThread thread, int frame) {
|
||||
super(trace, snap, thread, frame);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected CachedSpace newSpace(AddressSpace space, TraceMemorySpace backing) {
|
||||
return new CheckedCachedSpace(language, space, backing, snap);
|
||||
protected AbstractSpaceMap<CachedSpace> newSpaceMap() {
|
||||
return new TraceBackedSpaceMap() {
|
||||
@Override
|
||||
protected CachedSpace newSpace(AddressSpace space, TraceMemorySpace backing) {
|
||||
return new CheckedCachedSpace(language, space, backing, snap);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Decide what to do, give that a portion of a read is uninitialized
|
||||
*
|
||||
* @param backing the object backing the address space that was read
|
||||
* @param start the starting address of the requested read
|
||||
* @param size the size of the requested read
|
||||
* @param uninitialized the portion of the read that is uninitialized
|
||||
* @return the adjusted size of the read
|
||||
* @throws Exception to interrupt the emulator
|
||||
*/
|
||||
protected abstract int checkUninitialized(TraceMemorySpace backing, Address start, int size,
|
||||
AddressSet uninitialized);
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.pcode.exec.trace;
|
||||
|
||||
import ghidra.pcode.emu.PcodeEmulator;
|
||||
import ghidra.pcode.emu.PcodeThread;
|
||||
import ghidra.trace.model.Trace;
|
||||
import ghidra.trace.model.thread.TraceThread;
|
||||
|
||||
/**
|
||||
* An emulator that can read initial state from a trace and record its state back into it
|
||||
*/
|
||||
public class BytesTracePcodeEmulator extends PcodeEmulator implements TracePcodeMachine<byte[]> {
|
||||
protected final Trace trace;
|
||||
protected final long snap;
|
||||
|
||||
/**
|
||||
* Create a trace-bound emulator
|
||||
*
|
||||
* @param trace the trace
|
||||
* @param snap the snap from which it lazily reads its state
|
||||
*/
|
||||
public BytesTracePcodeEmulator(Trace trace, long snap) {
|
||||
super(trace.getBaseLanguage());
|
||||
this.trace = trace;
|
||||
this.snap = snap;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Trace getTrace() {
|
||||
return trace;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getSnap() {
|
||||
return snap;
|
||||
}
|
||||
|
||||
protected TracePcodeExecutorState<byte[]> newState(TraceThread thread) {
|
||||
return new BytesTracePcodeExecutorState(trace, snap, thread, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TracePcodeExecutorState<byte[]> createSharedState() {
|
||||
return newState(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TracePcodeExecutorState<byte[]> createLocalState(PcodeThread<byte[]> thread) {
|
||||
return newState(getTraceThread(thread));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
/* ###
|
||||
* 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.trace.model.Trace;
|
||||
import ghidra.trace.model.thread.TraceThread;
|
||||
|
||||
/**
|
||||
* A state composing a single {@link BytesTracePcodeExecutorStatePiece}
|
||||
*/
|
||||
class BytesTracePcodeExecutorState extends DefaultTracePcodeExecutorState<byte[]> {
|
||||
/**
|
||||
* Create the state
|
||||
*
|
||||
* @param trace the trace from which bytes are loaded
|
||||
* @param snap the snap from which bytes are loaded
|
||||
* @param thread if applicable, the thread identifying the register space
|
||||
* @param frame if applicable, the frame identifying the register space
|
||||
*/
|
||||
public BytesTracePcodeExecutorState(Trace trace, long snap, TraceThread thread, int frame) {
|
||||
super(new BytesTracePcodeExecutorStatePiece(trace, snap, thread, frame));
|
||||
}
|
||||
}
|
|
@ -20,9 +20,9 @@ import java.nio.ByteBuffer;
|
|||
import com.google.common.collect.*;
|
||||
import com.google.common.primitives.UnsignedLong;
|
||||
|
||||
import ghidra.pcode.exec.AbstractBytesPcodeExecutorState;
|
||||
import ghidra.pcode.exec.AbstractBytesPcodeExecutorStatePiece;
|
||||
import ghidra.pcode.exec.BytesPcodeExecutorStateSpace;
|
||||
import ghidra.pcode.exec.trace.TraceCachedWriteBytesPcodeExecutorState.CachedSpace;
|
||||
import ghidra.pcode.exec.trace.BytesTracePcodeExecutorStatePiece.CachedSpace;
|
||||
import ghidra.program.model.address.AddressSet;
|
||||
import ghidra.program.model.address.AddressSpace;
|
||||
import ghidra.program.model.lang.Language;
|
||||
|
@ -32,22 +32,23 @@ import ghidra.trace.model.thread.TraceThread;
|
|||
import ghidra.util.MathUtilities;
|
||||
|
||||
/**
|
||||
* A state which reads bytes from a trace, but caches writes internally.
|
||||
* A state piece 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 AbstractBytesPcodeExecutorState<TraceMemorySpace, CachedSpace> {
|
||||
public class BytesTracePcodeExecutorStatePiece
|
||||
extends AbstractBytesPcodeExecutorStatePiece<CachedSpace>
|
||||
implements TracePcodeExecutorStatePiece<byte[], byte[]> {
|
||||
|
||||
protected final Trace trace;
|
||||
protected final long snap;
|
||||
protected final TraceThread thread;
|
||||
protected final int frame;
|
||||
|
||||
public TraceCachedWriteBytesPcodeExecutorState(Trace trace, long snap, TraceThread thread,
|
||||
public BytesTracePcodeExecutorStatePiece(Trace trace, long snap, TraceThread thread,
|
||||
int frame) {
|
||||
super(trace.getBaseLanguage());
|
||||
this.trace = trace;
|
||||
|
@ -56,7 +57,7 @@ public class TraceCachedWriteBytesPcodeExecutorState
|
|||
this.frame = frame;
|
||||
}
|
||||
|
||||
public static class CachedSpace extends BytesPcodeExecutorStateSpace<TraceMemorySpace> {
|
||||
protected static class CachedSpace extends BytesPcodeExecutorStateSpace<TraceMemorySpace> {
|
||||
protected final RangeSet<UnsignedLong> written = TreeRangeSet.create();
|
||||
protected final long snap;
|
||||
|
||||
|
@ -126,50 +127,69 @@ public class TraceCachedWriteBytesPcodeExecutorState
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the state's source trace
|
||||
*
|
||||
* @return the trace
|
||||
*/
|
||||
public Trace getTrace() {
|
||||
return trace;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the source snap
|
||||
*
|
||||
* @return the snap
|
||||
*/
|
||||
public long getSnap() {
|
||||
return snap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the source thread, if a local state
|
||||
*
|
||||
* @return the thread
|
||||
*/
|
||||
public TraceThread getThread() {
|
||||
return thread;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the source frame, if a local state
|
||||
*
|
||||
* @return the frame, probably 0
|
||||
*/
|
||||
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) {
|
||||
@Override
|
||||
public void writeDown(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()) {
|
||||
for (CachedSpace cached : spaceMap.values()) {
|
||||
cached.writeDown(trace, snap, thread, frame);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected TraceMemorySpace getBacking(AddressSpace space) {
|
||||
return TraceSleighUtils.getSpaceForExecution(space, trace, thread, frame, false);
|
||||
/**
|
||||
* A space map which binds spaces to corresponding spaces in the trace
|
||||
*/
|
||||
protected class TraceBackedSpaceMap extends CacheingSpaceMap<TraceMemorySpace, CachedSpace> {
|
||||
@Override
|
||||
protected TraceMemorySpace getBacking(AddressSpace space) {
|
||||
return TraceSleighUtils.getSpaceForExecution(space, trace, thread, frame, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected CachedSpace newSpace(AddressSpace space, TraceMemorySpace backing) {
|
||||
return new CachedSpace(language, space, backing, snap);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected CachedSpace newSpace(AddressSpace space, TraceMemorySpace backing) {
|
||||
return new CachedSpace(language, space, backing, snap);
|
||||
protected AbstractSpaceMap<CachedSpace> newSpaceMap() {
|
||||
return new TraceBackedSpaceMap();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.pcode.exec.trace;
|
||||
|
||||
import ghidra.pcode.exec.DefaultPcodeExecutorState;
|
||||
import ghidra.trace.model.Trace;
|
||||
import ghidra.trace.model.thread.TraceThread;
|
||||
|
||||
/**
|
||||
* An adapter that implements {@link TracePcodeExecutorState} given a
|
||||
* {@link TracePcodeExecutorStatePiece} whose address and value types already match
|
||||
*
|
||||
* @param <T> the type of values
|
||||
*/
|
||||
public class DefaultTracePcodeExecutorState<T> extends DefaultPcodeExecutorState<T>
|
||||
implements TracePcodeExecutorState<T> {
|
||||
|
||||
protected final TracePcodeExecutorStatePiece<T, T> piece;
|
||||
|
||||
/**
|
||||
* Wrap a state piece
|
||||
*
|
||||
* @param piece the piece
|
||||
*/
|
||||
public DefaultTracePcodeExecutorState(TracePcodeExecutorStatePiece<T, T> piece) {
|
||||
super(piece);
|
||||
this.piece = piece;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeDown(Trace trace, long snap, TraceThread thread, int frame) {
|
||||
piece.writeDown(trace, snap, thread, frame);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
/* ###
|
||||
* 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 org.apache.commons.lang3.tuple.Pair;
|
||||
|
||||
import ghidra.pcode.exec.*;
|
||||
import ghidra.trace.model.Trace;
|
||||
import ghidra.trace.model.memory.TraceMemoryState;
|
||||
import ghidra.trace.model.thread.TraceThread;
|
||||
|
||||
/**
|
||||
* A state composing a single {@link DirectBytesTracePcodeExecutorStatePiece}
|
||||
*
|
||||
* <p>
|
||||
* Note this does not implement {@link DefaultTracePcodeExecutorState} because it treats the trace
|
||||
* as if it were a stand-alone state. The interface expects implementations to lazily load into a
|
||||
* cache and write it back down later. This does not do that.
|
||||
*
|
||||
* @see TraceSleighUtils
|
||||
*/
|
||||
public class DirectBytesTracePcodeExecutorState extends DefaultPcodeExecutorState<byte[]> {
|
||||
private final Trace trace;
|
||||
private final long snap;
|
||||
private final TraceThread thread;
|
||||
private final int frame;
|
||||
|
||||
/**
|
||||
* Create the state
|
||||
*
|
||||
* @param trace the trace the executor will access
|
||||
* @param snap the snap the executor will access
|
||||
* @param thread the thread for reading and writing registers
|
||||
* @param frame the frame for reading and writing registers
|
||||
*/
|
||||
public DirectBytesTracePcodeExecutorState(Trace trace, long snap, TraceThread thread,
|
||||
int frame) {
|
||||
super(new DirectBytesTracePcodeExecutorStatePiece(trace, snap, thread, frame));
|
||||
this.trace = trace;
|
||||
this.snap = snap;
|
||||
this.thread = thread;
|
||||
this.frame = frame;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pair this state with an auxiliary {@link TraceMemoryState} piece
|
||||
*
|
||||
* @return the new state, composing this state with the new piece
|
||||
* @see TraceSleighUtils#buildByteWithStateExecutor(Trace, long, TraceThread, int)
|
||||
*/
|
||||
public PcodeExecutorState<Pair<byte[], TraceMemoryState>> withMemoryState() {
|
||||
return new PairedPcodeExecutorState<>(this,
|
||||
new TraceMemoryStatePcodeExecutorStatePiece(trace, snap, thread, frame));
|
||||
}
|
||||
}
|
|
@ -17,14 +17,14 @@ package ghidra.pcode.exec.trace;
|
|||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.apache.commons.lang3.tuple.ImmutablePair;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
|
||||
import ghidra.generic.util.datastruct.SemisparseByteArray;
|
||||
import ghidra.pcode.exec.*;
|
||||
import ghidra.pcode.utils.Utils;
|
||||
import ghidra.pcode.exec.PcodeArithmetic.Purpose;
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.model.address.AddressSpace;
|
||||
import ghidra.program.model.lang.Language;
|
||||
import ghidra.program.model.mem.MemBuffer;
|
||||
import ghidra.trace.model.Trace;
|
||||
import ghidra.trace.model.memory.TraceMemorySpace;
|
||||
|
@ -32,8 +32,20 @@ import ghidra.trace.model.memory.TraceMemoryState;
|
|||
import ghidra.trace.model.thread.TraceThread;
|
||||
import ghidra.trace.util.DefaultTraceTimeViewport;
|
||||
|
||||
public class TraceBytesPcodeExecutorState
|
||||
extends AbstractLongOffsetPcodeExecutorState<byte[], TraceMemorySpace> {
|
||||
/**
|
||||
* An executor state piece that operates directly on trace memory and registers
|
||||
*
|
||||
* <p>
|
||||
* This differs from {@link BytesTracePcodeExecutorStatePiece} in that writes performed by the
|
||||
* emulator immediately affect the trace. There is no caching. In effect, the trace <em>is</em> the
|
||||
* state. This is used primarily in testing to initialize trace state using Sleigh, which is more
|
||||
* succinct than accessing trace memory and registers via the trace API. It may also be incorporated
|
||||
* into the UI at a later time.
|
||||
*
|
||||
* @see TraceSleighUtils
|
||||
*/
|
||||
public class DirectBytesTracePcodeExecutorStatePiece
|
||||
extends AbstractLongOffsetPcodeExecutorStatePiece<byte[], byte[], TraceMemorySpace> {
|
||||
|
||||
protected final SemisparseByteArray unique = new SemisparseByteArray();
|
||||
private final Trace trace;
|
||||
|
@ -43,8 +55,10 @@ public class TraceBytesPcodeExecutorState
|
|||
|
||||
private final DefaultTraceTimeViewport viewport;
|
||||
|
||||
public TraceBytesPcodeExecutorState(Trace trace, long snap, TraceThread thread, int frame) {
|
||||
super(trace.getBaseLanguage(), BytesPcodeArithmetic.forLanguage(trace.getBaseLanguage()));
|
||||
protected DirectBytesTracePcodeExecutorStatePiece(Language language,
|
||||
PcodeArithmetic<byte[]> arithmetic, Trace trace, long snap, TraceThread thread,
|
||||
int frame) {
|
||||
super(language, arithmetic, arithmetic);
|
||||
this.trace = trace;
|
||||
this.snap = snap;
|
||||
this.thread = thread;
|
||||
|
@ -54,48 +68,65 @@ public class TraceBytesPcodeExecutorState
|
|||
this.viewport.setSnap(snap);
|
||||
}
|
||||
|
||||
public PcodeExecutorState<Pair<byte[], TraceMemoryState>> withMemoryState() {
|
||||
return new PairedPcodeExecutorState<>(this,
|
||||
new TraceMemoryStatePcodeExecutorStatePiece(trace, snap, thread, frame)) {
|
||||
|
||||
@Override
|
||||
public void setVar(AddressSpace space, Pair<byte[], TraceMemoryState> offset, int size,
|
||||
boolean truncateAddressableUnit, Pair<byte[], TraceMemoryState> val) {
|
||||
if (offset.getRight() == TraceMemoryState.KNOWN) {
|
||||
super.setVar(space, offset, size, truncateAddressableUnit, val);
|
||||
return;
|
||||
}
|
||||
super.setVar(space, offset, size, truncateAddressableUnit,
|
||||
new ImmutablePair<>(val.getLeft(), TraceMemoryState.UNKNOWN));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Pair<byte[], TraceMemoryState> getVar(AddressSpace space,
|
||||
Pair<byte[], TraceMemoryState> offset, int size,
|
||||
boolean truncateAddressableUnit) {
|
||||
Pair<byte[], TraceMemoryState> result =
|
||||
super.getVar(space, offset, size, truncateAddressableUnit);
|
||||
if (offset.getRight() == TraceMemoryState.KNOWN) {
|
||||
return result;
|
||||
}
|
||||
return new ImmutablePair<>(result.getLeft(), TraceMemoryState.UNKNOWN);
|
||||
}
|
||||
};
|
||||
protected DirectBytesTracePcodeExecutorStatePiece(Language language, Trace trace, long snap,
|
||||
TraceThread thread, int frame) {
|
||||
this(language, BytesPcodeArithmetic.forLanguage(language), trace, snap, thread, frame);
|
||||
}
|
||||
|
||||
public DirectBytesTracePcodeExecutorStatePiece(Trace trace, long snap, TraceThread thread,
|
||||
int frame) {
|
||||
this(trace.getBaseLanguage(), trace, snap, thread, frame);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a state which computes an expression's {@link TraceMemoryState} as an auxiliary
|
||||
* attribute
|
||||
*
|
||||
* <p>
|
||||
* If every part of every input to the expression is {@link TraceMemoryState#KNOWN}, then the
|
||||
* expression's value will be marked {@link TraceMemoryState#KNOWN}. Otherwise, it's marked
|
||||
* {@link TraceMemoryState#UNKNOWN}.
|
||||
*
|
||||
* @return the paired executor state
|
||||
*/
|
||||
public PcodeExecutorStatePiece<byte[], Pair<byte[], TraceMemoryState>> withMemoryState() {
|
||||
return new PairedPcodeExecutorStatePiece<>(this,
|
||||
new TraceMemoryStatePcodeExecutorStatePiece(trace, snap, thread, frame));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the trace
|
||||
*
|
||||
* @return the trace
|
||||
*/
|
||||
public Trace getTrace() {
|
||||
return trace;
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-bind this state to another snap
|
||||
*
|
||||
* @param snap the new snap
|
||||
*/
|
||||
public void setSnap(long snap) {
|
||||
this.snap = snap;
|
||||
this.viewport.setSnap(snap);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current snap
|
||||
*
|
||||
* @return the snap
|
||||
*/
|
||||
public long getSnap() {
|
||||
return snap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-bind this state to another thread
|
||||
*
|
||||
* @param thread the new thread
|
||||
*/
|
||||
public void setThread(TraceThread thread) {
|
||||
if (thread != null & thread.getTrace() != trace) {
|
||||
throw new IllegalArgumentException("Thread, if given, must be part of the same trace");
|
||||
|
@ -103,28 +134,33 @@ public class TraceBytesPcodeExecutorState
|
|||
this.thread = thread;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current thread
|
||||
*
|
||||
* @return the thread
|
||||
*/
|
||||
public TraceThread getThread() {
|
||||
return thread;
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-bind this state to another frame
|
||||
*
|
||||
* @param frame the new frame
|
||||
*/
|
||||
public void setFrame(int frame) {
|
||||
this.frame = frame;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current frame
|
||||
*
|
||||
* @return the frame
|
||||
*/
|
||||
public int getFrame() {
|
||||
return frame;
|
||||
}
|
||||
|
||||
@Override
|
||||
public 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 void setUnique(long offset, int size, byte[] val) {
|
||||
assert size == val.length;
|
||||
|
@ -164,7 +200,7 @@ public class TraceBytesPcodeExecutorState
|
|||
}
|
||||
|
||||
@Override
|
||||
public MemBuffer getConcreteBuffer(Address address) {
|
||||
public MemBuffer getConcreteBuffer(Address address, Purpose purpose) {
|
||||
return trace.getMemoryManager().getBufferAt(snap, address);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
/* ###
|
||||
* 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 org.apache.commons.lang3.tuple.Pair;
|
||||
|
||||
import ghidra.pcode.exec.PairedPcodeExecutorState;
|
||||
import ghidra.trace.model.Trace;
|
||||
import ghidra.trace.model.thread.TraceThread;
|
||||
|
||||
/**
|
||||
* A trace-bound state composed of another trace-bound state and a piece
|
||||
*
|
||||
* @param <L> the type of values for the left state
|
||||
* @param <R> the type of values for the right piece
|
||||
* @see PairedPcodeExecutorState
|
||||
*/
|
||||
public class PairedTracePcodeExecutorState<L, R> extends PairedPcodeExecutorState<L, R>
|
||||
implements TracePcodeExecutorState<Pair<L, R>> {
|
||||
|
||||
private final TracePcodeExecutorStatePiece<L, L> left;
|
||||
private final TracePcodeExecutorStatePiece<L, R> right;
|
||||
|
||||
public PairedTracePcodeExecutorState(PairedTracePcodeExecutorStatePiece<L, L, R> piece) {
|
||||
super(piece);
|
||||
this.left = piece.getLeft();
|
||||
this.right = piece.getRight();
|
||||
}
|
||||
|
||||
public PairedTracePcodeExecutorState(TracePcodeExecutorState<L> left,
|
||||
TracePcodeExecutorStatePiece<L, R> right) {
|
||||
super(left, right);
|
||||
this.left = left;
|
||||
this.right = right;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeDown(Trace trace, long snap, TraceThread thread, int frame) {
|
||||
left.writeDown(trace, snap, thread, frame);
|
||||
right.writeDown(trace, snap, thread, frame);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,70 @@
|
|||
/* ###
|
||||
* 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 org.apache.commons.lang3.tuple.Pair;
|
||||
|
||||
import ghidra.pcode.exec.PairedPcodeExecutorStatePiece;
|
||||
import ghidra.pcode.exec.PcodeArithmetic;
|
||||
import ghidra.trace.model.Trace;
|
||||
import ghidra.trace.model.thread.TraceThread;
|
||||
|
||||
/**
|
||||
* A trace-bound state piece composed of two other trace-bound pieces sharing the same address type
|
||||
*
|
||||
* @see PairedPcodeExecutorStatePiece
|
||||
* @param <A> the type of addresses
|
||||
* @param <L> the type of values for the left piece
|
||||
* @param <R> the type of values for the right piece
|
||||
*/
|
||||
public class PairedTracePcodeExecutorStatePiece<A, L, R>
|
||||
extends PairedPcodeExecutorStatePiece<A, L, R>
|
||||
implements TracePcodeExecutorStatePiece<A, Pair<L, R>> {
|
||||
|
||||
protected final TracePcodeExecutorStatePiece<A, L> left;
|
||||
protected final TracePcodeExecutorStatePiece<A, R> right;
|
||||
|
||||
public PairedTracePcodeExecutorStatePiece(TracePcodeExecutorStatePiece<A, L> left,
|
||||
TracePcodeExecutorStatePiece<A, R> right) {
|
||||
super(left, right);
|
||||
this.left = left;
|
||||
this.right = right;
|
||||
}
|
||||
|
||||
public PairedTracePcodeExecutorStatePiece(TracePcodeExecutorStatePiece<A, L> left,
|
||||
TracePcodeExecutorStatePiece<A, R> right, PcodeArithmetic<A> addressArithmetic,
|
||||
PcodeArithmetic<Pair<L, R>> arithmetic) {
|
||||
super(left, right, addressArithmetic, arithmetic);
|
||||
this.left = left;
|
||||
this.right = right;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeDown(Trace trace, long snap, TraceThread thread, int frame) {
|
||||
left.writeDown(trace, snap, thread, frame);
|
||||
right.writeDown(trace, snap, thread, frame);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TracePcodeExecutorStatePiece<A, L> getLeft() {
|
||||
return left;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TracePcodeExecutorStatePiece<A, R> getRight() {
|
||||
return right;
|
||||
}
|
||||
}
|
|
@ -15,31 +15,26 @@
|
|||
*/
|
||||
package ghidra.pcode.exec.trace;
|
||||
|
||||
import com.google.common.collect.Range;
|
||||
|
||||
import ghidra.pcode.exec.AccessPcodeExecutionException;
|
||||
import ghidra.program.model.address.AddressSetView;
|
||||
import ghidra.trace.model.Trace;
|
||||
import ghidra.trace.model.memory.TraceMemorySpace;
|
||||
import ghidra.trace.model.memory.TraceMemoryState;
|
||||
import ghidra.trace.model.thread.TraceThread;
|
||||
|
||||
/**
|
||||
* A state composing a single {@link RequireHasKnownTraceCachedWriteBytesPcodeExecutorStatePiece}
|
||||
*/
|
||||
public class RequireHasKnownTraceCachedWriteBytesPcodeExecutorState
|
||||
extends RequireIsKnownTraceCachedWriteBytesPcodeExecutorState {
|
||||
extends DefaultTracePcodeExecutorState<byte[]> {
|
||||
|
||||
/**
|
||||
* Create the state
|
||||
*
|
||||
* @param trace the trace from which to load state
|
||||
* @param snap the snap from which to load state
|
||||
* @param thread if applicable, the thread identifying the register space
|
||||
* @param frame if applicable, the frame identifying the register space
|
||||
*/
|
||||
public RequireHasKnownTraceCachedWriteBytesPcodeExecutorState(Trace trace, long snap,
|
||||
TraceThread thread, int frame) {
|
||||
super(trace, snap, thread, frame);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected AddressSetView getKnown(TraceMemorySpace source) {
|
||||
return source.getAddressesWithState(Range.closed(0L, snap),
|
||||
s -> s == TraceMemoryState.KNOWN);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected AccessPcodeExecutionException excFor(AddressSetView unknown) {
|
||||
throw new AccessPcodeExecutionException("Memory at " + unknown + " has never been known.");
|
||||
super(new RequireHasKnownTraceCachedWriteBytesPcodeExecutorStatePiece(trace, snap, thread,
|
||||
frame));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,53 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.pcode.exec.trace;
|
||||
|
||||
import com.google.common.collect.Range;
|
||||
|
||||
import ghidra.pcode.exec.AccessPcodeExecutionException;
|
||||
import ghidra.program.model.address.AddressSetView;
|
||||
import ghidra.trace.model.Trace;
|
||||
import ghidra.trace.model.memory.TraceMemorySpace;
|
||||
import ghidra.trace.model.memory.TraceMemoryState;
|
||||
import ghidra.trace.model.thread.TraceThread;
|
||||
|
||||
/**
|
||||
* A relaxation of {@link RequireIsKnownTraceCachedWriteBytesPcodeExecutorStatePiece} that permits
|
||||
* reads of stale addresses
|
||||
*
|
||||
* <p>
|
||||
* An address can be read so long as it is {@link TraceMemoryState#KNOWN} for any non-scratch snap
|
||||
* up to and including the given snap.
|
||||
*/
|
||||
public class RequireHasKnownTraceCachedWriteBytesPcodeExecutorStatePiece
|
||||
extends RequireIsKnownTraceCachedWriteBytesPcodeExecutorStatePiece {
|
||||
|
||||
public RequireHasKnownTraceCachedWriteBytesPcodeExecutorStatePiece(Trace trace, long snap,
|
||||
TraceThread thread, int frame) {
|
||||
super(trace, snap, thread, frame);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected AddressSetView getKnown(TraceMemorySpace source) {
|
||||
return source.getAddressesWithState(Range.closed(0L, snap),
|
||||
s -> s == TraceMemoryState.KNOWN);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected AccessPcodeExecutionException excFor(AddressSetView unknown) {
|
||||
throw new AccessPcodeExecutionException("Memory at " + unknown + " has never been known.");
|
||||
}
|
||||
}
|
|
@ -15,46 +15,26 @@
|
|||
*/
|
||||
package ghidra.pcode.exec.trace;
|
||||
|
||||
import ghidra.pcode.exec.AccessPcodeExecutionException;
|
||||
import ghidra.program.model.address.*;
|
||||
import ghidra.trace.model.Trace;
|
||||
import ghidra.trace.model.memory.TraceMemorySpace;
|
||||
import ghidra.trace.model.memory.TraceMemoryState;
|
||||
import ghidra.trace.model.thread.TraceThread;
|
||||
|
||||
/**
|
||||
* A state composing a single {@link RequireIsKnownTraceCachedWriteBytesPcodeExecutorState}
|
||||
*/
|
||||
public class RequireIsKnownTraceCachedWriteBytesPcodeExecutorState
|
||||
extends AbstractCheckedTraceCachedWriteBytesPcodeExecutorState {
|
||||
extends DefaultTracePcodeExecutorState<byte[]> {
|
||||
|
||||
/**
|
||||
* Create the state
|
||||
*
|
||||
* @param trace the trace from which to load state
|
||||
* @param snap the snap from which to load state
|
||||
* @param thread if applicable, the thread identifying the register space
|
||||
* @param frame if applicable, the frame identifying the register space
|
||||
*/
|
||||
public RequireIsKnownTraceCachedWriteBytesPcodeExecutorState(Trace trace, long snap,
|
||||
TraceThread thread, int frame) {
|
||||
super(trace, snap, thread, frame);
|
||||
}
|
||||
|
||||
protected AddressSetView getKnown(TraceMemorySpace source) {
|
||||
return source.getAddressesWithState(snap, s -> s == TraceMemoryState.KNOWN);
|
||||
}
|
||||
|
||||
protected AccessPcodeExecutionException excFor(AddressSetView unknown) {
|
||||
return new AccessPcodeExecutionException("Memory at " + unknown + " is unknown.");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int checkUninitialized(TraceMemorySpace backing, Address start, int size,
|
||||
AddressSet uninitialized) {
|
||||
if (backing == null) {
|
||||
if (!uninitialized.contains(start)) {
|
||||
return (int) uninitialized.getMinAddress().subtract(start);
|
||||
}
|
||||
throw excFor(uninitialized);
|
||||
}
|
||||
// TODO: Could find first instead?
|
||||
AddressSetView unknown = uninitialized.subtract(getKnown(backing));
|
||||
if (unknown.isEmpty()) {
|
||||
return size;
|
||||
}
|
||||
if (!unknown.contains(start)) {
|
||||
return (int) unknown.getMinAddress().subtract(start);
|
||||
}
|
||||
throw excFor(unknown);
|
||||
super(new RequireIsKnownTraceCachedWriteBytesPcodeExecutorStatePiece(trace, snap, thread,
|
||||
frame));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,67 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.pcode.exec.trace;
|
||||
|
||||
import ghidra.pcode.exec.AccessPcodeExecutionException;
|
||||
import ghidra.program.model.address.*;
|
||||
import ghidra.trace.model.Trace;
|
||||
import ghidra.trace.model.memory.TraceMemorySpace;
|
||||
import ghidra.trace.model.memory.TraceMemoryState;
|
||||
import ghidra.trace.model.thread.TraceThread;
|
||||
|
||||
/**
|
||||
* A space which requires reads to be completely {@link TraceMemorySpace#KNOWN} memory.
|
||||
*
|
||||
* <p>
|
||||
* If a read can be partially completed, then it will proceed up to but not including the first
|
||||
* non-known address. If the start address is non-known, the emulator will be interrupted.
|
||||
*/
|
||||
public class RequireIsKnownTraceCachedWriteBytesPcodeExecutorStatePiece
|
||||
extends AbstractCheckedTraceCachedWriteBytesPcodeExecutorStatePiece {
|
||||
|
||||
public RequireIsKnownTraceCachedWriteBytesPcodeExecutorStatePiece(Trace trace, long snap,
|
||||
TraceThread thread, int frame) {
|
||||
super(trace, snap, thread, frame);
|
||||
}
|
||||
|
||||
protected AddressSetView getKnown(TraceMemorySpace source) {
|
||||
return source.getAddressesWithState(snap, s -> s == TraceMemoryState.KNOWN);
|
||||
}
|
||||
|
||||
protected AccessPcodeExecutionException excFor(AddressSetView unknown) {
|
||||
return new AccessPcodeExecutionException("Memory at " + unknown + " is unknown.");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int checkUninitialized(TraceMemorySpace backing, Address start, int size,
|
||||
AddressSet uninitialized) {
|
||||
if (backing == null) {
|
||||
if (!uninitialized.contains(start)) {
|
||||
return (int) uninitialized.getMinAddress().subtract(start);
|
||||
}
|
||||
throw excFor(uninitialized);
|
||||
}
|
||||
// TODO: Could find first instead?
|
||||
AddressSetView unknown = uninitialized.subtract(getKnown(backing));
|
||||
if (unknown.isEmpty()) {
|
||||
return size;
|
||||
}
|
||||
if (!unknown.contains(start)) {
|
||||
return (int) unknown.getMinAddress().subtract(start);
|
||||
}
|
||||
throw excFor(unknown);
|
||||
}
|
||||
}
|
|
@ -17,22 +17,41 @@ package ghidra.pcode.exec.trace;
|
|||
|
||||
import java.math.BigInteger;
|
||||
|
||||
import ghidra.pcode.exec.ConcretionError;
|
||||
import ghidra.pcode.exec.PcodeArithmetic;
|
||||
import ghidra.pcode.opbehavior.BinaryOpBehavior;
|
||||
import ghidra.pcode.opbehavior.UnaryOpBehavior;
|
||||
import ghidra.program.model.lang.Endian;
|
||||
import ghidra.trace.model.memory.TraceMemoryState;
|
||||
|
||||
/**
|
||||
* The p-code arithmetic for {@link TraceMemoryState}
|
||||
*
|
||||
* <p>
|
||||
* This arithmetic is meant to be used as an auxiliary to a concrete arithmetic. It should be used
|
||||
* with a state that knows how to load state markings from the same trace as the concrete state, so
|
||||
* that it can compute the "state" of a Sleigh expression's value. It essentially works like a
|
||||
* rudimentary taint analyzer: If any part of any input to the expression in tainted, i.e., not
|
||||
* {@link TraceMemoryState#KNOWN}, then the result is {@link TraceMemoryState#UNKNOWN}. This is best
|
||||
* exemplified in
|
||||
* {@link #binaryOp(BinaryOpBehavior, int, int, TraceMemoryState, int, TraceMemoryState)}.
|
||||
*/
|
||||
public enum TraceMemoryStatePcodeArithmetic implements PcodeArithmetic<TraceMemoryState> {
|
||||
/** The singleton instance */
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public TraceMemoryState unaryOp(UnaryOpBehavior op, int sizeout, int sizein1,
|
||||
public Endian getEndian() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TraceMemoryState unaryOp(int opcode, int sizeout, int sizein1,
|
||||
TraceMemoryState in1) {
|
||||
return in1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TraceMemoryState binaryOp(BinaryOpBehavior op, int sizeout, int sizein1,
|
||||
public TraceMemoryState binaryOp(int opcode, int sizeout, int sizein1,
|
||||
TraceMemoryState in1, int sizein2, TraceMemoryState in2) {
|
||||
if (in1 == TraceMemoryState.KNOWN && in2 == TraceMemoryState.KNOWN) {
|
||||
return TraceMemoryState.KNOWN;
|
||||
|
@ -41,7 +60,22 @@ public enum TraceMemoryStatePcodeArithmetic implements PcodeArithmetic<TraceMemo
|
|||
}
|
||||
|
||||
@Override
|
||||
public TraceMemoryState fromConst(long value, int size) {
|
||||
public TraceMemoryState modBeforeStore(int sizeout, int sizeinAddress,
|
||||
TraceMemoryState inAddress, int sizeinValue, TraceMemoryState inValue) {
|
||||
return inValue; // Shouldn't see STORE during Sleigh eval, anyway
|
||||
}
|
||||
|
||||
@Override
|
||||
public TraceMemoryState modAfterLoad(int sizeout, int sizeinAddress, TraceMemoryState inAddress,
|
||||
int sizeinValue, TraceMemoryState inValue) {
|
||||
if (inAddress == TraceMemoryState.KNOWN && inValue == TraceMemoryState.KNOWN) {
|
||||
return TraceMemoryState.KNOWN;
|
||||
}
|
||||
return TraceMemoryState.UNKNOWN;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TraceMemoryState fromConst(byte[] value) {
|
||||
return TraceMemoryState.KNOWN;
|
||||
}
|
||||
|
||||
|
@ -51,17 +85,17 @@ public enum TraceMemoryStatePcodeArithmetic implements PcodeArithmetic<TraceMemo
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean isTrue(TraceMemoryState cond) {
|
||||
throw new AssertionError("Cannot decide branches using TraceMemoryState");
|
||||
public TraceMemoryState fromConst(long value, int size) {
|
||||
return TraceMemoryState.KNOWN;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BigInteger toConcrete(TraceMemoryState value, boolean isContextreg) {
|
||||
throw new AssertionError("Cannot make TraceMemoryState a 'concrete value'");
|
||||
public byte[] toConcrete(TraceMemoryState value, Purpose purpose) {
|
||||
throw new ConcretionError("Cannot make TraceMemoryState concrete", purpose);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TraceMemoryState sizeOf(TraceMemoryState value) {
|
||||
public long sizeOf(TraceMemoryState value) {
|
||||
throw new AssertionError("Cannot get size of a TraceMemoryState");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,8 +20,8 @@ import java.util.Map;
|
|||
import com.google.common.collect.*;
|
||||
import com.google.common.primitives.UnsignedLong;
|
||||
|
||||
import ghidra.pcode.exec.AbstractLongOffsetPcodeExecutorStatePiece;
|
||||
import ghidra.pcode.utils.Utils;
|
||||
import ghidra.pcode.exec.*;
|
||||
import ghidra.pcode.exec.PcodeArithmetic.Purpose;
|
||||
import ghidra.program.model.address.*;
|
||||
import ghidra.program.model.mem.MemBuffer;
|
||||
import ghidra.trace.model.Trace;
|
||||
|
@ -30,6 +30,18 @@ import ghidra.trace.model.memory.TraceMemoryState;
|
|||
import ghidra.trace.model.thread.TraceThread;
|
||||
import ghidra.trace.util.DefaultTraceTimeViewport;
|
||||
|
||||
/**
|
||||
* The p-code execute state piece for {@link TraceMemoryState}
|
||||
*
|
||||
* <p>
|
||||
* This state piece is meant to be used as an auxiliary to a concrete trace-bound state. See
|
||||
* {@link DirectBytesTracePcodeExecutorState#withMemoryState()}. It should be used with
|
||||
* {@link TraceMemoryStatePcodeArithmetic} as a means of computing the "state" of a Sleigh
|
||||
* expression's value. It essentially works like a rudimentary taint analyzer: If any part of any
|
||||
* input to the expression in tainted, i.e., not {@link TraceMemoryState#KNOWN}, then the result is
|
||||
* {@link TraceMemoryState#UNKNOWN}. This is best exemplified in {@link #getUnique(long, int)},
|
||||
* though it's also exemplified in {@link #getFromSpace(TraceMemorySpace, long, int)}.
|
||||
*/
|
||||
public class TraceMemoryStatePcodeExecutorStatePiece extends
|
||||
AbstractLongOffsetPcodeExecutorStatePiece<byte[], TraceMemoryState, TraceMemorySpace> {
|
||||
|
||||
|
@ -43,7 +55,9 @@ public class TraceMemoryStatePcodeExecutorStatePiece extends
|
|||
|
||||
public TraceMemoryStatePcodeExecutorStatePiece(Trace trace, long snap, TraceThread thread,
|
||||
int frame) {
|
||||
super(trace.getBaseLanguage(), TraceMemoryStatePcodeArithmetic.INSTANCE);
|
||||
super(trace.getBaseLanguage(),
|
||||
BytesPcodeArithmetic.forLanguage(trace.getBaseLanguage()),
|
||||
TraceMemoryStatePcodeArithmetic.INSTANCE);
|
||||
this.trace = trace;
|
||||
this.snap = snap;
|
||||
this.thread = thread;
|
||||
|
@ -99,16 +113,6 @@ public class TraceMemoryStatePcodeExecutorStatePiece extends
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected long offsetToLong(byte[] offset) {
|
||||
return Utils.bytesToLong(offset, offset.length, language.isBigEndian());
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] longToOffset(AddressSpace space, long l) {
|
||||
return Utils.longToBytes(l, space.getPointerSize(), language.isBigEndian());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setUnique(long offset, int size, TraceMemoryState val) {
|
||||
unique.put(range(offset, size), val);
|
||||
|
@ -158,7 +162,7 @@ public class TraceMemoryStatePcodeExecutorStatePiece extends
|
|||
}
|
||||
|
||||
@Override
|
||||
public MemBuffer getConcreteBuffer(Address address) {
|
||||
throw new AssertionError("Cannot make TraceMemoryState into a concrete buffer");
|
||||
public MemBuffer getConcreteBuffer(Address address, Purpose purpose) {
|
||||
throw new ConcretionError("Cannot make TraceMemoryState into a concrete buffer", purpose);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,101 +0,0 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.pcode.exec.trace;
|
||||
|
||||
import com.google.common.collect.Range;
|
||||
|
||||
import ghidra.app.plugin.processors.sleigh.SleighLanguage;
|
||||
import ghidra.pcode.emu.PcodeEmulator;
|
||||
import ghidra.pcode.emu.PcodeThread;
|
||||
import ghidra.pcode.exec.PcodeExecutorState;
|
||||
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 PcodeEmulator {
|
||||
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) {
|
||||
super(assertSleigh(trace.getBaseLanguage()));
|
||||
this.trace = trace;
|
||||
this.snap = snap;
|
||||
}
|
||||
|
||||
protected PcodeExecutorState<byte[]> newState(TraceThread thread) {
|
||||
return new TraceCachedWriteBytesPcodeExecutorState(trace, snap, thread, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected PcodeExecutorState<byte[]> createSharedState() {
|
||||
return newState(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected PcodeExecutorState<byte[]> createLocalState(PcodeThread<byte[]> emuThread) {
|
||||
return newState(trace.getThreadManager().getLiveThreadByPath(snap, emuThread.getName()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 ss =
|
||||
(TraceCachedWriteBytesPcodeExecutorState) getSharedState();
|
||||
ss.writeCacheDown(trace, destSnap, null, 0);
|
||||
TraceThreadManager threadManager = trace.getThreadManager();
|
||||
for (PcodeThread<byte[]> emuThread : threads.values()) {
|
||||
TraceCachedWriteBytesPcodeExecutorState ls =
|
||||
(TraceCachedWriteBytesPcodeExecutorState) emuThread.getState().getLocalState();
|
||||
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);
|
||||
}
|
||||
ls.writeCacheDown(trace, destSnap, traceThread, 0);
|
||||
if (synthesizeStacks) {
|
||||
TraceStack stack = trace.getStackManager().getStack(traceThread, destSnap, true);
|
||||
stack.getFrame(0, true)
|
||||
.setProgramCounter(Range.atLeast(destSnap), emuThread.getCounter());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.pcode.exec.trace;
|
||||
|
||||
import ghidra.pcode.exec.PcodeExecutorState;
|
||||
import ghidra.trace.model.Trace;
|
||||
import ghidra.trace.model.thread.TraceThread;
|
||||
|
||||
/**
|
||||
* An interface for trace-bound states
|
||||
*
|
||||
* <p>
|
||||
* In particular, because this derives from {@link TracePcodeExecutorStatePiece}, such states are
|
||||
* required to implement {@link #writeDown(Trace, long, TraceThread, int)}. This interface also
|
||||
* derives from {@link PcodeExecutorState} so that, as the name implies, they can be used where a
|
||||
* state is required.
|
||||
*
|
||||
* @param <T> the type of values
|
||||
*/
|
||||
public interface TracePcodeExecutorState<T>
|
||||
extends PcodeExecutorState<T>, TracePcodeExecutorStatePiece<T, T> {
|
||||
// Nothing to add. Simply a composition of interfaces.
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.pcode.exec.trace;
|
||||
|
||||
import ghidra.pcode.exec.PcodeExecutorStatePiece;
|
||||
import ghidra.trace.model.Trace;
|
||||
import ghidra.trace.model.thread.TraceThread;
|
||||
|
||||
/**
|
||||
* A state piece which knows how to write its values back into a trace
|
||||
*
|
||||
* @param <A> the type of address offsets
|
||||
* @param <T> the type of values
|
||||
*/
|
||||
public interface TracePcodeExecutorStatePiece<A, T> extends PcodeExecutorStatePiece<A, T> {
|
||||
/**
|
||||
* Write the accumulated values (cache) into the given trace
|
||||
*
|
||||
* <p>
|
||||
* <b>NOTE:</b> 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
|
||||
* @see TracePcodeMachine#writeDown(Trace, long, long)
|
||||
*/
|
||||
void writeDown(Trace trace, long snap, TraceThread thread, int frame);
|
||||
}
|
|
@ -0,0 +1,147 @@
|
|||
/* ###
|
||||
* 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.PcodeMachine;
|
||||
import ghidra.pcode.emu.PcodeThread;
|
||||
import ghidra.program.model.lang.Register;
|
||||
import ghidra.program.model.lang.RegisterValue;
|
||||
import ghidra.trace.model.Trace;
|
||||
import ghidra.trace.model.memory.TraceMemoryRegisterSpace;
|
||||
import ghidra.trace.model.memory.TraceMemoryState;
|
||||
import ghidra.trace.model.thread.TraceThread;
|
||||
import ghidra.trace.model.thread.TraceThreadManager;
|
||||
|
||||
/**
|
||||
* A p-code machine which sources its state from a trace and can record back into it
|
||||
*
|
||||
* <p>
|
||||
* This is a "mix in" interface. It is part of the SPI, but not the API. That is, emulator
|
||||
* developers should use this interface, but emulator clients should not. Clients should use
|
||||
* {@link PcodeMachine} instead.
|
||||
*
|
||||
* @param <T> the type of values manipulated by the machine
|
||||
*/
|
||||
public interface TracePcodeMachine<T> extends PcodeMachine<T> {
|
||||
/**
|
||||
* Get the trace from which this emulator reads its initial state
|
||||
*
|
||||
* @return the trace
|
||||
*/
|
||||
Trace getTrace();
|
||||
|
||||
/**
|
||||
* Get the snapshot from which this emulator reads its initial state
|
||||
*
|
||||
* @return the snapshot key
|
||||
*/
|
||||
long getSnap();
|
||||
|
||||
/**
|
||||
* Get the trace thread corresponding to the given p-code thread
|
||||
*
|
||||
* @param thread the p-code thread
|
||||
* @return the trace thread
|
||||
*/
|
||||
default TraceThread getTraceThread(PcodeThread<T> thread) {
|
||||
return getTrace().getThreadManager().getLiveThreadByPath(getSnap(), thread.getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a shared state
|
||||
*
|
||||
* @return the shared state
|
||||
*/
|
||||
TracePcodeExecutorState<T> createSharedState();
|
||||
|
||||
/**
|
||||
* Create a local state
|
||||
*
|
||||
* @param thread the thread whose state is being created
|
||||
* @return the local state
|
||||
*/
|
||||
TracePcodeExecutorState<T> createLocalState(PcodeThread<T> thread);
|
||||
|
||||
/**
|
||||
* Check if a register has a {@link TraceMemoryState#KNOWN} value for the given thread
|
||||
*
|
||||
* @param thread the thread
|
||||
* @param register the register
|
||||
* @return true if known
|
||||
*/
|
||||
default boolean isRegisterKnown(PcodeThread<T> thread, Register register) {
|
||||
Trace trace = getTrace();
|
||||
long snap = getSnap();
|
||||
TraceThread traceThread =
|
||||
trace.getThreadManager().getLiveThreadByPath(snap, thread.getName());
|
||||
TraceMemoryRegisterSpace space =
|
||||
trace.getMemoryManager().getMemoryRegisterSpace(traceThread, false);
|
||||
if (space == null) {
|
||||
return false;
|
||||
}
|
||||
return space.getState(snap, register) == TraceMemoryState.KNOWN;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the given thread using context from the trace at its program counter
|
||||
*
|
||||
* @param thread the thread to initialize
|
||||
*/
|
||||
default void initializeThreadContext(PcodeThread<T> thread) {
|
||||
SleighLanguage language = getLanguage();
|
||||
Register contextreg = language.getContextBaseRegister();
|
||||
if (contextreg != Register.NO_CONTEXT && !isRegisterKnown(thread, contextreg)) {
|
||||
RegisterValue context = getTrace().getRegisterContextManager()
|
||||
.getValueWithDefault(language, contextreg, getSnap(), thread.getCounter());
|
||||
if (context != null) { // TODO: Why does this happen?
|
||||
thread.overrideContext(context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the accumulated emulator state into the given trace at the given snap
|
||||
*
|
||||
* <p>
|
||||
* <b>NOTE:</b> 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, usually the same as
|
||||
* {@link #getSnap()}
|
||||
*/
|
||||
default void writeDown(Trace trace, long destSnap, long threadsSnap) {
|
||||
TracePcodeExecutorState<T> ss = (TracePcodeExecutorState<T>) getSharedState();
|
||||
ss.writeDown(trace, destSnap, null, 0);
|
||||
TraceThreadManager threadManager = trace.getThreadManager();
|
||||
for (PcodeThread<T> emuThread : getAllThreads()) {
|
||||
TracePcodeExecutorState<T> ls =
|
||||
(TracePcodeExecutorState<T>) emuThread.getState().getLocalState();
|
||||
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);
|
||||
}
|
||||
ls.writeDown(trace, destSnap, traceThread, 0);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -32,9 +32,26 @@ import ghidra.trace.model.memory.TraceMemorySpace;
|
|||
import ghidra.trace.model.memory.TraceMemoryState;
|
||||
import ghidra.trace.model.thread.TraceThread;
|
||||
|
||||
/**
|
||||
* Various utilities for using Sleigh with traces
|
||||
*/
|
||||
public enum TraceSleighUtils {
|
||||
;
|
||||
|
||||
/**
|
||||
* Get the trace memory space for the given "coordinates"
|
||||
*
|
||||
* <p>
|
||||
* This is used to find "backing" objects for a p-code executor state bound to a trace, whether
|
||||
* direct or cached.
|
||||
*
|
||||
* @param space the address space
|
||||
* @param trace the trace
|
||||
* @param thread the thread, if a register space
|
||||
* @param frame the frame, if a register space
|
||||
* @param toWrite true if the state intends to write to the space, i.e., the space must exist
|
||||
* @return the space, or null if it doesn't exist
|
||||
*/
|
||||
public static TraceMemorySpace getSpaceForExecution(AddressSpace space, Trace trace,
|
||||
TraceThread thread, int frame, boolean toWrite) {
|
||||
if (space.isRegisterSpace()) {
|
||||
|
@ -47,10 +64,24 @@ public enum TraceSleighUtils {
|
|||
return trace.getMemoryManager().getMemorySpace(space, toWrite);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a p-code executor that operates directly on bytes of the given trace
|
||||
*
|
||||
* <p>
|
||||
* This execute is most suitable for evaluating Sleigh expression on a given trace snapshot, and
|
||||
* for manipulating or initializing variables using Sleigh code. It is generally not suitable
|
||||
* for use in an emulator. For that, consider {@link BytesTracePcodeEmulator}.
|
||||
*
|
||||
* @param trace the trace
|
||||
* @param snap the snap
|
||||
* @param thread the thread, required if register space is used
|
||||
* @param frame the frame, for when register space is used
|
||||
* @return the executor
|
||||
*/
|
||||
public static PcodeExecutor<byte[]> buildByteExecutor(Trace trace, long snap,
|
||||
TraceThread thread, int frame) {
|
||||
TraceBytesPcodeExecutorState state =
|
||||
new TraceBytesPcodeExecutorState(trace, snap, thread, frame);
|
||||
DirectBytesTracePcodeExecutorState state =
|
||||
new DirectBytesTracePcodeExecutorState(trace, snap, thread, frame);
|
||||
Language language = trace.getBaseLanguage();
|
||||
if (!(language instanceof SleighLanguage)) {
|
||||
throw new IllegalArgumentException("Trace must use a SLEIGH language");
|
||||
|
@ -59,10 +90,24 @@ public enum TraceSleighUtils {
|
|||
BytesPcodeArithmetic.forLanguage(language), state);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a p-code executor that operates directly on bytes and memory state of the given trace
|
||||
*
|
||||
* <p>
|
||||
* This executor is most suitable for evaluating Sleigh expressions on a given trace snapshot,
|
||||
* when the client would also like to know if all variables involved are
|
||||
* {@link TraceMemoryState#KNOWN}.
|
||||
*
|
||||
* @param trace the trace
|
||||
* @param snap the snap
|
||||
* @param thread the thread, required if register space is used
|
||||
* @param frame the frame, for when register space is used
|
||||
* @return the executor
|
||||
*/
|
||||
public static PcodeExecutor<Pair<byte[], TraceMemoryState>> buildByteWithStateExecutor(
|
||||
Trace trace, long snap, TraceThread thread, int frame) {
|
||||
TraceBytesPcodeExecutorState state =
|
||||
new TraceBytesPcodeExecutorState(trace, snap, thread, frame);
|
||||
DirectBytesTracePcodeExecutorState state =
|
||||
new DirectBytesTracePcodeExecutorState(trace, snap, thread, frame);
|
||||
PcodeExecutorState<Pair<byte[], TraceMemoryState>> paired = state.withMemoryState();
|
||||
Language language = trace.getBaseLanguage();
|
||||
if (!(language instanceof SleighLanguage)) {
|
||||
|
@ -73,6 +118,16 @@ public enum TraceSleighUtils {
|
|||
paired);
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluate a compiled p-code expression on the given trace
|
||||
*
|
||||
* @param expr the expression
|
||||
* @param trace the trace
|
||||
* @param snap the snap
|
||||
* @param thread the thread, required if register space is used
|
||||
* @param frame the frame, for when register space is used
|
||||
* @return the value of the expression as a byte array
|
||||
*/
|
||||
public static byte[] evaluateBytes(PcodeExpression expr, Trace trace, long snap,
|
||||
TraceThread thread, int frame) {
|
||||
SleighLanguage language = expr.getLanguage();
|
||||
|
@ -84,6 +139,16 @@ public enum TraceSleighUtils {
|
|||
return expr.evaluate(executor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluate a compiled p-code expression on the given trace
|
||||
*
|
||||
* @param expr the expression
|
||||
* @param trace the trace
|
||||
* @param snap the snap
|
||||
* @param thread the thread, required if register space is used
|
||||
* @param frame the frame, for when register space is used
|
||||
* @return the value of the expression as a big integer
|
||||
*/
|
||||
public static BigInteger evaluate(PcodeExpression expr, Trace trace, long snap,
|
||||
TraceThread thread, int frame) {
|
||||
byte[] bytes = evaluateBytes(expr, trace, snap, thread, frame);
|
||||
|
@ -91,6 +156,16 @@ public enum TraceSleighUtils {
|
|||
false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluate a compiled p-code expression on the given trace
|
||||
*
|
||||
* @param expr the expression
|
||||
* @param trace the trace
|
||||
* @param snap the snap
|
||||
* @param thread the thread, required if register space is used
|
||||
* @param frame the frame, for when register space is used
|
||||
* @return the value and state of the expression
|
||||
*/
|
||||
public static Pair<byte[], TraceMemoryState> evaluateBytesWithState(PcodeExpression expr,
|
||||
Trace trace, long snap, TraceThread thread, int frame) {
|
||||
SleighLanguage language = expr.getLanguage();
|
||||
|
@ -104,6 +179,16 @@ public enum TraceSleighUtils {
|
|||
return expr.evaluate(executor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluate a compiled p-code expression on the given trace
|
||||
*
|
||||
* @param expr the expression
|
||||
* @param trace the trace
|
||||
* @param snap the snap
|
||||
* @param thread the thread, required if register space is used
|
||||
* @param frame the frame, for when register space is used
|
||||
* @return the value and state of the expression
|
||||
*/
|
||||
public static Pair<BigInteger, TraceMemoryState> evaluateWithState(PcodeExpression expr,
|
||||
Trace trace, long snap, TraceThread thread, int frame) {
|
||||
Pair<byte[], TraceMemoryState> bytesPair =
|
||||
|
@ -114,6 +199,16 @@ public enum TraceSleighUtils {
|
|||
bytesPair.getRight());
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluate a Sleigh expression on the given trace
|
||||
*
|
||||
* @param expr the expression
|
||||
* @param trace the trace
|
||||
* @param snap the snap
|
||||
* @param thread the thread, required if register space is used
|
||||
* @param frame the frame, for when register space is used
|
||||
* @return the value of the expression as a byte array
|
||||
*/
|
||||
public static byte[] evaluateBytes(String expr, Trace trace, long snap, TraceThread thread,
|
||||
int frame) {
|
||||
Language language = trace.getBaseLanguage();
|
||||
|
@ -125,6 +220,16 @@ public enum TraceSleighUtils {
|
|||
trace, snap, thread, frame);
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluate a Sleigh expression on the given trace
|
||||
*
|
||||
* @param expr the expression
|
||||
* @param trace the trace
|
||||
* @param snap the snap
|
||||
* @param thread the thread, required if register space is used
|
||||
* @param frame the frame, for when register space is used
|
||||
* @return the value of the expression as a big integer
|
||||
*/
|
||||
public static BigInteger evaluate(String expr, Trace trace, long snap, TraceThread thread,
|
||||
int frame) {
|
||||
Language language = trace.getBaseLanguage();
|
||||
|
@ -135,6 +240,16 @@ public enum TraceSleighUtils {
|
|||
trace, snap, thread, frame);
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluate a Sleigh expression on the given trace
|
||||
*
|
||||
* @param expr the expression
|
||||
* @param trace the trace
|
||||
* @param snap the snap
|
||||
* @param thread the thread, required if register space is used
|
||||
* @param frame the frame, for when register space is used
|
||||
* @return the value and state of the expression
|
||||
*/
|
||||
public static Entry<byte[], TraceMemoryState> evaluateBytesWithState(String expr, Trace trace,
|
||||
long snap, TraceThread thread, int frame) {
|
||||
Language language = trace.getBaseLanguage();
|
||||
|
@ -146,6 +261,16 @@ public enum TraceSleighUtils {
|
|||
trace, snap, thread, frame);
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluate a Sleigh expression on the given trace
|
||||
*
|
||||
* @param expr the expression
|
||||
* @param trace the trace
|
||||
* @param snap the snap
|
||||
* @param thread the thread, required if register space is used
|
||||
* @param frame the frame, for when register space is used
|
||||
* @return the value and state of the expression
|
||||
*/
|
||||
public static Entry<BigInteger, TraceMemoryState> evaluateWithState(String expr, Trace trace,
|
||||
long snap, TraceThread thread, int frame) {
|
||||
Language language = trace.getBaseLanguage();
|
||||
|
@ -157,6 +282,18 @@ public enum TraceSleighUtils {
|
|||
trace, snap, thread, frame);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the expression for retrieving a memory range
|
||||
*
|
||||
* <p>
|
||||
* In general, it does not make sense to use this directly with the above evaluation methods.
|
||||
* More likely, this is used in the UI to aid the user in generating an expression. From the
|
||||
* API, it's much easier to access the memory state directly.
|
||||
*
|
||||
* @param language the language
|
||||
* @param range the range
|
||||
* @return the expression
|
||||
*/
|
||||
public static String generateExpressionForRange(Language language, AddressRange range) {
|
||||
AddressSpace space = range.getAddressSpace();
|
||||
long length = range.getLength();
|
||||
|
|
|
@ -0,0 +1,69 @@
|
|||
/* ###
|
||||
* 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.auxiliary;
|
||||
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
|
||||
import ghidra.pcode.emu.PcodeThread;
|
||||
import ghidra.pcode.emu.auxiliary.AuxEmulatorPartsFactory;
|
||||
import ghidra.pcode.exec.trace.*;
|
||||
|
||||
/**
|
||||
* An auxiliary emulator parts factory capable of integrating with a trace
|
||||
*
|
||||
* <p>
|
||||
* This can manufacture parts for an emulator that reads and writes its state (concrete and
|
||||
* auxiliary pieces) from and to a trace, as well as all the parts for the less integrated forms of
|
||||
* the same emulator. The pattern of use is generally to read from a given "source" snap, execute
|
||||
* some stepping schedule, then write the cache to a given "destination" snap.
|
||||
*
|
||||
* @param <U> the type of auxiliary values
|
||||
*/
|
||||
public interface AuxTraceEmulatorPartsFactory<U> extends AuxEmulatorPartsFactory<U> {
|
||||
/**
|
||||
* Create the shared (memory) state of a new trace-integrated emulator
|
||||
*
|
||||
* <p>
|
||||
* This is usually composed of pieces using {@link PairedTracePcodeExecutorStatePiece}, but it
|
||||
* does not have to be. It must incorporate the concrete piece provided. The state must be
|
||||
* capable of lazily loading state from a trace and later writing its cache back into the trace
|
||||
* at another snapshot. The given concrete piece is already capable of doing that for concrete
|
||||
* values. The auxiliary piece should be able to independently load its state from the trace,
|
||||
* since this is one way a user expects to initialize the auxiliary values.
|
||||
*
|
||||
* @param emulator the emulator
|
||||
* @param concrete the concrete piece
|
||||
* @return the composed state
|
||||
*/
|
||||
TracePcodeExecutorState<Pair<byte[], U>> createTraceSharedState(
|
||||
AuxTracePcodeEmulator<U> emulator, BytesTracePcodeExecutorStatePiece concrete);
|
||||
|
||||
/**
|
||||
* Create the local (register) state of a new trace-integrated thread
|
||||
*
|
||||
* <p>
|
||||
* This must have the same capabilities as
|
||||
* {@link #createTraceSharedState(AuxTracePcodeEmulator, BytesTracePcodeExecutorStatePiece)}.
|
||||
*
|
||||
* @param emulator the emulator
|
||||
* @param thread the new thread
|
||||
* @param concrete the concrete piece
|
||||
* @return the composed state
|
||||
*/
|
||||
TracePcodeExecutorState<Pair<byte[], U>> createTraceLocalState(
|
||||
AuxTracePcodeEmulator<U> emulator, PcodeThread<Pair<byte[], U>> thread,
|
||||
BytesTracePcodeExecutorStatePiece concrete);
|
||||
}
|
|
@ -0,0 +1,89 @@
|
|||
/* ###
|
||||
* 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.auxiliary;
|
||||
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
|
||||
import ghidra.pcode.emu.PcodeThread;
|
||||
import ghidra.pcode.emu.auxiliary.AuxEmulatorPartsFactory;
|
||||
import ghidra.pcode.emu.auxiliary.AuxPcodeEmulator;
|
||||
import ghidra.pcode.exec.trace.*;
|
||||
import ghidra.trace.model.Trace;
|
||||
|
||||
/**
|
||||
* An trace-integrated emulator whose parts are manufactured by a
|
||||
* {@link AuxTraceEmulatorPartsFactory}
|
||||
*
|
||||
* <p>
|
||||
* See the parts factory interface and its super interfaces:
|
||||
* <ul>
|
||||
* <li>{@link AuxTraceEmulatorPartsFactory}</li>
|
||||
* <li>{@link AuxEmulatorPartsFactory}</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param <U> the type of auxiliary values
|
||||
*/
|
||||
public abstract class AuxTracePcodeEmulator<U> extends AuxPcodeEmulator<U>
|
||||
implements TracePcodeMachine<Pair<byte[], U>> {
|
||||
|
||||
protected final Trace trace;
|
||||
protected final long snap;
|
||||
|
||||
/**
|
||||
* Create a new emulator
|
||||
*
|
||||
* @param trace the trace from which the emulator loads state
|
||||
* @param snap the snap from which the emulator loads state
|
||||
*/
|
||||
public AuxTracePcodeEmulator(Trace trace, long snap) {
|
||||
super(trace.getBaseLanguage());
|
||||
this.trace = trace;
|
||||
this.snap = snap;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected abstract AuxTraceEmulatorPartsFactory<U> getPartsFactory();
|
||||
|
||||
@Override
|
||||
public Trace getTrace() {
|
||||
return trace;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getSnap() {
|
||||
return snap;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected PcodeThread<Pair<byte[], U>> createThread(String name) {
|
||||
PcodeThread<Pair<byte[], U>> thread = super.createThread(name);
|
||||
initializeThreadContext(thread);
|
||||
return thread;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TracePcodeExecutorState<Pair<byte[], U>> createSharedState() {
|
||||
return getPartsFactory().createTraceSharedState(this,
|
||||
new BytesTracePcodeExecutorStatePiece(trace, snap, null, 0));
|
||||
}
|
||||
|
||||
@Override
|
||||
public TracePcodeExecutorState<Pair<byte[], U>> createLocalState(
|
||||
PcodeThread<Pair<byte[], U>> thread) {
|
||||
return getPartsFactory().createTraceLocalState(this, thread,
|
||||
new BytesTracePcodeExecutorStatePiece(trace, snap, getTraceThread(thread), 0));
|
||||
}
|
||||
}
|
|
@ -37,9 +37,19 @@ import ghidra.trace.database.map.DBTraceAddressSnapRangePropertyMapTree.Abstract
|
|||
import ghidra.util.database.DBAnnotatedObject;
|
||||
import ghidra.util.database.DBCachedObjectStoreFactory.AbstractDBFieldCodec;
|
||||
|
||||
/**
|
||||
* Various utilities used for implementing the trace database
|
||||
*
|
||||
* <p>
|
||||
* Some of these are also useful from the API perspective. TODO: We should probably separate trace
|
||||
* API utilities into another class.
|
||||
*/
|
||||
public enum DBTraceUtils {
|
||||
;
|
||||
|
||||
/**
|
||||
* A tuple used to index/locate a block in the trace's byte stores (memory manager)
|
||||
*/
|
||||
public static class OffsetSnap {
|
||||
public final long offset;
|
||||
public final long snap;
|
||||
|
@ -83,6 +93,9 @@ public enum DBTraceUtils {
|
|||
}
|
||||
|
||||
// TODO: Should this be in by default?
|
||||
/**
|
||||
* A codec or URLs
|
||||
*/
|
||||
public static class URLDBFieldCodec<OT extends DBAnnotatedObject>
|
||||
extends AbstractDBFieldCodec<URL, OT, StringField> {
|
||||
public URLDBFieldCodec(Class<OT> objectType, Field field, int column) {
|
||||
|
@ -125,6 +138,9 @@ public enum DBTraceUtils {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A codec for language IDs
|
||||
*/
|
||||
public static class LanguageIDDBFieldCodec<OT extends DBAnnotatedObject>
|
||||
extends AbstractDBFieldCodec<LanguageID, OT, StringField> {
|
||||
|
||||
|
@ -162,6 +178,9 @@ public enum DBTraceUtils {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A codec for compiler spec IDs
|
||||
*/
|
||||
public static class CompilerSpecIDDBFieldCodec<OT extends DBAnnotatedObject>
|
||||
extends AbstractDBFieldCodec<CompilerSpecID, OT, StringField> {
|
||||
|
||||
|
@ -199,6 +218,9 @@ public enum DBTraceUtils {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A (abstract) codec for the offset-snap tuple
|
||||
*/
|
||||
public abstract static class AbstractOffsetSnapDBFieldCodec<OT extends DBAnnotatedObject>
|
||||
extends AbstractDBFieldCodec<OffsetSnap, OT, BinaryField> {
|
||||
|
||||
|
@ -248,6 +270,7 @@ public enum DBTraceUtils {
|
|||
/**
|
||||
* Codec for storing {@link OffsetSnap}s as {@link BinaryField}s.
|
||||
*
|
||||
* <p>
|
||||
* Encodes the address space ID followed by the address then the snap.
|
||||
*
|
||||
* @param <OT> the type of the object whose field is encoded/decoded.
|
||||
|
@ -277,6 +300,9 @@ public enum DBTraceUtils {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A codec for reference types
|
||||
*/
|
||||
public static class RefTypeDBFieldCodec<OT extends DBAnnotatedObject>
|
||||
extends AbstractDBFieldCodec<RefType, OT, ByteField> {
|
||||
public RefTypeDBFieldCodec(Class<OT> objectType, Field field, int column) {
|
||||
|
@ -309,27 +335,103 @@ public enum DBTraceUtils {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A method outline for setting an entry in a range map where coalescing is desired
|
||||
*
|
||||
* @param <E> the type of entries
|
||||
* @param <D> the type of range bounds
|
||||
* @param <R> the type of ranges
|
||||
* @param <V> the type of values
|
||||
*/
|
||||
public static abstract class RangeMapSetter<E, D extends Comparable<D>, R, V> {
|
||||
/**
|
||||
* Get the range of the given entry
|
||||
*
|
||||
* @param entry the entry
|
||||
* @return the range
|
||||
*/
|
||||
protected abstract R getRange(E entry);
|
||||
|
||||
/**
|
||||
* Get the value of the given entry
|
||||
*
|
||||
* @param entry the entry
|
||||
* @return the value
|
||||
*/
|
||||
protected abstract V getValue(E entry);
|
||||
|
||||
/**
|
||||
* Remove an entry from the map
|
||||
*
|
||||
* @param entry the entry
|
||||
*/
|
||||
protected abstract void remove(E entry);
|
||||
|
||||
/**
|
||||
* Get the lower bound of the range
|
||||
*
|
||||
* @param range the range
|
||||
* @return the lower bound
|
||||
*/
|
||||
protected abstract D getLower(R range);
|
||||
|
||||
/**
|
||||
* Get the upper bound of the range
|
||||
*
|
||||
* @param range the range
|
||||
* @return the upper bound
|
||||
*/
|
||||
protected abstract D getUpper(R range);
|
||||
|
||||
/**
|
||||
* Create a closed range with the given bounds
|
||||
*
|
||||
* @param lower the lower bound
|
||||
* @param upper the upper bound
|
||||
* @return the range
|
||||
*/
|
||||
protected abstract R toRange(D lower, D upper);
|
||||
|
||||
/**
|
||||
* Get the number immediately preceding the given bound
|
||||
*
|
||||
* @param d the bound
|
||||
* @return the previous bound, or null if it doesn't exist
|
||||
*/
|
||||
protected abstract D getPrevious(D d);
|
||||
|
||||
/**
|
||||
* Get the number immediately following the given bound
|
||||
*
|
||||
* @param d the bound
|
||||
* @return the next bound, or null if it doesn't exist
|
||||
*/
|
||||
protected abstract D getNext(D d);
|
||||
|
||||
/**
|
||||
* Get all entries intersecting the closed range formed by the given bounds
|
||||
*
|
||||
* @param lower the lower bound
|
||||
* @param upper the upper bound
|
||||
* @return the intersecting entries
|
||||
*/
|
||||
protected abstract Iterable<E> getIntersecting(D lower, D upper);
|
||||
|
||||
/**
|
||||
* Place an entry into the map
|
||||
*
|
||||
* @param range the range of the entry
|
||||
* @param value the value of the entry
|
||||
* @return the new entry (or an existing entry)
|
||||
*/
|
||||
protected abstract E put(R range, V value);
|
||||
|
||||
/**
|
||||
* Get the previous bound or this same bound, if the previous doesn't exist
|
||||
*
|
||||
* @param d the bound
|
||||
* @return the previous or same bound
|
||||
*/
|
||||
protected D getPreviousOrSame(D d) {
|
||||
D prev = getPrevious(d);
|
||||
if (prev == null) {
|
||||
|
@ -338,6 +440,12 @@ public enum DBTraceUtils {
|
|||
return prev;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the next bound or this same bound, if the next doesn't exist
|
||||
*
|
||||
* @param d the bound
|
||||
* @return the next or same bound
|
||||
*/
|
||||
protected D getNextOrSame(D d) {
|
||||
D next = getNext(d);
|
||||
if (next == null) {
|
||||
|
@ -346,15 +454,40 @@ public enum DBTraceUtils {
|
|||
return next;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the two ranges are connected
|
||||
*
|
||||
* <p>
|
||||
* The ranges are connected if they intersect, or if their bounds abut.
|
||||
*
|
||||
* @param r1 the first range
|
||||
* @param r2 the second range
|
||||
* @return true if connected
|
||||
*/
|
||||
protected boolean connects(R r1, R r2) {
|
||||
return getPreviousOrSame(getLower(r1)).compareTo(getUpper(r2)) <= 0 ||
|
||||
getPreviousOrSame(getLower(r2)).compareTo(getUpper(r1)) <= 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Entry point: Set the given range to the given value, coalescing where possible
|
||||
*
|
||||
* @param range the range
|
||||
* @param value the value
|
||||
* @return the entry containing the value
|
||||
*/
|
||||
public E set(R range, V value) {
|
||||
return set(getLower(range), getUpper(range), value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Entry point: Set the given range to the given value, coalescing where possible
|
||||
*
|
||||
* @param lower the lower bound
|
||||
* @param upper the upper bound
|
||||
* @param value the value
|
||||
* @return the entry containing the value
|
||||
*/
|
||||
public E set(D lower, D upper, V value) {
|
||||
// Go one out to find abutting ranges, too.
|
||||
D prev = getPreviousOrSame(lower);
|
||||
|
@ -395,6 +528,12 @@ public enum DBTraceUtils {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A setter which works on ranges of addresses
|
||||
*
|
||||
* @param <E> the type of entry
|
||||
* @param <V> the type of value
|
||||
*/
|
||||
public static abstract class AddressRangeMapSetter<E, V>
|
||||
extends RangeMapSetter<E, Address, AddressRange, V> {
|
||||
@Override
|
||||
|
@ -423,6 +562,12 @@ public enum DBTraceUtils {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A setter which operates on spans of snapshot keys
|
||||
*
|
||||
* @param <E> the type of entry
|
||||
* @param <V> the type of value
|
||||
*/
|
||||
public static abstract class LifespanMapSetter<E, V>
|
||||
extends RangeMapSetter<E, Long, Range<Long>, V> {
|
||||
|
||||
|
@ -458,6 +603,16 @@ public enum DBTraceUtils {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the lower endpoint as stored in the database
|
||||
*
|
||||
* <p>
|
||||
* {@link Long#MIN_VALUE} represents no lower bound. Endpoints should always be closed unless
|
||||
* unbounded. If open, it will be converted to closed (at one greater).
|
||||
*
|
||||
* @param range the range
|
||||
* @return the endpoint
|
||||
*/
|
||||
public static long lowerEndpoint(Range<Long> range) {
|
||||
if (!range.hasLowerBound()) {
|
||||
return Long.MIN_VALUE;
|
||||
|
@ -468,6 +623,16 @@ public enum DBTraceUtils {
|
|||
return range.lowerEndpoint().longValue() + 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the upper endpoint as stored in the database
|
||||
*
|
||||
* <p>
|
||||
* {@link Long#MAX_VALUE} represents no upper bound. Endpoints should alwyas be closed unless
|
||||
* unbounded. If open, it will be converted to closed (at one less).
|
||||
*
|
||||
* @param range the range
|
||||
* @return the endpoint
|
||||
*/
|
||||
public static long upperEndpoint(Range<Long> range) {
|
||||
if (!range.hasUpperBound()) {
|
||||
return Long.MAX_VALUE;
|
||||
|
@ -478,6 +643,13 @@ public enum DBTraceUtils {
|
|||
return range.upperEndpoint().longValue() - 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the given enpoints to a range
|
||||
*
|
||||
* @param lowerEndpoint the lower endpoint, where {@link Long#MIN_VALUE} indicates unbounded
|
||||
* @param upperEndpoint the upper endpoint, where {@link Long#MAX_VALUE} indicates unbounded
|
||||
* @return the range
|
||||
*/
|
||||
public static Range<Long> toRange(long lowerEndpoint, long upperEndpoint) {
|
||||
if (lowerEndpoint == Long.MIN_VALUE && upperEndpoint == Long.MAX_VALUE) {
|
||||
return Range.all();
|
||||
|
@ -491,10 +663,27 @@ public enum DBTraceUtils {
|
|||
return Range.closed(lowerEndpoint, upperEndpoint);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the range starting at the given snap, to infinity
|
||||
*
|
||||
* @param snap the starting snap
|
||||
* @return the range [snap, +inf)
|
||||
*/
|
||||
public static Range<Long> toRange(long snap) {
|
||||
return toRange(snap, Long.MAX_VALUE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the two ranges intersect
|
||||
*
|
||||
* <p>
|
||||
* This is a bit obtuse in Guava's API, so here's the convenience method
|
||||
*
|
||||
* @param <T> the type of range endpoints
|
||||
* @param a the first range
|
||||
* @param b the second range
|
||||
* @return true if they intersect
|
||||
*/
|
||||
public static <T extends Comparable<T>> boolean intersect(Range<T> a, Range<T> b) {
|
||||
// Because we're working with a discrete domain, we have to be careful to never use open
|
||||
// lower bounds. Otherwise, the following two inputs would cause a true return value when,
|
||||
|
@ -502,10 +691,45 @@ public enum DBTraceUtils {
|
|||
return a.isConnected(b) && !a.intersection(b).isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a given snapshot key is designated as scratch space
|
||||
*
|
||||
* <p>
|
||||
* Conventionally, negative snaps are scratch space.
|
||||
*
|
||||
* @param snap the snap
|
||||
* @return true if scratch space
|
||||
*/
|
||||
public static boolean isScratch(long snap) {
|
||||
return snap < 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Form a range starting at the given snap that does not traverse both scratch and non-scratch
|
||||
* space
|
||||
*
|
||||
* @param start the starting snap
|
||||
* @return the range [start,0] if start is in scratch space, or [start, +inf) if start is not in
|
||||
* scratch space
|
||||
*/
|
||||
public static Range<Long> atLeastMaybeScratch(long start) {
|
||||
if (start < 0) {
|
||||
return Range.closed(start, -1L);
|
||||
}
|
||||
return Range.atLeast(start);
|
||||
}
|
||||
|
||||
/**
|
||||
* "Compare" two ranges
|
||||
*
|
||||
* <p>
|
||||
* This is just to impose a sorting order for display.
|
||||
*
|
||||
* @param <C> the type of endpoints
|
||||
* @param a the first range
|
||||
* @param b the second range
|
||||
* @return the result as in {@link Comparable#compareTo(Object)}
|
||||
*/
|
||||
public static <C extends Comparable<C>> int compareRanges(Range<C> a, Range<C> b) {
|
||||
int result;
|
||||
if (!a.hasLowerBound() && b.hasLowerBound()) {
|
||||
|
@ -548,6 +772,15 @@ public enum DBTraceUtils {
|
|||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Derive the table name for a given addres/register space
|
||||
*
|
||||
* @param baseName the base name of the table group
|
||||
* @param space the address space
|
||||
* @param threadKey the thread key, -1 usually indicating "no thread"
|
||||
* @param frameLevel the frame level
|
||||
* @return the table name
|
||||
*/
|
||||
public static String tableName(String baseName, AddressSpace space, long threadKey,
|
||||
int frameLevel) {
|
||||
if (space.isRegisterSpace()) {
|
||||
|
@ -560,15 +793,17 @@ public enum DBTraceUtils {
|
|||
}
|
||||
|
||||
/**
|
||||
* TODO: Document me
|
||||
* Truncate or delete an entry to make room
|
||||
*
|
||||
* <p>
|
||||
* Only call this method for entries which definitely intersect the given span
|
||||
* Only call this method for entries which definitely intersect the given span. This does not
|
||||
* verify intersection. If the data's start snap is contained in the span to clear, the entry is
|
||||
* deleted. Otherwise, it's end snap is set to one less than the span's start snap.
|
||||
*
|
||||
* @param data
|
||||
* @param span
|
||||
* @param lifespanSetter
|
||||
* @param deleter
|
||||
* @param data the entry subject to truncation or deletion
|
||||
* @param span the span to clear up
|
||||
* @param lifespanSetter the method used to truncate the entry
|
||||
* @param deleter the method used to delete the entry
|
||||
*/
|
||||
public static <DR extends AbstractDBTraceAddressSnapRangePropertyMapData<?>> void makeWay(
|
||||
DR data, Range<Long> span, BiConsumer<? super DR, Range<Long>> lifespanSetter,
|
||||
|
@ -582,6 +817,13 @@ public enum DBTraceUtils {
|
|||
lifespanSetter.accept(data, toRange(data.getY1(), lowerEndpoint(span) - 1));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sutract two ranges, yielding 0, 1, or 2 ranges
|
||||
*
|
||||
* @param a the first range
|
||||
* @param b the second range
|
||||
* @return the list of ranges
|
||||
*/
|
||||
public static List<Range<Long>> subtract(Range<Long> a, Range<Long> b) {
|
||||
RangeSet<Long> set = TreeRangeSet.create();
|
||||
set.add(a);
|
||||
|
@ -592,12 +834,25 @@ public enum DBTraceUtils {
|
|||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Cast an iterator to a less-specific type, given that it cannot insert elements
|
||||
*
|
||||
* @param <T> the desired type
|
||||
* @param it the iterator of more specific type
|
||||
* @return the same iterator
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T> Iterator<T> covariantIterator(Iterator<? extends T> it) {
|
||||
// Iterators only support read and remove, not insert. Safe to cast.
|
||||
return (Iterator<T>) it;
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterate over all the longs contained in a given range
|
||||
*
|
||||
* @param span the range
|
||||
* @return the iterator
|
||||
*/
|
||||
public static Iterator<Long> iterateSpan(Range<Long> span) {
|
||||
return new Iterator<>() {
|
||||
final long end = upperEndpoint(span);
|
||||
|
@ -617,6 +872,17 @@ public enum DBTraceUtils {
|
|||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all the addresses in a factory, starting at the given place
|
||||
*
|
||||
* <p>
|
||||
* If backward, this yields all addresses coming before start
|
||||
*
|
||||
* @param factory the factory
|
||||
* @param start the start (or end) address
|
||||
* @param forward true for all after, false for all before
|
||||
* @return the address set
|
||||
*/
|
||||
public static AddressSetView getAddressSet(AddressFactory factory, Address start,
|
||||
boolean forward) {
|
||||
AddressSet all = factory.getAddressSet();
|
||||
|
@ -628,6 +894,14 @@ public enum DBTraceUtils {
|
|||
return factory.getAddressSet(min, start);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an address range, checking the endpoints
|
||||
*
|
||||
* @param min the min address, which must be less than or equal to max
|
||||
* @param max the max address, which must be greater than or equal to min
|
||||
* @return the range
|
||||
* @throws IllegalArgumentException if max is less than min
|
||||
*/
|
||||
public static AddressRange toRange(Address min, Address max) {
|
||||
if (min.compareTo(max) > 0) {
|
||||
throw new IllegalArgumentException("min must precede max");
|
||||
|
|
|
@ -206,17 +206,16 @@ public class DBTraceDataSettingsAdapter
|
|||
}
|
||||
|
||||
@Override
|
||||
protected DBTraceAddressSnapRangePropertyMapSpace<DBTraceSettingsEntry, DBTraceSettingsEntry> createSpace(
|
||||
AddressSpace space, DBTraceSpaceEntry ent) throws VersionException, IOException {
|
||||
protected DBTraceDataSettingsSpace createSpace(AddressSpace space, DBTraceSpaceEntry ent)
|
||||
throws VersionException, IOException {
|
||||
return new DBTraceDataSettingsSpace(
|
||||
tableName(space, ent.getThreadKey(), ent.getFrameLevel()),
|
||||
trace.getStoreFactory(), lock, space, dataType, dataFactory);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected DBTraceAddressSnapRangePropertyMapRegisterSpace<DBTraceSettingsEntry, DBTraceSettingsEntry> createRegisterSpace(
|
||||
AddressSpace space, TraceThread thread, DBTraceSpaceEntry ent)
|
||||
throws VersionException, IOException {
|
||||
protected DBTraceDataSettingsRegisterSpace createRegisterSpace(AddressSpace space,
|
||||
TraceThread thread, DBTraceSpaceEntry ent) throws VersionException, IOException {
|
||||
return new DBTraceDataSettingsRegisterSpace(
|
||||
tableName(space, ent.getThreadKey(), ent.getFrameLevel()),
|
||||
trace.getStoreFactory(), lock, space, thread, ent.getFrameLevel(), dataType,
|
||||
|
|
|
@ -84,10 +84,10 @@ public interface DBTraceCodeUnitAdapter extends TraceCodeUnit, MemBufferAdapter
|
|||
@Override
|
||||
default <T> void setProperty(String name, Class<T> valueClass, T value) {
|
||||
try (LockHold hold = LockHold.lock(getTrace().getReadWriteLock().writeLock())) {
|
||||
TracePropertySetter<T> setter =
|
||||
getTrace().getInternalAddressPropertyManager()
|
||||
.getOrCreatePropertySetter(name, valueClass);
|
||||
setter.set(getLifespan(), getAddress(), value);
|
||||
TracePropertyMap<? super T> map = getTrace().getInternalAddressPropertyManager()
|
||||
.getOrCreatePropertyMapSuper(name, valueClass);
|
||||
TracePropertyMapSpace<? super T> space = map.getPropertyMapSpace(getTraceSpace(), true);
|
||||
space.set(getLifespan(), getAddress(), value);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -122,9 +122,18 @@ public interface DBTraceCodeUnitAdapter extends TraceCodeUnit, MemBufferAdapter
|
|||
@Override
|
||||
default <T> T getProperty(String name, Class<T> valueClass) {
|
||||
try (LockHold hold = LockHold.lock(getTrace().getReadWriteLock().readLock())) {
|
||||
TracePropertyGetter<T> getter =
|
||||
getTrace().getInternalAddressPropertyManager().getPropertyGetter(name, valueClass);
|
||||
return getter.get(getStartSnap(), getAddress());
|
||||
TracePropertyMap<? extends T> map =
|
||||
getTrace().getInternalAddressPropertyManager()
|
||||
.getPropertyMapExtends(name, valueClass);
|
||||
if (map == null) {
|
||||
return null;
|
||||
}
|
||||
TracePropertyMapSpace<? extends T> space =
|
||||
map.getPropertyMapSpace(getTraceSpace(), false);
|
||||
if (space == null) {
|
||||
return null;
|
||||
}
|
||||
return space.get(getStartSnap(), getAddress());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -150,7 +159,7 @@ public interface DBTraceCodeUnitAdapter extends TraceCodeUnit, MemBufferAdapter
|
|||
@Override
|
||||
default boolean hasProperty(String name) {
|
||||
try (LockHold hold = LockHold.lock(getTrace().getReadWriteLock().readLock())) {
|
||||
TracePropertyMap<?> map =
|
||||
TracePropertyMapOperations<?> map =
|
||||
getTrace().getInternalAddressPropertyManager().getPropertyMap(name);
|
||||
if (map == null) {
|
||||
return false;
|
||||
|
@ -163,13 +172,18 @@ public interface DBTraceCodeUnitAdapter extends TraceCodeUnit, MemBufferAdapter
|
|||
@Override
|
||||
default boolean getVoidProperty(String name) {
|
||||
// NOTE: Nearly identical to hasProperty, except named property must be Void type
|
||||
// NOTE: No need to use Extends. Nothing extends Void.
|
||||
try (LockHold hold = LockHold.lock(getTrace().getReadWriteLock().readLock())) {
|
||||
TracePropertyGetter<Void> getter =
|
||||
getTrace().getInternalAddressPropertyManager().getPropertyGetter(name, Void.class);
|
||||
if (getter == null) {
|
||||
TracePropertyMap<Void> map =
|
||||
getTrace().getInternalAddressPropertyManager().getPropertyMap(name, Void.class);
|
||||
if (map == null) {
|
||||
return false;
|
||||
}
|
||||
return getter.getAddressSetView(Range.singleton(getStartSnap())).contains(getAddress());
|
||||
TracePropertyMapSpace<Void> space = map.getPropertyMapSpace(getTraceSpace(), false);
|
||||
if (space == null) {
|
||||
return false;
|
||||
}
|
||||
return map.getAddressSetView(Range.singleton(getStartSnap())).contains(getAddress());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -184,7 +198,7 @@ public interface DBTraceCodeUnitAdapter extends TraceCodeUnit, MemBufferAdapter
|
|||
@Override
|
||||
default void removeProperty(String name) {
|
||||
try (LockHold hold = LockHold.lock(getTrace().getReadWriteLock().writeLock())) {
|
||||
TracePropertyMap<?> map =
|
||||
TracePropertyMapOperations<?> map =
|
||||
getTrace().getInternalAddressPropertyManager().getPropertyMap(name);
|
||||
if (map == null) {
|
||||
return;
|
||||
|
@ -196,7 +210,7 @@ public interface DBTraceCodeUnitAdapter extends TraceCodeUnit, MemBufferAdapter
|
|||
@Override
|
||||
default void visitProperty(PropertyVisitor visitor, String propertyName) {
|
||||
try (LockHold hold = LockHold.lock(getTrace().getReadWriteLock().readLock())) {
|
||||
TracePropertyMap<?> map =
|
||||
TracePropertyMapOperations<?> map =
|
||||
getTrace().getInternalAddressPropertyManager().getPropertyMap(propertyName);
|
||||
if (map == null) {
|
||||
return;
|
||||
|
|
|
@ -18,26 +18,29 @@ package ghidra.trace.database.map;
|
|||
import java.io.*;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.Collection;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.concurrent.locks.ReadWriteLock;
|
||||
|
||||
import com.google.common.collect.Range;
|
||||
|
||||
import db.*;
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.model.address.AddressRange;
|
||||
import ghidra.program.model.address.*;
|
||||
import ghidra.program.model.lang.Language;
|
||||
import ghidra.trace.database.DBTrace;
|
||||
import ghidra.trace.database.DBTraceUtils;
|
||||
import ghidra.trace.database.map.DBTraceAddressSnapRangePropertyMapTree.AbstractDBTraceAddressSnapRangePropertyMapData;
|
||||
import ghidra.trace.database.map.DBTraceAddressSnapRangePropertyMapTree.TraceAddressSnapRangeQuery;
|
||||
import ghidra.trace.database.thread.DBTraceThreadManager;
|
||||
import ghidra.trace.model.Trace;
|
||||
import ghidra.trace.model.TraceAddressSnapRange;
|
||||
import ghidra.trace.model.property.TracePropertyMap;
|
||||
import ghidra.trace.model.property.*;
|
||||
import ghidra.trace.model.thread.TraceThread;
|
||||
import ghidra.util.*;
|
||||
import ghidra.util.database.*;
|
||||
import ghidra.util.database.DBCachedObjectStoreFactory.AbstractDBFieldCodec;
|
||||
import ghidra.util.database.annot.*;
|
||||
import ghidra.util.exception.NotYetImplementedException;
|
||||
import ghidra.util.exception.VersionException;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
|
@ -53,10 +56,12 @@ public abstract class AbstractDBTracePropertyMap<T, DR extends AbstractDBTraceAd
|
|||
dataFactory);
|
||||
}
|
||||
|
||||
// TODO: These next several methods are repeated thrice in this file....
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
protected void makeWay(Entry<TraceAddressSnapRange, T> entry, Range<Long> span) {
|
||||
// TODO: Would rather not rely on implementation knowledge here
|
||||
// The shape is the database record in AbstracctDBTraceAddressSnapRangePropertyMapData
|
||||
// The shape is the database record in AbstractDBTraceAddressSnapRangePropertyMapData
|
||||
makeWay((DR) entry.getKey(), span);
|
||||
}
|
||||
|
||||
|
@ -87,12 +92,21 @@ public abstract class AbstractDBTracePropertyMap<T, DR extends AbstractDBTraceAd
|
|||
}
|
||||
|
||||
@Override
|
||||
public void clear(Range<Long> span, AddressRange range) {
|
||||
public Collection<Entry<TraceAddressSnapRange, T>> getEntries(Range<Long> lifespan,
|
||||
AddressRange range) {
|
||||
return reduce(TraceAddressSnapRangeQuery.intersecting(range, lifespan)).entries();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean clear(Range<Long> span, AddressRange range) {
|
||||
try (LockHold hold = LockHold.lock(lock.writeLock())) {
|
||||
boolean result = false;
|
||||
for (Entry<TraceAddressSnapRange, T> entry : reduce(
|
||||
TraceAddressSnapRangeQuery.intersecting(range, span)).entries()) {
|
||||
makeWay(entry, span);
|
||||
result = true;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -107,6 +121,211 @@ public abstract class AbstractDBTracePropertyMap<T, DR extends AbstractDBTraceAd
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected DBTracePropertyMapSpace createSpace(AddressSpace space,
|
||||
DBTraceSpaceEntry ent) throws VersionException, IOException {
|
||||
return new DBTracePropertyMapSpace(
|
||||
tableName(space, ent.getThreadKey(), ent.getFrameLevel()), trace.getStoreFactory(),
|
||||
lock, space, dataType, dataFactory);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected DBTracePropertyMapRegisterSpace createRegisterSpace(
|
||||
AddressSpace space, TraceThread thread, DBTraceSpaceEntry ent)
|
||||
throws VersionException, IOException {
|
||||
return new DBTracePropertyMapRegisterSpace(
|
||||
tableName(space, ent.getThreadKey(), ent.getFrameLevel()), trace.getStoreFactory(),
|
||||
lock, space, thread, ent.getFrameLevel(), dataType, dataFactory);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TracePropertyMapSpace<T> getPropertyMapSpace(AddressSpace space,
|
||||
boolean createIfAbsent) {
|
||||
return (DBTracePropertyMapSpace) getForSpace(space, createIfAbsent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TracePropertyMapRegisterSpace<T> getPropertyMapRegisterSpace(TraceThread thread,
|
||||
int frameLevel, boolean createIfAbsent) {
|
||||
return (DBTracePropertyMapRegisterSpace) getForRegisterSpace(thread, frameLevel,
|
||||
createIfAbsent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete() {
|
||||
throw new NotYetImplementedException();
|
||||
}
|
||||
|
||||
public class DBTracePropertyMapSpace
|
||||
extends DBTraceAddressSnapRangePropertyMapSpace<T, DR>
|
||||
implements TracePropertyMapSpace<T> {
|
||||
|
||||
public DBTracePropertyMapSpace(String tableName, DBCachedObjectStoreFactory storeFactory,
|
||||
ReadWriteLock lock, AddressSpace space, Class<DR> dataType,
|
||||
DBTraceAddressSnapRangePropertyMapDataFactory<T, DR> dataFactory)
|
||||
throws VersionException, IOException {
|
||||
super(tableName, storeFactory, lock, space, dataType, dataFactory);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Trace getTrace() {
|
||||
return trace;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<T> getValueClass() {
|
||||
return AbstractDBTracePropertyMap.this.getValueClass();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
protected void makeWay(Entry<TraceAddressSnapRange, T> entry, Range<Long> span) {
|
||||
// TODO: Would rather not rely on implementation knowledge here
|
||||
// The shape is the database record in AbstractDBTraceAddressSnapRangePropertyMapData
|
||||
makeWay((DR) entry.getKey(), span);
|
||||
}
|
||||
|
||||
protected void makeWay(DR data, Range<Long> span) {
|
||||
DBTraceUtils.makeWay(data, span, (d, s) -> d.doSetLifespan(s), d -> deleteData(d));
|
||||
// TODO: Any events?
|
||||
}
|
||||
|
||||
@Override
|
||||
public void set(Range<Long> lifespan, Address address, T value) {
|
||||
put(address, lifespan, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void set(Range<Long> lifespan, AddressRange range, T value) {
|
||||
put(range, lifespan, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public T get(long snap, Address address) {
|
||||
return reduce(TraceAddressSnapRangeQuery.at(address, snap)).firstValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Entry<TraceAddressSnapRange, T> getEntry(long snap, Address address) {
|
||||
return reduce(TraceAddressSnapRangeQuery.at(address, snap)).firstEntry();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Entry<TraceAddressSnapRange, T>> getEntries(Range<Long> lifespan,
|
||||
AddressRange range) {
|
||||
return reduce(TraceAddressSnapRangeQuery.intersecting(range, lifespan)).entries();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean clear(Range<Long> span, AddressRange range) {
|
||||
try (LockHold hold = LockHold.lock(lock.writeLock())) {
|
||||
boolean result = false;
|
||||
for (Entry<TraceAddressSnapRange, T> entry : reduce(
|
||||
TraceAddressSnapRangeQuery.intersecting(range, span)).entries()) {
|
||||
makeWay(entry, span);
|
||||
result = true;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public T put(TraceAddressSnapRange shape, T value) {
|
||||
try (LockHold hold = LockHold.lock(lock.writeLock())) {
|
||||
for (Entry<TraceAddressSnapRange, T> entry : reduce(
|
||||
TraceAddressSnapRangeQuery.intersecting(shape)).entries()) {
|
||||
makeWay(entry, shape.getLifespan());
|
||||
}
|
||||
return super.put(shape, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class DBTracePropertyMapRegisterSpace
|
||||
extends DBTraceAddressSnapRangePropertyMapRegisterSpace<T, DR>
|
||||
implements TracePropertyMapRegisterSpace<T> {
|
||||
|
||||
public DBTracePropertyMapRegisterSpace(String tableName,
|
||||
DBCachedObjectStoreFactory storeFactory, ReadWriteLock lock, AddressSpace space,
|
||||
TraceThread thread, int frameLevel, Class<DR> dataType,
|
||||
DBTraceAddressSnapRangePropertyMapDataFactory<T, DR> dataFactory)
|
||||
throws VersionException, IOException {
|
||||
super(tableName, storeFactory, lock, space, thread, frameLevel, dataType, dataFactory);
|
||||
// TODO Auto-generated constructor stub
|
||||
}
|
||||
|
||||
@Override
|
||||
public Trace getTrace() {
|
||||
return trace;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<T> getValueClass() {
|
||||
return AbstractDBTracePropertyMap.this.getValueClass();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
protected void makeWay(Entry<TraceAddressSnapRange, T> entry, Range<Long> span) {
|
||||
// TODO: Would rather not rely on implementation knowledge here
|
||||
// The shape is the database record in AbstractDBTraceAddressSnapRangePropertyMapData
|
||||
makeWay((DR) entry.getKey(), span);
|
||||
}
|
||||
|
||||
protected void makeWay(DR data, Range<Long> span) {
|
||||
DBTraceUtils.makeWay(data, span, (d, s) -> d.doSetLifespan(s), d -> deleteData(d));
|
||||
// TODO: Any events?
|
||||
}
|
||||
|
||||
@Override
|
||||
public void set(Range<Long> lifespan, Address address, T value) {
|
||||
put(address, lifespan, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void set(Range<Long> lifespan, AddressRange range, T value) {
|
||||
put(range, lifespan, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public T get(long snap, Address address) {
|
||||
return reduce(TraceAddressSnapRangeQuery.at(address, snap)).firstValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Entry<TraceAddressSnapRange, T> getEntry(long snap, Address address) {
|
||||
return reduce(TraceAddressSnapRangeQuery.at(address, snap)).firstEntry();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Entry<TraceAddressSnapRange, T>> getEntries(Range<Long> lifespan,
|
||||
AddressRange range) {
|
||||
return reduce(TraceAddressSnapRangeQuery.intersecting(range, lifespan)).entries();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean clear(Range<Long> span, AddressRange range) {
|
||||
try (LockHold hold = LockHold.lock(lock.writeLock())) {
|
||||
boolean result = false;
|
||||
for (Entry<TraceAddressSnapRange, T> entry : reduce(
|
||||
TraceAddressSnapRangeQuery.intersecting(range, span)).entries()) {
|
||||
makeWay(entry, span);
|
||||
result = true;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public T put(TraceAddressSnapRange shape, T value) {
|
||||
try (LockHold hold = LockHold.lock(lock.writeLock())) {
|
||||
for (Entry<TraceAddressSnapRange, T> entry : reduce(
|
||||
TraceAddressSnapRangeQuery.intersecting(shape)).entries()) {
|
||||
makeWay(entry, shape.getLifespan());
|
||||
}
|
||||
return super.put(shape, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class DBTraceIntPropertyMap
|
||||
extends AbstractDBTracePropertyMap<Integer, DBTraceIntPropertyMapEntry> {
|
||||
|
||||
|
|
|
@ -47,7 +47,7 @@ import ghidra.trace.model.listing.*;
|
|||
import ghidra.trace.model.memory.TraceMemoryRegion;
|
||||
import ghidra.trace.model.program.TraceProgramView;
|
||||
import ghidra.trace.model.program.TraceProgramViewListing;
|
||||
import ghidra.trace.model.property.TracePropertyMap;
|
||||
import ghidra.trace.model.property.TracePropertyMapOperations;
|
||||
import ghidra.trace.model.symbol.TraceFunctionSymbol;
|
||||
import ghidra.trace.util.*;
|
||||
import ghidra.util.*;
|
||||
|
@ -361,7 +361,7 @@ public abstract class AbstractDBTraceProgramViewListing implements TraceProgramV
|
|||
// TODO: Other "special" property types
|
||||
|
||||
// TODO: Cover this in testing
|
||||
TracePropertyMap<?> map =
|
||||
TracePropertyMapOperations<?> map =
|
||||
program.trace.getInternalAddressPropertyManager().getPropertyMap(property);
|
||||
if (map == null) {
|
||||
return new WrappingCodeUnitIterator(Collections.emptyIterator());
|
||||
|
@ -383,7 +383,7 @@ public abstract class AbstractDBTraceProgramViewListing implements TraceProgramV
|
|||
// TODO: Other "special" property types
|
||||
|
||||
// TODO: Cover this in testing
|
||||
TracePropertyMap<?> map =
|
||||
TracePropertyMapOperations<?> map =
|
||||
program.trace.getInternalAddressPropertyManager().getPropertyMap(property);
|
||||
if (map == null) {
|
||||
return new WrappingCodeUnitIterator(Collections.emptyIterator());
|
||||
|
@ -406,7 +406,7 @@ public abstract class AbstractDBTraceProgramViewListing implements TraceProgramV
|
|||
// TODO: Other "special" property types
|
||||
|
||||
// TODO: Cover this in testing
|
||||
TracePropertyMap<?> map =
|
||||
TracePropertyMapOperations<?> map =
|
||||
program.trace.getInternalAddressPropertyManager().getPropertyMap(property);
|
||||
if (map == null) {
|
||||
return new WrappingCodeUnitIterator(Collections.emptyIterator());
|
||||
|
|
|
@ -17,112 +17,435 @@ package ghidra.trace.database.program;
|
|||
|
||||
import java.util.Iterator;
|
||||
|
||||
import ghidra.program.model.address.Address;
|
||||
import com.google.common.collect.Range;
|
||||
|
||||
import ghidra.program.model.address.*;
|
||||
import ghidra.program.model.util.*;
|
||||
import ghidra.trace.database.DBTraceUtils;
|
||||
import ghidra.trace.model.property.TracePropertyMap;
|
||||
import ghidra.util.LockHold;
|
||||
import ghidra.util.Saveable;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.exception.DuplicateNameException;
|
||||
import ghidra.util.exception.*;
|
||||
import ghidra.util.prop.PropertyVisitor;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
public class DBTraceProgramViewPropertyMapManager implements PropertyMapManager {
|
||||
protected final DBTraceProgramView program;
|
||||
|
||||
protected abstract class AbstractDBTraceProgramViewPropertyMap<T> implements PropertyMap {
|
||||
protected final TracePropertyMap<T> map;
|
||||
protected final String name;
|
||||
|
||||
public AbstractDBTraceProgramViewPropertyMap(TracePropertyMap<T> map, String name) {
|
||||
this.map = map;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
protected AddressSetView getAddressSetView() {
|
||||
return map.getAddressSetView(Range.singleton(program.snap));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean intersects(Address start, Address end) {
|
||||
return getAddressSetView().intersects(start, end);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean intersects(AddressSetView set) {
|
||||
return getAddressSetView().intersects(set);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeRange(Address start, Address end) {
|
||||
return map.clear(Range.singleton(program.snap), new AddressRangeImpl(start, end));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean remove(Address addr) {
|
||||
return removeRange(addr, addr);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasProperty(Address addr) {
|
||||
return intersects(addr, addr);
|
||||
}
|
||||
|
||||
@Override
|
||||
public T getObject(Address addr) {
|
||||
return map.get(program.snap, addr);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Address getNextPropertyAddress(Address addr) {
|
||||
Address next = addr.next();
|
||||
if (next == null) {
|
||||
return null;
|
||||
}
|
||||
AddressRangeIterator it = getAddressSetView().getAddressRanges(next, true);
|
||||
if (!it.hasNext()) {
|
||||
return null;
|
||||
}
|
||||
AddressRange range = it.next();
|
||||
if (!range.contains(next)) {
|
||||
return next;
|
||||
}
|
||||
return range.getMinAddress();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Address getPreviousPropertyAddress(Address addr) {
|
||||
Address prev = addr.previous();
|
||||
if (prev == null) {
|
||||
return null;
|
||||
}
|
||||
AddressRangeIterator it = getAddressSetView().getAddressRanges(prev, false);
|
||||
if (!it.hasNext()) {
|
||||
return null;
|
||||
}
|
||||
AddressRange range = it.next();
|
||||
if (!range.contains(prev)) {
|
||||
return prev;
|
||||
}
|
||||
return range.getMaxAddress();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Address getFirstPropertyAddress() {
|
||||
return getAddressSetView().getMinAddress();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Address getLastPropertyAddress() {
|
||||
return getAddressSetView().getMaxAddress();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSize() {
|
||||
return (int) getAddressSetView().getNumAddresses();
|
||||
}
|
||||
|
||||
@Override
|
||||
public AddressIterator getPropertyIterator(Address start, Address end) {
|
||||
return getPropertyIterator(start, end, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AddressIterator getPropertyIterator(Address start, Address end, boolean forward) {
|
||||
return getAddressSetView().intersectRange(start, end).getAddresses(forward);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AddressIterator getPropertyIterator() {
|
||||
return getAddressSetView().getAddresses(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AddressIterator getPropertyIterator(AddressSetView asv) {
|
||||
return getPropertyIterator(asv, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AddressIterator getPropertyIterator(AddressSetView asv, boolean forward) {
|
||||
return getAddressSetView().intersect(asv).getAddresses(forward);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AddressIterator getPropertyIterator(Address start, boolean forward) {
|
||||
return getAddressSetView().getAddresses(start, forward);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void moveRange(Address start, Address end, Address newStart) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
|
||||
protected class DBTraceProgramViewIntPropertyMap
|
||||
extends AbstractDBTraceProgramViewPropertyMap<Integer> implements IntPropertyMap {
|
||||
|
||||
public DBTraceProgramViewIntPropertyMap(TracePropertyMap<Integer> map, String name) {
|
||||
super(map, name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyValue(PropertyVisitor visitor, Address addr) {
|
||||
Integer value = getObject(addr);
|
||||
if (value == null) {
|
||||
return;
|
||||
}
|
||||
visitor.visit(value.intValue());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add(Address addr, int value) {
|
||||
map.set(DBTraceUtils.atLeastMaybeScratch(program.snap), addr, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getInt(Address addr) throws NoValueException {
|
||||
Integer value = getObject(addr);
|
||||
if (value == null) {
|
||||
throw new NoValueException();
|
||||
}
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
protected class DBTraceProgramViewLongPropertyMap
|
||||
extends AbstractDBTraceProgramViewPropertyMap<Long> implements LongPropertyMap {
|
||||
|
||||
public DBTraceProgramViewLongPropertyMap(TracePropertyMap<Long> map, String name) {
|
||||
super(map, name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyValue(PropertyVisitor visitor, Address addr) {
|
||||
Long value = getObject(addr);
|
||||
if (value == null) {
|
||||
return;
|
||||
}
|
||||
// TODO: In program, this throws NotYetImplemented....
|
||||
visitor.visit(value.longValue());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add(Address addr, long value) {
|
||||
map.set(DBTraceUtils.atLeastMaybeScratch(program.snap), addr, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getLong(Address addr) throws NoValueException {
|
||||
Long value = getObject(addr);
|
||||
if (value == null) {
|
||||
throw new NoValueException();
|
||||
}
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
protected class DBTraceProgramViewStringPropertyMap
|
||||
extends AbstractDBTraceProgramViewPropertyMap<String> implements StringPropertyMap {
|
||||
|
||||
public DBTraceProgramViewStringPropertyMap(TracePropertyMap<String> map, String name) {
|
||||
super(map, name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyValue(PropertyVisitor visitor, Address addr) {
|
||||
String value = getObject(addr);
|
||||
visitor.visit(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add(Address addr, String value) {
|
||||
map.set(DBTraceUtils.atLeastMaybeScratch(program.snap), addr, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getString(Address addr) {
|
||||
return getObject(addr);
|
||||
}
|
||||
}
|
||||
|
||||
protected class DBTraceProgramViewObjectPropertyMap<T extends Saveable>
|
||||
extends AbstractDBTraceProgramViewPropertyMap<T> implements ObjectPropertyMap {
|
||||
|
||||
public DBTraceProgramViewObjectPropertyMap(TracePropertyMap<T> map, String name) {
|
||||
super(map, name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyValue(PropertyVisitor visitor, Address addr) {
|
||||
Saveable value = getObject(addr);
|
||||
visitor.visit(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add(Address addr, Saveable value) {
|
||||
map.set(DBTraceUtils.atLeastMaybeScratch(program.snap), addr,
|
||||
map.getValueClass().cast(value));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getObjectClass() {
|
||||
return map.getValueClass();
|
||||
}
|
||||
}
|
||||
|
||||
protected class DBTraceProgramViewVoidPropertyMap
|
||||
extends AbstractDBTraceProgramViewPropertyMap<Void> implements VoidPropertyMap {
|
||||
|
||||
public DBTraceProgramViewVoidPropertyMap(TracePropertyMap<Void> map, String name) {
|
||||
super(map, name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyValue(PropertyVisitor visitor, Address addr) {
|
||||
if (!hasProperty(addr)) {
|
||||
return;
|
||||
}
|
||||
visitor.visit();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add(Address addr) {
|
||||
map.set(DBTraceUtils.atLeastMaybeScratch(program.snap), addr, null);
|
||||
}
|
||||
}
|
||||
|
||||
public DBTraceProgramViewPropertyMapManager(DBTraceProgramView program) {
|
||||
this.program = program;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IntPropertyMap createIntPropertyMap(String propertyName) throws DuplicateNameException {
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
return new DBTraceProgramViewIntPropertyMap(program.trace.getAddressPropertyManager()
|
||||
.createPropertyMap(propertyName, Integer.class),
|
||||
propertyName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public LongPropertyMap createLongPropertyMap(String propertyName)
|
||||
throws DuplicateNameException {
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
return new DBTraceProgramViewLongPropertyMap(program.trace.getAddressPropertyManager()
|
||||
.createPropertyMap(propertyName, Long.class),
|
||||
propertyName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public StringPropertyMap createStringPropertyMap(String propertyName)
|
||||
throws DuplicateNameException {
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
return new DBTraceProgramViewStringPropertyMap(program.trace.getAddressPropertyManager()
|
||||
.createPropertyMap(propertyName, String.class),
|
||||
propertyName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ObjectPropertyMap createObjectPropertyMap(String propertyName,
|
||||
Class<? extends Saveable> objectClass) throws DuplicateNameException {
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
return new DBTraceProgramViewObjectPropertyMap<>(program.trace.getAddressPropertyManager()
|
||||
.createPropertyMap(propertyName, objectClass),
|
||||
propertyName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public VoidPropertyMap createVoidPropertyMap(String propertyName)
|
||||
throws DuplicateNameException {
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
return new DBTraceProgramViewVoidPropertyMap(program.trace.getAddressPropertyManager()
|
||||
.createPropertyMap(propertyName, Void.class),
|
||||
propertyName);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public PropertyMap getPropertyMap(String propertyName) {
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
TracePropertyMap<?> map =
|
||||
program.trace.getAddressPropertyManager().getPropertyMap(propertyName);
|
||||
if (map == null) {
|
||||
return null;
|
||||
}
|
||||
Class<?> cls = map.getValueClass();
|
||||
if (cls == Integer.class) {
|
||||
return new DBTraceProgramViewIntPropertyMap((TracePropertyMap<Integer>) map,
|
||||
propertyName);
|
||||
}
|
||||
if (cls == Long.class) {
|
||||
return new DBTraceProgramViewLongPropertyMap((TracePropertyMap<Long>) map,
|
||||
propertyName);
|
||||
}
|
||||
if (cls == String.class) {
|
||||
return new DBTraceProgramViewStringPropertyMap((TracePropertyMap<String>) map,
|
||||
propertyName);
|
||||
}
|
||||
if (cls == Void.class) {
|
||||
return new DBTraceProgramViewVoidPropertyMap((TracePropertyMap<Void>) map,
|
||||
propertyName);
|
||||
}
|
||||
if (Saveable.class.isAssignableFrom(cls)) {
|
||||
return new DBTraceProgramViewObjectPropertyMap<>(
|
||||
(TracePropertyMap<? extends Saveable>) map, propertyName);
|
||||
}
|
||||
throw new AssertionError("Where did this property map type come from? " + cls);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IntPropertyMap getIntPropertyMap(String propertyName) {
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
TracePropertyMap<Integer> map = program.trace.getAddressPropertyManager()
|
||||
.getPropertyMap(propertyName, Integer.class);
|
||||
return map == null ? null : new DBTraceProgramViewIntPropertyMap(map, propertyName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public LongPropertyMap getLongPropertyMap(String propertyName) {
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
TracePropertyMap<Long> map = program.trace.getAddressPropertyManager()
|
||||
.getPropertyMap(propertyName, Long.class);
|
||||
return map == null ? null : new DBTraceProgramViewLongPropertyMap(map, propertyName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public StringPropertyMap getStringPropertyMap(String propertyName) {
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
TracePropertyMap<String> map = program.trace.getAddressPropertyManager()
|
||||
.getPropertyMap(propertyName, String.class);
|
||||
return map == null ? null : new DBTraceProgramViewStringPropertyMap(map, propertyName);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public ObjectPropertyMap getObjectPropertyMap(String propertyName) {
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
TracePropertyMap<?> map =
|
||||
program.trace.getAddressPropertyManager().getPropertyMap(propertyName);
|
||||
if (map == null) {
|
||||
return null;
|
||||
}
|
||||
if (!Saveable.class.isAssignableFrom(map.getValueClass())) {
|
||||
throw new TypeMismatchException("Property " + propertyName + " is not object type");
|
||||
}
|
||||
return new DBTraceProgramViewObjectPropertyMap<>((TracePropertyMap<? extends Saveable>) map,
|
||||
propertyName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public VoidPropertyMap getVoidPropertyMap(String propertyName) {
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
TracePropertyMap<Void> map = program.trace.getAddressPropertyManager()
|
||||
.getPropertyMap(propertyName, Void.class);
|
||||
return map == null ? null : new DBTraceProgramViewVoidPropertyMap(map, propertyName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removePropertyMap(String propertyName) {
|
||||
// TODO Auto-generated method stub
|
||||
return false;
|
||||
// It would delete for entire trace, not just this view
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<String> propertyManagers() {
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
return program.trace.getAddressPropertyManager().getAllProperties().keySet().iterator();
|
||||
}
|
||||
|
||||
protected void removeAll(Range<Long> span, AddressRange range) {
|
||||
try (LockHold hold = program.trace.lockWrite()) {
|
||||
for (TracePropertyMap<?> map : program.trace.getAddressPropertyManager()
|
||||
.getAllProperties()
|
||||
.values()) {
|
||||
map.clear(span, range);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeAll(Address addr) {
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
removeAll(DBTraceUtils.atLeastMaybeScratch(program.snap), new AddressRangeImpl(addr, addr));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeAll(Address startAddr, Address endAddr, TaskMonitor monitor)
|
||||
throws CancelledException {
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
removeAll(DBTraceUtils.atLeastMaybeScratch(program.snap),
|
||||
new AddressRangeImpl(startAddr, endAddr));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,7 +30,8 @@ import ghidra.trace.database.DBTraceManager;
|
|||
import ghidra.trace.database.map.AbstractDBTracePropertyMap;
|
||||
import ghidra.trace.database.map.AbstractDBTracePropertyMap.*;
|
||||
import ghidra.trace.database.thread.DBTraceThreadManager;
|
||||
import ghidra.trace.model.property.*;
|
||||
import ghidra.trace.model.property.TraceAddressPropertyManager;
|
||||
import ghidra.trace.model.property.TracePropertyMap;
|
||||
import ghidra.util.*;
|
||||
import ghidra.util.database.*;
|
||||
import ghidra.util.database.annot.*;
|
||||
|
@ -197,6 +198,23 @@ public class DBTraceAddressPropertyManager implements TraceAddressPropertyManage
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> TracePropertyMap<? extends T> getPropertyMapExtends(String name,
|
||||
Class<T> valueClass) {
|
||||
try (LockHold hold = LockHold.lock(lock.readLock())) {
|
||||
AbstractDBTracePropertyMap<?, ?> map = propertyMapsByName.get(name);
|
||||
if (map == null) {
|
||||
return null;
|
||||
}
|
||||
if (!valueClass.isAssignableFrom(map.getValueClass())) {
|
||||
throw new TypeMismatchException("Property " + name + " has type " +
|
||||
map.getValueClass() + ", which does not extend " + valueClass);
|
||||
}
|
||||
return (TracePropertyMap<? extends T>) map;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> AbstractDBTracePropertyMap<T, ?> getOrCreatePropertyMap(String name,
|
||||
Class<T> valueClass) {
|
||||
|
@ -216,23 +234,7 @@ public class DBTraceAddressPropertyManager implements TraceAddressPropertyManage
|
|||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> TracePropertyGetter<T> getPropertyGetter(String name, Class<T> valueClass) {
|
||||
try (LockHold hold = LockHold.lock(lock.readLock())) {
|
||||
AbstractDBTracePropertyMap<?, ?> map = propertyMapsByName.get(name);
|
||||
if (map == null) {
|
||||
return null;
|
||||
}
|
||||
if (!valueClass.isAssignableFrom(map.getValueClass())) {
|
||||
throw new TypeMismatchException("Property " + name + " has type " +
|
||||
map.getValueClass() + ", which does not extend " + valueClass);
|
||||
}
|
||||
return (TracePropertyGetter<T>) map;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> TracePropertySetter<T> getOrCreatePropertySetter(String name,
|
||||
public <T> TracePropertyMap<? super T> getOrCreatePropertyMapSuper(String name,
|
||||
Class<T> valueClass) {
|
||||
try (LockHold hold = LockHold.lock(lock.writeLock())) {
|
||||
AbstractDBTracePropertyMap<?, ?> map = propertyMapsByName.get(name);
|
||||
|
@ -248,7 +250,7 @@ public class DBTraceAddressPropertyManager implements TraceAddressPropertyManage
|
|||
throw new TypeMismatchException("Property " + name + " has type " +
|
||||
map.getValueClass() + ", which is not a super-type of " + valueClass);
|
||||
}
|
||||
return (TracePropertyMap<T>) map;
|
||||
return (TracePropertyMap<? super T>) map;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -19,7 +19,8 @@ import java.util.Map;
|
|||
import java.util.Map.Entry;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import ghidra.trace.model.property.*;
|
||||
import ghidra.trace.model.property.TraceAddressPropertyManager;
|
||||
import ghidra.trace.model.property.TracePropertyMap;
|
||||
import ghidra.util.exception.DuplicateNameException;
|
||||
|
||||
class DBTraceAddressPropertyManagerApiView implements TraceAddressPropertyManager {
|
||||
|
@ -43,20 +44,21 @@ class DBTraceAddressPropertyManagerApiView implements TraceAddressPropertyManage
|
|||
}
|
||||
|
||||
@Override
|
||||
public <T> TracePropertyMap<T> getOrCreatePropertyMap(String name, Class<T> valueClass) {
|
||||
public <T> TracePropertyMap<? extends T> getPropertyMapExtends(String name,
|
||||
Class<T> valueClass) {
|
||||
return internalView.getPropertyMapExtends(API_PREFIX + name, valueClass);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> TracePropertyMap<T> getOrCreatePropertyMap(String name,
|
||||
Class<T> valueClass) {
|
||||
return internalView.getOrCreatePropertyMap(API_PREFIX + name, valueClass);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> TracePropertyGetter<T> getPropertyGetter(String name,
|
||||
public <T> TracePropertyMap<? super T> getOrCreatePropertyMapSuper(String name,
|
||||
Class<T> valueClass) {
|
||||
return internalView.getPropertyGetter(API_PREFIX + name, valueClass);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> TracePropertySetter<T> getOrCreatePropertySetter(String name,
|
||||
Class<T> valueClass) {
|
||||
return internalView.getOrCreatePropertySetter(API_PREFIX + name, valueClass);
|
||||
return internalView.getOrCreatePropertyMapSuper(API_PREFIX + name, valueClass);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -21,6 +21,13 @@ import ghidra.program.model.util.TypeMismatchException;
|
|||
import ghidra.util.Saveable;
|
||||
import ghidra.util.exception.DuplicateNameException;
|
||||
|
||||
/**
|
||||
* The manager for user properties of a trace
|
||||
*
|
||||
* <p>
|
||||
* Clients may create property maps of various value types. Each map is named, also considered the
|
||||
* "property name," and can be retrieve by that name.
|
||||
*/
|
||||
public interface TraceAddressPropertyManager {
|
||||
/**
|
||||
* Create a property map with the given name having the given type
|
||||
|
@ -59,6 +66,16 @@ public interface TraceAddressPropertyManager {
|
|||
*/
|
||||
<T> TracePropertyMap<T> getPropertyMap(String name, Class<T> valueClass);
|
||||
|
||||
/**
|
||||
* Get the property map with the given name, if its values extend the given type
|
||||
*
|
||||
* @param name the name
|
||||
* @param valueClass the expected type of values
|
||||
* @return the property map, or null if it does not exist
|
||||
* @throws TypeMismatchException if it exists but does not have the expected type
|
||||
*/
|
||||
<T> TracePropertyMap<? extends T> getPropertyMapExtends(String name, Class<T> valueClass);
|
||||
|
||||
/**
|
||||
* Get the property map with the given name, creating it if necessary, of the given type
|
||||
*
|
||||
|
@ -70,23 +87,17 @@ public interface TraceAddressPropertyManager {
|
|||
<T> TracePropertyMap<T> getOrCreatePropertyMap(String name, Class<T> valueClass);
|
||||
|
||||
/**
|
||||
* Get the property map with the given name, if its type extends the given type
|
||||
* Get the property map with the given name, creating it if necessary, of the given type
|
||||
*
|
||||
* <p>
|
||||
* If the map already exists, then its values' type must be a super type of that given.
|
||||
*
|
||||
* @see #getOrCreatePropertyMap(String, Class)
|
||||
* @param name the name
|
||||
* @param valueClass the expected type of values to get
|
||||
* @return the property map suitable for getting values of the given type
|
||||
* @param valueClass the expected type of values
|
||||
* @return the (possibly new) property map
|
||||
*/
|
||||
<T> TracePropertyGetter<T> getPropertyGetter(String name, Class<T> valueClass);
|
||||
|
||||
/**
|
||||
* Get the property map with the given name, if its type is a super-type of the given type
|
||||
*
|
||||
* @see #createPropertyMap(String, Class)
|
||||
* @param name the name
|
||||
* @param valueClass the expected type of values to set
|
||||
* @return the property map suitable for setting values of the given type
|
||||
*/
|
||||
<T> TracePropertySetter<T> getOrCreatePropertySetter(String name, Class<T> valueClass);
|
||||
<T> TracePropertyMap<? super T> getOrCreatePropertyMapSuper(String name, Class<T> valueClass);
|
||||
|
||||
/**
|
||||
* Get the property map with the given name.
|
||||
|
@ -94,8 +105,8 @@ public interface TraceAddressPropertyManager {
|
|||
* <p>
|
||||
* Note that no type checking is performed (there is no {@code valueClass} parameter). Thus, the
|
||||
* returned map is suitable only for clearing and querying where the property is present. The
|
||||
* caller may perform run-time type checking via the {@link TracePropertyMap#getValueClass()}
|
||||
* method.
|
||||
* caller may perform run-time type checking via the
|
||||
* {@link TracePropertyMapOperations#getValueClass()} method.
|
||||
*
|
||||
* @param name the name
|
||||
* @return the property map
|
||||
|
|
|
@ -1,47 +0,0 @@
|
|||
/* ###
|
||||
* 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.property;
|
||||
|
||||
import com.google.common.collect.Range;
|
||||
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.model.address.AddressSetView;
|
||||
|
||||
public interface TracePropertyGetter<T> {
|
||||
/**
|
||||
* Get the class for values of the map
|
||||
*
|
||||
* @return the value class
|
||||
*/
|
||||
Class<? extends T> getValueClass();
|
||||
|
||||
/**
|
||||
* Get the value at the given address-snap pair
|
||||
*
|
||||
* @param snap the snap
|
||||
* @param address the address
|
||||
* @return the value
|
||||
*/
|
||||
T get(long snap, Address address);
|
||||
|
||||
/**
|
||||
* Get the union of address ranges for entries which intersect the given span
|
||||
*
|
||||
* @param span the range of snaps
|
||||
* @return the address set
|
||||
*/
|
||||
AddressSetView getAddressSetView(Range<Long> span);
|
||||
}
|
|
@ -15,34 +15,82 @@
|
|||
*/
|
||||
package ghidra.trace.model.property;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.trace.model.Trace;
|
||||
import ghidra.trace.model.TraceAddressSnapRange;
|
||||
import ghidra.program.model.address.AddressSpace;
|
||||
import ghidra.trace.model.stack.TraceStackFrame;
|
||||
import ghidra.trace.model.thread.TraceThread;
|
||||
import ghidra.trace.util.TraceAddressSpace;
|
||||
|
||||
/**
|
||||
* A map from address-snap pairs to user-defined values in a {@link Trace}
|
||||
* A range map for storing properties in a trace
|
||||
*
|
||||
* <p>
|
||||
* Technically, each range is actually a "box" in two dimensions: time and space. Time is
|
||||
* represented by the span of snapshots covered, and space is represented by the range of addresses
|
||||
* covered. Currently, no effort is made to optimize coverage for entries having the same value. For
|
||||
* operations on entries, see {@link TracePropertyMapOperations}.
|
||||
*
|
||||
* <p>
|
||||
* This interface is the root of a multi-space property map. For memory spaces, clients can
|
||||
* generally use the operations inherited on this interface. For register spaces, clients must use
|
||||
* {@link #getPropertyMapRegisterSpace(TraceThread, int, boolean)} or similar.
|
||||
*
|
||||
* @param <T> the type of values
|
||||
*/
|
||||
public interface TracePropertyMap<T> extends TracePropertySetter<T>, TracePropertyGetter<T> {
|
||||
public interface TracePropertyMap<T> extends TracePropertyMapOperations<T> {
|
||||
/**
|
||||
* Get the class for values of the map
|
||||
* Get the map space for the given address space
|
||||
*
|
||||
* @return the value class
|
||||
* @param space the address space
|
||||
* @param createIfAbsent true to create the map space if it doesn't already exist
|
||||
* @return the space, or null
|
||||
*/
|
||||
@Override
|
||||
Class<T> getValueClass();
|
||||
TracePropertyMapSpace<T> getPropertyMapSpace(AddressSpace space, boolean createIfAbsent);
|
||||
|
||||
/**
|
||||
* Get the entry at the given address-snap pair
|
||||
* Get the map space for the registers of a given thread and frame
|
||||
*
|
||||
* @param thread the thread
|
||||
* @param frameLevel the frame level, 0 being the innermost
|
||||
* @param createIfAbsent true to create the map space if it doesn't already exist
|
||||
* @return the space, or null
|
||||
*/
|
||||
TracePropertyMapRegisterSpace<T> getPropertyMapRegisterSpace(TraceThread thread, int frameLevel,
|
||||
boolean createIfAbsent);
|
||||
|
||||
/**
|
||||
* Get the map space for the registers of a given frame (which knows its thread)
|
||||
*
|
||||
* @param frame the frame
|
||||
* @param createIfAbsent true to create the map space if it doesn't already exist
|
||||
* @return the space, or null
|
||||
*/
|
||||
default TracePropertyMapRegisterSpace<T> getPropertyMapRegisterSpace(TraceStackFrame frame,
|
||||
boolean createIfAbsent) {
|
||||
return getPropertyMapRegisterSpace(frame.getStack().getThread(), frame.getLevel(),
|
||||
createIfAbsent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the map space for the given trace space
|
||||
*
|
||||
* @param traceSpace the trace space, giving the memory space or thread/frame register space
|
||||
* @param createIfAbsent true to create the map space if it doesn't already exist
|
||||
* @return the space, or null
|
||||
*/
|
||||
default TracePropertyMapSpace<T> getPropertyMapSpace(TraceAddressSpace traceSpace,
|
||||
boolean createIfAbsent) {
|
||||
if (traceSpace.getAddressSpace().isRegisterSpace()) {
|
||||
return getPropertyMapRegisterSpace(traceSpace.getThread(), traceSpace.getFrameLevel(),
|
||||
createIfAbsent);
|
||||
}
|
||||
return getPropertyMapSpace(traceSpace.getAddressSpace(), createIfAbsent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete this property and remove all of its maps
|
||||
*
|
||||
* <p>
|
||||
* Because there exists {@link Map.Entry#setValue(Object)}, this method cannot be in
|
||||
* {@link TracePropertyGetter}.
|
||||
*
|
||||
* @param snap the snap
|
||||
* @param address the address
|
||||
* @return the entry, which includes the ranges and the value
|
||||
* The property can be re-created with the same or different value type.
|
||||
*/
|
||||
Map.Entry<TraceAddressSnapRange, T> getEntry(long snap, Address address);
|
||||
void delete();
|
||||
}
|
||||
|
|
|
@ -15,18 +15,25 @@
|
|||
*/
|
||||
package ghidra.trace.model.property;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
|
||||
import com.google.common.collect.Range;
|
||||
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.model.address.AddressRange;
|
||||
import ghidra.program.model.address.*;
|
||||
import ghidra.trace.model.Trace;
|
||||
import ghidra.trace.model.TraceAddressSnapRange;
|
||||
|
||||
public interface TracePropertySetter<T> {
|
||||
/**
|
||||
* A map from address-snap pairs to user-defined values in a {@link Trace}
|
||||
*/
|
||||
public interface TracePropertyMapOperations<T> {
|
||||
/**
|
||||
* Get the class for values of the map
|
||||
*
|
||||
* @return the value class
|
||||
*/
|
||||
Class<? super T> getValueClass();
|
||||
Class<T> getValueClass();
|
||||
|
||||
/**
|
||||
* Set a value at the given address over the given lifespan
|
||||
|
@ -59,7 +66,47 @@ public interface TracePropertySetter<T> {
|
|||
void set(Range<Long> lifespan, AddressRange range, T value);
|
||||
|
||||
/**
|
||||
* Remove or truncate entries so that the given span and range has no values
|
||||
* Get the value at the given address-snap pair
|
||||
*
|
||||
* @param snap the snap
|
||||
* @param address the address
|
||||
* @return the value
|
||||
*/
|
||||
T get(long snap, Address address);
|
||||
|
||||
/**
|
||||
* Get the entry at the given address-snap pair
|
||||
*
|
||||
* <p>
|
||||
* Because there exists {@link Map.Entry#setValue(Object)}, this method cannot be in
|
||||
* {@link TracePropertyGetter}.
|
||||
*
|
||||
* @param snap the snap
|
||||
* @param address the address
|
||||
* @return the entry, which includes the ranges and the value
|
||||
*/
|
||||
Map.Entry<TraceAddressSnapRange, T> getEntry(long snap, Address address);
|
||||
|
||||
/**
|
||||
* Get the entries intersecting the given bounds
|
||||
*
|
||||
* @param lifespan the range of snaps
|
||||
* @param range the range of addresses
|
||||
* @return the entries
|
||||
*/
|
||||
Collection<Map.Entry<TraceAddressSnapRange, T>> getEntries(Range<Long> lifespan,
|
||||
AddressRange range);
|
||||
|
||||
/**
|
||||
* Get the union of address ranges for entries which intersect the given span
|
||||
*
|
||||
* @param span the range of snaps
|
||||
* @return the address set
|
||||
*/
|
||||
AddressSetView getAddressSetView(Range<Long> span);
|
||||
|
||||
/**
|
||||
* Remove or truncate entries so that the given box contains no entries
|
||||
*
|
||||
* <p>
|
||||
* This applies the same truncation rule as in {@link #set(Range, AddressRange, Object)}, except
|
||||
|
@ -67,6 +114,7 @@ public interface TracePropertySetter<T> {
|
|||
*
|
||||
* @param span the range of snaps
|
||||
* @param range the address range
|
||||
* @return true if any entry was affected
|
||||
*/
|
||||
void clear(Range<Long> span, AddressRange range);
|
||||
boolean clear(Range<Long> span, AddressRange range);
|
||||
}
|
|
@ -0,0 +1,84 @@
|
|||
/* ###
|
||||
* 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.property;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
|
||||
import com.google.common.collect.Range;
|
||||
|
||||
import ghidra.program.model.lang.Register;
|
||||
import ghidra.trace.model.TraceAddressSnapRange;
|
||||
import ghidra.trace.model.thread.TraceThread;
|
||||
import ghidra.trace.util.TraceRegisterUtils;
|
||||
|
||||
/**
|
||||
* A property map space for a thread and frame
|
||||
*
|
||||
* <p>
|
||||
* Aside from providing the bound thread and frame, this also adds conveniences for setting and
|
||||
* getting properties on {@link Register}s.
|
||||
*
|
||||
* @param <T> the type of values
|
||||
*/
|
||||
public interface TracePropertyMapRegisterSpace<T> extends TracePropertyMapSpace<T> {
|
||||
/**
|
||||
* Get the thread for this space
|
||||
*
|
||||
* @return the thread
|
||||
*/
|
||||
TraceThread getThread();
|
||||
|
||||
/**
|
||||
* Get the frame level for this space
|
||||
*
|
||||
* @return the frame level, 0 being the innermost
|
||||
*/
|
||||
int getFrameLevel();
|
||||
|
||||
/**
|
||||
* Set a property on the given register for the given lifespan
|
||||
*
|
||||
* @param lifespan the range of snaps
|
||||
* @param register the register
|
||||
* @param value the value to set
|
||||
*/
|
||||
default void set(Range<Long> lifespan, Register register, T value) {
|
||||
set(lifespan, TraceRegisterUtils.rangeForRegister(register), value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all entries intersecting the given register and lifespan
|
||||
*
|
||||
* @param lifespan the range of snaps
|
||||
* @param register the register
|
||||
* @return the entries
|
||||
*/
|
||||
default Collection<Map.Entry<TraceAddressSnapRange, T>> getEntries(Range<Long> lifespan,
|
||||
Register register) {
|
||||
return getEntries(lifespan, TraceRegisterUtils.rangeForRegister(register));
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove or truncate entries so that the given box (register and lifespan) contains no entries
|
||||
*
|
||||
* @param span the range of snaps
|
||||
* @param register the register
|
||||
*/
|
||||
default void clear(Range<Long> span, Register register) {
|
||||
clear(span, TraceRegisterUtils.rangeForRegister(register));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
/* ###
|
||||
* 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.property;
|
||||
|
||||
import ghidra.program.model.address.AddressSpace;
|
||||
import ghidra.trace.model.Trace;
|
||||
|
||||
/**
|
||||
* A property map space for a memory space
|
||||
*
|
||||
* <p>
|
||||
* Note this interface is inherited by {@link TracePropertyMapRegisterSpace}, so "memory space" can
|
||||
* also mean the {@code register} space.
|
||||
*
|
||||
* @param <T> the type of values
|
||||
*/
|
||||
public interface TracePropertyMapSpace<T> extends TracePropertyMapOperations<T> {
|
||||
/**
|
||||
* Get the trace
|
||||
*
|
||||
* @return the trace
|
||||
*/
|
||||
Trace getTrace();
|
||||
|
||||
/**
|
||||
* Get the address space for this space
|
||||
*
|
||||
* <p>
|
||||
* If this is the {@code register} space, then {@link TracePropertyMapRegisterSpace#getThread()}
|
||||
* and {@link TracePropertyMapRegisterSpace#getFrameLevel()} are necessary to uniquely identify
|
||||
* this space.
|
||||
*
|
||||
* @return the address space
|
||||
*/
|
||||
AddressSpace getAddressSpace();
|
||||
}
|
|
@ -0,0 +1,92 @@
|
|||
/* ###
|
||||
* 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.*;
|
||||
|
||||
import com.google.common.collect.Range;
|
||||
|
||||
import ghidra.app.plugin.assembler.Assembler;
|
||||
import ghidra.app.plugin.assembler.Assemblers;
|
||||
import ghidra.app.plugin.processors.sleigh.SleighLanguage;
|
||||
import ghidra.pcode.exec.*;
|
||||
import ghidra.program.model.address.AddressRange;
|
||||
import ghidra.program.model.listing.Instruction;
|
||||
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.database.UndoableTransaction;
|
||||
|
||||
public class AbstractTracePcodeEmulatorTest extends AbstractGhidraHeadlessIntegrationTest {
|
||||
|
||||
public TraceThread initTrace(ToyDBTraceBuilder tb, List<String> stateInit,
|
||||
List<String> assembly) throws Throwable {
|
||||
return initTrace(tb, tb.range(0x00400000, 0x0040ffff), tb.range(0x00100000, 0x0010ffff),
|
||||
stateInit, assembly);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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, AddressRange text, AddressRange stack,
|
||||
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), text,
|
||||
TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE);
|
||||
mm.addRegion("Regions[stack1]", Range.atLeast(0L), stack,
|
||||
TraceMemoryFlag.READ, TraceMemoryFlag.WRITE);
|
||||
Assembler asm = Assemblers.getAssembler(tb.trace.getFixedProgramView(0));
|
||||
Iterator<Instruction> block = assembly.isEmpty() ? Collections.emptyIterator()
|
||||
: asm.assemble(text.getMinAddress(), assembly.toArray(String[]::new));
|
||||
Instruction last = null;
|
||||
while (block.hasNext()) {
|
||||
last = block.next();
|
||||
}
|
||||
Msg.info(this, "Assembly ended at: " + (last == null ? "null" : last.getMaxAddress()));
|
||||
if (!stateInit.isEmpty()) {
|
||||
PcodeExecutor<byte[]> exec =
|
||||
TraceSleighUtils.buildByteExecutor(tb.trace, 0, thread, 0);
|
||||
PcodeProgram initProg = SleighProgramCompiler.compileProgram(
|
||||
(SleighLanguage) tb.language, "test", stateInit,
|
||||
PcodeUseropLibrary.nil());
|
||||
exec.execute(initProg, PcodeUseropLibrary.nil());
|
||||
}
|
||||
}
|
||||
return thread;
|
||||
}
|
||||
|
||||
}
|
|
@ -21,7 +21,7 @@ import java.lang.invoke.MethodHandles;
|
|||
import java.lang.invoke.MethodHandles.Lookup;
|
||||
import java.math.BigInteger;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.*;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
|
@ -30,75 +30,18 @@ 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.address.*;
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.model.address.AddressRangeImpl;
|
||||
import ghidra.program.model.lang.*;
|
||||
import ghidra.program.model.listing.Instruction;
|
||||
import ghidra.test.AbstractGhidraHeadlessIntegrationTest;
|
||||
import ghidra.trace.database.ToyDBTraceBuilder;
|
||||
import ghidra.trace.database.context.DBTraceRegisterContextManager;
|
||||
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 {
|
||||
|
||||
public TraceThread initTrace(ToyDBTraceBuilder tb, List<String> stateInit,
|
||||
List<String> assembly) throws Throwable {
|
||||
return initTrace(tb, tb.range(0x00400000, 0x0040ffff), tb.range(0x00100000, 0x0010ffff),
|
||||
stateInit, assembly);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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, AddressRange text, AddressRange stack,
|
||||
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), text,
|
||||
TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE);
|
||||
mm.addRegion("Regions[stack1]", Range.atLeast(0L), stack,
|
||||
TraceMemoryFlag.READ, TraceMemoryFlag.WRITE);
|
||||
Assembler asm = Assemblers.getAssembler(tb.trace.getFixedProgramView(0));
|
||||
Iterator<Instruction> block = assembly.isEmpty() ? Collections.emptyIterator()
|
||||
: asm.assemble(text.getMinAddress(), assembly.toArray(String[]::new));
|
||||
Instruction last = null;
|
||||
while (block.hasNext()) {
|
||||
last = block.next();
|
||||
}
|
||||
Msg.info(this, "Assembly ended at: " + last.getMaxAddress());
|
||||
PcodeExecutor<byte[]> exec =
|
||||
TraceSleighUtils.buildByteExecutor(tb.trace, 0, thread, 0);
|
||||
PcodeProgram initProg = SleighProgramCompiler.compileProgram(
|
||||
(SleighLanguage) tb.language, "test", stateInit,
|
||||
PcodeUseropLibrary.nil());
|
||||
exec.execute(initProg, PcodeUseropLibrary.nil());
|
||||
}
|
||||
return thread;
|
||||
}
|
||||
public class BytesTracePcodeEmulatorTest extends AbstractTracePcodeEmulatorTest {
|
||||
|
||||
/**
|
||||
* Test a single instruction
|
||||
|
@ -118,7 +61,7 @@ public class TracePcodeEmulatorTest extends AbstractGhidraHeadlessIntegrationTes
|
|||
List.of(
|
||||
"PUSH 0xdeadbeef"));
|
||||
|
||||
TracePcodeEmulator emu = new TracePcodeEmulator(tb.trace, 0);
|
||||
BytesTracePcodeEmulator emu = new BytesTracePcodeEmulator(tb.trace, 0);
|
||||
PcodeThread<byte[]> emuThread = emu.newThread(thread.getPath());
|
||||
emuThread.overrideContextWithDefault();
|
||||
emuThread.stepInstruction();
|
||||
|
@ -130,7 +73,7 @@ public class TracePcodeEmulatorTest extends AbstractGhidraHeadlessIntegrationTes
|
|||
TraceSleighUtils.evaluate("*:4 0x0010fffc:8", tb.trace, 0, thread, 0));
|
||||
|
||||
try (UndoableTransaction tid = tb.startTransaction()) {
|
||||
emu.writeDown(tb.trace, 1, 1, true);
|
||||
emu.writeDown(tb.trace, 1, 1);
|
||||
}
|
||||
|
||||
// 4, not 8 bytes pushed?
|
||||
|
@ -138,12 +81,6 @@ public class TracePcodeEmulatorTest extends AbstractGhidraHeadlessIntegrationTes
|
|||
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(1));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -165,14 +102,14 @@ public class TracePcodeEmulatorTest extends AbstractGhidraHeadlessIntegrationTes
|
|||
"PUSH 0xdeadbeef",
|
||||
"PUSH 0xbaadf00d"));
|
||||
|
||||
TracePcodeEmulator emu = new TracePcodeEmulator(tb.trace, 0);
|
||||
BytesTracePcodeEmulator emu = new BytesTracePcodeEmulator(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);
|
||||
emu.writeDown(tb.trace, 1, 1);
|
||||
}
|
||||
|
||||
assertEquals(BigInteger.valueOf(0x0010fff8),
|
||||
|
@ -206,7 +143,7 @@ public class TracePcodeEmulatorTest extends AbstractGhidraHeadlessIntegrationTes
|
|||
"MOV EAX,0xdeadbeef", // 5 bytes
|
||||
"MOV ECX,0xbaadf00d")); // 5 bytes
|
||||
|
||||
TracePcodeEmulator emu = new TracePcodeEmulator(tb.trace, 0);
|
||||
BytesTracePcodeEmulator emu = new BytesTracePcodeEmulator(tb.trace, 0);
|
||||
PcodeThread<byte[]> emuThread = emu.newThread(thread.getPath());
|
||||
emuThread.overrideContextWithDefault();
|
||||
emuThread.stepInstruction();
|
||||
|
@ -218,7 +155,7 @@ public class TracePcodeEmulatorTest extends AbstractGhidraHeadlessIntegrationTes
|
|||
emuThread.stepInstruction();
|
||||
|
||||
try (UndoableTransaction tid = tb.startTransaction()) {
|
||||
emu.writeDown(tb.trace, 1, 1, false);
|
||||
emu.writeDown(tb.trace, 1, 1);
|
||||
}
|
||||
|
||||
assertEquals(BigInteger.valueOf(0x00110000),
|
||||
|
@ -267,7 +204,7 @@ public class TracePcodeEmulatorTest extends AbstractGhidraHeadlessIntegrationTes
|
|||
asm.patchProgram(mov, tb.addr(0x00401000));
|
||||
}
|
||||
|
||||
TracePcodeEmulator emu = new TracePcodeEmulator(tb.trace, 0);
|
||||
BytesTracePcodeEmulator emu = new BytesTracePcodeEmulator(tb.trace, 0);
|
||||
PcodeThread<byte[]> emuThread = emu.newThread(thread.getPath());
|
||||
emuThread.overrideContextWithDefault();
|
||||
emuThread.stepInstruction();
|
||||
|
@ -284,7 +221,7 @@ public class TracePcodeEmulatorTest extends AbstractGhidraHeadlessIntegrationTes
|
|||
emuThread.stepInstruction();
|
||||
|
||||
try (UndoableTransaction tid = tb.startTransaction()) {
|
||||
emu.writeDown(tb.trace, 1, 1, false);
|
||||
emu.writeDown(tb.trace, 1, 1);
|
||||
}
|
||||
|
||||
assertEquals(BigInteger.valueOf(0x00110000),
|
||||
|
@ -311,13 +248,13 @@ public class TracePcodeEmulatorTest extends AbstractGhidraHeadlessIntegrationTes
|
|||
List.of(
|
||||
"imm r0, #1234")); // decimal
|
||||
|
||||
TracePcodeEmulator emu = new TracePcodeEmulator(tb.trace, 0);
|
||||
BytesTracePcodeEmulator emu = new BytesTracePcodeEmulator(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);
|
||||
emu.writeDown(tb.trace, 1, 1);
|
||||
}
|
||||
|
||||
assertEquals(BigInteger.valueOf(0x00110000),
|
||||
|
@ -345,14 +282,14 @@ public class TracePcodeEmulatorTest extends AbstractGhidraHeadlessIntegrationTes
|
|||
"imm r0, #2020",
|
||||
"imm r1, #2021"));
|
||||
|
||||
TracePcodeEmulator emu = new TracePcodeEmulator(tb.trace, 0);
|
||||
BytesTracePcodeEmulator emu = new BytesTracePcodeEmulator(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);
|
||||
emu.writeDown(tb.trace, 1, 1);
|
||||
}
|
||||
|
||||
assertEquals(BigInteger.valueOf(0x00400008),
|
||||
|
@ -389,7 +326,7 @@ public class TracePcodeEmulatorTest extends AbstractGhidraHeadlessIntegrationTes
|
|||
"XOR byte ptr [0x00400007], 0xcc", // 7 bytes
|
||||
"MOV EAX,0xdeadbeef")); // 5 bytes
|
||||
|
||||
TracePcodeEmulator emu = new TracePcodeEmulator(tb.trace, 0);
|
||||
BytesTracePcodeEmulator emu = new BytesTracePcodeEmulator(tb.trace, 0);
|
||||
PcodeThread<byte[]> emuThread = emu.newThread(thread.getPath());
|
||||
emuThread.overrideContextWithDefault();
|
||||
|
||||
|
@ -397,7 +334,7 @@ public class TracePcodeEmulatorTest extends AbstractGhidraHeadlessIntegrationTes
|
|||
emuThread.stepInstruction();
|
||||
|
||||
try (UndoableTransaction tid = tb.startTransaction()) {
|
||||
emu.writeDown(tb.trace, 1, 1, false);
|
||||
emu.writeDown(tb.trace, 1, 1);
|
||||
}
|
||||
|
||||
assertEquals(BigInteger.valueOf(0x00110000),
|
||||
|
@ -427,7 +364,7 @@ public class TracePcodeEmulatorTest extends AbstractGhidraHeadlessIntegrationTes
|
|||
"PUSH 0xdeadbeef",
|
||||
"PUSH 0xbaadf00d"));
|
||||
|
||||
TracePcodeEmulator emu = new TracePcodeEmulator(tb.trace, 0);
|
||||
BytesTracePcodeEmulator emu = new BytesTracePcodeEmulator(tb.trace, 0);
|
||||
PcodeThread<byte[]> emuThread = emu.newThread(thread.getPath());
|
||||
emuThread.overrideContextWithDefault();
|
||||
assertNull(emuThread.getFrame());
|
||||
|
@ -451,7 +388,7 @@ public class TracePcodeEmulatorTest extends AbstractGhidraHeadlessIntegrationTes
|
|||
assertNull(emuThread.getFrame());
|
||||
|
||||
try (UndoableTransaction tid = tb.startTransaction()) {
|
||||
emu.writeDown(tb.trace, 1, 1, false);
|
||||
emu.writeDown(tb.trace, 1, 1);
|
||||
}
|
||||
|
||||
assertEquals(BigInteger.valueOf(0x0040000c),
|
||||
|
@ -495,7 +432,7 @@ public class TracePcodeEmulatorTest extends AbstractGhidraHeadlessIntegrationTes
|
|||
"PUSH 0xdeadbeef",
|
||||
"PUSH 0xbaadf00d"));
|
||||
|
||||
TracePcodeEmulator emu = new TracePcodeEmulator(tb.trace, 0) {
|
||||
BytesTracePcodeEmulator emu = new BytesTracePcodeEmulator(tb.trace, 0) {
|
||||
@Override
|
||||
protected PcodeUseropLibrary<byte[]> createUseropLibrary() {
|
||||
return hexLib;
|
||||
|
@ -543,7 +480,7 @@ public class TracePcodeEmulatorTest extends AbstractGhidraHeadlessIntegrationTes
|
|||
"PUSH 0xdeadbeef",
|
||||
"PUSH 0xbaadf00d"));
|
||||
|
||||
TracePcodeEmulator emu = new TracePcodeEmulator(tb.trace, 0) {
|
||||
BytesTracePcodeEmulator emu = new BytesTracePcodeEmulator(tb.trace, 0) {
|
||||
@Override
|
||||
protected PcodeUseropLibrary<byte[]> createUseropLibrary() {
|
||||
return hexLib;
|
||||
|
@ -576,7 +513,7 @@ public class TracePcodeEmulatorTest extends AbstractGhidraHeadlessIntegrationTes
|
|||
dumped.delete(0, dumped.length());
|
||||
|
||||
try (UndoableTransaction tid = tb.startTransaction()) {
|
||||
emu.writeDown(tb.trace, 1, 1, false);
|
||||
emu.writeDown(tb.trace, 1, 1);
|
||||
}
|
||||
|
||||
assertEquals(BigInteger.valueOf(0xbaadf00dL),
|
||||
|
@ -599,7 +536,7 @@ public class TracePcodeEmulatorTest extends AbstractGhidraHeadlessIntegrationTes
|
|||
"PUSH 0xdeadbeef",
|
||||
"PUSH 0xbaadf00d"));
|
||||
|
||||
TracePcodeEmulator emu = new TracePcodeEmulator(tb.trace, 0);
|
||||
BytesTracePcodeEmulator emu = new BytesTracePcodeEmulator(tb.trace, 0);
|
||||
emu.addBreakpoint(tb.addr(0x00400000), "RAX == 1");
|
||||
emu.addBreakpoint(tb.addr(0x00400006), "RAX == 0");
|
||||
PcodeThread<byte[]> emuThread = emu.newThread(thread.getPath());
|
||||
|
@ -632,13 +569,13 @@ public class TracePcodeEmulatorTest extends AbstractGhidraHeadlessIntegrationTes
|
|||
List.of(
|
||||
"clz r1, r0"));
|
||||
|
||||
TracePcodeEmulator emu = new TracePcodeEmulator(tb.trace, 0);
|
||||
BytesTracePcodeEmulator emu = new BytesTracePcodeEmulator(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);
|
||||
emu.writeDown(tb.trace, 1, 1);
|
||||
}
|
||||
|
||||
assertEquals(BigInteger.valueOf(16),
|
||||
|
@ -666,7 +603,7 @@ public class TracePcodeEmulatorTest extends AbstractGhidraHeadlessIntegrationTes
|
|||
List.of(
|
||||
"MOVAPS XMM0, xmmword ptr [0x00600000]"));
|
||||
|
||||
TracePcodeEmulator emu = new TracePcodeEmulator(tb.trace, 0);
|
||||
BytesTracePcodeEmulator emu = new BytesTracePcodeEmulator(tb.trace, 0);
|
||||
PcodeThread<byte[]> emuThread = emu.newThread(thread.getPath());
|
||||
emuThread.overrideContextWithDefault();
|
||||
emuThread.stepInstruction();
|
||||
|
@ -676,7 +613,7 @@ public class TracePcodeEmulatorTest extends AbstractGhidraHeadlessIntegrationTes
|
|||
emuThread.getState().getVar(pc));
|
||||
|
||||
try (UndoableTransaction tid = tb.startTransaction()) {
|
||||
emu.writeDown(tb.trace, 1, 1, false);
|
||||
emu.writeDown(tb.trace, 1, 1);
|
||||
}
|
||||
|
||||
assertEquals(new BigInteger("0123456789abcdeffedcba9876543210", 16),
|
||||
|
@ -704,7 +641,7 @@ public class TracePcodeEmulatorTest extends AbstractGhidraHeadlessIntegrationTes
|
|||
List.of(
|
||||
"SAR EAX, CL"));
|
||||
|
||||
TracePcodeEmulator emu = new TracePcodeEmulator(tb.trace, 0);
|
||||
BytesTracePcodeEmulator emu = new BytesTracePcodeEmulator(tb.trace, 0);
|
||||
PcodeThread<byte[]> emuThread = emu.newThread(thread.getPath());
|
||||
emuThread.overrideContextWithDefault();
|
||||
emuThread.stepInstruction();
|
||||
|
@ -714,7 +651,7 @@ public class TracePcodeEmulatorTest extends AbstractGhidraHeadlessIntegrationTes
|
|||
emuThread.getState().getVar(pc));
|
||||
|
||||
try (UndoableTransaction tid = tb.startTransaction()) {
|
||||
emu.writeDown(tb.trace, 1, 1, false);
|
||||
emu.writeDown(tb.trace, 1, 1);
|
||||
}
|
||||
|
||||
assertEquals(BigInteger.valueOf(0x7ffffff),
|
||||
|
@ -734,14 +671,14 @@ public class TracePcodeEmulatorTest extends AbstractGhidraHeadlessIntegrationTes
|
|||
"XOR AH, AH",
|
||||
"MOV RCX, RAX"));
|
||||
|
||||
TracePcodeEmulator emu = new TracePcodeEmulator(tb.trace, 0);
|
||||
BytesTracePcodeEmulator emu = new BytesTracePcodeEmulator(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);
|
||||
emu.writeDown(tb.trace, 1, 1);
|
||||
}
|
||||
|
||||
assertEquals(BigInteger.valueOf(0x12340078),
|
||||
|
@ -758,9 +695,9 @@ public class TracePcodeEmulatorTest extends AbstractGhidraHeadlessIntegrationTes
|
|||
List.of(
|
||||
"MOV RCX,RAX"));
|
||||
|
||||
TracePcodeEmulator emu = new TracePcodeEmulator(tb.trace, 0) {
|
||||
BytesTracePcodeEmulator emu = new BytesTracePcodeEmulator(tb.trace, 0) {
|
||||
@Override
|
||||
protected PcodeExecutorState<byte[]> newState(TraceThread thread) {
|
||||
protected TracePcodeExecutorState<byte[]> newState(TraceThread thread) {
|
||||
return new RequireIsKnownTraceCachedWriteBytesPcodeExecutorState(trace, snap,
|
||||
thread, 0);
|
||||
}
|
||||
|
@ -781,9 +718,9 @@ public class TracePcodeEmulatorTest extends AbstractGhidraHeadlessIntegrationTes
|
|||
List.of(
|
||||
"MOV RCX,RAX"));
|
||||
|
||||
TracePcodeEmulator emu = new TracePcodeEmulator(tb.trace, 0) {
|
||||
BytesTracePcodeEmulator emu = new BytesTracePcodeEmulator(tb.trace, 0) {
|
||||
@Override
|
||||
protected PcodeExecutorState<byte[]> newState(TraceThread thread) {
|
||||
protected TracePcodeExecutorState<byte[]> newState(TraceThread thread) {
|
||||
return new RequireIsKnownTraceCachedWriteBytesPcodeExecutorState(trace, snap,
|
||||
thread, 0);
|
||||
}
|
||||
|
@ -806,9 +743,9 @@ public class TracePcodeEmulatorTest extends AbstractGhidraHeadlessIntegrationTes
|
|||
"MOV RCX,RAX"));
|
||||
|
||||
// Start emulator one snap later
|
||||
TracePcodeEmulator emu = new TracePcodeEmulator(tb.trace, 1) {
|
||||
BytesTracePcodeEmulator emu = new BytesTracePcodeEmulator(tb.trace, 1) {
|
||||
@Override
|
||||
protected PcodeExecutorState<byte[]> newState(TraceThread thread) {
|
||||
protected TracePcodeExecutorState<byte[]> newState(TraceThread thread) {
|
||||
return new RequireIsKnownTraceCachedWriteBytesPcodeExecutorState(trace, snap,
|
||||
thread, 0);
|
||||
}
|
||||
|
@ -831,9 +768,9 @@ public class TracePcodeEmulatorTest extends AbstractGhidraHeadlessIntegrationTes
|
|||
"MOV RCX,RAX"));
|
||||
|
||||
// Start emulator one snap later, but with "has-known" checks
|
||||
TracePcodeEmulator emu = new TracePcodeEmulator(tb.trace, 1) {
|
||||
BytesTracePcodeEmulator emu = new BytesTracePcodeEmulator(tb.trace, 1) {
|
||||
@Override
|
||||
protected PcodeExecutorState<byte[]> newState(TraceThread thread) {
|
||||
protected TracePcodeExecutorState<byte[]> newState(TraceThread thread) {
|
||||
return new RequireHasKnownTraceCachedWriteBytesPcodeExecutorState(trace, snap,
|
||||
thread, 0);
|
||||
}
|
||||
|
@ -855,9 +792,9 @@ public class TracePcodeEmulatorTest extends AbstractGhidraHeadlessIntegrationTes
|
|||
"MOV RAX,0", // Have the program initialize it
|
||||
"MOV RCX,RAX"));
|
||||
|
||||
TracePcodeEmulator emu = new TracePcodeEmulator(tb.trace, 0) {
|
||||
BytesTracePcodeEmulator emu = new BytesTracePcodeEmulator(tb.trace, 0) {
|
||||
@Override
|
||||
protected PcodeExecutorState<byte[]> newState(TraceThread thread) {
|
||||
protected TracePcodeExecutorState<byte[]> newState(TraceThread thread) {
|
||||
return new RequireIsKnownTraceCachedWriteBytesPcodeExecutorState(trace, snap,
|
||||
thread, 0);
|
||||
}
|
||||
|
@ -896,7 +833,7 @@ public class TracePcodeEmulatorTest extends AbstractGhidraHeadlessIntegrationTes
|
|||
tb.trace.getMemoryManager().getBytes(0, tb.addr(0x00400000), buf);
|
||||
assertArrayEquals(tb.arr(0x48, 0x89, 0xc1), buf.array());
|
||||
|
||||
TracePcodeEmulator emu = new TracePcodeEmulator(tb.trace, 0);
|
||||
BytesTracePcodeEmulator emu = new BytesTracePcodeEmulator(tb.trace, 0);
|
||||
PcodeThread<byte[]> emuThread = emu.newThread(thread.getPath());
|
||||
// TODO: Seems the Trace-bound thread ought to know to do this in reInitialize()
|
||||
ctxVal = ctxManager.getValueWithDefault(lang, ctxReg, 0, tb.addr(0x00400000));
|
||||
|
@ -905,7 +842,7 @@ public class TracePcodeEmulatorTest extends AbstractGhidraHeadlessIntegrationTes
|
|||
emuThread.stepInstruction();
|
||||
|
||||
try (UndoableTransaction tid = tb.startTransaction()) {
|
||||
emu.writeDown(tb.trace, 1, 1, false);
|
||||
emu.writeDown(tb.trace, 1, 1);
|
||||
}
|
||||
|
||||
assertEquals(BigInteger.valueOf(0x00400003),
|
||||
|
@ -934,13 +871,13 @@ public class TracePcodeEmulatorTest extends AbstractGhidraHeadlessIntegrationTes
|
|||
List.of(
|
||||
"MOV EAX, dword ptr [RBP + -0x4]"));
|
||||
|
||||
TracePcodeEmulator emu = new TracePcodeEmulator(tb.trace, 0);
|
||||
BytesTracePcodeEmulator emu = new BytesTracePcodeEmulator(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);
|
||||
emu.writeDown(tb.trace, 1, 1);
|
||||
}
|
||||
|
||||
assertEquals(BigInteger.valueOf(0x12345678),
|
||||
|
@ -968,7 +905,7 @@ public class TracePcodeEmulatorTest extends AbstractGhidraHeadlessIntegrationTes
|
|||
List.of(
|
||||
"MOV EAX, dword ptr [RBP + -0x2]"));
|
||||
|
||||
TracePcodeEmulator emu = new TracePcodeEmulator(tb.trace, 0);
|
||||
BytesTracePcodeEmulator emu = new BytesTracePcodeEmulator(tb.trace, 0);
|
||||
PcodeThread<byte[]> emuThread = emu.newThread(thread.getPath());
|
||||
emuThread.overrideContextWithDefault();
|
||||
emuThread.stepInstruction();
|
||||
|
@ -991,7 +928,7 @@ public class TracePcodeEmulatorTest extends AbstractGhidraHeadlessIntegrationTes
|
|||
List.of(
|
||||
"MOV EAX, dword ptr [EBP + -0x2]"));
|
||||
|
||||
TracePcodeEmulator emu = new TracePcodeEmulator(tb.trace, 0);
|
||||
BytesTracePcodeEmulator emu = new BytesTracePcodeEmulator(tb.trace, 0);
|
||||
PcodeThread<byte[]> emuThread = emu.newThread(thread.getPath());
|
||||
emuThread.overrideContextWithDefault();
|
||||
emuThread.stepInstruction();
|
||||
|
@ -1014,7 +951,7 @@ public class TracePcodeEmulatorTest extends AbstractGhidraHeadlessIntegrationTes
|
|||
List.of(
|
||||
"unimpl"));
|
||||
|
||||
TracePcodeEmulator emu = new TracePcodeEmulator(tb.trace, 0);
|
||||
BytesTracePcodeEmulator emu = new BytesTracePcodeEmulator(tb.trace, 0);
|
||||
PcodeThread<byte[]> emuThread = emu.newThread(thread.getPath());
|
||||
emuThread.overrideContextWithDefault();
|
||||
emuThread.stepInstruction();
|
||||
|
@ -1037,13 +974,13 @@ public class TracePcodeEmulatorTest extends AbstractGhidraHeadlessIntegrationTes
|
|||
List.of(
|
||||
"mov.w [W1], W0"));
|
||||
|
||||
TracePcodeEmulator emu = new TracePcodeEmulator(tb.trace, 0);
|
||||
BytesTracePcodeEmulator emu = new BytesTracePcodeEmulator(tb.trace, 0);
|
||||
PcodeThread<byte[]> emuThread = emu.newThread(thread.getPath());
|
||||
//emuThread.overrideContextWithDefault(); // default context is null?
|
||||
emuThread.stepInstruction();
|
||||
|
||||
try (UndoableTransaction tid = tb.startTransaction()) {
|
||||
emu.writeDown(tb.trace, 1, 1, false);
|
||||
emu.writeDown(tb.trace, 1, 1);
|
||||
}
|
||||
|
||||
assertEquals(BigInteger.valueOf(0x000102),
|
|
@ -221,7 +221,7 @@ public class TraceSleighUtilsTest extends AbstractGhidraHeadlessIntegrationTest
|
|||
PcodeExecutor<byte[]> executor =
|
||||
new PcodeExecutor<>(sp.getLanguage(),
|
||||
BytesPcodeArithmetic.forLanguage(b.language),
|
||||
new TraceBytesPcodeExecutorState(b.trace, 0, thread, 0));
|
||||
new DirectBytesTracePcodeExecutorState(b.trace, 0, thread, 0));
|
||||
sp.execute(executor, PcodeUseropLibrary.nil());
|
||||
}
|
||||
|
||||
|
|
|
@ -53,6 +53,7 @@ import ghidra.trace.database.thread.DBTraceThreadManager;
|
|||
import ghidra.trace.model.*;
|
||||
import ghidra.trace.model.guest.TraceGuestPlatform;
|
||||
import ghidra.trace.model.guest.TracePlatform;
|
||||
import ghidra.trace.model.symbol.TraceReferenceManager;
|
||||
import ghidra.trace.model.thread.TraceThread;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.database.DBOpenMode;
|
||||
|
@ -60,12 +61,33 @@ import ghidra.util.database.UndoableTransaction;
|
|||
import ghidra.util.exception.*;
|
||||
import ghidra.util.task.ConsoleTaskMonitor;
|
||||
|
||||
/**
|
||||
* A convenient means of creating a {@link Trace} for testing
|
||||
*
|
||||
* <p>
|
||||
* There are two patterns for using this: 1) {@code try-with-resources}, and 2) in set up and tear
|
||||
* down. Some of our abstract test cases include one of these already. The constructors can build or
|
||||
* take a trace from a variety of sources, and it provides many methods for accessing parts of the
|
||||
* trace and/or program API more conveniently, esp., for generating addresses.
|
||||
*
|
||||
* <p>
|
||||
* The builder is a consumer of the trace and will automatically release it in {@link #close()}.
|
||||
*/
|
||||
public class ToyDBTraceBuilder implements AutoCloseable {
|
||||
public final Language language;
|
||||
public final DBTrace trace;
|
||||
public final TracePlatform host;
|
||||
public final LanguageService languageService = DefaultLanguageService.getLanguageService();
|
||||
|
||||
/**
|
||||
* Open a .gzf compressed trace
|
||||
*
|
||||
* @param file the .gzf file containing the trace
|
||||
* @throws CancelledException never, since the monitor cannot be cancelled
|
||||
* @throws VersionException if the trace's version is not as expected
|
||||
* @throws LanguageNotFoundException if the trace's language cannot be found
|
||||
* @throws IOException if there's an issue accessing the file
|
||||
*/
|
||||
public ToyDBTraceBuilder(File file)
|
||||
throws CancelledException, VersionException, LanguageNotFoundException, IOException {
|
||||
DBHandle handle = new DBHandle(file);
|
||||
|
@ -74,6 +96,13 @@ public class ToyDBTraceBuilder implements AutoCloseable {
|
|||
this.host = trace.getPlatformManager().getHostPlatform();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new trace with the given name and language
|
||||
*
|
||||
* @param name the name
|
||||
* @param langID the id of the language, as in {@link LanguageID}
|
||||
* @throws IOException if there's an issue creating the trace's database file(s)
|
||||
*/
|
||||
// TODO: A constructor for specifying compiler, too
|
||||
public ToyDBTraceBuilder(String name, String langID) throws IOException {
|
||||
this.language = languageService.getLanguage(new LanguageID(langID));
|
||||
|
@ -81,6 +110,14 @@ public class ToyDBTraceBuilder implements AutoCloseable {
|
|||
this.host = trace.getPlatformManager().getHostPlatform();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adopt the given trace
|
||||
*
|
||||
* <p>
|
||||
* The builder will add itself as a consumer of the trace, so the caller may safely release it.
|
||||
*
|
||||
* @param trace the trace
|
||||
*/
|
||||
public ToyDBTraceBuilder(Trace trace) {
|
||||
this.language = trace.getBaseLanguage();
|
||||
this.trace = (DBTrace) trace;
|
||||
|
@ -88,6 +125,14 @@ public class ToyDBTraceBuilder implements AutoCloseable {
|
|||
trace.addConsumer(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Manipulate the trace's memory and registers using Sleigh
|
||||
*
|
||||
* @param snap the snap to modify
|
||||
* @param frame the frame to modify
|
||||
* @param thread the thread to modify, can be {@code null} if only memory is used
|
||||
* @param sleigh the lines of Sleigh, including semicolons.
|
||||
*/
|
||||
public void exec(long snap, int frame, TraceThread thread, List<String> sleigh) {
|
||||
PcodeProgram program = SleighProgramCompiler.compileProgram((SleighLanguage) language,
|
||||
"builder", sleigh, PcodeUseropLibrary.nil());
|
||||
|
@ -95,74 +140,212 @@ public class ToyDBTraceBuilder implements AutoCloseable {
|
|||
.execute(program, PcodeUseropLibrary.nil());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the named register
|
||||
*
|
||||
* @param name the name
|
||||
* @return the register or null if it doesn't exist
|
||||
*/
|
||||
public Register reg(String name) {
|
||||
return language.getRegister(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* A shortcut for {@code space.getAdddress(offset)}
|
||||
*
|
||||
* @param space the space
|
||||
* @param offset the offset
|
||||
* @return the address
|
||||
*/
|
||||
public Address addr(AddressSpace space, long offset) {
|
||||
return space.getAddress(offset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an address in the given language's default space
|
||||
*
|
||||
* @param lang the langauge
|
||||
* @param offset the offset
|
||||
* @return the address
|
||||
*/
|
||||
public Address addr(Language lang, long offset) {
|
||||
return addr(lang.getDefaultSpace(), offset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an address in the trace's default space
|
||||
*
|
||||
* @param offset the offset
|
||||
* @return the address
|
||||
*/
|
||||
public Address addr(long offset) {
|
||||
return addr(language, offset);
|
||||
}
|
||||
|
||||
public Address addr(TracePlatform lang, long offset) {
|
||||
return lang.getLanguage().getDefaultSpace().getAddress(offset);
|
||||
/**
|
||||
* Create an address in the given platform's default space
|
||||
*
|
||||
* @param platform the platform
|
||||
* @param offset the offset
|
||||
* @return the address
|
||||
*/
|
||||
public Address addr(TracePlatform platform, long offset) {
|
||||
return platform.getLanguage().getDefaultSpace().getAddress(offset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an address in the given language's default data space
|
||||
*
|
||||
* @param lang the language
|
||||
* @param offset the offset
|
||||
* @return the address
|
||||
*/
|
||||
public Address data(Language lang, long offset) {
|
||||
return addr(lang.getDefaultDataSpace(), offset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an address in the trace's default data space
|
||||
*
|
||||
* @param offset the offset
|
||||
* @return the address
|
||||
*/
|
||||
public Address data(long offset) {
|
||||
return data(language, offset);
|
||||
}
|
||||
|
||||
public Address data(TraceGuestPlatform lang, long offset) {
|
||||
return data(lang.getLanguage(), offset);
|
||||
/**
|
||||
* Create an address in the given platform's default data space
|
||||
*
|
||||
* @param platform the platform
|
||||
* @param offset the offset
|
||||
* @return the address
|
||||
*/
|
||||
public Address data(TraceGuestPlatform platform, long offset) {
|
||||
return data(platform.getLanguage(), offset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an address range: shortcut for {@link new AddressRangeImpl(start, end)}
|
||||
*
|
||||
* @param start the start address
|
||||
* @param end the end address
|
||||
* @return the range
|
||||
*/
|
||||
public AddressRange range(Address start, Address end) {
|
||||
return new AddressRangeImpl(start, end);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an address range in the given space with the given start and end offsets
|
||||
*
|
||||
* @param space the space
|
||||
* @param start the start offset
|
||||
* @param end the end offset
|
||||
* @return the range
|
||||
*/
|
||||
public AddressRange range(AddressSpace space, long start, long end) {
|
||||
return range(addr(space, start), addr(space, end));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an address range in the given language's default space
|
||||
*
|
||||
* @param lang the language
|
||||
* @param start the start offset
|
||||
* @param end the end offset
|
||||
* @return the range
|
||||
*/
|
||||
public AddressRange range(Language lang, long start, long end) {
|
||||
return range(lang.getDefaultSpace(), start, end);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an address range in the trace's default space
|
||||
*
|
||||
* @param start the start offset
|
||||
* @param end the end offset
|
||||
* @return the range
|
||||
*/
|
||||
public AddressRange range(long start, long end) {
|
||||
return range(language, start, end);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a singleton address range in the trace's default space
|
||||
*
|
||||
* @param singleton the offset
|
||||
* @return the range
|
||||
*/
|
||||
public AddressRange range(long singleton) {
|
||||
return range(singleton, singleton);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an address-span box in the trace's default space with a singleton snap
|
||||
*
|
||||
* @param snap the snap
|
||||
* @param start the start address offset
|
||||
* @param end the end address offset
|
||||
* @return the box
|
||||
*/
|
||||
public TraceAddressSnapRange srange(long snap, long start, long end) {
|
||||
return new ImmutableTraceAddressSnapRange(addr(start), addr(end), snap, snap);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an address range in the given language's default data space
|
||||
*
|
||||
* @param lang the language
|
||||
* @param start the start offset
|
||||
* @param end the end offset
|
||||
* @return the range
|
||||
*/
|
||||
public AddressRange drng(Language lang, long start, long end) {
|
||||
return range(language.getDefaultDataSpace(), start, end);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an address range in the trace's default data space
|
||||
*
|
||||
* @param start the start offset
|
||||
* @param end the end offset
|
||||
* @return the range
|
||||
*/
|
||||
public AddressRange drng(long start, long end) {
|
||||
return drng(language, start, end);
|
||||
}
|
||||
|
||||
public AddressRange range(TraceGuestPlatform lang, long start, long end) {
|
||||
return range(lang.getLanguage(), start, end);
|
||||
/**
|
||||
* Create an address range in the given platform's default space
|
||||
*
|
||||
* @param platform the platform
|
||||
* @param start the start offset
|
||||
* @param end the end offset
|
||||
* @return the range
|
||||
*/
|
||||
public AddressRange range(TracePlatform platform, long start, long end) {
|
||||
return range(platform.getLanguage(), start, end);
|
||||
}
|
||||
|
||||
public AddressRange drng(TraceGuestPlatform lang, long start, long end) {
|
||||
return drng(lang.getLanguage(), start, end);
|
||||
/**
|
||||
* Create an address range in the given platform's default data space
|
||||
*
|
||||
* @param platform the platform
|
||||
* @param start the start offset
|
||||
* @param end the end offset
|
||||
* @return the range
|
||||
*/
|
||||
public AddressRange drng(TracePlatform platform, long start, long end) {
|
||||
return drng(platform.getLanguage(), start, end);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an address set from the given ranges
|
||||
*
|
||||
* @param ranges the ranges
|
||||
* @return the set
|
||||
*/
|
||||
public AddressSetView set(AddressRange... ranges) {
|
||||
AddressSet result = new AddressSet();
|
||||
for (AddressRange rng : ranges) {
|
||||
|
@ -171,6 +354,17 @@ public class ToyDBTraceBuilder implements AutoCloseable {
|
|||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a byte array
|
||||
*
|
||||
* <p>
|
||||
* This is basically syntactic sugar, since expressing a byte array literal can get obtuse in
|
||||
* Java. {@code new byte[] {0, 1, 2, (byte) 0x80, (byte) 0xff}} vs
|
||||
* {@code arr(0, 1, 2, 0x80, 0xff)}.
|
||||
*
|
||||
* @param e the bytes' values
|
||||
* @return the array
|
||||
*/
|
||||
public byte[] arr(int... e) {
|
||||
byte[] result = new byte[e.length];
|
||||
for (int i = 0; i < e.length; i++) {
|
||||
|
@ -179,10 +373,22 @@ public class ToyDBTraceBuilder implements AutoCloseable {
|
|||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a byte buffer
|
||||
*
|
||||
* @param e the bytes' values
|
||||
* @return the buffer, positioned at 0
|
||||
*/
|
||||
public ByteBuffer buf(int... e) {
|
||||
return ByteBuffer.wrap(arr(e));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a byte buffer, filled with a UTF-8 encoded string
|
||||
*
|
||||
* @param str the string to encode
|
||||
* @return the buffer, positioned at 0
|
||||
*/
|
||||
public ByteBuffer buf(String str) {
|
||||
CharsetEncoder ce = Charset.forName("UTF-8").newEncoder();
|
||||
ByteBuffer result =
|
||||
|
@ -192,15 +398,39 @@ public class ToyDBTraceBuilder implements AutoCloseable {
|
|||
return result.flip();
|
||||
}
|
||||
|
||||
/**
|
||||
* Start a transaction on the trace
|
||||
*
|
||||
* <p>
|
||||
* Use this in a {@code try-with-resources} block
|
||||
*
|
||||
* @return the transaction handle
|
||||
*/
|
||||
public UndoableTransaction startTransaction() {
|
||||
return UndoableTransaction.start(trace, "Testing");
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure the given bookmark type exists and retrieve it
|
||||
*
|
||||
* @param name the name of the type
|
||||
* @return the type
|
||||
*/
|
||||
public DBTraceBookmarkType getOrAddBookmarkType(String name) {
|
||||
DBTraceBookmarkManager manager = trace.getBookmarkManager();
|
||||
return manager.defineBookmarkType(name, null, Color.red, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a bookmark to the trace
|
||||
*
|
||||
* @param snap the starting snap
|
||||
* @param addr the address
|
||||
* @param typeName the name of its type
|
||||
* @param category the category
|
||||
* @param comment an optional comment
|
||||
* @return the new bookmark
|
||||
*/
|
||||
public DBTraceBookmark addBookmark(long snap, long addr, String typeName, String category,
|
||||
String comment) {
|
||||
DBTraceBookmarkType type = getOrAddBookmarkType(typeName);
|
||||
|
@ -216,8 +446,19 @@ public class ToyDBTraceBuilder implements AutoCloseable {
|
|||
return bm;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a bookmark on a register in the trace
|
||||
*
|
||||
* @param snap the starting snap
|
||||
* @param threadName the name of the thread
|
||||
* @param registerName the name of the regsiter
|
||||
* @param typeName the name of its type
|
||||
* @param category the category
|
||||
* @param comment an optional comment
|
||||
* @return the new bookmark
|
||||
*/
|
||||
public DBTraceBookmark addRegisterBookmark(long snap, String threadName, String registerName,
|
||||
String typeName, String category, String comment) throws DuplicateNameException {
|
||||
String typeName, String category, String comment) {
|
||||
Register register = language.getRegister(registerName);
|
||||
assertNotNull(register);
|
||||
TraceThread thread = getOrAddThread(threadName, snap);
|
||||
|
@ -234,12 +475,32 @@ public class ToyDBTraceBuilder implements AutoCloseable {
|
|||
return bm;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a data unit
|
||||
*
|
||||
* @param snap the starting snap
|
||||
* @param start the min address
|
||||
* @param type the data type of the unit
|
||||
* @param length the length, or -1 for the type's default
|
||||
* @return the new data unit
|
||||
* @throws CodeUnitInsertionException if the unit cannot be created
|
||||
*/
|
||||
public DBTraceDataAdapter addData(long snap, Address start, DataType type, int length)
|
||||
throws CodeUnitInsertionException {
|
||||
DBTraceCodeManager code = trace.getCodeManager();
|
||||
return code.definedData().create(Range.atLeast(snap), start, type, length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a data unit, first placing the given bytes
|
||||
*
|
||||
* @param snap the starting snap
|
||||
* @param start the min address
|
||||
* @param type the data type of the unit
|
||||
* @param buf the bytes to place, which will become the unit's bytes
|
||||
* @return the new data unit
|
||||
* @throws CodeUnitInsertionException if the unit cannot be created
|
||||
*/
|
||||
public DBTraceDataAdapter addData(long snap, Address start, DataType type, ByteBuffer buf)
|
||||
throws CodeUnitInsertionException {
|
||||
int length = buf.remaining();
|
||||
|
@ -250,6 +511,15 @@ public class ToyDBTraceBuilder implements AutoCloseable {
|
|||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an instruction unit by disassembling existing bytes
|
||||
*
|
||||
* @param snap the starting snap
|
||||
* @param start the min address
|
||||
* @param platform the platform for the language to disassemble
|
||||
* @return the instruction unit
|
||||
* @throws CodeUnitInsertionException if the instruction cannot be created
|
||||
*/
|
||||
public DBTraceInstruction addInstruction(long snap, Address start,
|
||||
TracePlatform platform) throws CodeUnitInsertionException {
|
||||
DBTraceCodeManager code = trace.getCodeManager();
|
||||
|
@ -267,6 +537,16 @@ public class ToyDBTraceBuilder implements AutoCloseable {
|
|||
.create(Range.atLeast(snap), start, platform, pseudoIns.getPrototype(), pseudoIns);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an instruction unit, first placing the given bytes, and disassembling
|
||||
*
|
||||
* @param snap the starting snap
|
||||
* @param start the min address
|
||||
* @param platform the platform the the language to disassemble
|
||||
* @param buf the bytes to place, which will become the unit's bytes
|
||||
* @return the instruction unit
|
||||
* @throws CodeUnitInsertionException if the instruction cannot be created
|
||||
*/
|
||||
public DBTraceInstruction addInstruction(long snap, Address start, TracePlatform platform,
|
||||
ByteBuffer buf) throws CodeUnitInsertionException {
|
||||
int length = buf.remaining();
|
||||
|
@ -277,20 +557,48 @@ public class ToyDBTraceBuilder implements AutoCloseable {
|
|||
return instruction;
|
||||
}
|
||||
|
||||
public TraceThread getOrAddThread(String name, long creationSnap)
|
||||
throws DuplicateNameException {
|
||||
/**
|
||||
* Ensure the given thread exists and retrieve it
|
||||
*
|
||||
* @param name the thread's name
|
||||
* @param creationSnap the snap where the thread must exist
|
||||
* @return the thread
|
||||
*/
|
||||
public TraceThread getOrAddThread(String name, long creationSnap) {
|
||||
DBTraceThreadManager manager = trace.getThreadManager();
|
||||
Collection<? extends TraceThread> threads = manager.getThreadsByPath(name);
|
||||
if (threads != null && !threads.isEmpty()) {
|
||||
return threads.iterator().next();
|
||||
}
|
||||
return manager.createThread(name, creationSnap);
|
||||
try {
|
||||
return manager.createThread(name, creationSnap);
|
||||
}
|
||||
catch (DuplicateNameException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a mnemonic memory reference
|
||||
*
|
||||
* @param creationSnap the starting snap
|
||||
* @param from the from address
|
||||
* @param to the to address
|
||||
* @return the reference
|
||||
*/
|
||||
public DBTraceReference addMemoryReference(long creationSnap, Address from, Address to) {
|
||||
return addMemoryReference(creationSnap, from, to, -1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an operand memory reference
|
||||
*
|
||||
* @param creationSnap the starting snap
|
||||
* @param from the from address
|
||||
* @param to the to address
|
||||
* @param operandIndex the operand index, or -1 for mnemonic
|
||||
* @return the reference
|
||||
*/
|
||||
public DBTraceReference addMemoryReference(long creationSnap, Address from, Address to,
|
||||
int operandIndex) {
|
||||
return trace.getReferenceManager()
|
||||
|
@ -298,6 +606,17 @@ public class ToyDBTraceBuilder implements AutoCloseable {
|
|||
RefType.DATA, SourceType.DEFAULT, operandIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a base-offset memory reference
|
||||
*
|
||||
* @param creationSnap the starting snap
|
||||
* @param from the from address
|
||||
* @param to the to address
|
||||
* @param toAddrIsBase true if {@code to} is the base address, implying offset must be added to
|
||||
* get the real to address.
|
||||
* @param offset the offset
|
||||
* @return the reference
|
||||
*/
|
||||
public DBTraceReference addOffsetReference(long creationSnap, Address from, Address to,
|
||||
boolean toAddrIsBase, long offset) {
|
||||
return trace.getReferenceManager()
|
||||
|
@ -305,6 +624,21 @@ public class ToyDBTraceBuilder implements AutoCloseable {
|
|||
offset, RefType.DATA, SourceType.DEFAULT, -1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a shifted memory reference
|
||||
*
|
||||
* <p>
|
||||
* TODO: This uses opIndex -1, which doesn't make sense for a shifted reference. The "to"
|
||||
* address is computed (I assume by the analyzer which places such reference) as the operand
|
||||
* value shifted by the given shift amount. What is the opIndex for a data unit? Probably 0,
|
||||
* since the "mnemonic" would be its type? Still, this suffices for testing the database.
|
||||
*
|
||||
* @param creationSnap the starting snap
|
||||
* @param from the from address
|
||||
* @param to the to address
|
||||
* @param shift the shift
|
||||
* @return the reference
|
||||
*/
|
||||
public DBTraceReference addShiftedReference(long creationSnap, Address from, Address to,
|
||||
int shift) {
|
||||
return trace.getReferenceManager()
|
||||
|
@ -312,18 +646,51 @@ public class ToyDBTraceBuilder implements AutoCloseable {
|
|||
to, shift, RefType.DATA, SourceType.DEFAULT, -1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a register reference
|
||||
*
|
||||
* <p>
|
||||
* See
|
||||
* {@link TraceReferenceManager#addRegisterReference(Range, Address, Register, RefType, SourceType, int)}
|
||||
* regarding potential confusion of the word "register" in this context.
|
||||
*
|
||||
* @param creationSnap the starting snap
|
||||
* @param from the from register
|
||||
* @param to the to address
|
||||
* @return the reference
|
||||
*/
|
||||
public DBTraceReference addRegisterReference(long creationSnap, Address from, String to) {
|
||||
return trace.getReferenceManager()
|
||||
.addRegisterReference(Range.atLeast(creationSnap), from,
|
||||
language.getRegister(to), RefType.DATA, SourceType.DEFAULT, -1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a stack reference
|
||||
*
|
||||
* <p>
|
||||
* See
|
||||
* {@link TraceReferenceManager#addStackReference(Range, Address, int, RefType, SourceType, int)}
|
||||
* regarding potential confusion of the word "stack" in this context.
|
||||
*
|
||||
* @param creationSnap the starting snap
|
||||
* @param from the from address
|
||||
* @param to the to stack offset
|
||||
* @return the reference
|
||||
*/
|
||||
public DBTraceReference addStackReference(long creationSnap, Address from, int to) {
|
||||
return trace.getReferenceManager()
|
||||
.addStackReference(Range.atLeast(creationSnap), from, to,
|
||||
RefType.DATA, SourceType.DEFAULT, -1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the trace to a temporary .gzf file
|
||||
*
|
||||
* @return the new file
|
||||
* @throws IOException if the trace could not be saved
|
||||
* @throws CancelledException never, since the monitor cannot be cancelled
|
||||
*/
|
||||
public File save() throws IOException, CancelledException {
|
||||
Path tmp = Files.createTempFile("test", ".db");
|
||||
Files.delete(tmp); // saveAs must create the file
|
||||
|
@ -331,19 +698,35 @@ public class ToyDBTraceBuilder implements AutoCloseable {
|
|||
return tmp.toFile();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the language with the given ID, as in {@link LangaugeID}
|
||||
*
|
||||
* @param id the ID
|
||||
* @return the langauge
|
||||
* @throws LanguageNotFoundException if the language does not exist
|
||||
*/
|
||||
public Language getLanguage(String id) throws LanguageNotFoundException {
|
||||
return languageService.getLanguage(new LanguageID(id));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the compiler spec with the given language and compiler IDs
|
||||
*
|
||||
* @param langID the language ID as in {@link LanguageID}
|
||||
* @param compID the compiler ID as in {@link CompilerSpecID}
|
||||
* @return the compiler spec
|
||||
* @throws CompilerSpecNotFoundException if the compiler spec does not exist
|
||||
* @throws LanguageNotFoundException if the langauge does not exist
|
||||
*/
|
||||
public CompilerSpec getCompiler(String langID, String compID)
|
||||
throws CompilerSpecNotFoundException, LanguageNotFoundException {
|
||||
return getLanguage(langID).getCompilerSpecByID(new CompilerSpecID(compID));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
if (trace.getConsumerList().contains(this)) {
|
||||
trace.release(this);
|
||||
}
|
||||
}
|
||||
|
||||
public Language getLanguage(String id) throws LanguageNotFoundException {
|
||||
return languageService.getLanguage(new LanguageID(id));
|
||||
}
|
||||
|
||||
public CompilerSpec getCompiler(String langID, String compID)
|
||||
throws CompilerSpecNotFoundException, LanguageNotFoundException {
|
||||
return getLanguage(langID).getCompilerSpecByID(new CompilerSpecID(compID));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -216,7 +216,7 @@ public class DBTraceAddressPropertyManagerTest extends AbstractGhidraHeadlessInt
|
|||
@Test
|
||||
public void testGetPropertyMap() throws Exception {
|
||||
assertNull(propertyManager.getPropertyMap("MyProp"));
|
||||
TracePropertyMap<String> map;
|
||||
TracePropertyMapOperations<String> map;
|
||||
try (UndoableTransaction tid = tb.startTransaction()) {
|
||||
map = propertyManager.createPropertyMap("MyProp", String.class);
|
||||
}
|
||||
|
@ -236,7 +236,7 @@ public class DBTraceAddressPropertyManagerTest extends AbstractGhidraHeadlessInt
|
|||
@Test
|
||||
public void testGetOrCreatePropertyMap() throws Exception {
|
||||
assertNull(propertyManager.getPropertyMap("MyProp"));
|
||||
TracePropertyMap<String> map;
|
||||
TracePropertyMapOperations<String> map;
|
||||
try (UndoableTransaction tid = tb.startTransaction()) {
|
||||
map = propertyManager.getOrCreatePropertyMap("MyProp", String.class);
|
||||
}
|
||||
|
@ -252,53 +252,10 @@ public class DBTraceAddressPropertyManagerTest extends AbstractGhidraHeadlessInt
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetPropertyGetter() throws Exception {
|
||||
assertNull(propertyManager.getPropertyGetter("MyProp", String.class));
|
||||
TracePropertyMap<String> map;
|
||||
try (UndoableTransaction tid = tb.startTransaction()) {
|
||||
map = propertyManager.createPropertyMap("MyProp", String.class);
|
||||
}
|
||||
assertNotNull(map);
|
||||
TracePropertyGetter<String> getter =
|
||||
propertyManager.getPropertyGetter("MyProp", String.class);
|
||||
assertSame(map, getter);
|
||||
assertSame(map, propertyManager.getPropertyGetter("MyProp", Object.class));
|
||||
|
||||
try {
|
||||
propertyManager.getPropertyGetter("MyProp", Integer.class);
|
||||
fail();
|
||||
}
|
||||
catch (TypeMismatchException e) {
|
||||
// pass
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetOrCreatePropertySetter() throws Exception {
|
||||
TracePropertyMap<MySaveable> map;
|
||||
try (UndoableTransaction tid = tb.startTransaction()) {
|
||||
map = propertyManager.createPropertyMap("MyProp", MySaveable.class);
|
||||
}
|
||||
assertNotNull(map);
|
||||
TracePropertySetter<ExtMySaveable> setter =
|
||||
propertyManager.getOrCreatePropertySetter("MyProp", ExtMySaveable.class);
|
||||
assertSame(map, setter);
|
||||
assertSame(map, propertyManager.getOrCreatePropertySetter("MyProp", MySaveable.class));
|
||||
|
||||
try {
|
||||
propertyManager.getOrCreatePropertySetter("MyProp", Saveable.class);
|
||||
fail();
|
||||
}
|
||||
catch (TypeMismatchException e) {
|
||||
// pass
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetAllProperties() throws Exception {
|
||||
assertEquals(0, propertyManager.getAllProperties().size());
|
||||
TracePropertyMap<String> map;
|
||||
TracePropertyMapOperations<String> map;
|
||||
try (UndoableTransaction tid = tb.startTransaction()) {
|
||||
map = propertyManager.createPropertyMap("MyProp", String.class);
|
||||
}
|
||||
|
@ -309,7 +266,7 @@ public class DBTraceAddressPropertyManagerTest extends AbstractGhidraHeadlessInt
|
|||
|
||||
@Test
|
||||
public void testMapGetValueClass() throws Exception {
|
||||
TracePropertyMap<String> map;
|
||||
TracePropertyMapOperations<String> map;
|
||||
try (UndoableTransaction tid = tb.startTransaction()) {
|
||||
map = propertyManager.createPropertyMap("MyProp", String.class);
|
||||
}
|
||||
|
@ -318,7 +275,7 @@ public class DBTraceAddressPropertyManagerTest extends AbstractGhidraHeadlessInt
|
|||
|
||||
protected <T> void doTestMap(Class<T> valueClass, T value) throws Exception {
|
||||
try (UndoableTransaction tid = tb.startTransaction()) {
|
||||
TracePropertyMap<T> map =
|
||||
TracePropertyMapOperations<T> map =
|
||||
propertyManager.createPropertyMap("MyProp", valueClass);
|
||||
assertSame(valueClass, map.getValueClass());
|
||||
|
||||
|
@ -342,7 +299,8 @@ public class DBTraceAddressPropertyManagerTest extends AbstractGhidraHeadlessInt
|
|||
|
||||
try (ToyDBTraceBuilder tb = new ToyDBTraceBuilder(file)) {
|
||||
TraceAddressPropertyManager propertyManager = tb.trace.getAddressPropertyManager();
|
||||
TracePropertyMap<T> map = propertyManager.getPropertyMap("MyProp", valueClass);
|
||||
TracePropertyMapOperations<T> map =
|
||||
propertyManager.getPropertyMap("MyProp", valueClass);
|
||||
assertNotNull(map);
|
||||
|
||||
Entry<TraceAddressSnapRange, T> entry = map.getEntry(4, tb.addr(0x00400001));
|
||||
|
|
|
@ -20,14 +20,26 @@ import java.util.List;
|
|||
|
||||
import ghidra.pcode.emu.AbstractPcodeMachine;
|
||||
import ghidra.pcode.emu.PcodeThread;
|
||||
import ghidra.pcode.exec.PcodeExecutorState;
|
||||
import ghidra.pcode.exec.PcodeUseropLibrary;
|
||||
import ghidra.pcode.exec.*;
|
||||
|
||||
/**
|
||||
* A mocked out machine that creates mocked out threads
|
||||
*
|
||||
* <p>
|
||||
* The purpose is to record the sequence of steps actually executed when testing
|
||||
* {@link TraceSchedule}.
|
||||
*/
|
||||
class TestMachine extends AbstractPcodeMachine<Void> {
|
||||
/** The record of steps taken */
|
||||
protected final List<String> record = new ArrayList<>();
|
||||
|
||||
public TestMachine() {
|
||||
super(TraceScheduleTest.TOY_BE_64_LANG, null);
|
||||
super(TraceScheduleTest.TOY_BE_64_LANG);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected PcodeArithmetic<Void> createArithmetic() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -17,6 +17,7 @@ package ghidra.trace.model.time.schedule;
|
|||
|
||||
import java.util.List;
|
||||
|
||||
import ghidra.app.plugin.processors.sleigh.SleighLanguage;
|
||||
import ghidra.pcode.emu.PcodeThread;
|
||||
import ghidra.pcode.emu.ThreadPcodeExecutorState;
|
||||
import ghidra.pcode.exec.*;
|
||||
|
@ -24,6 +25,12 @@ import ghidra.program.model.address.Address;
|
|||
import ghidra.program.model.lang.RegisterValue;
|
||||
import ghidra.program.model.listing.Instruction;
|
||||
|
||||
/**
|
||||
* A mocked out p-code thread
|
||||
*
|
||||
* <p>
|
||||
* This records the sequence of steps and Sleigh executions when testing {@link TraceSchedule}.
|
||||
*/
|
||||
class TestThread implements PcodeThread<Void> {
|
||||
protected final String name;
|
||||
protected final TestMachine machine;
|
||||
|
@ -43,9 +50,20 @@ class TestThread implements PcodeThread<Void> {
|
|||
return machine;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SleighLanguage getLanguage() {
|
||||
return TraceScheduleTest.TOY_BE_64_LANG;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PcodeArithmetic<Void> getArithmetic() {
|
||||
return machine.getArithmetic();
|
||||
}
|
||||
|
||||
@Override
|
||||
public PcodeExecutor<Void> getExecutor() {
|
||||
return new PcodeExecutor<>(TraceScheduleTest.TOY_BE_64_LANG, machine.getArithmetic(), getState()) {
|
||||
return new PcodeExecutor<>(TraceScheduleTest.TOY_BE_64_LANG, machine.getArithmetic(),
|
||||
getState()) {
|
||||
public PcodeFrame execute(PcodeProgram program, PcodeUseropLibrary<Void> library) {
|
||||
machine.record.add("x:" + name);
|
||||
// TODO: Verify the actual effect
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue