moved generic graph interfaces to features graph module
created graph service broker

first commit of program graph module adapted to new graph api

GT-3317 connected listeners, documented and prettied up code
changed GhidraGraph to preserve order of created graph. Removed edge
filtering from initial program graph display

GT-3317 added exporters for supported formats

GT-3317 fixed GhidraGraph bug where it lost edges

updates

changed to new action builder
removed icons, improved AttributeFilters

removed DialogComponentProviderBuilder
fixed generic alphabet soup

added vertex name updating.

GT-3317 added threading to sugiyama
adapted to take advantage of multi-threaded edge crossing reduction in
circle layout
eliminated parallel edges, improved sizing, updated jungrapht version

GT-3317 fixing AST graph and moving modules and packages
started help
GT-3317 updated min-cross and color selections
uses min-cross that optimizes for graph size

GT-3317 help, javadocs

changes from review comments and cleaning up warnings and simplifying
exporter code
fixing warnings, simplifying unnecessarily complicated code
more changes from review
more changes from review, simplifications. removed unnecessary
threading, renamed vertex, edge, etc
GT-3317 squashed many commits to make rebase easier. Mostly changes from
first code review.
This commit is contained in:
ghidravore 2019-12-06 11:32:03 -05:00
parent 0001ee2651
commit 410af5a272
112 changed files with 8736 additions and 1094 deletions

View file

@ -0,0 +1,57 @@
<?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="Graphing the Program" text="Graphing the Program" target="help/topics/ProgramGraphPlugin/ProgramGraph.htm" />
</tocref>
</tocroot>

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

View file

