GP-1486: Patching machine state better integrated into Emulation.

This commit is contained in:
Dan 2021-11-22 13:57:25 -05:00
parent 5c0f06ab8d
commit 9386d6fc67
35 changed files with 1819 additions and 1205 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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