GP-1451: Add sync selection actions, toggles

This commit is contained in:
Dan 2022-05-20 10:14:58 -04:00
parent ccbf264116
commit cfdf1051a1
32 changed files with 1068 additions and 424 deletions

View File

@ -128,7 +128,7 @@
</TBODY>
</TABLE>
<H3><A name="sync_static"></A>Sync to Static Listing</H3>
<H3><A name="auto_sync_cursor_static"></A>Auto-Sync Cursor with Static Listing</H3>
<P>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</A>
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.</P>
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.</P>
<H3><A name="auto_sync_selection_static"></A>Auto-Sync Selection with Static Listing</H3>
<P>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 <A href=
"help/topics/DebuggerStaticMappingPlugin/DebuggerStaticMappingPlugin.html">Static Mappings</A>
window. Portions of the selection which cannot be mapped are omitted.</P>
<H3><A name="sync_selection_into_static"></A>Sync Selection Here into Static Listing</H3>
<P>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 <A href=
"help/topics/DebuggerStaticMappingPlugin/DebuggerStaticMappingPlugin.html">Static Mappings</A>
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.</P>
<H3><A name="sync_selection_from_static"></A>Sync Selection Here from Static Listing</H3>
<P>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 <A href=
"help/topics/DebuggerStaticMappingPlugin/DebuggerStaticMappingPlugin.html">Static Mappings</A>
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.</P>
<H3><A name="open_program"></A>Open Program</H3>

View File

@ -325,9 +325,9 @@
<H3><A name="toggle_update_while_running">Update While Running</A></H3>
<P>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.</P>
<P>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.</P>
<H2><A name="color"></A>Color Options</H2>

View File

@ -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));
}
}

View File

@ -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<DebuggerStaticSyncTrait> 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<Trace> affectedTraces, Set<Program> 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<MappedAddressRange> 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
}
}

View File