@ -0,0 +1,397 @@
<!DOCTYPE doctype PUBLIC "-//W3C//DTD HTML 4.0 Frameset//EN">
<HTML>
<HEAD>
<META name="generator" content=
"HTML Tidy for Java (vers. 2009-12-01), see jtidy.sourceforge.net">
<TITLE>Graphing the Program</TITLE>
<META http-equiv="Content-Type" content="text/html; charset=windows-1252">
<LINK rel="stylesheet" type="text/css" href="../../shared/Frontpage.css">
</HEAD>
<BODY lang="EN-US">
<H1>Graphing the Program</H1>
<H3>Graph Output</H3>
<P>To display or export a graph, Ghidra supports multiple graph services. Ghidra has two
built-in graph services; one to display a graph and one to export a graph. Before invoking
the graph actions described below, make sure to choose the desired graph service. This choice
will direct the output of the graph function to the active graph service. To select a graph
service, use the <B>Graph<IMG src="../../shared/arrow.gif">Graph Output</B></LI> menu.
<H3>Graph types</H3>
<P>Program control flow Graphs can be created and then shown using an appropriate graph service.
A control flow graph is a representation of the flow from one portion of the code to
another. The nodes of the graph represent blocks of code and the edges represent flow between
the blocks of code.</P>
<P>There are two basic graph types, <A href="#Graph_Block_Flow">Block Flow</A> and <A href=
"#Graph_Calls_using_Default_Model">Call Graph</A>. Different colors and shapes are used to
depict each node and the flow between them. Multiple graphs of either type can exist at any
time.</P>
<BLOCKQUOTE>
<P>A <I>Call Graph</I> depicts subroutines as nodes. Calls between subroutines are shown as
edges. Under normal circumstances all nodes are subroutine entry points (orange triangle) and
all edges are calls (orange).</P>
<P>A Block Flow graph is a little more complicated. Each node is a contiguous set of
instructions or Code Block broken by an instruction that causes any type of flow. Move and
arithmetic instructions cause no change in flow so they will not break a block. Jump, Call,
and return instructions cause flow and will lie at the end of a Code Block. Block Flow graphs
are useful for looking at the control flow within a function in compiled code. For embedded
systems that have convoluted control flow structure, it may be beneficial to graph the entire
program.</P>
</BLOCKQUOTE>
<P>Selection and Location events are synchronized between each
graph and the other windows in the tool.
<H3>Selection</H3>
<P>The current selection within the graph display is represented by a red box around selected
nodes as shown below on the node labeled "00408133". A node is selected if any addresses it represents are contained within the
selection.</P>
<P align="center"><IMG src="images/SelectGraphNode.png"></P>
<P align="left"><BR>
When a selection is made in the graph display, all addresses represented by each selected node become
the new current selection within Ghidra.</P>
<BLOCKQUOTE>
<P><IMG src="../../shared/note.png"> See the documentation for the specific graph display for
descriptions of how to to make selections and navigate the graph.</P>
</BLOCKQUOTE>
<BLOCKQUOTE>
<P><IMG src="../../shared/tip.png"> A selection in one graph can be used to create a follow
on graph. When a graph is created, only blocks of code that fall within the current selection
will be part of the graph. For example, a single subroutine could be selected in a Call
Graph. From that selection, a <A href="#Graph_Block_Flow">Block Flow</A> graph can be created
from the basic blocks found within the selected subroutine.</P>
</BLOCKQUOTE>
<H3>Location</H3>
<P>The node containing the current address location is marked with a large red arrow as shown
below on the graph node labeled "00408133".</P>
<P align="center"><IMG src="images/FocusGraphNode.png"></P>
<P align="left">Whenever the cursor location changes in the Code Browser (or tool that created
the graph), the red arrow location on the graph is updated. The current location is also
changed when a selection of nodes is made within the graph display. The minimum address of all nodes
within the selection will become the current location. If the address of the current location
is not part of any node within the graph, the red arrow will not be visible.</P>
<P align="left">When the option <B><A href="#Show_Location_in_Graph">Graph<IMG border="0"
src="../../shared/arrow.gif"> Show Location</A></B> is turned on, the graph will re-orient
itself to insure that the red location arrow is always visible.</P>
<P align="left">Clicking on a node in the graph display causes the
current address location within Ghidra to change to the minimum address represented by the
graph node.</P>
<H3>Graph Representation</H3>
<P>By Default, the graphs use the following icons and colors to represent the nodes and edges.</P>
<CENTER>
<DIV align="center">
<CENTER>
<TABLE border="1" width="400" cellspacing="1">
<TBODY>
<TR>
<TD colspan="2" bgcolor="#c0c0c0">
<P align="center"><B>Graph Edge/Link Types</B></P>
</TD>
</TR>
<TR>
<TD width="200" align="center" bgcolor="#DDDDDD"><B>Edge Type</B></TD>
<TD width="200" align="center" bgcolor="#DDDDDD"><B>Color</B></TD>
</TR>
<TR>
<TD width="200" align="center" bgcolor="#0066FF">Fall-Through Flow</TD>
<TD width="200" align="center" bgcolor="#0066FF">Blue</TD>
</TR>
<TR>
<TD width="200" align="center" bgcolor="#00ff00">Unconditional Jump</TD>
<TD width="200" align="center" bgcolor="#00ff00">Green</TD>
</TR>
<TR>
<TD width="200" align="center" bgcolor="#ffff00">Conditional Jump</TD>
<TD width="200" align="center" bgcolor="#ffff00">Yellow</TD>
</TR>
<TR>
<TD width="200" align="center" bgcolor="#FF9900">Unconditional Call</TD>
<TD width="200" align="center" bgcolor="#FF9900">Orange</TD>
</TR>
<TR>
<TD width="200" align="center" bgcolor="#FF9900">Conditional Call</TD>
<TD width="200" align="center" bgcolor="#FF9900">Orange</TD>
</TR>
<TR>
<TD width="200" align="center" bgcolor="#00ffff">Computed Call or Jump</TD>
<TD width="200" align="center" bgcolor="#00ffff">Cyan</TD>
</TR>
<TR>
<TD width="200" align="center" bgcolor="#FF66FF">Indirect (i.e., pointer)</TD>
<TD width="200" align="center" bgcolor="#FF66FF">Pink</TD>
</TR>
<TR>
<TD width="200" align="center">From Entry Nexus</TD>
<TD width="200" align="center">White</TD>
</TR>
</TBODY>
</TABLE>
</CENTER>
</DIV>
</CENTER>
<BR>
<BR>
<CENTER>
<DIV align="center">
<CENTER>
<TABLE border="1" width="500" cellspacing="1">
<TBODY>
<TR>
<TD colspan="3" bgcolor="#c0c0c0" width="440">
<P align="center"><B>Graph Node/Vertex Types</B></P>
</TD>
</TR>
<TR>
<TD width="200" align="center" bgcolor="#DDDDDD"><B>Vertex Type</B></TD>
<TD width="200" align="center" bgcolor="#DDDDDD"><B>Color</B></TD>
<TD width="200" align="center" bgcolor="#DDDDDD"><B>Shape</B></TD>
</TR>
<TR>
<TD width="200" align="center" bgcolor="#FF9900">Entry</TD>
<TD width="200" align="center" bgcolor="#FF9900">Orange</TD>
<TD width="200" align="center" bgcolor="#FF9900">Triangle (down)</TD>
</TR>
<TR>
<TD width="200" align="center" bgcolor="#0000ff">Body</TD>
<TD width="200" align="center" bgcolor="#0000ff">Blue</TD>
<TD width="200" align="center" bgcolor="#0000ff">Oval</TD>
</TR>
<TR>
<TD width="200" align="center" bgcolor="#9900FF">Terminator</TD>
<TD width="200" align="center" bgcolor="#9900FF">Purple</TD>
<TD width="200" align="center" bgcolor="#9900FF">Triangle (up)</TD>
</TR>
<TR>
<TD width="200" align="center" bgcolor="#00ffff">Switch</TD>
<TD width="200" align="center" bgcolor="#00ffff">Cyan</TD>
<TD width="200" align="center" bgcolor="#00ffff">Diamond</TD>
</TR>
<TR>
<TD width="200" align="center" bgcolor="#ff0000">Bad</TD>
<TD width="200" align="center" bgcolor="#ff0000">Red</TD>
<TD width="200" align="center" bgcolor="#ff0000">Rectangle</TD>
</TR>
<TR>
<TD width="200" align="center" bgcolor="#FF66FF">Data</TD>
<TD width="200" align="center" bgcolor="#FF66FF">Pink</TD>
<TD width="200" align="center" bgcolor="#FF66FF">Cylinder</TD>
</TR>
<TR>
<TD width="200" align="center" bgcolor="#ffffff">Entry Nexus</TD>
<TD width="200" align="center" bgcolor="#ffffff">White</TD>
<TD width="200" align="center" bgcolor="#ffffff">Cone</TD>
</TR>
</TBODY>
</TABLE>
</CENTER>
</DIV>
</CENTER>
<H2><A name="Graph_Block_Flow"></A>Block Flow Graph</H2>
<P>A Block Flow Graph consists of nodes that represent Basic blocks of contiguous instructions.
Basic blocks are broken up by any instruction that causes a change in execution flow. All Jump,
Call, Branch, and Return instructions can cause the execution flow to change. Arithmetic and
store/load instructions do not break a Basic block because they do not change the execution
flow. A labeled instruction will always start a block regardless of the instruction type.</P>
<P>For example:</P>
<P align="center"><IMG src="images/BasicBlockExampleCode.png"></P>
<P>Would generate the following graph:</P>
<P align="center"><IMG src="images/BasicBlockGraph.png">
</P>
<BLOCKQUOTE>
<P><IMG src="../../shared/note.png"> If there is a current selection, the nodes and edges
will be restricted to blocks of code that fall within the selection.</P>
</BLOCKQUOTE>
<P>To Graph Block Flow Using the default model,</P>
<OL>
<LI>Select <B>Graph<IMG src="../../shared/arrow.gif"> Block Flow</B></LI>
<LI>A new graph window is created</LI>
</OL>
<H2><A name="Graph_Code_Flow"></A>Graph Code Flow</H2>
<P align="left">A Code Flow Graph is an extension of a <A href="#Graph_Block_Flow">Block Flow
Graph</A> in which each graph node (i.e., vertex) contains the list of instructions contained
within the associated block. The list of instructions are passed to the graph as the vertex
label.</P>
<P align="center"><BR>
<BR>
<IMG src="images/CodeBlockGraph.png"></P>
<H2><A name="Graph_Calls_using_Default_Model"></A>Graph Calls</H2>
<P>A graph of the call instruction flow from one subroutine to another can be created with
<B>Graph<IMG src="../../shared/arrow.gif"> Calls</B>. The graph is created using the default
Call Model. Several Subroutine Models are available. Each model provides a slightly
different perspective on what constitutes a subroutine.</P>
<BLOCKQUOTE>
<BLOCKQUOTE>
<P><IMG src="../../shared/note.png"> If there is a current selection, the nodes and edges
will be restricted to blocks of code that fall within the selection.</P>
</BLOCKQUOTE>
<P>To Graph Calls Using the default model,</P>
<OL>
<LI>Select <B>Graph<IMG src="../../shared/arrow.gif"> Calls</B></LI>
<LI>A new graph window is created</LI>
</OL>
<P><A name="Graph_Calls_Using_Model"></A>To Graph Calls Using a specific model*,</P>
<OL>
<LI>Select <B>Graph<IMG src="../../shared/arrow.gif"> Calls Using Model<IMG src=
"../../shared/arrow.gif"></B> &lt;<I><B>a Call Model</B></I>&gt;</LI>
<LI>
Select one of
<UL>
<LI>Isolated Entry Model</LI>
<LI>Multiple Entry Model</LI>
<LI>Overlapped Code Model</LI>
<LI>Partitioned Code Model</LI>
</UL>
</LI>
<LI>A new graph window is created</LI>
</OL>
<BLOCKQUOTE>
<P><IMG src="../../shared/note.png"> *For a more thorough description of each Call Block
Model (i.e., Subroutine Model), see <A href="help/topics/BlockModel/Block_Model.htm">Block
Models</A>. The specific list of models presented to the user may vary depending upon the
set of block models configured into the tool.</P>
</BLOCKQUOTE>
</BLOCKQUOTE>
<H2><A name="Reuse_Graph"></A>Reuse Graph</H2>
<P>When <I>Reuse Graph</I> is turned on, creating any new graphs will re-use the active graph
window. The active graph is the last graph window that was focused. This could be the
last graph window created, popped to the top, or interacted with to change the current
selection or location. Instead of a new window for each graph, all graphs will be rendered
using the same window. When a graph window is re-used the existing graph information is cleared
and replaced with the new graph information. If <A href="#Show_Location_in_Graph">Append
Graph</A> is also turned on, the information is also appended to the active graph window.</P>
<BLOCKQUOTE>
<P>To Reuse A Graph,</P>
<OL>
<LI>Turn on <B>Reuse Graph<BR>
</B> (a check mark will display next to the menu item)</LI>
<LI>Select an existing graph window to set it to the active window.<BR>
(this will pop the graph window to the front)</LI>
<LI>Create a graph using:<BR>
Select <B>Graph<IMG src="../../shared/arrow.gif"> Block Flow</B>, <B>Graph<IMG src=
"../../shared/arrow.gif"> Calls</B>, or any of the <B>Graph<IMG src=
"../../shared/arrow.gif"> Calls Using Model</B> items</LI>
</OL>
</BLOCKQUOTE>
<H2><A name="Append_Graph"></A>Append Graph</H2>
<P>When <I>Append Graph</I> is turned on, creating any new graphs will append the graph
information to the active graph. The active graph is the last graph window that was
focused. This could be the last graph window created, popped to the top, or interacted with to
change the current selection or location.</P>
<BLOCKQUOTE>
<P>To append to an existing graph,</P>
<OL>
<LI>Turn on <B>Append Graph</B><BR>
(a check mark will display next to the menu item)</LI>
<LI>Select the Graph window to append to<BR>
(this will pop the graph window to the front).</LI>
<LI>Create a graph using:<BR>
Select <B>Graph<IMG src="../../shared/arrow.gif"> Block Flow</B>, <B>Graph<IMG src=
"../../shared/arrow.gif"> Calls</B>, or any of the <B>Graph<IMG src=
"../../shared/arrow.gif"> Calls Using Model</B> menu items</LI>
</OL>
<P><IMG src="../../shared/note.png"> The <B>Reuse Graph</B> option must be enabled for the
<B>Append Graph</B> option to be considered. Toggling on the <B>Append Graph</B> option will
automatically turn on the <A href="#Reuse_Graph">Reuse Graph</A> option. Append Graph without
Reuse Graph will display new graphs in a new graph window, essentially having no effect.</P>
</BLOCKQUOTE>
<H2><A name="Show_Location_in_Graph"></A>Show Location</H2>
<P>When <I>Show Location</I> is turned <I><U>on</U></I>, the current address location will be
forced to visibly display within all graph windows. This may cause the graph to change
its view scale; resulting in disorientation when looking at a graph that has been carefully
arranged (clinically know as graphidisorientitis).</P>
<P>When Show Location is turned <U><I>off</I></U>, the graph view will not change as the
current address location changes.</P>
<BR>
<BR>
<BR>
<BR>
<BR>
</ul>
<P class="providedbyplugin">Provided by: <I>Program Graph Plugin</I></P>
<P class="relatedtopic">Related Topics</P>
<UL>
<LI><A href="help/topics/GraphServices/GraphDisplay.htm">Default Graph Display</A></LI>
<LI><A href="help/topics/GraphServices/GraphExport.htm">Graph Export</A></LI>
</UL><BR>
</BODY>
</HTML>

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

