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,10 +22,10 @@
</TBODY>
</TABLE>
<P>P-code is the "microcode" of Ghidra's processor specifications, compiled from its SLEIGH
<P>P-code is the "microcode" of Ghidra's processor specifications, compiled from its Sleigh
specification. Originally designed to facilitate static analysis, it is easily applied to
emulation as well. Stepping each p-code operation is an effective means of debugging the
SLEIGH. The plugin provides two panes: 1) The p-code listing, and 2) Temporary ("Unique")
Sleigh. The plugin provides two panes: 1) The p-code listing, and 2) Temporary ("Unique")
variables. The listing works similarly to the dynamic listing. It displays each p-code
operation, highlighting the current "counter", which is the next operation to be executed.
There is also a cursor, allowing selection of an operation. The variables view operates
@ -62,12 +62,12 @@
interact with the target, stepping at the p-code level implies you are no longer "at the
present."</P>
<H3><A name="step_trace_pcode_backward"></A>Step Trace p-code Backward</H3>
<H3><A name="emu_trace_pcode_backward"></A>Emulate Trace p-code Backward</H3>
<P>This action is available when the current coordinates have some positive number of p-code
ticks. It steps the trace backward to the previous p-code tick.</P>
<H3><A name="step_trace_pcode_forward"></A>Step Trace p-code Forward</H3>
<H3><A name="emu_trace_pcode_forward"></A>Emulate Trace p-code Forward</H3>
<P>This action is available when a thread is selected. It steps the current thread forward to
the next p-code tick, using emulation. Note that emulation does not affect the target.

View file

@ -109,14 +109,14 @@
trace forward to the next snapshot, causing most windows to display the recorded data from the
new point in time.</P>
<H3><A name="step_trace_tick_backward"></A><IMG alt="" src="images/stepback.png">Step Trace
<H3><A name="emu_trace_tick_backward"></A><IMG alt="" src="images/stepback.png">Emulate Trace
Tick Backward</H3>
<P>This action is available when the current point in time includes emulated steps. It steps
the trace backward to the previous tick.</P>
<H3><A name="step_trace_tick_forward"></A><IMG alt="" src="images/stepinto.png">Step Trace Tick
Forward</H3>
<H3><A name="emu_trace_tick_forward"></A><IMG alt="" src="images/stepinto.png">Emulate Trace
Tick Forward</H3>
<P>This action is available when a thread is selected. It steps the current thread forward to
the next tick, using emulation. Note that emulation does not affect the target. Furthermore,
@ -132,6 +132,37 @@
not cause any windows to update. Toggling it off then on is a quick way to return to the
present after browsing the past.</P>
<H3><A name="goto_time"></A>Go To Time</H3>
<P>This action is available when a trace is active. It prompts for a <B>Time Schedule</B>
expression. This is the same form as the expression in the title bar of the threads window. In
many cases, it is simply the snapshot number, e.g., <CODE>3</CODE>, which will go to the
snapshot with key 3. It may optionally include an emulation schedule, for example,
<CODE>3:10</CODE> will use snapshot 3 for an emulator's initial state and step 10 machine
instructions on snapshot 3's event thread. If the snapshot does not give an event thread, then
the thread must be specified in the expression, e.g., <CODE>3:t1-10</CODE>. That expression
will start at snapshot 3, get the thread with key 1, and step it 10 machine instructions. The
stepping commands can be repeated any number of times, separated by semicolons, to step threads
in a specified sequence, e.g., <CODE>3:t1-10;t2-5</CODE> will do the same as before, then get
thread 2 and step it 5 times.</P>
<P>The emulator's state can also be modified by the schedule. Instead of specifying a number of
steps, write a <B>Sleigh</B> statement, e.g., <CODE>3:t1-{r0=0x1234};10</CODE>. This will start
at snapshot 3, patch thread 1's r0 to 0x1234, then step 10 instructions. Like stepping
commands, the thread may be omitted for Sleigh commands. Each command without a thread
specified implicitly uses the one from the previous command, or in the case of the first
command, the event thread. Only one Sleigh statement is permitted per command.</P>
<P>A second command sequence may be appended, following a dot, to command the emulator at the
level of p-code operations as well. This is particularly useful when debugging a processor
specification. See also the <A href=
"help/topics/DebuggerPcodeStepperPlugin/DebuggerPcodeStepperPlugin.html">P-code Stepper</A>
window. For example, <CODE>3:2.10</CODE> will start at snapshot 3 and step the event thread 2
machine instructions, followed by 10 p-code operations. The same thread-by-thread sequencing
and state patching commands are allowed in the p-code command sequence. The <EM>entire</EM>
instruction sequence precedes the entire p-code sequence, i.e., only a single dot is allowed.
Once the expression enters p-code mode, it cannot re-enter instruction mode.</P>
<H2>Other Actions</H2>
<H3><A name="sync_focus"></A>Synchronize Trace and Target Focus</H3>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Before After
Before After

