GP-4334: Remove 'Synchronize Target Activation' toggle. Prohibit time navigation in Target mode.

This commit is contained in:
Dan
2024-02-20 12:40:40 -05:00
parent f5008f9f99
commit 5f7df08b67
7 changed files with 72 additions and 230 deletions

View File

@@ -303,7 +303,7 @@ public interface DebuggerTraceManagerService {
* *
* <p> * <p>
* If asynchronous notification is needed, use * If asynchronous notification is needed, use
* {@link #activateAndNotify(DebuggerCoordinates, boolean)}. * {@link #activateAndNotify(DebuggerCoordinates, ActivationCause)}.
* *
* @param coordinates the desired coordinates * @param coordinates the desired coordinates
* @param cause the cause of activation * @param cause the cause of activation
@@ -484,34 +484,6 @@ public interface DebuggerTraceManagerService {
activate(resolveObject(object)); 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 * Control whether traces should be saved by default
* *

View File

@@ -90,23 +90,10 @@ public enum ControlMode {
return false; return false;
} }
@Override
public ControlMode modeOnChange(DebuggerCoordinates coordinates) {
if (coordinates.isAliveAndPresent()) {
return this;
}
return getAlternative(coordinates);
}
@Override @Override
public boolean isSelectable(DebuggerCoordinates coordinates) { public boolean isSelectable(DebuggerCoordinates coordinates) {
return coordinates.isAlive(); 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. * Control actions, breakpoint commands, and state edits are all directed to the target.
@@ -159,23 +146,10 @@ public enum ControlMode {
return false; return false;
} }
@Override
public ControlMode modeOnChange(DebuggerCoordinates coordinates) {
if (coordinates.isAliveAndPresent()) {
return this;
}
return getAlternative(coordinates);
}
@Override @Override
public boolean isSelectable(DebuggerCoordinates coordinates) { public boolean isSelectable(DebuggerCoordinates coordinates) {
return coordinates.isAlive(); return coordinates.isAlive();
} }
@Override
public ControlMode getAlternative(DebuggerCoordinates coordinates) {
return RW_EMULATOR;
}
}, },
/** /**
* Control actions activate trace snapshots, breakpoint commands are directed to the emulator, * Control actions activate trace snapshots, breakpoint commands are directed to the emulator,
@@ -391,6 +365,38 @@ public enum ControlMode {
*/ */
public abstract boolean followsPresent(); public abstract boolean followsPresent();
/**
* Validate and/or adjust the given coordinates pre-activation
*
* <p>
* 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 * Check if (broadly speaking) the mode supports editing the given coordinates
* *
@@ -444,37 +450,6 @@ public enum ControlMode {
return true; return true;
} }
/**
* If the mode can no longer be selected for new coordinates, get the new mode
*
* <p>
* 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
*
* <p>
* 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 * Indicates whether this mode controls the target
* *

View File

@@ -480,6 +480,8 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter
@AutoServiceConsumed @AutoServiceConsumed
private DebuggerControlService controlService; private DebuggerControlService controlService;
@AutoServiceConsumed @AutoServiceConsumed
private DebuggerConsoleService consoleService;
@AutoServiceConsumed
private MarkerService markerService; // TODO: Mark address types (separate plugin?) private MarkerService markerService; // TODO: Mark address types (separate plugin?)
@SuppressWarnings("unused") @SuppressWarnings("unused")
private final AutoService.Wiring autoServiceWiring; private final AutoService.Wiring autoServiceWiring;
@@ -847,15 +849,7 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter
CompletableFuture<Void> future = editor.setRegister(rv); CompletableFuture<Void> future = editor.setRegister(rv);
future.exceptionally(ex -> { future.exceptionally(ex -> {
ex = AsyncUtils.unwrapThrowable(ex); ex = AsyncUtils.unwrapThrowable(ex);
if (ex instanceof DebuggerModelAccessException) { reportError("Edit Register", "Could not write target register", ex);
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);
}
return null; return null;
}); });
return; return;
@@ -1278,9 +1272,7 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter
current.getThread(), current.getFrame(), registers); current.getThread(), current.getFrame(), registers);
return future.exceptionally(ex -> { return future.exceptionally(ex -> {
ex = AsyncUtils.unwrapThrowable(ex); ex = AsyncUtils.unwrapThrowable(ex);
String msg = "Could not read target registers for selected thread: " + ex.getMessage(); reportError(null, "Could not read target registers for selected thread", ex);
Msg.info(this, msg);
plugin.getTool().setStatusInfo(msg);
return ExceptionUtils.rethrow(ex); return ExceptionUtils.rethrow(ex);
}).thenApply(__ -> null); }).thenApply(__ -> null);
} }
@@ -1301,4 +1293,17 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter
public DebuggerCoordinates getCurrent() { public DebuggerCoordinates getCurrent() {
return current; 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);
}
}
} }

View File

@@ -25,13 +25,10 @@ import org.apache.commons.lang3.ArrayUtils;
import docking.ActionContext; import docking.ActionContext;
import docking.WindowPosition; import docking.WindowPosition;
import docking.action.*; import docking.action.DockingActionIf;
import ghidra.app.plugin.core.debug.DebuggerPluginPackage; import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
import ghidra.app.plugin.core.debug.gui.DebuggerResources; import ghidra.app.plugin.core.debug.gui.DebuggerResources;
import ghidra.app.plugin.core.debug.gui.DebuggerResources.SynchronizeTargetAction;
import ghidra.app.plugin.core.debug.gui.DebuggerResources.ToToggleSelectionListener;
import ghidra.app.services.*; import ghidra.app.services.*;
import ghidra.app.services.DebuggerTraceManagerService.BooleanChangeAdapter;
import ghidra.debug.api.tracemgr.DebuggerCoordinates; import ghidra.debug.api.tracemgr.DebuggerCoordinates;
import ghidra.framework.model.DomainObjectChangeRecord; import ghidra.framework.model.DomainObjectChangeRecord;
import ghidra.framework.model.DomainObjectEvent; import ghidra.framework.model.DomainObjectEvent;
@@ -97,18 +94,14 @@ public class DebuggerThreadsProvider extends ComponentProviderAdapter {
final DebuggerThreadsPlugin plugin; final DebuggerThreadsPlugin plugin;
DebuggerCoordinates current = DebuggerCoordinates.NOWHERE; DebuggerCoordinates current = DebuggerCoordinates.NOWHERE;
Trace currentTrace; // Copy for transition
@AutoServiceConsumed @AutoServiceConsumed
DebuggerTargetService targetService; DebuggerTargetService targetService;
// @AutoServiceConsumed by method // @AutoServiceConsumed // via method
private DebuggerTraceManagerService traceManager; private DebuggerTraceManagerService traceManager;
@SuppressWarnings("unused") @SuppressWarnings("unused")
private final AutoService.Wiring autoServiceWiring; private final AutoService.Wiring autoServiceWiring;
private final BooleanChangeAdapter synchronizeTargetChangeListener =
this::changedSynchronizeTarget;
private final ForSnapsListener forSnapsListener = new ForSnapsListener(); private final ForSnapsListener forSnapsListener = new ForSnapsListener();
private JPanel mainPanel; private JPanel mainPanel;
@@ -118,15 +111,8 @@ public class DebuggerThreadsProvider extends ComponentProviderAdapter {
DebuggerThreadsPanel panel; DebuggerThreadsPanel panel;
DebuggerLegacyThreadsPanel legacyPanel; DebuggerLegacyThreadsPanel legacyPanel;
DockingAction actionSaveTrace;
// TODO: This should probably be moved to ModelProvider
ToggleDockingAction actionSyncTarget;
ActionContext myActionContext; ActionContext myActionContext;
// strong ref
ToToggleSelectionListener toToggleSelectionListener;
public DebuggerThreadsProvider(final DebuggerThreadsPlugin plugin) { public DebuggerThreadsProvider(final DebuggerThreadsPlugin plugin) {
super(plugin.getTool(), DebuggerResources.TITLE_PROVIDER_THREADS, plugin.getName()); super(plugin.getTool(), DebuggerResources.TITLE_PROVIDER_THREADS, plugin.getName());
this.plugin = plugin; this.plugin = plugin;
@@ -149,17 +135,7 @@ public class DebuggerThreadsProvider extends ComponentProviderAdapter {
@AutoServiceConsumed @AutoServiceConsumed
public void setTraceManager(DebuggerTraceManagerService traceManager) { public void setTraceManager(DebuggerTraceManagerService traceManager) {
if (this.traceManager != null) {
this.traceManager
.removeSynchronizeActiveChangeListener(synchronizeTargetChangeListener);
}
this.traceManager = traceManager; this.traceManager = traceManager;
if (traceManager != null) {
traceManager.addSynchronizeActiveChangeListener(synchronizeTargetChangeListener);
if (actionSyncTarget != null) {
actionSyncTarget.setSelected(traceManager.isSynchronizeActive());
}
}
contextChanged(); contextChanged();
} }
@@ -242,27 +218,6 @@ public class DebuggerThreadsProvider extends ComponentProviderAdapter {
} }
protected void createActions() { 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 @Override

View File

@@ -22,9 +22,9 @@ import java.util.concurrent.*;
import ghidra.app.plugin.PluginCategoryNames; import ghidra.app.plugin.PluginCategoryNames;
import ghidra.app.plugin.core.debug.AbstractDebuggerPlugin; import ghidra.app.plugin.core.debug.AbstractDebuggerPlugin;
import ghidra.app.plugin.core.debug.DebuggerPluginPackage; 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.*;
import ghidra.app.services.DebuggerTraceManagerService.ActivationCause;
import ghidra.debug.api.control.ControlMode; import ghidra.debug.api.control.ControlMode;
import ghidra.debug.api.tracemgr.DebuggerCoordinates; import ghidra.debug.api.tracemgr.DebuggerCoordinates;
import ghidra.framework.plugintool.*; import ghidra.framework.plugintool.*;
@@ -46,7 +46,6 @@ import ghidra.util.datastruct.ListenerSet;
status = PluginStatus.RELEASED, status = PluginStatus.RELEASED,
eventsConsumed = { eventsConsumed = {
TraceOpenedPluginEvent.class, TraceOpenedPluginEvent.class,
TraceActivatedPluginEvent.class,
TraceClosedPluginEvent.class, TraceClosedPluginEvent.class,
}, },
servicesRequired = { servicesRequired = {
@@ -263,29 +262,6 @@ public class DebuggerControlServicePlugin extends AbstractDebuggerPlugin
return new FollowsViewStateEditor(view); 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) { protected void installMemoryEditor(TraceProgramView view) {
TraceProgramViewMemory memory = view.getMemory(); TraceProgramViewMemory memory = view.getMemory();
if (memory.getLiveMemoryHandler() != null) { if (memory.getLiveMemoryHandler() != null) {
@@ -346,9 +322,6 @@ public class DebuggerControlServicePlugin extends AbstractDebuggerPlugin
if (event instanceof TraceOpenedPluginEvent ev) { if (event instanceof TraceOpenedPluginEvent ev) {
installAllMemoryEditors(ev.getTrace()); installAllMemoryEditors(ev.getTrace());
} }
else if (event instanceof TraceActivatedPluginEvent ev) {
coordinatesActivated(ev.getActiveCoordinates(), ev.getCause());
}
else if (event instanceof TraceClosedPluginEvent ev) { else if (event instanceof TraceClosedPluginEvent ev) {
uninstallAllMemoryEditors(ev.getTrace()); uninstallAllMemoryEditors(ev.getTrace());
} }

View File

@@ -253,7 +253,7 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
} }
DebuggerCoordinates coords = current; DebuggerCoordinates coords = current;
TraceObjectKeyPath focus = curTarget.getFocus(); TraceObjectKeyPath focus = curTarget.getFocus();
if (focus != null && synchronizeActive.get()) { if (focus != null) {
coords = coords.path(focus); coords = coords.path(focus);
} }
coords = coords.snap(curTarget.getSnap()); coords = coords.snap(curTarget.getSnap());
@@ -274,8 +274,6 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
@AutoConfigStateField(codec = BooleanAsyncConfigFieldCodec.class) @AutoConfigStateField(codec = BooleanAsyncConfigFieldCodec.class)
protected final AsyncReference<Boolean, Void> saveTracesByDefault = new AsyncReference<>(true); protected final AsyncReference<Boolean, Void> saveTracesByDefault = new AsyncReference<>(true);
@AutoConfigStateField(codec = BooleanAsyncConfigFieldCodec.class) @AutoConfigStateField(codec = BooleanAsyncConfigFieldCodec.class)
protected final AsyncReference<Boolean, Void> synchronizeActive = new AsyncReference<>(true);
@AutoConfigStateField(codec = BooleanAsyncConfigFieldCodec.class)
protected final AsyncReference<Boolean, Void> autoCloseOnTerminate = new AsyncReference<>(true); protected final AsyncReference<Boolean, Void> autoCloseOnTerminate = new AsyncReference<>(true);
// @AutoServiceConsumed via method // @AutoServiceConsumed via method
@@ -569,6 +567,7 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
newCurrent = newCurrent.snap(target.getSnap()); newCurrent = newCurrent.snap(target.getSnap());
} }
} }
newCurrent = validateCoordiantes(newCurrent, cause);
if (!doSetCurrent(newCurrent)) { if (!doSetCurrent(newCurrent)) {
return null; return null;
} }
@@ -583,10 +582,21 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
tool.contextChanged(null); 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) { private boolean isFollowsPresent(Trace trace) {
ControlMode mode = controlService == null ControlMode mode = getEffectiveControlMode(trace);
? ControlMode.DEFAULT
: controlService.getCurrentMode(trace);
return mode.followsPresent(); return mode.followsPresent();
} }
@@ -628,9 +638,6 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
return; return;
} }
DebuggerCoordinates toActivate = current.target(target); DebuggerCoordinates toActivate = current.target(target);
if (isFollowsPresent(current.getTrace())) {
toActivate = toActivate.snap(target.getSnap());
}
activate(toActivate, ActivationCause.FOLLOW_PRESENT); 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 (cause == ActivationCause.FOLLOW_PRESENT) {
if (!isFollowsPresent(newTrace)) { if (!isFollowsPresent(newTrace)) {
return AsyncUtils.nil(); return AsyncUtils.nil();
} }
if (current.getTrace() != newTrace) { if (current.getTrace() != newTrace) {
/** // The snap needs to match upon re-activating this trace.
* 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."
*/
try { try {
newTrace.getProgramView().setSnap(coordinates.getViewSnap()); newTrace.getProgramView().setSnap(coordinates.getViewSnap());
} }
@@ -1082,7 +1082,7 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
return null; return null;
}); });
if (!synchronizeActive.get() || cause != ActivationCause.USER) { if (cause != ActivationCause.USER) {
return future; return future;
} }
Target target = resolved.getTarget(); Target target = resolved.getTarget();
@@ -1152,27 +1152,6 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
return current.object(object); 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 @Override
public void setSaveTracesByDefault(boolean enabled) { public void setSaveTracesByDefault(boolean enabled) {
saveTracesByDefault.set(enabled, null); saveTracesByDefault.set(enabled, null);

View File

@@ -397,8 +397,6 @@ public class DebuggerTraceManagerServiceTest extends AbstractGhidraHeadedDebugge
@Test @Test
public void testSynchronizeFocusTraceToModel() throws Throwable { public void testSynchronizeFocusTraceToModel() throws Throwable {
assertTrue(traceManager.isSynchronizeActive());
createTestModel(); createTestModel();
mb.createTestProcessesAndThreads(); mb.createTestProcessesAndThreads();
@@ -448,18 +446,10 @@ public class DebuggerTraceManagerServiceTest extends AbstractGhidraHeadedDebugge
waitForSwing(); waitForSwing();
waitForPass(() -> assertEquals(frame0, mb.testModel.session.getFocus())); waitForPass(() -> assertEquals(frame0, mb.testModel.session.getFocus()));
traceManager.setSynchronizeActive(false);
traceManager.activateFrame(1);
waitForSwing();
waitForPass(() -> assertEquals(frame0, mb.testModel.session.getFocus()));
} }
@Test @Test
public void testSynchronizeFocusModelToTrace() throws Throwable { public void testSynchronizeFocusModelToTrace() throws Throwable {
assertTrue(traceManager.isSynchronizeActive());
createTestModel(); createTestModel();
mb.createTestProcessesAndThreads(); mb.createTestProcessesAndThreads();
@@ -503,12 +493,5 @@ public class DebuggerTraceManagerServiceTest extends AbstractGhidraHeadedDebugge
waitOn(mb.testModel.session.requestFocus(frame0)); waitOn(mb.testModel.session.requestFocus(frame0));
waitForPass(() -> assertEquals(0, traceManager.getCurrentFrame())); 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());
} }
} }