mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-05 02:39:44 +02:00
GP-1486: Patching machine state better integrated into Emulation.
This commit is contained in:
parent
5c0f06ab8d
commit
9386d6fc67
35 changed files with 1819 additions and 1205 deletions
|
@ -22,8 +22,8 @@ import ghidra.trace.database.thread.DBTraceThread;
|
|||
import ghidra.trace.model.Trace;
|
||||
import ghidra.trace.model.Trace.TraceSnapshotChangeType;
|
||||
import ghidra.trace.model.thread.TraceThread;
|
||||
import ghidra.trace.model.time.TraceSchedule;
|
||||
import ghidra.trace.model.time.TraceSnapshot;
|
||||
import ghidra.trace.model.time.schedule.TraceSchedule;
|
||||
import ghidra.trace.util.TraceChangeRecord;
|
||||
import ghidra.util.LockHold;
|
||||
import ghidra.util.Msg;
|
||||
|
|
|
@ -27,6 +27,7 @@ import ghidra.trace.database.DBTraceManager;
|
|||
import ghidra.trace.database.thread.DBTraceThreadManager;
|
||||
import ghidra.trace.model.Trace.TraceSnapshotChangeType;
|
||||
import ghidra.trace.model.time.*;
|
||||
import ghidra.trace.model.time.schedule.TraceSchedule;
|
||||
import ghidra.trace.util.TraceChangeRecord;
|
||||
import ghidra.util.LockHold;
|
||||
import ghidra.util.database.*;
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -17,6 +17,7 @@ package ghidra.trace.model.time;
|
|||
|
||||
import ghidra.trace.model.Trace;
|
||||
import ghidra.trace.model.thread.TraceThread;
|
||||
import ghidra.trace.model.time.schedule.TraceSchedule;
|
||||
|
||||
/**
|
||||
* A "snapshot in time" in a trace
|
||||
|
|
|
@ -17,6 +17,8 @@ package ghidra.trace.model.time;
|
|||
|
||||
import java.util.Collection;
|
||||
|
||||
import ghidra.trace.model.time.schedule.TraceSchedule;
|
||||
|
||||
public interface TraceTimeManager {
|
||||
/**
|
||||
* Create a new snapshot after the latest
|
||||
|
|
|
@ -0,0 +1,78 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.trace.model.time.schedule;
|
||||
|
||||
/**
|
||||
* The result of a rich comparison of two schedules (or parts thereof)
|
||||
*/
|
||||
public enum CompareResult {
|
||||
UNREL_LT(-1, false),
|
||||
REL_LT(-1, true),
|
||||
EQUALS(0, true),
|
||||
REL_GT(1, true),
|
||||
UNREL_GT(1, false);
|
||||
|
||||
/**
|
||||
* Enrich the result of {@link Comparable#compareTo(Object)}, given that the two are related
|
||||
*
|
||||
* @param compareTo the return from {@code compareTo}
|
||||
* @return the rich result
|
||||
*/
|
||||
public static CompareResult related(int compareTo) {
|
||||
if (compareTo < 0) {
|
||||
return REL_LT;
|
||||
}
|
||||
if (compareTo > 0) {
|
||||
return REL_GT;
|
||||
}
|
||||
return EQUALS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enrich the result of {@link Comparable#compareTo(Object)}, given that the two are not
|
||||
* related
|
||||
*
|
||||
* @param compareTo the return from {@code compareTo}
|
||||
* @return the rich result
|
||||
*/
|
||||
public static CompareResult unrelated(int compareTo) {
|
||||
if (compareTo < 0) {
|
||||
return UNREL_LT;
|
||||
}
|
||||
if (compareTo > 0) {
|
||||
return UNREL_GT;
|
||||
}
|
||||
return EQUALS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Maintain sort order, but specify the two are not in fact related
|
||||
*
|
||||
* @param result the result of another (usually recursive) rich comparison
|
||||
* @return the modified result
|
||||
*/
|
||||
public static CompareResult unrelated(CompareResult result) {
|
||||
return unrelated(result.compareTo);
|
||||
}
|
||||
|
||||
public final int compareTo;
|
||||
public final boolean related;
|
||||
|
||||
CompareResult(int compareTo, boolean related) {
|
||||
this.compareTo = compareTo;
|
||||
this.related = related;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,165 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.trace.model.time.schedule;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import javax.help.UnsupportedOperationException;
|
||||
|
||||
import ghidra.pcode.emu.PcodeThread;
|
||||
import ghidra.pcode.exec.PcodeProgram;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
public class PatchStep implements Step {
|
||||
protected final long threadKey;
|
||||
protected final String sleigh;
|
||||
protected final int hashCode;
|
||||
|
||||
public static PatchStep parse(long threadKey, String stepSpec) {
|
||||
// TODO: Can I parse and validate the sleigh here?
|
||||
if (!stepSpec.startsWith("{") || !stepSpec.endsWith("}")) {
|
||||
throw new IllegalArgumentException("Cannot parse step: '" + stepSpec + "'");
|
||||
}
|
||||
return new PatchStep(threadKey, stepSpec.substring(1, stepSpec.length() - 1));
|
||||
}
|
||||
|
||||
public PatchStep(long threadKey, String sleigh) {
|
||||
this.threadKey = threadKey;
|
||||
this.sleigh = Objects.requireNonNull(sleigh);
|
||||
this.hashCode = Objects.hash(threadKey, sleigh); // TODO: May become mutable
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return hashCode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (!(obj instanceof PatchStep)) {
|
||||
return false;
|
||||
}
|
||||
PatchStep that = (PatchStep) obj;
|
||||
if (this.threadKey != that.threadKey) {
|
||||
return false;
|
||||
}
|
||||
if (!this.sleigh.equals(that.sleigh)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
if (threadKey == -1) {
|
||||
return "{" + sleigh + "}";
|
||||
}
|
||||
return String.format("t%d-{%s}", threadKey, sleigh);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getTypeOrder() {
|
||||
// When comparing sequences, those with sleigh steps are ordered after those with ticks
|
||||
return 10;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isNop() {
|
||||
// TODO: If parsing beforehand, base on number of ops
|
||||
return sleigh.length() == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getThreadKey() {
|
||||
return threadKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getTickCount() {
|
||||
return 0; // Philosophically correct
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getPatchCount() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCompatible(Step step) {
|
||||
// TODO: Can we combine ops?
|
||||
return false; // For now, never combine sleigh steps
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addTo(Step step) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Step subtract(Step step) {
|
||||
if (this.equals(step)) {
|
||||
return Step.nop();
|
||||
}
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Step clone() {
|
||||
return new PatchStep(threadKey, sleigh);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long rewind(long count) {
|
||||
return count - 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompareResult compareStep(Step step) {
|
||||
CompareResult result;
|
||||
|
||||
result = compareStepType(step);
|
||||
if (result != CompareResult.EQUALS) {
|
||||
return result;
|
||||
}
|
||||
|
||||
PatchStep that = (PatchStep) step;
|
||||
result = CompareResult.unrelated(Long.compare(this.threadKey, that.threadKey));
|
||||
if (result != CompareResult.EQUALS) {
|
||||
return result;
|
||||
}
|
||||
|
||||
// TODO: Compare ops, if/when we pre-compile
|
||||
result = CompareResult.unrelated(this.sleigh.compareTo(that.sleigh));
|
||||
if (result != CompareResult.EQUALS) {
|
||||
return result;
|
||||
}
|
||||
|
||||
return CompareResult.EQUALS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> void execute(PcodeThread<T> emuThread, Consumer<PcodeThread<T>> stepAction,
|
||||
TaskMonitor monitor) throws CancelledException {
|
||||
PcodeProgram prog = emuThread.getMachine().compileSleigh("schedule", List.of(sleigh + ";"));
|
||||
emuThread.getExecutor().execute(prog, emuThread.getUseropLibrary());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,398 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.trace.model.time.schedule;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import ghidra.pcode.emu.PcodeMachine;
|
||||
import ghidra.pcode.emu.PcodeThread;
|
||||
import ghidra.trace.model.Trace;
|
||||
import ghidra.trace.model.thread.TraceThread;
|
||||
import ghidra.trace.model.thread.TraceThreadManager;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
/**
|
||||
* A sequence of thread steps, each repeated some number of times
|
||||
*/
|
||||
public class Sequence implements Comparable<Sequence> {
|
||||
public static final String SEP = ";";
|
||||
|
||||
/**
|
||||
* Parse (and normalize) a sequence of steps
|
||||
*
|
||||
* <p>
|
||||
* This takes a semicolon-separated list of steps in the form specified by
|
||||
* {@link Step#parse(String)}. Each step may or may not specify a thread, but it's uncommon for
|
||||
* any but the first step to omit the thread. The sequence is normalized as it is parsed, so any
|
||||
* step after the first that omits a thread will be combined with the previous step. When the
|
||||
* first step applies to the "last thread," it typically means the "event thread" of the source
|
||||
* trace snapshot.
|
||||
*
|
||||
* @param seqSpec the string specification of the sequence
|
||||
* @return the parsed sequence
|
||||
* @throws IllegalArgumentException if the specification is of the wrong form
|
||||
*/
|
||||
public static Sequence parse(String seqSpec) {
|
||||
Sequence result = new Sequence();
|
||||
for (String stepSpec : seqSpec.split(SEP)) {
|
||||
Step step = Step.parse(stepSpec);
|
||||
result.advance(step);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct (and normalize) a sequence of the specified steps
|
||||
*
|
||||
* @param steps the desired steps in order
|
||||
* @return the resulting sequence
|
||||
*/
|
||||
public static Sequence of(Step... steps) {
|
||||
return of(Arrays.asList(steps));
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct (and normalize) a sequence of the specified steps
|
||||
*
|
||||
* @param steps the desired steps in order
|
||||
* @return the resulting sequence
|
||||
*/
|
||||
public static Sequence of(List<? extends Step> steps) {
|
||||
Sequence result = new Sequence();
|
||||
for (Step step : steps) {
|
||||
result.advance(step);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct (and normalize) a sequence formed by the steps in a followed by the steps in b
|
||||
*
|
||||
* @param a the first sequence
|
||||
* @param b the second (appended) sequence
|
||||
* @return the resulting sequence
|
||||
*/
|
||||
public static Sequence catenate(Sequence a, Sequence b) {
|
||||
Sequence result = new Sequence();
|
||||
result.advance(a);
|
||||
result.advance(b);
|
||||
return result;
|
||||
}
|
||||
|
||||
private final List<Step> steps;
|
||||
|
||||
protected Sequence() {
|
||||
this(new ArrayList<>());
|
||||
}
|
||||
|
||||
protected Sequence(List<Step> steps) {
|
||||
this.steps = steps;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return StringUtils.join(steps, SEP);
|
||||
}
|
||||
|
||||
/**
|
||||
* Append the given step to this sequence
|
||||
*
|
||||
* @param step the step to append
|
||||
*/
|
||||
public void advance(Step step) {
|
||||
if (step.isNop()) {
|
||||
return;
|
||||
}
|
||||
if (steps.isEmpty()) {
|
||||
steps.add(step);
|
||||
return;
|
||||
}
|
||||
Step last = steps.get(steps.size() - 1);
|
||||
if (!last.isCompatible(step)) {
|
||||
steps.add(step.clone());
|
||||
return;
|
||||
}
|
||||
last.addTo(step);
|
||||
}
|
||||
|
||||
/**
|
||||
* Append the given sequence to this one
|
||||
*
|
||||
* @param seq the sequence to append
|
||||
*/
|
||||
public void advance(Sequence seq) {
|
||||
int size = seq.steps.size();
|
||||
// Clone early in case seq == this
|
||||
// I should store copies of subsequent steps, anyway
|
||||
List<Step> clone = seq.steps.stream()
|
||||
.map(Step::clone)
|
||||
.collect(Collectors.toList());
|
||||
if (size < 1) {
|
||||
return;
|
||||
}
|
||||
// intervening -1 could resolve and be combined with following
|
||||
advance(clone.get(0));
|
||||
if (size < 2) {
|
||||
return;
|
||||
}
|
||||
advance(clone.get(1));
|
||||
steps.addAll(clone.subList(2, size));
|
||||
}
|
||||
|
||||
/**
|
||||
* Rewind this sequence the given step count
|
||||
*
|
||||
* <p>
|
||||
* This modifies the sequence in place, removing the given count from the end of the sequence.
|
||||
* Any step whose count is reduced to 0 as a result of rewinding is removed entirely from the
|
||||
* sequence. Note that each sleigh step (modification) counts as one step when rewinding.
|
||||
*
|
||||
* @param count the step count to rewind
|
||||
* @return if count exceeds the steps of this sequence, the (positive) difference remaining
|
||||
*/
|
||||
public long rewind(long count) {
|
||||
if (count < 0) {
|
||||
throw new IllegalArgumentException("Cannot rewind a negative number");
|
||||
}
|
||||
while (!steps.isEmpty()) {
|
||||
int lastIndex = steps.size() - 1;
|
||||
count = steps.get(lastIndex).rewind(count);
|
||||
if (count >= 0) {
|
||||
steps.remove(lastIndex);
|
||||
}
|
||||
if (count <= 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return Long.max(0, count);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Sequence clone() {
|
||||
return new Sequence(
|
||||
steps.stream().map(Step::clone).collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain a clone of the steps
|
||||
*
|
||||
* <p>
|
||||
* Modifications to the returned steps have no effect on this sequence.
|
||||
*
|
||||
* @return the cloned steps
|
||||
*/
|
||||
public List<Step> getSteps() {
|
||||
return steps.stream().map(Step::clone).collect(Collectors.toUnmodifiableList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this sequence represents any actions
|
||||
*
|
||||
* @return true if the sequence is empty, false if not
|
||||
*/
|
||||
public boolean isNop() {
|
||||
return steps.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return steps.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (!(obj instanceof Sequence)) {
|
||||
return false;
|
||||
}
|
||||
Sequence that = (Sequence) obj;
|
||||
return Objects.equals(this.steps, that.steps);
|
||||
}
|
||||
|
||||
/**
|
||||
* Richly compare to sequences
|
||||
*
|
||||
* <p>
|
||||
* The result indicates not only which is "less" or "greater" than the other, but also indicates
|
||||
* whether the two are "related." Two sequences are considered related if one is the prefix to
|
||||
* the other. More precisely, they are related if it's possible to transform one into the other
|
||||
* solely by truncation (rewind) or solely by concatenation (advance). When related, the prefix
|
||||
* is considered "less than" the other. Equal sequences are trivially related.
|
||||
*
|
||||
* <p>
|
||||
* Examples:
|
||||
* <ul>
|
||||
* <li>{@code ""} is related to and less than {@code "10"}</li>
|
||||
* <li>{@code "10"} is related and equal to {@code "10"}</li>
|
||||
* <li>{@code "10"} is related to and less than {@code "11"}</li>
|
||||
* <li>{@code "t1-5"} is related to and less than {@code "t1-5;t2-4"}</li>
|
||||
* <li>{@code "t1-5"} is un-related to and less than {@code "t1-4;t2-4"}</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>
|
||||
* The {@link #compareTo(Sequence)} implementation defers to this method. Thus, in a sorted set
|
||||
* of step sequences, the floor of a given sequence is will be the longest prefix in that set to
|
||||
* the given sequence, assuming such a prefix is present.
|
||||
*
|
||||
* @param that the object of comparison (this being the subject)
|
||||
* @return a result describing the relationship from subject to object
|
||||
*/
|
||||
public CompareResult compareSeq(Sequence that) {
|
||||
int min = Math.min(this.steps.size(), that.steps.size());
|
||||
CompareResult result;
|
||||
for (int i = 0; i < min; i++) {
|
||||
Step s1 = this.steps.get(i);
|
||||
Step s2 = that.steps.get(i);
|
||||
result = s1.compareStep(s2);
|
||||
switch (result) {
|
||||
case UNREL_LT:
|
||||
case UNREL_GT:
|
||||
return result;
|
||||
case REL_LT:
|
||||
if (i + 1 == this.steps.size()) {
|
||||
return CompareResult.REL_LT;
|
||||
}
|
||||
else {
|
||||
return CompareResult.UNREL_LT;
|
||||
}
|
||||
case REL_GT:
|
||||
if (i + 1 == that.steps.size()) {
|
||||
return CompareResult.REL_GT;
|
||||
}
|
||||
else {
|
||||
return CompareResult.UNREL_GT;
|
||||
}
|
||||
default: // EQUALS, next step
|
||||
}
|
||||
}
|
||||
if (that.steps.size() > min) {
|
||||
return CompareResult.REL_LT;
|
||||
}
|
||||
if (this.steps.size() > min) {
|
||||
return CompareResult.REL_GT;
|
||||
}
|
||||
return CompareResult.EQUALS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(Sequence that) {
|
||||
return compareSeq(that).compareTo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the sequence which concatenated to the given prefix would result in this sequence
|
||||
*
|
||||
* <p>
|
||||
* The returned step sequence should not be manipulated, since it may just be this sequence.
|
||||
*
|
||||
* @see #compareSeq(Sequence)
|
||||
* @param prefix the prefix
|
||||
* @return the relative sequence from prefix to this
|
||||
* @throws IllegalArgumentException if prefix is not a prefix of this sequence
|
||||
*/
|
||||
public Sequence relativize(Sequence prefix) {
|
||||
if (prefix.isNop()) {
|
||||
return this;
|
||||
}
|
||||
CompareResult comp = compareSeq(prefix);
|
||||
Sequence result = new Sequence();
|
||||
if (comp == CompareResult.EQUALS) {
|
||||
return result;
|
||||
}
|
||||
if (comp != CompareResult.REL_GT) {
|
||||
throw new IllegalArgumentException(String.format(
|
||||
"The given prefix (%s) is not actually a prefix of this (%s).", prefix, this));
|
||||
}
|
||||
|
||||
int lastStepIndex = prefix.steps.size() - 1;
|
||||
Step ancestorLast = prefix.steps.get(lastStepIndex);
|
||||
Step continuation = this.steps.get(lastStepIndex);
|
||||
result.advance(continuation.subtract(ancestorLast));
|
||||
result.steps.addAll(steps.subList(prefix.steps.size(), steps.size()));
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute to total number of ticks specified
|
||||
*
|
||||
* @return the total
|
||||
*/
|
||||
public long totalTickCount() {
|
||||
long count = 0;
|
||||
for (Step step : steps) {
|
||||
count += step.getTickCount();
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute to total number of patches specified
|
||||
*
|
||||
* @return the total
|
||||
*/
|
||||
public long totalPatchCount() {
|
||||
long count = 0;
|
||||
for (Step step : steps) {
|
||||
count += step.getPatchCount();
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute this sequence upon the given machine
|
||||
*
|
||||
* <p>
|
||||
* Threads are retrieved from the database by key, then created in the machine (if not already
|
||||
* present) named by {@link TraceThread#getPath()}. The caller should ensure the machine's state
|
||||
* is bound to the given trace.
|
||||
*
|
||||
* @param trace the trace to which the machine is bound
|
||||
* @param eventThread the thread for the first step, if it applies to the "last thread"
|
||||
* @param machine the machine to step
|
||||
* @param action the action to step each thread
|
||||
* @param monitor a monitor for cancellation and progress reports
|
||||
* @return the last trace thread stepped during execution
|
||||
* @throws CancelledException if execution is cancelled
|
||||
*/
|
||||
public <T> TraceThread execute(Trace trace, TraceThread eventThread, PcodeMachine<T> machine,
|
||||
Consumer<PcodeThread<T>> action, TaskMonitor monitor) throws CancelledException {
|
||||
TraceThreadManager tm = trace.getThreadManager();
|
||||
TraceThread thread = eventThread;
|
||||
for (Step step : steps) {
|
||||
thread = step.execute(tm, thread, machine, action, monitor);
|
||||
}
|
||||
return thread;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the key of the last thread stepped
|
||||
*
|
||||
* @return the key, or -1 if no step in the sequence specifies a thread
|
||||
*/
|
||||
public long getLastThreadKey() {
|
||||
if (steps.isEmpty()) {
|
||||
return -1;
|
||||
}
|
||||
return steps.get(steps.size() - 1).getThreadKey();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,170 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.trace.model.time.schedule;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import ghidra.pcode.emu.PcodeMachine;
|
||||
import ghidra.pcode.emu.PcodeThread;
|
||||
import ghidra.trace.model.thread.TraceThread;
|
||||
import ghidra.trace.model.thread.TraceThreadManager;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
public interface Step extends Comparable<Step> {
|
||||
/**
|
||||
* Parse a step, possibly including a thread prefix, e.g., {@code "t1-..."}
|
||||
*
|
||||
* <p>
|
||||
* If the thread prefix is given, the step applies to the given thread. Otherwise, the step
|
||||
* applies to the last thread or the event thread.
|
||||
*
|
||||
* @param stepSpec the string specification
|
||||
* @return the parsed step
|
||||
* @throws IllegalArgumentException if the specification is of the wrong form
|
||||
*/
|
||||
static Step parse(String stepSpec) {
|
||||
if ("".equals(stepSpec)) {
|
||||
return nop();
|
||||
}
|
||||
String[] parts = stepSpec.split("-");
|
||||
if (parts.length == 1) {
|
||||
return parse(-1, parts[0].trim());
|
||||
}
|
||||
if (parts.length == 2) {
|
||||
String tPart = parts[0].trim();
|
||||
if (tPart.startsWith("t")) {
|
||||
return parse(Long.parseLong(tPart.substring(1)), parts[1].trim());
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("Cannot parse step: '" + stepSpec + "'");
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a step for the given thread key
|
||||
*
|
||||
* <p>
|
||||
* The form of the spec must either be numeric, indicating some number of ticks, or
|
||||
* brace-enclosed Sleigh code, e.g., {@code "{r0=0x1234;}"}. The latter allows patching machine
|
||||
* state during execution.
|
||||
*
|
||||
* @param threadKey the thread to step, or -1 for the last thread or event thread
|
||||
* @param stepSpec the string specification
|
||||
* @return the parsed step
|
||||
* @throws IllegalArgumentException if the specification is of the wrong form
|
||||
*/
|
||||
static Step parse(long threadKey, String stepSpec) {
|
||||
if (stepSpec.startsWith("{")) {
|
||||
return PatchStep.parse(threadKey, stepSpec);
|
||||
}
|
||||
return TickStep.parse(threadKey, stepSpec);
|
||||
}
|
||||
|
||||
static TickStep nop() {
|
||||
return new TickStep(-1, 0);
|
||||
}
|
||||
|
||||
int getTypeOrder();
|
||||
|
||||
boolean isNop();
|
||||
|
||||
long getThreadKey();
|
||||
|
||||
default boolean isEventThread() {
|
||||
return getThreadKey() == -1;
|
||||
}
|
||||
|
||||
default TraceThread getThread(TraceThreadManager tm, TraceThread eventThread) {
|
||||
TraceThread thread = isEventThread() ? eventThread : tm.getThread(getThreadKey());
|
||||
if (thread == null) {
|
||||
if (isEventThread()) {
|
||||
throw new IllegalArgumentException(
|
||||
"Thread key -1 can only be used if last/event thread is given");
|
||||
}
|
||||
throw new IllegalArgumentException(
|
||||
"Thread with key " + getThreadKey() + " does not exist in given trace");
|
||||
}
|
||||
return thread;
|
||||
}
|
||||
|
||||
long getTickCount();
|
||||
|
||||
long getPatchCount();
|
||||
|
||||
/**
|
||||
* Check if the given step can be combined with this one
|
||||
*
|
||||
* <p>
|
||||
* Two steps applied to the same thread can just be summed. If the given step applies to the
|
||||
* "last thread" or to the same thread as this step, then it can be combined.
|
||||
*
|
||||
* @param step the second step
|
||||
* @return true if combinable, false otherwise.
|
||||
*/
|
||||
boolean isCompatible(Step step);
|
||||
|
||||
void addTo(Step step);
|
||||
|
||||
Step subtract(Step step);
|
||||
|
||||
Step clone();
|
||||
|
||||
/**
|
||||
* Subtract from the count of this step
|
||||
*
|
||||
* <p>
|
||||
* If this step has a count exceeding that given, then this method simply subtracts the given
|
||||
* number from the {@code tickCount} and returns the (negative) difference. If this step has
|
||||
* exactly the count given, this method sets the count to 0 and returns 0, indicating this step
|
||||
* should be removed from the sequence. If the given count exceeds that of this step, this
|
||||
* method sets the count to 0 and returns the (positive) difference, indicating this step should
|
||||
* be removed from the sequence, and the remaining steps rewound from the preceding step.
|
||||
*
|
||||
* @param steps the count to rewind
|
||||
* @return the number of steps remaining
|
||||
*/
|
||||
long rewind(long count);
|
||||
|
||||
/**
|
||||
* Richly compare this step to another
|
||||
*
|
||||
* @param step the object of comparison (this being the subject)
|
||||
* @return a result describing the relationship from subject to object
|
||||
*/
|
||||
CompareResult compareStep(Step that);
|
||||
|
||||
default CompareResult compareStepType(Step that) {
|
||||
return CompareResult
|
||||
.unrelated(Integer.compare(this.getTypeOrder(), that.getTypeOrder()));
|
||||
}
|
||||
|
||||
@Override
|
||||
default int compareTo(Step that) {
|
||||
return compareStep(that).compareTo;
|
||||
}
|
||||
|
||||
default <T> TraceThread execute(TraceThreadManager tm, TraceThread eventThread,
|
||||
PcodeMachine<T> machine, Consumer<PcodeThread<T>> stepAction, TaskMonitor monitor)
|
||||
throws CancelledException {
|
||||
TraceThread thread = getThread(tm, eventThread);
|
||||
PcodeThread<T> emuThread = machine.getThread(thread.getPath(), true);
|
||||
execute(emuThread, stepAction, monitor);
|
||||
return thread;
|
||||
}
|
||||
|
||||
<T> void execute(PcodeThread<T> emuThread, Consumer<PcodeThread<T>> stepAction,
|
||||
TaskMonitor monitor) throws CancelledException;
|
||||
}
|
|
@ -0,0 +1,197 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.trace.model.time.schedule;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import ghidra.pcode.emu.PcodeThread;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
/**
|
||||
* A step of a given thread in a schedule: repeating some number of ticks
|
||||
*/
|
||||
public class TickStep implements Step {
|
||||
|
||||
public static TickStep parse(long threadKey, String stepSpec) {
|
||||
try {
|
||||
return new TickStep(threadKey, Long.parseLong(stepSpec));
|
||||
}
|
||||
catch (NumberFormatException e) {
|
||||
throw new IllegalArgumentException("Cannot parse step: '" + stepSpec + "'");
|
||||
}
|
||||
}
|
||||
|
||||
protected final long threadKey;
|
||||
protected long tickCount;
|
||||
|
||||
/**
|
||||
* Construct a step for the given thread with the given tick count
|
||||
*
|
||||
* @param threadKey the key of the thread in the trace, -1 for the "last thread"
|
||||
* @param tickCount the number of times to step the thread
|
||||
*/
|
||||
public TickStep(long threadKey, long tickCount) {
|
||||
if (tickCount < 0) {
|
||||
throw new IllegalArgumentException("Cannot step a negative number");
|
||||
}
|
||||
this.threadKey = threadKey;
|
||||
this.tickCount = tickCount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getTypeOrder() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
if (threadKey == -1) {
|
||||
return Long.toString(tickCount);
|
||||
}
|
||||
return String.format("t%d-%d", threadKey, tickCount);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isNop() {
|
||||
return tickCount == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getThreadKey() {
|
||||
return threadKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getTickCount() {
|
||||
return tickCount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getPatchCount() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TickStep clone() {
|
||||
return new TickStep(threadKey, tickCount);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add to the count of this step
|
||||
*
|
||||
* @param steps the count to add
|
||||
*/
|
||||
public void advance(long steps) {
|
||||
if (steps < 0) {
|
||||
throw new IllegalArgumentException("Cannot advance a negative number");
|
||||
}
|
||||
long newCount = tickCount + steps;
|
||||
if (newCount < 0) {
|
||||
throw new IllegalArgumentException("Total step count exceeds LONG_MAX");
|
||||
}
|
||||
this.tickCount = newCount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long rewind(long steps) {
|
||||
if (steps < 0) {
|
||||
throw new IllegalArgumentException("Cannot rewind a negative number");
|
||||
}
|
||||
long diff = this.tickCount - steps;
|
||||
this.tickCount = Long.max(0, diff);
|
||||
return -diff;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCompatible(Step step) {
|
||||
if (!(step instanceof TickStep)) {
|
||||
return false;
|
||||
}
|
||||
TickStep ts = (TickStep) step;
|
||||
return this.threadKey == ts.threadKey || ts.threadKey == -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addTo(Step step) {
|
||||
assert isCompatible(step);
|
||||
TickStep ts = (TickStep) step;
|
||||
advance(ts.tickCount);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Step subtract(Step step) {
|
||||
assert isCompatible(step);
|
||||
TickStep that = (TickStep) step;
|
||||
return new TickStep(this.threadKey, this.tickCount - that.tickCount);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Long.hashCode(threadKey) * 31 + Long.hashCode(tickCount);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (!(obj instanceof TickStep)) {
|
||||
return false;
|
||||
}
|
||||
TickStep that = (TickStep) obj;
|
||||
if (this.threadKey != that.threadKey) {
|
||||
return false;
|
||||
}
|
||||
if (this.tickCount != that.tickCount) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompareResult compareStep(Step step) {
|
||||
CompareResult result;
|
||||
|
||||
result = compareStepType(step);
|
||||
if (result != CompareResult.EQUALS) {
|
||||
return result;
|
||||
}
|
||||
|
||||
TickStep that = (TickStep) step;
|
||||
result = CompareResult.unrelated(Long.compare(this.threadKey, that.threadKey));
|
||||
if (result != CompareResult.EQUALS) {
|
||||
return result;
|
||||
}
|
||||
|
||||
result = CompareResult.related(Long.compare(this.tickCount, that.tickCount));
|
||||
if (result != CompareResult.EQUALS) {
|
||||
return result;
|
||||
}
|
||||
|
||||
return CompareResult.EQUALS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> void execute(PcodeThread<T> emuThread, Consumer<PcodeThread<T>> stepAction,
|
||||
TaskMonitor monitor) throws CancelledException {
|
||||
for (int i = 0; i < tickCount; i++) {
|
||||
monitor.incrementProgress(1);
|
||||
monitor.checkCanceled();
|
||||
stepAction.accept(emuThread);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,490 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.trace.model.time.schedule;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import ghidra.pcode.emu.PcodeMachine;
|
||||
import ghidra.pcode.emu.PcodeThread;
|
||||
import ghidra.trace.model.Trace;
|
||||
import ghidra.trace.model.thread.TraceThread;
|
||||
import ghidra.trace.model.time.TraceSnapshot;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
public class TraceSchedule implements Comparable<TraceSchedule> {
|
||||
public static final TraceSchedule ZERO = TraceSchedule.snap(0);
|
||||
|
||||
public static final TraceSchedule snap(long snap) {
|
||||
return new TraceSchedule(snap, new Sequence(), new Sequence());
|
||||
}
|
||||
|
||||
private static final String PARSE_ERR_MSG =
|
||||
"Time specification must have form 'snap[:steps[.pSteps]]'";
|
||||
|
||||
/**
|
||||
* Parse schedule in the form "{@code snap[:steps[.pSteps]]}"
|
||||
*
|
||||
* <p>
|
||||
* A schedule consists of a snap, a optional {@link Sequence} of thread instruction-level steps,
|
||||
* and optional p-code-level steps (pSteps). The form of {@code steps} and {@code pSteps} is
|
||||
* specified by {@link Sequence#parse(String)}. Each sequence consists of stepping selected
|
||||
* threads forward, and/or patching machine state.
|
||||
*
|
||||
* @param spec the string specification
|
||||
* @return the parsed schedule
|
||||
*/
|
||||
public static TraceSchedule parse(String spec) {
|
||||
String[] parts = spec.split(":", 2);
|
||||
if (parts.length > 2) {
|
||||
throw new AssertionError();
|
||||
}
|
||||
final long snap;
|
||||
final Sequence ticks;
|
||||
final Sequence pTicks;
|
||||
try {
|
||||
snap = Long.decode(parts[0]);
|
||||
}
|
||||
catch (NumberFormatException e) {
|
||||
throw new IllegalArgumentException(PARSE_ERR_MSG, e);
|
||||
}
|
||||
if (parts.length > 1) {
|
||||
String[] subs = parts[1].split("\\.");
|
||||
try {
|
||||
ticks = Sequence.parse(subs[0]);
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
throw new IllegalArgumentException(PARSE_ERR_MSG, e);
|
||||
}
|
||||
if (subs.length == 1) {
|
||||
pTicks = new Sequence();
|
||||
}
|
||||
else if (subs.length == 2) {
|
||||
try {
|
||||
pTicks = Sequence.parse(subs[1]);
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
throw new IllegalArgumentException(PARSE_ERR_MSG, e);
|
||||
}
|
||||
}
|
||||
else {
|
||||
throw new IllegalArgumentException(PARSE_ERR_MSG);
|
||||
}
|
||||
}
|
||||
else {
|
||||
ticks = new Sequence();
|
||||
pTicks = new Sequence();
|
||||
}
|
||||
return new TraceSchedule(snap, ticks, pTicks);
|
||||
}
|
||||
|
||||
private final long snap;
|
||||
private final Sequence steps;
|
||||
private final Sequence pSteps;
|
||||
|
||||
/**
|
||||
* Construct the given schedule
|
||||
*
|
||||
* @param snap the initial trace snapshot
|
||||
* @param steps the step sequence
|
||||
* @param pSteps the of p-code step sequence
|
||||
*/
|
||||
public TraceSchedule(long snap, Sequence steps, Sequence pSteps) {
|
||||
this.snap = snap;
|
||||
this.steps = steps;
|
||||
this.pSteps = pSteps;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
if (pSteps.isNop()) {
|
||||
if (steps.isNop()) {
|
||||
return Long.toString(snap);
|
||||
}
|
||||
return String.format("%d:%s", snap, steps);
|
||||
}
|
||||
return String.format("%d:%s.%s", snap, steps, pSteps);
|
||||
}
|
||||
|
||||
/**
|
||||
* Richly compare two schedules
|
||||
*
|
||||
* <p>
|
||||
* Schedules starting at different snapshots are never related, because there is no
|
||||
* emulator/simulator stepping action which advances to the next snapshot. Though p-code steps
|
||||
* may comprise a partial step, we do not consider a partial step to be a prefix of a full step,
|
||||
* since we cannot know <em>a priori</em> how many p-code steps comprise a full instruction
|
||||
* step. Consider, e.g., the user may specify 100 p-code steps, which could effect 20
|
||||
* instruction steps.
|
||||
*
|
||||
* @param that the object of comparison (this being the subject)
|
||||
* @return a result describing the relationship from subject to object
|
||||
*/
|
||||
public CompareResult compareSchedule(TraceSchedule that) {
|
||||
CompareResult result;
|
||||
|
||||
result = CompareResult.unrelated(Long.compare(this.snap, that.snap));
|
||||
if (result != CompareResult.EQUALS) {
|
||||
return result;
|
||||
}
|
||||
|
||||
result = this.steps.compareSeq(that.steps);
|
||||
switch (result) {
|
||||
case UNREL_LT:
|
||||
case UNREL_GT:
|
||||
return result;
|
||||
case REL_LT:
|
||||
if (this.pSteps.isNop()) {
|
||||
return CompareResult.REL_LT;
|
||||
}
|
||||
else {
|
||||
return CompareResult.UNREL_LT;
|
||||
}
|
||||
case REL_GT:
|
||||
if (that.pSteps.isNop()) {
|
||||
return CompareResult.REL_GT;
|
||||
}
|
||||
else {
|
||||
return CompareResult.UNREL_GT;
|
||||
}
|
||||
default: // EQUALS, compare pSteps
|
||||
}
|
||||
|
||||
result = this.pSteps.compareSeq(that.pSteps);
|
||||
if (result != CompareResult.EQUALS) {
|
||||
return result;
|
||||
}
|
||||
|
||||
return CompareResult.EQUALS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (!(obj instanceof TraceSchedule)) {
|
||||
return false;
|
||||
}
|
||||
TraceSchedule that = (TraceSchedule) obj;
|
||||
if (this.snap != that.snap) {
|
||||
return false;
|
||||
}
|
||||
if (!Objects.equals(this.steps, that.steps)) {
|
||||
return false;
|
||||
}
|
||||
if (!Objects.equals(this.pSteps, that.pSteps)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(snap, steps, pSteps);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(TraceSchedule o) {
|
||||
return compareSchedule(o).compareTo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this schedule requires any stepping
|
||||
*
|
||||
* @return true if no stepping is required, i.e., the resulting state can be realized simply by
|
||||
* loading a snapshot
|
||||
*/
|
||||
public boolean isSnapOnly() {
|
||||
return steps.isNop() && pSteps.isNop();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the source snapshot
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public long getSnap() {
|
||||
return snap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the last thread key stepped by this schedule
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public long getLastThreadKey() {
|
||||
long last = pSteps.getLastThreadKey();
|
||||
if (last != -1) {
|
||||
return last;
|
||||
}
|
||||
return steps.getLastThreadKey();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the event thread for this schedule in the context of the given trace
|
||||
*
|
||||
* <p>
|
||||
* This is the thread stepped when no thread is specified for the first step of the sequence.
|
||||
*
|
||||
* @param trace the trace containing the source snapshot and threads
|
||||
* @return the thread to use as "last thread" for the sequence
|
||||
*/
|
||||
public TraceThread getEventThread(Trace trace) {
|
||||
TraceSnapshot snapshot = trace.getTimeManager().getSnapshot(snap, false);
|
||||
return snapshot == null ? null : snapshot.getEventThread();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the last thread stepped by this schedule in the context of the given trace
|
||||
*
|
||||
* @param trace the trace containing the source snapshot and threads
|
||||
* @return the thread last stepped, or the "event thread" when no steps are taken
|
||||
*/
|
||||
public TraceThread getLastThread(Trace trace) {
|
||||
long lastKey = getLastThreadKey();
|
||||
if (lastKey == -1) {
|
||||
return getEventThread(trace);
|
||||
}
|
||||
return trace.getThreadManager().getThread(lastKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the total number of ticks taken, including the p-code ticks
|
||||
*
|
||||
* <p>
|
||||
* This is suitable for use with {@link TaskMonitor#initialize(long)}, where that monitor will
|
||||
* be passed to {@link #execute(Trace, PcodeMachine, TaskMonitor)} or similar. Note that patch
|
||||
* steps do not count as ticks.
|
||||
*
|
||||
* @return the number of ticks
|
||||
*/
|
||||
public long totalTickCount() {
|
||||
return steps.totalTickCount() + pSteps.totalTickCount();
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the total number of patches applied
|
||||
*
|
||||
* @return the number of patches
|
||||
*/
|
||||
public long totalPatchCount() {
|
||||
return steps.totalPatchCount() + pSteps.totalPatchCount();
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the number of ticks taken, excluding p-code ticks
|
||||
*
|
||||
* @return the number of ticks
|
||||
*/
|
||||
public long tickCount() {
|
||||
return steps.totalTickCount();
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the number of patches, excluding p-code patches
|
||||
*
|
||||
* @return the number of patches
|
||||
*/
|
||||
public long patchCount() {
|
||||
return steps.totalPatchCount();
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the number of p-code ticks taken
|
||||
*
|
||||
* @return the number of ticks
|
||||
*/
|
||||
public long pTickCount() {
|
||||
return pSteps.totalTickCount();
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the number of p-code patches applied
|
||||
*
|
||||
* @return the number of patches
|
||||
*/
|
||||
public long pPatchCount() {
|
||||
return pSteps.totalPatchCount();
|
||||
}
|
||||
|
||||
/**
|
||||
* Realize the machine state for this schedule using the given trace and machine
|
||||
*
|
||||
* <p>
|
||||
* This method executes this schedule and trailing p-code steps on the given machine, assuming
|
||||
* that machine is already "positioned" at the initial snapshot. Assuming successful execution,
|
||||
* that machine is now said to be "positioned" at this schedule, and its state is the result of
|
||||
* said execution.
|
||||
*
|
||||
* @param trace the trace containing the source snapshot and threads
|
||||
* @param machine a machine bound to the trace whose current state reflects the initial snapshot
|
||||
* @param monitor a monitor for cancellation and progress reporting
|
||||
* @throws CancelledException if the execution is cancelled
|
||||
*/
|
||||
public void execute(Trace trace, PcodeMachine<?> machine, TaskMonitor monitor)
|
||||
throws CancelledException {
|
||||
TraceThread lastThread = getEventThread(trace);
|
||||
lastThread =
|
||||
steps.execute(trace, lastThread, machine, PcodeThread::stepInstruction, monitor);
|
||||
lastThread =
|
||||
pSteps.execute(trace, lastThread, machine, PcodeThread::stepPcodeOp, monitor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Realize the machine state for this schedule using the given trace and pre-positioned machine
|
||||
*
|
||||
* <p>
|
||||
* This method executes the remaining steps of this schedule and trailing p-code steps on the
|
||||
* given machine, assuming that machine is already "positioned" at another given schedule.
|
||||
* Assuming successful execution, that machine is now said to be "positioned" at this schedule,
|
||||
* and its state is the result of said execution.
|
||||
*
|
||||
* @param trace the trace containing the source snapshot and threads
|
||||
* @param position the current schedule of the given machine
|
||||
* @param machine a machine bound to the trace whose current state reflects the given position
|
||||
* @param monitor a monitor for cancellation and progress reporting
|
||||
* @throws CancelledException if the execution is cancelled
|
||||
* @throws IllegalArgumentException if the given position is not a prefix of this schedule
|
||||
*/
|
||||
public void finish(Trace trace, TraceSchedule position, PcodeMachine<?> machine,
|
||||
TaskMonitor monitor) throws CancelledException {
|
||||
TraceThread lastThread = position.getLastThread(trace);
|
||||
Sequence remains = steps.relativize(position.steps);
|
||||
if (remains.isNop()) {
|
||||
Sequence pRemains = this.pSteps.relativize(position.pSteps);
|
||||
lastThread =
|
||||
pRemains.execute(trace, lastThread, machine, PcodeThread::stepPcodeOp, monitor);
|
||||
}
|
||||
else {
|
||||
lastThread =
|
||||
remains.execute(trace, lastThread, machine, PcodeThread::stepInstruction, monitor);
|
||||
lastThread =
|
||||
pSteps.execute(trace, lastThread, machine, PcodeThread::stepPcodeOp, monitor);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the equivalent of executing the schedule (ignoring p-code steps) followed by stepping
|
||||
* the given thread count more instructions
|
||||
*
|
||||
* <p>
|
||||
* This schedule is left unmodified. If it had any p-code steps, those steps are dropped in the
|
||||
* resulting schedule.
|
||||
*
|
||||
* @param thread the thread to step
|
||||
* @param tickCount the number of ticks to take the thread forward
|
||||
* @return the resulting schedule
|
||||
*/
|
||||
public TraceSchedule steppedForward(TraceThread thread, long tickCount) {
|
||||
Sequence steps = this.steps.clone();
|
||||
steps.advance(new TickStep(thread.getKey(), tickCount));
|
||||
return new TraceSchedule(snap, steps, new Sequence());
|
||||
}
|
||||
|
||||
protected TraceSchedule doSteppedBackward(Trace trace, long tickCount, Set<Long> visited) {
|
||||
if (!visited.add(snap)) {
|
||||
return null;
|
||||
}
|
||||
long excess = tickCount - totalTickCount() - totalPatchCount();
|
||||
if (excess > 0) {
|
||||
if (trace == null) {
|
||||
return null;
|
||||
}
|
||||
TraceSnapshot source = trace.getTimeManager().getSnapshot(snap, false);
|
||||
if (source == null) {
|
||||
return null;
|
||||
}
|
||||
TraceSchedule rec = source.getSchedule();
|
||||
if (rec == null) {
|
||||
return null;
|
||||
}
|
||||
return rec.doSteppedBackward(trace, excess, visited);
|
||||
}
|
||||
Sequence steps = this.steps.clone();
|
||||
steps.rewind(tickCount);
|
||||
return new TraceSchedule(snap, steps, new Sequence());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the equivalent of executing count instructions (and all p-code operations) less than
|
||||
* this schedule
|
||||
*
|
||||
* <p>
|
||||
* This schedule is left unmodified. If it had any p-code steps, those steps and subsequent
|
||||
* patches are dropped in the resulting schedule. If count exceeds this schedule's steps, it
|
||||
* will try (recursively) to step the source snapshot's schedule backward, if known. Both ticks
|
||||
* and patches counts as steps.
|
||||
*
|
||||
* @param trace the trace of this schedule, for context
|
||||
* @param stepCount the number of steps to take backward
|
||||
* @return the resulting schedule or null if it cannot be computed
|
||||
*/
|
||||
public TraceSchedule steppedBackward(Trace trace, long stepCount) {
|
||||
return doSteppedBackward(trace, stepCount, new HashSet<>());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the equivalent of executing the schedule followed by stepping the given thread
|
||||
* {@code pTickCount} more p-code operations
|
||||
*
|
||||
* @param thread the thread to step
|
||||
* @param pTickCount the number of p-code ticks to take the thread forward
|
||||
* @return the resulting schedule
|
||||
*/
|
||||
public TraceSchedule steppedPcodeForward(TraceThread thread, int pTickCount) {
|
||||
Sequence pTicks = this.pSteps.clone();
|
||||
pTicks.advance(new TickStep(thread.getKey(), pTickCount));
|
||||
return new TraceSchedule(snap, steps.clone(), pTicks);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the equivalent of executing count p-code operations less than this schedule
|
||||
*
|
||||
* <p>
|
||||
* If {@code pStepCount} exceeds the p-code steps of this schedule, null is returned, since we
|
||||
* cannot know <em>a priori</em> how many p-code steps would be required to complete the
|
||||
* preceding instruction step. Both p-code ticks and p-code patches counts as p-code steps.
|
||||
*
|
||||
* @param pStepCount the number of p-code steps to take backward
|
||||
* @return the resulting schedule or null if it cannot be computed
|
||||
*/
|
||||
public TraceSchedule steppedPcodeBackward(int pStepCount) {
|
||||
if (pStepCount > pSteps.totalTickCount()) {
|
||||
return null;
|
||||
}
|
||||
Sequence pTicks = this.pSteps.clone();
|
||||
pTicks.rewind(pStepCount);
|
||||
return new TraceSchedule(snap, steps.clone(), pTicks);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the equivalent of executing this schedule then performing a given patch
|
||||
*
|
||||
* @param sleigh a single line of sleigh, excluding the terminating semicolon.
|
||||
* @return the resulting schedule
|
||||
*/
|
||||
public TraceSchedule patched(TraceThread thread, String sleigh) {
|
||||
if (!this.pSteps.isNop()) {
|
||||
Sequence pTicks = this.pSteps.clone();
|
||||
pTicks.advance(new PatchStep(thread.getKey(), sleigh));
|
||||
return new TraceSchedule(snap, steps.clone(), pTicks);
|
||||
}
|
||||
Sequence ticks = this.steps.clone();
|
||||
ticks.advance(new PatchStep(thread.getKey(), sleigh));
|
||||
return new TraceSchedule(snap, ticks, new Sequence());
|
||||
}
|
||||
}
|
|
@ -29,6 +29,7 @@ import ghidra.trace.model.Trace.TraceSnapshotChangeType;
|
|||
import ghidra.trace.model.TraceDomainObjectListener;
|
||||
import ghidra.trace.model.program.TraceProgramView;
|
||||
import ghidra.trace.model.time.*;
|
||||
import ghidra.trace.model.time.schedule.TraceSchedule;
|
||||
import ghidra.util.*;
|
||||
import ghidra.util.datastruct.ListenerSet;
|
||||
import ghidra.util.exception.ClosedException;
|
||||
|
|
|
@ -19,6 +19,7 @@ import com.google.common.collect.*;
|
|||
|
||||
import ghidra.trace.model.Trace;
|
||||
import ghidra.trace.model.time.*;
|
||||
import ghidra.trace.model.time.schedule.TraceSchedule;
|
||||
import ghidra.util.AbstractPeekableIterator;
|
||||
|
||||
public class TraceViewportSpanIterator extends AbstractPeekableIterator<Range<Long>> {
|
||||
|
|
|
@ -13,87 +13,101 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.trace.model.time;
|
||||
package ghidra.trace.model.time.schedule;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import ghidra.app.plugin.processors.sleigh.SleighLanguage;
|
||||
import ghidra.pcode.emu.*;
|
||||
import ghidra.pcode.exec.*;
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.model.lang.RegisterValue;
|
||||
import ghidra.program.model.lang.*;
|
||||
import ghidra.test.AbstractGhidraHeadlessIntegrationTest;
|
||||
import ghidra.test.ToyProgramBuilder;
|
||||
import ghidra.trace.database.ToyDBTraceBuilder;
|
||||
import ghidra.trace.model.thread.TraceThread;
|
||||
import ghidra.trace.model.time.TraceSchedule.*;
|
||||
import ghidra.util.database.UndoableTransaction;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
public class TraceScheduleTest extends AbstractGhidraHeadlessIntegrationTest {
|
||||
protected static SleighLanguage TOY_BE_64_LANG;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
try {
|
||||
TOY_BE_64_LANG = (SleighLanguage) getLanguageService()
|
||||
.getLanguage(new LanguageID(ToyProgramBuilder._TOY64_BE));
|
||||
}
|
||||
catch (LanguageNotFoundException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParseZero() {
|
||||
TraceSchedule time = TraceSchedule.parse("0:0");
|
||||
assertEquals(new TraceSchedule(0, TickSequence.of(), TickSequence.of()), time);
|
||||
assertEquals(new TraceSchedule(0, Sequence.of(), Sequence.of()), time);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParseSimple() {
|
||||
TraceSchedule time = TraceSchedule.parse("0:100");
|
||||
assertEquals(
|
||||
new TraceSchedule(0, TickSequence.of(new TickStep(-1, 100)), TickSequence.of()), time);
|
||||
new TraceSchedule(0, Sequence.of(new TickStep(-1, 100)), Sequence.of()), time);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testToStringSimple() {
|
||||
assertEquals("0:100",
|
||||
new TraceSchedule(0, TickSequence.of(new TickStep(-1, 100)), TickSequence.of())
|
||||
new TraceSchedule(0, Sequence.of(new TickStep(-1, 100)), Sequence.of())
|
||||
.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParseWithPcodeSteps() {
|
||||
TraceSchedule time = TraceSchedule.parse("0:100.5");
|
||||
assertEquals(new TraceSchedule(0, TickSequence.of(new TickStep(-1, 100)),
|
||||
TickSequence.of(new TickStep(-1, 5))), time);
|
||||
assertEquals(new TraceSchedule(0, Sequence.of(new TickStep(-1, 100)),
|
||||
Sequence.of(new TickStep(-1, 5))), time);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testToStringWithPcodeSteps() {
|
||||
assertEquals("0:100.5", new TraceSchedule(0, TickSequence.of(new TickStep(-1, 100)),
|
||||
TickSequence.of(new TickStep(-1, 5))).toString());
|
||||
assertEquals("0:100.5", new TraceSchedule(0, Sequence.of(new TickStep(-1, 100)),
|
||||
Sequence.of(new TickStep(-1, 5))).toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParseWithThread() {
|
||||
TraceSchedule time = TraceSchedule.parse("1:t3-100");
|
||||
assertEquals(new TraceSchedule(1, TickSequence.of(new TickStep(3, 100)), TickSequence.of()),
|
||||
assertEquals(new TraceSchedule(1, Sequence.of(new TickStep(3, 100)), Sequence.of()),
|
||||
time);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testToStringWithThread() {
|
||||
assertEquals("1:t3-100",
|
||||
new TraceSchedule(1, TickSequence.of(new TickStep(3, 100)), TickSequence.of())
|
||||
new TraceSchedule(1, Sequence.of(new TickStep(3, 100)), Sequence.of())
|
||||
.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParseMultipleSteps() {
|
||||
TraceSchedule time = TraceSchedule.parse("1:50,t3-50");
|
||||
TraceSchedule time = TraceSchedule.parse("1:50;t3-50");
|
||||
assertEquals(new TraceSchedule(1,
|
||||
TickSequence.of(new TickStep(-1, 50), new TickStep(3, 50)), TickSequence.of()), time);
|
||||
Sequence.of(new TickStep(-1, 50), new TickStep(3, 50)), Sequence.of()), time);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testToStringMultipleSteps() {
|
||||
assertEquals("1:50,t3-50",
|
||||
new TraceSchedule(1, TickSequence.of(new TickStep(-1, 50), new TickStep(3, 50)),
|
||||
TickSequence.of()).toString());
|
||||
assertEquals("1:50;t3-50",
|
||||
new TraceSchedule(1, Sequence.of(new TickStep(-1, 50), new TickStep(3, 50)),
|
||||
Sequence.of()).toString());
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
|
@ -118,24 +132,24 @@ public class TraceScheduleTest extends AbstractGhidraHeadlessIntegrationTest {
|
|||
|
||||
@Test
|
||||
public void testAdvance() {
|
||||
TickSequence seq = new TickSequence();
|
||||
Sequence seq = new Sequence();
|
||||
seq.advance(new TickStep(-1, 0));
|
||||
assertEquals(TickSequence.of(), seq);
|
||||
assertEquals(Sequence.of(), seq);
|
||||
|
||||
seq.advance(new TickStep(-1, 10));
|
||||
assertEquals(TickSequence.of(new TickStep(-1, 10)), seq);
|
||||
assertEquals(Sequence.of(new TickStep(-1, 10)), seq);
|
||||
|
||||
seq.advance(new TickStep(-1, 10));
|
||||
assertEquals(TickSequence.of(new TickStep(-1, 20)), seq);
|
||||
assertEquals(Sequence.of(new TickStep(-1, 20)), seq);
|
||||
|
||||
seq.advance(new TickStep(1, 10));
|
||||
assertEquals(TickSequence.of(new TickStep(-1, 20), new TickStep(1, 10)), seq);
|
||||
assertEquals(Sequence.of(new TickStep(-1, 20), new TickStep(1, 10)), seq);
|
||||
|
||||
seq.advance(new TickStep(-1, 10));
|
||||
assertEquals(TickSequence.of(new TickStep(-1, 20), new TickStep(1, 20)), seq);
|
||||
assertEquals(Sequence.of(new TickStep(-1, 20), new TickStep(1, 20)), seq);
|
||||
|
||||
seq.advance(seq);
|
||||
assertEquals(TickSequence.of(new TickStep(-1, 20), new TickStep(1, 60)), seq);
|
||||
assertEquals(Sequence.of(new TickStep(-1, 20), new TickStep(1, 60)), seq);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
|
@ -150,13 +164,13 @@ public class TraceScheduleTest extends AbstractGhidraHeadlessIntegrationTest {
|
|||
|
||||
@Test
|
||||
public void testRewind() {
|
||||
TickSequence seq = TickSequence.parse("10,t1-20,t2-30");
|
||||
Sequence seq = Sequence.parse("10;t1-20;t2-30");
|
||||
|
||||
assertEquals(0, seq.rewind(5));
|
||||
assertEquals("10,t1-20,t2-25", seq.toString());
|
||||
assertEquals("10;t1-20;t2-25", seq.toString());
|
||||
|
||||
assertEquals(0, seq.rewind(25));
|
||||
assertEquals("10,t1-20", seq.toString());
|
||||
assertEquals("10;t1-20", seq.toString());
|
||||
|
||||
assertEquals(0, seq.rewind(27));
|
||||
assertEquals("3", seq.toString());
|
||||
|
@ -170,7 +184,7 @@ public class TraceScheduleTest extends AbstractGhidraHeadlessIntegrationTest {
|
|||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testRewindNegativeErr() {
|
||||
TickSequence seq = TickSequence.parse("10,t1-20,t2-30");
|
||||
Sequence seq = Sequence.parse("10;t1-20;t2-30");
|
||||
seq.rewind(-1);
|
||||
}
|
||||
|
||||
|
@ -214,22 +228,22 @@ public class TraceScheduleTest extends AbstractGhidraHeadlessIntegrationTest {
|
|||
expectU("0:t0-10", "0:t1-10");
|
||||
// We don't know how many p-code steps complete an instruction step
|
||||
expectU("0:t0-10.1", "0:t0-11");
|
||||
expectU("0:t0-10,t1-5", "0:t0-11,t1-5");
|
||||
expectU("0:t0-10;t1-5", "0:t0-11;t1-5");
|
||||
|
||||
expectR("0:t0-10", "0:t0-11");
|
||||
expectR("0:t0-10", "0:t0-10,t1-5");
|
||||
expectR("0:t0-10", "0:t0-11,t1-5");
|
||||
expectR("0:t0-10", "0:t0-10;t1-5");
|
||||
expectR("0:t0-10", "0:t0-11;t1-5");
|
||||
expectR("0:t0-10", "0:t0-10.1");
|
||||
expectR("0:t0-10", "0:t0-11.1");
|
||||
expectR("0:t0-10", "0:t0-10,t1-5.1");
|
||||
expectR("0:t0-10", "0:t0-11,t1-5.1");
|
||||
expectR("0:t0-10", "0:t0-10;t1-5.1");
|
||||
expectR("0:t0-10", "0:t0-11;t1-5.1");
|
||||
|
||||
expectE("0:t0-10", "0:t0-10");
|
||||
expectE("0:t0-10.1", "0:t0-10.1");
|
||||
}
|
||||
|
||||
public String strRelativize(String fromSpec, String toSpec) {
|
||||
TickSequence seq = TickSequence.parse(toSpec).relativize(TickSequence.parse(fromSpec));
|
||||
Sequence seq = Sequence.parse(toSpec).relativize(Sequence.parse(fromSpec));
|
||||
return seq == null ? null : seq.toString();
|
||||
}
|
||||
|
||||
|
@ -239,7 +253,7 @@ public class TraceScheduleTest extends AbstractGhidraHeadlessIntegrationTest {
|
|||
assertEquals("", strRelativize("10", "10"));
|
||||
assertEquals("9", strRelativize("1", "10"));
|
||||
assertEquals("t1-9", strRelativize("t1-1", "t1-10"));
|
||||
assertEquals("t1-10", strRelativize("5", "5,t1-10"));
|
||||
assertEquals("t1-10", strRelativize("5", "5;t1-10"));
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
|
@ -249,7 +263,7 @@ public class TraceScheduleTest extends AbstractGhidraHeadlessIntegrationTest {
|
|||
|
||||
@Test
|
||||
public void testTotalStepCount() {
|
||||
assertEquals(15, TraceSchedule.parse("0:4,t1-5.6").totalTickCount());
|
||||
assertEquals(15, TraceSchedule.parse("0:4;t1-5.6").totalTickCount());
|
||||
}
|
||||
|
||||
protected static class TestThread implements PcodeThread<Void> {
|
||||
|
@ -346,7 +360,13 @@ public class TraceScheduleTest extends AbstractGhidraHeadlessIntegrationTest {
|
|||
|
||||
@Override
|
||||
public PcodeExecutor<Void> getExecutor() {
|
||||
return null;
|
||||
return new PcodeExecutor<>(TOY_BE_64_LANG, machine.getArithmetic(), getState()) {
|
||||
public PcodeFrame execute(PcodeProgram program, SleighUseropLibrary<Void> library) {
|
||||
machine.record.add("x:" + name);
|
||||
// TODO: Verify the actual effect
|
||||
return null; //super.execute(program, library);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -376,7 +396,7 @@ public class TraceScheduleTest extends AbstractGhidraHeadlessIntegrationTest {
|
|||
protected final List<String> record = new ArrayList<>();
|
||||
|
||||
public TestMachine() {
|
||||
super(null, null, null);
|
||||
super(TOY_BE_64_LANG, null, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -398,8 +418,8 @@ public class TraceScheduleTest extends AbstractGhidraHeadlessIntegrationTest {
|
|||
@Test
|
||||
public void testExecute() throws Exception {
|
||||
TestMachine machine = new TestMachine();
|
||||
TraceSchedule time = TraceSchedule.parse("1:4,t0-3,t1-2.1");
|
||||
try (ToyDBTraceBuilder tb = new ToyDBTraceBuilder("test", "Toy:BE:64:default")) {
|
||||
TraceSchedule time = TraceSchedule.parse("1:4;t0-3;t1-2.1");
|
||||
try (ToyDBTraceBuilder tb = new ToyDBTraceBuilder("test", ToyProgramBuilder._TOY64_BE)) {
|
||||
TraceThread t2;
|
||||
try (UndoableTransaction tid = tb.startTransaction()) {
|
||||
tb.trace.getThreadManager().createThread("Threads[0]", 0);
|
||||
|
@ -424,10 +444,34 @@ public class TraceScheduleTest extends AbstractGhidraHeadlessIntegrationTest {
|
|||
machine.record);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSleighSteps() throws Exception {
|
||||
TestMachine machine = new TestMachine();
|
||||
TraceSchedule time = TraceSchedule.parse("1:{r0=0x1234};4");
|
||||
try (ToyDBTraceBuilder tb = new ToyDBTraceBuilder("test", "Toy:BE:64:default")) {
|
||||
TraceThread t2;
|
||||
try (UndoableTransaction tid = tb.startTransaction()) {
|
||||
tb.trace.getThreadManager().createThread("Threads[0]", 0);
|
||||
tb.trace.getThreadManager().createThread("Threads[1]", 0);
|
||||
t2 = tb.trace.getThreadManager().createThread("Threads[2]", 0);
|
||||
tb.trace.getTimeManager().getSnapshot(1, true).setEventThread(t2);
|
||||
}
|
||||
time.execute(tb.trace, machine, TaskMonitor.DUMMY);
|
||||
}
|
||||
|
||||
assertEquals(List.of(
|
||||
"x:Threads[2]",
|
||||
"s:Threads[2]",
|
||||
"s:Threads[2]",
|
||||
"s:Threads[2]",
|
||||
"s:Threads[2]"),
|
||||
machine.record);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testExecuteNoEventThreadErr() throws Exception {
|
||||
TestMachine machine = new TestMachine();
|
||||
TraceSchedule time = TraceSchedule.parse("1:4,t0-3,t1-2.1");
|
||||
TraceSchedule time = TraceSchedule.parse("1:4;t0-3;t1-2.1");
|
||||
try (ToyDBTraceBuilder tb = new ToyDBTraceBuilder("test", "Toy:BE:64:default")) {
|
||||
try (UndoableTransaction tid = tb.startTransaction()) {
|
||||
tb.trace.getThreadManager().createThread("Threads[0]", 0);
|
||||
|
@ -442,7 +486,7 @@ public class TraceScheduleTest extends AbstractGhidraHeadlessIntegrationTest {
|
|||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testExecuteBadThreadKeyErr() throws Exception {
|
||||
TestMachine machine = new TestMachine();
|
||||
TraceSchedule time = TraceSchedule.parse("1:4,t0-3,t5-2.1");
|
||||
TraceSchedule time = TraceSchedule.parse("1:4;t0-3;t5-2.1");
|
||||
try (ToyDBTraceBuilder tb = new ToyDBTraceBuilder("test", "Toy:BE:64:default")) {
|
||||
TraceThread t2;
|
||||
try (UndoableTransaction tid = tb.startTransaction()) {
|
||||
|
@ -458,7 +502,7 @@ public class TraceScheduleTest extends AbstractGhidraHeadlessIntegrationTest {
|
|||
@Test
|
||||
public void testFinish() throws Exception {
|
||||
TestMachine machine = new TestMachine();
|
||||
TraceSchedule time = TraceSchedule.parse("1:4,t0-3,t1-2.1");
|
||||
TraceSchedule time = TraceSchedule.parse("1:4;t0-3;t1-2.1");
|
||||
try (ToyDBTraceBuilder tb = new ToyDBTraceBuilder("test", "Toy:BE:64:default")) {
|
||||
TraceThread t2;
|
||||
try (UndoableTransaction tid = tb.startTransaction()) {
|
||||
|
@ -467,7 +511,7 @@ public class TraceScheduleTest extends AbstractGhidraHeadlessIntegrationTest {
|
|||
t2 = tb.trace.getThreadManager().createThread("Threads[2]", 0);
|
||||
tb.trace.getTimeManager().getSnapshot(1, true).setEventThread(t2);
|
||||
}
|
||||
time.finish(tb.trace, TraceSchedule.parse("1:4,t0-2"), machine, TaskMonitor.DUMMY);
|
||||
time.finish(tb.trace, TraceSchedule.parse("1:4;t0-2"), machine, TaskMonitor.DUMMY);
|
||||
}
|
||||
|
||||
assertEquals(List.of(
|
||||
|
@ -481,7 +525,7 @@ public class TraceScheduleTest extends AbstractGhidraHeadlessIntegrationTest {
|
|||
@Test
|
||||
public void testFinishPcode() throws Exception {
|
||||
TestMachine machine = new TestMachine();
|
||||
TraceSchedule time = TraceSchedule.parse("1:4,t0-3,t1-2.1");
|
||||
TraceSchedule time = TraceSchedule.parse("1:4;t0-3;t1-2.1");
|
||||
try (ToyDBTraceBuilder tb = new ToyDBTraceBuilder("test", "Toy:BE:64:default")) {
|
||||
TraceThread t2;
|
||||
try (UndoableTransaction tid = tb.startTransaction()) {
|
||||
|
@ -490,7 +534,7 @@ public class TraceScheduleTest extends AbstractGhidraHeadlessIntegrationTest {
|
|||
t2 = tb.trace.getThreadManager().createThread("Threads[2]", 0);
|
||||
tb.trace.getTimeManager().getSnapshot(1, true).setEventThread(t2);
|
||||
}
|
||||
time.finish(tb.trace, TraceSchedule.parse("1:4,t0-3,t1-2"), machine,
|
||||
time.finish(tb.trace, TraceSchedule.parse("1:4;t0-3;t1-2"), machine,
|
||||
TaskMonitor.DUMMY);
|
||||
}
|
||||
|
||||
|
@ -502,7 +546,7 @@ public class TraceScheduleTest extends AbstractGhidraHeadlessIntegrationTest {
|
|||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testFinishUnrelatedErr() throws Exception {
|
||||
TestMachine machine = new TestMachine();
|
||||
TraceSchedule time = TraceSchedule.parse("1:4,t0-3,t1-2.1");
|
||||
TraceSchedule time = TraceSchedule.parse("1:4;t0-3;t1-2.1");
|
||||
try (ToyDBTraceBuilder tb = new ToyDBTraceBuilder("test", "Toy:BE:64:default")) {
|
||||
TraceThread t2;
|
||||
try (UndoableTransaction tid = tb.startTransaction()) {
|
||||
|
@ -511,7 +555,7 @@ public class TraceScheduleTest extends AbstractGhidraHeadlessIntegrationTest {
|
|||
t2 = tb.trace.getThreadManager().createThread("Threads[2]", 0);
|
||||
tb.trace.getTimeManager().getSnapshot(1, true).setEventThread(t2);
|
||||
}
|
||||
time.finish(tb.trace, TraceSchedule.parse("1:4,t0-4"), machine, TaskMonitor.DUMMY);
|
||||
time.finish(tb.trace, TraceSchedule.parse("1:4;t0-4"), machine, TaskMonitor.DUMMY);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -26,7 +26,7 @@ import com.google.common.collect.*;
|
|||
import ghidra.test.AbstractGhidraHeadlessIntegrationTest;
|
||||
import ghidra.trace.database.ToyDBTraceBuilder;
|
||||
import ghidra.trace.database.time.DBTraceTimeManager;
|
||||
import ghidra.trace.model.time.TraceSchedule;
|
||||
import ghidra.trace.model.time.schedule.TraceSchedule;
|
||||
import ghidra.util.database.UndoableTransaction;
|
||||
|
||||
public class DefaultTraceTimeViewportTest extends AbstractGhidraHeadlessIntegrationTest {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue