diff --git a/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/control/ControlMode.java b/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/control/ControlMode.java index 45e9b4dfcf..b5d8b175b9 100644 --- a/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/control/ControlMode.java +++ b/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/control/ControlMode.java @@ -94,6 +94,11 @@ public enum ControlMode { public boolean isSelectable(DebuggerCoordinates coordinates) { return coordinates.isAlive(); } + + @Override + public ControlMode getAlternative(DebuggerCoordinates coordinates) { + return RW_EMULATOR; + } }, /** * Control actions, breakpoint commands, and state edits are all directed to the target. @@ -150,6 +155,11 @@ public enum ControlMode { public boolean isSelectable(DebuggerCoordinates coordinates) { return coordinates.isAlive(); } + + @Override + public ControlMode getAlternative(DebuggerCoordinates coordinates) { + return RW_EMULATOR; + } }, /** * Control actions activate trace snapshots, breakpoint commands are directed to the emulator, @@ -393,8 +403,9 @@ public enum ControlMode { "Cannot navigate time in %s mode. Switch to Trace or Emulate mode first." .formatted(name), true); + return null; } - return coordinates.snap(target.getSnap()); + return coordinates; } /** @@ -450,6 +461,37 @@ public enum ControlMode { return true; } + /** + * If the mode can no longer be selected for new coordinates, get the new mode + * + *
+ * For example, if a target terminates while the mode is {@link #RO_TARGET}, this specifies the + * new mode. + * + * @param coordinates the new coordinates + * @return the new mode + */ + public ControlMode getAlternative(DebuggerCoordinates coordinates) { + throw new AssertionError("INTERNAL: Non-selectable mode must provide alternative"); + } + + /** + * Find the new mode (or same) mode when activating the given coordinates + * + *
+ * The default is implemented using {@link #isSelectable(DebuggerCoordinates)} followed by + * {@link #getAlternative(DebuggerCoordinates)}. + * + * @param coordinates the new coordinates + * @return the mode + */ + public ControlMode modeOnChange(DebuggerCoordinates coordinates) { + if (isSelectable(coordinates)) { + return this; + } + return getAlternative(coordinates); + } + /** * Indicates whether this mode controls the target * diff --git a/Ghidra/Debug/Debugger-rmi-trace/src/test/java/ghidra/app/plugin/core/debug/gui/tracermi/connection/TraceRmiConnectionManagerProviderTest.java b/Ghidra/Debug/Debugger-rmi-trace/src/test/java/ghidra/app/plugin/core/debug/gui/tracermi/connection/TraceRmiConnectionManagerProviderTest.java index ea280b5762..b499c1b8ff 100644 --- a/Ghidra/Debug/Debugger-rmi-trace/src/test/java/ghidra/app/plugin/core/debug/gui/tracermi/connection/TraceRmiConnectionManagerProviderTest.java +++ b/Ghidra/Debug/Debugger-rmi-trace/src/test/java/ghidra/app/plugin/core/debug/gui/tracermi/connection/TraceRmiConnectionManagerProviderTest.java @@ -398,6 +398,8 @@ public class TraceRmiConnectionManagerProviderTest extends AbstractGhidraHeadedD waitForPass( () -> assertEquals(node, Unique.assertOne(provider.tree.getSelectedNodes()))); + controlService.setCurrentMode(target.getTrace(), ControlMode.RO_TRACE); + waitForSwing(); traceManager.activateSnap(0); waitForPass(() -> { assertEquals(0, traceManager.getCurrentSnap()); diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/control/DebuggerControlServicePlugin.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/control/DebuggerControlServicePlugin.java index 50e8942ac0..0ed085db2b 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/control/DebuggerControlServicePlugin.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/control/DebuggerControlServicePlugin.java @@ -22,9 +22,9 @@ import java.util.concurrent.*; import ghidra.app.plugin.PluginCategoryNames; import ghidra.app.plugin.core.debug.AbstractDebuggerPlugin; import ghidra.app.plugin.core.debug.DebuggerPluginPackage; -import ghidra.app.plugin.core.debug.event.TraceClosedPluginEvent; -import ghidra.app.plugin.core.debug.event.TraceOpenedPluginEvent; +import ghidra.app.plugin.core.debug.event.*; import ghidra.app.services.*; +import ghidra.app.services.DebuggerTraceManagerService.ActivationCause; import ghidra.debug.api.control.ControlMode; import ghidra.debug.api.tracemgr.DebuggerCoordinates; import ghidra.framework.plugintool.*; @@ -46,6 +46,7 @@ import ghidra.util.datastruct.ListenerSet; status = PluginStatus.RELEASED, eventsConsumed = { TraceOpenedPluginEvent.class, + TraceActivatedPluginEvent.class, TraceClosedPluginEvent.class, }, servicesRequired = { @@ -262,6 +263,29 @@ public class DebuggerControlServicePlugin extends AbstractDebuggerPlugin return new FollowsViewStateEditor(view); } + protected void coordinatesActivated(DebuggerCoordinates coordinates, ActivationCause cause) { + if (cause != ActivationCause.USER) { + return; + } + Trace trace = coordinates.getTrace(); + if (trace == null) { + return; + } + ControlMode oldMode; + ControlMode newMode; + synchronized (currentModes) { + oldMode = currentModes.getOrDefault(trace, ControlMode.DEFAULT); + newMode = oldMode.modeOnChange(coordinates); + if (newMode != oldMode) { + currentModes.put(trace, newMode); + } + } + if (newMode != oldMode) { + listeners.invoke().modeChanged(trace, newMode); + tool.contextChanged(null); + } + } + protected void installMemoryEditor(TraceProgramView view) { TraceProgramViewMemory memory = view.getMemory(); if (memory.getLiveMemoryHandler() != null) { @@ -322,6 +346,9 @@ public class DebuggerControlServicePlugin extends AbstractDebuggerPlugin if (event instanceof TraceOpenedPluginEvent ev) { installAllMemoryEditors(ev.getTrace()); } + else if (event instanceof TraceActivatedPluginEvent ev) { + coordinatesActivated(ev.getActiveCoordinates(), ev.getCause()); + } else if (event instanceof TraceClosedPluginEvent ev) { uninstallAllMemoryEditors(ev.getTrace()); } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/tracemgr/DebuggerTraceManagerServicePlugin.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/tracemgr/DebuggerTraceManagerServicePlugin.java index e71cd90dd0..e1a3438da5 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/tracemgr/DebuggerTraceManagerServicePlugin.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/tracemgr/DebuggerTraceManagerServicePlugin.java @@ -568,7 +568,7 @@ public class DebuggerTraceManagerServicePlugin extends Plugin } } newCurrent = validateCoordiantes(newCurrent, cause); - if (!doSetCurrent(newCurrent)) { + if (newCurrent == null || !doSetCurrent(newCurrent)) { return null; } return newCurrent; diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/model/DebuggerModelProviderTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/model/DebuggerModelProviderTest.java index 2a7a45e65c..44d27fb928 100644 --- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/model/DebuggerModelProviderTest.java +++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/model/DebuggerModelProviderTest.java @@ -25,8 +25,8 @@ import org.jdom.JDOMException; import org.junit.*; import db.Transaction; -import docking.widgets.table.DynamicTableColumn; -import docking.widgets.table.GDynamicColumnTableModel; +import docking.widgets.table.*; +import docking.widgets.table.ColumnSortState.SortDirection; import docking.widgets.tree.GTree; import docking.widgets.tree.GTreeNode; import docking.widgets.tree.support.GTreeSelectionEvent.EventOrigin; @@ -36,8 +36,7 @@ import ghidra.app.plugin.core.debug.gui.model.ObjectTableModel.PrimitiveRow; import ghidra.app.plugin.core.debug.gui.model.ObjectTableModel.ValueRow; import ghidra.app.plugin.core.debug.gui.model.ObjectTreeModel.AbstractNode; import ghidra.app.plugin.core.debug.gui.model.PathTableModel.PathRow; -import ghidra.app.plugin.core.debug.gui.model.columns.TraceValueLifePlotColumn; -import ghidra.app.plugin.core.debug.gui.model.columns.TraceValueValColumn; +import ghidra.app.plugin.core.debug.gui.model.columns.*; import ghidra.dbg.target.TargetEventScope; import ghidra.dbg.target.TargetObject; import ghidra.dbg.target.schema.SchemaContext; @@ -365,10 +364,18 @@ public class DebuggerModelProviderTest extends AbstractGhidraHeadedDebuggerTest modelProvider.setPath(TraceObjectKeyPath.parse("Processes[0].Handles")); waitForTasks(); + int keyColIndex = + waitForValue(() -> findColumnOfClass(modelProvider.elementsTablePanel.tableModel, + TraceValueKeyColumn.class)); int valColIndex = waitForValue(() -> findColumnOfClass(modelProvider.elementsTablePanel.tableModel, TraceValueValColumn.class)); + TableSortStateEditor sortEditor = new TableSortStateEditor(); + sortEditor.addSortedColumn(keyColIndex, SortDirection.ASCENDING); + modelProvider.elementsTablePanel.tableModel + .setTableSortState(sortEditor.createTableSortState()); + waitForPass(() -> { for (int i = 0; i < 10; i++) { Object obj = modelProvider.elementsTablePanel.tableModel.getValueAt(i, valColIndex); diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/watch/DebuggerWatchesProviderTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/watch/DebuggerWatchesProviderTest.java index f7432995f6..cec77a7f61 100644 --- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/watch/DebuggerWatchesProviderTest.java +++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/watch/DebuggerWatchesProviderTest.java @@ -35,7 +35,7 @@ import ghidra.app.plugin.core.debug.gui.register.*; import ghidra.app.plugin.core.debug.gui.watch.DebuggerWatchesProvider.WatchDataSettingsDialog; import ghidra.app.plugin.core.debug.service.control.DebuggerControlServicePlugin; import ghidra.app.plugin.core.debug.service.modules.DebuggerStaticMappingServicePlugin; -import ghidra.app.services.*; +import ghidra.app.services.DebuggerControlService; import ghidra.dbg.model.TestTargetRegisterBankInThread; import ghidra.debug.api.action.ActionSource; import ghidra.debug.api.control.ControlMode; diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/control/DebuggerControlServiceTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/control/DebuggerControlServiceTest.java index 934625056e..01a0c05d9d 100644 --- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/control/DebuggerControlServiceTest.java +++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/control/DebuggerControlServiceTest.java @@ -467,6 +467,15 @@ public class DebuggerControlServiceTest extends AbstractGhidraHeadedDebuggerTest waitForSwing(); assertEquals(recorder.getSnap(), traceManager.getCurrentSnap()); + traceManager.activateSnap(traceManager.getCurrentSnap() - 1); + waitForSwing(); + assertEquals( + "Cannot navigate time in Control Target mode. Switch to Trace or Emulate mode first.", + tool.getStatusInfo()); + assertEquals(recorder.getSnap(), traceManager.getCurrentSnap()); + + controlService.setCurrentMode(tb.trace, ControlMode.RW_EMULATOR); + waitForSwing(); traceManager.activateSnap(traceManager.getCurrentSnap() - 1); waitForSwing(); assertEquals(ControlMode.RW_EMULATOR, controlService.getCurrentMode(tb.trace));