GP-2737 - Function Graph - Added support for rendering Area Markers. Added support for a breakpoint margin area in each vertex.

This commit is contained in:
dragonmacher 2023-10-23 10:59:35 -04:00
parent 7e4d2bcfaa
commit 54a240f3b8
11 changed files with 240 additions and 34 deletions

View File

@ -30,6 +30,7 @@ dependencies {
api project(':Base')
api project(':ByteViewer')
api project(':Decompiler')
api project(':FunctionGraph')
api project(':ProposedUtils')
testImplementation project(path: ':Generic', configuration: 'testArtifacts')

View File

@ -41,6 +41,9 @@ import ghidra.app.plugin.core.debug.event.TraceOpenedPluginEvent;
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
import ghidra.app.plugin.core.debug.gui.DebuggerResources.*;
import ghidra.app.plugin.core.decompile.DecompilerActionContext;
import ghidra.app.plugin.core.functiongraph.FunctionGraphMarginService;
import ghidra.app.plugin.core.marker.MarginProviderSupplier;
import ghidra.app.plugin.core.marker.MarkerMarginProvider;
import ghidra.app.services.*;
import ghidra.app.util.viewer.listingpanel.MarkerClickedListener;
import ghidra.async.AsyncDebouncer;
@ -501,6 +504,16 @@ public class DebuggerBreakpointMarkerPlugin extends Plugin
}
}
private class DefaultMarginProviderSupplier implements MarginProviderSupplier {
@Override
public MarkerMarginProvider createMarginProvider() {
if (markerService != null) {
return markerService.createMarginProvider();
}
return null;
}
}
protected static State computeState(LogicalBreakpoint breakpoint, Program programOrView) {
if (programOrView instanceof TraceProgramView view) {
return breakpoint.computeStateForTrace(view.getTrace());
@ -735,6 +748,8 @@ public class DebuggerBreakpointMarkerPlugin extends Plugin
private DebuggerControlService controlService;
// @AutoServiceConsumed via method
DecompilerMarginService decompilerMarginService;
// @AutoServiceConsumed via method
private FunctionGraphMarginService functionGraphMarginService;
@SuppressWarnings("unused")
private final AutoService.Wiring autoServiceWiring;
@ -793,6 +808,8 @@ public class DebuggerBreakpointMarkerPlugin extends Plugin
DebuggerPlaceBreakpointDialog placeBreakpointDialog = new DebuggerPlaceBreakpointDialog();
BreakpointsDecompilerMarginProvider decompilerMarginProvider;
private MarginProviderSupplier functionGraphMarginSupplier =
new DefaultMarginProviderSupplier();
public DebuggerBreakpointMarkerPlugin(PluginTool tool) {
super(tool);
@ -1038,6 +1055,21 @@ public class DebuggerBreakpointMarkerPlugin extends Plugin
}
}
@AutoServiceConsumed
private void setFunctionGraphMarginService(
FunctionGraphMarginService functionGraphMarginService) {
if (this.functionGraphMarginService != null) {
this.functionGraphMarginService
.removeMarkerProviderSupplier(functionGraphMarginSupplier);
}
this.functionGraphMarginService = functionGraphMarginService;
if (this.functionGraphMarginService != null) {
this.functionGraphMarginService.addMarkerProviderSupplier(functionGraphMarginSupplier);
}
}
protected void createActions() {
actionSetSoftwareBreakpoint =
new SetBreakpointAction(Set.of(TraceBreakpointKind.SW_EXECUTE));

View File

@ -0,0 +1,28 @@
/* ###
* 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.marker;
/**
* Supplies {@link MarkerMarginProvider}s.
*/
public interface MarginProviderSupplier {
/**
* Creates a new marker margin provider.
* @return the provider.
*/
public MarkerMarginProvider createMarginProvider();
}

View File

@ -25,9 +25,7 @@ import ghidra.framework.plugintool.*;
import ghidra.framework.plugintool.util.PluginStatus;
import ghidra.util.HelpLocation;
/**
* Plugin to manage marker and navigation panels.
*/
//@formatter:off
@PluginInfo(
status = PluginStatus.RELEASED,
packageName = CorePluginPackage.NAME,
@ -43,15 +41,17 @@ import ghidra.util.HelpLocation;
"as bookmarks.",
servicesRequired = { CodeViewerService.class, GoToService.class },
servicesProvided = { MarkerService.class },
eventsConsumed = {})
eventsConsumed = {}
)
//@formatter:on
/**
* Plugin to manage marker and navigation panels.
*/
public class MarkerManagerPlugin extends Plugin {
private CodeViewerService codeViewerService;
private MarkerManager markerManager;
/**
* @param tool
*/
public MarkerManagerPlugin(PluginTool tool) {
super(tool);
markerManager = new MarkerManager(this);

View File

@ -32,6 +32,7 @@ import ghidra.app.plugin.core.functiongraph.graph.*;
import ghidra.app.plugin.core.functiongraph.graph.vertex.FGVertex;
import ghidra.app.plugin.core.functiongraph.graph.vertex.GroupedFunctionGraphVertex;
import ghidra.app.plugin.core.functiongraph.mvc.*;
import ghidra.app.plugin.core.marker.MarginProviderSupplier;
import ghidra.app.services.*;
import ghidra.app.util.ListingHighlightProvider;
import ghidra.framework.model.*;
@ -129,8 +130,6 @@ public class FGProvider extends VisualGraphComponentProvider<FGVertex, FGEdge, F
new SwingUpdateManager(250, 750, () -> setPendingLocationFromUpdateManager());
clipboardProvider = new FGClipboardProvider(tool, controller);
ClipboardService service = tool.getService(ClipboardService.class);
setClipboardService(service);
}
@Override
@ -139,7 +138,7 @@ public class FGProvider extends VisualGraphComponentProvider<FGVertex, FGEdge, F
return !isConnected();
}
public void setClipboardService(ClipboardService service) {
void setClipboardService(ClipboardService service) {
clipboardService = service;
if (clipboardService != null) {
clipboardService.registerClipboardContentProvider(clipboardProvider);
@ -1136,6 +1135,16 @@ public class FGProvider extends VisualGraphComponentProvider<FGVertex, FGEdge, F
controller.setGraphPerspective(info);
}
void addMarkerProviderSupplier(MarginProviderSupplier supplier) {
controller.addMarkerProviderSupplier(supplier);
refreshAndKeepPerspective();
}
void removeMarkerProviderSupplier(MarginProviderSupplier supplier) {
controller.removeMarkerProviderSupplier(supplier);
refreshAndKeepPerspective();
}
//==================================================================================================
// Navigatable interface methods
//==================================================================================================
@ -1298,7 +1307,8 @@ public class FGProvider extends VisualGraphComponentProvider<FGVertex, FGEdge, F
}
@Override
public void removeHighlightProvider(ListingHighlightProvider highlightProvider, Program program) {
public void removeHighlightProvider(ListingHighlightProvider highlightProvider,
Program program) {
// currently unsupported
}

View File

@ -0,0 +1,42 @@
/* ###
* 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.functiongraph;
import ghidra.app.plugin.core.functiongraph.graph.FunctionGraph;
import ghidra.app.plugin.core.marker.MarginProviderSupplier;
import ghidra.framework.plugintool.ServiceInfo;
/**
* A service that allows clients to add custom margins in the {@link FunctionGraph} UI.
*/
@ServiceInfo(defaultProvider = FunctionGraphPlugin.class)
public interface FunctionGraphMarginService {
/**
* Add a marker margin supplier to Function Graph's primary window. The supplier will be called
* for each node in the graph.
*
* @param supplier the supplier.
*/
public void addMarkerProviderSupplier(MarginProviderSupplier supplier);
/**
* Removes the given margin supplier from the Function Graph's UI.
*
* @param supplier the supplier.
*/
public void removeMarkerProviderSupplier(MarginProviderSupplier supplier);
}

View File

@ -33,6 +33,7 @@ import ghidra.app.plugin.core.colorizer.ColorizingService;
import ghidra.app.plugin.core.functiongraph.graph.layout.FGLayoutOptions;
import ghidra.app.plugin.core.functiongraph.graph.layout.FGLayoutProvider;
import ghidra.app.plugin.core.functiongraph.mvc.FunctionGraphOptions;
import ghidra.app.plugin.core.marker.MarginProviderSupplier;
import ghidra.app.services.*;
import ghidra.app.util.viewer.format.FormatManager;
import ghidra.framework.model.DomainFile;
@ -53,10 +54,13 @@ import ghidra.util.exception.AssertException;
category = PluginCategoryNames.GRAPH,
shortDescription = FunctionGraphPlugin.FUNCTION_GRAPH_NAME,
description = "Plugin for show a graphical representation of the code blocks of a function",
servicesRequired = { GoToService.class, BlockModelService.class, CodeViewerService.class, ProgramManager.class }
servicesRequired = { GoToService.class, BlockModelService.class, CodeViewerService.class, ProgramManager.class },
servicesProvided = { FunctionGraphMarginService.class }
)
//@formatter:on
public class FunctionGraphPlugin extends ProgramPlugin implements OptionsChangeListener {
public class FunctionGraphPlugin extends ProgramPlugin
implements OptionsChangeListener, FunctionGraphMarginService {
static final String FUNCTION_GRAPH_NAME = "Function Graph";
static final String OPTIONS_NAME_PATH =
ToolConstants.GRAPH_OPTIONS + Options.DELIMITER + FUNCTION_GRAPH_NAME;
@ -111,20 +115,32 @@ public class FunctionGraphPlugin extends ProgramPlugin implements OptionsChangeL
colorProvider = new ToolBasedColorProvider(this, (ColorizingService) service);
connectedProvider.refreshAndKeepPerspective();
}
else if (interfaceClass == MarkerService.class) {
for (FGProvider disconnectedProvider : disconnectedProviders) {
disconnectedProvider.refreshAndKeepPerspective();
}
connectedProvider.refreshAndKeepPerspective();
}
}
@Override
public void serviceRemoved(Class<?> interfaceClass, Object service) {
if (interfaceClass == ClipboardService.class) {
connectedProvider.setClipboardService((ClipboardService) service);
connectedProvider.setClipboardService(null);
for (FGProvider disconnectedProvider : disconnectedProviders) {
disconnectedProvider.setClipboardService((ClipboardService) service);
disconnectedProvider.setClipboardService(null);
}
}
else if (interfaceClass == ColorizingService.class) {
colorProvider = new IndependentColorProvider(tool);
connectedProvider.refreshAndKeepPerspective();
}
else if (interfaceClass == MarkerService.class) {
for (FGProvider disconnectedProvider : disconnectedProviders) {
disconnectedProvider.refreshAndKeepPerspective();
}
connectedProvider.refreshAndKeepPerspective();
}
}
private List<FGLayoutProvider> loadLayoutProviders() {
@ -194,6 +210,22 @@ public class FunctionGraphPlugin extends ProgramPlugin implements OptionsChangeL
}
}
@Override
public void addMarkerProviderSupplier(MarginProviderSupplier supplier) {
for (FGProvider disconnectedProvider : disconnectedProviders) {
disconnectedProvider.addMarkerProviderSupplier(supplier);
}
connectedProvider.addMarkerProviderSupplier(supplier);
}
@Override
public void removeMarkerProviderSupplier(MarginProviderSupplier supplier) {
for (FGProvider disconnectedProvider : disconnectedProviders) {
disconnectedProvider.removeMarkerProviderSupplier(supplier);
}
connectedProvider.removeMarkerProviderSupplier(supplier);
}
@Override
protected void programActivated(Program program) {
if (connectedProvider == null) {
@ -431,6 +463,10 @@ public class FunctionGraphPlugin extends ProgramPlugin implements OptionsChangeL
return colorProvider;
}
public <T> T getService(Class<T> serviceClass) {
return tool.getService(serviceClass);
}
public FunctionGraphOptions getFunctionGraphOptions() {
return functionGraphOptions;
}

View File

@ -39,9 +39,9 @@ public class FGVertexListingPanel extends ListingPanel {
//
// Unusual Code Alert!: when the data of the listing changes its preferred size
// may also change. If we don't invalidate the containing
// Java component, then the cached preferred size will be
// Java component, then the cached preferred size will be
// invalid.
//
//
getFieldPanel().invalidate();
controller.repaint();
}
@ -73,10 +73,9 @@ public class FGVertexListingPanel extends ListingPanel {
Color color = options.getDefaultVertexBackgroundColor();
setTextBackgroundColor(color);
// Custom colors are in use when the ColorizingService is not installed.
FGColorProvider colorProvider = controller.getColorProvider();
if (!colorProvider.isUsingCustomColors()) {
enablePropertyBasedColorModel(true); // turn on user colors in the graph
}
enablePropertyBasedColorModel(!colorProvider.isUsingCustomColors());
}
@Override
@ -94,8 +93,8 @@ public class FGVertexListingPanel extends ListingPanel {
* Overridden to set the view before the parent class notifies the listeners. This prevents
* our methods that calculate preferred size from going 'out to lunch' when attempting to
* examine the entire program instead of just the given view.
*
* @param model The listing model needed by the layout model *
*
* @param model The listing model needed by the layout model
* @return the new model adapter
*/
@Override

View File

@ -23,10 +23,12 @@ import java.awt.event.*;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import javax.swing.*;
import javax.swing.border.BevelBorder;
import javax.swing.border.Border;
import javax.swing.event.ChangeListener;
import docking.ActionContext;
import docking.GenericHeader;
@ -41,16 +43,19 @@ import generic.theme.GColor;
import generic.theme.GIcon;
import generic.theme.GThemeDefaults.Colors;
import generic.theme.GThemeDefaults.Colors.Tooltips;
import ghidra.app.plugin.core.codebrowser.MarkerServiceBackgroundColorModel;
import ghidra.app.plugin.core.codebrowser.hover.ListingHoverService;
import ghidra.app.plugin.core.functiongraph.FunctionGraphPlugin;
import ghidra.app.plugin.core.functiongraph.graph.FGEdge;
import ghidra.app.plugin.core.functiongraph.mvc.FGController;
import ghidra.app.plugin.core.functiongraph.mvc.FunctionGraphOptions;
import ghidra.app.plugin.core.marker.MarginProviderSupplier;
import ghidra.app.plugin.core.marker.MarkerMarginProvider;
import ghidra.app.services.HoverService;
import ghidra.app.services.MarkerService;
import ghidra.app.util.AddEditDialog;
import ghidra.app.util.viewer.format.FormatManager;
import ghidra.app.util.viewer.listingpanel.ListingHoverProvider;
import ghidra.app.util.viewer.listingpanel.ListingModel;
import ghidra.app.util.viewer.listingpanel.*;
import ghidra.app.util.viewer.util.AddressIndexMap;
import ghidra.app.util.viewer.util.FieldNavigator;
import ghidra.framework.plugintool.PluginTool;
@ -105,6 +110,12 @@ public class ListingGraphComponentPanel extends AbstractGraphComponentPanel {
}
};
private final ChangeListener markerChangeListener = e -> {
if (controller != null) {
controller.repaint();
}
};
ListingGraphComponentPanel(final FGVertex vertex, final FGController controller,
PluginTool tool, Program program, AddressSetView addressSet) {
super(controller, vertex);
@ -122,6 +133,21 @@ public class ListingGraphComponentPanel extends AbstractGraphComponentPanel {
.addButtonPressedListener(controller.getSharedHighlighterButtonPressedListener());
listingPanel.setStringSelectionListener(controller.getSharedStringSelectionListener());
MarkerService markerService = controller.getService(MarkerService.class);
if (markerService != null) {
ListingBackgroundColorModel colorModel = new MarkerServiceBackgroundColorModel(
markerService, listingPanel.getAddressIndexMap());
listingPanel.setBackgroundColorModel(colorModel);
markerService.addChangeListener(markerChangeListener);
}
// The margin providers may be installed by services other than the MarkerService
Set<MarginProviderSupplier> marginProviders = controller.getMarginProviderSuppliers();
for (MarginProviderSupplier supplier : marginProviders) {
MarkerMarginProvider marginProvider = supplier.createMarginProvider();
listingPanel.addMarginProvider(marginProvider);
}
fieldPanel = listingPanel.getFieldPanel();
fieldPanel.setCursorOn(false);
@ -676,6 +702,11 @@ public class ListingGraphComponentPanel extends AbstractGraphComponentPanel {
// references and removing the data from Jung's graph
//
MarkerService markerService = controller.getService(MarkerService.class);
if (markerService != null) {
markerService.removeChangeListener(markerChangeListener);
}
removeAll();
listingPanel.setStringSelectionListener(null);

View File

@ -18,9 +18,8 @@ package ghidra.app.plugin.core.functiongraph.mvc;
import java.awt.*;
import java.awt.event.MouseEvent;
import java.awt.geom.Point2D;
import java.util.ArrayList;
import java.util.*;
import java.util.List;
import java.util.Set;
import java.util.function.BiConsumer;
import javax.swing.JComponent;
@ -40,6 +39,7 @@ import ghidra.app.plugin.core.functiongraph.graph.FGEdge;
import ghidra.app.plugin.core.functiongraph.graph.FunctionGraph;
import ghidra.app.plugin.core.functiongraph.graph.layout.FGLayoutProvider;
import ghidra.app.plugin.core.functiongraph.graph.vertex.*;
import ghidra.app.plugin.core.marker.MarginProviderSupplier;
import ghidra.app.services.ButtonPressedListener;
import ghidra.app.services.CodeViewerService;
import ghidra.app.util.ListingHighlightProvider;
@ -58,6 +58,8 @@ import ghidra.program.model.symbol.*;
import ghidra.program.util.ProgramLocation;
import ghidra.program.util.ProgramSelection;
import ghidra.util.SystemUtilities;
import ghidra.util.datastruct.WeakDataStructureFactory;
import ghidra.util.datastruct.WeakSet;
public class FGController implements ProgramLocationListener, ProgramSelectionListener {
@ -90,6 +92,9 @@ public class FGController implements ProgramLocationListener, ProgramSelectionLi
// dummy
};
private WeakSet<MarginProviderSupplier> marginProviders =
WeakDataStructureFactory.createSingleThreadAccessWeakSet();
public FGController(FGProvider provider, FunctionGraphPlugin plugin) {
this.provider = provider;
this.plugin = plugin;
@ -421,7 +426,7 @@ public class FGController implements ProgramLocationListener, ProgramSelectionLi
//==================================================================================================
//==================================================================================================
// Methods call by the providers
// Methods called by the providers
//==================================================================================================
public void programClosed(Program program) {
@ -830,8 +835,16 @@ public class FGController implements ProgramLocationListener, ProgramSelectionLi
}
}
public void addMarkerProviderSupplier(MarginProviderSupplier supplier) {
marginProviders.add(supplier);
}
public void removeMarkerProviderSupplier(MarginProviderSupplier supplier) {
marginProviders.add(supplier);
}
//==================================================================================================
// Methods call by the model
// Methods called by the model
//==================================================================================================
public void setFunctionGraphData(FGData data) {
@ -916,7 +929,7 @@ public class FGController implements ProgramLocationListener, ProgramSelectionLi
}
//==================================================================================================
// Methods call by the vertices (actions and such)
// Methods called by the vertices (actions and such)
//==================================================================================================
/** Zooms so that the graph will fit completely into the size of the primary viewer */
@ -1007,6 +1020,10 @@ public class FGController implements ProgramLocationListener, ProgramSelectionLi
return plugin.getColorProvider();
}
public <T> T getService(Class<T> serviceClass) {
return plugin.getService(serviceClass);
}
/**
* 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.
@ -1063,6 +1080,10 @@ public class FGController implements ProgramLocationListener, ProgramSelectionLi
};
}
public Set<MarginProviderSupplier> getMarginProviderSuppliers() {
return Collections.unmodifiableSet(marginProviders);
}
//==================================================================================================
// Inner Classes
//==================================================================================================

View File

@ -28,7 +28,7 @@ import ghidra.util.exception.AssertException;
* it may depend on a service. The ServiceManager maintains a list of
* service names and plugins that provide those services. A plugin may
* dynamically add and remove services from the service registry. As services
* are added and removed, all the plugins (ServiceListener)
* are added and removed, all the plugins (ServiceListener)
* in the tool are notified.
*/
@ -93,20 +93,24 @@ public class ServiceManager {
/**
* Add the service to the tool. Notify the service listeners if the
* notification indicator is true; otherwise, add the service to a list
* that will be used to notify listeners when notifications are
* that will be used to notify listeners when notifications are
* turned on again.
* @param interfaceClass class of the service interface being added
* @param service implementation of the service; it may be a plugin or
* may be some object created by the plugin
*
* @see #setServiceAddedNotificationsOn(boolean)
*
* @see #setServiceAddedNotificationsOn(boolean)
*/
public synchronized <T> void addService(Class<? extends T> interfaceClass, T service) {
List<Object> list =
servicesByInterface.computeIfAbsent(interfaceClass, (k) -> new ArrayList<>());
if (list.contains(service)) {
// Note: this can happen if a plugin implements a service it declares and also calls
// Plugin.registerServiceProvided(), which is a mistake, since the plugin will get
// auto-wired when it implements the service interface.
throw new AssertException(
"Same Service implementation cannot be " + "added more than once");
"The same Service implementation cannot be added more than once. Interface: " +
interfaceClass + ". Service: " + service);
}
list.add(service);
@ -120,6 +124,8 @@ public class ServiceManager {
/**
* Remove the service from the tool.
* @param interfaceClass the service interface
* @param service the service implementation
*/
public void removeService(Class<?> interfaceClass, Object service) {
List<Object> list = servicesByInterface.get(interfaceClass);