View file

@ -62,9 +62,9 @@
<LI>Value - the raw bytes of the watched buffer. If the expression is a register, then this
is its hexadecimal value. This field is user modifiable when the <B>Enable Edits</B> toggle
is on. Changes are sent to the target if the trace is live and "at the present." If the value
has changed since the last navigation event, this cell is rendered in <FONT color=
"red">red</FONT>.</LI>
is on. Changes are sent to the target if the trace is live and "at the present." Otherwise,
the change is materialized via emulation. If the value has changed since the last navigation
event, this cell is rendered in <FONT color="red">red</FONT>.</LI>
<LI>Type - the user-modifiable type of the watch. Note the type is not marked up in the
trace. Clicking the Apply Data Type action will apply it to the current trace, if
@ -79,7 +79,7 @@
not necessarily cause alarm. An expression devised for one context may not have meaning under
another, even if it evaluates without error. E.g., <CODE>RIP</CODE> will disappear when
switching to a 32-bit trace, or <CODE>*:8 (*:8 (RSP+8))</CODE> may cause an invalid
dereference if an x86 <CODE>PUSH</CODE> causes <CODE>*:8 (RSP+8)</CODE> to become 0.</LI>
dereference when the stack pointer moves.</LI>
</UL>
<H2>Actions</H2>
@ -123,11 +123,15 @@
<H3><A name="enable_edits"></A>Enable Edits</H3>
<P>This toggle is a write protector for recorded and/or live values. To modify a watch's value,
this toggle must be enabled. Editing a value when the trace is live and "at the present" will
cause the value to be modified on the target. Editing historical and/or emulated values is
permitted, but it has no effect on the target. Note that only the raw "Value" column can be
edited directly. The "Repr" column cannot be edited, yet.</P>
<P>This toggle is a write protector for target machine state. To modify a watch's value, this
toggle must be enabled. Editing a value when the trace is live and "at the present" will cause
the value to be modified on the target. Editing emulated values is permitted, but it has no
effect on the target. Editing historical values is not permitted. All edits to non-live trace
values are performed in emulation. Specifically, it appends a patch command to the current
emulation schedule. This keeps trace history in tact, and it allows patched emulated states to
be annotated and recalled later, since they are stored in the trace's scratch space. Note that
only the raw "Value" column can be edited directly. The "Repr" column cannot be edited,
yet.</P>
<H2><A name="colors"></A>Tool Options: Colors</H2>

View file

@ -31,8 +31,8 @@ import ghidra.trace.database.DBTraceContentHandler;
import ghidra.trace.model.Trace;
import ghidra.trace.model.program.TraceProgramView;
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.DefaultTraceTimeViewport;
import ghidra.trace.util.TraceTimeViewport;
import ghidra.util.Msg;
@ -259,8 +259,10 @@ public class DebuggerCoordinates {
Collection<? extends TraceSnapshot> snapshots =
trace.getTimeManager().getSnapshotsWithSchedule(time);
if (snapshots.isEmpty()) {
Msg.warn(this, "Seems the emulation service did not create the requested snapshot");
return viewSnap = time.getSnap();
Msg.warn(this,
"Seems the emulation service did not create the requested snapshot, yet");
// NB. Don't cache viewSnap. Maybe next time, we'll get it.
return time.getSnap();
}
return viewSnap = snapshots.iterator().next().getKey();
}

