diff --git a/Ghidra/Debug/Debugger/certification.manifest b/Ghidra/Debug/Debugger/certification.manifest index 2e722617b4..adbc4a2a23 100644 --- a/Ghidra/Debug/Debugger/certification.manifest +++ b/Ghidra/Debug/Debugger/certification.manifest @@ -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/images/DebuggerThreadsPlugin.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/stepinto.png||GHIDRA||||END| src/main/help/help/topics/DebuggerTimePlugin/DebuggerTimePlugin.html||GHIDRA||||END| diff --git a/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerThreadsPlugin/DebuggerThreadsPlugin.html b/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerThreadsPlugin/DebuggerThreadsPlugin.html index 4c2cfc02a2..abff6b68fa 100644 --- a/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerThreadsPlugin/DebuggerThreadsPlugin.html +++ b/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerThreadsPlugin/DebuggerThreadsPlugin.html @@ -125,6 +125,18 @@ 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.

+

Emulate + Trace Skip Tick Forward

+ +

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; however, the stack may require + additional patching, e.g., to clean up stack parameters, depending on the calling convention. + This action does not perform those patches automatically. It only advances the program + counter. You may use Go To Time to append the require stack patch, + e.g., t0-{RSP=RSP+8}.

+

Seek Trace to Present

diff --git a/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerThreadsPlugin/images/DebuggerThreadsPlugin.png b/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerThreadsPlugin/images/DebuggerThreadsPlugin.png index 0b6569e3c2..3a10d754a3 100644 Binary files a/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerThreadsPlugin/images/DebuggerThreadsPlugin.png and b/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerThreadsPlugin/images/DebuggerThreadsPlugin.png differ diff --git a/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerThreadsPlugin/images/skipover.png b/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerThreadsPlugin/images/skipover.png new file mode 100644 index 0000000000..a477c46575 Binary files /dev/null and b/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerThreadsPlugin/images/skipover.png differ diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/DebuggerResources.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/DebuggerResources.java index 1aaf6e54f1..5f834dc155 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/DebuggerResources.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/DebuggerResources.java @@ -87,6 +87,7 @@ public interface DebuggerResources { ImageIcon ICON_STEP_INTO = ResourceManager.loadImage("images/stepinto.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_BACK = ResourceManager.loadImage("images/stepback.png"); // 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 { String NAME = "Step Trace Snap Backward"; String DESCRIPTION = "Navigate the recording backward one snap"; Icon ICON = ICON_SNAP_BACKWARD; String GROUP = GROUP_CONTROL; + String ORDER = "1"; String HELP_ANCHOR = "step_trace_snap_backward"; static ActionBuilder builder(Plugin owner) { @@ -1723,20 +1655,80 @@ public interface DebuggerResources { return new ActionBuilder(NAME, ownerName) .description(DESCRIPTION) .toolBarIcon(ICON) - .toolBarGroup(GROUP, "1") + .toolBarGroup(GROUP, ORDER) .helpLocation(new HelpLocation(ownerName, HELP_ANCHOR)); } } - abstract class AbstractStepSnapBackwardAction extends DockingAction { - public static final String NAME = StepSnapBackwardAction.NAME; - public static final Icon ICON = StepSnapBackwardAction.ICON;; - public static final String HELP_ANCHOR = StepSnapBackwardAction.HELP_ANCHOR; + 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 ORDER = "5"; + String HELP_ANCHOR = "step_trace_snap_forward"; - public AbstractStepSnapBackwardAction(Plugin owner) { - super(NAME, owner.getName()); - setDescription(StepSnapBackwardAction.DESCRIPTION); - setHelpLocation(new HelpLocation(owner.getName(), HELP_ANCHOR)); + 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 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"; Icon ICON = ICON_STEP_BACK; String GROUP = GROUP_CONTROL; + String ORDER = "2"; 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) { String ownerName = owner.getName(); return new ActionBuilder(NAME, ownerName) @@ -1757,15 +1786,20 @@ public interface DebuggerResources { } } - abstract class AbstractSeekTracePresentAction extends ToggleDockingAction { - public static final String NAME = "Seek Trace Present"; - public static final Icon ICON = ICON_SEEK_PRESENT; - public static final String HELP_ANCHOR = "seek_trace_present"; + interface SeekTracePresentAction { + String NAME = "Seek Trace Present"; + String DESCRIPTION = "Track the tool to the latest snap"; + Icon ICON = ICON_SEEK_PRESENT; + String GROUP = "zz"; + String HELP_ANCHOR = "seek_trace_present"; - public AbstractSeekTracePresentAction(Plugin owner) { - super(NAME, owner.getName()); - setDescription("Track the tool to the latest snap"); - setHelpLocation(new HelpLocation(owner.getName(), HELP_ANCHOR)); + static ToggleActionBuilder builder(Plugin owner) { + String ownerName = owner.getName(); + return new ToggleActionBuilder(NAME, ownerName) + .description(DESCRIPTION) + .toolBarIcon(ICON) + .toolBarGroup(GROUP) + .helpLocation(new HelpLocation(ownerName, HELP_ANCHOR)); } } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/thread/DebuggerThreadsProvider.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/thread/DebuggerThreadsProvider.java index 4c9cb5fd0f..bc81d21a9d 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/thread/DebuggerThreadsProvider.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/thread/DebuggerThreadsProvider.java @@ -87,172 +87,6 @@ public class DebuggerThreadsProvider extends ComponentProviderAdapter { 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 extends RowWrappedEnumeratedColumnTableModel< // ThreadTableColumns, ObjectKey, ThreadRow, TraceThread> { @@ -321,9 +155,9 @@ public class DebuggerThreadsProvider extends ComponentProviderAdapter { private final DebuggerThreadsPlugin plugin; - // @AutoServiceConsumed by method + // @AutoServiceConsumed by method private DebuggerModelService modelService; - @AutoServiceConsumed // NB, also by method + // @AutoServiceConsumed by method private DebuggerTraceManagerService traceManager; @AutoServiceConsumed // NB, also by method private DebuggerEmulationService emulationService; @@ -337,6 +171,10 @@ public class DebuggerThreadsProvider extends ComponentProviderAdapter { private final ThreadsListener threadsListener = new ThreadsListener(); private final CollectionChangeListener recordersListener = new RecordersChangeListener(); + private final BooleanChangeAdapter activatePresentChangeListener = + this::changedAutoActivatePresent; + private final BooleanChangeAdapter synchronizeFocusChangeListener = + this::changedSynchronizeFocus; /* package access for testing */ final RangeTableCellRenderer rangeRenderer = new RangeTableCellRenderer<>(); final RangeCursorTableHeaderRenderer headerRenderer = @@ -354,11 +192,12 @@ public class DebuggerThreadsProvider extends ComponentProviderAdapter { private ActionContext myActionContext; DockingAction actionSaveTrace; - StepSnapBackwardAction actionStepSnapBackward; - EmulateTickBackwardAction actionEmulateTickBackward; - EmulateTickForwardAction actionEmulateTickForward; - StepSnapForwardAction actionStepSnapForward; - SeekTracePresentAction actionSeekTracePresent; + DockingAction actionStepSnapBackward; + DockingAction actionEmulateTickBackward; + DockingAction actionEmulateTickForward; + DockingAction actionEmulateTickSkipForward; + DockingAction actionStepSnapForward; + ToggleDockingAction actionSeekTracePresent; ToggleDockingAction actionSyncFocus; DockingAction actionGoToTime; @@ -410,9 +249,21 @@ public class DebuggerThreadsProvider extends ComponentProviderAdapter { @AutoServiceConsumed public void setTraceManager(DebuggerTraceManagerService traceManager) { - if (traceManager != null && actionSeekTracePresent != null) { - actionSeekTracePresent.setSelected(traceManager.isAutoActivatePresent()); - actionSyncFocus.setSelected(traceManager.isSynchronizeFocus()); + if (this.traceManager != null) { + this.traceManager + .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(); } @@ -633,11 +484,34 @@ public class DebuggerThreadsProvider extends ComponentProviderAdapter { protected void createActions() { // TODO: Make other actions use builder? - actionStepSnapBackward = new StepSnapBackwardAction(); - actionEmulateTickBackward = new EmulateTickBackwardAction(); - actionEmulateTickForward = new EmulateTickForwardAction(); - actionStepSnapForward = new StepSnapForwardAction(); - actionSeekTracePresent = new SeekTracePresentAction(); + actionStepSnapBackward = StepSnapBackwardAction.builder(plugin) + .enabledWhen(this::isStepSnapBackwardEnabled) + .enabled(false) + .onAction(this::activatedStepSnapBackward) + .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) .selected(traceManager != null && traceManager.isSynchronizeFocus()) .enabledWhen(c -> traceManager != null) @@ -672,6 +546,129 @@ public class DebuggerThreadsProvider extends ComponentProviderAdapter { .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) { if (traceManager == null) { return; diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/schedule/AbstractStep.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/schedule/AbstractStep.java new file mode 100644 index 0000000000..4ecab29ae2 --- /dev/null +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/schedule/AbstractStep.java @@ -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 steps) { + return 0; + } +} diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/schedule/PatchStep.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/schedule/PatchStep.java index aef947e2b6..6dd5d8087a 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/schedule/PatchStep.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/schedule/PatchStep.java @@ -18,7 +18,6 @@ package ghidra.trace.model.time.schedule; import java.math.BigInteger; import java.util.*; import java.util.Map.Entry; -import java.util.function.Consumer; import java.util.stream.Stream; import javax.help.UnsupportedOperationException; @@ -234,9 +233,8 @@ public class PatchStep implements Step { } @Override - public int getTypeOrder() { - // When comparing sequences, those with sleigh steps are ordered after those with ticks - return 10; + public StepType getType() { + return StepType.PATCH; } @Override @@ -314,8 +312,8 @@ public class PatchStep implements Step { } @Override - public void execute(PcodeThread emuThread, Consumer> stepAction, - TaskMonitor monitor) throws CancelledException { + public void execute(PcodeThread emuThread, Stepper stepper, TaskMonitor monitor) + throws CancelledException { PcodeProgram prog = emuThread.getMachine().compileSleigh("schedule", List.of(sleigh + ";")); emuThread.getExecutor().execute(prog, emuThread.getUseropLibrary()); } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/schedule/Sequence.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/schedule/Sequence.java index 52c20d715c..3f30d30e44 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/schedule/Sequence.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/schedule/Sequence.java @@ -16,13 +16,11 @@ 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.program.model.lang.Language; import ghidra.trace.model.Trace; import ghidra.trace.model.thread.TraceThread; @@ -381,17 +379,17 @@ public class Sequence implements Comparable { * @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, 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 * @return the last trace thread stepped during execution * @throws CancelledException if execution is cancelled */ public TraceThread execute(Trace trace, TraceThread eventThread, PcodeMachine machine, - Consumer> action, TaskMonitor monitor) throws CancelledException { + Stepper stepper, TaskMonitor monitor) throws CancelledException { TraceThreadManager tm = trace.getThreadManager(); TraceThread thread = eventThread; for (Step step : steps) { - thread = step.execute(tm, thread, machine, action, monitor); + thread = step.execute(tm, thread, machine, stepper, monitor); } return thread; } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/schedule/SkipStep.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/schedule/SkipStep.java new file mode 100644 index 0000000000..f2e2797f21 --- /dev/null +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/schedule/SkipStep.java @@ -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 void execute(PcodeThread emuThread, Stepper stepper, TaskMonitor monitor) + throws CancelledException { + for (int i = 0; i < tickCount; i++) { + monitor.incrementProgress(1); + monitor.checkCanceled(); + stepper.skip(emuThread); + } + } +} diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/schedule/Step.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/schedule/Step.java index 631f6ad8f4..56dabec3b3 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/schedule/Step.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/schedule/Step.java @@ -16,7 +16,6 @@ package ghidra.trace.model.time.schedule; import java.util.List; -import java.util.function.Consumer; import ghidra.pcode.emu.PcodeMachine; import ghidra.pcode.emu.PcodeThread; @@ -27,6 +26,12 @@ import ghidra.util.exception.CancelledException; import ghidra.util.task.TaskMonitor; public interface Step extends Comparable { + enum StepType { + TICK, + SKIP, + PATCH, + } + /** * Parse a step, possibly including a thread prefix, e.g., {@code "t1-..."} * @@ -69,6 +74,9 @@ public interface Step extends Comparable { * @throws IllegalArgumentException if the specification is of the wrong form */ static Step parse(long threadKey, String stepSpec) { + if (stepSpec.startsWith("s")) { + return SkipStep.parse(threadKey, stepSpec); + } if (stepSpec.startsWith("{")) { return PatchStep.parse(threadKey, stepSpec); } @@ -79,7 +87,11 @@ public interface Step extends Comparable { return new TickStep(-1, 0); } - int getTypeOrder(); + StepType getType(); + + default int getTypeOrder() { + return getType().ordinal(); + } boolean isNop(); @@ -159,7 +171,7 @@ public interface Step extends Comparable { } default TraceThread execute(TraceThreadManager tm, TraceThread eventThread, - PcodeMachine machine, Consumer> stepAction, TaskMonitor monitor) + PcodeMachine machine, Stepper stepper, TaskMonitor monitor) throws CancelledException { TraceThread thread = getThread(tm, eventThread); if (machine == null) { @@ -167,12 +179,12 @@ public interface Step extends Comparable { return thread; } PcodeThread emuThread = machine.getThread(thread.getPath(), true); - execute(emuThread, stepAction, monitor); + execute(emuThread, stepper, monitor); return thread; } - void execute(PcodeThread emuThread, Consumer> stepAction, - TaskMonitor monitor) throws CancelledException; + void execute(PcodeThread emuThread, Stepper stepper, TaskMonitor monitor) + throws CancelledException; long coalescePatches(Language language, List steps); } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/schedule/Stepper.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/schedule/Stepper.java new file mode 100644 index 0000000000..fc21b6865b --- /dev/null +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/schedule/Stepper.java @@ -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 { + @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 Stepper instruction() { + return Enum.INSTRUCTION; + } + + @SuppressWarnings("unchecked") + static Stepper pcode() { + return Enum.PCODE; + } + + void tick(PcodeThread thread); + + void skip(PcodeThread thread); +} diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/schedule/TickStep.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/schedule/TickStep.java index 1aae2b99d0..27fd9bbe4d 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/schedule/TickStep.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/schedule/TickStep.java @@ -15,76 +15,42 @@ */ package ghidra.trace.model.time.schedule; -import java.util.List; -import java.util.function.Consumer; - import ghidra.pcode.emu.PcodeThread; -import ghidra.program.model.lang.Language; 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 class TickStep extends AbstractStep { 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 + "'"); + 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 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) { - if (tickCount < 0) { - throw new IllegalArgumentException("Cannot step a negative number"); - } - this.threadKey = threadKey; - this.tickCount = tickCount; + super(threadKey, tickCount); } @Override - public int getTypeOrder() { - return 0; + public StepType getType() { + return StepType.TICK; } @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; + protected String toStringStepPart() { + return Long.toString(tickCount); } @Override @@ -92,48 +58,6 @@ public class TickStep implements Step { 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); @@ -142,63 +66,12 @@ public class TickStep implements Step { } @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 void execute(PcodeThread emuThread, Consumer> stepAction, - TaskMonitor monitor) throws CancelledException { + public void execute(PcodeThread emuThread, Stepper stepper, TaskMonitor monitor) + throws CancelledException { for (int i = 0; i < tickCount; i++) { monitor.incrementProgress(1); monitor.checkCanceled(); - stepAction.accept(emuThread); + stepper.tick(emuThread); } } - - @Override - public long coalescePatches(Language language, List steps) { - return 0; - } } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/schedule/TraceSchedule.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/schedule/TraceSchedule.java index c429ec5a12..e8d9b60386 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/schedule/TraceSchedule.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/schedule/TraceSchedule.java @@ -18,7 +18,6 @@ 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; @@ -339,9 +338,9 @@ public class TraceSchedule implements Comparable { throws CancelledException { TraceThread lastThread = getEventThread(trace); lastThread = - steps.execute(trace, lastThread, machine, PcodeThread::stepInstruction, monitor); + steps.execute(trace, lastThread, machine, Stepper.instruction(), monitor); 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 { if (remains.isNop()) { Sequence pRemains = this.pSteps.relativize(position.pSteps); lastThread = - pRemains.execute(trace, lastThread, machine, PcodeThread::stepPcodeOp, monitor); + pRemains.execute(trace, lastThread, machine, Stepper.pcode(), monitor); } else { lastThread = - remains.execute(trace, lastThread, machine, PcodeThread::stepInstruction, monitor); + remains.execute(trace, lastThread, machine, Stepper.instruction(), monitor); 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 { 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 visited) { if (!visited.add(snap)) { return null; diff --git a/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/model/time/schedule/TestMachine.java b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/model/time/schedule/TestMachine.java new file mode 100644 index 0000000000..0d213ad1dc --- /dev/null +++ b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/model/time/schedule/TestMachine.java @@ -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 { + protected final List record = new ArrayList<>(); + + public TestMachine() { + super(TraceScheduleTest.TOY_BE_64_LANG, null); + } + + @Override + protected PcodeThread createThread(String name) { + return new TestThread(name, this); + } + + @Override + protected PcodeExecutorState createSharedState() { + return null; + } + + @Override + protected PcodeExecutorState createLocalState(PcodeThread thread) { + return null; + } + + @Override + protected PcodeUseropLibrary createUseropLibrary() { + return PcodeUseropLibrary.nil(); + } +} diff --git a/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/model/time/schedule/TestThread.java b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/model/time/schedule/TestThread.java new file mode 100644 index 0000000000..5aec1429ca --- /dev/null +++ b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/model/time/schedule/TestThread.java @@ -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 { + 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 getExecutor() { + return new PcodeExecutor<>(TraceScheduleTest.TOY_BE_64_LANG, machine.getArithmetic(), getState()) { + public PcodeFrame execute(PcodeProgram program, PcodeUseropLibrary 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 getUseropLibrary() { + return null; + } + + @Override + public ThreadPcodeExecutorState getState() { + return null; + } + + @Override + public void inject(Address address, List sleigh) { + } + + @Override + public void clearInject(Address address) { + } + + @Override + public void clearAllInjects() { + } +} diff --git a/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/model/time/schedule/TraceScheduleTest.java b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/model/time/schedule/TraceScheduleTest.java index b5ff70cb7e..ee2613fb07 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/model/time/schedule/TraceScheduleTest.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/model/time/schedule/TraceScheduleTest.java @@ -17,18 +17,14 @@ package ghidra.trace.model.time.schedule; import static org.junit.Assert.*; -import java.util.ArrayList; import java.util.List; import org.junit.Before; import org.junit.Test; import ghidra.app.plugin.processors.sleigh.SleighLanguage; -import ghidra.pcode.emu.*; -import ghidra.pcode.exec.*; -import ghidra.program.model.address.Address; -import ghidra.program.model.lang.*; -import ghidra.program.model.listing.Instruction; +import ghidra.program.model.lang.LanguageID; +import ghidra.program.model.lang.LanguageNotFoundException; import ghidra.test.AbstractGhidraHeadlessIntegrationTest; import ghidra.test.ToyProgramBuilder; import ghidra.trace.database.ToyDBTraceBuilder; @@ -267,165 +263,6 @@ public class TraceScheduleTest extends AbstractGhidraHeadlessIntegrationTest { assertEquals(15, TraceSchedule.parse("0:4;t1-5.6").totalTickCount()); } - protected static class TestThread implements PcodeThread { - 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 getExecutor() { - return new PcodeExecutor<>(TOY_BE_64_LANG, machine.getArithmetic(), getState()) { - public PcodeFrame execute(PcodeProgram program, PcodeUseropLibrary library) { - machine.record.add("x:" + name); - // TODO: Verify the actual effect - return null; //super.execute(program, library); - } - }; - } - - @Override - public PcodeUseropLibrary getUseropLibrary() { - return null; - } - - @Override - public ThreadPcodeExecutorState getState() { - return null; - } - - @Override - public void inject(Address address, List sleigh) { - } - - @Override - public void clearInject(Address address) { - } - - @Override - public void clearAllInjects() { - } - } - - protected static class TestMachine extends AbstractPcodeMachine { - protected final List record = new ArrayList<>(); - - public TestMachine() { - super(TOY_BE_64_LANG, null); - } - - @Override - protected PcodeThread createThread(String name) { - return new TestThread(name, this); - } - - @Override - protected PcodeExecutorState createSharedState() { - return null; - } - - @Override - protected PcodeExecutorState createLocalState(PcodeThread thread) { - return null; - } - - @Override - protected PcodeUseropLibrary createUseropLibrary() { - return PcodeUseropLibrary.nil(); - } - } - @Test public void testExecute() throws Exception { TestMachine machine = new TestMachine(); @@ -442,16 +279,45 @@ public class TraceScheduleTest extends AbstractGhidraHeadlessIntegrationTest { } assertEquals(List.of( - "s:Threads[2]", - "s:Threads[2]", - "s:Threads[2]", - "s:Threads[2]", - "s:Threads[0]", - "s:Threads[0]", - "s:Threads[0]", - "s:Threads[1]", - "s:Threads[1]", - "p:Threads[1]"), + "ti:Threads[2]", + "ti:Threads[2]", + "ti:Threads[2]", + "ti:Threads[2]", + "ti:Threads[0]", + "ti:Threads[0]", + "ti:Threads[0]", + "ti:Threads[1]", + "ti: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); } @@ -472,10 +338,10 @@ public class TraceScheduleTest extends AbstractGhidraHeadlessIntegrationTest { assertEquals(List.of( "x:Threads[2]", - "s:Threads[2]", - "s:Threads[2]", - "s:Threads[2]", - "s:Threads[2]"), + "ti:Threads[2]", + "ti:Threads[2]", + "ti:Threads[2]", + "ti:Threads[2]"), machine.record); } @@ -526,10 +392,10 @@ public class TraceScheduleTest extends AbstractGhidraHeadlessIntegrationTest { } assertEquals(List.of( - "s:Threads[0]", - "s:Threads[1]", - "s:Threads[1]", - "p:Threads[1]"), + "ti:Threads[0]", + "ti:Threads[1]", + "ti:Threads[1]", + "tp:Threads[1]"), machine.record); } @@ -550,7 +416,7 @@ public class TraceScheduleTest extends AbstractGhidraHeadlessIntegrationTest { } assertEquals(List.of( - "p:Threads[1]"), + "tp:Threads[1]"), machine.record); } diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/DefaultPcodeThread.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/DefaultPcodeThread.java index 251a100bf0..91689afaf6 100644 --- a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/DefaultPcodeThread.java +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/DefaultPcodeThread.java @@ -364,6 +364,19 @@ public class DefaultPcodeThread implements PcodeThread { } } + @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 */ diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/PcodeThread.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/PcodeThread.java index 5e5bb25198..ba8481576c 100644 --- a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/PcodeThread.java +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/PcodeThread.java @@ -184,6 +184,18 @@ public interface PcodeThread { } } + /** + * Skip emulation of a single p-code operation + * + *

+ * 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 * diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/PcodeExecutor.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/PcodeExecutor.java index 15c7f18b0c..4c15026754 100644 --- a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/PcodeExecutor.java +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/PcodeExecutor.java @@ -211,6 +211,10 @@ public class PcodeExecutor { } } + public void skip(PcodeFrame frame) { + frame.nextOp(); + } + protected int getIntConst(Varnode vn) { assert vn.getAddress().getAddressSpace().isConstantSpace(); return (int) vn.getAddress().getOffset();