mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-05 10:49:34 +02:00
GP-1486: Patching machine state better integrated into Emulation.
This commit is contained in:
parent
5c0f06ab8d
commit
9386d6fc67
35 changed files with 1819 additions and 1205 deletions
|
@ -22,10 +22,10 @@
|
||||||
</TBODY>
|
</TBODY>
|
||||||
</TABLE>
|
</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
|
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
|
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
|
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.
|
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
|
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
|
interact with the target, stepping at the p-code level implies you are no longer "at the
|
||||||
present."</P>
|
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
|
<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>
|
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
|
<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.
|
the next p-code tick, using emulation. Note that emulation does not affect the target.
|
||||||
|
|
|
@ -109,14 +109,14 @@
|
||||||
trace forward to the next snapshot, causing most windows to display the recorded data from the
|
trace forward to the next snapshot, causing most windows to display the recorded data from the
|
||||||
new point in time.</P>
|
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>
|
Tick Backward</H3>
|
||||||
|
|
||||||
<P>This action is available when the current point in time includes emulated steps. It steps
|
<P>This action is available when the current point in time includes emulated steps. It steps
|
||||||
the trace backward to the previous tick.</P>
|
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
|
<H3><A name="emu_trace_tick_forward"></A><IMG alt="" src="images/stepinto.png">Emulate Trace
|
||||||
Forward</H3>
|
Tick Forward</H3>
|
||||||
|
|
||||||
<P>This action is available when a thread is selected. It steps the current thread forward to
|
<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,
|
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
|
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>
|
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>
|
<H2>Other Actions</H2>
|
||||||
|
|
||||||
<H3><A name="sync_focus"></A>Synchronize Trace and Target Focus</H3>
|
<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 |
|
@ -62,9 +62,9 @@
|
||||||
|
|
||||||
<LI>Value - the raw bytes of the watched buffer. If the expression is a register, then this
|
<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 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
|
is on. Changes are sent to the target if the trace is live and "at the present." Otherwise,
|
||||||
has changed since the last navigation event, this cell is rendered in <FONT color=
|
the change is materialized via emulation. If the value has changed since the last navigation
|
||||||
"red">red</FONT>.</LI>
|
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
|
<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
|
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
|
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
|
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
|
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>
|
</UL>
|
||||||
|
|
||||||
<H2>Actions</H2>
|
<H2>Actions</H2>
|
||||||
|
@ -123,11 +123,15 @@
|
||||||
|
|
||||||
<H3><A name="enable_edits"></A>Enable Edits</H3>
|
<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,
|
<P>This toggle is a write protector for target machine state. To modify a watch's value, this
|
||||||
this toggle must be enabled. Editing a value when the trace is live and "at the present" will
|
toggle must be enabled. Editing a value when the trace is live and "at the present" will cause
|
||||||
cause the value to be modified on the target. Editing historical and/or emulated values is
|
the value to be modified on the target. Editing emulated values is permitted, but it has no
|
||||||
permitted, but it has no effect on the target. Note that only the raw "Value" column can be
|
effect on the target. Editing historical values is not permitted. All edits to non-live trace
|
||||||
edited directly. The "Repr" column cannot be edited, yet.</P>
|
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>
|
<H2><A name="colors"></A>Tool Options: Colors</H2>
|
||||||
|
|
||||||
|
|
|
@ -31,8 +31,8 @@ import ghidra.trace.database.DBTraceContentHandler;
|
||||||
import ghidra.trace.model.Trace;
|
import ghidra.trace.model.Trace;
|
||||||
import ghidra.trace.model.program.TraceProgramView;
|
import ghidra.trace.model.program.TraceProgramView;
|
||||||
import ghidra.trace.model.thread.TraceThread;
|
import ghidra.trace.model.thread.TraceThread;
|
||||||
import ghidra.trace.model.time.TraceSchedule;
|
|
||||||
import ghidra.trace.model.time.TraceSnapshot;
|
import ghidra.trace.model.time.TraceSnapshot;
|
||||||
|
import ghidra.trace.model.time.schedule.TraceSchedule;
|
||||||
import ghidra.trace.util.DefaultTraceTimeViewport;
|
import ghidra.trace.util.DefaultTraceTimeViewport;
|
||||||
import ghidra.trace.util.TraceTimeViewport;
|
import ghidra.trace.util.TraceTimeViewport;
|
||||||
import ghidra.util.Msg;
|
import ghidra.util.Msg;
|
||||||
|
@ -259,8 +259,10 @@ public class DebuggerCoordinates {
|
||||||
Collection<? extends TraceSnapshot> snapshots =
|
Collection<? extends TraceSnapshot> snapshots =
|
||||||
trace.getTimeManager().getSnapshotsWithSchedule(time);
|
trace.getTimeManager().getSnapshotsWithSchedule(time);
|
||||||
if (snapshots.isEmpty()) {
|
if (snapshots.isEmpty()) {
|
||||||
Msg.warn(this, "Seems the emulation service did not create the requested snapshot");
|
Msg.warn(this,
|
||||||
return viewSnap = time.getSnap();
|
"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();
|
return viewSnap = snapshots.iterator().next().getKey();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1468,24 +1468,24 @@ public interface DebuggerResources {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract class AbstractStepTickForwardAction extends DockingAction {
|
abstract class AbstractEmulateTickForwardAction extends DockingAction {
|
||||||
public static final String NAME = "Step Trace Tick Forward";
|
public static final String NAME = "Emulate Trace Tick Forward";
|
||||||
public static final Icon ICON = ICON_STEP_INTO;
|
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());
|
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));
|
setHelpLocation(new HelpLocation(owner.getName(), HELP_ANCHOR));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
interface StepPcodeForwardAction {
|
interface EmulatePcodeForwardAction {
|
||||||
String NAME = "Step Trace p-code Forward";
|
String NAME = "Emulate Trace p-code Forward";
|
||||||
String DESCRIPTION = "Navigate the recording forward one p-code tick";
|
String DESCRIPTION = "Navigate the recording forward one p-code tick";
|
||||||
Icon ICON = ICON_STEP_INTO;
|
Icon ICON = ICON_STEP_INTO;
|
||||||
String GROUP = GROUP_CONTROL;
|
String GROUP = GROUP_CONTROL;
|
||||||
String HELP_ANCHOR = "step_trace_pcode_forward";
|
String HELP_ANCHOR = "emu_trace_pcode_forward";
|
||||||
|
|
||||||
static ActionBuilder builder(Plugin owner) {
|
static ActionBuilder builder(Plugin owner) {
|
||||||
String ownerName = owner.getName();
|
String ownerName = owner.getName();
|
||||||
|
@ -1497,14 +1497,14 @@ public interface DebuggerResources {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract class AbstractStepTickBackwardAction extends DockingAction {
|
abstract class AbstractEmulateTickBackwardAction extends DockingAction {
|
||||||
public static final String NAME = "Step Trace Tick Backward";
|
public static final String NAME = "Emulate Trace Tick Backward";
|
||||||
public static final Icon ICON = ICON_STEP_BACK;
|
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());
|
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));
|
setHelpLocation(new HelpLocation(owner.getName(), HELP_ANCHOR));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1521,12 +1521,12 @@ public interface DebuggerResources {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
interface StepPcodeBackwardAction {
|
interface EmulatePcodeBackwardAction {
|
||||||
String NAME = "Step Trace p-code Backward";
|
String NAME = "Emulate Trace p-code Backward";
|
||||||
String DESCRIPTION = "Navigate the recording backward one p-code tick";
|
String DESCRIPTION = "Navigate the recording backward one p-code tick";
|
||||||
Icon ICON = ICON_STEP_BACK;
|
Icon ICON = ICON_STEP_BACK;
|
||||||
String GROUP = GROUP_CONTROL;
|
String GROUP = GROUP_CONTROL;
|
||||||
String HELP_ANCHOR = "step_trace_pcode_backward";
|
String HELP_ANCHOR = "emu_trace_pcode_backward";
|
||||||
|
|
||||||
static ActionBuilder builder(Plugin owner) {
|
static ActionBuilder builder(Plugin owner) {
|
||||||
String ownerName = owner.getName();
|
String ownerName = owner.getName();
|
||||||
|
@ -1553,15 +1553,30 @@ public interface DebuggerResources {
|
||||||
interface SynchronizeFocusAction {
|
interface SynchronizeFocusAction {
|
||||||
String NAME = "Synchronize Focus";
|
String NAME = "Synchronize Focus";
|
||||||
String DESCRIPTION = "Synchronize trace activation with debugger focus/select";
|
String DESCRIPTION = "Synchronize trace activation with debugger focus/select";
|
||||||
String GROUP = "zz";
|
|
||||||
Icon ICON = ICON_SYNC;
|
Icon ICON = ICON_SYNC;
|
||||||
String HELP_ANCHOR = "sync_focus";
|
String HELP_ANCHOR = "sync_focus";
|
||||||
|
|
||||||
static ToggleActionBuilder builder(Plugin owner) {
|
static ToggleActionBuilder builder(Plugin owner) {
|
||||||
String ownerName = owner.getName();
|
String ownerName = owner.getName();
|
||||||
return new ToggleActionBuilder(NAME, ownerName).description(DESCRIPTION)
|
return new ToggleActionBuilder(NAME, ownerName).description(DESCRIPTION)
|
||||||
.toolBarGroup(GROUP)
|
.menuPath(NAME)
|
||||||
.toolBarIcon(ICON)
|
.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));
|
.helpLocation(new HelpLocation(ownerName, HELP_ANCHOR));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -55,7 +55,7 @@ import ghidra.program.model.lang.Language;
|
||||||
import ghidra.program.model.pcode.PcodeOp;
|
import ghidra.program.model.pcode.PcodeOp;
|
||||||
import ghidra.program.model.pcode.Varnode;
|
import ghidra.program.model.pcode.Varnode;
|
||||||
import ghidra.trace.model.Trace;
|
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.ColorUtils;
|
||||||
import ghidra.util.HTMLUtilities;
|
import ghidra.util.HTMLUtilities;
|
||||||
import ghidra.util.database.UndoableTransaction;
|
import ghidra.util.database.UndoableTransaction;
|
||||||
|
@ -507,11 +507,11 @@ public class DebuggerPcodeStepperProvider extends ComponentProviderAdapter {
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void createActions() {
|
protected void createActions() {
|
||||||
actionStepBackward = DebuggerResources.StepPcodeBackwardAction.builder(plugin)
|
actionStepBackward = DebuggerResources.EmulatePcodeBackwardAction.builder(plugin)
|
||||||
.enabledWhen(c -> current.getTrace() != null && current.getTime().pTickCount() != 0)
|
.enabledWhen(c -> current.getTrace() != null && current.getTime().pTickCount() != 0)
|
||||||
.onAction(c -> stepBackwardActivated())
|
.onAction(c -> stepBackwardActivated())
|
||||||
.buildAndInstallLocal(this);
|
.buildAndInstallLocal(this);
|
||||||
actionStepForward = DebuggerResources.StepPcodeForwardAction.builder(plugin)
|
actionStepForward = DebuggerResources.EmulatePcodeForwardAction.builder(plugin)
|
||||||
.enabledWhen(
|
.enabledWhen(
|
||||||
c -> current.getThread() != null)
|
c -> current.getThread() != null)
|
||||||
.onAction(c -> stepForwardActivated())
|
.onAction(c -> stepForwardActivated())
|
||||||
|
|
|
@ -33,6 +33,7 @@ import docking.WindowPosition;
|
||||||
import docking.action.*;
|
import docking.action.*;
|
||||||
import docking.widgets.HorizontalTabPanel;
|
import docking.widgets.HorizontalTabPanel;
|
||||||
import docking.widgets.HorizontalTabPanel.TabListCellRenderer;
|
import docking.widgets.HorizontalTabPanel.TabListCellRenderer;
|
||||||
|
import docking.widgets.dialogs.InputDialog;
|
||||||
import docking.widgets.table.*;
|
import docking.widgets.table.*;
|
||||||
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
|
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
|
||||||
import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
|
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.TraceDomainObjectListener;
|
||||||
import ghidra.trace.model.thread.TraceThread;
|
import ghidra.trace.model.thread.TraceThread;
|
||||||
import ghidra.trace.model.thread.TraceThreadManager;
|
import ghidra.trace.model.thread.TraceThreadManager;
|
||||||
import ghidra.trace.model.time.TraceSchedule;
|
|
||||||
import ghidra.trace.model.time.TraceSnapshot;
|
import ghidra.trace.model.time.TraceSnapshot;
|
||||||
|
import ghidra.trace.model.time.schedule.TraceSchedule;
|
||||||
|
import ghidra.util.Msg;
|
||||||
import ghidra.util.Swing;
|
import ghidra.util.Swing;
|
||||||
import ghidra.util.database.ObjectKey;
|
import ghidra.util.database.ObjectKey;
|
||||||
import ghidra.util.datastruct.CollectionChangeListener;
|
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 static final String GROUP = DebuggerResources.GROUP_CONTROL;
|
||||||
|
|
||||||
public StepTickBackwardAction() {
|
public EmulateTickBackwardAction() {
|
||||||
super(plugin);
|
super(plugin);
|
||||||
setToolBarData(new ToolBarData(ICON, GROUP, "2"));
|
setToolBarData(new ToolBarData(ICON, GROUP, "2"));
|
||||||
addLocalAction(this);
|
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 static final String GROUP = DebuggerResources.GROUP_CONTROL;
|
||||||
|
|
||||||
public StepTickForwardAction() {
|
public EmulateTickForwardAction() {
|
||||||
super(plugin);
|
super(plugin);
|
||||||
setToolBarData(new ToolBarData(ICON, GROUP, "3"));
|
setToolBarData(new ToolBarData(ICON, GROUP, "3"));
|
||||||
addLocalAction(this);
|
addLocalAction(this);
|
||||||
|
@ -353,11 +355,12 @@ public class DebuggerThreadsProvider extends ComponentProviderAdapter {
|
||||||
|
|
||||||
DockingAction actionSaveTrace;
|
DockingAction actionSaveTrace;
|
||||||
StepSnapBackwardAction actionStepSnapBackward;
|
StepSnapBackwardAction actionStepSnapBackward;
|
||||||
StepTickBackwardAction actionStepTickBackward;
|
EmulateTickBackwardAction actionEmulateTickBackward;
|
||||||
StepTickForwardAction actionStepTickForward;
|
EmulateTickForwardAction actionEmulateTickForward;
|
||||||
StepSnapForwardAction actionStepSnapForward;
|
StepSnapForwardAction actionStepSnapForward;
|
||||||
SeekTracePresentAction actionSeekTracePresent;
|
SeekTracePresentAction actionSeekTracePresent;
|
||||||
ToggleDockingAction actionSyncFocus;
|
ToggleDockingAction actionSyncFocus;
|
||||||
|
DockingAction actionGoToTime;
|
||||||
Set<Object> strongRefs = new HashSet<>(); // Eww
|
Set<Object> strongRefs = new HashSet<>(); // Eww
|
||||||
|
|
||||||
public DebuggerThreadsProvider(final DebuggerThreadsPlugin plugin) {
|
public DebuggerThreadsProvider(final DebuggerThreadsPlugin plugin) {
|
||||||
|
@ -669,8 +672,8 @@ public class DebuggerThreadsProvider extends ComponentProviderAdapter {
|
||||||
protected void createActions() {
|
protected void createActions() {
|
||||||
// TODO: Make other actions use builder?
|
// TODO: Make other actions use builder?
|
||||||
actionStepSnapBackward = new StepSnapBackwardAction();
|
actionStepSnapBackward = new StepSnapBackwardAction();
|
||||||
actionStepTickBackward = new StepTickBackwardAction();
|
actionEmulateTickBackward = new EmulateTickBackwardAction();
|
||||||
actionStepTickForward = new StepTickForwardAction();
|
actionEmulateTickForward = new EmulateTickForwardAction();
|
||||||
actionStepSnapForward = new StepSnapForwardAction();
|
actionStepSnapForward = new StepSnapForwardAction();
|
||||||
actionSeekTracePresent = new SeekTracePresentAction();
|
actionSeekTracePresent = new SeekTracePresentAction();
|
||||||
actionSyncFocus = SynchronizeFocusAction.builder(plugin)
|
actionSyncFocus = SynchronizeFocusAction.builder(plugin)
|
||||||
|
@ -678,6 +681,10 @@ public class DebuggerThreadsProvider extends ComponentProviderAdapter {
|
||||||
.enabledWhen(c -> traceManager != null)
|
.enabledWhen(c -> traceManager != null)
|
||||||
.onAction(c -> toggleSyncFocus(actionSyncFocus.isSelected()))
|
.onAction(c -> toggleSyncFocus(actionSyncFocus.isSelected()))
|
||||||
.buildAndInstallLocal(this);
|
.buildAndInstallLocal(this);
|
||||||
|
actionGoToTime = GoToTimeAction.builder(plugin)
|
||||||
|
.enabledWhen(c -> current.getTrace() != null)
|
||||||
|
.onAction(c -> activatedGoToTime())
|
||||||
|
.buildAndInstallLocal(this);
|
||||||
traceManager.addSynchronizeFocusChangeListener(
|
traceManager.addSynchronizeFocusChangeListener(
|
||||||
strongRef(new ToToggleSelectionListener(actionSyncFocus)));
|
strongRef(new ToToggleSelectionListener(actionSyncFocus)));
|
||||||
}
|
}
|
||||||
|
@ -689,6 +696,22 @@ public class DebuggerThreadsProvider extends ComponentProviderAdapter {
|
||||||
traceManager.setSynchronizeFocus(enabled);
|
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) {
|
private void traceTabSelected(ListSelectionEvent e) {
|
||||||
if (e.getValueIsAdjusting()) {
|
if (e.getValueIsAdjusting()) {
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -24,19 +24,18 @@ import ghidra.framework.options.SaveState;
|
||||||
import ghidra.framework.plugintool.*;
|
import ghidra.framework.plugintool.*;
|
||||||
import ghidra.framework.plugintool.util.PluginStatus;
|
import ghidra.framework.plugintool.util.PluginStatus;
|
||||||
|
|
||||||
@PluginInfo( //
|
@PluginInfo(
|
||||||
shortDescription = "Lists recorded snapshots in a trace", //
|
shortDescription = "Lists recorded snapshots in a trace",
|
||||||
description = "Provides the component which lists snapshots and allows navigation", //
|
description = "Provides the component which lists snapshots and allows navigation",
|
||||||
category = PluginCategoryNames.DEBUGGER, //
|
category = PluginCategoryNames.DEBUGGER,
|
||||||
packageName = DebuggerPluginPackage.NAME, //
|
packageName = DebuggerPluginPackage.NAME,
|
||||||
status = PluginStatus.RELEASED, //
|
status = PluginStatus.RELEASED,
|
||||||
eventsConsumed = { //
|
eventsConsumed = {
|
||||||
TraceActivatedPluginEvent.class //
|
TraceActivatedPluginEvent.class
|
||||||
}, //
|
},
|
||||||
servicesRequired = { //
|
servicesRequired = {
|
||||||
DebuggerTraceManagerService.class //
|
DebuggerTraceManagerService.class
|
||||||
} //
|
})
|
||||||
)
|
|
||||||
public class DebuggerTimePlugin extends AbstractDebuggerPlugin {
|
public class DebuggerTimePlugin extends AbstractDebuggerPlugin {
|
||||||
protected DebuggerTimeProvider provider;
|
protected DebuggerTimeProvider provider;
|
||||||
|
|
||||||
|
|
|
@ -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.plugin.core.debug.gui.DebuggerResources.*;
|
import ghidra.app.plugin.core.debug.gui.DebuggerResources.*;
|
||||||
import ghidra.app.services.DebuggerListingService;
|
import ghidra.app.services.DebuggerListingService;
|
||||||
|
import ghidra.app.services.DebuggerTraceManagerService;
|
||||||
import ghidra.async.AsyncDebouncer;
|
import ghidra.async.AsyncDebouncer;
|
||||||
import ghidra.async.AsyncTimer;
|
import ghidra.async.AsyncTimer;
|
||||||
import ghidra.base.widgets.table.DataTypeTableCellEditor;
|
import ghidra.base.widgets.table.DataTypeTableCellEditor;
|
||||||
|
@ -60,6 +61,7 @@ import ghidra.program.util.ProgramSelection;
|
||||||
import ghidra.trace.model.*;
|
import ghidra.trace.model.*;
|
||||||
import ghidra.trace.model.Trace.TraceMemoryBytesChangeType;
|
import ghidra.trace.model.Trace.TraceMemoryBytesChangeType;
|
||||||
import ghidra.trace.model.Trace.TraceMemoryStateChangeType;
|
import ghidra.trace.model.Trace.TraceMemoryStateChangeType;
|
||||||
|
import ghidra.trace.model.time.schedule.TraceSchedule;
|
||||||
import ghidra.trace.util.TraceAddressSpace;
|
import ghidra.trace.util.TraceAddressSpace;
|
||||||
import ghidra.util.Msg;
|
import ghidra.util.Msg;
|
||||||
import ghidra.util.Swing;
|
import ghidra.util.Swing;
|
||||||
|
@ -237,8 +239,10 @@ public class DebuggerWatchesProvider extends ComponentProviderAdapter {
|
||||||
private Trace currentTrace; // Copy for transition
|
private Trace currentTrace; // Copy for transition
|
||||||
|
|
||||||
@AutoServiceConsumed
|
@AutoServiceConsumed
|
||||||
private DebuggerListingService listingService; // TODO: For goto and selection
|
private DebuggerListingService listingService; // For goto and selection
|
||||||
// TODO: Allow address marking
|
// TODO: Allow address marking
|
||||||
|
@AutoServiceConsumed
|
||||||
|
private DebuggerTraceManagerService traceManager; // For goto time (emu mods)
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
private final AutoService.Wiring autoServiceWiring;
|
private final AutoService.Wiring autoServiceWiring;
|
||||||
|
|
||||||
|
@ -639,4 +643,8 @@ public class DebuggerWatchesProvider extends ComponentProviderAdapter {
|
||||||
public boolean isEditsEnabled() {
|
public boolean isEditsEnabled() {
|
||||||
return actionEnableEdits.isSelected();
|
return actionEnableEdits.isSelected();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void goToTime(TraceSchedule time) {
|
||||||
|
traceManager.activateTime(time);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,6 @@
|
||||||
package ghidra.app.plugin.core.debug.gui.watch;
|
package ghidra.app.plugin.core.debug.gui.watch;
|
||||||
|
|
||||||
import java.math.BigInteger;
|
import java.math.BigInteger;
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
|
@ -33,14 +32,15 @@ import ghidra.pcode.utils.Utils;
|
||||||
import ghidra.program.model.address.*;
|
import ghidra.program.model.address.*;
|
||||||
import ghidra.program.model.data.DataType;
|
import ghidra.program.model.data.DataType;
|
||||||
import ghidra.program.model.lang.Language;
|
import ghidra.program.model.lang.Language;
|
||||||
|
import ghidra.program.model.lang.Register;
|
||||||
import ghidra.program.model.mem.ByteMemBufferImpl;
|
import ghidra.program.model.mem.ByteMemBufferImpl;
|
||||||
import ghidra.program.model.mem.MemBuffer;
|
import ghidra.program.model.mem.MemBuffer;
|
||||||
import ghidra.trace.model.Trace;
|
import ghidra.trace.model.Trace;
|
||||||
import ghidra.trace.model.memory.TraceMemorySpace;
|
import ghidra.trace.model.memory.TraceMemorySpace;
|
||||||
import ghidra.trace.model.memory.TraceMemoryState;
|
import ghidra.trace.model.memory.TraceMemoryState;
|
||||||
import ghidra.trace.model.thread.TraceThread;
|
import ghidra.trace.model.thread.TraceThread;
|
||||||
|
import ghidra.trace.model.time.schedule.TraceSchedule;
|
||||||
import ghidra.util.*;
|
import ghidra.util.*;
|
||||||
import ghidra.util.database.UndoableTransaction;
|
|
||||||
|
|
||||||
public class WatchRow {
|
public class WatchRow {
|
||||||
public static final int TRUNCATE_BYTES_LENGTH = 64;
|
public static final int TRUNCATE_BYTES_LENGTH = 64;
|
||||||
|
@ -415,7 +415,7 @@ public class WatchRow {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try (UndoableTransaction tid =
|
/*try (UndoableTransaction tid =
|
||||||
UndoableTransaction.start(trace, "Write watch at " + address, true)) {
|
UndoableTransaction.start(trace, "Write watch at " + address, true)) {
|
||||||
final TraceMemorySpace space;
|
final TraceMemorySpace space;
|
||||||
if (address.isRegisterAddress()) {
|
if (address.isRegisterAddress()) {
|
||||||
|
@ -427,7 +427,27 @@ public class WatchRow {
|
||||||
space = trace.getMemoryManager().getMemorySpace(address.getAddressSpace(), true);
|
space = trace.getMemoryManager().getMemorySpace(address.getAddressSpace(), true);
|
||||||
}
|
}
|
||||||
space.putBytes(coordinates.getViewSnap(), address, ByteBuffer.wrap(bytes));
|
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() {
|
public int getValueLength() {
|
||||||
|
|
|
@ -45,9 +45,9 @@ import ghidra.program.util.ProgramLocation;
|
||||||
import ghidra.trace.model.*;
|
import ghidra.trace.model.*;
|
||||||
import ghidra.trace.model.program.TraceProgramView;
|
import ghidra.trace.model.program.TraceProgramView;
|
||||||
import ghidra.trace.model.thread.TraceThread;
|
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.TraceSnapshot;
|
||||||
|
import ghidra.trace.model.time.schedule.CompareResult;
|
||||||
|
import ghidra.trace.model.time.schedule.TraceSchedule;
|
||||||
import ghidra.util.Msg;
|
import ghidra.util.Msg;
|
||||||
import ghidra.util.database.UndoableTransaction;
|
import ghidra.util.database.UndoableTransaction;
|
||||||
import ghidra.util.exception.CancelledException;
|
import ghidra.util.exception.CancelledException;
|
||||||
|
|
|
@ -53,8 +53,8 @@ import ghidra.trace.model.program.TraceProgramView;
|
||||||
import ghidra.trace.model.program.TraceVariableSnapProgramView;
|
import ghidra.trace.model.program.TraceVariableSnapProgramView;
|
||||||
import ghidra.trace.model.stack.TraceStackFrame;
|
import ghidra.trace.model.stack.TraceStackFrame;
|
||||||
import ghidra.trace.model.thread.TraceThread;
|
import ghidra.trace.model.thread.TraceThread;
|
||||||
import ghidra.trace.model.time.TraceSchedule;
|
|
||||||
import ghidra.trace.model.time.TraceSnapshot;
|
import ghidra.trace.model.time.TraceSnapshot;
|
||||||
|
import ghidra.trace.model.time.schedule.TraceSchedule;
|
||||||
import ghidra.util.*;
|
import ghidra.util.*;
|
||||||
import ghidra.util.datastruct.CollectionChangeListener;
|
import ghidra.util.datastruct.CollectionChangeListener;
|
||||||
import ghidra.util.exception.*;
|
import ghidra.util.exception.*;
|
||||||
|
@ -693,7 +693,11 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
|
||||||
}
|
}
|
||||||
varView.setSnap(emuSnap);
|
varView.setSnap(emuSnap);
|
||||||
fireLocationEvent(coordinates);
|
fireLocationEvent(coordinates);
|
||||||
}));
|
})).exceptionally(ex -> {
|
||||||
|
Msg.showError(this, null, "Emulate", "Could not navigate to emulated coordinates",
|
||||||
|
ex);
|
||||||
|
return null;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,7 +21,7 @@ import ghidra.app.plugin.core.debug.service.emulation.DebuggerEmulationServicePl
|
||||||
import ghidra.app.plugin.core.debug.service.emulation.DebuggerTracePcodeEmulator;
|
import ghidra.app.plugin.core.debug.service.emulation.DebuggerTracePcodeEmulator;
|
||||||
import ghidra.framework.plugintool.ServiceInfo;
|
import ghidra.framework.plugintool.ServiceInfo;
|
||||||
import ghidra.trace.model.Trace;
|
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.exception.CancelledException;
|
||||||
import ghidra.util.task.TaskMonitor;
|
import ghidra.util.task.TaskMonitor;
|
||||||
|
|
||||||
|
|
|
@ -25,7 +25,7 @@ import ghidra.framework.plugintool.ServiceInfo;
|
||||||
import ghidra.trace.model.Trace;
|
import ghidra.trace.model.Trace;
|
||||||
import ghidra.trace.model.program.TraceProgramView;
|
import ghidra.trace.model.program.TraceProgramView;
|
||||||
import ghidra.trace.model.thread.TraceThread;
|
import ghidra.trace.model.thread.TraceThread;
|
||||||
import ghidra.trace.model.time.TraceSchedule;
|
import ghidra.trace.model.time.schedule.TraceSchedule;
|
||||||
import ghidra.util.TriConsumer;
|
import ghidra.util.TriConsumer;
|
||||||
|
|
||||||
@ServiceInfo(defaultProvider = DebuggerTraceManagerServicePlugin.class)
|
@ServiceInfo(defaultProvider = DebuggerTraceManagerServicePlugin.class)
|
||||||
|
|
|
@ -29,7 +29,7 @@ import ghidra.test.ToyProgramBuilder;
|
||||||
import ghidra.trace.database.ToyDBTraceBuilder;
|
import ghidra.trace.database.ToyDBTraceBuilder;
|
||||||
import ghidra.trace.model.memory.TraceMemoryFlag;
|
import ghidra.trace.model.memory.TraceMemoryFlag;
|
||||||
import ghidra.trace.model.thread.TraceThread;
|
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.database.UndoableTransaction;
|
||||||
import help.screenshot.GhidraScreenShotGenerator;
|
import help.screenshot.GhidraScreenShotGenerator;
|
||||||
|
|
||||||
|
|
|
@ -22,8 +22,8 @@ import ghidra.app.services.DebuggerTraceManagerService;
|
||||||
import ghidra.test.ToyProgramBuilder;
|
import ghidra.test.ToyProgramBuilder;
|
||||||
import ghidra.trace.database.ToyDBTraceBuilder;
|
import ghidra.trace.database.ToyDBTraceBuilder;
|
||||||
import ghidra.trace.model.thread.TraceThread;
|
import ghidra.trace.model.thread.TraceThread;
|
||||||
import ghidra.trace.model.time.TraceSchedule;
|
|
||||||
import ghidra.trace.model.time.TraceSnapshot;
|
import ghidra.trace.model.time.TraceSnapshot;
|
||||||
|
import ghidra.trace.model.time.schedule.TraceSchedule;
|
||||||
import ghidra.util.database.UndoableTransaction;
|
import ghidra.util.database.UndoableTransaction;
|
||||||
import help.screenshot.GhidraScreenShotGenerator;
|
import help.screenshot.GhidraScreenShotGenerator;
|
||||||
|
|
||||||
|
|
|
@ -26,8 +26,8 @@ import org.junit.Test;
|
||||||
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest;
|
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest;
|
||||||
import ghidra.trace.database.time.DBTraceTimeManager;
|
import ghidra.trace.database.time.DBTraceTimeManager;
|
||||||
import ghidra.trace.model.thread.TraceThread;
|
import ghidra.trace.model.thread.TraceThread;
|
||||||
import ghidra.trace.model.time.TraceSchedule;
|
|
||||||
import ghidra.trace.model.time.TraceSnapshot;
|
import ghidra.trace.model.time.TraceSnapshot;
|
||||||
|
import ghidra.trace.model.time.schedule.TraceSchedule;
|
||||||
import ghidra.util.database.UndoableTransaction;
|
import ghidra.util.database.UndoableTransaction;
|
||||||
|
|
||||||
public class DebuggerTimeProviderTest extends AbstractGhidraHeadedDebuggerGUITest {
|
public class DebuggerTimeProviderTest extends AbstractGhidraHeadedDebuggerGUITest {
|
||||||
|
|
|
@ -292,39 +292,49 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerGUI
|
||||||
@Test
|
@Test
|
||||||
public void testDeadEditRegister() {
|
public void testDeadEditRegister() {
|
||||||
WatchRow row = prepareTestDeadEdit("r0");
|
WatchRow row = prepareTestDeadEdit("r0");
|
||||||
|
|
||||||
row.setRawValueString("0x1234");
|
|
||||||
waitForSwing();
|
|
||||||
|
|
||||||
TraceMemoryRegisterSpace regVals =
|
TraceMemoryRegisterSpace regVals =
|
||||||
tb.trace.getMemoryManager().getMemoryRegisterSpace(thread, false);
|
tb.trace.getMemoryManager().getMemoryRegisterSpace(thread, false);
|
||||||
assertEquals(BigInteger.valueOf(0x1234), regVals.getValue(0, r0).getUnsignedValue());
|
|
||||||
|
|
||||||
row.setRawValueString("1234");
|
row.setRawValueString("0x1234");
|
||||||
waitForSwing();
|
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
|
@Test
|
||||||
public void testDeadEditMemory() {
|
public void testDeadEditMemory() {
|
||||||
WatchRow row = prepareTestDeadEdit("*:8 r0");
|
WatchRow row = prepareTestDeadEdit("*:8 r0");
|
||||||
|
|
||||||
row.setRawValueString("0x1234");
|
|
||||||
waitForSwing();
|
|
||||||
|
|
||||||
TraceMemoryOperations mem = tb.trace.getMemoryManager();
|
TraceMemoryOperations mem = tb.trace.getMemoryManager();
|
||||||
ByteBuffer buf = ByteBuffer.allocate(8);
|
ByteBuffer buf = ByteBuffer.allocate(8);
|
||||||
mem.getBytes(0, tb.addr(0x00400000), buf);
|
|
||||||
buf.flip();
|
row.setRawValueString("0x1234");
|
||||||
assertEquals(0x1234, buf.getLong());
|
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 }");
|
row.setRawValueString("{ 12 34 56 78 9a bc de f0 }");
|
||||||
waitForSwing();
|
waitForPass(() -> {
|
||||||
buf.clear();
|
long viewSnap = traceManager.getCurrent().getViewSnap();
|
||||||
mem.getBytes(0, tb.addr(0x00400000), buf);
|
buf.clear();
|
||||||
buf.flip();
|
mem.getBytes(viewSnap, tb.addr(0x00400000), buf);
|
||||||
assertEquals(0x123456789abcdef0L, buf.getLong());
|
buf.flip();
|
||||||
|
assertEquals(0x123456789abcdef0L, buf.getLong());
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
protected WatchRow prepareTestLiveEdit(String expression) throws Exception {
|
protected WatchRow prepareTestLiveEdit(String expression) throws Exception {
|
||||||
|
@ -379,17 +389,18 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerGUI
|
||||||
public void testLiveEditNonMappableRegister() throws Throwable {
|
public void testLiveEditNonMappableRegister() throws Throwable {
|
||||||
WatchRow row = prepareTestLiveEdit("r1");
|
WatchRow row = prepareTestLiveEdit("r1");
|
||||||
TraceThread thread = recorder.getTraceThread(mb.testThread1);
|
TraceThread thread = recorder.getTraceThread(mb.testThread1);
|
||||||
|
|
||||||
// Sanity check
|
// Sanity check
|
||||||
assertFalse(recorder.isRegisterOnTarget(thread, r1));
|
assertFalse(recorder.isRegisterOnTarget(thread, r1));
|
||||||
|
|
||||||
row.setRawValueString("0x1234");
|
row.setRawValueString("0x1234");
|
||||||
waitForSwing();
|
waitForPass(() -> {
|
||||||
|
TraceMemoryRegisterSpace regs =
|
||||||
TraceMemoryRegisterSpace regs =
|
recorder.getTrace().getMemoryManager().getMemoryRegisterSpace(thread, false);
|
||||||
recorder.getTrace().getMemoryManager().getMemoryRegisterSpace(thread, false);
|
assertNotNull(regs);
|
||||||
assertEquals(BigInteger.valueOf(0x1234),
|
long viewSnap = traceManager.getCurrent().getViewSnap();
|
||||||
regs.getValue(recorder.getSnap(), r1).getUnsignedValue());
|
assertEquals(BigInteger.valueOf(0x1234),
|
||||||
|
regs.getValue(viewSnap, r1).getUnsignedValue());
|
||||||
|
});
|
||||||
|
|
||||||
assertFalse(bank.regVals.containsKey("r1"));
|
assertFalse(bank.regVals.containsKey("r1"));
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,7 +35,7 @@ import ghidra.program.model.mem.MemoryBlock;
|
||||||
import ghidra.trace.model.Trace;
|
import ghidra.trace.model.Trace;
|
||||||
import ghidra.trace.model.memory.TraceMemoryRegisterSpace;
|
import ghidra.trace.model.memory.TraceMemoryRegisterSpace;
|
||||||
import ghidra.trace.model.thread.TraceThread;
|
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.database.UndoableTransaction;
|
||||||
import ghidra.util.task.TaskMonitor;
|
import ghidra.util.task.TaskMonitor;
|
||||||
|
|
||||||
|
|
|
@ -22,8 +22,8 @@ import ghidra.trace.database.thread.DBTraceThread;
|
||||||
import ghidra.trace.model.Trace;
|
import ghidra.trace.model.Trace;
|
||||||
import ghidra.trace.model.Trace.TraceSnapshotChangeType;
|
import ghidra.trace.model.Trace.TraceSnapshotChangeType;
|
||||||
import ghidra.trace.model.thread.TraceThread;
|
import ghidra.trace.model.thread.TraceThread;
|
||||||
import ghidra.trace.model.time.TraceSchedule;
|
|
||||||
import ghidra.trace.model.time.TraceSnapshot;
|
import ghidra.trace.model.time.TraceSnapshot;
|
||||||
|
import ghidra.trace.model.time.schedule.TraceSchedule;
|
||||||
import ghidra.trace.util.TraceChangeRecord;
|
import ghidra.trace.util.TraceChangeRecord;
|
||||||
import ghidra.util.LockHold;
|
import ghidra.util.LockHold;
|
||||||
import ghidra.util.Msg;
|
import ghidra.util.Msg;
|
||||||
|
|
|
@ -27,6 +27,7 @@ import ghidra.trace.database.DBTraceManager;
|
||||||
import ghidra.trace.database.thread.DBTraceThreadManager;
|
import ghidra.trace.database.thread.DBTraceThreadManager;
|
||||||
import ghidra.trace.model.Trace.TraceSnapshotChangeType;
|
import ghidra.trace.model.Trace.TraceSnapshotChangeType;
|
||||||
import ghidra.trace.model.time.*;
|
import ghidra.trace.model.time.*;
|
||||||
|
import ghidra.trace.model.time.schedule.TraceSchedule;
|
||||||
import ghidra.trace.util.TraceChangeRecord;
|
import ghidra.trace.util.TraceChangeRecord;
|
||||||
import ghidra.util.LockHold;
|
import ghidra.util.LockHold;
|
||||||
import ghidra.util.database.*;
|
import ghidra.util.database.*;
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -17,6 +17,7 @@ package ghidra.trace.model.time;
|
||||||
|
|
||||||
import ghidra.trace.model.Trace;
|
import ghidra.trace.model.Trace;
|
||||||
import ghidra.trace.model.thread.TraceThread;
|
import ghidra.trace.model.thread.TraceThread;
|
||||||
|
import ghidra.trace.model.time.schedule.TraceSchedule;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A "snapshot in time" in a trace
|
* A "snapshot in time" in a trace
|
||||||
|
|
|
@ -17,6 +17,8 @@ package ghidra.trace.model.time;
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
|
||||||
|
import ghidra.trace.model.time.schedule.TraceSchedule;
|
||||||
|
|
||||||
public interface TraceTimeManager {
|
public interface TraceTimeManager {
|
||||||
/**
|
/**
|
||||||
* Create a new snapshot after the latest
|
* Create a new snapshot after the latest
|
||||||
|
|
|
@ -0,0 +1,78 @@
|
||||||
|
/* ###
|
||||||
|
* IP: GHIDRA
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package ghidra.trace.model.time.schedule;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The result of a rich comparison of two schedules (or parts thereof)
|
||||||
|
*/
|
||||||
|
public enum CompareResult {
|
||||||
|
UNREL_LT(-1, false),
|
||||||
|
REL_LT(-1, true),
|
||||||
|
EQUALS(0, true),
|
||||||
|
REL_GT(1, true),
|
||||||
|
UNREL_GT(1, false);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enrich the result of {@link Comparable#compareTo(Object)}, given that the two are related
|
||||||
|
*
|
||||||
|
* @param compareTo the return from {@code compareTo}
|
||||||
|
* @return the rich result
|
||||||
|
*/
|
||||||
|
public static CompareResult related(int compareTo) {
|
||||||
|
if (compareTo < 0) {
|
||||||
|
return REL_LT;
|
||||||
|
}
|
||||||
|
if (compareTo > 0) {
|
||||||
|
return REL_GT;
|
||||||
|
}
|
||||||
|
return EQUALS;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enrich the result of {@link Comparable#compareTo(Object)}, given that the two are not
|
||||||
|
* related
|
||||||
|
*
|
||||||
|
* @param compareTo the return from {@code compareTo}
|
||||||
|
* @return the rich result
|
||||||
|
*/
|
||||||
|
public static CompareResult unrelated(int compareTo) {
|
||||||
|
if (compareTo < 0) {
|
||||||
|
return UNREL_LT;
|
||||||
|
}
|
||||||
|
if (compareTo > 0) {
|
||||||
|
return UNREL_GT;
|
||||||
|
}
|
||||||
|
return EQUALS;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maintain sort order, but specify the two are not in fact related
|
||||||
|
*
|
||||||
|
* @param result the result of another (usually recursive) rich comparison
|
||||||
|
* @return the modified result
|
||||||
|
*/
|
||||||
|
public static CompareResult unrelated(CompareResult result) {
|
||||||
|
return unrelated(result.compareTo);
|
||||||
|
}
|
||||||
|
|
||||||
|
public final int compareTo;
|
||||||
|
public final boolean related;
|
||||||
|
|
||||||
|
CompareResult(int compareTo, boolean related) {
|
||||||
|
this.compareTo = compareTo;
|
||||||
|
this.related = related;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,165 @@
|
||||||
|
/* ###
|
||||||
|
* IP: GHIDRA
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package ghidra.trace.model.time.schedule;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
import javax.help.UnsupportedOperationException;
|
||||||
|
|
||||||
|
import ghidra.pcode.emu.PcodeThread;
|
||||||
|
import ghidra.pcode.exec.PcodeProgram;
|
||||||
|
import ghidra.util.exception.CancelledException;
|
||||||
|
import ghidra.util.task.TaskMonitor;
|
||||||
|
|
||||||
|
public class PatchStep implements Step {
|
||||||
|
protected final long threadKey;
|
||||||
|
protected final String sleigh;
|
||||||
|
protected final int hashCode;
|
||||||
|
|
||||||
|
public static PatchStep parse(long threadKey, String stepSpec) {
|
||||||
|
// TODO: Can I parse and validate the sleigh here?
|
||||||
|
if (!stepSpec.startsWith("{") || !stepSpec.endsWith("}")) {
|
||||||
|
throw new IllegalArgumentException("Cannot parse step: '" + stepSpec + "'");
|
||||||
|
}
|
||||||
|
return new PatchStep(threadKey, stepSpec.substring(1, stepSpec.length() - 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
public PatchStep(long threadKey, String sleigh) {
|
||||||
|
this.threadKey = threadKey;
|
||||||
|
this.sleigh = Objects.requireNonNull(sleigh);
|
||||||
|
this.hashCode = Objects.hash(threadKey, sleigh); // TODO: May become mutable
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return hashCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (this == obj) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!(obj instanceof PatchStep)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
PatchStep that = (PatchStep) obj;
|
||||||
|
if (this.threadKey != that.threadKey) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!this.sleigh.equals(that.sleigh)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
if (threadKey == -1) {
|
||||||
|
return "{" + sleigh + "}";
|
||||||
|
}
|
||||||
|
return String.format("t%d-{%s}", threadKey, sleigh);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getTypeOrder() {
|
||||||
|
// When comparing sequences, those with sleigh steps are ordered after those with ticks
|
||||||
|
return 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isNop() {
|
||||||
|
// TODO: If parsing beforehand, base on number of ops
|
||||||
|
return sleigh.length() == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getThreadKey() {
|
||||||
|
return threadKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getTickCount() {
|
||||||
|
return 0; // Philosophically correct
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getPatchCount() {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isCompatible(Step step) {
|
||||||
|
// TODO: Can we combine ops?
|
||||||
|
return false; // For now, never combine sleigh steps
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addTo(Step step) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Step subtract(Step step) {
|
||||||
|
if (this.equals(step)) {
|
||||||
|
return Step.nop();
|
||||||
|
}
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Step clone() {
|
||||||
|
return new PatchStep(threadKey, sleigh);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long rewind(long count) {
|
||||||
|
return count - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CompareResult compareStep(Step step) {
|
||||||
|
CompareResult result;
|
||||||
|
|
||||||
|
result = compareStepType(step);
|
||||||
|
if (result != CompareResult.EQUALS) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
PatchStep that = (PatchStep) step;
|
||||||
|
result = CompareResult.unrelated(Long.compare(this.threadKey, that.threadKey));
|
||||||
|
if (result != CompareResult.EQUALS) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Compare ops, if/when we pre-compile
|
||||||
|
result = CompareResult.unrelated(this.sleigh.compareTo(that.sleigh));
|
||||||
|
if (result != CompareResult.EQUALS) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
return CompareResult.EQUALS;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> void execute(PcodeThread<T> emuThread, Consumer<PcodeThread<T>> stepAction,
|
||||||
|
TaskMonitor monitor) throws CancelledException {
|
||||||
|
PcodeProgram prog = emuThread.getMachine().compileSleigh("schedule", List.of(sleigh + ";"));
|
||||||
|
emuThread.getExecutor().execute(prog, emuThread.getUseropLibrary());
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,398 @@
|
||||||
|
/* ###
|
||||||
|
* IP: GHIDRA
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package ghidra.trace.model.time.schedule;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
|
import ghidra.pcode.emu.PcodeMachine;
|
||||||
|
import ghidra.pcode.emu.PcodeThread;
|
||||||
|
import ghidra.trace.model.Trace;
|
||||||
|
import ghidra.trace.model.thread.TraceThread;
|
||||||
|
import ghidra.trace.model.thread.TraceThreadManager;
|
||||||
|
import ghidra.util.exception.CancelledException;
|
||||||
|
import ghidra.util.task.TaskMonitor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A sequence of thread steps, each repeated some number of times
|
||||||
|
*/
|
||||||
|
public class Sequence implements Comparable<Sequence> {
|
||||||
|
public static final String SEP = ";";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse (and normalize) a sequence of steps
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* This takes a semicolon-separated list of steps in the form specified by
|
||||||
|
* {@link Step#parse(String)}. Each step may or may not specify a thread, but it's uncommon for
|
||||||
|
* any but the first step to omit the thread. The sequence is normalized as it is parsed, so any
|
||||||
|
* step after the first that omits a thread will be combined with the previous step. When the
|
||||||
|
* first step applies to the "last thread," it typically means the "event thread" of the source
|
||||||
|
* trace snapshot.
|
||||||
|
*
|
||||||
|
* @param seqSpec the string specification of the sequence
|
||||||
|
* @return the parsed sequence
|
||||||
|
* @throws IllegalArgumentException if the specification is of the wrong form
|
||||||
|
*/
|
||||||
|
public static Sequence parse(String seqSpec) {
|
||||||
|
Sequence result = new Sequence();
|
||||||
|
for (String stepSpec : seqSpec.split(SEP)) {
|
||||||
|
Step step = Step.parse(stepSpec);
|
||||||
|
result.advance(step);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct (and normalize) a sequence of the specified steps
|
||||||
|
*
|
||||||
|
* @param steps the desired steps in order
|
||||||
|
* @return the resulting sequence
|
||||||
|
*/
|
||||||
|
public static Sequence of(Step... steps) {
|
||||||
|
return of(Arrays.asList(steps));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct (and normalize) a sequence of the specified steps
|
||||||
|
*
|
||||||
|
* @param steps the desired steps in order
|
||||||
|
* @return the resulting sequence
|
||||||
|
*/
|
||||||
|
public static Sequence of(List<? extends Step> steps) {
|
||||||
|
Sequence result = new Sequence();
|
||||||
|
for (Step step : steps) {
|
||||||
|
result.advance(step);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct (and normalize) a sequence formed by the steps in a followed by the steps in b
|
||||||
|
*
|
||||||
|
* @param a the first sequence
|
||||||
|
* @param b the second (appended) sequence
|
||||||
|
* @return the resulting sequence
|
||||||
|
*/
|
||||||
|
public static Sequence catenate(Sequence a, Sequence b) {
|
||||||
|
Sequence result = new Sequence();
|
||||||
|
result.advance(a);
|
||||||
|
result.advance(b);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private final List<Step> steps;
|
||||||
|
|
||||||
|
protected Sequence() {
|
||||||
|
this(new ArrayList<>());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Sequence(List<Step> steps) {
|
||||||
|
this.steps = steps;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return StringUtils.join(steps, SEP);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Append the given step to this sequence
|
||||||
|
*
|
||||||
|
* @param step the step to append
|
||||||
|
*/
|
||||||
|
public void advance(Step step) {
|
||||||
|
if (step.isNop()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (steps.isEmpty()) {
|
||||||
|
steps.add(step);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Step last = steps.get(steps.size() - 1);
|
||||||
|
if (!last.isCompatible(step)) {
|
||||||
|
steps.add(step.clone());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
last.addTo(step);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Append the given sequence to this one
|
||||||
|
*
|
||||||
|
* @param seq the sequence to append
|
||||||
|
*/
|
||||||
|
public void advance(Sequence seq) {
|
||||||
|
int size = seq.steps.size();
|
||||||
|
// Clone early in case seq == this
|
||||||
|
// I should store copies of subsequent steps, anyway
|
||||||
|
List<Step> clone = seq.steps.stream()
|
||||||
|
.map(Step::clone)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
if (size < 1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// intervening -1 could resolve and be combined with following
|
||||||
|
advance(clone.get(0));
|
||||||
|
if (size < 2) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
advance(clone.get(1));
|
||||||
|
steps.addAll(clone.subList(2, size));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rewind this sequence the given step count
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* This modifies the sequence in place, removing the given count from the end of the sequence.
|
||||||
|
* Any step whose count is reduced to 0 as a result of rewinding is removed entirely from the
|
||||||
|
* sequence. Note that each sleigh step (modification) counts as one step when rewinding.
|
||||||
|
*
|
||||||
|
* @param count the step count to rewind
|
||||||
|
* @return if count exceeds the steps of this sequence, the (positive) difference remaining
|
||||||
|
*/
|
||||||
|
public long rewind(long count) {
|
||||||
|
if (count < 0) {
|
||||||
|
throw new IllegalArgumentException("Cannot rewind a negative number");
|
||||||
|
}
|
||||||
|
while (!steps.isEmpty()) {
|
||||||
|
int lastIndex = steps.size() - 1;
|
||||||
|
count = steps.get(lastIndex).rewind(count);
|
||||||
|
if (count >= 0) {
|
||||||
|
steps.remove(lastIndex);
|
||||||
|
}
|
||||||
|
if (count <= 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Long.max(0, count);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Sequence clone() {
|
||||||
|
return new Sequence(
|
||||||
|
steps.stream().map(Step::clone).collect(Collectors.toList()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obtain a clone of the steps
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* Modifications to the returned steps have no effect on this sequence.
|
||||||
|
*
|
||||||
|
* @return the cloned steps
|
||||||
|
*/
|
||||||
|
public List<Step> getSteps() {
|
||||||
|
return steps.stream().map(Step::clone).collect(Collectors.toUnmodifiableList());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if this sequence represents any actions
|
||||||
|
*
|
||||||
|
* @return true if the sequence is empty, false if not
|
||||||
|
*/
|
||||||
|
public boolean isNop() {
|
||||||
|
return steps.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return steps.hashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (this == obj) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!(obj instanceof Sequence)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Sequence that = (Sequence) obj;
|
||||||
|
return Objects.equals(this.steps, that.steps);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Richly compare to sequences
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* The result indicates not only which is "less" or "greater" than the other, but also indicates
|
||||||
|
* whether the two are "related." Two sequences are considered related if one is the prefix to
|
||||||
|
* the other. More precisely, they are related if it's possible to transform one into the other
|
||||||
|
* solely by truncation (rewind) or solely by concatenation (advance). When related, the prefix
|
||||||
|
* is considered "less than" the other. Equal sequences are trivially related.
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* Examples:
|
||||||
|
* <ul>
|
||||||
|
* <li>{@code ""} is related to and less than {@code "10"}</li>
|
||||||
|
* <li>{@code "10"} is related and equal to {@code "10"}</li>
|
||||||
|
* <li>{@code "10"} is related to and less than {@code "11"}</li>
|
||||||
|
* <li>{@code "t1-5"} is related to and less than {@code "t1-5;t2-4"}</li>
|
||||||
|
* <li>{@code "t1-5"} is un-related to and less than {@code "t1-4;t2-4"}</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* The {@link #compareTo(Sequence)} implementation defers to this method. Thus, in a sorted set
|
||||||
|
* of step sequences, the floor of a given sequence is will be the longest prefix in that set to
|
||||||
|
* the given sequence, assuming such a prefix is present.
|
||||||
|
*
|
||||||
|
* @param that the object of comparison (this being the subject)
|
||||||
|
* @return a result describing the relationship from subject to object
|
||||||
|
*/
|
||||||
|
public CompareResult compareSeq(Sequence that) {
|
||||||
|
int min = Math.min(this.steps.size(), that.steps.size());
|
||||||
|
CompareResult result;
|
||||||
|
for (int i = 0; i < min; i++) {
|
||||||
|
Step s1 = this.steps.get(i);
|
||||||
|
Step s2 = that.steps.get(i);
|
||||||
|
result = s1.compareStep(s2);
|
||||||
|
switch (result) {
|
||||||
|
case UNREL_LT:
|
||||||
|
case UNREL_GT:
|
||||||
|
return result;
|
||||||
|
case REL_LT:
|
||||||
|
if (i + 1 == this.steps.size()) {
|
||||||
|
return CompareResult.REL_LT;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return CompareResult.UNREL_LT;
|
||||||
|
}
|
||||||
|
case REL_GT:
|
||||||
|
if (i + 1 == that.steps.size()) {
|
||||||
|
return CompareResult.REL_GT;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return CompareResult.UNREL_GT;
|
||||||
|
}
|
||||||
|
default: // EQUALS, next step
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (that.steps.size() > min) {
|
||||||
|
return CompareResult.REL_LT;
|
||||||
|
}
|
||||||
|
if (this.steps.size() > min) {
|
||||||
|
return CompareResult.REL_GT;
|
||||||
|
}
|
||||||
|
return CompareResult.EQUALS;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int compareTo(Sequence that) {
|
||||||
|
return compareSeq(that).compareTo;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compute the sequence which concatenated to the given prefix would result in this sequence
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* The returned step sequence should not be manipulated, since it may just be this sequence.
|
||||||
|
*
|
||||||
|
* @see #compareSeq(Sequence)
|
||||||
|
* @param prefix the prefix
|
||||||
|
* @return the relative sequence from prefix to this
|
||||||
|
* @throws IllegalArgumentException if prefix is not a prefix of this sequence
|
||||||
|
*/
|
||||||
|
public Sequence relativize(Sequence prefix) {
|
||||||
|
if (prefix.isNop()) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
CompareResult comp = compareSeq(prefix);
|
||||||
|
Sequence result = new Sequence();
|
||||||
|
if (comp == CompareResult.EQUALS) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
if (comp != CompareResult.REL_GT) {
|
||||||
|
throw new IllegalArgumentException(String.format(
|
||||||
|
"The given prefix (%s) is not actually a prefix of this (%s).", prefix, this));
|
||||||
|
}
|
||||||
|
|
||||||
|
int lastStepIndex = prefix.steps.size() - 1;
|
||||||
|
Step ancestorLast = prefix.steps.get(lastStepIndex);
|
||||||
|
Step continuation = this.steps.get(lastStepIndex);
|
||||||
|
result.advance(continuation.subtract(ancestorLast));
|
||||||
|
result.steps.addAll(steps.subList(prefix.steps.size(), steps.size()));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compute to total number of ticks specified
|
||||||
|
*
|
||||||
|
* @return the total
|
||||||
|
*/
|
||||||
|
public long totalTickCount() {
|
||||||
|
long count = 0;
|
||||||
|
for (Step step : steps) {
|
||||||
|
count += step.getTickCount();
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compute to total number of patches specified
|
||||||
|
*
|
||||||
|
* @return the total
|
||||||
|
*/
|
||||||
|
public long totalPatchCount() {
|
||||||
|
long count = 0;
|
||||||
|
for (Step step : steps) {
|
||||||
|
count += step.getPatchCount();
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute this sequence upon the given machine
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* Threads are retrieved from the database by key, then created in the machine (if not already
|
||||||
|
* present) named by {@link TraceThread#getPath()}. The caller should ensure the machine's state
|
||||||
|
* is bound to the given trace.
|
||||||
|
*
|
||||||
|
* @param trace the trace to which the machine is bound
|
||||||
|
* @param eventThread the thread for the first step, if it applies to the "last thread"
|
||||||
|
* @param machine the machine to step
|
||||||
|
* @param action the action to step each thread
|
||||||
|
* @param monitor a monitor for cancellation and progress reports
|
||||||
|
* @return the last trace thread stepped during execution
|
||||||
|
* @throws CancelledException if execution is cancelled
|
||||||
|
*/
|
||||||
|
public <T> TraceThread execute(Trace trace, TraceThread eventThread, PcodeMachine<T> machine,
|
||||||
|
Consumer<PcodeThread<T>> action, TaskMonitor monitor) throws CancelledException {
|
||||||
|
TraceThreadManager tm = trace.getThreadManager();
|
||||||
|
TraceThread thread = eventThread;
|
||||||
|
for (Step step : steps) {
|
||||||
|
thread = step.execute(tm, thread, machine, action, monitor);
|
||||||
|
}
|
||||||
|
return thread;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the key of the last thread stepped
|
||||||
|
*
|
||||||
|
* @return the key, or -1 if no step in the sequence specifies a thread
|
||||||
|
*/
|
||||||
|
public long getLastThreadKey() {
|
||||||
|
if (steps.isEmpty()) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return steps.get(steps.size() - 1).getThreadKey();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,170 @@
|
||||||
|
/* ###
|
||||||
|
* IP: GHIDRA
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package ghidra.trace.model.time.schedule;
|
||||||
|
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
import ghidra.pcode.emu.PcodeMachine;
|
||||||
|
import ghidra.pcode.emu.PcodeThread;
|
||||||
|
import ghidra.trace.model.thread.TraceThread;
|
||||||
|
import ghidra.trace.model.thread.TraceThreadManager;
|
||||||
|
import ghidra.util.exception.CancelledException;
|
||||||
|
import ghidra.util.task.TaskMonitor;
|
||||||
|
|
||||||
|
public interface Step extends Comparable<Step> {
|
||||||
|
/**
|
||||||
|
* Parse a step, possibly including a thread prefix, e.g., {@code "t1-..."}
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* If the thread prefix is given, the step applies to the given thread. Otherwise, the step
|
||||||
|
* applies to the last thread or the event thread.
|
||||||
|
*
|
||||||
|
* @param stepSpec the string specification
|
||||||
|
* @return the parsed step
|
||||||
|
* @throws IllegalArgumentException if the specification is of the wrong form
|
||||||
|
*/
|
||||||
|
static Step parse(String stepSpec) {
|
||||||
|
if ("".equals(stepSpec)) {
|
||||||
|
return nop();
|
||||||
|
}
|
||||||
|
String[] parts = stepSpec.split("-");
|
||||||
|
if (parts.length == 1) {
|
||||||
|
return parse(-1, parts[0].trim());
|
||||||
|
}
|
||||||
|
if (parts.length == 2) {
|
||||||
|
String tPart = parts[0].trim();
|
||||||
|
if (tPart.startsWith("t")) {
|
||||||
|
return parse(Long.parseLong(tPart.substring(1)), parts[1].trim());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new IllegalArgumentException("Cannot parse step: '" + stepSpec + "'");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse a step for the given thread key
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* The form of the spec must either be numeric, indicating some number of ticks, or
|
||||||
|
* brace-enclosed Sleigh code, e.g., {@code "{r0=0x1234;}"}. The latter allows patching machine
|
||||||
|
* state during execution.
|
||||||
|
*
|
||||||
|
* @param threadKey the thread to step, or -1 for the last thread or event thread
|
||||||
|
* @param stepSpec the string specification
|
||||||
|
* @return the parsed step
|
||||||
|
* @throws IllegalArgumentException if the specification is of the wrong form
|
||||||
|
*/
|
||||||
|
static Step parse(long threadKey, String stepSpec) {
|
||||||
|
if (stepSpec.startsWith("{")) {
|
||||||
|
return PatchStep.parse(threadKey, stepSpec);
|
||||||
|
}
|
||||||
|
return TickStep.parse(threadKey, stepSpec);
|
||||||
|
}
|
||||||
|
|
||||||
|
static TickStep nop() {
|
||||||
|
return new TickStep(-1, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
int getTypeOrder();
|
||||||
|
|
||||||
|
boolean isNop();
|
||||||
|
|
||||||
|
long getThreadKey();
|
||||||
|
|
||||||
|
default boolean isEventThread() {
|
||||||
|
return getThreadKey() == -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
default TraceThread getThread(TraceThreadManager tm, TraceThread eventThread) {
|
||||||
|
TraceThread thread = isEventThread() ? eventThread : tm.getThread(getThreadKey());
|
||||||
|
if (thread == null) {
|
||||||
|
if (isEventThread()) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"Thread key -1 can only be used if last/event thread is given");
|
||||||
|
}
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"Thread with key " + getThreadKey() + " does not exist in given trace");
|
||||||
|
}
|
||||||
|
return thread;
|
||||||
|
}
|
||||||
|
|
||||||
|
long getTickCount();
|
||||||
|
|
||||||
|
long getPatchCount();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the given step can be combined with this one
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* Two steps applied to the same thread can just be summed. If the given step applies to the
|
||||||
|
* "last thread" or to the same thread as this step, then it can be combined.
|
||||||
|
*
|
||||||
|
* @param step the second step
|
||||||
|
* @return true if combinable, false otherwise.
|
||||||
|
*/
|
||||||
|
boolean isCompatible(Step step);
|
||||||
|
|
||||||
|
void addTo(Step step);
|
||||||
|
|
||||||
|
Step subtract(Step step);
|
||||||
|
|
||||||
|
Step clone();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subtract from the count of this step
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* If this step has a count exceeding that given, then this method simply subtracts the given
|
||||||
|
* number from the {@code tickCount} and returns the (negative) difference. If this step has
|
||||||
|
* exactly the count given, this method sets the count to 0 and returns 0, indicating this step
|
||||||
|
* should be removed from the sequence. If the given count exceeds that of this step, this
|
||||||
|
* method sets the count to 0 and returns the (positive) difference, indicating this step should
|
||||||
|
* be removed from the sequence, and the remaining steps rewound from the preceding step.
|
||||||
|
*
|
||||||
|
* @param steps the count to rewind
|
||||||
|
* @return the number of steps remaining
|
||||||
|
*/
|
||||||
|
long rewind(long count);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Richly compare this step to another
|
||||||
|
*
|
||||||
|
* @param step the object of comparison (this being the subject)
|
||||||
|
* @return a result describing the relationship from subject to object
|
||||||
|
*/
|
||||||
|
CompareResult compareStep(Step that);
|
||||||
|
|
||||||
|
default CompareResult compareStepType(Step that) {
|
||||||
|
return CompareResult
|
||||||
|
.unrelated(Integer.compare(this.getTypeOrder(), that.getTypeOrder()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default int compareTo(Step that) {
|
||||||
|
return compareStep(that).compareTo;
|
||||||
|
}
|
||||||
|
|
||||||
|
default <T> TraceThread execute(TraceThreadManager tm, TraceThread eventThread,
|
||||||
|
PcodeMachine<T> machine, Consumer<PcodeThread<T>> stepAction, TaskMonitor monitor)
|
||||||
|
throws CancelledException {
|
||||||
|
TraceThread thread = getThread(tm, eventThread);
|
||||||
|
PcodeThread<T> emuThread = machine.getThread(thread.getPath(), true);
|
||||||
|
execute(emuThread, stepAction, monitor);
|
||||||
|
return thread;
|
||||||
|
}
|
||||||
|
|
||||||
|
<T> void execute(PcodeThread<T> emuThread, Consumer<PcodeThread<T>> stepAction,
|
||||||
|
TaskMonitor monitor) throws CancelledException;
|
||||||
|
}
|
|
@ -0,0 +1,197 @@
|
||||||
|
/* ###
|
||||||
|
* IP: GHIDRA
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package ghidra.trace.model.time.schedule;
|
||||||
|
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
import ghidra.pcode.emu.PcodeThread;
|
||||||
|
import ghidra.util.exception.CancelledException;
|
||||||
|
import ghidra.util.task.TaskMonitor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A step of a given thread in a schedule: repeating some number of ticks
|
||||||
|
*/
|
||||||
|
public class TickStep implements Step {
|
||||||
|
|
||||||
|
public static TickStep parse(long threadKey, String stepSpec) {
|
||||||
|
try {
|
||||||
|
return new TickStep(threadKey, Long.parseLong(stepSpec));
|
||||||
|
}
|
||||||
|
catch (NumberFormatException e) {
|
||||||
|
throw new IllegalArgumentException("Cannot parse step: '" + stepSpec + "'");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected final long threadKey;
|
||||||
|
protected long tickCount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct a step for the given thread with the given tick count
|
||||||
|
*
|
||||||
|
* @param threadKey the key of the thread in the trace, -1 for the "last thread"
|
||||||
|
* @param tickCount the number of times to step the thread
|
||||||
|
*/
|
||||||
|
public TickStep(long threadKey, long tickCount) {
|
||||||
|
if (tickCount < 0) {
|
||||||
|
throw new IllegalArgumentException("Cannot step a negative number");
|
||||||
|
}
|
||||||
|
this.threadKey = threadKey;
|
||||||
|
this.tickCount = tickCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getTypeOrder() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
if (threadKey == -1) {
|
||||||
|
return Long.toString(tickCount);
|
||||||
|
}
|
||||||
|
return String.format("t%d-%d", threadKey, tickCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isNop() {
|
||||||
|
return tickCount == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getThreadKey() {
|
||||||
|
return threadKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getTickCount() {
|
||||||
|
return tickCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getPatchCount() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TickStep clone() {
|
||||||
|
return new TickStep(threadKey, tickCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add to the count of this step
|
||||||
|
*
|
||||||
|
* @param steps the count to add
|
||||||
|
*/
|
||||||
|
public void advance(long steps) {
|
||||||
|
if (steps < 0) {
|
||||||
|
throw new IllegalArgumentException("Cannot advance a negative number");
|
||||||
|
}
|
||||||
|
long newCount = tickCount + steps;
|
||||||
|
if (newCount < 0) {
|
||||||
|
throw new IllegalArgumentException("Total step count exceeds LONG_MAX");
|
||||||
|
}
|
||||||
|
this.tickCount = newCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long rewind(long steps) {
|
||||||
|
if (steps < 0) {
|
||||||
|
throw new IllegalArgumentException("Cannot rewind a negative number");
|
||||||
|
}
|
||||||
|
long diff = this.tickCount - steps;
|
||||||
|
this.tickCount = Long.max(0, diff);
|
||||||
|
return -diff;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isCompatible(Step step) {
|
||||||
|
if (!(step instanceof TickStep)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
TickStep ts = (TickStep) step;
|
||||||
|
return this.threadKey == ts.threadKey || ts.threadKey == -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addTo(Step step) {
|
||||||
|
assert isCompatible(step);
|
||||||
|
TickStep ts = (TickStep) step;
|
||||||
|
advance(ts.tickCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Step subtract(Step step) {
|
||||||
|
assert isCompatible(step);
|
||||||
|
TickStep that = (TickStep) step;
|
||||||
|
return new TickStep(this.threadKey, this.tickCount - that.tickCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Long.hashCode(threadKey) * 31 + Long.hashCode(tickCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (this == obj) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!(obj instanceof TickStep)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
TickStep that = (TickStep) obj;
|
||||||
|
if (this.threadKey != that.threadKey) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (this.tickCount != that.tickCount) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CompareResult compareStep(Step step) {
|
||||||
|
CompareResult result;
|
||||||
|
|
||||||
|
result = compareStepType(step);
|
||||||
|
if (result != CompareResult.EQUALS) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
TickStep that = (TickStep) step;
|
||||||
|
result = CompareResult.unrelated(Long.compare(this.threadKey, that.threadKey));
|
||||||
|
if (result != CompareResult.EQUALS) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
result = CompareResult.related(Long.compare(this.tickCount, that.tickCount));
|
||||||
|
if (result != CompareResult.EQUALS) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
return CompareResult.EQUALS;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> void execute(PcodeThread<T> emuThread, Consumer<PcodeThread<T>> stepAction,
|
||||||
|
TaskMonitor monitor) throws CancelledException {
|
||||||
|
for (int i = 0; i < tickCount; i++) {
|
||||||
|
monitor.incrementProgress(1);
|
||||||
|
monitor.checkCanceled();
|
||||||
|
stepAction.accept(emuThread);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,490 @@
|
||||||
|
/* ###
|
||||||
|
* IP: GHIDRA
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package ghidra.trace.model.time.schedule;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
import ghidra.pcode.emu.PcodeMachine;
|
||||||
|
import ghidra.pcode.emu.PcodeThread;
|
||||||
|
import ghidra.trace.model.Trace;
|
||||||
|
import ghidra.trace.model.thread.TraceThread;
|
||||||
|
import ghidra.trace.model.time.TraceSnapshot;
|
||||||
|
import ghidra.util.exception.CancelledException;
|
||||||
|
import ghidra.util.task.TaskMonitor;
|
||||||
|
|
||||||
|
public class TraceSchedule implements Comparable<TraceSchedule> {
|
||||||
|
public static final TraceSchedule ZERO = TraceSchedule.snap(0);
|
||||||
|
|
||||||
|
public static final TraceSchedule snap(long snap) {
|
||||||
|
return new TraceSchedule(snap, new Sequence(), new Sequence());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final String PARSE_ERR_MSG =
|
||||||
|
"Time specification must have form 'snap[:steps[.pSteps]]'";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse schedule in the form "{@code snap[:steps[.pSteps]]}"
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* A schedule consists of a snap, a optional {@link Sequence} of thread instruction-level steps,
|
||||||
|
* and optional p-code-level steps (pSteps). The form of {@code steps} and {@code pSteps} is
|
||||||
|
* specified by {@link Sequence#parse(String)}. Each sequence consists of stepping selected
|
||||||
|
* threads forward, and/or patching machine state.
|
||||||
|
*
|
||||||
|
* @param spec the string specification
|
||||||
|
* @return the parsed schedule
|
||||||
|
*/
|
||||||
|
public static TraceSchedule parse(String spec) {
|
||||||
|
String[] parts = spec.split(":", 2);
|
||||||
|
if (parts.length > 2) {
|
||||||
|
throw new AssertionError();
|
||||||
|
}
|
||||||
|
final long snap;
|
||||||
|
final Sequence ticks;
|
||||||
|
final Sequence pTicks;
|
||||||
|
try {
|
||||||
|
snap = Long.decode(parts[0]);
|
||||||
|
}
|
||||||
|
catch (NumberFormatException e) {
|
||||||
|
throw new IllegalArgumentException(PARSE_ERR_MSG, e);
|
||||||
|
}
|
||||||
|
if (parts.length > 1) {
|
||||||
|
String[] subs = parts[1].split("\\.");
|
||||||
|
try {
|
||||||
|
ticks = Sequence.parse(subs[0]);
|
||||||
|
}
|
||||||
|
catch (IllegalArgumentException e) {
|
||||||
|
throw new IllegalArgumentException(PARSE_ERR_MSG, e);
|
||||||
|
}
|
||||||
|
if (subs.length == 1) {
|
||||||
|
pTicks = new Sequence();
|
||||||
|
}
|
||||||
|
else if (subs.length == 2) {
|
||||||
|
try {
|
||||||
|
pTicks = Sequence.parse(subs[1]);
|
||||||
|
}
|
||||||
|
catch (IllegalArgumentException e) {
|
||||||
|
throw new IllegalArgumentException(PARSE_ERR_MSG, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw new IllegalArgumentException(PARSE_ERR_MSG);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
ticks = new Sequence();
|
||||||
|
pTicks = new Sequence();
|
||||||
|
}
|
||||||
|
return new TraceSchedule(snap, ticks, pTicks);
|
||||||
|
}
|
||||||
|
|
||||||
|
private final long snap;
|
||||||
|
private final Sequence steps;
|
||||||
|
private final Sequence pSteps;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct the given schedule
|
||||||
|
*
|
||||||
|
* @param snap the initial trace snapshot
|
||||||
|
* @param steps the step sequence
|
||||||
|
* @param pSteps the of p-code step sequence
|
||||||
|
*/
|
||||||
|
public TraceSchedule(long snap, Sequence steps, Sequence pSteps) {
|
||||||
|
this.snap = snap;
|
||||||
|
this.steps = steps;
|
||||||
|
this.pSteps = pSteps;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
if (pSteps.isNop()) {
|
||||||
|
if (steps.isNop()) {
|
||||||
|
return Long.toString(snap);
|
||||||
|
}
|
||||||
|
return String.format("%d:%s", snap, steps);
|
||||||
|
}
|
||||||
|
return String.format("%d:%s.%s", snap, steps, pSteps);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Richly compare two schedules
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* Schedules starting at different snapshots are never related, because there is no
|
||||||
|
* emulator/simulator stepping action which advances to the next snapshot. Though p-code steps
|
||||||
|
* may comprise a partial step, we do not consider a partial step to be a prefix of a full step,
|
||||||
|
* since we cannot know <em>a priori</em> how many p-code steps comprise a full instruction
|
||||||
|
* step. Consider, e.g., the user may specify 100 p-code steps, which could effect 20
|
||||||
|
* instruction steps.
|
||||||
|
*
|
||||||
|
* @param that the object of comparison (this being the subject)
|
||||||
|
* @return a result describing the relationship from subject to object
|
||||||
|
*/
|
||||||
|
public CompareResult compareSchedule(TraceSchedule that) {
|
||||||
|
CompareResult result;
|
||||||
|
|
||||||
|
result = CompareResult.unrelated(Long.compare(this.snap, that.snap));
|
||||||
|
if (result != CompareResult.EQUALS) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
result = this.steps.compareSeq(that.steps);
|
||||||
|
switch (result) {
|
||||||
|
case UNREL_LT:
|
||||||
|
case UNREL_GT:
|
||||||
|
return result;
|
||||||
|
case REL_LT:
|
||||||
|
if (this.pSteps.isNop()) {
|
||||||
|
return CompareResult.REL_LT;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return CompareResult.UNREL_LT;
|
||||||
|
}
|
||||||
|
case REL_GT:
|
||||||
|
if (that.pSteps.isNop()) {
|
||||||
|
return CompareResult.REL_GT;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return CompareResult.UNREL_GT;
|
||||||
|
}
|
||||||
|
default: // EQUALS, compare pSteps
|
||||||
|
}
|
||||||
|
|
||||||
|
result = this.pSteps.compareSeq(that.pSteps);
|
||||||
|
if (result != CompareResult.EQUALS) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
return CompareResult.EQUALS;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (this == obj) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!(obj instanceof TraceSchedule)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
TraceSchedule that = (TraceSchedule) obj;
|
||||||
|
if (this.snap != that.snap) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!Objects.equals(this.steps, that.steps)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!Objects.equals(this.pSteps, that.pSteps)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(snap, steps, pSteps);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int compareTo(TraceSchedule o) {
|
||||||
|
return compareSchedule(o).compareTo;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if this schedule requires any stepping
|
||||||
|
*
|
||||||
|
* @return true if no stepping is required, i.e., the resulting state can be realized simply by
|
||||||
|
* loading a snapshot
|
||||||
|
*/
|
||||||
|
public boolean isSnapOnly() {
|
||||||
|
return steps.isNop() && pSteps.isNop();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the source snapshot
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public long getSnap() {
|
||||||
|
return snap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the last thread key stepped by this schedule
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public long getLastThreadKey() {
|
||||||
|
long last = pSteps.getLastThreadKey();
|
||||||
|
if (last != -1) {
|
||||||
|
return last;
|
||||||
|
}
|
||||||
|
return steps.getLastThreadKey();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the event thread for this schedule in the context of the given trace
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* This is the thread stepped when no thread is specified for the first step of the sequence.
|
||||||
|
*
|
||||||
|
* @param trace the trace containing the source snapshot and threads
|
||||||
|
* @return the thread to use as "last thread" for the sequence
|
||||||
|
*/
|
||||||
|
public TraceThread getEventThread(Trace trace) {
|
||||||
|
TraceSnapshot snapshot = trace.getTimeManager().getSnapshot(snap, false);
|
||||||
|
return snapshot == null ? null : snapshot.getEventThread();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the last thread stepped by this schedule in the context of the given trace
|
||||||
|
*
|
||||||
|
* @param trace the trace containing the source snapshot and threads
|
||||||
|
* @return the thread last stepped, or the "event thread" when no steps are taken
|
||||||
|
*/
|
||||||
|
public TraceThread getLastThread(Trace trace) {
|
||||||
|
long lastKey = getLastThreadKey();
|
||||||
|
if (lastKey == -1) {
|
||||||
|
return getEventThread(trace);
|
||||||
|
}
|
||||||
|
return trace.getThreadManager().getThread(lastKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compute the total number of ticks taken, including the p-code ticks
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* This is suitable for use with {@link TaskMonitor#initialize(long)}, where that monitor will
|
||||||
|
* be passed to {@link #execute(Trace, PcodeMachine, TaskMonitor)} or similar. Note that patch
|
||||||
|
* steps do not count as ticks.
|
||||||
|
*
|
||||||
|
* @return the number of ticks
|
||||||
|
*/
|
||||||
|
public long totalTickCount() {
|
||||||
|
return steps.totalTickCount() + pSteps.totalTickCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compute the total number of patches applied
|
||||||
|
*
|
||||||
|
* @return the number of patches
|
||||||
|
*/
|
||||||
|
public long totalPatchCount() {
|
||||||
|
return steps.totalPatchCount() + pSteps.totalPatchCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compute the number of ticks taken, excluding p-code ticks
|
||||||
|
*
|
||||||
|
* @return the number of ticks
|
||||||
|
*/
|
||||||
|
public long tickCount() {
|
||||||
|
return steps.totalTickCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compute the number of patches, excluding p-code patches
|
||||||
|
*
|
||||||
|
* @return the number of patches
|
||||||
|
*/
|
||||||
|
public long patchCount() {
|
||||||
|
return steps.totalPatchCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compute the number of p-code ticks taken
|
||||||
|
*
|
||||||
|
* @return the number of ticks
|
||||||
|
*/
|
||||||
|
public long pTickCount() {
|
||||||
|
return pSteps.totalTickCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compute the number of p-code patches applied
|
||||||
|
*
|
||||||
|
* @return the number of patches
|
||||||
|
*/
|
||||||
|
public long pPatchCount() {
|
||||||
|
return pSteps.totalPatchCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Realize the machine state for this schedule using the given trace and machine
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* This method executes this schedule and trailing p-code steps on the given machine, assuming
|
||||||
|
* that machine is already "positioned" at the initial snapshot. Assuming successful execution,
|
||||||
|
* that machine is now said to be "positioned" at this schedule, and its state is the result of
|
||||||
|
* said execution.
|
||||||
|
*
|
||||||
|
* @param trace the trace containing the source snapshot and threads
|
||||||
|
* @param machine a machine bound to the trace whose current state reflects the initial snapshot
|
||||||
|
* @param monitor a monitor for cancellation and progress reporting
|
||||||
|
* @throws CancelledException if the execution is cancelled
|
||||||
|
*/
|
||||||
|
public void execute(Trace trace, PcodeMachine<?> machine, TaskMonitor monitor)
|
||||||
|
throws CancelledException {
|
||||||
|
TraceThread lastThread = getEventThread(trace);
|
||||||
|
lastThread =
|
||||||
|
steps.execute(trace, lastThread, machine, PcodeThread::stepInstruction, monitor);
|
||||||
|
lastThread =
|
||||||
|
pSteps.execute(trace, lastThread, machine, PcodeThread::stepPcodeOp, monitor);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Realize the machine state for this schedule using the given trace and pre-positioned machine
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* This method executes the remaining steps of this schedule and trailing p-code steps on the
|
||||||
|
* given machine, assuming that machine is already "positioned" at another given schedule.
|
||||||
|
* Assuming successful execution, that machine is now said to be "positioned" at this schedule,
|
||||||
|
* and its state is the result of said execution.
|
||||||
|
*
|
||||||
|
* @param trace the trace containing the source snapshot and threads
|
||||||
|
* @param position the current schedule of the given machine
|
||||||
|
* @param machine a machine bound to the trace whose current state reflects the given position
|
||||||
|
* @param monitor a monitor for cancellation and progress reporting
|
||||||
|
* @throws CancelledException if the execution is cancelled
|
||||||
|
* @throws IllegalArgumentException if the given position is not a prefix of this schedule
|
||||||
|
*/
|
||||||
|
public void finish(Trace trace, TraceSchedule position, PcodeMachine<?> machine,
|
||||||
|
TaskMonitor monitor) throws CancelledException {
|
||||||
|
TraceThread lastThread = position.getLastThread(trace);
|
||||||
|
Sequence remains = steps.relativize(position.steps);
|
||||||
|
if (remains.isNop()) {
|
||||||
|
Sequence pRemains = this.pSteps.relativize(position.pSteps);
|
||||||
|
lastThread =
|
||||||
|
pRemains.execute(trace, lastThread, machine, PcodeThread::stepPcodeOp, monitor);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
lastThread =
|
||||||
|
remains.execute(trace, lastThread, machine, PcodeThread::stepInstruction, monitor);
|
||||||
|
lastThread =
|
||||||
|
pSteps.execute(trace, lastThread, machine, PcodeThread::stepPcodeOp, monitor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the equivalent of executing the schedule (ignoring p-code steps) followed by stepping
|
||||||
|
* the given thread count more instructions
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* This schedule is left unmodified. If it had any p-code steps, those steps are dropped in the
|
||||||
|
* resulting schedule.
|
||||||
|
*
|
||||||
|
* @param thread the thread to step
|
||||||
|
* @param tickCount the number of ticks to take the thread forward
|
||||||
|
* @return the resulting schedule
|
||||||
|
*/
|
||||||
|
public TraceSchedule steppedForward(TraceThread thread, long tickCount) {
|
||||||
|
Sequence steps = this.steps.clone();
|
||||||
|
steps.advance(new TickStep(thread.getKey(), tickCount));
|
||||||
|
return new TraceSchedule(snap, steps, new Sequence());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected TraceSchedule doSteppedBackward(Trace trace, long tickCount, Set<Long> visited) {
|
||||||
|
if (!visited.add(snap)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
long excess = tickCount - totalTickCount() - totalPatchCount();
|
||||||
|
if (excess > 0) {
|
||||||
|
if (trace == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
TraceSnapshot source = trace.getTimeManager().getSnapshot(snap, false);
|
||||||
|
if (source == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
TraceSchedule rec = source.getSchedule();
|
||||||
|
if (rec == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return rec.doSteppedBackward(trace, excess, visited);
|
||||||
|
}
|
||||||
|
Sequence steps = this.steps.clone();
|
||||||
|
steps.rewind(tickCount);
|
||||||
|
return new TraceSchedule(snap, steps, new Sequence());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the equivalent of executing count instructions (and all p-code operations) less than
|
||||||
|
* this schedule
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* This schedule is left unmodified. If it had any p-code steps, those steps and subsequent
|
||||||
|
* patches are dropped in the resulting schedule. If count exceeds this schedule's steps, it
|
||||||
|
* will try (recursively) to step the source snapshot's schedule backward, if known. Both ticks
|
||||||
|
* and patches counts as steps.
|
||||||
|
*
|
||||||
|
* @param trace the trace of this schedule, for context
|
||||||
|
* @param stepCount the number of steps to take backward
|
||||||
|
* @return the resulting schedule or null if it cannot be computed
|
||||||
|
*/
|
||||||
|
public TraceSchedule steppedBackward(Trace trace, long stepCount) {
|
||||||
|
return doSteppedBackward(trace, stepCount, new HashSet<>());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the equivalent of executing the schedule followed by stepping the given thread
|
||||||
|
* {@code pTickCount} more p-code operations
|
||||||
|
*
|
||||||
|
* @param thread the thread to step
|
||||||
|
* @param pTickCount the number of p-code ticks to take the thread forward
|
||||||
|
* @return the resulting schedule
|
||||||
|
*/
|
||||||
|
public TraceSchedule steppedPcodeForward(TraceThread thread, int pTickCount) {
|
||||||
|
Sequence pTicks = this.pSteps.clone();
|
||||||
|
pTicks.advance(new TickStep(thread.getKey(), pTickCount));
|
||||||
|
return new TraceSchedule(snap, steps.clone(), pTicks);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the equivalent of executing count p-code operations less than this schedule
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* If {@code pStepCount} exceeds the p-code steps of this schedule, null is returned, since we
|
||||||
|
* cannot know <em>a priori</em> how many p-code steps would be required to complete the
|
||||||
|
* preceding instruction step. Both p-code ticks and p-code patches counts as p-code steps.
|
||||||
|
*
|
||||||
|
* @param pStepCount the number of p-code steps to take backward
|
||||||
|
* @return the resulting schedule or null if it cannot be computed
|
||||||
|
*/
|
||||||
|
public TraceSchedule steppedPcodeBackward(int pStepCount) {
|
||||||
|
if (pStepCount > pSteps.totalTickCount()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
Sequence pTicks = this.pSteps.clone();
|
||||||
|
pTicks.rewind(pStepCount);
|
||||||
|
return new TraceSchedule(snap, steps.clone(), pTicks);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the equivalent of executing this schedule then performing a given patch
|
||||||
|
*
|
||||||
|
* @param sleigh a single line of sleigh, excluding the terminating semicolon.
|
||||||
|
* @return the resulting schedule
|
||||||
|
*/
|
||||||
|
public TraceSchedule patched(TraceThread thread, String sleigh) {
|
||||||
|
if (!this.pSteps.isNop()) {
|
||||||
|
Sequence pTicks = this.pSteps.clone();
|
||||||
|
pTicks.advance(new PatchStep(thread.getKey(), sleigh));
|
||||||
|
return new TraceSchedule(snap, steps.clone(), pTicks);
|
||||||
|
}
|
||||||
|
Sequence ticks = this.steps.clone();
|
||||||
|
ticks.advance(new PatchStep(thread.getKey(), sleigh));
|
||||||
|
return new TraceSchedule(snap, ticks, new Sequence());
|
||||||
|
}
|
||||||
|
}
|
|
@ -29,6 +29,7 @@ import ghidra.trace.model.Trace.TraceSnapshotChangeType;
|
||||||
import ghidra.trace.model.TraceDomainObjectListener;
|
import ghidra.trace.model.TraceDomainObjectListener;
|
||||||
import ghidra.trace.model.program.TraceProgramView;
|
import ghidra.trace.model.program.TraceProgramView;
|
||||||
import ghidra.trace.model.time.*;
|
import ghidra.trace.model.time.*;
|
||||||
|
import ghidra.trace.model.time.schedule.TraceSchedule;
|
||||||
import ghidra.util.*;
|
import ghidra.util.*;
|
||||||
import ghidra.util.datastruct.ListenerSet;
|
import ghidra.util.datastruct.ListenerSet;
|
||||||
import ghidra.util.exception.ClosedException;
|
import ghidra.util.exception.ClosedException;
|
||||||
|
|
|
@ -19,6 +19,7 @@ import com.google.common.collect.*;
|
||||||
|
|
||||||
import ghidra.trace.model.Trace;
|
import ghidra.trace.model.Trace;
|
||||||
import ghidra.trace.model.time.*;
|
import ghidra.trace.model.time.*;
|
||||||
|
import ghidra.trace.model.time.schedule.TraceSchedule;
|
||||||
import ghidra.util.AbstractPeekableIterator;
|
import ghidra.util.AbstractPeekableIterator;
|
||||||
|
|
||||||
public class TraceViewportSpanIterator extends AbstractPeekableIterator<Range<Long>> {
|
public class TraceViewportSpanIterator extends AbstractPeekableIterator<Range<Long>> {
|
||||||
|
|
|
@ -13,87 +13,101 @@
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package ghidra.trace.model.time;
|
package ghidra.trace.model.time.schedule;
|
||||||
|
|
||||||
import static org.junit.Assert.*;
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import ghidra.app.plugin.processors.sleigh.SleighLanguage;
|
||||||
import ghidra.pcode.emu.*;
|
import ghidra.pcode.emu.*;
|
||||||
import ghidra.pcode.exec.*;
|
import ghidra.pcode.exec.*;
|
||||||
import ghidra.program.model.address.Address;
|
import ghidra.program.model.address.Address;
|
||||||
import ghidra.program.model.lang.RegisterValue;
|
import ghidra.program.model.lang.*;
|
||||||
import ghidra.test.AbstractGhidraHeadlessIntegrationTest;
|
import ghidra.test.AbstractGhidraHeadlessIntegrationTest;
|
||||||
|
import ghidra.test.ToyProgramBuilder;
|
||||||
import ghidra.trace.database.ToyDBTraceBuilder;
|
import ghidra.trace.database.ToyDBTraceBuilder;
|
||||||
import ghidra.trace.model.thread.TraceThread;
|
import ghidra.trace.model.thread.TraceThread;
|
||||||
import ghidra.trace.model.time.TraceSchedule.*;
|
|
||||||
import ghidra.util.database.UndoableTransaction;
|
import ghidra.util.database.UndoableTransaction;
|
||||||
import ghidra.util.task.TaskMonitor;
|
import ghidra.util.task.TaskMonitor;
|
||||||
|
|
||||||
public class TraceScheduleTest extends AbstractGhidraHeadlessIntegrationTest {
|
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
|
@Test
|
||||||
public void testParseZero() {
|
public void testParseZero() {
|
||||||
TraceSchedule time = TraceSchedule.parse("0:0");
|
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
|
@Test
|
||||||
public void testParseSimple() {
|
public void testParseSimple() {
|
||||||
TraceSchedule time = TraceSchedule.parse("0:100");
|
TraceSchedule time = TraceSchedule.parse("0:100");
|
||||||
assertEquals(
|
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
|
@Test
|
||||||
public void testToStringSimple() {
|
public void testToStringSimple() {
|
||||||
assertEquals("0:100",
|
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());
|
.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testParseWithPcodeSteps() {
|
public void testParseWithPcodeSteps() {
|
||||||
TraceSchedule time = TraceSchedule.parse("0:100.5");
|
TraceSchedule time = TraceSchedule.parse("0:100.5");
|
||||||
assertEquals(new TraceSchedule(0, TickSequence.of(new TickStep(-1, 100)),
|
assertEquals(new TraceSchedule(0, Sequence.of(new TickStep(-1, 100)),
|
||||||
TickSequence.of(new TickStep(-1, 5))), time);
|
Sequence.of(new TickStep(-1, 5))), time);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testToStringWithPcodeSteps() {
|
public void testToStringWithPcodeSteps() {
|
||||||
assertEquals("0:100.5", new TraceSchedule(0, TickSequence.of(new TickStep(-1, 100)),
|
assertEquals("0:100.5", new TraceSchedule(0, Sequence.of(new TickStep(-1, 100)),
|
||||||
TickSequence.of(new TickStep(-1, 5))).toString());
|
Sequence.of(new TickStep(-1, 5))).toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testParseWithThread() {
|
public void testParseWithThread() {
|
||||||
TraceSchedule time = TraceSchedule.parse("1:t3-100");
|
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);
|
time);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testToStringWithThread() {
|
public void testToStringWithThread() {
|
||||||
assertEquals("1:t3-100",
|
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());
|
.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testParseMultipleSteps() {
|
public void testParseMultipleSteps() {
|
||||||
TraceSchedule time = TraceSchedule.parse("1:50,t3-50");
|
TraceSchedule time = TraceSchedule.parse("1:50;t3-50");
|
||||||
assertEquals(new TraceSchedule(1,
|
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
|
@Test
|
||||||
public void testToStringMultipleSteps() {
|
public void testToStringMultipleSteps() {
|
||||||
assertEquals("1:50,t3-50",
|
assertEquals("1:50;t3-50",
|
||||||
new TraceSchedule(1, TickSequence.of(new TickStep(-1, 50), new TickStep(3, 50)),
|
new TraceSchedule(1, Sequence.of(new TickStep(-1, 50), new TickStep(3, 50)),
|
||||||
TickSequence.of()).toString());
|
Sequence.of()).toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(expected = IllegalArgumentException.class)
|
@Test(expected = IllegalArgumentException.class)
|
||||||
|
@ -118,24 +132,24 @@ public class TraceScheduleTest extends AbstractGhidraHeadlessIntegrationTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testAdvance() {
|
public void testAdvance() {
|
||||||
TickSequence seq = new TickSequence();
|
Sequence seq = new Sequence();
|
||||||
seq.advance(new TickStep(-1, 0));
|
seq.advance(new TickStep(-1, 0));
|
||||||
assertEquals(TickSequence.of(), seq);
|
assertEquals(Sequence.of(), seq);
|
||||||
|
|
||||||
seq.advance(new TickStep(-1, 10));
|
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));
|
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));
|
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));
|
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);
|
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)
|
@Test(expected = IllegalArgumentException.class)
|
||||||
|
@ -150,13 +164,13 @@ public class TraceScheduleTest extends AbstractGhidraHeadlessIntegrationTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testRewind() {
|
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(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(0, seq.rewind(25));
|
||||||
assertEquals("10,t1-20", seq.toString());
|
assertEquals("10;t1-20", seq.toString());
|
||||||
|
|
||||||
assertEquals(0, seq.rewind(27));
|
assertEquals(0, seq.rewind(27));
|
||||||
assertEquals("3", seq.toString());
|
assertEquals("3", seq.toString());
|
||||||
|
@ -170,7 +184,7 @@ public class TraceScheduleTest extends AbstractGhidraHeadlessIntegrationTest {
|
||||||
|
|
||||||
@Test(expected = IllegalArgumentException.class)
|
@Test(expected = IllegalArgumentException.class)
|
||||||
public void testRewindNegativeErr() {
|
public void testRewindNegativeErr() {
|
||||||
TickSequence seq = TickSequence.parse("10,t1-20,t2-30");
|
Sequence seq = Sequence.parse("10;t1-20;t2-30");
|
||||||
seq.rewind(-1);
|
seq.rewind(-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -214,22 +228,22 @@ public class TraceScheduleTest extends AbstractGhidraHeadlessIntegrationTest {
|
||||||
expectU("0:t0-10", "0:t1-10");
|
expectU("0:t0-10", "0:t1-10");
|
||||||
// We don't know how many p-code steps complete an instruction step
|
// 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.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-11");
|
||||||
expectR("0:t0-10", "0:t0-10,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-11;t1-5");
|
||||||
expectR("0:t0-10", "0:t0-10.1");
|
expectR("0:t0-10", "0:t0-10.1");
|
||||||
expectR("0:t0-10", "0:t0-11.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-10;t1-5.1");
|
||||||
expectR("0:t0-10", "0:t0-11,t1-5.1");
|
expectR("0:t0-10", "0:t0-11;t1-5.1");
|
||||||
|
|
||||||
expectE("0:t0-10", "0:t0-10");
|
expectE("0:t0-10", "0:t0-10");
|
||||||
expectE("0:t0-10.1", "0:t0-10.1");
|
expectE("0:t0-10.1", "0:t0-10.1");
|
||||||
}
|
}
|
||||||
|
|
||||||
public String strRelativize(String fromSpec, String toSpec) {
|
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();
|
return seq == null ? null : seq.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -239,7 +253,7 @@ public class TraceScheduleTest extends AbstractGhidraHeadlessIntegrationTest {
|
||||||
assertEquals("", strRelativize("10", "10"));
|
assertEquals("", strRelativize("10", "10"));
|
||||||
assertEquals("9", strRelativize("1", "10"));
|
assertEquals("9", strRelativize("1", "10"));
|
||||||
assertEquals("t1-9", strRelativize("t1-1", "t1-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)
|
@Test(expected = IllegalArgumentException.class)
|
||||||
|
@ -249,7 +263,7 @@ public class TraceScheduleTest extends AbstractGhidraHeadlessIntegrationTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testTotalStepCount() {
|
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> {
|
protected static class TestThread implements PcodeThread<Void> {
|
||||||
|
@ -346,7 +360,13 @@ public class TraceScheduleTest extends AbstractGhidraHeadlessIntegrationTest {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public PcodeExecutor<Void> getExecutor() {
|
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
|
@Override
|
||||||
|
@ -376,7 +396,7 @@ public class TraceScheduleTest extends AbstractGhidraHeadlessIntegrationTest {
|
||||||
protected final List<String> record = new ArrayList<>();
|
protected final List<String> record = new ArrayList<>();
|
||||||
|
|
||||||
public TestMachine() {
|
public TestMachine() {
|
||||||
super(null, null, null);
|
super(TOY_BE_64_LANG, null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -398,8 +418,8 @@ public class TraceScheduleTest extends AbstractGhidraHeadlessIntegrationTest {
|
||||||
@Test
|
@Test
|
||||||
public void testExecute() throws Exception {
|
public void testExecute() throws Exception {
|
||||||
TestMachine machine = new TestMachine();
|
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 (ToyDBTraceBuilder tb = new ToyDBTraceBuilder("test", ToyProgramBuilder._TOY64_BE)) {
|
||||||
TraceThread t2;
|
TraceThread t2;
|
||||||
try (UndoableTransaction tid = tb.startTransaction()) {
|
try (UndoableTransaction tid = tb.startTransaction()) {
|
||||||
tb.trace.getThreadManager().createThread("Threads[0]", 0);
|
tb.trace.getThreadManager().createThread("Threads[0]", 0);
|
||||||
|
@ -424,10 +444,34 @@ public class TraceScheduleTest extends AbstractGhidraHeadlessIntegrationTest {
|
||||||
machine.record);
|
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)
|
@Test(expected = IllegalArgumentException.class)
|
||||||
public void testExecuteNoEventThreadErr() throws Exception {
|
public void testExecuteNoEventThreadErr() throws Exception {
|
||||||
TestMachine machine = new TestMachine();
|
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 (ToyDBTraceBuilder tb = new ToyDBTraceBuilder("test", "Toy:BE:64:default")) {
|
||||||
try (UndoableTransaction tid = tb.startTransaction()) {
|
try (UndoableTransaction tid = tb.startTransaction()) {
|
||||||
tb.trace.getThreadManager().createThread("Threads[0]", 0);
|
tb.trace.getThreadManager().createThread("Threads[0]", 0);
|
||||||
|
@ -442,7 +486,7 @@ public class TraceScheduleTest extends AbstractGhidraHeadlessIntegrationTest {
|
||||||
@Test(expected = IllegalArgumentException.class)
|
@Test(expected = IllegalArgumentException.class)
|
||||||
public void testExecuteBadThreadKeyErr() throws Exception {
|
public void testExecuteBadThreadKeyErr() throws Exception {
|
||||||
TestMachine machine = new TestMachine();
|
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")) {
|
try (ToyDBTraceBuilder tb = new ToyDBTraceBuilder("test", "Toy:BE:64:default")) {
|
||||||
TraceThread t2;
|
TraceThread t2;
|
||||||
try (UndoableTransaction tid = tb.startTransaction()) {
|
try (UndoableTransaction tid = tb.startTransaction()) {
|
||||||
|
@ -458,7 +502,7 @@ public class TraceScheduleTest extends AbstractGhidraHeadlessIntegrationTest {
|
||||||
@Test
|
@Test
|
||||||
public void testFinish() throws Exception {
|
public void testFinish() throws Exception {
|
||||||
TestMachine machine = new TestMachine();
|
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 (ToyDBTraceBuilder tb = new ToyDBTraceBuilder("test", "Toy:BE:64:default")) {
|
||||||
TraceThread t2;
|
TraceThread t2;
|
||||||
try (UndoableTransaction tid = tb.startTransaction()) {
|
try (UndoableTransaction tid = tb.startTransaction()) {
|
||||||
|
@ -467,7 +511,7 @@ public class TraceScheduleTest extends AbstractGhidraHeadlessIntegrationTest {
|
||||||
t2 = tb.trace.getThreadManager().createThread("Threads[2]", 0);
|
t2 = tb.trace.getThreadManager().createThread("Threads[2]", 0);
|
||||||
tb.trace.getTimeManager().getSnapshot(1, true).setEventThread(t2);
|
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(
|
assertEquals(List.of(
|
||||||
|
@ -481,7 +525,7 @@ public class TraceScheduleTest extends AbstractGhidraHeadlessIntegrationTest {
|
||||||
@Test
|
@Test
|
||||||
public void testFinishPcode() throws Exception {
|
public void testFinishPcode() throws Exception {
|
||||||
TestMachine machine = new TestMachine();
|
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 (ToyDBTraceBuilder tb = new ToyDBTraceBuilder("test", "Toy:BE:64:default")) {
|
||||||
TraceThread t2;
|
TraceThread t2;
|
||||||
try (UndoableTransaction tid = tb.startTransaction()) {
|
try (UndoableTransaction tid = tb.startTransaction()) {
|
||||||
|
@ -490,7 +534,7 @@ public class TraceScheduleTest extends AbstractGhidraHeadlessIntegrationTest {
|
||||||
t2 = tb.trace.getThreadManager().createThread("Threads[2]", 0);
|
t2 = tb.trace.getThreadManager().createThread("Threads[2]", 0);
|
||||||
tb.trace.getTimeManager().getSnapshot(1, true).setEventThread(t2);
|
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);
|
TaskMonitor.DUMMY);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -502,7 +546,7 @@ public class TraceScheduleTest extends AbstractGhidraHeadlessIntegrationTest {
|
||||||
@Test(expected = IllegalArgumentException.class)
|
@Test(expected = IllegalArgumentException.class)
|
||||||
public void testFinishUnrelatedErr() throws Exception {
|
public void testFinishUnrelatedErr() throws Exception {
|
||||||
TestMachine machine = new TestMachine();
|
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 (ToyDBTraceBuilder tb = new ToyDBTraceBuilder("test", "Toy:BE:64:default")) {
|
||||||
TraceThread t2;
|
TraceThread t2;
|
||||||
try (UndoableTransaction tid = tb.startTransaction()) {
|
try (UndoableTransaction tid = tb.startTransaction()) {
|
||||||
|
@ -511,7 +555,7 @@ public class TraceScheduleTest extends AbstractGhidraHeadlessIntegrationTest {
|
||||||
t2 = tb.trace.getThreadManager().createThread("Threads[2]", 0);
|
t2 = tb.trace.getThreadManager().createThread("Threads[2]", 0);
|
||||||
tb.trace.getTimeManager().getSnapshot(1, true).setEventThread(t2);
|
tb.trace.getTimeManager().getSnapshot(1, true).setEventThread(t2);
|
||||||
}
|
}
|
||||||
time.finish(tb.trace, TraceSchedule.parse("1:4,t0-4"), machine, TaskMonitor.DUMMY);
|
time.finish(tb.trace, TraceSchedule.parse("1:4;t0-4"), machine, TaskMonitor.DUMMY);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -26,7 +26,7 @@ import com.google.common.collect.*;
|
||||||
import ghidra.test.AbstractGhidraHeadlessIntegrationTest;
|
import ghidra.test.AbstractGhidraHeadlessIntegrationTest;
|
||||||
import ghidra.trace.database.ToyDBTraceBuilder;
|
import ghidra.trace.database.ToyDBTraceBuilder;
|
||||||
import ghidra.trace.database.time.DBTraceTimeManager;
|
import ghidra.trace.database.time.DBTraceTimeManager;
|
||||||
import ghidra.trace.model.time.TraceSchedule;
|
import ghidra.trace.model.time.schedule.TraceSchedule;
|
||||||
import ghidra.util.database.UndoableTransaction;
|
import ghidra.util.database.UndoableTransaction;
|
||||||
|
|
||||||
public class DefaultTraceTimeViewportTest extends AbstractGhidraHeadlessIntegrationTest {
|
public class DefaultTraceTimeViewportTest extends AbstractGhidraHeadlessIntegrationTest {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue