Candidate release of source code.

This commit is contained in:
Dan 2019-03-26 13:45:32 -04:00
parent db81e6b3b0
commit 79d8f164f8
12449 changed files with 2800756 additions and 16 deletions

View file

@ -0,0 +1,69 @@
<?xml version='1.0' encoding='ISO-8859-1' ?>
<!--
This is an XML file intended to be parsed by the Ghidra help system. It is loosely based
upon the JavaHelp table of contents document format. The Ghidra help system uses a
TOC_Source.xml file to allow a module with help to define how its contents appear in the
Ghidra help viewer's table of contents. The main document (in the Base module)
defines a basic structure for the
Ghidra table of contents system. Other TOC_Source.xml files may use this structure to insert
their files directly into this structure (and optionally define a substructure).
In this document, a tag can be either a <tocdef> or a <tocref>. The former is a definition
of an XML item that may have a link and may contain other <tocdef> and <tocref> children.
<tocdef> items may be referred to in other documents by using a <tocref> tag with the
appropriate id attribute value. Using these two tags allows any module to define a place
in the table of contents system (<tocdef>), which also provides a place for
other TOC_Source.xml files to insert content (<tocref>).
During the help build time, all TOC_Source.xml files will be parsed and validated to ensure
that all <tocref> tags point to valid <tocdef> tags. From these files will be generated
<module name>_TOC.xml files, which are table of contents files written in the format
desired by the JavaHelp system. Additionally, the genated files will be merged together
as they are loaded by the JavaHelp system. In the end, when displaying help in the Ghidra
help GUI, there will be on table of contents that has been created from the definitions in
all of the modules' TOC_Source.xml files.
Tags and Attributes
<tocdef>
-id - the name of the definition (this must be unique across all TOC_Source.xml files)
-text - the display text of the node, as seen in the help GUI
-target** - the file to display when the node is clicked in the GUI
-sortgroup - this is a string that defines where a given node should appear under a given
parent. The string values will be sorted by the JavaHelp system using
a javax.text.RulesBasedCollator. If this attribute is not specified, then
the text of attribute will be used.
<tocref>
-id - The id of the <tocdef> that this reference points to
**The URL for the target is relative and should start with 'help/topics'. This text is
used by the Ghidra help system to provide a universal starting point for all links so that
they can be resolved at runtime, across modules.
-->
<tocroot>
<tocref id="Graphing">
<tocdef id="Function Graph" text="Function Graph" target="help/topics/FunctionGraphPlugin/Function_Graph.html" >
<tocdef id="Primary View" sortgroup="a" text="Primary View" target="help/topics/FunctionGraphPlugin/Function_Graph.html#Primary_View" />
<tocdef id="Satellite View" sortgroup="b" text="Satellite View" target="help/topics/FunctionGraphPlugin/Function_Graph.html#Satellite_View" />
<tocdef id="Vertices" sortgroup="c" text="Vertices" target="help/topics/FunctionGraphPlugin/Function_Graph.html#Vertices">
<tocdef id="Vertex Grouping" sortgroup="a" text="Vertex Grouping" target="help/topics/FunctionGraphPlugin/Function_Graph.html#Vertex_Grouping" />
</tocdef>
<tocdef id="Graph Actions" sortgroup="e" text="Graph Actions" target="help/topics/FunctionGraphPlugin/Function_Graph.html#Function_Graph_Actions" />
<tocdef id="Popups" sortgroup="f" text="Popups" target="help/topics/FunctionGraphPlugin/Function_Graph.html#Popups" />
<tocdef id="Zooming" sortgroup="g" text="Zooming" target="help/topics/FunctionGraphPlugin/Function_Graph.html#Zoom" />
</tocdef>
</tocref>
</tocroot>

View file