View file

@ -0,0 +1,522 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.graph.program;
import java.awt.Color;
import java.util.*;
import ghidra.app.plugin.core.colorizer.ColorizingService;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSetView;
import ghidra.program.model.block.*;
import ghidra.program.model.listing.*;
import ghidra.program.model.symbol.*;
import ghidra.program.util.ProgramSelection;
import ghidra.service.graph.*;
import ghidra.util.HTMLUtilities;
import ghidra.util.Msg;
import ghidra.util.exception.CancelledException;
import ghidra.util.exception.GraphException;
import ghidra.util.task.Task;
import ghidra.util.task.TaskMonitor;
/**
* <CODE>GraphTask</CODE> is a threaded task creating either a block or call graph.
*/
public class BlockGraphTask extends Task {
private static final String CODE_ATTRIBUTE = "Code";
private static final String SYMBOLS_ATTRIBUTE = "Symbols";
protected static final String PROGRESS_DIALOG_TITLE = "Graphing Program";
protected static final String INIT_PROGRESS_MSG = "Graphing Program...";
private boolean graphEntryPointNexus = false;
private boolean showCode = false;
private int codeLimitPerBlock = 10;
private ColorizingService colorizingService;
/**
* Edge flow tags
*/
protected final static int FALLTHROUGH = 0;
protected final static int CONDITIONAL_RETURN = 1;
protected final static int UNCONDITIONAL_JUMP = 2;
protected final static int CONDITIONAL_JUMP = 3;
protected final static int UNCONDITIONAL_CALL = 4;
protected final static int CONDITIONAL_CALL = 5;
protected final static int TERMINATOR = 6;
protected final static int COMPUTED = 7;
protected final static int INDIRECTION = 8;
protected final static int ENTRY = 9; // from Entry Nexus
protected final static String[] edgeNames =
{ "1", "2", "3", "4", "5", "6", "7", "13", "14", "15" };
// @formatter:off
protected final static String[] edgeTypes = {
"Fall-Through",
"Conditional-Return",
"Unconditional-Jump",
"Conditional-Jump",
"Unconditional-Call",
"Conditional-Call",
"Terminator",
"Computed",
"Indirection",
"Entry"
};
// @formatter:on
private final static String ENTRY_NODE = "Entry";
// "1"; // beginning of a block, someone calls it
private final static String BODY_NODE = "Body";
// "2"; // Body block, no flow
private final static String EXIT_NODE = "Exit";
// "3"; // Terminator
private final static String SWITCH_NODE = "Switch";
// "4"; // Switch/computed jump
private final static String BAD_NODE = "Bad";
// "5"; // Bad destination
private final static String DATA_NODE = "Data";
// "6"; // Data Node, used for indirection
private final static String ENTRY_NEXUS = "Entry-Nexus";
// "7"; //
private final static String EXTERNAL_NODE = "External";
// "8"; // node is external to program
private final static String ENTRY_NEXUS_NAME = "Entry Points";
private CodeBlockModel blockModel;
private AddressSetView selection;
private GraphDisplayProvider graphService;
private boolean reuseGraph;
private boolean appendGraph;
private PluginTool tool;
private String actionName;
private Program program;
public BlockGraphTask(String actionName, boolean graphEntryPointNexus, boolean showCode,
boolean reuseGraph,
boolean appendGraph, PluginTool tool, ProgramSelection selection,
CodeBlockModel blockModel, GraphDisplayProvider graphService) {
super("Graph Program", true, false, true);
this.actionName = actionName;
this.graphEntryPointNexus = graphEntryPointNexus;
this.showCode = showCode;
this.reuseGraph = reuseGraph;
this.appendGraph = appendGraph;
this.tool = tool;
this.blockModel = blockModel;
this.graphService = graphService;
this.colorizingService = tool.getService(ColorizingService.class);
this.selection = selection;
this.program = blockModel.getProgram();
}
/**
* Runs the move memory operation.
*/
@Override
public void run(TaskMonitor monitor) throws CancelledException {
AttributedGraph graph = createGraph();
monitor.setMessage("Generating Graph...");
try {
GraphDisplay display = graphService.getGraphDisplay(reuseGraph, monitor);
display.setGraphDisplayListener(
new BlockModelGraphDisplayListener(tool, blockModel, display));
if (showCode) {
display.defineVertexAttribute(CODE_ATTRIBUTE);
display.defineVertexAttribute(SYMBOLS_ATTRIBUTE);
display.setVertexLabel(CODE_ATTRIBUTE, GraphDisplay.ALIGN_LEFT, 12, true,
codeLimitPerBlock + 1);
}
display.setGraph(graph, actionName, appendGraph, monitor);
}
catch (GraphException e) {
if (!monitor.isCancelled()) {
Msg.showError(this, null, "Graphing Error", e.getMessage());
}
}
}
/**
* Set the maximum number of code lines which will be used per block when
* showCode is enabled.
* @param maxLines maximum number of code lines
*/
public void setCodeLimitPerBlock(int maxLines) {
codeLimitPerBlock = maxLines;
}
protected AttributedGraph createGraph() throws CancelledException {
int blockCount = 0;
AttributedGraph graph = new AttributedGraph();
CodeBlockIterator it = getBlockIterator();
List<AttributedVertex> entryPoints = new ArrayList<>();
while (it.hasNext()) {
CodeBlock curBB = it.next();
Address start = graphBlock(graph, curBB, entryPoints);
if (start != null && (++blockCount % 50) == 0) {
taskMonitor.setMessage("Process Block: " + start.toString());
}
}
// if option is set and there is more than one entry point vertex, create fake entry node
// and connect to each entry point vertex
if (graphEntryPointNexus && entryPoints.size() > 1) {
addEntryEdges(graph, entryPoints);
}
return graph;
}
private CodeBlockIterator getBlockIterator() throws CancelledException {
if (selection == null || selection.isEmpty()) {
return blockModel.getCodeBlocks(taskMonitor);
}
return blockModel.getCodeBlocksContaining(selection, taskMonitor);
}
private Address graphBlock(AttributedGraph graph, CodeBlock curBB, List<AttributedVertex> entries)
throws CancelledException {
Address[] startAddrs = curBB.getStartAddresses();
if (startAddrs == null || startAddrs.length == 0) {
Msg.error(this, "Block not graphed, missing start address: " + curBB.getMinAddress());
return null;
}
AttributedVertex vertex = graphBasicBlock(graph, curBB);
if (graphEntryPointNexus && hasExternalEntryPoint(startAddrs)) {
entries.add(vertex);
}
return startAddrs[0];
}
private boolean hasExternalEntryPoint(Address[] startAddrs) {
SymbolTable symbolTable = program.getSymbolTable();
for (Address address : startAddrs) {
if (symbolTable.isExternalEntryPoint(address)) {
return true;
}
}
return false;
}
private void addEntryEdges(AttributedGraph graph, List<AttributedVertex> entries) {
AttributedVertex entryNexusVertex = getEntryNexusVertex(graph);
for (AttributedVertex vertex : entries) {
AttributedEdge edge = graph.addEdge(entryNexusVertex, vertex);
edge.setAttribute("Name", edgeNames[ENTRY]);
edge.setAttribute("EdgeType", edgeTypes[ENTRY]);
}
}
protected AttributedVertex graphBasicBlock(AttributedGraph graph, CodeBlock curBB)
throws CancelledException {
AttributedVertex fromVertex = getBasicBlockVertex(graph, curBB);
// for each destination block
// create a vertex if it doesn't exit and add an edge to the destination vertex
CodeBlockReferenceIterator refIter = curBB.getDestinations(taskMonitor);
while (refIter.hasNext()) {
CodeBlockReference cbRef = refIter.next();
CodeBlock db = cbRef.getDestinationBlock();
// must be a reference to a data block
if (db == null) {
continue;
}
// don't include destination if it does not overlap selection
// always include if selection is empty
if (selection != null && !selection.isEmpty() && !selection.intersects(db)) {
continue;
}
AttributedVertex toVertex = getBasicBlockVertex(graph, db);
if (toVertex == null) {
continue;
}
// put the edge in the graph
String edgeAddr = cbRef.getReferent().toString();
AttributedEdge newEdge = graph.addEdge(fromVertex, toVertex);
// set it's attributes (really its name)
setEdgeAttributes(newEdge, cbRef);
setEdgeColor(newEdge, fromVertex, toVertex);
}
return fromVertex;
}
private void setEdgeColor(AttributedEdge edge, AttributedVertex fromVertex, AttributedVertex toVertex) {
// color the edge: first on the 'from' vertex, then try to 'to' vertex
String fromColor = fromVertex.getAttribute("Color");
String toColor = toVertex.getAttribute("Color");
if (fromColor != null || toColor != null) {
if (fromColor != null) {
edge.setAttribute("Color", fromColor);
}
else if (toColor != null) {
edge.setAttribute("Color", toColor);
}
}
}
private String getVertexId(CodeBlock bb) {
// vertex has attributes of Name = Label
// Address = address of blocks start
// VertexType = flow type of vertex
Address addr = bb.getFirstStartAddress();
if (addr.isExternalAddress()) {
Symbol s = bb.getModel().getProgram().getSymbolTable().getPrimarySymbol(addr);
return s.getName(true);
}
return addr.toString();
}
protected AttributedVertex getBasicBlockVertex(AttributedGraph graph, CodeBlock bb)
throws CancelledException {
String vertexId = getVertexId(bb);
AttributedVertex vertex = graph.getVertex(vertexId);
if (vertex != null) {
return vertex;
}
String vertexName = bb.getName();
vertex = graph.addVertex(vertexId, vertexName);
// add attributes for this vertex -
setVertexAttributes(vertex, bb, vertexName.equals(vertexId) ? false : isEntryNode(bb));
if (showCode) {
addSymbolAttribute(vertex, bb);
addCodeAttribute(vertex, bb);
}
return vertex;
}
private void addCodeAttribute(AttributedVertex vertex, CodeBlock bb) {
if (!bb.getMinAddress().isMemoryAddress()) {
vertex.setAttribute(CODE_ATTRIBUTE, vertex.getAttribute(SYMBOLS_ATTRIBUTE));
}
Listing listing = program.getListing();
CodeUnitIterator cuIter = listing.getCodeUnits(bb, true);
int cnt = 0;
int maxMnemonicFieldLen = 0;
StringBuffer buf = new StringBuffer();
while (cuIter.hasNext()) {
CodeUnit cu = cuIter.next();
if (cnt != 0) {
buf.append('\n');
}
String line = cu.toString();
int ix = line.indexOf(' ');
if (ix > maxMnemonicFieldLen) {
maxMnemonicFieldLen = ix;
}
buf.append(line);
if (++cnt == codeLimitPerBlock) {
buf.append("\n...");
break;
}
}
vertex.setAttribute(CODE_ATTRIBUTE, adjustCode(buf, maxMnemonicFieldLen + 1));
}
private void addSymbolAttribute(AttributedVertex vertex, CodeBlock bb) {
Symbol[] symbols = program.getSymbolTable().getSymbols(bb.getMinAddress());
if (symbols.length != 0) {
StringBuffer buf = new StringBuffer();
for (int i = 0; i < symbols.length; i++) {
if (i != 0) {
buf.append('\n');
}
buf.append(symbols[i].getName());
}
vertex.setAttribute(SYMBOLS_ATTRIBUTE, buf.toString());
}
}
private String adjustCode(StringBuffer buf, int mnemonicFieldLen) {
if (mnemonicFieldLen <= 1) {
return buf.toString();
}
int ix = 0;
char[] pad = new char[mnemonicFieldLen];
Arrays.fill(pad, ' ');
while (ix < buf.length()) {
int eolIx = buf.indexOf("\n", ix);
if (eolIx < 0) {
eolIx = buf.length();
}
int padIx = buf.indexOf(" ", ix);
if (padIx > 0 && padIx < eolIx) {
int padSize = mnemonicFieldLen - padIx + ix;
if (padSize > 0) {
buf.insert(padIx, pad, 0, padSize);
eolIx += padSize;
}
}
ix = eolIx + 1;
}
return buf.toString();
}
/**
* Determine if the specified block is an entry node.
* @param block the basic block to test
* @return true if the specified block is an entry node.
* @throws CancelledException if the operation is cancelled
*/
protected boolean isEntryNode(CodeBlock block) throws CancelledException {
CodeBlockReferenceIterator iter = block.getSources(taskMonitor);
boolean isSource = true;
while (iter.hasNext()) {
isSource = false;
if (iter.next().getFlowType().isCall()) {
return true;
}
}
return isSource;
}
protected void setEdgeAttributes(AttributedEdge edge, CodeBlockReference ref) {
int edgeType;
FlowType flowType = ref.getFlowType();
if (flowType == RefType.FALL_THROUGH) {
edgeType = FALLTHROUGH;
}
else if (flowType == RefType.UNCONDITIONAL_JUMP) {
edgeType = UNCONDITIONAL_JUMP;
}
else if (flowType == RefType.CONDITIONAL_JUMP) {
edgeType = CONDITIONAL_JUMP;
}
else if (flowType == RefType.UNCONDITIONAL_CALL) {
edgeType = UNCONDITIONAL_CALL;
}
else if (flowType == RefType.CONDITIONAL_CALL) {
edgeType = CONDITIONAL_CALL;
}
else if (flowType.isComputed()) {
edgeType = COMPUTED;
}
else if (flowType.isIndirect()) {
edgeType = INDIRECTION;
}
else if (flowType == RefType.TERMINATOR) {
edgeType = TERMINATOR;
}
else { // only FlowType.CONDITIONAL_TERMINATOR remains unchecked
edgeType = CONDITIONAL_RETURN;
}
// set attributes on this edge
edge.setAttribute("Name", edgeNames[edgeType]);
edge.setAttribute("EdgeType", edgeTypes[edgeType]);
}
protected void setVertexAttributes(AttributedVertex vertex, CodeBlock bb, boolean isEntry) {
String vertexType = BODY_NODE;
Address firstStartAddress = bb.getFirstStartAddress();
if (firstStartAddress.isExternalAddress()) {
vertexType = EXTERNAL_NODE;
}
else if (isEntry) {
vertexType = ENTRY_NODE;
}
else {
FlowType flowType = bb.getFlowType();
if (flowType.isTerminal()) {
vertexType = EXIT_NODE;
}
else if (flowType.isComputed()) {
vertexType = SWITCH_NODE;
}
else if (flowType == RefType.INDIRECTION) {
vertexType = DATA_NODE;
}
else if (flowType == RefType.INVALID) {
vertexType = BAD_NODE;
}
}
vertex.setAttribute("VertexType", vertexType);
setVertexColor(vertex, vertexType, firstStartAddress);
}
private void setVertexColor(AttributedVertex vertex, String vertexType, Address address) {
if (colorizingService == null) {
return;
}
Color color = colorizingService.getBackgroundColor(address);
if (color == null) {
return;
}
// color format: RGBrrrgggbbb
// -where rrr/ggg/bbb is a three digit int value for each respective color range
String rgb = "RGB" + HTMLUtilities.toRGBString(color);
vertex.setAttribute("Color", rgb); // sets the vertex color
// This value triggers the vertex to be painted with its color and not a
// while background.
if (showCode) {
// our own custom override of Labels/Icons
vertex.setAttribute("VertexType", "ColorFilled");
}
else {
// the default preferences for VertexType
vertex.setAttribute("VertexType", vertexType + ".Filled");
}
}
private AttributedVertex getEntryNexusVertex(AttributedGraph graph) {
AttributedVertex vertex = graph.getVertex(ENTRY_NEXUS_NAME);
if (vertex == null) {
vertex = graph.addVertex(ENTRY_NEXUS_NAME, ENTRY_NEXUS_NAME);
vertex.setAttribute("VertexType", ENTRY_NEXUS);
}
return vertex;
}
}

