GP-2062: Add Skip Instruction button for emulator

This commit is contained in:
Dan 2022-06-16 09:08:17 -04:00
parent 00dbd26511
commit 4736a3c924
20 changed files with 961 additions and 614 deletions

View File

@ -120,6 +120,7 @@ src/main/help/help/topics/DebuggerTargetsPlugin/images/disconnect.png||GHIDRA|||
src/main/help/help/topics/DebuggerThreadsPlugin/DebuggerThreadsPlugin.html||GHIDRA||||END| src/main/help/help/topics/DebuggerThreadsPlugin/DebuggerThreadsPlugin.html||GHIDRA||||END|
src/main/help/help/topics/DebuggerThreadsPlugin/images/DebuggerThreadsPlugin.png||GHIDRA||||END| src/main/help/help/topics/DebuggerThreadsPlugin/images/DebuggerThreadsPlugin.png||GHIDRA||||END|
src/main/help/help/topics/DebuggerThreadsPlugin/images/continue.png||GHIDRA||||END| src/main/help/help/topics/DebuggerThreadsPlugin/images/continue.png||GHIDRA||||END|
src/main/help/help/topics/DebuggerThreadsPlugin/images/skipover.png||GHIDRA||||END|
src/main/help/help/topics/DebuggerThreadsPlugin/images/stepback.png||GHIDRA||||END| src/main/help/help/topics/DebuggerThreadsPlugin/images/stepback.png||GHIDRA||||END|
src/main/help/help/topics/DebuggerThreadsPlugin/images/stepinto.png||GHIDRA||||END| src/main/help/help/topics/DebuggerThreadsPlugin/images/stepinto.png||GHIDRA||||END|
src/main/help/help/topics/DebuggerTimePlugin/DebuggerTimePlugin.html||GHIDRA||||END| src/main/help/help/topics/DebuggerTimePlugin/DebuggerTimePlugin.html||GHIDRA||||END|

View File

@ -125,6 +125,18 @@
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,
emulation may halt early if it encounters certain instructions or causes an exception.</P> emulation may halt early if it encounters certain instructions or causes an exception.</P>
<H3><A name="emu_trace_skip_tick_forward"></A><IMG alt="" src="images/skipover.png">Emulate
Trace Skip Tick Forward</H3>
<P>This action is available when a thread is selected. It steps the current thread forward by
skipping the next instruction, using emulation. Note that emulation does not affect the target.
Furthermore, emulation may halt early if it encounters certain instructions or causes an
exception. This action may be used skip subroutines; <B>however</B>, the stack may require
additional patching, e.g., to clean up stack parameters, depending on the calling convention.
This action <EM>does not</EM> perform those patches automatically. It only advances the program
counter. You may use <A href="#goto_time">Go To Time</A> to append the require stack patch,
e.g., <CODE>t0-{RSP=RSP+8}</CODE>.</P>
<H3><A name="seek_trace_present"></A><IMG alt="" src="images/continue.png">Seek Trace to <H3><A name="seek_trace_present"></A><IMG alt="" src="images/continue.png">Seek Trace to
Present</H3> Present</H3>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 320 B

View File