@ -0,0 +1,58 @@
/* ###
* 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.
*/
/*
WARNING!
This file is copied to all help directories. If you change this file, you must copy it
to each src/main/help/help/shared directory.
Java Help Note: JavaHelp does not accept sizes (like in 'margin-top') in anything but
px (pixel) or with no type marking.
*/
body { margin-bottom: 50px; margin-left: 10px; margin-right: 10px; margin-top: 10px; } /* some padding to improve readability */
li { font-family:times new roman; font-size:14pt; }
h1 { color:#000080; font-family:times new roman; font-size:36pt; font-style:italic; font-weight:bold; text-align:center; }
h2 { margin: 10px; margin-top: 20px; color:#984c4c; font-family:times new roman; font-size:18pt; font-weight:bold; }
h3 { margin-left: 10px; margin-top: 20px; color:#0000ff; font-family:times new roman; font-size:14pt; font-weight:bold; }
h4 { margin-left: 10px; margin-top: 20px; font-family:times new roman; font-size:14pt; font-style:italic; }
/*
P tag code. Most of the help files nest P tags inside of blockquote tags (the was the
way it had been done in the beginning). The net effect is that the text is indented. In
modern HTML we would use CSS to do this. We need to support the Ghidra P tags, nested in
blockquote tags, as well as naked P tags. The following two lines accomplish this. Note
that the 'blockquote p' definition will inherit from the first 'p' definition.
*/
p { margin-left: 40px; font-family:times new roman; font-size:14pt; }
blockquote p { margin-left: 10px; }
p.providedbyplugin { color:#7f7f7f; margin-left: 10px; font-size:14pt; margin-top:100px }
p.ProvidedByPlugin { color:#7f7f7f; margin-left: 10px; font-size:14pt; margin-top:100px }
p.relatedtopic { color:#800080; margin-left: 10px; font-size:14pt; }
p.RelatedTopic { color:#800080; margin-left: 10px; font-size:14pt; }
/*
We wish for a tables to have space between it and the preceding element, so that text
is not too close to the top of the table. Also, nest the table a bit so that it is clear
the table relates to the preceding text.
*/
table { margin-left: 20px; margin-top: 10px; width: 80%;}
td { font-family:times new roman; font-size:14pt; vertical-align: top; }
th { font-family:times new roman; font-size:14pt; font-weight:bold; background-color: #EDF3FE; }
code { color: black; font-family: courier new; font-size: 14pt; }

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 859 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 187 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 185 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 864 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 591 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 237 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 426 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 806 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 703 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 309 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 538 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 539 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 666 B

View file

@ -0,0 +1,35 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.functiongraph;
import java.util.Set;
import ghidra.app.plugin.core.functiongraph.graph.layout.FGLayout;
import ghidra.app.plugin.core.functiongraph.graph.layout.FGLayoutProvider;
import ghidra.util.classfinder.ClassSearcher;
/**
* Finds {@link FGLayout}s via the classpath
*/
public class DiscoverableFGLayoutFinder implements FGLayoutFinder {
@Override
public Set<FGLayoutProvider> findLayouts() {
Set<FGLayoutProvider> instances = ClassSearcher.getInstances(FGLayoutProvider.class);
return instances;
}
}

View file

@ -0,0 +1,57 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.functiongraph;
import ghidra.graph.viewer.PathHighlightMode;
/**
* An enum for mapping the {@link PathHighlightMode} to values for use in UI actions.
*/
public enum EdgeDisplayType {
PathsToVertex,
PathsFromVertex,
PathsFromToVertex,
Cycles,
AllCycles,
PathsFromVertexToVertex,
ScopedFlowsFromVertex,
ScopedFlowsToVertex,
Off;
public PathHighlightMode getAsPathHighlightHoverMode() {
switch (this) {
case PathsToVertex:
return PathHighlightMode.IN;
case PathsFromVertex:
return PathHighlightMode.OUT;
case PathsFromToVertex:
return PathHighlightMode.INOUT;
case Cycles:
return PathHighlightMode.CYCLE;
case AllCycles:
return PathHighlightMode.ALLCYCLE;
case PathsFromVertexToVertex:
return PathHighlightMode.PATH;
case ScopedFlowsFromVertex:
return PathHighlightMode.SCOPED_FORWARD;
case ScopedFlowsToVertex:
return PathHighlightMode.SCOPED_REVERSE;
case Off:
default:
return PathHighlightMode.OFF;
}
}
}

View file

@ -0,0 +1,141 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.functiongraph;
import java.awt.Rectangle;
import java.awt.datatransfer.Transferable;
import docking.ActionContext;
import docking.widgets.fieldpanel.Layout;
import docking.widgets.fieldpanel.internal.EmptyLayoutBackgroundColorManager;
import docking.widgets.fieldpanel.internal.LayoutBackgroundColorManager;
import generic.text.TextLayoutGraphics;
import ghidra.app.context.ListingActionContext;
import ghidra.app.plugin.core.clipboard.CodeBrowserClipboardProvider;
import ghidra.app.plugin.core.functiongraph.graph.FunctionGraph;
import ghidra.app.plugin.core.functiongraph.graph.vertex.FGVertex;
import ghidra.app.plugin.core.functiongraph.mvc.FGController;
import ghidra.app.plugin.core.functiongraph.mvc.FGData;
import ghidra.app.util.viewer.listingpanel.ListingModel;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.address.*;
import ghidra.util.task.TaskMonitor;
public class FGClipboardProvider extends CodeBrowserClipboardProvider {
private FGController controller;
FGClipboardProvider(PluginTool tool, FGController controller) {
super(tool, controller.getProvider());
this.controller = controller;
}
@Override
public boolean isValidContext(ActionContext context) {
if (!(context instanceof ListingActionContext)) {
return false;
}
return context.getComponentProvider() == componentProvider;
}
/**
* Overridden because we don't have a single listing model from which to copy, but rather
* many different ones, depending upon the which vertex contains the selection.
*/
@Override
protected Transferable copyCode(TaskMonitor monitor) {
try {
TextLayoutGraphics g = new TextLayoutGraphics();
Rectangle rect = new Rectangle(2048, 2048);
AddressRangeIterator rangeItr = currentSelection.getAddressRanges();
while (rangeItr.hasNext()) {
AddressRange curRange = rangeItr.next();
Address curAddress = curRange.getMinAddress();
Address maxAddress = curRange.getMaxAddress();
while (!monitor.isCancelled()) {
if (curAddress != null && curAddress.compareTo(maxAddress) > 0) {
break;
}
curAddress = copyDataForAddress(curAddress, curRange, g, rect);
if (curAddress == null) {
break;
}
}
}
return createStringTransferable(g.getBuffer().toString());
}
catch (Exception e) {
String msg = e.getMessage();
if (msg == null) {
msg = e.toString();
}
tool.setStatusInfo("Copy failed: " + msg);
tool.getToolFrame().getToolkit().beep();
}
return null;
}
private Address copyDataForAddress(Address address, AddressRange currentRange,
TextLayoutGraphics g, Rectangle rectangle) {
FGData functionGraphData = controller.getFunctionGraphData();
FunctionGraph functionGraph = functionGraphData.getFunctionGraph();
FGVertex vertex = functionGraph.getVertexForAddress(address);
if (vertex == null) {
return null; // shouldn't happen
}
ListingModel listingModel = vertex.getListingModel(address);
// Add the layout for the present address
Layout layout = listingModel.getLayout(address, false);
if (layout != null) {
LayoutBackgroundColorManager layoutColorMap =
new EmptyLayoutBackgroundColorManager(PAINT_CONTEXT.getBackground());
layout.paint(null, g, PAINT_CONTEXT, rectangle, layoutColorMap, null);
g.flush();
}
// Get the next Address and update the page index
if (address.equals(currentRange.getMaxAddress())) {
return null;
}
Address addressAfter = listingModel.getAddressAfter(address);
if (addressAfter != null) {
return addressAfter;
}
// A null address could mean that we have reached the end of the listing for the given
// vertex. If that is the case, we should look the next address by adding to the current
// address. This will allow a future call to this method to get the vertex that contains
// that address.
Address nextAddress = null;
try {
nextAddress = address.add(layout.getIndexSize());
}
catch (AddressOutOfBoundsException oobe) {
// ignore and give up!
}
return nextAddress;
}
}

View file

@ -0,0 +1,46 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.functiongraph;
import ghidra.app.plugin.core.functiongraph.graph.vertex.FGVertex;
import ghidra.app.plugin.core.functiongraph.mvc.FunctionGraphVertexAttributes;
import ghidra.framework.options.SaveState;
import java.awt.Color;
import java.util.List;
public interface FGColorProvider {
public boolean isUsingCustomColors();
public List<Color> getRecentColors();
public Color getMostRecentColor();
public Color getColorFromUser(Color oldColor);
public void savePluginColors(SaveState saveState);
public void loadPluginColor(SaveState saveState);
public void saveVertexColors(FGVertex vertex, FunctionGraphVertexAttributes settings);
public void loadVertexColors(FGVertex vertex, FunctionGraphVertexAttributes settings);
public void setVertexColor(FGVertex vertex, Color newColor);
public void clearVertexColor(FGVertex vertex);
}

View file

@ -0,0 +1,28 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.functiongraph;
import java.util.Set;
import ghidra.app.plugin.core.functiongraph.graph.layout.FGLayout;
import ghidra.app.plugin.core.functiongraph.graph.layout.FGLayoutProvider;
/**
* An interface that provides {@link FGLayout}s
*/
public interface FGLayoutFinder {
public Set<FGLayoutProvider> findLayouts();
}

View file

@ -0,0 +1,59 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.functiongraph;
import ghidra.app.nav.LocationMemento;
import ghidra.app.plugin.core.functiongraph.graph.FGEdge;
import ghidra.app.plugin.core.functiongraph.graph.vertex.FGVertex;
import ghidra.framework.options.SaveState;
import ghidra.graph.viewer.GraphPerspectiveInfo;
import ghidra.program.model.listing.Program;
import ghidra.program.util.ProgramLocation;
public class FGLocationMemento extends LocationMemento {
private GraphPerspectiveInfo<FGVertex, FGEdge> info;
FGLocationMemento(Program program, ProgramLocation location,
GraphPerspectiveInfo<FGVertex, FGEdge> info) {
super(program, location);
this.info = info;
}
public FGLocationMemento(SaveState saveState, Program[] programs) {
super(saveState, programs);
info = new GraphPerspectiveInfo<>(saveState);
}
@Override
public void saveState(SaveState saveState) {
super.saveState(saveState);
info.saveState(saveState);
}
GraphPerspectiveInfo<FGVertex, FGEdge> getGraphPerspectiveInfo() {
return info;
}
@Override
public String toString() {
//@formatter:off
return "FG Memento [\n\tperspective=" + info +
",\n\taddress=" + programLocation.getAddress() +
",\n\tlocation=" + programLocation +
"\n]";
//@formatter:on
}
}

View file

@ -0,0 +1,69 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.functiongraph;
import java.awt.Dimension;
import java.awt.event.MouseEvent;
import javax.swing.Icon;
import javax.swing.JComponent;
import docking.*;
import ghidra.app.plugin.core.functiongraph.mvc.FGController;
import ghidra.framework.plugintool.ComponentProviderAdapter;
import ghidra.util.HelpLocation;
import resources.ResourceManager;
public class FGSatelliteUndockedProvider extends ComponentProviderAdapter {
static final String NAME = "Function Graph Satellite";
private static final Icon ICON = ResourceManager.loadImage("images/network-wireless-16.png");
private FGController controller;
private JComponent satelliteComponent;
public FGSatelliteUndockedProvider(FunctionGraphPlugin plugin, FGController controller,
JComponent satelliteComponent) {
super(plugin.getTool(), NAME, plugin.getName());
this.controller = controller;
this.satelliteComponent = satelliteComponent;
satelliteComponent.setMinimumSize(new Dimension(400, 400));
setHelpLocation(new HelpLocation("FunctionGraphPlugin", "Satellite_View_Dock"));
setIcon(ICON);
setDefaultWindowPosition(WindowPosition.WINDOW);
setWindowMenuGroup(FunctionGraphPlugin.FUNCTION_GRAPH_NAME);
addToTool();
}
@Override
public ActionContext getActionContext(MouseEvent event) {
ComponentProvider primaryProvider = controller.getProvider();
return primaryProvider.getActionContext(event);
}
@Override
public JComponent getComponent() {
return satelliteComponent;
}
@Override
public void componentShown() {
controller.satelliteProviderShown();
}
}

View file

@ -0,0 +1,425 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.functiongraph;
import java.util.*;
import javax.swing.ImageIcon;
import org.jdom.Element;
import docking.ActionContext;
import docking.action.DockingAction;
import docking.action.ToolBarData;
import ghidra.GhidraOptions;
import ghidra.app.CorePluginPackage;
import ghidra.app.events.*;
import ghidra.app.plugin.PluginCategoryNames;
import ghidra.app.plugin.ProgramPlugin;
import ghidra.app.plugin.core.colorizer.ColorizingService;
import ghidra.app.plugin.core.functiongraph.mvc.FunctionGraphOptions;
import ghidra.app.services.*;
import ghidra.app.util.viewer.format.FormatManager;
import ghidra.framework.model.DomainFile;
import ghidra.framework.options.*;
import ghidra.framework.plugintool.PluginInfo;
import ghidra.framework.plugintool.PluginTool;
import ghidra.framework.plugintool.util.OptionsService;
import ghidra.framework.plugintool.util.PluginStatus;
import ghidra.graph.viewer.options.VisualGraphOptions;
import ghidra.program.model.listing.Program;
import ghidra.program.util.ProgramLocation;
import ghidra.program.util.ProgramSelection;
import ghidra.util.HelpLocation;
import resources.ResourceManager;
//@formatter:off
@PluginInfo(
status = PluginStatus.RELEASED,
packageName = CorePluginPackage.NAME,
category = PluginCategoryNames.GRAPH,
shortDescription = FunctionGraphPlugin.FUNCTION_GRAPH_NAME,
description = "Plugin for show a graphical representation of the code blocks of a function",
servicesRequired = { GoToService.class, BlockModelService.class, CodeViewerService.class, ProgramManager.class }
)
//@formatter:on
public class FunctionGraphPlugin extends ProgramPlugin implements OptionsChangeListener {
static final String FUNCTION_GRAPH_NAME = "Function Graph";
static final String PLUGIN_OPTIONS_NAME = FUNCTION_GRAPH_NAME;
static final ImageIcon ICON = ResourceManager.loadImage("images/function_graph.png");
public static final ImageIcon GROUP_ICON =
ResourceManager.loadImage("images/shape_handles.png");
public static final ImageIcon GROUP_ADD_ICON =
ResourceManager.loadImage("images/shape_square_add.png");
public static final ImageIcon UNGROUP_ICON =
ResourceManager.loadImage("images/shape_ungroup.png");
private static final String USER_DEFINED_FORMAT_CONFIG_NAME = "USER_DEFINED_FORMAT_MANAGER";
private static final String PROVIDER_ID = "Provider";
private static final String PROGRAM_PATH_ID = "Program Path";
private static final String DISCONNECTED_COUNT_ID = "Disconnected Count";
private DockingAction showFunctionGraphAction;
private FGProvider connectedProvider;
private List<FGProvider> disconnectedProviders = new ArrayList<>();
private FormatManager userDefinedFormatManager;
private FunctionGraphOptions functionGraphOptions = new FunctionGraphOptions();
private FGColorProvider colorProvider;
public FunctionGraphPlugin(PluginTool tool) {
super(tool, true, true, true);
createActions();
colorProvider = new IndependentColorProvider(tool);
}
@Override
protected void init() {
super.init();
createNewProvider();
initializeOptions();
ColorizingService colorizingService = tool.getService(ColorizingService.class);
if (colorizingService != null) {
colorProvider = new ToolBasedColorProvider(this, colorizingService);
}
}
@Override
public void serviceAdded(Class<?> interfaceClass, Object service) {
if (interfaceClass == ClipboardService.class) {
connectedProvider.setClipboardService((ClipboardService) service);
for (FGProvider disconnectedProvider : disconnectedProviders) {
disconnectedProvider.setClipboardService((ClipboardService) service);
}
}
else if (interfaceClass == ColorizingService.class) {
colorProvider = new ToolBasedColorProvider(this, (ColorizingService) service);
connectedProvider.refreshAndKeepPerspective();
}
}
@Override
public void serviceRemoved(Class<?> interfaceClass, Object service) {
if (interfaceClass == ClipboardService.class) {
connectedProvider.setClipboardService((ClipboardService) service);
for (FGProvider disconnectedProvider : disconnectedProviders) {
disconnectedProvider.setClipboardService((ClipboardService) service);
}
}
else if (interfaceClass == ColorizingService.class) {
colorProvider = new IndependentColorProvider(tool);
connectedProvider.refreshAndKeepPerspective();
}
}
private void initializeOptions() {
ToolOptions options = tool.getOptions(PLUGIN_OPTIONS_NAME);
options.addOptionsChangeListener(this);
functionGraphOptions.initializeOptions(this, options);
functionGraphOptions.loadOptions(this, options);
}
@Override
public void optionsChanged(ToolOptions options, String optionName, Object oldValue,
Object newValue) {
functionGraphOptions.loadOptions(this, options);
connectedProvider.getComponent().repaint();
for (FGProvider provider : disconnectedProviders) {
provider.getComponent().repaint();
}
if (VisualGraphOptions.USE_CONDENSED_LAYOUT.equals(optionName)) {
// the condensed setting requires us to reposition the graph
connectedProvider.refreshAndKeepPerspective();
}
else if (VisualGraphOptions.VIEW_RESTORE_OPTIONS_KEY.equals(optionName)) {
connectedProvider.clearViewSettings();
}
}
@Override
protected void programActivated(Program program) {
if (connectedProvider == null) {
return;
}
connectedProvider.doSetProgram(program);
}
@Override
protected void programDeactivated(Program program) {
if (connectedProvider == null) {
return;
}
connectedProvider.doSetProgram(null);
}
@Override
protected void locationChanged(ProgramLocation location) {
if (connectedProvider == null) {
return;
}
connectedProvider.setLocation(location);
}
@Override
protected void selectionChanged(ProgramSelection selection) {
if (connectedProvider == null) {
return;
}
connectedProvider.setSelection(selection);
}
@Override
protected void highlightChanged(ProgramSelection highlight) {
if (connectedProvider == null) {
return;
}
connectedProvider.setHighlight(highlight);
}
@Override
protected void programClosed(Program program) {
if (currentProgram == program) {
currentProgram = null;
}
connectedProvider.programClosed(program);
Iterator<FGProvider> iterator = disconnectedProviders.iterator();
while (iterator.hasNext()) {
FGProvider provider = iterator.next();
if (provider.getProgram() == program) {
iterator.remove();
removeProvider(provider);
}
}
}
private void createActions() {
showFunctionGraphAction = new DockingAction("Display Function Graph", getName()) {
@Override
public void actionPerformed(ActionContext context) {
showProvider();
}
};
showFunctionGraphAction.setToolBarData(new ToolBarData(ICON, "View"));
showFunctionGraphAction.setHelpLocation(
new HelpLocation("FunctionGraphPlugin", "Function_Graph_Plugin"));
tool.addAction(showFunctionGraphAction);
}
void showProvider() {
connectedProvider.setVisible(true);
connectedProvider.setLocation(currentLocation);
}
void closeProvider(FGProvider provider) {
if (provider == connectedProvider) {
tool.showComponentProvider(provider, false);
}
else {
disconnectedProviders.remove(provider);
removeProvider(provider);
}
}
private void createNewProvider() {
connectedProvider = new FGProvider(this, true);
connectedProvider.doSetProgram(currentProgram);
connectedProvider.setLocation(currentLocation);
connectedProvider.setSelection(currentSelection);
}
FGProvider createNewDisconnectedProvider() {
FGProvider provider = new FGProvider(this, false);
disconnectedProviders.add(provider);
tool.showComponentProvider(provider, true);
return provider;
}
@Override
protected void dispose() {
super.dispose();
currentProgram = null;
removeProvider(connectedProvider);
for (FGProvider provider : disconnectedProviders) {
removeProvider(provider);
}
disconnectedProviders.clear();
}
private void removeProvider(FGProvider provider) {
if (provider == null) {
return;
}
provider.dispose();
tool.removeComponentProvider(provider);
}
public void handleProviderLocationChanged(FGProvider provider, ProgramLocation location) {
if (provider != connectedProvider) {
return;
}
firePluginEvent(new ProgramLocationPluginEvent(getName(), location, location.getProgram()));
}
public void handleProviderSelectionChanged(FGProvider provider, ProgramSelection selection) {
if (provider != connectedProvider) {
return;
}
if (selection == null) {
return;
}
firePluginEvent(
new ProgramSelectionPluginEvent(getName(), selection, provider.getProgram()));
}
public void handleProviderHighlightChanged(FGProvider provider, ProgramSelection highlight) {
if (provider != connectedProvider) {
return;
}
if (highlight == null) {
return;
}
firePluginEvent(
new ProgramHighlightPluginEvent(getName(), highlight, provider.getProgram()));
}
public void setUserDefinedFormat(FormatManager formatManager) {
userDefinedFormatManager = formatManager;
tool.setConfigChanged(true);
}
public FormatManager getUserDefinedFormat() {
return userDefinedFormatManager;
}
@Override
public void readConfigState(SaveState saveState) {
Element formatElement = saveState.getXmlElement(USER_DEFINED_FORMAT_CONFIG_NAME);
if (formatElement != null) {
OptionsService options = getTool().getService(OptionsService.class);
ToolOptions displayOptions = options.getOptions(GhidraOptions.CATEGORY_BROWSER_DISPLAY);
ToolOptions fieldOptions = options.getOptions(GhidraOptions.CATEGORY_BROWSER_FIELDS);
userDefinedFormatManager = new FormatManager(displayOptions, fieldOptions);
SaveState formatState = new SaveState(formatElement);
userDefinedFormatManager.readState(formatState);
connectedProvider.formatChanged();
}
colorProvider.savePluginColors(saveState);
connectedProvider.readConfigState(saveState);
}
@Override
public void writeConfigState(SaveState saveState) {
if (userDefinedFormatManager != null) {
SaveState formatState = new SaveState();
userDefinedFormatManager.saveState(formatState);
Element element = formatState.saveToXml();
saveState.putXmlElement(USER_DEFINED_FORMAT_CONFIG_NAME, element);
}
colorProvider.loadPluginColor(saveState);
if (connectedProvider != null) {
connectedProvider.writeConfigState(saveState);
}
}
@Override
public void writeDataState(SaveState saveState) {
if (connectedProvider != null) {
connectedProvider.writeDataState(saveState);
connectedProvider.writeConfigState(saveState);
}
saveState.putInt(DISCONNECTED_COUNT_ID, disconnectedProviders.size());
int i = 0;
for (FGProvider provider : disconnectedProviders) {
SaveState providerSaveState = new SaveState();
DomainFile df = provider.getProgram().getDomainFile();
if (df.getParent() == null) {
continue; // not contained within project
}
String programPathname = df.getPathname();
providerSaveState.putString(PROGRAM_PATH_ID, programPathname);
provider.writeDataState(providerSaveState);
provider.writeConfigState(providerSaveState);
String disconnectedName = PROVIDER_ID + i;
saveState.putXmlElement(disconnectedName, providerSaveState.saveToXml());
i++;
}
}
@Override
public void readDataState(SaveState saveState) {
ProgramManager programManagerService = tool.getService(ProgramManager.class);
if (connectedProvider != null) {
connectedProvider.readDataState(saveState);
connectedProvider.readConfigState(saveState);
}
int numDisconnected = saveState.getInt(DISCONNECTED_COUNT_ID, 0);
for (int i = 0; i < numDisconnected; i++) {
String disconnectedName = PROVIDER_ID + i;
Element xmlElement = saveState.getXmlElement(disconnectedName);
SaveState providerSaveState = new SaveState(xmlElement);
String programPath = providerSaveState.getString(PROGRAM_PATH_ID, "");
DomainFile file = tool.getProject().getProjectData().getFile(programPath);
if (file == null) {
continue;
}
Program program = programManagerService.openProgram(file);
if (program != null) {
FGProvider provider = createNewDisconnectedProvider();
provider.doSetProgram(program);
provider.readDataState(providerSaveState);
provider.readConfigState(providerSaveState);
}
}
}
@Override
public void dataStateRestoreCompleted() {
super.dataStateRestoreCompleted();
// ProgramLocation location = ProgramLocation.getLocation(
// currentProgram, dataSaveState, null );
}
public FGColorProvider getColorProvider() {
return colorProvider;
}
public FunctionGraphOptions getFunctionGraphOptions() {
return functionGraphOptions;
}
}

View file

@ -0,0 +1,173 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.functiongraph;
import java.awt.Color;
import java.awt.Component;
import java.util.*;
import org.jdom.Element;
import docking.ComponentPlaceholder;
import docking.DockingWindowManager;
import docking.options.editor.GhidraColorChooser;
import ghidra.app.plugin.core.functiongraph.graph.vertex.FGVertex;
import ghidra.app.plugin.core.functiongraph.mvc.FunctionGraphVertexAttributes;
import ghidra.framework.options.SaveState;
import ghidra.framework.plugintool.PluginTool;
class IndependentColorProvider implements FGColorProvider {
private static final String VERTEX_COLORS = "VERTEX_COLORS";
private RecentColorCache recentColorCache = new RecentColorCache();
private final PluginTool tool;
IndependentColorProvider(PluginTool tool) {
this.tool = tool;
}
@Override
public boolean isUsingCustomColors() {
return true;
}
@Override
public Color getColorFromUser(Color startColor) {
GhidraColorChooser chooser = new GhidraColorChooser(startColor);
chooser.setTitle("Please Select Background Color");
List<Color> recentColors = recentColorCache.getMRUColorList();
chooser.setColorHistory(recentColors);
Color newColor = chooser.showDialog(getActiveComponent());
if (newColor != null && !newColor.equals(startColor)) {
recentColorCache.addColor(newColor);
tool.setConfigChanged(true);
}
return newColor;
}
private Component getActiveComponent() {
DockingWindowManager manager = DockingWindowManager.getActiveInstance();
ComponentPlaceholder placeholder = manager.getFocusedComponent();
if (placeholder != null) { // may be null if the app loses focus
return placeholder.getComponent();
}
return manager.getActiveComponent();
}
@Override
public void setVertexColor(FGVertex vertex, Color newColor) {
vertex.setBackgroundColor(newColor);
}
@Override
public void clearVertexColor(FGVertex vertex) {
vertex.clearColor();
}
@Override
public Color getMostRecentColor() {
return recentColorCache.getMostRecentColor();
}
@Override
public List<Color> getRecentColors() {
return recentColorCache.getMRUColorList();
}
@Override
public void savePluginColors(SaveState saveState) {
// store off global colors for vertices
Element colorsElement = new Element(VERTEX_COLORS);
for (Color color : recentColorCache) {
Element element = new Element("COLOR");
element.setAttribute("RGB", Integer.toString(color.getRGB()));
colorsElement.addContent(element);
}
saveState.putXmlElement(VERTEX_COLORS, colorsElement);
}
@SuppressWarnings("unchecked")
// casting the getChildren() of element
@Override
public void loadPluginColor(SaveState saveState) {
// globally used vertex colors
Element xmlElement = saveState.getXmlElement(VERTEX_COLORS);
if (xmlElement != null) {
List<Element> colorElements = xmlElement.getChildren("COLOR");
for (Element element : colorElements) {
String rgbString = element.getAttributeValue("RGB");
int rgb = Integer.parseInt(rgbString);
recentColorCache.addColor(new Color(rgb, true));
}
}
}
@Override
public void saveVertexColors(FGVertex vertex, FunctionGraphVertexAttributes settings) {
Color userDefinedColor = vertex.getUserDefinedColor();
if (userDefinedColor != null) {
settings.putVertexColor(vertex.getVertexAddress(), userDefinedColor);
}
}
@Override
public void loadVertexColors(FGVertex vertex, FunctionGraphVertexAttributes settings) {
Color savedColor = settings.getVertexColor(vertex.getVertexAddress());
if (savedColor != null) {
vertex.restoreColor(savedColor);
}
}
//==================================================================================================
// Inner Classes
//==================================================================================================
private class RecentColorCache extends LinkedHashMap<Color, Color> implements Iterable<Color> {
private static final int MAX_SIZE = 10;
private Color mostRecentColor = Color.blue;
RecentColorCache() {
super(16, 0.75f, true);
}
@Override
protected boolean removeEldestEntry(Map.Entry<Color, Color> eldest) {
return size() > MAX_SIZE;
}
@Override
public Iterator<Color> iterator() {
return keySet().iterator();
}
public void addColor(Color color) {
put(color, color);
mostRecentColor = color;
}
public List<Color> getMRUColorList() {
List<Color> list = new ArrayList<>(this.keySet());
Collections.reverse(list); // we are in LRU order, so reverse it
return list;
}
public Color getMostRecentColor() {
return mostRecentColor;
}
}
}

View file

@ -0,0 +1,199 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.functiongraph;
import java.awt.BorderLayout;
import java.awt.event.MouseEvent;
import java.util.List;
import javax.swing.JComponent;
import javax.swing.JPanel;
import org.jdom.Element;
import docking.ActionContext;
import docking.DialogComponentProvider;
import docking.action.*;
import docking.widgets.OptionDialog;
import ghidra.app.util.viewer.format.*;
import ghidra.app.util.viewer.listingpanel.ListingPanel;
import ghidra.framework.options.SaveState;
import ghidra.framework.plugintool.ServiceProvider;
import ghidra.program.model.address.AddressSetView;
import ghidra.program.model.listing.Program;
public class SetFormatDialogComponentProvider extends DialogComponentProvider {
private final FormatManager currentFormatManager;
private final FormatManager defaultFormatManager;
private FormatManager newFormatManager;
private ListingPanel listingPanel;
private final Program program;
private final AddressSetView view;
public SetFormatDialogComponentProvider(FormatManager defaultManager,
FormatManager currentFormatManager, ServiceProvider serviceProvider, Program program,
AddressSetView view) {
super("Edit Code Layout", true, false, true, false);
this.defaultFormatManager = defaultManager;
this.currentFormatManager = currentFormatManager;
this.program = program;
this.view = view;
setPreferredSize(600, 500);
addWorkPanel(createWorkPanel());
addOKButton();
addCancelButton();
List<DockingActionIf> headerActions = listingPanel.getHeaderActions(getTitle());
for (DockingActionIf action : headerActions) {
if ("Reset All Formats".equals(action.getName())) {
continue;
}
else if ("Reset Format".equals(action.getName())) {
continue;
}
addAction(action);
}
addAction(new CustomResetFormatAction());
addAction(new CustomResetAllFormatAction());
}
private JComponent createWorkPanel() {
JPanel container = new JPanel(new BorderLayout());
listingPanel = createListingPanel();
listingPanel.showHeader(true);
container.add(listingPanel);
return container;
}
private ListingPanel createListingPanel() {
FormatManager formatManagerCopy = currentFormatManager.createClone();
ListingPanel panel = new ListingPanel(formatManagerCopy, program);
panel.setView(view);
return panel;
}
public FormatManager getNewFormatManager() {
return newFormatManager;
}
@Override
protected void okCallback() {
newFormatManager = listingPanel.getFormatManager();
close();
}
@Override
protected void cancelCallback() {
newFormatManager = null;
super.cancelCallback();
}
@Override
public void close() {
super.close();
listingPanel.dispose();
}
@Override
public ActionContext getActionContext(MouseEvent event) {
if (event == null) {
return null;
}
FieldHeader headerPanel = listingPanel.getFieldHeader();
if (headerPanel != null && headerPanel.isAncestorOf(event.getComponent())) {
FieldHeaderLocation fhLoc = headerPanel.getFieldHeaderLocation(event.getPoint());
return new ActionContext(null, fhLoc);
}
return null;
}
/*testing*/ FieldHeader getFieldHeader() {
return listingPanel.getFieldHeader();
}
//==================================================================================================
// Inner Classes
//==================================================================================================
private class CustomResetAllFormatAction extends DockingAction {
public CustomResetAllFormatAction() {
super("Reset All Formats", getTitle(), false);
setPopupMenuData(new MenuData(new String[] { "Reset All Formats" }, null, "format"));
setEnabled(true);
}
@Override
public boolean isEnabledForContext(ActionContext context) {
return context.getContextObject() instanceof FieldHeaderLocation;
}
@Override
public void actionPerformed(ActionContext context) {
int userChoice = OptionDialog.showOptionDialog(listingPanel, "Reset All Formats?",
"There is no undo for this action.\n" +
"Are you sure you want to reset all formats?",
"Continue", OptionDialog.WARNING_MESSAGE);
if (userChoice == OptionDialog.CANCEL_OPTION) {
return;
}
FormatManager listingFormatManager = listingPanel.getFormatManager();
SaveState saveState = new SaveState();
defaultFormatManager.saveState(saveState);
// update the dialog's GUI (which will later be used as the new format if the
// user presses OK)
listingFormatManager.readState(saveState);
}
}
private class CustomResetFormatAction extends DockingAction {
public CustomResetFormatAction() {
super("Reset Format", getTitle(), false);
setPopupMenuData(new MenuData(new String[] { "Reset Format" }, null, "format"));
setEnabled(true);
}
@Override
public boolean isEnabledForContext(ActionContext context) {
return context.getContextObject() instanceof FieldHeaderLocation;
}
@Override
public void actionPerformed(ActionContext context) {
FieldHeader fieldHeader = listingPanel.getFieldHeader();
int index = fieldHeader.getSelectedIndex();
FieldFormatModel originalModel = defaultFormatManager.getModel(index);
Element originalXML = originalModel.saveToXml();
// update the dialog's GUI (which will later be used as the new format if the
// user presses OK)
FormatManager listingFormatManager = listingPanel.getFormatManager();
FieldFormatModel currentModel = listingFormatManager.getModel(index);
currentModel.restoreFromXml(originalXML);
}
}
}

View file

@ -0,0 +1,111 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.functiongraph;
import java.awt.Color;
import java.util.List;
import ghidra.app.plugin.core.colorizer.ColorizingService;
import ghidra.app.plugin.core.functiongraph.graph.vertex.FGVertex;
import ghidra.app.plugin.core.functiongraph.mvc.FunctionGraphVertexAttributes;
import ghidra.framework.options.SaveState;
import ghidra.program.model.listing.Program;
class ToolBasedColorProvider implements FGColorProvider {
private final ColorizingService service;
private final FunctionGraphPlugin plugin;
ToolBasedColorProvider(FunctionGraphPlugin plugin, ColorizingService colorizingService) {
this.plugin = plugin;
this.service = colorizingService;
}
@Override
public boolean isUsingCustomColors() {
return false;
}
@Override
public void setVertexColor(FGVertex vertex, Color color) {
Program program = plugin.getCurrentProgram();
int id = program.startTransaction("Set Background Color");
try {
service.setBackgroundColor(vertex.getAddresses(), color);
}
finally {
program.endTransaction(id, true);
}
vertex.setBackgroundColor(color);
}
@Override
public void clearVertexColor(FGVertex vertex) {
Program program = plugin.getCurrentProgram();
int id = program.startTransaction("Set Background Color");
try {
service.clearBackgroundColor(vertex.getAddresses());
}
finally {
program.endTransaction(id, true);
}
vertex.clearColor();
}
@Override
public Color getColorFromUser(Color startColor) {
return service.getColorFromUser(startColor);
}
@Override
public Color getMostRecentColor() {
return service.getMostRecentColor();
}
@Override
public List<Color> getRecentColors() {
return service.getRecentColors();
}
@Override
public void savePluginColors(SaveState saveState) {
// no-op; the loading/saving of colors is handled automatically by the service
}
@Override
public void loadPluginColor(SaveState saveState) {
// no-op; the loading/saving of colors is handled automatically by the service
}
@Override
public void saveVertexColors(FGVertex vertex, FunctionGraphVertexAttributes settings) {
// no-op; the loading/saving of colors is handled automatically by the service
}
@Override
public void loadVertexColors(FGVertex vertex, FunctionGraphVertexAttributes settings) {
// The loading/saving of colors is handled automatically by the service, but this is
// for the background of the code units stored in the DB. We still have to let this
// vertex know that it has a custom background.
Color savedColor = service.getBackgroundColor(vertex.getVertexAddress());
if (savedColor != null) {
vertex.restoreColor(savedColor);
}
}
}

View file

@ -0,0 +1,25 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.functiongraph.action;
import ghidra.app.plugin.core.functiongraph.graph.vertex.FGVertex;
public class FullScreenModeVertexActionContextInfo extends VertexActionContextInfo {
public FullScreenModeVertexActionContextInfo(FGVertex vertex) {
super(vertex);
}
}

View file

@ -0,0 +1,55 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.functiongraph.action;
import ghidra.app.context.ListingActionContext;
import ghidra.app.context.RestrictedAddressSetContext;
import ghidra.app.plugin.core.functiongraph.FGProvider;
import ghidra.app.plugin.core.functiongraph.graph.vertex.FGVertex;
import java.util.Set;
public class FunctionGraphEditableVertexLocationActionContext extends ListingActionContext
implements FunctionGraphVertexLocationContextIf, RestrictedAddressSetContext {
private final VertexActionContextInfo vertexInfo;
public FunctionGraphEditableVertexLocationActionContext(
FGProvider functionGraphProvider, VertexActionContextInfo vertexInfo) {
super(functionGraphProvider, functionGraphProvider);
if (vertexInfo == null) {
throw new NullPointerException("VertexActionContextInfo cannot be null");
}
this.vertexInfo = vertexInfo;
}
@Override
public FGVertex getVertex() {
return vertexInfo.getActiveVertex();
}
@Override
public VertexActionContextInfo getVertexInfo() {
return vertexInfo;
}
@Override
public Set<FGVertex> getSelectedVertices() {
return vertexInfo.getSelectedVertices();
}
}

View file

@ -0,0 +1,33 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.functiongraph.action;
import ghidra.app.context.ProgramActionContext;
import ghidra.app.plugin.core.functiongraph.FGProvider;
import ghidra.graph.viewer.actions.VisualGraphActionContext;
public class FunctionGraphEmptyGraphActionContext extends ProgramActionContext
implements VisualGraphActionContext {
public FunctionGraphEmptyGraphActionContext(FGProvider functionGraphProvider) {
super(functionGraphProvider, functionGraphProvider.getProgram());
}
@Override
public boolean shouldShowSatelliteActions() {
return true;
}
}

View file

@ -0,0 +1,29 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.functiongraph.action;
import docking.ActionContext;
import ghidra.app.plugin.core.functiongraph.FGProvider;
import ghidra.graph.viewer.actions.VisualGraphSatelliteActionContext;
public class FunctionGraphSatelliteViewerActionContext extends ActionContext
implements VisualGraphSatelliteActionContext {
public FunctionGraphSatelliteViewerActionContext(FGProvider functionGraphProvider) {
super(functionGraphProvider, null);
}
}

View file

@ -0,0 +1,54 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.functiongraph.action;
import ghidra.app.context.ProgramActionContext;
import ghidra.app.plugin.core.functiongraph.FGProvider;
import ghidra.app.plugin.core.functiongraph.graph.vertex.FGVertex;
import java.util.Set;
public class FunctionGraphUneditableVertexLocationActionContext extends ProgramActionContext
implements FunctionGraphVertexLocationContextIf {
private final VertexActionContextInfo vertexInfo;
public FunctionGraphUneditableVertexLocationActionContext(
FGProvider functionGraphProvider, VertexActionContextInfo vertexInfo) {
super(functionGraphProvider, functionGraphProvider.getProgram());
if (vertexInfo == null) {
throw new NullPointerException("VertexActionContextInfo cannot be null");
}
this.vertexInfo = vertexInfo;
}
@Override
public Set<FGVertex> getSelectedVertices() {
return vertexInfo.getSelectedVertices();
}
@Override
public FGVertex getVertex() {
return vertexInfo.getActiveVertex();
}
@Override
public VertexActionContextInfo getVertexInfo() {
return vertexInfo;
}
}

View file

@ -0,0 +1,40 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.functiongraph.action;
import java.util.Set;
import ghidra.app.context.NavigatableActionContext;
import ghidra.app.plugin.core.functiongraph.FGProvider;
import ghidra.app.plugin.core.functiongraph.graph.vertex.FGVertex;
import ghidra.graph.viewer.actions.VisualGraphActionContext;
public class FunctionGraphValidGraphActionContext extends NavigatableActionContext
implements FunctionGraphValidGraphActionContextIf, VisualGraphActionContext {
private final Set<FGVertex> selectedVertices;
public FunctionGraphValidGraphActionContext(FGProvider functionGraphProvider,
Set<FGVertex> selectedVertices) {
super(functionGraphProvider, functionGraphProvider);
this.selectedVertices = selectedVertices;
}
@Override
public Set<FGVertex> getSelectedVertices() {
return selectedVertices;
}
}

View file

@ -0,0 +1,25 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.functiongraph.action;
import ghidra.app.plugin.core.functiongraph.graph.vertex.FGVertex;
import java.util.Set;
public interface FunctionGraphValidGraphActionContextIf {
public Set<FGVertex> getSelectedVertices();
}

View file

@ -0,0 +1,28 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.functiongraph.action;
import ghidra.app.plugin.core.functiongraph.graph.vertex.FGVertex;
import ghidra.graph.viewer.actions.VisualGraphVertexActionContext;
public interface FunctionGraphVertexLocationContextIf
extends FunctionGraphValidGraphActionContextIf, VisualGraphVertexActionContext<FGVertex> {
@Override
public FGVertex getVertex();
public VertexActionContextInfo getVertexInfo();
}

View file

@ -0,0 +1,28 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.functiongraph.action;
import ghidra.app.plugin.core.functiongraph.FGProvider;
import ghidra.app.plugin.core.functiongraph.graph.vertex.FGVertex;
public class FunctionGraphVertexLocationInFullViewModeActionContext
extends FunctionGraphEditableVertexLocationActionContext {
public FunctionGraphVertexLocationInFullViewModeActionContext(
FGProvider functionGraphProvider, FGVertex vertex ) {
super( functionGraphProvider, new FullScreenModeVertexActionContextInfo( vertex ) );
}
}

View file

@ -0,0 +1,68 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.functiongraph.action;
import java.util.Collections;
import java.util.Set;
import ghidra.app.plugin.core.functiongraph.graph.vertex.FGVertex;
import ghidra.program.model.address.AddressSet;
/**
* A container object use by the graph's context system to give actions the state of the graph.
*
* <P>This class has a vertex which is considered the active vertex, which may be the vertex
* under the mouse (for mouse driven data) or the focused vertex (for key event driven data).
*/
public class VertexActionContextInfo {
private final FGVertex activeVertex;
private final AddressSet hoveredVertexAddresses;
private final AddressSet selectedVertexAddresses;
private final Set<FGVertex> selectedVertices;
protected VertexActionContextInfo(FGVertex activeVertex) {
this(activeVertex, Collections.emptySet());
}
public VertexActionContextInfo(FGVertex activeVertex, Set<FGVertex> selectedVertices) {
this(activeVertex, selectedVertices, new AddressSet(), new AddressSet());
}
public VertexActionContextInfo(FGVertex activeVertex, Set<FGVertex> selectedVertices,
AddressSet hoveredVertexAddresses, AddressSet selectedVertexAddresses) {
this.activeVertex = activeVertex;
this.selectedVertices = selectedVertices;
this.hoveredVertexAddresses = hoveredVertexAddresses;
this.selectedVertexAddresses = selectedVertexAddresses;
}
public FGVertex getActiveVertex() {
return activeVertex;
}
public Set<FGVertex> getSelectedVertices() {
return selectedVertices;
}
public AddressSet getHoveredVertexAddresses() {
return hoveredVertexAddresses;
}
public AddressSet getSelectedVertexAddresses() {
return selectedVertexAddresses;
}
}

View file

@ -0,0 +1,315 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.functiongraph.graph;
import java.awt.*;
import java.util.*;
import java.util.Map.Entry;
import org.jdom.Element;
import edu.uci.ics.jung.algorithms.layout.Layout;
import edu.uci.ics.jung.visualization.RenderContext;
import edu.uci.ics.jung.visualization.picking.PickedState;
import edu.uci.ics.jung.visualization.renderers.DefaultEdgeLabelRenderer;
import edu.uci.ics.jung.visualization.renderers.Renderer;
import edu.uci.ics.jung.visualization.util.Caching;
import ghidra.app.plugin.core.functiongraph.graph.jung.renderer.FGEdgePaintTransformer;
import ghidra.app.plugin.core.functiongraph.graph.jung.renderer.FGVertexRenderer;
import ghidra.app.plugin.core.functiongraph.graph.jung.transformer.FGVertexPickableBackgroundPaintTransformer;
import ghidra.app.plugin.core.functiongraph.graph.layout.FGLayout;
import ghidra.app.plugin.core.functiongraph.graph.vertex.*;
import ghidra.app.plugin.core.functiongraph.mvc.*;
import ghidra.graph.viewer.*;
import ghidra.graph.viewer.layout.LayoutListener.ChangeType;
import ghidra.graph.viewer.layout.LayoutProvider;
import ghidra.graph.viewer.layout.VisualGraphLayout;
import ghidra.program.model.listing.Function;
import ghidra.program.util.ProgramLocation;
import ghidra.util.SystemUtilities;
import ghidra.util.UndefinedFunction;
public class FGComponent extends GraphComponent<FGVertex, FGEdge, FunctionGraph> {
private static final Color END_COLOR = new Color(255, 127, 127);
private static final Color START_COLOR = new Color(127, 255, 127);
private static final Color UNDEFINED_FUNCTION_COLOR = new Color(220, 220, 220);
/**
* A somewhat arbitrary value that is used to signal a 'big' graph, which is one that will
* slow down the system during the rendering process.
*/
private static final int REALLY_BIG_FG_VERTEX_COUNT = 75;
private final FGView functionGraphView;
private FGData functionGraphData;
private FunctionGraph functionGraph;
public FGComponent(FGView functionGraphView, FGData data,
LayoutProvider<FGVertex, FGEdge, FunctionGraph> layoutProvider) {
// Note: we cannot call super here, as we need to set our variables below before
// the base class builds.
// super(data.getFunctionGraph());
setGraph(data.getFunctionGraph());
this.functionGraphView = functionGraphView;
this.functionGraphData = data;
this.functionGraph = data.getFunctionGraph();
build();
String message = data.getMessage();
if (message != null) {
setStatusMessage(message);
}
// Note: can't do this here due to timing...restoring the groups may trigger
// callbacks into the view code, which at the point of this constructor has
// not yet been initialized
//
// restoreSettings();
}
@Override
protected FGVertex getInitialVertex() {
FGVertex focused = super.getInitialVertex();
if (focused == null) {
return functionGraph.getRootVertex();
}
return focused;
}
@Override
protected void zoomInCompletely(FGVertex v) {
super.zoomInCompletely(v);
FGVertex vertex = graph.getFocusedVertex();
if (vertex == null) {
return; // no graph
}
Rectangle cursorBounds = vertex.getCursorBounds();
if (cursorBounds != null) {
ensureCursorVisible(vertex);
return;
}
// do later; the cursor has not yet been rendered when called from the initialization phase
SystemUtilities.runSwingLater(() -> ensureCursorVisible(vertex));
}
public void restoreSettings() {
restoreGroupedVertices();
restoreVertexLocations();
}
private void restoreGroupedVertices() {
FGController controller = functionGraphView.getController();
Element groupedVertexXML = functionGraph.getSavedGroupedVertexSettings();
if (groupedVertexXML != null) {
GroupVertexSerializer.recreateGroupedVertices(controller, groupedVertexXML);
}
Element regroupVertexXML = functionGraph.getSavedGroupHistory();
if (regroupVertexXML != null) {
Collection<GroupHistoryInfo> history =
GroupVertexSerializer.recreateGroupHistory(controller, regroupVertexXML);
functionGraph.setGroupHistory(history);
}
}
private FGLayout getFunctionGraphLayout() {
Layout<FGVertex, FGEdge> graphLayout = primaryViewer.getVisualGraphLayout();
FGLayout layout = (FGLayout) graphLayout;
return layout;
}
private void restoreVertexLocations() {
Map<FGVertex, Point> vertexLocations = functionGraph.getSavedVertexLocations();
Set<Entry<FGVertex, Point>> entrySet = vertexLocations.entrySet();
FGLayout layout = getFunctionGraphLayout();
for (Entry<FGVertex, Point> entry : entrySet) {
layout.setLocation(entry.getKey(), entry.getValue(), ChangeType.RESTORE);
}
// hack to make sure that location algorithms use the correct position values
if (layout instanceof Caching) {
((Caching) layout).clear();
}
VisualGraphViewUpdater<FGVertex, FGEdge> viewUpdater = getViewUpdater();
if (isSatelliteShowing()) {
viewUpdater.fitGraphToViewerNow(satelliteViewer);
}
Set<FGEdge> edges = new HashSet<>();
Set<FGVertex> vertices = vertexLocations.keySet();
for (FGVertex vertex : vertices) {
edges.addAll(graph.getIncidentEdges(vertex));
}
viewUpdater.updateEdgeShapes(edges);
repaint();
}
public void clearLayoutPositionCache() {
functionGraph.clearSavedVertexLocations();
}
public void clearAllUserLayoutSettings() {
functionGraph.clearAllUserLayoutSettings();
}
@Override
protected void refreshCurrentLayout() {
super.refreshCurrentLayout();
functionGraphView.broadcastLayoutRefreshNeeded();
}
@Override
protected FGPrimaryViewer createPrimaryGraphViewer(VisualGraphLayout<FGVertex, FGEdge> layout,
Dimension viewerSize) {
FGPrimaryViewer viewer = new FGPrimaryViewer(this, layout, viewerSize);
RenderContext<FGVertex, FGEdge> renderContext = viewer.getRenderContext();
FGEdgePaintTransformer edgePaintTransformer =
new FGEdgePaintTransformer(getFucntionGraphOptions());
renderContext.setEdgeDrawPaintTransformer(edgePaintTransformer);
renderContext.setArrowDrawPaintTransformer(edgePaintTransformer);
renderContext.setArrowFillPaintTransformer(edgePaintTransformer);
Renderer<FGVertex, FGEdge> renderer = viewer.getRenderer();
renderer.setVertexRenderer(new FGVertexRenderer());
// for background colors when we are zoomed to far to render the listing
PickedState<FGVertex> pickedVertexState = viewer.getPickedVertexState();
renderContext.setVertexFillPaintTransformer(new FGVertexPickableBackgroundPaintTransformer(
pickedVertexState, Color.YELLOW, START_COLOR, END_COLOR));
// edge label rendering
com.google.common.base.Function<FGEdge, String> edgeLabelTransformer = e -> e.getLabel();
renderContext.setEdgeLabelTransformer(edgeLabelTransformer);
DefaultEdgeLabelRenderer edgeLabelRenderer = new DefaultEdgeLabelRenderer(Color.ORANGE);
edgeLabelRenderer.setRotateEdgeLabels(false);
renderContext.setEdgeLabelRenderer(edgeLabelRenderer);
// Give user notice when seeing the graph for a non-function.
Function function = functionGraphData.getFunction();
if (function instanceof UndefinedFunction) {
viewer.setBackground(UNDEFINED_FUNCTION_COLOR);
}
else {
viewer.setBackground(Color.WHITE);
}
return viewer;
}
@Override
protected SatelliteGraphViewer<FGVertex, FGEdge> createSatelliteGraphViewer(
GraphViewer<FGVertex, FGEdge> masterViewer, Dimension viewerSize) {
SatelliteGraphViewer<FGVertex, FGEdge> viewer =
super.createSatelliteGraphViewer(masterViewer, viewerSize);
RenderContext<FGVertex, FGEdge> renderContext = viewer.getRenderContext();
FGEdgePaintTransformer edgePaintTransformer =
new FGEdgePaintTransformer(getFucntionGraphOptions());
renderContext.setEdgeDrawPaintTransformer(edgePaintTransformer);
renderContext.setArrowDrawPaintTransformer(edgePaintTransformer);
renderContext.setArrowFillPaintTransformer(edgePaintTransformer);
PickedState<FGVertex> pickedVertexState = viewer.getPickedVertexState();
renderContext.setVertexFillPaintTransformer(new FGVertexPickableBackgroundPaintTransformer(
pickedVertexState, Color.YELLOW, START_COLOR, END_COLOR));
return viewer;
}
@Override
protected boolean isReallyBigData() {
return graph.getVertices().size() > REALLY_BIG_FG_VERTEX_COUNT;
}
//==================================================================================================
// Accessor Methods
//==================================================================================================
@Override
public void dispose() {
// big assumption - the components below will be disposed by the controller, so we don't
// dispose them, as they may be cached
functionGraph = null;
functionGraphData = null;
super.dispose();
}
//==================================================================================================
// FG-specific Client Methods
//==================================================================================================
public FunctionGraphOptions getFucntionGraphOptions() {
return functionGraphView.getController().getFunctionGraphOptions();
}
public void ensureCursorVisible(FGVertex vertex) {
VisualGraphViewUpdater<FGVertex, FGEdge> viewUpdater = getViewUpdater();
Rectangle cursorBounds = vertex.getCursorBounds();
if (cursorBounds != null) {
viewUpdater.ensureVertexAreaVisible(vertex, cursorBounds, null);
return;
}
// just make the entire vertex visible
RenderContext<FGVertex, FGEdge> renderContext = primaryViewer.getRenderContext();
com.google.common.base.Function<? super FGVertex, Shape> transformer =
renderContext.getVertexShapeTransformer();
Shape shape = transformer.apply(vertex);
Rectangle bounds = shape.getBounds();
viewUpdater.ensureVertexAreaVisible(vertex, bounds, null);
}
public void setVertexFocused(FGVertex v, ProgramLocation location) {
//
// NOTE: we must focus the vertex before we set the program location, as focusing the
// vertex will turn on the cursor, which allows the cursor to be properly set when we
// set the location. Reversing these two calls will not allow the cursor to be set
// properly.
//
boolean wasFocused = v.isFocused();
// As per the note above, the vertex must think it is focused to update its cursor, so
// focus it, but DO NOT send out the event. The 'pick to sync' will not trigger an
// API-wide notification of the focused vertex.
gPickedState.pickToSync(v);
v.setProgramLocation(location);
if (!wasFocused) {
// was not focused; signal to the external API that there is a new vertex in town
gPickedState.pickToActivate(v);
}
}
}

View file

@ -0,0 +1,34 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.functiongraph.graph;
import ghidra.app.plugin.core.functiongraph.graph.vertex.FGVertex;
import ghidra.graph.viewer.VisualEdge;
import ghidra.program.model.symbol.FlowType;
public interface FGEdge extends VisualEdge<FGVertex> {
public FlowType getFlowType();
public String getLabel();
public void setLabel(String label);
@SuppressWarnings("unchecked")
// Suppressing warning on the return type; we know our class is the right type
@Override
public FGEdge cloneEdge(FGVertex start, FGVertex end);
}

View file

@ -0,0 +1,192 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.functiongraph.graph;
import java.awt.geom.Point2D;
import java.util.*;
import ghidra.app.plugin.core.functiongraph.graph.vertex.FGVertex;
import ghidra.app.plugin.core.functiongraph.mvc.FunctionGraphOptions;
import ghidra.program.model.symbol.FlowType;
public class FGEdgeImpl implements FGEdge {
private final FGVertex startVertex;
private final FGVertex destinationVertex;
private final FlowType flowType;
private final FunctionGraphOptions options;
private List<Point2D> layoutArticulationPoints = new ArrayList<>();
boolean doHashCode = true;
int hashCode;
private boolean inActivePath = false;
private boolean selected = false;
private double emphasis = 0D;
private double alpha = 1D;
private String edgeLabel = null;
public FGEdgeImpl(FGVertex startVertex, FGVertex destinationVertex, FlowType flowType,
FunctionGraphOptions options) {
this.options = options;
this.startVertex = Objects.requireNonNull(startVertex, "Edge start vertex cannot be null");
this.destinationVertex =
Objects.requireNonNull(destinationVertex, "Edge end vertex cannot be null");
this.flowType = flowType;
}
@Override
public boolean isInActivePath() {
return inActivePath;
}
@Override
public void setInActivePath(boolean inActivePath) {
this.inActivePath = inActivePath;
}
@Override
public boolean isSelected() {
return selected;
}
@Override
public void setSelected(boolean selected) {
this.selected = selected;
}
@Override
public double getEmphasis() {
return emphasis;
}
@Override
public void setEmphasis(double emphasis) {
this.emphasis = emphasis;
}
@Override
public void setAlpha(double alpha) {
this.alpha = alpha;
}
@Override
public double getAlpha() {
return alpha;
}
@Override
public List<Point2D> getArticulationPoints() {
return layoutArticulationPoints;
}
@Override
public void setArticulationPoints(List<Point2D> layoutArticulationPoints) {
if (layoutArticulationPoints == null) {
this.layoutArticulationPoints = Collections.emptyList();
}
else {
this.layoutArticulationPoints = Collections.unmodifiableList(layoutArticulationPoints);
}
}
@Override
public FGVertex getStart() {
return startVertex;
}
@Override
public FGVertex getEnd() {
return destinationVertex;
}
@Override
public FlowType getFlowType() {
return flowType;
}
@SuppressWarnings("unchecked")
// Suppressing warning on the return type; we know our class is the right type
@Override
public FGEdgeImpl cloneEdge(FGVertex newStartVertex, FGVertex newDestinationVertex) {
FGEdgeImpl newEdge =
new FGEdgeImpl(newStartVertex, newDestinationVertex, flowType, options);
List<Point2D> newPoints = new ArrayList<>(layoutArticulationPoints.size());
for (Point2D point : layoutArticulationPoints) {
newPoints.add((Point2D) point.clone());
}
newEdge.layoutArticulationPoints = newPoints;
newEdge.alpha = alpha;
newEdge.inActivePath = inActivePath;
newEdge.selected = selected;
return newEdge;
}
@Override
public String getLabel() {
return edgeLabel;
}
@Override
public void setLabel(String label) {
this.edgeLabel = label;
}
//==================================================================================================
// Overridden Default Methods
//==================================================================================================
@Override
public int hashCode() {
if (doHashCode) {
final int destHashCode = destinationVertex.hashCode();
final int rearranged = destHashCode >> 16 | (destHashCode << 16);
hashCode = startVertex.hashCode() ^ rearranged;
doHashCode = false;
}
return hashCode;
}
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (obj == this) {
return true;
}
if (getClass() != obj.getClass()) {
return false;
}
FGEdgeImpl other = (FGEdgeImpl) obj;
return startVertex.equals(other.startVertex) &&
destinationVertex.equals(other.destinationVertex) && flowType.equals(other.flowType);
}
@Override
public String toString() {
return "(" + getStart() + " -> " + getEnd() + ")";
}
}

View file

@ -0,0 +1,39 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.functiongraph.graph;
import java.awt.Dimension;
import ghidra.app.plugin.core.functiongraph.graph.vertex.FGVertex;
import ghidra.app.plugin.core.functiongraph.graph.vertex.FGVertexTooltipProvider;
import ghidra.graph.viewer.*;
import ghidra.graph.viewer.layout.VisualGraphLayout;
public class FGPrimaryViewer extends GraphViewer<FGVertex, FGEdge> {
FGPrimaryViewer(FGComponent graphComponent, VisualGraphLayout<FGVertex, FGEdge> layout,
Dimension size) {
super(layout, size);
setVertexTooltipProvider(new FGVertexTooltipProvider());
setGraphOptions(graphComponent.getFucntionGraphOptions());
}
@Override
protected VisualGraphViewUpdater<FGVertex, FGEdge> createViewUpdater() {
return new FGViewUpdater(this, getVisualGraph());
}
}

View file

@ -0,0 +1,35 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.functiongraph.graph;
public enum FGVertexType {
//@formatter:off
BODY,
ENTRY,
EXIT,
GROUP,
SINGLETON;
//@formatter:on
public boolean isEntry() {
return this == ENTRY || this == SINGLETON;
}
public boolean isExit() {
return this == EXIT || this == SINGLETON;
}
}

View file

@ -0,0 +1,600 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.functiongraph.graph;
import java.awt.Point;
import java.util.*;
import org.jdom.Element;
import edu.uci.ics.jung.graph.Graph;
import ghidra.app.plugin.core.functiongraph.graph.layout.FGLayout;
import ghidra.app.plugin.core.functiongraph.graph.vertex.*;
import ghidra.app.plugin.core.functiongraph.mvc.*;
import ghidra.graph.GDirectedGraph;
import ghidra.graph.graphs.GroupingVisualGraph;
import ghidra.graph.viewer.layout.LayoutListener.ChangeType;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSet;
import ghidra.program.model.listing.Function;
import ghidra.program.util.ProgramSelection;
/**
* The Function Graph is a composite object that contains a Graph (for holding vertices and
* edges), a layout (for arranging the vertices and edges visually), settings (for things like
* coloring and grouping of nodes), and edge information (for things like finding paths between
* nodes).
*/
public class FunctionGraph extends GroupingVisualGraph<FGVertex, FGEdge> {
/** Keep a copy around for later retrieval */
private Set<FGEdge> ungroupedEdges = new HashSet<>();
private Set<GroupHistoryInfo> groupHistorySet = new HashSet<>();
private Function function;
private FGVertex rootVertex;
private FunctionGraphVertexAttributes settings; // refers to vertex location info
private FunctionGraphOptions options; // refers to layout options and such
private FGLayout graphLayout;// set after construction
private GroupListener groupListener = new GroupListener() {
@Override
public void groupDescriptionChanged(String oldText, String newText) {
updateGroupHistory(oldText, newText);
}
private void updateGroupHistory(String oldText, String newText) {
for (GroupHistoryInfo info : groupHistorySet) {
String currentDescription = info.getGroupDescription();
if (currentDescription.equals(oldText)) {
info.setGroupDescription(newText);
}
}
}
};
/**
* Construct a function graph with the given (optional) vertices and edges
*
* @param function the function upon which this graph is based
* @param settings the settings that will be used for vertices added in the future
* @param vertices the vertices
* @param edges the edges
*/
FunctionGraph(Function function, FunctionGraphVertexAttributes settings,
Collection<FGVertex> vertices, Collection<FGEdge> edges) {
this(function, settings);
vertices.forEach(v -> addVertex(v));
edges.forEach(e -> addEdge(e));
restoreSettings();
}
/**
* Construct an empty graph with data from this graph
*
* @param function the function upon which this graph is based
* @param settings the settings that will be used for vertices added in the future
*/
private FunctionGraph(Function function, FunctionGraphVertexAttributes settings) {
this.function = function;
this.settings = settings;
}
@Override
public FGVertex findMatchingVertex(FGVertex v) {
FGVertex matching = getVertexForAddress(v.getVertexAddress());
return matching;
}
@Override
public FGVertex findMatchingVertex(FGVertex v, Collection<FGVertex> ignore) {
FGVertex matching = getVertexForAddress(v.getVertexAddress(), ignore);
return matching;
}
public Function getFunction() {
return function;
}
public void restoreSettings() {
for (FGVertex vertex : getVertices()) {
vertex.readSettings(settings);
}
}
public void saveSettings() {
saveVertexSettings();
settings.save();
}
private void saveVertexSettings() {
for (FGVertex vertex : getVertices()) {
vertex.writeSettings(settings);
}
}
public void clearVertexColor(FGVertex vertex) {
settings.clearVertexColor(vertex.getVertexAddress());
}
public FunctionGraphVertexAttributes getSettings() {
return settings;
}
public Element getSavedGroupedVertexSettings() {
return settings.getGroupedVertexSettings(this);
}
public Element getSavedGroupHistory() {
return settings.getRegroupVertexSettings(this);
}
/**
* Returns any saved location information for the vertices of this graph. Location information
* is saved when users manually move vertices in the graph.
*
* @return any saved location information for the vertices of this graph.
*/
public Map<FGVertex, Point> getSavedVertexLocations() {
return settings.getVertexLocations(this);
}
private void clearSavedVertexLocation(FGVertex vertex) {
settings.clearVertexLocation(vertex);
}
// TODO make private?
public void clearSavedVertexLocations() {
settings.clearVertexLocations(this);
}
// TODO make private?
public void clearAllUserLayoutSettings() {
clearSavedVertexLocations();
settings.clearGroupSettings(this);
settings.clearRegroupSettings(this);
}
@Override
public void vertexLocationChanged(FGVertex v, Point point, ChangeType changeType) {
if (changeType == ChangeType.USER) {
settings.putVertexLocation(v, point);
}
}
public FunctionGraphOptions getOptions() {
return options;
}
public void setOptions(FunctionGraphOptions options) {
this.options = options;
}
public void setGraphLayout(FGLayout layout) {
this.graphLayout = layout;
}
@Override
public FGLayout getLayout() {
return graphLayout;
}
public FGVertex getVertexForAddress(Address address) {
return getVertexForAddress(address, Collections.emptySet());
}
public FGVertex getVertexForAddress(Address address, Collection<FGVertex> ignore) {
for (FGVertex v : getVertices()) {
if (v.containsAddress(address) && !ignore.contains(v)) {
return v;
}
}
return null;
}
public void setProgramSelection(ProgramSelection selection) {
for (FGVertex vertex : getVertices()) {
vertex.setProgramSelection(selection);
}
}
public void setProgramHighlight(ProgramSelection highlight) {
for (FGVertex vertex : getVertices()) {
vertex.setProgramHighlight(highlight);
}
}
/**
* This method returns all vertices known by this class. This differs from the vertices
* returned from {@link Graph} in that those may be a smaller subset of the collection returned
* here. Due to graph manipulation after creation time (e.g., vertex grouping), the
* vertices known by the {@link Graph} may not include all vertices created at the same time
* as the graph and any others created by graph mutating operations <b>that are not the
* result of a grouping operation</b>.
*
* @return all vertices known by this class, visible or not.
*/
public Set<FGVertex> getUngroupedVertices() {
return generateUngroupedVertices();
}
private Set<FGVertex> generateUngroupedVertices() {
Set<FGVertex> ungrouped = new HashSet<>();
for (FGVertex v : getVertices()) {
accumulateUngroupedVertices(v, ungrouped);
}
return ungrouped;
}
private void accumulateUngroupedVertices(FGVertex v, Set<FGVertex> ungrouped) {
if (!(v instanceof GroupedFunctionGraphVertex)) {
ungrouped.add(v);
return;
}
GroupedFunctionGraphVertex gv = (GroupedFunctionGraphVertex) v;
Set<FGVertex> grouped = gv.getVertices();
for (FGVertex child : grouped) {
accumulateUngroupedVertices(child, ungrouped);
}
}
/**
* This method returns all edges known by this class. This differs from the edges
* returned from {@link Graph} in that those may be a smaller subset of the collection returned
* here. Due to graph manipulation after creation time (e.g., vertex grouping), the
* edges known by the {@link Graph} may not include all edges created at the same time
* as the graph and any others created by graph mutating operations <b>that are not the
* result of a grouping operation</b>.
*
* @return all edges known by this class, visible or not.
*/
public Set<FGEdge> getUngroupedEdges() {
return generateUngroupedEdges();
}
private Set<FGEdge> generateUngroupedEdges() {
Set<FGEdge> ungrouped = new HashSet<>();
for (FGVertex v : getVertices()) {
accumulateUngroupedEdges(v, ungrouped);
}
ungrouped.addAll(getCurrentUngroupedEdges());
return ungrouped;
}
private Collection<FGEdge> getCurrentUngroupedEdges() {
Collection<FGEdge> result = new HashSet<>(getEdges());
result.removeIf(e -> e.getStart() instanceof GroupedFunctionGraphVertex ||
e.getEnd() instanceof GroupedFunctionGraphVertex);
return result;
}
private void accumulateUngroupedEdges(FGVertex v, Set<FGEdge> ungrouped) {
if (!(v instanceof GroupedFunctionGraphVertex)) {
return;
}
GroupedFunctionGraphVertex gv = (GroupedFunctionGraphVertex) v;
Set<FGEdge> gvEdges = gv.getUngroupedEdges();
ungrouped.addAll(gvEdges);
Set<FGVertex> grouped = gv.getVertices();
for (FGVertex child : grouped) {
accumulateUngroupedEdges(child, ungrouped);
}
}
/**
* Returns history objects that represent previously grouped vertices and their description.
*
* @param vertex the vertex for which to check group belonging
* @return history objects that represent previously grouped vertices and their description.
*/
public GroupHistoryInfo getGroupHistory(FGVertex vertex) {
return vertex.getGroupInfo();
}
public Collection<GroupHistoryInfo> getGroupHistory() {
return Collections.unmodifiableSet(groupHistorySet);
}
public void setGroupHistory(Collection<GroupHistoryInfo> history) {
this.groupHistorySet = new HashSet<>(history);
for (GroupHistoryInfo info : history) {
Set<FGVertex> infoVertices = info.getVertices();
notifyVerticesOfGroupAssociation(infoVertices, info);
}
}
public void removeFromGroupHistory(FGVertex vertex) {
removeFromAllHistory(vertex);
}
/**
* A signal that the given group has been 'regrouped'.
*
* @param group the restored group
*/
public void groupRestored(GroupedFunctionGraphVertex group) {
group.addGroupListener(groupListener);
}
/**
* A signal to this graph that a group has been created and added to the graph.
* @param group the ungrouped group
*/
public void groupAdded(GroupedFunctionGraphVertex group) {
group.addGroupListener(groupListener);
removeAssociatedGroups(group.getVertices());
}
/**
* A signal to this graph that a group has been ungrouped.
* @param group the ungrouped group
*/
public void groupRemoved(GroupedFunctionGraphVertex group) {
group.removeGroupListener(groupListener);
Set<FGVertex> groupVertices = group.getVertices();
if (hasExistingGroupInfo(groupVertices)) {
return;
}
GroupHistoryInfo info = new GroupHistoryInfo(this, group);
notifyVerticesOfGroupAssociation(groupVertices, info);
groupHistorySet.add(info);
}
private boolean hasExistingGroupInfo(Set<FGVertex> groupVertices) {
Iterator<FGVertex> iterator = groupVertices.iterator();
if (!iterator.hasNext()) {
return false;// this should never happen
}
return iterator.next().getGroupInfo() != null;
}
/**
* Any time a vertex is grouped we want to make sure that any previous group affiliations
* are removed.
*
* @param groupVertices the new grouping of vertices
*/
private void removeAssociatedGroups(Collection<FGVertex> groupVertices) {
for (FGVertex vertex : groupVertices) {
Iterator<GroupHistoryInfo> iterator = groupHistorySet.iterator();
for (; iterator.hasNext();) {
GroupHistoryInfo info = iterator.next();
if (!info.contains(vertex)) {
continue;
}
//
// NOTE: this code is setup such that for any given GroupHistoryInfo, if any of
// its internal vertices are *moved to a new group*, then the entire history
// is removed. We could do something different, like simply remove the
// vertex from the history entry. For now, the current code seems simpler
// in that once you alter an 'uncollapsed' group entry, the whole thing
// goes away.
//
// SUBNOTE: if a vertex is manually removed from an 'uncollapsed' group, then the
// history is NOT removed.
//
notifyVerticesOfGroupAssociation(info.getVertices(), null);
iterator.remove();
}
}
}
private void removeFromAllHistory(FGVertex vertex) {
boolean didRemove = false;
Iterator<GroupHistoryInfo> iterator = groupHistorySet.iterator();
for (; iterator.hasNext();) {
GroupHistoryInfo info = iterator.next();
if (!info.contains(vertex)) {
continue;
}
info.removeVertex(vertex);
didRemove = true;
// we want to update the vertices associated with the info, which lets them update
// their display with any new info
notifyVerticesOfGroupAssociation(info.getVertices(), info);
}
if (didRemove) {
notifyVerticesOfGroupAssociation(Arrays.asList(vertex), null);
}
}
private void notifyVerticesOfGroupAssociation(Collection<FGVertex> groupVertices,
GroupHistoryInfo groupInfo) {
for (FGVertex vertex : groupVertices) {
vertex.updateGroupAssociationStatus(groupInfo);
}
}
public FGVertex getRootVertex() {
return rootVertex;
}
public void setRootVertex(FGVertex rootVertex) {
if (this.rootVertex != null) {
throw new IllegalStateException("Cannot set the root vertex more than once!");
}
this.rootVertex = rootVertex;
//
// Unusual Code Alert!: we are putting into the settings an object that will pull the
// group state of this graph at the time of saving. This allows
// other clients to clear the settings object, which will also
// clear this lazy loading object, thus preventing saving. This
// differs a bit from the normal settings mechanism, which is based
// upon either 1) updating the settings object as the data changes, or
// 2) pulling the data to save on command.
//
settings.putGroupedVertexSettings(this, new LazyGraphGroupSaveableXML(this));
settings.putRegroupSettings(this, new LazyGraphRegroupSaveableXML(this));
}
public ProgramSelection getProgramSelectionForAllVertices() {
AddressSet addresses = new AddressSet();
for (FGVertex vertex : getVertices()) {
ProgramSelection programSelection = vertex.getProgramSelection();
if (programSelection == null) {
continue;
}
addresses.add(programSelection);
}
return new ProgramSelection(addresses);
}
public Set<FGVertex> getEntryPoints() {
HashSet<FGVertex> result = new LinkedHashSet<>();
for (FGVertex vertex : getVertices()) {
FGVertexType vertexType = vertex.getVertexType();
if (vertexType.isEntry()) {
result.add(vertex);
}
else if (vertexType == FGVertexType.GROUP) {
if (groupContainsEntry((GroupedFunctionGraphVertex) vertex)) {
result.add(vertex);
}
}
}
return result;
}
private boolean groupContainsEntry(GroupedFunctionGraphVertex vertex) {
Set<FGVertex> groupVertices = vertex.getVertices();
for (FGVertex groupedVertex : groupVertices) {
FGVertexType vertexType = groupedVertex.getVertexType();
if (vertexType.isEntry()) {
return true;
}
else if (vertexType == FGVertexType.GROUP) {
return groupContainsEntry((GroupedFunctionGraphVertex) groupedVertex);
}
}
return false;
}
private boolean groupContainsExit(GroupedFunctionGraphVertex vertex) {
Set<FGVertex> groupVertices = vertex.getVertices();
for (FGVertex groupedVertex : groupVertices) {
FGVertexType vertexType = groupedVertex.getVertexType();
if (vertexType.isExit()) {
return true;
}
else if (vertexType == FGVertexType.GROUP) {
return groupContainsExit((GroupedFunctionGraphVertex) groupedVertex);
}
}
return false;
}
public Set<FGVertex> getExitPoints() {
HashSet<FGVertex> result = new LinkedHashSet<>();
for (FGVertex vertex : getVertices()) {
if (vertex.getVertexType().isExit()) {
result.add(vertex);
}
else if (vertex.getVertexType() == FGVertexType.GROUP) {
if (groupContainsExit((GroupedFunctionGraphVertex) vertex)) {
result.add(vertex);
}
}
}
return result;
}
@Override
public void dispose() {
//
// Let's go a bit overboard and help the garbage collector cleanup by nulling out
// references and removing the data from Jung's graph
//
for (FGVertex vertex : getVertices()) {
vertex.dispose();
}
vertices.clear();
edges.clear();
ungroupedEdges.clear();
groupHistorySet.clear();
ungroupedEdges = null;
groupHistorySet = null;
focusedVertex = null;
rootVertex = null;
settings = null;
graphLayout.dispose();
graphLayout = null;
super.dispose();
}
@Override
protected void verticesRemoved(Collection<FGVertex> removed) {
removed.forEach(v -> {
clearSavedVertexLocation(v);
});
super.fireVerticesRemoved(removed);
}
/**
* Creates a copy of the given graph <b>while using the exact vertex and edge instances
* used in the original graph</b>.
*
* @return the newly created graph
*/
@Override
public FunctionGraph copy() {
Collection<FGVertex> v = getVertices();
Collection<FGEdge> e = getEdges();
FunctionGraph newGraph = new FunctionGraph(getFunction(), getSettings(), v, e);
newGraph.setOptions(getOptions());
return newGraph;
}
//==================================================================================================
// Overridden Methods
//==================================================================================================
@Override
public GDirectedGraph<FGVertex, FGEdge> emptyCopy() {
return new FunctionGraph(function, settings);
}
}

View file

@ -0,0 +1,358 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.functiongraph.graph;
import static ghidra.app.plugin.core.functiongraph.graph.FGVertexType.*;
import java.util.*;
import org.apache.commons.collections4.BidiMap;
import org.apache.commons.collections4.bidimap.DualHashBidiMap;
import edu.uci.ics.jung.graph.Graph;
import ghidra.app.plugin.core.functiongraph.graph.layout.*;
import ghidra.app.plugin.core.functiongraph.graph.vertex.FGVertex;
import ghidra.app.plugin.core.functiongraph.graph.vertex.ListingFunctionGraphVertex;
import ghidra.app.plugin.core.functiongraph.mvc.*;
import ghidra.program.model.address.AddressSetView;
import ghidra.program.model.block.*;
import ghidra.program.model.listing.Function;
import ghidra.program.model.listing.Program;
import ghidra.program.model.symbol.FlowType;
import ghidra.util.Msg;
import ghidra.util.SystemUtilities;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;
public class FunctionGraphFactory {
/**
* Clones the given function graph, creating a new graph with cloned versions of each
* vertex and edge.
*
* @param data the data of the current graph
* @param newController controller for the new graph
* @return a new data object containing the new graph
*/
public static FGData createClonedGraph(FGData data, FGController newController) {
Function function = data.getFunction();
FunctionGraph originalGraph = data.getFunctionGraph();
FunctionGraph newGraph = cloneGraph(originalGraph, newController);
cloneLayout(originalGraph, newGraph);
return new FGData(function, newGraph);
}
private static FunctionGraph cloneGraph(FunctionGraph originalGraph,
FGController newController) {
Map<FGVertex, FGVertex> oldToNewCloneMap =
cloneVertices(originalGraph.getUngroupedVertices(), newController);
Collection<FGVertex> vertices = oldToNewCloneMap.values();
Set<FGEdge> originalEdges = originalGraph.getUngroupedEdges();
Collection<FGEdge> edges =
cloneEdges(originalGraph, oldToNewCloneMap, originalEdges, newController);
Program program = newController.getProgram();
FunctionGraphVertexAttributes newSettings = cloneSettings(originalGraph, program);
Function function = originalGraph.getFunction();
FunctionGraph newGraph = new FunctionGraph(function, newSettings, vertices, edges);
newGraph.setOptions(originalGraph.getOptions());
FGVertex entry = newGraph.getVertexForAddress(function.getEntryPoint());
newGraph.setRootVertex(entry);
return newGraph;
}
private static void cloneLayout(FunctionGraph originalGraph, FunctionGraph newGraph) {
FGLayout originalLayout = originalGraph.getLayout();
FGLayout newLayout = originalLayout.cloneLayout(newGraph);
newGraph.setGraphLayout(newLayout);
}
private static Map<FGVertex, FGVertex> cloneVertices(Collection<FGVertex> vertices,
FGController newController) {
Map<FGVertex, FGVertex> map = new HashMap<>();
for (FGVertex vertex : vertices) {
FGVertex newVertex = vertex.cloneVertex(newController);
map.put(vertex, newVertex);
}
return map;
}
private static Collection<FGEdge> cloneEdges(FunctionGraph currentGraph,
Map<FGVertex, FGVertex> oldToNewCloneMap, Collection<FGEdge> originalEdges,
FGController newController) {
List<FGEdge> edges = new ArrayList<>();
for (FGEdge edge : originalEdges) {
FGVertex newStartVertex = oldToNewCloneMap.get(edge.getStart());
FGVertex newVertex = oldToNewCloneMap.get(edge.getEnd());
if (newStartVertex == null || newVertex == null) {
Msg.debug(null, "no nulls!");
}
FGEdge newEdge = edge.cloneEdge(newStartVertex, newVertex);
edges.add(newEdge);
}
return edges;
}
private static FunctionGraphVertexAttributes cloneSettings(FunctionGraph originalFunctionGraph,
Program program) {
originalFunctionGraph.saveSettings();
return new FunctionGraphVertexAttributes(program);
}
/**
* Creates a new function graph for the given function
*
* @param function the function to graph
* @param controller the controller needed by the function graph
* @param monitor the task monitor
* @return the new graph
* @throws CancelledException if the task is cancelled via the monitor
*/
public static FGData createNewGraph(Function function, FGController controller, Program program,
TaskMonitor monitor) throws CancelledException {
FunctionGraph graph = createGraph(function, controller, monitor);
if (graph.getVertices().size() == 0) {
return new EmptyFunctionGraphData("No data in function: " + function.getName());
}
if (!isEntryPointValid(function, controller, monitor)) {
return new EmptyFunctionGraphData(
"No instruction at function entry point: " + function.getName());
}
FGVertex functionEntryVertex = graph.getVertexForAddress(function.getEntryPoint());
graph.setRootVertex(functionEntryVertex);
graph.setOptions(controller.getFunctionGraphOptions());
// doing this here will keep the potentially slow work off of the Swing thread, as the
// results are cached
String errorMessage = layoutGraph(function, controller, graph, monitor);
return new FGData(function, graph, errorMessage);
}
/*
* Returns true if the given function has an entry point that represents a valid instruction
* (cannot be undefined).
*/
private static boolean isEntryPointValid(Function function, FGController controller,
TaskMonitor monitor) throws CancelledException {
CodeBlockModel blockModel = new BasicBlockModel(controller.getProgram());
CodeBlock[] codeBlock =
blockModel.getCodeBlocksContaining(function.getEntryPoint(), monitor);
if (codeBlock == null || codeBlock.length == 0) {
monitor.setMessage("No instruction at function entry point.");
return false;
}
return true;
}
private static String layoutGraph(Function function, FGController controller,
FunctionGraph functionGraph, TaskMonitor monitor) throws CancelledException {
if (!performSwingThreadRequiredWork(functionGraph)) {
return null;// shouldn't happen
}
monitor.setMessage("Performing graph layout...");
FGLayoutProvider layoutProvider = controller.getLayoutProvider();
try {
FGLayout layout = layoutProvider.getLayout(functionGraph, monitor);
functionGraph.setGraphLayout(layout);
return null;
}
catch (CancelledException ce) {
throw ce;
}
catch (Exception e) {
Msg.error(FunctionGraphFactory.class,
"Exception performing graph layout for function " + function, e);
controller.setStatusMessage("Problem performing graph layout--try another layout");
}
//
// Setup a default/dummy layout
//
functionGraph.setGraphLayout(new EmptyLayout(functionGraph));
return "Problem performing graph layout using the \"" + layoutProvider.getLayoutName() +
"\" (try another layout)";
}
private static boolean performSwingThreadRequiredWork(FunctionGraph functionGraph) {
final Collection<FGVertex> vertices = functionGraph.getVertices();
try {
SystemUtilities.runSwingNow(() -> {
for (FGVertex v : vertices) {
v.getComponent();
}
});
return true;
}
catch (Exception e) {
return false;
}
}
private static boolean isEntry(CodeBlock codeBlock) {
boolean isSource = true;
try {
CodeBlockReferenceIterator iter = codeBlock.getSources(TaskMonitor.DUMMY);
while (iter.hasNext()) {
isSource = false;
if (iter.next().getFlowType().isCall()) {
// any calls into a code block will make it an 'entry'
return true;
}
}
}
catch (CancelledException e) {
// will never happen, because I don't have a monitor
}
return isSource;
}
private static FunctionGraph createGraph(Function function, FGController controller,
TaskMonitor monitor) throws CancelledException {
BidiMap<CodeBlock, FGVertex> vertices = createVertices(function, controller, monitor);
Collection<FGEdge> edges = createdEdges(vertices, controller, monitor);
FunctionGraphVertexAttributes settings =
new FunctionGraphVertexAttributes(controller.getProgram());
FunctionGraph graph = new FunctionGraph(function, settings, vertices.values(), edges);
for (FGVertex vertex : vertices.values()) {
vertex.setVertexType(getVertexType(graph, vertex));
}
return graph;
}
private static Collection<FGEdge> createdEdges(BidiMap<CodeBlock, FGVertex> vertices,
FGController controller, TaskMonitor monitor) throws CancelledException {
List<FGEdge> edges = new ArrayList<>();
for (FGVertex startVertex : vertices.values()) {
Collection<FGEdge> vertexEdges =
getEdgesForStartVertex(vertices, startVertex, controller, monitor);
edges.addAll(vertexEdges);
}
return edges;
}
private static Collection<FGEdge> getEdgesForStartVertex(
BidiMap<CodeBlock, FGVertex> blockToVertexMap, FGVertex startVertex,
FGController controller, TaskMonitor monitor) throws CancelledException {
List<FGEdge> edges = new ArrayList<>();
CodeBlock codeBlock = blockToVertexMap.getKey(startVertex);
CodeBlockReferenceIterator destinations = codeBlock.getDestinations(monitor);
for (; destinations.hasNext();) {
CodeBlockReference reference = destinations.next();
CodeBlock destinationBlock = reference.getDestinationBlock();
FGVertex destinationVertex = blockToVertexMap.get(destinationBlock);
if (destinationVertex == null) {
continue;// no vertex means the code block is not in our function
}
edges.add(new FGEdgeImpl(startVertex, destinationVertex, reference.getFlowType(),
controller.getFunctionGraphOptions()));
}
return edges;
}
private static BidiMap<CodeBlock, FGVertex> createVertices(Function function,
final FGController controller, TaskMonitor monitor) throws CancelledException {
BidiMap<CodeBlock, FGVertex> vertices = new DualHashBidiMap<>();
CodeBlockModel blockModel = new BasicBlockModel(controller.getProgram());
AddressSetView addresses = function.getBody();
CodeBlockIterator iterator = blockModel.getCodeBlocksContaining(addresses, monitor);
monitor.initialize(addresses.getNumAddresses());
for (; iterator.hasNext();) {
CodeBlock codeBlock = iterator.next();
FlowType flowType = codeBlock.getFlowType();
boolean isEntry = isEntry(codeBlock);
FGVertex vertex =
new ListingFunctionGraphVertex(controller, codeBlock, flowType, isEntry);
vertices.put(codeBlock, vertex);
long blockAddressCount = codeBlock.getNumAddresses();
long currentProgress = monitor.getProgress();
monitor.setProgress(currentProgress + blockAddressCount);
}
return vertices;
}
private static FGVertexType getVertexType(Graph<FGVertex, FGEdge> graph, FGVertex v) {
boolean isEntry = v.isEntry();
boolean isExit = false;
FlowType flowType = v.getFlowType();
if (flowType.isTerminal()) {
isExit = true;
}
if (graph.getOutEdges(v).isEmpty()) {
isExit = true;
}
FGVertexType type = BODY;
if (isEntry) {
if (isExit) {
type = SINGLETON;
}
else {
type = ENTRY;
}
}
else if (isExit) {
type = EXIT;
}
return type;
}
}

View file

@ -0,0 +1,94 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.functiongraph.graph.jung.algorithms;
import java.util.*;
public class CompositeVertex<V, E> {
private Collection<V> vertices;
private Collection<E> internalEdges;
private Collection<CompositeVertex<V, E>> nestedComposites;
boolean doHashCode = true;
int hashCode;
@Override
public int hashCode() {
if (doHashCode) {
int sa = 0;
for (V v : collectSimpleVertices()) {
final int hc = v.hashCode();
final int rearranged = hc >> (32 - sa) | (hc << sa);
hashCode ^= rearranged;
sa += 11;
while (sa > 31) {
sa -= 32;
}
}
doHashCode = false;
}
return hashCode;
}
public CompositeVertex(V vertex) {
this(Arrays.asList(vertex), new ArrayList<CompositeVertex<V, E>>(0));
}
public CompositeVertex(Collection<CompositeVertex<V, E>> nestedComposites) {
this(new ArrayList<V>(0), nestedComposites);
}
public CompositeVertex(Collection<V> vertices,
Collection<CompositeVertex<V, E>> nestedComposites) {
this.vertices = Collections.unmodifiableCollection(vertices);
this.internalEdges = new ArrayList<E>();
this.nestedComposites = Collections.unmodifiableCollection(nestedComposites);
}
public void addInternalEdge(E edge) {
internalEdges.add(edge);
}
public Set<V> collectSimpleVertices() {
HashSet<V> result = new HashSet<V>();
result.addAll(vertices);
for (CompositeVertex<V, E> composite : nestedComposites) {
Set<V> simpleVertices = composite.collectSimpleVertices();
result.addAll(simpleVertices);
}
return result;
}
public Set<E> collectInternalEdges() {
HashSet<E> result = new HashSet<E>();
result.addAll(internalEdges);
for (CompositeVertex<V, E> composite : nestedComposites) {
Set<E> edges = composite.collectInternalEdges();
result.addAll(edges);
}
return result;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("V: ");
sb.append(collectSimpleVertices());
sb.append(" E: ");
sb.append(collectInternalEdges());
return sb.toString();
}
}

View file

@ -0,0 +1,37 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.functiongraph.graph.jung.renderer;
import ghidra.app.plugin.core.functiongraph.graph.FGEdge;
import ghidra.app.plugin.core.functiongraph.graph.vertex.FGVertex;
import ghidra.graph.viewer.shape.ArticulatedEdgeTransformer;
import ghidra.program.model.symbol.FlowType;
public class FGArticulatedEdgeTransformer extends ArticulatedEdgeTransformer<FGVertex, FGEdge> {
@Override
public int getOverlapOffset(FGEdge edge) {
FlowType flowType = edge.getFlowType();
if (!flowType.isUnConditional() && flowType.isJump()) {
return -OVERLAPPING_EDGE_OFFSET;
}
else if (flowType.isFallthrough()) {
return OVERLAPPING_EDGE_OFFSET;
}
return 0;
}
}

View file

@ -0,0 +1,41 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.functiongraph.graph.jung.renderer;
import java.awt.Color;
import java.awt.Paint;
import com.google.common.base.Function;
import ghidra.app.plugin.core.functiongraph.graph.FGEdge;
import ghidra.app.plugin.core.functiongraph.mvc.FunctionGraphOptions;
import ghidra.program.model.symbol.FlowType;
public class FGEdgePaintTransformer implements Function<FGEdge, Paint> {
private FunctionGraphOptions options;
public FGEdgePaintTransformer(FunctionGraphOptions options) {
this.options = options;
}
@Override
public Paint apply(FGEdge e) {
FlowType flowType = e.getFlowType();
Color color = options.getColor(flowType);
return color;
}
}

View file

@ -0,0 +1,49 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.functiongraph.graph.jung.renderer;
import java.awt.Color;
import edu.uci.ics.jung.graph.Graph;
import ghidra.app.plugin.core.functiongraph.graph.FGEdge;
import ghidra.app.plugin.core.functiongraph.graph.FunctionGraph;
import ghidra.app.plugin.core.functiongraph.graph.vertex.FGVertex;
import ghidra.app.plugin.core.functiongraph.mvc.FunctionGraphOptions;
import ghidra.graph.viewer.renderer.ArticulatedEdgeRenderer;
/**
* A renderer used by the Function Graph API to provide additional edge coloring, as
* determined by the {@link FunctionGraphOptions}.
*/
public class FGEdgeRenderer extends ArticulatedEdgeRenderer<FGVertex, FGEdge> {
@Override
public Color getBaseColor(Graph<FGVertex, FGEdge> g, FGEdge e) {
FunctionGraphOptions options = getOptions(g);
return options.getColor(e.getFlowType());
}
@Override
public Color getHighlightColor(Graph<FGVertex, FGEdge> g, FGEdge e) {
FunctionGraphOptions options = getOptions(g);
return options.getHighlightColor(e.getFlowType());
}
private FunctionGraphOptions getOptions(Graph<FGVertex, FGEdge> g) {
FunctionGraph fg = (FunctionGraph) g;
return fg.getOptions();
}
}

View file

@ -0,0 +1,120 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.functiongraph.graph.jung.renderer;
import java.awt.Rectangle;
import java.awt.Shape;
import java.util.Set;
import edu.uci.ics.jung.algorithms.layout.Layout;
import edu.uci.ics.jung.visualization.RenderContext;
import edu.uci.ics.jung.visualization.transform.shape.GraphicsDecorator;
import ghidra.app.plugin.core.functiongraph.graph.FGEdge;
import ghidra.app.plugin.core.functiongraph.graph.vertex.FGVertex;
import ghidra.app.plugin.core.functiongraph.graph.vertex.GroupedFunctionGraphVertex;
import ghidra.graph.viewer.vertex.VisualVertexRenderer;
public class FGVertexRenderer extends VisualVertexRenderer<FGVertex, FGEdge> {
@Override
protected void paintDropShadow(RenderContext<FGVertex, FGEdge> rc, GraphicsDecorator g,
Shape shape, FGVertex vertex) {
Rectangle bounds = shape.getBounds();
if (vertex instanceof GroupedFunctionGraphVertex) {
// paint depth images offset from main vertex
Rectangle originalBounds = bounds;
Rectangle paintBounds = (Rectangle) originalBounds.clone();
Set<FGVertex> vertices = ((GroupedFunctionGraphVertex) vertex).getVertices();
int offset = 15;
int size = vertices.size();
if (size > 3) {
size = size / 3; // don't paint one-for-one, that's a bit much
size = Math.max(size, 2);
}
int currentOffset = offset * size;
for (int i = size - 1; i >= 0; i--) {
paintBounds.x = originalBounds.x + currentOffset;
paintBounds.y = originalBounds.y + currentOffset;
currentOffset -= offset;
super.paintDropShadow(rc, g, paintBounds);
}
}
super.paintDropShadow(rc, g, bounds);
}
@Override
protected void paintVertexOrVertexShape(RenderContext<FGVertex, FGEdge> rc, GraphicsDecorator g,
Layout<FGVertex, FGEdge> layout, FGVertex vertex, Shape compactShape, Shape fullShape) {
if (isScaledPastVertexPaintingThreshold(rc)) {
paintScaledVertex(rc, vertex, g, compactShape);
return;
}
if (vertex instanceof GroupedFunctionGraphVertex) {
// paint depth images offset from main vertex
Rectangle originalBounds = fullShape.getBounds();
Rectangle paintBounds = (Rectangle) originalBounds.clone();
Set<FGVertex> vertices = ((GroupedFunctionGraphVertex) vertex).getVertices();
int offset = 5;
int size = vertices.size();
if (size > 3) {
size = size / 3; // don't paint one-for-one, that's a bit much
size = Math.max(size, 2); // we want at least 2, to give some depth
}
int currentOffset = offset * size;
for (int i = size - 1; i >= 0; i--) {
paintBounds.x = originalBounds.x + currentOffset;
paintBounds.y = originalBounds.y + currentOffset;
currentOffset -= offset;
paintVertex(rc, g, vertex, paintBounds, layout);
}
}
// paint one final time
Rectangle bounds = fullShape.getBounds();
paintVertex(rc, g, vertex, bounds, layout);
}
@Override
protected void paintVertex(RenderContext<FGVertex, FGEdge> rc, GraphicsDecorator g,
FGVertex vertex, Rectangle bounds, Layout<FGVertex, FGEdge> layout) {
refreshVertexAsNeeded(vertex);
vertex.setShowing(true); // hack to make sure the component paints
super.paintVertex(rc, g, vertex, bounds, layout);
vertex.setShowing(false); // turn off painting (this fix keeps tooltips from painting)
}
/**
* <center>Odd Code Alert!</center><br><p>
* We use a lazy model for rebuilding the Listings inside of each vertex as the model's
* data changes. We need a good place to tell the vertex to rebuild itself. We
* decided that placing the call to rebuild here inside of the paint code is the best
* place because it will only happen when the vertex is actually being painted (i.e.,
* this paint call does not happen if the vertex is outside of the viewing area or
* if it is scaled past the interaction threshold). Finally, calling this method is
* not a performance problem, as the vertex's model will not rebuild itself if no
* changes have been made
*
*/
private void refreshVertexAsNeeded(FGVertex vertex) {
vertex.refreshModel();
}
}

View file

@ -0,0 +1,86 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.functiongraph.graph.jung.transformer;
import java.awt.Color;
import java.awt.Paint;
import com.google.common.base.Function;
import edu.uci.ics.jung.visualization.picking.PickedInfo;
import ghidra.app.plugin.core.functiongraph.graph.FGVertexType;
import ghidra.app.plugin.core.functiongraph.graph.vertex.FGVertex;
public class FGVertexPickableBackgroundPaintTransformer implements Function<FGVertex, Paint> {
private final PickedInfo<FGVertex> info;
private final Color pickedColor;
private final Color entryColor;
private final Color exitColor;
private final Color pickedStartColor;
private final Color pickedEndColor;
private static Color mix(Color c1, Color c2) {
return new Color((c1.getRed() + c2.getRed()) / 2, (c1.getGreen() + c2.getGreen()) / 2,
(c1.getBlue() + c2.getBlue()) / 2);
}
public FGVertexPickableBackgroundPaintTransformer(PickedInfo<FGVertex> info, Color pickedColor,
Color startColor, Color endColor) {
if (info == null) {
throw new IllegalArgumentException("PickedInfo instance must be non-null");
}
this.info = info;
this.pickedColor = pickedColor;
this.entryColor = startColor;
this.exitColor = endColor;
this.pickedStartColor = mix(pickedColor, startColor);
this.pickedEndColor = mix(pickedColor, endColor);
}
@Override
public Paint apply(FGVertex v) {
Color backgroundColor = v.getBackgroundColor();
FGVertexType vertexType = v.getVertexType();
if (info.isPicked(v)) {
if (v.isDefaultBackgroundColor()) {
if (vertexType.isEntry()) {
return pickedStartColor;
}
if (vertexType.isExit()) {
return pickedEndColor;
}
return pickedColor;
}
if (vertexType.isEntry()) {
return pickedStartColor.darker();
}
if (vertexType.isExit()) {
return pickedEndColor.darker();
}
return pickedColor.darker();
}
if (vertexType.isEntry()) {
return entryColor;
}
if (vertexType.isExit()) {
return exitColor;
}
return backgroundColor;
}
}

View file

@ -0,0 +1,75 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.functiongraph.graph.layout;
import edu.uci.ics.jung.visualization.renderers.BasicEdgeRenderer;
import ghidra.app.plugin.core.functiongraph.graph.FGEdge;
import ghidra.app.plugin.core.functiongraph.graph.FunctionGraph;
import ghidra.app.plugin.core.functiongraph.graph.jung.renderer.FGEdgeRenderer;
import ghidra.app.plugin.core.functiongraph.graph.vertex.FGVertex;
import ghidra.app.plugin.core.functiongraph.mvc.FunctionGraphOptions;
import ghidra.graph.VisualGraph;
import ghidra.graph.viewer.layout.AbstractVisualGraphLayout;
import ghidra.graph.viewer.layout.VisualGraphLayout;
import ghidra.program.model.listing.Function;
/**
* An abstract class that is the root for Function Graph layouts. It changes the type of
* the graph returned to {@link FunctionGraph} and defines a clone method that takes in a
* Function Graph.
*/
public abstract class AbstractFGLayout extends AbstractVisualGraphLayout<FGVertex, FGEdge>
implements FGLayout {
protected Function function;
protected FunctionGraphOptions options;
protected AbstractFGLayout(FunctionGraph graph) {
super(graph);
this.function = graph.getFunction();
this.options = graph.getOptions();
}
protected abstract AbstractVisualGraphLayout<FGVertex, FGEdge> createClonedFGLayout(
FunctionGraph newGraph);
@Override
public FunctionGraph getVisualGraph() {
return (FunctionGraph) getGraph();
}
@Override
public AbstractVisualGraphLayout<FGVertex, FGEdge> createClonedLayout(
VisualGraph<FGVertex, FGEdge> newGraph) {
return createClonedFGLayout((FunctionGraph) newGraph);
}
@Override
public FGLayout cloneLayout(VisualGraph<FGVertex, FGEdge> newGraph) {
VisualGraphLayout<FGVertex, FGEdge> clone = super.cloneLayout(newGraph);
return (FGLayout) clone;
}
@Override
protected boolean isCondensedLayout() {
return options.useCondensedLayout();
}
@Override
public BasicEdgeRenderer<FGVertex, FGEdge> getEdgeRenderer() {
return new FGEdgeRenderer();
}
}

View file

@ -0,0 +1,21 @@
/* ###
* 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.plugin.core.functiongraph.graph.layout;
public interface DisposableLayout {
public void dispose();
}

View file

@ -0,0 +1,116 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.functiongraph.graph.layout;
import java.awt.Shape;
import java.awt.geom.Point2D;
import com.google.common.base.Function;
import edu.uci.ics.jung.visualization.renderers.BasicEdgeRenderer;
import ghidra.app.plugin.core.functiongraph.graph.FGEdge;
import ghidra.app.plugin.core.functiongraph.graph.FunctionGraph;
import ghidra.app.plugin.core.functiongraph.graph.vertex.FGVertex;
import ghidra.graph.VisualGraph;
import ghidra.graph.viewer.layout.*;
import ghidra.graph.viewer.layout.LayoutListener.ChangeType;
import ghidra.graph.viewer.renderer.ArticulatedEdgeRenderer;
import ghidra.graph.viewer.shape.ArticulatedEdgeTransformer;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;
public class EmptyLayout extends AbstractVisualGraphLayout<FGVertex, FGEdge> implements FGLayout {
public EmptyLayout(FunctionGraph graph) {
super(graph);
}
@Override
public void initialize() {
// stub
}
@Override
public void reset() {
// stub
}
@Override
public BasicEdgeRenderer<FGVertex, FGEdge> getEdgeRenderer() {
return new ArticulatedEdgeRenderer<>();
}
@Override
public Function<FGEdge, Shape> getEdgeShapeTransformer() {
return new ArticulatedEdgeTransformer<>();
}
@Override
protected GridLocationMap<FGVertex, FGEdge> performInitialGridLayout(
VisualGraph<FGVertex, FGEdge> g) throws CancelledException {
// Note: this is not called, since we overrode calculateLocations()
return null;
}
@Override
public LayoutPositions<FGVertex, FGEdge> calculateLocations(VisualGraph<FGVertex, FGEdge> g,
TaskMonitor taskMonitor) {
return LayoutPositions.createEmptyPositions();
}
@Override
public AbstractVisualGraphLayout<FGVertex, FGEdge> createClonedLayout(
VisualGraph<FGVertex, FGEdge> newGraph) {
return new EmptyLayout((FunctionGraph) newGraph);
}
@Override
public FGLayout cloneLayout(VisualGraph<FGVertex, FGEdge> newGraph) {
return (FGLayout) super.cloneLayout(newGraph);
}
@Override
public boolean usesEdgeArticulations() {
return false;
}
@Override
public void setLocation(FGVertex v, Point2D location, ChangeType changeType) {
// stub
}
@Override
public void addLayoutListener(LayoutListener<FGVertex, FGEdge> listener) {
// stub
}
@Override
public void removeLayoutListener(LayoutListener<FGVertex, FGEdge> listener) {
// stub
}
@Override
public void dispose() {
// stub
}
@Override
public FunctionGraph getVisualGraph() {
return (FunctionGraph) getGraph();
}
}

View file

@ -0,0 +1,35 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.functiongraph.graph.layout;
import javax.swing.Icon;
import resources.ResourceManager;
public abstract class ExperimentalLayoutProvider implements FGLayoutProvider {
private static final Icon ICON = ResourceManager.loadImage("images/package_development.png");
@Override
public Icon getActionIcon() {
return ICON;
}
@Override
public int getPriorityLevel() {
return -100; // below the others
}
}

View file

@ -0,0 +1,31 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.functiongraph.graph.layout;
import ghidra.app.plugin.core.functiongraph.graph.FGEdge;
import ghidra.app.plugin.core.functiongraph.graph.FunctionGraph;
import ghidra.app.plugin.core.functiongraph.graph.vertex.FGVertex;
import ghidra.graph.VisualGraph;
import ghidra.graph.viewer.layout.VisualGraphLayout;
public interface FGLayout extends VisualGraphLayout<FGVertex, FGEdge> {
@Override
public FGLayout cloneLayout(VisualGraph<FGVertex, FGEdge> newGraph);
@Override
public FunctionGraph getVisualGraph();
}

View file

@ -0,0 +1,37 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.functiongraph.graph.layout;
import ghidra.app.plugin.core.functiongraph.graph.FGEdge;
import ghidra.app.plugin.core.functiongraph.graph.FunctionGraph;
import ghidra.app.plugin.core.functiongraph.graph.vertex.FGVertex;
import ghidra.graph.viewer.layout.LayoutProvider;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;
public interface FGLayoutProvider extends LayoutProvider<FGVertex, FGEdge, FunctionGraph> {
// Suppressing warning on the return type; we know our class is the right type
@Override
public default FGLayout getLayout(FunctionGraph graph, TaskMonitor monitor)
throws CancelledException {
return getFGLayout(graph, monitor);
}
public FGLayout getFGLayout(FunctionGraph graph, TaskMonitor monitor) throws CancelledException;
}

View file

@ -0,0 +1,465 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.functiongraph.graph.vertex;
import java.awt.*;
import java.awt.event.MouseEvent;
import java.awt.geom.Point2D;
import javax.swing.JButton;
import javax.swing.JComponent;
import edu.uci.ics.jung.graph.Graph;
import ghidra.app.plugin.core.functiongraph.graph.*;
import ghidra.app.plugin.core.functiongraph.mvc.*;
import ghidra.app.util.viewer.listingpanel.ListingModel;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSetView;
import ghidra.program.model.listing.Program;
import ghidra.program.model.symbol.FlowType;
import ghidra.program.util.ProgramLocation;
import ghidra.program.util.ProgramSelection;
import ghidra.util.SystemUtilities;
import ghidra.util.exception.AssertException;
public abstract class AbstractFunctionGraphVertex implements FGVertex {
private FGController controller;
private final Program program;
private final AddressSetView addressSet;
private Point2D location;
private double emphasisLevel;
private double alpha = 1D;
private FGVertexType vertexType;
private FlowType flowType;
private boolean isEntry;
private boolean doHashCode = true;
private int hashCode;
/**
* To be restored when the component for this vertex is created.
*/
protected Color pendingRestoreColor;
private GroupHistoryInfo groupInfo;
AbstractFunctionGraphVertex(FGController controller, Program program, AddressSetView addresses,
FlowType flowType, boolean isEntry) {
if (addresses == null || addresses.isEmpty()) {
throw new IllegalArgumentException("Vertex cannot have null or empty address body");
}
this.controller = controller;
this.program = program;
this.addressSet = addresses;
this.flowType = flowType;
this.isEntry = isEntry;
this.location = new Point2D.Double();
}
/** Copy constructor */
AbstractFunctionGraphVertex(FGController controller, AbstractFunctionGraphVertex vertex) {
this.controller = controller;
this.program = vertex.program;
this.addressSet = vertex.addressSet;
this.location = vertex.location;
this.vertexType = vertex.vertexType;
this.isEntry = vertex.isEntry;
this.flowType = vertex.flowType;
this.groupInfo = vertex.groupInfo;
}
abstract boolean hasLoadedComponent();
abstract AbstractGraphComponentPanel doGetComponent();
@Override
public void writeSettings(FunctionGraphVertexAttributes settings) {
controller.saveVertexColors(this, settings);
}
@Override
public void readSettings(FunctionGraphVertexAttributes settings) {
controller.restoreVertexColors(this, settings);
}
@Override
public void updateGroupAssociationStatus(GroupHistoryInfo newGroupInfo) {
this.groupInfo = newGroupInfo;
doGetComponent().updateGroupAssociationStatus(groupInfo != null);
}
@Override
public GroupHistoryInfo getGroupInfo() {
return groupInfo;
}
@Override
public boolean isUncollapsedGroupMember() {
if (groupInfo == null) {
return false;
}
// we are an uncollapsed group member if we have a group info and we *are* in the graph
// (not being in the graph means that we are inside of a group)
return isInGraph();
}
private boolean isInGraph() {
FGData graphData = controller.getFunctionGraphData();
FunctionGraph functionGraph = graphData.getFunctionGraph();
Graph<FGVertex, FGEdge> graph = functionGraph;
return graph.containsVertex(this);
}
@Override
public JComponent getComponent() {
return doGetComponent();
}
FGController getController() {
return controller;
}
@Override
public Program getProgram() {
return program;
}
@Override
public Address getVertexAddress() {
return addressSet.getMinAddress();
}
@Override
public AddressSetView getAddresses() {
return addressSet;
}
@Override
public boolean containsProgramLocation(ProgramLocation location) {
return addressSet.contains(location.getAddress());
}
@Override
public boolean containsAddress(Address address) {
return addressSet.contains(address);
}
@Override
public void setEmphasis(double emphasisLevel) {
this.emphasisLevel = emphasisLevel;
}
@Override
public double getEmphasis() {
return emphasisLevel;
}
@Override
public void setAlpha(double alpha) {
this.alpha = alpha;
}
@Override
public double getAlpha() {
return alpha;
}
@Override
public void setLocation(Point2D location) {
this.location = location;
}
@Override
public Point2D getLocation() {
return location;
}
@Override
public FGVertexType getVertexType() {
return vertexType;
}
@Override
public void setVertexType(FGVertexType vertexType) {
if (this.vertexType != null) {
throw new AssertException("Cannot set the vertex type more than once. " +
"Previous type was " + vertexType + " on vertex " + this);
}
this.vertexType = vertexType;
}
@Override
public boolean isEntry() {
return isEntry;
}
@Override
public FlowType getFlowType() {
return flowType;
}
@Override
public int hashCode() {
// code blocks don't overlap, so min address is sufficient for a good hash value
if (doHashCode) {
hashCode = addressSet.getMinAddress().hashCode();
doHashCode = false;
}
return hashCode;
}
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (obj == this) {
return true;
}
if (getClass() != obj.getClass()) {
return false;
}
AbstractFunctionGraphVertex other = (AbstractFunctionGraphVertex) obj;
Address minAddress = addressSet.getMinAddress();
Address otherMinAddress = other.addressSet.getMinAddress();
if (!SystemUtilities.isEqual(minAddress, otherMinAddress)) {
return false;
}
Address maxAddress = addressSet.getMaxAddress();
Address otherMaxAddress = other.addressSet.getMaxAddress();
return SystemUtilities.isEqual(maxAddress, otherMaxAddress);
}
@Override
public void dispose() {
controller = null;
}
//==================================================================================================
// GraphComponentPanel Delegate Methods
//==================================================================================================
@Override
public void restoreColor(Color color) {
if (hasLoadedComponent()) {
doGetComponent().restoreColor(color);
return;
}
pendingRestoreColor = color;
}
@Override
public Color getUserDefinedColor() {
return doGetComponent().getUserDefinedColor();
}
@Override
public Color getDefaultBackgroundColor() {
return doGetComponent().getDefaultBackgroundColor();
}
@Override
public Color getBackgroundColor() {
return doGetComponent().getBackgroundColor();
}
@Override
public void clearColor() {
doGetComponent().clearColor();
}
@Override
public String getTitle() {
return doGetComponent().getTitle();
}
@Override
public String getToolTipText(MouseEvent event) {
return doGetComponent().getToolTipText(event);
}
@Override
public JComponent getToolTipComponentForEdge(FGEdge edge) {
return doGetComponent().getToolTipComponentForEdge(edge);
}
@Override
public JComponent getToolTipComponentForVertex() {
return doGetComponent().getToolTipComponentForVertex();
}
@Override
public boolean isDefaultBackgroundColor() {
return doGetComponent().isDefaultBackgroundColor();
}
@Override
public Rectangle getBounds() {
return doGetComponent().getBounds();
}
@Override
public boolean isFullScreenMode() {
return doGetComponent().isFullScreenMode();
}
@Override
public void setFullScreenMode(boolean fullScreen) {
doGetComponent().setFullScreenMode(fullScreen);
}
@Override
public boolean isSelected() {
return doGetComponent().isSelected();
}
@Override
public void setSelected(boolean selected) {
doGetComponent().setSelected(selected);
}
@Override
public void setHovered(boolean hovered) {
// we don't support this for now
}
@Override
public boolean isHovered() {
// we don't support this for now
return false;
}
@Override
public void editLabel(JComponent parentComponent) {
doGetComponent().editLabel(parentComponent);
}
@Override
public void setFocused(boolean focused) {
AbstractGraphComponentPanel component = doGetComponent();
component.setSelected(focused);
component.setFocused(focused);
}
@Override
public boolean isFocused() {
AbstractGraphComponentPanel component = doGetComponent();
return component.isFocused();
}
@Override
public void setProgramSelection(ProgramSelection selection) {
doGetComponent().setProgramSelection(selection);
}
@Override
public ProgramSelection getProgramSelection() {
return doGetComponent().getProgramSelection();
}
@Override
public void setProgramHighlight(ProgramSelection highlight) {
doGetComponent().setProgramHighlight(highlight);
}
@Override
public void setProgramLocation(ProgramLocation location) {
doGetComponent().setProgramLocation(location);
}
@Override
public ProgramLocation getProgramLocation() {
return doGetComponent().getProgramLocation();
}
@Override
public ListingModel getListingModel(Address address) {
return doGetComponent().getListingModel(address);
}
@Override
public Rectangle getCursorBounds() {
return doGetComponent().getCursorBounds();
}
@Override
public void setBackgroundColor(Color color) {
doGetComponent().setBackgroundColor(color);
}
@Override
public boolean isHeaderClick(Component clickedComponent) {
return doGetComponent().isHeaderClick(clickedComponent);
}
@Override
public boolean isGrabbable(Component c) {
if (!doGetComponent().isHeaderClick(c)) {
return false; // only the header is grabbable
}
// the user cannot grab buttons, as they can press them
return !(c instanceof JButton);
}
@Override
public String toString() {
if (getController() == null || !hasLoadedComponent()) {
// disposed!
return getClass().getSimpleName() + "@" + getVertexAddress().toString();
}
return doGetComponent().getTitle();
}
@Override
public void refreshModel() {
doGetComponent().refreshModel();
}
@Override
public void refreshDisplay() {
doGetComponent().refreshDisplay();
}
@Override
public void refreshDisplayForAddress(Address address) {
doGetComponent().refreshDisplayForAddress(address);
}
@Override
public void setShowing(boolean isShowing) {
doGetComponent().setShowing(isShowing);
}
@Override
public Component getMaximizedViewComponent() {
return doGetComponent().getMaximizedViewComponent();
}
}

View file

@ -0,0 +1,190 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.functiongraph.graph.vertex;
import java.awt.*;
import java.awt.event.MouseEvent;
import java.awt.geom.Point2D;
import javax.swing.*;
import edu.uci.ics.jung.visualization.VisualizationViewer;
import ghidra.app.plugin.core.functiongraph.graph.FGEdge;
import ghidra.app.plugin.core.functiongraph.mvc.FGController;
import ghidra.app.plugin.core.functiongraph.mvc.FGView;
import ghidra.app.util.viewer.listingpanel.ListingModel;
import ghidra.program.model.address.Address;
import ghidra.program.util.ProgramLocation;
import ghidra.program.util.ProgramSelection;
public abstract class AbstractGraphComponentPanel extends JPanel {
protected FGController controller;
protected FGVertex vertex;
protected String title;
private boolean isShowingOverride = true;
private boolean isFocused;
AbstractGraphComponentPanel(FGController controller, FGVertex vertex) {
this.controller = controller;
this.vertex = vertex;
}
void setShowing(boolean isShowing) {
isShowingOverride = isShowing;
}
String getTitle() {
return title;
}
@Override
// overridden so that we always paint
public boolean isShowing() {
return isShowingOverride;
}
@Override
public Dimension getSize() {
return getPreferredSize();
}
FGController getController() {
return controller;
}
void dispose() {
controller = null;
vertex = null;
}
protected void groupVertices() {
FGView view = controller.getView();
VisualizationViewer<FGVertex, FGEdge> primaryViewer = view.getPrimaryGraphViewer();
edu.uci.ics.jung.algorithms.layout.Layout<FGVertex, FGEdge> graphLayout =
primaryViewer.getGraphLayout();
Point2D location = graphLayout.apply(vertex);
controller.groupSelectedVertices(location);
}
protected void regroupVertices() {
controller.regroupVertices(vertex);
}
//======================================================
abstract Color getBackgroundColor();
abstract Color getUserDefinedColor();
abstract Color getDefaultBackgroundColor();
abstract void clearColor();
abstract JComponent getHeader();
@Override
public abstract String getToolTipText(MouseEvent event);
abstract ListingModel getListingModel(Address address);
abstract JComponent getToolTipComponentForEdge(FGEdge edge);
abstract JComponent getToolTipComponentForVertex();
abstract boolean isSelected();
abstract void setSelected(boolean selected);
abstract void setCursorPosition(ProgramLocation location);
abstract Rectangle getCursorBounds();
abstract void setProgramSelection(ProgramSelection selection);
abstract ProgramSelection getProgramSelection();
abstract void setProgramHighlight(ProgramSelection highlight);
void setProgramLocation(ProgramLocation location) {
setSelected(true);
setCursorPosition(location);
}
abstract ProgramLocation getProgramLocation();
boolean isDefaultBackgroundColor() {
return getBackgroundColor().equals(Color.WHITE);
}
boolean isHeaderClick(Component clickedComponent) {
if (clickedComponent == null) {
return false;
}
Component header = getHeader();
if (clickedComponent == null) {
return false;
}
return SwingUtilities.isDescendingFrom(clickedComponent, header);
}
@Override
public Rectangle getBounds() {
Rectangle bounds = super.getBounds();
Dimension preferredSize = getPreferredSize();
bounds.setSize(preferredSize);
return bounds;
}
abstract void setBackgroundColor(Color color);
abstract void restoreColor(Color color);
@Override
public String toString() {
return getTitle();
}
abstract void refreshModel();
abstract void refreshDisplay();
abstract void refreshDisplayForAddress(Address address);
abstract Component getMaximizedViewComponent();
abstract boolean isFullScreenMode();
abstract void setFullScreenMode(boolean fullScreen);
abstract void updateGroupAssociationStatus(boolean groupMember);
abstract void editLabel(JComponent parentComponent);
public void setFocused(boolean focused) {
this.isFocused = focused;
doSetFocused(focused);
}
abstract void doSetFocused(boolean focused);
public boolean isFocused() {
return isFocused;
}
}

View file

@ -0,0 +1,61 @@
/* ###
* 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.plugin.core.functiongraph.graph.vertex;
import ghidra.program.model.address.Address;
import ghidra.util.SystemUtilities;
// TODO if we ever have AddressSet implement hashCode(), then we don't really need this class
class AddressHasher {
private final Address startAddress;
private final Address endAddress;
AddressHasher(Address startAddress, Address endAddress) {
this.startAddress = startAddress;
this.endAddress = endAddress;
}
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (obj == this) {
return true;
}
if (getClass() != obj.getClass()) {
return false;
}
AddressHasher other = (AddressHasher) obj;
return SystemUtilities.isEqual(startAddress, other.startAddress) &&
SystemUtilities.isEqual(endAddress, other.endAddress);
}
@Override
public int hashCode() {
return startAddress.hashCode() ^ endAddress.hashCode();
}
@Override
public String toString() {
return getClass().getSimpleName() + "[start=" + startAddress + ", end=" + endAddress + "]";
}
}

View file

@ -0,0 +1,66 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.functiongraph.graph.vertex;
import ghidra.program.model.address.AddressSetView;
import org.jdom.Element;
public class AddressInfo {
private static final String START_ADDRESS = "START_ADDRESS";
private static final String END_ADDRESS = "END_ADDRESS";
static final String VERTEX_ADDRESS_INFO_ELEMENT_NAME = "VERTEX_ADDRESS_INFO";
String addressRangeStart;
String addressRangeEnd;
AddressInfo(FGVertex vertex) {
if (vertex == null) {
throw new NullPointerException("Vertex cannot be null");
}
AddressSetView addresses = vertex.getAddresses();
this.addressRangeStart = addresses.getMinAddress().toString();
this.addressRangeEnd = addresses.getMaxAddress().toString();
}
AddressInfo(Element element) {
addressRangeStart = element.getAttributeValue(START_ADDRESS);
addressRangeEnd = element.getAttributeValue(END_ADDRESS);
if (addressRangeStart == null) {
throw new NullPointerException("Error reading XML for " + getClass().getName());
}
if (addressRangeEnd == null) {
throw new NullPointerException("Error reading XML for " + getClass().getName());
}
}
void write(Element parent) {
Element element = new Element(VERTEX_ADDRESS_INFO_ELEMENT_NAME);
element.setAttribute(START_ADDRESS, addressRangeStart);
element.setAttribute(END_ADDRESS, addressRangeEnd);
parent.addContent(element);
}
@Override
public String toString() {
return getClass().getSimpleName() + "[start=" + addressRangeStart + ", end=" +
addressRangeEnd + "]";
}
}

View file

@ -0,0 +1,209 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.functiongraph.graph.vertex;
import java.awt.*;
import java.awt.event.MouseEvent;
import javax.swing.JComponent;
import ghidra.app.plugin.core.functiongraph.graph.FGEdge;
import ghidra.app.plugin.core.functiongraph.graph.FGVertexType;
import ghidra.app.plugin.core.functiongraph.mvc.FGController;
import ghidra.app.plugin.core.functiongraph.mvc.FunctionGraphVertexAttributes;
import ghidra.app.util.viewer.listingpanel.ListingModel;
import ghidra.graph.viewer.VisualVertex;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSetView;
import ghidra.program.model.listing.Program;
import ghidra.program.model.symbol.FlowType;
import ghidra.program.util.ProgramLocation;
import ghidra.program.util.ProgramSelection;
/**
* A visual vertex that represents a code block within a function. This class understands
* software modeling concepts and deals with things like program selections and locations.
*/
public interface FGVertex extends VisualVertex {
static final Color TOOLTIP_BACKGROUND_COLOR = new Color(255, 255, 230);
public FGVertex cloneVertex(FGController newController);
/** A chance for this vertex to save off changed settings */
public void writeSettings(FunctionGraphVertexAttributes settings);
/** A chance for this vertex to read in stored settings */
public void readSettings(FunctionGraphVertexAttributes settings);
public void restoreColor(Color color);
public Color getUserDefinedColor();
public FGVertexType getVertexType();
/**
* Sets the vertex type. This can only be called once. Repeated calls will except.
*
* @param vertexType the type
*/
public void setVertexType(FGVertexType vertexType);
public Address getVertexAddress();
/**
* Returns true if this vertex is considered an entry. Normally, a vertex is considered
* an entry if it is a source, with no incoming edges. This vertex can be considered an
* entry even if it has incoming edges, such as when another function directly calls the
* code block associated with this vertex.
*
* @return true if this vertex is an entry
*/
public boolean isEntry();
public FlowType getFlowType();
public AddressSetView getAddresses();
public Program getProgram();
public ListingModel getListingModel(Address address);
public Color getDefaultBackgroundColor();
public Color getBackgroundColor();
public void setBackgroundColor(Color color);
public void clearColor();
/**
* Signals to this vertex that it is associated with a group. False implies that this vertex
* is not and has not been part of a group. True signals that this vertex is not currently
* grouped, but that it has been part of a group and can be put back into its group form
* again.
*
* @param groupMember True if this vertex is a associate with a group
*/
public void updateGroupAssociationStatus(GroupHistoryInfo groupInfo);
public GroupHistoryInfo getGroupInfo();
/**
* Returns true if this vertex is a member of an uncollapsed group
* @return true if this vertex is a member of an uncollapsed group
*/
public boolean isUncollapsedGroupMember();
public String getTitle();
public String getToolTipText(MouseEvent event);
public JComponent getToolTipComponentForEdge(FGEdge edge);
public JComponent getToolTipComponentForVertex();
public boolean isDefaultBackgroundColor();
public Rectangle getBounds();
public boolean containsProgramLocation(ProgramLocation location);
public boolean containsAddress(Address address);
public void setProgramLocation(ProgramLocation location);
public void setProgramSelection(ProgramSelection selection);
public ProgramSelection getProgramSelection();
public void setProgramHighlight(ProgramSelection highlight);
public ProgramLocation getProgramLocation();
public Rectangle getCursorBounds();
/**
* Edits the label for the vertex. This could be the label for the minimum address of the
* vertex's code block or this could be the text of the vertex's display (as it is for a
* grouped vertex).
*
* @param component the parent component of any shown dialogs
*/
public void editLabel(JComponent component);
/**
* Returns true if the clicked component is or is inside of the header of the vertex
*
* @param clickedComponent the clicked component
* @return true if the clicked component is or is inside of the header of the vertex
*/
public boolean isHeaderClick(Component clickedComponent);
/**
* Signals that this vertex is being rendered such that it takes up the entire graph
* window.
*
* @return true if full-screen
*/
public boolean isFullScreenMode();
/**
* Sets whether this vertex is in full-screen mode. When in full-screen, a larger
* view of the code block will be provided. When not in full-screen, a condensed view
* of this vertex is provided.
*
* @param fullScreen true for full-screen
*/
public void setFullScreenMode(boolean fullScreen);
/**
* Returns the full-screen view of this vertex.
* @return the full-screen view
*/
public Component getMaximizedViewComponent();
/**
* Signals to rebuild this vertex's data model. This call will not do any real work
* if the model is not 'dirty'.
*/
public void refreshModel();
/**
* Triggers a refresh of the visual components of this vertex, such as the title.
*/
public void refreshDisplay();
/**
* Refresh the vertex's display information if the given address is the entry point
* of the vertex.
*/
public void refreshDisplayForAddress(Address address);
/**
* Tells this vertex whether it is showing. This actually overrides the underlying
* Java component's {@link JComponent#isShowing()} method in order to prevent it from
* showing tooltips (we manage tooltips ourselves).
*
* <P>We have to set this to true painting, but then false when we are done (Java
* components will not paint themselves if the are not showing).
*
* @param isShowing true if the component is showing
*/
public void setShowing(boolean isShowing);
public void dispose();
}

View file

@ -0,0 +1,49 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.functiongraph.graph.vertex;
import ghidra.app.util.viewer.format.FormatManager;
import ghidra.app.util.viewer.listingpanel.ProgramBigListingModel;
import ghidra.framework.model.DomainObjectChangedEvent;
import ghidra.program.model.listing.Program;
class FGVertexListingModel extends ProgramBigListingModel {
private boolean isDirty;
FGVertexListingModel(Program program, FormatManager formatManager) {
super(program, formatManager);
}
boolean refresh() {
if (!isDirty) {
return false;
}
isDirty = false;
if (!program.isClosed()) {
notifyDataChanged(true);
return true;
}
return false;
}
@Override
public void domainObjectChanged(DomainObjectChangedEvent ev) {
isDirty = true;
}
}

View file

@ -0,0 +1,178 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.functiongraph.graph.vertex;
import java.awt.Dimension;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.List;
import docking.widgets.fieldpanel.*;
import ghidra.app.plugin.core.functiongraph.FGColorProvider;
import ghidra.app.plugin.core.functiongraph.mvc.FGController;
import ghidra.app.util.viewer.format.FormatManager;
import ghidra.app.util.viewer.listingpanel.*;
import ghidra.program.model.address.AddressSetView;
import ghidra.program.model.listing.Program;
public class FGVertexListingPanel extends ListingPanel {
private ListingModelListener listener = new ListingModelListener() {
@Override
public void dataChanged(boolean updateImmediately) {
//
// Unusual Code Alert!: when the data of the listing changes its preferred size
// may also change. If we don't invalidate the containing
// Java component, then the cached preferred size will be
// invalid.
//
getFieldPanel().invalidate();
controller.repaint();
}
@Override
public void modelSizeChanged() {
// don't care
}
};
private FGController controller;
private AddressSetView addressSetView;
private Dimension preferredSizeCache;
private Dimension lastParentPreferredSize;
FGVertexListingPanel(final FGController controller, FormatManager formatManager,
Program program, AddressSetView view) {
super(formatManager);
this.controller = controller;
this.addressSetView = view;
setNeverSroll(); // must be before setProgram()
setProgram(program);
ListingModel model = getListingModel();
model.addListener(listener);
FGColorProvider colorProvider = controller.getColorProvider();
if (!colorProvider.isUsingCustomColors()) {
enablePropertyBasedColorModel(true); // turn on user colors in the graph
}
}
@Override
public void setView(AddressSetView view) {
this.addressSetView = view;
super.setView(view);
}
@Override
protected ListingModel createListingModel(Program program) {
return new FGVertexListingModel(program, getFormatManager());
}
/**
* Overridden to set the view before the parent class notifies the listeners. This prevents
* our methods that calculate preferred size from going 'out to lunch' when attempting to
* examine the entire program instead of just the given view.
*
* @param model The listing model needed by the layout model *
* @return the new model adapter
*/
@Override
protected ListingModelAdapter createLayoutModel(ListingModel model) {
ListingModelAdapter adapter = super.createLayoutModel(model);
if (model != null) {
adapter.setAddressSet(addressSetView);
}
return adapter;
}
@Override
protected FieldPanel createFieldPanel(LayoutModel model) {
return new FGVertexFieldPanel(model);
}
@Override
public Dimension getPreferredSize() {
Dimension preferredSize = super.getPreferredSize();
int maxWidth = getFormatManager().getMaxWidth();
if (preferredSize.width < maxWidth) {
preferredSize.width += 10; // some padding on the end to avoid clipping
}
return preferredSize;
}
// Overridden, as we wish to customize our width to be as small as possible, based upon the format
@Override
protected int getNewWindowDefaultWidth() {
return 0;
}
public void refreshModel() {
FGVertexListingModel fgModel = (FGVertexListingModel) getListingModel();
if (fgModel.refresh()) {
preferredSizeCache = null;
}
}
//
// Overridden to allow for a smaller preferred size, as dictated by the layout
//
private class FGVertexFieldPanel extends FieldPanel {
public FGVertexFieldPanel(LayoutModel model) {
super(model);
}
@Override
public Dimension getPreferredSize() {
Dimension preferredSize = super.getPreferredSize();
if (preferredSize.equals(lastParentPreferredSize) && preferredSizeCache != null) {
return preferredSizeCache;
}
lastParentPreferredSize = preferredSize;
LayoutModel layoutModel = getLayoutModel();
List<Layout> layouts = getAllLayouts(layoutModel);
int largestWidth = 0;
for (Layout layout : layouts) {
int width = layout.getCompressableWidth();
if (width > largestWidth) {
largestWidth = width;
}
}
preferredSize.width = largestWidth;
preferredSizeCache = preferredSize;
return preferredSize;
}
private List<Layout> getAllLayouts(LayoutModel layoutModel) {
List<Layout> list = new ArrayList<>();
Layout layout = layoutModel.getLayout(BigInteger.ZERO);
BigInteger index = BigInteger.ONE;
while (layout != null) {
list.add(layout);
index = layoutModel.getIndexAfter(index);
layout = layoutModel.getLayout(index);
}
return list;
}
}
}

View file

@ -0,0 +1,45 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.functiongraph.graph.vertex;
import java.awt.event.MouseEvent;
import javax.swing.JComponent;
import ghidra.app.plugin.core.functiongraph.graph.FGEdge;
import ghidra.graph.viewer.event.mouse.VertexTooltipProvider;
public class FGVertexTooltipProvider implements VertexTooltipProvider<FGVertex, FGEdge> {
@Override
public JComponent getTooltip(FGVertex v) {
JComponent c = v.getToolTipComponentForVertex();
return c;
}
@Override
public JComponent getTooltip(FGVertex v, FGEdge e) {
JComponent c = v.getToolTipComponentForEdge(e);
return c;
}
@Override
public String getTooltipText(FGVertex v, MouseEvent e) {
String tooltip = v.getToolTipText(e);
return tooltip;
}
}

View file

@ -0,0 +1,297 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.functiongraph.graph.vertex;
import java.awt.geom.Point2D;
import java.util.*;
import org.jdom.Element;
import edu.uci.ics.jung.algorithms.layout.Layout;
import edu.uci.ics.jung.graph.Graph;
import ghidra.app.plugin.core.functiongraph.graph.FGEdge;
import ghidra.app.plugin.core.functiongraph.graph.FunctionGraph;
import ghidra.app.plugin.core.functiongraph.mvc.FGController;
import ghidra.app.plugin.core.functiongraph.mvc.FGData;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSetView;
import ghidra.util.Msg;
import ghidra.util.xml.XmlUtilities;
public class GroupHistoryInfo {
static final String GROUP_HISTORY_ELEMENT_NAME = "GROUP_HISTORY";
private static final String GROUP_DESCRIPTION_ATTRIBUTE = "GROUP_DESCRIPTION";
// TODO may want to keep track of the original grouped vertices (they can change) so that
// we can reconstitute the original group text so that we can know to updated when later
// regrouping
private final Set<FGVertex> groupVertices;
private String groupDescription;
private final AddressInfo addressInfo;
private final PointInfo locationInfo;
public GroupHistoryInfo(FunctionGraph functionGraph, GroupedFunctionGraphVertex groupVertex) {
this.groupVertices = new HashSet<>(groupVertex.getVertices());
if (groupVertices.isEmpty()) {
throw new IllegalArgumentException(
"Cannot create a group history entry with no vertices!");
}
this.groupDescription = groupVertex.getUserText();
if (groupDescription == null) {
throw new IllegalArgumentException("Group description cannot be null");
}
Layout<FGVertex, FGEdge> graphLayout = functionGraph.getLayout();
Point2D location = graphLayout.apply(groupVertex);
locationInfo = new PointInfo(location);
addressInfo = new AddressInfo(groupVertex);
}
@SuppressWarnings("unchecked")
public GroupHistoryInfo(FGController controller, Element element) {
FGData functionGraphData = controller.getFunctionGraphData();
FunctionGraph functionGraph = functionGraphData.getFunctionGraph();
Map<AddressHasher, FGVertex> vertexMap = hashVerticesByStartAndEndAddress(functionGraph);
groupVertices = new HashSet<>();
List<Element> children = element.getChildren(VertexInfo.VERTEX_INFO_ELEMENT_NAME);
for (Element vertexInfoElement : children) {
// NOTE: we use the VertexInfo here to deserialize itself, which it does well
VertexInfo vertexInfo = new VertexInfo(vertexInfoElement);
FGVertex vertex = vertexInfo.getVertex(controller, vertexMap);
if (vertex != null) {
groupVertices.add(vertex);
}
else {
// Could be null if the structure of the function changes (like during background
// analysis). This can also happen if a graph is cloned and the saved settings
// don't match the current state of the graph.
Msg.debug(this, "Unable to re-serialize vertex for info: " + vertexInfo);
}
}
children = element.getChildren(GroupedVertexInfo.GROUPED_VERTEX_INFO_ELEMENT_NAME);
for (Element vertexInfoElement : children) {
GroupedVertexInfo vertexInfo = new GroupedVertexInfo(vertexInfoElement);
FGVertex vertex = vertexInfo.locateVertex(controller, vertexMap);
if (vertex != null) {
groupVertices.add(vertex);
}
else {
// could be null if the structure of the function changes (not sure when this
// can happen in reality--there is probably a bug here)
Msg.debug(this, "Unable to re-serialize vertex for info: " + vertexInfo);
}
}
String escpapedGroupDescription = element.getAttributeValue(GROUP_DESCRIPTION_ATTRIBUTE);
groupDescription = XmlUtilities.unEscapeElementEntities(escpapedGroupDescription);
Element vertexInfoElement = element.getChild(AddressInfo.VERTEX_ADDRESS_INFO_ELEMENT_NAME);
addressInfo = new AddressInfo(vertexInfoElement);
Element locationElement = element.getChild(VertexInfo.LOCATION_INFO_ELEMENT_NAME);
Element pointInfoElement = locationElement.getChild(PointInfo.POINT_INFO_ELEMENT_NAME);
locationInfo = new PointInfo(pointInfoElement);
}
/**
* Signals that the user has changed the text of a group node and that and this pre-existing
* info needs to update.
*
* @param text The new text
*/
public void setGroupDescription(String text) {
this.groupDescription = text;
for (FGVertex vertex : groupVertices) {
vertex.updateGroupAssociationStatus(this); // the vertices may be caching this info
}
}
public boolean contains(FGVertex vertex) {
for (FGVertex child : groupVertices) {
if (matchesOrContains(child, vertex)) {
return true;
}
}
return false;
}
private boolean matchesOrContains(FGVertex potentialMatch, FGVertex vertex) {
if (potentialMatch.equals(vertex)) {
return true;
}
if (potentialMatch instanceof GroupedFunctionGraphVertex) {
Set<FGVertex> vertices = ((GroupedFunctionGraphVertex) potentialMatch).getVertices();
for (FGVertex child : vertices) {
if (matchesOrContains(child, vertex)) {
return true;
}
}
}
return false;
}
public void removeVertex(FGVertex vertex) {
updateGroupDescription(vertex);
groupVertices.remove(vertex);
// also fixup any internal groups that may contain the given vertex
removeFromGroups(vertex);
}
private void updateGroupDescription(FGVertex vertex) {
String text = GroupedFunctionGraphVertex.getVertexDescription(vertex);
int index = groupDescription.indexOf(text);
if (index != -1) {
StringBuffer buffy = new StringBuffer(groupDescription);
buffy.delete(index, index + text.length());
groupDescription = buffy.toString();
}
}
private void removeFromGroups(FGVertex oldVertex) {
// copy, as we may mutate
Set<FGVertex> vertices = new HashSet<>(groupVertices);
for (FGVertex vertex : vertices) {
if (vertex.equals(oldVertex)) {
groupVertices.remove(oldVertex);
continue;
}
if (!(vertex instanceof GroupedFunctionGraphVertex)) {
continue;
}
GroupedFunctionGraphVertex oldGroup = (GroupedFunctionGraphVertex) vertex;
GroupedFunctionGraphVertex newGroup = removeFromGroup(oldVertex, oldGroup);
if (newGroup != null) {
// the vertex has been removed--update out vertices
groupVertices.remove(oldGroup);
groupVertices.add(newGroup);
}
}
}
private GroupedFunctionGraphVertex removeFromGroup(FGVertex oldVertex,
GroupedFunctionGraphVertex oldGroup) {
Set<FGVertex> toRemove = new HashSet<>();
Set<FGVertex> vertices = oldGroup.getVertices();
for (FGVertex vertex : vertices) {
if (vertex.equals(oldVertex)) {
toRemove.add(vertex);
}
if (!(vertex instanceof GroupedFunctionGraphVertex)) {
continue;
}
GroupedFunctionGraphVertex newGroup =
removeFromGroup(oldVertex, (GroupedFunctionGraphVertex) vertex);
if (newGroup != null) {
// the vertex has been removed--update out vertices
groupVertices.remove(oldGroup);
groupVertices.add(newGroup);
}
}
return oldGroup.removeAll(toRemove);
}
public Point2D getGroupLocation() {
return locationInfo.getPoint();
}
public Set<FGVertex> getVertices() {
return Collections.unmodifiableSet(groupVertices);
}
public String getGroupDescription() {
return groupDescription;
}
public Element toXML(FunctionGraph functionGraph) {
Element element = new Element(GROUP_HISTORY_ELEMENT_NAME);
//
// Grouped vertices content
//
for (FGVertex vertex : groupVertices) {
if (vertex instanceof GroupedFunctionGraphVertex) {
GroupedVertexInfo vertexInfo =
new GroupedVertexInfo((GroupedFunctionGraphVertex) vertex, functionGraph);
element.addContent(vertexInfo.toXML());
}
else {
VertexInfo vertexInfo = new VertexInfo(vertex, functionGraph);
element.addContent(vertexInfo.toXML());
}
}
//
// Group description
//
String escapedText = XmlUtilities.escapeElementEntities(groupDescription);
element.setAttribute(GROUP_DESCRIPTION_ATTRIBUTE, escapedText);
//
// Group vertex address
//
addressInfo.write(element);
//
// Group location
//
Element locationElement = new Element(VertexInfo.LOCATION_INFO_ELEMENT_NAME);
locationInfo.write(locationElement);
element.addContent(locationElement);
return element;
}
@Override
public String toString() {
return "text=\"" + groupDescription + "\", AddressInfo=" + addressInfo + ", location=" +
locationInfo;
}
private static Map<AddressHasher, FGVertex> hashVerticesByStartAndEndAddress(
FunctionGraph functionGraph) {
Map<AddressHasher, FGVertex> map = new HashMap<>();
Graph<FGVertex, FGEdge> graph = functionGraph;
Collection<FGVertex> vertices = graph.getVertices();
for (FGVertex vertex : vertices) {
AddressSetView addresses = vertex.getAddresses();
Address minAddress = addresses.getMinAddress();
Address maxAddress = addresses.getMaxAddress();
map.put(new AddressHasher(minAddress, maxAddress), vertex);
}
return map;
}
}

View file

@ -0,0 +1,21 @@
/* ###
* 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.plugin.core.functiongraph.graph.vertex;
public interface GroupListener {
public void groupDescriptionChanged(String oldText, String newText);
}

Some files were not shown because too many files have changed in this diff Show more