mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-04 10:19:23 +02:00
GP-5523: Allow tool-wide configuration of radix for displaying trace times.
This commit is contained in:
parent
92e2b6b5d4
commit
004712026b
38 changed files with 751 additions and 246 deletions
|
@ -27,6 +27,7 @@ import ghidra.trace.model.thread.TraceObjectThread;
|
|||
import ghidra.trace.model.thread.TraceThread;
|
||||
import ghidra.trace.model.time.TraceSnapshot;
|
||||
import ghidra.trace.model.time.schedule.TraceSchedule;
|
||||
import ghidra.trace.model.time.schedule.TraceSchedule.TimeRadix;
|
||||
import ghidra.util.LockHold;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.database.*;
|
||||
|
@ -85,7 +86,7 @@ public class DBTraceSnapshot extends DBAnnotatedObject implements TraceSnapshot
|
|||
eventThread = manager.threadManager.getThread(threadKey);
|
||||
if (!"".equals(scheduleStr)) {
|
||||
try {
|
||||
schedule = TraceSchedule.parse(scheduleStr);
|
||||
schedule = TraceSchedule.parse(scheduleStr, TimeRadix.DEC);
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
Msg.error(this, "Could not parse schedule: " + schedule, e);
|
||||
|
@ -205,7 +206,7 @@ public class DBTraceSnapshot extends DBAnnotatedObject implements TraceSnapshot
|
|||
public void setSchedule(TraceSchedule schedule) {
|
||||
try (LockHold hold = LockHold.lock(manager.lock.writeLock())) {
|
||||
this.schedule = schedule;
|
||||
this.scheduleStr = schedule == null ? "" : schedule.toString();
|
||||
this.scheduleStr = schedule == null ? "" : schedule.toString(TimeRadix.DEC);
|
||||
update(SCHEDULE_COLUMN);
|
||||
manager.notifySnapshotChanged(this);
|
||||
}
|
||||
|
|
|
@ -25,10 +25,14 @@ import db.DBHandle;
|
|||
import ghidra.framework.data.OpenMode;
|
||||
import ghidra.trace.database.DBTrace;
|
||||
import ghidra.trace.database.DBTraceManager;
|
||||
import ghidra.trace.database.target.DBTraceObject;
|
||||
import ghidra.trace.database.thread.DBTraceThreadManager;
|
||||
import ghidra.trace.model.Lifespan;
|
||||
import ghidra.trace.model.target.TraceObjectValue;
|
||||
import ghidra.trace.model.time.TraceSnapshot;
|
||||
import ghidra.trace.model.time.TraceTimeManager;
|
||||
import ghidra.trace.model.time.schedule.TraceSchedule;
|
||||
import ghidra.trace.model.time.schedule.TraceSchedule.TimeRadix;
|
||||
import ghidra.trace.util.TraceChangeRecord;
|
||||
import ghidra.trace.util.TraceEvents;
|
||||
import ghidra.util.LockHold;
|
||||
|
@ -181,4 +185,34 @@ public class DBTraceTimeManager implements TraceTimeManager, DBTraceManager {
|
|||
notifySnapshotDeleted(snapshot);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTimeRadix(TimeRadix radix) {
|
||||
DBTraceObject root = trace.getObjectManager().getRootObject();
|
||||
if (root == null) {
|
||||
throw new IllegalStateException(
|
||||
"There must be a root object in the ObjectManager before setting the TimeRadix");
|
||||
}
|
||||
root.setAttribute(Lifespan.ALL, KEY_TIME_RADIX, switch (radix) {
|
||||
case DEC -> "dec";
|
||||
case HEX_UPPER -> "HEX";
|
||||
case HEX_LOWER -> "hex";
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public TimeRadix getTimeRadix() {
|
||||
DBTraceObject root = trace.getObjectManager().getRootObject();
|
||||
if (root == null) {
|
||||
return TimeRadix.DEFAULT;
|
||||
}
|
||||
TraceObjectValue attribute = root.getAttribute(0, KEY_TIME_RADIX);
|
||||
if (attribute == null) {
|
||||
return TimeRadix.DEFAULT;
|
||||
}
|
||||
return switch (attribute.getValue()) {
|
||||
case String s -> TimeRadix.fromStr(s);
|
||||
default -> TimeRadix.DEFAULT;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -281,7 +281,7 @@ public sealed interface Lifespan extends Span<Long, Lifespan>, Iterable<Long> {
|
|||
|
||||
@Override
|
||||
public String toString() {
|
||||
return doToString();
|
||||
return toString(DOMAIN::toString);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -329,7 +329,7 @@ public sealed interface Lifespan extends Span<Long, Lifespan>, Iterable<Long> {
|
|||
public record Impl(long lmin, long lmax) implements Lifespan {
|
||||
@Override
|
||||
public String toString() {
|
||||
return doToString();
|
||||
return toString(DOMAIN::toString);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -18,8 +18,12 @@ package ghidra.trace.model.time;
|
|||
import java.util.Collection;
|
||||
|
||||
import ghidra.trace.model.time.schedule.TraceSchedule;
|
||||
import ghidra.trace.model.time.schedule.TraceSchedule.TimeRadix;
|
||||
|
||||
public interface TraceTimeManager {
|
||||
/** The attribute key for controlling the time radix */
|
||||
String KEY_TIME_RADIX = "_time_radix";
|
||||
|
||||
/**
|
||||
* Create a new snapshot after the latest
|
||||
*
|
||||
|
@ -32,6 +36,7 @@ public interface TraceTimeManager {
|
|||
* Get the snapshot with the given key, optionally creating it
|
||||
*
|
||||
* @param snap the snapshot key
|
||||
* @param createIfAbsent create the snapshot if it's missing
|
||||
* @return the snapshot or {@code null}
|
||||
*/
|
||||
TraceSnapshot getSnapshot(long snap, boolean createIfAbsent);
|
||||
|
@ -105,4 +110,24 @@ public interface TraceTimeManager {
|
|||
* @return the count
|
||||
*/
|
||||
long getSnapshotCount();
|
||||
|
||||
/**
|
||||
* Set the radix for displaying and parsing time (snapshots and step counts)
|
||||
*
|
||||
* <p>
|
||||
* This only affects the GUI, but storing it in the trace gives the back end a means of
|
||||
* controlling it.
|
||||
*
|
||||
* @param radix the radix
|
||||
*/
|
||||
void setTimeRadix(TimeRadix radix);
|
||||
|
||||
/**
|
||||
* Get the radix for displaying and parsing time (snapshots and step counts)
|
||||
*
|
||||
* @see #setTimeRadix(TimeRadix)
|
||||
* @return radix the radix
|
||||
*/
|
||||
TimeRadix getTimeRadix();
|
||||
|
||||
}
|
||||
|
|
|
@ -4,9 +4,9 @@
|
|||
* 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.
|
||||
|
@ -18,6 +18,7 @@ package ghidra.trace.model.time.schedule;
|
|||
import java.util.List;
|
||||
|
||||
import ghidra.program.model.lang.Language;
|
||||
import ghidra.trace.model.time.schedule.TraceSchedule.TimeRadix;
|
||||
|
||||
public abstract class AbstractStep implements Step {
|
||||
protected final long threadKey;
|
||||
|
@ -34,16 +35,22 @@ public abstract class AbstractStep implements Step {
|
|||
/**
|
||||
* Return the step portion of {@link #toString()}
|
||||
*
|
||||
* @param radix the radix
|
||||
* @return the string
|
||||
*/
|
||||
protected abstract String toStringStepPart();
|
||||
protected abstract String toStringStepPart(TimeRadix radix);
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return toString(TimeRadix.DEFAULT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString(TimeRadix radix) {
|
||||
if (threadKey == -1) {
|
||||
return toStringStepPart();
|
||||
return toStringStepPart(radix);
|
||||
}
|
||||
return String.format("t%d-", threadKey) + toStringStepPart();
|
||||
return String.format("t%d-%s", threadKey, toStringStepPart(radix));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -34,6 +34,7 @@ import ghidra.program.model.lang.Language;
|
|||
import ghidra.program.model.lang.Register;
|
||||
import ghidra.program.model.pcode.PcodeOp;
|
||||
import ghidra.program.model.pcode.Varnode;
|
||||
import ghidra.trace.model.time.schedule.TraceSchedule.TimeRadix;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
|
@ -108,7 +109,7 @@ public class PatchStep implements Step {
|
|||
|
||||
protected static void generateSleigh(List<String> result, Language language, Address address,
|
||||
byte[] data) {
|
||||
SemisparseByteArray array = new SemisparseByteArray(); // TODO: Seems heavy-handed
|
||||
SemisparseByteArray array = new SemisparseByteArray(); // Seems heavy-handed
|
||||
array.putData(address.getOffset(), data);
|
||||
generateSleigh(result, language, address.getAddressSpace(), array);
|
||||
}
|
||||
|
@ -190,7 +191,7 @@ public class PatchStep implements Step {
|
|||
}
|
||||
|
||||
public static PatchStep parse(long threadKey, String stepSpec) {
|
||||
// TODO: Can I parse and validate the sleigh here?
|
||||
// Would be nice to parse and validate the sleigh here, but need a language.
|
||||
if (!stepSpec.startsWith("{") || !stepSpec.endsWith("}")) {
|
||||
throw new IllegalArgumentException("Cannot parse step: '" + stepSpec + "'");
|
||||
}
|
||||
|
@ -200,7 +201,7 @@ public class PatchStep implements Step {
|
|||
public PatchStep(long threadKey, String sleigh) {
|
||||
this.threadKey = threadKey;
|
||||
this.sleigh = Objects.requireNonNull(sleigh);
|
||||
this.hashCode = Objects.hash(threadKey, sleigh); // TODO: May become mutable
|
||||
this.hashCode = Objects.hash(threadKey, sleigh);
|
||||
}
|
||||
|
||||
private void setSleigh(String sleigh) {
|
||||
|
@ -233,6 +234,11 @@ public class PatchStep implements Step {
|
|||
|
||||
@Override
|
||||
public String toString() {
|
||||
return toString(TimeRadix.DEFAULT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString(TimeRadix radix) {
|
||||
if (threadKey == -1) {
|
||||
return "{" + sleigh + "}";
|
||||
}
|
||||
|
@ -251,7 +257,6 @@ public class PatchStep implements Step {
|
|||
|
||||
@Override
|
||||
public boolean isNop() {
|
||||
// TODO: If parsing beforehand, base on number of ops
|
||||
return sleigh.length() == 0;
|
||||
}
|
||||
|
||||
|
@ -272,7 +277,7 @@ public class PatchStep implements Step {
|
|||
|
||||
@Override
|
||||
public boolean isCompatible(Step step) {
|
||||
// TODO: Can we combine ops?
|
||||
// Can we combine ops?
|
||||
return false; // For now, never combine sleigh steps
|
||||
}
|
||||
|
||||
|
@ -314,7 +319,7 @@ public class PatchStep implements Step {
|
|||
return result;
|
||||
}
|
||||
|
||||
// TODO: Compare ops, if/when we pre-compile
|
||||
// Compare ops, if/when we pre-compile?
|
||||
result = CompareResult.unrelated(this.sleigh.compareTo(that.sleigh));
|
||||
if (result != CompareResult.EQUALS) {
|
||||
return result;
|
||||
|
|
|
@ -18,14 +18,13 @@ package ghidra.trace.model.time.schedule;
|
|||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import ghidra.pcode.emu.PcodeMachine;
|
||||
import ghidra.pcode.emu.PcodeThread;
|
||||
import ghidra.program.model.lang.Language;
|
||||
import ghidra.trace.model.Trace;
|
||||
import ghidra.trace.model.thread.TraceThread;
|
||||
import ghidra.trace.model.thread.TraceThreadManager;
|
||||
import ghidra.trace.model.time.schedule.TraceSchedule.TimeRadix;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
|
@ -33,27 +32,28 @@ 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 = ";";
|
||||
private 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.
|
||||
* {@link Step#parse(String, TimeRadix)}. 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
|
||||
* @param radix the radix
|
||||
* @return the parsed sequence
|
||||
* @throws IllegalArgumentException if the specification is of the wrong form
|
||||
*/
|
||||
public static Sequence parse(String seqSpec) {
|
||||
public static Sequence parse(String seqSpec, TimeRadix radix) {
|
||||
Sequence result = new Sequence();
|
||||
for (String stepSpec : seqSpec.split(SEP)) {
|
||||
Step step = Step.parse(stepSpec);
|
||||
Step step = Step.parse(stepSpec, radix);
|
||||
result.advance(step);
|
||||
}
|
||||
return result;
|
||||
|
@ -109,7 +109,11 @@ public class Sequence implements Comparable<Sequence> {
|
|||
|
||||
@Override
|
||||
public String toString() {
|
||||
return StringUtils.join(steps, SEP);
|
||||
return toString(TimeRadix.DEFAULT);
|
||||
}
|
||||
|
||||
public String toString(TimeRadix radix) {
|
||||
return steps.stream().map(s -> s.toString(radix)).collect(Collectors.joining(SEP));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -16,17 +16,18 @@
|
|||
package ghidra.trace.model.time.schedule;
|
||||
|
||||
import ghidra.pcode.emu.PcodeThread;
|
||||
import ghidra.trace.model.time.schedule.TraceSchedule.TimeRadix;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
public class SkipStep extends AbstractStep {
|
||||
|
||||
public static SkipStep parse(long threadKey, String stepSpec) {
|
||||
public static SkipStep parse(long threadKey, String stepSpec, TimeRadix radix) {
|
||||
if (!stepSpec.startsWith("s")) {
|
||||
throw new IllegalArgumentException("Cannot parse skip step: '" + stepSpec + "'");
|
||||
}
|
||||
try {
|
||||
return new SkipStep(threadKey, Long.parseLong(stepSpec.substring(1)));
|
||||
return new SkipStep(threadKey, radix.decode(stepSpec.substring(1)));
|
||||
}
|
||||
catch (NumberFormatException e) {
|
||||
throw new IllegalArgumentException("Cannot parse skip step: '" + stepSpec + "'");
|
||||
|
@ -54,8 +55,8 @@ public class SkipStep extends AbstractStep {
|
|||
}
|
||||
|
||||
@Override
|
||||
protected String toStringStepPart() {
|
||||
return String.format("s%d", tickCount);
|
||||
protected String toStringStepPart(TimeRadix radix) {
|
||||
return "s" + radix.format(tickCount);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -22,6 +22,7 @@ import ghidra.pcode.emu.PcodeThread;
|
|||
import ghidra.program.model.lang.Language;
|
||||
import ghidra.trace.model.thread.TraceThread;
|
||||
import ghidra.trace.model.thread.TraceThreadManager;
|
||||
import ghidra.trace.model.time.schedule.TraceSchedule.TimeRadix;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
|
@ -40,21 +41,22 @@ public interface Step extends Comparable<Step> {
|
|||
* applies to the last thread or the event thread.
|
||||
*
|
||||
* @param stepSpec the string specification
|
||||
* @param radix the radix
|
||||
* @return the parsed step
|
||||
* @throws IllegalArgumentException if the specification is of the wrong form
|
||||
*/
|
||||
static Step parse(String stepSpec) {
|
||||
static Step parse(String stepSpec, TimeRadix radix) {
|
||||
if ("".equals(stepSpec)) {
|
||||
return nop();
|
||||
}
|
||||
String[] parts = stepSpec.split("-");
|
||||
if (parts.length == 1) {
|
||||
return parse(-1, parts[0].trim());
|
||||
return parse(-1, parts[0].trim(), radix);
|
||||
}
|
||||
if (parts.length == 2) {
|
||||
String tPart = parts[0].trim();
|
||||
if (tPart.startsWith("t")) {
|
||||
return parse(Long.parseLong(tPart.substring(1)), parts[1].trim());
|
||||
return parse(Long.parseLong(tPart.substring(1)), parts[1].trim(), radix);
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("Cannot parse step: '" + stepSpec + "'");
|
||||
|
@ -70,23 +72,26 @@ public interface Step extends Comparable<Step> {
|
|||
*
|
||||
* @param threadKey the thread to step, or -1 for the last thread or event thread
|
||||
* @param stepSpec the string specification
|
||||
* @param radix the radix
|
||||
* @return the parsed step
|
||||
* @throws IllegalArgumentException if the specification is of the wrong form
|
||||
*/
|
||||
static Step parse(long threadKey, String stepSpec) {
|
||||
static Step parse(long threadKey, String stepSpec, TimeRadix radix) {
|
||||
if (stepSpec.startsWith("s")) {
|
||||
return SkipStep.parse(threadKey, stepSpec);
|
||||
return SkipStep.parse(threadKey, stepSpec, radix);
|
||||
}
|
||||
if (stepSpec.startsWith("{")) {
|
||||
return PatchStep.parse(threadKey, stepSpec);
|
||||
}
|
||||
return TickStep.parse(threadKey, stepSpec);
|
||||
return TickStep.parse(threadKey, stepSpec, radix);
|
||||
}
|
||||
|
||||
static TickStep nop() {
|
||||
return new TickStep(-1, 0);
|
||||
}
|
||||
|
||||
String toString(TimeRadix radix);
|
||||
|
||||
StepType getType();
|
||||
|
||||
default int getTypeOrder() {
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
package ghidra.trace.model.time.schedule;
|
||||
|
||||
import ghidra.pcode.emu.PcodeThread;
|
||||
import ghidra.trace.model.time.schedule.TraceSchedule.TimeRadix;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
|
@ -24,9 +25,9 @@ import ghidra.util.task.TaskMonitor;
|
|||
*/
|
||||
public class TickStep extends AbstractStep {
|
||||
|
||||
public static TickStep parse(long threadKey, String stepSpec) {
|
||||
public static TickStep parse(long threadKey, String stepSpec, TimeRadix radix) {
|
||||
try {
|
||||
return new TickStep(threadKey, Long.parseLong(stepSpec));
|
||||
return new TickStep(threadKey, radix.decode(stepSpec));
|
||||
}
|
||||
catch (NumberFormatException e) {
|
||||
throw new IllegalArgumentException("Cannot parse tick step: '" + stepSpec + "'");
|
||||
|
@ -54,8 +55,8 @@ public class TickStep extends AbstractStep {
|
|||
}
|
||||
|
||||
@Override
|
||||
protected String toStringStepPart() {
|
||||
return Long.toString(tickCount);
|
||||
protected String toStringStepPart(TimeRadix radix) {
|
||||
return radix.format(tickCount);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -35,6 +35,62 @@ public class TraceSchedule implements Comparable<TraceSchedule> {
|
|||
*/
|
||||
public static final TraceSchedule ZERO = TraceSchedule.snap(0);
|
||||
|
||||
/**
|
||||
* Format for rendering and parsing snaps and step counts
|
||||
*/
|
||||
public enum TimeRadix {
|
||||
/** Use decimal (default) */
|
||||
DEC("dec", 10, "%d"),
|
||||
/** Use upper-case hexadecimal */
|
||||
HEX_UPPER("HEX", 16, "%X"),
|
||||
/** Use lower-case hexadecimal */
|
||||
HEX_LOWER("hex", 16, "%x");
|
||||
|
||||
/** The default radix (decimal) */
|
||||
public static final TimeRadix DEFAULT = DEC;
|
||||
|
||||
/**
|
||||
* Get the radix specified by the given string
|
||||
*
|
||||
* @param s the name of the specified radix
|
||||
* @return the radix
|
||||
*/
|
||||
public static TimeRadix fromStr(String s) {
|
||||
return switch (s) {
|
||||
case "dec" -> DEC;
|
||||
case "HEX" -> HEX_UPPER;
|
||||
case "hex" -> HEX_LOWER;
|
||||
default -> DEFAULT;
|
||||
};
|
||||
}
|
||||
|
||||
public final String name;
|
||||
public final int n;
|
||||
public final String fmt;
|
||||
|
||||
private TimeRadix(String name, int n, String fmt) {
|
||||
this.name = name;
|
||||
this.n = n;
|
||||
this.fmt = fmt;
|
||||
}
|
||||
|
||||
public String format(long time) {
|
||||
return fmt.formatted(time);
|
||||
}
|
||||
|
||||
public long decode(String nm) {
|
||||
if (nm.startsWith("0x") || nm.startsWith("0X") ||
|
||||
nm.startsWith("-0x") || nm.startsWith("-0X")) {
|
||||
return Long.parseLong(nm, 16);
|
||||
}
|
||||
if (nm.startsWith("0n") || nm.startsWith("0N") ||
|
||||
nm.startsWith("-0n") || nm.startsWith("-0N")) {
|
||||
return Long.parseLong(nm, 10);
|
||||
}
|
||||
return Long.parseLong(nm, n);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies forms of a stepping schedule.
|
||||
*
|
||||
|
@ -196,14 +252,15 @@ public class TraceSchedule implements Comparable<TraceSchedule> {
|
|||
* <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.
|
||||
* specified by {@link Sequence#parse(String, TimeRadix)}. Each sequence consists of stepping
|
||||
* selected threads forward, and/or patching machine state.
|
||||
*
|
||||
* @param spec the string specification
|
||||
* @param source the presumed source of the schedule
|
||||
* @param radix the radix
|
||||
* @return the parsed schedule
|
||||
*/
|
||||
public static TraceSchedule parse(String spec, Source source) {
|
||||
public static TraceSchedule parse(String spec, Source source, TimeRadix radix) {
|
||||
String[] parts = spec.split(":", 2);
|
||||
if (parts.length > 2) {
|
||||
throw new AssertionError();
|
||||
|
@ -212,7 +269,7 @@ public class TraceSchedule implements Comparable<TraceSchedule> {
|
|||
final Sequence ticks;
|
||||
final Sequence pTicks;
|
||||
try {
|
||||
snap = Long.decode(parts[0]);
|
||||
snap = radix.decode(parts[0]);
|
||||
}
|
||||
catch (NumberFormatException e) {
|
||||
throw new IllegalArgumentException(PARSE_ERR_MSG, e);
|
||||
|
@ -220,7 +277,7 @@ public class TraceSchedule implements Comparable<TraceSchedule> {
|
|||
if (parts.length > 1) {
|
||||
String[] subs = parts[1].split("\\.");
|
||||
try {
|
||||
ticks = Sequence.parse(subs[0]);
|
||||
ticks = Sequence.parse(subs[0], radix);
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
throw new IllegalArgumentException(PARSE_ERR_MSG, e);
|
||||
|
@ -230,7 +287,7 @@ public class TraceSchedule implements Comparable<TraceSchedule> {
|
|||
}
|
||||
else if (subs.length == 2) {
|
||||
try {
|
||||
pTicks = Sequence.parse(subs[1]);
|
||||
pTicks = Sequence.parse(subs[1], radix);
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
throw new IllegalArgumentException(PARSE_ERR_MSG, e);
|
||||
|
@ -248,13 +305,24 @@ public class TraceSchedule implements Comparable<TraceSchedule> {
|
|||
}
|
||||
|
||||
/**
|
||||
* As in {@link #parse(String, Source)}, but assumed abnormal
|
||||
* As in {@link #parse(String, Source, TimeRadix)}, but assumed abnormal
|
||||
*
|
||||
* @param spec the string specification
|
||||
* @param radix the radix
|
||||
* @return the parsed schedule
|
||||
*/
|
||||
public static TraceSchedule parse(String spec, TimeRadix radix) {
|
||||
return parse(spec, Source.INPUT, radix);
|
||||
}
|
||||
|
||||
/**
|
||||
* As in {@link #parse(String, TimeRadix)}, but with the {@link TimeRadix#DEFAULT} radix.
|
||||
*
|
||||
* @param spec the string specification
|
||||
* @return the parse sequence
|
||||
*/
|
||||
public static TraceSchedule parse(String spec) {
|
||||
return parse(spec, Source.INPUT);
|
||||
return parse(spec, TimeRadix.DEFAULT);
|
||||
}
|
||||
|
||||
public enum Source {
|
||||
|
@ -318,13 +386,18 @@ public class TraceSchedule implements Comparable<TraceSchedule> {
|
|||
|
||||
@Override
|
||||
public String toString() {
|
||||
return toString(TimeRadix.DEFAULT);
|
||||
}
|
||||
|
||||
public String toString(TimeRadix radix) {
|
||||
if (pSteps.isNop()) {
|
||||
if (steps.isNop()) {
|
||||
return Long.toString(snap);
|
||||
return radix.format(snap);
|
||||
}
|
||||
return String.format("%d:%s", snap, steps);
|
||||
return String.format("%s:%s", radix.format(snap), steps.toString(radix));
|
||||
}
|
||||
return String.format("%d:%s.%s", snap, steps, pSteps);
|
||||
return String.format("%s:%s.%s", radix.format(snap), steps.toString(radix),
|
||||
pSteps.toString(radix));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -30,6 +30,7 @@ import ghidra.test.AbstractGhidraHeadlessIntegrationTest;
|
|||
import ghidra.test.ToyProgramBuilder;
|
||||
import ghidra.trace.database.ToyDBTraceBuilder;
|
||||
import ghidra.trace.model.thread.TraceThread;
|
||||
import ghidra.trace.model.time.schedule.TraceSchedule.TimeRadix;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
public class TraceScheduleTest extends AbstractGhidraHeadlessIntegrationTest {
|
||||
|
@ -161,7 +162,7 @@ public class TraceScheduleTest extends AbstractGhidraHeadlessIntegrationTest {
|
|||
|
||||
@Test
|
||||
public void testRewind() {
|
||||
Sequence seq = Sequence.parse("10;t1-20;t2-30");
|
||||
Sequence seq = Sequence.parse("10;t1-20;t2-30", TimeRadix.DEC);
|
||||
|
||||
assertEquals(0, seq.rewind(5));
|
||||
assertEquals("10;t1-20;t2-25", seq.toString());
|
||||
|
@ -181,7 +182,7 @@ public class TraceScheduleTest extends AbstractGhidraHeadlessIntegrationTest {
|
|||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testRewindNegativeErr() {
|
||||
Sequence seq = Sequence.parse("10;t1-20;t2-30");
|
||||
Sequence seq = Sequence.parse("10;t1-20;t2-30", TimeRadix.DEC);
|
||||
seq.rewind(-1);
|
||||
}
|
||||
|
||||
|
@ -251,7 +252,8 @@ public class TraceScheduleTest extends AbstractGhidraHeadlessIntegrationTest {
|
|||
}
|
||||
|
||||
public String strRelativize(String fromSpec, String toSpec) {
|
||||
Sequence seq = Sequence.parse(toSpec).relativize(Sequence.parse(fromSpec));
|
||||
Sequence seq = Sequence.parse(toSpec, TimeRadix.DEC)
|
||||
.relativize(Sequence.parse(fromSpec, TimeRadix.DEC));
|
||||
return seq == null ? null : seq.toString();
|
||||
}
|
||||
|
||||
|
@ -503,4 +505,13 @@ public class TraceScheduleTest extends AbstractGhidraHeadlessIntegrationTest {
|
|||
assertFalse(
|
||||
TraceSchedule.parse("1:1.1;{r0=1}").differsOnlyByPatch(TraceSchedule.parse("1:1.1")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTimeRadix() throws Exception {
|
||||
TraceSchedule schedule = TraceSchedule.parse("A:t10-B.C", TimeRadix.HEX_UPPER);
|
||||
|
||||
assertEquals("10:t10-11.12", schedule.toString(TimeRadix.DEC));
|
||||
assertEquals("A:t10-B.C", schedule.toString(TimeRadix.HEX_UPPER));
|
||||
assertEquals("a:t10-b.c", schedule.toString(TimeRadix.HEX_LOWER));
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue