diff --git a/Ghidra/Debug/Debugger-api/src/main/java/ghidra/app/services/DebuggerTraceManagerService.java b/Ghidra/Debug/Debugger-api/src/main/java/ghidra/app/services/DebuggerTraceManagerService.java index a78c3ab4ab..3b8c369845 100644 --- a/Ghidra/Debug/Debugger-api/src/main/java/ghidra/app/services/DebuggerTraceManagerService.java +++ b/Ghidra/Debug/Debugger-api/src/main/java/ghidra/app/services/DebuggerTraceManagerService.java @@ -303,7 +303,7 @@ public interface DebuggerTraceManagerService { * *

* If asynchronous notification is needed, use - * {@link #activateAndNotify(DebuggerCoordinates, boolean)}. + * {@link #activateAndNotify(DebuggerCoordinates, ActivationCause)}. * * @param coordinates the desired coordinates * @param cause the cause of activation @@ -484,34 +484,6 @@ public interface DebuggerTraceManagerService { activate(resolveObject(object)); } - /** - * Control whether trace activation is synchronized with debugger activation - * - * @param enabled true to synchronize, false otherwise - */ - void setSynchronizeActive(boolean enabled); - - /** - * Check whether trace activation is synchronized with debugger activation - * - * @return true if synchronized, false otherwise - */ - boolean isSynchronizeActive(); - - /** - * Add a listener for changes to activation synchronization enablement - * - * @param listener the listener to receive change notifications - */ - void addSynchronizeActiveChangeListener(BooleanChangeAdapter listener); - - /** - * Remove a listener for changes to activation synchronization enablement - * - * @param listener the listener receiving change notifications - */ - void removeSynchronizeActiveChangeListener(BooleanChangeAdapter listener); - /** * Control whether traces should be saved by default * 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 823bb8be77..45e9b4dfcf 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 @@ -90,23 +90,10 @@ public enum ControlMode { return false; } - @Override - public ControlMode modeOnChange(DebuggerCoordinates coordinates) { - if (coordinates.isAliveAndPresent()) { - return this; - } - return getAlternative(coordinates); - } - @Override public boolean isSelectable(DebuggerCoordinates coordinates) { return coordinates.isAlive(); } - - @Override - public ControlMode getAlternative(DebuggerCoordinates coordinates) { - return RO_TRACE; - } }, /** * Control actions, breakpoint commands, and state edits are all directed to the target. @@ -159,23 +146,10 @@ public enum ControlMode { return false; } - @Override - public ControlMode modeOnChange(DebuggerCoordinates coordinates) { - if (coordinates.isAliveAndPresent()) { - return this; - } - return getAlternative(coordinates); - } - @Override 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, @@ -391,6 +365,38 @@ public enum ControlMode { */ public abstract boolean followsPresent(); + /** + * Validate and/or adjust the given coordinates pre-activation + * + *

+ * This is called by the trace manager whenever there is a request to activate new coordinates. + * The control mode may adjust or reject the request before the trace manager actually performs + * and notifies the activation. + * + * @param tool the tool for displaying status messages + * @param coordinates the requested coordinates + * @param cause the cause of the activation + * @return the effective coordinates or null to reject + */ + public DebuggerCoordinates validateCoordinates(PluginTool tool, + DebuggerCoordinates coordinates, ActivationCause cause) { + if (!followsPresent()) { + return coordinates; + } + Target target = coordinates.getTarget(); + if (target == null) { + return coordinates; + } + if (cause == ActivationCause.USER && + (!coordinates.getTime().isSnapOnly() || coordinates.getSnap() != target.getSnap())) { + tool.setStatusInfo( + "Cannot navigate time in %s mode. Switch to Trace or Emulate mode first." + .formatted(name), + true); + } + return coordinates.snap(target.getSnap()); + } + /** * Check if (broadly speaking) the mode supports editing the given coordinates * @@ -444,37 +450,6 @@ 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/src/main/java/ghidra/app/plugin/core/debug/gui/register/DebuggerRegistersProvider.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/register/DebuggerRegistersProvider.java index 298fdce36a..bf7ba68498 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/register/DebuggerRegistersProvider.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/register/DebuggerRegistersProvider.java @@ -480,6 +480,8 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter @AutoServiceConsumed private DebuggerControlService controlService; @AutoServiceConsumed + private DebuggerConsoleService consoleService; + @AutoServiceConsumed private MarkerService markerService; // TODO: Mark address types (separate plugin?) @SuppressWarnings("unused") private final AutoService.Wiring autoServiceWiring; @@ -847,15 +849,7 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter CompletableFuture future = editor.setRegister(rv); future.exceptionally(ex -> { ex = AsyncUtils.unwrapThrowable(ex); - if (ex instanceof DebuggerModelAccessException) { - Msg.error(this, "Could not write target register", ex); - plugin.getTool() - .setStatusInfo("Could not write target register: " + ex.getMessage()); - } - else { - Msg.showError(this, getComponent(), "Edit Register", - "Could not write target register", ex); - } + reportError("Edit Register", "Could not write target register", ex); return null; }); return; @@ -1278,9 +1272,7 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter current.getThread(), current.getFrame(), registers); return future.exceptionally(ex -> { ex = AsyncUtils.unwrapThrowable(ex); - String msg = "Could not read target registers for selected thread: " + ex.getMessage(); - Msg.info(this, msg); - plugin.getTool().setStatusInfo(msg); + reportError(null, "Could not read target registers for selected thread", ex); return ExceptionUtils.rethrow(ex); }).thenApply(__ -> null); } @@ -1301,4 +1293,17 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter public DebuggerCoordinates getCurrent() { return current; } + + private void reportError(String title, String message, Throwable ex) { + plugin.getTool().setStatusInfo(message + ": " + ex.getMessage()); + if (title != null && !(ex instanceof DebuggerModelAccessException)) { + Msg.showError(this, getComponent(), title, message, ex); + } + else if (consoleService != null) { + consoleService.log(DebuggerResources.ICON_LOG_ERROR, message, ex); + } + else { + Msg.error(this, message, ex); + } + } } 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 f2b146ab23..216f208c81 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 @@ -25,13 +25,10 @@ import org.apache.commons.lang3.ArrayUtils; import docking.ActionContext; import docking.WindowPosition; -import docking.action.*; +import docking.action.DockingActionIf; import ghidra.app.plugin.core.debug.DebuggerPluginPackage; import ghidra.app.plugin.core.debug.gui.DebuggerResources; -import ghidra.app.plugin.core.debug.gui.DebuggerResources.SynchronizeTargetAction; -import ghidra.app.plugin.core.debug.gui.DebuggerResources.ToToggleSelectionListener; import ghidra.app.services.*; -import ghidra.app.services.DebuggerTraceManagerService.BooleanChangeAdapter; import ghidra.debug.api.tracemgr.DebuggerCoordinates; import ghidra.framework.model.DomainObjectChangeRecord; import ghidra.framework.model.DomainObjectEvent; @@ -97,18 +94,14 @@ public class DebuggerThreadsProvider extends ComponentProviderAdapter { final DebuggerThreadsPlugin plugin; DebuggerCoordinates current = DebuggerCoordinates.NOWHERE; - Trace currentTrace; // Copy for transition @AutoServiceConsumed DebuggerTargetService targetService; - // @AutoServiceConsumed by method + // @AutoServiceConsumed // via method private DebuggerTraceManagerService traceManager; @SuppressWarnings("unused") private final AutoService.Wiring autoServiceWiring; - private final BooleanChangeAdapter synchronizeTargetChangeListener = - this::changedSynchronizeTarget; - private final ForSnapsListener forSnapsListener = new ForSnapsListener(); private JPanel mainPanel; @@ -118,15 +111,8 @@ public class DebuggerThreadsProvider extends ComponentProviderAdapter { DebuggerThreadsPanel panel; DebuggerLegacyThreadsPanel legacyPanel; - DockingAction actionSaveTrace; - // TODO: This should probably be moved to ModelProvider - ToggleDockingAction actionSyncTarget; - ActionContext myActionContext; - // strong ref - ToToggleSelectionListener toToggleSelectionListener; - public DebuggerThreadsProvider(final DebuggerThreadsPlugin plugin) { super(plugin.getTool(), DebuggerResources.TITLE_PROVIDER_THREADS, plugin.getName()); this.plugin = plugin; @@ -149,17 +135,7 @@ public class DebuggerThreadsProvider extends ComponentProviderAdapter { @AutoServiceConsumed public void setTraceManager(DebuggerTraceManagerService traceManager) { - if (this.traceManager != null) { - this.traceManager - .removeSynchronizeActiveChangeListener(synchronizeTargetChangeListener); - } this.traceManager = traceManager; - if (traceManager != null) { - traceManager.addSynchronizeActiveChangeListener(synchronizeTargetChangeListener); - if (actionSyncTarget != null) { - actionSyncTarget.setSelected(traceManager.isSynchronizeActive()); - } - } contextChanged(); } @@ -242,27 +218,6 @@ public class DebuggerThreadsProvider extends ComponentProviderAdapter { } protected void createActions() { - actionSyncTarget = SynchronizeTargetAction.builder(plugin) - .selected(traceManager != null && traceManager.isSynchronizeActive()) - .enabledWhen(c -> traceManager != null) - .onAction(c -> toggleSyncFocus(actionSyncTarget.isSelected())) - .buildAndInstallLocal(this); - traceManager.addSynchronizeActiveChangeListener( - toToggleSelectionListener = new ToToggleSelectionListener(actionSyncTarget)); - } - - private void changedSynchronizeTarget(boolean value) { - if (actionSyncTarget == null || actionSyncTarget.isSelected()) { - return; - } - actionSyncTarget.setSelected(value); - } - - private void toggleSyncFocus(boolean enabled) { - if (traceManager == null) { - return; - } - traceManager.setSynchronizeActive(enabled); } @Override 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 0ed085db2b..50e8942ac0 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.*; +import ghidra.app.plugin.core.debug.event.TraceClosedPluginEvent; +import ghidra.app.plugin.core.debug.event.TraceOpenedPluginEvent; 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,7 +46,6 @@ import ghidra.util.datastruct.ListenerSet; status = PluginStatus.RELEASED, eventsConsumed = { TraceOpenedPluginEvent.class, - TraceActivatedPluginEvent.class, TraceClosedPluginEvent.class, }, servicesRequired = { @@ -263,29 +262,6 @@ 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) { @@ -346,9 +322,6 @@ 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 576f9e86ce..e71cd90dd0 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 @@ -253,7 +253,7 @@ public class DebuggerTraceManagerServicePlugin extends Plugin } DebuggerCoordinates coords = current; TraceObjectKeyPath focus = curTarget.getFocus(); - if (focus != null && synchronizeActive.get()) { + if (focus != null) { coords = coords.path(focus); } coords = coords.snap(curTarget.getSnap()); @@ -274,8 +274,6 @@ public class DebuggerTraceManagerServicePlugin extends Plugin @AutoConfigStateField(codec = BooleanAsyncConfigFieldCodec.class) protected final AsyncReference saveTracesByDefault = new AsyncReference<>(true); @AutoConfigStateField(codec = BooleanAsyncConfigFieldCodec.class) - protected final AsyncReference synchronizeActive = new AsyncReference<>(true); - @AutoConfigStateField(codec = BooleanAsyncConfigFieldCodec.class) protected final AsyncReference autoCloseOnTerminate = new AsyncReference<>(true); // @AutoServiceConsumed via method @@ -569,6 +567,7 @@ public class DebuggerTraceManagerServicePlugin extends Plugin newCurrent = newCurrent.snap(target.getSnap()); } } + newCurrent = validateCoordiantes(newCurrent, cause); if (!doSetCurrent(newCurrent)) { return null; } @@ -583,10 +582,21 @@ public class DebuggerTraceManagerServicePlugin extends Plugin tool.contextChanged(null); } + private ControlMode getEffectiveControlMode(Trace trace) { + if (trace == null) { + return ControlMode.RO_TRACE; + } + return controlService == null ? ControlMode.DEFAULT : controlService.getCurrentMode(trace); + } + + private DebuggerCoordinates validateCoordiantes(DebuggerCoordinates coordinates, + ActivationCause cause) { + ControlMode mode = getEffectiveControlMode(coordinates.getTrace()); + return mode.validateCoordinates(tool, coordinates, cause); + } + private boolean isFollowsPresent(Trace trace) { - ControlMode mode = controlService == null - ? ControlMode.DEFAULT - : controlService.getCurrentMode(trace); + ControlMode mode = getEffectiveControlMode(trace); return mode.followsPresent(); } @@ -628,9 +638,6 @@ public class DebuggerTraceManagerServicePlugin extends Plugin return; } DebuggerCoordinates toActivate = current.target(target); - if (isFollowsPresent(current.getTrace())) { - toActivate = toActivate.snap(target.getSnap()); - } activate(toActivate, ActivationCause.FOLLOW_PRESENT); } @@ -1043,19 +1050,12 @@ public class DebuggerTraceManagerServicePlugin extends Plugin } } - if (!synchronizeActive.get() && cause == ActivationCause.SYNC_MODEL) { - return AsyncUtils.nil(); - } if (cause == ActivationCause.FOLLOW_PRESENT) { if (!isFollowsPresent(newTrace)) { return AsyncUtils.nil(); } if (current.getTrace() != newTrace) { - /** - * The snap needs to match upon re-activating this trace, lest it look like the user - * intentionally navigated to the past. That may cause the control mode to switch - * off of "Target." - */ + // The snap needs to match upon re-activating this trace. try { newTrace.getProgramView().setSnap(coordinates.getViewSnap()); } @@ -1082,7 +1082,7 @@ public class DebuggerTraceManagerServicePlugin extends Plugin return null; }); - if (!synchronizeActive.get() || cause != ActivationCause.USER) { + if (cause != ActivationCause.USER) { return future; } Target target = resolved.getTarget(); @@ -1152,27 +1152,6 @@ public class DebuggerTraceManagerServicePlugin extends Plugin return current.object(object); } - @Override - public void setSynchronizeActive(boolean enabled) { - synchronizeActive.set(enabled, null); - // TODO: Which action to take here, if any? - } - - @Override - public boolean isSynchronizeActive() { - return synchronizeActive.get(); - } - - @Override - public void addSynchronizeActiveChangeListener(BooleanChangeAdapter listener) { - synchronizeActive.addChangeListener(listener); - } - - @Override - public void removeSynchronizeActiveChangeListener(BooleanChangeAdapter listener) { - synchronizeActive.removeChangeListener(listener); - } - @Override public void setSaveTracesByDefault(boolean enabled) { saveTracesByDefault.set(enabled, null); diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/tracemgr/DebuggerTraceManagerServiceTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/tracemgr/DebuggerTraceManagerServiceTest.java index e507856185..c9d5830177 100644 --- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/tracemgr/DebuggerTraceManagerServiceTest.java +++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/tracemgr/DebuggerTraceManagerServiceTest.java @@ -397,8 +397,6 @@ public class DebuggerTraceManagerServiceTest extends AbstractGhidraHeadedDebugge @Test public void testSynchronizeFocusTraceToModel() throws Throwable { - assertTrue(traceManager.isSynchronizeActive()); - createTestModel(); mb.createTestProcessesAndThreads(); @@ -448,18 +446,10 @@ public class DebuggerTraceManagerServiceTest extends AbstractGhidraHeadedDebugge waitForSwing(); waitForPass(() -> assertEquals(frame0, mb.testModel.session.getFocus())); - - traceManager.setSynchronizeActive(false); - traceManager.activateFrame(1); - waitForSwing(); - - waitForPass(() -> assertEquals(frame0, mb.testModel.session.getFocus())); } @Test public void testSynchronizeFocusModelToTrace() throws Throwable { - assertTrue(traceManager.isSynchronizeActive()); - createTestModel(); mb.createTestProcessesAndThreads(); @@ -503,12 +493,5 @@ public class DebuggerTraceManagerServiceTest extends AbstractGhidraHeadedDebugge waitOn(mb.testModel.session.requestFocus(frame0)); waitForPass(() -> assertEquals(0, traceManager.getCurrentFrame())); - - traceManager.setSynchronizeActive(false); - waitOn(mb.testModel.session.requestFocus(frame1)); - // Not super reliable, but at least wait for it to change in case it does - Thread.sleep(200); - - assertEquals(0, traceManager.getCurrentFrame()); } }