mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-04 18:29:37 +02:00
GP-3696 - cleaning up function compare windows.
This commit is contained in:
parent
770f5447e1
commit
5ea8e97805
77 changed files with 4065 additions and 5654 deletions
|
@ -852,16 +852,6 @@ public abstract class AbstractCodeBrowserPlugin<P extends CodeViewerProvider> ex
|
|||
return getAddressIndexMap().getAddress(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void formatModelAdded(FieldFormatModel model) {
|
||||
// uninterested
|
||||
}
|
||||
|
||||
@Override
|
||||
public void formatModelRemoved(FieldFormatModel model) {
|
||||
// uninterested
|
||||
}
|
||||
|
||||
@Override
|
||||
public void formatModelChanged(FieldFormatModel model) {
|
||||
tool.setConfigChanged(true);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -15,6 +15,9 @@
|
|||
*/
|
||||
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.event.MouseEvent;
|
||||
import java.lang.reflect.Constructor;
|
||||
|
@ -29,11 +32,10 @@ import javax.swing.event.ChangeListener;
|
|||
import docking.ActionContext;
|
||||
import docking.ComponentProvider;
|
||||
import docking.action.*;
|
||||
import docking.widgets.fieldpanel.internal.FieldPanelCoordinator;
|
||||
import docking.widgets.tabbedpane.DockingTabRenderer;
|
||||
import generic.theme.GIcon;
|
||||
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.plugintool.ComponentProviderAdapter;
|
||||
import ghidra.framework.plugintool.PluginTool;
|
||||
|
@ -43,6 +45,7 @@ import ghidra.program.model.listing.*;
|
|||
import ghidra.util.HelpLocation;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.classfinder.ClassSearcher;
|
||||
import ghidra.util.datastruct.Duo;
|
||||
import help.Help;
|
||||
import help.HelpService;
|
||||
|
||||
|
@ -54,11 +57,9 @@ import help.HelpService;
|
|||
* be compared, use a {@link MultiFunctionComparisonPanel}
|
||||
*/
|
||||
public class FunctionComparisonPanel extends JPanel implements ChangeListener {
|
||||
private static final String ORIENTATION_PROPERTY_NAME = "ORIENTATION";
|
||||
|
||||
private FunctionComparisonData leftComparisonData;
|
||||
private FunctionComparisonData rightComparisonData;
|
||||
|
||||
private static final String DEFAULT_CODE_COMPARISON_VIEW = ListingCodeComparisonPanel.TITLE;
|
||||
private static final String DEFAULT_CODE_COMPARISON_VIEW = ListingCodeComparisonPanel.NAME;
|
||||
private static final String COMPARISON_VIEW_DISPLAYED = "COMPARISON_VIEW_DISPLAYED";
|
||||
private static final String 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;
|
||||
protected PluginTool tool;
|
||||
protected ComponentProviderAdapter provider;
|
||||
private List<CodeComparisonPanel<? extends FieldPanelCoordinator>> codeComparisonPanels;
|
||||
private List<CodeComparisonPanel> codeComparisonPanels;
|
||||
private ToggleScrollLockAction toggleScrollLockAction;
|
||||
private boolean syncScrolling = false;
|
||||
|
||||
private Duo<ComparisonData> comparisonData = new Duo<ComparisonData>();
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param provider the GUI provider that includes 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,
|
||||
Function leftFunction, Function rightFunction) {
|
||||
public FunctionComparisonPanel(ComponentProviderAdapter provider, PluginTool tool) {
|
||||
this.provider = provider;
|
||||
this.tool = tool;
|
||||
this.leftComparisonData = new FunctionComparisonData();
|
||||
this.rightComparisonData = new FunctionComparisonData();
|
||||
this.leftComparisonData.setFunction(leftFunction);
|
||||
this.rightComparisonData.setFunction(rightFunction);
|
||||
this.comparisonData = new Duo<>(EMPTY, EMPTY);
|
||||
this.codeComparisonPanels = getCodeComparisonPanels();
|
||||
tabNameToComponentMap = new HashMap<>();
|
||||
createMainPanel();
|
||||
|
@ -113,15 +110,11 @@ public class FunctionComparisonPanel extends JPanel implements ChangeListener {
|
|||
* @param rightFunction The function for the right side of the panel
|
||||
*/
|
||||
public void loadFunctions(Function leftFunction, Function rightFunction) {
|
||||
leftComparisonData.setFunction(leftFunction);
|
||||
rightComparisonData.setFunction(rightFunction);
|
||||
|
||||
CodeComparisonPanel<? extends FieldPanelCoordinator> activePanel =
|
||||
getActiveComparisonPanel();
|
||||
if (activePanel != null) {
|
||||
activePanel.loadFunctions(leftComparisonData.getFunction(),
|
||||
rightComparisonData.getFunction());
|
||||
}
|
||||
ComparisonData left =
|
||||
leftFunction == null ? EMPTY : new FunctionComparisonData(leftFunction);
|
||||
ComparisonData right =
|
||||
rightFunction == null ? EMPTY : new FunctionComparisonData(rightFunction);
|
||||
loadComparisons(left, right);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -131,14 +124,19 @@ public class FunctionComparisonPanel extends JPanel implements ChangeListener {
|
|||
* @param rightData The data for the right side of the panel
|
||||
*/
|
||||
public void loadData(Data leftData, Data rightData) {
|
||||
leftComparisonData.setData(leftData);
|
||||
rightComparisonData.setData(rightData);
|
||||
|
||||
CodeComparisonPanel<? extends FieldPanelCoordinator> activePanel =
|
||||
getActiveComparisonPanel();
|
||||
if (activePanel != null) {
|
||||
activePanel.loadData(leftComparisonData.getData(), rightComparisonData.getData());
|
||||
ComparisonData left = new DataComparisonData(leftData, rightData.getLength());
|
||||
ComparisonData right = new DataComparisonData(rightData, leftData.getLength());
|
||||
loadComparisons(left, right);
|
||||
}
|
||||
|
||||
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,
|
||||
AddressSetView leftAddresses, AddressSetView rightAddresses) {
|
||||
leftComparisonData.setAddressSet(leftAddresses);
|
||||
rightComparisonData.setAddressSet(rightAddresses);
|
||||
leftComparisonData.setProgram(leftProgram);
|
||||
rightComparisonData.setProgram(rightProgram);
|
||||
CodeComparisonPanel<? extends FieldPanelCoordinator> activePanel =
|
||||
getActiveComparisonPanel();
|
||||
if (activePanel != null) {
|
||||
activePanel.loadAddresses(leftComparisonData.getProgram(),
|
||||
rightComparisonData.getProgram(), leftComparisonData.getAddressSet(),
|
||||
rightComparisonData.getAddressSet());
|
||||
}
|
||||
ComparisonData left = new AddressSetComparisonData(leftProgram, leftAddresses);
|
||||
ComparisonData right = new AddressSetComparisonData(rightProgram, rightAddresses);
|
||||
loadComparisons(left, right);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -183,35 +173,23 @@ public class FunctionComparisonPanel extends JPanel implements ChangeListener {
|
|||
* @return the description
|
||||
*/
|
||||
public String getDescription() {
|
||||
Function leftFunc = leftComparisonData.getFunction();
|
||||
Function rightFunc = rightComparisonData.getFunction();
|
||||
Data leftData = leftComparisonData.getData();
|
||||
Data rightData = rightComparisonData.getData();
|
||||
String leftShort = comparisonData.get(LEFT).getShortDescription();
|
||||
String rightShort = comparisonData.get(LEFT).getShortDescription();
|
||||
|
||||
if (leftFunc != null && rightFunc != null) {
|
||||
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";
|
||||
return leftShort + " & " + rightShort;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear both sides of this panel
|
||||
*/
|
||||
public void clear() {
|
||||
leftComparisonData.clear();
|
||||
rightComparisonData.clear();
|
||||
comparisonData = new Duo<>(EMPTY, EMPTY);
|
||||
|
||||
// Setting the addresses to be displayed to null effectively clears
|
||||
// the display
|
||||
CodeComparisonPanel<? extends FieldPanelCoordinator> activePanel =
|
||||
getActiveComparisonPanel();
|
||||
CodeComparisonPanel activePanel = getActiveComparisonPanel();
|
||||
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
|
||||
*/
|
||||
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
|
||||
*/
|
||||
public ListingCodeComparisonPanel getDualListingPanel() {
|
||||
for (CodeComparisonPanel<? extends FieldPanelCoordinator> codeComparisonPanel : codeComparisonPanels) {
|
||||
JComponent component = codeComparisonPanel.getComponent();
|
||||
if (component instanceof ListingCodeComparisonPanel) {
|
||||
return (ListingCodeComparisonPanel) component;
|
||||
for (CodeComparisonPanel codeComparisonPanel : codeComparisonPanels) {
|
||||
if (codeComparisonPanel instanceof ListingCodeComparisonPanel listingPanel) {
|
||||
return listingPanel;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
|
@ -300,11 +277,26 @@ public class FunctionComparisonPanel extends JPanel implements ChangeListener {
|
|||
tabbedPane.removeAll();
|
||||
|
||||
setVisible(false);
|
||||
for (CodeComparisonPanel<? extends FieldPanelCoordinator> codeComparisonPanel : codeComparisonPanels) {
|
||||
for (CodeComparisonPanel codeComparisonPanel : codeComparisonPanels) {
|
||||
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
|
||||
*/
|
||||
|
@ -317,12 +309,9 @@ public class FunctionComparisonPanel extends JPanel implements ChangeListener {
|
|||
add(tabbedPane, BorderLayout.CENTER);
|
||||
setPreferredSize(new Dimension(200, 300));
|
||||
|
||||
for (CodeComparisonPanel<? extends FieldPanelCoordinator> codeComparisonPanel : codeComparisonPanels) {
|
||||
codeComparisonPanel.loadFunctions(leftComparisonData.getFunction(),
|
||||
rightComparisonData.getFunction());
|
||||
JComponent component = codeComparisonPanel.getComponent();
|
||||
tabbedPane.add(codeComparisonPanel.getTitle(), component);
|
||||
tabNameToComponentMap.put(codeComparisonPanel.getTitle(), component);
|
||||
for (CodeComparisonPanel codeComparisonPanel : codeComparisonPanels) {
|
||||
tabbedPane.add(codeComparisonPanel.getName(), codeComparisonPanel);
|
||||
tabNameToComponentMap.put(codeComparisonPanel.getName(), codeComparisonPanel);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -331,24 +320,11 @@ public class FunctionComparisonPanel extends JPanel implements ChangeListener {
|
|||
* the appropriate data to be compared.
|
||||
*/
|
||||
private void tabChanged() {
|
||||
CodeComparisonPanel<? extends FieldPanelCoordinator> activePanel =
|
||||
getActiveComparisonPanel();
|
||||
CodeComparisonPanel activePanel = getActiveComparisonPanel();
|
||||
if (activePanel == null) {
|
||||
return; // initializing
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
activePanel.loadComparisons(comparisonData.get(LEFT), comparisonData.get(RIGHT));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -357,100 +333,8 @@ public class FunctionComparisonPanel extends JPanel implements ChangeListener {
|
|||
* @return the currently selected comparison panel, or null if nothing
|
||||
* selected
|
||||
*/
|
||||
private CodeComparisonPanel<? extends FieldPanelCoordinator> getActiveComparisonPanel() {
|
||||
JComponent c = (JComponent) 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);
|
||||
}
|
||||
private CodeComparisonPanel getActiveComparisonPanel() {
|
||||
return (CodeComparisonPanel) tabbedPane.getSelectedComponent();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -466,9 +350,10 @@ public class FunctionComparisonPanel extends JPanel implements ChangeListener {
|
|||
setCurrentTabbedComponent(currentTabView);
|
||||
setScrollingSyncState(
|
||||
saveState.getBoolean(prefix + CODE_COMPARISON_LOCK_SCROLLING_TOGETHER, true));
|
||||
ListingCodeComparisonPanel dualListingPanel = getDualListingPanel();
|
||||
if (dualListingPanel != null) {
|
||||
dualListingPanel.readConfigState(prefix, saveState);
|
||||
|
||||
for (CodeComparisonPanel panel : codeComparisonPanels) {
|
||||
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.putBoolean(prefix + CODE_COMPARISON_LOCK_SCROLLING_TOGETHER, isScrollingSynced());
|
||||
ListingCodeComparisonPanel dualListingPanel = getDualListingPanel();
|
||||
if (dualListingPanel != null) {
|
||||
dualListingPanel.writeConfigState(prefix, saveState);
|
||||
|
||||
for (CodeComparisonPanel panel : codeComparisonPanels) {
|
||||
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() {
|
||||
ArrayList<DockingAction> dockingActionList = new ArrayList<>();
|
||||
|
||||
// Get actions for this functionComparisonPanel
|
||||
DockingAction[] functionComparisonActions = getActions();
|
||||
for (DockingAction dockingAction : functionComparisonActions) {
|
||||
dockingActionList.add(dockingAction);
|
||||
}
|
||||
|
||||
// Get actions for each CodeComparisonPanel
|
||||
for (CodeComparisonPanel<? extends FieldPanelCoordinator> codeComparisonPanel : codeComparisonPanels) {
|
||||
DockingAction[] actions = codeComparisonPanel.getActions();
|
||||
for (DockingAction dockingAction : actions) {
|
||||
dockingActionList.add(dockingAction);
|
||||
}
|
||||
for (CodeComparisonPanel codeComparisonPanel : codeComparisonPanels) {
|
||||
dockingActionList.addAll(codeComparisonPanel.getActions());
|
||||
}
|
||||
|
||||
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) {
|
||||
Component[] components = tabbedPane.getComponents();
|
||||
for (Component component : components) {
|
||||
if (component instanceof CodeComparisonPanel<?>) {
|
||||
((CodeComparisonPanel<?>) component).setTitlePrefixes(leftTitlePrefix,
|
||||
if (component instanceof CodeComparisonPanel) {
|
||||
((CodeComparisonPanel) component).setTitlePrefixes(leftTitlePrefix,
|
||||
rightTitlePrefix);
|
||||
}
|
||||
}
|
||||
|
@ -539,34 +427,10 @@ public class FunctionComparisonPanel extends JPanel implements ChangeListener {
|
|||
* @return the action context
|
||||
*/
|
||||
public ActionContext getActionContext(MouseEvent event, ComponentProvider componentProvider) {
|
||||
Object source = (event != null) ? event.getSource() : null;
|
||||
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();
|
||||
CodeComparisonPanel activePanel = getDisplayedPanel();
|
||||
if (activePanel != null) {
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -594,8 +458,8 @@ public class FunctionComparisonPanel extends JPanel implements ChangeListener {
|
|||
toggleScrollLockAction.setToolBarData(new ToolBarData(
|
||||
syncScrolling ? SYNC_SCROLLING_ICON : UNSYNC_SCROLLING_ICON, SCROLLING_GROUP));
|
||||
// Notify each comparison panel of the scrolling sync state.
|
||||
for (CodeComparisonPanel<? extends FieldPanelCoordinator> codeComparisonPanel : codeComparisonPanels) {
|
||||
codeComparisonPanel.setScrollingSyncState(syncScrolling);
|
||||
for (CodeComparisonPanel codeComparisonPanel : codeComparisonPanels) {
|
||||
codeComparisonPanel.setSynchronizedScrolling(syncScrolling);
|
||||
}
|
||||
this.syncScrolling = syncScrolling;
|
||||
}
|
||||
|
@ -605,17 +469,17 @@ public class FunctionComparisonPanel extends JPanel implements ChangeListener {
|
|||
*
|
||||
* @return the current panel or null.
|
||||
*/
|
||||
public CodeComparisonPanel<? extends FieldPanelCoordinator> getDisplayedPanel() {
|
||||
public CodeComparisonPanel getDisplayedPanel() {
|
||||
int selectedIndex = tabbedPane.getSelectedIndex();
|
||||
Component component = tabbedPane.getComponentAt(selectedIndex);
|
||||
return (CodeComparisonPanel<?>) component;
|
||||
return (CodeComparisonPanel) component;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the enablement for all actions provided by each panel
|
||||
*/
|
||||
public void updateActionEnablement() {
|
||||
for (CodeComparisonPanel<? extends FieldPanelCoordinator> codeComparisonPanel : codeComparisonPanels) {
|
||||
for (CodeComparisonPanel codeComparisonPanel : codeComparisonPanels) {
|
||||
codeComparisonPanel.updateActionEnablement();
|
||||
}
|
||||
}
|
||||
|
@ -625,8 +489,8 @@ public class FunctionComparisonPanel extends JPanel implements ChangeListener {
|
|||
*
|
||||
* @return null if there is no code comparison panel
|
||||
*/
|
||||
CodeComparisonPanel<? extends FieldPanelCoordinator> getCurrentComponent() {
|
||||
return (CodeComparisonPanel<?>) tabbedPane.getSelectedComponent();
|
||||
CodeComparisonPanel getCurrentComponent() {
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -686,17 +550,17 @@ public class FunctionComparisonPanel extends JPanel implements ChangeListener {
|
|||
*
|
||||
* @return the CodeComparisonPanels which are extension points
|
||||
*/
|
||||
private List<CodeComparisonPanel<? extends FieldPanelCoordinator>> getCodeComparisonPanels() {
|
||||
private List<CodeComparisonPanel> getCodeComparisonPanels() {
|
||||
if (codeComparisonPanels == null) {
|
||||
codeComparisonPanels = createAllPossibleCodeComparisonPanels();
|
||||
codeComparisonPanels.sort((p1, p2) -> p1.getTitle().compareTo(p2.getTitle()));
|
||||
codeComparisonPanels.sort((p1, p2) -> p1.getName().compareTo(p2.getName()));
|
||||
}
|
||||
return codeComparisonPanels;
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||
private ArrayList<CodeComparisonPanel<? extends FieldPanelCoordinator>> createAllPossibleCodeComparisonPanels() {
|
||||
ArrayList<CodeComparisonPanel<? extends FieldPanelCoordinator>> instances =
|
||||
private ArrayList<CodeComparisonPanel> createAllPossibleCodeComparisonPanels() {
|
||||
ArrayList<CodeComparisonPanel> instances =
|
||||
new ArrayList<>();
|
||||
List<Class<? extends CodeComparisonPanel>> classes =
|
||||
ClassSearcher.getClasses(CodeComparisonPanel.class);
|
||||
|
|
|
@ -19,19 +19,13 @@ import java.util.Set;
|
|||
import java.util.function.Supplier;
|
||||
|
||||
import ghidra.app.CorePluginPackage;
|
||||
import ghidra.app.events.ProgramActivatedPluginEvent;
|
||||
import ghidra.app.events.ProgramClosedPluginEvent;
|
||||
import ghidra.app.events.ProgramSelectionPluginEvent;
|
||||
import ghidra.app.events.*;
|
||||
import ghidra.app.plugin.PluginCategoryNames;
|
||||
import ghidra.app.plugin.ProgramPlugin;
|
||||
import ghidra.app.plugin.core.functioncompare.actions.CompareFunctionsAction;
|
||||
import ghidra.app.plugin.core.functioncompare.actions.CompareFunctionsFromListingAction;
|
||||
import ghidra.app.services.FunctionComparisonService;
|
||||
import ghidra.framework.model.DomainObjectChangeRecord;
|
||||
import ghidra.framework.model.DomainObjectChangedEvent;
|
||||
import ghidra.framework.model.DomainObjectEvent;
|
||||
import ghidra.framework.model.DomainObjectListener;
|
||||
import ghidra.framework.model.EventType;
|
||||
import ghidra.framework.model.*;
|
||||
import ghidra.framework.plugintool.PluginInfo;
|
||||
import ghidra.framework.plugintool.PluginTool;
|
||||
import ghidra.framework.plugintool.util.PluginStatus;
|
||||
|
@ -117,7 +111,7 @@ public class FunctionComparisonPlugin extends ProgramPlugin
|
|||
|
||||
EventType eventType = doRecord.getEventType();
|
||||
if (eventType == DomainObjectEvent.RESTORED) {
|
||||
functionComparisonManager.domainObjectRestored(ev);
|
||||
functionComparisonManager.domainObjectRestored((Program) ev.getSource());
|
||||
}
|
||||
else if (eventType == ProgramEvent.FUNCTION_REMOVED) {
|
||||
ProgramChangeRecord rec = (ProgramChangeRecord) ev.getChangeRecord(i);
|
||||
|
|
|
@ -15,6 +15,8 @@
|
|||
*/
|
||||
package ghidra.app.plugin.core.functioncompare;
|
||||
|
||||
import static ghidra.util.datastruct.Duo.Side.*;
|
||||
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.util.*;
|
||||
|
||||
|
@ -23,7 +25,6 @@ import docking.Tool;
|
|||
import docking.action.DockingAction;
|
||||
import docking.action.DockingActionIf;
|
||||
import docking.actions.PopupActionProvider;
|
||||
import docking.widgets.fieldpanel.internal.FieldPanelCoordinator;
|
||||
import ghidra.app.services.FunctionComparisonModel;
|
||||
import ghidra.app.services.FunctionComparisonService;
|
||||
import ghidra.app.util.viewer.listingpanel.ListingCodeComparisonPanel;
|
||||
|
@ -87,7 +88,7 @@ public class FunctionComparisonProvider extends ComponentProviderAdapter
|
|||
@Override
|
||||
public FunctionComparisonPanel getComponent() {
|
||||
if (functionComparisonPanel == null) {
|
||||
functionComparisonPanel = new FunctionComparisonPanel(this, tool, null, null);
|
||||
functionComparisonPanel = new FunctionComparisonPanel(this, tool);
|
||||
}
|
||||
return functionComparisonPanel;
|
||||
}
|
||||
|
@ -107,19 +108,14 @@ public class FunctionComparisonProvider extends ComponentProviderAdapter
|
|||
buff.append(getName() + "\n");
|
||||
buff.append("Tab Text: ");
|
||||
buff.append(getTabText() + "\n");
|
||||
Function leftFunction = functionComparisonPanel.getLeftFunction();
|
||||
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(functionComparisonPanel.getDescription());
|
||||
buff.append("tool = " + tool + "\n");
|
||||
return buff.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ActionContext getActionContext(MouseEvent event) {
|
||||
CodeComparisonPanel<? extends FieldPanelCoordinator> currentComponent =
|
||||
CodeComparisonPanel currentComponent =
|
||||
functionComparisonPanel.getCurrentComponent();
|
||||
return currentComponent.getActionContext(this, event);
|
||||
}
|
||||
|
@ -145,7 +141,7 @@ public class FunctionComparisonProvider extends ComponentProviderAdapter
|
|||
ListingCodeComparisonPanel dualListingPanel =
|
||||
functionComparisonPanel.getDualListingPanel();
|
||||
if (dualListingPanel != null) {
|
||||
ListingPanel leftPanel = dualListingPanel.getLeftPanel();
|
||||
ListingPanel leftPanel = dualListingPanel.getListingPanel(LEFT);
|
||||
return leftPanel.getHeaderActions(getName());
|
||||
}
|
||||
}
|
||||
|
@ -178,6 +174,7 @@ public class FunctionComparisonProvider extends ComponentProviderAdapter
|
|||
* @param program the program being closed
|
||||
*/
|
||||
public void programClosed(Program program) {
|
||||
functionComparisonPanel.programClosed(program);
|
||||
model.removeFunctions(program);
|
||||
closeIfEmpty();
|
||||
}
|
||||
|
@ -210,7 +207,7 @@ public class FunctionComparisonProvider extends ComponentProviderAdapter
|
|||
* @param program the program that was restored (undo/redo)
|
||||
*/
|
||||
public void programRestored(Program program) {
|
||||
CodeComparisonPanel<? extends FieldPanelCoordinator> comparePanel =
|
||||
CodeComparisonPanel comparePanel =
|
||||
functionComparisonPanel.getCurrentComponent();
|
||||
comparePanel.programRestored(program);
|
||||
}
|
||||
|
@ -283,6 +280,10 @@ public class FunctionComparisonProvider extends ComponentProviderAdapter
|
|||
this.closeListener = Callback.dummyIfNull(closeListener);
|
||||
}
|
||||
|
||||
public CodeComparisonPanel getCodeComparisonPanelByName(String name) {
|
||||
return functionComparisonPanel.getCodeComparisonPanelByName(name);
|
||||
}
|
||||
|
||||
public void dispose() {
|
||||
functionComparisonPanel.dispose();
|
||||
}
|
||||
|
|
|
@ -20,7 +20,6 @@ import java.util.Set;
|
|||
import java.util.concurrent.CopyOnWriteArraySet;
|
||||
|
||||
import docking.ComponentProviderActivationListener;
|
||||
import ghidra.framework.model.*;
|
||||
import ghidra.program.model.listing.Function;
|
||||
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
|
||||
* 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) {
|
||||
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;
|
||||
public void domainObjectRestored(Program program) {
|
||||
providers.stream().forEach(p -> p.programRestored(program));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -15,6 +15,8 @@
|
|||
*/
|
||||
package ghidra.app.plugin.core.functioncompare;
|
||||
|
||||
import static ghidra.util.datastruct.Duo.Side.*;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.event.ItemEvent;
|
||||
import java.awt.event.ItemListener;
|
||||
|
@ -23,11 +25,11 @@ import java.util.Set;
|
|||
|
||||
import javax.swing.*;
|
||||
|
||||
import docking.widgets.fieldpanel.internal.FieldPanelCoordinator;
|
||||
import ghidra.app.services.FunctionComparisonModel;
|
||||
import ghidra.app.util.viewer.util.CodeComparisonPanel;
|
||||
import ghidra.framework.plugintool.PluginTool;
|
||||
import ghidra.program.model.listing.Function;
|
||||
import ghidra.util.datastruct.Duo.Side;
|
||||
import help.Help;
|
||||
import help.HelpService;
|
||||
|
||||
|
@ -67,7 +69,7 @@ public class MultiFunctionComparisonPanel extends FunctionComparisonPanel {
|
|||
* @param tool the active plugin tool
|
||||
*/
|
||||
public MultiFunctionComparisonPanel(MultiFunctionComparisonProvider provider, PluginTool tool) {
|
||||
super(provider, tool, null, null);
|
||||
super(provider, tool);
|
||||
|
||||
JPanel choicePanel = new JPanel(new GridLayout(1, 2));
|
||||
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
|
||||
// comparison panel because the name of the function/data being shown
|
||||
// is already visible in the combo box
|
||||
getComparisonPanels().forEach(p -> p.setShowTitles(false));
|
||||
getComparisonPanels().forEach(p -> p.setShowDataTitles(false));
|
||||
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
|
||||
// actions would not be properly enabled/disabled
|
||||
tool.contextChanged(provider);
|
||||
tool.setStatusInfo("function comparisons updated");
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -108,10 +109,14 @@ public class MultiFunctionComparisonPanel extends FunctionComparisonPanel {
|
|||
* @return the focused component
|
||||
*/
|
||||
public JComboBox<Function> getFocusedComponent() {
|
||||
CodeComparisonPanel<? extends FieldPanelCoordinator> currentComponent =
|
||||
getCurrentComponent();
|
||||
boolean sourceHasFocus = currentComponent.leftPanelHasFocus();
|
||||
return sourceHasFocus ? sourceFunctionsCB : targetFunctionsCB;
|
||||
CodeComparisonPanel currentComponent = getCurrentComponent();
|
||||
Side side = currentComponent.getActiveSide();
|
||||
return side == LEFT ? sourceFunctionsCB : targetFunctionsCB;
|
||||
}
|
||||
|
||||
public Side getFocusedSide() {
|
||||
CodeComparisonPanel currentComponent = getCurrentComponent();
|
||||
return currentComponent.getActiveSide();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -260,9 +265,6 @@ public class MultiFunctionComparisonPanel extends FunctionComparisonPanel {
|
|||
return;
|
||||
}
|
||||
|
||||
Function selected = (Function) sourceFunctionsCBModel.getSelectedItem();
|
||||
loadFunctions(selected, null);
|
||||
|
||||
// Each time a source function is selected we need
|
||||
// to load the targets associated with it
|
||||
reloadTargetList((Function) sourceFunctionsCBModel.getSelectedItem());
|
||||
|
|
|
@ -15,21 +15,27 @@
|
|||
*/
|
||||
package ghidra.app.plugin.core.functioncompare.actions;
|
||||
|
||||
import static ghidra.util.datastruct.Duo.Side.*;
|
||||
|
||||
import java.awt.event.*;
|
||||
import java.util.List;
|
||||
|
||||
import javax.swing.Icon;
|
||||
import javax.swing.JComboBox;
|
||||
|
||||
import docking.ActionContext;
|
||||
import docking.action.ToggleDockingAction;
|
||||
import docking.action.ToolBarData;
|
||||
import docking.widgets.fieldpanel.internal.FieldPanelCoordinator;
|
||||
import ghidra.app.plugin.core.functioncompare.*;
|
||||
import ghidra.app.plugin.core.functioncompare.MultiFunctionComparisonPanel;
|
||||
import ghidra.app.plugin.core.functioncompare.MultiFunctionComparisonProvider;
|
||||
import ghidra.app.services.GoToService;
|
||||
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.Program;
|
||||
import ghidra.util.HTMLUtilities;
|
||||
import ghidra.util.HelpLocation;
|
||||
import ghidra.util.datastruct.Duo.Side;
|
||||
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 MultiFunctionComparisonPanel comparisonPanel;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
|
@ -57,6 +65,7 @@ public class NavigateToFunctionAction extends ToggleDockingAction {
|
|||
*/
|
||||
public NavigateToFunctionAction(MultiFunctionComparisonProvider provider) {
|
||||
super("Navigate To Selected Function", provider.getName());
|
||||
comparisonPanel = (MultiFunctionComparisonPanel) provider.getComponent();
|
||||
|
||||
goToService = provider.getTool().getService(GoToService.class);
|
||||
|
||||
|
@ -70,8 +79,15 @@ public class NavigateToFunctionAction extends ToggleDockingAction {
|
|||
setHelpLocation(
|
||||
new HelpLocation(MultiFunctionComparisonPanel.HELP_TOPIC, "Navigate_To_Function"));
|
||||
|
||||
addFocusListeners(provider);
|
||||
addChangeListeners(provider);
|
||||
addFocusListeners();
|
||||
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
|
||||
* is generated for the entry point of the function.
|
||||
*
|
||||
* @param provider the function comparison provider
|
||||
*/
|
||||
private void addChangeListeners(MultiFunctionComparisonProvider provider) {
|
||||
MultiFunctionComparisonPanel panel = (MultiFunctionComparisonPanel) provider.getComponent();
|
||||
private void addChangeListeners() {
|
||||
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,
|
||||
* triggered when focus has been changed. If focused is gained in a panel,
|
||||
* 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();
|
||||
List<CodeComparisonPanel<? extends FieldPanelCoordinator>> panels =
|
||||
mainPanel.getComparisonPanels();
|
||||
for (CodeComparisonPanel panel : panels) {
|
||||
panel.getComparisonComponent(LEFT)
|
||||
.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
|
||||
public void focusGained(FocusEvent e) {
|
||||
if (NavigateToFunctionAction.this.isSelected()) {
|
||||
|
||||
Address addr = null;
|
||||
|
||||
if (panel.getRightFunction() != null) {
|
||||
addr = panel.getRightFunction().getBody().getMinAddress();
|
||||
if (!isSelected()) {
|
||||
return;
|
||||
}
|
||||
else if (panel.getRightData() != null) {
|
||||
addr = panel.getRightData().getAddress();
|
||||
Program program = panel.getProgram(side);
|
||||
AddressSetView addresses = panel.getAddresses(side);
|
||||
if (program != null && addresses != null && !addresses.isEmpty()) {
|
||||
goToService.goTo(addresses.getMinAddress(), program);
|
||||
}
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -107,6 +107,11 @@ public class MarkerManager implements MarkerService {
|
|||
Gui.addThemeListener(themeListener);
|
||||
}
|
||||
|
||||
public void clearAll() {
|
||||
programMarkersByGroup.clear();
|
||||
markerSetCache.clear();
|
||||
}
|
||||
|
||||
private void themeChanged(ThemeEvent e) {
|
||||
if (e instanceof ColorChangedThemeEvent) {
|
||||
markerSetCache.clearColors();
|
||||
|
|
|
@ -40,15 +40,6 @@ public class FieldHeader extends JTabbedPane implements ChangeListener {
|
|||
|
||||
private FormatManager formatManager;
|
||||
private FormatModelListener formatListener = new FormatModelListener() {
|
||||
@Override
|
||||
public void formatModelAdded(FieldFormatModel formatModel) {
|
||||
createTabs();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void formatModelRemoved(FieldFormatModel formatModel) {
|
||||
createTabs();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void formatModelChanged(FieldFormatModel formatModel) {
|
||||
|
|
|
@ -159,8 +159,10 @@ public class FormatManager implements OptionsChangeListener {
|
|||
* @param listener the listener to be added
|
||||
*/
|
||||
public void addFormatModelListener(FormatModelListener listener) {
|
||||
if (listener != null) {
|
||||
formatListeners.add(listener);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the given listener from the list of listeners to be notified of a
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
* REVIEWED: YES
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -20,16 +19,26 @@ package ghidra.app.util.viewer.format;
|
|||
* Interface for listeners to format model changes.
|
||||
*/
|
||||
public interface FormatModelListener {
|
||||
|
||||
/**
|
||||
* Notifies that a new format model was added to the format manager.
|
||||
* @param model the new model.
|
||||
* Format model added. Not used.
|
||||
* @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.
|
||||
* @param model the model that was removed.
|
||||
* Format model removed. Not used.
|
||||
* @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.
|
||||
|
|
|
@ -16,57 +16,32 @@
|
|||
package ghidra.app.util.viewer.listingpanel;
|
||||
|
||||
import docking.ComponentProvider;
|
||||
import docking.widgets.fieldpanel.internal.FieldPanelCoordinator;
|
||||
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.
|
||||
*/
|
||||
public class DualListingActionContext extends CodeComparisonActionContext {
|
||||
|
||||
private CodeComparisonPanel<? extends FieldPanelCoordinator> codeComparisonPanel = null;
|
||||
private ListingCodeComparisonPanel codeComparisonPanel = null;
|
||||
|
||||
/**
|
||||
* Constructor for a dual listing's action context.
|
||||
* @param provider the provider that uses this action context.
|
||||
* @param panel the ListingCodeComparisonPanel that generated this context
|
||||
*/
|
||||
public DualListingActionContext(ComponentProvider provider) {
|
||||
super(provider);
|
||||
public DualListingActionContext(ComponentProvider provider, ListingCodeComparisonPanel panel) {
|
||||
super(provider, panel, panel.getActiveListingPanel().getFieldPanel());
|
||||
this.codeComparisonPanel = panel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the CodeComparisonPanel associated with this context.
|
||||
* @param codeComparisonPanel the code comparison panel
|
||||
* Returns the {@link ListingCodeComparisonPanel} that generated this context
|
||||
* @return the listing comparison panel that generated this context
|
||||
*/
|
||||
public void setCodeComparisonPanel(
|
||||
CodeComparisonPanel<? extends FieldPanelCoordinator> codeComparisonPanel) {
|
||||
this.codeComparisonPanel = codeComparisonPanel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CodeComparisonPanel<? extends FieldPanelCoordinator> getCodeComparisonPanel() {
|
||||
public ListingCodeComparisonPanel 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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -22,6 +22,7 @@ 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.datastruct.Duo.Side;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
/**
|
||||
|
@ -32,7 +33,7 @@ import ghidra.util.task.TaskMonitor;
|
|||
class DualListingGoToService implements GoToService {
|
||||
|
||||
private ListingCodeComparisonPanel dualListing;
|
||||
private boolean isLeftSide;
|
||||
private Side side;
|
||||
private GoToOverrideService overrideService;
|
||||
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
|
||||
* GoToService methods don't pertain specifically to the left or right 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,
|
||||
boolean isLeftSide) {
|
||||
Side side) {
|
||||
this.goToService = goToService;
|
||||
this.dualListing = dualListing;
|
||||
this.isLeftSide = isLeftSide;
|
||||
this.side = side;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -92,8 +93,7 @@ class DualListingGoToService implements GoToService {
|
|||
if (addr == null) {
|
||||
return false;
|
||||
}
|
||||
AddressSetView addresses =
|
||||
isLeftSide ? dualListing.getLeftAddresses() : dualListing.getRightAddresses();
|
||||
AddressSetView addresses = dualListing.getAddresses(side);
|
||||
if (!addresses.contains(addr)) {
|
||||
dualListing.setStatusInfo(
|
||||
"\"" + addr.toString() + "\" is outside the current listing's view.");
|
||||
|
@ -112,8 +112,7 @@ class DualListingGoToService implements GoToService {
|
|||
return false;
|
||||
}
|
||||
|
||||
ListingPanel listingPanel =
|
||||
(isLeftSide) ? dualListing.getLeftPanel() : dualListing.getRightPanel();
|
||||
ListingPanel listingPanel = dualListing.getListingPanel(side);
|
||||
return listingPanel.goTo(loc);
|
||||
}
|
||||
|
||||
|
@ -124,8 +123,7 @@ class DualListingGoToService implements GoToService {
|
|||
return false;
|
||||
}
|
||||
|
||||
ListingPanel listingPanel =
|
||||
(isLeftSide) ? dualListing.getLeftPanel() : dualListing.getRightPanel();
|
||||
ListingPanel listingPanel = dualListing.getListingPanel(side);
|
||||
return listingPanel.goTo(addr);
|
||||
}
|
||||
|
||||
|
|
|
@ -36,15 +36,14 @@ class DualListingNavigator implements Navigatable {
|
|||
|
||||
/**
|
||||
* Constructor for a dual listing navigator.
|
||||
* @param dualListingPanel 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 listingPanel the dual listing whose left or right listing panel is to be controlled.
|
||||
* @param goToService which side LEFT or RIGHT
|
||||
* false means it's for the right side listing.
|
||||
*/
|
||||
DualListingNavigator(ListingCodeComparisonPanel dualListingPanel, boolean isLeftSide) {
|
||||
DualListingNavigator(ListingPanel listingPanel, GoToService goToService) {
|
||||
|
||||
this.listingPanel =
|
||||
isLeftSide ? dualListingPanel.getLeftPanel() : dualListingPanel.getRightPanel();
|
||||
this.goToService = dualListingPanel.getGoToService(isLeftSide);
|
||||
this.listingPanel = listingPanel;
|
||||
this.goToService = goToService;
|
||||
id = UniversalIdGenerator.nextID().getValue();
|
||||
}
|
||||
|
||||
|
@ -152,7 +151,8 @@ class DualListingNavigator implements Navigatable {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void removeHighlightProvider(ListingHighlightProvider highlightProvider, Program program) {
|
||||
public void removeHighlightProvider(ListingHighlightProvider highlightProvider,
|
||||
Program program) {
|
||||
// currently unsupported
|
||||
}
|
||||
|
||||
|
|
|
@ -18,6 +18,7 @@ package ghidra.app.util.viewer.listingpanel;
|
|||
import ghidra.app.services.GoToService;
|
||||
import ghidra.framework.plugintool.ServiceProvider;
|
||||
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
|
||||
|
@ -35,14 +36,13 @@ class DualListingServiceProvider implements ServiceProvider {
|
|||
* Constructor for a DualListingServiceProvider.
|
||||
* @param serviceProvider the service provider to use for acquiring services other than goTo.
|
||||
* @param panel the dual listing code comparison panel.
|
||||
* @param isLeftPanel true indicates this is the left listing panel of the dual panel.
|
||||
* false indicates the right panel.
|
||||
* @param side LEFT or RIGHT
|
||||
*/
|
||||
DualListingServiceProvider(ServiceProvider serviceProvider, ListingCodeComparisonPanel panel,
|
||||
boolean isLeftPanel) {
|
||||
Side side) {
|
||||
this.serviceProvider = serviceProvider;
|
||||
GoToService goToService = serviceProvider.getService(GoToService.class);
|
||||
this.dualListingGoToService = new DualListingGoToService(goToService, panel, isLeftPanel);
|
||||
this.dualListingGoToService = new DualListingGoToService(goToService, panel, side);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
File diff suppressed because it is too large
Load diff
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -15,6 +15,9 @@
|
|||
*/
|
||||
package ghidra.app.util.viewer.listingpanel;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import javax.swing.Icon;
|
||||
|
||||
import docking.ActionContext;
|
||||
|
@ -71,9 +74,9 @@ public class ListingDiffActionManager {
|
|||
* Gets the actions.
|
||||
* @return the docking actions.
|
||||
*/
|
||||
public DockingAction[] getActions() {
|
||||
return new DockingAction[] { toggleIgnoreByteDiffsAction, toggleIgnoreConstantsAction,
|
||||
toggleIgnoreRegisterNamesAction };
|
||||
public List<DockingAction> getActions() {
|
||||
return Arrays.asList(toggleIgnoreByteDiffsAction, toggleIgnoreConstantsAction,
|
||||
toggleIgnoreRegisterNamesAction);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
package ghidra.app.util.viewer.listingpanel;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.util.ArrayList;
|
||||
import java.util.*;
|
||||
|
||||
import docking.widgets.fieldpanel.support.Highlight;
|
||||
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.Instruction;
|
||||
import ghidra.program.util.ListingDiff;
|
||||
import ghidra.util.datastruct.Duo.Side;
|
||||
|
||||
public class ListingDiffHighlightProvider implements ListingHighlightProvider {
|
||||
|
||||
|
@ -33,22 +34,22 @@ public class ListingDiffHighlightProvider implements ListingHighlightProvider {
|
|||
private static final String DEFAULT_OPERAND_SEPARATOR = ",";
|
||||
|
||||
private ListingDiff listingDiff;
|
||||
private boolean isListing1;
|
||||
private Side side;
|
||||
private ListingCodeComparisonOptions comparisonOptions;
|
||||
|
||||
/**
|
||||
* Constructor for this highlight provider.
|
||||
* @param listingDiff the ListingDiff to use to determine where there are differences that
|
||||
* 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.
|
||||
* @param comparisonOptions the tool options that indicate the current
|
||||
* background colors for the Listing code comparison panel.
|
||||
*/
|
||||
public ListingDiffHighlightProvider(ListingDiff listingDiff, boolean isListing1,
|
||||
public ListingDiffHighlightProvider(ListingDiff listingDiff, Side side,
|
||||
ListingCodeComparisonOptions comparisonOptions) {
|
||||
this.listingDiff = listingDiff;
|
||||
this.isListing1 = isListing1;
|
||||
this.side = side;
|
||||
this.comparisonOptions = comparisonOptions;
|
||||
}
|
||||
|
||||
|
@ -77,14 +78,12 @@ public class ListingDiffHighlightProvider implements ListingHighlightProvider {
|
|||
private Highlight[] getByteDiffHighlights(String text, CodeUnit codeUnit,
|
||||
int cursorTextOffset) {
|
||||
Address minAddress = codeUnit.getMinAddress();
|
||||
AddressSetView unmatchedDiffs = (isListing1) ? listingDiff.getListing1UnmatchedCode()
|
||||
: listingDiff.getListing2UnmatchedCode();
|
||||
AddressSetView unmatchedDiffs = listingDiff.getUnmatchedCode(side);
|
||||
if (unmatchedDiffs.contains(minAddress)) {
|
||||
return NO_HIGHLIGHTS;
|
||||
}
|
||||
Color byteDiffsBackgroundColor = comparisonOptions.getByteDiffsBackgroundColor();
|
||||
AddressSetView byteDiffs =
|
||||
(isListing1) ? listingDiff.getListing1ByteDiffs() : listingDiff.getListing2ByteDiffs();
|
||||
AddressSetView byteDiffs = listingDiff.getByteDiffs(side);
|
||||
// Get intersection of Byte Diff addresses and this code unit's addresses
|
||||
AddressSet diffSet = new AddressSet(codeUnit.getMinAddress(), codeUnit.getMaxAddress());
|
||||
diffSet = diffSet.intersect(byteDiffs);
|
||||
|
@ -108,19 +107,17 @@ public class ListingDiffHighlightProvider implements ListingHighlightProvider {
|
|||
private Highlight[] getMnemonicDiffHighlights(String text, CodeUnit codeUnit,
|
||||
int cursorTextOffset) {
|
||||
Address minAddress = codeUnit.getMinAddress();
|
||||
AddressSetView unmatchedDiffs = (isListing1) ? listingDiff.getListing1UnmatchedCode()
|
||||
: listingDiff.getListing2UnmatchedCode();
|
||||
AddressSetView unmatchedDiffs = listingDiff.getUnmatchedCode(side);
|
||||
if (unmatchedDiffs.contains(minAddress)) {
|
||||
return NO_HIGHLIGHTS;
|
||||
}
|
||||
Color mnemonicDiffsBackgroundColor = comparisonOptions.getMnemonicDiffsBackgroundColor();
|
||||
AddressSetView codeUnitDiffs = (isListing1) ? listingDiff.getListing1CodeUnitDiffs()
|
||||
: listingDiff.getListing2CodeUnitDiffs();
|
||||
AddressSetView codeUnitDiffs = listingDiff.getCodeUnitDiffs(side);
|
||||
// Get intersection of Code Unit Diff addresses and this code unit's addresses
|
||||
AddressSet diffSet = new AddressSet(codeUnit.getMinAddress(), codeUnit.getMaxAddress());
|
||||
diffSet = diffSet.intersect(codeUnitDiffs);
|
||||
if (!diffSet.isEmpty()) {
|
||||
CodeUnit otherCodeUnit = listingDiff.getMatchingCodeUnit(codeUnit, isListing1);
|
||||
CodeUnit otherCodeUnit = listingDiff.getMatchingCodeUnit(codeUnit, side);
|
||||
if (otherCodeUnit == null) {
|
||||
return entireTextHighlight(text, cursorTextOffset, mnemonicDiffsBackgroundColor);
|
||||
}
|
||||
|
@ -137,32 +134,32 @@ public class ListingDiffHighlightProvider implements ListingHighlightProvider {
|
|||
private Highlight[] getOperandDiffHighlights(String text, CodeUnit codeUnit,
|
||||
int cursorTextOffset) {
|
||||
Address minAddress = codeUnit.getMinAddress();
|
||||
AddressSetView unmatchedDiffs = (isListing1) ? listingDiff.getListing1UnmatchedCode()
|
||||
: listingDiff.getListing2UnmatchedCode();
|
||||
AddressSetView unmatchedDiffs = listingDiff.getUnmatchedCode(side);
|
||||
if (unmatchedDiffs.contains(minAddress)) {
|
||||
return NO_HIGHLIGHTS;
|
||||
}
|
||||
Color operandDiffsBackgroundColor = comparisonOptions.getOperandDiffsBackgroundColor();
|
||||
AddressSetView codeUnitDiffs = (isListing1) ? listingDiff.getListing1CodeUnitDiffs()
|
||||
: listingDiff.getListing2CodeUnitDiffs();
|
||||
AddressSetView codeUnitDiffs = listingDiff.getCodeUnitDiffs(side);
|
||||
// Get intersection of Code Unit Diff addresses and this code unit's addresses
|
||||
AddressSet diffSet = new AddressSet(codeUnit.getMinAddress(), codeUnit.getMaxAddress());
|
||||
diffSet = diffSet.intersect(codeUnitDiffs);
|
||||
if (!diffSet.isEmpty()) {
|
||||
CodeUnit matchingCodeUnit = listingDiff.getMatchingCodeUnit(codeUnit, isListing1);
|
||||
CodeUnit matchingCodeUnit = listingDiff.getMatchingCodeUnit(codeUnit, side);
|
||||
if (listingDiff.doesEntireOperandSetDiffer(codeUnit, matchingCodeUnit)) {
|
||||
return entireTextHighlight(text, cursorTextOffset, operandDiffsBackgroundColor);
|
||||
}
|
||||
Pair[] pairs = getOperandPairs(text, codeUnit);
|
||||
List<Range> operandRanges = getOperandRanges(text, codeUnit);
|
||||
int numOperands = codeUnit.getNumOperands();
|
||||
if (pairs.length != numOperands) {
|
||||
if (operandRanges.size() != numOperands) {
|
||||
return entireTextHighlight(text, cursorTextOffset, operandDiffsBackgroundColor);
|
||||
}
|
||||
int[] diffOpIndices = listingDiff.getOperandsThatDiffer(codeUnit, matchingCodeUnit);
|
||||
ArrayList<Highlight> highlights = new ArrayList<>();
|
||||
for (int diffOpIndex : diffOpIndices) {
|
||||
// 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));
|
||||
}
|
||||
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.
|
||||
* @param text the full text from the operand field
|
||||
* @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.
|
||||
*/
|
||||
private Pair[] getOperandPairs(String text, CodeUnit codeUnit) {
|
||||
private List<Range> getOperandRanges(String text, CodeUnit codeUnit) {
|
||||
if (text == null || text.isEmpty()) {
|
||||
return new Pair[0];
|
||||
return Collections.emptyList();
|
||||
}
|
||||
Instruction instruction = (codeUnit instanceof Instruction) ? (Instruction) codeUnit : null;
|
||||
ArrayList<Pair> list = new ArrayList<>();
|
||||
ArrayList<Range> list = new ArrayList<>();
|
||||
int opIndex = 0;
|
||||
int textLength = text.length();
|
||||
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);
|
||||
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) {
|
||||
++opIndex; // Increment the opIndex since we find the separator before an operand.
|
||||
separator = DEFAULT_OPERAND_SEPARATOR; // default separator
|
||||
|
@ -209,17 +206,15 @@ public class ListingDiffHighlightProvider implements ListingHighlightProvider {
|
|||
separatorIndex =
|
||||
(separator != null && !separator.isEmpty()) ? text.indexOf(separator, start) : -1;
|
||||
if (separatorIndex == -1) {
|
||||
// Add the last operand's index Pair to the list.
|
||||
list.add(new Pair(start, textLength - 1));
|
||||
list.add(new Range(start, textLength - 1));
|
||||
start = textLength;
|
||||
continue;
|
||||
}
|
||||
// Add the current operand's index Pair to the list.
|
||||
list.add(new Pair(start, separatorIndex - 1));
|
||||
list.add(new Range(start, separatorIndex - 1));
|
||||
// Move start to the beginning of the next operand.
|
||||
start = separatorIndex + separator.length();
|
||||
}
|
||||
return list.toArray(new Pair[list.size()]);
|
||||
return list;
|
||||
}
|
||||
|
||||
private Highlight[] entireTextHighlight(String text, int cursorTextOffset, Color color) {
|
||||
|
@ -229,21 +224,12 @@ public class ListingDiffHighlightProvider implements ListingHighlightProvider {
|
|||
return new Highlight[] { highlight };
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 class Range {
|
||||
|
||||
private int start;
|
||||
private int end;
|
||||
|
||||
private Pair(int start, int end) {
|
||||
private Range(int start, int end) {
|
||||
this.start = start;
|
||||
this.end = end;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -19,7 +19,6 @@ import java.awt.*;
|
|||
import java.awt.event.*;
|
||||
import java.math.BigInteger;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import javax.swing.*;
|
||||
|
@ -68,7 +67,7 @@ public class ListingPanel extends JPanel implements FieldMouseListener, FieldLoc
|
|||
|
||||
private ListingModel listingModel;
|
||||
private FieldHeader headerPanel;
|
||||
private ButtonPressedListener[] buttonListeners = new ButtonPressedListener[0];
|
||||
private List<ButtonPressedListener> buttonListeners = new ArrayList<>();
|
||||
private List<ChangeListener> indexMapChangeListeners = new ArrayList<>();
|
||||
|
||||
private ListingHoverProvider listingHoverHandler;
|
||||
|
@ -448,9 +447,7 @@ public class ListingPanel extends JPanel implements FieldMouseListener, FieldLoc
|
|||
* @param listener the ButtonPressedListener to add.
|
||||
*/
|
||||
public void addButtonPressedListener(ButtonPressedListener listener) {
|
||||
List<ButtonPressedListener> list = new ArrayList<>(Arrays.asList(buttonListeners));
|
||||
list.add(listener);
|
||||
buttonListeners = list.toArray(new ButtonPressedListener[list.size()]);
|
||||
buttonListeners.add(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -459,9 +456,7 @@ public class ListingPanel extends JPanel implements FieldMouseListener, FieldLoc
|
|||
* @param listener the ButtonPressedListener to remove.
|
||||
*/
|
||||
public void removeButtonPressedListener(ButtonPressedListener listener) {
|
||||
List<ButtonPressedListener> list = new ArrayList<>(Arrays.asList(buttonListeners));
|
||||
list.remove(listener);
|
||||
buttonListeners = list.toArray(new ButtonPressedListener[list.size()]);
|
||||
buttonListeners.remove(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -569,7 +564,7 @@ public class ListingPanel extends JPanel implements FieldMouseListener, FieldLoc
|
|||
layoutModel.dispose();
|
||||
layoutModel = createLayoutModel(null);
|
||||
layoutModel.dispose();
|
||||
buttonListeners = null;
|
||||
buttonListeners.clear();
|
||||
|
||||
fieldPanel.dispose();
|
||||
}
|
||||
|
@ -1225,4 +1220,16 @@ public class ListingPanel extends JPanel implements FieldMouseListener, FieldLoc
|
|||
public void removeDisplayListener(AddressSetDisplayListener 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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -513,21 +513,11 @@ public class ProgramBigListingModel implements ListingModel, FormatModelListener
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void formatModelAdded(FieldFormatModel model) {
|
||||
notifyModelSizeChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void formatModelChanged(FieldFormatModel model) {
|
||||
notifyModelSizeChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void formatModelRemoved(FieldFormatModel model) {
|
||||
notifyModelSizeChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addListener(ListingModelListener listener) {
|
||||
listeners.add(listener);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
||||
public void formatModelChanged(FieldFormatModel model) {
|
||||
modelSizeChanged();
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -20,29 +20,22 @@ import java.awt.Component;
|
|||
import docking.ComponentProvider;
|
||||
import docking.DefaultActionContext;
|
||||
import ghidra.program.model.listing.Function;
|
||||
import ghidra.util.datastruct.Duo.Side;
|
||||
|
||||
public abstract class CodeComparisonActionContext extends DefaultActionContext
|
||||
implements CodeComparisonPanelActionContext {
|
||||
/**
|
||||
* Constructor with no source component and no context object
|
||||
* @param provider the ComponentProvider that generated this context.
|
||||
*/
|
||||
public CodeComparisonActionContext(ComponentProvider provider) {
|
||||
super(provider);
|
||||
}
|
||||
private CodeComparisonPanel comparisonPanel;
|
||||
|
||||
/**
|
||||
* Constructor with source component and context object
|
||||
*
|
||||
* @param provider the ComponentProvider that generated this context.
|
||||
* @param contextObject an optional contextObject that the ComponentProvider can provide; this
|
||||
* 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
|
||||
* Constructor
|
||||
* @param provider the ComponentProvider containing the code comparison panel
|
||||
* @param panel the CodeComparisonPanel that generated this context
|
||||
* @param component the focusable component for associated with the comparison panel
|
||||
*/
|
||||
public CodeComparisonActionContext(ComponentProvider provider, Object contextObject,
|
||||
Component sourceComponent) {
|
||||
super(provider, contextObject, sourceComponent);
|
||||
public CodeComparisonActionContext(ComponentProvider provider, CodeComparisonPanel panel,
|
||||
Component component) {
|
||||
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.
|
||||
* @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
|
||||
* side of the function diff window that is active.
|
||||
* @return the function to apply information to
|
||||
*/
|
||||
public abstract Function getTargetFunction();
|
||||
|
||||
public Function getTargetFunction() {
|
||||
Side activeSide = comparisonPanel.getActiveSide();
|
||||
return comparisonPanel.getFunction(activeSide);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,25 +15,31 @@
|
|||
*/
|
||||
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.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.border.Border;
|
||||
|
||||
import docking.ActionContext;
|
||||
import docking.ComponentProvider;
|
||||
import docking.action.DockingAction;
|
||||
import docking.widgets.fieldpanel.FieldPanel;
|
||||
import docking.widgets.fieldpanel.internal.FieldPanelCoordinator;
|
||||
import docking.action.*;
|
||||
import generic.theme.GThemeDefaults.Colors.Palette;
|
||||
import ghidra.app.plugin.core.functioncompare.FunctionComparisonPanel;
|
||||
import ghidra.framework.plugintool.PluginTool;
|
||||
import ghidra.program.model.address.AddressSet;
|
||||
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.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
|
||||
|
@ -42,42 +48,29 @@ import ghidra.util.classfinder.ExtensionPoint;
|
|||
* <p>
|
||||
* NOTE: ALL CodeComparisonPanel CLASSES MUST END IN
|
||||
* <code>CodeComparisonPanel</code> so they are discoverable by the {@link ClassSearcher}
|
||||
* @param <T> the type
|
||||
*/
|
||||
public abstract class CodeComparisonPanel<T extends FieldPanelCoordinator> extends JPanel
|
||||
implements ExtensionPoint, FocusListener {
|
||||
|
||||
// MINIMUM_PANEL_WIDTH is used to establish a minimum panel width for the left or right panel.
|
||||
// Without it a long title in either panel can cause the split pane's divider to become locked.
|
||||
protected static final int MINIMUM_PANEL_WIDTH = 50;
|
||||
|
||||
protected static final int LEFT = 0;
|
||||
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();
|
||||
public abstract class CodeComparisonPanel extends JPanel
|
||||
implements ExtensionPoint {
|
||||
public static final String HELP_TOPIC = "FunctionComparison";
|
||||
private static final Color ACTIVE_BORDER_COLOR = Palette.getColor("lightpink");
|
||||
private 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 =
|
||||
BorderFactory.createMatteBorder(3, 3, 3, 3, ACTIVE_BORDER_COLOR);
|
||||
|
||||
protected String owner;
|
||||
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 syncScrolling = false;
|
||||
|
||||
private T fieldPanelCoordinator;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
|
@ -87,91 +80,83 @@ public abstract class CodeComparisonPanel<T extends FieldPanelCoordinator> exten
|
|||
protected CodeComparisonPanel(String owner, PluginTool tool) {
|
||||
this.owner = owner;
|
||||
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
|
||||
*
|
||||
* @return the title
|
||||
* Clears out the current comparisonDatas
|
||||
*/
|
||||
public abstract String getTitle();
|
||||
|
||||
/**
|
||||
* 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);
|
||||
public void clearComparisons() {
|
||||
loadComparisons(ComparisonData.EMPTY, ComparisonData.EMPTY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the actions for this panel
|
||||
*
|
||||
* @return an array of docking actions
|
||||
*/
|
||||
public DockingAction[] getActions() {
|
||||
// No actions currently that appear for each CodeComparisonPanel.
|
||||
// Classes that extend this class will override this method to get all actions
|
||||
// specific to that CodeComparisonPanel.
|
||||
DockingAction[] actions = new DockingAction[] {};
|
||||
return actions;
|
||||
public List<DockingAction> getActions() {
|
||||
List<DockingAction> actionList = new ArrayList<>();
|
||||
actionList.add(toggleOrientationAction);
|
||||
return actionList;
|
||||
}
|
||||
|
||||
public boolean getShowTitles() {
|
||||
return showTitles;
|
||||
}
|
||||
|
||||
public void setShowTitles(boolean showTitles) {
|
||||
/**
|
||||
* Toggles whether or not to display data titles for each side.
|
||||
* @param showTitles true to show data titles
|
||||
*/
|
||||
public void setShowDataTitles(boolean 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
|
||||
* 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.
|
||||
* @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.
|
||||
* @return true if the left side of the code comparison has focus.
|
||||
* Called when a program is closed.
|
||||
* @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
|
||||
|
@ -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 rightTitlePrefix the prefix string to prepend to the right panel's title.
|
||||
*/
|
||||
public abstract void setTitlePrefixes(String leftTitlePrefix, String rightTitlePrefix);
|
||||
|
||||
/**
|
||||
* Gets the program being viewed in the left side of this panel.
|
||||
* @return the program or null
|
||||
*/
|
||||
public Program getLeftProgram() {
|
||||
return programs[LEFT];
|
||||
public void setTitlePrefixes(String leftTitlePrefix, String rightTitlePrefix) {
|
||||
titlePrefixes = new Duo<>(leftTitlePrefix, rightTitlePrefix);
|
||||
updateTitles();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the program being viewed in the right side of this panel.
|
||||
* @return the program or null
|
||||
* Returns the program being shown in the given side.
|
||||
* @param side the {@link Side} to get the program for
|
||||
* @return the program for the given side.
|
||||
*/
|
||||
public Program getRightProgram() {
|
||||
return programs[RIGHT];
|
||||
public Program getProgram(Side side) {
|
||||
return comparisonData.get(side).getProgram();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the function loaded in the left side of this panel.
|
||||
* @return the function or null
|
||||
* Returns the function being shown in the given side.
|
||||
* @param side the {@link Side} to get the function for
|
||||
* @return the function for the given side.
|
||||
*/
|
||||
public Function getLeftFunction() {
|
||||
return functions[LEFT];
|
||||
public Function getFunction(Side side) {
|
||||
return comparisonData.get(side).getFunction();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the function loaded in the right side of this panel.
|
||||
* @return the function or null
|
||||
* Returns the addresses being shown in the given side.
|
||||
* @param side the {@link Side} to get the program for
|
||||
* @return the address set for the given side
|
||||
*/
|
||||
public Function getRightFunction() {
|
||||
return functions[RIGHT];
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
public AddressSetView getAddresses(Side side) {
|
||||
return comparisonData.get(side).getAddressSet();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -286,75 +254,148 @@ public abstract class CodeComparisonPanel<T extends FieldPanelCoordinator> exten
|
|||
*/
|
||||
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.
|
||||
* @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) {
|
||||
if (isScrollingSynced() == syncScrolling) {
|
||||
return;
|
||||
}
|
||||
this.syncScrolling = syncScrolling;
|
||||
public abstract void setSynchronizedScrolling(boolean b);
|
||||
|
||||
// Refresh the left panel.
|
||||
FieldPanel leftPanel = getLeftFieldPanel();
|
||||
leftPanel.validate();
|
||||
leftPanel.invalidate();
|
||||
// Refresh the right panel.
|
||||
FieldPanel rightPanel = getRightFieldPanel();
|
||||
rightPanel.validate();
|
||||
rightPanel.invalidate();
|
||||
/**
|
||||
* Returns the Component for the given {@link Side}
|
||||
* @param side the Side to its component
|
||||
* @return the Component for the given {@link Side}
|
||||
*/
|
||||
public abstract JComponent getComparisonComponent(Side side);
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -15,8 +15,6 @@
|
|||
*/
|
||||
package ghidra.app.util.viewer.util;
|
||||
|
||||
import docking.widgets.fieldpanel.internal.FieldPanelCoordinator;
|
||||
|
||||
/**
|
||||
* Action context for a CodeComparisonPanel.
|
||||
*/
|
||||
|
@ -26,6 +24,6 @@ public interface CodeComparisonPanelActionContext {
|
|||
* Gets the CodeComparisonPanel associated with this context.
|
||||
* @return the code comparison panel.
|
||||
*/
|
||||
public abstract CodeComparisonPanel<? extends FieldPanelCoordinator> getCodeComparisonPanel();
|
||||
public abstract CodeComparisonPanel getCodeComparisonPanel();
|
||||
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -15,6 +15,10 @@
|
|||
*/
|
||||
package ghidra.program.util;
|
||||
|
||||
import static ghidra.util.datastruct.Duo.Side.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import ghidra.app.util.viewer.listingpanel.ListingDiffChangeListener;
|
||||
import ghidra.program.model.address.*;
|
||||
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.mem.MemoryAccessException;
|
||||
import ghidra.program.model.scalar.Scalar;
|
||||
import ghidra.program.util.ListingAddressCorrelation;
|
||||
import ghidra.util.Msg;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import ghidra.util.datastruct.Duo.Side;
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
init();
|
||||
AddressSetView addrSet1 = correlation.getAddressesInFirst();
|
||||
AddressSetView addrSet2 = correlation.getAddressesInSecond();
|
||||
Listing listing1 = correlation.getFirstProgram().getListing();
|
||||
Listing listing2 = correlation.getSecondProgram().getListing();
|
||||
AddressSetView addrSet1 = correlation.getAddresses(LEFT);
|
||||
AddressSetView addrSet2 = correlation.getAddresses(RIGHT);
|
||||
Listing listing1 = correlation.getProgram(LEFT).getListing();
|
||||
Listing listing2 = correlation.getProgram(RIGHT).getListing();
|
||||
CodeUnitIterator cuIter1 = listing1.getCodeUnits(addrSet1, true);
|
||||
CodeUnitIterator cuIter2 = listing2.getCodeUnits(addrSet2, true);
|
||||
for (CodeUnit cu1 : cuIter1) {
|
||||
Address min1 = cu1.getMinAddress();
|
||||
Address addr2 = correlation.getAddressInSecond(min1);
|
||||
Address addr2 = correlation.getAddress(RIGHT, min1);
|
||||
if (addr2 == null) {
|
||||
// Add codeunit1 to the unmatchedDiffs
|
||||
unmatchedCode1.addRange(cu1.getMinAddress(), cu1.getMaxAddress());
|
||||
|
@ -122,7 +124,7 @@ public class ListingDiff {
|
|||
}
|
||||
for (CodeUnit cu2 : cuIter2) {
|
||||
Address min2 = cu2.getMinAddress();
|
||||
Address addr1 = correlation.getAddressInFirst(min2);
|
||||
Address addr1 = correlation.getAddress(LEFT, min2);
|
||||
if (addr1 == null) {
|
||||
// Add codeunit2 to the unmatchedDiffs
|
||||
unmatchedCode2.addRange(cu2.getMinAddress(), cu2.getMaxAddress());
|
||||
|
@ -138,18 +140,18 @@ public class ListingDiff {
|
|||
}
|
||||
|
||||
private void recomputeCodeUnitDiffs() {
|
||||
AddressSetView addressSet1 = correlation.getAddressesInFirst();
|
||||
AddressSetView addressSet2 = correlation.getAddressesInSecond();
|
||||
AddressSetView addressSet1 = correlation.getAddresses(LEFT);
|
||||
AddressSetView addressSet2 = correlation.getAddresses(RIGHT);
|
||||
AddressSetView matchedAddresses1 = addressSet1.subtract(unmatchedCode1);
|
||||
AddressSetView matchedAddresses2 = addressSet2.subtract(unmatchedCode2);
|
||||
Listing listing1 = correlation.getFirstProgram().getListing();
|
||||
Listing listing2 = correlation.getSecondProgram().getListing();
|
||||
Listing listing1 = correlation.getProgram(LEFT).getListing();
|
||||
Listing listing2 = correlation.getProgram(RIGHT).getListing();
|
||||
CodeUnitIterator cuIter1 = listing1.getCodeUnits(matchedAddresses1, true);
|
||||
CodeUnitIterator cuIter2 = listing2.getCodeUnits(matchedAddresses2, true);
|
||||
codeUnitDiffs1.clear();
|
||||
for (CodeUnit cu1 : cuIter1) {
|
||||
Address min1 = cu1.getMinAddress();
|
||||
Address addr2 = correlation.getAddressInSecond(min1);
|
||||
Address addr2 = correlation.getAddress(RIGHT, min1);
|
||||
if (addr2 == null) {
|
||||
continue;
|
||||
}
|
||||
|
@ -160,7 +162,7 @@ public class ListingDiff {
|
|||
codeUnitDiffs2.clear();
|
||||
for (CodeUnit cu2 : cuIter2) {
|
||||
Address min2 = cu2.getMinAddress();
|
||||
Address addr1 = correlation.getAddressInFirst(min2);
|
||||
Address addr1 = correlation.getAddress(LEFT, min2);
|
||||
if (addr1 == null) {
|
||||
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.
|
||||
* @param side the LEFT or RIGHT side
|
||||
* @return the addresses of the unmatched code in the first listing.
|
||||
*/
|
||||
public AddressSetView getListing1UnmatchedCode() {
|
||||
return new AddressSet(unmatchedCode1);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
public AddressSetView getUnmatchedCode(Side side) {
|
||||
return side == LEFT ? unmatchedCode1 : unmatchedCode2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the addresses in the first listing where differences were found based on the current
|
||||
* difference settings.
|
||||
* @param side the side (LEFT or RIGHT) to get the listing diffs for
|
||||
* @return the addresses with differences in the first listing.
|
||||
*/
|
||||
public AddressSetView getListing1Diffs() {
|
||||
AddressSet diffs = new AddressSet(getListing1ByteDiffs());
|
||||
diffs.add(getListing1CodeUnitDiffs());
|
||||
return DiffUtility.getCodeUnitSet(diffs, correlation.getFirstProgram());
|
||||
}
|
||||
public AddressSetView getDiffs(Side side) {
|
||||
|
||||
/**
|
||||
* Gets the addresses in the second listing where differences were found based on the current
|
||||
* difference settings.
|
||||
* @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());
|
||||
AddressSet diffs = new AddressSet(getByteDiffs(side));
|
||||
diffs.add(getCodeUnitDiffs(side));
|
||||
return DiffUtility.getCodeUnitSet(diffs, correlation.getProgram(side));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the addresses in the first listing where code unit (mnemonic and/or operand) differences
|
||||
* 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.
|
||||
*/
|
||||
public AddressSetView getListing1CodeUnitDiffs() {
|
||||
return new AddressSet(codeUnitDiffs1);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
public AddressSetView getCodeUnitDiffs(Side side) {
|
||||
return new AddressSet(side == LEFT ? codeUnitDiffs1 : codeUnitDiffs2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the addresses in the first listing where byte differences were found based on the
|
||||
* 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.
|
||||
*/
|
||||
public AddressSetView getListing1ByteDiffs() {
|
||||
public AddressSetView getByteDiffs(Side side) {
|
||||
if (ignoreByteDiffs) {
|
||||
return new AddressSet();
|
||||
}
|
||||
return new AddressSet(byteDiffs1);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
return new AddressSet(side == LEFT ? byteDiffs1 : byteDiffs2);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -480,9 +446,9 @@ public class ListingDiff {
|
|||
return null;
|
||||
}
|
||||
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.
|
||||
* @param codeUnit the code unit whose match this determines.
|
||||
* @param isListing1 true indicates the code unit is from the first listing. false indicates
|
||||
* it is from the second listing.
|
||||
* @param side the side the code unit came from
|
||||
* @return the matching code unit or null
|
||||
*/
|
||||
public CodeUnit getMatchingCodeUnit(CodeUnit codeUnit, boolean isListing1) {
|
||||
public CodeUnit getMatchingCodeUnit(CodeUnit codeUnit, Side side) {
|
||||
if (correlation == null) {
|
||||
return null;
|
||||
}
|
||||
Side otherSide = side.otherSide();
|
||||
|
||||
Address minAddress = codeUnit.getMinAddress();
|
||||
Program sourceProgram = correlation.getFirstProgram();
|
||||
Program destinationProgram = correlation.getSecondProgram();
|
||||
if (isListing1) {
|
||||
Address destination = correlation.getAddressInSecond(minAddress);
|
||||
if (destination != null) {
|
||||
return destinationProgram.getListing().getCodeUnitAt(destination);
|
||||
}
|
||||
}
|
||||
else {
|
||||
Address source = correlation.getAddressInFirst(minAddress);
|
||||
if (source != null) {
|
||||
return sourceProgram.getListing().getCodeUnitAt(source);
|
||||
}
|
||||
Program otherSideProgram = correlation.getProgram(otherSide);
|
||||
|
||||
Address otherAddress = correlation.getAddress(otherSide, minAddress);
|
||||
if (otherAddress != null) {
|
||||
return otherSideProgram.getListing().getCodeUnitAt(otherAddress);
|
||||
}
|
||||
|
||||
// code unit not from our programs.
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
*/
|
||||
package ghidra.app.plugin.core.functioncompare;
|
||||
|
||||
import static ghidra.util.datastruct.Duo.Side.*;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.awt.Window;
|
||||
|
@ -149,7 +150,7 @@ public class CompareFunctionsSlowTest extends AbstractGhidraHeadedIntegrationTes
|
|||
assertTrue(prevAction.isEnabledForContext(context));
|
||||
|
||||
JPanel rightPanel =
|
||||
provider.getComponent().getDualListingPanel().getRightPanel().getFieldPanel();
|
||||
provider.getComponent().getDualListingPanel().getListingPanel(RIGHT).getFieldPanel();
|
||||
clickMouse(rightPanel, 1, 30, 30, 1, 0);
|
||||
waitForSwing();
|
||||
provider.getComponent().updateActionEnablement();
|
||||
|
@ -243,7 +244,7 @@ public class CompareFunctionsSlowTest extends AbstractGhidraHeadedIntegrationTes
|
|||
* Builds a program with 2 functions
|
||||
*/
|
||||
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.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
|
||||
*/
|
||||
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.setProperty(Program.DATE_CREATED, new Date(100000000)); // arbitrary, but consistent
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
||||
}
|
||||
}
|
|
@ -26,6 +26,7 @@ import generic.theme.GColor;
|
|||
import ghidra.app.decompiler.ClangSyntaxToken;
|
||||
import ghidra.app.decompiler.ClangToken;
|
||||
import ghidra.app.decompiler.component.*;
|
||||
import ghidra.codecompare.decompile.DecompilerCodeComparisonOptions;
|
||||
import ghidra.codecompare.graphanalysis.TokenBin;
|
||||
import ghidra.util.ColorUtils;
|
||||
import ghidra.util.SystemUtilities;
|
||||
|
|
|
@ -13,7 +13,9 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* 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.List;
|
||||
|
@ -22,16 +24,17 @@ import docking.ActionContext;
|
|||
import docking.action.DockingAction;
|
||||
import ghidra.app.decompiler.ClangToken;
|
||||
import ghidra.app.decompiler.DecompilerLocation;
|
||||
import ghidra.app.decompiler.component.*;
|
||||
import ghidra.app.decompiler.component.DecompilerPanel;
|
||||
import ghidra.codecompare.graphanalysis.TokenBin;
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.util.datastruct.Duo.Side;
|
||||
|
||||
/**
|
||||
* This is a base class for actions in a {@link DecompilerDiffCodeComparisonPanel}
|
||||
*/
|
||||
public abstract class AbstractMatchedTokensAction extends DockingAction {
|
||||
|
||||
protected DecompilerDiffCodeComparisonPanel diffPanel;
|
||||
protected DecompilerCodeComparisonPanel diffPanel;
|
||||
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
|
||||
*/
|
||||
public AbstractMatchedTokensAction(String actionName, String owner,
|
||||
DecompilerDiffCodeComparisonPanel diffPanel, boolean disableOnReadOnly) {
|
||||
DecompilerCodeComparisonPanel diffPanel, boolean disableOnReadOnly) {
|
||||
super(actionName, owner);
|
||||
this.diffPanel = diffPanel;
|
||||
this.disableOnReadOnly = disableOnReadOnly;
|
||||
|
@ -63,26 +66,20 @@ public abstract class AbstractMatchedTokensAction extends DockingAction {
|
|||
if (!(context instanceof DualDecompilerActionContext compareContext)) {
|
||||
return false;
|
||||
}
|
||||
if (!(compareContext
|
||||
.getCodeComparisonPanel() instanceof DecompilerCodeComparisonPanel decompPanel)) {
|
||||
return false;
|
||||
}
|
||||
DecompilerCodeComparisonPanel decompPanel = compareContext.getCodeComparisonPanel();
|
||||
|
||||
if (disableOnReadOnly) {
|
||||
//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) {
|
||||
return false; //panel initializing; don't enable action
|
||||
}
|
||||
if (!decompPanel.leftPanelHasFocus()) {
|
||||
program = decompPanel.getRightProgram();
|
||||
}
|
||||
if (!program.canSave()) {
|
||||
return false; //program is read-only, don't enable action
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
TokenPair currentPair = getCurrentTokenPair(decompPanel);
|
||||
return enabledForTokens(currentPair);
|
||||
|
||||
|
@ -96,9 +93,9 @@ public abstract class AbstractMatchedTokensAction extends DockingAction {
|
|||
* @return matching tokens (or null if no match)
|
||||
*/
|
||||
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)) {
|
||||
return null;
|
||||
|
@ -126,7 +123,8 @@ public abstract class AbstractMatchedTokensAction extends DockingAction {
|
|||
while (tokenIter.hasNext()) {
|
||||
ClangToken currentMatch = tokenIter.next();
|
||||
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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -13,7 +13,9 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.codecompare;
|
||||
package ghidra.codecompare.decompile;
|
||||
|
||||
import static ghidra.util.datastruct.Duo.Side.*;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.util.ArrayList;
|
||||
|
@ -25,7 +27,6 @@ import org.apache.commons.collections4.bidimap.DualHashBidiMap;
|
|||
import docking.widgets.fieldpanel.support.ViewerPosition;
|
||||
import ghidra.app.decompiler.*;
|
||||
import ghidra.app.decompiler.component.DecompilerPanel;
|
||||
import ghidra.app.decompiler.component.DualDecompilerFieldPanelCoordinator;
|
||||
import ghidra.codecompare.graphanalysis.TokenBin;
|
||||
import ghidra.program.model.pcode.HighFunction;
|
||||
import ghidra.program.util.ProgramLocation;
|
||||
|
@ -52,10 +53,10 @@ public class CodeDiffFieldPanelCoordinator extends DualDecompilerFieldPanelCoord
|
|||
* Constructor
|
||||
* @param dualDecompilerPanel decomp comparison panel
|
||||
*/
|
||||
public CodeDiffFieldPanelCoordinator(DecompilerDiffCodeComparisonPanel dualDecompilerPanel) {
|
||||
public CodeDiffFieldPanelCoordinator(DecompilerCodeComparisonPanel dualDecompilerPanel) {
|
||||
super(dualDecompilerPanel);
|
||||
this.leftDecompilerPanel = dualDecompilerPanel.getLeftDecompilerPanel();
|
||||
this.rightDecompilerPanel = dualDecompilerPanel.getRightDecompilerPanel();
|
||||
this.leftDecompilerPanel = dualDecompilerPanel.getDecompilerPanel(LEFT);
|
||||
this.rightDecompilerPanel = dualDecompilerPanel.getDecompilerPanel(RIGHT);
|
||||
leftToRightLineNumberPairing = new DualHashBidiMap<>();
|
||||
}
|
||||
|
|
@ -13,13 +13,13 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.codecompare;
|
||||
package ghidra.codecompare.decompile;
|
||||
|
||||
import static ghidra.util.datastruct.Duo.Side.*;
|
||||
|
||||
import docking.ActionContext;
|
||||
import docking.action.MenuData;
|
||||
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.services.FunctionComparisonService;
|
||||
import ghidra.framework.plugintool.PluginTool;
|
||||
|
@ -45,7 +45,7 @@ public class CompareFuncsFromMatchedTokensAction extends AbstractMatchedTokensAc
|
|||
* @param diffPanel diff Panel
|
||||
* @param tool tool
|
||||
*/
|
||||
public CompareFuncsFromMatchedTokensAction(DecompilerDiffCodeComparisonPanel diffPanel,
|
||||
public CompareFuncsFromMatchedTokensAction(DecompilerCodeComparisonPanel diffPanel,
|
||||
PluginTool tool) {
|
||||
super(ACTION_NAME, tool.getName(), diffPanel, false);
|
||||
this.tool = tool;
|
||||
|
@ -84,12 +84,8 @@ public class CompareFuncsFromMatchedTokensAction extends AbstractMatchedTokensAc
|
|||
return;
|
||||
}
|
||||
|
||||
if (!(compareContext
|
||||
.getCodeComparisonPanel() instanceof DecompilerCodeComparisonPanel decompPanel)) {
|
||||
return;
|
||||
}
|
||||
DecompilerCodeComparisonPanel decompPanel = compareContext.getCodeComparisonPanel();
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
TokenPair currentPair = getCurrentTokenPair(decompPanel);
|
||||
if (currentPair == null || currentPair.leftToken() == null ||
|
||||
currentPair.rightToken() == null) {
|
||||
|
@ -99,8 +95,8 @@ public class CompareFuncsFromMatchedTokensAction extends AbstractMatchedTokensAc
|
|||
ClangFuncNameToken leftFuncToken = (ClangFuncNameToken) currentPair.leftToken();
|
||||
ClangFuncNameToken rightFuncToken = (ClangFuncNameToken) currentPair.rightToken();
|
||||
|
||||
Function leftFunction = getFuncFromToken(leftFuncToken, decompPanel.getLeftProgram());
|
||||
Function rightFunction = getFuncFromToken(rightFuncToken, decompPanel.getRightProgram());
|
||||
Function leftFunction = getFuncFromToken(leftFuncToken, decompPanel.getProgram(LEFT));
|
||||
Function rightFunction = getFuncFromToken(rightFuncToken, decompPanel.getProgram(RIGHT));
|
||||
if (leftFunction == null || rightFunction == null) {
|
||||
return;
|
||||
}
|
|
@ -13,7 +13,7 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.codecompare;
|
||||
package ghidra.codecompare.decompile;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
|
@ -13,18 +13,23 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.codecompare;
|
||||
package ghidra.codecompare.decompile;
|
||||
|
||||
import java.awt.Color;
|
||||
|
||||
import generic.theme.GColor;
|
||||
import ghidra.framework.options.OptionsChangeListener;
|
||||
import ghidra.framework.options.ToolOptions;
|
||||
import ghidra.framework.plugintool.PluginTool;
|
||||
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.
|
||||
*/
|
||||
public class DecompilerCodeComparisonOptions {
|
||||
public class DecompilerCodeComparisonOptions implements OptionsChangeListener {
|
||||
|
||||
private static final String MATCHING_TOKEN_HIGHLIGHT_KEY = "Focused Token Match 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 ineligibleTokenHighlight;
|
||||
private Color diffHighlight;
|
||||
private Callback optionsChangedCallback;
|
||||
|
||||
public static final String OPTIONS_CATEGORY_NAME = "Decompiler Code Comparison";
|
||||
public static final String HELP_TOPIC = "FunctionComparison";
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public DecompilerCodeComparisonOptions() {
|
||||
|
||||
public DecompilerCodeComparisonOptions(PluginTool tool, Callback optionsChangedCallback) {
|
||||
this.optionsChangedCallback = Dummy.ifNull(optionsChangedCallback);
|
||||
ToolOptions options =
|
||||
tool.getOptions(DecompilerCodeComparisonOptions.OPTIONS_CATEGORY_NAME);
|
||||
options.addOptionsChangeListener(this);
|
||||
registerOptions(options);
|
||||
loadOptions(options);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -136,4 +144,11 @@ public class DecompilerCodeComparisonOptions {
|
|||
return diffHighlight;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void optionsChanged(ToolOptions options, String optionName, Object oldValue,
|
||||
Object newValue) throws OptionsVetoException {
|
||||
loadOptions(options);
|
||||
optionsChangedCallback.call();
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -13,7 +13,9 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* 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;
|
||||
|
||||
|
@ -23,17 +25,17 @@ import docking.ActionContext;
|
|||
import docking.DockingUtils;
|
||||
import docking.action.*;
|
||||
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.util.HelpTopics;
|
||||
import ghidra.app.util.viewer.util.CodeComparisonPanel;
|
||||
import ghidra.framework.plugintool.PluginTool;
|
||||
import ghidra.util.HelpLocation;
|
||||
import ghidra.util.datastruct.Duo;
|
||||
import ghidra.util.datastruct.Duo.Side;
|
||||
|
||||
public class DecompilerDiffViewFindAction extends DockingAction {
|
||||
|
||||
private FindDialog leftFindDialog;
|
||||
private FindDialog rightFindDialog;
|
||||
private Duo<FindDialog> findDialogs;
|
||||
private PluginTool tool;
|
||||
|
||||
public DecompilerDiffViewFindAction(String owner, PluginTool tool) {
|
||||
|
@ -53,33 +55,21 @@ public class DecompilerDiffViewFindAction extends DockingAction {
|
|||
|
||||
@Override
|
||||
public boolean isEnabledForContext(ActionContext context) {
|
||||
if (!(context instanceof DualDecompilerActionContext dualContext)) {
|
||||
return false;
|
||||
}
|
||||
CodeComparisonPanel<? extends FieldPanelCoordinator> compPanel =
|
||||
dualContext.getCodeComparisonPanel();
|
||||
if (!(compPanel instanceof DecompilerCodeComparisonPanel decompilerCompPanel)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
return (context instanceof DualDecompilerActionContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionContext context) {
|
||||
DualDecompilerActionContext dualContext = (DualDecompilerActionContext) context;
|
||||
@SuppressWarnings("unchecked")
|
||||
DecompilerCodeComparisonPanel<? extends FieldPanelCoordinator> decompilerCompPanel =
|
||||
(DecompilerCodeComparisonPanel<? extends FieldPanelCoordinator>) dualContext
|
||||
.getCodeComparisonPanel();
|
||||
DecompilerCodeComparisonPanel decompilerCompPanel =
|
||||
dualContext.getCodeComparisonPanel();
|
||||
|
||||
DecompilerPanel focusedPanel = decompilerCompPanel.getLeftDecompilerPanel();
|
||||
FindDialog dialog = leftFindDialog;
|
||||
if (!decompilerCompPanel.leftPanelHasFocus()) {
|
||||
focusedPanel = decompilerCompPanel.getRightDecompilerPanel();
|
||||
dialog = rightFindDialog;
|
||||
}
|
||||
Side focusedSide = decompilerCompPanel.getActiveSide();
|
||||
DecompilerPanel focusedPanel = decompilerCompPanel.getDecompilerPanel(focusedSide);
|
||||
FindDialog dialog = findDialogs.get(focusedSide);
|
||||
if (dialog == null) {
|
||||
dialog = createFindDialog(focusedPanel, decompilerCompPanel.leftPanelHasFocus());
|
||||
dialog = createFindDialog(focusedPanel, focusedSide);
|
||||
findDialogs = findDialogs.with(focusedSide, dialog);
|
||||
}
|
||||
|
||||
String text = focusedPanel.getSelectedText();
|
||||
|
@ -89,8 +79,8 @@ public class DecompilerDiffViewFindAction extends DockingAction {
|
|||
tool.showDialog(dialog);
|
||||
}
|
||||
|
||||
private FindDialog createFindDialog(DecompilerPanel decompilerPanel, boolean isLeftFocused) {
|
||||
String title = (isLeftFocused ? "Left" : "Right");
|
||||
private FindDialog createFindDialog(DecompilerPanel decompilerPanel, Side side) {
|
||||
String title = (side == LEFT ? "Left" : "Right");
|
||||
title += " Decompiler Find Text";
|
||||
|
||||
FindDialog dialog = new FindDialog(title, new DecompilerSearcher(decompilerPanel)) {
|
||||
|
@ -102,13 +92,6 @@ public class DecompilerDiffViewFindAction extends DockingAction {
|
|||
};
|
||||
dialog.setHelpLocation(new HelpLocation(HelpTopics.DECOMPILER, "ActionFind"));
|
||||
|
||||
if (isLeftFocused) {
|
||||
leftFindDialog = dialog;
|
||||
}
|
||||
else {
|
||||
rightFindDialog = dialog;
|
||||
}
|
||||
|
||||
return dialog;
|
||||
}
|
||||
|
|
@ -13,12 +13,13 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.codecompare;
|
||||
package ghidra.codecompare.decompile;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import ghidra.app.decompiler.ClangToken;
|
||||
import ghidra.codecompare.DiffClangHighlightController;
|
||||
import ghidra.codecompare.graphanalysis.TokenBin;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.task.Task;
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -13,7 +13,9 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* 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.internal.LineLockedFieldPanelCoordinator;
|
||||
|
@ -22,9 +24,9 @@ import ghidra.program.util.ProgramLocation;
|
|||
abstract public class DualDecompilerFieldPanelCoordinator extends LineLockedFieldPanelCoordinator {
|
||||
|
||||
public DualDecompilerFieldPanelCoordinator(
|
||||
DecompilerCodeComparisonPanel<? extends DualDecompilerFieldPanelCoordinator> dualDecompilerPanel) {
|
||||
super(new FieldPanel[] { dualDecompilerPanel.getLeftDecompilerPanel().getFieldPanel(),
|
||||
dualDecompilerPanel.getRightDecompilerPanel().getFieldPanel() });
|
||||
DecompilerCodeComparisonPanel dualDecompilerPanel) {
|
||||
super(new FieldPanel[] { dualDecompilerPanel.getDecompilerPanel(LEFT).getFieldPanel(),
|
||||
dualDecompilerPanel.getDecompilerPanel(RIGHT).getFieldPanel() });
|
||||
}
|
||||
|
||||
abstract public void leftLocationChanged(ProgramLocation leftLocation);
|
|
@ -13,7 +13,7 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.codecompare;
|
||||
package ghidra.codecompare.decompile;
|
||||
|
||||
import ghidra.app.decompiler.ClangToken;
|
||||
|
|
@ -73,6 +73,7 @@ public class DecompileResults {
|
|||
hfunc = null;
|
||||
hparamid = null;
|
||||
docroot = null;
|
||||
this.processState = processState;
|
||||
//dumpResults(raw);
|
||||
decodeStream(decoder);
|
||||
}
|
||||
|
@ -144,6 +145,14 @@ public class DecompileResults {
|
|||
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
|
||||
* decompilation producing these results. Generally,
|
||||
|
@ -180,7 +189,7 @@ public class DecompileResults {
|
|||
/**
|
||||
* Get the marked up C code associated with these
|
||||
* 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
|
||||
*/
|
||||
public ClangTokenGroup getCCodeMarkup() {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -15,6 +15,9 @@
|
|||
*/
|
||||
package ghidra.app.decompiler.component;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import docking.widgets.fieldpanel.support.ViewerPosition;
|
||||
import ghidra.app.decompiler.ClangTokenGroup;
|
||||
import ghidra.app.decompiler.DecompileResults;
|
||||
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.util.ProgramLocation;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import docking.widgets.fieldpanel.support.ViewerPosition;
|
||||
|
||||
public class DecompileData {
|
||||
|
||||
private final Program program;
|
||||
|
@ -57,6 +56,10 @@ public class DecompileData {
|
|||
return decompileResults.getCCodeMarkup() != null;
|
||||
}
|
||||
|
||||
public boolean isValid() {
|
||||
return decompileResults != null && decompileResults.isValid();
|
||||
}
|
||||
|
||||
public DecompileResults getDecompileResults() {
|
||||
return decompileResults;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
}
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1214,6 +1214,18 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field
|
|||
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() {
|
||||
removeAll();
|
||||
add(buildLeftComponent(), BorderLayout.WEST);
|
||||
|
|
|
@ -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);
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -15,11 +15,13 @@
|
|||
*/
|
||||
package ghidra.feature.vt.api.correlator.address;
|
||||
|
||||
import static ghidra.util.datastruct.Duo.Side.*;
|
||||
|
||||
import ghidra.program.model.address.*;
|
||||
import ghidra.program.model.correlate.HashedFunctionAddressCorrelation;
|
||||
import ghidra.program.model.listing.Function;
|
||||
import ghidra.program.model.mem.MemoryAccessException;
|
||||
import ghidra.program.util.AddressCorrelation;
|
||||
import ghidra.program.util.*;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
@ -35,14 +37,15 @@ public class VTHashedFunctionAddressCorrelation implements AddressCorrelation {
|
|||
|
||||
private final Function sourceFunction;
|
||||
private final Function destinationFunction;
|
||||
private HashedFunctionAddressCorrelation addressCorrelation;
|
||||
private ListingAddressCorrelation addressCorrelation;
|
||||
|
||||
/**
|
||||
* Constructs an address correlation between two functions.
|
||||
* @param sourceFunction the source function
|
||||
* @param destinationFunction the destination function
|
||||
*/
|
||||
public VTHashedFunctionAddressCorrelation(Function sourceFunction, Function destinationFunction) {
|
||||
public VTHashedFunctionAddressCorrelation(Function sourceFunction,
|
||||
Function destinationFunction) {
|
||||
this.sourceFunction = sourceFunction;
|
||||
this.destinationFunction = destinationFunction;
|
||||
addressCorrelation = null;
|
||||
|
@ -58,7 +61,7 @@ public class VTHashedFunctionAddressCorrelation implements AddressCorrelation {
|
|||
throws CancelledException {
|
||||
try {
|
||||
initializeCorrelation(monitor);
|
||||
Address destinationAddress = addressCorrelation.getAddressInSecond(sourceAddress);
|
||||
Address destinationAddress = addressCorrelation.getAddress(RIGHT, sourceAddress);
|
||||
if (destinationAddress == null) {
|
||||
return null; // No matching destination.
|
||||
}
|
||||
|
@ -83,8 +86,13 @@ public class VTHashedFunctionAddressCorrelation implements AddressCorrelation {
|
|||
if (addressCorrelation != null) {
|
||||
return;
|
||||
}
|
||||
if (sourceFunction != null && destinationFunction != null) {
|
||||
addressCorrelation =
|
||||
new HashedFunctionAddressCorrelation(sourceFunction, destinationFunction,
|
||||
monitor);
|
||||
}
|
||||
else {
|
||||
addressCorrelation = new DummyListingAddressCorrelation();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,6 +15,8 @@
|
|||
*/
|
||||
package ghidra.feature.vt.gui.duallisting;
|
||||
|
||||
import static ghidra.util.datastruct.Duo.Side.*;
|
||||
|
||||
import java.awt.Point;
|
||||
import java.awt.datatransfer.DataFlavor;
|
||||
import java.awt.datatransfer.Transferable;
|
||||
|
@ -32,12 +34,11 @@ import ghidra.program.model.address.Address;
|
|||
import ghidra.program.util.ProgramLocation;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.SystemUtilities;
|
||||
import ghidra.util.datastruct.Duo;
|
||||
|
||||
public class VTDualListingDragNDropHandler implements Draggable, Droppable {
|
||||
|
||||
private final int SOURCE = 0; // left side
|
||||
private final int DESTINATION = 1; // right side
|
||||
private ListingPanel[] listingPanels = new ListingPanel[2];
|
||||
private Duo<ListingPanel> listingPanels;
|
||||
|
||||
private VTController controller;
|
||||
ListingCodeComparisonPanel dualListingPanel;
|
||||
|
@ -55,8 +56,9 @@ public class VTDualListingDragNDropHandler implements Draggable, Droppable {
|
|||
ListingCodeComparisonPanel dualListingPanel) {
|
||||
this.controller = controller;
|
||||
this.dualListingPanel = dualListingPanel;
|
||||
listingPanels[SOURCE] = dualListingPanel.getLeftPanel();
|
||||
listingPanels[DESTINATION] = dualListingPanel.getRightPanel();
|
||||
ListingPanel leftPanel = dualListingPanel.getListingPanel(LEFT);
|
||||
ListingPanel rightPanel = dualListingPanel.getListingPanel(RIGHT);
|
||||
listingPanels = new Duo<>(leftPanel, rightPanel);
|
||||
setUpDragDrop();
|
||||
}
|
||||
|
||||
|
@ -68,7 +70,7 @@ public class VTDualListingDragNDropHandler implements Draggable, Droppable {
|
|||
dragSource = DragSource.getDefaultDragSource();
|
||||
dragGestureAdapter = new DragGestureAdapter(this);
|
||||
dragSourceAdapter = new DragSrcAdapter(this);
|
||||
dragSource.createDefaultDragGestureRecognizer(listingPanels[SOURCE].getFieldPanel(),
|
||||
dragSource.createDefaultDragGestureRecognizer(listingPanels.get(LEFT).getFieldPanel(),
|
||||
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.
|
||||
dropTargetAdapter =
|
||||
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);
|
||||
dropTarget.setActive(true);
|
||||
}
|
||||
|
@ -100,13 +102,14 @@ public class VTDualListingDragNDropHandler implements Draggable, Droppable {
|
|||
|
||||
@Override
|
||||
public boolean isStartDragOk(DragGestureEvent e) {
|
||||
if (!listingPanels[SOURCE].isStartDragOk()) {
|
||||
if (!listingPanels.get(LEFT).isStartDragOk()) {
|
||||
return false;
|
||||
}
|
||||
Point p = e.getDragOrigin();
|
||||
ProgramLocation programLocation = listingPanels[SOURCE].getProgramLocation(p);
|
||||
VTMarkupItem markupItem = controller.getCurrentMarkupForLocation(programLocation,
|
||||
dualListingPanel.getLeftProgram());
|
||||
ProgramLocation programLocation = listingPanels.get(LEFT).getProgramLocation(p);
|
||||
VTMarkupItem markupItem =
|
||||
controller.getCurrentMarkupForLocation(programLocation,
|
||||
dualListingPanel.getProgram(LEFT));
|
||||
if (markupItem == null) {
|
||||
return false;
|
||||
}
|
||||
|
@ -122,13 +125,13 @@ public class VTDualListingDragNDropHandler implements Draggable, Droppable {
|
|||
|
||||
@Override
|
||||
public Transferable getTransferable(Point p) {
|
||||
if (!listingPanels[SOURCE].contains(p)) {
|
||||
if (!listingPanels.get(LEFT).contains(p)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
ProgramLocation programLocation = listingPanels[SOURCE].getProgramLocation(p);
|
||||
ProgramLocation programLocation = listingPanels.get(LEFT).getProgramLocation(p);
|
||||
VTMarkupItem markupItem = controller.getCurrentMarkupForLocation(programLocation,
|
||||
dualListingPanel.getLeftProgram());
|
||||
dualListingPanel.getProgram(LEFT));
|
||||
if (markupItem == null) {
|
||||
return null;
|
||||
}
|
||||
|
@ -145,10 +148,10 @@ public class VTDualListingDragNDropHandler implements Draggable, Droppable {
|
|||
VTMarkupItem markupItem = (VTMarkupItem) obj;
|
||||
VTMarkupType markupType = markupItem.getMarkupType();
|
||||
Point p = event.getLocation();
|
||||
ProgramLocation loc = listingPanels[DESTINATION].getProgramLocation(p);
|
||||
ProgramLocation loc = listingPanels.get(RIGHT).getProgramLocation(p);
|
||||
|
||||
Address newDestinationAddress =
|
||||
markupType.getAddress(loc, dualListingPanel.getRightProgram());
|
||||
markupType.getAddress(loc, dualListingPanel.getProgram(RIGHT));
|
||||
if (newDestinationAddress == null) {
|
||||
Msg.showInfo(getClass(), dualListingPanel, "Invalid Drop 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.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.
|
||||
ApplyMarkupAtDestinationAddressTask task = new ApplyMarkupAtDestinationAddressTask(
|
||||
controller.getSession(), arrayList, newDestinationAddress, controller.getOptions());
|
||||
|
|
|
@ -16,7 +16,6 @@
|
|||
package ghidra.feature.vt.gui.duallisting;
|
||||
|
||||
import docking.ComponentProvider;
|
||||
import docking.widgets.fieldpanel.internal.FieldPanelCoordinator;
|
||||
import ghidra.app.context.ListingActionContext;
|
||||
import ghidra.app.nav.Navigatable;
|
||||
import ghidra.app.util.viewer.util.CodeComparisonPanel;
|
||||
|
@ -28,7 +27,7 @@ import ghidra.app.util.viewer.util.CodeComparisonPanelActionContext;
|
|||
public class VTListingContext extends ListingActionContext
|
||||
implements CodeComparisonPanelActionContext {
|
||||
|
||||
private CodeComparisonPanel<? extends FieldPanelCoordinator> codeComparisonPanel = null;
|
||||
private CodeComparisonPanel codeComparisonPanel = null;
|
||||
|
||||
/**
|
||||
* Creates an action context for a VT listing.
|
||||
|
@ -44,12 +43,12 @@ public class VTListingContext extends ListingActionContext
|
|||
* @param codeComparisonPanel the code comparison panel.
|
||||
*/
|
||||
public void setCodeComparisonPanel(
|
||||
CodeComparisonPanel<? extends FieldPanelCoordinator> codeComparisonPanel) {
|
||||
CodeComparisonPanel codeComparisonPanel) {
|
||||
this.codeComparisonPanel = codeComparisonPanel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CodeComparisonPanel<? extends FieldPanelCoordinator> getCodeComparisonPanel() {
|
||||
public CodeComparisonPanel getCodeComparisonPanel() {
|
||||
return codeComparisonPanel;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -92,14 +92,8 @@ public class VTListingNavigator implements Navigatable {
|
|||
|
||||
@Override
|
||||
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 went;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isConnected() {
|
||||
|
@ -147,7 +141,8 @@ public class VTListingNavigator implements Navigatable {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void removeHighlightProvider(ListingHighlightProvider highlightProvider, Program program) {
|
||||
public void removeHighlightProvider(ListingHighlightProvider highlightProvider,
|
||||
Program program) {
|
||||
// currently unsupported
|
||||
}
|
||||
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
package ghidra.feature.vt.gui.provider.functionassociation;
|
||||
|
||||
import static ghidra.feature.vt.gui.provider.functionassociation.FilterSettings.*;
|
||||
import static ghidra.util.datastruct.Duo.Side.*;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.event.*;
|
||||
|
@ -220,7 +221,7 @@ public class VTFunctionAssociationProvider extends ComponentProviderAdapter
|
|||
ListingCodeComparisonPanel dualListingPanel =
|
||||
functionComparisonPanel.getDualListingPanel();
|
||||
if (dualListingPanel != null) {
|
||||
ListingPanel leftPanel = dualListingPanel.getLeftPanel();
|
||||
ListingPanel leftPanel = dualListingPanel.getListingPanel(LEFT);
|
||||
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?
|
||||
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
|
||||
// on one of the listings in the ListingCodeComparisonPanel
|
||||
|
@ -367,10 +368,9 @@ public class VTFunctionAssociationProvider extends ComponentProviderAdapter
|
|||
statusPanel.add(statusLabel, BorderLayout.CENTER);
|
||||
dualTablePanel.add(statusPanel, BorderLayout.SOUTH);
|
||||
|
||||
functionComparisonPanel =
|
||||
new FunctionComparisonPanel(this, tool, (Function) null, (Function) null);
|
||||
functionComparisonPanel = new FunctionComparisonPanel(this, tool);
|
||||
addSpecificCodeComparisonActions();
|
||||
functionComparisonPanel.setCurrentTabbedComponent(ListingCodeComparisonPanel.TITLE);
|
||||
functionComparisonPanel.setCurrentTabbedComponent(ListingCodeComparisonPanel.NAME);
|
||||
functionComparisonPanel.setTitlePrefixes("Source:", "Destination:");
|
||||
|
||||
comparisonSplitPane =
|
||||
|
|
|
@ -18,7 +18,6 @@ package ghidra.feature.vt.gui.provider.markuptable;
|
|||
import java.util.List;
|
||||
|
||||
import docking.DefaultActionContext;
|
||||
import docking.widgets.fieldpanel.internal.FieldPanelCoordinator;
|
||||
import ghidra.app.util.viewer.util.CodeComparisonPanel;
|
||||
import ghidra.app.util.viewer.util.CodeComparisonPanelActionContext;
|
||||
import ghidra.feature.vt.api.main.VTMarkupItem;
|
||||
|
@ -30,7 +29,7 @@ public class VTMarkupItemContext extends DefaultActionContext
|
|||
implements CodeComparisonPanelActionContext {
|
||||
|
||||
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.
|
||||
|
@ -55,12 +54,12 @@ public class VTMarkupItemContext extends DefaultActionContext
|
|||
* @param codeComparisonPanel the code comparison panel.
|
||||
*/
|
||||
public void setCodeComparisonPanel(
|
||||
CodeComparisonPanel<? extends FieldPanelCoordinator> codeComparisonPanel) {
|
||||
CodeComparisonPanel codeComparisonPanel) {
|
||||
this.codeComparisonPanel = codeComparisonPanel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CodeComparisonPanel<? extends FieldPanelCoordinator> getCodeComparisonPanel() {
|
||||
public CodeComparisonPanel getCodeComparisonPanel() {
|
||||
return codeComparisonPanel;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@ package ghidra.feature.vt.gui.provider.markuptable;
|
|||
import static ghidra.feature.vt.api.impl.VTEvent.*;
|
||||
import static ghidra.feature.vt.gui.plugin.VTPlugin.*;
|
||||
import static ghidra.framework.model.DomainObjectEvent.*;
|
||||
import static ghidra.util.datastruct.Duo.Side.*;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.event.*;
|
||||
|
@ -34,7 +35,6 @@ import docking.action.*;
|
|||
import docking.actions.PopupActionProvider;
|
||||
import docking.widgets.EventTrigger;
|
||||
import docking.widgets.fieldpanel.FieldPanel;
|
||||
import docking.widgets.fieldpanel.internal.FieldPanelCoordinator;
|
||||
import docking.widgets.table.GTable;
|
||||
import docking.widgets.table.RowObjectTableModel;
|
||||
import docking.widgets.table.threaded.ThreadedTableModel;
|
||||
|
@ -150,23 +150,24 @@ public class VTMarkupItemsTableProvider extends ComponentProviderAdapter
|
|||
markupItemsTablePanel.add(tablePanel, BorderLayout.CENTER);
|
||||
markupItemsTablePanel.add(filterAreaPanel, BorderLayout.SOUTH);
|
||||
|
||||
functionComparisonPanel =
|
||||
new FunctionComparisonPanel(this, tool, (Function) null, (Function) null);
|
||||
functionComparisonPanel = new FunctionComparisonPanel(this, tool);
|
||||
addSpecificCodeComparisonActions();
|
||||
functionComparisonPanel.setCurrentTabbedComponent(ListingCodeComparisonPanel.TITLE);
|
||||
functionComparisonPanel.setCurrentTabbedComponent(ListingCodeComparisonPanel.NAME);
|
||||
functionComparisonPanel.setTitlePrefixes("Source:", "Destination:");
|
||||
ListingCodeComparisonPanel dualListingPanel = functionComparisonPanel.getDualListingPanel();
|
||||
if (dualListingPanel != null) {
|
||||
dualListingPanel.setLeftProgramLocationListener(new SourceProgramLocationListener());
|
||||
dualListingPanel
|
||||
.setRightProgramLocationListener(new DestinationProgramLocationListener());
|
||||
|
||||
dualListingPanel.getListingPanel(LEFT)
|
||||
.setProgramLocationListener(new SourceProgramLocationListener());
|
||||
dualListingPanel.getListingPanel(RIGHT).setProgramLocationListener(
|
||||
new DestinationProgramLocationListener());
|
||||
|
||||
sourceHighlightProvider = new VTDualListingHighlightProvider(controller, true);
|
||||
destinationHighlightProvider = new VTDualListingHighlightProvider(controller, false);
|
||||
dualListingPanel.addHighlightProviders(sourceHighlightProvider,
|
||||
destinationHighlightProvider);
|
||||
sourceHighlightProvider.setListingPanel(dualListingPanel.getLeftPanel());
|
||||
destinationHighlightProvider.setListingPanel(dualListingPanel.getRightPanel());
|
||||
sourceHighlightProvider.setListingPanel(dualListingPanel.getListingPanel(LEFT));
|
||||
destinationHighlightProvider.setListingPanel(dualListingPanel.getListingPanel(RIGHT));
|
||||
|
||||
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.
|
||||
if (!processingSourceLocationChange &&
|
||||
!processingDestinationLocationChange) {
|
||||
dualListingPanel.setLeftLocation(dualListingPanel.getLeftProgram(),
|
||||
dualListingPanel.setLocation(LEFT, dualListingPanel.getProgram(LEFT),
|
||||
markupItem.getSourceLocation());
|
||||
dualListingPanel.setRightLocation(dualListingPanel.getRightProgram(),
|
||||
dualListingPanel.setLocation(RIGHT, dualListingPanel.getProgram(RIGHT),
|
||||
markupItem.getDestinationLocation());
|
||||
}
|
||||
else {
|
||||
// Only adjust the side of the dual listing panel that didn't initiate this.
|
||||
ProgramLocation sourceLocation = markupItem.getSourceLocation();
|
||||
if (processingDestinationLocationChange && sourceLocation != null) {
|
||||
dualListingPanel.setLeftLocation(dualListingPanel.getLeftProgram(),
|
||||
sourceLocation);
|
||||
dualListingPanel.setLocation(LEFT,
|
||||
dualListingPanel.getProgram(LEFT), sourceLocation);
|
||||
}
|
||||
|
||||
ProgramLocation destinationLocation =
|
||||
markupItem.getDestinationLocation();
|
||||
if (processingSourceLocationChange && destinationLocation != null) {
|
||||
dualListingPanel.setRightLocation(
|
||||
dualListingPanel.getRightProgram(), destinationLocation);
|
||||
dualListingPanel.setLocation(RIGHT,
|
||||
dualListingPanel.getProgram(RIGHT), destinationLocation);
|
||||
}
|
||||
}
|
||||
dualListingPanel.updateListings(); // refresh the dual listing's background markup colors.
|
||||
|
@ -361,10 +362,10 @@ public class VTMarkupItemsTableProvider extends ComponentProviderAdapter
|
|||
splitPane.add(functionComparisonPanel);
|
||||
markupPanel.add(splitPane, BorderLayout.CENTER);
|
||||
if (dualListingPanel != null) {
|
||||
dualListingPanel
|
||||
.setLeftProgramLocationListener(new SourceProgramLocationListener());
|
||||
dualListingPanel.setRightProgramLocationListener(
|
||||
new DestinationProgramLocationListener());
|
||||
dualListingPanel.getListingPanel(LEFT)
|
||||
.setProgramLocationListener(new SourceProgramLocationListener());
|
||||
dualListingPanel.getListingPanel(LEFT)
|
||||
.setProgramLocationListener(new DestinationProgramLocationListener());
|
||||
}
|
||||
|
||||
markupPanel.validate();
|
||||
|
@ -378,8 +379,8 @@ public class VTMarkupItemsTableProvider extends ComponentProviderAdapter
|
|||
if (contains) {
|
||||
// Remove the split pane.
|
||||
if (dualListingPanel != null) {
|
||||
dualListingPanel.setLeftProgramLocationListener(null);
|
||||
dualListingPanel.setRightProgramLocationListener(null);
|
||||
dualListingPanel.getListingPanel(LEFT).setProgramLocationListener(null);
|
||||
dualListingPanel.getListingPanel(RIGHT).setProgramLocationListener(null);
|
||||
}
|
||||
markupPanel.remove(splitPane);
|
||||
splitPane.remove(functionComparisonPanel);
|
||||
|
@ -462,7 +463,7 @@ public class VTMarkupItemsTableProvider extends ComponentProviderAdapter
|
|||
public List<DockingActionIf> getPopupActions(Tool t, ActionContext context) {
|
||||
ListingCodeComparisonPanel dualListingPanel = functionComparisonPanel.getDualListingPanel();
|
||||
if (context.getComponentProvider() == this && dualListingPanel != null) {
|
||||
ListingPanel sourcePanel = dualListingPanel.getLeftPanel();
|
||||
ListingPanel sourcePanel = dualListingPanel.getListingPanel(LEFT);
|
||||
return sourcePanel.getHeaderActions(getName());
|
||||
}
|
||||
return new ArrayList<>();
|
||||
|
@ -477,7 +478,7 @@ public class VTMarkupItemsTableProvider extends ComponentProviderAdapter
|
|||
List<VTMarkupItem> selectedItems = getSelectedMarkupItems();
|
||||
VTMarkupItemContext vtMarkupItemContext = new VTMarkupItemContext(this, selectedItems);
|
||||
if (functionComparisonPanel.isVisible()) {
|
||||
CodeComparisonPanel<? extends FieldPanelCoordinator> displayedPanel =
|
||||
CodeComparisonPanel displayedPanel =
|
||||
functionComparisonPanel.getDisplayedPanel();
|
||||
vtMarkupItemContext.setCodeComparisonPanel(displayedPanel);
|
||||
}
|
||||
|
@ -626,7 +627,7 @@ public class VTMarkupItemsTableProvider extends ComponentProviderAdapter
|
|||
}
|
||||
}
|
||||
else {
|
||||
functionComparisonPanel.loadFunctions(null, null);
|
||||
functionComparisonPanel.clear();
|
||||
}
|
||||
|
||||
if (sourceHighlightProvider != null) {
|
||||
|
|
|
@ -37,22 +37,23 @@ public class LayoutLockedFieldPanelCoordinator extends LineLockedFieldPanelCoord
|
|||
* Constructor for the coordinator.
|
||||
* @param panels the field panels that will have their positions coordinated with each other.
|
||||
*/
|
||||
public LayoutLockedFieldPanelCoordinator(FieldPanel[] panels) {
|
||||
public LayoutLockedFieldPanelCoordinator(FieldPanel... panels) {
|
||||
super(panels);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void viewChanged(FieldPanel fp, BigInteger index, int xPos, int yPos) {
|
||||
if (valuesChanging)
|
||||
if (valuesChanging) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
valuesChanging = true;
|
||||
// "lockedLineIndex" is the IndexMap index indicating where this field panel
|
||||
// is locked to the other when scrolling.
|
||||
BigInteger lockedLineIndex1 = getLockedLineForPanel(fp);
|
||||
if (lockedLineIndex1 == null) { // This shouldn't happen.
|
||||
throw new AssertException("Couldn't find line number for indicated field panel."
|
||||
+ " FieldPanel is not one of those being managed by this coordinator.");
|
||||
throw new AssertException("Couldn't find line number for indicated field panel." +
|
||||
" FieldPanel is not one of those being managed by this coordinator.");
|
||||
}
|
||||
|
||||
// "topIndex" is the IndexMap index of the top of the listing in view.
|
||||
|
|
|
@ -15,6 +15,8 @@
|
|||
*/
|
||||
package ghidra.program.model.correlate;
|
||||
|
||||
import static ghidra.util.datastruct.Duo.Side.*;
|
||||
|
||||
import java.util.*;
|
||||
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.listing.*;
|
||||
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.task.TaskMonitor;
|
||||
|
||||
|
@ -41,7 +45,6 @@ import ghidra.util.task.TaskMonitor;
|
|||
* 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.
|
||||
* 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
|
||||
* 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,
|
||||
|
@ -49,7 +52,7 @@ import ghidra.util.task.TaskMonitor;
|
|||
* 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
|
||||
|
@ -59,17 +62,17 @@ public class HashedFunctionAddressCorrelation implements FunctionAddressCorrelat
|
|||
public Hash hash; // The disambiguating (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 DisambiguatorEntry(Hash h,InstructHash inst) {
|
||||
|
||||
public DisambiguatorEntry(Hash h, InstructHash inst) {
|
||||
hash = h;
|
||||
instruct = inst;
|
||||
count = 1;
|
||||
}
|
||||
}
|
||||
|
||||
private Function srcFunction;
|
||||
private Function destFunction;
|
||||
private TreeMap<Address,Address> srcToDest; // Final source -> destination address mapping
|
||||
private TreeMap<Address,Address> destToSrc; // Final destination -> source address mapping
|
||||
private Duo<Function> functions;
|
||||
private TreeMap<Address, Address> srcToDest; // Final source -> destination 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 destStore; // List of destination n-grams
|
||||
private HashCalculator hashCalc; // Object that calculates n-gram hashes
|
||||
|
@ -77,46 +80,36 @@ public class HashedFunctionAddressCorrelation implements FunctionAddressCorrelat
|
|||
|
||||
/**
|
||||
* Correlates addresses between the two specified functions.
|
||||
* @param function1 the first function
|
||||
* @param function2 the second function
|
||||
* @param mon the task monitor that indicates progress and allows the user to cancel.
|
||||
* @param leftFunction the first function
|
||||
* @param rightFunction the second function
|
||||
* @param monitor the task monitor that indicates progress and allows the user to cancel.
|
||||
* @throws CancelledException if the user cancels
|
||||
* @throws MemoryAccessException if either functions memory can't be accessed.
|
||||
*/
|
||||
public HashedFunctionAddressCorrelation(Function function1, Function function2, TaskMonitor mon)
|
||||
throws CancelledException, MemoryAccessException {
|
||||
srcFunction = function1;
|
||||
destFunction = function2;
|
||||
monitor = mon;
|
||||
srcToDest = new TreeMap<Address,Address>();
|
||||
destToSrc = new TreeMap<Address,Address>();
|
||||
if (function1 == null || function2 == null)
|
||||
return;
|
||||
srcStore = new HashStore(function1, monitor);
|
||||
destStore = new HashStore(function2, monitor);
|
||||
public HashedFunctionAddressCorrelation(Function leftFunction, Function rightFunction,
|
||||
TaskMonitor monitor) throws CancelledException, MemoryAccessException {
|
||||
if (leftFunction == null || rightFunction == null) {
|
||||
throw new IllegalArgumentException("Functions can't be null!");
|
||||
}
|
||||
this.functions = new Duo<>(leftFunction, rightFunction);
|
||||
this.monitor = monitor;
|
||||
srcToDest = new TreeMap<Address, Address>();
|
||||
destToSrc = new TreeMap<Address, Address>();
|
||||
srcStore = new HashStore(leftFunction, monitor);
|
||||
destStore = new HashStore(rightFunction, monitor);
|
||||
hashCalc = new MnemonicHashCalculator();
|
||||
calculate();
|
||||
buildFinalMaps();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Program getFirstProgram() {
|
||||
return srcFunction.getProgram();
|
||||
public Program getProgram(Side side) {
|
||||
return functions.get(side).getProgram();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Program getSecondProgram() {
|
||||
return destFunction.getProgram();
|
||||
}
|
||||
|
||||
@Override
|
||||
public AddressSetView getAddressesInFirst() {
|
||||
return srcFunction.getBody();
|
||||
}
|
||||
|
||||
@Override
|
||||
public AddressSetView getAddressesInSecond() {
|
||||
return destFunction.getBody();
|
||||
public AddressSetView getAddresses(Side side) {
|
||||
return functions.get(side).getBody();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -180,7 +173,8 @@ public class HashedFunctionAddressCorrelation implements FunctionAddressCorrelat
|
|||
* @param destInstruct is (the starting Instruction of) the destination n-gram
|
||||
* @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;
|
||||
int matchSize = srcEntry.hash.size;
|
||||
// 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
|
||||
cancelMatch = true; // Cancel the match
|
||||
}
|
||||
if (cancelMatch) return;
|
||||
if (cancelMatch)
|
||||
return;
|
||||
ArrayList<Instruction> srcInstructVec = new ArrayList<Instruction>();
|
||||
ArrayList<Instruction> destInstructVec = new ArrayList<Instruction>();
|
||||
ArrayList<CodeBlock> srcBlockVec = new ArrayList<CodeBlock>();
|
||||
|
@ -202,7 +197,7 @@ public class HashedFunctionAddressCorrelation implements FunctionAddressCorrelat
|
|||
HashStore.extendMatch(matchSize, srcInstruct, srcMatch, destInstruct, destMatch, hashCalc);
|
||||
srcStore.matchHash(srcMatch, srcInstructVec, srcBlockVec);
|
||||
destStore.matchHash(destMatch, destInstructVec, destBlockVec);
|
||||
for(int i=0;i<srcInstructVec.size();++i)
|
||||
for (int i = 0; i < srcInstructVec.size(); ++i)
|
||||
srcToDest.put(srcInstructVec.get(i).getAddress(), destInstructVec.get(i).getAddress());
|
||||
}
|
||||
|
||||
|
@ -219,16 +214,16 @@ public class HashedFunctionAddressCorrelation implements FunctionAddressCorrelat
|
|||
private static TreeMap<Hash, DisambiguatorEntry> constructDisambiguatorTree(HashEntry entry,
|
||||
HashStore store, DisambiguateStrategy strategy)
|
||||
throws CancelledException, MemoryAccessException {
|
||||
TreeMap<Hash,DisambiguatorEntry> entryMap = new TreeMap<Hash,DisambiguatorEntry>();
|
||||
TreeMap<Hash, DisambiguatorEntry> entryMap = new TreeMap<Hash, DisambiguatorEntry>();
|
||||
int matchSize = entry.hash.size;
|
||||
for(InstructHash curInstruct : entry.instList) {
|
||||
for (InstructHash curInstruct : entry.instList) {
|
||||
ArrayList<Hash> hashList = strategy.calcHashes(curInstruct, matchSize, store);
|
||||
Iterator<Hash> iter = hashList.iterator();
|
||||
while(iter.hasNext()) {
|
||||
while (iter.hasNext()) {
|
||||
Hash curHash = iter.next();
|
||||
DisambiguatorEntry curEntry = entryMap.get(curHash);
|
||||
if (curEntry == null) {
|
||||
curEntry = new DisambiguatorEntry(curHash,curInstruct);
|
||||
curEntry = new DisambiguatorEntry(curHash, curInstruct);
|
||||
entryMap.put(curHash, curEntry);
|
||||
}
|
||||
else
|
||||
|
@ -247,24 +242,30 @@ public class HashedFunctionAddressCorrelation implements FunctionAddressCorrelat
|
|||
* @throws CancelledException
|
||||
* @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 =
|
||||
constructDisambiguatorTree(srcEntry, srcStore, strategy);
|
||||
TreeMap<Hash, DisambiguatorEntry> destDisambig =
|
||||
constructDisambiguatorTree(destEntry, destStore, strategy);
|
||||
int count = 0;
|
||||
Iterator<DisambiguatorEntry> iter = srcDisambig.values().iterator();
|
||||
while(iter.hasNext()) {
|
||||
while (iter.hasNext()) {
|
||||
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
|
||||
if (srcDisEntry.instruct.isMatched) continue;
|
||||
if (srcDisEntry.instruct.isMatched)
|
||||
continue;
|
||||
DisambiguatorEntry destDisEntry = destDisambig.get(srcDisEntry.hash);
|
||||
if (destDisEntry == null) continue;
|
||||
if (destDisEntry.count != 1) continue;
|
||||
if (destDisEntry.instruct.isMatched) continue;
|
||||
if (destDisEntry == null)
|
||||
continue;
|
||||
if (destDisEntry.count != 1)
|
||||
continue;
|
||||
if (destDisEntry.instruct.isMatched)
|
||||
continue;
|
||||
// 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;
|
||||
}
|
||||
return count;
|
||||
|
@ -278,21 +279,27 @@ public class HashedFunctionAddressCorrelation implements FunctionAddressCorrelat
|
|||
* @throws CancelledException
|
||||
* @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())
|
||||
return false;
|
||||
if (destEntry.hasDuplicateBlocks())
|
||||
return false;
|
||||
if (srcEntry.hash.size != destEntry.hash.size)
|
||||
return false; // This likely never happens, because we know the hash values are equal
|
||||
int count = disambiguateNgramsWithStrategy(new DisambiguateByParent(),srcEntry,destEntry);
|
||||
if (count != 0) return true;
|
||||
count = disambiguateNgramsWithStrategy(new DisambiguateByChild(),srcEntry,destEntry);
|
||||
if (count != 0) return true;
|
||||
count = disambiguateNgramsWithStrategy(new DisambiguateByBytes(),srcEntry,destEntry);
|
||||
if (count != 0) return true;
|
||||
count= disambiguateNgramsWithStrategy(new DisambiguateByParentWithOrder(),srcEntry,destEntry);
|
||||
if (count != 0) return true;
|
||||
int count = disambiguateNgramsWithStrategy(new DisambiguateByParent(), srcEntry, destEntry);
|
||||
if (count != 0)
|
||||
return true;
|
||||
count = disambiguateNgramsWithStrategy(new DisambiguateByChild(), srcEntry, destEntry);
|
||||
if (count != 0)
|
||||
return true;
|
||||
count = disambiguateNgramsWithStrategy(new DisambiguateByBytes(), srcEntry, destEntry);
|
||||
if (count != 0)
|
||||
return true;
|
||||
count = disambiguateNgramsWithStrategy(new DisambiguateByParentWithOrder(), srcEntry,
|
||||
destEntry);
|
||||
if (count != 0)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -303,7 +310,7 @@ public class HashedFunctionAddressCorrelation implements FunctionAddressCorrelat
|
|||
* @throws CancelledException
|
||||
*/
|
||||
private void findMatches() throws MemoryAccessException, CancelledException {
|
||||
while(!srcStore.isEmpty() && !destStore.isEmpty()) {
|
||||
while (!srcStore.isEmpty() && !destStore.isEmpty()) {
|
||||
HashEntry srcEntry = srcStore.getFirstEntry();
|
||||
HashEntry destEntry = destStore.getEntry(srcEntry.hash);
|
||||
if (destEntry == null) {
|
||||
|
@ -311,7 +318,8 @@ public class HashedFunctionAddressCorrelation implements FunctionAddressCorrelat
|
|||
}
|
||||
else if (srcEntry.instList.size() == 1 && destEntry.instList.size() == 1) {
|
||||
// Found a unique match
|
||||
declareMatch(srcEntry,srcEntry.instList.getFirst(),destEntry,destEntry.instList.getFirst());
|
||||
declareMatch(srcEntry, srcEntry.instList.getFirst(), destEntry,
|
||||
destEntry.instList.getFirst());
|
||||
}
|
||||
else {
|
||||
HashEntry destEntry2 = destStore.getFirstEntry();
|
||||
|
@ -321,7 +329,8 @@ public class HashedFunctionAddressCorrelation implements FunctionAddressCorrelat
|
|||
}
|
||||
else if (srcEntry2.instList.size() == 1 && destEntry2.instList.size() == 1) {
|
||||
// Found a unique match
|
||||
declareMatch(srcEntry2,srcEntry2.instList.getFirst(),destEntry2,destEntry2.instList.getFirst());
|
||||
declareMatch(srcEntry2, srcEntry2.instList.getFirst(), destEntry2,
|
||||
destEntry2.instList.getFirst());
|
||||
}
|
||||
else {
|
||||
if (!disambiguateMatchingNgrams(srcEntry, destEntry))
|
||||
|
@ -344,12 +353,14 @@ public class HashedFunctionAddressCorrelation implements FunctionAddressCorrelat
|
|||
* @throws MemoryAccessException
|
||||
* @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);
|
||||
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();
|
||||
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();
|
||||
destStore.clearSort();
|
||||
|
||||
|
@ -357,7 +368,8 @@ public class HashedFunctionAddressCorrelation implements FunctionAddressCorrelat
|
|||
destStore.insertHashes();
|
||||
|
||||
findMatches();
|
||||
if (curMatch == srcStore.numMatchedInstructions()) break; // quit if no new matched instructions
|
||||
if (curMatch == srcStore.numMatchedInstructions())
|
||||
break; // quit if no new matched instructions
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -377,33 +389,40 @@ public class HashedFunctionAddressCorrelation implements FunctionAddressCorrelat
|
|||
|
||||
findMatches();
|
||||
|
||||
if (srcStore.numMatchedInstructions() == srcStore.getTotalInstructions()) return;
|
||||
if (destStore.numMatchedInstructions() == destStore.getTotalInstructions()) return;
|
||||
if (srcStore.numMatchedInstructions() == srcStore.getTotalInstructions())
|
||||
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
|
||||
// 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 (destStore.numMatchedInstructions() == destStore.getTotalInstructions()) return;
|
||||
if (srcStore.numMatchedInstructions() == srcStore.getTotalInstructions())
|
||||
return;
|
||||
if (destStore.numMatchedInstructions() == destStore.getTotalInstructions())
|
||||
return;
|
||||
|
||||
// Repeat with big n-grams
|
||||
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() == srcStore.getTotalInstructions()) return;
|
||||
if (destStore.numMatchedInstructions() == destStore.getTotalInstructions()) return;
|
||||
if (srcStore.numMatchedInstructions() == curMatch)
|
||||
return; // No progress
|
||||
if (srcStore.numMatchedInstructions() == srcStore.getTotalInstructions())
|
||||
return;
|
||||
if (destStore.numMatchedInstructions() == destStore.getTotalInstructions())
|
||||
return;
|
||||
|
||||
// Repeat with small n-grams
|
||||
runPasses(3,4,true,true,10);
|
||||
runPasses(3, 4, true, true, 10);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@literal Given the src -> dest map, build the dest -> src map}
|
||||
*/
|
||||
private void buildFinalMaps() {
|
||||
for(Entry<Address,Address> entry : srcToDest.entrySet()) {
|
||||
for (Entry<Address, Address> entry : srcToDest.entrySet()) {
|
||||
destToSrc.put(entry.getValue(), entry.getKey()); // Build the reverse map of srcToDest
|
||||
}
|
||||
}
|
||||
|
@ -412,27 +431,20 @@ public class HashedFunctionAddressCorrelation implements FunctionAddressCorrelat
|
|||
* Gets an iterator of the matching addresses from the first function to the second.
|
||||
* @return the iterator
|
||||
*/
|
||||
public Iterator<Entry<Address,Address>> getFirstToSecondIterator() {
|
||||
public Iterator<Entry<Address, Address>> getFirstToSecondIterator() {
|
||||
return srcToDest.entrySet().iterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Address getAddressInSecond(Address addressInFirst) {
|
||||
return srcToDest.get(addressInFirst);
|
||||
public Address getAddress(Side side, Address otherSideAddress) {
|
||||
if (side == LEFT) {
|
||||
return destToSrc.get(otherSideAddress);
|
||||
}
|
||||
return srcToDest.get(otherSideAddress);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Address getAddressInFirst(Address addressInSecond) {
|
||||
return destToSrc.get(addressInSecond);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Function getFirstFunction() {
|
||||
return srcFunction;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Function getSecondFunction() {
|
||||
return destFunction;
|
||||
public Function getFunction(Side side) {
|
||||
return functions.get(side);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,25 +15,32 @@
|
|||
*/
|
||||
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.Program;
|
||||
import ghidra.util.datastruct.Duo.Side;
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
public class DummyListingAddressCorrelation implements ListingAddressCorrelation {
|
||||
|
||||
/**
|
||||
* Gets the first function for this address correlator.
|
||||
* @return the first function.
|
||||
*/
|
||||
public Function getFirstFunction();
|
||||
@Override
|
||||
public Program getProgram(Side side) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the second function for this address correlator.
|
||||
* @return the second function.
|
||||
*/
|
||||
public Function getSecondFunction();
|
||||
@Override
|
||||
public Function getFunction(Side side) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AddressSetView getAddresses(Side side) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Address getAddress(Side side, Address otherSideAddress) {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
|
@ -17,54 +17,47 @@ 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.Program;
|
||||
import ghidra.util.datastruct.Duo.Side;
|
||||
|
||||
/**
|
||||
* 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
|
||||
* with addresses from another part of the same program. Given an address from the address set
|
||||
* in the first program it determines the matching address from the address set for the second
|
||||
* program if possible.
|
||||
* with addresses from another part of the same program. Given an address from one program, it
|
||||
* can provide the corresponding address for the other program. The two programs are referred to
|
||||
* as the LEFT program and the RIGHT program. See {@link ghidra.util.datastruct.Duo.Side}
|
||||
*/
|
||||
public interface ListingAddressCorrelation {
|
||||
|
||||
/**
|
||||
* Gets the program containing the first set of addresses.
|
||||
* @return the program for the first set of addresses.
|
||||
* Gets the program for the given side.
|
||||
* @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.
|
||||
* This program may be different from or the same as the first program.
|
||||
* @return the program for the second set of addresses.
|
||||
* Gets the function for the given side. This will be null if the addresses are not function
|
||||
* based.
|
||||
* @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.
|
||||
* @return the first set of addresses.
|
||||
* Gets the addresses that are part of the correlator for the given side
|
||||
* @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.
|
||||
* @return the second set of addresses.
|
||||
* Gets the address for the given side that matches the given address from the other side.
|
||||
* @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();
|
||||
|
||||
/**
|
||||
* 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);
|
||||
public abstract Address getAddress(Side side, Address otherSideAddress);
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -15,6 +15,8 @@
|
|||
*/
|
||||
package help.screenshot;
|
||||
|
||||
import static ghidra.util.datastruct.Duo.Side.*;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import javax.swing.table.TableColumn;
|
||||
|
@ -102,7 +104,7 @@ public class FunctionComparisonScreenShots extends GhidraScreenShotGenerator {
|
|||
functionComparisonPanel.setCurrentTabbedComponent("Listing View");
|
||||
ListingCodeComparisonPanel dualListing =
|
||||
(ListingCodeComparisonPanel) functionComparisonPanel.getDisplayedPanel();
|
||||
ListingPanel leftPanel = dualListing.getLeftPanel();
|
||||
ListingPanel leftPanel = dualListing.getListingPanel(LEFT);
|
||||
leftPanel.goTo(addr(0x004119aa));
|
||||
});
|
||||
waitForSwing();
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue