GP-3696 - cleaning up function compare windows.

This commit is contained in:
ghidragon 2024-05-23 11:26:17 -04:00
parent 770f5447e1
commit 5ea8e97805
77 changed files with 4065 additions and 5654 deletions

View file

@ -852,16 +852,6 @@ public abstract class AbstractCodeBrowserPlugin<P extends CodeViewerProvider> ex
return getAddressIndexMap().getAddress(index); return getAddressIndexMap().getAddress(index);
} }
@Override
public void formatModelAdded(FieldFormatModel model) {
// uninterested
}
@Override
public void formatModelRemoved(FieldFormatModel model) {
// uninterested
}
@Override @Override
public void formatModelChanged(FieldFormatModel model) { public void formatModelChanged(FieldFormatModel model) {
tool.setConfigChanged(true); tool.setConfigChanged(true);

View file

@ -1,184 +0,0 @@
/* ###
* 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.functioncompare;
import ghidra.program.model.address.AddressSet;
import ghidra.program.model.address.AddressSetView;
import ghidra.program.model.listing.*;
/**
* Defines the information being displayed in the left or right panels
* of a {@link FunctionComparisonPanel}, which can display either
* {@link Function functions}, {@link Data data}, or specified
* {@link AddressSet address sets}. At any given time, only one of the
* Function or Data attributes may be set; the other will be
* set to null.
*/
class FunctionComparisonData {
protected Program program;
protected Function function;
protected Data data;
protected AddressSetView addressSet = new AddressSet();
/**
* Returns the program for this model
*
* @return the program, or null if not set
*/
public Program getProgram() {
return program;
}
/**
* Sets the program for this model
*
* @param program the program to set
*/
public void setProgram(Program program) {
this.program = program;
}
/**
* Returns the function for this model
*
* @return the function, or null if not set
*/
public Function getFunction() {
return function;
}
/**
* Sets the function for this model
*
* @param function the function to set
*/
public void setFunction(Function function) {
if (function == null) {
clear();
return;
}
this.function = function;
this.data = null;
this.program = function.getProgram();
this.addressSet = function.getBody();
}
/**
* Returns the data for this model
*
* @return the data, or null if not set
*/
public Data getData() {
return data;
}
/**
* Sets the data for this model
*
* @param data the data to set
*/
public void setData(Data data) {
if (data == null) {
clear();
return;
}
this.data = data;
this.function = null;
this.program = data.getProgram();
this.addressSet = new AddressSet(data.getMinAddress(), data.getMaxAddress());
}
/**
* Returns the address set for this model
*
* @return the address set, or null if not set
*/
public AddressSetView getAddressSet() {
return addressSet;
}
/**
* Sets the address for this model
*
* @param addressSet the addressSet to set
*/
public void setAddressSet(AddressSetView addressSet) {
this.addressSet = addressSet;
this.data = null;
this.function = null;
}
/**
* Returns true if the data being managed by this model is of type
* {@link Data}
*
* @return true if this model is set to display {@link Data}
*/
public boolean isData() {
return data != null;
}
/**
* Returns true if the data being managed by this model is of type
* {@link Function}
*
* @return true if this model is set to display a {@link Function}
*/
public boolean isFunction() {
return function != null;
}
/**
* Returns true if this class holds no function, data or address set
* information
*
* @return true if this class holds no function, data or address set
* information
*/
public boolean isEmpty() {
return function == null && data == null && addressSet == null;
}
/**
* Resets all fields in this model to a nominal state
*/
public void clear() {
this.function = null;
this.data = null;
this.addressSet = new AddressSet();
this.program = null;
}
public String toString() {
String str = "";
if (function != null) {
str = function.getName();
}
else if (data != null) {
str = data.getAddress().toString();
}
else if (addressSet != null) {
str = addressSet.toString();
}
else {
str = "none";
}
return str;
}
}

View file

@ -15,6 +15,9 @@
*/ */
package ghidra.app.plugin.core.functioncompare; package ghidra.app.plugin.core.functioncompare;
import static ghidra.app.util.viewer.util.ComparisonData.*;
import static ghidra.util.datastruct.Duo.Side.*;
import java.awt.*; import java.awt.*;
import java.awt.event.MouseEvent; import java.awt.event.MouseEvent;
import java.lang.reflect.Constructor; import java.lang.reflect.Constructor;
@ -29,11 +32,10 @@ import javax.swing.event.ChangeListener;
import docking.ActionContext; import docking.ActionContext;
import docking.ComponentProvider; import docking.ComponentProvider;
import docking.action.*; import docking.action.*;
import docking.widgets.fieldpanel.internal.FieldPanelCoordinator;
import docking.widgets.tabbedpane.DockingTabRenderer; import docking.widgets.tabbedpane.DockingTabRenderer;
import generic.theme.GIcon; import generic.theme.GIcon;
import ghidra.app.util.viewer.listingpanel.ListingCodeComparisonPanel; import ghidra.app.util.viewer.listingpanel.ListingCodeComparisonPanel;
import ghidra.app.util.viewer.util.CodeComparisonPanel; import ghidra.app.util.viewer.util.*;
import ghidra.framework.options.SaveState; import ghidra.framework.options.SaveState;
import ghidra.framework.plugintool.ComponentProviderAdapter; import ghidra.framework.plugintool.ComponentProviderAdapter;
import ghidra.framework.plugintool.PluginTool; import ghidra.framework.plugintool.PluginTool;
@ -43,6 +45,7 @@ import ghidra.program.model.listing.*;
import ghidra.util.HelpLocation; import ghidra.util.HelpLocation;
import ghidra.util.Msg; import ghidra.util.Msg;
import ghidra.util.classfinder.ClassSearcher; import ghidra.util.classfinder.ClassSearcher;
import ghidra.util.datastruct.Duo;
import help.Help; import help.Help;
import help.HelpService; import help.HelpService;
@ -54,11 +57,9 @@ import help.HelpService;
* be compared, use a {@link MultiFunctionComparisonPanel} * be compared, use a {@link MultiFunctionComparisonPanel}
*/ */
public class FunctionComparisonPanel extends JPanel implements ChangeListener { public class FunctionComparisonPanel extends JPanel implements ChangeListener {
private static final String ORIENTATION_PROPERTY_NAME = "ORIENTATION";
private FunctionComparisonData leftComparisonData; private static final String DEFAULT_CODE_COMPARISON_VIEW = ListingCodeComparisonPanel.NAME;
private FunctionComparisonData rightComparisonData;
private static final String DEFAULT_CODE_COMPARISON_VIEW = ListingCodeComparisonPanel.TITLE;
private static final String COMPARISON_VIEW_DISPLAYED = "COMPARISON_VIEW_DISPLAYED"; private static final String COMPARISON_VIEW_DISPLAYED = "COMPARISON_VIEW_DISPLAYED";
private static final String CODE_COMPARISON_LOCK_SCROLLING_TOGETHER = private static final String CODE_COMPARISON_LOCK_SCROLLING_TOGETHER =
"CODE_COMPARISON_LOCK_SCROLLING_TOGETHER"; "CODE_COMPARISON_LOCK_SCROLLING_TOGETHER";
@ -78,26 +79,22 @@ public class FunctionComparisonPanel extends JPanel implements ChangeListener {
private Map<String, JComponent> tabNameToComponentMap; private Map<String, JComponent> tabNameToComponentMap;
protected PluginTool tool; protected PluginTool tool;
protected ComponentProviderAdapter provider; protected ComponentProviderAdapter provider;
private List<CodeComparisonPanel<? extends FieldPanelCoordinator>> codeComparisonPanels; private List<CodeComparisonPanel> codeComparisonPanels;
private ToggleScrollLockAction toggleScrollLockAction; private ToggleScrollLockAction toggleScrollLockAction;
private boolean syncScrolling = false; private boolean syncScrolling = false;
private Duo<ComparisonData> comparisonData = new Duo<ComparisonData>();
/** /**
* Constructor * Constructor
* *
* @param provider the GUI provider that includes this panel * @param provider the GUI provider that includes this panel
* @param tool the tool containing this panel * @param tool the tool containing this panel
* @param leftFunction the function displayed in the left side of the panel
* @param rightFunction the function displayed in the right side of the panel
*/ */
public FunctionComparisonPanel(ComponentProviderAdapter provider, PluginTool tool, public FunctionComparisonPanel(ComponentProviderAdapter provider, PluginTool tool) {
Function leftFunction, Function rightFunction) {
this.provider = provider; this.provider = provider;
this.tool = tool; this.tool = tool;
this.leftComparisonData = new FunctionComparisonData(); this.comparisonData = new Duo<>(EMPTY, EMPTY);
this.rightComparisonData = new FunctionComparisonData();
this.leftComparisonData.setFunction(leftFunction);
this.rightComparisonData.setFunction(rightFunction);
this.codeComparisonPanels = getCodeComparisonPanels(); this.codeComparisonPanels = getCodeComparisonPanels();
tabNameToComponentMap = new HashMap<>(); tabNameToComponentMap = new HashMap<>();
createMainPanel(); createMainPanel();
@ -113,15 +110,11 @@ public class FunctionComparisonPanel extends JPanel implements ChangeListener {
* @param rightFunction The function for the right side of the panel * @param rightFunction The function for the right side of the panel
*/ */
public void loadFunctions(Function leftFunction, Function rightFunction) { public void loadFunctions(Function leftFunction, Function rightFunction) {
leftComparisonData.setFunction(leftFunction); ComparisonData left =
rightComparisonData.setFunction(rightFunction); leftFunction == null ? EMPTY : new FunctionComparisonData(leftFunction);
ComparisonData right =
CodeComparisonPanel<? extends FieldPanelCoordinator> activePanel = rightFunction == null ? EMPTY : new FunctionComparisonData(rightFunction);
getActiveComparisonPanel(); loadComparisons(left, right);
if (activePanel != null) {
activePanel.loadFunctions(leftComparisonData.getFunction(),
rightComparisonData.getFunction());
}
} }
/** /**
@ -131,14 +124,19 @@ public class FunctionComparisonPanel extends JPanel implements ChangeListener {
* @param rightData The data for the right side of the panel * @param rightData The data for the right side of the panel
*/ */
public void loadData(Data leftData, Data rightData) { public void loadData(Data leftData, Data rightData) {
leftComparisonData.setData(leftData); ComparisonData left = new DataComparisonData(leftData, rightData.getLength());
rightComparisonData.setData(rightData); ComparisonData right = new DataComparisonData(rightData, leftData.getLength());
loadComparisons(left, right);
CodeComparisonPanel<? extends FieldPanelCoordinator> activePanel =
getActiveComparisonPanel();
if (activePanel != null) {
activePanel.loadData(leftComparisonData.getData(), rightComparisonData.getData());
} }
public void loadComparisons(ComparisonData left, ComparisonData right) {
comparisonData = new Duo<>(left, right);
CodeComparisonPanel activePanel = getActiveComparisonPanel();
if (activePanel != null) {
activePanel.loadComparisons(left, right);
}
} }
/** /**
@ -154,17 +152,9 @@ public class FunctionComparisonPanel extends JPanel implements ChangeListener {
*/ */
public void loadAddresses(Program leftProgram, Program rightProgram, public void loadAddresses(Program leftProgram, Program rightProgram,
AddressSetView leftAddresses, AddressSetView rightAddresses) { AddressSetView leftAddresses, AddressSetView rightAddresses) {
leftComparisonData.setAddressSet(leftAddresses); ComparisonData left = new AddressSetComparisonData(leftProgram, leftAddresses);
rightComparisonData.setAddressSet(rightAddresses); ComparisonData right = new AddressSetComparisonData(rightProgram, rightAddresses);
leftComparisonData.setProgram(leftProgram); loadComparisons(left, right);
rightComparisonData.setProgram(rightProgram);
CodeComparisonPanel<? extends FieldPanelCoordinator> activePanel =
getActiveComparisonPanel();
if (activePanel != null) {
activePanel.loadAddresses(leftComparisonData.getProgram(),
rightComparisonData.getProgram(), leftComparisonData.getAddressSet(),
rightComparisonData.getAddressSet());
}
} }
/** /**
@ -183,35 +173,23 @@ public class FunctionComparisonPanel extends JPanel implements ChangeListener {
* @return the description * @return the description
*/ */
public String getDescription() { public String getDescription() {
Function leftFunc = leftComparisonData.getFunction(); String leftShort = comparisonData.get(LEFT).getShortDescription();
Function rightFunc = rightComparisonData.getFunction(); String rightShort = comparisonData.get(LEFT).getShortDescription();
Data leftData = leftComparisonData.getData();
Data rightData = rightComparisonData.getData();
if (leftFunc != null && rightFunc != null) { return leftShort + " & " + rightShort;
return leftFunc.getName(true) + " & " + rightFunc.getName(true);
}
if (leftData != null && rightData != null) {
return leftData.getDataType().getName() + " & " + rightData.getDataType().getName();
}
// Otherwise give a simple description for address sets
return "Nothing selected";
} }
/** /**
* Clear both sides of this panel * Clear both sides of this panel
*/ */
public void clear() { public void clear() {
leftComparisonData.clear(); comparisonData = new Duo<>(EMPTY, EMPTY);
rightComparisonData.clear();
// Setting the addresses to be displayed to null effectively clears // Setting the addresses to be displayed to null effectively clears
// the display // the display
CodeComparisonPanel<? extends FieldPanelCoordinator> activePanel = CodeComparisonPanel activePanel = getActiveComparisonPanel();
getActiveComparisonPanel();
if (activePanel != null) { if (activePanel != null) {
activePanel.loadAddresses(null, null, null, null); activePanel.clearComparisons();
} }
} }
@ -222,7 +200,7 @@ public class FunctionComparisonPanel extends JPanel implements ChangeListener {
* @return true if the comparison window has no information to display * @return true if the comparison window has no information to display
*/ */
public boolean isEmpty() { public boolean isEmpty() {
return leftComparisonData.isEmpty() || rightComparisonData.isEmpty(); return comparisonData.get(LEFT).isEmpty() || comparisonData.get(RIGHT).isEmpty();
} }
/** /**
@ -232,10 +210,9 @@ public class FunctionComparisonPanel extends JPanel implements ChangeListener {
* @return the comparison panel or null * @return the comparison panel or null
*/ */
public ListingCodeComparisonPanel getDualListingPanel() { public ListingCodeComparisonPanel getDualListingPanel() {
for (CodeComparisonPanel<? extends FieldPanelCoordinator> codeComparisonPanel : codeComparisonPanels) { for (CodeComparisonPanel codeComparisonPanel : codeComparisonPanels) {
JComponent component = codeComparisonPanel.getComponent(); if (codeComparisonPanel instanceof ListingCodeComparisonPanel listingPanel) {
if (component instanceof ListingCodeComparisonPanel) { return listingPanel;
return (ListingCodeComparisonPanel) component;
} }
} }
return null; return null;
@ -300,11 +277,26 @@ public class FunctionComparisonPanel extends JPanel implements ChangeListener {
tabbedPane.removeAll(); tabbedPane.removeAll();
setVisible(false); setVisible(false);
for (CodeComparisonPanel<? extends FieldPanelCoordinator> codeComparisonPanel : codeComparisonPanels) { for (CodeComparisonPanel codeComparisonPanel : codeComparisonPanels) {
codeComparisonPanel.dispose(); codeComparisonPanel.dispose();
} }
} }
void programClosed(Program program) {
for (CodeComparisonPanel codeComparisonPanel : codeComparisonPanels) {
codeComparisonPanel.programClosed(program);
}
}
CodeComparisonPanel getCodeComparisonPanelByName(String name) {
for (CodeComparisonPanel codeComparisonPanel : codeComparisonPanels) {
if (name.equals(codeComparisonPanel.getName())) {
return codeComparisonPanel;
}
}
return null;
}
/** /**
* Create the main tabbed panel * Create the main tabbed panel
*/ */
@ -317,12 +309,9 @@ public class FunctionComparisonPanel extends JPanel implements ChangeListener {
add(tabbedPane, BorderLayout.CENTER); add(tabbedPane, BorderLayout.CENTER);
setPreferredSize(new Dimension(200, 300)); setPreferredSize(new Dimension(200, 300));
for (CodeComparisonPanel<? extends FieldPanelCoordinator> codeComparisonPanel : codeComparisonPanels) { for (CodeComparisonPanel codeComparisonPanel : codeComparisonPanels) {
codeComparisonPanel.loadFunctions(leftComparisonData.getFunction(), tabbedPane.add(codeComparisonPanel.getName(), codeComparisonPanel);
rightComparisonData.getFunction()); tabNameToComponentMap.put(codeComparisonPanel.getName(), codeComparisonPanel);
JComponent component = codeComparisonPanel.getComponent();
tabbedPane.add(codeComparisonPanel.getTitle(), component);
tabNameToComponentMap.put(codeComparisonPanel.getTitle(), component);
} }
} }
@ -331,24 +320,11 @@ public class FunctionComparisonPanel extends JPanel implements ChangeListener {
* the appropriate data to be compared. * the appropriate data to be compared.
*/ */
private void tabChanged() { private void tabChanged() {
CodeComparisonPanel<? extends FieldPanelCoordinator> activePanel = CodeComparisonPanel activePanel = getActiveComparisonPanel();
getActiveComparisonPanel();
if (activePanel == null) { if (activePanel == null) {
return; // initializing return; // initializing
} }
activePanel.loadComparisons(comparisonData.get(LEFT), comparisonData.get(RIGHT));
if (leftComparisonData.isFunction() || rightComparisonData.isFunction()) {
activePanel.loadFunctions(leftComparisonData.getFunction(),
rightComparisonData.getFunction());
}
else if (leftComparisonData.isData() || rightComparisonData.isData()) {
activePanel.loadData(leftComparisonData.getData(), rightComparisonData.getData());
}
else {
activePanel.loadAddresses(leftComparisonData.getProgram(),
rightComparisonData.getProgram(), leftComparisonData.getAddressSet(),
rightComparisonData.getAddressSet());
}
} }
/** /**
@ -357,100 +333,8 @@ public class FunctionComparisonPanel extends JPanel implements ChangeListener {
* @return the currently selected comparison panel, or null if nothing * @return the currently selected comparison panel, or null if nothing
* selected * selected
*/ */
private CodeComparisonPanel<? extends FieldPanelCoordinator> getActiveComparisonPanel() { private CodeComparisonPanel getActiveComparisonPanel() {
JComponent c = (JComponent) tabbedPane.getSelectedComponent(); return (CodeComparisonPanel) tabbedPane.getSelectedComponent();
for (CodeComparisonPanel<? extends FieldPanelCoordinator> codeComparisonPanel : codeComparisonPanels) {
JComponent component = codeComparisonPanel.getComponent();
if (c == component) {
return codeComparisonPanel;
}
}
return null;
}
/**
* Returns the comparison data object for the left panel
*
* @return the comparison data object for the left panel
*/
public FunctionComparisonData getLeftComparisonData() {
return leftComparisonData;
}
/**
* Returns the comparison data object for the right panel
*
* @return the comparison data object for the right panel
*/
public FunctionComparisonData getRightComparisonData() {
return rightComparisonData;
}
/**
* Gets the function currently displayed in the left side of this panel
*
* @return the left function or null
*/
public Function getLeftFunction() {
return leftComparisonData.getFunction();
}
/**
* Sets the function to display in the left side of this panel
*
* @param function the function to display
*/
protected void setLeftFunction(Function function) {
loadFunctions(function, rightComparisonData.getFunction());
}
/**
* Gets the function currently displayed in the right side of this panel
*
* @return the right function or null
*/
public Function getRightFunction() {
return rightComparisonData.getFunction();
}
/**
* Sets the function to display in the right side of this panel
*
* @param function the function to display
*/
protected void setRightFunction(Function function) {
loadFunctions(leftComparisonData.getFunction(), function);
}
/**
* Gets the data displayed in the left side of this panel
*
* @return the left data or null
*/
public Data getLeftData() {
return leftComparisonData.getData();
}
/**
* Gets the data displayed in the right side of this panel
*
* @return the right data
*/
public Data getRightData() {
return rightComparisonData.getData();
}
/**
* Enables/disables mouse navigation for all the CodeComparisonPanels
* displayed by this panel
*
* @param enabled true to enable mouse navigation in the panels
*/
public void setMouseNavigationEnabled(boolean enabled) {
for (CodeComparisonPanel<? extends FieldPanelCoordinator> codeComparisonPanel : codeComparisonPanels) {
codeComparisonPanel.setMouseNavigationEnabled(enabled);
}
} }
/** /**
@ -466,9 +350,10 @@ public class FunctionComparisonPanel extends JPanel implements ChangeListener {
setCurrentTabbedComponent(currentTabView); setCurrentTabbedComponent(currentTabView);
setScrollingSyncState( setScrollingSyncState(
saveState.getBoolean(prefix + CODE_COMPARISON_LOCK_SCROLLING_TOGETHER, true)); saveState.getBoolean(prefix + CODE_COMPARISON_LOCK_SCROLLING_TOGETHER, true));
ListingCodeComparisonPanel dualListingPanel = getDualListingPanel();
if (dualListingPanel != null) { for (CodeComparisonPanel panel : codeComparisonPanels) {
dualListingPanel.readConfigState(prefix, saveState); String key = prefix + panel.getName() + ORIENTATION_PROPERTY_NAME;
panel.setSideBySide(saveState.getBoolean(key, true));
} }
} }
@ -485,10 +370,13 @@ public class FunctionComparisonPanel extends JPanel implements ChangeListener {
saveState.putString(prefix + COMPARISON_VIEW_DISPLAYED, getCurrentComponentName()); saveState.putString(prefix + COMPARISON_VIEW_DISPLAYED, getCurrentComponentName());
} }
saveState.putBoolean(prefix + CODE_COMPARISON_LOCK_SCROLLING_TOGETHER, isScrollingSynced()); saveState.putBoolean(prefix + CODE_COMPARISON_LOCK_SCROLLING_TOGETHER, isScrollingSynced());
ListingCodeComparisonPanel dualListingPanel = getDualListingPanel();
if (dualListingPanel != null) { for (CodeComparisonPanel panel : codeComparisonPanels) {
dualListingPanel.writeConfigState(prefix, saveState); String key = prefix + panel.getName() + ORIENTATION_PROPERTY_NAME;
boolean sideBySide = panel.isSideBySide();
saveState.putBoolean(key, sideBySide);
} }
} }
/** /**
@ -499,18 +387,18 @@ public class FunctionComparisonPanel extends JPanel implements ChangeListener {
*/ */
public DockingAction[] getCodeComparisonActions() { public DockingAction[] getCodeComparisonActions() {
ArrayList<DockingAction> dockingActionList = new ArrayList<>(); ArrayList<DockingAction> dockingActionList = new ArrayList<>();
// Get actions for this functionComparisonPanel // Get actions for this functionComparisonPanel
DockingAction[] functionComparisonActions = getActions(); DockingAction[] functionComparisonActions = getActions();
for (DockingAction dockingAction : functionComparisonActions) { for (DockingAction dockingAction : functionComparisonActions) {
dockingActionList.add(dockingAction); dockingActionList.add(dockingAction);
} }
// Get actions for each CodeComparisonPanel // Get actions for each CodeComparisonPanel
for (CodeComparisonPanel<? extends FieldPanelCoordinator> codeComparisonPanel : codeComparisonPanels) { for (CodeComparisonPanel codeComparisonPanel : codeComparisonPanels) {
DockingAction[] actions = codeComparisonPanel.getActions(); dockingActionList.addAll(codeComparisonPanel.getActions());
for (DockingAction dockingAction : actions) {
dockingActionList.add(dockingAction);
}
} }
return dockingActionList.toArray(new DockingAction[dockingActionList.size()]); return dockingActionList.toArray(new DockingAction[dockingActionList.size()]);
} }
@ -524,8 +412,8 @@ public class FunctionComparisonPanel extends JPanel implements ChangeListener {
public void setTitlePrefixes(String leftTitlePrefix, String rightTitlePrefix) { public void setTitlePrefixes(String leftTitlePrefix, String rightTitlePrefix) {
Component[] components = tabbedPane.getComponents(); Component[] components = tabbedPane.getComponents();
for (Component component : components) { for (Component component : components) {
if (component instanceof CodeComparisonPanel<?>) { if (component instanceof CodeComparisonPanel) {
((CodeComparisonPanel<?>) component).setTitlePrefixes(leftTitlePrefix, ((CodeComparisonPanel) component).setTitlePrefixes(leftTitlePrefix,
rightTitlePrefix); rightTitlePrefix);
} }
} }
@ -539,34 +427,10 @@ public class FunctionComparisonPanel extends JPanel implements ChangeListener {
* @return the action context * @return the action context
*/ */
public ActionContext getActionContext(MouseEvent event, ComponentProvider componentProvider) { public ActionContext getActionContext(MouseEvent event, ComponentProvider componentProvider) {
Object source = (event != null) ? event.getSource() : null; CodeComparisonPanel activePanel = getDisplayedPanel();
Component sourceComponent = (source instanceof Component) ? (Component) source : null;
ListingCodeComparisonPanel dualListingPanel = getDualListingPanel();
// Is the action being taken on the dual listing.
if (dualListingPanel != null && dualListingPanel.isAncestorOf(sourceComponent)) {
return dualListingPanel.getActionContext(event, componentProvider);
}
CodeComparisonPanel<? extends FieldPanelCoordinator> activePanel =
getFocusedComparisonPanel();
if (activePanel != null) { if (activePanel != null) {
return activePanel.getActionContext(componentProvider, event); return activePanel.getActionContext(componentProvider, event);
} }
return null;
}
private CodeComparisonPanel<? extends FieldPanelCoordinator> getFocusedComparisonPanel() {
Component focused = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner();
if (focused != null) {
for (CodeComparisonPanel<? extends FieldPanelCoordinator> codeComparisonPanel : codeComparisonPanels) {
JComponent component = codeComparisonPanel.getComponent();
boolean isParent = SwingUtilities.isDescendingFrom(focused, component);
if (isParent) {
return codeComparisonPanel;
}
}
}
return null; return null;
} }
@ -594,8 +458,8 @@ public class FunctionComparisonPanel extends JPanel implements ChangeListener {
toggleScrollLockAction.setToolBarData(new ToolBarData( toggleScrollLockAction.setToolBarData(new ToolBarData(
syncScrolling ? SYNC_SCROLLING_ICON : UNSYNC_SCROLLING_ICON, SCROLLING_GROUP)); syncScrolling ? SYNC_SCROLLING_ICON : UNSYNC_SCROLLING_ICON, SCROLLING_GROUP));
// Notify each comparison panel of the scrolling sync state. // Notify each comparison panel of the scrolling sync state.
for (CodeComparisonPanel<? extends FieldPanelCoordinator> codeComparisonPanel : codeComparisonPanels) { for (CodeComparisonPanel codeComparisonPanel : codeComparisonPanels) {
codeComparisonPanel.setScrollingSyncState(syncScrolling); codeComparisonPanel.setSynchronizedScrolling(syncScrolling);
} }
this.syncScrolling = syncScrolling; this.syncScrolling = syncScrolling;
} }
@ -605,17 +469,17 @@ public class FunctionComparisonPanel extends JPanel implements ChangeListener {
* *
* @return the current panel or null. * @return the current panel or null.
*/ */
public CodeComparisonPanel<? extends FieldPanelCoordinator> getDisplayedPanel() { public CodeComparisonPanel getDisplayedPanel() {
int selectedIndex = tabbedPane.getSelectedIndex(); int selectedIndex = tabbedPane.getSelectedIndex();
Component component = tabbedPane.getComponentAt(selectedIndex); Component component = tabbedPane.getComponentAt(selectedIndex);
return (CodeComparisonPanel<?>) component; return (CodeComparisonPanel) component;
} }
/** /**
* Updates the enablement for all actions provided by each panel * Updates the enablement for all actions provided by each panel
*/ */
public void updateActionEnablement() { public void updateActionEnablement() {
for (CodeComparisonPanel<? extends FieldPanelCoordinator> codeComparisonPanel : codeComparisonPanels) { for (CodeComparisonPanel codeComparisonPanel : codeComparisonPanels) {
codeComparisonPanel.updateActionEnablement(); codeComparisonPanel.updateActionEnablement();
} }
} }
@ -625,8 +489,8 @@ public class FunctionComparisonPanel extends JPanel implements ChangeListener {
* *
* @return null if there is no code comparison panel * @return null if there is no code comparison panel
*/ */
CodeComparisonPanel<? extends FieldPanelCoordinator> getCurrentComponent() { CodeComparisonPanel getCurrentComponent() {
return (CodeComparisonPanel<?>) tabbedPane.getSelectedComponent(); return (CodeComparisonPanel) tabbedPane.getSelectedComponent();
} }
/** /**
@ -677,7 +541,7 @@ public class FunctionComparisonPanel extends JPanel implements ChangeListener {
} }
} }
public List<CodeComparisonPanel<? extends FieldPanelCoordinator>> getComparisonPanels() { public List<CodeComparisonPanel> getComparisonPanels() {
return codeComparisonPanels; return codeComparisonPanels;
} }
@ -686,17 +550,17 @@ public class FunctionComparisonPanel extends JPanel implements ChangeListener {
* *
* @return the CodeComparisonPanels which are extension points * @return the CodeComparisonPanels which are extension points
*/ */
private List<CodeComparisonPanel<? extends FieldPanelCoordinator>> getCodeComparisonPanels() { private List<CodeComparisonPanel> getCodeComparisonPanels() {
if (codeComparisonPanels == null) { if (codeComparisonPanels == null) {
codeComparisonPanels = createAllPossibleCodeComparisonPanels(); codeComparisonPanels = createAllPossibleCodeComparisonPanels();
codeComparisonPanels.sort((p1, p2) -> p1.getTitle().compareTo(p2.getTitle())); codeComparisonPanels.sort((p1, p2) -> p1.getName().compareTo(p2.getName()));
} }
return codeComparisonPanels; return codeComparisonPanels;
} }
@SuppressWarnings({ "rawtypes", "unchecked" }) @SuppressWarnings({ "rawtypes", "unchecked" })
private ArrayList<CodeComparisonPanel<? extends FieldPanelCoordinator>> createAllPossibleCodeComparisonPanels() { private ArrayList<CodeComparisonPanel> createAllPossibleCodeComparisonPanels() {
ArrayList<CodeComparisonPanel<? extends FieldPanelCoordinator>> instances = ArrayList<CodeComparisonPanel> instances =
new ArrayList<>(); new ArrayList<>();
List<Class<? extends CodeComparisonPanel>> classes = List<Class<? extends CodeComparisonPanel>> classes =
ClassSearcher.getClasses(CodeComparisonPanel.class); ClassSearcher.getClasses(CodeComparisonPanel.class);

View file

@ -19,19 +19,13 @@ import java.util.Set;
import java.util.function.Supplier; import java.util.function.Supplier;
import ghidra.app.CorePluginPackage; import ghidra.app.CorePluginPackage;
import ghidra.app.events.ProgramActivatedPluginEvent; import ghidra.app.events.*;
import ghidra.app.events.ProgramClosedPluginEvent;
import ghidra.app.events.ProgramSelectionPluginEvent;
import ghidra.app.plugin.PluginCategoryNames; import ghidra.app.plugin.PluginCategoryNames;
import ghidra.app.plugin.ProgramPlugin; import ghidra.app.plugin.ProgramPlugin;
import ghidra.app.plugin.core.functioncompare.actions.CompareFunctionsAction; import ghidra.app.plugin.core.functioncompare.actions.CompareFunctionsAction;
import ghidra.app.plugin.core.functioncompare.actions.CompareFunctionsFromListingAction; import ghidra.app.plugin.core.functioncompare.actions.CompareFunctionsFromListingAction;
import ghidra.app.services.FunctionComparisonService; import ghidra.app.services.FunctionComparisonService;
import ghidra.framework.model.DomainObjectChangeRecord; import ghidra.framework.model.*;
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.PluginInfo;
import ghidra.framework.plugintool.PluginTool; import ghidra.framework.plugintool.PluginTool;
import ghidra.framework.plugintool.util.PluginStatus; import ghidra.framework.plugintool.util.PluginStatus;
@ -117,7 +111,7 @@ public class FunctionComparisonPlugin extends ProgramPlugin
EventType eventType = doRecord.getEventType(); EventType eventType = doRecord.getEventType();
if (eventType == DomainObjectEvent.RESTORED) { if (eventType == DomainObjectEvent.RESTORED) {
functionComparisonManager.domainObjectRestored(ev); functionComparisonManager.domainObjectRestored((Program) ev.getSource());
} }
else if (eventType == ProgramEvent.FUNCTION_REMOVED) { else if (eventType == ProgramEvent.FUNCTION_REMOVED) {
ProgramChangeRecord rec = (ProgramChangeRecord) ev.getChangeRecord(i); ProgramChangeRecord rec = (ProgramChangeRecord) ev.getChangeRecord(i);

View file

@ -15,6 +15,8 @@
*/ */
package ghidra.app.plugin.core.functioncompare; package ghidra.app.plugin.core.functioncompare;
import static ghidra.util.datastruct.Duo.Side.*;
import java.awt.event.MouseEvent; import java.awt.event.MouseEvent;
import java.util.*; import java.util.*;
@ -23,7 +25,6 @@ import docking.Tool;
import docking.action.DockingAction; import docking.action.DockingAction;
import docking.action.DockingActionIf; import docking.action.DockingActionIf;
import docking.actions.PopupActionProvider; import docking.actions.PopupActionProvider;
import docking.widgets.fieldpanel.internal.FieldPanelCoordinator;
import ghidra.app.services.FunctionComparisonModel; import ghidra.app.services.FunctionComparisonModel;
import ghidra.app.services.FunctionComparisonService; import ghidra.app.services.FunctionComparisonService;
import ghidra.app.util.viewer.listingpanel.ListingCodeComparisonPanel; import ghidra.app.util.viewer.listingpanel.ListingCodeComparisonPanel;
@ -87,7 +88,7 @@ public class FunctionComparisonProvider extends ComponentProviderAdapter
@Override @Override
public FunctionComparisonPanel getComponent() { public FunctionComparisonPanel getComponent() {
if (functionComparisonPanel == null) { if (functionComparisonPanel == null) {
functionComparisonPanel = new FunctionComparisonPanel(this, tool, null, null); functionComparisonPanel = new FunctionComparisonPanel(this, tool);
} }
return functionComparisonPanel; return functionComparisonPanel;
} }
@ -107,19 +108,14 @@ public class FunctionComparisonProvider extends ComponentProviderAdapter
buff.append(getName() + "\n"); buff.append(getName() + "\n");
buff.append("Tab Text: "); buff.append("Tab Text: ");
buff.append(getTabText() + "\n"); buff.append(getTabText() + "\n");
Function leftFunction = functionComparisonPanel.getLeftFunction(); buff.append(functionComparisonPanel.getDescription());
String leftName = (leftFunction != null) ? leftFunction.getName() : "No Function";
buff.append("Function 1: " + leftName + "\n");
Function rightFunction = functionComparisonPanel.getRightFunction();
String rightName = (rightFunction != null) ? rightFunction.getName() : "No Function";
buff.append("Function 2: " + rightName + "\n");
buff.append("tool = " + tool + "\n"); buff.append("tool = " + tool + "\n");
return buff.toString(); return buff.toString();
} }
@Override @Override
public ActionContext getActionContext(MouseEvent event) { public ActionContext getActionContext(MouseEvent event) {
CodeComparisonPanel<? extends FieldPanelCoordinator> currentComponent = CodeComparisonPanel currentComponent =
functionComparisonPanel.getCurrentComponent(); functionComparisonPanel.getCurrentComponent();
return currentComponent.getActionContext(this, event); return currentComponent.getActionContext(this, event);
} }
@ -145,7 +141,7 @@ public class FunctionComparisonProvider extends ComponentProviderAdapter
ListingCodeComparisonPanel dualListingPanel = ListingCodeComparisonPanel dualListingPanel =
functionComparisonPanel.getDualListingPanel(); functionComparisonPanel.getDualListingPanel();
if (dualListingPanel != null) { if (dualListingPanel != null) {
ListingPanel leftPanel = dualListingPanel.getLeftPanel(); ListingPanel leftPanel = dualListingPanel.getListingPanel(LEFT);
return leftPanel.getHeaderActions(getName()); return leftPanel.getHeaderActions(getName());
} }
} }
@ -178,6 +174,7 @@ public class FunctionComparisonProvider extends ComponentProviderAdapter
* @param program the program being closed * @param program the program being closed
*/ */
public void programClosed(Program program) { public void programClosed(Program program) {
functionComparisonPanel.programClosed(program);
model.removeFunctions(program); model.removeFunctions(program);
closeIfEmpty(); closeIfEmpty();
} }
@ -210,7 +207,7 @@ public class FunctionComparisonProvider extends ComponentProviderAdapter
* @param program the program that was restored (undo/redo) * @param program the program that was restored (undo/redo)
*/ */
public void programRestored(Program program) { public void programRestored(Program program) {
CodeComparisonPanel<? extends FieldPanelCoordinator> comparePanel = CodeComparisonPanel comparePanel =
functionComparisonPanel.getCurrentComponent(); functionComparisonPanel.getCurrentComponent();
comparePanel.programRestored(program); comparePanel.programRestored(program);
} }
@ -283,6 +280,10 @@ public class FunctionComparisonProvider extends ComponentProviderAdapter
this.closeListener = Callback.dummyIfNull(closeListener); this.closeListener = Callback.dummyIfNull(closeListener);
} }
public CodeComparisonPanel getCodeComparisonPanelByName(String name) {
return functionComparisonPanel.getCodeComparisonPanelByName(name);
}
public void dispose() { public void dispose() {
functionComparisonPanel.dispose(); functionComparisonPanel.dispose();
} }

View file

@ -20,7 +20,6 @@ import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet; import java.util.concurrent.CopyOnWriteArraySet;
import docking.ComponentProviderActivationListener; import docking.ComponentProviderActivationListener;
import ghidra.framework.model.*;
import ghidra.program.model.listing.Function; import ghidra.program.model.listing.Function;
import ghidra.program.model.listing.Program; import ghidra.program.model.listing.Program;
@ -225,20 +224,10 @@ public class FunctionComparisonProviderManager implements FunctionComparisonProv
* will notify all the function comparison providers. This allows them to * will notify all the function comparison providers. This allows them to
* refresh if they are showing a function from the program * refresh if they are showing a function from the program
* *
* @param ev the object changed event * @param program the program that was restored
*/ */
public void domainObjectRestored(DomainObjectChangedEvent ev) { public void domainObjectRestored(Program program) {
for (DomainObjectChangeRecord domainObjectChangeRecord : ev) {
EventType eventType = domainObjectChangeRecord.getEventType();
if (eventType != DomainObjectEvent.RESTORED) {
return;
}
Object source = ev.getSource();
if (source instanceof Program) {
Program program = (Program) source;
providers.stream().forEach(p -> p.programRestored(program)); providers.stream().forEach(p -> p.programRestored(program));
} }
}
}
} }

View file

@ -15,6 +15,8 @@
*/ */
package ghidra.app.plugin.core.functioncompare; package ghidra.app.plugin.core.functioncompare;
import static ghidra.util.datastruct.Duo.Side.*;
import java.awt.*; import java.awt.*;
import java.awt.event.ItemEvent; import java.awt.event.ItemEvent;
import java.awt.event.ItemListener; import java.awt.event.ItemListener;
@ -23,11 +25,11 @@ import java.util.Set;
import javax.swing.*; import javax.swing.*;
import docking.widgets.fieldpanel.internal.FieldPanelCoordinator;
import ghidra.app.services.FunctionComparisonModel; import ghidra.app.services.FunctionComparisonModel;
import ghidra.app.util.viewer.util.CodeComparisonPanel; import ghidra.app.util.viewer.util.CodeComparisonPanel;
import ghidra.framework.plugintool.PluginTool; import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.listing.Function; import ghidra.program.model.listing.Function;
import ghidra.util.datastruct.Duo.Side;
import help.Help; import help.Help;
import help.HelpService; import help.HelpService;
@ -67,7 +69,7 @@ public class MultiFunctionComparisonPanel extends FunctionComparisonPanel {
* @param tool the active plugin tool * @param tool the active plugin tool
*/ */
public MultiFunctionComparisonPanel(MultiFunctionComparisonProvider provider, PluginTool tool) { public MultiFunctionComparisonPanel(MultiFunctionComparisonProvider provider, PluginTool tool) {
super(provider, tool, null, null); super(provider, tool);
JPanel choicePanel = new JPanel(new GridLayout(1, 2)); JPanel choicePanel = new JPanel(new GridLayout(1, 2));
choicePanel.add(createSourcePanel()); choicePanel.add(createSourcePanel());
@ -77,7 +79,7 @@ public class MultiFunctionComparisonPanel extends FunctionComparisonPanel {
// For the multi-panels we don't need to show the title of each // For the multi-panels we don't need to show the title of each
// comparison panel because the name of the function/data being shown // comparison panel because the name of the function/data being shown
// is already visible in the combo box // is already visible in the combo box
getComparisonPanels().forEach(p -> p.setShowTitles(false)); getComparisonPanels().forEach(p -> p.setShowDataTitles(false));
setPreferredSize(new Dimension(1200, 600)); setPreferredSize(new Dimension(1200, 600));
} }
@ -99,7 +101,6 @@ public class MultiFunctionComparisonPanel extends FunctionComparisonPanel {
// Fire a notification to update the UI state; without this the // Fire a notification to update the UI state; without this the
// actions would not be properly enabled/disabled // actions would not be properly enabled/disabled
tool.contextChanged(provider); tool.contextChanged(provider);
tool.setStatusInfo("function comparisons updated");
} }
/** /**
@ -108,10 +109,14 @@ public class MultiFunctionComparisonPanel extends FunctionComparisonPanel {
* @return the focused component * @return the focused component
*/ */
public JComboBox<Function> getFocusedComponent() { public JComboBox<Function> getFocusedComponent() {
CodeComparisonPanel<? extends FieldPanelCoordinator> currentComponent = CodeComparisonPanel currentComponent = getCurrentComponent();
getCurrentComponent(); Side side = currentComponent.getActiveSide();
boolean sourceHasFocus = currentComponent.leftPanelHasFocus(); return side == LEFT ? sourceFunctionsCB : targetFunctionsCB;
return sourceHasFocus ? sourceFunctionsCB : targetFunctionsCB; }
public Side getFocusedSide() {
CodeComparisonPanel currentComponent = getCurrentComponent();
return currentComponent.getActiveSide();
} }
/** /**
@ -260,9 +265,6 @@ public class MultiFunctionComparisonPanel extends FunctionComparisonPanel {
return; return;
} }
Function selected = (Function) sourceFunctionsCBModel.getSelectedItem();
loadFunctions(selected, null);
// Each time a source function is selected we need // Each time a source function is selected we need
// to load the targets associated with it // to load the targets associated with it
reloadTargetList((Function) sourceFunctionsCBModel.getSelectedItem()); reloadTargetList((Function) sourceFunctionsCBModel.getSelectedItem());

View file

@ -15,21 +15,27 @@
*/ */
package ghidra.app.plugin.core.functioncompare.actions; package ghidra.app.plugin.core.functioncompare.actions;
import static ghidra.util.datastruct.Duo.Side.*;
import java.awt.event.*; import java.awt.event.*;
import java.util.List; import java.util.List;
import javax.swing.Icon; import javax.swing.Icon;
import javax.swing.JComboBox;
import docking.ActionContext;
import docking.action.ToggleDockingAction; import docking.action.ToggleDockingAction;
import docking.action.ToolBarData; import docking.action.ToolBarData;
import docking.widgets.fieldpanel.internal.FieldPanelCoordinator; import ghidra.app.plugin.core.functioncompare.MultiFunctionComparisonPanel;
import ghidra.app.plugin.core.functioncompare.*; import ghidra.app.plugin.core.functioncompare.MultiFunctionComparisonProvider;
import ghidra.app.services.GoToService; import ghidra.app.services.GoToService;
import ghidra.app.util.viewer.util.CodeComparisonPanel; import ghidra.app.util.viewer.util.CodeComparisonPanel;
import ghidra.program.model.address.Address; import ghidra.program.model.address.AddressSetView;
import ghidra.program.model.listing.Function; import ghidra.program.model.listing.Function;
import ghidra.program.model.listing.Program;
import ghidra.util.HTMLUtilities; import ghidra.util.HTMLUtilities;
import ghidra.util.HelpLocation; import ghidra.util.HelpLocation;
import ghidra.util.datastruct.Duo.Side;
import resources.Icons; import resources.Icons;
/** /**
@ -50,6 +56,8 @@ public class NavigateToFunctionAction extends ToggleDockingAction {
private static final Icon NAV_FUNCTION_ICON = Icons.NAVIGATE_ON_INCOMING_EVENT_ICON; private static final Icon NAV_FUNCTION_ICON = Icons.NAVIGATE_ON_INCOMING_EVENT_ICON;
private MultiFunctionComparisonPanel comparisonPanel;
/** /**
* Constructor * Constructor
* *
@ -57,6 +65,7 @@ public class NavigateToFunctionAction extends ToggleDockingAction {
*/ */
public NavigateToFunctionAction(MultiFunctionComparisonProvider provider) { public NavigateToFunctionAction(MultiFunctionComparisonProvider provider) {
super("Navigate To Selected Function", provider.getName()); super("Navigate To Selected Function", provider.getName());
comparisonPanel = (MultiFunctionComparisonPanel) provider.getComponent();
goToService = provider.getTool().getService(GoToService.class); goToService = provider.getTool().getService(GoToService.class);
@ -70,8 +79,15 @@ public class NavigateToFunctionAction extends ToggleDockingAction {
setHelpLocation( setHelpLocation(
new HelpLocation(MultiFunctionComparisonPanel.HELP_TOPIC, "Navigate_To_Function")); new HelpLocation(MultiFunctionComparisonPanel.HELP_TOPIC, "Navigate_To_Function"));
addFocusListeners(provider); addFocusListeners();
addChangeListeners(provider); addChangeListeners();
}
@Override
public void actionPerformed(ActionContext context) {
JComboBox<Function> combo = comparisonPanel.getFocusedComponent();
Function f = (Function) combo.getSelectedItem();
goToService.goTo(f.getEntryPoint(), f.getProgram());
} }
/** /**
@ -79,108 +95,75 @@ public class NavigateToFunctionAction extends ToggleDockingAction {
* comparison provider. When a new function is selected, a GoTo event * comparison provider. When a new function is selected, a GoTo event
* is generated for the entry point of the function. * is generated for the entry point of the function.
* *
* @param provider the function comparison provider
*/ */
private void addChangeListeners(MultiFunctionComparisonProvider provider) { private void addChangeListeners() {
MultiFunctionComparisonPanel panel = (MultiFunctionComparisonPanel) provider.getComponent(); JComboBox<Function> sourceCombo = comparisonPanel.getSourceComponent();
JComboBox<Function> targetCombo = comparisonPanel.getTargetComponent();
sourceCombo.addItemListener(new PanelItemListener(LEFT));
targetCombo.addItemListener(new PanelItemListener(RIGHT));
panel.getSourceComponent().addItemListener(new ItemListener() {
@Override
public void itemStateChanged(ItemEvent e) {
if (e.getStateChange() != ItemEvent.SELECTED) {
return;
}
if (panel.getFocusedComponent() != panel.getSourceComponent()) {
return;
}
if (NavigateToFunctionAction.this.isSelected()) {
Function f = (Function) panel.getSourceComponent().getSelectedItem();
goToService.goTo(f.getEntryPoint(), f.getProgram());
}
}
});
panel.getTargetComponent().addItemListener(new ItemListener() {
@Override
public void itemStateChanged(ItemEvent e) {
if (e.getStateChange() != ItemEvent.SELECTED) {
return;
}
if (panel.getFocusedComponent() != panel.getTargetComponent()) {
return;
}
if (NavigateToFunctionAction.this.isSelected()) {
Function f = (Function) panel.getTargetComponent().getSelectedItem();
goToService.goTo(f.getEntryPoint(), f.getProgram());
}
}
});
} }
/** /**
* Adds a listener to each panel in the function comparison provider, * Adds a listener to each panel in the function comparison provider,
* triggered when focus has been changed. If focused is gained in a panel, * triggered when focus has been changed. If focused is gained in a panel,
* a GoTo event is issued containing the function start address. * a GoTo event is issued containing the function start address.
*
* @param provider the function comparison provider
*/ */
private void addFocusListeners(MultiFunctionComparisonProvider provider) { private void addFocusListeners() {
List<CodeComparisonPanel> panels = comparisonPanel.getComparisonPanels();
FunctionComparisonPanel mainPanel = provider.getComponent(); for (CodeComparisonPanel panel : panels) {
List<CodeComparisonPanel<? extends FieldPanelCoordinator>> panels = panel.getComparisonComponent(LEFT)
mainPanel.getComparisonPanels(); .addFocusListener(new PanelFocusListener(panel, Side.LEFT));
panel.getComparisonComponent(RIGHT)
.addFocusListener(new PanelFocusListener(panel, Side.RIGHT));
}
}
for (CodeComparisonPanel<? extends FieldPanelCoordinator> panel : panels) { private class PanelItemListener implements ItemListener {
private Side side;
panel.getRightFieldPanel().addFocusListener(new FocusAdapter() { PanelItemListener(Side side) {
this.side = side;
}
@Override
public void itemStateChanged(ItemEvent e) {
if (e.getStateChange() != ItemEvent.SELECTED) {
return;
}
if (comparisonPanel.getFocusedSide() != side) {
return;
}
if (isSelected()) {
JComboBox<?> combo = (JComboBox<?>) e.getSource();
Function f = (Function) combo.getSelectedItem();
goToService.goTo(f.getEntryPoint(), f.getProgram());
}
}
}
private class PanelFocusListener extends FocusAdapter {
private CodeComparisonPanel panel;
private Side side;
PanelFocusListener(CodeComparisonPanel panel, Side side) {
this.panel = panel;
this.side = side;
}
@Override @Override
public void focusGained(FocusEvent e) { public void focusGained(FocusEvent e) {
if (NavigateToFunctionAction.this.isSelected()) { if (!isSelected()) {
return;
Address addr = null; }
Program program = panel.getProgram(side);
if (panel.getRightFunction() != null) { AddressSetView addresses = panel.getAddresses(side);
addr = panel.getRightFunction().getBody().getMinAddress(); if (program != null && addresses != null && !addresses.isEmpty()) {
goToService.goTo(addresses.getMinAddress(), program);
} }
else if (panel.getRightData() != null) {
addr = panel.getRightData().getAddress();
}
else if (panel.getRightAddresses() != null) {
addr = panel.getRightAddresses().getMinAddress();
}
goToService.goTo(addr, panel.getRightProgram());
}
}
});
panel.getLeftFieldPanel().addFocusListener(new FocusAdapter() {
@Override
public void focusGained(FocusEvent e) {
if (NavigateToFunctionAction.this.isSelected()) {
Address addr = null;
if (panel.getLeftFunction() != null) {
addr = panel.getLeftFunction().getBody().getMinAddress();
}
else if (panel.getLeftData() != null) {
addr = panel.getLeftData().getAddress();
}
else if (panel.getLeftAddresses() != null) {
addr = panel.getLeftAddresses().getMinAddress();
}
goToService.goTo(addr, panel.getLeftProgram());
}
}
});
} }
} }
} }

View file

@ -107,6 +107,11 @@ public class MarkerManager implements MarkerService {
Gui.addThemeListener(themeListener); Gui.addThemeListener(themeListener);
} }
public void clearAll() {
programMarkersByGroup.clear();
markerSetCache.clear();
}
private void themeChanged(ThemeEvent e) { private void themeChanged(ThemeEvent e) {
if (e instanceof ColorChangedThemeEvent) { if (e instanceof ColorChangedThemeEvent) {
markerSetCache.clearColors(); markerSetCache.clearColors();

View file

@ -40,15 +40,6 @@ public class FieldHeader extends JTabbedPane implements ChangeListener {
private FormatManager formatManager; private FormatManager formatManager;
private FormatModelListener formatListener = new FormatModelListener() { private FormatModelListener formatListener = new FormatModelListener() {
@Override
public void formatModelAdded(FieldFormatModel formatModel) {
createTabs();
}
@Override
public void formatModelRemoved(FieldFormatModel formatModel) {
createTabs();
}
@Override @Override
public void formatModelChanged(FieldFormatModel formatModel) { public void formatModelChanged(FieldFormatModel formatModel) {

View file

@ -159,8 +159,10 @@ public class FormatManager implements OptionsChangeListener {
* @param listener the listener to be added * @param listener the listener to be added
*/ */
public void addFormatModelListener(FormatModelListener listener) { public void addFormatModelListener(FormatModelListener listener) {
if (listener != null) {
formatListeners.add(listener); formatListeners.add(listener);
} }
}
/** /**
* Removes the given listener from the list of listeners to be notified of a * Removes the given listener from the list of listeners to be notified of a

View file

@ -1,6 +1,5 @@
/* ### /* ###
* IP: GHIDRA * IP: GHIDRA
* REVIEWED: YES
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -20,16 +19,26 @@ package ghidra.app.util.viewer.format;
* Interface for listeners to format model changes. * Interface for listeners to format model changes.
*/ */
public interface FormatModelListener { public interface FormatModelListener {
/** /**
* Notifies that a new format model was added to the format manager. * Format model added. Not used.
* @param model the new model. * @param model the model that was added
* @deprecated not used
*/ */
void formatModelAdded(FieldFormatModel model); @Deprecated(since = "11.2", forRemoval = true)
default void formatModelAdded(FieldFormatModel model) {
// not used
}
/** /**
* Notifies that a format model was removed. * Format model removed. Not used.
* @param model the model that was removed. * @param model the model that was added
* @deprecated not used
*/ */
void formatModelRemoved(FieldFormatModel model); @Deprecated(since = "11.2", forRemoval = true)
default void formatModelRemoved(FieldFormatModel model) {
// not used
}
/** /**
* Notifies that the given format model was changed. * Notifies that the given format model was changed.

View file

@ -16,57 +16,32 @@
package ghidra.app.util.viewer.listingpanel; package ghidra.app.util.viewer.listingpanel;
import docking.ComponentProvider; import docking.ComponentProvider;
import docking.widgets.fieldpanel.internal.FieldPanelCoordinator;
import ghidra.app.util.viewer.util.CodeComparisonActionContext; import ghidra.app.util.viewer.util.CodeComparisonActionContext;
import ghidra.app.util.viewer.util.CodeComparisonPanel;
import ghidra.program.model.listing.Function;
// Note: If you want to get the typical actions for things like comments, labels, bookmarks, etc.
// that are available in the CodeBrowser then change this to extend ListingActionContext.
// This currently extends NavigatableActionContext so that it does NOT get the typical
// CodeBrowser Listing actions.
/** /**
* Action context for a ListingCodeComparisonPanel. * Action context for a ListingCodeComparisonPanel.
*/ */
public class DualListingActionContext extends CodeComparisonActionContext { public class DualListingActionContext extends CodeComparisonActionContext {
private CodeComparisonPanel<? extends FieldPanelCoordinator> codeComparisonPanel = null; private ListingCodeComparisonPanel codeComparisonPanel = null;
/** /**
* Constructor for a dual listing's action context. * Constructor for a dual listing's action context.
* @param provider the provider that uses this action context. * @param provider the provider that uses this action context.
* @param panel the ListingCodeComparisonPanel that generated this context
*/ */
public DualListingActionContext(ComponentProvider provider) { public DualListingActionContext(ComponentProvider provider, ListingCodeComparisonPanel panel) {
super(provider); super(provider, panel, panel.getActiveListingPanel().getFieldPanel());
this.codeComparisonPanel = panel;
} }
/** /**
* Sets the CodeComparisonPanel associated with this context. * Returns the {@link ListingCodeComparisonPanel} that generated this context
* @param codeComparisonPanel the code comparison panel * @return the listing comparison panel that generated this context
*/ */
public void setCodeComparisonPanel(
CodeComparisonPanel<? extends FieldPanelCoordinator> codeComparisonPanel) {
this.codeComparisonPanel = codeComparisonPanel;
}
@Override @Override
public CodeComparisonPanel<? extends FieldPanelCoordinator> getCodeComparisonPanel() { public ListingCodeComparisonPanel getCodeComparisonPanel() {
return codeComparisonPanel; return codeComparisonPanel;
} }
@Override
public Function getSourceFunction() {
boolean leftHasFocus = codeComparisonPanel.leftPanelHasFocus();
return leftHasFocus ? codeComparisonPanel.getRightFunction()
: codeComparisonPanel.getLeftFunction();
}
@Override
public Function getTargetFunction() {
boolean leftHasFocus = codeComparisonPanel.leftPanelHasFocus();
return leftHasFocus ? codeComparisonPanel.getLeftFunction()
: codeComparisonPanel.getRightFunction();
}
} }

View file

@ -1,37 +0,0 @@
/* ###
* 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.util.viewer.listingpanel;
import ghidra.program.util.ProgramLocation;
import docking.widgets.fieldpanel.listener.ViewListener;
/**
* Coordinates the locations between the left and right sides of a dual listing panel.
*/
public interface DualListingFieldPanelCoordinator extends ViewListener {
/**
* Method that gets called when the location changes in the left side's program listing.
* @param leftLocation the new location in the left side.
*/
public void leftLocationChanged(ProgramLocation leftLocation);
/**
* Method that gets called when the location changes in the right side's program listing.
* @param rightLocation the new location in the right side.
*/
public void rightLocationChanged(ProgramLocation rightLocation);
}

View file

@ -22,6 +22,7 @@ import ghidra.program.model.address.AddressSetView;
import ghidra.program.model.listing.Program; import ghidra.program.model.listing.Program;
import ghidra.program.model.symbol.ExternalLocation; import ghidra.program.model.symbol.ExternalLocation;
import ghidra.program.util.ProgramLocation; import ghidra.program.util.ProgramLocation;
import ghidra.util.datastruct.Duo.Side;
import ghidra.util.task.TaskMonitor; import ghidra.util.task.TaskMonitor;
/** /**
@ -32,7 +33,7 @@ import ghidra.util.task.TaskMonitor;
class DualListingGoToService implements GoToService { class DualListingGoToService implements GoToService {
private ListingCodeComparisonPanel dualListing; private ListingCodeComparisonPanel dualListing;
private boolean isLeftSide; private Side side;
private GoToOverrideService overrideService; private GoToOverrideService overrideService;
private GoToService goToService; private GoToService goToService;
@ -41,13 +42,13 @@ class DualListingGoToService implements GoToService {
* @param goToService the GoToService that this overrides and that can be used when the * @param goToService the GoToService that this overrides and that can be used when the
* GoToService methods don't pertain specifically to the left or right listing panel. * GoToService methods don't pertain specifically to the left or right listing panel.
* @param dualListing the dual listing panel * @param dualListing the dual listing panel
* @param isLeftSide true means this GoToService is for the left listing panel of the dual listing. * @param side the LEFT or RIGHT side
*/ */
DualListingGoToService(GoToService goToService, ListingCodeComparisonPanel dualListing, DualListingGoToService(GoToService goToService, ListingCodeComparisonPanel dualListing,
boolean isLeftSide) { Side side) {
this.goToService = goToService; this.goToService = goToService;
this.dualListing = dualListing; this.dualListing = dualListing;
this.isLeftSide = isLeftSide; this.side = side;
} }
@Override @Override
@ -92,8 +93,7 @@ class DualListingGoToService implements GoToService {
if (addr == null) { if (addr == null) {
return false; return false;
} }
AddressSetView addresses = AddressSetView addresses = dualListing.getAddresses(side);
isLeftSide ? dualListing.getLeftAddresses() : dualListing.getRightAddresses();
if (!addresses.contains(addr)) { if (!addresses.contains(addr)) {
dualListing.setStatusInfo( dualListing.setStatusInfo(
"\"" + addr.toString() + "\" is outside the current listing's view."); "\"" + addr.toString() + "\" is outside the current listing's view.");
@ -112,8 +112,7 @@ class DualListingGoToService implements GoToService {
return false; return false;
} }
ListingPanel listingPanel = ListingPanel listingPanel = dualListing.getListingPanel(side);
(isLeftSide) ? dualListing.getLeftPanel() : dualListing.getRightPanel();
return listingPanel.goTo(loc); return listingPanel.goTo(loc);
} }
@ -124,8 +123,7 @@ class DualListingGoToService implements GoToService {
return false; return false;
} }
ListingPanel listingPanel = ListingPanel listingPanel = dualListing.getListingPanel(side);
(isLeftSide) ? dualListing.getLeftPanel() : dualListing.getRightPanel();
return listingPanel.goTo(addr); return listingPanel.goTo(addr);
} }

View file

@ -36,15 +36,14 @@ class DualListingNavigator implements Navigatable {
/** /**
* Constructor for a dual listing navigator. * Constructor for a dual listing navigator.
* @param dualListingPanel the dual listing whose left or right listing panel is to be controlled. * @param listingPanel the dual listing whose left or right listing panel is to be controlled.
* @param isLeftSide true indicates that this navigator is for the left side listing. * @param goToService which side LEFT or RIGHT
* false means it's for the right side listing. * false means it's for the right side listing.
*/ */
DualListingNavigator(ListingCodeComparisonPanel dualListingPanel, boolean isLeftSide) { DualListingNavigator(ListingPanel listingPanel, GoToService goToService) {
this.listingPanel = this.listingPanel = listingPanel;
isLeftSide ? dualListingPanel.getLeftPanel() : dualListingPanel.getRightPanel(); this.goToService = goToService;
this.goToService = dualListingPanel.getGoToService(isLeftSide);
id = UniversalIdGenerator.nextID().getValue(); id = UniversalIdGenerator.nextID().getValue();
} }
@ -152,7 +151,8 @@ class DualListingNavigator implements Navigatable {
} }
@Override @Override
public void removeHighlightProvider(ListingHighlightProvider highlightProvider, Program program) { public void removeHighlightProvider(ListingHighlightProvider highlightProvider,
Program program) {
// currently unsupported // currently unsupported
} }

View file

@ -18,6 +18,7 @@ package ghidra.app.util.viewer.listingpanel;
import ghidra.app.services.GoToService; import ghidra.app.services.GoToService;
import ghidra.framework.plugintool.ServiceProvider; import ghidra.framework.plugintool.ServiceProvider;
import ghidra.framework.plugintool.util.ServiceListener; import ghidra.framework.plugintool.util.ServiceListener;
import ghidra.util.datastruct.Duo.Side;
/** /**
* This provides services, but overrides and implements its own goTo for one of the listing * This provides services, but overrides and implements its own goTo for one of the listing
@ -35,14 +36,13 @@ class DualListingServiceProvider implements ServiceProvider {
* Constructor for a DualListingServiceProvider. * Constructor for a DualListingServiceProvider.
* @param serviceProvider the service provider to use for acquiring services other than goTo. * @param serviceProvider the service provider to use for acquiring services other than goTo.
* @param panel the dual listing code comparison panel. * @param panel the dual listing code comparison panel.
* @param isLeftPanel true indicates this is the left listing panel of the dual panel. * @param side LEFT or RIGHT
* false indicates the right panel.
*/ */
DualListingServiceProvider(ServiceProvider serviceProvider, ListingCodeComparisonPanel panel, DualListingServiceProvider(ServiceProvider serviceProvider, ListingCodeComparisonPanel panel,
boolean isLeftPanel) { Side side) {
this.serviceProvider = serviceProvider; this.serviceProvider = serviceProvider;
GoToService goToService = serviceProvider.getService(GoToService.class); GoToService goToService = serviceProvider.getService(GoToService.class);
this.dualListingGoToService = new DualListingGoToService(goToService, panel, isLeftPanel); this.dualListingGoToService = new DualListingGoToService(goToService, panel, side);
} }
@Override @Override

View file

@ -0,0 +1,96 @@
/* ###
* 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.util.viewer.listingpanel;
import static ghidra.util.datastruct.Duo.Side.*;
import ghidra.app.util.viewer.util.ComparisonData;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSetView;
import ghidra.program.model.listing.*;
import ghidra.program.util.ListingAddressCorrelation;
import ghidra.util.datastruct.Duo;
import ghidra.util.datastruct.Duo.Side;
/**
* Creates an address correlation with a simplistic correlation where each address correlates based
* on an offset from the address set's minimum address.
*/
public class LinearAddressCorrelation implements ListingAddressCorrelation {
private Duo<ComparisonData> comparisonData;
public LinearAddressCorrelation(Duo<ComparisonData> comparisonData) {
this.comparisonData = comparisonData;
}
@Override
public Program getProgram(Side side) {
return comparisonData.get(LEFT).getProgram();
}
@Override
public AddressSetView getAddresses(Side side) {
return comparisonData.get(LEFT).getAddressSet();
}
@Override
public Function getFunction(Side side) {
return null;
}
@Override
public Address getAddress(Side side, Address otherAddress) {
Side otherSide = side.otherSide();
if (!isValidAddress(otherSide, otherAddress) || !isCodeUnitStart(otherSide, otherAddress)) {
return null;
}
AddressSetView otherSet = comparisonData.get(otherSide).getAddressSet();
Address minOtherAddress = otherSet.getMinAddress();
long offset = otherAddress.subtract(minOtherAddress);
Address minAddress = comparisonData.get(side).getAddressSet().getMinAddress();
Address address = minAddress.addWrap(offset);
if (!isValidAddress(side, address)) {
return null;
}
return normalizeToCodeUnitStart(side, address);
}
private boolean isValidAddress(Side side, Address address) {
AddressSetView addresses = comparisonData.get(side).getAddressSet();
return addresses.contains(address);
}
private boolean isCodeUnitStart(Side side, Address address) {
Listing listing = getListing(side);
CodeUnit cu = listing.getCodeUnitAt(address);
return cu != null;
}
private Address normalizeToCodeUnitStart(Side side, Address address) {
Listing listing = getListing(side);
CodeUnit cu = listing.getCodeUnitContaining(address);
Address minAddress = cu.getMinAddress();
if (isValidAddress(side, minAddress)) {
return minAddress;
}
return null;
}
private Listing getListing(Side side) {
return comparisonData.get(side).getProgram().getListing();
}
}

View file

@ -1,149 +0,0 @@
/* ###
* 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.util.viewer.listingpanel;
import java.math.BigInteger;
import docking.widgets.fieldpanel.FieldPanel;
import docking.widgets.fieldpanel.internal.LayoutLockedFieldPanelCoordinator;
import docking.widgets.fieldpanel.support.ViewerPosition;
import ghidra.app.util.viewer.util.AddressIndexMap;
import ghidra.program.model.address.Address;
import ghidra.program.util.ListingAddressCorrelation;
import ghidra.program.util.ProgramLocation;
/**
* Coordinates cursor location and scrolling between the two sides of a ListingCodeComparisonPanel.
*/
public class ListingComparisonFieldPanelCoordinator extends LayoutLockedFieldPanelCoordinator
implements DualListingFieldPanelCoordinator {
private ListingCodeComparisonPanel dualListingPanel;
private ListingAddressCorrelation addressCorrelation;
private Address[] lockLineAddresses = new Address[2];
/**
* Constructor for this dual listing field panel coordinator.
* @param dualListingPanel the dual listing to be controlled by this coordinator.
*/
public ListingComparisonFieldPanelCoordinator(ListingCodeComparisonPanel dualListingPanel) {
super(new FieldPanel[] { dualListingPanel.getLeftPanel().getFieldPanel(),
dualListingPanel.getRightPanel().getFieldPanel() });
this.dualListingPanel = dualListingPanel;
}
/**
* Sets a new address correlation for associating addresses between the left and right sides.
* The field panels can then be coordinated by locking the layouts together whenever the
* current location on one side can be correlated with a location on the other side.
* @param addressCorrelation the correlation to use for locking the two sides together for
* scrolling.
*/
public void setCorrelation(ListingAddressCorrelation addressCorrelation) {
this.addressCorrelation = addressCorrelation;
resetLockedLines();
}
@Override
public void leftLocationChanged(ProgramLocation leftLocation) {
if (addressCorrelation == null) {
return; // Do nothing since no address correlator.
}
Address leftAddress = leftLocation.getAddress();
if (leftAddress == null) {
return; // Do nothing since can't get a location address.
}
// The correlation only gives a right side address for a left side address that is a code unit minimum.
Address rightAddress = addressCorrelation.getAddressInSecond(leftAddress);
if (rightAddress == null) {
return; // Do nothing since can't get a matching address.
}
// Got an address so let's try to lock the two panels at the indexes for the matching addresses.
setLockedAddresses(leftAddress, rightAddress);
FieldPanel fp = dualListingPanel.getLeftPanel().getFieldPanel();
adjustFieldPanel(fp);
}
@Override
public void rightLocationChanged(ProgramLocation rightLocation) {
if (addressCorrelation == null) {
return; // Do nothing since no address correlator.
}
Address rightAddress = rightLocation.getAddress();
if (rightAddress == null) {
return; // Do nothing since can't get a location address.
}
// The correlation only gives a left side address for a right side address that is a code unit minimum.
Address leftAddress = addressCorrelation.getAddressInFirst(rightAddress);
if (leftAddress == null) {
return; // Do nothing since can't get a matching address.
}
// Got an address so let's try to lock the two panels at the indexes for the matching addresses.
setLockedAddresses(leftAddress, rightAddress);
FieldPanel fp = dualListingPanel.getRightPanel().getFieldPanel();
adjustFieldPanel(fp);
}
/**
* Kicks the field panels viewChanged() method so that the field panels will realign their
* layouts using the locked line numbers.
*
* @param fp the field panel that has focus.
*/
void adjustFieldPanel(FieldPanel fp) {
ViewerPosition viewerPosition = fp.getViewerPosition();
BigInteger topIndex = viewerPosition.getIndex();
int topXOffset = viewerPosition.getXOffset();
int topYOffset = viewerPosition.getYOffset();
viewChanged(fp, topIndex, topXOffset, topYOffset);
}
/**
* Sets the left and right addresses that should currently be locked together for
* synchronized scrolling.
*
* @param leftAddress the address in the left listing.
* @param rightAddress the address in the right listing.
*/
void setLockedAddresses(Address leftAddress, Address rightAddress) {
lockLineAddresses[0] = leftAddress;
lockLineAddresses[1] = rightAddress;
ListingPanel leftListingPanel = dualListingPanel.getLeftPanel();
ListingPanel rightListingPanel = dualListingPanel.getRightPanel();
AddressIndexMap leftAddressIndexMap = leftListingPanel.getAddressIndexMap();
AddressIndexMap rightAddressIndexMap = rightListingPanel.getAddressIndexMap();
BigInteger leftIndex =
(leftAddress != null) ? leftAddressIndexMap.getIndex(leftAddress) : null;
BigInteger rightIndex =
(rightAddress != null) ? rightAddressIndexMap.getIndex(rightAddress) : null;
//lockLines will set null args to BigInteger.ZERO
lockLines(leftIndex, rightIndex);
}
/**
* Gets the left and right addresses that are currently locked together for synchronized
* scrolling.
*
* @return an array containing the left (index 0) and right (index 1) addresses that are
* locked together.
*/
Address[] getLockedAddresses() {
return lockLineAddresses;
}
}

View file

@ -1,61 +0,0 @@
/* ###
* 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.util.viewer.listingpanel;
import javax.swing.Icon;
import generic.theme.GIcon;
import ghidra.framework.plugintool.ComponentProviderAdapter;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.address.AddressSetView;
import ghidra.program.model.listing.Program;
/**
* Provider for displaying a ListingCodeComparisonPanel.
*/
public class ListingComparisonProvider extends ComponentProviderAdapter {
private static final Icon DUAL_LISTING_ICON =
new GIcon("icon.base.util.listingcompare.provider");
private ListingCodeComparisonPanel dualListingPanel;
/**
* Constructor for a provider that can display a ListingCodeComparisonPanel.
* @param tool the tool that contains this provider.
* @param name the owner of this provider, which is usually a plugin name.
* @param p1 program for the listing displayed in the left side of the panel.
* @param p2 program for the listing displayed in the right side of the panel.
* @param set1 the address set indicating the portion of the listing displayed in the left side
* of the panel.
* @param set2 the address set indicating the portion of the listing displayed in the right side
* of the panel.
*/
public ListingComparisonProvider(PluginTool tool, String name, Program p1, Program p2,
AddressSetView set1, AddressSetView set2) {
super(tool, "Listing Comparison", name);
setIcon(DUAL_LISTING_ICON);
dualListingPanel = new ListingCodeComparisonPanel(name, tool);
dualListingPanel.loadAddresses(p1, p2, set1, set2);
setTransient();
tool.addComponentProvider(this, true);
}
@Override
public ListingCodeComparisonPanel getComponent() {
return dualListingPanel;
}
}

View file

@ -0,0 +1,128 @@
/* ###
* 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.util.viewer.listingpanel;
import static ghidra.util.datastruct.Duo.Side.*;
import java.math.BigInteger;
import docking.widgets.fieldpanel.FieldPanel;
import docking.widgets.fieldpanel.internal.LayoutLockedFieldPanelCoordinator;
import docking.widgets.fieldpanel.internal.LineLockedFieldPanelCoordinator;
import docking.widgets.fieldpanel.support.ViewerPosition;
import ghidra.app.util.viewer.util.AddressIndexMap;
import ghidra.program.model.address.Address;
import ghidra.program.util.ListingAddressCorrelation;
import ghidra.program.util.ProgramLocation;
import ghidra.util.datastruct.Duo;
import ghidra.util.datastruct.Duo.Side;
/**
* Keeps two listing panels synchronized, both the view and cursor location
*/
public class ListingCoordinator {
private Duo<ListingDisplay> displays;
private Duo<Address> lockLineAddresses = new Duo<>();
private ProgramLocationTranslator locationTranslator;
private LineLockedFieldPanelCoordinator viewCoordinator;
ListingCoordinator(Duo<ListingDisplay> displays, ListingAddressCorrelation correlator) {
this.displays = displays;
this.locationTranslator = new ProgramLocationTranslator(correlator);
FieldPanel left = displays.get(LEFT).getListingPanel().getFieldPanel();
FieldPanel right = displays.get(RIGHT).getListingPanel().getFieldPanel();
viewCoordinator = new LayoutLockedFieldPanelCoordinator(left, right);
}
/**
* notification that the given side change to the given location
* @param side the side that changed
* @param location the location from the given side
*/
void setLocation(Side side, ProgramLocation location) {
// Only set other side's cursor if we are coordinating right now.
Side otherSide = side.otherSide();
ProgramLocation otherLocation = locationTranslator.getProgramLocation(otherSide, location);
if (otherLocation != null) {
updateViewCoordinator(side, location, otherLocation);
displays.get(otherSide).goTo(otherLocation);
displays.get(side.otherSide()).updateCursorMarkers(otherLocation);
}
}
void dispose() {
viewCoordinator.dispose();
}
/**
* synchronized the two listings using the given side as the source
* @param side to synchronize from
*/
void sync(Side side) {
adjustFieldPanel(displays.get(side).getListingPanel().getFieldPanel());
ProgramLocation programLocation = displays.get(side).getProgramLocation();
if (programLocation != null) {
setLocation(side, programLocation);
}
}
/**
* Kicks the field panels viewChanged() method so that the field panels will realign their
* layouts using the locked line numbers.
*
* @param fieldPanel the field panel that has focus.
*/
private void adjustFieldPanel(FieldPanel fieldPanel) {
ViewerPosition viewerPosition = fieldPanel.getViewerPosition();
BigInteger topIndex = viewerPosition.getIndex();
int topXOffset = viewerPosition.getXOffset();
int topYOffset = viewerPosition.getYOffset();
viewCoordinator.viewChanged(fieldPanel, topIndex, topXOffset, topYOffset);
}
/**
* Sets the left and right addresses that should currently be locked together for
* synchronized scrolling.
*
* @param leftAddress the address in the left listing.
* @param rightAddress the address in the right listing.
*/
private void setLockedAddresses(Address leftAddress, Address rightAddress) {
if (leftAddress == null || rightAddress == null) {
return;
}
lockLineAddresses = new Duo<>(leftAddress, rightAddress);
AddressIndexMap leftMap = displays.get(LEFT).getListingPanel().getAddressIndexMap();
AddressIndexMap rightMap = displays.get(RIGHT).getListingPanel().getAddressIndexMap();
BigInteger leftIndex = leftMap.getIndex(leftAddress);
BigInteger rightIndex = rightMap.getIndex(rightAddress);
viewCoordinator.lockLines(leftIndex, rightIndex);
}
private void updateViewCoordinator(Side side, ProgramLocation location,
ProgramLocation otherLocation) {
Address leftAddress = side == LEFT ? location.getAddress() : otherLocation.getAddress();
Address rightAddress = side == LEFT ? otherLocation.getAddress() : location.getAddress();
setLockedAddresses(leftAddress, rightAddress);
FieldPanel fp = displays.get(side).getListingPanel().getFieldPanel();
adjustFieldPanel(fp);
}
}

View file

@ -15,6 +15,9 @@
*/ */
package ghidra.app.util.viewer.listingpanel; package ghidra.app.util.viewer.listingpanel;
import java.util.Arrays;
import java.util.List;
import javax.swing.Icon; import javax.swing.Icon;
import docking.ActionContext; import docking.ActionContext;
@ -71,9 +74,9 @@ public class ListingDiffActionManager {
* Gets the actions. * Gets the actions.
* @return the docking actions. * @return the docking actions.
*/ */
public DockingAction[] getActions() { public List<DockingAction> getActions() {
return new DockingAction[] { toggleIgnoreByteDiffsAction, toggleIgnoreConstantsAction, return Arrays.asList(toggleIgnoreByteDiffsAction, toggleIgnoreConstantsAction,
toggleIgnoreRegisterNamesAction }; toggleIgnoreRegisterNamesAction);
} }
/** /**

View file

@ -16,7 +16,7 @@
package ghidra.app.util.viewer.listingpanel; package ghidra.app.util.viewer.listingpanel;
import java.awt.Color; import java.awt.Color;
import java.util.ArrayList; import java.util.*;
import docking.widgets.fieldpanel.support.Highlight; import docking.widgets.fieldpanel.support.Highlight;
import ghidra.app.util.ListingHighlightProvider; import ghidra.app.util.ListingHighlightProvider;
@ -26,6 +26,7 @@ import ghidra.program.model.address.*;
import ghidra.program.model.listing.CodeUnit; import ghidra.program.model.listing.CodeUnit;
import ghidra.program.model.listing.Instruction; import ghidra.program.model.listing.Instruction;
import ghidra.program.util.ListingDiff; import ghidra.program.util.ListingDiff;
import ghidra.util.datastruct.Duo.Side;
public class ListingDiffHighlightProvider implements ListingHighlightProvider { public class ListingDiffHighlightProvider implements ListingHighlightProvider {
@ -33,22 +34,22 @@ public class ListingDiffHighlightProvider implements ListingHighlightProvider {
private static final String DEFAULT_OPERAND_SEPARATOR = ","; private static final String DEFAULT_OPERAND_SEPARATOR = ",";
private ListingDiff listingDiff; private ListingDiff listingDiff;
private boolean isListing1; private Side side;
private ListingCodeComparisonOptions comparisonOptions; private ListingCodeComparisonOptions comparisonOptions;
/** /**
* Constructor for this highlight provider. * Constructor for this highlight provider.
* @param listingDiff the ListingDiff to use to determine where there are differences that * @param listingDiff the ListingDiff to use to determine where there are differences that
* need highlighting. * need highlighting.
* @param isListing1 true means that these are the highlights for the first listing. * @param side LEFT or RIGHT
* false means the highlights are for the second listing. * false means the highlights are for the second listing.
* @param comparisonOptions the tool options that indicate the current * @param comparisonOptions the tool options that indicate the current
* background colors for the Listing code comparison panel. * background colors for the Listing code comparison panel.
*/ */
public ListingDiffHighlightProvider(ListingDiff listingDiff, boolean isListing1, public ListingDiffHighlightProvider(ListingDiff listingDiff, Side side,
ListingCodeComparisonOptions comparisonOptions) { ListingCodeComparisonOptions comparisonOptions) {
this.listingDiff = listingDiff; this.listingDiff = listingDiff;
this.isListing1 = isListing1; this.side = side;
this.comparisonOptions = comparisonOptions; this.comparisonOptions = comparisonOptions;
} }
@ -77,14 +78,12 @@ public class ListingDiffHighlightProvider implements ListingHighlightProvider {
private Highlight[] getByteDiffHighlights(String text, CodeUnit codeUnit, private Highlight[] getByteDiffHighlights(String text, CodeUnit codeUnit,
int cursorTextOffset) { int cursorTextOffset) {
Address minAddress = codeUnit.getMinAddress(); Address minAddress = codeUnit.getMinAddress();
AddressSetView unmatchedDiffs = (isListing1) ? listingDiff.getListing1UnmatchedCode() AddressSetView unmatchedDiffs = listingDiff.getUnmatchedCode(side);
: listingDiff.getListing2UnmatchedCode();
if (unmatchedDiffs.contains(minAddress)) { if (unmatchedDiffs.contains(minAddress)) {
return NO_HIGHLIGHTS; return NO_HIGHLIGHTS;
} }
Color byteDiffsBackgroundColor = comparisonOptions.getByteDiffsBackgroundColor(); Color byteDiffsBackgroundColor = comparisonOptions.getByteDiffsBackgroundColor();
AddressSetView byteDiffs = AddressSetView byteDiffs = listingDiff.getByteDiffs(side);
(isListing1) ? listingDiff.getListing1ByteDiffs() : listingDiff.getListing2ByteDiffs();
// Get intersection of Byte Diff addresses and this code unit's addresses // Get intersection of Byte Diff addresses and this code unit's addresses
AddressSet diffSet = new AddressSet(codeUnit.getMinAddress(), codeUnit.getMaxAddress()); AddressSet diffSet = new AddressSet(codeUnit.getMinAddress(), codeUnit.getMaxAddress());
diffSet = diffSet.intersect(byteDiffs); diffSet = diffSet.intersect(byteDiffs);
@ -108,19 +107,17 @@ public class ListingDiffHighlightProvider implements ListingHighlightProvider {
private Highlight[] getMnemonicDiffHighlights(String text, CodeUnit codeUnit, private Highlight[] getMnemonicDiffHighlights(String text, CodeUnit codeUnit,
int cursorTextOffset) { int cursorTextOffset) {
Address minAddress = codeUnit.getMinAddress(); Address minAddress = codeUnit.getMinAddress();
AddressSetView unmatchedDiffs = (isListing1) ? listingDiff.getListing1UnmatchedCode() AddressSetView unmatchedDiffs = listingDiff.getUnmatchedCode(side);
: listingDiff.getListing2UnmatchedCode();
if (unmatchedDiffs.contains(minAddress)) { if (unmatchedDiffs.contains(minAddress)) {
return NO_HIGHLIGHTS; return NO_HIGHLIGHTS;
} }
Color mnemonicDiffsBackgroundColor = comparisonOptions.getMnemonicDiffsBackgroundColor(); Color mnemonicDiffsBackgroundColor = comparisonOptions.getMnemonicDiffsBackgroundColor();
AddressSetView codeUnitDiffs = (isListing1) ? listingDiff.getListing1CodeUnitDiffs() AddressSetView codeUnitDiffs = listingDiff.getCodeUnitDiffs(side);
: listingDiff.getListing2CodeUnitDiffs();
// Get intersection of Code Unit Diff addresses and this code unit's addresses // Get intersection of Code Unit Diff addresses and this code unit's addresses
AddressSet diffSet = new AddressSet(codeUnit.getMinAddress(), codeUnit.getMaxAddress()); AddressSet diffSet = new AddressSet(codeUnit.getMinAddress(), codeUnit.getMaxAddress());
diffSet = diffSet.intersect(codeUnitDiffs); diffSet = diffSet.intersect(codeUnitDiffs);
if (!diffSet.isEmpty()) { if (!diffSet.isEmpty()) {
CodeUnit otherCodeUnit = listingDiff.getMatchingCodeUnit(codeUnit, isListing1); CodeUnit otherCodeUnit = listingDiff.getMatchingCodeUnit(codeUnit, side);
if (otherCodeUnit == null) { if (otherCodeUnit == null) {
return entireTextHighlight(text, cursorTextOffset, mnemonicDiffsBackgroundColor); return entireTextHighlight(text, cursorTextOffset, mnemonicDiffsBackgroundColor);
} }
@ -137,32 +134,32 @@ public class ListingDiffHighlightProvider implements ListingHighlightProvider {
private Highlight[] getOperandDiffHighlights(String text, CodeUnit codeUnit, private Highlight[] getOperandDiffHighlights(String text, CodeUnit codeUnit,
int cursorTextOffset) { int cursorTextOffset) {
Address minAddress = codeUnit.getMinAddress(); Address minAddress = codeUnit.getMinAddress();
AddressSetView unmatchedDiffs = (isListing1) ? listingDiff.getListing1UnmatchedCode() AddressSetView unmatchedDiffs = listingDiff.getUnmatchedCode(side);
: listingDiff.getListing2UnmatchedCode();
if (unmatchedDiffs.contains(minAddress)) { if (unmatchedDiffs.contains(minAddress)) {
return NO_HIGHLIGHTS; return NO_HIGHLIGHTS;
} }
Color operandDiffsBackgroundColor = comparisonOptions.getOperandDiffsBackgroundColor(); Color operandDiffsBackgroundColor = comparisonOptions.getOperandDiffsBackgroundColor();
AddressSetView codeUnitDiffs = (isListing1) ? listingDiff.getListing1CodeUnitDiffs() AddressSetView codeUnitDiffs = listingDiff.getCodeUnitDiffs(side);
: listingDiff.getListing2CodeUnitDiffs();
// Get intersection of Code Unit Diff addresses and this code unit's addresses // Get intersection of Code Unit Diff addresses and this code unit's addresses
AddressSet diffSet = new AddressSet(codeUnit.getMinAddress(), codeUnit.getMaxAddress()); AddressSet diffSet = new AddressSet(codeUnit.getMinAddress(), codeUnit.getMaxAddress());
diffSet = diffSet.intersect(codeUnitDiffs); diffSet = diffSet.intersect(codeUnitDiffs);
if (!diffSet.isEmpty()) { if (!diffSet.isEmpty()) {
CodeUnit matchingCodeUnit = listingDiff.getMatchingCodeUnit(codeUnit, isListing1); CodeUnit matchingCodeUnit = listingDiff.getMatchingCodeUnit(codeUnit, side);
if (listingDiff.doesEntireOperandSetDiffer(codeUnit, matchingCodeUnit)) { if (listingDiff.doesEntireOperandSetDiffer(codeUnit, matchingCodeUnit)) {
return entireTextHighlight(text, cursorTextOffset, operandDiffsBackgroundColor); return entireTextHighlight(text, cursorTextOffset, operandDiffsBackgroundColor);
} }
Pair[] pairs = getOperandPairs(text, codeUnit); List<Range> operandRanges = getOperandRanges(text, codeUnit);
int numOperands = codeUnit.getNumOperands(); int numOperands = codeUnit.getNumOperands();
if (pairs.length != numOperands) { if (operandRanges.size() != numOperands) {
return entireTextHighlight(text, cursorTextOffset, operandDiffsBackgroundColor); return entireTextHighlight(text, cursorTextOffset, operandDiffsBackgroundColor);
} }
int[] diffOpIndices = listingDiff.getOperandsThatDiffer(codeUnit, matchingCodeUnit); int[] diffOpIndices = listingDiff.getOperandsThatDiffer(codeUnit, matchingCodeUnit);
ArrayList<Highlight> highlights = new ArrayList<>(); ArrayList<Highlight> highlights = new ArrayList<>();
for (int diffOpIndex : diffOpIndices) { for (int diffOpIndex : diffOpIndices) {
// Highlight each operand that differs. // Highlight each operand that differs.
highlights.add(new Highlight(pairs[diffOpIndex].start, pairs[diffOpIndex].end, highlights.add(
new Highlight(operandRanges.get(diffOpIndex).start,
operandRanges.get(diffOpIndex).end,
operandDiffsBackgroundColor)); operandDiffsBackgroundColor));
} }
return highlights.toArray(new Highlight[highlights.size()]); return highlights.toArray(new Highlight[highlights.size()]);
@ -174,15 +171,15 @@ public class ListingDiffHighlightProvider implements ListingHighlightProvider {
* Gets an array of start/end positions for each operand within the operand field's full text. * Gets an array of start/end positions for each operand within the operand field's full text.
* @param text the full text from the operand field * @param text the full text from the operand field
* @param codeUnit the code unit whose operand text is provided * @param codeUnit the code unit whose operand text is provided
* @return the operand pairs indicating the start and end offsets for each individual operand * @return the operand ranges indicating the start and end offsets for each individual operand
* within the text. * within the text.
*/ */
private Pair[] getOperandPairs(String text, CodeUnit codeUnit) { private List<Range> getOperandRanges(String text, CodeUnit codeUnit) {
if (text == null || text.isEmpty()) { if (text == null || text.isEmpty()) {
return new Pair[0]; return Collections.emptyList();
} }
Instruction instruction = (codeUnit instanceof Instruction) ? (Instruction) codeUnit : null; Instruction instruction = (codeUnit instanceof Instruction) ? (Instruction) codeUnit : null;
ArrayList<Pair> list = new ArrayList<>(); ArrayList<Range> list = new ArrayList<>();
int opIndex = 0; int opIndex = 0;
int textLength = text.length(); int textLength = text.length();
int start = 0; // Start index in the text for the current operand. int start = 0; // Start index in the text for the current operand.
@ -197,7 +194,7 @@ public class ListingDiffHighlightProvider implements ListingHighlightProvider {
separatorIndex = text.indexOf(separator, start); separatorIndex = text.indexOf(separator, start);
start = separatorIndex + separator.length(); start = separatorIndex + separator.length();
} }
// Get a start/end Pair of indexes for each operand. // Get a start/end range of indexes for each operand.
while (start < textLength) { while (start < textLength) {
++opIndex; // Increment the opIndex since we find the separator before an operand. ++opIndex; // Increment the opIndex since we find the separator before an operand.
separator = DEFAULT_OPERAND_SEPARATOR; // default separator separator = DEFAULT_OPERAND_SEPARATOR; // default separator
@ -209,17 +206,15 @@ public class ListingDiffHighlightProvider implements ListingHighlightProvider {
separatorIndex = separatorIndex =
(separator != null && !separator.isEmpty()) ? text.indexOf(separator, start) : -1; (separator != null && !separator.isEmpty()) ? text.indexOf(separator, start) : -1;
if (separatorIndex == -1) { if (separatorIndex == -1) {
// Add the last operand's index Pair to the list. list.add(new Range(start, textLength - 1));
list.add(new Pair(start, textLength - 1));
start = textLength; start = textLength;
continue; continue;
} }
// Add the current operand's index Pair to the list. list.add(new Range(start, separatorIndex - 1));
list.add(new Pair(start, separatorIndex - 1));
// Move start to the beginning of the next operand. // Move start to the beginning of the next operand.
start = separatorIndex + separator.length(); start = separatorIndex + separator.length();
} }
return list.toArray(new Pair[list.size()]); return list;
} }
private Highlight[] entireTextHighlight(String text, int cursorTextOffset, Color color) { private Highlight[] entireTextHighlight(String text, int cursorTextOffset, Color color) {
@ -229,21 +224,12 @@ public class ListingDiffHighlightProvider implements ListingHighlightProvider {
return new Highlight[] { highlight }; return new Highlight[] { highlight };
} }
/** private class Range {
* Determines if this highlight provider is for the first listing of the ListingDiff.
* @return true if this provider's highlights are for the first listing. false if the
* highlights are for the second listing.
*/
public boolean isListing1() {
return isListing1;
}
private class Pair {
private int start; private int start;
private int end; private int end;
private Pair(int start, int end) { private Range(int start, int end) {
this.start = start; this.start = start;
this.end = end; this.end = end;
} }

View file

@ -0,0 +1,345 @@
/* ###
* 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.util.viewer.listingpanel;
import static ghidra.GhidraOptions.*;
import java.awt.Color;
import javax.swing.Icon;
import docking.widgets.fieldpanel.support.ViewerPosition;
import generic.theme.GIcon;
import ghidra.GhidraOptions;
import ghidra.app.plugin.core.codebrowser.MarkerServiceBackgroundColorModel;
import ghidra.app.plugin.core.codebrowser.hover.*;
import ghidra.app.plugin.core.marker.MarkerManager;
import ghidra.app.services.*;
import ghidra.app.util.ListingHighlightProvider;
import ghidra.app.util.viewer.format.FormatManager;
import ghidra.app.util.viewer.util.AddressIndexMap;
import ghidra.app.util.viewer.util.FieldNavigator;
import ghidra.framework.options.ToolOptions;
import ghidra.framework.plugintool.PluginTool;
import ghidra.framework.plugintool.ServiceProviderStub;
import ghidra.program.model.address.AddressSetView;
import ghidra.program.model.listing.Program;
import ghidra.program.util.ListingDiff;
import ghidra.program.util.ProgramLocation;
import ghidra.util.datastruct.Duo.Side;
/**
* Represents one side of a dual listing compare window. It holds the listing panel and
* related state information for one side.
*/
public class ListingDisplay implements ListingDiffChangeListener {
private static final Icon CURSOR_LOC_ICON = new GIcon("icon.base.util.listingcompare.cursor");
private ListingPanel listingPanel;
private PluginTool tool;
private ListingDisplayServiceProvider serviceProvider;
private MarkerManager markerManager;
private ListingCodeComparisonOptions comparisonOptions;
private Color cursorHighlightColor;
private MarkerSet unmatchedMarkers;
private MarkerSet diffMarkers;
private MarkerSet currentCursorMarkers;
private ListingDiffHighlightProvider diffHighlights;
private FieldNavigator fieldNavigator;
private ListingDiff listingDiff;
private Side side;
public ListingDisplay(PluginTool tool, String owner, ListingDiff listingDiff,
ListingCodeComparisonOptions comparsionOptions, Side side) {
this.tool = tool;
this.listingDiff = listingDiff;
this.comparisonOptions = comparsionOptions;
this.side = side;
FormatManager formatManager = createFormatManager();
loadOptions();
listingPanel = new ListingPanel(formatManager);
// Turn off selection in the listings so it can be set up as desired elsewhere.
listingPanel.getFieldPanel().enableSelection(false);
serviceProvider = new ListingDisplayServiceProvider();
formatManager.setServiceProvider(serviceProvider);
fieldNavigator = new FieldNavigator(serviceProvider, null);
setMouseNavigationEnabled(true);
createMarkerManager(owner);
listingPanel.addHoverService(new ReferenceListingHover(tool, () -> formatManager));
listingPanel.addHoverService(new DataTypeListingHover(tool));
listingPanel.addHoverService(new TruncatedTextListingHover(tool));
listingPanel.addHoverService(new FunctionNameListingHover(tool));
listingDiff.addListingDiffChangeListener(this);
setHoverMode(true);
}
private void createMarkerManager(String owner) {
markerManager = new ListingDisplayMarkerManager(tool, owner);
markerManager.addChangeListener(e -> listingPanel.repaint());
MarginProvider marginProvider = markerManager.getMarginProvider();
listingPanel.addMarginProvider(marginProvider);
OverviewProvider overviewProvider = markerManager.getOverviewProvider();
listingPanel.addOverviewProvider(overviewProvider);
}
void setProgramLocationListener(ProgramLocationListener listener) {
listingPanel.setProgramLocationListener(listener);
}
private FormatManager createFormatManager() {
ToolOptions displayOptions = tool.getOptions(GhidraOptions.CATEGORY_BROWSER_DISPLAY);
ToolOptions fieldOptions = tool.getOptions(GhidraOptions.CATEGORY_BROWSER_FIELDS);
FormatManager formatManager = new FormatManager(displayOptions, fieldOptions);
return formatManager;
}
public void repaint() {
listingPanel.getFieldPanel().repaint();
}
public void setDiffHighlightProvider(ListingDiffHighlightProvider newDiffHighlights) {
if (diffHighlights != null) {
removeHighlightProvider(diffHighlights);
}
diffHighlights = newDiffHighlights;
if (diffHighlights != null) {
addHighlightProvider(diffHighlights);
}
}
public void addHighlightProvider(ListingHighlightProvider highlightProvider) {
listingPanel.getFormatManager().addHighlightProvider(highlightProvider);
}
public void removeHighlightProvider(ListingHighlightProvider highlightProvider) {
if (highlightProvider == null) {
return;
}
listingPanel.getFormatManager().removeHighlightProvider(highlightProvider);
}
public void addHoverService(ListingHoverService service) {
listingPanel.addHoverService(service);
}
public void showHeader(boolean show) {
listingPanel.showHeader(show);
listingPanel.validate();
listingPanel.invalidate();
}
public void setHoverMode(boolean enabled) {
listingPanel.setHoverMode(enabled);
}
public void setView(AddressSetView view) {
ProgramLocation saved = listingPanel.getProgramLocation();
listingPanel.setView(view);
if (saved != null) {
listingPanel.goTo(saved);
}
}
public boolean isHeaderShowing() {
return listingPanel.isHeaderShowing();
}
public void setProgramView(Program program, AddressSetView view, String name) {
listingPanel.setProgram(program);
markerManager.clearAll();
listingPanel.setView(view);
AddressIndexMap indexMap = listingPanel.getAddressIndexMap();
markerManager.getOverviewProvider().setProgram(program, indexMap);
listingPanel.setBackgroundColorModel(
new MarkerServiceBackgroundColorModel(markerManager, program, indexMap));
setUpAreaMarkerSets(program, name);
if (!view.isEmpty()) {
goTo(new ProgramLocation(program, view.getMinAddress()));
}
repaint();
}
void setUpAreaMarkerSets(Program program, String name) {
if (program == null) {
return;
}
Color diffColor = comparisonOptions.getDiffCodeUnitsBackgroundColor();
Color unmatchedColor = comparisonOptions.getUnmatchedCodeUnitsBackgroundColor();
AddressIndexMap indexMap = listingPanel.getAddressIndexMap();
listingPanel.getFieldPanel().setBackgroundColorModel(new MarkerServiceBackgroundColorModel(
markerManager, program, indexMap));
unmatchedMarkers = markerManager.createAreaMarker(name + " Unmatched Code",
"Instructions that are not matched to an instruction in the other function.",
program, MarkerService.DIFF_PRIORITY, true, true, true,
unmatchedColor);
diffMarkers = markerManager.createAreaMarker(name + " Diffs",
"Instructions that have a difference.", program, MarkerService.DIFF_PRIORITY,
true, true, true, diffColor);
currentCursorMarkers = markerManager.createPointMarker("Cursor",
"Cursor Location", program, MarkerService.FUNCTION_COMPARE_CURSOR_PRIORITY,
true, true, true, cursorHighlightColor, CURSOR_LOC_ICON, false);
}
public ProgramLocation getProgramLocation() {
return listingPanel.getProgramLocation();
}
private void loadOptions() {
ToolOptions fieldOptions = tool.getOptions(GhidraOptions.CATEGORY_BROWSER_FIELDS);
//
// Unusual Code Alert!
// In a normal tool, this option is registered by the Code Browse Plugin. In the VT
// tool, nobody registers this option. Our system logs a warning if an option is used
// but not registered. So, when in a real tool, use the registered/managed option.
// Otherwise, just use the default.
//
if (fieldOptions.isRegistered(GhidraOptions.HIGHLIGHT_CURSOR_LINE_COLOR)) {
cursorHighlightColor = fieldOptions.getColor(GhidraOptions.HIGHLIGHT_CURSOR_LINE_COLOR,
DEFAULT_CURSOR_LINE_COLOR);
}
else {
cursorHighlightColor = DEFAULT_CURSOR_LINE_COLOR;
}
}
private class ListingDisplayMarkerManager extends MarkerManager {
private ListingDisplayMarkerManager(PluginTool tool, String owner) {
super(owner, tool);
}
@Override
public GoToService getGoToService() {
return serviceProvider.getService(GoToService.class);
}
}
private class ListingDisplayServiceProvider extends ServiceProviderStub {
private GoToService goToService;
ListingDisplayServiceProvider() {
goToService = new ListingDisplayGoToService(listingPanel);
}
@SuppressWarnings("unchecked")
@Override
public <T> T getService(Class<T> serviceClass) {
if (serviceClass == GoToService.class) {
return (T) goToService;
}
return null;
}
}
public void updateCursorMarkers(ProgramLocation location) {
if (currentCursorMarkers != null) {
currentCursorMarkers.clearAll();
if (location != null) {
currentCursorMarkers.add(location.getAddress());
}
}
repaint();
}
private void setAreaMarkers(MarkerSet markers, AddressSetView diffAddresses, Color color) {
if (markers == null) {
return;
}
markers.setMarkerColor(color);
markers.clearAll();
markers.add(diffAddresses);
repaint();
}
public void goTo(ProgramLocation location) {
if (location != null) {
listingPanel.goTo(location);
}
updateCursorMarkers(location);
}
public ListingPanel getListingPanel() {
return listingPanel;
}
void dispose() {
listingDiff.removeListingDiffChangeListener(this);
setDiffHighlightProvider(null);
markerManager.dispose();
listingPanel.removeButtonPressedListener(fieldNavigator);
listingPanel.dispose();
}
public FormatManager getFormatManager() {
return listingPanel.getFormatManager();
}
public ViewerPosition getViewerPosition() {
return listingPanel.getFieldPanel().getViewerPosition();
}
public void setViewerPosition(ViewerPosition position) {
listingPanel.getFieldPanel().setViewerPosition(position.getIndex(), position.getXOffset(),
position.getYOffset());
}
public void setMouseNavigationEnabled(boolean enabled) {
listingPanel.removeButtonPressedListener(fieldNavigator);
if (enabled) {
listingPanel.addButtonPressedListener(new FieldNavigator(serviceProvider, null));
}
}
@Override
public void listingDiffChanged() {
updateFunctionComparisonDiffHighlights();
setUnmatchedCodeUnitAreaMarkers();
setDiffAreaMarkers();
}
private void updateFunctionComparisonDiffHighlights() {
setDiffHighlightProvider(
new ListingDiffHighlightProvider(listingDiff, side, comparisonOptions));
}
private void setDiffAreaMarkers() {
Color color = comparisonOptions.getDiffCodeUnitsBackgroundColor();
AddressSetView addresses = listingDiff.getDiffs(side);
setAreaMarkers(diffMarkers, addresses, color);
}
private void setUnmatchedCodeUnitAreaMarkers() {
Color color = comparisonOptions.getUnmatchedCodeUnitsBackgroundColor();
AddressSetView addresses = listingDiff.getUnmatchedCode(side);
setAreaMarkers(unmatchedMarkers, addresses, color);
}
}

View file

@ -0,0 +1,175 @@
/* ###
* 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.util.viewer.listingpanel;
import docking.DockingWindowManager;
import ghidra.app.nav.Navigatable;
import ghidra.app.services.*;
import ghidra.app.util.viewer.util.AddressIndexMap;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSetView;
import ghidra.program.model.listing.Program;
import ghidra.program.model.symbol.ExternalLocation;
import ghidra.program.util.ProgramLocation;
import ghidra.util.Msg;
import ghidra.util.task.TaskMonitor;
/**
* This is a GoToService for a dual listing panel. It allows the goTo to occur relative to the
* left or right listing panel of a dual listing panel, since the left and right sides can be
* displaying totally different addresses.
*/
class ListingDisplayGoToService implements GoToService {
private ListingPanel listingPanel;
/**
* Constructs a goTo service for a dual listing panel.
* @param listingPanel the listing panel to be navigated to
*/
ListingDisplayGoToService(ListingPanel listingPanel) {
this.listingPanel = listingPanel;
}
@Override
public GoToOverrideService getOverrideService() {
return null;
}
@Override
public boolean goTo(ProgramLocation loc) {
return doGoTo(loc);
}
@Override
public boolean goTo(Navigatable navigatable, Program program, Address address,
Address refAddress) {
return doGoTo(new ProgramLocation(program, address));
}
@Override
public boolean goTo(ProgramLocation loc, Program program) {
return doGoTo(loc);
}
@Override
public boolean goTo(Navigatable navigatable, ProgramLocation loc, Program program) {
return doGoTo(loc);
}
@Override
public boolean goTo(Navigatable navigatable, Address goToAddress) {
return doGoTo(goToAddress);
}
@Override
public boolean goTo(Address currentAddress, Address goToAddress) {
return doGoTo(goToAddress);
}
@Override
public boolean goTo(Address goToAddress) {
return doGoTo(goToAddress);
}
@Override
public boolean goTo(Address goToAddress, Program program) {
return doGoTo(goToAddress);
}
@Override
public boolean goToExternalLocation(ExternalLocation extLoc, boolean checkNavigationOption) {
Msg.showError(this, null, "Go To Failed!",
"Can't naviagate to an external function from here");
return false;
}
@Override
public boolean goToExternalLocation(Navigatable navigatable, ExternalLocation extLoc,
boolean checkNavigationOption) {
Msg.showError(this, null, "Go To Failed!",
"Can't naviagate to an external function from here");
return false;
}
@Override
public boolean goToQuery(Address fromAddr, QueryData queryData, GoToServiceListener listener,
TaskMonitor monitor) {
throw new UnsupportedOperationException(
"Go To Address or Label Query is not allowed in a dual listing view.");
}
@Override
public boolean goToQuery(Navigatable navigatable, Address fromAddr, QueryData queryData,
GoToServiceListener listener, TaskMonitor monitor) {
throw new UnsupportedOperationException(
"Go To Address or Label Query is not allowed in a dual listing view.");
}
@Override
public void setOverrideService(GoToOverrideService override) {
// ignored
}
@Override
public Navigatable getDefaultNavigatable() {
return new DualListingNavigator(listingPanel, this);
}
private boolean doGoTo(Address addr) {
// Only go if the address is in the listing's current address set.
if (!validateAddress(addr)) {
return false;
}
return listingPanel.goTo(addr);
}
private boolean doGoTo(ProgramLocation loc) {
if (loc == null) {
return false;
}
// Only go if the location address is in the listing's current address set.
if (!validateAddress(loc.getAddress())) {
return false;
}
return listingPanel.goTo(loc);
}
/**
* Checks the address to make sure the listing won't navigate outside the addresses
* it currently has loaded. If it is not a valid address it will set a status message
* on the dual listing.
* @param addr the address to check
* @return true if the address is valid for navigation.
*/
private boolean validateAddress(Address addr) {
if (addr == null) {
return false;
}
AddressIndexMap map = listingPanel.getAddressIndexMap();
AddressSetView addresses = map.getOriginalAddressSet();
if (!addresses.contains(addr)) {
DockingWindowManager.getActiveInstance().setStatusText(
"\"" + addr.toString() + "\" is outside the current listing's view.");
return false;
}
return true;
}
}

View file

@ -19,7 +19,6 @@ import java.awt.*;
import java.awt.event.*; import java.awt.event.*;
import java.math.BigInteger; import java.math.BigInteger;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.List; import java.util.List;
import javax.swing.*; import javax.swing.*;
@ -68,7 +67,7 @@ public class ListingPanel extends JPanel implements FieldMouseListener, FieldLoc
private ListingModel listingModel; private ListingModel listingModel;
private FieldHeader headerPanel; private FieldHeader headerPanel;
private ButtonPressedListener[] buttonListeners = new ButtonPressedListener[0]; private List<ButtonPressedListener> buttonListeners = new ArrayList<>();
private List<ChangeListener> indexMapChangeListeners = new ArrayList<>(); private List<ChangeListener> indexMapChangeListeners = new ArrayList<>();
private ListingHoverProvider listingHoverHandler; private ListingHoverProvider listingHoverHandler;
@ -448,9 +447,7 @@ public class ListingPanel extends JPanel implements FieldMouseListener, FieldLoc
* @param listener the ButtonPressedListener to add. * @param listener the ButtonPressedListener to add.
*/ */
public void addButtonPressedListener(ButtonPressedListener listener) { public void addButtonPressedListener(ButtonPressedListener listener) {
List<ButtonPressedListener> list = new ArrayList<>(Arrays.asList(buttonListeners)); buttonListeners.add(listener);
list.add(listener);
buttonListeners = list.toArray(new ButtonPressedListener[list.size()]);
} }
/** /**
@ -459,9 +456,7 @@ public class ListingPanel extends JPanel implements FieldMouseListener, FieldLoc
* @param listener the ButtonPressedListener to remove. * @param listener the ButtonPressedListener to remove.
*/ */
public void removeButtonPressedListener(ButtonPressedListener listener) { public void removeButtonPressedListener(ButtonPressedListener listener) {
List<ButtonPressedListener> list = new ArrayList<>(Arrays.asList(buttonListeners)); buttonListeners.remove(listener);
list.remove(listener);
buttonListeners = list.toArray(new ButtonPressedListener[list.size()]);
} }
/** /**
@ -569,7 +564,7 @@ public class ListingPanel extends JPanel implements FieldMouseListener, FieldLoc
layoutModel.dispose(); layoutModel.dispose();
layoutModel = createLayoutModel(null); layoutModel = createLayoutModel(null);
layoutModel.dispose(); layoutModel.dispose();
buttonListeners = null; buttonListeners.clear();
fieldPanel.dispose(); fieldPanel.dispose();
} }
@ -1225,4 +1220,16 @@ public class ListingPanel extends JPanel implements FieldMouseListener, FieldLoc
public void removeDisplayListener(AddressSetDisplayListener listener) { public void removeDisplayListener(AddressSetDisplayListener listener) {
displayListeners.remove(listener); displayListeners.remove(listener);
} }
@Override
public synchronized void addFocusListener(FocusListener l) {
// we are not focusable, defer to contained field panel
fieldPanel.addFocusListener(l);
}
@Override
public synchronized void removeFocusListener(FocusListener l) {
// we are not focusable, defer to contained field panel
fieldPanel.removeFocusListener(l);
}
} }

View file

@ -513,21 +513,11 @@ public class ProgramBigListingModel implements ListingModel, FormatModelListener
} }
} }
@Override
public void formatModelAdded(FieldFormatModel model) {
notifyModelSizeChanged();
}
@Override @Override
public void formatModelChanged(FieldFormatModel model) { public void formatModelChanged(FieldFormatModel model) {
notifyModelSizeChanged(); notifyModelSizeChanged();
} }
@Override
public void formatModelRemoved(FieldFormatModel model) {
notifyModelSizeChanged();
}
@Override @Override
public void addListener(ListingModelListener listener) { public void addListener(ListingModelListener listener) {
listeners.add(listener); listeners.add(listener);

View file

@ -0,0 +1,305 @@
/* ###
* 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.util.viewer.listingpanel;
import ghidra.app.util.SymbolPath;
import ghidra.framework.options.SaveState;
import ghidra.program.model.address.Address;
import ghidra.program.model.listing.*;
import ghidra.program.model.symbol.*;
import ghidra.program.util.*;
import ghidra.util.datastruct.Duo.Side;
/**
* Class for converting a program location from one program to another
*/
public class ProgramLocationTranslator {
private ListingAddressCorrelation correlator;
/**
* Constructor given a correlator for translating addresses
* @param correlator converts address from one program to another
*/
public ProgramLocationTranslator(ListingAddressCorrelation correlator) {
this.correlator = correlator;
}
/**
* Converts a program location from the other side to the given side.
* @param side the side to get a location for
* @param otherSideLocation the location from the other side
* @return a program location for the given side that matches the other given location
*/
public ProgramLocation getProgramLocation(Side side, ProgramLocation otherSideLocation) {
if (correlator == null) {
return null;
}
if (otherSideLocation == null) {
return null;
}
if (otherSideLocation instanceof VariableLocation) {
return getVariableLocation(side, (VariableLocation) otherSideLocation);
}
SaveState saveState = new SaveState();
otherSideLocation.saveState(saveState);
Address otherSideAddress = otherSideLocation.getAddress();
// Try to get the indicated side's address using one of the address correlators.
Address address = getAddress(side, otherSideAddress);
if (address == null || address == Address.NO_ADDRESS) {
return null; // Couldn't determine the indicated side's address.
}
saveState.remove("_ADDRESS");
saveState.putString("_ADDRESS", address.toString());
Address byteAddress = otherSideLocation.getByteAddress();
saveState.remove("_BYTE_ADDR");
Address desiredByteAddress = null;
Program program = correlator.getProgram(side);
if (byteAddress != null) {
// Try to get the indicated side's byte address using one of the address
// correlators or by inferring it.
desiredByteAddress =
inferDesiredByteAddress(otherSideAddress, address, byteAddress,
otherSideLocation.getProgram(), program);
if (desiredByteAddress != null) {
saveState.putString("_BYTE_ADDR", desiredByteAddress.toString());
}
}
// Adjust symbol path for labels if it is part of the location.
adjustSymbolPath(saveState, otherSideAddress, address, byteAddress, desiredByteAddress,
otherSideLocation.getProgram(), program);
// ref address can't be used with indicated side so remove it.
saveState.remove("_REF_ADDRESS");
// Don't know how to find equivalent referenced address for the indicated side,
// so don't put any _REF_ADDRESS back.
return ProgramLocation.getLocation(program, saveState);
}
/**
* Gets the matching address for the given side given an address from the other side. This
* method first attempts to translate the address directly. If that fails, it then attempts
* to get an address for the start of the code unit containing the given address because the
* correlator may only have translations for code unit starts.
*
* @param side the LEFT or RIGHT side to get an address for
* @param otherSidesAddress address the address from the other side
* @return the match address for the given side given an address from the other side
*/
private Address getAddress(Side side, Address otherSidesAddress) {
Side otherSide = side.otherSide();
Address address = correlator.getAddress(side, otherSidesAddress);
if (address != null) {
return address;
}
// Couldn't directly correlate the address.
CodeUnit otherCodeUnit =
correlator.getProgram(otherSide).getListing().getCodeUnitContaining(otherSidesAddress);
if (otherCodeUnit == null) {
return null; // Can't get the code unit's address.
}
Address otherCodeUnitAddress = otherCodeUnit.getMinAddress();
return correlator.getAddress(side, otherCodeUnitAddress);
}
/**
* Gets an matching variable location when given a variable location from the other side.
*
* @param side LEFT or RIGHT indicating which side's variable location is needed.
* @param variableLocation the variable location from the other side.
* @return a variable location for the desired side. Otherwise, null.
*/
private ProgramLocation getVariableLocation(Side side, VariableLocation variableLocation) {
if (variableLocation == null) {
return null;
}
SaveState saveState = new SaveState();
variableLocation.saveState(saveState);
Address address = variableLocation.getAddress();
Address byteAddress = variableLocation.getByteAddress();
Address functionAddress = variableLocation.getFunctionAddress();
// Try to get the indicated side's address using one of the address correlators.
Address desiredAddress = getAddress(side, address);
if (desiredAddress == null || desiredAddress == Address.NO_ADDRESS) {
return null; // Couldn't determine the indicated side's address.
}
// Try to use a byte address.
Address desiredByteAddress = null;
if (byteAddress != null) {
desiredByteAddress = getAddress(side, byteAddress);
}
Address desiredFunctionAddress = null;
if (functionAddress != null) {
desiredFunctionAddress = getAddress(side, functionAddress);
}
Function function = correlator.getFunction(side);
if ((desiredFunctionAddress == null) && (function != null)) {
// If this is a thunk function get the thunked address.
Function thunkedFunction = function.getThunkedFunction(true);
if (thunkedFunction != null) {
desiredFunctionAddress = thunkedFunction.getEntryPoint();
}
}
saveState.remove("_ADDRESS");
saveState.putString("_ADDRESS", desiredAddress.toString());
saveState.remove("_BYTE_ADDR");
if (desiredByteAddress != null) {
saveState.putString("_BYTE_ADDR", desiredByteAddress.toString());
}
saveState.remove("_FUNC_ADDRESS");
if (desiredFunctionAddress != null) {
saveState.putString("_FUNC_ADDRESS", desiredFunctionAddress.toString());
}
// ref address can't be used with indicated side so remove it.
saveState.remove("_REF_ADDRESS");
// Don't know how to find equivalent referenced address for the indicated side,
// so don't put any _REF_ADDRESS back.
return ProgramLocation.getLocation(correlator.getProgram(side), saveState);
}
private void adjustSymbolPath(SaveState saveState, Address address, Address desiredAddress,
Address byteAddress, Address desiredByteAddress, Program program,
Program desiredProgram) {
String[] symbolPathArray = saveState.getStrings("_SYMBOL_PATH", new String[0]);
saveState.remove("_SYMBOL_PATH");
if (symbolPathArray.length == 0) {
return; // save state has no labels for program location.
}
Address symbolAddress = (byteAddress != null) ? byteAddress : address;
Address desiredSymbolAddress =
(desiredByteAddress != null) ? desiredByteAddress : desiredAddress;
if (symbolAddress == null || desiredSymbolAddress == null) {
return; // no address match.
}
Symbol[] symbols = program.getSymbolTable().getSymbols(symbolAddress);
if (symbols.length == 0) {
return; // no symbols in program for matching.
}
Symbol[] desiredSymbols = desiredProgram.getSymbolTable().getSymbols(desiredSymbolAddress);
if (desiredSymbols.length == 0) {
return; // no symbols in desiredProgram for matching.
}
int desiredRow = adjustSymbolRow(saveState, symbols, desiredSymbols);
int desiredIndex = getDesiredSymbolIndex(desiredSymbols, desiredRow);
// Now get the desired symbol.
Symbol desiredSymbol = desiredSymbols[desiredIndex];
SymbolPath symbolPath = getSymbolPath(desiredSymbol);
// Set symbol path for desiredProgram in the save state.
saveState.putStrings("_SYMBOL_PATH", symbolPath.asArray());
}
private int adjustSymbolRow(SaveState saveState, Symbol[] symbols, Symbol[] desiredSymbols) {
// For now just try to choose the same label index if more than one.
int row = saveState.getInt("_ROW", 0);
int desiredRow = row;
if (desiredRow >= desiredSymbols.length) {
desiredRow = desiredSymbols.length - 1;
}
saveState.putInt("_ROW", desiredRow);
return desiredRow;
}
private SymbolPath getSymbolPath(Symbol desiredSymbol) {
String label = desiredSymbol.getName();
Namespace namespace = desiredSymbol.getParentNamespace();
SymbolPath symbolPath;
if (namespace == null || namespace.isGlobal()) {
symbolPath = new SymbolPath(label);
}
else {
symbolPath = new SymbolPath(new SymbolPath(namespace.getSymbol()), label);
}
return symbolPath;
}
private int getDesiredSymbolIndex(Symbol[] desiredSymbols, int desiredRow) {
boolean hasFunction = desiredSymbols[0].getSymbolType().equals(SymbolType.FUNCTION);
// Get the array index of the desired symbol.
int desiredIndex = 0; // Default to first entry in array.
if (desiredRow >= 0 && desiredRow < desiredSymbols.length) {
desiredIndex = desiredRow;
}
if (hasFunction) {
// Last row in GUI is also first entry in array.
if (desiredIndex == desiredSymbols.length - 1) {
desiredIndex = 0; // Set to function element.
}
else {
desiredIndex++; // Adjust for function element at start of array.
}
}
return desiredIndex;
}
/**
* Infers a desired byte address based on the specified <code>byteAddress</code> as well as the
* <code>address</code> and <code>desiredAddress</code> that were matched.
*
* @param address matches up with the <code>desiredAddress</code> from the other function/data.
* @param desiredAddress matches up with the <code>address</code> from the other function/data.
* @param byteAddress the byte address that is associated with <code>address</code>
* @param program the program for the <code>address</code> and <code>byteAddress</code>.
* @param desiredProgram the program for the <code>desiredAddress</code> and
* <code>desiredByteAddress</code>.
* @return the desired byte address that matches up with the indicated <code>byteAddress</code>
* or null if it can't be determined.
*/
private Address inferDesiredByteAddress(Address address, Address desiredAddress,
Address byteAddress, Program program, Program desiredProgram) {
long numBytesIntoCodeUnit = byteAddress.subtract(address);
if (numBytesIntoCodeUnit == 0) {
return desiredAddress;
}
if (numBytesIntoCodeUnit > 0) {
CodeUnit codeUnit = program.getListing().getCodeUnitAt(address);
CodeUnit desiredCodeUnit = desiredProgram.getListing().getCodeUnitAt(desiredAddress);
if (codeUnit != null && desiredCodeUnit != null) {
int desiredCodeUnitLength = desiredCodeUnit.getLength();
if (numBytesIntoCodeUnit < desiredCodeUnitLength) {
// Position at byte within code unit.
return desiredAddress.add(numBytesIntoCodeUnit);
}
// Otherwise position at last byte of code unit.
return desiredAddress.add(desiredCodeUnitLength - 1);
}
}
return null;
}
}

View file

@ -321,25 +321,6 @@ public class MultiListingLayoutModel implements ListingModelListener, FormatMode
} }
/**
* @see ghidra.app.util.viewer.format.FormatModelListener#formatModelAdded(ghidra.app.util.viewer.format.FieldFormatModel)
*/
@Override
public void formatModelAdded(FieldFormatModel model) {
dataChanged(true);
}
/**
* @see ghidra.app.util.viewer.format.FormatModelListener#formatModelRemoved(ghidra.app.util.viewer.format.FieldFormatModel)
*/
@Override
public void formatModelRemoved(FieldFormatModel model) {
dataChanged(true);
}
/**
* @see ghidra.app.util.viewer.format.FormatModelListener#formatModelChanged(ghidra.app.util.viewer.format.FieldFormatModel)
*/
@Override @Override
public void formatModelChanged(FieldFormatModel model) { public void formatModelChanged(FieldFormatModel model) {
modelSizeChanged(); modelSizeChanged();

View file

@ -0,0 +1,80 @@
/* ###
* 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.util.viewer.util;
import java.util.Objects;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSetView;
import ghidra.program.model.listing.Function;
import ghidra.program.model.listing.Program;
import ghidra.util.HTMLUtilities;
/**
* ComparisonData for a generic set of addresses.
*/
public class AddressSetComparisonData implements ComparisonData {
private Program program;
private AddressSetView addresses;
public AddressSetComparisonData(Program program, AddressSetView addresses) {
this.program = Objects.requireNonNull(program);
this.addresses = Objects.requireNonNull(addresses);
}
@Override
public Function getFunction() {
return null;
}
@Override
public AddressSetView getAddressSet() {
return addresses;
}
@Override
public Program getProgram() {
return program;
}
@Override
public String getShortDescription() {
Address minAddress = addresses.getMinAddress();
Address maxAddress = addresses.getMinAddress();
if (minAddress == null) {
return "Empty";
}
return minAddress + ":" + maxAddress;
}
@Override
public String getDescription() {
StringBuffer buf = new StringBuffer();
String padStr = HTMLUtilities.spaces(4);
buf.append(padStr);
String programStr = HTMLUtilities.friendlyEncodeHTML(program.getDomainFile().getPathname());
String specialProgramStr = HTMLUtilities.colorString(FG_COLOR_TITLE, programStr);
buf.append(specialProgramStr);
buf.append(padStr);
return HTMLUtilities.wrapAsHTML(buf.toString());
}
@Override
public boolean isEmpty() {
return addresses.isEmpty();
}
}

View file

@ -20,29 +20,22 @@ import java.awt.Component;
import docking.ComponentProvider; import docking.ComponentProvider;
import docking.DefaultActionContext; import docking.DefaultActionContext;
import ghidra.program.model.listing.Function; import ghidra.program.model.listing.Function;
import ghidra.util.datastruct.Duo.Side;
public abstract class CodeComparisonActionContext extends DefaultActionContext public abstract class CodeComparisonActionContext extends DefaultActionContext
implements CodeComparisonPanelActionContext { implements CodeComparisonPanelActionContext {
/** private CodeComparisonPanel comparisonPanel;
* Constructor with no source component and no context object
* @param provider the ComponentProvider that generated this context.
*/
public CodeComparisonActionContext(ComponentProvider provider) {
super(provider);
}
/** /**
* Constructor with source component and context object * Constructor
* * @param provider the ComponentProvider containing the code comparison panel
* @param provider the ComponentProvider that generated this context. * @param panel the CodeComparisonPanel that generated this context
* @param contextObject an optional contextObject that the ComponentProvider can provide; this * @param component the focusable component for associated with the comparison panel
* can be anything that actions wish to later retrieve
* @param sourceComponent an optional source object; this is intended to be the component that
* is the source of the context, usually the focused component
*/ */
public CodeComparisonActionContext(ComponentProvider provider, Object contextObject, public CodeComparisonActionContext(ComponentProvider provider, CodeComparisonPanel panel,
Component sourceComponent) { Component component) {
super(provider, contextObject, sourceComponent); super(provider, panel, component);
this.comparisonPanel = panel;
} }
/** /**
@ -50,13 +43,18 @@ public abstract class CodeComparisonActionContext extends DefaultActionContext
* side of the function diff window that isn't active. * side of the function diff window that isn't active.
* @return the function to get information from * @return the function to get information from
*/ */
public abstract Function getSourceFunction(); public Function getSourceFunction() {
Side activeSide = comparisonPanel.getActiveSide();
return comparisonPanel.getFunction(activeSide.otherSide());
}
/** /**
* Returns the function that is the target of the info being applied. This will be whichever * Returns the function that is the target of the info being applied. This will be whichever
* side of the function diff window that is active. * side of the function diff window that is active.
* @return the function to apply information to * @return the function to apply information to
*/ */
public abstract Function getTargetFunction(); public Function getTargetFunction() {
Side activeSide = comparisonPanel.getActiveSide();
return comparisonPanel.getFunction(activeSide);
}
} }

View file

@ -15,25 +15,31 @@
*/ */
package ghidra.app.util.viewer.util; package ghidra.app.util.viewer.util;
import java.awt.Color; import static ghidra.app.util.viewer.util.ComparisonData.*;
import static ghidra.util.datastruct.Duo.Side.*;
import java.awt.*;
import java.awt.event.*; import java.awt.event.*;
import java.util.ArrayList;
import java.util.List;
import javax.swing.*; import javax.swing.*;
import javax.swing.border.Border; import javax.swing.border.Border;
import docking.ActionContext; import docking.ActionContext;
import docking.ComponentProvider; import docking.ComponentProvider;
import docking.action.DockingAction; import docking.action.*;
import docking.widgets.fieldpanel.FieldPanel;
import docking.widgets.fieldpanel.internal.FieldPanelCoordinator;
import generic.theme.GThemeDefaults.Colors.Palette; import generic.theme.GThemeDefaults.Colors.Palette;
import ghidra.app.plugin.core.functioncompare.FunctionComparisonPanel; import ghidra.app.plugin.core.functioncompare.FunctionComparisonPanel;
import ghidra.framework.plugintool.PluginTool; import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.address.AddressSet;
import ghidra.program.model.address.AddressSetView; import ghidra.program.model.address.AddressSetView;
import ghidra.program.model.listing.*; import ghidra.program.model.listing.Function;
import ghidra.program.model.listing.Program;
import ghidra.util.HTMLUtilities;
import ghidra.util.classfinder.ClassSearcher; import ghidra.util.classfinder.ClassSearcher;
import ghidra.util.classfinder.ExtensionPoint; import ghidra.util.classfinder.ExtensionPoint;
import ghidra.util.datastruct.Duo;
import ghidra.util.datastruct.Duo.Side;
/** /**
* The CodeComparisonPanel class should be extended by any class that is to be * The CodeComparisonPanel class should be extended by any class that is to be
@ -42,42 +48,29 @@ import ghidra.util.classfinder.ExtensionPoint;
* <p> * <p>
* NOTE: ALL CodeComparisonPanel CLASSES MUST END IN * NOTE: ALL CodeComparisonPanel CLASSES MUST END IN
* <code>CodeComparisonPanel</code> so they are discoverable by the {@link ClassSearcher} * <code>CodeComparisonPanel</code> so they are discoverable by the {@link ClassSearcher}
* @param <T> the type
*/ */
public abstract class CodeComparisonPanel<T extends FieldPanelCoordinator> extends JPanel public abstract class CodeComparisonPanel extends JPanel
implements ExtensionPoint, FocusListener { implements ExtensionPoint {
public static final String HELP_TOPIC = "FunctionComparison";
// MINIMUM_PANEL_WIDTH is used to establish a minimum panel width for the left or right panel. private static final Color ACTIVE_BORDER_COLOR = Palette.getColor("lightpink");
// Without it a long title in either panel can cause the split pane's divider to become locked. private static final int MINIMUM_PANEL_WIDTH = 50;
protected static final int MINIMUM_PANEL_WIDTH = 50; private static final Border NON_ACTIVE_BORDER = BorderFactory.createEmptyBorder(3, 3, 3, 3);
private static final Border ACTIVE_BORDER =
protected static final int LEFT = 0; BorderFactory.createMatteBorder(3, 3, 3, 3, ACTIVE_BORDER_COLOR);
protected static final int RIGHT = 1;
private static final Color FOCUS_BORDER_COLOR = Palette.getColor("lightpink");
protected static final Border FOCUS_BORDER =
BorderFactory.createMatteBorder(3, 3, 3, 3, FOCUS_BORDER_COLOR);
protected static final Border NON_FOCUS_BORDER = BorderFactory.createEmptyBorder(3, 3, 3, 3);
protected static final AddressSetView EMPTY_ADDRESS_SET = new AddressSet();
protected String owner; protected String owner;
protected PluginTool tool; protected PluginTool tool;
protected JComponent topComp;
protected JComponent bottomComp;
protected TitledPanel[] titlePanels = new TitledPanel[2];
protected String leftTitlePrefix = "";
protected String rightTitlePrefix = "";
protected int currProgramIndex = LEFT; // Side with current focus (LEFT or RIGHT)
protected Program[] programs = new Program[2];
protected Function[] functions = new Function[2];
protected Data[] data = new Data[2];
/** If true, the title of each comparison panel will be shown */ protected Duo<ComparisonData> comparisonData = new Duo<>(EMPTY, EMPTY);
private Duo<String> titlePrefixes = new Duo<>("", "");
private Duo<TitledPanel> titlePanels = new Duo<>();
protected Side activeSide = LEFT;
private JSplitPane splitPane;
private ToggleOrientationAction toggleOrientationAction;
private JComponent northComponent;
private boolean showTitles = true; private boolean showTitles = true;
private boolean syncScrolling = false;
private T fieldPanelCoordinator;
/** /**
* Constructor * Constructor
* *
@ -87,91 +80,83 @@ public abstract class CodeComparisonPanel<T extends FieldPanelCoordinator> exten
protected CodeComparisonPanel(String owner, PluginTool tool) { protected CodeComparisonPanel(String owner, PluginTool tool) {
this.owner = owner; this.owner = owner;
this.tool = tool; this.tool = tool;
toggleOrientationAction = new ToggleOrientationAction(getName());
// Important! Subclasses must call the build() method instead of calling it here. This is
// to avoid java's constructor ordering problem
} }
/** /**
* The GUI component for this CodeComparisonPanel * Displays a comparison of two ComparisonData objects
* *
* @return the component * @param left the comparisonData for the left side
* @param right the comparisonData for the right side
*/ */
public abstract JComponent getComponent(); public void loadComparisons(ComparisonData left, ComparisonData right) {
if (comparisonData.equals(left, right)) {
return;
}
comparisonData = new Duo<>(left, right);
comparisonDataChanged();
updateTitles();
}
/** /**
* The title for this code comparison panel * Clears out the current comparisonDatas
*
* @return the title
*/ */
public abstract String getTitle(); public void clearComparisons() {
loadComparisons(ComparisonData.EMPTY, ComparisonData.EMPTY);
/** }
* Specifies the two programs to be compared by this panel
*
* @param leftProgram the program for the left side
* @param rightProgram the program for the right side
*/
protected abstract void setPrograms(Program leftProgram, Program rightProgram);
/**
* Displays a comparison of two program's functions
*
* @param leftFunction the function to show in the left side of the code comparison view
* @param rightFunction the function to show in the right side of the code comparison view
*/
public abstract void loadFunctions(Function leftFunction, Function rightFunction);
/**
* Displays a comparison of two program's data items
*
* @param leftData the data item to show in the left side of the code comparison view
* @param rightData the data item to show in the right side of the code comparison view
*/
public abstract void loadData(Data leftData, Data rightData);
/**
* Displays program information for a particular set of addresses in the two programs
* being compared
*
* @param leftProgram the program in the left side of the code comparison view
* @param rightProgram the program in the right side of the code comparison view
* @param leftAddresses the addresses of the program info to show in the left side
* @param rightAddresses the addresses of the program info to show in the right side
*/
public abstract void loadAddresses(Program leftProgram, Program rightProgram,
AddressSetView leftAddresses, AddressSetView rightAddresses);
/**
* Cleans up resources when this panel is no longer needed
*/
public abstract void dispose();
/**
* Enable/disable navigation in this panel using the mouse
*
* @param enabled false disables mouse navigation
*/
public abstract void setMouseNavigationEnabled(boolean enabled);
/** /**
* Returns the actions for this panel * Returns the actions for this panel
* *
* @return an array of docking actions * @return an array of docking actions
*/ */
public DockingAction[] getActions() { public List<DockingAction> getActions() {
// No actions currently that appear for each CodeComparisonPanel. List<DockingAction> actionList = new ArrayList<>();
// Classes that extend this class will override this method to get all actions actionList.add(toggleOrientationAction);
// specific to that CodeComparisonPanel. return actionList;
DockingAction[] actions = new DockingAction[] {};
return actions;
} }
public boolean getShowTitles() { /**
return showTitles; * Toggles whether or not to display data titles for each side.
} * @param showTitles true to show data titles
*/
public void setShowTitles(boolean showTitles) { public void setShowDataTitles(boolean showTitles) {
this.showTitles = showTitles; this.showTitles = showTitles;
} }
/**
* Returns true if dual panels are displayed horizontally, false if displayed vertically.
* @return true if dual panels are displayed horizontally, false if displayed vertically
*/
public boolean isSideBySide() {
return toggleOrientationAction.isSelected();
}
/**
* Sets the orientation for the dual panels.
* @param b if true, panels will be display horizontally, otherwise vertically
*/
public void setSideBySide(boolean b) {
toggleOrientationAction.setSelected(b);
updateOrientation();
}
/**
* Force subclasses to supply a descriptive name.
*
* @return a descriptive name for this panel type
*/
@Override
public abstract String getName();
/**
* Cleans up resources when this panel is no longer needed
*/
public abstract void dispose();
/** /**
* Returns the context object which corresponds to the area of focus within this provider's * Returns the context object which corresponds to the area of focus within this provider's
* component. Null is returned when there is no context. * component. Null is returned when there is no context.
@ -189,13 +174,41 @@ public abstract class CodeComparisonPanel<T extends FieldPanelCoordinator> exten
* refreshing itself) to respond to the program changing. * refreshing itself) to respond to the program changing.
* @param program the program that was restored. * @param program the program that was restored.
*/ */
public abstract void programRestored(Program program); public void programRestored(Program program) {
updateTitles();
}
/** /**
* Determines if the left code panel currently has focus. * Called when a program is closed.
* @return true if the left side of the code comparison has focus. * @param program the closed program
*/ */
public abstract boolean leftPanelHasFocus(); public void programClosed(Program program) {
// do nothing by default
}
/**
* Returns the {@link Side} that is currently active
* @return the {@link Side} that is currently active
*/
public Side getActiveSide() {
return activeSide;
}
/**
* Sets the component displayed in the top of this panel.
*
* @param component the component.
*/
public void setTopComponent(JComponent component) {
if (northComponent != null) {
remove(northComponent);
}
northComponent = component;
if (northComponent != null) {
add(northComponent, BorderLayout.NORTH);
}
validate();
}
/** /**
* A CodeComparisonPanel should provide a title based on what the code comparison panel * A CodeComparisonPanel should provide a title based on what the code comparison panel
@ -204,81 +217,36 @@ public abstract class CodeComparisonPanel<T extends FieldPanelCoordinator> exten
* @param leftTitlePrefix the prefix string to prepend to the left panel's title. * @param leftTitlePrefix the prefix string to prepend to the left panel's title.
* @param rightTitlePrefix the prefix string to prepend to the right panel's title. * @param rightTitlePrefix the prefix string to prepend to the right panel's title.
*/ */
public abstract void setTitlePrefixes(String leftTitlePrefix, String rightTitlePrefix); public void setTitlePrefixes(String leftTitlePrefix, String rightTitlePrefix) {
titlePrefixes = new Duo<>(leftTitlePrefix, rightTitlePrefix);
/** updateTitles();
* Gets the program being viewed in the left side of this panel.
* @return the program or null
*/
public Program getLeftProgram() {
return programs[LEFT];
} }
/** /**
* Gets the program being viewed in the right side of this panel. * Returns the program being shown in the given side.
* @return the program or null * @param side the {@link Side} to get the program for
* @return the program for the given side.
*/ */
public Program getRightProgram() { public Program getProgram(Side side) {
return programs[RIGHT]; return comparisonData.get(side).getProgram();
} }
/** /**
* Gets the function loaded in the left side of this panel. * Returns the function being shown in the given side.
* @return the function or null * @param side the {@link Side} to get the function for
* @return the function for the given side.
*/ */
public Function getLeftFunction() { public Function getFunction(Side side) {
return functions[LEFT]; return comparisonData.get(side).getFunction();
} }
/** /**
* Gets the function loaded in the right side of this panel. * Returns the addresses being shown in the given side.
* @return the function or null * @param side the {@link Side} to get the program for
* @return the address set for the given side
*/ */
public Function getRightFunction() { public AddressSetView getAddresses(Side side) {
return functions[RIGHT]; return comparisonData.get(side).getAddressSet();
}
/**
* Gets the data loaded in the left side of this panel.
* @return the data or null
*/
public Data getLeftData() {
return data[LEFT];
}
/**
* Gets the data loaded in the right side of this panel.
* @return the data or null
*/
public Data getRightData() {
return data[RIGHT];
}
/**
* Gets the addresses loaded in the left side of this panel.
* @return the addresses or an empty set
*/
public abstract AddressSetView getLeftAddresses();
/**
* Gets the addresses loaded in the right side of this panel.
* @return the addresses or an empty set
*/
public abstract AddressSetView getRightAddresses();
/**
* Refreshes the left side of this panel.
*/
public abstract void refreshLeftPanel();
/**
* Refreshes the right side of this panel.
*/
public abstract void refreshRightPanel();
@Override
public void focusLost(FocusEvent e) {
// Do nothing.
} }
/** /**
@ -286,75 +254,148 @@ public abstract class CodeComparisonPanel<T extends FieldPanelCoordinator> exten
*/ */
public abstract void updateActionEnablement(); public abstract void updateActionEnablement();
/**
* Sets the coordinator for the two views within this code comparison panel. It coordinates
* their scrolling and location synchronization.
* @param fieldPanelCoordinator the coordinator for the two views
*/
public void setFieldPanelCoordinator(T fieldPanelCoordinator) {
if (this.fieldPanelCoordinator != null) {
this.fieldPanelCoordinator.dispose();
}
this.fieldPanelCoordinator = fieldPanelCoordinator;
}
/**
* Gets the current field panel coordinator used to synchronize scrolling between the
* left and right view for this CodeComparisonPanel.
* @return the current FieldPanelCoordinator. Otherwise, null if scrolling is not
* currently synchronized.
*/
protected T getFieldPanelCoordinator() {
return fieldPanelCoordinator;
}
/**
* Creates a new FieldPanelCoordinator used to synchronize scrolling between the
* left and right view for this CodeComparisonPanel.
* @return a new FieldPanelCoordinator
*/
protected abstract T createFieldPanelCoordinator();
/**
* Gets the left field panel for this CodeComparisonPanel.
* @return the left FieldPanel.
*/
public abstract FieldPanel getLeftFieldPanel();
/**
* Gets the right field panel for this CodeComparisonPanel.
* @return the right FieldPanel.
*/
public abstract FieldPanel getRightFieldPanel();
/**
* Determines if the layouts of the views are synchronized with respect to scrolling and
* location.
* @return true if scrolling is synchronized between the two views.
*/
public final boolean isScrollingSynced() {
return syncScrolling;
}
/** /**
* Sets whether or not scrolling is synchronized. * Sets whether or not scrolling is synchronized.
* @param syncScrolling true means synchronize scrolling and location between the two views. * @param b true means synchronize scrolling between the two views.
*/ */
public void setScrollingSyncState(boolean syncScrolling) { public abstract void setSynchronizedScrolling(boolean b);
if (isScrollingSynced() == syncScrolling) {
return;
}
this.syncScrolling = syncScrolling;
// Refresh the left panel. /**
FieldPanel leftPanel = getLeftFieldPanel(); * Returns the Component for the given {@link Side}
leftPanel.validate(); * @param side the Side to its component
leftPanel.invalidate(); * @return the Component for the given {@link Side}
// Refresh the right panel. */
FieldPanel rightPanel = getRightFieldPanel(); public abstract JComponent getComparisonComponent(Side side);
rightPanel.validate();
rightPanel.invalidate();
setFieldPanelCoordinator(syncScrolling ? createFieldPanelCoordinator() : null); /**
* Notification to subclasses that the comparison data has changed
*/
protected abstract void comparisonDataChanged();
private final String getTitle(Side side) {
return comparisonData.get(side).getDescription();
}
private void updateTitles() {
updateTitle(Side.LEFT);
updateTitle(Side.RIGHT);
}
private void updateTitle(Side side) {
String title = showTitles ? getTitle(side) : "";
setTitle(titlePanels.get(side), titlePrefixes.get(side), title);
}
private void updateOrientation() {
int orientation = toggleOrientationAction.isSelected() ? JSplitPane.HORIZONTAL_SPLIT
: JSplitPane.VERTICAL_SPLIT;
splitPane.setOrientation(orientation);
splitPane.setDividerLocation(0.5);
}
private void setTitle(TitledPanel titlePanel, String titlePrefix, String title) {
if (!titlePrefix.isEmpty()) {
titlePrefix += " "; // Add a space between prefix and title.
}
String htmlPrefix = "<html>";
if (title.startsWith(htmlPrefix)) {
titlePanel.setTitleName(htmlPrefix + HTMLUtilities.friendlyEncodeHTML(titlePrefix) +
title.substring(htmlPrefix.length()));
}
else {
titlePanel.setTitleName(titlePrefix + title);
} }
} }
protected final void buildPanel() {
setLayout(new BorderLayout());
TitledPanel leftPanel = new TitledPanel(getTitle(LEFT), getComparisonComponent(LEFT), 5);
TitledPanel rightPanel = new TitledPanel(getTitle(RIGHT), getComparisonComponent(RIGHT), 5);
titlePanels = new Duo<>(leftPanel, rightPanel);
// Set the MINIMUM_PANEL_WIDTH for the left and right panel to prevent the split pane's
// divider from becoming locked (can't be moved) due to extra long title names.
titlePanels.get(LEFT).setMinimumSize(
new Dimension(MINIMUM_PANEL_WIDTH, titlePanels.get(LEFT).getMinimumSize().height));
titlePanels.get(RIGHT).setMinimumSize(
new Dimension(MINIMUM_PANEL_WIDTH, titlePanels.get(RIGHT).getMinimumSize().height));
splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, titlePanels.get(LEFT),
titlePanels.get(RIGHT));
splitPane.setResizeWeight(0.5);
splitPane.setDividerSize(4);
splitPane.setBorder(BorderFactory.createEmptyBorder());
add(splitPane, BorderLayout.CENTER);
updateOrientation();
addMouseAndFocusListeners(LEFT);
addMouseAndFocusListeners(RIGHT);
setActiveSide(LEFT);
}
private void addMouseAndFocusListeners(Side side) {
JComponent comp = getComparisonComponent(side);
comp.addFocusListener(new FocusAdapter() {
@Override
public void focusGained(FocusEvent e) {
setActiveSide(side);
updateContextForFocusGained(e.getComponent());
}
});
comp = getComparisonComponent(side);
MouseAdapter mouseListener = new MouseAdapter() {
@Override
public void mousePressed(MouseEvent e) {
setActiveSide(side);
}
};
addMouseListenerRecursively(comp, mouseListener);
}
private void updateContextForFocusGained(Component component) {
ComponentProvider provider = tool.getWindowManager().getProvider(component);
if (provider != null) {
provider.contextChanged();
}
}
private void addMouseListenerRecursively(Component component, MouseListener listener) {
component.addMouseListener(listener);
if (component instanceof Container container) {
for (int i = 0; i < container.getComponentCount(); i++) {
Component child = container.getComponent(i);
addMouseListenerRecursively(child, listener);
}
}
}
protected void setActiveSide(Side side) {
activeSide = side;
getComparisonComponent(side).setBorder(ACTIVE_BORDER);
getComparisonComponent(side.otherSide()).setBorder(NON_ACTIVE_BORDER);
}
private class ToggleOrientationAction extends ToggleDockingAction {
ToggleOrientationAction(String name) {
super(name + " Toggle Orientation", "FunctionComparison");
setDescription(
"<html>Toggle the layout to be either side by side or one above the other");
setEnabled(true);
MenuData menuData =
new MenuData(new String[] { "Show " + name + " Side-by-Side" }, "Orientation");
setMenuBarData(menuData);
setSelected(true);
}
@Override
public void actionPerformed(ActionContext context) {
updateOrientation();
}
}
}

View file

@ -15,8 +15,6 @@
*/ */
package ghidra.app.util.viewer.util; package ghidra.app.util.viewer.util;
import docking.widgets.fieldpanel.internal.FieldPanelCoordinator;
/** /**
* Action context for a CodeComparisonPanel. * Action context for a CodeComparisonPanel.
*/ */
@ -26,6 +24,6 @@ public interface CodeComparisonPanelActionContext {
* Gets the CodeComparisonPanel associated with this context. * Gets the CodeComparisonPanel associated with this context.
* @return the code comparison panel. * @return the code comparison panel.
*/ */
public abstract CodeComparisonPanel<? extends FieldPanelCoordinator> getCodeComparisonPanel(); public abstract CodeComparisonPanel getCodeComparisonPanel();
} }

View file

@ -0,0 +1,71 @@
/* ###
* 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.util.viewer.util;
import java.awt.Color;
import generic.theme.GThemeDefaults.Colors.Palette;
import ghidra.program.model.address.AddressSetView;
import ghidra.program.model.listing.Function;
import ghidra.program.model.listing.Program;
/**
* ComparisonData is an abstract of items that can be compared in a {@link CodeComparisonPanel}.
* Not all comparison panels can handle all types of comparison data. For example, the decompiler
* comparison only works when the comparison data is a function.
*/
public interface ComparisonData {
public static final Color FG_COLOR_TITLE = Palette.DARK_GRAY;
public static final ComparisonData EMPTY = new EmptyComparisonData();
/**
* Returns the function being compared or null if this comparison data is not function based.
* @return the function being compared or null if this comparison data is not function based
*/
public Function getFunction();
/**
* Returns the set of addresses being compared. Currently, all comparisons are address based,
* so this should never be null.
* @return the set of addresses being compared
*/
public AddressSetView getAddressSet();
/**
* Returns the program containing the data being compared.
* @return the program containing the data being compared.
*/
public Program getProgram();
/**
* Returns a description of the data being compared.
* @return a description of the data being compared.
*/
public String getDescription();
/**
* Returns a short description (useful for tab name)
* @return a short description
*/
public String getShortDescription();
/**
* Returns true if this comparison has no addresses to compare
* @return true if this comparison has no addresses to compare
*/
public boolean isEmpty();
}

View file

@ -0,0 +1,111 @@
/* ###
* 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.util.viewer.util;
import java.util.Objects;
import ghidra.program.model.address.*;
import ghidra.program.model.listing.*;
import ghidra.program.model.mem.MemoryBlock;
import ghidra.util.HTMLUtilities;
/**
* ComparisonData for a Data object
*/
public class DataComparisonData implements ComparisonData {
private final Data data;
private final AddressSet addresses;
public DataComparisonData(Data data, int otherLength) {
this.data = Objects.requireNonNull(data);
int size = Math.max(data.getLength(), otherLength);
this.addresses = new AddressSet(data.getMinAddress(), getEndAddress(size));
}
@Override
public AddressSetView getAddressSet() {
return addresses;
}
@Override
public Program getProgram() {
return data.getProgram();
}
@Override
public Function getFunction() {
return null;
}
@Override
public boolean isEmpty() {
return false;
}
@Override
public String getShortDescription() {
return data.getDataType().getName();
}
@Override
public String getDescription() {
StringBuffer buf = new StringBuffer();
String padStr = HTMLUtilities.spaces(4);
buf.append(padStr);
String dataLabel = data.getLabel();
if (dataLabel == null) { // If we can't get a label for the data then use the address .
Address address = data.getAddress();
dataLabel = address.toString();
}
String dataStr = HTMLUtilities.friendlyEncodeHTML(dataLabel);
String specialDataStr = HTMLUtilities.bold(dataStr);
buf.append(specialDataStr);
Program program = data.getProgram();
if (program != null) {
buf.append(" in ");
String programStr =
HTMLUtilities.friendlyEncodeHTML(program.getDomainFile().getPathname());
String specialProgramStr = HTMLUtilities.colorString(FG_COLOR_TITLE, programStr);
buf.append(specialProgramStr);
buf.append(padStr);
}
return HTMLUtilities.wrapAsHTML(buf.toString());
}
private Address getEndAddress(int size) {
Address minAddress = data.getMinAddress();
if (minAddress.isExternalAddress()) {
return minAddress; // Begin and end address are same for external data.
}
MemoryBlock block = data.getProgram().getMemory().getBlock(minAddress);
Address blockEnd = block.getEnd();
Address endAddress;
try {
endAddress = minAddress.add(size);
if (endAddress.compareTo(blockEnd) > 0) {
endAddress = blockEnd;
}
}
catch (AddressOutOfBoundsException e) {
endAddress = blockEnd;
}
return endAddress;
}
}

View file

@ -0,0 +1,54 @@
/* ###
* 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.util.viewer.util;
import ghidra.program.model.address.AddressSet;
import ghidra.program.model.address.AddressSetView;
import ghidra.program.model.listing.Function;
import ghidra.program.model.listing.Program;
public class EmptyComparisonData implements ComparisonData {
@Override
public Function getFunction() {
return null;
}
@Override
public AddressSetView getAddressSet() {
return new AddressSet();
}
@Override
public Program getProgram() {
return null;
}
@Override
public String getDescription() {
return "No Comparison Data";
}
@Override
public String getShortDescription() {
return "Empty";
}
@Override
public boolean isEmpty() {
return true;
}
}

View file

@ -0,0 +1,87 @@
/* ###
* 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.util.viewer.util;
import java.util.Objects;
import ghidra.program.model.address.AddressSet;
import ghidra.program.model.address.AddressSetView;
import ghidra.program.model.listing.Function;
import ghidra.program.model.listing.Program;
import ghidra.util.HTMLUtilities;
/**
* ComparisonData for a function
*/
public class FunctionComparisonData implements ComparisonData {
private final Function function;
public FunctionComparisonData(Function function) {
this.function = Objects.requireNonNull(function);
}
@Override
public Function getFunction() {
return function;
}
@Override
public AddressSetView getAddressSet() {
if (function.isExternal()) {
return new AddressSet(function.getEntryPoint(), function.getEntryPoint());
}
return function.getBody();
}
@Override
public Program getProgram() {
return function.getProgram();
}
@Override
public String getDescription() {
StringBuffer buf = new StringBuffer();
String padStr = HTMLUtilities.spaces(4);
buf.append(padStr);
String functionStr = HTMLUtilities.friendlyEncodeHTML(function.getName(true) + "()");
String specialFunctionStr = HTMLUtilities.bold(functionStr);
buf.append(specialFunctionStr);
Program program = function.getProgram();
if (program != null) {
buf.append(" in ");
String programStr =
HTMLUtilities.friendlyEncodeHTML(program.getDomainFile().getPathname());
String specialProgramStr = HTMLUtilities.colorString(FG_COLOR_TITLE, programStr);
buf.append(specialProgramStr);
buf.append(padStr);
}
return HTMLUtilities.wrapAsHTML(buf.toString());
}
@Override
public String getShortDescription() {
return function.getName();
}
@Override
public boolean isEmpty() {
return false;
}
}

View file

@ -15,6 +15,10 @@
*/ */
package ghidra.program.util; package ghidra.program.util;
import static ghidra.util.datastruct.Duo.Side.*;
import java.util.ArrayList;
import ghidra.app.util.viewer.listingpanel.ListingDiffChangeListener; import ghidra.app.util.viewer.listingpanel.ListingDiffChangeListener;
import ghidra.program.model.address.*; import ghidra.program.model.address.*;
import ghidra.program.model.data.DataType; import ghidra.program.model.data.DataType;
@ -22,10 +26,8 @@ import ghidra.program.model.lang.Register;
import ghidra.program.model.listing.*; import ghidra.program.model.listing.*;
import ghidra.program.model.mem.MemoryAccessException; import ghidra.program.model.mem.MemoryAccessException;
import ghidra.program.model.scalar.Scalar; import ghidra.program.model.scalar.Scalar;
import ghidra.program.util.ListingAddressCorrelation;
import ghidra.util.Msg; import ghidra.util.Msg;
import ghidra.util.datastruct.Duo.Side;
import java.util.ArrayList;
/** /**
* Determines where instructions couldn't be matched and where they differ between sets of * Determines where instructions couldn't be matched and where they differ between sets of
@ -100,15 +102,15 @@ public class ListingDiff {
private void getDiffs() throws MemoryAccessException { private void getDiffs() throws MemoryAccessException {
init(); init();
AddressSetView addrSet1 = correlation.getAddressesInFirst(); AddressSetView addrSet1 = correlation.getAddresses(LEFT);
AddressSetView addrSet2 = correlation.getAddressesInSecond(); AddressSetView addrSet2 = correlation.getAddresses(RIGHT);
Listing listing1 = correlation.getFirstProgram().getListing(); Listing listing1 = correlation.getProgram(LEFT).getListing();
Listing listing2 = correlation.getSecondProgram().getListing(); Listing listing2 = correlation.getProgram(RIGHT).getListing();
CodeUnitIterator cuIter1 = listing1.getCodeUnits(addrSet1, true); CodeUnitIterator cuIter1 = listing1.getCodeUnits(addrSet1, true);
CodeUnitIterator cuIter2 = listing2.getCodeUnits(addrSet2, true); CodeUnitIterator cuIter2 = listing2.getCodeUnits(addrSet2, true);
for (CodeUnit cu1 : cuIter1) { for (CodeUnit cu1 : cuIter1) {
Address min1 = cu1.getMinAddress(); Address min1 = cu1.getMinAddress();
Address addr2 = correlation.getAddressInSecond(min1); Address addr2 = correlation.getAddress(RIGHT, min1);
if (addr2 == null) { if (addr2 == null) {
// Add codeunit1 to the unmatchedDiffs // Add codeunit1 to the unmatchedDiffs
unmatchedCode1.addRange(cu1.getMinAddress(), cu1.getMaxAddress()); unmatchedCode1.addRange(cu1.getMinAddress(), cu1.getMaxAddress());
@ -122,7 +124,7 @@ public class ListingDiff {
} }
for (CodeUnit cu2 : cuIter2) { for (CodeUnit cu2 : cuIter2) {
Address min2 = cu2.getMinAddress(); Address min2 = cu2.getMinAddress();
Address addr1 = correlation.getAddressInFirst(min2); Address addr1 = correlation.getAddress(LEFT, min2);
if (addr1 == null) { if (addr1 == null) {
// Add codeunit2 to the unmatchedDiffs // Add codeunit2 to the unmatchedDiffs
unmatchedCode2.addRange(cu2.getMinAddress(), cu2.getMaxAddress()); unmatchedCode2.addRange(cu2.getMinAddress(), cu2.getMaxAddress());
@ -138,18 +140,18 @@ public class ListingDiff {
} }
private void recomputeCodeUnitDiffs() { private void recomputeCodeUnitDiffs() {
AddressSetView addressSet1 = correlation.getAddressesInFirst(); AddressSetView addressSet1 = correlation.getAddresses(LEFT);
AddressSetView addressSet2 = correlation.getAddressesInSecond(); AddressSetView addressSet2 = correlation.getAddresses(RIGHT);
AddressSetView matchedAddresses1 = addressSet1.subtract(unmatchedCode1); AddressSetView matchedAddresses1 = addressSet1.subtract(unmatchedCode1);
AddressSetView matchedAddresses2 = addressSet2.subtract(unmatchedCode2); AddressSetView matchedAddresses2 = addressSet2.subtract(unmatchedCode2);
Listing listing1 = correlation.getFirstProgram().getListing(); Listing listing1 = correlation.getProgram(LEFT).getListing();
Listing listing2 = correlation.getSecondProgram().getListing(); Listing listing2 = correlation.getProgram(RIGHT).getListing();
CodeUnitIterator cuIter1 = listing1.getCodeUnits(matchedAddresses1, true); CodeUnitIterator cuIter1 = listing1.getCodeUnits(matchedAddresses1, true);
CodeUnitIterator cuIter2 = listing2.getCodeUnits(matchedAddresses2, true); CodeUnitIterator cuIter2 = listing2.getCodeUnits(matchedAddresses2, true);
codeUnitDiffs1.clear(); codeUnitDiffs1.clear();
for (CodeUnit cu1 : cuIter1) { for (CodeUnit cu1 : cuIter1) {
Address min1 = cu1.getMinAddress(); Address min1 = cu1.getMinAddress();
Address addr2 = correlation.getAddressInSecond(min1); Address addr2 = correlation.getAddress(RIGHT, min1);
if (addr2 == null) { if (addr2 == null) {
continue; continue;
} }
@ -160,7 +162,7 @@ public class ListingDiff {
codeUnitDiffs2.clear(); codeUnitDiffs2.clear();
for (CodeUnit cu2 : cuIter2) { for (CodeUnit cu2 : cuIter2) {
Address min2 = cu2.getMinAddress(); Address min2 = cu2.getMinAddress();
Address addr1 = correlation.getAddressInFirst(min2); Address addr1 = correlation.getAddress(LEFT, min2);
if (addr1 == null) { if (addr1 == null) {
continue; continue;
} }
@ -386,85 +388,49 @@ public class ListingDiff {
} }
/** /**
* Gets the addresses in the first listing where matching code couldn't be determined in the * Gets the address set for unmatched code for the given side
* second listing. * second listing.
* @param side the LEFT or RIGHT side
* @return the addresses of the unmatched code in the first listing. * @return the addresses of the unmatched code in the first listing.
*/ */
public AddressSetView getListing1UnmatchedCode() { public AddressSetView getUnmatchedCode(Side side) {
return new AddressSet(unmatchedCode1); return side == LEFT ? unmatchedCode1 : unmatchedCode2;
}
/**
* Gets the addresses in the second listing where matching code couldn't be determined in the
* first listing.
* @return the addresses of the unmatched code in the second listing.
*/
public AddressSetView getListing2UnmatchedCode() {
return new AddressSet(unmatchedCode2);
} }
/** /**
* Gets the addresses in the first listing where differences were found based on the current * Gets the addresses in the first listing where differences were found based on the current
* difference settings. * difference settings.
* @param side the side (LEFT or RIGHT) to get the listing diffs for
* @return the addresses with differences in the first listing. * @return the addresses with differences in the first listing.
*/ */
public AddressSetView getListing1Diffs() { public AddressSetView getDiffs(Side side) {
AddressSet diffs = new AddressSet(getListing1ByteDiffs());
diffs.add(getListing1CodeUnitDiffs());
return DiffUtility.getCodeUnitSet(diffs, correlation.getFirstProgram());
}
/** AddressSet diffs = new AddressSet(getByteDiffs(side));
* Gets the addresses in the second listing where differences were found based on the current diffs.add(getCodeUnitDiffs(side));
* difference settings. return DiffUtility.getCodeUnitSet(diffs, correlation.getProgram(side));
* @return the addresses with differences in the second listing.
*/
public AddressSetView getListing2Diffs() {
AddressSet diffs = new AddressSet(getListing2ByteDiffs());
diffs.add(getListing2CodeUnitDiffs());
return DiffUtility.getCodeUnitSet(diffs, correlation.getSecondProgram());
} }
/** /**
* Gets the addresses in the first listing where code unit (mnemonic and/or operand) differences * Gets the addresses in the first listing where code unit (mnemonic and/or operand) differences
* were found based on the current difference settings. * were found based on the current difference settings.
* @param side the side (LEFT or RIGHT) to get the code unit diffs for
* @return the addresses with code unit differences in the first listing. * @return the addresses with code unit differences in the first listing.
*/ */
public AddressSetView getListing1CodeUnitDiffs() { public AddressSetView getCodeUnitDiffs(Side side) {
return new AddressSet(codeUnitDiffs1); return new AddressSet(side == LEFT ? codeUnitDiffs1 : codeUnitDiffs2);
}
/**
* Gets the addresses in the second listing where code unit (mnemonic and/or operand) differences
* were found based on the current difference settings.
* @return the addresses with code unit differences in the second listing.
*/
public AddressSetView getListing2CodeUnitDiffs() {
return new AddressSet(codeUnitDiffs2);
} }
/** /**
* Gets the addresses in the first listing where byte differences were found based on the * Gets the addresses in the first listing where byte differences were found based on the
* current difference settings. * current difference settings.
* @param side the side (LEFT or RIGHT) to get the byte diffs for
* @return the addresses with byte differences in the first listing. * @return the addresses with byte differences in the first listing.
*/ */
public AddressSetView getListing1ByteDiffs() { public AddressSetView getByteDiffs(Side side) {
if (ignoreByteDiffs) { if (ignoreByteDiffs) {
return new AddressSet(); return new AddressSet();
} }
return new AddressSet(byteDiffs1); return new AddressSet(side == LEFT ? byteDiffs1 : byteDiffs2);
}
/**
* Gets the addresses in the second listing where byte differences were found based on the
* current difference settings.
* @return the addresses with byte differences in the second listing.
*/
public AddressSetView getListing2ByteDiffs() {
if (ignoreByteDiffs) {
return new AddressSet();
}
return new AddressSet(byteDiffs2);
} }
/** /**
@ -480,9 +446,9 @@ public class ListingDiff {
return null; return null;
} }
if (isListing1) { if (isListing1) {
return correlation.getAddressInSecond(address); return correlation.getAddress(RIGHT, address);
} }
return correlation.getAddressInFirst(address); return correlation.getAddress(LEFT, address);
} }
/** /**
@ -604,32 +570,26 @@ public class ListingDiff {
} }
/** /**
* Gets the matching code unit from the other listing for the specified code unit from one * Gets the matching code unit from the other side listing given a code unit from a given side
* of the two listings whose differences this class determines. * of the two listings whose differences this class determines.
* @param codeUnit the code unit whose match this determines. * @param codeUnit the code unit whose match this determines.
* @param isListing1 true indicates the code unit is from the first listing. false indicates * @param side the side the code unit came from
* it is from the second listing.
* @return the matching code unit or null * @return the matching code unit or null
*/ */
public CodeUnit getMatchingCodeUnit(CodeUnit codeUnit, boolean isListing1) { public CodeUnit getMatchingCodeUnit(CodeUnit codeUnit, Side side) {
if (correlation == null) { if (correlation == null) {
return null; return null;
} }
Side otherSide = side.otherSide();
Address minAddress = codeUnit.getMinAddress(); Address minAddress = codeUnit.getMinAddress();
Program sourceProgram = correlation.getFirstProgram(); Program otherSideProgram = correlation.getProgram(otherSide);
Program destinationProgram = correlation.getSecondProgram();
if (isListing1) { Address otherAddress = correlation.getAddress(otherSide, minAddress);
Address destination = correlation.getAddressInSecond(minAddress); if (otherAddress != null) {
if (destination != null) { return otherSideProgram.getListing().getCodeUnitAt(otherAddress);
return destinationProgram.getListing().getCodeUnitAt(destination);
}
}
else {
Address source = correlation.getAddressInFirst(minAddress);
if (source != null) {
return sourceProgram.getListing().getCodeUnitAt(source);
}
} }
// code unit not from our programs. // code unit not from our programs.
return null; return null;
} }

View file

@ -15,6 +15,7 @@
*/ */
package ghidra.app.plugin.core.functioncompare; package ghidra.app.plugin.core.functioncompare;
import static ghidra.util.datastruct.Duo.Side.*;
import static org.junit.Assert.*; import static org.junit.Assert.*;
import java.awt.Window; import java.awt.Window;
@ -149,7 +150,7 @@ public class CompareFunctionsSlowTest extends AbstractGhidraHeadedIntegrationTes
assertTrue(prevAction.isEnabledForContext(context)); assertTrue(prevAction.isEnabledForContext(context));
JPanel rightPanel = JPanel rightPanel =
provider.getComponent().getDualListingPanel().getRightPanel().getFieldPanel(); provider.getComponent().getDualListingPanel().getListingPanel(RIGHT).getFieldPanel();
clickMouse(rightPanel, 1, 30, 30, 1, 0); clickMouse(rightPanel, 1, 30, 30, 1, 0);
waitForSwing(); waitForSwing();
provider.getComponent().updateActionEnablement(); provider.getComponent().updateActionEnablement();
@ -243,7 +244,7 @@ public class CompareFunctionsSlowTest extends AbstractGhidraHeadedIntegrationTes
* Builds a program with 2 functions * Builds a program with 2 functions
*/ */
private ProgramBuilder buildTestProgram1() throws Exception { private ProgramBuilder buildTestProgram1() throws Exception {
ProgramBuilder builder = new ProgramBuilder("TestPgm1", ProgramBuilder._TOY_BE); ProgramBuilder builder = new ProgramBuilder("Program 1", ProgramBuilder._TOY_BE);
builder.createMemory(".text", "0x1001000", 0x6600); builder.createMemory(".text", "0x1001000", 0x6600);
builder.setProperty(Program.DATE_CREATED, new Date(100000000)); // arbitrary, but consistent builder.setProperty(Program.DATE_CREATED, new Date(100000000)); // arbitrary, but consistent
@ -261,7 +262,7 @@ public class CompareFunctionsSlowTest extends AbstractGhidraHeadedIntegrationTes
* Builds a program with 1 function * Builds a program with 1 function
*/ */
private ProgramBuilder buildTestProgram2() throws Exception { private ProgramBuilder buildTestProgram2() throws Exception {
ProgramBuilder builder = new ProgramBuilder("TestPgm1", ProgramBuilder._TOY_BE); ProgramBuilder builder = new ProgramBuilder("Program 2", ProgramBuilder._TOY_BE);
builder.createMemory(".text", "0x1001000", 0x6600); builder.createMemory(".text", "0x1001000", 0x6600);
builder.setProperty(Program.DATE_CREATED, new Date(100000000)); // arbitrary, but consistent builder.setProperty(Program.DATE_CREATED, new Date(100000000)); // arbitrary, but consistent

View file

@ -0,0 +1,191 @@
/* ###
* 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.util.viewer.listingpanel;
import static ghidra.util.datastruct.Duo.Side.*;
import static org.junit.Assert.*;
import org.junit.*;
import generic.test.AbstractGenericTest;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSetView;
import ghidra.program.model.data.IntegerDataType;
import ghidra.program.model.listing.*;
import ghidra.program.model.symbol.Symbol;
import ghidra.program.util.*;
import ghidra.test.ToyProgramBuilder;
import ghidra.util.datastruct.Duo.Side;
public class ProgramLocationTranslatorTest extends AbstractGenericTest {
private static long PROGRAM1_BASE = 0x100000;
private static long PROGRAM2_BASE = 0x200000;
private Program program1;
private Program program2;
private Function function1;
private Function functionA;
private TestAddressCorrelation correlator;
private ProgramLocationTranslator translator;
@Before
public void setUp() throws Exception {
program1 = createProgram1();
program2 = createProgram2();
function1 = program1.getListing().getFunctionAt(addr(program1, PROGRAM1_BASE));
functionA = program1.getListing().getFunctionAt(addr(program2, PROGRAM2_BASE));
correlator = new TestAddressCorrelation();
translator = new ProgramLocationTranslator(correlator);
}
@After
public void tearDown() {
program1.release(this);
program2.release(this);
}
@Test
public void testBasicProgramLocation() {
ProgramLocation location = new ProgramLocation(program1, program1.getMinAddress());
ProgramLocation otherLocation = translator.getProgramLocation(RIGHT, location);
assertEquals(program2, otherLocation.getProgram());
assertEquals(program2.getMinAddress(), otherLocation.getAddress());
ProgramLocation roundTripLocation = translator.getProgramLocation(LEFT, otherLocation);
assertEquals(location, roundTripLocation);
}
@Test
public void testBytesLocation() {
Address minAddress = program1.getMinAddress();
Address byteAddress = minAddress.add(1);
ProgramLocation location =
new BytesFieldLocation(program1, minAddress, byteAddress, null, 2);
ProgramLocation otherLocation = translator.getProgramLocation(RIGHT, location);
assertTrue(otherLocation instanceof BytesFieldLocation);
assertEquals(program2, otherLocation.getProgram());
assertEquals(program2.getMinAddress(), otherLocation.getAddress());
assertEquals(program2.getMinAddress().add(1), otherLocation.getByteAddress());
assertEquals(2, otherLocation.getCharOffset());
ProgramLocation roundTripLocation = translator.getProgramLocation(LEFT, otherLocation);
assertEquals(location, roundTripLocation);
}
@Test
public void testVariableLocation() {
Function f = program1.getListing().getFunctionAt(addr(program1, PROGRAM1_BASE));
Variable[] variables = f.getAllVariables();
VariableLocation location = new VariableLocation(program1, variables[0], 0, 1);
ProgramLocation otherLocation = translator.getProgramLocation(RIGHT, location);
assertTrue(otherLocation instanceof VariableLocation);
ProgramLocation roundTripLocation = translator.getProgramLocation(LEFT, otherLocation);
assertEquals(location, roundTripLocation);
}
@Test
public void testLableFieldLocation() {
Symbol[] symbols = program1.getSymbolTable().getSymbols(addr(program1, PROGRAM1_BASE));
assertEquals(1, symbols.length);
LabelFieldLocation location = new LabelFieldLocation(symbols[0], 0, 3);
ProgramLocation otherLocation = translator.getProgramLocation(RIGHT, location);
assertTrue(otherLocation instanceof LabelFieldLocation);
ProgramLocation roundTripLocation = translator.getProgramLocation(LEFT, otherLocation);
assertEquals(location, roundTripLocation);
}
private Address addr(Program program, long offset) {
return program.getAddressFactory().getDefaultAddressSpace().getAddress(offset);
}
private Program createProgram1() throws Exception {
ToyProgramBuilder builder = new ToyProgramBuilder("program1", true, this);
builder.createMemory("text", Long.toHexString(PROGRAM1_BASE), 0x5000);
buildFunction(builder, "fun1", PROGRAM1_BASE);
buildFunction(builder, "fun2", PROGRAM1_BASE + 0x1000);
buildFunction(builder, "fun3", PROGRAM1_BASE + 0x2000);
Program program = builder.getProgram();
builder.dispose();
return program;
}
private Program createProgram2() throws Exception {
ToyProgramBuilder builder = new ToyProgramBuilder("program2", true, this);
builder.createMemory("text", Long.toHexString(PROGRAM2_BASE), 5000);
buildFunction(builder, "funA", PROGRAM2_BASE);
buildFunction(builder, "funB", PROGRAM2_BASE + 0x1000);
Program program = builder.getProgram();
builder.dispose();
return program;
}
private void buildFunction(ToyProgramBuilder builder, String name, long address)
throws Exception {
builder.addBytesFallthrough(address);
builder.addBytesReturn(address + 2);
String functionStart = Long.toHexString(address);
builder.disassemble(functionStart, 3, true);
Function function = builder.createFunction(functionStart);
Variable var =
new LocalVariableImpl("i", new IntegerDataType(), -0x4, builder.getProgram());
builder.addFunctionVariable(function, var);
builder.createLabel(functionStart, name);// function label
}
private class TestAddressCorrelation implements ListingAddressCorrelation {
@Override
public Program getProgram(Side side) {
return side == LEFT ? program1 : program2;
}
@Override
public Function getFunction(Side side) {
return side == LEFT ? function1 : functionA;
}
@Override
public AddressSetView getAddresses(Side side) {
return side == LEFT ? program1.getMemory() : program2.getMemory();
}
@Override
public Address getAddress(Side side, Address otherSideAddress) {
if (side == LEFT) {
long offset = otherSideAddress.getOffset() - PROGRAM2_BASE;
return addr(program1, PROGRAM1_BASE + offset);
}
long offset = otherSideAddress.getOffset() - PROGRAM1_BASE;
return addr(program2, PROGRAM2_BASE + offset);
}
}
}

View file

@ -1,262 +0,0 @@
/* ###
* 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.codecompare;
import java.util.List;
import javax.swing.Icon;
import docking.ActionContext;
import docking.action.DockingAction;
import docking.action.ToggleDockingAction;
import docking.action.ToolBarData;
import generic.theme.GIcon;
import ghidra.app.decompiler.component.DecompileData;
import ghidra.app.decompiler.component.DecompilerCodeComparisonPanel;
import ghidra.app.decompiler.component.DualDecompileResultsListener;
import ghidra.app.decompiler.component.DualDecompilerActionContext;
import ghidra.codecompare.graphanalysis.TokenBin;
import ghidra.framework.options.OptionsChangeListener;
import ghidra.framework.options.ToolOptions;
import ghidra.framework.plugintool.PluginTool;
import ghidra.util.HTMLUtilities;
import ghidra.util.HelpLocation;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.Task;
import ghidra.util.task.TaskBuilder;
import ghidra.util.task.TaskLauncher;
import ghidra.util.task.TaskListener;
import resources.Icons;
import resources.MultiIcon;
/**
* This is a CodeComparisonPanel that gets discovered by other providers that display dual
* comparison views.<br>
* Note: Therefore there may not be any other classes that refer directly to it.
*/
public class DecompilerDiffCodeComparisonPanel
extends DecompilerCodeComparisonPanel<CodeDiffFieldPanelCoordinator>
implements DualDecompileResultsListener, OptionsChangeListener {
public static final String CODE_DIFF_VIEW = "Decompiler Diff View";
private static final String HELP_TOPIC = "FunctionComparison";
private DecompileDataDiff decompileDataDiff;
private DiffClangHighlightController leftHighlightController;
private DiffClangHighlightController rightHighlightController;
private CodeDiffFieldPanelCoordinator decompilerFieldPanelCoordinator;
private MyToggleExactConstantMatching toggleExactConstantMatchingAction;
private boolean isMatchingConstantsExactly = true;
private boolean toggleFlagWhenLastVisible = isMatchingConstantsExactly;
private CompareFuncsFromMatchedTokensAction compareFuncsAction;
private DecompilerCodeComparisonOptions comparisonOptions;
/**
* Constructor
* @param owner owner
* @param tool tool
*/
public DecompilerDiffCodeComparisonPanel(String owner, PluginTool tool) {
super(owner, tool);
comparisonOptions = new DecompilerCodeComparisonOptions();
initializeOptions();
leftHighlightController = new DiffClangHighlightController(comparisonOptions);
rightHighlightController = new DiffClangHighlightController(comparisonOptions);
setHighlightControllers(leftHighlightController, rightHighlightController);
// Make the left highlight listen to the right.
leftHighlightController.addListener(rightHighlightController);
// Make the right highlight listen to the left.
rightHighlightController.addListener(leftHighlightController);
addDualDecompileResultsListener(this);
decompilerFieldPanelCoordinator = new CodeDiffFieldPanelCoordinator(this);
setFieldPanelCoordinator(decompilerFieldPanelCoordinator);
}
private void initializeOptions() {
ToolOptions options =
tool.getOptions(DecompilerCodeComparisonOptions.OPTIONS_CATEGORY_NAME);
options.addOptionsChangeListener(this);
comparisonOptions.registerOptions(options);
comparisonOptions.loadOptions(options);
}
@Override
public void optionsChanged(ToolOptions options, String optionName, Object oldValue,
Object newValue) {
comparisonOptions.loadOptions(options);
repaint();
}
@Override
public void setVisible(boolean aFlag) {
if (aFlag == isVisible()) {
return;
}
if (aFlag) {
// Becoming visible.
if (toggleFlagWhenLastVisible != isMatchingConstantsExactly) {
if (decompileDataDiff != null) {
determineDecompilerDifferences();
}
}
}
else {
// No longer visible.
toggleFlagWhenLastVisible = isMatchingConstantsExactly;
}
super.setVisible(aFlag);
updateActionEnablement();
}
@Override
public String getTitle() {
return CODE_DIFF_VIEW;
}
@Override
public void decompileResultsSet(DecompileData leftDecompileResults,
DecompileData rightDecompileResults) {
if ((leftDecompileResults == null) || (rightDecompileResults == null) ||
(leftDecompileResults.getFunction() == null) ||
(rightDecompileResults.getFunction() == null)) {
return;
}
decompileDataDiff = new DecompileDataDiff(leftDecompileResults, rightDecompileResults);
determineDecompilerDifferences();
}
List<TokenBin> getHighBins() {
return decompilerFieldPanelCoordinator.getHighBins();
}
private void determineDecompilerDifferences() {
if (decompileDataDiff == null) {
return;
}
DetermineDecompilerDifferencesTask task =
new DetermineDecompilerDifferencesTask(decompileDataDiff, isMatchingConstantsExactly,
leftHighlightController, rightHighlightController, decompilerFieldPanelCoordinator);
task.addTaskListener(new TaskListener() {
@Override
public void taskCompleted(Task completedTask) {
// Does this need anything here?
}
@Override
public void taskCancelled(Task cancelledTask) {
// Does this need anything here?
}
});
new TaskLauncher(task, getComponent());
}
@Override
protected void createActions() {
super.createActions();
toggleExactConstantMatchingAction = new MyToggleExactConstantMatching(getClass().getName());
compareFuncsAction = new CompareFuncsFromMatchedTokensAction(this, tool);
}
@Override
public DockingAction[] getActions() {
DockingAction[] parentActions = super.getActions();
DockingAction[] myActions =
new DockingAction[] { toggleExactConstantMatchingAction, compareFuncsAction };
DockingAction[] allActions = new DockingAction[parentActions.length + myActions.length];
System.arraycopy(parentActions, 0, allActions, 0, parentActions.length);
System.arraycopy(myActions, 0, allActions, parentActions.length, myActions.length);
return allActions;
}
@Override
public void updateActionEnablement() {
// Need to enable/disable toolbar button.
toggleExactConstantMatchingAction.setEnabled(isVisible());
}
public class MyToggleExactConstantMatching extends ToggleDockingAction {
private final Icon EXACT_CONSTANT_MATCHING_ICON = new GIcon("icon.base.source.c");
private final Icon NO_EXACT_CONSTANT_MATCHING_ICON =
new MultiIcon(EXACT_CONSTANT_MATCHING_ICON, Icons.NOT_ALLOWED_ICON);
/**
* Creates an action for toggling exact constant matching in the code diff's
* dual decompiler.
* @param owner the owner of this action (typically the provider).
*/
public MyToggleExactConstantMatching(String owner) {
super("Toggle Exact Constant Matching", owner);
setHelpLocation(new HelpLocation(HELP_TOPIC, "Toggle Exact Constant Matching"));
this.setToolBarData(new ToolBarData(NO_EXACT_CONSTANT_MATCHING_ICON, "toggles"));
setDescription(HTMLUtilities.toHTML("Toggle whether or not constants must\n" +
"be exactly the same value to be a match\n" + "in the " + CODE_DIFF_VIEW + "."));
setSelected(false);
setEnabled(true);
}
@Override
public boolean isEnabledForContext(ActionContext context) {
if (context instanceof DualDecompilerActionContext) {
return true;
}
return false;
}
@Override
public void actionPerformed(ActionContext context) {
isMatchingConstantsExactly = !isSelected();
if (DecompilerDiffCodeComparisonPanel.this.isVisible()) {
DecompilerDiffCodeComparisonPanel.this.determineDecompilerDifferences();
}
}
@Override
public void setSelected(boolean selected) {
getToolBarData().setIcon(
selected ? NO_EXACT_CONSTANT_MATCHING_ICON : EXACT_CONSTANT_MATCHING_ICON);
super.setSelected(selected);
}
}
@Override
protected CodeDiffFieldPanelCoordinator createFieldPanelCoordinator() {
CodeDiffFieldPanelCoordinator coordinator = new CodeDiffFieldPanelCoordinator(this);
if (decompileDataDiff != null) {
TaskBuilder.withRunnable(monitor -> {
try {
coordinator.replaceDecompileDataDiff(decompileDataDiff,
isMatchingConstantsExactly, monitor);
}
catch (CancelledException e) {
coordinator.clearLineNumberPairing();
}
}).setTitle("Initializing Code Compare").launchNonModal();
}
return coordinator;
}
}

View file

@ -26,6 +26,7 @@ import generic.theme.GColor;
import ghidra.app.decompiler.ClangSyntaxToken; import ghidra.app.decompiler.ClangSyntaxToken;
import ghidra.app.decompiler.ClangToken; import ghidra.app.decompiler.ClangToken;
import ghidra.app.decompiler.component.*; import ghidra.app.decompiler.component.*;
import ghidra.codecompare.decompile.DecompilerCodeComparisonOptions;
import ghidra.codecompare.graphanalysis.TokenBin; import ghidra.codecompare.graphanalysis.TokenBin;
import ghidra.util.ColorUtils; import ghidra.util.ColorUtils;
import ghidra.util.SystemUtilities; import ghidra.util.SystemUtilities;

View file

@ -13,7 +13,9 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package ghidra.codecompare; package ghidra.codecompare.decompile;
import static ghidra.util.datastruct.Duo.Side.*;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
@ -22,16 +24,17 @@ import docking.ActionContext;
import docking.action.DockingAction; import docking.action.DockingAction;
import ghidra.app.decompiler.ClangToken; import ghidra.app.decompiler.ClangToken;
import ghidra.app.decompiler.DecompilerLocation; import ghidra.app.decompiler.DecompilerLocation;
import ghidra.app.decompiler.component.*; import ghidra.app.decompiler.component.DecompilerPanel;
import ghidra.codecompare.graphanalysis.TokenBin; import ghidra.codecompare.graphanalysis.TokenBin;
import ghidra.program.model.listing.Program; import ghidra.program.model.listing.Program;
import ghidra.util.datastruct.Duo.Side;
/** /**
* This is a base class for actions in a {@link DecompilerDiffCodeComparisonPanel} * This is a base class for actions in a {@link DecompilerDiffCodeComparisonPanel}
*/ */
public abstract class AbstractMatchedTokensAction extends DockingAction { public abstract class AbstractMatchedTokensAction extends DockingAction {
protected DecompilerDiffCodeComparisonPanel diffPanel; protected DecompilerCodeComparisonPanel diffPanel;
protected boolean disableOnReadOnly; protected boolean disableOnReadOnly;
/** /**
@ -43,7 +46,7 @@ public abstract class AbstractMatchedTokensAction extends DockingAction {
* @param disableOnReadOnly if true, action will be disabled for read-only programs * @param disableOnReadOnly if true, action will be disabled for read-only programs
*/ */
public AbstractMatchedTokensAction(String actionName, String owner, public AbstractMatchedTokensAction(String actionName, String owner,
DecompilerDiffCodeComparisonPanel diffPanel, boolean disableOnReadOnly) { DecompilerCodeComparisonPanel diffPanel, boolean disableOnReadOnly) {
super(actionName, owner); super(actionName, owner);
this.diffPanel = diffPanel; this.diffPanel = diffPanel;
this.disableOnReadOnly = disableOnReadOnly; this.disableOnReadOnly = disableOnReadOnly;
@ -63,26 +66,20 @@ public abstract class AbstractMatchedTokensAction extends DockingAction {
if (!(context instanceof DualDecompilerActionContext compareContext)) { if (!(context instanceof DualDecompilerActionContext compareContext)) {
return false; return false;
} }
if (!(compareContext DecompilerCodeComparisonPanel decompPanel = compareContext.getCodeComparisonPanel();
.getCodeComparisonPanel() instanceof DecompilerCodeComparisonPanel decompPanel)) {
return false;
}
if (disableOnReadOnly) { if (disableOnReadOnly) {
//get the program corresponding to the panel with focus //get the program corresponding to the panel with focus
Program program = decompPanel.getLeftProgram(); Side focusedSide = decompPanel.getActiveSide();
Program program = decompPanel.getProgram(focusedSide);
if (program == null) { if (program == null) {
return false; //panel initializing; don't enable action return false; //panel initializing; don't enable action
} }
if (!decompPanel.leftPanelHasFocus()) {
program = decompPanel.getRightProgram();
}
if (!program.canSave()) { if (!program.canSave()) {
return false; //program is read-only, don't enable action return false; //program is read-only, don't enable action
} }
} }
@SuppressWarnings("unchecked")
TokenPair currentPair = getCurrentTokenPair(decompPanel); TokenPair currentPair = getCurrentTokenPair(decompPanel);
return enabledForTokens(currentPair); return enabledForTokens(currentPair);
@ -96,9 +93,9 @@ public abstract class AbstractMatchedTokensAction extends DockingAction {
* @return matching tokens (or null if no match) * @return matching tokens (or null if no match)
*/ */
protected TokenPair getCurrentTokenPair( protected TokenPair getCurrentTokenPair(
DecompilerCodeComparisonPanel<? extends DualDecompilerFieldPanelCoordinator> decompPanel) { DecompilerCodeComparisonPanel decompPanel) {
DecompilerPanel focusedPanel = decompPanel.getFocusedDecompilerPanel().getDecompilerPanel(); DecompilerPanel focusedPanel = decompPanel.getActiveDisplay().getDecompilerPanel();
if (!(focusedPanel.getCurrentLocation() instanceof DecompilerLocation focusedLocation)) { if (!(focusedPanel.getCurrentLocation() instanceof DecompilerLocation focusedLocation)) {
return null; return null;
@ -126,7 +123,8 @@ public abstract class AbstractMatchedTokensAction extends DockingAction {
while (tokenIter.hasNext()) { while (tokenIter.hasNext()) {
ClangToken currentMatch = tokenIter.next(); ClangToken currentMatch = tokenIter.next();
if (currentMatch.getClass().equals(focusedToken.getClass())) { if (currentMatch.getClass().equals(focusedToken.getClass())) {
return decompPanel.leftPanelHasFocus() ? new TokenPair(focusedToken, currentMatch) return decompPanel.getActiveSide() == LEFT
? new TokenPair(focusedToken, currentMatch)
: new TokenPair(currentMatch, focusedToken); : new TokenPair(currentMatch, focusedToken);
} }
} }

View file

@ -0,0 +1,200 @@
/* ###
* 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.codecompare.decompile;
import java.math.BigInteger;
import java.util.function.Consumer;
import docking.widgets.fieldpanel.FieldPanel;
import docking.widgets.fieldpanel.support.FieldLocation;
import docking.widgets.fieldpanel.support.ViewerPosition;
import ghidra.GhidraOptions;
import ghidra.app.decompiler.DecompileOptions;
import ghidra.app.decompiler.component.*;
import ghidra.codecompare.DiffClangHighlightController;
import ghidra.framework.options.ToolOptions;
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.util.ProgramLocation;
/**
* Represents one side of a dual decompiler compare window. It holds the decompiler controller and
* related state information for one side.
*/
public class CDisplay {
private final static String OPTIONS_TITLE = "Decompiler";
private DecompilerController controller;
private DecompileOptions decompileOptions;
private FieldLocation lastCursorPosition;
private DiffClangHighlightController highlightController;
private Program program;
private DecompilerProgramListener programListener;
public CDisplay(DecompilerCodeComparisonOptions comparisonOptions,
DecompileResultsListener decompileListener,
Consumer<ProgramLocation> locationConsumer) {
highlightController = new DiffClangHighlightController(comparisonOptions);
decompileOptions = new DecompileOptions();
DecompilerCallbackHandler handler = new DecompilerCallbackHandlerAdapter() {
@Override
public void locationChanged(ProgramLocation programLocation) {
locationConsumer.accept(programLocation);
}
};
controller = new DecompilerController(handler, decompileOptions, null) {
@Override
public void setDecompileData(DecompileData decompileData) {
super.setDecompileData(decompileData);
decompileListener.setDecompileData(decompileData);
controller.getDecompilerPanel().validate();
}
};
controller.getDecompilerPanel().setHighlightController(highlightController);
programListener = new DecompilerProgramListener(controller, () -> refresh());
}
public DecompilerPanel getDecompilerPanel() {
return controller.getDecompilerPanel();
}
private void updateProgram(PluginTool tool, Function function) {
Program newProgram = function == null ? null : function.getProgram();
if (program == newProgram) {
return;
}
if (program != null) {
program.removeListener(programListener);
}
program = newProgram;
if (program != null) {
program.addListener(programListener);
initializeOptions(tool, function);
}
}
public void showFunction(PluginTool tool, Function function) {
updateProgram(tool, function);
lastCursorPosition = null;
if (function == null) {
clearAndShowMessage("No Function");
return;
}
if (function.isExternal()) {
clearAndShowMessage("\"" + function.getName(true) + "\" is an external function.");
return;
}
Address entry = function.getEntryPoint();
ProgramLocation location = new ProgramLocation(program, entry);
controller.display(program, location, new ViewerPosition(0, 0, 0));
}
public void clearAndShowMessage(String message) {
controller.setDecompileData(new EmptyDecompileData(message));
DecompilerPanel decompilerPanel = getDecompilerPanel();
decompilerPanel.paintImmediately(decompilerPanel.getBounds());
}
public void setMouseNavigationEnabled(boolean enabled) {
controller.setMouseNavigationEnabled(enabled);
}
public void dispose() {
if (program != null) {
program.removeListener(programListener);
program = null;
}
programListener.dispose();
controller.dispose();
}
public DecompilerController getController() {
return controller;
}
public void refresh() {
saveCursorPosition();
DecompileData data = getDecompileData();
if (data != null) {
controller.refreshDisplay(data.getProgram(), data.getLocation(), null);
}
}
public void programClosed(Program closedProgram) {
if (closedProgram == this.program) {
controller.clear();
controller.programClosed(closedProgram);
updateProgram(null, null);
}
}
public boolean isBusy() {
return controller.isDecompiling();
}
public DecompileData getDecompileData() {
DecompileData decompileData = controller.getDecompileData();
if (decompileData instanceof EmptyDecompileData) {
return null;
}
return decompileData;
}
public void initializeOptions(PluginTool tool, Function function) {
if (tool == null) {
return;
}
ToolOptions fieldOptions = tool.getOptions(GhidraOptions.CATEGORY_BROWSER_FIELDS);
ToolOptions options = tool.getOptions(OPTIONS_TITLE);
Program program = function == null ? null : function.getProgram();
decompileOptions.grabFromToolAndProgram(fieldOptions, options, program);
}
DiffClangHighlightController getHighlightController() {
return highlightController;
}
private void saveCursorPosition() {
lastCursorPosition = getDecompilerPanel().getFieldPanel().getCursorLocation();
}
void restoreCursorPosition() {
if (lastCursorPosition != null) {
BigInteger index = lastCursorPosition.getIndex();
int fieldNum = lastCursorPosition.getFieldNum();
int row = lastCursorPosition.getRow();
int col = lastCursorPosition.getCol();
FieldPanel fieldPanel = getDecompilerPanel().getFieldPanel();
fieldPanel.setCursorPosition(index, fieldNum, row, col);
}
}
}

View file

@ -13,7 +13,9 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package ghidra.codecompare; package ghidra.codecompare.decompile;
import static ghidra.util.datastruct.Duo.Side.*;
import java.math.BigInteger; import java.math.BigInteger;
import java.util.ArrayList; import java.util.ArrayList;
@ -25,7 +27,6 @@ import org.apache.commons.collections4.bidimap.DualHashBidiMap;
import docking.widgets.fieldpanel.support.ViewerPosition; import docking.widgets.fieldpanel.support.ViewerPosition;
import ghidra.app.decompiler.*; import ghidra.app.decompiler.*;
import ghidra.app.decompiler.component.DecompilerPanel; import ghidra.app.decompiler.component.DecompilerPanel;
import ghidra.app.decompiler.component.DualDecompilerFieldPanelCoordinator;
import ghidra.codecompare.graphanalysis.TokenBin; import ghidra.codecompare.graphanalysis.TokenBin;
import ghidra.program.model.pcode.HighFunction; import ghidra.program.model.pcode.HighFunction;
import ghidra.program.util.ProgramLocation; import ghidra.program.util.ProgramLocation;
@ -52,10 +53,10 @@ public class CodeDiffFieldPanelCoordinator extends DualDecompilerFieldPanelCoord
* Constructor * Constructor
* @param dualDecompilerPanel decomp comparison panel * @param dualDecompilerPanel decomp comparison panel
*/ */
public CodeDiffFieldPanelCoordinator(DecompilerDiffCodeComparisonPanel dualDecompilerPanel) { public CodeDiffFieldPanelCoordinator(DecompilerCodeComparisonPanel dualDecompilerPanel) {
super(dualDecompilerPanel); super(dualDecompilerPanel);
this.leftDecompilerPanel = dualDecompilerPanel.getLeftDecompilerPanel(); this.leftDecompilerPanel = dualDecompilerPanel.getDecompilerPanel(LEFT);
this.rightDecompilerPanel = dualDecompilerPanel.getRightDecompilerPanel(); this.rightDecompilerPanel = dualDecompilerPanel.getDecompilerPanel(RIGHT);
leftToRightLineNumberPairing = new DualHashBidiMap<>(); leftToRightLineNumberPairing = new DualHashBidiMap<>();
} }

View file

@ -13,13 +13,13 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package ghidra.codecompare; package ghidra.codecompare.decompile;
import static ghidra.util.datastruct.Duo.Side.*;
import docking.ActionContext; import docking.ActionContext;
import docking.action.MenuData; import docking.action.MenuData;
import ghidra.app.decompiler.ClangFuncNameToken; import ghidra.app.decompiler.ClangFuncNameToken;
import ghidra.app.decompiler.component.DecompilerCodeComparisonPanel;
import ghidra.app.decompiler.component.DualDecompilerActionContext;
import ghidra.app.plugin.core.functioncompare.FunctionComparisonProvider; import ghidra.app.plugin.core.functioncompare.FunctionComparisonProvider;
import ghidra.app.services.FunctionComparisonService; import ghidra.app.services.FunctionComparisonService;
import ghidra.framework.plugintool.PluginTool; import ghidra.framework.plugintool.PluginTool;
@ -45,7 +45,7 @@ public class CompareFuncsFromMatchedTokensAction extends AbstractMatchedTokensAc
* @param diffPanel diff Panel * @param diffPanel diff Panel
* @param tool tool * @param tool tool
*/ */
public CompareFuncsFromMatchedTokensAction(DecompilerDiffCodeComparisonPanel diffPanel, public CompareFuncsFromMatchedTokensAction(DecompilerCodeComparisonPanel diffPanel,
PluginTool tool) { PluginTool tool) {
super(ACTION_NAME, tool.getName(), diffPanel, false); super(ACTION_NAME, tool.getName(), diffPanel, false);
this.tool = tool; this.tool = tool;
@ -84,12 +84,8 @@ public class CompareFuncsFromMatchedTokensAction extends AbstractMatchedTokensAc
return; return;
} }
if (!(compareContext DecompilerCodeComparisonPanel decompPanel = compareContext.getCodeComparisonPanel();
.getCodeComparisonPanel() instanceof DecompilerCodeComparisonPanel decompPanel)) {
return;
}
@SuppressWarnings("unchecked")
TokenPair currentPair = getCurrentTokenPair(decompPanel); TokenPair currentPair = getCurrentTokenPair(decompPanel);
if (currentPair == null || currentPair.leftToken() == null || if (currentPair == null || currentPair.leftToken() == null ||
currentPair.rightToken() == null) { currentPair.rightToken() == null) {
@ -99,8 +95,8 @@ public class CompareFuncsFromMatchedTokensAction extends AbstractMatchedTokensAc
ClangFuncNameToken leftFuncToken = (ClangFuncNameToken) currentPair.leftToken(); ClangFuncNameToken leftFuncToken = (ClangFuncNameToken) currentPair.leftToken();
ClangFuncNameToken rightFuncToken = (ClangFuncNameToken) currentPair.rightToken(); ClangFuncNameToken rightFuncToken = (ClangFuncNameToken) currentPair.rightToken();
Function leftFunction = getFuncFromToken(leftFuncToken, decompPanel.getLeftProgram()); Function leftFunction = getFuncFromToken(leftFuncToken, decompPanel.getProgram(LEFT));
Function rightFunction = getFuncFromToken(rightFuncToken, decompPanel.getRightProgram()); Function rightFunction = getFuncFromToken(rightFuncToken, decompPanel.getProgram(RIGHT));
if (leftFunction == null || rightFunction == null) { if (leftFunction == null || rightFunction == null) {
return; return;
} }

View file

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package ghidra.codecompare; package ghidra.codecompare.decompile;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashSet; import java.util.HashSet;

View file

@ -13,18 +13,23 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package ghidra.codecompare; package ghidra.codecompare.decompile;
import java.awt.Color; import java.awt.Color;
import generic.theme.GColor; import generic.theme.GColor;
import ghidra.framework.options.OptionsChangeListener;
import ghidra.framework.options.ToolOptions; import ghidra.framework.options.ToolOptions;
import ghidra.framework.plugintool.PluginTool;
import ghidra.util.HelpLocation; import ghidra.util.HelpLocation;
import ghidra.util.bean.opteditor.OptionsVetoException;
import utility.function.Callback;
import utility.function.Dummy;
/** /**
* This class holds the options for the decompiler diff view. * This class holds the options for the decompiler diff view.
*/ */
public class DecompilerCodeComparisonOptions { public class DecompilerCodeComparisonOptions implements OptionsChangeListener {
private static final String MATCHING_TOKEN_HIGHLIGHT_KEY = "Focused Token Match Highlight"; private static final String MATCHING_TOKEN_HIGHLIGHT_KEY = "Focused Token Match Highlight";
private static final String UNMATCHED_TOKEN_HIGHLIGHT_KEY = "Focused Token Unmatched Highlight"; private static final String UNMATCHED_TOKEN_HIGHLIGHT_KEY = "Focused Token Unmatched Highlight";
@ -53,15 +58,18 @@ public class DecompilerCodeComparisonOptions {
private Color unmatchedTokenHighlight; private Color unmatchedTokenHighlight;
private Color ineligibleTokenHighlight; private Color ineligibleTokenHighlight;
private Color diffHighlight; private Color diffHighlight;
private Callback optionsChangedCallback;
public static final String OPTIONS_CATEGORY_NAME = "Decompiler Code Comparison"; public static final String OPTIONS_CATEGORY_NAME = "Decompiler Code Comparison";
public static final String HELP_TOPIC = "FunctionComparison"; public static final String HELP_TOPIC = "FunctionComparison";
/** public DecompilerCodeComparisonOptions(PluginTool tool, Callback optionsChangedCallback) {
* Constructor this.optionsChangedCallback = Dummy.ifNull(optionsChangedCallback);
*/ ToolOptions options =
public DecompilerCodeComparisonOptions() { tool.getOptions(DecompilerCodeComparisonOptions.OPTIONS_CATEGORY_NAME);
options.addOptionsChangeListener(this);
registerOptions(options);
loadOptions(options);
} }
/** /**
@ -136,4 +144,11 @@ public class DecompilerCodeComparisonOptions {
return diffHighlight; return diffHighlight;
} }
@Override
public void optionsChanged(ToolOptions options, String optionName, Object oldValue,
Object newValue) throws OptionsVetoException {
loadOptions(options);
optionsChangedCallback.call();
}
} }

View file

@ -0,0 +1,395 @@
/* ###
* 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.codecompare.decompile;
import static ghidra.util.datastruct.Duo.Side.*;
import java.awt.Component;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.List;
import javax.swing.Icon;
import javax.swing.JComponent;
import docking.ActionContext;
import docking.ComponentProvider;
import docking.action.*;
import docking.options.OptionsService;
import generic.theme.GIcon;
import ghidra.app.decompiler.component.DecompileData;
import ghidra.app.decompiler.component.DecompilerPanel;
import ghidra.app.util.viewer.util.CodeComparisonPanel;
import ghidra.codecompare.DiffClangHighlightController;
import ghidra.codecompare.graphanalysis.TokenBin;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.listing.Function;
import ghidra.program.model.listing.Program;
import ghidra.program.util.ProgramLocation;
import ghidra.util.HTMLUtilities;
import ghidra.util.HelpLocation;
import ghidra.util.datastruct.Duo;
import ghidra.util.datastruct.Duo.Side;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskBuilder;
import ghidra.util.task.TaskLauncher;
import resources.Icons;
import resources.MultiIcon;
/**
* Panel that displays two decompilers for comparison
*/
public class DecompilerCodeComparisonPanel
extends CodeComparisonPanel {
public static final String NAME = "Decompile Diff View";
private boolean isStale = true;
private Duo<CDisplay> cDisplays = new Duo<>();
private DecompilerCodeComparisonOptions comparisonOptions;
private CodeDiffFieldPanelCoordinator coordinator;
private DecompileDataDiff decompileDataDiff;
private ToggleExactConstantMatching toggleExactConstantMatchingAction;
private List<DockingAction> actions = new ArrayList<>();
private Duo<Function> functions = new Duo<>();
/**
* Creates a comparison panel with two decompilers
*
* @param owner the owner of this panel
* @param tool the tool displaying this panel
*/
public DecompilerCodeComparisonPanel(String owner, PluginTool tool) {
super(owner, tool);
comparisonOptions = new DecompilerCodeComparisonOptions(tool, () -> repaint());
cDisplays = new Duo<>(buildCDisplay(LEFT), buildCDisplay(RIGHT));
cDisplays.get(LEFT).initializeOptions(tool, getFunction(LEFT));
cDisplays.get(RIGHT).initializeOptions(tool, getFunction(RIGHT));
buildPanel();
setSynchronizedScrolling(true);
linkHighlightControllers();
createActions();
}
@Override
public String getName() {
return NAME;
}
@Override
protected void comparisonDataChanged() {
maybeLoadFunction(LEFT, comparisonData.get(LEFT).getFunction());
maybeLoadFunction(RIGHT, comparisonData.get(RIGHT).getFunction());
if (coordinator != null) {
coordinator.leftLocationChanged((ProgramLocation) null);
}
}
public void maybeLoadFunction(Side side, Function function) {
// we keep a local copy of the function so we know if it is already decompiled
if (functions.get(side) == function) {
return;
}
// Clear the scroll info and highlight info to prevent unnecessary highlighting, etc.
loadFunction(side, null);
loadFunction(side, function);
}
@Override
public void dispose() {
setSynchronizedScrolling(false); // disposes any exiting coordinator
cDisplays.each(CDisplay::dispose);
comparisonOptions = null;
}
/**
* Gets the display from the active side.
* @return the active display
*/
public CDisplay getActiveDisplay() {
return cDisplays.get(activeSide);
}
/**
* Gets the left side's C display panel.
* @return the left C display panel
*/
public CDisplay getLeftPanel() {
return cDisplays.get(LEFT);
}
/**
* Gets the right side's C display panel.
* @return the right C display panel
*/
public CDisplay getRightPanel() {
return cDisplays.get(RIGHT);
}
@Override
public List<DockingAction> getActions() {
List<DockingAction> allActions = super.getActions();
allActions.addAll(actions);
return allActions;
}
@Override
public ActionContext getActionContext(ComponentProvider provider, MouseEvent event) {
Component component = event != null ? event.getComponent()
: getActiveDisplay().getDecompilerPanel().getFieldPanel();
DualDecompilerActionContext dualDecompContext =
new DualDecompilerActionContext(provider, this, component);
return dualDecompContext;
}
@Override
public void programClosed(Program program) {
cDisplays.each(c -> c.programClosed(program));
}
@Override
public JComponent getComparisonComponent(Side side) {
return cDisplays.get(side).getDecompilerPanel();
}
private void createActions() {
toggleExactConstantMatchingAction = new ToggleExactConstantMatching(getClass().getName());
actions.add(new DecompilerDiffViewFindAction(owner, tool));
actions.add(new DecompilerCodeComparisonOptionsAction());
actions.add(toggleExactConstantMatchingAction);
actions.add(new CompareFuncsFromMatchedTokensAction(this, tool));
}
private void decompileDataSet(Side side, DecompileData dcompileData) {
cDisplays.get(side).restoreCursorPosition();
updateDiffs();
}
private boolean isMatchingConstantsExactly() {
return !toggleExactConstantMatchingAction.isSelected();
}
private void loadFunction(Side side, Function function) {
if (functions.get(side) != function) {
functions = functions.with(side, function);
cDisplays.get(side).showFunction(tool, function);
}
}
private void locationChanged(Side side, ProgramLocation location) {
if (coordinator != null) {
if (side == LEFT) {
coordinator.leftLocationChanged(location);
}
else {
coordinator.rightLocationChanged(location);
}
}
}
private void updateDiffs() {
DecompileData leftDecompileData = cDisplays.get(LEFT).getDecompileData();
DecompileData rightDecompileData = cDisplays.get(RIGHT).getDecompileData();
if (isValid(leftDecompileData) && isValid(rightDecompileData)) {
decompileDataDiff = new DecompileDataDiff(leftDecompileData, rightDecompileData);
determineDecompilerDifferences();
}
}
private boolean isValid(DecompileData decompileData) {
return decompileData != null && decompileData.isValid();
}
private CDisplay buildCDisplay(Side side) {
return new CDisplay(comparisonOptions,
decompileData -> decompileDataSet(side, decompileData),
l -> locationChanged(side, l));
}
private void linkHighlightControllers() {
DiffClangHighlightController left = cDisplays.get(LEFT).getHighlightController();
DiffClangHighlightController right = cDisplays.get(RIGHT).getHighlightController();
left.addListener(right);
right.addListener(left);
}
@Override
public void setSynchronizedScrolling(boolean synchronize) {
if (coordinator != null) {
coordinator.dispose();
coordinator = null;
}
if (synchronize) {
coordinator = createCoordinator();
doSynchronize();
}
}
private void doSynchronize() {
CDisplay activeDisplay = getActiveDisplay();
ProgramLocation programLocation =
activeDisplay.getDecompilerPanel().getCurrentLocation();
if (activeDisplay == cDisplays.get(LEFT)) {
coordinator.leftLocationChanged(programLocation);
}
else {
coordinator.rightLocationChanged(programLocation);
}
}
private CodeDiffFieldPanelCoordinator createCoordinator() {
CodeDiffFieldPanelCoordinator panelCoordinator = new CodeDiffFieldPanelCoordinator(this);
if (decompileDataDiff != null) {
TaskBuilder.withRunnable(monitor -> {
try {
panelCoordinator.replaceDecompileDataDiff(decompileDataDiff,
isMatchingConstantsExactly(), monitor);
}
catch (CancelledException e) {
panelCoordinator.clearLineNumberPairing();
}
}).setTitle("Initializing Code Compare").launchNonModal();
}
return panelCoordinator;
}
private class DecompilerCodeComparisonOptionsAction extends DockingAction {
DecompilerCodeComparisonOptionsAction() {
super("Decompiler Code Comparison Options", owner);
setDescription("Show the tool options for the Decompiler Code Comparison.");
setPopupMenuData(new MenuData(new String[] { "Properties" }, null, "Z_Properties"));
setHelpLocation(
new HelpLocation("FunctionComparison", "Decompiler_Code_Comparison_Options"));
}
@Override
public boolean isEnabledForContext(ActionContext context) {
return (context instanceof DualDecompilerActionContext);
}
@Override
public void actionPerformed(ActionContext context) {
OptionsService service = tool.getService(OptionsService.class);
service.showOptionsDialog("FunctionComparison", "Decompiler Code Comparison");
}
}
@Override
public void setVisible(boolean b) {
super.setVisible(b);
if (b && isStale) {
determineDecompilerDifferences();
isStale = false;
}
updateActionEnablement();
}
List<TokenBin> getHighBins() {
return coordinator == null ? null : coordinator.getHighBins();
}
private void determineDecompilerDifferences() {
if (decompileDataDiff == null) {
return;
}
DiffClangHighlightController leftHighlights = cDisplays.get(LEFT).getHighlightController();
DiffClangHighlightController rightHighlights =
cDisplays.get(RIGHT).getHighlightController();
DetermineDecompilerDifferencesTask task =
new DetermineDecompilerDifferencesTask(decompileDataDiff, isMatchingConstantsExactly(),
leftHighlights, rightHighlights, coordinator);
new TaskLauncher(task, this);
}
@Override
public void updateActionEnablement() {
// Need to enable/disable toolbar button.
toggleExactConstantMatchingAction.setEnabled(isVisible());
}
public boolean isBusy() {
return cDisplays.get(LEFT).isBusy() || cDisplays.get(RIGHT).isBusy();
}
public DecompilerPanel getDecompilerPanel(Side side) {
return cDisplays.get(side).getDecompilerPanel();
}
public class ToggleExactConstantMatching extends ToggleDockingAction {
private final Icon EXACT_CONSTANT_MATCHING_ICON = new GIcon("icon.base.source.c");
private final Icon NO_EXACT_CONSTANT_MATCHING_ICON =
new MultiIcon(EXACT_CONSTANT_MATCHING_ICON, Icons.NOT_ALLOWED_ICON);
/**
* Creates an action for toggling exact constant matching in the code diff's
* dual decompiler.
* @param owner the owner of this action (typically the provider).
*/
public ToggleExactConstantMatching(String owner) {
super("Toggle Exact Constant Matching", owner);
setHelpLocation(new HelpLocation(HELP_TOPIC, "Toggle Exact Constant Matching"));
this.setToolBarData(new ToolBarData(NO_EXACT_CONSTANT_MATCHING_ICON, "toggles"));
setDescription(HTMLUtilities.toHTML("Toggle whether or not constants must\n" +
"be exactly the same value to be a match\n" + "in the Decomiler Diff View."));
setSelected(false);
setEnabled(true);
}
@Override
public boolean isEnabledForContext(ActionContext context) {
if (context instanceof DualDecompilerActionContext) {
return true;
}
return false;
}
@Override
public void actionPerformed(ActionContext context) {
if (isVisible()) {
determineDecompilerDifferences();
}
}
@Override
public void setSelected(boolean selected) {
getToolBarData().setIcon(
selected ? NO_EXACT_CONSTANT_MATCHING_ICON : EXACT_CONSTANT_MATCHING_ICON);
super.setSelected(selected);
}
}
}

View file

@ -13,7 +13,9 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package ghidra.app.decompiler.component; package ghidra.codecompare.decompile;
import static ghidra.util.datastruct.Duo.Side.*;
import java.awt.event.KeyEvent; import java.awt.event.KeyEvent;
@ -23,17 +25,17 @@ import docking.ActionContext;
import docking.DockingUtils; import docking.DockingUtils;
import docking.action.*; import docking.action.*;
import docking.widgets.FindDialog; import docking.widgets.FindDialog;
import docking.widgets.fieldpanel.internal.FieldPanelCoordinator; import ghidra.app.decompiler.component.DecompilerPanel;
import ghidra.app.plugin.core.decompile.actions.DecompilerSearcher; import ghidra.app.plugin.core.decompile.actions.DecompilerSearcher;
import ghidra.app.util.HelpTopics; import ghidra.app.util.HelpTopics;
import ghidra.app.util.viewer.util.CodeComparisonPanel;
import ghidra.framework.plugintool.PluginTool; import ghidra.framework.plugintool.PluginTool;
import ghidra.util.HelpLocation; import ghidra.util.HelpLocation;
import ghidra.util.datastruct.Duo;
import ghidra.util.datastruct.Duo.Side;
public class DecompilerDiffViewFindAction extends DockingAction { public class DecompilerDiffViewFindAction extends DockingAction {
private FindDialog leftFindDialog; private Duo<FindDialog> findDialogs;
private FindDialog rightFindDialog;
private PluginTool tool; private PluginTool tool;
public DecompilerDiffViewFindAction(String owner, PluginTool tool) { public DecompilerDiffViewFindAction(String owner, PluginTool tool) {
@ -53,33 +55,21 @@ public class DecompilerDiffViewFindAction extends DockingAction {
@Override @Override
public boolean isEnabledForContext(ActionContext context) { public boolean isEnabledForContext(ActionContext context) {
if (!(context instanceof DualDecompilerActionContext dualContext)) { return (context instanceof DualDecompilerActionContext);
return false;
}
CodeComparisonPanel<? extends FieldPanelCoordinator> compPanel =
dualContext.getCodeComparisonPanel();
if (!(compPanel instanceof DecompilerCodeComparisonPanel decompilerCompPanel)) {
return false;
}
return true;
} }
@Override @Override
public void actionPerformed(ActionContext context) { public void actionPerformed(ActionContext context) {
DualDecompilerActionContext dualContext = (DualDecompilerActionContext) context; DualDecompilerActionContext dualContext = (DualDecompilerActionContext) context;
@SuppressWarnings("unchecked") DecompilerCodeComparisonPanel decompilerCompPanel =
DecompilerCodeComparisonPanel<? extends FieldPanelCoordinator> decompilerCompPanel = dualContext.getCodeComparisonPanel();
(DecompilerCodeComparisonPanel<? extends FieldPanelCoordinator>) dualContext
.getCodeComparisonPanel();
DecompilerPanel focusedPanel = decompilerCompPanel.getLeftDecompilerPanel(); Side focusedSide = decompilerCompPanel.getActiveSide();
FindDialog dialog = leftFindDialog; DecompilerPanel focusedPanel = decompilerCompPanel.getDecompilerPanel(focusedSide);
if (!decompilerCompPanel.leftPanelHasFocus()) { FindDialog dialog = findDialogs.get(focusedSide);
focusedPanel = decompilerCompPanel.getRightDecompilerPanel();
dialog = rightFindDialog;
}
if (dialog == null) { if (dialog == null) {
dialog = createFindDialog(focusedPanel, decompilerCompPanel.leftPanelHasFocus()); dialog = createFindDialog(focusedPanel, focusedSide);
findDialogs = findDialogs.with(focusedSide, dialog);
} }
String text = focusedPanel.getSelectedText(); String text = focusedPanel.getSelectedText();
@ -89,8 +79,8 @@ public class DecompilerDiffViewFindAction extends DockingAction {
tool.showDialog(dialog); tool.showDialog(dialog);
} }
private FindDialog createFindDialog(DecompilerPanel decompilerPanel, boolean isLeftFocused) { private FindDialog createFindDialog(DecompilerPanel decompilerPanel, Side side) {
String title = (isLeftFocused ? "Left" : "Right"); String title = (side == LEFT ? "Left" : "Right");
title += " Decompiler Find Text"; title += " Decompiler Find Text";
FindDialog dialog = new FindDialog(title, new DecompilerSearcher(decompilerPanel)) { FindDialog dialog = new FindDialog(title, new DecompilerSearcher(decompilerPanel)) {
@ -102,13 +92,6 @@ public class DecompilerDiffViewFindAction extends DockingAction {
}; };
dialog.setHelpLocation(new HelpLocation(HelpTopics.DECOMPILER, "ActionFind")); dialog.setHelpLocation(new HelpLocation(HelpTopics.DECOMPILER, "ActionFind"));
if (isLeftFocused) {
leftFindDialog = dialog;
}
else {
rightFindDialog = dialog;
}
return dialog; return dialog;
} }

View file

@ -13,12 +13,13 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package ghidra.codecompare; package ghidra.codecompare.decompile;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import ghidra.app.decompiler.ClangToken; import ghidra.app.decompiler.ClangToken;
import ghidra.codecompare.DiffClangHighlightController;
import ghidra.codecompare.graphanalysis.TokenBin; import ghidra.codecompare.graphanalysis.TokenBin;
import ghidra.util.exception.CancelledException; import ghidra.util.exception.CancelledException;
import ghidra.util.task.Task; import ghidra.util.task.Task;

View file

@ -0,0 +1,52 @@
/* ###
* 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.codecompare.decompile;
import java.awt.Component;
import docking.ComponentProvider;
import ghidra.app.context.RestrictedAddressSetContext;
import ghidra.app.util.viewer.util.CodeComparisonActionContext;
/**
* Action context for a dual decompiler panel.
*/
public class DualDecompilerActionContext extends CodeComparisonActionContext
implements RestrictedAddressSetContext {
private DecompilerCodeComparisonPanel decompilerComparisonPanel = null;
/**
* Creates an action context for a dual decompiler panel.
* @param provider the provider for this context
* @param panel the DecompilerComparisonPanel
* @param source the source of the action
*/
public DualDecompilerActionContext(ComponentProvider provider,
DecompilerCodeComparisonPanel panel, Component source) {
super(provider, panel, source);
this.decompilerComparisonPanel = panel;
}
/**
* Returns the {@link DecompilerCodeComparisonPanel} that generated this context
* @return the decompiler comparison panel that generated this context
*/
@Override
public DecompilerCodeComparisonPanel getCodeComparisonPanel() {
return decompilerComparisonPanel;
}
}

View file

@ -13,7 +13,9 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package ghidra.app.decompiler.component; package ghidra.codecompare.decompile;
import static ghidra.util.datastruct.Duo.Side.*;
import docking.widgets.fieldpanel.FieldPanel; import docking.widgets.fieldpanel.FieldPanel;
import docking.widgets.fieldpanel.internal.LineLockedFieldPanelCoordinator; import docking.widgets.fieldpanel.internal.LineLockedFieldPanelCoordinator;
@ -22,9 +24,9 @@ import ghidra.program.util.ProgramLocation;
abstract public class DualDecompilerFieldPanelCoordinator extends LineLockedFieldPanelCoordinator { abstract public class DualDecompilerFieldPanelCoordinator extends LineLockedFieldPanelCoordinator {
public DualDecompilerFieldPanelCoordinator( public DualDecompilerFieldPanelCoordinator(
DecompilerCodeComparisonPanel<? extends DualDecompilerFieldPanelCoordinator> dualDecompilerPanel) { DecompilerCodeComparisonPanel dualDecompilerPanel) {
super(new FieldPanel[] { dualDecompilerPanel.getLeftDecompilerPanel().getFieldPanel(), super(new FieldPanel[] { dualDecompilerPanel.getDecompilerPanel(LEFT).getFieldPanel(),
dualDecompilerPanel.getRightDecompilerPanel().getFieldPanel() }); dualDecompilerPanel.getDecompilerPanel(RIGHT).getFieldPanel() });
} }
abstract public void leftLocationChanged(ProgramLocation leftLocation); abstract public void leftLocationChanged(ProgramLocation leftLocation);

View file

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package ghidra.codecompare; package ghidra.codecompare.decompile;
import ghidra.app.decompiler.ClangToken; import ghidra.app.decompiler.ClangToken;

View file

@ -73,6 +73,7 @@ public class DecompileResults {
hfunc = null; hfunc = null;
hparamid = null; hparamid = null;
docroot = null; docroot = null;
this.processState = processState;
//dumpResults(raw); //dumpResults(raw);
decodeStream(decoder); decodeStream(decoder);
} }
@ -144,6 +145,14 @@ public class DecompileResults {
return processState == DecompileProcess.DisposeState.DISPOSED_ON_STARTUP_FAILURE; return processState == DecompileProcess.DisposeState.DISPOSED_ON_STARTUP_FAILURE;
} }
/**
* Returns true if the decompile completed normally
* @return true if the decompile completed normally
*/
public boolean isValid() {
return errMsg == null || errMsg.isBlank();
}
/** /**
* Return any error message associated with the * Return any error message associated with the
* decompilation producing these results. Generally, * decompilation producing these results. Generally,
@ -180,7 +189,7 @@ public class DecompileResults {
/** /**
* Get the marked up C code associated with these * Get the marked up C code associated with these
* decompilation results. If there was an error, or * decompilation results. If there was an error, or
* code generation was turned off, retur null * code generation was turned off, return null
* @return the resulting root of C markup * @return the resulting root of C markup
*/ */
public ClangTokenGroup getCCodeMarkup() { public ClangTokenGroup getCCodeMarkup() {

View file

@ -1,187 +0,0 @@
/* ###
* 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.decompiler.component;
import java.awt.BorderLayout;
import javax.swing.JPanel;
import docking.widgets.fieldpanel.support.ViewerPosition;
import ghidra.app.decompiler.DecompileOptions;
import ghidra.app.plugin.core.decompile.DecompilerClipboardProvider;
import ghidra.app.util.viewer.listingpanel.ProgramLocationListener;
import ghidra.program.model.address.Address;
import ghidra.program.model.listing.Function;
import ghidra.program.model.listing.Program;
import ghidra.program.util.ProgramLocation;
import ghidra.program.util.ProgramSelection;
import ghidra.util.bean.field.AnnotatedTextFieldElement;
import utility.function.Callback;
public class CDisplayPanel extends JPanel implements DecompilerCallbackHandler {
private DecompilerController controller;
private DecompileResultsListener listener;
private ProgramLocationListener locationListener;
public CDisplayPanel(DecompileResultsListener listener) {
this(new DecompileOptions(), listener);
}
public CDisplayPanel(DecompileOptions decompileOptions, DecompileResultsListener listener) {
super(new BorderLayout());
this.listener = listener;
controller = new ExtendedDecompilerController(this, decompileOptions, null);
DecompilerPanel decompilerPanel = controller.getDecompilerPanel();
add(decompilerPanel);
}
public void setProgramLocationListener(ProgramLocationListener locationListener) {
this.locationListener = locationListener;
}
class ExtendedDecompilerController extends DecompilerController {
public ExtendedDecompilerController(DecompilerCallbackHandler handler,
DecompileOptions options, DecompilerClipboardProvider clipboard) {
super(handler, options, clipboard);
}
@Override
public void setDecompileData(DecompileData decompileData) {
super.setDecompileData(decompileData);
if (listener != null) {
listener.setDecompileData(decompileData);
}
}
@Override
public void dispose() {
listener = null;
super.dispose();
}
}
public DecompilerPanel getDecompilerPanel() {
return controller.getDecompilerPanel();
}
public void showFunction(Program program, Address address) {
controller.display(program, new ProgramLocation(program, address),
new ViewerPosition(0, 0, 0));
}
public void showFunction(Function function) {
if (function == null) {
clearAndShowMessage("No Function");
return;
}
if (function.isExternal()) {
clearAndShowMessage("\"" + function.getName(true) + "\" is an external function.");
return;
}
Program program = function.getProgram();
Address entry = function.getEntryPoint();
ProgramLocation location = new ProgramLocation(program, entry);
controller.display(program, location, new ViewerPosition(0, 0, 0));
}
@Override
public void contextChanged() {
// stub
}
@Override
public void decompileDataChanged(DecompileData decompileData) {
// stub
}
@Override
public void exportLocation() {
// stub
}
@Override
public void annotationClicked(AnnotatedTextFieldElement annotation, boolean newWindow) {
// stub
}
@Override
public void goToAddress(Address addr, boolean newWindow) {
// stub
}
@Override
public void goToLabel(String labelName, boolean newWindow) {
// stub
}
@Override
public void goToScalar(long value, boolean newWindow) {
// stub
}
@Override
public void goToFunction(Function function, boolean newWindow) {
// stub
}
@Override
public void locationChanged(ProgramLocation programLocation) {
if (locationListener == null) {
return;
}
this.locationListener.programLocationChanged(programLocation, null);
}
@Override
public void selectionChanged(ProgramSelection programSelection) {
// stub
}
@Override
public void setStatusMessage(String message) {
// stub
}
@Override
public void doWhenNotBusy(Callback c) {
// stub
}
public void clearAndShowMessage(String message) {
controller.setDecompileData(new EmptyDecompileData(message));
paintImmediately(getBounds());
}
public void setMouseNavigationEnabled(boolean enabled) {
controller.setMouseNavigationEnabled(enabled);
}
public void dispose() {
controller.dispose();
}
public DecompilerController getController() {
return controller;
}
public void refresh(DecompileData data) {
controller.refreshDisplay(data.getProgram(), data.getLocation(), null);
}
}

View file

@ -15,6 +15,9 @@
*/ */
package ghidra.app.decompiler.component; package ghidra.app.decompiler.component;
import java.io.File;
import docking.widgets.fieldpanel.support.ViewerPosition;
import ghidra.app.decompiler.ClangTokenGroup; import ghidra.app.decompiler.ClangTokenGroup;
import ghidra.app.decompiler.DecompileResults; import ghidra.app.decompiler.DecompileResults;
import ghidra.program.model.address.Address; import ghidra.program.model.address.Address;
@ -24,10 +27,6 @@ import ghidra.program.model.listing.Program;
import ghidra.program.model.pcode.HighFunction; import ghidra.program.model.pcode.HighFunction;
import ghidra.program.util.ProgramLocation; import ghidra.program.util.ProgramLocation;
import java.io.File;
import docking.widgets.fieldpanel.support.ViewerPosition;
public class DecompileData { public class DecompileData {
private final Program program; private final Program program;
@ -57,6 +56,10 @@ public class DecompileData {
return decompileResults.getCCodeMarkup() != null; return decompileResults.getCCodeMarkup() != null;
} }
public boolean isValid() {
return decompileResults != null && decompileResults.isValid();
}
public DecompileResults getDecompileResults() { public DecompileResults getDecompileResults() {
return decompileResults; return decompileResults;
} }

View file

@ -0,0 +1,87 @@
/* ###
* 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.decompiler.component;
import ghidra.program.model.address.Address;
import ghidra.program.model.listing.Function;
import ghidra.program.util.ProgramLocation;
import ghidra.program.util.ProgramSelection;
import ghidra.util.bean.field.AnnotatedTextFieldElement;
import utility.function.Callback;
public class DecompilerCallbackHandlerAdapter implements DecompilerCallbackHandler {
@Override
public void decompileDataChanged(DecompileData decompileData) {
// stub
}
@Override
public void contextChanged() {
// stub
}
@Override
public void setStatusMessage(String message) {
// stub
}
@Override
public void locationChanged(ProgramLocation programLocation) {
// stub
}
@Override
public void selectionChanged(ProgramSelection programSelection) {
// stub
}
@Override
public void annotationClicked(AnnotatedTextFieldElement annotation, boolean newWindow) {
// stub
}
@Override
public void goToLabel(String labelName, boolean newWindow) {
// stub
}
@Override
public void goToAddress(Address addr, boolean newWindow) {
// stub
}
@Override
public void goToScalar(long value, boolean newWindow) {
// stub
}
@Override
public void exportLocation() {
// stub
}
@Override
public void goToFunction(Function function, boolean newWindow) {
// stub
}
@Override
public void doWhenNotBusy(Callback c) {
// stub
}
}

View file

@ -1,821 +0,0 @@
/* ###
* 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.decompiler.component;
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import javax.swing.*;
import docking.ActionContext;
import docking.ComponentProvider;
import docking.action.*;
import docking.options.OptionsService;
import docking.widgets.fieldpanel.FieldPanel;
import docking.widgets.fieldpanel.support.FieldLocation;
import docking.widgets.label.GDHtmlLabel;
import ghidra.GhidraOptions;
import ghidra.app.decompiler.DecompileOptions;
import ghidra.app.util.viewer.listingpanel.ProgramLocationListener;
import ghidra.app.util.viewer.util.CodeComparisonPanel;
import ghidra.app.util.viewer.util.TitledPanel;
import ghidra.framework.options.ToolOptions;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.address.AddressSetView;
import ghidra.program.model.listing.*;
import ghidra.program.util.FunctionUtility;
import ghidra.program.util.ProgramLocation;
import ghidra.util.HTMLUtilities;
import ghidra.util.HelpLocation;
/**
* Panel that displays two decompilers for comparison
*/
public abstract class DecompilerCodeComparisonPanel<T extends DualDecompilerFieldPanelCoordinator>
extends CodeComparisonPanel<DualDecompilerFieldPanelCoordinator> {
private static final String NO_FUNCTION_TITLE = "No Function";
final static String OPTIONS_TITLE = "Decompiler";
private JSplitPane splitPane;
private CDisplayPanel[] cPanels = new CDisplayPanel[2];
private DualDecompilerFieldPanelCoordinator dualDecompilerCoordinator;
private DecompileData leftDecompileData;
private DecompileData rightDecompileData;
private boolean isMatchingConstantsExactly = true;
private DecompileOptions leftDecompileOptions;
private DecompileOptions rightDecompileOptions;
private ClangHighlightController[] highlightControllers = new ClangHighlightController[2];
private ArrayList<DualDecompileResultsListener> dualDecompileResultsListenerList =
new ArrayList<>();
private String leftTitle = NO_FUNCTION_TITLE;
private String rightTitle = NO_FUNCTION_TITLE;
private ProgramLocationListener leftDecompilerLocationListener;
private ProgramLocationListener rightDecompilerLocationListener;
private DecompilerDiffViewFindAction diffViewFindAction;
private boolean isSideBySide = true;
private ToggleOrientationAction toggleOrientationAction;
private DecompilerCodeComparisonOptionsAction decompOptionsAction;
private DecompilerProgramListener leftProgramListener;
private DecompilerProgramListener rightProgramListener;
/**
* Creates a comparison panel with two decompilers
*
* @param owner the owner of this panel
* @param tool the tool displaying this panel
*/
public DecompilerCodeComparisonPanel(String owner, PluginTool tool) {
super(owner, tool);
functions = new Function[2];
buildPanel();
loadFunctions(null, null);
highlightControllers[LEFT] = new LocationClangHighlightController();
highlightControllers[RIGHT] = new LocationClangHighlightController();
setHighlightControllers(highlightControllers[LEFT], highlightControllers[RIGHT]);
initialize();
}
private void initialize() {
ToolOptions fieldOptions = tool.getOptions(GhidraOptions.CATEGORY_BROWSER_FIELDS);
ToolOptions options = tool.getOptions(OPTIONS_TITLE);
leftDecompileOptions.grabFromToolAndProgram(fieldOptions, options,
(functions[LEFT] != null) ? functions[LEFT].getProgram() : null);
rightDecompileOptions.grabFromToolAndProgram(fieldOptions, options,
(functions[RIGHT] != null) ? functions[RIGHT].getProgram() : null);
setFieldPanelCoordinator(createFieldPanelCoordinator());
setScrollingSyncState(true);
createActions();
}
@Override
public JComponent getComponent() {
return this;
}
@Override
public String getTitle() {
return "Decompile View";
}
@Override
public void setVisible(boolean aFlag) {
super.setVisible(aFlag);
// If actions are added in the future, you may need to update their enablement here.
// The applyFunctionSignatureAction enablement is already handled via context.
}
private void setTitles(String leftTitle, String rightTitle) {
setLeftTitle(leftTitle);
setRightTitle(rightTitle);
}
private void setTitle(TitledPanel titlePanel, String titlePrefix, String title) {
if (!titlePrefix.isEmpty()) {
titlePrefix += " "; // Add a space between prefix and title.
}
String htmlPrefix = "<html>";
if (title.startsWith(htmlPrefix)) {
titlePanel.setTitleName(htmlPrefix + HTMLUtilities.friendlyEncodeHTML(titlePrefix) +
title.substring(htmlPrefix.length()));
}
else {
titlePanel.setTitleName(titlePrefix + title);
}
}
/**
* Sets the title for the left side's decompiler.
* @param leftTitle the title
*/
public void setLeftTitle(String leftTitle) {
this.leftTitle = leftTitle;
setTitle(titlePanels[LEFT], leftTitlePrefix, leftTitle);
}
/**
* Sets the title for the right side's decompiler.
* @param rightTitle the title
*/
public void setRightTitle(String rightTitle) {
this.rightTitle = rightTitle;
setTitle(titlePanels[RIGHT], rightTitlePrefix, rightTitle);
}
private void setTitles(Function leftFunction, Function rightFunction) {
setTitles(getTitleForFunction(leftFunction), getTitleForFunction(rightFunction));
}
private String getTitleForFunction(Function function) {
String title = NO_FUNCTION_TITLE;
if (function != null) {
String programName = function.getProgram().getDomainFile().getPathname();
title = function.getName(true) + " [" + programName + "]";
}
return title;
}
public boolean isMatchingConstantsExactly() {
return isMatchingConstantsExactly;
}
@Override
public void loadFunctions(Function leftFunction, Function rightFunction) {
if (leftFunction == functions[LEFT] && rightFunction == functions[RIGHT]) {
return;
}
// Clear the scroll info and highlight info to prevent unnecessary highlighting, etc.
if (leftFunction != functions[LEFT]) {
leftDecompileData = null;
}
if (rightFunction != functions[RIGHT]) {
rightDecompileData = null;
}
notifyDecompileResultsListeners();
Program leftProgram = (leftFunction != null) ? leftFunction.getProgram() : null;
Program rightProgram = (rightFunction != null) ? rightFunction.getProgram() : null;
setPrograms(leftProgram, rightProgram);
loadLeftFunction(leftFunction);
loadRightFunction(rightFunction);
if (getShowTitles()) {
setTitles(leftFunction, rightFunction);
}
else {
setTitles("", "");
}
if (dualDecompilerCoordinator != null) {
dualDecompilerCoordinator.leftLocationChanged((ProgramLocation) null);
}
}
private void notifyDecompileResultsListeners() {
// Notify any decompile results listener we have new left or right decompile results.
for (DualDecompileResultsListener listener : dualDecompileResultsListenerList) {
listener.decompileResultsSet(leftDecompileData, rightDecompileData);
}
}
private void loadLeftFunction(Function function) {
if (function == functions[LEFT]) {
return;
}
functions[LEFT] = function;
cPanels[LEFT].showFunction(function);
}
private void loadRightFunction(Function function) {
if (function == functions[RIGHT]) {
return;
}
functions[RIGHT] = function;
cPanels[RIGHT].showFunction(function);
}
private void buildPanel() {
setLayout(new BorderLayout());
leftDecompileOptions = new DecompileOptions();
rightDecompileOptions = new DecompileOptions();
// Options options = tool.getOptions(OPTIONS_TITLE);
// leftDecompileOptions.grabFromToolAndProgram(null, options,
// (functions[LEFT] != null) ? functions[LEFT].getProgram() : null);
// rightDecompileOptions.grabFromToolAndProgram(null, options,
// (functions[LEFT] != null) ? functions[RIGHT].getProgram() : null);
cPanels[LEFT] = new CDisplayPanel(leftDecompileOptions,
decompileData -> leftDecompileDataSet(decompileData));
cPanels[RIGHT] = new CDisplayPanel(rightDecompileOptions,
decompileData -> rightDecompileDataSet(decompileData));
DecompilerController leftController = cPanels[LEFT].getController();
leftProgramListener = new DecompilerProgramListener(leftController, () -> refresh(LEFT));
DecompilerController rightController = cPanels[RIGHT].getController();
rightProgramListener = new DecompilerProgramListener(rightController, () -> refresh(RIGHT));
leftDecompilerLocationListener = (leftLocation, trigger) -> {
if (dualDecompilerCoordinator != null) {
dualDecompilerCoordinator.leftLocationChanged(leftLocation);
}
};
rightDecompilerLocationListener = (rightLocation, trigger) -> {
if (dualDecompilerCoordinator != null) {
dualDecompilerCoordinator.rightLocationChanged(rightLocation);
}
};
cPanels[LEFT].setProgramLocationListener(leftDecompilerLocationListener);
cPanels[RIGHT].setProgramLocationListener(rightDecompilerLocationListener);
// Initialize focus listeners on decompiler panels.
for (int i = 0; i < cPanels.length; i++) {
FieldPanel fieldPanel = cPanels[i].getDecompilerPanel().getFieldPanel();
fieldPanel.addFocusListener(this);
fieldPanel.addMouseListener(new DualDecompilerMouseListener(i));
}
setDualPanelFocus(currProgramIndex);
String leftTitle1 = FunctionUtility.getFunctionTitle(functions[LEFT]);
String rightTitle1 = FunctionUtility.getFunctionTitle(functions[RIGHT]);
// use mutable labels, as the titles update when functions are selected
GDHtmlLabel leftTitleLabel = new GDHtmlLabel(leftTitle1);
GDHtmlLabel rightTitleLabel = new GDHtmlLabel(rightTitle1);
titlePanels[LEFT] = new TitledPanel(leftTitleLabel, cPanels[LEFT], 5);
titlePanels[RIGHT] = new TitledPanel(rightTitleLabel, cPanels[RIGHT], 5);
// Set the MINIMUM_PANEL_WIDTH for the left and right panel to prevent the split pane's
// divider from becoming locked (can't be moved) due to extra long title names.
titlePanels[LEFT].setMinimumSize(
new Dimension(MINIMUM_PANEL_WIDTH, titlePanels[LEFT].getMinimumSize().height));
titlePanels[RIGHT].setMinimumSize(
new Dimension(MINIMUM_PANEL_WIDTH, titlePanels[RIGHT].getMinimumSize().height));
splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, titlePanels[LEFT],
titlePanels[RIGHT]);
splitPane.setResizeWeight(0.5);
splitPane.setDividerSize(4);
splitPane.setBorder(BorderFactory.createEmptyBorder());
add(splitPane, BorderLayout.CENTER);
}
private void refresh(int side) {
DecompileData decompileData = side == LEFT ? leftDecompileData : rightDecompileData;
if (decompileData == null) {
return;
}
cPanels[side].refresh(decompileData);
}
/**
* Adds the indicated listener to be notified when the decompile results have completed.
* @param listener the listener
* @return true if the listener was added.
*/
public boolean addDualDecompileResultsListener(DualDecompileResultsListener listener) {
return dualDecompileResultsListenerList.add(listener);
}
/**
* Removes the indicated listener from being notified about decompile results.
* @param listener the listener
* @return true if the listener was removed.
*/
public boolean removeDualDecompileResultsListener(DualDecompileResultsListener listener) {
return dualDecompileResultsListenerList.remove(listener);
}
/**
* Sets the highlight controllers for both decompiler panels.
* @param leftHighlightController the left side's highlight controller
* @param rightHighlightController the right side's highlight controller
*/
public void setHighlightControllers(ClangHighlightController leftHighlightController,
ClangHighlightController rightHighlightController) {
highlightControllers[LEFT] = leftHighlightController;
highlightControllers[RIGHT] = rightHighlightController;
cPanels[LEFT].getDecompilerPanel().setHighlightController(leftHighlightController);
cPanels[RIGHT].getDecompilerPanel().setHighlightController(rightHighlightController);
}
/**
* Sets the coordinator for the two decompiler panels within this code comparison panel.
* It coordinates their scrolling and location synchronization.
* @param fieldPanelCoordinator the coordinator for the two decompiler panels
*/
@Override
public void setFieldPanelCoordinator(
DualDecompilerFieldPanelCoordinator fieldPanelCoordinator) {
if (this.dualDecompilerCoordinator == fieldPanelCoordinator) {
return;
}
if (this.dualDecompilerCoordinator != null) {
this.dualDecompilerCoordinator.dispose();
cPanels[LEFT].setProgramLocationListener(null);
cPanels[RIGHT].setProgramLocationListener(null);
}
this.dualDecompilerCoordinator = fieldPanelCoordinator;
if (fieldPanelCoordinator != null) {
cPanels[LEFT].setProgramLocationListener(leftDecompilerLocationListener);
cPanels[RIGHT].setProgramLocationListener(rightDecompilerLocationListener);
CDisplayPanel focusedDecompilerPanel = getFocusedDecompilerPanel();
ProgramLocation programLocation =
focusedDecompilerPanel.getDecompilerPanel().getCurrentLocation();
if (programLocation != null) {
focusedDecompilerPanel.locationChanged(programLocation);
}
}
}
protected void rightDecompileDataSet(DecompileData decompileData) {
rightDecompileData = decompileData;
notifyDecompileResultsListeners();
}
protected void leftDecompileDataSet(DecompileData decompileData) {
leftDecompileData = decompileData;
notifyDecompileResultsListeners();
}
/**
* Sets the component displayed in the top of this panel.
* @param comp the component.
*/
public void setTopComponent(JComponent comp) {
if (topComp == comp) {
return;
}
if (topComp != null) {
remove(topComp);
}
topComp = comp;
if (topComp != null) {
add(topComp, BorderLayout.NORTH);
}
validate();
}
/**
* Sets the component displayed in the bottom of this panel.
* @param comp the component.
*/
public void setBottomComponent(JComponent comp) {
if (bottomComp == comp) {
return;
}
if (bottomComp != null) {
remove(bottomComp);
}
validate(); // Since we are removing this while the panel is on the screen.
bottomComp = comp;
if (bottomComp != null) {
add(bottomComp, BorderLayout.SOUTH);
}
validate(); // Since we are adding this while the panel is on the screen.
}
/**
* Gets the display panel from the left or right side that has or last had focus.
* @return the last C display panel with focus
*/
public CDisplayPanel getFocusedDecompilerPanel() {
return cPanels[currProgramIndex];
}
/**
* Gets the left side's C display panel.
* @return the left C display panel
*/
public CDisplayPanel getLeftPanel() {
return cPanels[LEFT];
}
/**
* Gets the right side's C display panel.
* @return the right C display panel
*/
public CDisplayPanel getRightPanel() {
return cPanels[RIGHT];
}
@Override
public void dispose() {
removeProgramListeners();
setFieldPanelCoordinator(null);
cPanels[LEFT].dispose();
cPanels[RIGHT].dispose();
leftProgramListener.dispose();
rightProgramListener.dispose();
}
@Override
public void focusGained(FocusEvent e) {
Component comp = e.getComponent();
for (int i = 0; i < cPanels.length; i++) {
if (cPanels[i].getDecompilerPanel().getFieldPanel() == comp) {
setDualPanelFocus(i);
}
}
// Kick the tool so action buttons will be updated
ComponentProvider provider = tool.getWindowManager().getProvider(comp);
if (provider != null) {
provider.contextChanged();
}
}
private void setDualPanelFocus(int leftOrRight) {
currProgramIndex = leftOrRight;
cPanels[leftOrRight].setBorder(FOCUS_BORDER);
cPanels[((leftOrRight == LEFT) ? RIGHT : LEFT)].setBorder(NON_FOCUS_BORDER);
}
@SuppressWarnings("unused")
private void clearBothDisplaysAndShowMessage(String message) {
cPanels[LEFT].clearAndShowMessage(message);
cPanels[RIGHT].clearAndShowMessage(message);
}
/**
* Disable mouse navigation from within this dual decompiler panel.
* @param enabled false disables navigation
*/
@Override
public void setMouseNavigationEnabled(boolean enabled) {
cPanels[LEFT].setMouseNavigationEnabled(enabled);
cPanels[RIGHT].setMouseNavigationEnabled(enabled);
}
@Override
protected void setPrograms(Program leftProgram, Program rightProgram) {
removeProgramListeners();
ToolOptions fieldOptions =
(tool != null) ? tool.getOptions(GhidraOptions.CATEGORY_BROWSER_FIELDS) : null;
ToolOptions options = (tool != null) ? tool.getOptions(OPTIONS_TITLE) : null;
if (leftProgram != programs[LEFT]) {
programs[LEFT] = leftProgram;
if (options != null) {
leftDecompileOptions.grabFromToolAndProgram(fieldOptions, options, leftProgram);
}
}
if (rightProgram != programs[RIGHT]) {
programs[RIGHT] = rightProgram;
if (options != null) {
rightDecompileOptions.grabFromToolAndProgram(fieldOptions, options, rightProgram);
}
}
addProgramListeners();
}
private void addProgramListeners() {
if (programs[LEFT] != null) {
programs[LEFT].addListener(leftProgramListener);
}
if (programs[RIGHT] != null) {
programs[RIGHT].addListener(rightProgramListener);
}
}
private void removeProgramListeners() {
if (programs[LEFT] != null) {
programs[LEFT].removeListener(leftProgramListener);
}
if (programs[RIGHT] != null) {
programs[RIGHT].removeListener(rightProgramListener);
}
}
@Override
public void loadData(Data leftData, Data rightData) {
loadFunctions(null, null);
}
@Override
public void loadAddresses(Program leftProgram, Program rightProgram,
AddressSetView leftAddresses, AddressSetView rightAddresses) {
loadFunctions(null, null);
}
/**
* Gets the left side's decompiler panel.
* @return the left decompiler panel
*/
public DecompilerPanel getLeftDecompilerPanel() {
return cPanels[LEFT].getDecompilerPanel();
}
/**
* Gets the right side's decompiler panel.
* @return the right decompiler panel
*/
public DecompilerPanel getRightDecompilerPanel() {
return cPanels[RIGHT].getDecompilerPanel();
}
@Override
public void updateActionEnablement() {
// Nothing to do.
// applyFunctionSignature enablement is handled by context.
}
/**
* Creates the actions provided by this panel.
*/
protected void createActions() {
diffViewFindAction = new DecompilerDiffViewFindAction(owner, tool);
toggleOrientationAction = new ToggleOrientationAction();
decompOptionsAction = new DecompilerCodeComparisonOptionsAction();
}
@Override
public DockingAction[] getActions() {
DockingAction[] codeCompActions = super.getActions();
DockingAction[] otherActions = new DockingAction[] { diffViewFindAction,
toggleOrientationAction, decompOptionsAction };
int compCount = codeCompActions.length;
int otherCount = otherActions.length;
DockingAction[] actions = new DockingAction[compCount + otherCount];
System.arraycopy(codeCompActions, 0, actions, 0, compCount);
System.arraycopy(otherActions, 0, actions, compCount, otherCount);
return actions;
}
/**
* Sets whether or not the decompilers are displayed side by side.
*
* @param sideBySide if true, the decompilers are side by side, otherwise one is above
* the other.
*/
private void showSideBySide(boolean sideBySide) {
isSideBySide = sideBySide;
splitPane.setOrientation(
isSideBySide ? JSplitPane.HORIZONTAL_SPLIT : JSplitPane.VERTICAL_SPLIT);
splitPane.setDividerLocation(0.5);
toggleOrientationAction.setSelected(sideBySide);
}
private boolean isSideBySide() {
return isSideBySide;
}
@Override
public ActionContext getActionContext(ComponentProvider provider, MouseEvent event) {
Component component = event == null ? null : event.getComponent();
CDisplayPanel focusedDecompilerPanel = getFocusedDecompilerPanel();
DualDecompilerActionContext dualDecompContext =
new DualDecompilerActionContext(provider, focusedDecompilerPanel, component);
dualDecompContext.setCodeComparisonPanel(this);
return dualDecompContext;
}
@Override
public void programRestored(Program program) {
Function leftFunction = getLeftFunction();
Function rightFunction = getRightFunction();
Program leftProgram = (leftFunction != null) ? leftFunction.getProgram() : null;
Program rightProgram = (rightFunction != null) ? rightFunction.getProgram() : null;
if (leftProgram == program) {
titlePanels[LEFT].setTitleName(FunctionUtility.getFunctionTitle(leftFunction));
refreshLeftPanel();
}
if (rightProgram == program) {
titlePanels[RIGHT].setTitleName(FunctionUtility.getFunctionTitle(rightFunction));
refreshRightPanel();
}
}
@SuppressWarnings("unchecked")
private void refreshPanel(int leftOrRight) {
// Hold onto functions for reloading them after the indicated side is cleared,
// because that will have cleared it in the functions array.
Function leftFunction = functions[LEFT];
Function rightFunction = functions[RIGHT];
// Save the location so it can be restored after getting new decompiler results.
FieldLocation leftCursorLocation =
getLeftDecompilerPanel().getFieldPanel().getCursorLocation();
FieldLocation rightCursorLocation =
getRightDecompilerPanel().getFieldPanel().getCursorLocation();
MyDecompileResultsListener listener =
new MyDecompileResultsListener(leftCursorLocation, rightCursorLocation);
//TEMP FIX - correct when refactoring
// Clear any previous MyDecompileResultsListener that is for a decompiler load
//that hasn't finished.
Set<MyDecompileResultsListener> toRemove = new HashSet<>();
for (DualDecompileResultsListener l : dualDecompileResultsListenerList) {
if (MyDecompileResultsListener.class.isInstance(l)) {
toRemove.add((DecompilerCodeComparisonPanel<T>.MyDecompileResultsListener) l);
}
}
dualDecompileResultsListenerList.removeAll(toRemove);
// Clear the left or right function by passing null to the load method
// and then reload it below to get it to update.
loadFunctions(((leftOrRight == LEFT) ? null : leftFunction),
((leftOrRight == RIGHT) ? null : rightFunction));
// Setup to restore location to left or right decompiler panel.
addDualDecompileResultsListener(listener);
// Reload the left or right function to get it to update.
loadFunctions(leftFunction, rightFunction);
}
/**
* Refreshes the left side of this panel.
*/
@Override
public void refreshLeftPanel() {
refreshPanel(LEFT);
}
/**
* Refreshes the right side of this panel.
*/
@Override
public void refreshRightPanel() {
refreshPanel(RIGHT);
}
@Override
public boolean leftPanelHasFocus() {
return currProgramIndex == LEFT;
}
@Override
public void setTitlePrefixes(String leftTitlePrefix, String rightTitlePrefix) {
this.leftTitlePrefix = leftTitlePrefix;
this.rightTitlePrefix = rightTitlePrefix;
setTitles(leftTitle, rightTitle);
}
@Override
public AddressSetView getLeftAddresses() {
return (functions[LEFT] != null) ? functions[LEFT].getBody() : EMPTY_ADDRESS_SET;
}
@Override
public AddressSetView getRightAddresses() {
return (functions[RIGHT] != null) ? functions[RIGHT].getBody() : EMPTY_ADDRESS_SET;
}
@Override
public FieldPanel getLeftFieldPanel() {
return getLeftDecompilerPanel().getFieldPanel();
}
@Override
public FieldPanel getRightFieldPanel() {
return getRightDecompilerPanel().getFieldPanel();
}
@Override
protected abstract DualDecompilerFieldPanelCoordinator createFieldPanelCoordinator();
private class MyDecompileResultsListener implements DualDecompileResultsListener {
private FieldLocation leftCursorLocation;
private FieldLocation rightCursorLocation;
private MyDecompileResultsListener(FieldLocation leftCursorLocation,
FieldLocation rightCursorLocation) {
this.leftCursorLocation = leftCursorLocation;
this.rightCursorLocation = rightCursorLocation;
}
@Override
public void decompileResultsSet(final DecompileData myLeftDecompileData,
final DecompileData myRightDecompileData) {
SwingUtilities.invokeLater(() -> {
if (myLeftDecompileData != null) {
// The left side may have reloaded with decompiler results,
// so restore the cursor location.
restoreCursor(getLeftDecompilerPanel(), leftCursorLocation);
}
if (myRightDecompileData != null) {
// The right side may have reloaded with decompiler results,
// so restore the cursor location.
restoreCursor(getRightDecompilerPanel(), rightCursorLocation);
}
// The listener did its job so now remove it.
removeDualDecompileResultsListener(MyDecompileResultsListener.this);
});
}
private void restoreCursor(DecompilerPanel decompilerPanel, FieldLocation cursorLocation) {
FieldPanel fieldPanel = decompilerPanel.getFieldPanel();
FieldLocation currentLocation = fieldPanel.getCursorLocation();
if (cursorLocation != null && !cursorLocation.equals(currentLocation)) {
fieldPanel.setCursorPosition(cursorLocation.getIndex(),
cursorLocation.getFieldNum(), cursorLocation.getRow(), cursorLocation.getCol());
}
}
}
private class DualDecompilerMouseListener extends MouseAdapter {
private int leftOrRight;
DualDecompilerMouseListener(int leftOrRight) {
this.leftOrRight = leftOrRight;
}
@Override
public void mousePressed(MouseEvent e) {
setDualPanelFocus(leftOrRight);
}
}
private class ToggleOrientationAction extends ToggleDockingAction {
ToggleOrientationAction() {
super("Dual Decompiler Toggle Orientation", "FunctionComparison");
setDescription("<html>Toggle the layout of the decompiler " +
"<BR>between side-by-side and one above the other.");
setEnabled(true);
setSelected(isSideBySide);
MenuData menuData =
new MenuData(new String[] { "Show Decompilers Side-by-Side" }, "Dual Decompiler");
setMenuBarData(menuData);
}
@Override
public void actionPerformed(ActionContext context) {
boolean sideBySide = !isSideBySide();
showSideBySide(sideBySide);
}
}
private class DecompilerCodeComparisonOptionsAction extends DockingAction {
DecompilerCodeComparisonOptionsAction() {
super("Decompiler Code Comparison Options", owner);
setDescription("Show the tool options for the Decompiler Code Comparison.");
setPopupMenuData(new MenuData(new String[] { "Properties" }, null, "Z_Properties"));
setHelpLocation(
new HelpLocation("FunctionComparison", "Decompiler_Code_Comparison_Options"));
}
@Override
public boolean isEnabledForContext(ActionContext context) {
return (context instanceof DualDecompilerActionContext);
}
@Override
public void actionPerformed(ActionContext context) {
OptionsService service = tool.getService(OptionsService.class);
service.showOptionsDialog("FunctionComparison", "Decompiler Code Comparison");
}
}
}

View file

@ -1214,6 +1214,18 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field
buildPanels(); buildPanels();
} }
@Override
public synchronized void addFocusListener(FocusListener l) {
// we are not focusable, defer to contained field panel
fieldPanel.addFocusListener(l);
}
@Override
public synchronized void removeFocusListener(FocusListener l) {
// we are not focusable, defer to contained field panel
fieldPanel.removeFocusListener(l);
}
private void buildPanels() { private void buildPanels() {
removeAll(); removeAll();
add(buildLeftComponent(), BorderLayout.WEST); add(buildLeftComponent(), BorderLayout.WEST);

View file

@ -1,26 +0,0 @@
/* ###
* 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.
* 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.decompiler.component;
public interface DualDecompileResultsListener {
// void setProvider(DecompilerFunctionComparisonProvider provider);
void decompileResultsSet(DecompileData leftDecompileData, DecompileData rightDecompileData);
}

View file

@ -1,75 +0,0 @@
/* ###
* 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.decompiler.component;
import java.awt.Component;
import docking.ComponentProvider;
import docking.widgets.fieldpanel.internal.FieldPanelCoordinator;
import ghidra.app.context.RestrictedAddressSetContext;
import ghidra.app.util.viewer.util.CodeComparisonActionContext;
import ghidra.app.util.viewer.util.CodeComparisonPanel;
import ghidra.program.model.listing.Function;
/**
* Action context for a dual decompiler panel.
*/
public class DualDecompilerActionContext extends CodeComparisonActionContext
implements RestrictedAddressSetContext {
private CodeComparisonPanel<? extends FieldPanelCoordinator> codeComparisonPanel = null;
/**
* Creates an action context for a dual decompiler panel.
* @param provider the provider for this context
* @param cPanel the decompiler panel associated with this context
* @param source the source of the action
*/
public DualDecompilerActionContext(ComponentProvider provider, CDisplayPanel cPanel,
Component source) {
super(provider, cPanel, source);
}
/**
* Sets the CodeComparisonPanel associated with this context.
* @param codeComparisonPanel the code comparison panel.
*/
public void setCodeComparisonPanel(
CodeComparisonPanel<? extends FieldPanelCoordinator> codeComparisonPanel) {
this.codeComparisonPanel = codeComparisonPanel;
}
@Override
public CodeComparisonPanel<? extends FieldPanelCoordinator> getCodeComparisonPanel() {
return codeComparisonPanel;
}
@Override
public Function getSourceFunction() {
boolean leftHasFocus = codeComparisonPanel.leftPanelHasFocus();
return leftHasFocus ? codeComparisonPanel.getRightFunction()
: codeComparisonPanel.getLeftFunction();
}
@Override
public Function getTargetFunction() {
boolean leftHasFocus = codeComparisonPanel.leftPanelHasFocus();
return leftHasFocus ? codeComparisonPanel.getLeftFunction()
: codeComparisonPanel.getRightFunction();
}
}

View file

@ -15,11 +15,13 @@
*/ */
package ghidra.feature.vt.api.correlator.address; package ghidra.feature.vt.api.correlator.address;
import static ghidra.util.datastruct.Duo.Side.*;
import ghidra.program.model.address.*; import ghidra.program.model.address.*;
import ghidra.program.model.correlate.HashedFunctionAddressCorrelation; import ghidra.program.model.correlate.HashedFunctionAddressCorrelation;
import ghidra.program.model.listing.Function; import ghidra.program.model.listing.Function;
import ghidra.program.model.mem.MemoryAccessException; import ghidra.program.model.mem.MemoryAccessException;
import ghidra.program.util.AddressCorrelation; import ghidra.program.util.*;
import ghidra.util.Msg; import ghidra.util.Msg;
import ghidra.util.exception.CancelledException; import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor; import ghidra.util.task.TaskMonitor;
@ -35,14 +37,15 @@ public class VTHashedFunctionAddressCorrelation implements AddressCorrelation {
private final Function sourceFunction; private final Function sourceFunction;
private final Function destinationFunction; private final Function destinationFunction;
private HashedFunctionAddressCorrelation addressCorrelation; private ListingAddressCorrelation addressCorrelation;
/** /**
* Constructs an address correlation between two functions. * Constructs an address correlation between two functions.
* @param sourceFunction the source function * @param sourceFunction the source function
* @param destinationFunction the destination function * @param destinationFunction the destination function
*/ */
public VTHashedFunctionAddressCorrelation(Function sourceFunction, Function destinationFunction) { public VTHashedFunctionAddressCorrelation(Function sourceFunction,
Function destinationFunction) {
this.sourceFunction = sourceFunction; this.sourceFunction = sourceFunction;
this.destinationFunction = destinationFunction; this.destinationFunction = destinationFunction;
addressCorrelation = null; addressCorrelation = null;
@ -58,7 +61,7 @@ public class VTHashedFunctionAddressCorrelation implements AddressCorrelation {
throws CancelledException { throws CancelledException {
try { try {
initializeCorrelation(monitor); initializeCorrelation(monitor);
Address destinationAddress = addressCorrelation.getAddressInSecond(sourceAddress); Address destinationAddress = addressCorrelation.getAddress(RIGHT, sourceAddress);
if (destinationAddress == null) { if (destinationAddress == null) {
return null; // No matching destination. return null; // No matching destination.
} }
@ -83,8 +86,13 @@ public class VTHashedFunctionAddressCorrelation implements AddressCorrelation {
if (addressCorrelation != null) { if (addressCorrelation != null) {
return; return;
} }
if (sourceFunction != null && destinationFunction != null) {
addressCorrelation = addressCorrelation =
new HashedFunctionAddressCorrelation(sourceFunction, destinationFunction, new HashedFunctionAddressCorrelation(sourceFunction, destinationFunction,
monitor); monitor);
} }
else {
addressCorrelation = new DummyListingAddressCorrelation();
}
}
} }

View file

@ -15,6 +15,8 @@
*/ */
package ghidra.feature.vt.gui.duallisting; package ghidra.feature.vt.gui.duallisting;
import static ghidra.util.datastruct.Duo.Side.*;
import java.awt.Point; import java.awt.Point;
import java.awt.datatransfer.DataFlavor; import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable; import java.awt.datatransfer.Transferable;
@ -32,12 +34,11 @@ import ghidra.program.model.address.Address;
import ghidra.program.util.ProgramLocation; import ghidra.program.util.ProgramLocation;
import ghidra.util.Msg; import ghidra.util.Msg;
import ghidra.util.SystemUtilities; import ghidra.util.SystemUtilities;
import ghidra.util.datastruct.Duo;
public class VTDualListingDragNDropHandler implements Draggable, Droppable { public class VTDualListingDragNDropHandler implements Draggable, Droppable {
private final int SOURCE = 0; // left side private Duo<ListingPanel> listingPanels;
private final int DESTINATION = 1; // right side
private ListingPanel[] listingPanels = new ListingPanel[2];
private VTController controller; private VTController controller;
ListingCodeComparisonPanel dualListingPanel; ListingCodeComparisonPanel dualListingPanel;
@ -55,8 +56,9 @@ public class VTDualListingDragNDropHandler implements Draggable, Droppable {
ListingCodeComparisonPanel dualListingPanel) { ListingCodeComparisonPanel dualListingPanel) {
this.controller = controller; this.controller = controller;
this.dualListingPanel = dualListingPanel; this.dualListingPanel = dualListingPanel;
listingPanels[SOURCE] = dualListingPanel.getLeftPanel(); ListingPanel leftPanel = dualListingPanel.getListingPanel(LEFT);
listingPanels[DESTINATION] = dualListingPanel.getRightPanel(); ListingPanel rightPanel = dualListingPanel.getListingPanel(RIGHT);
listingPanels = new Duo<>(leftPanel, rightPanel);
setUpDragDrop(); setUpDragDrop();
} }
@ -68,7 +70,7 @@ public class VTDualListingDragNDropHandler implements Draggable, Droppable {
dragSource = DragSource.getDefaultDragSource(); dragSource = DragSource.getDefaultDragSource();
dragGestureAdapter = new DragGestureAdapter(this); dragGestureAdapter = new DragGestureAdapter(this);
dragSourceAdapter = new DragSrcAdapter(this); dragSourceAdapter = new DragSrcAdapter(this);
dragSource.createDefaultDragGestureRecognizer(listingPanels[SOURCE].getFieldPanel(), dragSource.createDefaultDragGestureRecognizer(listingPanels.get(LEFT).getFieldPanel(),
dragAction, dragGestureAdapter); dragAction, dragGestureAdapter);
} }
@ -79,7 +81,7 @@ public class VTDualListingDragNDropHandler implements Draggable, Droppable {
// set up the destination fieldPanel as a drop target that accepts mark-up items. // set up the destination fieldPanel as a drop target that accepts mark-up items.
dropTargetAdapter = dropTargetAdapter =
new DropTgtAdapter(this, DnDConstants.ACTION_COPY_OR_MOVE, acceptableFlavors); new DropTgtAdapter(this, DnDConstants.ACTION_COPY_OR_MOVE, acceptableFlavors);
dropTarget = new DropTarget(listingPanels[DESTINATION].getFieldPanel(), dropTarget = new DropTarget(listingPanels.get(RIGHT).getFieldPanel(),
DnDConstants.ACTION_COPY_OR_MOVE, dropTargetAdapter, true); DnDConstants.ACTION_COPY_OR_MOVE, dropTargetAdapter, true);
dropTarget.setActive(true); dropTarget.setActive(true);
} }
@ -100,13 +102,14 @@ public class VTDualListingDragNDropHandler implements Draggable, Droppable {
@Override @Override
public boolean isStartDragOk(DragGestureEvent e) { public boolean isStartDragOk(DragGestureEvent e) {
if (!listingPanels[SOURCE].isStartDragOk()) { if (!listingPanels.get(LEFT).isStartDragOk()) {
return false; return false;
} }
Point p = e.getDragOrigin(); Point p = e.getDragOrigin();
ProgramLocation programLocation = listingPanels[SOURCE].getProgramLocation(p); ProgramLocation programLocation = listingPanels.get(LEFT).getProgramLocation(p);
VTMarkupItem markupItem = controller.getCurrentMarkupForLocation(programLocation, VTMarkupItem markupItem =
dualListingPanel.getLeftProgram()); controller.getCurrentMarkupForLocation(programLocation,
dualListingPanel.getProgram(LEFT));
if (markupItem == null) { if (markupItem == null) {
return false; return false;
} }
@ -122,13 +125,13 @@ public class VTDualListingDragNDropHandler implements Draggable, Droppable {
@Override @Override
public Transferable getTransferable(Point p) { public Transferable getTransferable(Point p) {
if (!listingPanels[SOURCE].contains(p)) { if (!listingPanels.get(LEFT).contains(p)) {
return null; return null;
} }
ProgramLocation programLocation = listingPanels[SOURCE].getProgramLocation(p); ProgramLocation programLocation = listingPanels.get(LEFT).getProgramLocation(p);
VTMarkupItem markupItem = controller.getCurrentMarkupForLocation(programLocation, VTMarkupItem markupItem = controller.getCurrentMarkupForLocation(programLocation,
dualListingPanel.getLeftProgram()); dualListingPanel.getProgram(LEFT));
if (markupItem == null) { if (markupItem == null) {
return null; return null;
} }
@ -145,10 +148,10 @@ public class VTDualListingDragNDropHandler implements Draggable, Droppable {
VTMarkupItem markupItem = (VTMarkupItem) obj; VTMarkupItem markupItem = (VTMarkupItem) obj;
VTMarkupType markupType = markupItem.getMarkupType(); VTMarkupType markupType = markupItem.getMarkupType();
Point p = event.getLocation(); Point p = event.getLocation();
ProgramLocation loc = listingPanels[DESTINATION].getProgramLocation(p); ProgramLocation loc = listingPanels.get(RIGHT).getProgramLocation(p);
Address newDestinationAddress = Address newDestinationAddress =
markupType.getAddress(loc, dualListingPanel.getRightProgram()); markupType.getAddress(loc, dualListingPanel.getProgram(RIGHT));
if (newDestinationAddress == null) { if (newDestinationAddress == null) {
Msg.showInfo(getClass(), dualListingPanel, "Invalid Drop Location", Msg.showInfo(getClass(), dualListingPanel, "Invalid Drop Location",
markupType.getDisplayName() + " was not dropped at a valid location."); markupType.getDisplayName() + " was not dropped at a valid location.");
@ -166,11 +169,6 @@ public class VTDualListingDragNDropHandler implements Draggable, Droppable {
ArrayList<VTMarkupItem> arrayList = new ArrayList<VTMarkupItem>(); ArrayList<VTMarkupItem> arrayList = new ArrayList<VTMarkupItem>();
arrayList.add(markupItem); arrayList.add(markupItem);
// Use the following if you only want to set the address.
// SetMarkupItemDestinationAddressTask task =
// new SetMarkupItemDestinationAddressTask(controller.getSession(), arrayList,
// newDestinationAddress);
// Use the following if you want to set the address and apply the markup item using the default action. // Use the following if you want to set the address and apply the markup item using the default action.
ApplyMarkupAtDestinationAddressTask task = new ApplyMarkupAtDestinationAddressTask( ApplyMarkupAtDestinationAddressTask task = new ApplyMarkupAtDestinationAddressTask(
controller.getSession(), arrayList, newDestinationAddress, controller.getOptions()); controller.getSession(), arrayList, newDestinationAddress, controller.getOptions());

View file

@ -16,7 +16,6 @@
package ghidra.feature.vt.gui.duallisting; package ghidra.feature.vt.gui.duallisting;
import docking.ComponentProvider; import docking.ComponentProvider;
import docking.widgets.fieldpanel.internal.FieldPanelCoordinator;
import ghidra.app.context.ListingActionContext; import ghidra.app.context.ListingActionContext;
import ghidra.app.nav.Navigatable; import ghidra.app.nav.Navigatable;
import ghidra.app.util.viewer.util.CodeComparisonPanel; import ghidra.app.util.viewer.util.CodeComparisonPanel;
@ -28,7 +27,7 @@ import ghidra.app.util.viewer.util.CodeComparisonPanelActionContext;
public class VTListingContext extends ListingActionContext public class VTListingContext extends ListingActionContext
implements CodeComparisonPanelActionContext { implements CodeComparisonPanelActionContext {
private CodeComparisonPanel<? extends FieldPanelCoordinator> codeComparisonPanel = null; private CodeComparisonPanel codeComparisonPanel = null;
/** /**
* Creates an action context for a VT listing. * Creates an action context for a VT listing.
@ -44,12 +43,12 @@ public class VTListingContext extends ListingActionContext
* @param codeComparisonPanel the code comparison panel. * @param codeComparisonPanel the code comparison panel.
*/ */
public void setCodeComparisonPanel( public void setCodeComparisonPanel(
CodeComparisonPanel<? extends FieldPanelCoordinator> codeComparisonPanel) { CodeComparisonPanel codeComparisonPanel) {
this.codeComparisonPanel = codeComparisonPanel; this.codeComparisonPanel = codeComparisonPanel;
} }
@Override @Override
public CodeComparisonPanel<? extends FieldPanelCoordinator> getCodeComparisonPanel() { public CodeComparisonPanel getCodeComparisonPanel() {
return codeComparisonPanel; return codeComparisonPanel;
} }
} }

View file

@ -92,14 +92,8 @@ public class VTListingNavigator implements Navigatable {
@Override @Override
public boolean goTo(Program program, ProgramLocation location) { public boolean goTo(Program program, ProgramLocation location) {
boolean went = listingPanel.goTo(location);
// If we tried to go but couldn't, try again after showing entire listing.
if (!went && !dualListingPanel.isEntireListingShowing()) {
dualListingPanel.showEntireListing(true);
return listingPanel.goTo(location); return listingPanel.goTo(location);
} }
return went;
}
@Override @Override
public boolean isConnected() { public boolean isConnected() {
@ -147,7 +141,8 @@ public class VTListingNavigator implements Navigatable {
} }
@Override @Override
public void removeHighlightProvider(ListingHighlightProvider highlightProvider, Program program) { public void removeHighlightProvider(ListingHighlightProvider highlightProvider,
Program program) {
// currently unsupported // currently unsupported
} }

View file

@ -16,6 +16,7 @@
package ghidra.feature.vt.gui.provider.functionassociation; package ghidra.feature.vt.gui.provider.functionassociation;
import static ghidra.feature.vt.gui.provider.functionassociation.FilterSettings.*; import static ghidra.feature.vt.gui.provider.functionassociation.FilterSettings.*;
import static ghidra.util.datastruct.Duo.Side.*;
import java.awt.*; import java.awt.*;
import java.awt.event.*; import java.awt.event.*;
@ -220,7 +221,7 @@ public class VTFunctionAssociationProvider extends ComponentProviderAdapter
ListingCodeComparisonPanel dualListingPanel = ListingCodeComparisonPanel dualListingPanel =
functionComparisonPanel.getDualListingPanel(); functionComparisonPanel.getDualListingPanel();
if (dualListingPanel != null) { if (dualListingPanel != null) {
ListingPanel leftPanel = dualListingPanel.getLeftPanel(); ListingPanel leftPanel = dualListingPanel.getListingPanel(LEFT);
return leftPanel.getHeaderActions(getName()); return leftPanel.getHeaderActions(getName());
} }
} }
@ -261,7 +262,7 @@ public class VTFunctionAssociationProvider extends ComponentProviderAdapter
} }
// Is the action being taken on a toolbar button while the dual listing is visible? // Is the action being taken on a toolbar button while the dual listing is visible?
else if (isToolbarButtonAction && isShowingDualListing) { else if (isToolbarButtonAction && isShowingDualListing) {
listingPanel = dualListingPanel.getFocusedListingPanel(); listingPanel = dualListingPanel.getActiveListingPanel();
} }
// If the dual listing is showing and this is a toolbar action or the action is // If the dual listing is showing and this is a toolbar action or the action is
// on one of the listings in the ListingCodeComparisonPanel // on one of the listings in the ListingCodeComparisonPanel
@ -367,10 +368,9 @@ public class VTFunctionAssociationProvider extends ComponentProviderAdapter
statusPanel.add(statusLabel, BorderLayout.CENTER); statusPanel.add(statusLabel, BorderLayout.CENTER);
dualTablePanel.add(statusPanel, BorderLayout.SOUTH); dualTablePanel.add(statusPanel, BorderLayout.SOUTH);
functionComparisonPanel = functionComparisonPanel = new FunctionComparisonPanel(this, tool);
new FunctionComparisonPanel(this, tool, (Function) null, (Function) null);
addSpecificCodeComparisonActions(); addSpecificCodeComparisonActions();
functionComparisonPanel.setCurrentTabbedComponent(ListingCodeComparisonPanel.TITLE); functionComparisonPanel.setCurrentTabbedComponent(ListingCodeComparisonPanel.NAME);
functionComparisonPanel.setTitlePrefixes("Source:", "Destination:"); functionComparisonPanel.setTitlePrefixes("Source:", "Destination:");
comparisonSplitPane = comparisonSplitPane =

View file

@ -18,7 +18,6 @@ package ghidra.feature.vt.gui.provider.markuptable;
import java.util.List; import java.util.List;
import docking.DefaultActionContext; import docking.DefaultActionContext;
import docking.widgets.fieldpanel.internal.FieldPanelCoordinator;
import ghidra.app.util.viewer.util.CodeComparisonPanel; import ghidra.app.util.viewer.util.CodeComparisonPanel;
import ghidra.app.util.viewer.util.CodeComparisonPanelActionContext; import ghidra.app.util.viewer.util.CodeComparisonPanelActionContext;
import ghidra.feature.vt.api.main.VTMarkupItem; import ghidra.feature.vt.api.main.VTMarkupItem;
@ -30,7 +29,7 @@ public class VTMarkupItemContext extends DefaultActionContext
implements CodeComparisonPanelActionContext { implements CodeComparisonPanelActionContext {
private final List<VTMarkupItem> selectedItems; private final List<VTMarkupItem> selectedItems;
private CodeComparisonPanel<? extends FieldPanelCoordinator> codeComparisonPanel = null; private CodeComparisonPanel codeComparisonPanel = null;
/** /**
* Creates an action context for the VT markup item provider. * Creates an action context for the VT markup item provider.
@ -55,12 +54,12 @@ public class VTMarkupItemContext extends DefaultActionContext
* @param codeComparisonPanel the code comparison panel. * @param codeComparisonPanel the code comparison panel.
*/ */
public void setCodeComparisonPanel( public void setCodeComparisonPanel(
CodeComparisonPanel<? extends FieldPanelCoordinator> codeComparisonPanel) { CodeComparisonPanel codeComparisonPanel) {
this.codeComparisonPanel = codeComparisonPanel; this.codeComparisonPanel = codeComparisonPanel;
} }
@Override @Override
public CodeComparisonPanel<? extends FieldPanelCoordinator> getCodeComparisonPanel() { public CodeComparisonPanel getCodeComparisonPanel() {
return codeComparisonPanel; return codeComparisonPanel;
} }
} }

View file

@ -18,6 +18,7 @@ package ghidra.feature.vt.gui.provider.markuptable;
import static ghidra.feature.vt.api.impl.VTEvent.*; import static ghidra.feature.vt.api.impl.VTEvent.*;
import static ghidra.feature.vt.gui.plugin.VTPlugin.*; import static ghidra.feature.vt.gui.plugin.VTPlugin.*;
import static ghidra.framework.model.DomainObjectEvent.*; import static ghidra.framework.model.DomainObjectEvent.*;
import static ghidra.util.datastruct.Duo.Side.*;
import java.awt.*; import java.awt.*;
import java.awt.event.*; import java.awt.event.*;
@ -34,7 +35,6 @@ import docking.action.*;
import docking.actions.PopupActionProvider; import docking.actions.PopupActionProvider;
import docking.widgets.EventTrigger; import docking.widgets.EventTrigger;
import docking.widgets.fieldpanel.FieldPanel; import docking.widgets.fieldpanel.FieldPanel;
import docking.widgets.fieldpanel.internal.FieldPanelCoordinator;
import docking.widgets.table.GTable; import docking.widgets.table.GTable;
import docking.widgets.table.RowObjectTableModel; import docking.widgets.table.RowObjectTableModel;
import docking.widgets.table.threaded.ThreadedTableModel; import docking.widgets.table.threaded.ThreadedTableModel;
@ -150,23 +150,24 @@ public class VTMarkupItemsTableProvider extends ComponentProviderAdapter
markupItemsTablePanel.add(tablePanel, BorderLayout.CENTER); markupItemsTablePanel.add(tablePanel, BorderLayout.CENTER);
markupItemsTablePanel.add(filterAreaPanel, BorderLayout.SOUTH); markupItemsTablePanel.add(filterAreaPanel, BorderLayout.SOUTH);
functionComparisonPanel = functionComparisonPanel = new FunctionComparisonPanel(this, tool);
new FunctionComparisonPanel(this, tool, (Function) null, (Function) null);
addSpecificCodeComparisonActions(); addSpecificCodeComparisonActions();
functionComparisonPanel.setCurrentTabbedComponent(ListingCodeComparisonPanel.TITLE); functionComparisonPanel.setCurrentTabbedComponent(ListingCodeComparisonPanel.NAME);
functionComparisonPanel.setTitlePrefixes("Source:", "Destination:"); functionComparisonPanel.setTitlePrefixes("Source:", "Destination:");
ListingCodeComparisonPanel dualListingPanel = functionComparisonPanel.getDualListingPanel(); ListingCodeComparisonPanel dualListingPanel = functionComparisonPanel.getDualListingPanel();
if (dualListingPanel != null) { if (dualListingPanel != null) {
dualListingPanel.setLeftProgramLocationListener(new SourceProgramLocationListener());
dualListingPanel dualListingPanel.getListingPanel(LEFT)
.setRightProgramLocationListener(new DestinationProgramLocationListener()); .setProgramLocationListener(new SourceProgramLocationListener());
dualListingPanel.getListingPanel(RIGHT).setProgramLocationListener(
new DestinationProgramLocationListener());
sourceHighlightProvider = new VTDualListingHighlightProvider(controller, true); sourceHighlightProvider = new VTDualListingHighlightProvider(controller, true);
destinationHighlightProvider = new VTDualListingHighlightProvider(controller, false); destinationHighlightProvider = new VTDualListingHighlightProvider(controller, false);
dualListingPanel.addHighlightProviders(sourceHighlightProvider, dualListingPanel.addHighlightProviders(sourceHighlightProvider,
destinationHighlightProvider); destinationHighlightProvider);
sourceHighlightProvider.setListingPanel(dualListingPanel.getLeftPanel()); sourceHighlightProvider.setListingPanel(dualListingPanel.getListingPanel(LEFT));
destinationHighlightProvider.setListingPanel(dualListingPanel.getRightPanel()); destinationHighlightProvider.setListingPanel(dualListingPanel.getListingPanel(RIGHT));
new VTDualListingDragNDropHandler(controller, dualListingPanel); new VTDualListingDragNDropHandler(controller, dualListingPanel);
} }
@ -267,24 +268,24 @@ public class VTMarkupItemsTableProvider extends ComponentProviderAdapter
// Don't set source or destination if the location change was initiated by the dual listing. // Don't set source or destination if the location change was initiated by the dual listing.
if (!processingSourceLocationChange && if (!processingSourceLocationChange &&
!processingDestinationLocationChange) { !processingDestinationLocationChange) {
dualListingPanel.setLeftLocation(dualListingPanel.getLeftProgram(), dualListingPanel.setLocation(LEFT, dualListingPanel.getProgram(LEFT),
markupItem.getSourceLocation()); markupItem.getSourceLocation());
dualListingPanel.setRightLocation(dualListingPanel.getRightProgram(), dualListingPanel.setLocation(RIGHT, dualListingPanel.getProgram(RIGHT),
markupItem.getDestinationLocation()); markupItem.getDestinationLocation());
} }
else { else {
// Only adjust the side of the dual listing panel that didn't initiate this. // Only adjust the side of the dual listing panel that didn't initiate this.
ProgramLocation sourceLocation = markupItem.getSourceLocation(); ProgramLocation sourceLocation = markupItem.getSourceLocation();
if (processingDestinationLocationChange && sourceLocation != null) { if (processingDestinationLocationChange && sourceLocation != null) {
dualListingPanel.setLeftLocation(dualListingPanel.getLeftProgram(), dualListingPanel.setLocation(LEFT,
sourceLocation); dualListingPanel.getProgram(LEFT), sourceLocation);
} }
ProgramLocation destinationLocation = ProgramLocation destinationLocation =
markupItem.getDestinationLocation(); markupItem.getDestinationLocation();
if (processingSourceLocationChange && destinationLocation != null) { if (processingSourceLocationChange && destinationLocation != null) {
dualListingPanel.setRightLocation( dualListingPanel.setLocation(RIGHT,
dualListingPanel.getRightProgram(), destinationLocation); dualListingPanel.getProgram(RIGHT), destinationLocation);
} }
} }
dualListingPanel.updateListings(); // refresh the dual listing's background markup colors. dualListingPanel.updateListings(); // refresh the dual listing's background markup colors.
@ -361,10 +362,10 @@ public class VTMarkupItemsTableProvider extends ComponentProviderAdapter
splitPane.add(functionComparisonPanel); splitPane.add(functionComparisonPanel);
markupPanel.add(splitPane, BorderLayout.CENTER); markupPanel.add(splitPane, BorderLayout.CENTER);
if (dualListingPanel != null) { if (dualListingPanel != null) {
dualListingPanel dualListingPanel.getListingPanel(LEFT)
.setLeftProgramLocationListener(new SourceProgramLocationListener()); .setProgramLocationListener(new SourceProgramLocationListener());
dualListingPanel.setRightProgramLocationListener( dualListingPanel.getListingPanel(LEFT)
new DestinationProgramLocationListener()); .setProgramLocationListener(new DestinationProgramLocationListener());
} }
markupPanel.validate(); markupPanel.validate();
@ -378,8 +379,8 @@ public class VTMarkupItemsTableProvider extends ComponentProviderAdapter
if (contains) { if (contains) {
// Remove the split pane. // Remove the split pane.
if (dualListingPanel != null) { if (dualListingPanel != null) {
dualListingPanel.setLeftProgramLocationListener(null); dualListingPanel.getListingPanel(LEFT).setProgramLocationListener(null);
dualListingPanel.setRightProgramLocationListener(null); dualListingPanel.getListingPanel(RIGHT).setProgramLocationListener(null);
} }
markupPanel.remove(splitPane); markupPanel.remove(splitPane);
splitPane.remove(functionComparisonPanel); splitPane.remove(functionComparisonPanel);
@ -462,7 +463,7 @@ public class VTMarkupItemsTableProvider extends ComponentProviderAdapter
public List<DockingActionIf> getPopupActions(Tool t, ActionContext context) { public List<DockingActionIf> getPopupActions(Tool t, ActionContext context) {
ListingCodeComparisonPanel dualListingPanel = functionComparisonPanel.getDualListingPanel(); ListingCodeComparisonPanel dualListingPanel = functionComparisonPanel.getDualListingPanel();
if (context.getComponentProvider() == this && dualListingPanel != null) { if (context.getComponentProvider() == this && dualListingPanel != null) {
ListingPanel sourcePanel = dualListingPanel.getLeftPanel(); ListingPanel sourcePanel = dualListingPanel.getListingPanel(LEFT);
return sourcePanel.getHeaderActions(getName()); return sourcePanel.getHeaderActions(getName());
} }
return new ArrayList<>(); return new ArrayList<>();
@ -477,7 +478,7 @@ public class VTMarkupItemsTableProvider extends ComponentProviderAdapter
List<VTMarkupItem> selectedItems = getSelectedMarkupItems(); List<VTMarkupItem> selectedItems = getSelectedMarkupItems();
VTMarkupItemContext vtMarkupItemContext = new VTMarkupItemContext(this, selectedItems); VTMarkupItemContext vtMarkupItemContext = new VTMarkupItemContext(this, selectedItems);
if (functionComparisonPanel.isVisible()) { if (functionComparisonPanel.isVisible()) {
CodeComparisonPanel<? extends FieldPanelCoordinator> displayedPanel = CodeComparisonPanel displayedPanel =
functionComparisonPanel.getDisplayedPanel(); functionComparisonPanel.getDisplayedPanel();
vtMarkupItemContext.setCodeComparisonPanel(displayedPanel); vtMarkupItemContext.setCodeComparisonPanel(displayedPanel);
} }
@ -626,7 +627,7 @@ public class VTMarkupItemsTableProvider extends ComponentProviderAdapter
} }
} }
else { else {
functionComparisonPanel.loadFunctions(null, null); functionComparisonPanel.clear();
} }
if (sourceHighlightProvider != null) { if (sourceHighlightProvider != null) {

View file

@ -37,22 +37,23 @@ public class LayoutLockedFieldPanelCoordinator extends LineLockedFieldPanelCoord
* Constructor for the coordinator. * Constructor for the coordinator.
* @param panels the field panels that will have their positions coordinated with each other. * @param panels the field panels that will have their positions coordinated with each other.
*/ */
public LayoutLockedFieldPanelCoordinator(FieldPanel[] panels) { public LayoutLockedFieldPanelCoordinator(FieldPanel... panels) {
super(panels); super(panels);
} }
@Override @Override
public void viewChanged(FieldPanel fp, BigInteger index, int xPos, int yPos) { public void viewChanged(FieldPanel fp, BigInteger index, int xPos, int yPos) {
if (valuesChanging) if (valuesChanging) {
return; return;
}
try { try {
valuesChanging = true; valuesChanging = true;
// "lockedLineIndex" is the IndexMap index indicating where this field panel // "lockedLineIndex" is the IndexMap index indicating where this field panel
// is locked to the other when scrolling. // is locked to the other when scrolling.
BigInteger lockedLineIndex1 = getLockedLineForPanel(fp); BigInteger lockedLineIndex1 = getLockedLineForPanel(fp);
if (lockedLineIndex1 == null) { // This shouldn't happen. if (lockedLineIndex1 == null) { // This shouldn't happen.
throw new AssertException("Couldn't find line number for indicated field panel." throw new AssertException("Couldn't find line number for indicated field panel." +
+ " FieldPanel is not one of those being managed by this coordinator."); " FieldPanel is not one of those being managed by this coordinator.");
} }
// "topIndex" is the IndexMap index of the top of the listing in view. // "topIndex" is the IndexMap index of the top of the listing in view.

View file

@ -15,6 +15,8 @@
*/ */
package ghidra.program.model.correlate; package ghidra.program.model.correlate;
import static ghidra.util.datastruct.Duo.Side.*;
import java.util.*; import java.util.*;
import java.util.Map.Entry; import java.util.Map.Entry;
@ -23,7 +25,9 @@ import ghidra.program.model.address.AddressSetView;
import ghidra.program.model.block.CodeBlock; import ghidra.program.model.block.CodeBlock;
import ghidra.program.model.listing.*; import ghidra.program.model.listing.*;
import ghidra.program.model.mem.MemoryAccessException; import ghidra.program.model.mem.MemoryAccessException;
import ghidra.program.util.FunctionAddressCorrelation; import ghidra.program.util.ListingAddressCorrelation;
import ghidra.util.datastruct.Duo;
import ghidra.util.datastruct.Duo.Side;
import ghidra.util.exception.CancelledException; import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor; import ghidra.util.task.TaskMonitor;
@ -41,7 +45,6 @@ import ghidra.util.task.TaskMonitor;
* 5) Sequences with no corresponding match are also removed from consideration. * 5) Sequences with no corresponding match are also removed from consideration.
* 6) Sequences are limited to a single basic-block, and the algorithm is basic-block aware. * 6) Sequences are limited to a single basic-block, and the algorithm is basic-block aware.
* Once a match establishes a correspondence between a pair of basic blocks, the algorithm uses * Once a match establishes a correspondence between a pair of basic blocks, the algorithm uses
* that information to further narrow in on and disambiguate matching sequences.
* 7) If a particular sequence has matches that are not unique, the algorithm tries to disambiguate the potential * 7) If a particular sequence has matches that are not unique, the algorithm tries to disambiguate the potential
* matches by looking at parent/child relationships of the containing basic-blocks. (see DisambiguateStrategy) * matches by looking at parent/child relationships of the containing basic-blocks. (see DisambiguateStrategy)
* 8) Multiple passes are attempted, each time the set of potential sequences is completely regenerated, * 8) Multiple passes are attempted, each time the set of potential sequences is completely regenerated,
@ -49,7 +52,7 @@ import ghidra.util.task.TaskMonitor;
* allows matches discovered by earlier passes to disambiguate sequences in later passes. * allows matches discovered by earlier passes to disambiguate sequences in later passes.
* *
*/ */
public class HashedFunctionAddressCorrelation implements FunctionAddressCorrelation { public class HashedFunctionAddressCorrelation implements ListingAddressCorrelation {
/** /**
* A helper class for sorting through, disambiguating, sequences with identical hashes * A helper class for sorting through, disambiguating, sequences with identical hashes
@ -59,6 +62,7 @@ public class HashedFunctionAddressCorrelation implements FunctionAddressCorrelat
public Hash hash; // The disambiguating (secondary) hash public Hash hash; // The disambiguating (secondary) hash
public int count; // Number of sequences (n-grams) in the subset matching the secondary hash public int count; // Number of sequences (n-grams) in the subset matching the secondary hash
public InstructHash instruct; // (Starting Instruction of) the n-gram public InstructHash instruct; // (Starting Instruction of) the n-gram
public DisambiguatorEntry(Hash h, InstructHash inst) { public DisambiguatorEntry(Hash h, InstructHash inst) {
hash = h; hash = h;
instruct = inst; instruct = inst;
@ -66,8 +70,7 @@ public class HashedFunctionAddressCorrelation implements FunctionAddressCorrelat
} }
} }
private Function srcFunction; private Duo<Function> functions;
private Function destFunction;
private TreeMap<Address, Address> srcToDest; // Final source -> destination address mapping private TreeMap<Address, Address> srcToDest; // Final source -> destination address mapping
private TreeMap<Address, Address> destToSrc; // Final destination -> source address mapping private TreeMap<Address, Address> destToSrc; // Final destination -> source address mapping
private HashStore srcStore; // Sorted list of source n-grams from which to draw potential matches private HashStore srcStore; // Sorted list of source n-grams from which to draw potential matches
@ -77,46 +80,36 @@ public class HashedFunctionAddressCorrelation implements FunctionAddressCorrelat
/** /**
* Correlates addresses between the two specified functions. * Correlates addresses between the two specified functions.
* @param function1 the first function * @param leftFunction the first function
* @param function2 the second function * @param rightFunction the second function
* @param mon the task monitor that indicates progress and allows the user to cancel. * @param monitor the task monitor that indicates progress and allows the user to cancel.
* @throws CancelledException if the user cancels * @throws CancelledException if the user cancels
* @throws MemoryAccessException if either functions memory can't be accessed. * @throws MemoryAccessException if either functions memory can't be accessed.
*/ */
public HashedFunctionAddressCorrelation(Function function1, Function function2, TaskMonitor mon) public HashedFunctionAddressCorrelation(Function leftFunction, Function rightFunction,
throws CancelledException, MemoryAccessException { TaskMonitor monitor) throws CancelledException, MemoryAccessException {
srcFunction = function1; if (leftFunction == null || rightFunction == null) {
destFunction = function2; throw new IllegalArgumentException("Functions can't be null!");
monitor = mon; }
this.functions = new Duo<>(leftFunction, rightFunction);
this.monitor = monitor;
srcToDest = new TreeMap<Address, Address>(); srcToDest = new TreeMap<Address, Address>();
destToSrc = new TreeMap<Address, Address>(); destToSrc = new TreeMap<Address, Address>();
if (function1 == null || function2 == null) srcStore = new HashStore(leftFunction, monitor);
return; destStore = new HashStore(rightFunction, monitor);
srcStore = new HashStore(function1, monitor);
destStore = new HashStore(function2, monitor);
hashCalc = new MnemonicHashCalculator(); hashCalc = new MnemonicHashCalculator();
calculate(); calculate();
buildFinalMaps(); buildFinalMaps();
} }
@Override @Override
public Program getFirstProgram() { public Program getProgram(Side side) {
return srcFunction.getProgram(); return functions.get(side).getProgram();
} }
@Override @Override
public Program getSecondProgram() { public AddressSetView getAddresses(Side side) {
return destFunction.getProgram(); return functions.get(side).getBody();
}
@Override
public AddressSetView getAddressesInFirst() {
return srcFunction.getBody();
}
@Override
public AddressSetView getAddressesInSecond() {
return destFunction.getBody();
} }
/** /**
@ -180,7 +173,8 @@ public class HashedFunctionAddressCorrelation implements FunctionAddressCorrelat
* @param destInstruct is (the starting Instruction of) the destination n-gram * @param destInstruct is (the starting Instruction of) the destination n-gram
* @throws MemoryAccessException * @throws MemoryAccessException
*/ */
private void declareMatch(HashEntry srcEntry,InstructHash srcInstruct,HashEntry destEntry,InstructHash destInstruct) throws MemoryAccessException { private void declareMatch(HashEntry srcEntry, InstructHash srcInstruct, HashEntry destEntry,
InstructHash destInstruct) throws MemoryAccessException {
boolean cancelMatch = false; boolean cancelMatch = false;
int matchSize = srcEntry.hash.size; int matchSize = srcEntry.hash.size;
// Its possible that some instructions of the n-gram have already been matched // Its possible that some instructions of the n-gram have already been matched
@ -192,7 +186,8 @@ public class HashedFunctionAddressCorrelation implements FunctionAddressCorrelat
destStore.removeHash(destEntry); // Remove this HashEntry destStore.removeHash(destEntry); // Remove this HashEntry
cancelMatch = true; // Cancel the match cancelMatch = true; // Cancel the match
} }
if (cancelMatch) return; if (cancelMatch)
return;
ArrayList<Instruction> srcInstructVec = new ArrayList<Instruction>(); ArrayList<Instruction> srcInstructVec = new ArrayList<Instruction>();
ArrayList<Instruction> destInstructVec = new ArrayList<Instruction>(); ArrayList<Instruction> destInstructVec = new ArrayList<Instruction>();
ArrayList<CodeBlock> srcBlockVec = new ArrayList<CodeBlock>(); ArrayList<CodeBlock> srcBlockVec = new ArrayList<CodeBlock>();
@ -247,7 +242,8 @@ public class HashedFunctionAddressCorrelation implements FunctionAddressCorrelat
* @throws CancelledException * @throws CancelledException
* @throws MemoryAccessException * @throws MemoryAccessException
*/ */
private int disambiguateNgramsWithStrategy(DisambiguateStrategy strategy,HashEntry srcEntry,HashEntry destEntry) throws CancelledException, MemoryAccessException { private int disambiguateNgramsWithStrategy(DisambiguateStrategy strategy, HashEntry srcEntry,
HashEntry destEntry) throws CancelledException, MemoryAccessException {
TreeMap<Hash, DisambiguatorEntry> srcDisambig = TreeMap<Hash, DisambiguatorEntry> srcDisambig =
constructDisambiguatorTree(srcEntry, srcStore, strategy); constructDisambiguatorTree(srcEntry, srcStore, strategy);
TreeMap<Hash, DisambiguatorEntry> destDisambig = TreeMap<Hash, DisambiguatorEntry> destDisambig =
@ -256,13 +252,18 @@ public class HashedFunctionAddressCorrelation implements FunctionAddressCorrelat
Iterator<DisambiguatorEntry> iter = srcDisambig.values().iterator(); Iterator<DisambiguatorEntry> iter = srcDisambig.values().iterator();
while (iter.hasNext()) { while (iter.hasNext()) {
DisambiguatorEntry srcDisEntry = iter.next(); DisambiguatorEntry srcDisEntry = iter.next();
if (srcDisEntry.count != 1) continue; if (srcDisEntry.count != 1)
continue;
// Its possible for this InstructHash to have been matched by an earlier DisambiguatorEntry // Its possible for this InstructHash to have been matched by an earlier DisambiguatorEntry
if (srcDisEntry.instruct.isMatched) continue; if (srcDisEntry.instruct.isMatched)
continue;
DisambiguatorEntry destDisEntry = destDisambig.get(srcDisEntry.hash); DisambiguatorEntry destDisEntry = destDisambig.get(srcDisEntry.hash);
if (destDisEntry == null) continue; if (destDisEntry == null)
if (destDisEntry.count != 1) continue; continue;
if (destDisEntry.instruct.isMatched) continue; if (destDisEntry.count != 1)
continue;
if (destDisEntry.instruct.isMatched)
continue;
// If both sides have exactly one matching InstructHash, call it a match // If both sides have exactly one matching InstructHash, call it a match
declareMatch(srcEntry, srcDisEntry.instruct, destEntry, destDisEntry.instruct); declareMatch(srcEntry, srcDisEntry.instruct, destEntry, destDisEntry.instruct);
count += 1; count += 1;
@ -278,7 +279,8 @@ public class HashedFunctionAddressCorrelation implements FunctionAddressCorrelat
* @throws CancelledException * @throws CancelledException
* @throws MemoryAccessException * @throws MemoryAccessException
*/ */
private boolean disambiguateMatchingNgrams(HashEntry srcEntry,HashEntry destEntry) throws CancelledException, MemoryAccessException { private boolean disambiguateMatchingNgrams(HashEntry srcEntry, HashEntry destEntry)
throws CancelledException, MemoryAccessException {
if (srcEntry.hasDuplicateBlocks()) if (srcEntry.hasDuplicateBlocks())
return false; return false;
if (destEntry.hasDuplicateBlocks()) if (destEntry.hasDuplicateBlocks())
@ -286,13 +288,18 @@ public class HashedFunctionAddressCorrelation implements FunctionAddressCorrelat
if (srcEntry.hash.size != destEntry.hash.size) if (srcEntry.hash.size != destEntry.hash.size)
return false; // This likely never happens, because we know the hash values are equal return false; // This likely never happens, because we know the hash values are equal
int count = disambiguateNgramsWithStrategy(new DisambiguateByParent(), srcEntry, destEntry); int count = disambiguateNgramsWithStrategy(new DisambiguateByParent(), srcEntry, destEntry);
if (count != 0) return true; if (count != 0)
return true;
count = disambiguateNgramsWithStrategy(new DisambiguateByChild(), srcEntry, destEntry); count = disambiguateNgramsWithStrategy(new DisambiguateByChild(), srcEntry, destEntry);
if (count != 0) return true; if (count != 0)
return true;
count = disambiguateNgramsWithStrategy(new DisambiguateByBytes(), srcEntry, destEntry); count = disambiguateNgramsWithStrategy(new DisambiguateByBytes(), srcEntry, destEntry);
if (count != 0) return true; if (count != 0)
count= disambiguateNgramsWithStrategy(new DisambiguateByParentWithOrder(),srcEntry,destEntry); return true;
if (count != 0) return true; count = disambiguateNgramsWithStrategy(new DisambiguateByParentWithOrder(), srcEntry,
destEntry);
if (count != 0)
return true;
return false; return false;
} }
@ -311,7 +318,8 @@ public class HashedFunctionAddressCorrelation implements FunctionAddressCorrelat
} }
else if (srcEntry.instList.size() == 1 && destEntry.instList.size() == 1) { else if (srcEntry.instList.size() == 1 && destEntry.instList.size() == 1) {
// Found a unique match // Found a unique match
declareMatch(srcEntry,srcEntry.instList.getFirst(),destEntry,destEntry.instList.getFirst()); declareMatch(srcEntry, srcEntry.instList.getFirst(), destEntry,
destEntry.instList.getFirst());
} }
else { else {
HashEntry destEntry2 = destStore.getFirstEntry(); HashEntry destEntry2 = destStore.getFirstEntry();
@ -321,7 +329,8 @@ public class HashedFunctionAddressCorrelation implements FunctionAddressCorrelat
} }
else if (srcEntry2.instList.size() == 1 && destEntry2.instList.size() == 1) { else if (srcEntry2.instList.size() == 1 && destEntry2.instList.size() == 1) {
// Found a unique match // Found a unique match
declareMatch(srcEntry2,srcEntry2.instList.getFirst(),destEntry2,destEntry2.instList.getFirst()); declareMatch(srcEntry2, srcEntry2.instList.getFirst(), destEntry2,
destEntry2.instList.getFirst());
} }
else { else {
if (!disambiguateMatchingNgrams(srcEntry, destEntry)) if (!disambiguateMatchingNgrams(srcEntry, destEntry))
@ -344,12 +353,14 @@ public class HashedFunctionAddressCorrelation implements FunctionAddressCorrelat
* @throws MemoryAccessException * @throws MemoryAccessException
* @throws CancelledException * @throws CancelledException
*/ */
private void runPasses(int minLength,int maxLength,boolean wholeBlock,boolean matchBlock,int maxPasses) throws MemoryAccessException, CancelledException { private void runPasses(int minLength, int maxLength, boolean wholeBlock, boolean matchBlock,
int maxPasses) throws MemoryAccessException, CancelledException {
srcStore.calcHashes(minLength, maxLength, wholeBlock, matchBlock, hashCalc); srcStore.calcHashes(minLength, maxLength, wholeBlock, matchBlock, hashCalc);
destStore.calcHashes(minLength, maxLength, wholeBlock, matchBlock, hashCalc); destStore.calcHashes(minLength, maxLength, wholeBlock, matchBlock, hashCalc);
for (int pass = 0; pass < maxPasses; ++pass) { for (int pass = 0; pass < maxPasses; ++pass) {
int curMatch = srcStore.numMatchedInstructions(); int curMatch = srcStore.numMatchedInstructions();
if (curMatch == srcStore.getTotalInstructions()) break; // quit if there are no unmatched instructions if (curMatch == srcStore.getTotalInstructions())
break; // quit if there are no unmatched instructions
srcStore.clearSort(); srcStore.clearSort();
destStore.clearSort(); destStore.clearSort();
@ -357,7 +368,8 @@ public class HashedFunctionAddressCorrelation implements FunctionAddressCorrelat
destStore.insertHashes(); destStore.insertHashes();
findMatches(); findMatches();
if (curMatch == srcStore.numMatchedInstructions()) break; // quit if no new matched instructions if (curMatch == srcStore.numMatchedInstructions())
break; // quit if no new matched instructions
} }
} }
@ -377,23 +389,30 @@ public class HashedFunctionAddressCorrelation implements FunctionAddressCorrelat
findMatches(); findMatches();
if (srcStore.numMatchedInstructions() == srcStore.getTotalInstructions()) return; if (srcStore.numMatchedInstructions() == srcStore.getTotalInstructions())
if (destStore.numMatchedInstructions() == destStore.getTotalInstructions()) return; return;
if (destStore.numMatchedInstructions() == destStore.getTotalInstructions())
return;
// Now try multiple passes of 3 and 4 long n-grams hopefully filling in a lot of small holes in our match // Now try multiple passes of 3 and 4 long n-grams hopefully filling in a lot of small holes in our match
// given a scaffolding of previously matched basic blocks // given a scaffolding of previously matched basic blocks
runPasses(3, 4, true, true, 10); runPasses(3, 4, true, true, 10);
if (srcStore.numMatchedInstructions() == srcStore.getTotalInstructions()) return; if (srcStore.numMatchedInstructions() == srcStore.getTotalInstructions())
if (destStore.numMatchedInstructions() == destStore.getTotalInstructions()) return; return;
if (destStore.numMatchedInstructions() == destStore.getTotalInstructions())
return;
// Repeat with big n-grams // Repeat with big n-grams
int curMatch = srcStore.numMatchedInstructions(); int curMatch = srcStore.numMatchedInstructions();
runPasses(5, 10, false, false, 3); runPasses(5, 10, false, false, 3);
if (srcStore.numMatchedInstructions() == curMatch) return; // No progress if (srcStore.numMatchedInstructions() == curMatch)
if (srcStore.numMatchedInstructions() == srcStore.getTotalInstructions()) return; return; // No progress
if (destStore.numMatchedInstructions() == destStore.getTotalInstructions()) return; if (srcStore.numMatchedInstructions() == srcStore.getTotalInstructions())
return;
if (destStore.numMatchedInstructions() == destStore.getTotalInstructions())
return;
// Repeat with small n-grams // Repeat with small n-grams
runPasses(3, 4, true, true, 10); runPasses(3, 4, true, true, 10);
@ -417,22 +436,15 @@ public class HashedFunctionAddressCorrelation implements FunctionAddressCorrelat
} }
@Override @Override
public Address getAddressInSecond(Address addressInFirst) { public Address getAddress(Side side, Address otherSideAddress) {
return srcToDest.get(addressInFirst); if (side == LEFT) {
return destToSrc.get(otherSideAddress);
}
return srcToDest.get(otherSideAddress);
} }
@Override @Override
public Address getAddressInFirst(Address addressInSecond) { public Function getFunction(Side side) {
return destToSrc.get(addressInSecond); return functions.get(side);
}
@Override
public Function getFirstFunction() {
return srcFunction;
}
@Override
public Function getSecondFunction() {
return destFunction;
} }
} }

View file

@ -15,25 +15,32 @@
*/ */
package ghidra.program.util; package ghidra.program.util;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSetView;
import ghidra.program.model.listing.Function; import ghidra.program.model.listing.Function;
import ghidra.program.model.listing.Program;
import ghidra.util.datastruct.Duo.Side;
/** public class DummyListingAddressCorrelation implements ListingAddressCorrelation {
* This is the interface for a correlator that associates instructions from one function to
* instructions from another function. Given an address from one function it determines the matching
* address in the other function if possible.
*/
public interface FunctionAddressCorrelation extends ListingAddressCorrelation {
/** @Override
* Gets the first function for this address correlator. public Program getProgram(Side side) {
* @return the first function. return null;
*/ }
public Function getFirstFunction();
/** @Override
* Gets the second function for this address correlator. public Function getFunction(Side side) {
* @return the second function. return null;
*/ }
public Function getSecondFunction();
@Override
public AddressSetView getAddresses(Side side) {
return null;
}
@Override
public Address getAddress(Side side, Address otherSideAddress) {
return null;
}
} }

View file

@ -17,54 +17,47 @@ package ghidra.program.util;
import ghidra.program.model.address.Address; import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSetView; import ghidra.program.model.address.AddressSetView;
import ghidra.program.model.listing.Function;
import ghidra.program.model.listing.Program; import ghidra.program.model.listing.Program;
import ghidra.util.datastruct.Duo.Side;
/** /**
* This is the interface for a correlator that associates addresses from one program with * This is the interface for a correlator that associates addresses from one program with
* addresses from another program or it can associate addresses from one part of a program * addresses from another program or it can associate addresses from one part of a program
* with addresses from another part of the same program. Given an address from the address set * with addresses from another part of the same program. Given an address from one program, it
* in the first program it determines the matching address from the address set for the second * can provide the corresponding address for the other program. The two programs are referred to
* program if possible. * as the LEFT program and the RIGHT program. See {@link ghidra.util.datastruct.Duo.Side}
*/ */
public interface ListingAddressCorrelation { public interface ListingAddressCorrelation {
/** /**
* Gets the program containing the first set of addresses. * Gets the program for the given side.
* @return the program for the first set of addresses. * @param side LEFT or RIGHT
* @return the program for the given side
*/ */
public abstract Program getFirstProgram(); public abstract Program getProgram(Side side);
/** /**
* Gets the program containing the second set of addresses. * Gets the function for the given side. This will be null if the addresses are not function
* This program may be different from or the same as the first program. * based.
* @return the program for the second set of addresses. * @param side LEFT or RIGHT
* @return the function for the given side or null if not function based
*/ */
public abstract Program getSecondProgram(); public abstract Function getFunction(Side side);
/** /**
* Gets the first set of addresses for this correlator. * Gets the addresses that are part of the correlator for the given side
* @return the first set of addresses. * @param side LEFT or RIGHT
* @return the addresses that are part of the correlator for the given side
*/ */
public abstract AddressSetView getAddressesInFirst(); public abstract AddressSetView getAddresses(Side side);
/** /**
* Gets the second set of addresses for this correlator. * Gets the address for the given side that matches the given address from the other side.
* @return the second set of addresses. * @param side the side to get an address for
* @param otherSideAddress the address from the other side to find a match for
* @return the address for the given side that matches the given address from the other side.
*/ */
public abstract AddressSetView getAddressesInSecond(); public abstract Address getAddress(Side side, Address otherSideAddress);
/**
* Determine the address from the second set that matches the specified address in the first set.
* @param addressInFirst the address in the first address set.
* @return the matching address in the second set or null if a match couldn't be determined.
*/
public abstract Address getAddressInSecond(Address addressInFirst);
/**
* Determine the address from the first set that matches the specified address in the second set.
* @param addressInSecond the address in the second address set.
* @return the matching address in the first set or null if a match couldn't be determined.
*/
public abstract Address getAddressInFirst(Address addressInSecond);
} }

View file

@ -0,0 +1,126 @@
/* ###
* 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.util.datastruct;
import static ghidra.util.datastruct.Duo.Side.*;
import java.util.Objects;
import java.util.function.Consumer;
/**
* Class for holding two objects of the same type. We are using the idiom of LEFT and RIGHT to
* refer to each item in this pair of objects.
* The enum "Side" is used to represent either the LEFT (or first) or RIGHT (or second) item.
*
* @param <T> The type of item that is stored in this Duo.
*/
public class Duo<T> {
public enum Side {
LEFT, RIGHT;
public Side otherSide() {
return this == LEFT ? RIGHT : LEFT;
}
}
private final T left;
private final T right;
/**
* Constructor with no values.
*/
public Duo() {
this(null, null);
}
/**
* Constructor with a left and right value.
* @param left the left value
* @param right the right value
*/
public Duo(T left, T right) {
this.left = left;
this.right = right;
}
/**
* Gets the value for the given side.
* @param side LEFT or RIGHT
* @return the value for the given side
*/
public T get(Side side) {
return side == LEFT ? left : right;
}
/**
* Creates a new Duo, replacing the value for just one side. The other side uses the value
* from this Duo.
* @param side the side that gets a new value
* @param newValue the new value for the given side
* @return the new Duo
* value as this
*/
public Duo<T> with(Side side, T newValue) {
if (side == LEFT) {
return new Duo<>(newValue, right);
}
return new Duo<>(left, newValue);
}
/**
* Invokes the given consumer on both the left and right values.
* @param c the consumer to invoke on both values
*/
public void each(Consumer<T> c) {
if (left != null) {
c.accept(left);
}
if (right != null) {
c.accept(right);
}
}
/**
* Returns true if both values are equals to this objects values.
* @param otherLeft the value to compare to our left side value
* @param otherRight the value to compare to our right side value
* @return true if both values are equals to this objects values
*/
public boolean equals(T otherLeft, T otherRight) {
return Objects.equals(left, otherLeft) && Objects.equals(right, otherRight);
}
@Override
public int hashCode() {
return Objects.hash(left, right);
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
Duo<?> other = (Duo<?>) obj;
return Objects.equals(left, other.left) && Objects.equals(right, other.right);
}
}

View file

@ -15,6 +15,8 @@
*/ */
package help.screenshot; package help.screenshot;
import static ghidra.util.datastruct.Duo.Side.*;
import java.io.IOException; import java.io.IOException;
import javax.swing.table.TableColumn; import javax.swing.table.TableColumn;
@ -102,7 +104,7 @@ public class FunctionComparisonScreenShots extends GhidraScreenShotGenerator {
functionComparisonPanel.setCurrentTabbedComponent("Listing View"); functionComparisonPanel.setCurrentTabbedComponent("Listing View");
ListingCodeComparisonPanel dualListing = ListingCodeComparisonPanel dualListing =
(ListingCodeComparisonPanel) functionComparisonPanel.getDisplayedPanel(); (ListingCodeComparisonPanel) functionComparisonPanel.getDisplayedPanel();
ListingPanel leftPanel = dualListing.getLeftPanel(); ListingPanel leftPanel = dualListing.getListingPanel(LEFT);
leftPanel.goTo(addr(0x004119aa)); leftPanel.goTo(addr(0x004119aa));
}); });
waitForSwing(); waitForSwing();

View file

@ -0,0 +1,104 @@
/* ###
* 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.compare;
import static org.junit.Assert.*;
import java.util.Set;
import org.junit.*;
import ghidra.app.plugin.core.functioncompare.*;
import ghidra.codecompare.decompile.CDisplay;
import ghidra.codecompare.decompile.DecompilerCodeComparisonPanel;
import ghidra.program.model.address.Address;
import ghidra.program.model.listing.*;
import ghidra.test.*;
/**
* Tests for the {@link FunctionComparisonPlugin function comparison plugin}
* that involve the GUI
*/
public class CompareFunctionsDecompilerViewTest extends AbstractGhidraHeadedIntegrationTest {
private TestEnv env;
private Program program1;
private Function fun1;
private Function fun2;
private FunctionComparisonPlugin plugin;
private FunctionComparisonProvider provider;
@Before
public void setUp() throws Exception {
env = new TestEnv();
plugin = env.addPlugin(FunctionComparisonPlugin.class);
program1 = buildTestProgram();
showTool(plugin.getTool());
env.open(program1);
FunctionManager functionManager = program1.getFunctionManager();
fun1 = functionManager.getFunctionAt(addr(0x01002cf5));
fun2 = functionManager.getFunctionAt(addr(0x0100415a));
}
private Address addr(long offset) {
return program1.getAddressFactory().getDefaultAddressSpace().getAddress(offset);
}
@After
public void tearDown() throws Exception {
env.dispose();
}
@Test
public void testDecompDifView() throws Exception {
Set<Function> functions = CompareFunctionsTestUtility.getFunctionsAsSet(fun1, fun2);
provider = compareFunctions(functions);
CompareFunctionsTestUtility.checkSourceFunctions(provider, fun1, fun2);
DecompilerCodeComparisonPanel panel =
(DecompilerCodeComparisonPanel) provider
.getCodeComparisonPanelByName(DecompilerCodeComparisonPanel.NAME);
waitForDecompiler(panel);
assertHasLines(panel.getLeftPanel(), 28);
assertHasLines(panel.getRightPanel(), 23);
}
private void assertHasLines(CDisplay panel, int lineCount) {
assertEquals(lineCount, panel.getDecompilerPanel().getLines().size());
}
private void waitForDecompiler(DecompilerCodeComparisonPanel panel) {
waitForSwing();
waitForCondition(() -> !panel.isBusy());
waitForSwing();
}
private FunctionComparisonProvider compareFunctions(Set<Function> functions) {
provider = runSwing(() -> plugin.compareFunctions(functions));
provider.setVisible(true);
waitForSwing();
return provider;
}
private Program buildTestProgram() throws Exception {
ClassicSampleX86ProgramBuilder builder =
new ClassicSampleX86ProgramBuilder("Test", false);
return builder.getProgram();
}
}