GP-1230: Add Taint Analysis prototype and emulator framework support

This commit is contained in:
Dan 2022-08-22 14:15:14 -04:00
parent 4bfd8d1112
commit 51a1933ab3
205 changed files with 11214 additions and 3714 deletions

View file

@ -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);
}

View file

@ -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));
}
}

View file

@ -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));
}
}

View file

@ -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();
}
}

View file

@ -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);
}
}

View file

@ -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));
}
}

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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;
}
}

View file

@ -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));
}
}

View file

@ -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.");
}
}

View file

@ -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));
}
}

View file

@ -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);
}
}

View file

@ -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");
}
}

View file

@ -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);
}
}

View file

@ -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());
}
}
}
}

View file

@ -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.
}

View file

@ -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);
}

View file

@ -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);
}
}
}

View file

@ -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();

View file

@ -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);
}

View file

@ -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));
}
}

View file

@ -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");

View file

@ -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,

View file

@ -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;

View file

@ -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> {

View file

@ -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());

View file

@ -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));
}
}

View file

@ -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;
}
}

View file

@ -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

View file

@ -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

View file

@ -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);
}

View file

@ -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();
}

View file

@ -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);
}

View file

@ -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));
}
}

View file

@ -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();
}

View file

@ -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;
}
}

View file

@ -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),

View file

@ -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());
}

View file

@ -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));
}
}

View file

@ -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));

View file

@ -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

View file

@ -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