diff --git a/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerListingPlugin/DebuggerListingPlugin.html b/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerListingPlugin/DebuggerListingPlugin.html index 108aa48893..ff1c20b779 100644 --- a/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerListingPlugin/DebuggerListingPlugin.html +++ b/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerListingPlugin/DebuggerListingPlugin.html @@ -128,7 +128,7 @@ -

Sync to Static Listing

+

Auto-Sync Cursor with Static Listing

This action is always available, but only on the primary dynamic listing. It configures location synchronization with the (primary) static listing. When enabled, navigation in either @@ -139,7 +139,41 @@ "help/topics/DebuggerStaticMappingPlugin/DebuggerStaticMappingPlugin.html">Static Mappings window. When you navigate to a location contained by a module, but there is no corresponding static location, the listing logs a "missing module" to the console, offering either to import - the module or map it to an existing program.

+ the module or map it to an existing program. If the cursor cannot be mapped, the other + listing's location is left unchanged. If this does not seem correct. Check your module list and + static mappings.

+ +

Auto-Sync Selection with Static Listing

+ +

This action is always available, but only on the primary dynamic listing. It configures + selection synchronization with the (primary) static listing. When enabled, selection in either + listing automatically selects the corresponding ranges, if applicable, in the other. In + general, "corresponding ranges" are computed using information about loaded modules reported by + the debugger. For the finer details, see the Static Mappings + window. Portions of the selection which cannot be mapped are omitted.

+ +

Sync Selection Here into Static Listing

+ +

This action is available whenever the current context is dynamic and has a selection. It + maps the current dynamic selection to corresponding static ranges and selects those in the + static listing. In general, "corresponding ranges" are computed using information about loaded + modules reported by the debugger. For the finer details, see the Static Mappings + window. Portions of the selection which cannot be mapped are omitted. If no part of the + selection is mappable, an error is displayed in the status bar. This can happen if the module + list is missing, or Ghidra could not find the program for the current module.

+ +

Sync Selection Here from Static Listing

+ +

This action is available whenever the current context is static and has a selection. It maps + the current static selection to corresponding dynamic ranges and selects those in the dynamic + listing. In general, "corresponding ranges" are computed using information about loaded modules + reported by the debugger. For the finer details, see the Static Mappings + window. Portions of the selection which cannot be mapped are omitted. If no part of the + selection is mappable, an error is displayed in the status bar. This can happen if the module + list is missing, or Ghidra could not find the program for the current module.

Open Program

diff --git a/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerObjectsPlugin/DebuggerObjectsPlugin.html b/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerObjectsPlugin/DebuggerObjectsPlugin.html index df1f77ae1c..e03a7277b2 100644 --- a/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerObjectsPlugin/DebuggerObjectsPlugin.html +++ b/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerObjectsPlugin/DebuggerObjectsPlugin.html @@ -325,9 +325,9 @@

Update While Running

-

By default, events are passed to the Objects Viewer even while the target is running. - The resulting changes in the GUI may be distracting for some. To disable updates to the - Objects Viewer, toggle "Updates While Running" off.

+

By default, events are passed to the Objects Viewer even while the target is running. The + resulting changes in the GUI may be distracting for some. To disable updates to the Objects + Viewer, toggle "Updates While Running" off.