View file

@ -0,0 +1,140 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.graph.program;
import java.util.*;
import ghidra.app.plugin.core.graph.AddressBasedGraphDisplayListener;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.address.*;
import ghidra.program.model.block.*;
import ghidra.program.model.symbol.Symbol;
import ghidra.program.model.symbol.SymbolTable;
import ghidra.service.graph.GraphDisplay;
import ghidra.service.graph.GraphDisplayListener;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;
/**
* {@link GraphDisplayListener} that handle events back and from from program graphs.
*/
public class BlockModelGraphDisplayListener extends AddressBasedGraphDisplayListener {
private CodeBlockModel blockModel;
public BlockModelGraphDisplayListener(PluginTool tool, CodeBlockModel blockModel,
GraphDisplay display) {
super(tool, blockModel.getProgram(), display);
this.blockModel = blockModel;
}
@Override
protected String getVertexIdForAddress(Address address) {
try {
CodeBlock[] blocks = blockModel.getCodeBlocksContaining(address, TaskMonitor.DUMMY);
if (blocks != null && blocks.length > 0) {
return super.getVertexIdForAddress(blocks[0].getFirstStartAddress());
}
}
catch (CancelledException e) {
// Will not happen with dummyMonitor
// Model has already done the work when the graph was created
}
return super.getVertexIdForAddress(address);
}
@Override
protected List<String> getVertices(AddressSetView addrSet) {
if (addrSet.isEmpty()) {
return Collections.emptyList();
}
// Identify all blocks which have an entry point within the selection address set
ArrayList<String> blockList = new ArrayList<String>();
try {
SymbolTable symTable = program.getSymbolTable();
CodeBlockIterator cbIter =
blockModel.getCodeBlocksContaining(addrSet, TaskMonitor.DUMMY);
while (cbIter.hasNext()) {
CodeBlock block = cbIter.next();
String addrString;
Address addr = block.getFirstStartAddress();
if (addr.isExternalAddress()) {
Symbol s = symTable.getPrimarySymbol(addr);
addrString = s.getName(true);
}
else {
addrString = addr.toString();
}
blockList.add(addrString);
}
}
catch (CancelledException e) {
// Will not happen with dummyMonitor
// Model has already done the work when the graph was created
}
return blockList;
}
@Override
protected AddressSet getAddressSetForVertices(List<String> vertexIds) {
AddressSet addrSet = new AddressSet();
try {
// for each address string, translate it into a block
// and add it to the address set.
for (String vertexId : vertexIds) {
Address blockAddr = getAddressForVertexId(vertexId);
if (!isValidAddress(blockAddr)) {
continue;
}
CodeBlock blocks[] = null;
if (blockModel != null) {
CodeBlock block = blockModel.getCodeBlockAt(blockAddr, TaskMonitor.DUMMY);
if (block != null) {
blocks = new CodeBlock[1];
blocks[0] = block;
}
else {
blocks = blockModel.getCodeBlocksContaining(blockAddr, TaskMonitor.DUMMY);
}
}
if (blocks != null && blocks.length > 0) {
for (CodeBlock block : blocks) {
addrSet.add(block);
}
}
else {
addrSet.addRange(blockAddr, blockAddr);
}
}
}
catch (CancelledException e) {
// Will not happen with dummyMonitor
// Model has already done the work when the graph was created
}
return addrSet;
}
protected boolean isValidAddress(Address addr) {
if (addr == null || program == null) {
return false;
}
return program.getMemory().contains(addr) || addr.isExternalAddress();
}
}