@ -87,6 +87,7 @@ public interface DebuggerResources {
ImageIcon ICON_STEP_INTO = ResourceManager.loadImage("images/stepinto.png"); ImageIcon ICON_STEP_INTO = ResourceManager.loadImage("images/stepinto.png");
ImageIcon ICON_STEP_OVER = ResourceManager.loadImage("images/stepover.png"); ImageIcon ICON_STEP_OVER = ResourceManager.loadImage("images/stepover.png");
ImageIcon ICON_SKIP_OVER = ResourceManager.loadImage("images/skipover.png");
ImageIcon ICON_STEP_FINISH = ResourceManager.loadImage("images/stepout.png"); ImageIcon ICON_STEP_FINISH = ResourceManager.loadImage("images/stepout.png");
ImageIcon ICON_STEP_BACK = ResourceManager.loadImage("images/stepback.png"); ImageIcon ICON_STEP_BACK = ResourceManager.loadImage("images/stepback.png");
// TODO: Draw new icons? // TODO: Draw new icons?
@ -1641,81 +1642,12 @@ public interface DebuggerResources {
} }
} }
interface StepSnapForwardAction {
String NAME = "Step Trace Snap Forward";
String DESCRIPTION = "Navigate the recording forward one snap";
Icon ICON = ICON_SNAP_FORWARD;
String GROUP = GROUP_CONTROL;
String HELP_ANCHOR = "step_trace_snap_forward";
static ActionBuilder builder(Plugin owner) {
String ownerName = owner.getName();
return new ActionBuilder(NAME, ownerName)
.description(DESCRIPTION)
.toolBarIcon(ICON)
.toolBarGroup(GROUP, "4")
.helpLocation(new HelpLocation(ownerName, HELP_ANCHOR));
}
}
abstract class AbstractStepSnapForwardAction extends DockingAction {
public static final String NAME = StepSnapForwardAction.NAME;
public static final Icon ICON = StepSnapForwardAction.ICON;
public static final String HELP_ANCHOR = StepSnapForwardAction.HELP_ANCHOR;
public AbstractStepSnapForwardAction(Plugin owner) {
super(NAME, owner.getName());
setDescription(StepSnapForwardAction.DESCRIPTION);
setHelpLocation(new HelpLocation(owner.getName(), HELP_ANCHOR));
}
}
abstract class AbstractEmulateTickForwardAction extends DockingAction {
public static final String NAME = "Emulate Trace Tick Forward";
public static final Icon ICON = ICON_STEP_INTO;
public static final String HELP_ANCHOR = "emu_trace_tick_forward";
public AbstractEmulateTickForwardAction(Plugin owner) {
super(NAME, owner.getName());
setDescription("Emulate the recording forward one tick");
setHelpLocation(new HelpLocation(owner.getName(), HELP_ANCHOR));
}
}
interface EmulatePcodeForwardAction {
String NAME = "Emulate Trace p-code Forward";
String DESCRIPTION = "Navigate the recording forward one p-code tick";
Icon ICON = ICON_STEP_INTO;
String GROUP = GROUP_CONTROL;
String HELP_ANCHOR = "emu_trace_pcode_forward";
static ActionBuilder builder(Plugin owner) {
String ownerName = owner.getName();
return new ActionBuilder(NAME, ownerName)
.description(DESCRIPTION)
.toolBarIcon(ICON)
.toolBarGroup(GROUP)
.helpLocation(new HelpLocation(ownerName, HELP_ANCHOR));
}
}
abstract class AbstractEmulateTickBackwardAction extends DockingAction {
public static final String NAME = "Emulate Trace Tick Backward";
public static final Icon ICON = ICON_STEP_BACK;
public static final String HELP_ANCHOR = "emu_trace_tick_backward";
public AbstractEmulateTickBackwardAction(Plugin owner) {
super(NAME, owner.getName());
setDescription("Emulate the recording backward one tick");
setHelpLocation(new HelpLocation(owner.getName(), HELP_ANCHOR));
}
}
interface StepSnapBackwardAction { interface StepSnapBackwardAction {
String NAME = "Step Trace Snap Backward"; String NAME = "Step Trace Snap Backward";
String DESCRIPTION = "Navigate the recording backward one snap"; String DESCRIPTION = "Navigate the recording backward one snap";
Icon ICON = ICON_SNAP_BACKWARD; Icon ICON = ICON_SNAP_BACKWARD;
String GROUP = GROUP_CONTROL; String GROUP = GROUP_CONTROL;
String ORDER = "1";
String HELP_ANCHOR = "step_trace_snap_backward"; String HELP_ANCHOR = "step_trace_snap_backward";
static ActionBuilder builder(Plugin owner) { static ActionBuilder builder(Plugin owner) {
@ -1723,20 +1655,80 @@ public interface DebuggerResources {
return new ActionBuilder(NAME, ownerName) return new ActionBuilder(NAME, ownerName)
.description(DESCRIPTION) .description(DESCRIPTION)
.toolBarIcon(ICON) .toolBarIcon(ICON)
.toolBarGroup(GROUP, "1") .toolBarGroup(GROUP, ORDER)
.helpLocation(new HelpLocation(ownerName, HELP_ANCHOR)); .helpLocation(new HelpLocation(ownerName, HELP_ANCHOR));
} }
} }
abstract class AbstractStepSnapBackwardAction extends DockingAction { interface StepSnapForwardAction {
public static final String NAME = StepSnapBackwardAction.NAME; String NAME = "Step Trace Snap Forward";
public static final Icon ICON = StepSnapBackwardAction.ICON;; String DESCRIPTION = "Navigate the recording forward one snap";
public static final String HELP_ANCHOR = StepSnapBackwardAction.HELP_ANCHOR; Icon ICON = ICON_SNAP_FORWARD;
String GROUP = GROUP_CONTROL;
String ORDER = "5";
String HELP_ANCHOR = "step_trace_snap_forward";
public AbstractStepSnapBackwardAction(Plugin owner) { static ActionBuilder builder(Plugin owner) {
super(NAME, owner.getName()); String ownerName = owner.getName();
setDescription(StepSnapBackwardAction.DESCRIPTION); return new ActionBuilder(NAME, ownerName)
setHelpLocation(new HelpLocation(owner.getName(), HELP_ANCHOR)); .description(DESCRIPTION)
.toolBarIcon(ICON)
.toolBarGroup(GROUP, ORDER)
.helpLocation(new HelpLocation(ownerName, HELP_ANCHOR));
}
}
interface EmulateTickBackwardAction {
String NAME = "Emulate Trace Tick Backward";
String DESCRIPTION = "Emulate the recording backward one tick";
Icon ICON = ICON_STEP_BACK;
String GROUP = GROUP_CONTROL;
String ORDER = "2";
String HELP_ANCHOR = "emu_trace_tick_backward";
static ActionBuilder builder(Plugin owner) {
String ownerName = owner.getName();
return new ActionBuilder(NAME, ownerName)
.description(DESCRIPTION)
.toolBarIcon(ICON)
.toolBarGroup(GROUP, ORDER)
.helpLocation(new HelpLocation(ownerName, HELP_ANCHOR));
}
}
interface EmulateTickForwardAction {
String NAME = "Emulate Trace Tick Forward";
String DESCRIPTION = "Emulate the recording forward one instruction";
Icon ICON = ICON_STEP_INTO;
String GROUP = GROUP_CONTROL;
String ORDER = "3";
String HELP_ANCHOR = "emu_trace_tick_forward";
static ActionBuilder builder(Plugin owner) {
String ownerName = owner.getName();
return new ActionBuilder(NAME, ownerName)
.description(DESCRIPTION)
.toolBarIcon(ICON)
.toolBarGroup(GROUP, ORDER)
.helpLocation(new HelpLocation(ownerName, HELP_ANCHOR));
}
}
interface EmulateSkipTickForwardAction {
String NAME = "Emulate Trace Skip Tick Forward";
String DESCRIPTION = "Emulate the recording forward by skipping one instruction";
Icon ICON = ICON_SKIP_OVER;
String GROUP = GROUP_CONTROL;
String ORDER = "4";
String HELP_ANCHOR = "emu_trace_skip_tick_forward";
static ActionBuilder builder(Plugin owner) {
String ownerName = owner.getName();
return new ActionBuilder(NAME, ownerName)
.description(DESCRIPTION)
.toolBarIcon(ICON)
.toolBarGroup(GROUP, ORDER)
.helpLocation(new HelpLocation(ownerName, HELP_ANCHOR));
} }
} }
@ -1745,8 +1737,45 @@ public interface DebuggerResources {
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 ORDER = "2";
String HELP_ANCHOR = "emu_trace_pcode_backward"; String HELP_ANCHOR = "emu_trace_pcode_backward";
static ActionBuilder builder(Plugin owner) {
String ownerName = owner.getName();
return new ActionBuilder(NAME, ownerName)
.description(DESCRIPTION)
.toolBarIcon(ICON)
.toolBarGroup(GROUP, ORDER)
.helpLocation(new HelpLocation(ownerName, HELP_ANCHOR));
}
}
interface EmulatePcodeForwardAction {
String NAME = "Emulate Trace p-code Forward";
String DESCRIPTION = "Emulate the recording forward one p-code tick";
Icon ICON = ICON_STEP_INTO;
String GROUP = GROUP_CONTROL;
String ORDER = "3";
String HELP_ANCHOR = "emu_trace_pcode_forward";
static ActionBuilder builder(Plugin owner) {
String ownerName = owner.getName();
return new ActionBuilder(NAME, ownerName)
.description(DESCRIPTION)
.toolBarIcon(ICON)
.toolBarGroup(GROUP, ORDER)
.helpLocation(new HelpLocation(ownerName, HELP_ANCHOR));
}
}
interface EmulateSkipPcodeForwardAction {
String NAME = "Emulate Trace Skip P-code Forward";
String DESCRIPTION = "Emulate the recording forward by skipping one p-code op";
Icon ICON = ICON_SKIP_OVER;
String GROUP = GROUP_CONTROL;
String ORDER = "4";
String HELP_ANCHOR = "emu_trace_skip_pcode_forward";
static ActionBuilder builder(Plugin owner) { static ActionBuilder builder(Plugin owner) {
String ownerName = owner.getName(); String ownerName = owner.getName();
return new ActionBuilder(NAME, ownerName) return new ActionBuilder(NAME, ownerName)
@ -1757,15 +1786,20 @@ public interface DebuggerResources {
} }
} }
abstract class AbstractSeekTracePresentAction extends ToggleDockingAction { interface SeekTracePresentAction {
public static final String NAME = "Seek Trace Present"; String NAME = "Seek Trace Present";
public static final Icon ICON = ICON_SEEK_PRESENT; String DESCRIPTION = "Track the tool to the latest snap";
public static final String HELP_ANCHOR = "seek_trace_present"; Icon ICON = ICON_SEEK_PRESENT;
String GROUP = "zz";
String HELP_ANCHOR = "seek_trace_present";
public AbstractSeekTracePresentAction(Plugin owner) { static ToggleActionBuilder builder(Plugin owner) {
super(NAME, owner.getName()); String ownerName = owner.getName();
setDescription("Track the tool to the latest snap"); return new ToggleActionBuilder(NAME, ownerName)
setHelpLocation(new HelpLocation(owner.getName(), HELP_ANCHOR)); .description(DESCRIPTION)
.toolBarIcon(ICON)
.toolBarGroup(GROUP)
.helpLocation(new HelpLocation(ownerName, HELP_ANCHOR));
} }
} }

View File

@ -87,172 +87,6 @@ public class DebuggerThreadsProvider extends ComponentProviderAdapter {
return true; return true;
} }
protected class StepSnapBackwardAction extends AbstractStepSnapBackwardAction {
public static final String GROUP = DebuggerResources.GROUP_CONTROL;
public StepSnapBackwardAction() {
super(plugin);
setToolBarData(new ToolBarData(ICON, GROUP, "1"));
addLocalAction(this);
setEnabled(false);
}
@Override
public void actionPerformed(ActionContext context) {
if (current.getTime().isSnapOnly()) {
traceManager.activateSnap(current.getSnap() - 1);
}
else {
traceManager.activateSnap(current.getSnap());
}
}
@Override
public boolean isEnabledForContext(ActionContext context) {
if (current.getTrace() == null) {
return false;
}
if (!current.getTime().isSnapOnly()) {
return true;
}
if (current.getSnap() <= 0) {
return false;
}
return true;
}
}
protected class EmulateTickBackwardAction extends AbstractEmulateTickBackwardAction {
public static final String GROUP = DebuggerResources.GROUP_CONTROL;
public EmulateTickBackwardAction() {
super(plugin);
setToolBarData(new ToolBarData(ICON, GROUP, "2"));
addLocalAction(this);
setEnabled(false);
}
@Override
public void actionPerformed(ActionContext context) {
if (current.getTrace() == null) {
return;
}
TraceSchedule time = current.getTime().steppedBackward(current.getTrace(), 1);
if (time == null) {
return;
}
traceManager.activateTime(time);
}
@Override
public boolean isEnabledForContext(ActionContext context) {
if (emulationService == null) {
return false;
}
if (current.getTrace() == null) {
return false;
}
if (current.getTime().steppedBackward(current.getTrace(), 1) == null) {
return false;
}
return true;
}
}
protected class EmulateTickForwardAction extends AbstractEmulateTickForwardAction {
public static final String GROUP = DebuggerResources.GROUP_CONTROL;
public EmulateTickForwardAction() {
super(plugin);
setToolBarData(new ToolBarData(ICON, GROUP, "3"));
addLocalAction(this);
setEnabled(false);
}
@Override
public void actionPerformed(ActionContext context) {
if (current.getThread() == null) {
return;
}
TraceSchedule time = current.getTime().steppedForward(current.getThread(), 1);
traceManager.activateTime(time);
}
@Override
public boolean isEnabledForContext(ActionContext context) {
if (emulationService == null) {
return false;
}
if (current.getThread() == null) {
return false;
}
return true;
}
}
protected class StepSnapForwardAction extends AbstractStepSnapForwardAction {
public static final String GROUP = DebuggerResources.GROUP_CONTROL;
public StepSnapForwardAction() {
super(plugin);
setToolBarData(new ToolBarData(ICON, GROUP, "4"));
addLocalAction(this);
setEnabled(false);
}
@Override
public void actionPerformed(ActionContext context) {
traceManager.activateSnap(current.getSnap() + 1);
}
@Override
public boolean isEnabledForContext(ActionContext context) {
Trace curTrace = current.getTrace();
if (curTrace == null) {
return false;
}
Long maxSnap = curTrace.getTimeManager().getMaxSnap();
if (maxSnap == null || current.getSnap() >= maxSnap) {
return false;
}
return true;
}
}
protected class SeekTracePresentAction extends AbstractSeekTracePresentAction
implements BooleanChangeAdapter {
public static final String GROUP = "zz";
public SeekTracePresentAction() {
super(plugin);
setToolBarData(new ToolBarData(ICON, GROUP));
addLocalAction(this);
setSelected(traceManager == null ? false : traceManager.isAutoActivatePresent());
traceManager.addAutoActivatePresentChangeListener(this);
}
@Override
public boolean isEnabledForContext(ActionContext context) {
return traceManager != null;
}
@Override
public void actionPerformed(ActionContext context) {
if (traceManager == null) {
return;
}
traceManager.setAutoActivatePresent(isSelected());
}
@Override
public void changed(Boolean value) {
if (isSelected() == value) {
return;
}
setSelected(value);
}
}
protected static class ThreadTableModel protected static class ThreadTableModel
extends RowWrappedEnumeratedColumnTableModel< // extends RowWrappedEnumeratedColumnTableModel< //
ThreadTableColumns, ObjectKey, ThreadRow, TraceThread> { ThreadTableColumns, ObjectKey, ThreadRow, TraceThread> {
@ -321,9 +155,9 @@ public class DebuggerThreadsProvider extends ComponentProviderAdapter {
private final DebuggerThreadsPlugin plugin; private final DebuggerThreadsPlugin plugin;
// @AutoServiceConsumed by method // @AutoServiceConsumed by method
private DebuggerModelService modelService; private DebuggerModelService modelService;
@AutoServiceConsumed // NB, also by method // @AutoServiceConsumed by method
private DebuggerTraceManagerService traceManager; private DebuggerTraceManagerService traceManager;
@AutoServiceConsumed // NB, also by method @AutoServiceConsumed // NB, also by method
private DebuggerEmulationService emulationService; private DebuggerEmulationService emulationService;
@ -337,6 +171,10 @@ public class DebuggerThreadsProvider extends ComponentProviderAdapter {
private final ThreadsListener threadsListener = new ThreadsListener(); private final ThreadsListener threadsListener = new ThreadsListener();
private final CollectionChangeListener<TraceRecorder> recordersListener = private final CollectionChangeListener<TraceRecorder> recordersListener =
new RecordersChangeListener(); new RecordersChangeListener();
private final BooleanChangeAdapter activatePresentChangeListener =
this::changedAutoActivatePresent;
private final BooleanChangeAdapter synchronizeFocusChangeListener =
this::changedSynchronizeFocus;
/* package access for testing */ /* package access for testing */
final RangeTableCellRenderer<Long> rangeRenderer = new RangeTableCellRenderer<>(); final RangeTableCellRenderer<Long> rangeRenderer = new RangeTableCellRenderer<>();
final RangeCursorTableHeaderRenderer<Long> headerRenderer = final RangeCursorTableHeaderRenderer<Long> headerRenderer =
@ -354,11 +192,12 @@ public class DebuggerThreadsProvider extends ComponentProviderAdapter {
private ActionContext myActionContext; private ActionContext myActionContext;
DockingAction actionSaveTrace; DockingAction actionSaveTrace;
StepSnapBackwardAction actionStepSnapBackward; DockingAction actionStepSnapBackward;
EmulateTickBackwardAction actionEmulateTickBackward; DockingAction actionEmulateTickBackward;
EmulateTickForwardAction actionEmulateTickForward; DockingAction actionEmulateTickForward;
StepSnapForwardAction actionStepSnapForward; DockingAction actionEmulateTickSkipForward;
SeekTracePresentAction actionSeekTracePresent; DockingAction actionStepSnapForward;
ToggleDockingAction actionSeekTracePresent;
ToggleDockingAction actionSyncFocus; ToggleDockingAction actionSyncFocus;
DockingAction actionGoToTime; DockingAction actionGoToTime;
@ -410,9 +249,21 @@ public class DebuggerThreadsProvider extends ComponentProviderAdapter {
@AutoServiceConsumed @AutoServiceConsumed
public void setTraceManager(DebuggerTraceManagerService traceManager) { public void setTraceManager(DebuggerTraceManagerService traceManager) {
if (traceManager != null && actionSeekTracePresent != null) { if (this.traceManager != null) {
actionSeekTracePresent.setSelected(traceManager.isAutoActivatePresent()); this.traceManager
actionSyncFocus.setSelected(traceManager.isSynchronizeFocus()); .removeAutoActivatePresentChangeListener(activatePresentChangeListener);
this.traceManager.removeSynchronizeFocusChangeListener(synchronizeFocusChangeListener);
}
this.traceManager = traceManager;
if (traceManager != null) {
traceManager.addAutoActivatePresentChangeListener(activatePresentChangeListener);
traceManager.addSynchronizeFocusChangeListener(synchronizeFocusChangeListener);
if (actionSeekTracePresent != null) {
actionSeekTracePresent.setSelected(traceManager.isAutoActivatePresent());
}
if (actionSyncFocus != null) {
actionSyncFocus.setSelected(traceManager.isSynchronizeFocus());
}
} }
contextChanged(); contextChanged();
} }
@ -633,11 +484,34 @@ 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 = StepSnapBackwardAction.builder(plugin)
actionEmulateTickBackward = new EmulateTickBackwardAction(); .enabledWhen(this::isStepSnapBackwardEnabled)
actionEmulateTickForward = new EmulateTickForwardAction(); .enabled(false)
actionStepSnapForward = new StepSnapForwardAction(); .onAction(this::activatedStepSnapBackward)
actionSeekTracePresent = new SeekTracePresentAction(); .buildAndInstallLocal(this);
actionEmulateTickBackward = EmulateTickBackwardAction.builder(plugin)
.enabledWhen(this::isEmulateTickBackwardEnabled)
.onAction(this::activatedEmulateTickBackward)
.buildAndInstallLocal(this);
actionEmulateTickForward = EmulateTickForwardAction.builder(plugin)
.enabledWhen(this::isEmulateTickForwardEnabled)
.onAction(this::activatedEmulateTickForward)
.buildAndInstallLocal(this);
actionEmulateTickSkipForward = EmulateSkipTickForwardAction.builder(plugin)
.enabledWhen(this::isEmulateSkipTickForwardEnabled)
.onAction(this::activatedEmulateSkipTickForward)
.buildAndInstallLocal(this);
actionStepSnapForward = StepSnapForwardAction.builder(plugin)
.enabledWhen(this::isStepSnapForwardEnabled)
.enabled(false)
.onAction(this::activatedStepSnapForward)
.buildAndInstallLocal(this);
actionSeekTracePresent = SeekTracePresentAction.builder(plugin)
.enabledWhen(this::isSeekTracePresentEnabled)
.onAction(this::toggledSeekTracePresent)
.selected(traceManager == null ? false : traceManager.isAutoActivatePresent())
.buildAndInstallLocal(this);
actionSyncFocus = SynchronizeFocusAction.builder(plugin) actionSyncFocus = SynchronizeFocusAction.builder(plugin)
.selected(traceManager != null && traceManager.isSynchronizeFocus()) .selected(traceManager != null && traceManager.isSynchronizeFocus())
.enabledWhen(c -> traceManager != null) .enabledWhen(c -> traceManager != null)
@ -672,6 +546,129 @@ public class DebuggerThreadsProvider extends ComponentProviderAdapter {
.buildAndInstallLocal(this); .buildAndInstallLocal(this);
} }
private boolean isStepSnapBackwardEnabled(ActionContext context) {
if (current.getTrace() == null) {
return false;
}
if (!current.getTime().isSnapOnly()) {
return true;
}
if (current.getSnap() <= 0) {
return false;
}
return true;
}
private void activatedStepSnapBackward(ActionContext context) {
if (current.getTime().isSnapOnly()) {
traceManager.activateSnap(current.getSnap() - 1);
}
else {
traceManager.activateSnap(current.getSnap());
}
}
private boolean isEmulateTickBackwardEnabled(ActionContext context) {
if (emulationService == null) {
return false;
}
if (current.getTrace() == null) {
return false;
}
if (current.getTime().steppedBackward(current.getTrace(), 1) == null) {
return false;
}
return true;
}
private void activatedEmulateTickBackward(ActionContext context) {
if (current.getTrace() == null) {
return;
}
TraceSchedule time = current.getTime().steppedBackward(current.getTrace(), 1);
if (time == null) {
return;
}
traceManager.activateTime(time);
}
private boolean isEmulateTickForwardEnabled(ActionContext context) {
if (emulationService == null) {
return false;
}
if (current.getThread() == null) {
return false;
}
return true;
}
private void activatedEmulateTickForward(ActionContext context) {
if (current.getThread() == null) {
return;
}
TraceSchedule time = current.getTime().steppedForward(current.getThread(), 1);
traceManager.activateTime(time);
}
private boolean isEmulateSkipTickForwardEnabled(ActionContext context) {
if (emulationService == null) {
return false;
}
if (current.getThread() == null) {
return false;
}
return true;
}
private void activatedEmulateSkipTickForward(ActionContext context) {
if (current.getThread() == null) {
return;
}
TraceSchedule time = current.getTime().skippedForward(current.getThread(), 1);
traceManager.activateTime(time);
}
private boolean isStepSnapForwardEnabled(ActionContext context) {
Trace curTrace = current.getTrace();
if (curTrace == null) {
return false;
}
Long maxSnap = curTrace.getTimeManager().getMaxSnap();
if (maxSnap == null || current.getSnap() >= maxSnap) {
return false;
}
return true;
}
private void activatedStepSnapForward(ActionContext contetxt) {
traceManager.activateSnap(current.getSnap() + 1);
}
private boolean isSeekTracePresentEnabled(ActionContext context) {
return traceManager != null;
}
private void toggledSeekTracePresent(ActionContext context) {
if (traceManager == null) {
return;
}
traceManager.setAutoActivatePresent(actionSeekTracePresent.isSelected());
}
private void changedAutoActivatePresent(boolean value) {
if (actionSeekTracePresent == null || actionSeekTracePresent.isSelected()) {
return;
}
actionSeekTracePresent.setSelected(value);
}
private void changedSynchronizeFocus(boolean value) {
if (actionSyncFocus == null || actionSyncFocus.isSelected()) {
return;
}
actionSyncFocus.setSelected(value);
}
private void toggleSyncFocus(boolean enabled) { private void toggleSyncFocus(boolean enabled) {
if (traceManager == null) { if (traceManager == null) {
return; return;

View File

@ -0,0 +1,164 @@
/* ###
* 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 ghidra.program.model.lang.Language;
public abstract class AbstractStep implements Step {
protected final long threadKey;
protected long tickCount;
protected AbstractStep(long threadKey, long tickCount) {
if (tickCount < 0) {
throw new IllegalArgumentException("Cannot step a negative number");
}
this.threadKey = threadKey;
this.tickCount = tickCount;
}
/**
* Return the step portion of {@link #toString()}
*
* @return the string
*/
protected abstract String toStringStepPart();
@Override
public String toString() {
if (threadKey == -1) {
return toStringStepPart();
}
return String.format("t%d-", threadKey) + toStringStepPart();
}
@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 abstract AbstractStep clone();
/**
* 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.getClass() == this.getClass())) {
return false;
}
AbstractStep as = (AbstractStep) step;
return this.threadKey == as.threadKey || as.threadKey == -1;
}
@Override
public void addTo(Step step) {
assert isCompatible(step);
AbstractStep as = (AbstractStep) step;
advance(as.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.getClass() != this.getClass()) {
return false;
}
AbstractStep that = (AbstractStep) 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;
}
AbstractStep that = (AbstractStep) 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 long coalescePatches(Language language, List<Step> steps) {
return 0;
}
}

View File

@ -18,7 +18,6 @@ package ghidra.trace.model.time.schedule;
import java.math.BigInteger; import java.math.BigInteger;
import java.util.*; import java.util.*;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.function.Consumer;
import java.util.stream.Stream; import java.util.stream.Stream;
import javax.help.UnsupportedOperationException; import javax.help.UnsupportedOperationException;
@ -234,9 +233,8 @@ public class PatchStep implements Step {
} }
@Override @Override
public int getTypeOrder() { public StepType getType() {
// When comparing sequences, those with sleigh steps are ordered after those with ticks return StepType.PATCH;
return 10;
} }
@Override @Override
@ -314,8 +312,8 @@ public class PatchStep implements Step {
} }
@Override @Override
public <T> void execute(PcodeThread<T> emuThread, Consumer<PcodeThread<T>> stepAction, public <T> void execute(PcodeThread<T> emuThread, Stepper<T> stepper, TaskMonitor monitor)
TaskMonitor monitor) throws CancelledException { throws CancelledException {
PcodeProgram prog = emuThread.getMachine().compileSleigh("schedule", List.of(sleigh + ";")); PcodeProgram prog = emuThread.getMachine().compileSleigh("schedule", List.of(sleigh + ";"));
emuThread.getExecutor().execute(prog, emuThread.getUseropLibrary()); emuThread.getExecutor().execute(prog, emuThread.getUseropLibrary());
} }

View File

@ -16,13 +16,11 @@
package ghidra.trace.model.time.schedule; package ghidra.trace.model.time.schedule;
import java.util.*; import java.util.*;
import java.util.function.Consumer;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import ghidra.pcode.emu.PcodeMachine; import ghidra.pcode.emu.PcodeMachine;
import ghidra.pcode.emu.PcodeThread;
import ghidra.program.model.lang.Language; import ghidra.program.model.lang.Language;
import ghidra.trace.model.Trace; import ghidra.trace.model.Trace;
import ghidra.trace.model.thread.TraceThread; import ghidra.trace.model.thread.TraceThread;
@ -381,17 +379,17 @@ public class Sequence implements Comparable<Sequence> {
* @param trace the trace to which the machine is bound * @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 eventThread the thread for the first step, if it applies to the "last thread"
* @param machine the machine to step, or null to validate the sequence * @param machine the machine to step, or null to validate the sequence
* @param action the action to step each thread * @param stepper the actions to step each thread
* @param monitor a monitor for cancellation and progress reports * @param monitor a monitor for cancellation and progress reports
* @return the last trace thread stepped during execution * @return the last trace thread stepped during execution
* @throws CancelledException if execution is cancelled * @throws CancelledException if execution is cancelled
*/ */
public <T> TraceThread execute(Trace trace, TraceThread eventThread, PcodeMachine<T> machine, public <T> TraceThread execute(Trace trace, TraceThread eventThread, PcodeMachine<T> machine,
Consumer<PcodeThread<T>> action, TaskMonitor monitor) throws CancelledException { Stepper<T> stepper, TaskMonitor monitor) throws CancelledException {
TraceThreadManager tm = trace.getThreadManager(); TraceThreadManager tm = trace.getThreadManager();
TraceThread thread = eventThread; TraceThread thread = eventThread;
for (Step step : steps) { for (Step step : steps) {
thread = step.execute(tm, thread, machine, action, monitor); thread = step.execute(tm, thread, machine, stepper, monitor);
} }
return thread; return thread;
} }

View File

@ -0,0 +1,77 @@
/* ###
* 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 ghidra.pcode.emu.PcodeThread;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;
public class SkipStep extends AbstractStep {
public static SkipStep parse(long threadKey, String stepSpec) {
if (!stepSpec.startsWith("s")) {
throw new IllegalArgumentException("Cannot parse skip step: '" + stepSpec + "'");
}
try {
return new SkipStep(threadKey, Long.parseLong(stepSpec.substring(1)));
}
catch (NumberFormatException e) {
throw new IllegalArgumentException("Cannot parse skip step: '" + stepSpec + "'");
}
}
/**
* Construct a skip 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 ticks to skip on the thread
*/
public SkipStep(long threadKey, long tickCount) {
super(threadKey, tickCount);
}
@Override
public StepType getType() {
return StepType.SKIP;
}
@Override
protected String toStringStepPart() {
return String.format("s%d", tickCount);
}
@Override
public AbstractStep clone() {
return new SkipStep(threadKey, tickCount);
}
@Override
public Step subtract(Step step) {
assert isCompatible(step);
SkipStep that = (SkipStep) step;
return new SkipStep(this.threadKey, this.tickCount - that.tickCount);
}
@Override
public <T> void execute(PcodeThread<T> emuThread, Stepper<T> stepper, TaskMonitor monitor)
throws CancelledException {
for (int i = 0; i < tickCount; i++) {
monitor.incrementProgress(1);
monitor.checkCanceled();
stepper.skip(emuThread);
}
}
}

View File

@ -16,7 +16,6 @@
package ghidra.trace.model.time.schedule; package ghidra.trace.model.time.schedule;
import java.util.List; import java.util.List;
import java.util.function.Consumer;
import ghidra.pcode.emu.PcodeMachine; import ghidra.pcode.emu.PcodeMachine;
import ghidra.pcode.emu.PcodeThread; import ghidra.pcode.emu.PcodeThread;
@ -27,6 +26,12 @@ import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor; import ghidra.util.task.TaskMonitor;
public interface Step extends Comparable<Step> { public interface Step extends Comparable<Step> {
enum StepType {
TICK,
SKIP,
PATCH,
}
/** /**
* Parse a step, possibly including a thread prefix, e.g., {@code "t1-..."} * Parse a step, possibly including a thread prefix, e.g., {@code "t1-..."}
* *
@ -69,6 +74,9 @@ public interface Step extends Comparable<Step> {
* @throws IllegalArgumentException if the specification is of the wrong form * @throws IllegalArgumentException if the specification is of the wrong form
*/ */
static Step parse(long threadKey, String stepSpec) { static Step parse(long threadKey, String stepSpec) {
if (stepSpec.startsWith("s")) {
return SkipStep.parse(threadKey, stepSpec);
}
if (stepSpec.startsWith("{")) { if (stepSpec.startsWith("{")) {
return PatchStep.parse(threadKey, stepSpec); return PatchStep.parse(threadKey, stepSpec);
} }
@ -79,7 +87,11 @@ public interface Step extends Comparable<Step> {
return new TickStep(-1, 0); return new TickStep(-1, 0);
} }
int getTypeOrder(); StepType getType();
default int getTypeOrder() {
return getType().ordinal();
}
boolean isNop(); boolean isNop();
@ -159,7 +171,7 @@ public interface Step extends Comparable<Step> {
} }
default <T> TraceThread execute(TraceThreadManager tm, TraceThread eventThread, default <T> TraceThread execute(TraceThreadManager tm, TraceThread eventThread,
PcodeMachine<T> machine, Consumer<PcodeThread<T>> stepAction, TaskMonitor monitor) PcodeMachine<T> machine, Stepper<T> stepper, TaskMonitor monitor)
throws CancelledException { throws CancelledException {
TraceThread thread = getThread(tm, eventThread); TraceThread thread = getThread(tm, eventThread);
if (machine == null) { if (machine == null) {
@ -167,12 +179,12 @@ public interface Step extends Comparable<Step> {
return thread; return thread;
} }
PcodeThread<T> emuThread = machine.getThread(thread.getPath(), true); PcodeThread<T> emuThread = machine.getThread(thread.getPath(), true);
execute(emuThread, stepAction, monitor); execute(emuThread, stepper, monitor);
return thread; return thread;
} }
<T> void execute(PcodeThread<T> emuThread, Consumer<PcodeThread<T>> stepAction, <T> void execute(PcodeThread<T> emuThread, Stepper<T> stepper, TaskMonitor monitor)
TaskMonitor monitor) throws CancelledException; throws CancelledException;
long coalescePatches(Language language, List<Step> steps); long coalescePatches(Language language, List<Step> steps);
} }

View File

@ -0,0 +1,60 @@
/* ###
* 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 ghidra.pcode.emu.PcodeThread;
public interface Stepper<T> {
@SuppressWarnings("rawtypes")
enum Enum implements Stepper {
INSTRUCTION {
@Override
public void tick(PcodeThread thread) {
thread.stepInstruction();
}
@Override
public void skip(PcodeThread thread) {
thread.skipInstruction();
}
},
PCODE {
@Override
public void tick(PcodeThread thread) {
thread.stepPcodeOp();
}
@Override
public void skip(PcodeThread thread) {
thread.skipPcodeOp();
}
};
}
@SuppressWarnings("unchecked")
static <T> Stepper<T> instruction() {
return Enum.INSTRUCTION;
}
@SuppressWarnings("unchecked")
static <T> Stepper<T> pcode() {
return Enum.PCODE;
}
void tick(PcodeThread<T> thread);
void skip(PcodeThread<T> thread);
}

View File

@ -15,76 +15,42 @@
*/ */
package ghidra.trace.model.time.schedule; package ghidra.trace.model.time.schedule;
import java.util.List;
import java.util.function.Consumer;
import ghidra.pcode.emu.PcodeThread; import ghidra.pcode.emu.PcodeThread;
import ghidra.program.model.lang.Language;
import ghidra.util.exception.CancelledException; import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor; import ghidra.util.task.TaskMonitor;
/** /**
* A step of a given thread in a schedule: repeating some number of ticks * A step of a given thread in a schedule: repeating some number of ticks
*/ */
public class TickStep implements Step { public class TickStep extends AbstractStep {
public static TickStep parse(long threadKey, String stepSpec) { public static TickStep parse(long threadKey, String stepSpec) {
try { try {
return new TickStep(threadKey, Long.parseLong(stepSpec)); return new TickStep(threadKey, Long.parseLong(stepSpec));
} }
catch (NumberFormatException e) { catch (NumberFormatException e) {
throw new IllegalArgumentException("Cannot parse step: '" + stepSpec + "'"); throw new IllegalArgumentException("Cannot parse tick step: '" + stepSpec + "'");
} }
} }
protected final long threadKey;
protected long tickCount;
/** /**
* Construct a step for the given thread with the given tick count * Construct a tick 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 threadKey the key of the thread in the trace, -1 for the "last thread"
* @param tickCount the number of times to step the thread * @param tickCount the number of ticks to step on the thread
*/ */
public TickStep(long threadKey, long tickCount) { public TickStep(long threadKey, long tickCount) {
if (tickCount < 0) { super(threadKey, tickCount);
throw new IllegalArgumentException("Cannot step a negative number");
}
this.threadKey = threadKey;
this.tickCount = tickCount;
} }
@Override @Override
public int getTypeOrder() { public StepType getType() {
return 0; return StepType.TICK;
} }
@Override @Override
public String toString() { protected String toStringStepPart() {
if (threadKey == -1) { return Long.toString(tickCount);
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 @Override
@ -92,48 +58,6 @@ public class TickStep implements Step {
return new TickStep(threadKey, tickCount); 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 @Override
public Step subtract(Step step) { public Step subtract(Step step) {
assert isCompatible(step); assert isCompatible(step);
@ -142,63 +66,12 @@ public class TickStep implements Step {
} }
@Override @Override
public int hashCode() { public <T> void execute(PcodeThread<T> emuThread, Stepper<T> stepper, TaskMonitor monitor)
return Long.hashCode(threadKey) * 31 + Long.hashCode(tickCount); throws CancelledException {
}
@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++) { for (int i = 0; i < tickCount; i++) {
monitor.incrementProgress(1); monitor.incrementProgress(1);
monitor.checkCanceled(); monitor.checkCanceled();
stepAction.accept(emuThread); stepper.tick(emuThread);
} }
} }
@Override
public long coalescePatches(Language language, List<Step> steps) {
return 0;
}
} }

View File

@ -18,7 +18,6 @@ package ghidra.trace.model.time.schedule;
import java.util.*; import java.util.*;
import ghidra.pcode.emu.PcodeMachine; import ghidra.pcode.emu.PcodeMachine;
import ghidra.pcode.emu.PcodeThread;
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.TraceSnapshot; import ghidra.trace.model.time.TraceSnapshot;
@ -339,9 +338,9 @@ public class TraceSchedule implements Comparable<TraceSchedule> {
throws CancelledException { throws CancelledException {
TraceThread lastThread = getEventThread(trace); TraceThread lastThread = getEventThread(trace);
lastThread = lastThread =
steps.execute(trace, lastThread, machine, PcodeThread::stepInstruction, monitor); steps.execute(trace, lastThread, machine, Stepper.instruction(), monitor);
lastThread = lastThread =
pSteps.execute(trace, lastThread, machine, PcodeThread::stepPcodeOp, monitor); pSteps.execute(trace, lastThread, machine, Stepper.pcode(), monitor);
} }
/** /**
@ -383,13 +382,13 @@ public class TraceSchedule implements Comparable<TraceSchedule> {
if (remains.isNop()) { if (remains.isNop()) {
Sequence pRemains = this.pSteps.relativize(position.pSteps); Sequence pRemains = this.pSteps.relativize(position.pSteps);
lastThread = lastThread =
pRemains.execute(trace, lastThread, machine, PcodeThread::stepPcodeOp, monitor); pRemains.execute(trace, lastThread, machine, Stepper.pcode(), monitor);
} }
else { else {
lastThread = lastThread =
remains.execute(trace, lastThread, machine, PcodeThread::stepInstruction, monitor); remains.execute(trace, lastThread, machine, Stepper.instruction(), monitor);
lastThread = lastThread =
pSteps.execute(trace, lastThread, machine, PcodeThread::stepPcodeOp, monitor); pSteps.execute(trace, lastThread, machine, Stepper.pcode(), monitor);
} }
} }
@ -411,6 +410,19 @@ public class TraceSchedule implements Comparable<TraceSchedule> {
return new TraceSchedule(snap, steps, new Sequence()); return new TraceSchedule(snap, steps, new Sequence());
} }
/**
* Behaves as in {@link #steppedForward(TraceThread, long)}, but by appending skips
*
* @param thread the thread to step, or null for the "last thread"
* @param tickCount the number of skips to take the thread forward
* @return the resulting schedule
*/
public TraceSchedule skippedForward(TraceThread thread, long tickCount) {
Sequence steps = this.steps.clone();
steps.advance(new SkipStep(thread == null ? -1 : thread.getKey(), tickCount));
return new TraceSchedule(snap, steps, new Sequence());
}
protected TraceSchedule doSteppedBackward(Trace trace, long tickCount, Set<Long> visited) { protected TraceSchedule doSteppedBackward(Trace trace, long tickCount, Set<Long> visited) {
if (!visited.add(snap)) { if (!visited.add(snap)) {
return null; return null;

View File

@ -0,0 +1,52 @@
/* ###
* 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.ArrayList;
import java.util.List;
import ghidra.pcode.emu.AbstractPcodeMachine;
import ghidra.pcode.emu.PcodeThread;
import ghidra.pcode.exec.PcodeExecutorState;
import ghidra.pcode.exec.PcodeUseropLibrary;
class TestMachine extends AbstractPcodeMachine<Void> {
protected final List<String> record = new ArrayList<>();
public TestMachine() {
super(TraceScheduleTest.TOY_BE_64_LANG, null);
}
@Override
protected PcodeThread<Void> createThread(String name) {
return new TestThread(name, this);
}
@Override
protected PcodeExecutorState<Void> createSharedState() {
return null;
}
@Override
protected PcodeExecutorState<Void> createLocalState(PcodeThread<Void> thread) {
return null;
}
@Override
protected PcodeUseropLibrary<Void> createUseropLibrary() {
return PcodeUseropLibrary.nil();
}
}

View File

@ -0,0 +1,162 @@
/* ###
* 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 ghidra.pcode.emu.PcodeThread;
import ghidra.pcode.emu.ThreadPcodeExecutorState;
import ghidra.pcode.exec.*;
import ghidra.program.model.address.Address;
import ghidra.program.model.lang.RegisterValue;
import ghidra.program.model.listing.Instruction;
class TestThread implements PcodeThread<Void> {
protected final String name;
protected final TestMachine machine;
public TestThread(String name, TestMachine machine) {
this.name = name;
this.machine = machine;
}
@Override
public String getName() {
return name;
}
@Override
public TestMachine getMachine() {
return machine;
}
@Override
public PcodeExecutor<Void> getExecutor() {
return new PcodeExecutor<>(TraceScheduleTest.TOY_BE_64_LANG, machine.getArithmetic(), getState()) {
public PcodeFrame execute(PcodeProgram program, PcodeUseropLibrary<Void> library) {
machine.record.add("x:" + name);
// TODO: Verify the actual effect
return null; //super.execute(program, library);
}
};
}
@Override
public void stepInstruction() {
machine.record.add("ti:" + name);
}
@Override
public void skipInstruction() {
machine.record.add("si:" + name);
}
@Override
public void stepPcodeOp() {
machine.record.add("tp:" + name);
}
@Override
public void skipPcodeOp() {
machine.record.add("sp:" + name);
}
@Override
public void setCounter(Address counter) {
}
@Override
public Address getCounter() {
return null;
}
@Override
public void overrideCounter(Address counter) {
}
@Override
public void assignContext(RegisterValue context) {
}
@Override
public RegisterValue getContext() {
return null;
}
@Override
public void overrideContext(RegisterValue context) {
}
@Override
public void overrideContextWithDefault() {
}
@Override
public void reInitialize() {
}
@Override
public PcodeFrame getFrame() {
return null;
}
@Override
public Instruction getInstruction() {
return null;
}
@Override
public void executeInstruction() {
}
@Override
public void finishInstruction() {
}
@Override
public void dropInstruction() {
}
@Override
public void run() {
}
@Override
public void setSuspended(boolean suspended) {
}
@Override
public PcodeUseropLibrary<Void> getUseropLibrary() {
return null;
}
@Override
public ThreadPcodeExecutorState<Void> getState() {
return null;
}
@Override
public void inject(Address address, List<String> sleigh) {
}
@Override
public void clearInject(Address address) {
}
@Override
public void clearAllInjects() {
}
}

View File

@ -17,18 +17,14 @@ package ghidra.trace.model.time.schedule;
import static org.junit.Assert.*; import static org.junit.Assert.*;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import ghidra.app.plugin.processors.sleigh.SleighLanguage; import ghidra.app.plugin.processors.sleigh.SleighLanguage;
import ghidra.pcode.emu.*; import ghidra.program.model.lang.LanguageID;
import ghidra.pcode.exec.*; import ghidra.program.model.lang.LanguageNotFoundException;
import ghidra.program.model.address.Address;
import ghidra.program.model.lang.*;
import ghidra.program.model.listing.Instruction;
import ghidra.test.AbstractGhidraHeadlessIntegrationTest; import ghidra.test.AbstractGhidraHeadlessIntegrationTest;
import ghidra.test.ToyProgramBuilder; import ghidra.test.ToyProgramBuilder;
import ghidra.trace.database.ToyDBTraceBuilder; import ghidra.trace.database.ToyDBTraceBuilder;
@ -267,165 +263,6 @@ public class TraceScheduleTest extends AbstractGhidraHeadlessIntegrationTest {
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 final String name;
protected final TestMachine machine;
public TestThread(String name, TestMachine machine) {
this.name = name;
this.machine = machine;
}
@Override
public String getName() {
return name;
}
@Override
public TestMachine getMachine() {
return machine;
}
@Override
public void setCounter(Address counter) {
}
@Override
public Address getCounter() {
return null;
}
@Override
public void overrideCounter(Address counter) {
}
@Override
public void assignContext(RegisterValue context) {
}
@Override
public RegisterValue getContext() {
return null;
}
@Override
public void overrideContext(RegisterValue context) {
}
@Override
public void overrideContextWithDefault() {
}
@Override
public void reInitialize() {
}
@Override
public void stepInstruction() {
machine.record.add("s:" + name);
}
@Override
public void stepPcodeOp() {
machine.record.add("p:" + name);
}
@Override
public PcodeFrame getFrame() {
return null;
}
@Override
public Instruction getInstruction() {
return null;
}
@Override
public void executeInstruction() {
}
@Override
public void finishInstruction() {
}
@Override
public void skipInstruction() {
}
@Override
public void dropInstruction() {
}
@Override
public void run() {
}
@Override
public void setSuspended(boolean suspended) {
}
@Override
public PcodeExecutor<Void> getExecutor() {
return new PcodeExecutor<>(TOY_BE_64_LANG, machine.getArithmetic(), getState()) {
public PcodeFrame execute(PcodeProgram program, PcodeUseropLibrary<Void> library) {
machine.record.add("x:" + name);
// TODO: Verify the actual effect
return null; //super.execute(program, library);
}
};
}
@Override
public PcodeUseropLibrary<Void> getUseropLibrary() {
return null;
}
@Override
public ThreadPcodeExecutorState<Void> getState() {
return null;
}
@Override
public void inject(Address address, List<String> sleigh) {
}
@Override
public void clearInject(Address address) {
}
@Override
public void clearAllInjects() {
}
}
protected static class TestMachine extends AbstractPcodeMachine<Void> {
protected final List<String> record = new ArrayList<>();
public TestMachine() {
super(TOY_BE_64_LANG, null);
}
@Override
protected PcodeThread<Void> createThread(String name) {
return new TestThread(name, this);
}
@Override
protected PcodeExecutorState<Void> createSharedState() {
return null;
}
@Override
protected PcodeExecutorState<Void> createLocalState(PcodeThread<Void> thread) {
return null;
}
@Override
protected PcodeUseropLibrary<Void> createUseropLibrary() {
return PcodeUseropLibrary.nil();
}
}
@Test @Test
public void testExecute() throws Exception { public void testExecute() throws Exception {
TestMachine machine = new TestMachine(); TestMachine machine = new TestMachine();
@ -442,16 +279,45 @@ public class TraceScheduleTest extends AbstractGhidraHeadlessIntegrationTest {
} }
assertEquals(List.of( assertEquals(List.of(
"s:Threads[2]", "ti:Threads[2]",
"s:Threads[2]", "ti:Threads[2]",
"s:Threads[2]", "ti:Threads[2]",
"s:Threads[2]", "ti:Threads[2]",
"s:Threads[0]", "ti:Threads[0]",
"s:Threads[0]", "ti:Threads[0]",
"s:Threads[0]", "ti:Threads[0]",
"s:Threads[1]", "ti:Threads[1]",
"s:Threads[1]", "ti:Threads[1]",
"p:Threads[1]"), "tp:Threads[1]"),
machine.record);
}
@Test
public void testExecuteWithSkips() throws Exception {
TestMachine machine = new TestMachine();
TraceSchedule time = TraceSchedule.parse("1:4;t0-s3;t1-2.s1");
try (ToyDBTraceBuilder tb = new ToyDBTraceBuilder("test", ToyProgramBuilder._TOY64_BE)) {
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(
"ti:Threads[2]",
"ti:Threads[2]",
"ti:Threads[2]",
"ti:Threads[2]",
"si:Threads[0]",
"si:Threads[0]",
"si:Threads[0]",
"ti:Threads[1]",
"ti:Threads[1]",
"sp:Threads[1]"),
machine.record); machine.record);
} }
@ -472,10 +338,10 @@ public class TraceScheduleTest extends AbstractGhidraHeadlessIntegrationTest {
assertEquals(List.of( assertEquals(List.of(
"x:Threads[2]", "x:Threads[2]",
"s:Threads[2]", "ti:Threads[2]",
"s:Threads[2]", "ti:Threads[2]",
"s:Threads[2]", "ti:Threads[2]",
"s:Threads[2]"), "ti:Threads[2]"),
machine.record); machine.record);
} }
@ -526,10 +392,10 @@ public class TraceScheduleTest extends AbstractGhidraHeadlessIntegrationTest {
} }
assertEquals(List.of( assertEquals(List.of(
"s:Threads[0]", "ti:Threads[0]",
"s:Threads[1]", "ti:Threads[1]",
"s:Threads[1]", "ti:Threads[1]",
"p:Threads[1]"), "tp:Threads[1]"),
machine.record); machine.record);
} }
@ -550,7 +416,7 @@ public class TraceScheduleTest extends AbstractGhidraHeadlessIntegrationTest {
} }
assertEquals(List.of( assertEquals(List.of(
"p:Threads[1]"), "tp:Threads[1]"),
machine.record); machine.record);
} }

View File

@ -364,6 +364,19 @@ public class DefaultPcodeThread<T> implements PcodeThread<T> {
} }
} }
@Override
public void skipPcodeOp() {
if (frame == null) {
beginInstructionOrInject();
}
else if (!frame.isFinished()) {
executor.skip(frame);
}
else {
advanceAfterFinished();
}
}
/** /**
* Start execution of the instruction or inject at the program counter * Start execution of the instruction or inject at the program counter
*/ */

View File

@ -184,6 +184,18 @@ public interface PcodeThread<T> {
} }
} }
/**
* Skip emulation of a single p-code operation
*
* <p>
* If there is no current frame, this behaves as in {@link #stepPcodeOp()}. Otherwise, this
* skips the current pcode op, advancing as if a fall-through op. If no ops remain in the frame,
* this behaves as in {@link #stepPcodeOp()}. Please note to skip an extranal branch, the op
* itself must be skipped. "Skipping" the following op, which disposes the frame, cannot prevent
* the branch.
*/
void skipPcodeOp();
/** /**
* Get the current frame, if present * Get the current frame, if present
* *

View File

@ -211,6 +211,10 @@ public class PcodeExecutor<T> {
} }
} }
public void skip(PcodeFrame frame) {
frame.nextOp();
}
protected int getIntConst(Varnode vn) { protected int getIntConst(Varnode vn) {
assert vn.getAddress().getAddressSpace().isConstantSpace(); assert vn.getAddress().getAddressSpace().isConstantSpace();
return (int) vn.getAddress().getOffset(); return (int) vn.getAddress().getOffset();