mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2024-11-10 14:11:59 +00:00
GP-1451: Add sync selection actions, toggles
This commit is contained in:
parent
ccbf264116
commit
cfdf1051a1
@ -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>
|
||||
|
||||
|
@ -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>
|
||||
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
|
@ -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();
|
||||
|
@ -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) {
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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");
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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() {
|
||||
|
@ -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 {
|
||||
|
@ -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() {
|
||||
|
@ -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) {
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
|
@ -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) {
|
||||
|
@ -178,7 +178,6 @@ public class ByteViewerComponent extends FieldPanel implements FieldMouseListene
|
||||
*/
|
||||
@Override
|
||||
public void selectionChanged(FieldSelection selection, EventTrigger trigger) {
|
||||
|
||||
if (blockSet == null || doingRefresh) {
|
||||
return;
|
||||
}
|
||||
|
@ -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 {
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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.
|
||||
*/
|
||||
|
@ -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
|
||||
*/
|
||||
|
@ -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();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user