@ -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<DebuggerLis
@SuppressWarnings("unused")
private AutoOptions.Wiring autoOptionsWiring;
//private final SuppressableCallback<Void> cbGoTo = new SuppressableCallback<>();
private final SuppressableCallback<Void> cbProgramLocationEvents = new SuppressableCallback<>();
private final SuppressableCallback<Void> cbProgramSelectionEvents =
new SuppressableCallback<>();
private DebuggerCoordinates current = DebuggerCoordinates.NOWHERE;
@ -242,7 +245,7 @@ public class DebuggerListingPlugin extends AbstractCodeBrowserPlugin<DebuggerLis
// TODO Nothing, yet
}
protected boolean heedLocationEvent(ProgramLocationPluginEvent ev) {
protected boolean heedLocationEvent(PluginEvent ev) {
PluginEvent trigger = ev.getTriggerEvent();
/*Msg.debug(this, "Location event");
Msg.debug(this, " Program: " + ev.getProgram());
@ -268,6 +271,10 @@ public class DebuggerListingPlugin extends AbstractCodeBrowserPlugin<DebuggerLis
return true;
}
protected boolean heedSelectionEvent(PluginEvent ev) {
return heedLocationEvent(ev);
}
@Override
public void processEvent(PluginEvent event) {
if (event instanceof ProgramLocationPluginEvent) {
@ -278,6 +285,15 @@ public class DebuggerListingPlugin extends AbstractCodeBrowserPlugin<DebuggerLis
}
});
}
if (event instanceof ProgramSelectionPluginEvent) {
cbProgramSelectionEvents.invoke(() -> {
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<DebuggerLis
ProgramClosedPluginEvent ev = (ProgramClosedPluginEvent) event;
allProviders(p -> 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<DebuggerLis
}
}
void fireStaticSelectionEvent(Program staticProg, ProgramSelection staticSel) {
assert Swing.isSwingThread();
try (Suppression supp = cbProgramSelectionEvents.suppress(null)) {
tool.firePluginEvent(new ProgramSelectionPluginEvent(getName(), staticSel, staticProg));
}
}
protected void allProviders(Consumer<DebuggerListingProvider> action) {
action.accept(connectedProvider);
for (DebuggerListingProvider provider : disconnectedProviders) {
@ -359,11 +386,9 @@ public class DebuggerListingPlugin extends AbstractCodeBrowserPlugin<DebuggerLis
if (!result) {
return false;
}
//cbGoTo.invoke(() -> {
DebuggerListingProvider provider = connectedProvider;
provider.doSyncToStatic(location);
provider.doAutoSyncCursorIntoStatic(location);
provider.doCheckCurrentModuleMissing();
//});
return true;
}

View File

@ -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<CodeViewerService> 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<AutoReadMemorySpec> actionAutoReadMemory;
protected DockingAction actionReadSelectedMemory;
protected DockingAction actionOpenProgram;
protected MultiStateDockingAction<LocationTrackingSpec> 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();

View File

@ -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<AutoReadMemorySpec> actionAutoReadMemory;
protected DockingAction actionReadSelectedMemory;
protected MultiStateDockingAction<LocationTrackingSpec> 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();

View File

@ -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<TraceSpan, Collection<MappedAddressRange>> 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) {

View File

@ -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<AddressRange, AddressSet, AddressSet> toAddressSet() {
return new Collector<>() {
@Override
public Supplier<AddressSet> supplier() {
return AddressSet::new;
}
@Override
public BiConsumer<AddressSet, AddressRange> accumulator() {
return AddressSet::add;
}
@Override
public BinaryOperator<AddressSet> combiner() {
return (s1, s2) -> {
s1.add(s2);
return s1;
};
}
@Override
public Function<AddressSet, AddressSet> finisher() {
return Function.identity();
}
@Override
public Set<Characteristics> characteristics() {
return Set.of();
}
};
}
public AddressSetView getAccessible() {
synchronized (regions) {
return regions.values()
.stream()
.collect(toAddressSet());
.collect(AddressCollectors.toAddressSet());
}
}
}

View File

@ -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);
}

View File

@ -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");

View File

@ -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);

View File

@ -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();

View File

@ -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);
}
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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 <code>x86:LE:32:default</code>
* @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 <code>x86:LE:32:default</code>
* @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 <T> the return type
* @param <E> 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<Symbol> 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.
*
* <P>Note: this is a blocking operation. Your test will not proceed while this method is
* sleeping.
* <P>
* Note: this is a blocking operation. Your test will not proceed while this method is sleeping.
*
* <P><B>Do not leave this call in your test when committing changes.</B>
* <P>
* <B>Do not leave this call in your test when committing changes.</B>
*
* @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() {

View File

@ -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 {

View File

@ -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() {

View File

@ -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) {

View File

@ -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) {

View File

@ -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

View File

@ -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) {

View File

@ -178,7 +178,6 @@ public class ByteViewerComponent extends FieldPanel implements FieldMouseListene
*/
@Override
public void selectionChanged(FieldSelection selection, EventTrigger trigger) {
if (blockSet == null || doingRefresh) {
return;
}

View File

@ -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.
*
* <p>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.
* <p>
* 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<ClangToken> 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
* <code>goTo</code>, 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
* <code>goTo</code>, 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 {

View File

@ -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);
}
}

View File

@ -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) {

View File

@ -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();
}
}

View File

@ -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.
*/

View File

@ -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
*/

View File

@ -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<AddressRange, AddressSet, AddressSet> toAddressSet() {
return new Collector<>() {
@Override
public Supplier<AddressSet> supplier() {
return AddressSet::new;
}
@Override
public BiConsumer<AddressSet, AddressRange> accumulator() {
return AddressSet::add;
}
@Override
public BinaryOperator<AddressSet> combiner() {
return (s1, s2) -> {
s1.add(s2);
return s1;
};
}
@Override
public Function<AddressSet, AddressSet> finisher() {
return Function.identity();
}
@Override
public Set<Characteristics> characteristics() {
return Set.of();
}
};
}
}

View File

@ -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));
}
}