Candidate release of source code.
|
@ -0,0 +1,60 @@
|
|||
<?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="FunctionCallGraph"
|
||||
text="Function Call Graph"
|
||||
target="help/topics/FunctionCallGraphPlugin/Function_Call_Graph.html" />
|
||||
</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 |
After Width: | Height: | Size: 4.1 KiB |
After Width: | Height: | Size: 4.1 KiB |
After Width: | Height: | Size: 187 B |
After Width: | Height: | Size: 1.6 KiB |
After Width: | Height: | Size: 185 B |
After Width: | Height: | Size: 1.3 KiB |
|
@ -0,0 +1,248 @@
|
|||
<!DOCTYPE doctype PUBLIC "-//W3C//DTD HTML 4.0 Frameset//EN">
|
||||
|
||||
<HTML>
|
||||
<HEAD>
|
||||
<TITLE>Function Call Graph Plugin</TITLE>
|
||||
<META http-equiv="Content-Type" content="text/html; charset=windows-1252">
|
||||
<LINK rel="stylesheet" type="text/css" href="../../shared/Frontpage.css">
|
||||
</HEAD>
|
||||
|
||||
<BODY>
|
||||
<H1><A name="FunctionCallGraphPlugin"></A> <A name="Display_Function_Call_Graph">
|
||||
</A>Function Call Graph Plugin</H1>
|
||||
|
||||
|
||||
<TABLE border="0" width="100%">
|
||||
<TBODY>
|
||||
<TR>
|
||||
<TD align="center">
|
||||
<IMG src="images/FunctionCallGraphProvider.png" alt="" border="1"></TD>
|
||||
</TR>
|
||||
</TBODY>
|
||||
</TABLE>
|
||||
|
||||
<BLOCKQUOTE>
|
||||
|
||||
<P>The Function Call Graph Plugin is a simple graph display that shows incoming and
|
||||
outgoing calls (as edges) for the function containing the current address, also known as
|
||||
the <I>Source Function</I>, in the
|
||||
Listing. This display provides some context for how a function is used within the
|
||||
program. The functions are organized by <A HREF="#Level">Level</A>.
|
||||
</P>
|
||||
|
||||
<P>
|
||||
To show the Function Call Graph provider window,
|
||||
select the <b>Window</b><img border="0" src="../../shared/arrow.gif">
|
||||
<b>Function Call Graph</b> option on the tool menu.
|
||||
</P>
|
||||
|
||||
<P>
|
||||
The graph of function calls related to the source function being displayed can be
|
||||
explored by <A HREF="#Expand_Collapse">adding existing function calls</A>
|
||||
to the initial graph display.
|
||||
</P>
|
||||
|
||||
<BLOCKQUOTE>
|
||||
<P>
|
||||
<IMG src="images/tip.png" border="0">The graph updates itself as you navigate within
|
||||
the tool. To prevent losing graph state (e.g., expanded functions, node locations,
|
||||
etc), a small number of graphs will be cached. For example, if you navigate
|
||||
away from a function and then immediately return, the graph will be restored to
|
||||
its previous state.
|
||||
</P>
|
||||
</BLOCKQUOTE>
|
||||
|
||||
|
||||
|
||||
<H2>Terms</H2>
|
||||
|
||||
<BLOCKQUOTE>
|
||||
|
||||
<UL>
|
||||
<LI><B>Source Function</B>: the function that contains the current address in
|
||||
the Listing. This function is considered the center of the graph, with all
|
||||
other callers/callees added to the graph at a new level.
|
||||
<BR>
|
||||
<BR>
|
||||
</LI>
|
||||
<LI><A NAME="Level"></A><B>Level</B>: Each function node in the graph belongs to a
|
||||
level. The source function is at level 1; the source function's incoming
|
||||
calls are at level 2; the source function's outgoing calls are also at level 2.
|
||||
Organizing functions by level allows the user to quickly see how many hops, or
|
||||
calls, a given function is from the source function.
|
||||
<BR>
|
||||
<BR>
|
||||
New levels of calls can be added to the graph by the user.
|
||||
<BR>
|
||||
<BR>
|
||||
</LI>
|
||||
|
||||
<LI><B>Direction</B>: Each function node, other than the source function, is
|
||||
considered to be in one of two directions: In or Out. All function nodes in
|
||||
a given level share the same direction. So, all nodes that directly call the
|
||||
source function node are considered to be the <I>In</I> direction; all nodes
|
||||
directly called by the source function are considered to be the <I>Out</I>
|
||||
direction.
|
||||
<BR>
|
||||
<BR>
|
||||
When a given node's level is expanded in the graph, the nodes added are based
|
||||
upon the selected node's direction: for <I>In</I> nodes, the newly added nodes
|
||||
will be those nodes that <B>call</B> the selected node; for <I>Out</I> nodes, the
|
||||
newly added nodes will be those nodes <B>called by</B> the selected node.
|
||||
<BR>
|
||||
<BR>
|
||||
</LI>
|
||||
<LI><B>Direct Edges</B>: An edge (a call) between two adjacent levels.
|
||||
<BR>
|
||||
<BR>
|
||||
</LI>
|
||||
<LI><A NAME="Indirect_Edges"></A><B>Indirect Edges</B>: An edge (a call)
|
||||
between two non-adjacent levels or an edge within the same level. These
|
||||
edges are rendered with less emphasis than <I>direct edges</I>.
|
||||
<BR>
|
||||
<BR>
|
||||
</LI>
|
||||
</UL>
|
||||
|
||||
</BLOCKQUOTE>
|
||||
|
||||
|
||||
|
||||
<H2>Actions</H2>
|
||||
|
||||
<BLOCKQUOTE>
|
||||
|
||||
<H3><A NAME="Expand_Collapse"></A>Show/Hide Edges Action</H3>
|
||||
|
||||
<P>
|
||||
Within the <I>Function Call Graph</I> you can show and hide function calls
|
||||
as desired.
|
||||
Showing additional function calls can be accomplished multiple ways. From
|
||||
any function node, you can select the <I>Expand</I> icon (
|
||||
<IMG SRC="Icons.EXPAND_ALL_ICON" />), which appears
|
||||
on a node when hovered. When clicked, this button will toggle related
|
||||
function calls: showing them if not already in the graph; hiding them
|
||||
if they are in the graph.
|
||||
</P>
|
||||
|
||||
<P>
|
||||
Additionally, these same functionality is provided from the popup menu
|
||||
actions (i.e., <B>Show/Hide Incoming Edges</B> and </B>Show/Hide Outgoing
|
||||
Edges</B>).
|
||||
</P>
|
||||
|
||||
<BLOCKQUOTE>
|
||||
<P>
|
||||
<IMG src="images/note.png" border="0">As new vertices are added to the
|
||||
graph, any <A HREF="#Indirect_Edges">indirect edges</A> will be added to the
|
||||
graph.
|
||||
</P>
|
||||
</BLOCKQUOTE>
|
||||
|
||||
<P>Note here how new vertices may appear in odd places when expanding (such as
|
||||
when they are already in the graph at a previous level).
|
||||
|
||||
<BLOCKQUOTE>
|
||||
<P>
|
||||
<IMG src="../../shared/warning.png" border="0">It is important to understand
|
||||
that the graph is only a subset of the entire program graph. This
|
||||
graph does not represent all functions and function calls in the
|
||||
program.
|
||||
</P>
|
||||
</BLOCKQUOTE>
|
||||
<BLOCKQUOTE>
|
||||
<P>
|
||||
<IMG src="../../shared/warning.png" border="0">Sometimes a function
|
||||
may have too many references to display in the graph. When this
|
||||
happens, the function node will be a gray color, with the expand icon
|
||||
replaced with a warning icon, as so:<BR>
|
||||
|
||||
<CENTER>
|
||||
<IMG src="images/TooManyReferences.png" border="1">
|
||||
</CENTER>
|
||||
|
||||
</P>
|
||||
</BLOCKQUOTE>
|
||||
|
||||
</BLOCKQUOTE>
|
||||
|
||||
<BLOCKQUOTE>
|
||||
|
||||
<H3>Show/Hide Level Edges Action</H3>
|
||||
|
||||
<P>
|
||||
All functions that relate to the <A HREF="#Level">Level</A> of
|
||||
the selected function will be shown, <b>not just calls to the selected
|
||||
function.</b>
|
||||
</P>
|
||||
|
||||
</BLOCKQUOTE>
|
||||
|
||||
<BLOCKQUOTE>
|
||||
|
||||
<H3><A NAME="Navigation_Incoming"></A>Navigate on Incoming Location Changes</H3>
|
||||
|
||||
<P>
|
||||
This action (<IMG SRC="Icons.NAVIGATE_ON_INCOMING_EVENT_ICON" />), when
|
||||
toggled on, upon receiving Program Location changes from the tool, will graph
|
||||
the function containing that location. When toggled off, location changes will
|
||||
not affect the graph.
|
||||
</P>
|
||||
<P>
|
||||
Having this action on is useful if you wish to quickly see the graph of
|
||||
different functions as you navigate the program. Alternatively, having this
|
||||
action off is useful when you wish to explore the program by navigating from
|
||||
within the graph, say by double-clicking function nodes in the graph.
|
||||
</P>
|
||||
|
||||
|
||||
</BLOCKQUOTE>
|
||||
|
||||
<BLOCKQUOTE>
|
||||
|
||||
<H3><A NAME="Relayout_Graph"></A>Layout Action</H3>
|
||||
|
||||
<P>This action (<IMG SRC="Icons.REFRESH_ICON" />) will relayout the current graph and
|
||||
reset the graph to show only the initial nodes.
|
||||
</P>
|
||||
|
||||
|
||||
</BLOCKQUOTE>
|
||||
|
||||
|
||||
<BLOCKQUOTE>
|
||||
|
||||
<H3><A NAME="Graph_Node_Function_Calls"></A>Graph '<I>Function Name</I>'</H3>
|
||||
|
||||
<P>This action is available from the popup menu of any node that is not the
|
||||
currently graphed node. When pressed, this action will graph the clicked
|
||||
function.
|
||||
</P>
|
||||
|
||||
|
||||
</BLOCKQUOTE>
|
||||
|
||||
|
||||
<H2><A name="Satellite_View"></A>Satellite View</H2>
|
||||
|
||||
<BLOCKQUOTE>
|
||||
<P>The Satellite View works exactly as the
|
||||
<A href="help/topics/FunctionGraphPlugin/Function_Graph.html#Satellite_View">
|
||||
Function Graph's Satellite View</A>.
|
||||
</P>
|
||||
</BLOCKQUOTE>
|
||||
|
||||
|
||||
</BLOCKQUOTE>
|
||||
|
||||
<P class="providedbyplugin">Provided by: <I>Function Call Graph Plugin</I></P>
|
||||
|
||||
<P class="relatedtopic">Related Topics:</P>
|
||||
|
||||
<UL>
|
||||
<!-- TODO update this when we introduce the generic graphing help -->
|
||||
<LI>Graphs</LI>
|
||||
</UL><BR>
|
||||
<BR>
|
||||
</BODY>
|
||||
</HTML>
|
After Width: | Height: | Size: 31 KiB |
After Width: | Height: | Size: 2.2 KiB |
|
@ -0,0 +1,38 @@
|
|||
/* ###
|
||||
* 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 functioncalls.graph;
|
||||
|
||||
/**
|
||||
* Represents whether a vertex is an incoming vertex (the start or from) on an edge,
|
||||
* an outgoing vertex (the end or to) on an edge, or if it is both.
|
||||
*/
|
||||
public enum FcgDirection {
|
||||
|
||||
// this order is from top to bottom: In -> In/Out -> Out
|
||||
IN, IN_AND_OUT, OUT;
|
||||
|
||||
public boolean isSource() {
|
||||
return this == IN_AND_OUT;
|
||||
}
|
||||
|
||||
public boolean isIn() {
|
||||
return this == IN;
|
||||
}
|
||||
|
||||
public boolean isOut() {
|
||||
return this == OUT;
|
||||
}
|
||||
}
|
|
@ -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.
|
||||
*/
|
||||
package functioncalls.graph;
|
||||
|
||||
import ghidra.graph.viewer.edge.AbstractVisualEdge;
|
||||
|
||||
/**
|
||||
* A {@link FunctionCallGraph} edge
|
||||
*/
|
||||
public class FcgEdge extends AbstractVisualEdge<FcgVertex> {
|
||||
|
||||
public FcgEdge(FcgVertex start, FcgVertex end) {
|
||||
super(start, end);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
// Suppressing warning on the return type; we know our class is the right type
|
||||
@Override
|
||||
public FcgEdge cloneEdge(FcgVertex start, FcgVertex end) {
|
||||
return new FcgEdge(start, end);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this edge is a direct edge from a lower level. Any other edges are
|
||||
* considered indirect and are less important in the graph.
|
||||
*
|
||||
* @return true if this edge is a direct edge from a lower level
|
||||
*/
|
||||
public boolean isDirectEdge() {
|
||||
FcgLevel startLevel = getStart().getLevel();
|
||||
FcgLevel endLevel = getEnd().getLevel();
|
||||
if (startLevel.isSource() || endLevel.isSource()) {
|
||||
// all info leaving the source is important/'direct'
|
||||
return true;
|
||||
}
|
||||
|
||||
FcgLevel parent = startLevel.parent();
|
||||
if (parent.equals(endLevel)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
FcgLevel child = startLevel.child();
|
||||
return child.equals(endLevel);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,219 @@
|
|||
/* ###
|
||||
* 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 functioncalls.graph;
|
||||
|
||||
import static functioncalls.graph.FcgDirection.IN_AND_OUT;
|
||||
import static functioncalls.graph.FcgDirection.OUT;
|
||||
|
||||
/**
|
||||
* A container class that represents a {@link FunctionCallGraph} level, or row. A level
|
||||
* is both the row of the vertex (the number of hops from the source vertex) and the
|
||||
* direction.
|
||||
*/
|
||||
public class FcgLevel implements Comparable<FcgLevel> {
|
||||
|
||||
/** A 1-based */
|
||||
private int row;
|
||||
private FcgDirection direction;
|
||||
|
||||
public static FcgLevel sourceLevel() {
|
||||
return new FcgLevel(0, IN_AND_OUT);
|
||||
}
|
||||
|
||||
public FcgLevel(int distance, FcgDirection direction) {
|
||||
this.row = toRow(distance);
|
||||
this.direction = direction;
|
||||
|
||||
if (row == 0) {
|
||||
throw new IllegalArgumentException("The FcgLevel uses a 1-based row system");
|
||||
}
|
||||
|
||||
if (row == 1 && direction != IN_AND_OUT) {
|
||||
throw new IllegalArgumentException("Row 1 must be FcgDirection.IN_AND_OUT");
|
||||
}
|
||||
}
|
||||
|
||||
private int toRow(int distance) {
|
||||
int oneBased = distance + 1;
|
||||
return (direction == OUT) ? -oneBased : oneBased;
|
||||
}
|
||||
|
||||
public int getRow() {
|
||||
return row;
|
||||
}
|
||||
|
||||
public int getDistance() {
|
||||
return Math.abs(row) - 1;
|
||||
}
|
||||
|
||||
public FcgDirection getDirection() {
|
||||
return direction;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this level represents the source level from which all other levels
|
||||
* emanate, which is row 1.
|
||||
*
|
||||
* @return true if this level represents the source level
|
||||
*/
|
||||
public boolean isSource() {
|
||||
return direction.isSource();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the parent level of this level. The parent of a level has the same direction
|
||||
* as this level, with a distance of one less than this level.
|
||||
*
|
||||
* @return returns the parent level of this level
|
||||
* @throws IllegalArgumentException if this is the source level, which is row 1
|
||||
*/
|
||||
public FcgLevel parent() {
|
||||
if (direction == IN_AND_OUT) {
|
||||
// undefined--we are the parent of all
|
||||
throw new IllegalArgumentException(
|
||||
"To get the parent of the source level you must use the constructor directly");
|
||||
}
|
||||
|
||||
int newDistance = getDistance() - 1;
|
||||
FcgDirection newDirection = direction;
|
||||
if (newDistance == 0) {
|
||||
newDirection = IN_AND_OUT;
|
||||
}
|
||||
return new FcgLevel(newDistance, newDirection);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the child level of this level. The child of a level has the same direction
|
||||
* as this level, with a distance of one more than this level.
|
||||
*
|
||||
* @return returns the child level of this level
|
||||
* @throws IllegalArgumentException if this is the source level, which is row 1
|
||||
*/
|
||||
public FcgLevel child() {
|
||||
if (direction == IN_AND_OUT) {
|
||||
// undefined--this node goes in both directions
|
||||
throw new IllegalArgumentException(
|
||||
"To get the child of the source level you " + "must use the constructor directly");
|
||||
}
|
||||
|
||||
return child(direction);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this level is the immediate predecessor of the given other level.
|
||||
*
|
||||
* <P>The <tt>source</tt> level is the parent of the first level in either direction.
|
||||
*
|
||||
* @param other the other level that is a potential child level
|
||||
* @return true if this level is the immediate predecessor of the given other level
|
||||
*/
|
||||
public boolean isParentOf(FcgLevel other) {
|
||||
if (isSource()) {
|
||||
return other.getDistance() == 1;
|
||||
}
|
||||
|
||||
if (direction != other.direction) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// e.g., row 2 - row 1 = 1
|
||||
return other.getDistance() - getDistance() == 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this level is the immediate successor of the given other level
|
||||
*
|
||||
* @param other the other level that is a potential child level
|
||||
* @return true if this level is the immediate successor of the given other level
|
||||
*/
|
||||
public boolean isChildOf(FcgLevel other) {
|
||||
return other.isParentOf(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the child level of this level. The child of a level has the same direction
|
||||
* as this level, with a distance of one more than this level.
|
||||
*
|
||||
* @param the direction of the child
|
||||
* @return returns the child level of this level
|
||||
* @throws IllegalArgumentException if this is the source level, which is row 1
|
||||
*/
|
||||
public FcgLevel child(FcgDirection newDirection) {
|
||||
if (newDirection == IN_AND_OUT) {
|
||||
// undefined--IN_AND_OUT goes in both directions
|
||||
throw new IllegalArgumentException("Direction cannot be IN_AND_OUT");
|
||||
}
|
||||
|
||||
int newDistance = getDistance() + 1;
|
||||
return new FcgLevel(newDistance, newDirection);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return direction + " - row " + Integer.toString(getRelativeRow());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + ((direction == null) ? 0 : direction.hashCode());
|
||||
result = prime * result + row;
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
FcgLevel other = (FcgLevel) obj;
|
||||
if (direction != other.direction) {
|
||||
return false;
|
||||
}
|
||||
if (row != other.row) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the row of this vertex
|
||||
* @return the row of this vertex
|
||||
*/
|
||||
private int getRelativeRow() {
|
||||
return direction == OUT ? -row : row;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(FcgLevel l2) {
|
||||
|
||||
int result = getDirection().compareTo(l2.getDirection());
|
||||
if (result != 0) {
|
||||
return result; // compare by direction first In on top; Out on bottom
|
||||
}
|
||||
|
||||
// same direction, use row
|
||||
return -(getRelativeRow() - l2.getRelativeRow());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,621 @@
|
|||
/* ###
|
||||
* 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 functioncalls.graph;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.geom.Area;
|
||||
import java.awt.geom.Ellipse2D;
|
||||
import java.awt.geom.Ellipse2D.Double;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.util.Objects;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.border.Border;
|
||||
import javax.swing.border.LineBorder;
|
||||
|
||||
import docking.widgets.EmptyBorderButton;
|
||||
import ghidra.graph.viewer.vertex.AbstractVisualVertex;
|
||||
import ghidra.graph.viewer.vertex.VertexShapeProvider;
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.model.listing.Function;
|
||||
import ghidra.util.StringUtilities;
|
||||
import resources.Icons;
|
||||
import resources.ResourceManager;
|
||||
|
||||
/**
|
||||
* A {@link FunctionCallGraph} vertex
|
||||
*/
|
||||
public class FcgVertex extends AbstractVisualVertex implements VertexShapeProvider {
|
||||
|
||||
// TODO to be made an option in an upcoming ticket
|
||||
public static final Color DEFAULT_VERTEX_SHAPE_COLOR = new Color(110, 197, 174);
|
||||
private static final Color TOO_BIG_VERTEX_SHAPE_COLOR = Color.LIGHT_GRAY;
|
||||
|
||||
public static final Icon NOT_ALLOWED_ICON = Icons.ERROR_ICON;
|
||||
private static final Icon EXPAND_ICON =
|
||||
ResourceManager.getScaledIcon(Icons.EXPAND_ALL_ICON, 10, 10);
|
||||
private static final Icon COLLAPSE_ICON =
|
||||
ResourceManager.getScaledIcon(Icons.COLLAPSE_ALL_ICON, 10, 10);
|
||||
|
||||
// higher numbered layers go on top
|
||||
private static final Integer VERTEX_SHAPE_LAYER = new Integer(100);
|
||||
private static final Integer TOGGLE_BUTTON_LAYER = new Integer(200);
|
||||
private static final Integer LABEL_LAYER = new Integer(300);
|
||||
|
||||
private static final int GAP = 2;
|
||||
private static final int VERTEX_SHAPE_SIZE = 50;
|
||||
|
||||
// TODO to be made an option in an upcoming ticket
|
||||
// based upon the default function name, plus some extra
|
||||
private static final int MAX_NAME_LENGTH = 30;
|
||||
|
||||
private Function function;
|
||||
|
||||
private JLayeredPane layeredPane;
|
||||
private JButton toggleInsButton = new EmptyBorderButton(EXPAND_ICON);
|
||||
private JButton toggleOutsButton = new EmptyBorderButton(EXPAND_ICON);
|
||||
private JLabel nameLabel = new JLabel();
|
||||
private JLabel vertexImageLabel = new JLabel();
|
||||
|
||||
private Double vertexShape;
|
||||
private Double compactShape;
|
||||
private Shape fullShape;
|
||||
|
||||
// these values are set after construction from external sources
|
||||
private boolean hasIncomingReferences;
|
||||
private boolean hasOutgoingReferences;
|
||||
private boolean tooManyIncomingReferences;
|
||||
private boolean tooManyOutgoingReferences;
|
||||
private boolean incomingExpanded;
|
||||
private boolean outgoingExpanded;
|
||||
|
||||
// set this to true to see borders around the components of this vertex
|
||||
private boolean useDebugBorders = false;
|
||||
|
||||
private Paint inPaint;
|
||||
private Paint outPaint;
|
||||
|
||||
private FcgLevel level;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param function the function represented by this vertex
|
||||
* @param level the level of this vertex
|
||||
* @param expansionListener the listener for expanding connections to this vertex
|
||||
*/
|
||||
public FcgVertex(Function function, FcgLevel level,
|
||||
FcgVertexExpansionListener expansionListener) {
|
||||
this.function = function;
|
||||
this.level = level;
|
||||
Objects.requireNonNull(expansionListener);
|
||||
|
||||
toggleInsButton.addActionListener(e -> {
|
||||
if (tooManyIncomingReferences) {
|
||||
return;
|
||||
}
|
||||
expansionListener.toggleIncomingVertices(FcgVertex.this);
|
||||
});
|
||||
|
||||
toggleOutsButton.addActionListener(e -> {
|
||||
if (tooManyOutgoingReferences) {
|
||||
return;
|
||||
}
|
||||
expansionListener.toggleOutgoingVertices(FcgVertex.this);
|
||||
});
|
||||
|
||||
buildUi();
|
||||
|
||||
setTogglesVisible(false);
|
||||
}
|
||||
|
||||
private void createPaints() {
|
||||
|
||||
Color vertexShapeColor = getVertexShapeColor();
|
||||
|
||||
Color lightColor = vertexShapeColor;
|
||||
Color darkColor = vertexShapeColor.darker();
|
||||
Color darkestColor = darkColor.darker();
|
||||
int offset = 5 * level.getDistance();
|
||||
int half = VERTEX_SHAPE_SIZE / 2;
|
||||
int start = 0;
|
||||
int end = half + offset;
|
||||
|
||||
// paint top-down: dark to light for incoming; light to dark for outgoing
|
||||
inPaint = new LinearGradientPaint(new Point(0, start), new Point(0, end),
|
||||
new float[] { .0f, .2f, 1f }, new Color[] { darkestColor, darkColor, lightColor });
|
||||
|
||||
start = half - offset; // (offset + 10);
|
||||
end = VERTEX_SHAPE_SIZE;
|
||||
outPaint = new LinearGradientPaint(new Point(0, start), new Point(0, end),
|
||||
new float[] { .0f, .8f, 1f }, new Color[] { lightColor, darkColor, darkestColor });
|
||||
}
|
||||
|
||||
private void buildUi() {
|
||||
|
||||
createPaints();
|
||||
|
||||
// init the components
|
||||
String truncated = StringUtilities.trimMiddle(getName(), MAX_NAME_LENGTH);
|
||||
nameLabel.setText(truncated);
|
||||
buildVertexShape();
|
||||
|
||||
// calculate the needed size
|
||||
layeredPane = new JLayeredPane();
|
||||
Border border = createDebugBorder(new LineBorder(Color.YELLOW.darker(), 1));
|
||||
layeredPane.setBorder(border);
|
||||
|
||||
updateLayeredPaneSize();
|
||||
|
||||
// layout the components
|
||||
addVertexShape();
|
||||
addToggleButtons();
|
||||
addNameLabel();
|
||||
|
||||
buildFullShape();
|
||||
}
|
||||
|
||||
private Border createDebugBorder(Border border) {
|
||||
if (useDebugBorders) {
|
||||
return border;
|
||||
}
|
||||
return BorderFactory.createEmptyBorder();
|
||||
}
|
||||
|
||||
private void buildFullShape() {
|
||||
|
||||
// Note: this method assumes all bounds have been set
|
||||
Area parent = new Area();
|
||||
|
||||
Area v = new Area(vertexShape);
|
||||
Area name = new Area(nameLabel.getBounds());
|
||||
parent.add(v);
|
||||
parent.add(name);
|
||||
|
||||
// for now, the buttons only appear on hover, but if we want to avoid clipping when
|
||||
// painting, we need to account for them in the shape's overall bounds
|
||||
Area in = new Area(toggleInsButton.getBounds());
|
||||
Area out = new Area(toggleOutsButton.getBounds());
|
||||
parent.add(in);
|
||||
parent.add(out);
|
||||
|
||||
fullShape = parent;
|
||||
}
|
||||
|
||||
private void updateLayeredPaneSize() {
|
||||
|
||||
//
|
||||
// The overall component size is the total width and height of all components, with any
|
||||
// spacing between them.
|
||||
//
|
||||
|
||||
Dimension shapeSize = vertexImageLabel.getPreferredSize();
|
||||
Dimension nameLabelSize = nameLabel.getPreferredSize();
|
||||
int height = shapeSize.height + GAP + nameLabelSize.height;
|
||||
|
||||
Dimension insSize = toggleInsButton.getPreferredSize();
|
||||
Dimension outsSize = toggleOutsButton.getPreferredSize();
|
||||
int buttonWidth = Math.max(insSize.width, outsSize.width);
|
||||
int offset = buttonWidth / 3; // overlap the vertex shape
|
||||
|
||||
int width = offset + shapeSize.width;
|
||||
width = Math.max(width, nameLabelSize.width);
|
||||
|
||||
layeredPane.setPreferredSize(new Dimension(width, height));
|
||||
}
|
||||
|
||||
private void buildVertexShape() {
|
||||
int w = VERTEX_SHAPE_SIZE;
|
||||
int h = VERTEX_SHAPE_SIZE;
|
||||
Double circle = new Ellipse2D.Double(0, 0, w, h);
|
||||
|
||||
BufferedImage image = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
|
||||
Graphics2D g2 = (Graphics2D) image.getGraphics();
|
||||
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
||||
|
||||
FcgDirection direction = level.getDirection();
|
||||
if (direction.isSource()) {
|
||||
g2.setColor(getVertexShapeColor());
|
||||
}
|
||||
else if (direction.isIn()) {
|
||||
g2.setPaint(inPaint);
|
||||
}
|
||||
else {
|
||||
g2.setPaint(outPaint);
|
||||
}
|
||||
|
||||
g2.fill(circle);
|
||||
|
||||
g2.dispose();
|
||||
|
||||
vertexShape = circle;
|
||||
compactShape = (Double) vertexShape.clone();
|
||||
vertexImageLabel.setIcon(new ImageIcon(image));
|
||||
|
||||
Border border = createDebugBorder(new LineBorder(Color.PINK, 1));
|
||||
vertexImageLabel.setBorder(border);
|
||||
}
|
||||
|
||||
private Color getVertexShapeColor() {
|
||||
|
||||
if (isInDirection() && tooManyIncomingReferences) {
|
||||
return TOO_BIG_VERTEX_SHAPE_COLOR;
|
||||
}
|
||||
|
||||
if (isOutDirection() && tooManyOutgoingReferences) {
|
||||
return TOO_BIG_VERTEX_SHAPE_COLOR;
|
||||
}
|
||||
|
||||
return DEFAULT_VERTEX_SHAPE_COLOR;
|
||||
}
|
||||
|
||||
private boolean isInDirection() {
|
||||
FcgDirection direction = level.getDirection();
|
||||
boolean isIn = direction.isIn() || direction.isSource();
|
||||
return isIn;
|
||||
}
|
||||
|
||||
private boolean isOutDirection() {
|
||||
FcgDirection direction = level.getDirection();
|
||||
boolean isOut = direction.isOut() || direction.isSource();
|
||||
return isOut;
|
||||
}
|
||||
|
||||
private void addVertexShape() {
|
||||
|
||||
Dimension parentSize = layeredPane.getPreferredSize();
|
||||
Dimension size = vertexImageLabel.getPreferredSize();
|
||||
|
||||
// centered
|
||||
int x = (parentSize.width / 2) - (size.width / 2);
|
||||
int y = 0;
|
||||
|
||||
vertexImageLabel.setBounds(x, y, size.width, size.height);
|
||||
Dimension shapeSize = vertexShape.getBounds().getSize();
|
||||
|
||||
// setFrame() will make sure the shape's x,y values are where they need to be
|
||||
// for the later 'full shape' creation
|
||||
vertexShape.setFrame(x, y, shapeSize.width, shapeSize.height);
|
||||
layeredPane.add(vertexImageLabel, VERTEX_SHAPE_LAYER);
|
||||
}
|
||||
|
||||
private void addNameLabel() {
|
||||
|
||||
Border border = createDebugBorder(new LineBorder(Color.GREEN, 1));
|
||||
nameLabel.setBorder(border);
|
||||
|
||||
// assume the vertex label has been bounded
|
||||
Rectangle parentBounds = vertexImageLabel.getBounds();
|
||||
Dimension size = nameLabel.getPreferredSize();
|
||||
|
||||
// bottom, centered under the shape
|
||||
int x = (parentBounds.x + (parentBounds.width / 2)) - (size.width / 2);
|
||||
int y = parentBounds.y + parentBounds.height + GAP;
|
||||
nameLabel.setBounds(x, y, size.width, size.height);
|
||||
layeredPane.add(nameLabel, LABEL_LAYER);
|
||||
}
|
||||
|
||||
private void addToggleButtons() {
|
||||
|
||||
// hide the button background
|
||||
toggleInsButton.setBackground(new Color(255, 255, 255, 0));
|
||||
toggleOutsButton.setBackground(new Color(255, 255, 255, 0));
|
||||
|
||||
Rectangle parentBounds = vertexImageLabel.getBounds();
|
||||
Dimension size = toggleInsButton.getPreferredSize();
|
||||
|
||||
// upper toggle; upper-left
|
||||
int x = parentBounds.x - (size.width / 3);
|
||||
int y = 0;
|
||||
toggleInsButton.setBounds(x, y, size.width, size.height);
|
||||
layeredPane.add(toggleInsButton, TOGGLE_BUTTON_LAYER);
|
||||
|
||||
// lower toggle; lower-left, lined-up with the vertex shape
|
||||
size = toggleOutsButton.getPreferredSize();
|
||||
Dimension vertexSize = parentBounds.getSize();
|
||||
y = vertexSize.height - size.height;
|
||||
toggleOutsButton.setBounds(x, y, size.width, size.height);
|
||||
layeredPane.add(toggleOutsButton, TOGGLE_BUTTON_LAYER);
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return function.getName();
|
||||
}
|
||||
|
||||
public Function getFunction() {
|
||||
return function;
|
||||
}
|
||||
|
||||
public Address getAddress() {
|
||||
return function.getEntryPoint();
|
||||
}
|
||||
|
||||
public FcgLevel getLevel() {
|
||||
return level;
|
||||
}
|
||||
|
||||
public int getDegree() {
|
||||
return level.getRow();
|
||||
}
|
||||
|
||||
public FcgDirection getDirection() {
|
||||
return level.getDirection();
|
||||
}
|
||||
|
||||
public JButton getIncomingToggleButton() {
|
||||
return toggleInsButton;
|
||||
}
|
||||
|
||||
public JButton getOutgoingToggleButton() {
|
||||
return toggleOutsButton;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets to true if this vertex is showing all edges in the incoming direction
|
||||
*
|
||||
* @param setExpanded true if this vertex is showing all edges in the incoming direction
|
||||
*/
|
||||
public void setIncomingExpanded(boolean setExpanded) {
|
||||
|
||||
validateIncomingExpandedState(setExpanded);
|
||||
|
||||
this.incomingExpanded = setExpanded;
|
||||
toggleInsButton.setIcon(setExpanded ? COLLAPSE_ICON : EXPAND_ICON);
|
||||
String hideShow = setExpanded ? "hide" : "show";
|
||||
toggleInsButton.setToolTipText("Click to " + hideShow + " incoming edges");
|
||||
}
|
||||
|
||||
private void validateOutgoingExpandedState(boolean isExpanding) {
|
||||
if (isExpanding) {
|
||||
if (!canExpandOutgoingReferences()) {
|
||||
throw new IllegalStateException("Vertex cannot be expanded: " + this);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// collapsing
|
||||
if (!isOutgoingExpanded()) {
|
||||
throw new IllegalStateException("Vertex cannot be collapsed: " + this);
|
||||
}
|
||||
}
|
||||
|
||||
private void validateIncomingExpandedState(boolean expanding) {
|
||||
|
||||
if (expanding) {
|
||||
if (!canExpandIncomingReferences()) {
|
||||
throw new IllegalStateException("Vertex cannot be expanded: " + this);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// collapsing
|
||||
if (!isIncomingExpanded()) {
|
||||
throw new IllegalStateException("Vertex cannot be collapsed: " + this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this vertex is showing all edges in the incoming direction
|
||||
*
|
||||
* @return true if this vertex is showing all edges in the incoming direction
|
||||
*/
|
||||
public boolean isIncomingExpanded() {
|
||||
return incomingExpanded;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets to true if this vertex is showing all edges in the outgoing direction
|
||||
*
|
||||
* @param setExpanded true if this vertex is showing all edges in the outgoing direction
|
||||
*/
|
||||
public void setOutgoingExpanded(boolean setExpanded) {
|
||||
|
||||
validateOutgoingExpandedState(setExpanded);
|
||||
|
||||
this.outgoingExpanded = setExpanded;
|
||||
toggleOutsButton.setIcon(setExpanded ? COLLAPSE_ICON : EXPAND_ICON);
|
||||
String hideShow = setExpanded ? "hide" : "show";
|
||||
toggleInsButton.setToolTipText("Click to " + hideShow + " outgoing edges");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this vertex is showing all edges in the outgoing direction
|
||||
*
|
||||
* @return true if this vertex is showing all edges in the outgoing direction
|
||||
*/
|
||||
public boolean isOutgoingExpanded() {
|
||||
return outgoingExpanded;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this vertex is fully expanded in its current direction
|
||||
*
|
||||
* @return whether this vertex is fully expanded in its current direction
|
||||
*/
|
||||
public boolean isExpanded() {
|
||||
FcgDirection direction = level.getDirection();
|
||||
if (direction.isSource()) {
|
||||
return isIncomingExpanded() && isOutgoingExpanded();
|
||||
}
|
||||
if (direction.isIn()) {
|
||||
return isIncomingExpanded();
|
||||
}
|
||||
return isOutgoingExpanded();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether this vertex has too many incoming references, where too many is subjectively
|
||||
* defined by this class. Too many nodes in the display would ruin rendering and general
|
||||
* usability.
|
||||
*
|
||||
* @param tooMany if there are too many references
|
||||
*/
|
||||
public void setTooManyIncomingReferences(boolean tooMany) {
|
||||
this.tooManyIncomingReferences = tooMany;
|
||||
toggleInsButton.setIcon(NOT_ALLOWED_ICON);
|
||||
toggleInsButton.setToolTipText("Too many incoming references to show");
|
||||
buildUi();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether this vertex has too many outgoing references, where too many is subjectively
|
||||
* defined by this class. Too many nodes in the display would ruin rendering and general
|
||||
* usability.
|
||||
*
|
||||
* @param tooMany if there are too many references
|
||||
*/
|
||||
public void setTooManyOutgoingReferences(boolean tooMany) {
|
||||
this.tooManyOutgoingReferences = tooMany;
|
||||
toggleOutsButton.setIcon(NOT_ALLOWED_ICON);
|
||||
toggleOutsButton.setToolTipText("Too many outgoing references to show");
|
||||
buildUi();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this vertex has too many incoming references, where too many is subjectively
|
||||
* defined by this class. Too many nodes in the display would ruin rendering and general
|
||||
* usability.
|
||||
*
|
||||
* @return true if there are too many references
|
||||
*/
|
||||
public boolean hasTooManyIncomingReferences() {
|
||||
return tooManyIncomingReferences;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this vertex has too many outgoing references, where too many is subjectively
|
||||
* defined by this class. Too many nodes in the display would ruin rendering and general
|
||||
* usability.
|
||||
*
|
||||
* @return true if there are too many references
|
||||
*/
|
||||
public boolean hasTooManyOutgoingReferences() {
|
||||
return tooManyOutgoingReferences;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this vertex can expand itself in its current direction, or in either
|
||||
* direction if this is a source vertex
|
||||
*
|
||||
* @return true if this vertex can be expanded
|
||||
*/
|
||||
public boolean canExpand() {
|
||||
FcgDirection direction = level.getDirection();
|
||||
if (direction.isSource()) {
|
||||
return canExpandIncomingReferences() || canExpandOutgoingReferences();
|
||||
}
|
||||
|
||||
if (direction.isIn()) {
|
||||
return canExpandIncomingReferences();
|
||||
}
|
||||
|
||||
return canExpandOutgoingReferences();
|
||||
}
|
||||
|
||||
public boolean canExpandIncomingReferences() {
|
||||
return hasIncomingReferences && !tooManyIncomingReferences && !incomingExpanded;
|
||||
}
|
||||
|
||||
public boolean canExpandOutgoingReferences() {
|
||||
return hasOutgoingReferences && !tooManyOutgoingReferences && !outgoingExpanded;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether this vertex has any incoming references
|
||||
*
|
||||
* @param hasIncoming true if this vertex has any incoming references
|
||||
*/
|
||||
public void setHasIncomingReferences(boolean hasIncoming) {
|
||||
this.hasIncomingReferences = hasIncoming;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether this vertex has any outgoing references
|
||||
*
|
||||
* @param hasIncoming true if this vertex has any incoming references
|
||||
*/
|
||||
|
||||
public void setHasOutgoingReferences(boolean hasOutgoing) {
|
||||
this.hasOutgoingReferences = hasOutgoing;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setHovered(boolean hovered) {
|
||||
super.setHovered(hovered);
|
||||
|
||||
setTogglesVisible(hovered);
|
||||
}
|
||||
|
||||
private void setTogglesVisible(boolean visible) {
|
||||
|
||||
boolean isIn = isInDirection();
|
||||
boolean turnOn = isIn && hasIncomingReferences && visible;
|
||||
toggleInsButton.setVisible(turnOn);
|
||||
|
||||
boolean isOut = isOutDirection();
|
||||
turnOn = isOut && hasOutgoingReferences && visible;
|
||||
toggleOutsButton.setVisible(turnOn);
|
||||
}
|
||||
|
||||
@Override
|
||||
public JComponent getComponent() {
|
||||
return layeredPane;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Shape getCompactShape() {
|
||||
return compactShape;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Shape getFullShape() {
|
||||
return fullShape;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getName();// + " @ " + level; // + " (" + System.identityHashCode(this) + ')';
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + ((function == null) ? 0 : function.hashCode());
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
FcgVertex other = (FcgVertex) obj;
|
||||
return Objects.equals(function, other.function);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
// nothing to do
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
/* ###
|
||||
* 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 functioncalls.graph;
|
||||
|
||||
/**
|
||||
* A listener to know when a vertex has been told to expand
|
||||
*/
|
||||
public interface FcgVertexExpansionListener {
|
||||
|
||||
/**
|
||||
* Show or hide those vertices that are on incoming edges to v
|
||||
*
|
||||
* @param v the vertex
|
||||
*/
|
||||
public void toggleIncomingVertices(FcgVertex v);
|
||||
|
||||
/**
|
||||
* Show or hide those vertices that are on outgoing edges to v
|
||||
*
|
||||
* @param v the vertex
|
||||
*/
|
||||
public void toggleOutgoingVertices(FcgVertex v);
|
||||
}
|
|
@ -0,0 +1,161 @@
|
|||
/* ###
|
||||
* 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 functioncalls.graph;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import org.apache.commons.collections4.IterableUtils;
|
||||
import org.apache.commons.collections4.map.LazyMap;
|
||||
|
||||
import functioncalls.plugin.FunctionCallGraphPlugin;
|
||||
import ghidra.graph.graphs.FilteringVisualGraph;
|
||||
import ghidra.graph.viewer.layout.VisualGraphLayout;
|
||||
import ghidra.program.model.listing.Function;
|
||||
|
||||
/**
|
||||
* A graph for the {@link FunctionCallGraphPlugin}
|
||||
*/
|
||||
public class FunctionCallGraph extends FilteringVisualGraph<FcgVertex, FcgEdge> {
|
||||
|
||||
private VisualGraphLayout<FcgVertex, FcgEdge> layout;
|
||||
private FcgVertex source;
|
||||
private Map<Function, FcgVertex> verticesByFunction = new HashMap<>();
|
||||
private Comparator<FcgVertex> vertexComparator =
|
||||
(v1, v2) -> v1.getAddress().compareTo(v2.getAddress());
|
||||
private Map<FcgLevel, Set<FcgVertex>> verticesByLevel =
|
||||
LazyMap.lazyMap(new HashMap<>(), () -> new TreeSet<>(vertexComparator));
|
||||
|
||||
/**
|
||||
* Sets the source vertex from which the graph is created
|
||||
* @param source the source vertex from which the graph is created
|
||||
*/
|
||||
public void setSource(FcgVertex source) {
|
||||
if (this.source != null) {
|
||||
throw new IllegalStateException("Cannot change graph source once it has been created");
|
||||
}
|
||||
|
||||
this.source = source;
|
||||
addVertex(source);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the vertex from which the graph is created
|
||||
* @return the vertex from which the graph is created
|
||||
*/
|
||||
public FcgVertex getSource() {
|
||||
return source;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the vertex mapped to the given function; null if there is no matching vertex
|
||||
*
|
||||
* @param f the function
|
||||
* @return the vertex
|
||||
*/
|
||||
public FcgVertex getVertex(Function f) {
|
||||
return verticesByFunction.get(f);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this graph contains a vertex for the given function
|
||||
*
|
||||
* @param f the function
|
||||
* @return true if this graph contains a vertex for the given function
|
||||
*/
|
||||
public boolean containsFunction(Function f) {
|
||||
return verticesByFunction.containsKey(f);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all vertices in the given level. The result will be non-null.
|
||||
*
|
||||
* @param level the level of the vertices to retrieve
|
||||
* @return all vertices in the given level
|
||||
*/
|
||||
public Iterable<FcgVertex> getVerticesByLevel(FcgLevel level) {
|
||||
return IterableUtils.unmodifiableIterable(verticesByLevel.get(level));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the largest level (the furthest level from the source node) in the given
|
||||
* direction
|
||||
*
|
||||
* @param direction the direction to search
|
||||
* @return the largest level
|
||||
*/
|
||||
public FcgLevel getLargestLevel(FcgDirection direction) {
|
||||
|
||||
FcgLevel greatest = new FcgLevel(1, direction);
|
||||
|
||||
Set<FcgLevel> keys = verticesByLevel.keySet();
|
||||
for (FcgLevel level : keys) {
|
||||
if (level.getDirection() != direction) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (level.getRow() > greatest.getRow()) {
|
||||
greatest = level;
|
||||
}
|
||||
}
|
||||
|
||||
return greatest;
|
||||
}
|
||||
|
||||
@Override
|
||||
public VisualGraphLayout<FcgVertex, FcgEdge> getLayout() {
|
||||
return layout;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FunctionCallGraph copy() {
|
||||
|
||||
FunctionCallGraph newGraph = new FunctionCallGraph();
|
||||
for (FcgVertex v : vertices.keySet()) {
|
||||
newGraph.addVertex(v);
|
||||
}
|
||||
|
||||
for (FcgEdge e : edges.keySet()) {
|
||||
newGraph.addEdge(e);
|
||||
}
|
||||
|
||||
return newGraph;
|
||||
}
|
||||
|
||||
public void setLayout(VisualGraphLayout<FcgVertex, FcgEdge> layout) {
|
||||
this.layout = layout;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void verticesAdded(Collection<FcgVertex> added) {
|
||||
for (FcgVertex v : added) {
|
||||
Function f = v.getFunction();
|
||||
verticesByFunction.put(f, v);
|
||||
verticesByLevel.get(v.getLevel()).add(v);
|
||||
}
|
||||
super.verticesAdded(added);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void verticesRemoved(Collection<FcgVertex> removed) {
|
||||
for (FcgVertex v : removed) {
|
||||
Function f = v.getFunction();
|
||||
verticesByFunction.remove(f);
|
||||
verticesByLevel.get(v.getLevel()).remove(v);
|
||||
}
|
||||
super.verticesRemoved(removed);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,365 @@
|
|||
/* ###
|
||||
* 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 functioncalls.graph.job;
|
||||
|
||||
import java.awt.Rectangle;
|
||||
import java.awt.Shape;
|
||||
import java.awt.geom.Point2D;
|
||||
import java.awt.geom.Rectangle2D;
|
||||
import java.util.*;
|
||||
|
||||
import com.google.common.base.Function;
|
||||
|
||||
import edu.uci.ics.jung.algorithms.layout.Layout;
|
||||
import edu.uci.ics.jung.visualization.RenderContext;
|
||||
import functioncalls.graph.*;
|
||||
import functioncalls.graph.layout.BowTieLayout;
|
||||
import ghidra.graph.job.AbstractGraphTransitionJob;
|
||||
import ghidra.graph.viewer.GraphViewer;
|
||||
import ghidra.graph.viewer.GraphViewerUtils;
|
||||
import ghidra.util.Msg;
|
||||
|
||||
/**
|
||||
* A graph job to layout a given set of graph vertices. Most of the work for this class is
|
||||
* done in {@link #arrangeNewVertices()}.
|
||||
*
|
||||
* <P>This class is handed a group of edges to processes. In this group there are vertices that
|
||||
* do not need to be arranged, referred to as the <tt>existing</tt> vertices. This
|
||||
* classes uses {@link VertexCollection} to find and store the new vertices that need
|
||||
* to be arranged.
|
||||
*/
|
||||
public class BowTieExpandVerticesJob extends AbstractGraphTransitionJob<FcgVertex, FcgEdge> {
|
||||
|
||||
private boolean incoming;
|
||||
private FcgLevel expandingLevel;
|
||||
private FcgExpandingVertexCollection newVertexCollection;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param viewer the graph viewer
|
||||
* @param newVertexCollection the collection of new vertices and edges being addeds
|
||||
* @param useAnimation true to use animation
|
||||
*/
|
||||
public BowTieExpandVerticesJob(GraphViewer<FcgVertex, FcgEdge> viewer,
|
||||
FcgExpandingVertexCollection newVertexCollection, boolean useAnimation) {
|
||||
super(viewer, useAnimation);
|
||||
|
||||
this.newVertexCollection = newVertexCollection;
|
||||
this.incoming = newVertexCollection.isIncoming();
|
||||
this.expandingLevel = newVertexCollection.getExpandingLevel();
|
||||
|
||||
if (!(graphLayout instanceof BowTieLayout)) {
|
||||
throw new IllegalArgumentException("The current graph layout must be the " +
|
||||
BowTieLayout.class.getSimpleName() + " to use this job");
|
||||
}
|
||||
|
||||
Msg.trace(this,
|
||||
"\nBow Tie Expand Job - new vertices: " + newVertexCollection.getNewVertices());
|
||||
|
||||
// for debug
|
||||
// duration = 5000;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isTooBigToAnimate() {
|
||||
return graph.getVertexCount() > 1000; // not sure about the best number here
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateOpacity(double percentComplete) {
|
||||
|
||||
/*
|
||||
Aesthetic Note: due to the colors used in the graph in conjunction with the
|
||||
alpha used here, when using the same opacity for the vertices and the edges, the
|
||||
edges can be seen through the vertices, which looks bad. To fix this, have the
|
||||
edges be less visible until the vertices are more opaque.
|
||||
*/
|
||||
double x = percentComplete;
|
||||
double x2 = x * x; // change slower than x
|
||||
double remaining = 1 - percentComplete; // start opacity towards the end
|
||||
double y = x2 - remaining;
|
||||
|
||||
//Msg.debug(this, String.format("%%: %.3f - x^2: %.3f - remaining: %.3f - edge alpha: %.3f",
|
||||
// percentComplete, (x * x), remaining, y));
|
||||
|
||||
Set<FcgVertex> newVertices = newVertexCollection.getNewVertices();
|
||||
|
||||
double vertexAlpha = x;
|
||||
double edgeAlpha = Math.max(y, 0);
|
||||
for (FcgVertex v : newVertices) {
|
||||
v.setAlpha(vertexAlpha);
|
||||
}
|
||||
|
||||
Iterable<FcgEdge> newEdges = newVertexCollection.getNewEdges();
|
||||
for (FcgEdge edge : newEdges) {
|
||||
edge.setAlpha(edgeAlpha);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canShortcut() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void shortcut() {
|
||||
isShortcut = true;
|
||||
|
||||
if (vertexLocations.isEmpty()) {
|
||||
// have not yet initialized; do so now before the final locations are applied
|
||||
initializeVertexLocations();
|
||||
}
|
||||
|
||||
stop();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initializeVertexLocations() {
|
||||
|
||||
Map<FcgVertex, TransitionPoints> destinationLocations = createDestinationLocation();
|
||||
vertexLocations.putAll(destinationLocations);
|
||||
}
|
||||
|
||||
private Map<FcgVertex, TransitionPoints> createDestinationLocation() {
|
||||
|
||||
// note: both collections of vertices are sorted
|
||||
Map<FcgVertex, Point2D> finalDestinations = arrangeNewVertices();
|
||||
|
||||
Map<FcgVertex, TransitionPoints> transitions = new HashMap<>();
|
||||
FcgLevel parentLevel = expandingLevel.parent();
|
||||
Iterable<FcgEdge> newEdges = newVertexCollection.getNewEdges();
|
||||
Set<FcgVertex> newVertices = newVertexCollection.getNewVertices();
|
||||
for (FcgEdge e : newEdges) {
|
||||
|
||||
FcgVertex newVertex = incoming ? e.getStart() : e.getEnd();
|
||||
if (!finalDestinations.containsKey(newVertex)) {
|
||||
continue; // this implies the edges is between 2 existing vertices
|
||||
}
|
||||
|
||||
if (!newVertices.contains(newVertex)) {
|
||||
continue; // a new edge to an existing vertex
|
||||
}
|
||||
|
||||
FcgVertex existingVertex = incoming ? e.getEnd() : e.getStart();
|
||||
FcgLevel existingLevel = existingVertex.getLevel();
|
||||
if (!existingLevel.equals(parentLevel)) {
|
||||
// Only the parent level can be the source of the transition. This ensures
|
||||
// that the animation starts at the level that was expanded and not at some
|
||||
// other level in the graph that happened to have an edge to the new node.
|
||||
continue;
|
||||
}
|
||||
|
||||
Point2D start = (Point2D) toLocation(existingVertex).clone();
|
||||
Point2D end = finalDestinations.get(newVertex);
|
||||
|
||||
TransitionPoints trans = new TransitionPoints(start, end);
|
||||
transitions.put(newVertex, trans);
|
||||
}
|
||||
|
||||
return transitions;
|
||||
}
|
||||
|
||||
private Map<FcgVertex, Point2D> arrangeNewVertices() {
|
||||
|
||||
/*
|
||||
Add the new row above (or below) the existing row that is being expanded. So,
|
||||
the new graph will appear as so:
|
||||
|
||||
v3.1 v3.2 v3.4 v3.5 v3.6
|
||||
v2.1 v2.2
|
||||
v1.1
|
||||
|
||||
Where the '3.x' nodes are those being added. They will be centered in relation to
|
||||
the existing row.
|
||||
|
||||
*/
|
||||
|
||||
BowTieLayout bowTie = (BowTieLayout) graphLayout;
|
||||
boolean isCondensed = bowTie.isCondensedLayout();
|
||||
int widthPadding = isCondensed ? GraphViewerUtils.EXTRA_LAYOUT_COLUMN_SPACING_CONDENSED
|
||||
: GraphViewerUtils.EXTRA_LAYOUT_COLUMN_SPACING;
|
||||
|
||||
// More space the further away from center, for aesthetics/readability (as the graph
|
||||
// gets larger, more edges are added, cluttering up the display).
|
||||
widthPadding *= expandingLevel.getDistance();
|
||||
int heightPadding = calculateHeightPadding(isCondensed);
|
||||
|
||||
FcgLevel parentLevel = expandingLevel.parent();
|
||||
List<FcgVertex> parentLevelVertices = newVertexCollection.getVerticesByLevel(parentLevel);
|
||||
if (parentLevelVertices.isEmpty()) {
|
||||
// this can happen if all the new edges being added already exist in the graph
|
||||
// at a different level than the parent
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
|
||||
Rectangle existingRowBounds = getBounds(parentLevelVertices);
|
||||
Msg.trace(this, "existing row bounds " + existingRowBounds);
|
||||
double existingY = existingRowBounds.y;
|
||||
double existingCenterX = existingRowBounds.x + (existingRowBounds.width / 2);
|
||||
|
||||
// Layout all vertices at the new level, even the hidden ones, so the new ones fit
|
||||
// within the overall level. This allows future expansions to put the new nodes in
|
||||
// the correct spot.
|
||||
List<FcgVertex> allLevelVertices = newVertexCollection.getAllVerticesAtNewLevel();
|
||||
double newRowWidth = getWidth(allLevelVertices, widthPadding);
|
||||
double newRowHeight = getHeight(allLevelVertices);
|
||||
double newRowX = existingCenterX - (newRowWidth / 2);
|
||||
|
||||
double newRowY = 0;
|
||||
if (newVertexCollection.isIncoming()) {
|
||||
newRowY = existingY - newRowHeight - heightPadding;
|
||||
}
|
||||
else {
|
||||
newRowY = existingY + existingRowBounds.height + heightPadding;
|
||||
}
|
||||
|
||||
Msg.trace(this, "new row bounds " +
|
||||
new Rectangle2D.Double(newRowX, newRowY, newRowWidth, newRowHeight));
|
||||
|
||||
Map<FcgVertex, Point2D> locations = getExistingLocations(allLevelVertices);
|
||||
if (!locations.isEmpty()) {
|
||||
// use the existing locations so that the nodes appear where the user expects
|
||||
return locations;
|
||||
}
|
||||
|
||||
RenderContext<FcgVertex, FcgEdge> renderContext = viewer.getRenderContext();
|
||||
Function<? super FcgVertex, Shape> shaper = renderContext.getVertexShapeTransformer();
|
||||
|
||||
double x = newRowX;
|
||||
double y = newRowY;
|
||||
|
||||
int n = allLevelVertices.size();
|
||||
for (int i = 0; i < n; i++) {
|
||||
FcgVertex v = allLevelVertices.get(i);
|
||||
Rectangle myBounds = shaper.apply(v).getBounds();
|
||||
double myHalf = myBounds.width / 2;
|
||||
|
||||
double nextHalf = 0;
|
||||
boolean isLast = i == n - 1;
|
||||
if (!isLast) {
|
||||
FcgVertex nextV = allLevelVertices.get(i + 1);
|
||||
Rectangle nextBounds = shaper.apply(nextV).getBounds();
|
||||
nextHalf = nextBounds.width / 2;
|
||||
}
|
||||
|
||||
Point2D p = new Point2D.Double(x, y);
|
||||
locations.put(v, p);
|
||||
|
||||
double vWidth = myHalf + widthPadding + nextHalf;
|
||||
Msg.trace(this, v + " at x,width: " + x + "," + vWidth);
|
||||
x += vWidth;
|
||||
}
|
||||
|
||||
return locations;
|
||||
}
|
||||
|
||||
private int calculateHeightPadding(boolean isCondensed) {
|
||||
|
||||
// give each successive level
|
||||
int basePadding = isCondensed ? GraphViewerUtils.EXTRA_LAYOUT_ROW_SPACING_CONDENSED
|
||||
: GraphViewerUtils.EXTRA_LAYOUT_ROW_SPACING;
|
||||
|
||||
double separationFactor = expandingLevel.getDistance();
|
||||
|
||||
/*
|
||||
Let's scale the distance between 2 rows. As the level increases so too should the
|
||||
distance, based upon how busyness (how many edges) of the new level being added.
|
||||
-If the new level is not busy, then keep the standard distance;
|
||||
-If the new level is busy, increase the y-distance, up to the max, based upon
|
||||
the busyness
|
||||
|
||||
*/
|
||||
List<FcgVertex> allLevelVertices = newVertexCollection.getAllVerticesAtNewLevel();
|
||||
int count = allLevelVertices.size();
|
||||
|
||||
// grow each layer more than linear to add space for the edges
|
||||
double to = 1.25; // 1.5;
|
||||
double power = Math.pow(separationFactor, to);
|
||||
int maxPadding = (int) (basePadding * power);
|
||||
|
||||
// range from 0-1 (%) based on edge count, with 20 being the high-side
|
||||
int delta = maxPadding - basePadding;
|
||||
double percent = Math.min(count / 20f, 1);
|
||||
int padding = basePadding + (int) (delta * percent);
|
||||
return padding;
|
||||
}
|
||||
|
||||
private Map<FcgVertex, Point2D> getExistingLocations(List<FcgVertex> vertices) {
|
||||
|
||||
Map<FcgVertex, Point2D> locations = new HashMap<>();
|
||||
for (FcgVertex v : vertices) {
|
||||
Point2D p = toLocation(v);
|
||||
if (p.getX() == 0 && p.getY() == 0) {
|
||||
// no location for this vertex--we have to build them
|
||||
return new HashMap<>();
|
||||
}
|
||||
locations.put(v, (Point2D) p.clone());
|
||||
}
|
||||
return locations;
|
||||
}
|
||||
|
||||
private Rectangle getBounds(List<FcgVertex> vertices) {
|
||||
RenderContext<FcgVertex, FcgEdge> renderContext = viewer.getRenderContext();
|
||||
Function<? super FcgVertex, Shape> shaper = renderContext.getVertexShapeTransformer();
|
||||
|
||||
Layout<FcgVertex, FcgEdge> layout = viewer.getGraphLayout();
|
||||
|
||||
Rectangle area = null;
|
||||
for (FcgVertex v : vertices) {
|
||||
Rectangle bounds = shaper.apply(v).getBounds();
|
||||
Point2D loc = layout.apply(v);
|
||||
int x = (int) loc.getX();
|
||||
int y = (int) loc.getY();
|
||||
// do we need to compensate for vertex centering (like is done in the default layout)?
|
||||
// x -= (bounds.width / 2);
|
||||
// y -= (bounds.height / 2);
|
||||
bounds.setLocation(x, y);
|
||||
if (area == null) {
|
||||
area = bounds; // initialize
|
||||
}
|
||||
area.add(bounds);
|
||||
}
|
||||
|
||||
return area;
|
||||
}
|
||||
|
||||
private int getWidth(List<FcgVertex> vertices, int widthPadding) {
|
||||
|
||||
RenderContext<FcgVertex, FcgEdge> renderContext = viewer.getRenderContext();
|
||||
Function<? super FcgVertex, Shape> shaper = renderContext.getVertexShapeTransformer();
|
||||
|
||||
int width = 0;
|
||||
for (FcgVertex v : vertices) {
|
||||
width += shaper.apply(v).getBounds().width + widthPadding;
|
||||
}
|
||||
|
||||
return width;
|
||||
}
|
||||
|
||||
private int getHeight(List<FcgVertex> vertices) {
|
||||
|
||||
RenderContext<FcgVertex, FcgEdge> renderContext = viewer.getRenderContext();
|
||||
Function<? super FcgVertex, Shape> shaper = renderContext.getVertexShapeTransformer();
|
||||
|
||||
int height = 0;
|
||||
for (FcgVertex v : vertices) {
|
||||
height = Math.max(height, shaper.apply(v).getBounds().height);
|
||||
}
|
||||
|
||||
return height;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
/* ###
|
||||
* 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 functioncalls.graph.job;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import functioncalls.graph.FcgEdge;
|
||||
import functioncalls.graph.FcgVertex;
|
||||
import ghidra.graph.job.AbstractGraphVisibilityTransitionJob;
|
||||
import ghidra.graph.viewer.GraphViewer;
|
||||
|
||||
/**
|
||||
* A job to emphasize a given set of edges. This will make them bigger and then restore them
|
||||
* to a non-emphasized state.
|
||||
*/
|
||||
public class FcgEmphasizeEdgesJob extends AbstractGraphVisibilityTransitionJob<FcgVertex, FcgEdge> {
|
||||
|
||||
private Set<FcgEdge> edges;
|
||||
|
||||
public FcgEmphasizeEdgesJob(GraphViewer<FcgVertex, FcgEdge> viewer, Set<FcgEdge> edges) {
|
||||
super(viewer, true);
|
||||
this.edges = edges;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateOpacity(double percentComplete) {
|
||||
|
||||
double remaining = percentComplete;
|
||||
if (percentComplete > .5) {
|
||||
remaining = 1 - percentComplete;
|
||||
}
|
||||
|
||||
double modified = remaining * 10;
|
||||
|
||||
//double remaining = 1 - percentComplete; // start opacity towards the end
|
||||
for (FcgEdge e : edges) {
|
||||
e.setEmphasis(modified);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,256 @@
|
|||
/* ###
|
||||
* 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 functioncalls.graph.job;
|
||||
|
||||
import java.awt.geom.Point2D;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.apache.commons.collections4.IterableUtils;
|
||||
import org.apache.commons.collections4.map.LazyMap;
|
||||
|
||||
import edu.uci.ics.jung.algorithms.layout.Layout;
|
||||
import functioncalls.graph.*;
|
||||
import ghidra.graph.viewer.GraphViewer;
|
||||
import util.CollectionUtils;
|
||||
|
||||
/**
|
||||
* A container to house all newly added vertices (those being arranged) and the sources, or
|
||||
* 'from' vertices, of the new vertices.
|
||||
*
|
||||
* <P>This offers exiting vertices and new vertices pre-sorted by position in the graph in
|
||||
* order to minimize edge crossings. Specifically, the new vertices will be sorted
|
||||
* by the level of the parent and then the x-value of the parent so that the
|
||||
* immediate parent level will be preferred, with the x-value dictating where to place
|
||||
* the child so that we can minimize edge crossings.
|
||||
*/
|
||||
public class FcgExpandingVertexCollection {
|
||||
|
||||
private Comparator<FcgVertex> sourceVertexComparator = this::compareVerticesByLayoutPosition;
|
||||
private Comparator<FcgVertex> addressComparator =
|
||||
(v1, v2) -> v1.getAddress().compareTo(v2.getAddress());
|
||||
|
||||
private Map<FcgVertex, TreeSet<FcgVertex>> newVerticesBySource = LazyMap.lazyMap(
|
||||
new TreeMap<>(sourceVertexComparator), () -> new TreeSet<>(addressComparator));
|
||||
|
||||
private GraphViewer<FcgVertex, FcgEdge> viewer;
|
||||
private FcgLevel parentLevel;
|
||||
private FcgLevel expandingLevel;
|
||||
private Set<FcgVertex> newVertices;
|
||||
private Set<FcgEdge> newEdges;
|
||||
private Set<FcgEdge> indirectEdges = Collections.emptySet();
|
||||
private boolean isIncoming;
|
||||
private Iterable<FcgVertex> sources;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param sources all vertices that are the source of the expansion. This will be either a
|
||||
* single vertex, clicked by the user, or all vertices of a source level being expanded
|
||||
* @param parentLevel a.k.a, the source vertex level for the vertex that is the source of the
|
||||
* new vertices
|
||||
* @param expandingLevel the level of the new vertices
|
||||
* @param newVertices the expanding vertices; those that are emanating from the parent
|
||||
* @param newEdges the new edges being added as a result of adding the new vertices
|
||||
* @param allParentLevelEdges all edges from all siblings of the parent vertex
|
||||
* @param isIncoming true if the newly added vertices are callers of the source vertex;
|
||||
* false if the newly added vertices are called by the source vertex
|
||||
* @param viewer the viewer that is painting the graph. This is needed to sort by layout
|
||||
* position
|
||||
*/
|
||||
public FcgExpandingVertexCollection(Iterable<FcgVertex> sources, FcgLevel parentLevel,
|
||||
FcgLevel expandingLevel, Set<FcgVertex> newVertices, Set<FcgEdge> newEdges,
|
||||
Set<FcgEdge> allParentLevelEdges, boolean isIncoming,
|
||||
GraphViewer<FcgVertex, FcgEdge> viewer) {
|
||||
|
||||
this.sources = sources;
|
||||
this.parentLevel = parentLevel;
|
||||
this.newVertices = newVertices;
|
||||
this.newEdges = newEdges;
|
||||
this.isIncoming = isIncoming;
|
||||
this.viewer = viewer;
|
||||
this.expandingLevel = expandingLevel;
|
||||
|
||||
// we need to use the parent edges to generate the siblings of the newly
|
||||
// added vertices, as well as to sort the new vertices amongst their siblings
|
||||
for (FcgEdge e : allParentLevelEdges) {
|
||||
|
||||
FcgVertex start = e.getStart();
|
||||
FcgVertex end = e.getEnd();
|
||||
FcgLevel startLevel = start.getLevel();
|
||||
FcgLevel endLevel = end.getLevel();
|
||||
|
||||
if (expandingLevel.equals(startLevel)) {
|
||||
if (expandingLevel.equals(endLevel)) {
|
||||
// self-loop
|
||||
newVerticesBySource.get(start).add(end);
|
||||
newVerticesBySource.get(end).add(start);
|
||||
}
|
||||
else {
|
||||
newVerticesBySource.get(end).add(start);
|
||||
}
|
||||
}
|
||||
else {
|
||||
newVerticesBySource.get(start).add(end);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int compareVerticesByLayoutPosition(FcgVertex v1, FcgVertex v2) {
|
||||
|
||||
Layout<FcgVertex, FcgEdge> layout = viewer.getGraphLayout();
|
||||
|
||||
FcgLevel l1 = v1.getLevel();
|
||||
FcgLevel l2 = v2.getLevel();
|
||||
int result = l1.compareTo(l2);
|
||||
if (result != 0) {
|
||||
|
||||
// prefer the parent level over all
|
||||
if (l1.equals(parentLevel)) {
|
||||
return -1;
|
||||
}
|
||||
else if (l2.equals(parentLevel)) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
Point2D p1 = layout.apply(v1);
|
||||
Point2D p2 = layout.apply(v2);
|
||||
return (int) (p1.getX() - p2.getX());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all vertices at the given level
|
||||
*
|
||||
* @param level the level to filter on
|
||||
* @return the vertices
|
||||
*/
|
||||
public List<FcgVertex> getVerticesByLevel(FcgLevel level) {
|
||||
|
||||
// note: these are sorted, since they are housed in a TreeMap
|
||||
Set<FcgVertex> existingVertices = newVerticesBySource.keySet();
|
||||
|
||||
//@formatter:off
|
||||
List<FcgVertex> verticesAtLevel = existingVertices
|
||||
.stream()
|
||||
.filter(v -> v.getLevel().equals(level))
|
||||
.collect(Collectors.toList())
|
||||
;
|
||||
//@formatter:on
|
||||
|
||||
return verticesAtLevel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all vertices that have just been added to the graph; those now being arranged
|
||||
*
|
||||
* @return all vertices that have just been added to the graph; those now being arranged
|
||||
*/
|
||||
public List<FcgVertex> getAllVerticesAtNewLevel() {
|
||||
|
||||
// note: these are sorted, since they are housed in a TreeMap
|
||||
Set<FcgVertex> existingVertices = newVerticesBySource.keySet();
|
||||
|
||||
//@formatter:off
|
||||
LinkedHashSet<FcgVertex> sortedVertices = existingVertices
|
||||
.stream()
|
||||
.map(v -> newVerticesBySource.get(v))
|
||||
.flatMap(set -> set.stream())
|
||||
.filter(v -> v.getLevel().equals(expandingLevel)) // only include vertices not in graph
|
||||
.collect(Collectors.toCollection(LinkedHashSet::new)) // unique, sorted by traversal from above
|
||||
;
|
||||
//@formatter:on
|
||||
|
||||
return new ArrayList<>(sortedVertices);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all vertices being added to the graph
|
||||
* @return the vertices
|
||||
*/
|
||||
public Set<FcgVertex> getNewVertices() {
|
||||
return newVertices;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all new edges being added to the graph
|
||||
* @return the edges
|
||||
*/
|
||||
public Iterable<FcgEdge> getNewEdges() {
|
||||
return IterableUtils.chainedIterable(newEdges, indirectEdges);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all edges that are being added to existing nodes
|
||||
* @return the edges
|
||||
*/
|
||||
public Set<FcgEdge> getIndirectEdges() {
|
||||
return indirectEdges;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of newly added edges
|
||||
* @return the number of newly added edges
|
||||
*/
|
||||
public int getNewEdgeCount() {
|
||||
return newEdges.size() + indirectEdges.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets indirect edges--those edges that are not a direct link between the source
|
||||
* vertices and the newly added vertices
|
||||
* @param indirectEdges the edges
|
||||
*/
|
||||
public void setIndirectEdges(Set<FcgEdge> indirectEdges) {
|
||||
this.indirectEdges = CollectionUtils.asSet(indirectEdges);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the level of the newly added vertices, which is the level that is being expanded
|
||||
* @return the level
|
||||
*/
|
||||
public FcgLevel getExpandingLevel() {
|
||||
return expandingLevel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the direction of the expansion
|
||||
* @return the direction of the expansion
|
||||
*/
|
||||
public FcgDirection getExpandingDirection() {
|
||||
return expandingLevel.getDirection();
|
||||
}
|
||||
|
||||
/**
|
||||
* All vertices that are the source of the expansion
|
||||
* @return the source vertices
|
||||
*/
|
||||
public Iterable<FcgVertex> getSources() {
|
||||
return sources;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the newly added vertices are callers of the source vertex; false if
|
||||
* the newly added vertices are called by the source vertex
|
||||
*
|
||||
* @return true if the new vertices are incoming calls
|
||||
*/
|
||||
public boolean isIncoming() {
|
||||
return isIncoming;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,160 @@
|
|||
/* ###
|
||||
* 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 functioncalls.graph.layout;
|
||||
|
||||
import java.awt.Rectangle;
|
||||
import java.awt.geom.Point2D;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import functioncalls.graph.*;
|
||||
import ghidra.graph.VisualGraph;
|
||||
import ghidra.graph.viewer.layout.*;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
/**
|
||||
* A layout that will arrange vertices around a single vertex, with the incoming and outgoing
|
||||
* vertices above and below the source vertex, respectively.
|
||||
*
|
||||
* <p>The result will look loosely like this:
|
||||
* <pre>
|
||||
* In1 In2 In3
|
||||
* \ | /
|
||||
* \ | /
|
||||
* V
|
||||
* / | \
|
||||
* / | \
|
||||
* Out1 Out2 Out3
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
public class BowTieLayout extends AbstractVisualGraphLayout<FcgVertex, FcgEdge> {
|
||||
|
||||
protected BowTieLayout(FunctionCallGraph graph) {
|
||||
super(graph);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AbstractVisualGraphLayout<FcgVertex, FcgEdge> createClonedLayout(
|
||||
VisualGraph<FcgVertex, FcgEdge> newGraph) {
|
||||
|
||||
if (!(newGraph instanceof FunctionCallGraph)) {
|
||||
throw new IllegalArgumentException(
|
||||
"Must pass a " + FunctionCallGraph.class.getSimpleName() + "to clone the " +
|
||||
getClass().getSimpleName());
|
||||
}
|
||||
|
||||
BowTieLayout newLayout = new BowTieLayout((FunctionCallGraph) newGraph);
|
||||
return newLayout;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Point2D getVertexLocation(FcgVertex v, Column col, Row<FcgVertex> row,
|
||||
Rectangle bounds) {
|
||||
return getCenteredVertexLocation(v, col, row, bounds);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCondensedLayout() {
|
||||
// TODO revisit; condensing looks odd with only one column, as it is not centered;
|
||||
// try some real data and maybe set condensed based upon how many columns are
|
||||
// present? When not condensed, the width of each cell will be that of the largest
|
||||
// column, which means a really long name could cause normally-sized names to
|
||||
// consume way more space than is needed
|
||||
// return false;
|
||||
return true; // not sure about this
|
||||
}
|
||||
|
||||
@Override
|
||||
public FunctionCallGraph getVisualGraph() {
|
||||
return (FunctionCallGraph) getGraph();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected GridLocationMap<FcgVertex, FcgEdge> performInitialGridLayout(
|
||||
VisualGraph<FcgVertex, FcgEdge> g) throws CancelledException {
|
||||
|
||||
if (!(g instanceof FunctionCallGraph)) {
|
||||
throw new IllegalArgumentException(
|
||||
"This layout can only be used with the " + FunctionCallGraph.class);
|
||||
}
|
||||
|
||||
return layoutFunctionCallGraph((FunctionCallGraph) g);
|
||||
}
|
||||
|
||||
@Override
|
||||
public LayoutPositions<FcgVertex, FcgEdge> calculateLocations(VisualGraph<FcgVertex, FcgEdge> g,
|
||||
TaskMonitor taskMonitor) {
|
||||
|
||||
LayoutPositions<FcgVertex, FcgEdge> locs = super.calculateLocations(g, taskMonitor);
|
||||
|
||||
// TODO put x offset manipulation here...
|
||||
// -if the number of vertices in each row is not the same odd/even as the
|
||||
// largest row, then slide the x values for each vertex in the row left or
|
||||
// right as needed
|
||||
|
||||
return locs;
|
||||
}
|
||||
|
||||
private GridLocationMap<FcgVertex, FcgEdge> layoutFunctionCallGraph(FunctionCallGraph g) {
|
||||
|
||||
GridLocationMap<FcgVertex, FcgEdge> grid = new GridLocationMap<>();
|
||||
|
||||
FcgVertex source = Objects.requireNonNull(g.getSource());
|
||||
|
||||
//
|
||||
// Incoming nodes on top
|
||||
// -sorted by address
|
||||
//
|
||||
List<FcgEdge> inEdges = new ArrayList<>(g.getInEdges(source));
|
||||
List<FcgVertex> inVertices =
|
||||
inEdges.stream().map(e -> e.getStart()).collect(Collectors.toList());
|
||||
inVertices.sort((v1, v2) -> v1.getAddress().compareTo(v2.getAddress()));
|
||||
int row = 0; // first row
|
||||
for (int col = 0; col < inVertices.size(); col++) {
|
||||
FcgVertex v = inVertices.get(col);
|
||||
grid.set(v, row, col);
|
||||
}
|
||||
|
||||
//
|
||||
// Source node
|
||||
//
|
||||
row = 1; // middle row
|
||||
grid.set(source, row, 0);
|
||||
|
||||
// Outgoing nodes on the bottom
|
||||
// -sorted by address
|
||||
//
|
||||
List<FcgEdge> outEdges = new ArrayList<>(g.getOutEdges(source));
|
||||
List<FcgVertex> outVertices =
|
||||
outEdges.stream().map(e -> e.getEnd()).collect(Collectors.toList());
|
||||
|
||||
// leave already processed vertices in the top row; this can happen if the in vertex is
|
||||
// also called by the source function, creating a cycle
|
||||
outVertices.removeAll(inVertices);
|
||||
outVertices.sort((v1, v2) -> v1.getAddress().compareTo(v2.getAddress()));
|
||||
row = 2; // last
|
||||
for (int col = 0; col < outVertices.size(); col++) {
|
||||
FcgVertex v = outVertices.get(col);
|
||||
grid.set(v, row, col);
|
||||
}
|
||||
|
||||
grid.centerRows();
|
||||
|
||||
return grid;
|
||||
}
|
||||
}
|
|
@ -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 functioncalls.graph.layout;
|
||||
|
||||
import javax.swing.Icon;
|
||||
|
||||
import functioncalls.graph.*;
|
||||
import ghidra.graph.viewer.layout.AbstractLayoutProvider;
|
||||
import ghidra.graph.viewer.layout.VisualGraphLayout;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
import resources.ResourceManager;
|
||||
|
||||
/**
|
||||
* A layout provider for the {@link BowTieLayout}
|
||||
*/
|
||||
public class BowTieLayoutProvider
|
||||
extends AbstractLayoutProvider<FcgVertex, FcgEdge, FunctionCallGraph> {
|
||||
|
||||
public static final String NAME = "Bow Tie Layout";
|
||||
|
||||
private static final Icon DEFAULT_ICON = ResourceManager.loadImage("images/color_swatch.png");
|
||||
|
||||
@Override
|
||||
public VisualGraphLayout<FcgVertex, FcgEdge> getLayout(FunctionCallGraph graph,
|
||||
TaskMonitor monitor) throws CancelledException {
|
||||
|
||||
BowTieLayout layout = new BowTieLayout(graph);
|
||||
initVertexLocations(graph, layout);
|
||||
return layout;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getLayoutName() {
|
||||
return NAME;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Icon getActionIcon() {
|
||||
return DEFAULT_ICON;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,78 @@
|
|||
/* ###
|
||||
* 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 functioncalls.graph.renderer;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.Paint;
|
||||
|
||||
import com.google.common.base.Function;
|
||||
|
||||
import functioncalls.graph.FcgEdge;
|
||||
|
||||
/**
|
||||
* Generates colors for a given {@link FcgEdge}
|
||||
*/
|
||||
public class FcgEdgePaintTransformer implements Function<FcgEdge, Paint> {
|
||||
|
||||
// private static final Paint LESS_IMPORTANT_COLOR = new Color(125, 125, 125, 75);
|
||||
private Color directColor;
|
||||
private Color indirectColor;
|
||||
|
||||
private Color[] directColorWithAlpha = new Color[10];
|
||||
|
||||
// only one color for now; may have more; these should be changeable via graph options
|
||||
public FcgEdgePaintTransformer(Color directColor, Color indirectColor) {
|
||||
this.directColor = directColor;
|
||||
this.indirectColor = indirectColor;
|
||||
|
||||
directColorWithAlpha = alphatize(directColor);
|
||||
}
|
||||
|
||||
private Color[] alphatize(Color c) {
|
||||
Color[] alphad = new Color[10];
|
||||
alphad[0] = c;
|
||||
for (int i = 1; i < 10; i++) {
|
||||
double newAlpha = 255 - (i * 25.5);
|
||||
alphad[i] = new Color(c.getRed(), c.getGreen(), c.getBlue(), (int) newAlpha);
|
||||
}
|
||||
return alphad;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Paint apply(FcgEdge e) {
|
||||
if (e.isDirectEdge()) {
|
||||
return getDirectEdgeColor(e);
|
||||
}
|
||||
|
||||
return indirectColor;
|
||||
}
|
||||
|
||||
private Color getDirectEdgeColor(FcgEdge e) {
|
||||
|
||||
return directColor;
|
||||
|
||||
/*// this allows us to make the edges fainter as the outward levels increase
|
||||
FcgVertex start = e.getStart();
|
||||
FcgVertex end = e.getEnd();
|
||||
FcgLevel sl = start.getLevel();
|
||||
FcgLevel el = end.getLevel();
|
||||
int level = Math.min(sl.getDistance(), el.getDistance());
|
||||
level = Math.min(level, 9);
|
||||
Color c = directColorWithAlpha[level];
|
||||
return c;
|
||||
*/
|
||||
}
|
||||
}
|
|
@ -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 functioncalls.graph.renderer;
|
||||
|
||||
import java.awt.Component;
|
||||
import java.awt.event.MouseEvent;
|
||||
|
||||
import javax.swing.*;
|
||||
|
||||
import functioncalls.graph.FcgEdge;
|
||||
import functioncalls.graph.FcgVertex;
|
||||
import ghidra.graph.viewer.event.mouse.VertexTooltipProvider;
|
||||
|
||||
/**
|
||||
* A class that provides tooltips for a given vertex
|
||||
*/
|
||||
public class FcgTooltipProvider implements VertexTooltipProvider<FcgVertex, FcgEdge> {
|
||||
|
||||
@Override
|
||||
public JComponent getTooltip(FcgVertex v) {
|
||||
JToolTip tip = new JToolTip();
|
||||
tip.setTipText(v.getName());
|
||||
return tip;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JComponent getTooltip(FcgVertex v, FcgEdge e) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTooltipText(FcgVertex v, MouseEvent e) {
|
||||
// TODO we could have the name label return just the function name; the vertex shape
|
||||
// return a full function signature and the +/- toggle buttons return a tip
|
||||
|
||||
Component child = e.getComponent();
|
||||
|
||||
// the buttons may have extra information
|
||||
if (child instanceof JButton) {
|
||||
return ((JButton) child).getToolTipText();
|
||||
}
|
||||
|
||||
return v.getName();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
/* ###
|
||||
* 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 functioncalls.graph.renderer;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.Paint;
|
||||
|
||||
import com.google.common.base.Function;
|
||||
|
||||
import functioncalls.graph.FcgVertex;
|
||||
import functioncalls.graph.FunctionCallGraph;
|
||||
|
||||
/**
|
||||
* A class that takes a {@link FunctionCallGraph} vertex and determines which fill color
|
||||
* should be used to paint
|
||||
*/
|
||||
public class FcgVertexPaintTransformer implements Function<FcgVertex, Paint> {
|
||||
|
||||
private Color color;
|
||||
|
||||
// only one color for now; may have more; these should be changeable via graph options
|
||||
public FcgVertexPaintTransformer(Color color) {
|
||||
this.color = color;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Paint apply(FcgVertex v) {
|
||||
return color;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,105 @@
|
|||
/* ###
|
||||
* 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 functioncalls.graph.view;
|
||||
|
||||
import java.awt.Color;
|
||||
|
||||
import edu.uci.ics.jung.visualization.RenderContext;
|
||||
import functioncalls.graph.*;
|
||||
import functioncalls.graph.renderer.FcgEdgePaintTransformer;
|
||||
import functioncalls.graph.renderer.FcgVertexPaintTransformer;
|
||||
import functioncalls.plugin.FunctionCallGraphPlugin;
|
||||
import ghidra.graph.viewer.*;
|
||||
import ghidra.graph.viewer.layout.VisualGraphLayout;
|
||||
|
||||
/**
|
||||
* A graph component for the {@link FunctionCallGraphPlugin}
|
||||
*/
|
||||
public class FcgComponent extends GraphComponent<FcgVertex, FcgEdge, FunctionCallGraph> {
|
||||
|
||||
// our parent stores a reference to this graph, but we do it here to maintain its type
|
||||
private FunctionCallGraph fcGraph;
|
||||
|
||||
// TODO use options for color
|
||||
private FcgVertexPaintTransformer vertexPaintTransformer =
|
||||
new FcgVertexPaintTransformer(FcgVertex.DEFAULT_VERTEX_SHAPE_COLOR);
|
||||
|
||||
private Color lightGreen = new Color(143, 197, 143);
|
||||
private Color lightGray = new Color(233, 233, 233);
|
||||
|
||||
// the satellite gets too cluttered, so wash out the edges
|
||||
private Color washedOutBlack = new Color(0, 0, 0, 25);
|
||||
|
||||
private FcgEdgePaintTransformer edgePaintTransformer =
|
||||
new FcgEdgePaintTransformer(lightGreen, lightGray);
|
||||
private FcgEdgePaintTransformer satelliteEdgePaintTransformer =
|
||||
new FcgEdgePaintTransformer(washedOutBlack, new Color(125, 125, 125, 25));
|
||||
|
||||
FcgComponent(FunctionCallGraph g) {
|
||||
|
||||
setGraph(g);
|
||||
build();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected FcgVertex getInitialVertex() {
|
||||
return fcGraph.getSource();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void decoratePrimaryViewer(GraphViewer<FcgVertex, FcgEdge> viewer,
|
||||
VisualGraphLayout<FcgVertex, FcgEdge> layout) {
|
||||
|
||||
super.decoratePrimaryViewer(viewer, layout);
|
||||
|
||||
RenderContext<FcgVertex, FcgEdge> renderContext = viewer.getRenderContext();
|
||||
renderContext.setVertexFillPaintTransformer(vertexPaintTransformer);
|
||||
|
||||
// Note: setting the fill for the edges has the effect of drawing a filled-in circle
|
||||
// instead of just the outer edge.
|
||||
// renderContext.setEdgeFillPaintTransformer(edgePaintTransformer);
|
||||
renderContext.setEdgeDrawPaintTransformer(edgePaintTransformer);
|
||||
renderContext.setArrowFillPaintTransformer(edgePaintTransformer);
|
||||
renderContext.setArrowDrawPaintTransformer(edgePaintTransformer);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void decorateSatelliteViewer(SatelliteGraphViewer<FcgVertex, FcgEdge> viewer,
|
||||
VisualGraphLayout<FcgVertex, FcgEdge> layout) {
|
||||
|
||||
super.decorateSatelliteViewer(viewer, layout);
|
||||
|
||||
RenderContext<FcgVertex, FcgEdge> renderContext = viewer.getRenderContext();
|
||||
renderContext.setVertexFillPaintTransformer(vertexPaintTransformer);
|
||||
//renderContext.setEdgeFillPaintTransformer(satelliteEdgePaintTransformer);
|
||||
renderContext.setEdgeDrawPaintTransformer(satelliteEdgePaintTransformer);
|
||||
renderContext.setArrowFillPaintTransformer(satelliteEdgePaintTransformer);
|
||||
renderContext.setArrowDrawPaintTransformer(satelliteEdgePaintTransformer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
|
||||
fcGraph = null;
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@Override // open access for testing
|
||||
public VisualGraphViewUpdater<FcgVertex, FcgEdge> getViewUpdater() {
|
||||
return super.getViewUpdater();
|
||||
}
|
||||
}
|
|
@ -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 functioncalls.graph.view;
|
||||
|
||||
import functioncalls.graph.*;
|
||||
import functioncalls.plugin.FunctionCallGraphPlugin;
|
||||
import ghidra.graph.viewer.VisualGraphView;
|
||||
import ghidra.graph.viewer.options.VisualGraphOptions;
|
||||
|
||||
/**
|
||||
* A graph view for the {@link FunctionCallGraphPlugin}
|
||||
*/
|
||||
public class FcgView extends VisualGraphView<FcgVertex, FcgEdge, FunctionCallGraph> {
|
||||
|
||||
@Override
|
||||
protected void installGraphViewer() {
|
||||
|
||||
FcgComponent component = createGraphComponent();
|
||||
component.setGraphOptions(new VisualGraphOptions());
|
||||
setGraphComponent(component);
|
||||
}
|
||||
|
||||
private FcgComponent createGraphComponent() {
|
||||
|
||||
FcgComponent component = new FcgComponent(getVisualGraph());
|
||||
return component;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FcgComponent getGraphComponent() {
|
||||
return (FcgComponent) super.getGraphComponent();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,71 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package functioncalls.plugin;
|
||||
|
||||
import functioncalls.graph.*;
|
||||
import ghidra.graph.viewer.GraphPerspectiveInfo;
|
||||
import ghidra.program.model.listing.Function;
|
||||
|
||||
/**
|
||||
* An empty data that is used to avoid null checks
|
||||
*/
|
||||
public class EmptyFcgData implements FcgData {
|
||||
|
||||
@Override
|
||||
public Function getFunction() {
|
||||
throw new UnsupportedOperationException("Empty data has no function");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isFunction(Function f) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FunctionCallGraph getGraph() {
|
||||
throw new UnsupportedOperationException("Empty data has no graph");
|
||||
}
|
||||
|
||||
@Override
|
||||
public FunctionEdgeCache getFunctionEdgeCache() {
|
||||
throw new UnsupportedOperationException("Empty data has no function edge cache");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasResults() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
// we are empty; nothing to do
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInitialized() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GraphPerspectiveInfo<FcgVertex, FcgEdge> getGraphPerspective() {
|
||||
throw new UnsupportedOperationException("Empty data does not need view information");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setGraphPerspective(GraphPerspectiveInfo<FcgVertex, FcgEdge> info) {
|
||||
throw new UnsupportedOperationException("Empty data does not need view information");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,89 @@
|
|||
/* ###
|
||||
* 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 functioncalls.plugin;
|
||||
|
||||
import functioncalls.graph.*;
|
||||
import ghidra.graph.viewer.GraphPerspectiveInfo;
|
||||
import ghidra.program.model.listing.Function;
|
||||
|
||||
/**
|
||||
* This class allows clients to retrieve and work on the graph and its related data. Also,
|
||||
* this class makes caching the data herein simple.
|
||||
*/
|
||||
interface FcgData {
|
||||
|
||||
/**
|
||||
* The function of this data
|
||||
* @return the function
|
||||
*/
|
||||
Function getFunction();
|
||||
|
||||
/**
|
||||
* The graph of this data
|
||||
*
|
||||
* @return the graph
|
||||
*/
|
||||
FunctionCallGraph getGraph();
|
||||
|
||||
/**
|
||||
* Returns the cache of {@link Function} edges. These edges are not in the graph, but
|
||||
* rather are simple edges that represent a link between two functions. This is used to
|
||||
* track existing edges that are not yet in the graph, which may be added later as the
|
||||
* relevant nodes are inserted into the graph.
|
||||
*
|
||||
* @return the cache
|
||||
*/
|
||||
FunctionEdgeCache getFunctionEdgeCache();
|
||||
|
||||
/**
|
||||
* True if this data has a valid function
|
||||
* @return true if this data has a valid function
|
||||
*/
|
||||
boolean hasResults();
|
||||
|
||||
/**
|
||||
* False if the graph in this data has not yet been loaded
|
||||
* @return false if the graph in this data has not yet been loaded
|
||||
*/
|
||||
boolean isInitialized();
|
||||
|
||||
/**
|
||||
* Dispose the contents of this data
|
||||
*/
|
||||
void dispose();
|
||||
|
||||
/**
|
||||
* Returns the view's graph perspective. This is used by the view to restore itself.
|
||||
* @return the view's graph perspective
|
||||
*/
|
||||
GraphPerspectiveInfo<FcgVertex, FcgEdge> getGraphPerspective();
|
||||
|
||||
/**
|
||||
* Sets the view information for this graph data. This will be later used by the view
|
||||
* to restore itself.
|
||||
*
|
||||
* @param info the perspective
|
||||
*/
|
||||
void setGraphPerspective(GraphPerspectiveInfo<FcgVertex, FcgEdge> info);
|
||||
|
||||
/**
|
||||
* Returns true if this data's function is equal to the given function
|
||||
*
|
||||
* @param f the function to test
|
||||
* @return true if this data's function is equal to the given function
|
||||
*/
|
||||
boolean isFunction(Function f);
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
/* ###
|
||||
* 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 functioncalls.plugin;
|
||||
|
||||
import com.google.common.cache.*;
|
||||
|
||||
import functioncalls.graph.FunctionCallGraph;
|
||||
import ghidra.program.model.listing.Function;
|
||||
|
||||
/**
|
||||
* A factory that will create {@link FunctionCallGraph} data objects for a given function.
|
||||
* Internally, this factory uses an MRU cache.
|
||||
*/
|
||||
public class FcgDataFactory {
|
||||
|
||||
private LoadingCache<Function, FcgData> cache;
|
||||
|
||||
FcgDataFactory(RemovalListener<Function, FcgData> listener) {
|
||||
//@formatter:off
|
||||
cache = CacheBuilder
|
||||
.newBuilder()
|
||||
.maximumSize(5)
|
||||
.removalListener(listener)
|
||||
// Note: using soft values means that sometimes our data is reclaimed by the
|
||||
// Garbage Collector. We don't want that, we wish to call dispose() on the data
|
||||
//.softValues()
|
||||
.build(new CacheLoader<Function, FcgData>() {
|
||||
@Override
|
||||
public FcgData load(Function f) throws Exception {
|
||||
return new ValidFcgData(f, new FunctionCallGraph());
|
||||
}
|
||||
});
|
||||
//@formatter:on
|
||||
}
|
||||
|
||||
FcgData create(Function f) {
|
||||
if (f == null) {
|
||||
return new EmptyFcgData();
|
||||
}
|
||||
|
||||
FcgData data = cache.getUnchecked(f);
|
||||
return data;
|
||||
}
|
||||
|
||||
void remove(Function f) {
|
||||
cache.invalidate(f);
|
||||
}
|
||||
|
||||
void dispose() {
|
||||
cache.invalidateAll();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,145 @@
|
|||
/* ###
|
||||
* 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 functioncalls.plugin;
|
||||
|
||||
import docking.ActionContext;
|
||||
import docking.action.DockingAction;
|
||||
import ghidra.app.CorePluginPackage;
|
||||
import ghidra.app.plugin.PluginCategoryNames;
|
||||
import ghidra.app.plugin.ProgramPlugin;
|
||||
import ghidra.app.services.GoToService;
|
||||
import ghidra.framework.options.SaveState;
|
||||
import ghidra.framework.plugintool.PluginInfo;
|
||||
import ghidra.framework.plugintool.PluginTool;
|
||||
import ghidra.framework.plugintool.util.PluginStatus;
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.util.ProgramLocation;
|
||||
import ghidra.util.HelpLocation;
|
||||
import ghidra.util.SystemUtilities;
|
||||
import ghidra.util.task.SwingUpdateManager;
|
||||
|
||||
/**
|
||||
* Plugin to show a graph of function calls for a given function
|
||||
*/
|
||||
//@formatter:off
|
||||
@PluginInfo(
|
||||
status = PluginStatus.RELEASED,
|
||||
packageName = CorePluginPackage.NAME,
|
||||
category = PluginCategoryNames.GRAPH,
|
||||
shortDescription = "Function Call Graph Plugin",
|
||||
description = "Displays a graph of incoming and outgoing calls for a given function."
|
||||
)
|
||||
//@formatter:on
|
||||
public class FunctionCallGraphPlugin extends ProgramPlugin {
|
||||
|
||||
/*package*/ static final String NAME = "Function Call Graph";
|
||||
/*package*/ static final String SHOW_PROVIDER_ACTION_NAME = "Display Function Call Graph";
|
||||
/*package*/ static final HelpLocation DEFAULT_HELP =
|
||||
new HelpLocation(FunctionCallGraphPlugin.class.getSimpleName(),
|
||||
FunctionCallGraphPlugin.class.getSimpleName());
|
||||
|
||||
private FcgProvider provider;
|
||||
|
||||
// enough time for users to click around without the graph starting its work
|
||||
private static final int MIN_UPDATE_DELAY = 750;
|
||||
private SwingUpdateManager locationUpdater = new SwingUpdateManager(MIN_UPDATE_DELAY, () -> {
|
||||
doLocationChanged();
|
||||
});
|
||||
|
||||
public FunctionCallGraphPlugin(PluginTool tool) {
|
||||
super(tool, true, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void init() {
|
||||
provider = new FcgProvider(tool, this);
|
||||
createActions();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeConfigState(SaveState state) {
|
||||
provider.writeConfigState(state);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readConfigState(SaveState state) {
|
||||
provider.readConfigState(state);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void locationChanged(ProgramLocation loc) {
|
||||
locationUpdater.update();
|
||||
}
|
||||
|
||||
private void doLocationChanged() {
|
||||
provider.locationChanged(getCurrentLocation());
|
||||
}
|
||||
|
||||
void handleProviderLocationChanged(ProgramLocation location) {
|
||||
// For snapshots
|
||||
// if (provider != connectedProvider) {
|
||||
// return;
|
||||
// }
|
||||
|
||||
GoToService goTo = tool.getService(GoToService.class);
|
||||
if (goTo == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// do later so the current event processing can finish
|
||||
SystemUtilities.runSwingLater(() -> {
|
||||
goTo.goTo(location);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void dispose() {
|
||||
provider.dispose();
|
||||
}
|
||||
|
||||
private void createActions() {
|
||||
DockingAction showProviderAction = new DockingAction(SHOW_PROVIDER_ACTION_NAME, getName()) {
|
||||
@Override
|
||||
public void actionPerformed(ActionContext context) {
|
||||
provider.setVisible(true);
|
||||
}
|
||||
};
|
||||
|
||||
// TODO create icon from scratch: bow-tie
|
||||
// ImageIcon icon = ResourceManager.loadImage("images/applications-development.png");
|
||||
// showProviderAction.setToolBarData(new ToolBarData(icon, "View"));
|
||||
tool.addAction(showProviderAction);
|
||||
}
|
||||
|
||||
void showProvider() {
|
||||
provider.setVisible(true);
|
||||
}
|
||||
|
||||
FcgProvider getProvider() {
|
||||
return provider;
|
||||
}
|
||||
|
||||
Address getCurrentAddress() {
|
||||
if (currentLocation == null) {
|
||||
return null;
|
||||
}
|
||||
return currentLocation.getAddress();
|
||||
}
|
||||
|
||||
ProgramLocation getCurrentLocation() {
|
||||
return currentLocation;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,81 @@
|
|||
/* ###
|
||||
* 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 functioncalls.plugin;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import ghidra.program.model.listing.Function;
|
||||
|
||||
/**
|
||||
* An edge between two functions. This edge is never added to the graph, but exists for us
|
||||
* to maintain relationships between functions outside of the visual graph.
|
||||
*/
|
||||
class FunctionEdge {
|
||||
|
||||
private Function start;
|
||||
private Function end;
|
||||
|
||||
FunctionEdge(Function start, Function end) {
|
||||
this.start = start;
|
||||
this.end = end;
|
||||
}
|
||||
|
||||
Function getStart() {
|
||||
return start;
|
||||
}
|
||||
|
||||
Function getEnd() {
|
||||
return end;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "[" + start + ", " + end + "]";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + ((end == null) ? 0 : end.hashCode());
|
||||
result = prime * result + ((start == null) ? 0 : start.hashCode());
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
FunctionEdge other = (FunctionEdge) obj;
|
||||
if (!Objects.equals(end, other.end)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!Objects.equals(start, other.start)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
/* ###
|
||||
* 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 functioncalls.plugin;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import org.apache.commons.collections4.map.LazyMap;
|
||||
|
||||
import ghidra.program.model.listing.Function;
|
||||
|
||||
/**
|
||||
* A class to cache known function edges
|
||||
*/
|
||||
public class FunctionEdgeCache {
|
||||
|
||||
/** Contains all known edges, even those not showing in the graph */
|
||||
private Map<Function, Set<FunctionEdge>> allEdgesByFunction =
|
||||
LazyMap.lazyMap(new HashMap<>(), () -> new HashSet<>());
|
||||
|
||||
// note: having a function as a key in the above map is not enough to know if it has been
|
||||
// processed already (as the function can be added by processing edges of other
|
||||
// nodes). Being in this structure means that it has been processed for its
|
||||
// incoming and outgoing connections
|
||||
private Set<Function> tracked = new HashSet<>();
|
||||
|
||||
public Set<FunctionEdge> get(Function f) {
|
||||
return allEdgesByFunction.get(f);
|
||||
}
|
||||
|
||||
public boolean isTracked(Function f) {
|
||||
return tracked.contains(f);
|
||||
}
|
||||
|
||||
public void setTracked(Function f) {
|
||||
tracked.add(f);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,85 @@
|
|||
/* ###
|
||||
* 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 functioncalls.plugin;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import functioncalls.graph.*;
|
||||
import ghidra.graph.viewer.GraphPerspectiveInfo;
|
||||
import ghidra.program.model.listing.Function;
|
||||
|
||||
/**
|
||||
* A simple class that is used to cache graph data for a given function
|
||||
*/
|
||||
public class ValidFcgData implements FcgData {
|
||||
|
||||
private Function function;
|
||||
private FunctionCallGraph graph;
|
||||
private GraphPerspectiveInfo<FcgVertex, FcgEdge> perspectiveInfo;
|
||||
|
||||
/** Contains all known edges, even those not showing in the graph */
|
||||
private FunctionEdgeCache allEdgesByFunction = new FunctionEdgeCache();
|
||||
|
||||
ValidFcgData(Function function, FunctionCallGraph graph) {
|
||||
this.function = Objects.requireNonNull(function);
|
||||
this.graph = Objects.requireNonNull(graph);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Function getFunction() {
|
||||
return function;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isFunction(Function f) {
|
||||
return function.equals(f);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FunctionCallGraph getGraph() {
|
||||
return graph;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FunctionEdgeCache getFunctionEdgeCache() {
|
||||
return allEdgesByFunction;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasResults() {
|
||||
return true; // this object is always considered valid; use EmptyFcgData for bad Functions
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInitialized() {
|
||||
return !graph.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
graph.dispose();
|
||||
}
|
||||
|
||||
@Override
|
||||
public GraphPerspectiveInfo<FcgVertex, FcgEdge> getGraphPerspective() {
|
||||
return perspectiveInfo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setGraphPerspective(GraphPerspectiveInfo<FcgVertex, FcgEdge> info) {
|
||||
this.perspectiveInfo = info;
|
||||
}
|
||||
}
|
|
@ -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 functioncalls.plugin.context;
|
||||
|
||||
import docking.ActionContext;
|
||||
import functioncalls.graph.FunctionCallGraph;
|
||||
import functioncalls.plugin.FcgProvider;
|
||||
|
||||
/**
|
||||
* Context for the {@link FunctionCallGraph}
|
||||
*/
|
||||
public class FcgActionContext extends ActionContext {
|
||||
|
||||
public FcgActionContext(FcgProvider provider) {
|
||||
this(provider, null);
|
||||
}
|
||||
|
||||
public FcgActionContext(FcgProvider provider, Object contextObject) {
|
||||
super(provider, contextObject);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
/* ###
|
||||
* 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 functioncalls.plugin.context;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import functioncalls.graph.FcgVertex;
|
||||
import functioncalls.plugin.FcgProvider;
|
||||
|
||||
/**
|
||||
* Context that contains a single vertex
|
||||
*/
|
||||
public class FcgVertexActionContext extends FcgActionContext {
|
||||
|
||||
private FcgVertex vertex;
|
||||
|
||||
public FcgVertexActionContext(FcgProvider provider, FcgVertex vertex) {
|
||||
super(provider);
|
||||
this.vertex = Objects.requireNonNull(vertex);
|
||||
}
|
||||
|
||||
public FcgVertex getVertex() {
|
||||
return vertex;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,191 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package help.screenshot;
|
||||
|
||||
import java.awt.*;
|
||||
|
||||
import javax.swing.SwingUtilities;
|
||||
|
||||
import org.junit.*;
|
||||
|
||||
import docking.DockableComponent;
|
||||
import edu.uci.ics.jung.visualization.picking.PickedState;
|
||||
import functioncalls.graph.*;
|
||||
import functioncalls.graph.view.FcgView;
|
||||
import functioncalls.plugin.*;
|
||||
import generic.test.TestUtils;
|
||||
import ghidra.graph.viewer.*;
|
||||
import ghidra.program.model.address.TestAddress;
|
||||
import ghidra.program.model.listing.Function;
|
||||
|
||||
public class FunctionCallGraphPluginScreenShots extends GhidraScreenShotGenerator {
|
||||
|
||||
private FcgProvider provider;
|
||||
|
||||
@Override
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
|
||||
super.setUp();
|
||||
|
||||
FunctionCallGraphPlugin plugin = env.addPlugin(FunctionCallGraphPlugin.class);
|
||||
provider = new FcgProvider(tool, plugin);
|
||||
provider.setVisible(true);
|
||||
|
||||
setTestFunctionInProvider();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dockingSetUp() {
|
||||
// We get an error dialog about default tools, since this test is not in the
|
||||
// Integration Test project. Disable the error dialogs.
|
||||
setErrorGUIEnabled(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
@After
|
||||
public void tearDown() throws Exception {
|
||||
tool.showComponentProvider(provider, false);
|
||||
super.tearDown();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFunctionCallGraphProvider() {
|
||||
|
||||
Window w = moveProviderToItsOwnWindow(provider, 700, 500);
|
||||
centerGraph();
|
||||
captureWindow(w);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTooManyReferences() {
|
||||
|
||||
FcgFunction source = new FcgFunction("FUN_1234", new TestAddress(1));
|
||||
|
||||
createManyOutgoingReferences(source, FcgProvider.MAX_REFERENCES + 1);
|
||||
|
||||
setFunction(source);
|
||||
|
||||
FcgVertex v = graph().getVertex(source);
|
||||
clearSelection();
|
||||
runSwing(() -> v.setHovered(true));
|
||||
|
||||
captureProvider(provider);
|
||||
captureNode(v);
|
||||
}
|
||||
|
||||
private void clearSelection() {
|
||||
GraphViewer<FcgVertex, FcgEdge> viewer = viewer();
|
||||
PickedState<FcgVertex> picker = viewer.getPickedVertexState();
|
||||
runSwing(() -> {
|
||||
picker.clear();
|
||||
});
|
||||
}
|
||||
|
||||
private GraphViewer<FcgVertex, FcgEdge> viewer() {
|
||||
FcgView view = provider.getView();
|
||||
return view.getPrimaryGraphViewer();
|
||||
}
|
||||
|
||||
private void captureNode(FcgVertex v) {
|
||||
FcgView view = provider.getView();
|
||||
GraphViewer<FcgVertex, FcgEdge> viewer = view.getPrimaryGraphViewer();
|
||||
Rectangle bounds = GraphViewerUtils.getVertexBoundsInViewSpace(viewer, v);
|
||||
|
||||
DockableComponent dockableComponent = getDockableComponent(viewer);
|
||||
Point loc = SwingUtilities.convertPoint(viewer, bounds.getLocation(), dockableComponent);
|
||||
bounds.setLocation(loc);
|
||||
|
||||
Rectangle area = new Rectangle(bounds);
|
||||
int offset = 10;
|
||||
area.x -= offset;
|
||||
area.y -= offset;
|
||||
area.width += (2 * offset);
|
||||
area.height += (2 * offset);
|
||||
|
||||
// drawRectangle(Color.ORANGE, area, 5);
|
||||
|
||||
crop(area);
|
||||
}
|
||||
|
||||
private FunctionCallGraph graph() {
|
||||
return (FunctionCallGraph) invokeInstanceMethod("getGraph", provider);
|
||||
}
|
||||
|
||||
private void createManyOutgoingReferences(FcgFunction f, int n) {
|
||||
int counter = 10;
|
||||
for (int i = 0; i < n; i++) {
|
||||
FcgFunction newF =
|
||||
new FcgFunction("Many_Outgoing_Function_" + (i + 1), new TestAddress(counter++));
|
||||
f.addCalledFunction(newF);
|
||||
}
|
||||
}
|
||||
|
||||
private void setTestFunctionInProvider() {
|
||||
int counter = 0;
|
||||
|
||||
FcgFunction f = new FcgFunction("Source_Function", new TestAddress(counter++));
|
||||
|
||||
FcgFunction in = new FcgFunction("FUN_IN_1", new TestAddress(counter++));
|
||||
in.addCalledFunction(f);
|
||||
f.addCallerFunction(in);
|
||||
|
||||
in = new FcgFunction("FUN_IN_2", new TestAddress(counter++));
|
||||
in.addCalledFunction(f);
|
||||
f.addCallerFunction(in);
|
||||
|
||||
for (int i = 1; i < 5; i++) {
|
||||
String name = "FUN_OUT_" + i;
|
||||
FcgFunction out = new FcgFunction(name, new TestAddress(counter++));
|
||||
out.addCallerFunction(f);
|
||||
f.addCalledFunction(out);
|
||||
}
|
||||
|
||||
setFunction(f);
|
||||
}
|
||||
|
||||
private void setFunction(FcgFunction f) {
|
||||
runSwing(() -> {
|
||||
TestUtils.invokeInstanceMethod("setFunction", provider, Function.class, f);
|
||||
});
|
||||
}
|
||||
|
||||
private void centerGraph() {
|
||||
|
||||
waitForGraph();
|
||||
|
||||
VisualGraphViewUpdater<FcgVertex, FcgEdge> updater = getGraphUpdater();
|
||||
updater.fitGraphToViewerNow();
|
||||
waitForGraph();
|
||||
}
|
||||
|
||||
private VisualGraphViewUpdater<FcgVertex, FcgEdge> getGraphUpdater() {
|
||||
@SuppressWarnings("unchecked")
|
||||
VisualGraphView<FcgVertex, FcgEdge, FunctionCallGraph> view =
|
||||
(VisualGraphView<FcgVertex, FcgEdge, FunctionCallGraph>) getInstanceField("view",
|
||||
provider);
|
||||
|
||||
GraphViewer<FcgVertex, FcgEdge> primaryViewer = view.getPrimaryGraphViewer();
|
||||
VisualGraphViewUpdater<FcgVertex, FcgEdge> updater = primaryViewer.getViewUpdater();
|
||||
return updater;
|
||||
}
|
||||
|
||||
private void waitForGraph() {
|
||||
VisualGraphViewUpdater<FcgVertex, FcgEdge> updater = getGraphUpdater();
|
||||
waitForCondition(() -> !updater.isBusy());
|
||||
waitForSwing();
|
||||
}
|
||||
}
|
|
@ -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 functioncalls.plugin;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import ghidra.program.model.FunctionTestDouble;
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.model.listing.Function;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
/**
|
||||
* A fake function double for use in testing the {@link FunctionCallGraphPlugin}
|
||||
*/
|
||||
public class FcgFunction extends FunctionTestDouble {
|
||||
|
||||
private Set<Function> calledFunctions = new HashSet<>();
|
||||
private Set<Function> callingFunctions = new HashSet<>();
|
||||
private Address entry;
|
||||
|
||||
public FcgFunction(String name, Address entry) {
|
||||
super(name);
|
||||
this.entry = entry;
|
||||
}
|
||||
|
||||
public void addCalledFunction(Function f) {
|
||||
calledFunctions.add(f);
|
||||
}
|
||||
|
||||
public void addCallerFunction(Function f) {
|
||||
callingFunctions.add(f);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Address getEntryPoint() {
|
||||
return entry;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Function> getCalledFunctions(TaskMonitor monitor) {
|
||||
return Collections.unmodifiableSet(calledFunctions);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Function> getCallingFunctions(TaskMonitor monitor) {
|
||||
return Collections.unmodifiableSet(callingFunctions);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return super.toString() + " @ " + getEntryPoint().getOffset();
|
||||
}
|
||||
}
|