Color Options

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 c9b1867215..e8b7a4db3a 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 @@ -833,20 +833,68 @@ public interface DebuggerResources { String ownerName = owner.getName(); return new ActionBuilder(NAME, ownerName).description(DESCRIPTION) .menuPath(NAME) + .menuGroup("a") .keyBinding(KeyStroke.getKeyStroke(KeyEvent.VK_G, 0)) .helpLocation(new HelpLocation(ownerName, HELP_ANCHOR)); } } - abstract class AbstractSyncToStaticListingAction extends ToggleDockingAction { - public static final String NAME = "Sync to Static Listing"; - public static final String HELP_ANCHOR = "sync_static"; + interface AutoSyncCursorWithStaticListingAction { + String NAME = "Auto-Sync Cursor with Static Listing"; + String DESCRIPTION = "Automatically synchronize the static and dynamic listings' cursors"; + String HELP_ANCHOR = "auto_sync_cursor_static"; - public AbstractSyncToStaticListingAction(Plugin owner) { - super(NAME, owner.getName()); - setDescription("Synchronize the static listing (and related providers)" + - " to the dynamic listing (and related providers) where a mapping is" + " known"); - setHelpLocation(new HelpLocation(owner.getName(), HELP_ANCHOR)); + static ToggleActionBuilder builder(Plugin owner) { + String ownerName = owner.getName(); + return new ToggleActionBuilder(NAME, ownerName) + .description(DESCRIPTION) + .menuPath(NAME) + .helpLocation(new HelpLocation(ownerName, HELP_ANCHOR)); + } + } + + interface AutoSyncSelectionWithStaticListingAction { + String NAME = "Auto-Sync Selection with Static Listing"; + String DESCRIPTION = + "Automatically synchronize the static and dynamic listings' selections"; + String HELP_ANCHOR = "auto_sync_selection_static"; + + static ToggleActionBuilder builder(Plugin owner) { + String ownerName = owner.getName(); + return new ToggleActionBuilder(NAME, ownerName) + .description(DESCRIPTION) + .menuPath(NAME) + .helpLocation(new HelpLocation(ownerName, HELP_ANCHOR)); + } + } + + interface SyncSelectionIntoStaticListingAction { + String NAME = "Sync Selection into Static Listing"; + String DESCRIPTION = + "Change the static listing's selection to synchronize with this component's selection"; + String HELP_ANCHOR = "sync_selection_into_static"; + + static ActionBuilder builder(Plugin owner) { + String ownerName = owner.getName(); + return new ActionBuilder(NAME, ownerName) + .description(DESCRIPTION) + .menuPath(NAME) + .helpLocation(new HelpLocation(ownerName, HELP_ANCHOR)); + } + } + + interface SyncSelectionFromStaticListingAction { + String NAME = "Sync Selection from Static Listing"; + String DESCRIPTION = + "Change this component's selection to synchronize with the static listing's selection"; + String HELP_ANCHOR = "sync_selection_from_static"; + + static ActionBuilder builder(Plugin owner) { + String ownerName = owner.getName(); + return new ActionBuilder(NAME, ownerName) + .description(DESCRIPTION) + .menuPath(NAME) + .helpLocation(new HelpLocation(ownerName, HELP_ANCHOR)); } } @@ -884,15 +932,18 @@ public interface DebuggerResources { } } - abstract class AbstractFollowsCurrentThreadAction extends ToggleDockingAction { - public static final String NAME = "Follows Selected Thread"; - public static final String HELP_ANCHOR = "follows_thread"; + interface FollowsCurrentThreadAction { + String NAME = "Follows Selected Thread"; + String DESCRIPTION = "Register tracking follows selected thread (and contents" + + " follow selected trace)"; + String HELP_ANCHOR = "follows_thread"; - public AbstractFollowsCurrentThreadAction(Plugin owner) { - super(NAME, owner.getName()); - setDescription("Register tracking follows selected thread (and contents" + - " follow selected trace)"); - setHelpLocation(new HelpLocation(owner.getName(), HELP_ANCHOR)); + static ToggleActionBuilder builder(Plugin owner) { + String ownerName = owner.getName(); + return new ToggleActionBuilder(NAME, ownerName) + .description(DESCRIPTION) + .menuPath(NAME) + .helpLocation(new HelpLocation(ownerName, HELP_ANCHOR)); } } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/DebuggerStaticSyncTrait.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/DebuggerStaticSyncTrait.java new file mode 100644 index 0000000000..4d0579c54b --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/DebuggerStaticSyncTrait.java @@ -0,0 +1,399 @@ +/* ### + * 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.app.plugin.core.debug.gui.action; + +import java.lang.invoke.MethodHandles; +import java.util.Collection; +import java.util.Set; + +import docking.ActionContext; +import docking.ComponentProvider; +import docking.action.DockingAction; +import docking.action.ToggleDockingAction; +import docking.widgets.EventTrigger; +import ghidra.app.context.ProgramLocationActionContext; +import ghidra.app.plugin.core.debug.DebuggerCoordinates; +import ghidra.app.plugin.core.debug.gui.DebuggerResources.*; +import ghidra.app.services.DebuggerStaticMappingChangeListener; +import ghidra.app.services.DebuggerStaticMappingService; +import ghidra.app.services.DebuggerStaticMappingService.MappedAddressRange; +import ghidra.framework.options.SaveState; +import ghidra.framework.plugintool.*; +import ghidra.framework.plugintool.annotation.AutoConfigStateField; +import ghidra.framework.plugintool.annotation.AutoServiceConsumed; +import ghidra.program.model.address.AddressCollectors; +import ghidra.program.model.address.AddressSet; +import ghidra.program.model.listing.Program; +import ghidra.program.util.ProgramLocation; +import ghidra.program.util.ProgramSelection; +import ghidra.trace.model.Trace; +import ghidra.trace.model.program.TraceProgramView; +import ghidra.util.Msg; +import ghidra.util.Swing; + +public class DebuggerStaticSyncTrait { + protected static final AutoConfigState.ClassHandler CONFIG_STATE_HANDLER = + AutoConfigState.wireHandler(DebuggerStaticSyncTrait.class, MethodHandles.lookup()); + + private static boolean dynamicHasSelection(ProgramLocationActionContext ctx) { + if (ctx == null) { + return false; + } + ProgramSelection sel = ctx.getSelection(); + if (sel == null || sel.isEmpty()) { + return false; + } + return true; + } + + protected class ForStaticSyncMappingChangeListener + implements DebuggerStaticMappingChangeListener { + @Override + public void mappingsChanged(Set affectedTraces, Set affectedPrograms) { + Swing.runIfSwingOrRunLater(() -> { + if (current.getView() == null) { + return; + } + if (!affectedTraces.contains(current.getTrace())) { + return; + } + doAutoSyncCursorIntoStatic(currentDynamicLocation); + // TODO: Remember last sync direction, or just always take dyn->static + doAutoSyncSelectionIntoStatic(current.getView(), currentDynamicSelection); + }); + + /** + * TODO: Remove "missing" entry in modules dialog, if present? There's some nuance here, + * because the trace presenting the mapping may not be the same as the trace that missed + * the module originally. I'm tempted to just leave it and let the user remove it. + */ + } + } + + protected ToggleDockingAction actionAutoSyncCursorWithStaticListing; + protected ToggleDockingAction actionAutoSyncSelectionWithStaticListing; + protected DockingAction actionSyncSelectionIntoStaticListing; + protected DockingAction actionSyncSelectionFromStaticListing; + + @AutoConfigStateField + private boolean autoSyncCursorWithStaticListing; + @AutoConfigStateField + private boolean autoSyncSelectionWithStaticListing; + + private final PluginTool tool; + private final Plugin plugin; + private final ComponentProvider provider; + private final boolean isAutoSyncAllowed; + + //@AutoServiceConsumed via method + private DebuggerStaticMappingService mappingService; + @SuppressWarnings("unused") + private final AutoService.Wiring autoServiceWiring; + + private DebuggerCoordinates current = DebuggerCoordinates.NOWHERE; + private ProgramLocation currentDynamicLocation; + private ProgramSelection currentDynamicSelection; + + private Program currentStaticProgram; + private ProgramLocation currentStaticLocation; + private ProgramSelection currentStaticSelection; + + protected final ForStaticSyncMappingChangeListener mappingChangeListener = + new ForStaticSyncMappingChangeListener(); + + public DebuggerStaticSyncTrait(PluginTool tool, Plugin plugin, ComponentProvider provider, + boolean isAutoSyncAllowed) { + this.tool = tool; + this.plugin = plugin; + this.provider = provider; + this.isAutoSyncAllowed = isAutoSyncAllowed; + + this.autoServiceWiring = AutoService.wireServicesConsumed(plugin, this); + + this.autoSyncCursorWithStaticListing = isAutoSyncAllowed; + this.autoSyncSelectionWithStaticListing = isAutoSyncAllowed; + } + + @AutoServiceConsumed + private void setMappingService(DebuggerStaticMappingService mappingService) { + if (this.mappingService != null) { + this.mappingService.removeChangeListener(mappingChangeListener); + } + this.mappingService = mappingService; + if (this.mappingService != null) { + this.mappingService.addChangeListener(mappingChangeListener); + doAutoSyncCursorIntoStatic(currentDynamicLocation); + } + } + + public ToggleDockingAction installAutoSyncCursorWithStaticListingAction() { + return actionAutoSyncCursorWithStaticListing = AutoSyncCursorWithStaticListingAction + .builder(plugin) + .enabled(true) + .selected(true) + .onAction(ctx -> doSetAutoSyncCursorWithStaticListing( + actionAutoSyncCursorWithStaticListing.isSelected())) + .buildAndInstallLocal(provider); + } + + public ToggleDockingAction installAutoSyncSelectionWithStaticListingAction() { + return actionAutoSyncSelectionWithStaticListing = AutoSyncSelectionWithStaticListingAction + .builder(plugin) + .enabled(true) + .selected(true) + .onAction(ctx -> doSetAutoSyncSelectionWithStaticListing( + actionAutoSyncSelectionWithStaticListing.isSelected())) + .buildAndInstallLocal(provider); + } + + public DockingAction installSyncSelectionIntoStaticListingAction() { + return actionSyncSelectionIntoStaticListing = SyncSelectionIntoStaticListingAction + .builder(plugin) + .withContext(ProgramLocationActionContext.class) + .enabledWhen(ctx -> dynamicHasSelection(ctx)) + .onAction(this::activatedSyncSelectionIntoStatic) + .buildAndInstallLocal(provider); + } + + public DockingAction installSyncSelectionFromStaticListingAction() { + return actionSyncSelectionFromStaticListing = SyncSelectionFromStaticListingAction + .builder(plugin) + .withContext(ProgramLocationActionContext.class) + .enabledWhen(ctx -> staticHasSelection(ctx)) + .onAction(this::activatedSyncSelectionFromStatic) + .buildAndInstallLocal(provider); + } + + private boolean staticHasSelection(ActionContext ctx) { + return currentStaticSelection != null && !currentStaticSelection.isEmpty(); + } + + protected void activatedSyncSelectionIntoStatic(ProgramLocationActionContext ctx) { + ProgramSelection result = doSyncSelectionIntoStatic(ctx.getProgram(), ctx.getSelection()); + if (result != null && result.isEmpty()) { + displayMapError("the dynamic view", "the static listing"); + } + } + + protected void activatedSyncSelectionFromStatic(ActionContext ctx) { + ProgramSelection result = doSyncSelectionFromStatic(); + if (result != null && result.isEmpty()) { + displayMapError("the static listing", "the dynamic view"); + } + } + + protected void doSyncCursorIntoStatic(ProgramLocation location) { + if (location == null) { + return; + } + ProgramLocation staticLoc = mappingService.getStaticLocationFromDynamic(location); + if (staticLoc == null) { + return; + } + staticGoTo(staticLoc); + } + + protected void doSyncCursorFromStatic() { + TraceProgramView view = current.getView(); // NB. Used for snap (don't want emuSnap) + if (view == null || currentStaticLocation == null) { + return; + } + ProgramLocation dynamicLoc = + mappingService.getDynamicLocationFromStatic(view, currentStaticLocation); + if (dynamicLoc == null) { + return; + } + dynamicGoTo(dynamicLoc); + } + + public void doAutoSyncCursorIntoStatic(ProgramLocation location) { + if (!isAutoSyncCursorWithStaticListing()) { + return; + } + doSyncCursorIntoStatic(location); + } + + public void doAutoSyncCursorFromStatic() { + if (!isAutoSyncCursorWithStaticListing()) { + return; + } + doSyncCursorFromStatic(); + } + + protected void doSetAutoSyncCursorWithStaticListing(boolean sync) { + this.autoSyncCursorWithStaticListing = sync; + provider.contextChanged(); + doAutoSyncCursorIntoStatic(currentDynamicLocation); + } + + protected void doSetAutoSyncSelectionWithStaticListing(boolean sync) { + this.autoSyncSelectionWithStaticListing = sync; + provider.contextChanged(); + doAutoSyncSelectionIntoStatic(current.getView(), currentDynamicSelection); + } + + protected ProgramSelection doSyncSelectionIntoStatic(Program program, ProgramSelection sel) { + if (program == null || sel == null || currentStaticProgram == null) { + return null; + } + TraceProgramView view = (TraceProgramView) program; + Collection ranges = + mappingService.getOpenMappedViews(view.getTrace(), sel, view.getSnap()) + .get(currentStaticProgram); + AddressSet mapped; + if (ranges == null) { + mapped = new AddressSet(); + } + else { + mapped = ranges.stream() + .map(r -> r.getDestinationAddressRange()) + .collect(AddressCollectors.toAddressSet()); + } + ProgramSelection result = new ProgramSelection(mapped); + staticSelect(currentStaticProgram, result); + return result; + } + + protected ProgramSelection doSyncSelectionFromStatic() { + TraceProgramView view = current.getView(); + if (view == null || currentStaticProgram == null || currentStaticSelection == null) { + return null; + } + AddressSet mapped = + mappingService.getOpenMappedViews(currentStaticProgram, currentStaticSelection) + .entrySet() + .stream() + .filter(e -> e.getKey().getTrace() == current.getTrace()) + .filter(e -> e.getKey().getSpan().contains(current.getSnap())) + .flatMap(e -> e.getValue().stream()) + .map(r -> r.getDestinationAddressRange()) + .collect(AddressCollectors.toAddressSet()); + ProgramSelection result = new ProgramSelection(mapped); + dynamicSelect(view, result); + return result; + } + + protected void doAutoSyncSelectionIntoStatic(Program program, ProgramSelection selection) { + if (isAutoSyncSelectionWithStaticListing()) { + doSyncSelectionIntoStatic(program, selection); + } + } + + protected void doAutoSyncSelectionFromStatic() { + if (isAutoSyncSelectionWithStaticListing()) { + doSyncSelectionFromStatic(); + } + } + + protected void displayMapError(String from, String to) { + tool.setStatusInfo("No selected addresses in " + from + " are mappable to " + to + + ". Check your module list and static mappings.", true); + } + + public void goToCoordinates(DebuggerCoordinates coordinates) { + this.current = coordinates; + } + + public void dynamicProgramLocationChanged(ProgramLocation location, EventTrigger trigger) { + currentDynamicLocation = location; + if (trigger != EventTrigger.GUI_ACTION) { + return; + } + doAutoSyncCursorIntoStatic(location); + } + + public void dynamicSelectionChanged(Program program, ProgramSelection selection, + EventTrigger trigger) { + currentDynamicSelection = selection; + provider.contextChanged(); + if (trigger != EventTrigger.GUI_ACTION) { + return; + } + doAutoSyncSelectionIntoStatic(program, selection); + } + + public void staticProgramActivated(Program program) { + currentStaticProgram = program; + } + + public void staticProgramLocationChanged(ProgramLocation location) { + currentStaticLocation = location; + doAutoSyncCursorFromStatic(); + } + + public void staticProgramSelectionChanged(Program program, ProgramSelection selection) { + if (program != currentStaticProgram) { + Msg.warn(this, "Got selection change for not the current static program"); + return; + } + currentStaticProgram = program; + currentStaticSelection = selection; + provider.contextChanged(); + doAutoSyncSelectionFromStatic(); + } + + public void setAutoSyncCursorWithStaticListing(boolean sync) { + actionAutoSyncCursorWithStaticListing.setSelected(sync); + doSetAutoSyncCursorWithStaticListing(sync); + } + + public boolean isAutoSyncCursorWithStaticListing() { + return autoSyncCursorWithStaticListing; + } + + public void setAutoSyncSelectionWithStaticListing(boolean sync) { + actionAutoSyncSelectionWithStaticListing.setSelected(sync); + doSetAutoSyncSelectionWithStaticListing(sync); + } + + public boolean isAutoSyncSelectionWithStaticListing() { + return autoSyncSelectionWithStaticListing; + } + + public void readConfigState(SaveState saveState) { + CONFIG_STATE_HANDLER.readConfigState(this, saveState); + + if (isAutoSyncAllowed) { + if (actionAutoSyncCursorWithStaticListing != null) { + actionAutoSyncCursorWithStaticListing.setSelected(autoSyncCursorWithStaticListing); + } + if (actionAutoSyncSelectionWithStaticListing != null) { + actionAutoSyncSelectionWithStaticListing + .setSelected(autoSyncSelectionWithStaticListing); + } + } + else { + autoSyncCursorWithStaticListing = false; + autoSyncSelectionWithStaticListing = false; + } + } + + protected void staticGoTo(ProgramLocation location) { + // listener method + } + + protected void staticSelect(Program program, ProgramSelection selection) { + // listener method + } + + protected void dynamicGoTo(ProgramLocation location) { + // listener method + } + + protected void dynamicSelect(Program program, ProgramSelection selection) { + // listener method + } +} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingPlugin.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingPlugin.java index 4fc878e3e8..1165bfffaf 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingPlugin.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingPlugin.java @@ -47,6 +47,7 @@ import ghidra.framework.plugintool.*; import ghidra.framework.plugintool.annotation.AutoServiceConsumed; import ghidra.framework.plugintool.util.PluginStatus; import ghidra.program.model.address.*; +import ghidra.program.model.listing.Program; import ghidra.program.util.ProgramLocation; import ghidra.program.util.ProgramSelection; import ghidra.trace.model.program.TraceProgramView; @@ -63,17 +64,18 @@ import utilities.util.SuppressableCallback.Suppression; packageName = DebuggerPluginPackage.NAME, status = PluginStatus.RELEASED, eventsConsumed = { - // ProgramSelectionPluginEvent.class, // TODO: Later or remove // ProgramHighlightPluginEvent.class, // TODO: Later or remove ProgramOpenedPluginEvent.class, // For auto-open log cleanup ProgramClosedPluginEvent.class, // For marker set cleanup + ProgramActivatedPluginEvent.class, // To track the static program for sync ProgramLocationPluginEvent.class, // For static listing sync + ProgramSelectionPluginEvent.class, // For static listing sync TraceActivatedPluginEvent.class, // Trace/thread activation and register tracking TraceClosedPluginEvent.class, }, eventsProduced = { ProgramLocationPluginEvent.class, - // ProgramSelectionPluginEvent.class, + ProgramSelectionPluginEvent.class, TraceLocationPluginEvent.class, TraceSelectionPluginEvent.class }, @@ -150,8 +152,9 @@ public class DebuggerListingPlugin extends AbstractCodeBrowserPlugin cbGoTo = new SuppressableCallback<>(); private final SuppressableCallback cbProgramLocationEvents = new SuppressableCallback<>(); + private final SuppressableCallback cbProgramSelectionEvents = + new SuppressableCallback<>(); private DebuggerCoordinates current = DebuggerCoordinates.NOWHERE; @@ -242,7 +245,7 @@ public class DebuggerListingPlugin extends AbstractCodeBrowserPlugin { + ProgramSelectionPluginEvent ev = (ProgramSelectionPluginEvent) event; + if (heedSelectionEvent(ev)) { + connectedProvider.staticProgramSelectionChanged(ev.getProgram(), + ev.getSelection()); + } + }); + } if (event instanceof ProgramOpenedPluginEvent) { ProgramOpenedPluginEvent ev = (ProgramOpenedPluginEvent) event; allProviders(p -> p.programOpened(ev.getProgram())); @@ -286,6 +302,10 @@ public class DebuggerListingPlugin extends AbstractCodeBrowserPlugin p.programClosed(ev.getProgram())); } + if (event instanceof ProgramActivatedPluginEvent) { + ProgramActivatedPluginEvent ev = (ProgramActivatedPluginEvent) event; + allProviders(p -> p.staticProgramActivated(ev.getActiveProgram())); + } if (event instanceof TraceActivatedPluginEvent) { TraceActivatedPluginEvent ev = (TraceActivatedPluginEvent) event; current = ev.getActiveCoordinates(); @@ -312,6 +332,13 @@ public class DebuggerListingPlugin extends AbstractCodeBrowserPlugin action) { action.accept(connectedProvider); for (DebuggerListingProvider provider : disconnectedProviders) { @@ -359,11 +386,9 @@ public class DebuggerListingPlugin extends AbstractCodeBrowserPlugin { DebuggerListingProvider provider = connectedProvider; - provider.doSyncToStatic(location); + provider.doAutoSyncCursorIntoStatic(location); provider.doCheckCurrentModuleMissing(); - //}); return true; } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingProvider.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingProvider.java index 265bd1042f..ebd954ac3f 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingProvider.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingProvider.java @@ -30,10 +30,9 @@ import javax.swing.event.ChangeListener; import org.apache.commons.lang3.StringUtils; import org.jdom.Element; -import docking.ActionContext; import docking.WindowPosition; import docking.action.DockingAction; -import docking.action.MenuData; +import docking.action.ToggleDockingAction; import docking.menu.MultiStateDockingAction; import docking.widgets.EventTrigger; import docking.widgets.fieldpanel.support.ViewerPosition; @@ -43,7 +42,8 @@ import ghidra.app.plugin.core.codebrowser.MarkerServiceBackgroundColorModel; import ghidra.app.plugin.core.debug.DebuggerCoordinates; import ghidra.app.plugin.core.debug.gui.DebuggerLocationLabel; import ghidra.app.plugin.core.debug.gui.DebuggerResources; -import ghidra.app.plugin.core.debug.gui.DebuggerResources.*; +import ghidra.app.plugin.core.debug.gui.DebuggerResources.FollowsCurrentThreadAction; +import ghidra.app.plugin.core.debug.gui.DebuggerResources.OpenProgramAction; import ghidra.app.plugin.core.debug.gui.action.*; import ghidra.app.plugin.core.debug.gui.modules.DebuggerMissingModuleActionContext; import ghidra.app.plugin.core.debug.utils.ProgramLocationUtils; @@ -105,36 +105,6 @@ public class DebuggerListingProvider extends CodeViewerProvider { return true; } - protected class SyncToStaticListingAction extends AbstractSyncToStaticListingAction { - public SyncToStaticListingAction() { - super(plugin); - setMenuBarData(new MenuData(new String[] { getName() })); - setSelected(true); - addLocalAction(this); - setEnabled(true); - } - - @Override - public void actionPerformed(ActionContext context) { - doSetSyncToStaticListing(isSelected()); - } - } - - protected class FollowsCurrentThreadAction extends AbstractFollowsCurrentThreadAction { - public FollowsCurrentThreadAction() { - super(plugin); - setMenuBarData(new MenuData(new String[] { NAME })); - setSelected(true); - addLocalAction(this); - setEnabled(true); - } - - @Override - public void actionPerformed(ActionContext context) { - doSetFollowsCurrentThread(isSelected()); - } - } - protected class MarkerSetChangeListener implements ChangeListener { @Override public void stateChanged(ChangeEvent e) { @@ -154,7 +124,6 @@ public class DebuggerListingProvider extends CodeViewerProvider { return; } doMarkTrackedLocation(); - doSyncToStatic(getLocation()); }); /** @@ -165,6 +134,53 @@ public class DebuggerListingProvider extends CodeViewerProvider { } } + protected class ForListingSyncTrait extends DebuggerStaticSyncTrait { + public ForListingSyncTrait() { + super(DebuggerListingProvider.this.tool, DebuggerListingProvider.this.plugin, + DebuggerListingProvider.this, isMainListing()); + } + + @Override + protected void staticGoTo(ProgramLocation location) { + Swing.runIfSwingOrRunLater(() -> plugin.fireStaticLocationEvent(location)); + } + + @Override + protected void staticSelect(Program program, ProgramSelection selection) { + Swing.runIfSwingOrRunLater(() -> plugin.fireStaticSelectionEvent(program, selection)); + if (selection.isEmpty()) { + return; + } + Optional codeViewer = + Stream.of(tool.getServices(CodeViewerService.class)) + .filter(cv -> cv != plugin) + .findFirst(); + if (codeViewer.isEmpty()) { + return; + } + Swing.runIfSwingOrRunLater( + () -> codeViewer.get() + .getListingPanel() + .scrollTo(new ProgramLocation(program, selection.getMinAddress()))); + } + + @Override + protected void dynamicGoTo(ProgramLocation location) { + Swing.runIfSwingOrRunLater(() -> goTo(location.getProgram(), location)); + } + + @Override + protected void dynamicSelect(Program program, ProgramSelection selection) { + Swing.runIfSwingOrRunLater(() -> { + setSelection(selection); + if (!selection.isEmpty()) { + getListingPanel() + .scrollTo(new ProgramLocation(program, selection.getMinAddress())); + } + }); + } + } + protected class ForListingGoToTrait extends DebuggerGoToTrait { public ForListingGoToTrait() { super(DebuggerListingProvider.this.tool, DebuggerListingProvider.this.plugin, @@ -242,20 +258,22 @@ public class DebuggerListingProvider extends CodeViewerProvider { protected MarkerSet trackingMarker; protected DockingAction actionGoTo; - protected SyncToStaticListingAction actionSyncToStaticListing; - protected FollowsCurrentThreadAction actionFollowsCurrentThread; + protected ToggleDockingAction actionAutoSyncCursorWithStaticListing; + protected ToggleDockingAction actionAutoSyncSelectionWithStaticListing; + protected DockingAction actionSyncSelectionIntoStaticListing; + protected DockingAction actionSyncSelectionFromStaticListing; + protected ToggleDockingAction actionFollowsCurrentThread; protected MultiStateDockingAction actionAutoReadMemory; protected DockingAction actionReadSelectedMemory; protected DockingAction actionOpenProgram; protected MultiStateDockingAction actionTrackLocation; - @AutoConfigStateField - protected boolean syncToStaticListing; @AutoConfigStateField protected boolean followsCurrentThread = true; // TODO: followsCurrentSnap? - protected final DebuggerGoToTrait goToTrait; + protected final ForListingSyncTrait syncTrait; + protected final ForListingGoToTrait goToTrait; protected final ForListingTrackingTrait trackingTrait; protected final ForListingReadsMemoryTrait readsMemTrait; @@ -283,6 +301,7 @@ public class DebuggerListingProvider extends CodeViewerProvider { this.plugin = plugin; this.isMainListing = isConnected; + syncTrait = new ForListingSyncTrait(); goToTrait = new ForListingGoToTrait(); trackingTrait = new ForListingTrackingTrait(); readsMemTrait = new ForListingReadsMemoryTrait(); @@ -295,7 +314,6 @@ public class DebuggerListingProvider extends CodeViewerProvider { autoServiceWiring = AutoService.wireServicesConsumed(plugin, this); autoOptionsWiring = AutoOptions.wireOptionsConsumed(plugin, this); - syncToStaticListing = isConnected; setVisible(true); createActions(); @@ -306,7 +324,6 @@ public class DebuggerListingProvider extends CodeViewerProvider { // TODO: An icon to distinguish dynamic from static - //getComponent().setBorder(BorderFactory.createEmptyBorder()); addDisplayListener(readsMemTrait.getDisplayListener()); this.setNorthComponent(locationLabel); @@ -412,15 +429,14 @@ public class DebuggerListingProvider extends CodeViewerProvider { } CONFIG_STATE_HANDLER.readConfigState(this, saveState); + syncTrait.readConfigState(saveState); trackingTrait.readConfigState(saveState); readsMemTrait.readConfigState(saveState); if (isMainListing()) { - actionSyncToStaticListing.setSelected(syncToStaticListing); followsCurrentThread = true; } else { - syncToStaticListing = false; actionFollowsCurrentThread.setSelected(followsCurrentThread); updateBorder(); } @@ -458,7 +474,6 @@ public class DebuggerListingProvider extends CodeViewerProvider { if (this.mappingService != null) { this.mappingService.addChangeListener(mappingChangeListener); doMarkTrackedLocation(); - doSyncToStatic(getLocation()); } } @@ -560,6 +575,10 @@ public class DebuggerListingProvider extends CodeViewerProvider { } } + public void staticProgramActivated(Program program) { + syncTrait.staticProgramActivated(program); + } + @Override protected void doSetProgram(Program newProgram) { if (newProgram != null && newProgram != current.getView()) { @@ -633,12 +652,25 @@ public class DebuggerListingProvider extends CodeViewerProvider { protected void createActions() { if (isMainListing()) { - actionSyncToStaticListing = new SyncToStaticListingAction(); + actionAutoSyncCursorWithStaticListing = + syncTrait.installAutoSyncCursorWithStaticListingAction(); + actionAutoSyncSelectionWithStaticListing = + syncTrait.installAutoSyncSelectionWithStaticListingAction(); } else { - actionFollowsCurrentThread = new FollowsCurrentThreadAction(); + actionFollowsCurrentThread = FollowsCurrentThreadAction.builder(plugin) + .enabled(true) + .selected(true) + .onAction( + ctx -> doSetFollowsCurrentThread(actionFollowsCurrentThread.isSelected())) + .buildAndInstallLocal(this); } + actionSyncSelectionIntoStaticListing = + syncTrait.installSyncSelectionIntoStaticListingAction(); + actionSyncSelectionFromStaticListing = + syncTrait.installSyncSelectionFromStaticListingAction(); + actionGoTo = goToTrait.installAction(); actionTrackLocation = trackingTrait.installAction(); actionAutoReadMemory = readsMemTrait.installAutoReadAction(); @@ -717,8 +749,6 @@ public class DebuggerListingProvider extends CodeViewerProvider { return false; } if (super.goTo(gotoProgram, location)) { - //doSyncToStatic(location); - //doAutoImportCurrentModule(); return true; } return false; @@ -733,19 +763,16 @@ public class DebuggerListingProvider extends CodeViewerProvider { location = ProgramLocationUtils.fixLocation(location, false); } super.programLocationChanged(location, trigger); + syncTrait.dynamicProgramLocationChanged(location, trigger); if (trigger == EventTrigger.GUI_ACTION) { - doSyncToStatic(location); doCheckCurrentModuleMissing(); } } - protected void doSyncToStatic(ProgramLocation location) { - if (isSyncToStaticListing() && location != null) { - ProgramLocation staticLoc = mappingService.getStaticLocationFromDynamic(location); - if (staticLoc != null) { - Swing.runIfSwingOrRunLater(() -> plugin.fireStaticLocationEvent(staticLoc)); - } - } + @Override + public void programSelectionChanged(ProgramSelection selection, EventTrigger trigger) { + super.programSelectionChanged(selection, trigger); + syncTrait.dynamicSelectionChanged(getProgram(), selection, trigger); } protected void doTryOpenProgram(DomainFile df, int version, int state) { @@ -799,7 +826,7 @@ public class DebuggerListingProvider extends CodeViewerProvider { protected void doCheckCurrentModuleMissing() { // Is there any reason to try to open the module if we're not syncing listings? // I don't think so. - if (!isSyncToStaticListing()) { + if (!syncTrait.isAutoSyncCursorWithStaticListing()) { return; } Trace trace = current.getTrace(); @@ -890,23 +917,20 @@ public class DebuggerListingProvider extends CodeViewerProvider { trackingSpecChangeListeners.remove(listener); } - public void setSyncToStaticListing(boolean sync) { + public void setAutoSyncCursorWithStaticListing(boolean sync) { if (!isMainListing()) { throw new IllegalStateException( "Only the main dynamic listing can be synced to the main static listing"); } - actionSyncToStaticListing.setSelected(sync); - doSetSyncToStaticListing(sync); + syncTrait.setAutoSyncCursorWithStaticListing(sync); } - protected void doSetSyncToStaticListing(boolean sync) { - this.syncToStaticListing = sync; - contextChanged(); - doSyncToStatic(getLocation()); - } - - public boolean isSyncToStaticListing() { - return syncToStaticListing; + public void setAutoSyncSelectionWithStaticListing(boolean sync) { + if (!isMainListing()) { + throw new IllegalStateException( + "Only the main dynamic listing can be synced to the main static listing"); + } + syncTrait.setAutoSyncSelectionWithStaticListing(sync); } public void setFollowsCurrentThread(boolean follows) { @@ -943,6 +967,10 @@ public class DebuggerListingProvider extends CodeViewerProvider { return readsMemTrait.getAutoSpec(); } + public void doAutoSyncCursorIntoStatic(ProgramLocation location) { + syncTrait.doAutoSyncCursorIntoStatic(location); + } + protected ProgramLocation doMarkTrackedLocation() { ProgramLocation trackedLocation = trackingTrait.getTrackedLocation(); if (trackedLocation == null) { @@ -962,7 +990,7 @@ public class DebuggerListingProvider extends CodeViewerProvider { return; } TraceProgramView curView = current.getView(); - if (!syncToStaticListing || trackedStatic == null) { + if (!syncTrait.isAutoSyncCursorWithStaticListing() || trackedStatic == null) { Swing.runIfSwingOrRunLater(() -> { goTo(curView, loc); doCheckCurrentModuleMissing(); @@ -987,18 +1015,6 @@ public class DebuggerListingProvider extends CodeViewerProvider { } } - public void staticProgramLocationChanged(ProgramLocation location) { - TraceProgramView view = current.getView(); // NB. Used for snap (don't want emuSnap) - if (!isSyncToStaticListing() || view == null || location == null) { - return; - } - ProgramLocation dyn = mappingService.getDynamicLocationFromStatic(view, location); - if (dyn == null) { - return; - } - goTo(view, dyn); - } - protected DebuggerCoordinates adjustCoordinates(DebuggerCoordinates coordinates) { if (followsCurrentThread) { return coordinates; @@ -1014,6 +1030,7 @@ public class DebuggerListingProvider extends CodeViewerProvider { } current = coordinates; doSetProgram(current.getView()); + syncTrait.goToCoordinates(coordinates); goToTrait.goToCoordinates(coordinates); trackingTrait.goToCoordinates(coordinates); readsMemTrait.goToCoordinates(coordinates); @@ -1032,6 +1049,14 @@ public class DebuggerListingProvider extends CodeViewerProvider { } } + public void staticProgramLocationChanged(ProgramLocation location) { + syncTrait.staticProgramLocationChanged(location); + } + + public void staticProgramSelectionChanged(Program program, ProgramSelection selection) { + syncTrait.staticProgramSelectionChanged(program, selection); + } + @Override public void cloneWindow() { final DebuggerListingProvider newProvider = plugin.createNewDisconnectedProvider(); diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerMemoryBytesProvider.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerMemoryBytesProvider.java index de9c6bca8a..aaa122c90f 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerMemoryBytesProvider.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerMemoryBytesProvider.java @@ -22,16 +22,15 @@ import java.util.*; import org.apache.commons.lang3.StringUtils; -import docking.ActionContext; import docking.action.DockingAction; -import docking.action.MenuData; +import docking.action.ToggleDockingAction; import docking.menu.MultiStateDockingAction; import docking.widgets.fieldpanel.support.ViewerPosition; import ghidra.app.plugin.core.byteviewer.*; import ghidra.app.plugin.core.debug.DebuggerCoordinates; import ghidra.app.plugin.core.debug.gui.DebuggerLocationLabel; import ghidra.app.plugin.core.debug.gui.DebuggerResources; -import ghidra.app.plugin.core.debug.gui.DebuggerResources.AbstractFollowsCurrentThreadAction; +import ghidra.app.plugin.core.debug.gui.DebuggerResources.FollowsCurrentThreadAction; import ghidra.app.plugin.core.debug.gui.action.*; import ghidra.app.plugin.core.debug.gui.action.AutoReadMemorySpec.AutoReadMemorySpecConfigFieldCodec; import ghidra.app.plugin.core.format.ByteBlock; @@ -76,21 +75,6 @@ public class DebuggerMemoryBytesProvider extends ProgramByteViewerComponentProvi return true; } - protected class FollowsCurrentThreadAction extends AbstractFollowsCurrentThreadAction { - public FollowsCurrentThreadAction() { - super(plugin); - setMenuBarData(new MenuData(new String[] { NAME })); - setSelected(true); - addLocalAction(this); - setEnabled(true); - } - - @Override - public void actionPerformed(ActionContext context) { - doSetFollowsCurrentThread(isSelected()); - } - } - protected class ForMemoryBytesGoToTrait extends DebuggerGoToTrait { public ForMemoryBytesGoToTrait() { super(DebuggerMemoryBytesProvider.this.tool, DebuggerMemoryBytesProvider.this.plugin, @@ -150,7 +134,7 @@ public class DebuggerMemoryBytesProvider extends ProgramByteViewerComponentProvi private final AutoService.Wiring autoServiceWiring; protected DockingAction actionGoTo; - protected FollowsCurrentThreadAction actionFollowsCurrentThread; + protected ToggleDockingAction actionFollowsCurrentThread; protected MultiStateDockingAction actionAutoReadMemory; protected DockingAction actionReadSelectedMemory; protected MultiStateDockingAction actionTrackLocation; @@ -280,7 +264,12 @@ public class DebuggerMemoryBytesProvider extends ProgramByteViewerComponentProvi initTraits(); if (!isMainViewer()) { - actionFollowsCurrentThread = new FollowsCurrentThreadAction(); + actionFollowsCurrentThread = FollowsCurrentThreadAction.builder(plugin) + .enabled(true) + .selected(true) + .onAction( + ctx -> doSetFollowsCurrentThread(actionFollowsCurrentThread.isSelected())) + .buildAndInstallLocal(this); } actionGoTo = goToTrait.installAction(); diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/watch/DebuggerWatchesProvider.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/watch/DebuggerWatchesProvider.java index b55001c87c..7c5b79cfc6 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/watch/DebuggerWatchesProvider.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/watch/DebuggerWatchesProvider.java @@ -18,9 +18,9 @@ package ghidra.app.plugin.core.debug.gui.watch; import java.awt.*; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; -import java.util.*; +import java.util.ArrayList; import java.util.List; -import java.util.Map.Entry; +import java.util.Objects; import java.util.function.BiConsumer; import java.util.function.Function; import java.util.function.Predicate; @@ -44,7 +44,6 @@ import ghidra.app.plugin.core.debug.gui.DebuggerResources.*; import ghidra.app.plugin.core.debug.gui.register.DebuggerRegisterActionContext; import ghidra.app.plugin.core.debug.gui.register.RegisterRow; import ghidra.app.services.*; -import ghidra.app.services.DebuggerStaticMappingService.MappedAddressRange; import ghidra.async.AsyncDebouncer; import ghidra.async.AsyncTimer; import ghidra.base.widgets.table.DataTypeTableCellEditor; @@ -602,21 +601,14 @@ public class DebuggerWatchesProvider extends ComponentProviderAdapter { if (set == null) { return null; } - AddressSet result = new AddressSet(); - for (Entry> ent : mappingService - .getOpenMappedViews(program, set) - .entrySet()) { - if (ent.getKey().getTrace() != current.getTrace()) { - continue; - } - if (!ent.getKey().getSpan().contains(current.getSnap())) { - continue; - } - for (MappedAddressRange rng : ent.getValue()) { - result.add(rng.getDestinationAddressRange()); - } - } - return result; + return mappingService.getOpenMappedViews(program, set) + .entrySet() + .stream() + .filter(e -> e.getKey().getTrace() == current.getTrace()) + .filter(e -> e.getKey().getSpan().contains(current.getSnap())) + .flatMap(e -> e.getValue().stream()) + .map(r -> r.getDestinationAddressRange()) + .collect(AddressCollectors.toAddressSet()); } private boolean hasDynamicLocation(ProgramLocationActionContext context) { diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/record/MemoryRecorder.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/record/MemoryRecorder.java index aa13d511c6..94064fc390 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/record/MemoryRecorder.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/record/MemoryRecorder.java @@ -18,8 +18,6 @@ package ghidra.app.plugin.core.debug.service.model.record; import java.nio.ByteBuffer; import java.util.*; import java.util.concurrent.CompletableFuture; -import java.util.function.*; -import java.util.stream.Collector; import java.util.stream.Collectors; import ghidra.dbg.error.DebuggerMemoryAccessException; @@ -137,43 +135,11 @@ class MemoryRecorder { return true; } - protected Collector toAddressSet() { - return new Collector<>() { - @Override - public Supplier supplier() { - return AddressSet::new; - } - - @Override - public BiConsumer accumulator() { - return AddressSet::add; - } - - @Override - public BinaryOperator combiner() { - return (s1, s2) -> { - s1.add(s2); - return s1; - }; - } - - @Override - public Function finisher() { - return Function.identity(); - } - - @Override - public Set characteristics() { - return Set.of(); - } - }; - } - public AddressSetView getAccessible() { synchronized (regions) { return regions.values() .stream() - .collect(toAddressSet()); + .collect(AddressCollectors.toAddressSet()); } } } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/modules/DebuggerStaticMappingServicePlugin.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/modules/DebuggerStaticMappingServicePlugin.java index c3c66b0388..782b929f43 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/modules/DebuggerStaticMappingServicePlugin.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/modules/DebuggerStaticMappingServicePlugin.java @@ -927,7 +927,7 @@ public class DebuggerStaticMappingServicePlugin extends Plugin synchronized (lock) { InfoPerProgram info = requireTrackedInfo(program); if (info == null) { - return null; + return Map.of(); } return info.getOpenMappedViews(set); } diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingProviderTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingProviderTest.java index 0966dc3f88..8d2c90b763 100644 --- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingProviderTest.java +++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingProviderTest.java @@ -15,7 +15,6 @@ */ package ghidra.app.plugin.core.debug.gui.listing; -import static ghidra.lifecycle.Unfinished.TODO; import static org.junit.Assert.*; import java.awt.Color; @@ -34,10 +33,12 @@ import docking.menu.ActionState; import docking.menu.MultiStateDockingAction; import docking.widgets.EventTrigger; import generic.test.category.NightlyCategory; +import ghidra.app.plugin.core.codebrowser.CodeBrowserPlugin; +import ghidra.app.plugin.core.codebrowser.CodeViewerProvider; import ghidra.app.plugin.core.debug.DebuggerCoordinates; import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest; import ghidra.app.plugin.core.debug.gui.DebuggerResources; -import ghidra.app.plugin.core.debug.gui.DebuggerResources.AbstractFollowsCurrentThreadAction; +import ghidra.app.plugin.core.debug.gui.DebuggerResources.FollowsCurrentThreadAction; import ghidra.app.plugin.core.debug.gui.action.DebuggerGoToDialog; import ghidra.app.plugin.core.debug.gui.console.DebuggerConsolePlugin; import ghidra.app.plugin.core.debug.gui.console.DebuggerConsoleProvider.BoundAction; @@ -47,6 +48,7 @@ import ghidra.app.plugin.core.debug.service.modules.DebuggerStaticMappingUtils; import ghidra.app.services.*; import ghidra.async.SwingExecutorService; import ghidra.framework.model.*; +import ghidra.lifecycle.Unfinished; import ghidra.plugin.importer.ImporterPlugin; import ghidra.program.model.address.*; import ghidra.program.model.lang.Register; @@ -74,15 +76,19 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI protected DebuggerListingProvider listingProvider; protected DebuggerStaticMappingService mappingService; - protected CodeViewerService codeViewer; + protected CodeBrowserPlugin codePlugin; + protected CodeViewerProvider codeProvider; @Before public void setUpListingProviderTest() throws Exception { + // Do before listingPlugin, since types collide + codePlugin = addPlugin(tool, CodeBrowserPlugin.class); + codeProvider = waitForComponentProvider(CodeViewerProvider.class); + listingPlugin = addPlugin(tool, DebuggerListingPlugin.class); listingProvider = waitForComponentProvider(DebuggerListingProvider.class); mappingService = tool.getService(DebuggerStaticMappingService.class); - codeViewer = tool.getService(CodeViewerService.class); } protected void goToDyn(Address address) { @@ -101,6 +107,33 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI return data; } + protected void createMappedTraceAndProgram() throws Exception { + createAndOpenTrace(); + createAndOpenProgramFromTrace(); + intoProject(tb.trace); + intoProject(program); + + AddressSpace ss = program.getAddressFactory().getDefaultAddressSpace(); + try (UndoableTransaction tid = UndoableTransaction.start(program, "Add block", true)) { + program.getMemory() + .createInitializedBlock(".text", ss.getAddress(0x00600000), 0x10000, (byte) 0, + monitor, false); + } + try (UndoableTransaction tid = tb.startTransaction()) { + DBTraceMemoryManager memory = tb.trace.getMemoryManager(); + memory.addRegion("exe:.text", Range.atLeast(0L), tb.range(0x00400000, 0x0040ffff), + TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE); + TraceLocation from = + new DefaultTraceLocation(tb.trace, null, Range.atLeast(0L), tb.addr(0x00400000)); + ProgramLocation to = new ProgramLocation(program, ss.getAddress(0x00600000)); + DebuggerStaticMappingUtils.addMapping(from, to, 0x8000, false); + } + waitForProgram(program); + waitForDomainObject(tb.trace); + traceManager.activateTrace(tb.trace); + waitForSwing(); + } + @Test public void testListingViewIsRegionsActivateThenAdd() throws Exception { createAndOpenTrace(); @@ -418,31 +451,10 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI } @Test - public void testSyncToStaticListingStaticToDynamicOnGoto() throws Exception { - createAndOpenTrace(); - createAndOpenProgramFromTrace(); - intoProject(tb.trace); - intoProject(program); - + public void testSyncCursorToStaticListingStaticToDynamicOnGoto() throws Exception { + createMappedTraceAndProgram(); AddressSpace ss = program.getAddressFactory().getDefaultAddressSpace(); - try (UndoableTransaction tid = UndoableTransaction.start(program, "Add block", true)) { - program.getMemory() - .createInitializedBlock(".text", ss.getAddress(0x00600000), 0x10000, (byte) 0, - monitor, false); - } - try (UndoableTransaction tid = tb.startTransaction()) { - DBTraceMemoryManager memory = tb.trace.getMemoryManager(); - memory.addRegion("exe:.text", Range.atLeast(0L), tb.range(0x00400000, 0x0040ffff), - TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE); - TraceLocation from = - new DefaultTraceLocation(tb.trace, null, Range.atLeast(0L), tb.addr(0x00400000)); - ProgramLocation to = new ProgramLocation(program, ss.getAddress(0x00600000)); - DebuggerStaticMappingUtils.addMapping(from, to, 0x8000, false); - } - waitForProgram(program); - waitForDomainObject(tb.trace); - traceManager.activateTrace(tb.trace); - waitForSwing(); + ProgramLocation loc; goTo(tool, program, ss.getAddress(0x00601234)); @@ -468,7 +480,7 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI } @Test - public void testSyncToStaticListingDynamicToStaticOnSnapChange() throws Exception { + public void testSyncCursorToStaticListingDynamicToStaticOnSnapChange() throws Exception { createAndOpenTrace(); createAndOpenProgramFromTrace(); intoProject(tb.trace); @@ -503,37 +515,15 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI traceManager.activateSnap(1); waitForSwing(); - ProgramLocation loc = codeViewer.getCurrentLocation(); + ProgramLocation loc = codePlugin.getCurrentLocation(); assertEquals(program, loc.getProgram()); assertEquals(ss.getAddress(0x00601234), loc.getAddress()); } @Test - public void testSyncToStaticListingDynamicToStaticOnLocationChange() throws Exception { - createAndOpenTrace(); - createAndOpenProgramFromTrace(); - intoProject(tb.trace); - intoProject(program); - + public void testSyncCursorToStaticListingDynamicToStaticOnLocationChange() throws Exception { + createMappedTraceAndProgram(); AddressSpace ss = program.getAddressFactory().getDefaultAddressSpace(); - try (UndoableTransaction tid = UndoableTransaction.start(program, "Add block", true)) { - program.getMemory() - .createInitializedBlock(".text", ss.getAddress(0x00600000), 0x10000, (byte) 0, - monitor, false); - } - try (UndoableTransaction tid = tb.startTransaction()) { - DBTraceMemoryManager memory = tb.trace.getMemoryManager(); - memory.addRegion("exe:.text", Range.atLeast(0L), tb.range(0x00400000, 0x0040ffff), - TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE); - TraceLocation from = - new DefaultTraceLocation(tb.trace, null, Range.atLeast(0L), tb.addr(0x00400000)); - ProgramLocation to = new ProgramLocation(program, ss.getAddress(0x00600000)); - DebuggerStaticMappingUtils.addMapping(from, to, 0x8000, false); - } - waitForProgram(program); - waitForDomainObject(tb.trace); - traceManager.activateTrace(tb.trace); - waitForSwing(); listingProvider.getListingPanel() .setCursorPosition( @@ -541,11 +531,41 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI EventTrigger.GUI_ACTION); waitForSwing(); - ProgramLocation loc = codeViewer.getCurrentLocation(); + ProgramLocation loc = codePlugin.getCurrentLocation(); assertEquals(program, loc.getProgram()); assertEquals(ss.getAddress(0x00601234), loc.getAddress()); } + @Test + public void testSyncSelectionToStaticListingDynamicToStaticOnSelectionChange() + throws Exception { + createMappedTraceAndProgram(); + AddressSpace ss = program.getAddressFactory().getDefaultAddressSpace(); + + listingProvider.getListingPanel() + .setSelection(new ProgramSelection(tb.addr(0x00401234), tb.addr(0x00404321)), + EventTrigger.GUI_ACTION); + waitForSwing(); + + assertEquals(tb.set(tb.range(ss, 0x00601234, 0x00604321)), + codePlugin.getCurrentSelection()); + } + + @Test + public void testSyncSelectionToStaticListingStaticToDynamicOnSelectionChange() + throws Exception { + createMappedTraceAndProgram(); + AddressSpace ss = program.getAddressFactory().getDefaultAddressSpace(); + + codePlugin.getListingPanel() + .setSelection( + new ProgramSelection(tb.addr(ss, 0x00601234), tb.addr(ss, 0x00604321)), + EventTrigger.GUI_ACTION); + waitForSwing(); + + assertEquals(tb.set(tb.range(0x00401234, 0x00404321)), listingPlugin.getCurrentSelection()); + } + @Test public void testDynamicListingMarksTrackedRegister() throws Exception { createAndOpenTrace(); @@ -573,7 +593,7 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI } @Test - public void testSyncToStaticListingMarksMappedTrackedRegister() throws Exception { + public void testSyncCursorToStaticListingMarksMappedTrackedRegister() throws Exception { createAndOpenTrace(); createAndOpenProgramFromTrace(); intoProject(tb.trace); @@ -607,18 +627,18 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI waitForSwing(); assertListingBackgroundAt(DebuggerResources.DEFAULT_COLOR_REGISTER_MARKERS, - codeViewer.getListingPanel(), ss.getAddress(0x00601234), 0); + codePlugin.getListingPanel(), ss.getAddress(0x00601234), 0); // For verifying static view didn't move - Address cur = codeViewer.getCurrentLocation().getAddress(); + Address cur = codePlugin.getCurrentLocation().getAddress(); // Verify mark disappears when register value moves outside the mapped address range traceManager.activateSnap(1); waitForSwing(); // While we're here, ensure static view didn't track anywhere - assertEquals(cur, codeViewer.getCurrentLocation().getAddress()); - assertListingBackgroundAt(Color.WHITE, codeViewer.getListingPanel(), + assertEquals(cur, codePlugin.getCurrentLocation().getAddress()); + assertListingBackgroundAt(Color.WHITE, codePlugin.getListingPanel(), ss.getAddress(0x00601234), 0); } @@ -832,56 +852,100 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI @Ignore("Haven't specified this action, yet") public void testActionTrackOtherRegister() { // TODO: Actually, can we make this an arbitrary (pcode/sleigh?) expression. - TODO(); + Unfinished.TODO(); } @Test - public void testActionSyncToStaticListing() throws Exception { - assertTrue(listingProvider.actionSyncToStaticListing.isEnabled()); - createAndOpenTrace(); - createAndOpenProgramFromTrace(); - intoProject(tb.trace); - intoProject(program); + public void testActionSyncCursorToStaticListing() throws Exception { + assertTrue(listingProvider.actionAutoSyncCursorWithStaticListing.isEnabled()); + createMappedTraceAndProgram(); AddressSpace ss = program.getAddressFactory().getDefaultAddressSpace(); - try (UndoableTransaction tid = UndoableTransaction.start(program, "Add block", true)) { - program.getMemory() - .createInitializedBlock(".text", ss.getAddress(0x00600000), 0x10000, (byte) 0, - monitor, false); - } - try (UndoableTransaction tid = tb.startTransaction()) { - DBTraceMemoryManager memory = tb.trace.getMemoryManager(); - memory.addRegion("exe:.text", Range.atLeast(0L), tb.range(0x00400000, 0x0040ffff), - TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE); - TraceLocation from = - new DefaultTraceLocation(tb.trace, null, Range.atLeast(0L), tb.addr(0x00400000)); - ProgramLocation to = new ProgramLocation(program, ss.getAddress(0x00600000)); - DebuggerStaticMappingUtils.addMapping(from, to, 0x8000, false); - } - waitForProgram(program); - waitForDomainObject(tb.trace); - traceManager.activateTrace(tb.trace); - waitForSwing(); // Check default is on - assertTrue(listingProvider.actionSyncToStaticListing.isSelected()); + assertTrue(listingProvider.actionAutoSyncCursorWithStaticListing.isSelected()); goTo(tool, program, ss.getAddress(0x00601234)); waitForSwing(); assertEquals(tb.addr(0x00401234), listingProvider.getLocation().getAddress()); - performAction(listingProvider.actionSyncToStaticListing); - assertFalse(listingProvider.actionSyncToStaticListing.isSelected()); + performAction(listingProvider.actionAutoSyncCursorWithStaticListing); + assertFalse(listingProvider.actionAutoSyncCursorWithStaticListing.isSelected()); goTo(tool, program, ss.getAddress(0x00608765)); waitForSwing(); // Verify the goTo was effective, but no change to dynamic listing location - assertEquals(ss.getAddress(0x00608765), codeViewer.getCurrentLocation().getAddress()); + assertEquals(ss.getAddress(0x00608765), codePlugin.getCurrentLocation().getAddress()); assertEquals(tb.addr(0x00401234), listingProvider.getLocation().getAddress()); - listingProvider.setSyncToStaticListing(true); + listingProvider.setAutoSyncCursorWithStaticListing(true); // NOTE: Toggling adjusts the static listing, not the dynamic waitForSwing(); - assertTrue(listingProvider.actionSyncToStaticListing.isSelected()); - assertEquals(ss.getAddress(0x00601234), codeViewer.getCurrentLocation().getAddress()); + assertTrue(listingProvider.actionAutoSyncCursorWithStaticListing.isSelected()); + assertEquals(ss.getAddress(0x00601234), codePlugin.getCurrentLocation().getAddress()); + } + + @Test + public void testActionSyncSelectionToStaticListing() throws Exception { + assertTrue(listingProvider.actionAutoSyncCursorWithStaticListing.isEnabled()); + + createMappedTraceAndProgram(); + AddressSpace ss = program.getAddressFactory().getDefaultAddressSpace(); + + // Check default is on + assertTrue(listingProvider.actionAutoSyncSelectionWithStaticListing.isSelected()); + makeSelection(tool, program, tb.range(ss, 0x00601234, 0x00604321)); + goTo(tool, program, ss.getAddress(0x00601234)); + waitForSwing(); + assertEquals(tb.set(tb.range(0x00401234, 0x00404321)), listingPlugin.getCurrentSelection()); + + performAction(listingProvider.actionAutoSyncSelectionWithStaticListing); + assertFalse(listingProvider.actionAutoSyncSelectionWithStaticListing.isSelected()); + goTo(tool, program, ss.getAddress(0x00608765)); + makeSelection(tool, program, tb.range(ss, 0x00605678, 0x00608765)); + waitForSwing(); + // Verify the makeSelection was effective, but no change to dynamic listing location + assertEquals(tb.set(tb.range(ss, 0x00605678, 0x00608765)), + codePlugin.getCurrentSelection()); + assertEquals(tb.set(tb.range(0x00401234, 0x00404321)), listingPlugin.getCurrentSelection()); + + listingProvider.setAutoSyncSelectionWithStaticListing(true); + // NOTE: Toggling adjusts the static listing, not the dynamic + waitForSwing(); + assertTrue(listingProvider.actionAutoSyncSelectionWithStaticListing.isSelected()); + assertEquals(tb.set(tb.range(ss, 0x00601234, 0x00604321)), + codePlugin.getCurrentSelection()); + assertEquals(tb.set(tb.range(0x00401234, 0x00404321)), listingPlugin.getCurrentSelection()); + } + + @Test + public void testActionMapAddressesToStatic() throws Exception { + listingProvider.setAutoSyncSelectionWithStaticListing(false); + createMappedTraceAndProgram(); + AddressSpace ss = program.getAddressFactory().getDefaultAddressSpace(); + + listingProvider.getListingPanel() + .setSelection(new ProgramSelection(tb.set(tb.range(0x00401234, 0x00404321))), + EventTrigger.GUI_ACTION); + assertTrue(codePlugin.getCurrentSelection().isEmpty()); + + performAction(listingProvider.actionSyncSelectionIntoStaticListing, + listingProvider.getActionContext(null), true); + assertEquals(tb.set(tb.range(ss, 0x00601234, 0x00604321)), + codePlugin.getCurrentSelection()); + } + + @Test + public void testActionMapAddressesToDynamic() throws Exception { + listingProvider.setAutoSyncSelectionWithStaticListing(false); + createMappedTraceAndProgram(); + AddressSpace ss = program.getAddressFactory().getDefaultAddressSpace(); + + makeSelection(tool, program, tb.set(tb.range(ss, 0x00601234, 0x00604321))); + assertTrue(listingPlugin.getCurrentSelection().isEmpty()); + + performAction(listingProvider.actionSyncSelectionFromStaticListing, + codeProvider.getActionContext(null), true); + assertEquals(tb.set(tb.range(0x00401234, 0x00404321)), + listingPlugin.getCurrentSelection()); } @Test @@ -915,8 +979,8 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI // Verify it has immediately tracked on creation assertEquals(tb.trace.getProgramView(), extraProvider.getLocation().getProgram()); assertEquals(thread1, extraProvider.current.getThread()); - assertNull(getLocalAction(listingProvider, AbstractFollowsCurrentThreadAction.NAME)); - assertNotNull(getLocalAction(extraProvider, AbstractFollowsCurrentThreadAction.NAME)); + assertNull(getLocalAction(listingProvider, FollowsCurrentThreadAction.NAME)); + assertNotNull(getLocalAction(extraProvider, FollowsCurrentThreadAction.NAME)); performAction(extraProvider.actionFollowsCurrentThread); traceManager.activateThread(thread2); @@ -1064,17 +1128,18 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI TraceModule bin = tb.trace.getModuleManager() .addLoadedModule("/bin/bash", "/bin/bash", tb.range(0x00400000, 0x0041ffff), 0); bin.addSection("bash[.text]", tb.range(0x00400000, 0x0040ffff)); - - traceManager.activateTrace(tb.trace); } + waitForDomainObject(tb.trace); + traceManager.activateTrace(tb.trace); + waitForSwing(); // In the module, but not in its section - listingPlugin.goTo(tb.addr(0x00411234), true); + assertTrue(listingPlugin.goTo(tb.addr(0x00411234), true)); waitForSwing(); waitForPass(() -> assertEquals(0, consolePlugin.getRowCount(DebuggerMissingModuleActionContext.class))); - listingPlugin.goTo(tb.addr(0x00401234), true); + assertTrue(listingPlugin.goTo(tb.addr(0x00401234), true)); waitForSwing(); waitForPass(() -> assertEquals(1, consolePlugin.getRowCount(DebuggerMissingModuleActionContext.class))); @@ -1093,12 +1158,13 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI tb.trace.getModuleManager() .addLoadedModule("/bin/bash", "/bin/bash", tb.range(0x00400000, 0x0041ffff), 0); - - traceManager.activateTrace(tb.trace); } + waitForDomainObject(tb.trace); + traceManager.activateTrace(tb.trace); + waitForSwing(); // In the module, but not in its section - listingPlugin.goTo(tb.addr(0x00411234), true); + assertTrue(listingPlugin.goTo(tb.addr(0x00411234), true)); waitForSwing(); waitForPass(() -> assertEquals(1, consolePlugin.getRowCount(DebuggerMissingModuleActionContext.class))); @@ -1364,7 +1430,7 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI } @Test - public void testSyncToStaticListingOpensModule() throws Exception { + public void testSyncCursorToStaticListingOpensModule() throws Exception { DebuggerConsolePlugin consolePlugin = addPlugin(tool, DebuggerConsolePlugin.class); createAndOpenTrace(); @@ -1410,7 +1476,7 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI } @Test - public void testSyncToStaticLogsRecoverableProgram() throws Exception { + public void testSyncCursorToStaticLogsRecoverableProgram() throws Exception { DebuggerConsolePlugin consolePlugin = addPlugin(tool, DebuggerConsolePlugin.class); TestDummyDomainFolder root = new TestDummyDomainFolder(null, "root"); @@ -1431,7 +1497,7 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI } @Test - public void testSyncToStaticLogsUpgradeableProgram() throws Exception { + public void testSyncCursorToStaticLogsUpgradeableProgram() throws Exception { DebuggerConsolePlugin consolePlugin = addPlugin(tool, DebuggerConsolePlugin.class); TestDummyDomainFolder root = new TestDummyDomainFolder(null, "root"); diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerMemoryBytesProviderTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerMemoryBytesProviderTest.java index b0708d7795..e7b7d71b4a 100644 --- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerMemoryBytesProviderTest.java +++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerMemoryBytesProviderTest.java @@ -47,7 +47,7 @@ import ghidra.app.plugin.core.clipboard.ClipboardPlugin; import ghidra.app.plugin.core.debug.DebuggerCoordinates; import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest; import ghidra.app.plugin.core.debug.gui.DebuggerResources; -import ghidra.app.plugin.core.debug.gui.DebuggerResources.AbstractFollowsCurrentThreadAction; +import ghidra.app.plugin.core.debug.gui.DebuggerResources.FollowsCurrentThreadAction; import ghidra.app.plugin.core.debug.gui.action.DebuggerGoToDialog; import ghidra.app.plugin.core.debug.gui.listing.DebuggerListingPlugin; import ghidra.app.plugin.core.debug.service.editing.DebuggerStateEditingServicePlugin; @@ -725,8 +725,8 @@ public class DebuggerMemoryBytesProviderTest extends AbstractGhidraHeadedDebugge // Verify it has immediately tracked on creation assertEquals(tb.trace.getProgramView(), extraProvider.getProgram()); assertEquals(thread1, extraProvider.current.getThread()); - assertNull(getLocalAction(memBytesProvider, AbstractFollowsCurrentThreadAction.NAME)); - assertNotNull(getLocalAction(extraProvider, AbstractFollowsCurrentThreadAction.NAME)); + assertNull(getLocalAction(memBytesProvider, FollowsCurrentThreadAction.NAME)); + assertNotNull(getLocalAction(extraProvider, FollowsCurrentThreadAction.NAME)); performAction(extraProvider.actionFollowsCurrentThread); traceManager.activateThread(thread2); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/codebrowser/CodeViewerProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/codebrowser/CodeViewerProvider.java index 2f0ad80960..e506d03b6d 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/codebrowser/CodeViewerProvider.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/codebrowser/CodeViewerProvider.java @@ -4,9 +4,9 @@ * 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. @@ -527,7 +527,10 @@ public class CodeViewerProvider extends NavigatableComponentProviderAdapter } @Override - public void programSelectionChanged(ProgramSelection selection) { + public void programSelectionChanged(ProgramSelection selection, EventTrigger trigger) { + if (trigger != EventTrigger.GUI_ACTION) { + return; + } doSetSelection(selection); } @@ -757,7 +760,7 @@ public class CodeViewerProvider extends NavigatableComponentProviderAdapter public void clearPanel() { if (otherPanel != null) { removeHoverServices(otherPanel); - programSelectionChanged(new ProgramSelection()); + programSelectionChanged(new ProgramSelection(), EventTrigger.GUI_ACTION); FieldPanel fp = listingPanel.getFieldPanel(); FieldLocation loc = fp.getCursorLocation(); ViewerPosition vp = fp.getViewerPosition(); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/ListingPanel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/ListingPanel.java index d06514f0cb..fe7b5024ae 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/ListingPanel.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/ListingPanel.java @@ -1046,8 +1046,18 @@ public class ListingPanel extends JPanel implements FieldMouseListener, FieldLoc * @param sel the new selection */ public void setSelection(ProgramSelection sel) { + setSelection(sel, EventTrigger.API_CALL); + } + + /** + * Sets the selection. + * + * @param sel the new selection + * @param trigger the cause of the change + */ + public void setSelection(ProgramSelection sel, EventTrigger trigger) { if (sel == null) { - fieldPanel.setSelection(layoutModel.getFieldSelection(null)); + fieldPanel.setSelection(layoutModel.getFieldSelection(null), trigger); return; } @@ -1074,12 +1084,12 @@ public class ListingPanel extends JPanel implements FieldMouseListener, FieldLoc } fieldSel.addRange(new FieldLocation(loc1.getIndex(), fieldNum1, 0, 0), new FieldLocation(index2, fieldNum2, 0, 0)); - fieldPanel.setSelection(fieldSel); + fieldPanel.setSelection(fieldSel, trigger); return; } } } - fieldPanel.setSelection(layoutModel.getFieldSelection(sel)); + fieldPanel.setSelection(layoutModel.getFieldSelection(sel), trigger); } /** @@ -1113,14 +1123,12 @@ public class ListingPanel extends JPanel implements FieldMouseListener, FieldLoc return; } - if (trigger != EventTrigger.API_CALL) { - if (listingModel.getProgram() == null || programSelectionListener == null) { - return; - } - ProgramSelection ps = layoutModel.getProgramSelection(selection); - if (ps != null) { - programSelectionListener.programSelectionChanged(ps); - } + if (listingModel.getProgram() == null || programSelectionListener == null) { + return; + } + ProgramSelection ps = layoutModel.getProgramSelection(selection); + if (ps != null) { + programSelectionListener.programSelectionChanged(ps, trigger); } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/ProgramLocationListener.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/ProgramLocationListener.java index 5df94710e7..e04b06d9af 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/ProgramLocationListener.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/ProgramLocationListener.java @@ -1,6 +1,5 @@ /* ### * IP: GHIDRA - * REVIEWED: YES * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,13 +19,15 @@ import docking.widgets.EventTrigger; import ghidra.program.util.ProgramLocation; /** - * Listener interface for when the program location changes. + * Listener that is notified when the program location changes. */ public interface ProgramLocationListener { + /** * Called whenever the program location changes. + * * @param loc the new program location. - * @param trigger TODO + * @param trigger the cause of the change */ void programLocationChanged(ProgramLocation loc, EventTrigger trigger); } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/ProgramSelectionListener.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/ProgramSelectionListener.java index 68bdc24ec0..d0fde2f111 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/ProgramSelectionListener.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/ProgramSelectionListener.java @@ -1,6 +1,5 @@ /* ### * IP: GHIDRA - * REVIEWED: YES * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,19 +15,19 @@ */ package ghidra.app.util.viewer.listingpanel; +import docking.widgets.EventTrigger; import ghidra.program.util.ProgramSelection; /** - * * Listener that is notified when the program selection changes - * - * */ public interface ProgramSelectionListener { /** - * Called whenever the program slection changes. + * Called whenever the program selection changes. + * * @param selection the new program selection. + * @param trigger the cause of the change */ - public void programSelectionChanged(ProgramSelection selection); + public void programSelectionChanged(ProgramSelection selection, EventTrigger trigger); } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/test/AbstractGhidraHeadlessIntegrationTest.java b/Ghidra/Features/Base/src/main/java/ghidra/test/AbstractGhidraHeadlessIntegrationTest.java index 422e1cf128..2934968a55 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/test/AbstractGhidraHeadlessIntegrationTest.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/test/AbstractGhidraHeadlessIntegrationTest.java @@ -15,7 +15,7 @@ */ package ghidra.test; -import static org.junit.Assert.*; +import static org.junit.Assert.assertNotNull; import java.io.File; import java.io.IOException; @@ -112,9 +112,10 @@ public abstract class AbstractGhidraHeadlessIntegrationTest extends AbstractDock } /** - * Get the language and compiler spec associated with an old language name string. - * If the language no longer exists, and suitable replacement language will be returned - * if found. If no language is found, an exception will be thrown. + * Get the language and compiler spec associated with an old language name string. If the + * language no longer exists, and suitable replacement language will be returned if found. If no + * language is found, an exception will be thrown. + * * @param oldLanguageName old language name string * @return the language compiler and spec * @throws LanguageNotFoundException if the language is not found @@ -137,6 +138,7 @@ public abstract class AbstractGhidraHeadlessIntegrationTest extends AbstractDock /** * Creates an in-memory program with the given language + * * @param name the program name * @param languageString a language string of the format x86:LE:32:default * @param consumer a consumer for the program @@ -155,6 +157,7 @@ public abstract class AbstractGhidraHeadlessIntegrationTest extends AbstractDock /** * Creates an in-memory program with the given language + * * @param name the program name * @param languageString a language string of the format x86:LE:32:default * @param compilerSpecID the ID @@ -173,8 +176,8 @@ public abstract class AbstractGhidraHeadlessIntegrationTest extends AbstractDock } /** - * Run a command against the specified program within a transaction. - * The transaction will be committed unless the command throws a RollbackException. + * Run a command against the specified program within a transaction. The transaction will be + * committed unless the command throws a RollbackException. * * @param program the program * @param cmd the command to apply @@ -233,6 +236,7 @@ public abstract class AbstractGhidraHeadlessIntegrationTest extends AbstractDock /** * Provides a convenient method for modifying the current program, handling the transaction * logic and returning a result. + * * @param the return type * @param the exception type * @param p the program @@ -262,7 +266,7 @@ public abstract class AbstractGhidraHeadlessIntegrationTest extends AbstractDock /** * Provides a convenient method for modifying the current program, handling the transaction - * logic. This method is calls {@link #tx(Program, ExceptionalCallback)}, but helps with + * logic. This method is calls {@link #tx(Program, ExceptionalCallback)}, but helps with * semantics. * * @param p the program @@ -304,9 +308,10 @@ public abstract class AbstractGhidraHeadlessIntegrationTest extends AbstractDock /** * Undo the last transaction on the domain object and wait for all events to be flushed. + * * @param dobj The domain object upon which to perform the undo. - * @param wait if true, wait for undo to fully complete in Swing thread. - * If a modal dialog may result from this undo, wait should be set false. + * @param wait if true, wait for undo to fully complete in Swing thread. If a modal dialog may + * result from this undo, wait should be set false. */ public static void undo(UndoableDomainObject dobj, boolean wait) { Runnable r = () -> { @@ -326,11 +331,11 @@ public abstract class AbstractGhidraHeadlessIntegrationTest extends AbstractDock } /** - * Redo the last undone transaction on the domain object and wait for all - * events to be flushed. + * Redo the last undone transaction on the domain object and wait for all events to be flushed. + * * @param dobj The domain object upon which to perform the redo. - * @param wait if true, wait for redo to fully complete in Swing thread. - * If a modal dialog may result from this redo, wait should be set false. + * @param wait if true, wait for redo to fully complete in Swing thread. If a modal dialog may + * result from this redo, wait should be set false. */ public static void redo(UndoableDomainObject dobj, boolean wait) { Runnable r = () -> { @@ -350,8 +355,8 @@ public abstract class AbstractGhidraHeadlessIntegrationTest extends AbstractDock } /** - * Undo the last transaction on the domain object and wait for all - * events to be flushed. + * Undo the last transaction on the domain object and wait for all events to be flushed. + * * @param dobj The domain object upon which to perform the undo. */ public static void undo(final UndoableDomainObject dobj) { @@ -359,8 +364,8 @@ public abstract class AbstractGhidraHeadlessIntegrationTest extends AbstractDock } /** - * Redo the last undone transaction on domain object and wait for all - * events to be flushed. + * Redo the last undone transaction on domain object and wait for all events to be flushed. + * * @param dobj The domain object upon which to perform the redo. */ public static void redo(final UndoableDomainObject dobj) { @@ -368,8 +373,9 @@ public abstract class AbstractGhidraHeadlessIntegrationTest extends AbstractDock } /** - * Undo the last 'count' transactions on the domain object and wait for all - * events to be flushed. + * Undo the last 'count' transactions on the domain object and wait for all events to be + * flushed. + * * @param dobj The domain object upon which to perform the undo. * @param count number of transactions to undo */ @@ -380,8 +386,9 @@ public abstract class AbstractGhidraHeadlessIntegrationTest extends AbstractDock } /** - * Redo the last 'count' undone transactions on the domain object and wait for all - * events to be flushed. + * Redo the last 'count' undone transactions on the domain object and wait for all events to be + * flushed. + * * @param dobj The domain object upon which to perform the redo. * @param count number of transactions to redo */ @@ -458,11 +465,6 @@ public abstract class AbstractGhidraHeadlessIntegrationTest extends AbstractDock waitForSwing(); } - public void makeSelection(PluginTool tool, Program p, Address... addrs) { - AddressSet set = toAddressSet(Arrays.asList(addrs)); - makeSelection(tool, p, set); - } - public void makeSelection(PluginTool tool, Program p, AddressRange... ranges) { AddressSet set = toAddressSet(ranges); makeSelection(tool, p, set); @@ -475,12 +477,12 @@ public abstract class AbstractGhidraHeadlessIntegrationTest extends AbstractDock } /** - * Returns the global symbol with the given name if and only if it is the only - * global symbol with that name. + * Returns the global symbol with the given name if and only if it is the only global symbol + * with that name. * * @param program the program to search. * @param name the name of the global symbol to find. - * @return the global symbol with the given name if and only if it is the only one. + * @return the global symbol with the given name if and only if it is the only one. */ public Symbol getUniqueSymbol(Program program, String name) { return getUniqueSymbol(program, name, null); @@ -493,7 +495,7 @@ public abstract class AbstractGhidraHeadlessIntegrationTest extends AbstractDock * @param program the program to search. * @param name the name of the symbol to find. * @param namespace the parent namespace; may be null - * @return the symbol with the given name if and only if it is the only one in that namespace + * @return the symbol with the given name if and only if it is the only one in that namespace */ public Symbol getUniqueSymbol(Program program, String name, Namespace namespace) { List symbols = program.getSymbolTable().getSymbols(name, namespace); @@ -504,13 +506,15 @@ public abstract class AbstractGhidraHeadlessIntegrationTest extends AbstractDock } /** - * A convenience method that allows you to open the given program in a default tool, - * navigating to the given address. + * A convenience method that allows you to open the given program in a default tool, navigating + * to the given address. * - *

Note: this is a blocking operation. Your test will not proceed while this method is - * sleeping. + *

+ * Note: this is a blocking operation. Your test will not proceed while this method is sleeping. * - *

Do not leave this call in your test when committing changes. + *

+ * Do not leave this call in your test when committing changes. + * * @param p the program * @param address the address * @@ -610,6 +614,7 @@ public abstract class AbstractGhidraHeadlessIntegrationTest extends AbstractDock /** * Get language service used for testing. + * * @return language service. */ public synchronized static LanguageService getLanguageService() { diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/codebrowser/CodeBrowserScreenMovementTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/codebrowser/CodeBrowserScreenMovementTest.java index 81314ac334..4781a26186 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/codebrowser/CodeBrowserScreenMovementTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/codebrowser/CodeBrowserScreenMovementTest.java @@ -681,10 +681,7 @@ public class CodeBrowserScreenMovementTest extends AbstractProgramBasedTest { } private void setFieldSelection(FieldPanel fp, FieldSelection sel) { - fp.setSelection(sel); - Class[] argClasses = new Class[] { EventTrigger.class }; - Object[] args = new Object[] { EventTrigger.GUI_ACTION }; - invokeInstanceMethod("notifySelectionChanged", fp, argClasses, args); + fp.setSelection(sel, EventTrigger.GUI_ACTION); } private void setUpCodeBrowserTool(PluginTool tool) throws Exception { diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/colorizer/ColorizingPluginTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/colorizer/ColorizingPluginTest.java index a6b0409958..6d36f0db19 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/colorizer/ColorizingPluginTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/colorizer/ColorizingPluginTest.java @@ -256,7 +256,8 @@ public class ColorizingPluginTest extends AbstractGhidraHeadedIntegrationTest { /** * Tests navigation of offcut ranges when coloring is set from a GUI/API point-of-view. - * @throws Exception + * + * @throws Exception */ @Test public void testNavigateTopBottomOffcutColorRanges() throws Exception { @@ -321,7 +322,8 @@ public class ColorizingPluginTest extends AbstractGhidraHeadedIntegrationTest { /** * Tests navigation of offcut ranges when coloring is set from a plugin point-of-view. - * @throws Exception + * + * @throws Exception */ @Test public void testPluginNavigateTopBottomOffcutColorRanges() throws Exception { @@ -625,13 +627,7 @@ public class ColorizingPluginTest extends AbstractGhidraHeadedIntegrationTest { } private void setSelection(FieldPanel fp, FieldSelection sel) { - fp.setSelection(sel); - Class[] argClasses = new Class[] { EventTrigger.class }; - Object[] args = new Object[] { EventTrigger.GUI_ACTION }; - - runSwing(() -> { - invokeInstanceMethod("notifySelectionChanged", fp, argClasses, args); - }); + runSwing(() -> fp.setSelection(sel, EventTrigger.GUI_ACTION)); } private ActionContext getActionContext() { diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/disassembler/DisassemblerPluginTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/disassembler/DisassemblerPluginTest.java index 70e0558496..88976c50b6 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/disassembler/DisassemblerPluginTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/disassembler/DisassemblerPluginTest.java @@ -457,10 +457,7 @@ public class DisassemblerPluginTest extends AbstractGhidraHeadedIntegrationTest } private void setSelection(FieldPanel fp, FieldSelection sel) { - fp.setSelection(sel); - Class[] argClasses = new Class[] { EventTrigger.class }; - Object[] args = new Object[] { EventTrigger.GUI_ACTION }; - invokeInstanceMethod("notifySelectionChanged", fp, argClasses, args); + runSwing(() -> fp.setSelection(sel, EventTrigger.GUI_ACTION)); } private void clear(Address addr) { diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/marker/MarkerTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/marker/MarkerTest.java index 10aad15b45..3e8a6f6ee9 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/marker/MarkerTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/marker/MarkerTest.java @@ -493,10 +493,7 @@ public class MarkerTest extends AbstractGhidraHeadedIntegrationTest { } private void setSelection(FieldPanel fp, FieldSelection sel) { - fp.setSelection(sel); - Class[] argClasses = new Class[] { EventTrigger.class }; - Object[] args = new Object[] { EventTrigger.GUI_ACTION }; - runSwing(() -> invokeInstanceMethod("notifySelectionChanged", fp, argClasses, args)); + runSwing(() -> fp.setSelection(sel, EventTrigger.GUI_ACTION)); } private AddressSet getAddresses(MarkerSet ms) { diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/navigation/NextPrevSelectionHighlightTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/navigation/NextPrevSelectionHighlightTest.java index 49d63d31d5..0f7bc1caf5 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/navigation/NextPrevSelectionHighlightTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/navigation/NextPrevSelectionHighlightTest.java @@ -101,10 +101,7 @@ public class NextPrevSelectionHighlightTest extends AbstractGhidraHeadedIntegrat } private void setSelection(FieldPanel fp, FieldSelection sel) { - fp.setSelection(sel); - Class[] argClasses = new Class[] { EventTrigger.class }; - Object[] args = new Object[] { EventTrigger.GUI_ACTION }; - invokeInstanceMethod("notifySelectionChanged", fp, argClasses, args); + runSwing(() -> fp.setSelection(sel, EventTrigger.GUI_ACTION)); } @Test diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/select/RestoreSelectionPluginTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/select/RestoreSelectionPluginTest.java index 7d16518de2..8aff8d8d7d 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/select/RestoreSelectionPluginTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/select/RestoreSelectionPluginTest.java @@ -223,13 +223,7 @@ public class RestoreSelectionPluginTest extends AbstractGhidraHeadedIntegrationT } private void setSelection(FieldPanel fp, FieldSelection sel) { - fp.setSelection(sel); - Class[] argClasses = new Class[] { EventTrigger.class }; - Object[] args = new Object[] { EventTrigger.GUI_ACTION }; - - runSwing(() -> { - invokeInstanceMethod("notifySelectionChanged", fp, argClasses, args); - }); + runSwing(() -> fp.setSelection(sel, EventTrigger.GUI_ACTION)); } private void clearSelection(final Program program) { diff --git a/Ghidra/Features/ByteViewer/src/main/java/ghidra/app/plugin/core/byteviewer/ByteViewerComponent.java b/Ghidra/Features/ByteViewer/src/main/java/ghidra/app/plugin/core/byteviewer/ByteViewerComponent.java index 4c00de8144..b2c8fb1beb 100644 --- a/Ghidra/Features/ByteViewer/src/main/java/ghidra/app/plugin/core/byteviewer/ByteViewerComponent.java +++ b/Ghidra/Features/ByteViewer/src/main/java/ghidra/app/plugin/core/byteviewer/ByteViewerComponent.java @@ -178,7 +178,6 @@ public class ByteViewerComponent extends FieldPanel implements FieldMouseListene */ @Override public void selectionChanged(FieldSelection selection, EventTrigger trigger) { - if (blockSet == null || doingRefresh) { return; } diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/DecompilerPanel.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/DecompilerPanel.java index 7a901511f8..b2d4b81490 100644 --- a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/DecompilerPanel.java +++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/DecompilerPanel.java @@ -171,6 +171,7 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field /** * Removes all secondary highlights for the current function + * * @param function the function containing the secondary highlights */ public void removeSecondaryHighlights(Function function) { @@ -282,11 +283,12 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field * This function is used to alert the panel that a token was renamed. If the token being renamed * had a secondary highlight, we must re-apply the highlight to the new token. * - *

This is not needed for highlighter service highlights, since they get called again to - * re-apply highlights. It is up to that highlighter to determine if highlighting still applies - * to the new token name. Alternatively, for secondary highlights, we know the user chose the - * highlight based upon name. Thus, when the name changes, we need to take action to update - * the secondary highlight. + *

+ * This is not needed for highlighter service highlights, since they get called again to + * re-apply highlights. It is up to that highlighter to determine if highlighting still applies + * to the new token name. Alternatively, for secondary highlights, we know the user chose the + * highlight based upon name. Thus, when the name changes, we need to take action to update the + * secondary highlight. * * @param token the token being renamed * @param newName the new name of the token @@ -331,6 +333,7 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field /** * Called by the provider to clone all highlights in the source panel and apply them to this * panel + * * @param sourcePanel the panel that was cloned */ public void cloneHighlights(DecompilerPanel sourcePanel) { @@ -374,6 +377,7 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field /** * This function sets the current window display based on our display state + * * @param decompileData the new data */ void setDecompileData(DecompileData decompileData) { @@ -546,6 +550,7 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field /** * Put cursor on first token in the list + * * @param tokens the tokens to search for */ private void goToBeginningOfLine(List tokens) { @@ -608,8 +613,8 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field /** * Translate Ghidra address to decompiler address. Functions within an overlay space are - * decompiled in their physical space, therefore decompiler results refer to the - * functions underlying .physical space + * decompiled in their physical space, therefore decompiler results refer to the functions + * underlying .physical space * * @param addr the Ghidra address * @return the decompiler address @@ -627,9 +632,9 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field } /** - * Translate Ghidra address set to decompiler address set. Functions within an overlay - * space are decompiled in their physical space, therefore decompiler results - * refer to the functions underlying .physical space + * Translate Ghidra address set to decompiler address set. Functions within an overlay space are + * decompiled in their physical space, therefore decompiler results refer to the functions + * underlying .physical space * * @param set the Ghidra addresses * @return the decompiler addresses @@ -705,8 +710,9 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field } /** - * Passing false signals to disallow navigating to new functions from within the panel by - * using the mouse. + * Passing false signals to disallow navigating to new functions from within the panel by using + * the mouse. + * * @param enabled false disabled mouse function navigation */ void setMouseNavigationEnabled(boolean enabled) { @@ -1013,8 +1019,9 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field } /** - * The color used in a primary highlight to mark the token that was clicked. This is used - * in 'slice' actions to mark the source of the slice. + * The color used in a primary highlight to mark the token that was clicked. This is used in + * 'slice' actions to mark the source of the slice. + * * @return the color */ public Color getSpecialHighlightColor() { @@ -1076,6 +1083,7 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field /** * Returns a single selected token; null if there is no selection or multiple tokens selected. + * * @return a single selected token; null if there is no selection or multiple tokens selected. */ public ClangToken getSelectedToken() { @@ -1165,14 +1173,11 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field fieldPanel.requestFocus(); } - public void selectAll() { + public void selectAll(EventTrigger trigger) { BigInteger numIndexes = layoutMgr.getNumIndexes(); FieldSelection selection = new FieldSelection(); selection.addRange(BigInteger.ZERO, numIndexes); - fieldPanel.setSelection(selection); - - // fake it out that the selection was caused by the field panel GUI. - selectionChanged(selection, EventTrigger.GUI_ACTION); + fieldPanel.setSelection(selection, trigger); } public void optionsChanged(DecompileOptions decompilerOptions) { @@ -1270,10 +1275,10 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field } /** - * Moves this field panel to the given line and column. Further, this navigation will - * fire an event to the rest of the tool. (This is in contrast to a field panel - * goTo, which we use to simply move the cursor, but not trigger an - * tool-level navigation event.) + * Moves this field panel to the given line and column. Further, this navigation will fire + * an event to the rest of the tool. (This is in contrast to a field panel + * goTo, which we use to simply move the cursor, but not trigger an tool-level + * navigation event.) * * @param lineNumber the line number * @param column the column within the line @@ -1285,8 +1290,8 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field } /** - * A class to track pending location updates. This allows us to buffer updates, only sending - * the last one received. + * A class to track pending location updates. This allows us to buffer updates, only sending the + * last one received. */ private class PendingHighlightUpdate { diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/SelectAllAction.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/SelectAllAction.java index a98865d569..160e523712 100644 --- a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/SelectAllAction.java +++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/SelectAllAction.java @@ -21,6 +21,7 @@ import java.awt.event.KeyEvent; import docking.ActionContext; import docking.action.DockingAction; import docking.action.KeyBindingData; +import docking.widgets.EventTrigger; import ghidra.app.decompiler.component.DecompilerPanel; import ghidra.app.util.HelpTopics; import ghidra.util.HelpLocation; @@ -41,7 +42,6 @@ public class SelectAllAction extends DockingAction { @Override public void actionPerformed(ActionContext context) { - panel.selectAll(); + panel.selectAll(EventTrigger.GUI_ACTION); } - } diff --git a/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/mvc/FGController.java b/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/mvc/FGController.java index 6a4515e331..fd1ae8a26a 100644 --- a/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/mvc/FGController.java +++ b/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/mvc/FGController.java @@ -288,6 +288,7 @@ public class FGController implements ProgramLocationListener, ProgramSelectionLi /** * Sets the message that will appear in the lower part of the graph. + * * @param message the message to display */ public void setStatusMessage(String message) { @@ -336,7 +337,10 @@ public class FGController implements ProgramLocationListener, ProgramSelectionLi } @Override - public void programSelectionChanged(ProgramSelection selection) { + public void programSelectionChanged(ProgramSelection selection, EventTrigger trigger) { + if (trigger != EventTrigger.GUI_ACTION) { + return; + } // We need to translate the given selection (which is from a single vertex) to the current // overall selection for the graph (which includes the selection from all vertices). We // do this so that a selection change in one vertex does not clear the selection in @@ -785,12 +789,13 @@ public class FGController implements ProgramLocationListener, ProgramSelectionLi } /** - * Signals that something major has changed for the program and we don't know where, so - * clear all cached functions for the given program. + * Signals that something major has changed for the program and we don't know where, so clear + * all cached functions for the given program. * * BLEH!: I don't like clearing the cache this way...another options is to mark all cached - * values as stale, somehow. If we did this, then when the view reuses the cached - * data, it could signal to the user that the graph is out-of-date. + * values as stale, somehow. If we did this, then when the view reuses the cached data, it could + * signal to the user that the graph is out-of-date. + * * @param program the program */ public void invalidateAllCacheForProgram(Program program) { @@ -997,8 +1002,8 @@ public class FGController implements ProgramLocationListener, ProgramSelectionLi } /** - * Update the graph's notion of the current location based upon that of the Tool. This - * method is meant to be called from internal mutative operations. + * Update the graph's notion of the current location based upon that of the Tool. This method is + * meant to be called from internal mutative operations. */ public void synchronizeProgramLocationAfterEdit() { // It is assumed that the provider's location is the correct location. @@ -1007,6 +1012,7 @@ public class FGController implements ProgramLocationListener, ProgramSelectionLi /** * Will broadcast the given vertex location to the external system + * * @param location the location coming from the vertex */ public void synchronizeProgramLocationToVertex(ProgramLocation location) { diff --git a/Ghidra/Features/ProgramDiff/src/main/java/ghidra/app/plugin/core/diff/ProgramDiffPlugin.java b/Ghidra/Features/ProgramDiff/src/main/java/ghidra/app/plugin/core/diff/ProgramDiffPlugin.java index eba347d6c4..21ce1b4ca4 100644 --- a/Ghidra/Features/ProgramDiff/src/main/java/ghidra/app/plugin/core/diff/ProgramDiffPlugin.java +++ b/Ghidra/Features/ProgramDiff/src/main/java/ghidra/app/plugin/core/diff/ProgramDiffPlugin.java @@ -724,7 +724,10 @@ public class ProgramDiffPlugin extends ProgramPlugin * which displays P2. */ @Override - public void programSelectionChanged(ProgramSelection newP2Selection) { + public void programSelectionChanged(ProgramSelection newP2Selection, EventTrigger trigger) { + if (trigger != EventTrigger.GUI_ACTION) { + return; + } setProgram2Selection(newP2Selection); } @@ -1831,7 +1834,8 @@ public class ProgramDiffPlugin extends ProgramPlugin MarkerSet selectionMarkers = getSelectionMarkers(); selectionMarkers.clearAll(); - programSelectionChanged(new ProgramSelection(p2AddressFactory, set)); + programSelectionChanged(new ProgramSelection(p2AddressFactory, set), + EventTrigger.GUI_ACTION); updatePgm2Enablement(); } } diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/FieldPanel.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/FieldPanel.java index 280612ee87..4d17046b8e 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/FieldPanel.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/FieldPanel.java @@ -15,7 +15,7 @@ */ package docking.widgets.fieldpanel; -import static docking.widgets.EventTrigger.*; +import static docking.widgets.EventTrigger.INTERNAL_ONLY; import java.awt.*; import java.awt.event.*; @@ -392,6 +392,7 @@ public class FieldPanel extends JPanel /** * Returns the default background color. + * * @return the default background color. * @see #getBackground() */ @@ -433,6 +434,7 @@ public class FieldPanel extends JPanel /** * * Returns the foreground color. + * * @return the foreground color. */ public Color getForegroundColor() { @@ -441,6 +443,7 @@ public class FieldPanel extends JPanel /** * Returns the color used as the background for selected items. + * * @return the color used as the background for selected items. */ public Color getSelectionColor() { @@ -449,6 +452,7 @@ public class FieldPanel extends JPanel /** * Returns the color color used as the background for highlighted items. + * * @return the color color used as the background for highlighted items. */ public Color getHighlightColor() { @@ -457,6 +461,7 @@ public class FieldPanel extends JPanel /** * Returns the cursor color when this field panel is focused. + * * @return the cursor color when this field panel is focused. */ public Color getFocusedCursorColor() { @@ -465,6 +470,7 @@ public class FieldPanel extends JPanel /** * Returns the cursor color when this field panel is not focused. + * * @return the cursor color when this field panel is not focused. */ public Color getNonFocusCursorColor() { @@ -495,6 +501,7 @@ public class FieldPanel extends JPanel /** * Returns the point in pixels of where the cursor is located. + * * @return the point in pixels of where the cursor is located. */ public Point getCursorPoint() { @@ -542,6 +549,7 @@ public class FieldPanel extends JPanel /** * Adds a selection listener that will be notified while the selection is being created + * * @param listener the listener to be notified */ public void addLiveFieldSelectionListener(FieldSelectionListener listener) { @@ -550,6 +558,7 @@ public class FieldPanel extends JPanel /** * Removes the selection listener from being notified when the selection is being created + * * @param listener the listener to be removed from being notified */ public void removeLiveFieldSelectionListener(FieldSelectionListener listener) { @@ -691,6 +700,7 @@ public class FieldPanel extends JPanel /** * Returns the current selection. + * * @return the current selection. */ public FieldSelection getSelection() { @@ -699,6 +709,7 @@ public class FieldPanel extends JPanel /** * Returns the current highlight (marked area). + * * @return the current highlight (marked area). */ public FieldSelection getHighlight() { @@ -711,12 +722,22 @@ public class FieldPanel extends JPanel * @param sel the selection to set. */ public void setSelection(FieldSelection sel) { + setSelection(sel, EventTrigger.API_CALL); + } + + /** + * Sets the current selection. + * + * @param sel the selection to set. + * @param trigger the cause of the change + */ + public void setSelection(FieldSelection sel, EventTrigger trigger) { if (!selectionHandler.isSelectionOn()) { return; } selection = new FieldSelection(sel); repaint(); - notifySelectionChanged(EventTrigger.API_CALL); + notifySelectionChanged(trigger); } /** @@ -774,6 +795,7 @@ public class FieldPanel extends JPanel /** * Returns the state of the cursor. True if on, false if off. + * * @return the state of the cursor. True if on, false if off. */ public boolean isCursorOn() { @@ -871,6 +893,7 @@ public class FieldPanel extends JPanel * that layout. For example, if the layout is completely displayed, yPos will be 0. If part of * the layout is off the top off the screen, then yPos will have a negative value (indicating * that it begins above the displayable part of the screen. + * * @return the position */ public ViewerPosition getViewerPosition() { @@ -1057,6 +1080,7 @@ public class FieldPanel extends JPanel /** * Returns the offset of the cursor from the top of the screen + * * @return the offset of the cursor from the top of the screen */ public int getCursorOffset() { @@ -1253,6 +1277,7 @@ public class FieldPanel extends JPanel /** * Finds the layout containing the given y position. + * * @param y the y position. * @return the layout. */ diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/listener/FieldSelectionListener.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/listener/FieldSelectionListener.java index 29a972b514..cc2e389747 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/listener/FieldSelectionListener.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/listener/FieldSelectionListener.java @@ -1,6 +1,5 @@ /* ### * IP: GHIDRA - * REVIEWED: YES * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,6 +25,7 @@ public interface FieldSelectionListener { /** * Called whenever the FieldViewer selection changes. + * * @param selection the new selection. * @param trigger indicates the cause of the selection changing */ diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/address/AddressCollectors.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/address/AddressCollectors.java new file mode 100644 index 0000000000..b51852086b --- /dev/null +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/address/AddressCollectors.java @@ -0,0 +1,63 @@ +/* ### + * 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.program.model.address; + +import java.util.Set; +import java.util.function.*; +import java.util.stream.Collector; + +/** + * Utilities for using addresses and ranges in streams + */ +public class AddressCollectors { + + /** + * Union a stream of address ranges into a single mutable address set + * + * @return the address set + */ + public static Collector toAddressSet() { + return new Collector<>() { + @Override + public Supplier supplier() { + return AddressSet::new; + } + + @Override + public BiConsumer accumulator() { + return AddressSet::add; + } + + @Override + public BinaryOperator combiner() { + return (s1, s2) -> { + s1.add(s2); + return s1; + }; + } + + @Override + public Function finisher() { + return Function.identity(); + } + + @Override + public Set characteristics() { + return Set.of(); + } + }; + } +} diff --git a/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/app/plugin/core/clipboard/ClipboardPluginTest.java b/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/app/plugin/core/clipboard/ClipboardPluginTest.java index 1f131a3424..56dd465b6d 100644 --- a/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/app/plugin/core/clipboard/ClipboardPluginTest.java +++ b/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/app/plugin/core/clipboard/ClipboardPluginTest.java @@ -32,6 +32,7 @@ import org.junit.*; import docking.*; import docking.action.*; import docking.dnd.GClipboard; +import docking.widgets.EventTrigger; import docking.widgets.OptionDialog; import docking.widgets.fieldpanel.FieldPanel; import docking.widgets.fieldpanel.support.FieldSelection; @@ -65,8 +66,7 @@ import ghidra.util.Msg; /** * - * Note: This test is sensitive to focus. So, don't click any windows while this test - * is running. + * Note: This test is sensitive to focus. So, don't click any windows while this test is running. * */ @@ -1691,7 +1691,8 @@ public class ClipboardPluginTest extends AbstractGhidraHeadedIntegrationTest { @Override public void clearSelection() { - runSwing(() -> provider.programSelectionChanged(new ProgramSelection())); + runSwing(() -> provider.programSelectionChanged(new ProgramSelection(), + EventTrigger.GUI_ACTION)); } }