Candidate release of source code.
|
@ -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>
|
|
@ -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; }
|
After Width: | Height: | Size: 69 B |
After Width: | Height: | Size: 859 B |
After Width: | Height: | Size: 62 B |
After Width: | Height: | Size: 4.1 KiB |
BIN
Ghidra/Features/FunctionGraph/src/main/help/help/shared/note.png
Normal file
After Width: | Height: | Size: 4.1 KiB |
After Width: | Height: | Size: 4.1 KiB |
BIN
Ghidra/Features/FunctionGraph/src/main/help/help/shared/redo.png
Normal file
After Width: | Height: | Size: 187 B |
BIN
Ghidra/Features/FunctionGraph/src/main/help/help/shared/tip.png
Normal file
After Width: | Height: | Size: 1.6 KiB |
BIN
Ghidra/Features/FunctionGraph/src/main/help/help/shared/undo.png
Normal file
After Width: | Height: | Size: 185 B |
After Width: | Height: | Size: 1.3 KiB |
After Width: | Height: | Size: 60 KiB |
After Width: | Height: | Size: 354 B |
After Width: | Height: | Size: 555 B |
After Width: | Height: | Size: 510 B |
After Width: | Height: | Size: 367 B |
After Width: | Height: | Size: 513 B |
After Width: | Height: | Size: 337 B |
After Width: | Height: | Size: 578 B |
After Width: | Height: | Size: 476 B |
After Width: | Height: | Size: 495 B |
After Width: | Height: | Size: 16 KiB |
After Width: | Height: | Size: 6.2 KiB |
After Width: | Height: | Size: 29 KiB |
After Width: | Height: | Size: 82 KiB |
After Width: | Height: | Size: 4.2 KiB |
After Width: | Height: | Size: 11 KiB |
After Width: | Height: | Size: 12 KiB |
After Width: | Height: | Size: 593 B |
After Width: | Height: | Size: 565 B |
After Width: | Height: | Size: 461 B |
After Width: | Height: | Size: 730 B |
After Width: | Height: | Size: 21 KiB |
After Width: | Height: | Size: 6.8 KiB |
After Width: | Height: | Size: 864 B |
After Width: | Height: | Size: 591 B |
After Width: | Height: | Size: 237 B |
After Width: | Height: | Size: 426 B |
After Width: | Height: | Size: 3.5 KiB |
After Width: | Height: | Size: 806 B |
After Width: | Height: | Size: 2.3 KiB |
After Width: | Height: | Size: 703 B |
After Width: | Height: | Size: 309 B |
After Width: | Height: | Size: 538 B |
After Width: | Height: | Size: 539 B |
After Width: | Height: | Size: 666 B |
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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 ) );
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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() + ")";
|
||||
}
|
||||
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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;
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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 + "]";
|
||||
}
|
||||
}
|
|
@ -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 + "]";
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|