View file

@ -0,0 +1,334 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.graph.program;
import java.util.ArrayList;
import java.util.List;
import docking.ActionContext;
import docking.action.DockingAction;
import docking.action.ToggleDockingAction;
import docking.action.builder.ActionBuilder;
import docking.action.builder.ToggleActionBuilder;
import ghidra.app.CorePluginPackage;
import ghidra.app.events.ProgramLocationPluginEvent;
import ghidra.app.events.ProgramSelectionPluginEvent;
import ghidra.app.plugin.PluginCategoryNames;
import ghidra.app.plugin.ProgramPlugin;
import ghidra.app.plugin.core.graph.GraphDisplayBrokerListener;
import ghidra.app.services.*;
import ghidra.framework.options.*;
import ghidra.framework.plugintool.PluginInfo;
import ghidra.framework.plugintool.PluginTool;
import ghidra.framework.plugintool.util.PluginStatus;
import ghidra.program.model.block.CodeBlockModel;
import ghidra.service.graph.GraphDisplayProvider;
import ghidra.util.HelpLocation;
import ghidra.util.Msg;
import ghidra.util.exception.NotFoundException;
import ghidra.util.task.TaskLauncher;
/**
* Plugin for generating program graphs. It uses the GraphServiceBroker to consume/display
* the graphs that it generates. This plugin generates several different types of program graphs.
* Both the "Block flow" and "code flow" actions generate graph of basic block flows. The only
* difference is that the "code flow" action generates a graph that
* displays the assembly for for each basic block, whereas the "block flow" action generates a graph
* that displays the symbol or address at the start of the basic block. This plugin also
* generates call graphs, using either the default subroutine model or one that the user chooses.
*/
//@formatter:off
@PluginInfo(
status = PluginStatus.RELEASED,
packageName = CorePluginPackage.NAME,
category = PluginCategoryNames.GRAPH,
shortDescription = "Program graph generator",
description = "This plugin provides actions for creating and managing program graphs"
+ " (block graphs and call graphs)."
+ "Once a graph is created, it uses the currenly selected graph output to display "
+ "or export the graph. The plugin "
+ "also provides event handling to facilitate interaction between "
+ "the graph and the tool.",
servicesRequired = { GoToService.class, BlockModelService.class, GraphDisplayBroker.class },
eventsProduced = { ProgramLocationPluginEvent.class, ProgramSelectionPluginEvent.class }
)
//@formatter:on
public class ProgramGraphPlugin extends ProgramPlugin
implements OptionsChangeListener, BlockModelServiceListener, GraphDisplayBrokerListener {
private static final String MAX_CODE_LINES_DISPLAYED = "Max Code Lines Displayed";
private static final String REUSE_GRAPH = "Reuse Graph";
private static final String GRAPH_ENTRY_POINT_NEXUS = "Graph Entry Point Nexus";
private static final String FORCE_LOCATION_DISPLAY_OPTION = "Force Location Visible on Graph";
public static final String MENU_GRAPH = "&Graph";
private BlockModelService blockModelService;
private List<DockingAction> subUsingGraphActions = new ArrayList<>();
private ToggleDockingAction reuseGraphAction;
private ToggleDockingAction appendGraphAction;
private boolean reuseGraph = false;
private boolean appendToGraph = false;
private boolean graphEntryPointNexus = false;
private int codeLimitPerBlock = 10;
private ToggleDockingAction forceLocationVisibleAction;
private GraphDisplayBroker broker;
private GraphDisplayProvider defaultGraphService;
public ProgramGraphPlugin(PluginTool tool) {
super(tool, true, true);
intializeOptions();
}
private void intializeOptions() {
HelpLocation help = new HelpLocation(getName(), "Graph_Option");
ToolOptions options = tool.getOptions("Graph");
options.registerOption(MAX_CODE_LINES_DISPLAYED, codeLimitPerBlock, help,
"Specifies the maximum number of instructions to display in each graph " +
"node in a Code Flow Graph.");
options.registerOption(REUSE_GRAPH, false, help,
"Determines whether the graph will reuse the active graph window when displaying graphs.");
options.registerOption(GRAPH_ENTRY_POINT_NEXUS, false, help,
"Add a dummy node at the root of the graph and adds dummy edges to each node that has " +
"no incoming edges.");
options.registerOption(FORCE_LOCATION_DISPLAY_OPTION, false, help,
"Specifies whether or not " +
"graph displays should force the visible graph to pan and/or scale to ensure that focused " +
"locations are visible.");
setOptions(options);
options.addOptionsChangeListener(this);
options.setOptionsHelpLocation(new HelpLocation(getName(), "Graph_Option"));
}
@Override
protected void init() {
broker = tool.getService(GraphDisplayBroker.class);
broker.addGraphDisplayBrokerListener(this);
defaultGraphService = broker.getDefaultGraphDisplayProvider();
blockModelService = tool.getService(BlockModelService.class);
blockModelService.addListener(this);
createActions();
}
@Override
public void dispose() {
super.dispose();
if (blockModelService != null) {
blockModelService.removeListener(this);
blockModelService = null;
}
}
/**
* Notification that an option changed.
*
* @param options
* options object containing the property that changed
* @param optionName
* name of option that changed
* @param oldValue
* old value of the option
* @param newValue
* new value of the option
*/
@Override
public void optionsChanged(ToolOptions options, String optionName, Object oldValue,
Object newValue) {
setOptions(options);
}
private void setOptions(Options options) {
codeLimitPerBlock = options.getInt(MAX_CODE_LINES_DISPLAYED, codeLimitPerBlock);
graphEntryPointNexus = options.getBoolean(GRAPH_ENTRY_POINT_NEXUS, false);
reuseGraph = options.getBoolean(REUSE_GRAPH, false);
if (reuseGraphAction != null) {
reuseGraphAction.setSelected(reuseGraph);
}
// Note: we don't care about the FORCE_LOCATION_DISPLAY_OPTION. We register it, but its
// the actually the various GraphDisplays the make use of it.
}
private void createActions() {
new ActionBuilder("Graph Block Flow", getName())
.menuPath(MENU_GRAPH, "&Block Flow")
.menuGroup("Graph", "A")
.onAction(c -> graphBlockFlow())
.enabledWhen(this::canGraph)
.buildAndInstall(tool);
new ActionBuilder("Graph Code Flow", getName())
.menuPath(MENU_GRAPH, "C&ode Flow")
.menuGroup("Graph", "B")
.onAction(c -> graphCodeFlow())
.enabledWhen(this::canGraph)
.buildAndInstall(tool);
new ActionBuilder("Graph Calls Using Default Model", getName())
.menuPath(MENU_GRAPH, "&Calls")
.menuGroup("Graph", "C")
.onAction(c -> graphSubroutines())
.enabledWhen(this::canGraph)
.buildAndInstall(tool);
reuseGraphAction = new ToggleActionBuilder("Reuse Graph", getName())
.menuPath(MENU_GRAPH, "Reuse Graph")
.menuGroup("Graph Options")
.selected(reuseGraph)
.onAction(c -> reuseGraph = reuseGraphAction.isSelected())
.enabledWhen(this::canGraph)
.buildAndInstall(tool);
appendGraphAction = new ToggleActionBuilder("Append Graph", getName())
.menuPath(MENU_GRAPH, "Append Graph")
.menuGroup("Graph Options")
.selected(false)
.onAction(c -> updateAppendAndReuseGraph())
.enabledWhen(this::canGraph)
.buildAndInstall(tool);
forceLocationVisibleAction = new ToggleActionBuilder("Show Location in Graph", getName())
.menuPath(MENU_GRAPH, "Show Location")
.description("Tell the graph to pan/scale as need to keep location changes visible")
.menuGroup("Graph Options")
.onAction(c -> toggleForceLocationVisible())
.enabledWhen(this::canGraph)
.buildAndInstall(tool);
updateSubroutineActions();
}
private boolean canGraph(ActionContext context) {
return currentProgram != null && defaultGraphService != null;
}
private void toggleForceLocationVisible() {
ToolOptions options = tool.getOptions("Graph");
options.setBoolean(FORCE_LOCATION_DISPLAY_OPTION, forceLocationVisibleAction.isSelected());
}
private void updateAppendAndReuseGraph() {
appendToGraph = appendGraphAction.isSelected();
if (appendToGraph && !reuseGraph) {
reuseGraph = true;
reuseGraphAction.setSelected(true);
}
}
private void updateSubroutineActions() {
// Remove old actions
for (DockingAction action : subUsingGraphActions) {
tool.removeAction(action);
}
// Create subroutine graph actions for each subroutine provided by BlockModelService
String[] subModels =
blockModelService.getAvailableModelNames(BlockModelService.SUBROUTINE_MODEL);
if (subModels.length <= 1) { // Not needed if only one subroutine model
return;
}
HelpLocation helpLoc = new HelpLocation(getName(), "Graph_Calls_Using_Model");
for (String blockModelName : subModels) {
DockingAction action = buildGraphActionWithModel(blockModelName, helpLoc);
subUsingGraphActions.add(action);
}
tool.setMenuGroup(new String[] { "Graph", "Calls Using Model" }, "Graph");
}
private DockingAction buildGraphActionWithModel(String blockModelName, HelpLocation helpLoc) {
return new ActionBuilder("Graph Calls using " + blockModelName, getName())
.menuPath("Graph", "Calls Using Model", blockModelName)
.menuGroup("Graph")
.helpLocation(helpLoc)
.onAction(c -> graphSubroutinesUsing(blockModelName))
.enabledWhen(this::canGraph)
.buildAndInstall(tool);
}
private void graphBlockFlow() {
graph("Flow Graph", blockModelService.getActiveBlockModelName(), false);
}
private void graphCodeFlow() {
graph("Code Graph", blockModelService.getActiveBlockModelName(), true);
}
private void graphSubroutines() {
graph("Call Graph", blockModelService.getActiveSubroutineModelName(), false);
}
private void graphSubroutinesUsing(String modelName) {
graph("Call Graph", modelName, false);
}
private void graph(String actionName, String modelName, boolean showCode) {
try {
CodeBlockModel model =
blockModelService.getNewModelByName(modelName, currentProgram, true);
BlockGraphTask task =
new BlockGraphTask(actionName, graphEntryPointNexus, showCode, reuseGraph,
appendToGraph, tool, currentSelection, model, defaultGraphService);
task.setCodeLimitPerBlock(codeLimitPerBlock);
new TaskLauncher(task, tool.getToolFrame());
}
catch (NotFoundException e) {
Msg.showError(this, null, "Error That Can't Happen",
"Can't find a block model from a name that we got from the existing block models!");
}
}
String getProgramName() {
return currentProgram != null ? currentProgram.getName() : null;
}
@Override
public void modelAdded(String modeName, int modelType) {
if (modelType == BlockModelService.SUBROUTINE_MODEL) {
updateSubroutineActions();
}
}
@Override
public void modelRemoved(String modeName, int modelType) {
if (modelType == BlockModelService.SUBROUTINE_MODEL) {
updateSubroutineActions();
}
}
@Override
public void providersChanged() {
defaultGraphService = broker.getDefaultGraphDisplayProvider();
}
}