diff --git a/Ghidra/Debug/Debugger/build.gradle b/Ghidra/Debug/Debugger/build.gradle index ee7ec43991..1e60998a31 100644 --- a/Ghidra/Debug/Debugger/build.gradle +++ b/Ghidra/Debug/Debugger/build.gradle @@ -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') diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/breakpoint/DebuggerBreakpointMarkerPlugin.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/breakpoint/DebuggerBreakpointMarkerPlugin.java index edea70f2e4..fbcafb5079 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/breakpoint/DebuggerBreakpointMarkerPlugin.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/breakpoint/DebuggerBreakpointMarkerPlugin.java @@ -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)); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/marker/MarginProviderSupplier.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/marker/MarginProviderSupplier.java new file mode 100644 index 0000000000..5df2919cd7 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/marker/MarginProviderSupplier.java @@ -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(); +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/marker/MarkerManagerPlugin.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/marker/MarkerManagerPlugin.java index f47ec73f93..2662929ecb 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/marker/MarkerManagerPlugin.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/marker/MarkerManagerPlugin.java @@ -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); diff --git a/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/FGProvider.java b/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/FGProvider.java index 2d01d3f286..bf5a65ad85 100644 --- a/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/FGProvider.java +++ b/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/FGProvider.java @@ -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 setPendingLocationFromUpdateManager()); clipboardProvider = new FGClipboardProvider(tool, controller); - ClipboardService service = tool.getService(ClipboardService.class); - setClipboardService(service); } @Override @@ -139,7 +138,7 @@ public class FGProvider extends VisualGraphComponentProvider 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 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 getService(Class serviceClass) { + return tool.getService(serviceClass); + } + public FunctionGraphOptions getFunctionGraphOptions() { return functionGraphOptions; } diff --git a/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/graph/vertex/FGVertexListingPanel.java b/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/graph/vertex/FGVertexListingPanel.java index 174284b266..20092df3d3 100644 --- a/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/graph/vertex/FGVertexListingPanel.java +++ b/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/graph/vertex/FGVertexListingPanel.java @@ -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 diff --git a/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/graph/vertex/ListingGraphComponentPanel.java b/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/graph/vertex/ListingGraphComponentPanel.java index dd891e06f3..7d0bd9a296 100644 --- a/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/graph/vertex/ListingGraphComponentPanel.java +++ b/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/graph/vertex/ListingGraphComponentPanel.java @@ -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 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); diff --git a/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/mvc/FGController.java b/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/mvc/FGController.java index 94f7ae7455..54584607df 100644 --- a/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/mvc/FGController.java +++ b/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/mvc/FGController.java @@ -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 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 getService(Class 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 getMarginProviderSuppliers() { + return Collections.unmodifiableSet(marginProviders); + } + //================================================================================================== // Inner Classes //================================================================================================== diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/mgr/ServiceManager.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/mgr/ServiceManager.java index aa222fcabb..64ef8d896b 100644 --- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/mgr/ServiceManager.java +++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/mgr/ServiceManager.java @@ -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 void addService(Class interfaceClass, T service) { List 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 list = servicesByInterface.get(interfaceClass);