View file

@ -1468,24 +1468,24 @@ public interface DebuggerResources {
}
}
abstract class AbstractStepTickForwardAction extends DockingAction {
public static final String NAME = "Step Trace Tick Forward";
abstract class AbstractEmulateTickForwardAction extends DockingAction {
public static final String NAME = "Emulate Trace Tick Forward";
public static final Icon ICON = ICON_STEP_INTO;
public static final String HELP_ANCHOR = "step_trace_tick_forward";
public static final String HELP_ANCHOR = "emu_trace_tick_forward";
public AbstractStepTickForwardAction(Plugin owner) {
public AbstractEmulateTickForwardAction(Plugin owner) {
super(NAME, owner.getName());
setDescription("Navigate the recording forward one tick");
setDescription("Emulate the recording forward one tick");
setHelpLocation(new HelpLocation(owner.getName(), HELP_ANCHOR));
}
}
interface StepPcodeForwardAction {
String NAME = "Step Trace p-code Forward";
interface EmulatePcodeForwardAction {
String NAME = "Emulate Trace p-code Forward";
String DESCRIPTION = "Navigate the recording forward one p-code tick";
Icon ICON = ICON_STEP_INTO;
String GROUP = GROUP_CONTROL;
String HELP_ANCHOR = "step_trace_pcode_forward";
String HELP_ANCHOR = "emu_trace_pcode_forward";
static ActionBuilder builder(Plugin owner) {
String ownerName = owner.getName();
@ -1497,14 +1497,14 @@ public interface DebuggerResources {
}
}
abstract class AbstractStepTickBackwardAction extends DockingAction {
public static final String NAME = "Step Trace Tick Backward";
abstract class AbstractEmulateTickBackwardAction extends DockingAction {
public static final String NAME = "Emulate Trace Tick Backward";
public static final Icon ICON = ICON_STEP_BACK;
public static final String HELP_ANCHOR = "step_trace_tick_backward";
public static final String HELP_ANCHOR = "emu_trace_tick_backward";
public AbstractStepTickBackwardAction(Plugin owner) {
public AbstractEmulateTickBackwardAction(Plugin owner) {
super(NAME, owner.getName());
setDescription("Navigate the recording backward one tick");
setDescription("Emulate the recording backward one tick");
setHelpLocation(new HelpLocation(owner.getName(), HELP_ANCHOR));
}
}
@ -1521,12 +1521,12 @@ public interface DebuggerResources {
}
}
interface StepPcodeBackwardAction {
String NAME = "Step Trace p-code Backward";
interface EmulatePcodeBackwardAction {
String NAME = "Emulate Trace p-code Backward";
String DESCRIPTION = "Navigate the recording backward one p-code tick";
Icon ICON = ICON_STEP_BACK;
String GROUP = GROUP_CONTROL;
String HELP_ANCHOR = "step_trace_pcode_backward";
String HELP_ANCHOR = "emu_trace_pcode_backward";
static ActionBuilder builder(Plugin owner) {
String ownerName = owner.getName();
@ -1553,15 +1553,30 @@ public interface DebuggerResources {
interface SynchronizeFocusAction {
String NAME = "Synchronize Focus";
String DESCRIPTION = "Synchronize trace activation with debugger focus/select";
String GROUP = "zz";
Icon ICON = ICON_SYNC;
String HELP_ANCHOR = "sync_focus";
static ToggleActionBuilder builder(Plugin owner) {
String ownerName = owner.getName();
return new ToggleActionBuilder(NAME, ownerName).description(DESCRIPTION)
.toolBarGroup(GROUP)
.toolBarIcon(ICON)
.menuPath(NAME)
.menuIcon(ICON)
.helpLocation(new HelpLocation(ownerName, HELP_ANCHOR));
}
}
interface GoToTimeAction {
String NAME = "Go To Time";
String DESCRIPTION = "Go to a specific time, optionally using emulation";
Icon ICON = ICON_TIME;
String HELP_ANCHOR = "goto_time";
static ActionBuilder builder(Plugin owner) {
String ownerName = owner.getName();
return new ActionBuilder(NAME, ownerName).description(DESCRIPTION)
.menuPath(NAME)
.menuIcon(ICON)
.keyBinding("CTRL SHIFT T")
.helpLocation(new HelpLocation(ownerName, HELP_ANCHOR));
}
}

View file

@ -55,7 +55,7 @@ import ghidra.program.model.lang.Language;
import ghidra.program.model.pcode.PcodeOp;
import ghidra.program.model.pcode.Varnode;
import ghidra.trace.model.Trace;
import ghidra.trace.model.time.TraceSchedule;
import ghidra.trace.model.time.schedule.TraceSchedule;
import ghidra.util.ColorUtils;
import ghidra.util.HTMLUtilities;
import ghidra.util.database.UndoableTransaction;
@ -507,11 +507,11 @@ public class DebuggerPcodeStepperProvider extends ComponentProviderAdapter {
}
protected void createActions() {
actionStepBackward = DebuggerResources.StepPcodeBackwardAction.builder(plugin)
actionStepBackward = DebuggerResources.EmulatePcodeBackwardAction.builder(plugin)
.enabledWhen(c -> current.getTrace() != null && current.getTime().pTickCount() != 0)
.onAction(c -> stepBackwardActivated())
.buildAndInstallLocal(this);
actionStepForward = DebuggerResources.StepPcodeForwardAction.builder(plugin)
actionStepForward = DebuggerResources.EmulatePcodeForwardAction.builder(plugin)
.enabledWhen(
c -> current.getThread() != null)
.onAction(c -> stepForwardActivated())

View file

@ -33,6 +33,7 @@ import docking.WindowPosition;
import docking.action.*;
import docking.widgets.HorizontalTabPanel;
import docking.widgets.HorizontalTabPanel.TabListCellRenderer;
import docking.widgets.dialogs.InputDialog;
import docking.widgets.table.*;
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
@ -53,8 +54,9 @@ import ghidra.trace.model.Trace.TraceThreadChangeType;
import ghidra.trace.model.TraceDomainObjectListener;
import ghidra.trace.model.thread.TraceThread;
import ghidra.trace.model.thread.TraceThreadManager;
import ghidra.trace.model.time.TraceSchedule;
import ghidra.trace.model.time.TraceSnapshot;
import ghidra.trace.model.time.schedule.TraceSchedule;
import ghidra.util.Msg;
import ghidra.util.Swing;
import ghidra.util.database.ObjectKey;
import ghidra.util.datastruct.CollectionChangeListener;
@ -120,10 +122,10 @@ public class DebuggerThreadsProvider extends ComponentProviderAdapter {
}
}
protected class StepTickBackwardAction extends AbstractStepTickBackwardAction {
protected class EmulateTickBackwardAction extends AbstractEmulateTickBackwardAction {
public static final String GROUP = DebuggerResources.GROUP_CONTROL;
public StepTickBackwardAction() {
public EmulateTickBackwardAction() {
super(plugin);
setToolBarData(new ToolBarData(ICON, GROUP, "2"));
addLocalAction(this);
@ -157,10 +159,10 @@ public class DebuggerThreadsProvider extends ComponentProviderAdapter {
}
}
protected class StepTickForwardAction extends AbstractStepTickForwardAction {
protected class EmulateTickForwardAction extends AbstractEmulateTickForwardAction {
public static final String GROUP = DebuggerResources.GROUP_CONTROL;
public StepTickForwardAction() {
public EmulateTickForwardAction() {
super(plugin);
setToolBarData(new ToolBarData(ICON, GROUP, "3"));
addLocalAction(this);
@ -353,11 +355,12 @@ public class DebuggerThreadsProvider extends ComponentProviderAdapter {
DockingAction actionSaveTrace;
StepSnapBackwardAction actionStepSnapBackward;
StepTickBackwardAction actionStepTickBackward;
StepTickForwardAction actionStepTickForward;
EmulateTickBackwardAction actionEmulateTickBackward;
EmulateTickForwardAction actionEmulateTickForward;
StepSnapForwardAction actionStepSnapForward;
SeekTracePresentAction actionSeekTracePresent;
ToggleDockingAction actionSyncFocus;
DockingAction actionGoToTime;
Set<Object> strongRefs = new HashSet<>(); // Eww
public DebuggerThreadsProvider(final DebuggerThreadsPlugin plugin) {
@ -669,8 +672,8 @@ public class DebuggerThreadsProvider extends ComponentProviderAdapter {
protected void createActions() {
// TODO: Make other actions use builder?
actionStepSnapBackward = new StepSnapBackwardAction();
actionStepTickBackward = new StepTickBackwardAction();
actionStepTickForward = new StepTickForwardAction();
actionEmulateTickBackward = new EmulateTickBackwardAction();
actionEmulateTickForward = new EmulateTickForwardAction();
actionStepSnapForward = new StepSnapForwardAction();
actionSeekTracePresent = new SeekTracePresentAction();
actionSyncFocus = SynchronizeFocusAction.builder(plugin)
@ -678,6 +681,10 @@ public class DebuggerThreadsProvider extends ComponentProviderAdapter {
.enabledWhen(c -> traceManager != null)
.onAction(c -> toggleSyncFocus(actionSyncFocus.isSelected()))
.buildAndInstallLocal(this);
actionGoToTime = GoToTimeAction.builder(plugin)
.enabledWhen(c -> current.getTrace() != null)
.onAction(c -> activatedGoToTime())
.buildAndInstallLocal(this);
traceManager.addSynchronizeFocusChangeListener(
strongRef(new ToToggleSelectionListener(actionSyncFocus)));
}
@ -689,6 +696,22 @@ public class DebuggerThreadsProvider extends ComponentProviderAdapter {
traceManager.setSynchronizeFocus(enabled);
}
private void activatedGoToTime() {
InputDialog dialog =
new InputDialog("Go To Time", "Schedule:", current.getTime().toString());
tool.showDialog(dialog);
if (dialog.isCanceled()) {
return;
}
try {
TraceSchedule time = TraceSchedule.parse(dialog.getValue());
traceManager.activateTime(time);
}
catch (IllegalArgumentException e) {
Msg.showError(this, getComponent(), "Go To Time", "Could not parse schedule");
}
}
private void traceTabSelected(ListSelectionEvent e) {
if (e.getValueIsAdjusting()) {
return;

View file

@ -24,19 +24,18 @@ import ghidra.framework.options.SaveState;
import ghidra.framework.plugintool.*;
import ghidra.framework.plugintool.util.PluginStatus;
@PluginInfo( //
shortDescription = "Lists recorded snapshots in a trace", //
description = "Provides the component which lists snapshots and allows navigation", //
category = PluginCategoryNames.DEBUGGER, //
packageName = DebuggerPluginPackage.NAME, //
status = PluginStatus.RELEASED, //
eventsConsumed = { //
TraceActivatedPluginEvent.class //
}, //
servicesRequired = { //
DebuggerTraceManagerService.class //
} //
)
@PluginInfo(
shortDescription = "Lists recorded snapshots in a trace",
description = "Provides the component which lists snapshots and allows navigation",
category = PluginCategoryNames.DEBUGGER,
packageName = DebuggerPluginPackage.NAME,
status = PluginStatus.RELEASED,
eventsConsumed = {
TraceActivatedPluginEvent.class
},
servicesRequired = {
DebuggerTraceManagerService.class
})
public class DebuggerTimePlugin extends AbstractDebuggerPlugin {
protected DebuggerTimeProvider provider;

View file

@ -38,6 +38,7 @@ import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
import ghidra.app.plugin.core.debug.gui.DebuggerResources.*;
import ghidra.app.services.DebuggerListingService;
import ghidra.app.services.DebuggerTraceManagerService;
import ghidra.async.AsyncDebouncer;
import ghidra.async.AsyncTimer;
import ghidra.base.widgets.table.DataTypeTableCellEditor;
@ -60,6 +61,7 @@ import ghidra.program.util.ProgramSelection;
import ghidra.trace.model.*;
import ghidra.trace.model.Trace.TraceMemoryBytesChangeType;
import ghidra.trace.model.Trace.TraceMemoryStateChangeType;
import ghidra.trace.model.time.schedule.TraceSchedule;
import ghidra.trace.util.TraceAddressSpace;
import ghidra.util.Msg;
import ghidra.util.Swing;
@ -237,8 +239,10 @@ public class DebuggerWatchesProvider extends ComponentProviderAdapter {
private Trace currentTrace; // Copy for transition
@AutoServiceConsumed
private DebuggerListingService listingService; // TODO: For goto and selection
private DebuggerListingService listingService; // For goto and selection
// TODO: Allow address marking
@AutoServiceConsumed
private DebuggerTraceManagerService traceManager; // For goto time (emu mods)
@SuppressWarnings("unused")
private final AutoService.Wiring autoServiceWiring;
@ -639,4 +643,8 @@ public class DebuggerWatchesProvider extends ComponentProviderAdapter {
public boolean isEditsEnabled() {
return actionEnableEdits.isSelected();
}
public void goToTime(TraceSchedule time) {
traceManager.activateTime(time);
}
}

View file

@ -16,7 +16,6 @@
package ghidra.app.plugin.core.debug.gui.watch;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.Objects;
@ -33,14 +32,15 @@ import ghidra.pcode.utils.Utils;
import ghidra.program.model.address.*;
import ghidra.program.model.data.DataType;
import ghidra.program.model.lang.Language;
import ghidra.program.model.lang.Register;
import ghidra.program.model.mem.ByteMemBufferImpl;
import ghidra.program.model.mem.MemBuffer;
import ghidra.trace.model.Trace;
import ghidra.trace.model.memory.TraceMemorySpace;
import ghidra.trace.model.memory.TraceMemoryState;
import ghidra.trace.model.thread.TraceThread;
import ghidra.trace.model.time.schedule.TraceSchedule;
import ghidra.util.*;
import ghidra.util.database.UndoableTransaction;
public class WatchRow {
public static final int TRUNCATE_BYTES_LENGTH = 64;
@ -415,7 +415,7 @@ public class WatchRow {
return;
}
try (UndoableTransaction tid =
/*try (UndoableTransaction tid =
UndoableTransaction.start(trace, "Write watch at " + address, true)) {
final TraceMemorySpace space;
if (address.isRegisterAddress()) {
@ -427,7 +427,27 @@ public class WatchRow {
space = trace.getMemoryManager().getMemorySpace(address.getAddressSpace(), true);
}
space.putBytes(coordinates.getViewSnap(), address, ByteBuffer.wrap(bytes));
}*/
TraceSchedule time =
coordinates.getTime().patched(coordinates.getThread(), generateSleigh(bytes));
provider.goToTime(time);
}
protected String generateSleigh(byte[] bytes) {
BigInteger value = Utils.bytesToBigInteger(bytes, bytes.length,
trace.getBaseLanguage().isBigEndian(), false);
if (address.isMemoryAddress()) {
AddressSpace space = address.getAddressSpace();
return String.format("*[%s]:%d 0x%s:%d=0x%s",
space.getName(), bytes.length,
address.getOffsetAsBigInteger().toString(16), space.getPointerSize(),
value.toString(16));
}
Register register = trace.getBaseLanguage().getRegister(address, bytes.length);
if (register == null) {
throw new AssertionError("Can only modify memory or register");
}
return String.format("%s=0x%s", register, value.toString(16));
}
public int getValueLength() {

View file

@ -45,9 +45,9 @@ import ghidra.program.util.ProgramLocation;
import ghidra.trace.model.*;
import ghidra.trace.model.program.TraceProgramView;
import ghidra.trace.model.thread.TraceThread;
import ghidra.trace.model.time.TraceSchedule;
import ghidra.trace.model.time.TraceSchedule.CompareResult;
import ghidra.trace.model.time.TraceSnapshot;
import ghidra.trace.model.time.schedule.CompareResult;
import ghidra.trace.model.time.schedule.TraceSchedule;
import ghidra.util.Msg;
import ghidra.util.database.UndoableTransaction;
import ghidra.util.exception.CancelledException;

View file

@ -53,8 +53,8 @@ import ghidra.trace.model.program.TraceProgramView;
import ghidra.trace.model.program.TraceVariableSnapProgramView;
import ghidra.trace.model.stack.TraceStackFrame;
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.util.*;
import ghidra.util.datastruct.CollectionChangeListener;
import ghidra.util.exception.*;
@ -693,7 +693,11 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
}
varView.setSnap(emuSnap);
fireLocationEvent(coordinates);
}));
})).exceptionally(ex -> {
Msg.showError(this, null, "Emulate", "Could not navigate to emulated coordinates",
ex);
return null;
});
}
}

View file

@ -21,7 +21,7 @@ import ghidra.app.plugin.core.debug.service.emulation.DebuggerEmulationServicePl
import ghidra.app.plugin.core.debug.service.emulation.DebuggerTracePcodeEmulator;
import ghidra.framework.plugintool.ServiceInfo;
import ghidra.trace.model.Trace;
import ghidra.trace.model.time.TraceSchedule;
import ghidra.trace.model.time.schedule.TraceSchedule;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;

View file

@ -25,7 +25,7 @@ import ghidra.framework.plugintool.ServiceInfo;
import ghidra.trace.model.Trace;
import ghidra.trace.model.program.TraceProgramView;
import ghidra.trace.model.thread.TraceThread;
import ghidra.trace.model.time.TraceSchedule;
import ghidra.trace.model.time.schedule.TraceSchedule;
import ghidra.util.TriConsumer;
@ServiceInfo(defaultProvider = DebuggerTraceManagerServicePlugin.class)

View file

@ -29,7 +29,7 @@ import ghidra.test.ToyProgramBuilder;
import ghidra.trace.database.ToyDBTraceBuilder;
import ghidra.trace.model.memory.TraceMemoryFlag;
import ghidra.trace.model.thread.TraceThread;
import ghidra.trace.model.time.TraceSchedule;
import ghidra.trace.model.time.schedule.TraceSchedule;
import ghidra.util.database.UndoableTransaction;
import help.screenshot.GhidraScreenShotGenerator;

View file

@ -22,8 +22,8 @@ import ghidra.app.services.DebuggerTraceManagerService;
import ghidra.test.ToyProgramBuilder;
import ghidra.trace.database.ToyDBTraceBuilder;
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.util.database.UndoableTransaction;
import help.screenshot.GhidraScreenShotGenerator;

View file

@ -26,8 +26,8 @@ import org.junit.Test;
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest;
import ghidra.trace.database.time.DBTraceTimeManager;
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.util.database.UndoableTransaction;
public class DebuggerTimeProviderTest extends AbstractGhidraHeadedDebuggerGUITest {

View file

@ -292,39 +292,49 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerGUI
@Test
public void testDeadEditRegister() {
WatchRow row = prepareTestDeadEdit("r0");
row.setRawValueString("0x1234");
waitForSwing();
TraceMemoryRegisterSpace regVals =
tb.trace.getMemoryManager().getMemoryRegisterSpace(thread, false);
assertEquals(BigInteger.valueOf(0x1234), regVals.getValue(0, r0).getUnsignedValue());
row.setRawValueString("1234");
waitForSwing();
row.setRawValueString("0x1234");
waitForPass(() -> {
long viewSnap = traceManager.getCurrent().getViewSnap();
assertEquals(BigInteger.valueOf(0x1234),
regVals.getValue(viewSnap, r0).getUnsignedValue());
assertEquals("0x1234", row.getRawValueString());
});
assertEquals(BigInteger.valueOf(1234), regVals.getValue(0, r0).getUnsignedValue());
row.setRawValueString("1234"); // Decimal this time
waitForPass(() -> {
long viewSnap = traceManager.getCurrent().getViewSnap();
assertEquals(BigInteger.valueOf(1234),
regVals.getValue(viewSnap, r0).getUnsignedValue());
assertEquals("0x4d2", row.getRawValueString());
});
}
@Test
public void testDeadEditMemory() {
WatchRow row = prepareTestDeadEdit("*:8 r0");
row.setRawValueString("0x1234");
waitForSwing();
TraceMemoryOperations mem = tb.trace.getMemoryManager();
ByteBuffer buf = ByteBuffer.allocate(8);
mem.getBytes(0, tb.addr(0x00400000), buf);
buf.flip();
assertEquals(0x1234, buf.getLong());
row.setRawValueString("0x1234");
waitForPass(() -> {
long viewSnap = traceManager.getCurrent().getViewSnap();
buf.clear();
mem.getBytes(viewSnap, tb.addr(0x00400000), buf);
buf.flip();
assertEquals(0x1234, buf.getLong());
});
row.setRawValueString("{ 12 34 56 78 9a bc de f0 }");
waitForSwing();
buf.clear();
mem.getBytes(0, tb.addr(0x00400000), buf);
buf.flip();
assertEquals(0x123456789abcdef0L, buf.getLong());
waitForPass(() -> {
long viewSnap = traceManager.getCurrent().getViewSnap();
buf.clear();
mem.getBytes(viewSnap, tb.addr(0x00400000), buf);
buf.flip();
assertEquals(0x123456789abcdef0L, buf.getLong());
});
}
protected WatchRow prepareTestLiveEdit(String expression) throws Exception {
@ -379,17 +389,18 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerGUI
public void testLiveEditNonMappableRegister() throws Throwable {
WatchRow row = prepareTestLiveEdit("r1");
TraceThread thread = recorder.getTraceThread(mb.testThread1);
// Sanity check
assertFalse(recorder.isRegisterOnTarget(thread, r1));
row.setRawValueString("0x1234");
waitForSwing();
TraceMemoryRegisterSpace regs =
recorder.getTrace().getMemoryManager().getMemoryRegisterSpace(thread, false);
assertEquals(BigInteger.valueOf(0x1234),
regs.getValue(recorder.getSnap(), r1).getUnsignedValue());
waitForPass(() -> {
TraceMemoryRegisterSpace regs =
recorder.getTrace().getMemoryManager().getMemoryRegisterSpace(thread, false);
assertNotNull(regs);
long viewSnap = traceManager.getCurrent().getViewSnap();
assertEquals(BigInteger.valueOf(0x1234),
regs.getValue(viewSnap, r1).getUnsignedValue());
});
assertFalse(bank.regVals.containsKey("r1"));
}

View file

@ -35,7 +35,7 @@ import ghidra.program.model.mem.MemoryBlock;
import ghidra.trace.model.Trace;
import ghidra.trace.model.memory.TraceMemoryRegisterSpace;
import ghidra.trace.model.thread.TraceThread;
import ghidra.trace.model.time.TraceSchedule;
import ghidra.trace.model.time.schedule.TraceSchedule;
import ghidra.util.database.UndoableTransaction;
import ghidra.util.task.TaskMonitor;

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 {