diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/FunctionComparisonPlugin.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/FunctionComparisonPlugin.java index f7ec3d6201..33abb8bb5d 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/FunctionComparisonPlugin.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/FunctionComparisonPlugin.java @@ -19,13 +19,19 @@ import java.util.Set; import java.util.function.Supplier; import ghidra.app.CorePluginPackage; -import ghidra.app.events.*; +import ghidra.app.events.ProgramActivatedPluginEvent; +import ghidra.app.events.ProgramClosedPluginEvent; +import ghidra.app.events.ProgramSelectionPluginEvent; import ghidra.app.plugin.PluginCategoryNames; import ghidra.app.plugin.ProgramPlugin; import ghidra.app.plugin.core.functioncompare.actions.CompareFunctionsAction; import ghidra.app.plugin.core.functioncompare.actions.CompareFunctionsFromListingAction; import ghidra.app.services.FunctionComparisonService; -import ghidra.framework.model.*; +import ghidra.framework.model.DomainObjectChangeRecord; +import ghidra.framework.model.DomainObjectChangedEvent; +import ghidra.framework.model.DomainObjectEvent; +import ghidra.framework.model.DomainObjectListener; +import ghidra.framework.model.EventType; import ghidra.framework.plugintool.PluginInfo; import ghidra.framework.plugintool.PluginTool; import ghidra.framework.plugintool.util.PluginStatus; @@ -170,6 +176,13 @@ public class FunctionComparisonPlugin extends ProgramPlugin return getFromSwingBlocking(() -> functionComparisonManager.compareFunctions(functions)); } + @Override + public FunctionComparisonProvider compareFunctions(Set sourceFunctions, + Set destinationFunctions) { + return getFromSwingBlocking(() -> functionComparisonManager + .compareFunctions(sourceFunctions, destinationFunctions)); + } + @Override public void compareFunctions(Set functions, FunctionComparisonProvider provider) { runOnSwingNonBlocking( diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/FunctionComparisonProviderManager.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/FunctionComparisonProviderManager.java index c00894eed7..dbd28b902a 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/FunctionComparisonProviderManager.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/FunctionComparisonProviderManager.java @@ -20,7 +20,10 @@ import java.util.Set; import java.util.concurrent.CopyOnWriteArraySet; import docking.ComponentProviderActivationListener; -import ghidra.framework.model.*; +import ghidra.framework.model.DomainObjectChangeRecord; +import ghidra.framework.model.DomainObjectChangedEvent; +import ghidra.framework.model.DomainObjectEvent; +import ghidra.framework.model.EventType; import ghidra.program.model.listing.Function; import ghidra.program.model.listing.Program; @@ -81,6 +84,23 @@ public class FunctionComparisonProviderManager implements FunctionComparisonProv return provider; } + /** + * Create a new comparison between two given sets of functions + * + * @param sourceFunctions + * @param destinationFunctions + * @return the new comparison provider + */ + public FunctionComparisonProvider compareFunctions(Set sourceFunctions, + Set destinationFunctions) { + if (sourceFunctions.isEmpty() || destinationFunctions.isEmpty()) { + return null; + } + FunctionComparisonProvider provider = createProvider(); + provider.getModel().compareFunctions(sourceFunctions, destinationFunctions); + return provider; + } + /** * Creates a new comparison comparison between two functions * diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/services/FunctionComparisonModel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/services/FunctionComparisonModel.java index 6490ef423f..1e750187f0 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/services/FunctionComparisonModel.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/services/FunctionComparisonModel.java @@ -18,12 +18,20 @@ */ package ghidra.app.services; -import java.util.*; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Set; import java.util.stream.Collectors; import org.apache.commons.collections4.CollectionUtils; -import ghidra.app.plugin.core.functioncompare.*; +import ghidra.app.plugin.core.functioncompare.FunctionComparison; +import ghidra.app.plugin.core.functioncompare.FunctionComparisonModelListener; +import ghidra.app.plugin.core.functioncompare.FunctionComparisonProvider; import ghidra.program.model.listing.Function; import ghidra.program.model.listing.Program; import ghidra.util.Msg; @@ -128,6 +136,35 @@ public class FunctionComparisonModel { fireModelChanged(); } + /** + * Updates the model with two sets of functions to compare. This will add the + * functions to any existing {@link FunctionComparison comparisons} in the + * model and create new comparisons for functions not represented. + *

+ * Note: It is assumed that when using this method, all source functions can be + * compared to all destination functions; meaning all functions in the source function set will + * be added as sources, and all functions in the destination function set will be added as targets. + * + * @param sourceFunctions + * @param destinationFunctions + */ + public void compareFunctions(Set sourceFunctions, + Set destinationFunctions) { + if (CollectionUtils.isEmpty(sourceFunctions) || + CollectionUtils.isEmpty(destinationFunctions)) { + return; // not an error, just return + } + + for (Function f : sourceFunctions) { + FunctionComparison comparison = new FunctionComparison(); + + comparison.setSource(f); + comparison.addTargets(destinationFunctions); + comparisons.add(comparison); + } + fireModelChanged(); + } + /** * Compares two functions. If a comparison already exists in the model for * the given source, the target will simply be added to it; otherwise a diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/services/FunctionComparisonService.java b/Ghidra/Features/Base/src/main/java/ghidra/app/services/FunctionComparisonService.java index 833b1550c7..c2926ba56d 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/services/FunctionComparisonService.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/services/FunctionComparisonService.java @@ -63,6 +63,19 @@ public interface FunctionComparisonService { */ public FunctionComparisonProvider compareFunctions(Set functions); + /** + * Creates a comparison between two sets of functions, where all the functions in source list can + * be compared against all functions in the destination list. + *

+ * Note that this method will always create a new provider. + * + * @param sourceFunctions + * @param destinationFunctions + * @return the new comparison provider + */ + public FunctionComparisonProvider compareFunctions(Set sourceFunctions, + Set destinationFunctions); + /** * Creates a comparison between two functions, where the source function * will be shown on the left side of the comparison dialog and the target diff --git a/Ghidra/Features/VersionTracking/src/main/help/help/topics/VersionTrackingPlugin/providers/VT_Matches_Table.html b/Ghidra/Features/VersionTracking/src/main/help/help/topics/VersionTrackingPlugin/providers/VT_Matches_Table.html index 9e944764ab..58223bb5a2 100644 --- a/Ghidra/Features/VersionTracking/src/main/help/help/topics/VersionTrackingPlugin/providers/VT_Matches_Table.html +++ b/Ghidra/Features/VersionTracking/src/main/help/help/topics/VersionTrackingPlugin/providers/VT_Matches_Table.html @@ -383,11 +383,19 @@

- -

The Settings action will bring up the version tracking accept and apply options.

- + +

The Compare Functions + action allows users to visually compare selected matched functions. To initiate this action, + select one or more matches from the Version Tracking Matches Table, then choose + Compare Functions from the pop up menu. This will open a new + Function Comparison + table containing a list of source functions and a list of destination functions. + The user can choose one from each list at a time to visually compare to each other. + Note: You cannot compare Data matches or External Functions using this action, so + if you select either of these as matches they will not be populated in the table.

+

Match Filters

diff --git a/Ghidra/Features/VersionTracking/src/main/help/help/topics/VersionTrackingPlugin/providers/VT_Related_Associations_Table.html b/Ghidra/Features/VersionTracking/src/main/help/help/topics/VersionTrackingPlugin/providers/VT_Related_Associations_Table.html index 8993671b89..e88dba6672 100644 --- a/Ghidra/Features/VersionTracking/src/main/help/help/topics/VersionTrackingPlugin/providers/VT_Related_Associations_Table.html +++ b/Ghidra/Features/VersionTracking/src/main/help/help/topics/VersionTrackingPlugin/providers/VT_Related_Associations_Table.html @@ -109,6 +109,24 @@

Actions

+ +
+

Compare Functions

+ +
+

To initiate this action, navigate to the Related Matches table in either the + Destination Tool or the Source Tool. Select one or more matches in the table, then choose + Compare Functions from the right-mouse menu. This action will open a new + Function Comparison + table. The left side of the window will display the function in the current tool location. + The right side of the window will contain a pull-down list with the selected matched + function(s) from the other program. This allows a user to visually compare the function at + the current tool location with the selected matched function(s) from the other program.

+ +

Note: You cannot compare Data matches or External Functions using this action, so + if you select either of these as matches they will not be populated in the table.

+
+

Select Match in VT diff --git a/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/plugin/VTPlugin.java b/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/plugin/VTPlugin.java index 0dc78124f0..e76f605c50 100644 --- a/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/plugin/VTPlugin.java +++ b/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/plugin/VTPlugin.java @@ -16,8 +16,8 @@ package ghidra.feature.vt.gui.plugin; import java.net.URL; -import java.util.List; -import java.util.Set; +import java.util.*; +import java.util.stream.Collectors; import javax.swing.Icon; import javax.swing.JFrame; @@ -42,6 +42,7 @@ import ghidra.framework.model.*; import ghidra.framework.options.Options; import ghidra.framework.options.SaveState; import ghidra.framework.plugintool.*; +import ghidra.framework.plugintool.util.PluginException; import ghidra.framework.plugintool.util.PluginStatus; import ghidra.framework.preferences.Preferences; import ghidra.program.model.address.AddressSetView; @@ -119,6 +120,7 @@ public class VTPlugin extends Plugin { new ImpliedMatchAssociationHook(controller); initializeOptions(); + } private DockingActionIf getToolAction(String actionName) { @@ -141,9 +143,37 @@ public class VTPlugin extends Plugin { @Override protected void init() { + addCustomPlugins(); + maybeShowHelp(); } + private void addCustomPlugins() { + + List names = new ArrayList<>( + List.of("ghidra.app.plugin.core.functioncompare.FunctionComparisonPlugin")); + List plugins = tool.getManagedPlugins(); + Set existingNames = + plugins.stream().map(c -> c.getName()).collect(Collectors.toSet()); + + // Note: we check to see if the plugins we want to add have already been added to the tool. + // We should not needed to do this, but once the tool has been saved with the plugins added, + // they will get added again the next time the tool is loaded. Adding this check here seems + // easier than modifying the default to file to load the plugins, since the amount of xml + // required for that is non-trivial. + try { + for (String className : names) { + if (!existingNames.contains(className)) { + tool.addPlugin(className); + } + } + + } + catch (PluginException e) { + Msg.error(this, "Unable to load plugin", e); + } + } + private void maybeShowHelp() { if (SystemUtilities.isInDevelopmentMode() || SystemUtilities.isInTestingMode()) { return; // don't show help for dev mode diff --git a/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/plugin/VTSubToolManager.java b/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/plugin/VTSubToolManager.java index 35cc45600a..c1434f273a 100644 --- a/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/plugin/VTSubToolManager.java +++ b/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/plugin/VTSubToolManager.java @@ -658,7 +658,7 @@ public class VTSubToolManager implements VTControllerListener, OptionsChangeList * * @return The source tool from the VT session. */ - PluginTool getSourceTool() { + public PluginTool getSourceTool() { return sourceTool; } diff --git a/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/provider/matchtable/VTMatchContext.java b/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/provider/matchtable/VTMatchContext.java index a37d31ba86..4206c03418 100644 --- a/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/provider/matchtable/VTMatchContext.java +++ b/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/provider/matchtable/VTMatchContext.java @@ -15,9 +15,12 @@ */ package ghidra.feature.vt.gui.provider.matchtable; +import java.util.ArrayList; import java.util.List; import docking.DefaultActionContext; +import ghidra.feature.vt.api.main.VTAssociation; +import ghidra.feature.vt.api.main.VTAssociationType; import ghidra.feature.vt.api.main.VTMatch; import ghidra.feature.vt.api.main.VTSession; @@ -37,7 +40,25 @@ public class VTMatchContext extends DefaultActionContext { return selectedMatches; } + public int getSelectedRowCount() { + return selectedMatches.size(); + } + public VTSession getSession() { return session; } + + public List getFunctionMatches() { + List functionMatches = new ArrayList<>(); + + for (VTMatch match : selectedMatches) { + VTAssociation association = match.getAssociation(); + if (association.getType() != VTAssociationType.FUNCTION) { + continue; + } + + functionMatches.add(match); + } + return functionMatches; + } } diff --git a/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/provider/matchtable/VTMatchTableProvider.java b/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/provider/matchtable/VTMatchTableProvider.java index 1a0fd53fd5..b4b3e58b99 100644 --- a/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/provider/matchtable/VTMatchTableProvider.java +++ b/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/provider/matchtable/VTMatchTableProvider.java @@ -15,39 +15,170 @@ */ package ghidra.feature.vt.gui.provider.matchtable; -import static ghidra.feature.vt.api.impl.VTEvent.*; -import static ghidra.feature.vt.gui.actions.TableSelectionTrackingState.*; -import static ghidra.feature.vt.gui.plugin.VTPlugin.*; -import static ghidra.feature.vt.gui.util.VTOptionDefines.*; -import static ghidra.framework.model.DomainObjectEvent.*; +import static ghidra.feature.vt.api.impl.VTEvent.ASSOCIATION_STATUS_CHANGED; +import static ghidra.feature.vt.api.impl.VTEvent.MATCH_SET_ADDED; +import static ghidra.feature.vt.gui.actions.TableSelectionTrackingState.MAINTAIN_SELECTED_ROW_INDEX; +import static ghidra.feature.vt.gui.actions.TableSelectionTrackingState.MAINTAIN_SELECTED_ROW_VALUE; +import static ghidra.feature.vt.gui.actions.TableSelectionTrackingState.NO_SELECTION_TRACKING; +import static ghidra.feature.vt.gui.plugin.VTPlugin.FILTERED_ICON; +import static ghidra.feature.vt.gui.plugin.VTPlugin.UNFILTERED_ICON; +import static ghidra.feature.vt.gui.util.VTOptionDefines.ACCEPT_MATCH_OPTIONS_NAME; +import static ghidra.feature.vt.gui.util.VTOptionDefines.APPLY_DATA_NAME_ON_ACCEPT; +import static ghidra.feature.vt.gui.util.VTOptionDefines.APPLY_FUNCTION_NAME_ON_ACCEPT; +import static ghidra.feature.vt.gui.util.VTOptionDefines.APPLY_IMPLIED_MATCHES_OPTION; +import static ghidra.feature.vt.gui.util.VTOptionDefines.APPLY_MARKUP_OPTIONS_NAME; +import static ghidra.feature.vt.gui.util.VTOptionDefines.AUTO_CREATE_IMPLIED_MATCH; +import static ghidra.feature.vt.gui.util.VTOptionDefines.AUTO_VT_DATA_CORRELATOR; +import static ghidra.feature.vt.gui.util.VTOptionDefines.AUTO_VT_DUPLICATE_FUNCTION_CORRELATOR; +import static ghidra.feature.vt.gui.util.VTOptionDefines.AUTO_VT_EXACT_FUNCTION_CORRELATORS; +import static ghidra.feature.vt.gui.util.VTOptionDefines.AUTO_VT_IMPLIED_MATCH_CORRELATOR; +import static ghidra.feature.vt.gui.util.VTOptionDefines.AUTO_VT_OPTIONS_NAME; +import static ghidra.feature.vt.gui.util.VTOptionDefines.AUTO_VT_REFERENCE_CORRELATORS; +import static ghidra.feature.vt.gui.util.VTOptionDefines.AUTO_VT_SYMBOL_CORRELATOR; +import static ghidra.feature.vt.gui.util.VTOptionDefines.CALLING_CONVENTION; +import static ghidra.feature.vt.gui.util.VTOptionDefines.CALL_FIXUP; +import static ghidra.feature.vt.gui.util.VTOptionDefines.CREATE_IMPLIED_MATCHES_OPTION; +import static ghidra.feature.vt.gui.util.VTOptionDefines.DATA_CORRELATOR_MIN_LEN_OPTION; +import static ghidra.feature.vt.gui.util.VTOptionDefines.DATA_MATCH_DATA_TYPE; +import static ghidra.feature.vt.gui.util.VTOptionDefines.DEFAULT_OPTION_FOR_CALLING_CONVENTION; +import static ghidra.feature.vt.gui.util.VTOptionDefines.DEFAULT_OPTION_FOR_CALL_FIXUP; +import static ghidra.feature.vt.gui.util.VTOptionDefines.DEFAULT_OPTION_FOR_DATA_MATCH_DATA_TYPE; +import static ghidra.feature.vt.gui.util.VTOptionDefines.DEFAULT_OPTION_FOR_EOL_COMMENTS; +import static ghidra.feature.vt.gui.util.VTOptionDefines.DEFAULT_OPTION_FOR_FUNCTION_NAME; +import static ghidra.feature.vt.gui.util.VTOptionDefines.DEFAULT_OPTION_FOR_FUNCTION_RETURN_TYPE; +import static ghidra.feature.vt.gui.util.VTOptionDefines.DEFAULT_OPTION_FOR_FUNCTION_SIGNATURE; +import static ghidra.feature.vt.gui.util.VTOptionDefines.DEFAULT_OPTION_FOR_HIGHEST_NAME_PRIORITY; +import static ghidra.feature.vt.gui.util.VTOptionDefines.DEFAULT_OPTION_FOR_IGNORE_EXCLUDED_MARKUP_ITEMS; +import static ghidra.feature.vt.gui.util.VTOptionDefines.DEFAULT_OPTION_FOR_IGNORE_INCOMPLETE_MARKUP_ITEMS; +import static ghidra.feature.vt.gui.util.VTOptionDefines.DEFAULT_OPTION_FOR_INLINE; +import static ghidra.feature.vt.gui.util.VTOptionDefines.DEFAULT_OPTION_FOR_LABELS; +import static ghidra.feature.vt.gui.util.VTOptionDefines.DEFAULT_OPTION_FOR_NO_RETURN; +import static ghidra.feature.vt.gui.util.VTOptionDefines.DEFAULT_OPTION_FOR_PARAMETER_COMMENTS; +import static ghidra.feature.vt.gui.util.VTOptionDefines.DEFAULT_OPTION_FOR_PARAMETER_DATA_TYPES; +import static ghidra.feature.vt.gui.util.VTOptionDefines.DEFAULT_OPTION_FOR_PARAMETER_NAMES; +import static ghidra.feature.vt.gui.util.VTOptionDefines.DEFAULT_OPTION_FOR_PARAMETER_NAMES_REPLACE_IF_SAME_PRIORITY; +import static ghidra.feature.vt.gui.util.VTOptionDefines.DEFAULT_OPTION_FOR_PLATE_COMMENTS; +import static ghidra.feature.vt.gui.util.VTOptionDefines.DEFAULT_OPTION_FOR_POST_COMMENTS; +import static ghidra.feature.vt.gui.util.VTOptionDefines.DEFAULT_OPTION_FOR_PRE_COMMENTS; +import static ghidra.feature.vt.gui.util.VTOptionDefines.DEFAULT_OPTION_FOR_REPEATABLE_COMMENTS; +import static ghidra.feature.vt.gui.util.VTOptionDefines.DEFAULT_OPTION_FOR_VAR_ARGS; +import static ghidra.feature.vt.gui.util.VTOptionDefines.DISPLAY_APPLY_MARKUP_OPTIONS; +import static ghidra.feature.vt.gui.util.VTOptionDefines.DUPE_FUNCTION_CORRELATOR_MIN_LEN_OPTION; +import static ghidra.feature.vt.gui.util.VTOptionDefines.END_OF_LINE_COMMENT; +import static ghidra.feature.vt.gui.util.VTOptionDefines.FUNCTION_CORRELATOR_MIN_LEN_OPTION; +import static ghidra.feature.vt.gui.util.VTOptionDefines.FUNCTION_NAME; +import static ghidra.feature.vt.gui.util.VTOptionDefines.FUNCTION_RETURN_TYPE; +import static ghidra.feature.vt.gui.util.VTOptionDefines.FUNCTION_SIGNATURE; +import static ghidra.feature.vt.gui.util.VTOptionDefines.HIGHEST_NAME_PRIORITY; +import static ghidra.feature.vt.gui.util.VTOptionDefines.IGNORE_EXCLUDED_MARKUP_ITEMS; +import static ghidra.feature.vt.gui.util.VTOptionDefines.IGNORE_INCOMPLETE_MARKUP_ITEMS; +import static ghidra.feature.vt.gui.util.VTOptionDefines.INLINE; +import static ghidra.feature.vt.gui.util.VTOptionDefines.LABELS; +import static ghidra.feature.vt.gui.util.VTOptionDefines.MAX_CONFLICTS_OPTION; +import static ghidra.feature.vt.gui.util.VTOptionDefines.MIN_VOTES_OPTION; +import static ghidra.feature.vt.gui.util.VTOptionDefines.NO_RETURN; +import static ghidra.feature.vt.gui.util.VTOptionDefines.PARAMETER_COMMENTS; +import static ghidra.feature.vt.gui.util.VTOptionDefines.PARAMETER_DATA_TYPES; +import static ghidra.feature.vt.gui.util.VTOptionDefines.PARAMETER_NAMES; +import static ghidra.feature.vt.gui.util.VTOptionDefines.PARAMETER_NAMES_REPLACE_IF_SAME_PRIORITY; +import static ghidra.feature.vt.gui.util.VTOptionDefines.PLATE_COMMENT; +import static ghidra.feature.vt.gui.util.VTOptionDefines.POST_COMMENT; +import static ghidra.feature.vt.gui.util.VTOptionDefines.PRE_COMMENT; +import static ghidra.feature.vt.gui.util.VTOptionDefines.REF_CORRELATOR_MIN_CONF_OPTION; +import static ghidra.feature.vt.gui.util.VTOptionDefines.REF_CORRELATOR_MIN_SCORE_OPTION; +import static ghidra.feature.vt.gui.util.VTOptionDefines.REPEATABLE_COMMENT; +import static ghidra.feature.vt.gui.util.VTOptionDefines.RUN_DUPE_FUNCTION_OPTION; +import static ghidra.feature.vt.gui.util.VTOptionDefines.RUN_EXACT_DATA_OPTION; +import static ghidra.feature.vt.gui.util.VTOptionDefines.RUN_EXACT_FUNCTION_BYTES_OPTION; +import static ghidra.feature.vt.gui.util.VTOptionDefines.RUN_EXACT_FUNCTION_INST_OPTION; +import static ghidra.feature.vt.gui.util.VTOptionDefines.RUN_EXACT_SYMBOL_OPTION; +import static ghidra.feature.vt.gui.util.VTOptionDefines.RUN_REF_CORRELATORS_OPTION; +import static ghidra.feature.vt.gui.util.VTOptionDefines.SYMBOL_CORRELATOR_MIN_LEN_OPTION; +import static ghidra.feature.vt.gui.util.VTOptionDefines.VAR_ARGS; +import static ghidra.framework.model.DomainObjectEvent.RESTORED; -import java.awt.*; -import java.awt.event.*; -import java.util.*; +import java.awt.Adjustable; +import java.awt.BorderLayout; +import java.awt.Dimension; +import java.awt.Rectangle; +import java.awt.event.FocusAdapter; +import java.awt.event.FocusEvent; +import java.awt.event.MouseEvent; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; import java.util.List; +import java.util.Set; -import javax.swing.*; +import javax.swing.BorderFactory; +import javax.swing.Icon; +import javax.swing.JButton; +import javax.swing.JComponent; +import javax.swing.JPanel; +import javax.swing.JScrollBar; +import javax.swing.JTable; +import javax.swing.ListSelectionModel; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; -import javax.swing.table.*; +import javax.swing.table.TableCellRenderer; +import javax.swing.table.TableColumn; +import javax.swing.table.TableColumnModel; -import docking.*; -import docking.widgets.table.*; +import docking.ActionContext; +import docking.DockingWindowManager; +import docking.WindowPosition; +import docking.action.builder.ActionBuilder; +import docking.widgets.table.AbstractSortedTableModel; +import docking.widgets.table.GTable; +import docking.widgets.table.RowObjectSelectionManager; +import docking.widgets.table.RowObjectTableModel; +import docking.widgets.table.SelectionManager; import docking.widgets.table.threaded.ThreadedTableModel; +import generic.theme.GIcon; +import ghidra.app.services.FunctionComparisonService; import ghidra.feature.vt.api.impl.VTEvent; import ghidra.feature.vt.api.impl.VersionTrackingChangeRecord; -import ghidra.feature.vt.api.main.*; -import ghidra.feature.vt.gui.actions.*; +import ghidra.feature.vt.api.main.VTMarkupItem; +import ghidra.feature.vt.api.main.VTMatch; +import ghidra.feature.vt.api.main.VTSession; +import ghidra.feature.vt.gui.actions.AcceptMatchAction; +import ghidra.feature.vt.gui.actions.ApplyBlockedMatchAction; +import ghidra.feature.vt.gui.actions.ApplyMatchAction; +import ghidra.feature.vt.gui.actions.ChooseMatchTagAction; +import ghidra.feature.vt.gui.actions.ClearMatchAction; +import ghidra.feature.vt.gui.actions.CreateSelectionAction; +import ghidra.feature.vt.gui.actions.EditAllTagsAction; +import ghidra.feature.vt.gui.actions.MatchTableSelectionAction; +import ghidra.feature.vt.gui.actions.RejectMatchAction; +import ghidra.feature.vt.gui.actions.RemoveMatchAction; +import ghidra.feature.vt.gui.actions.RemoveMatchTagAction; +import ghidra.feature.vt.gui.actions.TableSelectionTrackingState; import ghidra.feature.vt.gui.editors.MatchTagCellEditor; -import ghidra.feature.vt.gui.filters.*; +import ghidra.feature.vt.gui.filters.AncillaryFilterDialogComponentProvider; +import ghidra.feature.vt.gui.filters.Filter; import ghidra.feature.vt.gui.filters.Filter.FilterEditingStatus; -import ghidra.feature.vt.gui.plugin.*; -import ghidra.feature.vt.gui.util.*; -import ghidra.feature.vt.gui.util.AbstractVTMatchTableModel.*; -import ghidra.framework.model.*; +import ghidra.feature.vt.gui.filters.FilterDialogModel; +import ghidra.feature.vt.gui.filters.FilterStatusListener; +import ghidra.feature.vt.gui.plugin.VTController; +import ghidra.feature.vt.gui.plugin.VTControllerListener; +import ghidra.feature.vt.gui.plugin.VTPlugin; +import ghidra.feature.vt.gui.plugin.VersionTrackingPluginPackage; +import ghidra.feature.vt.gui.util.AbstractVTMatchTableModel.DestinationLabelTableColumn; +import ghidra.feature.vt.gui.util.AbstractVTMatchTableModel.SourceLabelTableColumn; +import ghidra.feature.vt.gui.util.AbstractVTMatchTableModel.StatusTableColumn; +import ghidra.feature.vt.gui.util.AbstractVTMatchTableModel.TagTableColumn; +import ghidra.feature.vt.gui.util.AllTextFilter; +import ghidra.feature.vt.gui.util.FilterIconFlashTimer; +import ghidra.feature.vt.gui.util.MatchInfo; +import ghidra.feature.vt.gui.util.MatchStatusRenderer; +import ghidra.feature.vt.gui.util.VTSymbolRenderer; +import ghidra.framework.model.DomainObjectChangeRecord; +import ghidra.framework.model.DomainObjectChangedEvent; +import ghidra.framework.model.EventType; import ghidra.framework.options.Options; import ghidra.framework.options.SaveState; import ghidra.framework.plugintool.ComponentProviderAdapter; +import ghidra.program.model.listing.Function; import ghidra.util.HelpLocation; import ghidra.util.SystemUtilities; import ghidra.util.exception.AssertException; @@ -60,6 +191,8 @@ import help.HelpService; public class VTMatchTableProvider extends ComponentProviderAdapter implements FilterDialogModel, VTControllerListener { + private static final Icon COMPARISON_ICON = new GIcon("icon.plugin.functioncompare.new"); + private static final String TITLE = "Version Tracking Matches"; private static final String TABLE_SELECTION_STATE = "TABLE_SELECTION_STATE"; @@ -128,6 +261,42 @@ public class VTMatchTableProvider extends ComponentProviderAdapter addLocalAction(new CreateSelectionAction(controller)); tableSelectionStateAction = new MatchTableSelectionAction(this); addLocalAction(tableSelectionStateAction); + + new ActionBuilder("Compare Functions", getName()).popupMenuPath("Compare Functions") + .popupMenuGroup("Selection") + .popupMenuIcon(COMPARISON_ICON) + .keyBinding("shift c") + .sharedKeyBinding() + .description("Compares the Function(s) with its remote match") + .helpLocation( + new HelpLocation("VersionTrackingPlugin", "Match_Table_Compare_Functions")) + .withContext(VTMatchContext.class) + .enabledWhen(this::isValidFunctionComparison) + .onAction(this::compareFunctions) + .buildAndInstallLocal(this); + } + + private boolean isValidFunctionComparison(VTMatchContext context) { + List functionMatches = context.getFunctionMatches(); + return !functionMatches.isEmpty(); + } + + private void compareFunctions(VTMatchContext c) { + Set sourceFunctions = new HashSet<>(); + Set destinationFunctions = new HashSet<>(); + List matches = c.getFunctionMatches(); + + for (VTMatch match : matches) { + MatchInfo matchInfo = controller.getMatchInfo(match); + + Function sourceFunction = matchInfo.getSourceFunction(); + sourceFunctions.add(sourceFunction); + Function destinationFunction = matchInfo.getDestinationFunction(); + destinationFunctions.add(destinationFunction); + } + + FunctionComparisonService service = tool.getService(FunctionComparisonService.class); + service.compareFunctions(sourceFunctions, destinationFunctions); } // callback method from the MatchTableSelectionAction @@ -601,12 +770,14 @@ public class VTMatchTableProvider extends ComponentProviderAdapter @Override public void optionsChanged(Options options) { - // implemented as ControllerListener. Don't care about options changed right now. + // implemented as ControllerListener. Don't care about options changed right + // now. } @Override public void markupItemSelected(VTMarkupItem markupItem) { - // Do nothing since the matches table doesn't need to respond to the mark-up that is selected. + // Do nothing since the matches table doesn't need to respond to the mark-up + // that is selected. } private void initializeOptions() { @@ -828,8 +999,8 @@ public class VTMatchTableProvider extends ComponentProviderAdapter } /** - * Forces a refilter, even though filtering operations may be disabled. The reload - * is necessary since the model contents may have changed + * Forces a refilter, even though filtering operations may be disabled. The + * reload is necessary since the model contents may have changed */ @Override public void forceRefilter() { @@ -891,20 +1062,22 @@ public class VTMatchTableProvider extends ComponentProviderAdapter } /** - * A class meant to override the default table selection behavior in special situations. + * A class meant to override the default table selection behavior in special + * situations. *

- * Issue 1: Accepting or applying a match can trigger the match to be filtered out - * of the table. The default SelectionManager does not restore the selection for that item, - * as it knows that the item is gone. + * Issue 1: Accepting or applying a match can trigger the match to be + * filtered out of the table. The default SelectionManager does not restore the + * selection for that item, as it knows that the item is gone. *

- * Issue 2: Accepting or applying a match can trigger the match to be moved due to a - * sort operation after the edit. + * Issue 2: Accepting or applying a match can trigger the match to be + * moved due to a sort operation after the edit. *

- * Desired Behavior: Have the selection restored to the previous location, even if the - * item is moved or removed. + * Desired Behavior: Have the selection restored to the previous + * location, even if the item is moved or removed. *

- * Creating this object will cancel the default behavior. Calling restoreSelection - * will set the new selection, depending upon the conditions described above. + * Creating this object will cancel the default behavior. Calling + * restoreSelection will set the new selection, depending upon the + * conditions described above. */ private class SelectionOverrideMemento { private final int row; @@ -946,13 +1119,14 @@ public class VTMatchTableProvider extends ComponentProviderAdapter ListSelectionModel selectionModel = matchesTable.getSelectionModel(); int rowToSelect = row; if (row > matchesTableModel.getRowCount()) { - // The model has shrunk. Not sure what the best action is? + // The model has shrunk. Not sure what the best action is? tryToSelectMatch(selectionModel);// this only works if we are tracking by match and not index return; } // At this point the selection model may still believe that its selection is the - // value we are setting. Calling clearSelection() will kick the model. Without the + // value we are setting. Calling clearSelection() will kick the model. Without + // the // kick, the setSelectionInterval() call we make may ultimately have no effect. selectionModel.clearSelection(); @@ -989,8 +1163,8 @@ public class VTMatchTableProvider extends ComponentProviderAdapter } /** - * Override the built-in SelectionManager so that we can respond to the current table - * selection mode. + * Override the built-in SelectionManager so that we can respond to the current + * table selection mode. */ private class VTMatchTableSelectionManager extends RowObjectSelectionManager { VTMatchTableSelectionManager(JTable table, AbstractSortedTableModel tableModel) { diff --git a/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/provider/onetomany/VTMatchOneToManyContext.java b/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/provider/onetomany/VTMatchOneToManyContext.java index 250c810554..d6465522a5 100644 --- a/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/provider/onetomany/VTMatchOneToManyContext.java +++ b/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/provider/onetomany/VTMatchOneToManyContext.java @@ -15,9 +15,12 @@ */ package ghidra.feature.vt.gui.provider.onetomany; +import java.util.ArrayList; import java.util.List; import docking.DefaultActionContext; +import ghidra.feature.vt.api.main.VTAssociation; +import ghidra.feature.vt.api.main.VTAssociationType; import ghidra.feature.vt.api.main.VTMatch; public class VTMatchOneToManyContext extends DefaultActionContext { @@ -32,4 +35,23 @@ public class VTMatchOneToManyContext extends DefaultActionContext { public List getSelectedMatches() { return selectedItems; } + + public int getSelectedRowCount() { + return selectedItems.size(); + } + + public List getFunctionMatches() { + List functionMatches = new ArrayList<>(); + + for (VTMatch match : selectedItems) { + VTAssociation association = match.getAssociation(); + if (association.getType() != VTAssociationType.FUNCTION) { + continue; + } + + functionMatches.add(match); + } + return functionMatches; + } + } diff --git a/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/provider/onetomany/VTMatchOneToManyTableProvider.java b/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/provider/onetomany/VTMatchOneToManyTableProvider.java index b8f741c8b1..a3019f2a23 100644 --- a/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/provider/onetomany/VTMatchOneToManyTableProvider.java +++ b/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/provider/onetomany/VTMatchOneToManyTableProvider.java @@ -15,39 +15,70 @@ */ package ghidra.feature.vt.gui.provider.onetomany; -import java.awt.*; +import java.awt.Adjustable; +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Rectangle; import java.awt.event.MouseEvent; -import java.util.*; +import java.util.ArrayList; +import java.util.HashSet; import java.util.List; +import java.util.Set; -import javax.swing.*; +import javax.swing.Icon; +import javax.swing.JComponent; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JScrollBar; +import javax.swing.JTable; +import javax.swing.JToggleButton; +import javax.swing.ListSelectionModel; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; -import javax.swing.table.*; +import javax.swing.table.TableCellRenderer; +import javax.swing.table.TableColumn; +import javax.swing.table.TableColumnModel; import docking.ActionContext; +import docking.action.builder.ActionBuilder; import docking.widgets.label.GDLabel; import docking.widgets.table.GTable; import docking.widgets.table.RowObjectTableModel; import docking.widgets.table.threaded.ThreadedTableModel; import generic.theme.GColor; import generic.theme.GIcon; +import ghidra.app.services.FunctionComparisonService; import ghidra.feature.vt.api.impl.VTEvent; -import ghidra.feature.vt.api.main.*; -import ghidra.feature.vt.gui.actions.*; -import ghidra.feature.vt.gui.filters.*; +import ghidra.feature.vt.api.main.VTMarkupItem; +import ghidra.feature.vt.api.main.VTMatch; +import ghidra.feature.vt.api.main.VTSession; +import ghidra.feature.vt.gui.actions.AcceptMatchAction; +import ghidra.feature.vt.gui.actions.ClearMatchAction; +import ghidra.feature.vt.gui.actions.SetVTMatchFromOneToManyAction; +import ghidra.feature.vt.gui.filters.Filter; import ghidra.feature.vt.gui.filters.Filter.FilterEditingStatus; -import ghidra.feature.vt.gui.plugin.*; +import ghidra.feature.vt.gui.filters.FilterDialogModel; +import ghidra.feature.vt.gui.filters.FilterStatusListener; +import ghidra.feature.vt.gui.plugin.VTController; +import ghidra.feature.vt.gui.plugin.VTControllerListener; +import ghidra.feature.vt.gui.plugin.VTPlugin; +import ghidra.feature.vt.gui.plugin.VTSubToolManager; +import ghidra.feature.vt.gui.plugin.VTSubToolManagerListener; import ghidra.feature.vt.gui.provider.markuptable.DisplayableListingAddress; import ghidra.feature.vt.gui.provider.matchtable.MatchTableRenderer; import ghidra.feature.vt.gui.util.AbstractVTMatchTableModel.StatusTableColumn; import ghidra.feature.vt.gui.util.MatchInfo; import ghidra.feature.vt.gui.util.MatchStatusRenderer; -import ghidra.framework.model.*; +import ghidra.framework.model.DomainObjectChangeRecord; +import ghidra.framework.model.DomainObjectChangedEvent; +import ghidra.framework.model.DomainObjectEvent; +import ghidra.framework.model.EventType; import ghidra.framework.options.Options; import ghidra.framework.plugintool.ComponentProviderAdapter; import ghidra.framework.plugintool.PluginTool; import ghidra.program.model.address.Address; +import ghidra.program.model.listing.Function; import ghidra.program.model.listing.Program; import ghidra.program.model.symbol.Symbol; import ghidra.program.model.symbol.SymbolTable; @@ -58,24 +89,25 @@ import ghidra.util.table.GhidraTable; import ghidra.util.table.GhidraThreadedTablePanel; /** - * The docking window that provides a table of the other tool's function matches for the function - * containing the current cursor location in this tool's listing. + * The docking window that provides a table of the other tool's function matches + * for the function containing the current cursor location in this tool's + * listing. */ public abstract class VTMatchOneToManyTableProvider extends ComponentProviderAdapter implements FilterDialogModel, VTControllerListener, VTSubToolManagerListener { private static final String TITLE_PREFIX = "Version Tracking Matches for "; private static final Icon ICON = new GIcon("icon.version.tracking.provider.one.to.many"); + private static final Icon COMPARISON_ICON = new GIcon("icon.plugin.functioncompare.new"); - protected static final Color LOCAL_INFO_FOREGROUND_COLOR = - new GColor("color.fg.version.tracking.function.match.local.info"); + protected static final Color LOCAL_INFO_FOREGROUND_COLOR = new GColor( + "color.fg.version.tracking.function.match.local.info"); private JComponent component; private MatchThreadedTablePanel tablePanel; protected GhidraTable matchesTable; private ListSelectionListener matchSelectionListener; protected VTMatchOneToManyTableModel oneToManyTableModel; - private JToggleButton ancillaryFilterButton; private Set> filters = new HashSet<>(); private FilterStatusListener refilterListener = new RefilterListener(); @@ -100,7 +132,8 @@ public abstract class VTMatchOneToManyTableProvider extends ComponentProviderAda private VTMatch pendingMatchSelection; public VTMatchOneToManyTableProvider(PluginTool tool, VTController controller, - VTSubToolManager subToolManager, boolean isSource) { + VTSubToolManager subToolManager, + boolean isSource) { super(tool, TITLE_PREFIX + (isSource ? "Source" : "Destination"), VTPlugin.OWNER); this.controller = controller; this.subToolManager = subToolManager; @@ -130,6 +163,43 @@ public abstract class VTMatchOneToManyTableProvider extends ComponentProviderAda addLocalAction(new SetVTMatchFromOneToManyAction(controller, true)); addLocalAction(new ClearMatchAction(controller)); addLocalAction(new AcceptMatchAction(controller)); + new ActionBuilder("Compare Functions", getName()) + .popupMenuPath("Compare Functions") + .popupMenuIcon(COMPARISON_ICON) + .popupMenuGroup("AAA_VT_Main") + .keyBinding("shift c") + .sharedKeyBinding() + .description("Compares the Function(s) with its remote match") + .helpLocation(new HelpLocation("VersionTrackingPlugin", "Compare_Functions")) + .withContext(VTMatchOneToManyContext.class) + .enabledWhen(this::isValidFunctionComparison) + .onAction(this::compareFunctions) + .buildAndInstallLocal(this); + } + + private boolean isValidFunctionComparison(VTMatchOneToManyContext context) { + List functionMatches = context.getFunctionMatches(); + return !functionMatches.isEmpty(); + } + + private void compareFunctions(VTMatchOneToManyContext c) { + List selectedMatches = c.getSelectedMatches(); + + for (VTMatch match : selectedMatches) { + MatchInfo matchInfo = controller.getMatchInfo(match); + + // Whichever codebrowser we are currently in, is what will be on the left + // side of the compare functions window. + Function leftFunction = matchInfo.getSourceFunction(), + rightFunction = matchInfo.getDestinationFunction(); + if (!isSource) { + leftFunction = matchInfo.getDestinationFunction(); + rightFunction = matchInfo.getSourceFunction(); + } + + FunctionComparisonService service = tool.getService(FunctionComparisonService.class); + service.compareFunctions(leftFunction, rightFunction); + } } @Override @@ -292,9 +362,9 @@ public abstract class VTMatchOneToManyTableProvider extends ComponentProviderAda private List getSelectedMatches() { List list = new ArrayList<>(); - int selectedRowCount = matchesTable.getSelectedRowCount(); - if (selectedRowCount == 1) { - int row = matchesTable.getSelectedRow(); + int[] selectedRows = matchesTable.getSelectedRows(); + + for (int row : selectedRows) { VTMatch mySelectedMatch = oneToManyTableModel.getRowObject(row); list.add(mySelectedMatch); } @@ -467,7 +537,8 @@ public abstract class VTMatchOneToManyTableProvider extends ComponentProviderAda @Override public void markupItemSelected(VTMarkupItem markupItem) { - // Do nothing since the one to many match table doesn't need to respond to the mark-up that is selected. + // Do nothing since the one to many match table doesn't need to respond to the + // mark-up that is selected. } //==================================================================================================