mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2026-05-22 10:02:49 +08:00
GP-5481 Created prototype data graph feature
This commit is contained in:
@@ -31,6 +31,7 @@ dependencies {
|
||||
api project(':DecompilerDependent')
|
||||
api project(':FunctionGraph')
|
||||
api project(':ProposedUtils')
|
||||
api project(':DataGraph')
|
||||
|
||||
testImplementation project(path: ':Generic', configuration: 'testArtifacts')
|
||||
testImplementation project(path: ':Base', configuration: 'testArtifacts')
|
||||
|
||||
+76
@@ -0,0 +1,76 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.app.plugin.core.debug.gui.graph.data;
|
||||
|
||||
import datagraph.AbstractDataGraphPlugin;
|
||||
import ghidra.app.context.ListingActionContext;
|
||||
import ghidra.app.plugin.PluginCategoryNames;
|
||||
import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
|
||||
import ghidra.app.plugin.core.debug.event.TraceLocationPluginEvent;
|
||||
import ghidra.framework.plugintool.*;
|
||||
import ghidra.framework.plugintool.util.PluginStatus;
|
||||
import ghidra.program.util.ProgramLocation;
|
||||
|
||||
/**
|
||||
* Plugin for showing a graph of data from the listing.
|
||||
*/
|
||||
//@formatter:off
|
||||
@PluginInfo(
|
||||
status = PluginStatus.RELEASED,
|
||||
packageName = DebuggerPluginPackage.NAME,
|
||||
category = PluginCategoryNames.DEBUGGER,
|
||||
shortDescription = "Debugger Data Graph",
|
||||
description = """
|
||||
Plugin for displaying graphs of data objects in memory. From any data object in the
|
||||
listing, the user can display a graph of that data object. Initially, a graph will be shown
|
||||
with one vertex that has a scrollable view of the values in memory associated with that data.
|
||||
Also, any pointers or references from or to that data can be explored by following the
|
||||
references and creating additional vertices for the referenced code or data.
|
||||
""",
|
||||
eventsConsumed = {
|
||||
TraceLocationPluginEvent.class,
|
||||
},
|
||||
eventsProduced = {
|
||||
TraceLocationPluginEvent.class,
|
||||
}
|
||||
)
|
||||
//@formatter:on
|
||||
public class DebuggerDataGraphPlugin extends AbstractDataGraphPlugin {
|
||||
public DebuggerDataGraphPlugin(PluginTool plugintool) {
|
||||
super(plugintool);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void processEvent(PluginEvent event) {
|
||||
if (event instanceof TraceLocationPluginEvent ev) {
|
||||
ProgramLocation location = ev.getLocation();
|
||||
goTo(location);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fireLocationEvent(ProgramLocation location) {
|
||||
firePluginEvent(new TraceLocationPluginEvent(getName(), location));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isGraphActionEnabled(ListingActionContext context) {
|
||||
if (!context.getNavigatable().isDynamic()) {
|
||||
return false;
|
||||
}
|
||||
return super.isGraphActionEnabled(context);
|
||||
}
|
||||
}
|
||||
+7
-1
@@ -73,6 +73,7 @@ public class CircleWithLabelVertexShapeProvider implements VertexShapeProvider {
|
||||
protected boolean useDebugBorders = false;
|
||||
|
||||
private String fullLabelText;
|
||||
private int circleCenterYOffset;
|
||||
|
||||
public CircleWithLabelVertexShapeProvider(String label) {
|
||||
this.fullLabelText = label;
|
||||
@@ -85,6 +86,10 @@ public class CircleWithLabelVertexShapeProvider implements VertexShapeProvider {
|
||||
buildUi();
|
||||
}
|
||||
|
||||
public int getCircleCenterYOffset() {
|
||||
return circleCenterYOffset;
|
||||
}
|
||||
|
||||
protected void buildUi() {
|
||||
|
||||
String name = generateLabelText();
|
||||
@@ -201,6 +206,7 @@ public class CircleWithLabelVertexShapeProvider implements VertexShapeProvider {
|
||||
|
||||
vertexImageLabel.setBounds(x, y, size.width, size.height);
|
||||
Dimension shapeSize = vertexShape.getBounds().getSize();
|
||||
circleCenterYOffset = shapeSize.height / 2 - parentSize.height / 2;
|
||||
|
||||
// setFrame() will make sure the shape's x,y values are where they need to be
|
||||
// for the later 'full shape' creation
|
||||
@@ -332,7 +338,7 @@ public class CircleWithLabelVertexShapeProvider implements VertexShapeProvider {
|
||||
return false;
|
||||
}
|
||||
|
||||
protected void setTogglesVisible(boolean visible) {
|
||||
public void setTogglesVisible(boolean visible) {
|
||||
toggleInsButton.setVisible(visible);
|
||||
toggleOutsButton.setVisible(visible);
|
||||
}
|
||||
|
||||
@@ -397,6 +397,50 @@ public class ProgramBuilder {
|
||||
}
|
||||
}
|
||||
|
||||
public void setString(String address, String string) throws Exception {
|
||||
byte[] bytes = string.getBytes();
|
||||
setBytes(address, bytes);
|
||||
}
|
||||
|
||||
public void setShort(String address, short value) throws Exception {
|
||||
DataConverter converter = getDataConverter();
|
||||
byte[] bytes = converter.getBytes(value);
|
||||
setBytes(address, bytes);
|
||||
}
|
||||
|
||||
public void setInt(String address, int value) throws Exception {
|
||||
DataConverter converter = getDataConverter();
|
||||
byte[] bytes = converter.getBytes(value);
|
||||
setBytes(address, bytes);
|
||||
}
|
||||
|
||||
public void setLong(String address, long value) throws Exception {
|
||||
DataConverter converter = getDataConverter();
|
||||
byte[] bytes = converter.getBytes(value);
|
||||
setBytes(address, bytes);
|
||||
}
|
||||
|
||||
public void putAddress(String address, String pointerAddress) throws Exception {
|
||||
Address pointer = addr(pointerAddress);
|
||||
long offset = pointer.getOffset();
|
||||
int pointerSize = pointer.getAddressSpace().getPointerSize();
|
||||
switch (pointerSize) {
|
||||
case 2:
|
||||
setShort(address, (short) offset);
|
||||
break;
|
||||
case 4:
|
||||
setInt(address, (int) offset);
|
||||
break;
|
||||
default:
|
||||
setLong(address, offset);
|
||||
}
|
||||
}
|
||||
|
||||
private DataConverter getDataConverter() {
|
||||
boolean bigEndian = program.getMemory().isBigEndian();
|
||||
return bigEndian ? BigEndianDataConverter.INSTANCE : LittleEndianDataConverter.INSTANCE;
|
||||
}
|
||||
|
||||
public void setRead(MemoryBlock block, boolean r) {
|
||||
tx(() -> block.setRead(r));
|
||||
}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
EXCLUDE FROM GHIDRA JAR: true
|
||||
@@ -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.
|
||||
*/
|
||||
apply from: "$rootProject.projectDir/gradle/distributableGhidraModule.gradle"
|
||||
apply from: "$rootProject.projectDir/gradle/javaProject.gradle"
|
||||
apply from: "$rootProject.projectDir/gradle/helpProject.gradle"
|
||||
apply from: "$rootProject.projectDir/gradle/jacocoProject.gradle"
|
||||
apply from: "$rootProject.projectDir/gradle/javaTestProject.gradle"
|
||||
apply plugin: 'eclipse'
|
||||
|
||||
eclipse.project.name = 'Features Data Graph'
|
||||
|
||||
|
||||
// Note: this module's name is 'Data Graph'
|
||||
dependencies {
|
||||
api project(":Base")
|
||||
|
||||
// These have abstract test classes and stubs needed by this module
|
||||
testImplementation project(path: ':Project', configuration: 'testArtifacts')
|
||||
testImplementation project(path: ':SoftwareModeling', configuration: 'testArtifacts')
|
||||
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
##VERSION: 2.0
|
||||
Module.manifest||GHIDRA||||END|
|
||||
data/datagraph.theme.properties||GHIDRA||||END|
|
||||
src/main/help/help/TOC_Source.xml||GHIDRA||||END|
|
||||
src/main/help/help/topics/DataGraphPlugin/Data_Graph.html||GHIDRA||||END|
|
||||
src/main/help/help/topics/DataGraphPlugin/images/CodeVertex.png||GHIDRA||||END|
|
||||
src/main/help/help/topics/DataGraphPlugin/images/DataGraph.png||GHIDRA||||END|
|
||||
src/main/resources/images/view_detailed_16.png||Nuvola Icons - LGPL 2.1|||Nuvola icon set|END|
|
||||
@@ -0,0 +1,4 @@
|
||||
[Defaults]
|
||||
color.fg.datagraph.value = color.palette.blue
|
||||
icon.plugin.datagraph.action.viewer.vertex.format = view_detailed_16.png
|
||||
icon.plugin.datagraph.action.viewer.reset = icon.refresh
|
||||
@@ -0,0 +1,64 @@
|
||||
<?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="Data Graph" text="Data Graph" target="help/topics/DataGraphPlugin/Data_Graph.html" >
|
||||
<tocdef id="Vertices" sortgroup="a" text="Vertices" target="help/topics/DataGraphPlugin/Data_Graph.html#Vertices"/>
|
||||
<tocdef id="Graph Actions" sortgroup="b" text="Graph Actions" target="help/topics/DataGraphPlugin/Data_Graph.html#Data_Graph_Actions" />
|
||||
<tocdef id="Popups" sortgroup="c" text="Popups" target="help/topics/DataGraphPlugin/Data_Graph.html#Popups" />
|
||||
<tocdef id="Layout" sortgroup="d" text="Layout" target="help/topics/DataGraphPlugin/Data_Graph.html#Layout" />
|
||||
|
||||
</tocdef>
|
||||
</tocref>
|
||||
|
||||
</tocroot>
|
||||
@@ -0,0 +1,250 @@
|
||||
<!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>Function Graph Plugin</TITLE>
|
||||
<META http-equiv="Content-Type" content="text/html; charset=windows-1252">
|
||||
<LINK rel="stylesheet" type="text/css" href="help/shared/DefaultStyle.css">
|
||||
</HEAD>
|
||||
|
||||
<BODY lang="EN-US">
|
||||
<H1><A name="Data_Graph"></A> <A name="DataGraphPlugin"></A>Data Graph</H1>
|
||||
|
||||
<TABLE x-use-null-cells="" width="100%">
|
||||
<TBODY>
|
||||
<TR>
|
||||
<TD align="center" width="100%"><IMG alt="" border="1" src="images/DataGraph.png"></TD>
|
||||
</TR>
|
||||
</TBODY>
|
||||
</TABLE>
|
||||
|
||||
<BLOCKQUOTE>
|
||||
<P>The data graph is a graph display that shows data objects in memory. Each data vertex
|
||||
displays a scrollable view of a data object and its contents.</P>
|
||||
|
||||
<P>Initially, a data graph is generated from a specific data program location. The graph
|
||||
will be populated with one data vertex showing the data for that location. From this original
|
||||
source vertex, the references to and from this vertex can be explored to add additional
|
||||
vertices to the graph. In the data vertex display, any data elements that have an outgoing
|
||||
reference will have an <IMG src="Icons.RIGHT_ICON" alt="" border="0"> icon that can be clicked
|
||||
to quickly explore that reference.</P>
|
||||
|
||||
<P>If a reference leads from/to code, a simple code vertex is generated
|
||||
that simply shows the function and offset of the reference from the function's entry point.
|
||||
For the purposes of the data graph, code vertices are end points and cannot be explored
|
||||
further.</P>
|
||||
|
||||
<P>The display consists of the <A href="#Primary_View">Primary View</A> and an optional <A
|
||||
href="help/topics/VisualGraph/Visual_Graph.html#Satellite_View">Satellite View</A>.</P>
|
||||
</BLOCKQUOTE>
|
||||
|
||||
<H2><A name="Primary_View"></A>Primary View</H2>
|
||||
|
||||
<BLOCKQUOTE>
|
||||
<P>The primary view shows an initial data object that was used to create the graph. This
|
||||
data vertex can be used to explore references and pointers. As you explore, new vertices
|
||||
will be added to the graph. All vertices in the graph can trace a reference relationship
|
||||
back to the original source data object.</P>
|
||||
|
||||
<BLOCKQUOTE>
|
||||
<P><IMG src="help/shared/tip.png" alt="" border="0">The original source data vertex
|
||||
has a <IMG src="Icons.HOME_ICON" alt="" border="0"> icon in its header to indicate it is the original source vertex.</P>
|
||||
</BLOCKQUOTE>
|
||||
</BLOCKQUOTE>
|
||||
|
||||
<H2><A name="Vertices"></A>Vertices</H2>
|
||||
<BLOCKQUOTE>
|
||||
<H3>Data Vertices</H3>
|
||||
|
||||
<BLOCKQUOTE>
|
||||
<P>Data vertices show information about the data and values they contain. If the vertex
|
||||
datatype is a primitive, such as an int, the vertex contains only one row that shows its name
|
||||
and value. If the data is more complex such as a structure or array, the vertex will display
|
||||
multiple rows showing the name and value of each field in the structure. If the structure
|
||||
contains other structures or arrays of data, those data items can be expanded in a tree/table
|
||||
structure showing the names and values of that internal data.</P>
|
||||
|
||||
<P>Any elements that are pointers (or have attached outgoing references) will display a
|
||||
<IMG src="Icons.RIGHT_ICON" alt="" border="0"> icon that can be clicked to explore those
|
||||
references to add new vertices.</P>
|
||||
|
||||
<P>Data vertices can be resized by dragging the bottom right corner. Also, the interior column
|
||||
sizes can be adjusted by hovering the mouse on a column boundary and dragging left or
|
||||
right.</P>
|
||||
|
||||
<P>The header of a vertex contains the name of the label and/or address of the data object.
|
||||
The header also contains buttons that allow you to perform some common operations on the
|
||||
vertex.</P>
|
||||
|
||||
<P>As long as you are within the <A href="help/topics/VisualGraph/Visual_Graph.html#Interaction_Threshold">interaction threshold</A>,
|
||||
you may interact with the vertex to expand/collapse sub-data elements and to click on any
|
||||
pointer references to add vertices to the graph.</P>
|
||||
|
||||
</BLOCKQUOTE>
|
||||
<H3>Code Vertices</H3>
|
||||
<BLOCKQUOTE>
|
||||
<P>Generally vertices in the graph show data objects, but a vertex can also represent a
|
||||
reference from or to code. In the data graph, code vertices are terminal vertices, simply
|
||||
showing the function name and offset into or out of that function.</P>
|
||||
|
||||
<TABLE x-use-null-cells="" width="100%">
|
||||
<TBODY>
|
||||
<TR>
|
||||
<TD align="center" width="100%"><IMG alt="" border="1" src=
|
||||
"images/CodeVertex.png"></TD>
|
||||
</TR>
|
||||
</TBODY>
|
||||
</TABLE>
|
||||
|
||||
<P>In the image above, the graph is showing two references to the same string.</P>
|
||||
</BLOCKQUOTE>
|
||||
|
||||
<H3><A name="Layout">Vertex Layout</A></H3>
|
||||
<BLOCKQUOTE>
|
||||
<P>The data graph uses a special layout to attempt to maintain a logical structure as
|
||||
more vertices are added to the graph. This layout uses the exploration order
|
||||
to layout the vertices.</P>
|
||||
|
||||
<P>Starting with the original data vertex. All vertices that were
|
||||
added to the graph by following outgoing references are displayed in a column to the right of
|
||||
the vertex. Within this column, the vertices are ordered by the order they are first referenced
|
||||
from the source vertex.</P>
|
||||
|
||||
<P>Similarly, vertices added to the graph by following incoming references are shown in a
|
||||
column to the left of the source vertex. Within this column, the vertices are ordered by
|
||||
address.</P>
|
||||
|
||||
<P>Additional layers of vertices can be added by further exploring the child vertices and their
|
||||
descendants. These additional vertices are ordered in the same way relative to their immediate
|
||||
source vertex.</P>
|
||||
|
||||
<P>Whenever a vertex is removed from the graph, all vertices that were discovered by exploring
|
||||
from that vertex are also removed.</P>
|
||||
|
||||
</BLOCKQUOTE>
|
||||
<H3>Vertex Actions</H3>
|
||||
|
||||
<BLOCKQUOTE>
|
||||
<P>The following toolbar actions are available on a data vertex.</P>
|
||||
|
||||
<UL>
|
||||
<LI><A name="Expand_All"><IMG alt="" src="Icons.EXPAND_ALL_ICON"> <B> Expand All</B> -
|
||||
Expands all expandable sub-data elements recursively contained in the data object. Note
|
||||
that this action is not available if the data has no sub-data elements.</A></LI>
|
||||
|
||||
<LI><A name="Collapse_All"><IMG alt="" src="Icons.COLLAPSE_ALL_ICON"> <B>Collapse All</B>
|
||||
- Collapses all expanded sub-data elements. Note that this action is not available if the
|
||||
data has no sub-data elements.</A></LI>
|
||||
|
||||
<LI><A name="Delete_Vertex"><IMG alt="" src="Icons.CLOSE_ICON"> <B>Delete Vertex</B> - Removes this vertex and all vertices
|
||||
that descend from this vertex.</A></LI>
|
||||
</UL>
|
||||
|
||||
<P><A name="Popups">The following popup actions are are available depending on where the
|
||||
mouse is positioned when the popup is triggered.</A></P>
|
||||
|
||||
<UL>
|
||||
<LI><A name="Add_Outgoing"><B>Add All Outgoing References</B> - All outgoing references
|
||||
from this data element or its sub-elements will generate a new vertex in the graph, if
|
||||
not already present.</A></LI>
|
||||
|
||||
<LI><A name="Add_Incoming"><B>Add All Incoming References</B> - The program will be
|
||||
searched for any references to this data element or its sub-elents and a new vertex be
|
||||
created each discovered reference, if not already present.</A></LI>
|
||||
|
||||
<LI><A name="Expand_Fully"><B>Expand Fully</B> - If the mouse is over an
|
||||
expandable row in data vertex, the vertex and all it's child elements will be fully
|
||||
expanded.</A></LI>
|
||||
|
||||
<LI><A name="Original_Source"><B>Set Original Source</B> - Makes this the original source
|
||||
root vertex. All other vertices are reorganized and laid out as if they were discovered
|
||||
by following references from this vertex.See</A> <A href="#Layout">Vertex
|
||||
Layout.</A></LI>
|
||||
|
||||
<LI><A name="Delete_Selected"><B>Delete Selected Vertices</B> - Deletes the selected
|
||||
vertices and any descendants vertices (vertices that were discovered via exploring from
|
||||
that vertex.)</A></LI>
|
||||
</UL>
|
||||
</BLOCKQUOTE>
|
||||
<H3>Selecting Vertices</H3>
|
||||
|
||||
<BLOCKQUOTE>
|
||||
<P>Left-clicking a vertex will select that vertex. To select multiple vertices, hold down
|
||||
the <B><TT>Ctrl</TT></B> key while clicking. To deselect
|
||||
a block, hold the <TT>Ctrl</TT> key while clicking the block. To clear all selected
|
||||
blocks, click in an empty area of the primary view. When selected, a block is adorned
|
||||
with a halo.</P>
|
||||
|
||||
<P>You may also select multiple vertices in one action by holding the <B><TT>Ctrl</TT></B> key
|
||||
while performing a drag operation. Press the <B><TT>Ctrl</TT></B> key and start the drag in an
|
||||
empty area of the primary view (not over a vertex). This will create a bounding rectangle
|
||||
on the screen that will select any vertices contained therein when the action is
|
||||
finished.</P>
|
||||
</BLOCKQUOTE>
|
||||
|
||||
<H3>Navigating Vertices</H3>
|
||||
|
||||
<BLOCKQUOTE>
|
||||
<P>By default the data graph can be used to navigate the main views of the tool. Clicking
|
||||
on a vertex or a row within a vertex will generate a goto event for that address, causing
|
||||
the main tool to navigate to that location. See the actions below to turn off this
|
||||
behavior.</P>
|
||||
|
||||
<P>Also, the data graph can listen for tool location changes and select the appropriate
|
||||
vertex in the graph if any vertices contain the tool location address. This behavior is
|
||||
off by default, but can be turn on via a popup action.</P>
|
||||
</BLOCKQUOTE>
|
||||
</BLOCKQUOTE>
|
||||
|
||||
<H2><A name="Data_Graph_Actions"></A>Data Graph Actions</H2>
|
||||
<BLOCKQUOTE>
|
||||
<H3>Toolbar Actions</H3>
|
||||
|
||||
<UL>
|
||||
<LI><A name="Relayout_Graph"><IMG alt="" src=
|
||||
"icon.plugin.datagraph.action.viewer.reset"> <B>Refresh Layout</B> - All manually positioned
|
||||
vertices will be reset and the graph will relayout to its automated locations.</A></LI>
|
||||
|
||||
<LI><A name="Select_Home_Vertex"><IMG alt="" src=
|
||||
"Icons.HOME_ICON"> <B>Go To Source Vertex</B> - The original source vertex will be selected
|
||||
and centered in the graph.</A></LI>
|
||||
|
||||
<LI><A name="Navigate_In"><IMG alt="" src=
|
||||
"Icons.NAVIGATE_ON_INCOMING_EVENT_ICON"> <B>Navigate In</B> - If selected, the graph will
|
||||
listen for tool location events and select the vertex that contains the location address,
|
||||
is one exists.</A></LI>
|
||||
|
||||
<LI><A name="Navigate_Out"><IMG alt="" src=
|
||||
"Icons.NAVIGATE_ON_OUTGOING_EVENT_ICON"> <B>Navigate Out</B> - If selected, the graph will
|
||||
generate tool location events when vertices are selected or rows within a vertex are
|
||||
selected.</A></LI>
|
||||
|
||||
<LI><A name="Expanded_Format"><IMG alt="" src=
|
||||
"icon.plugin.datagraph.action.viewer.vertex.format"> <B>Expanded Format</B> - If selected,
|
||||
vertices will show more information for each row in the display. In compact mode, a data
|
||||
row will generally show the field name and its value. In expanded mode, a data row will
|
||||
generally show the datatype, field name, and its value.</A></LI>
|
||||
</UL>
|
||||
</BLOCKQUOTE
|
||||
<H2>Standard Graph Features and Actions</H2>
|
||||
<BLOCKQUOTE>
|
||||
<P>The data graph is a type of Ghidra Visual Graph and has some standard concepts, features
|
||||
and actions.
|
||||
<BLOCKQUOTE>
|
||||
<UL>
|
||||
<LI><A href="help/topics/VisualGraph/Visual_Graph.html#Satellite_View">Satellite View</A></LI>
|
||||
<LI><A href="help/topics/VisualGraph/Visual_Graph.html#Pan">Panning</A></LI>
|
||||
<LI><A href="help/topics/VisualGraph/Visual_Graph.html#Zoom">Zooming</A></LI>
|
||||
<LI><A href="help/topics/VisualGraph/Visual_Graph.html#Interaction_Threshold">Interaction Threshold</A></LI>
|
||||
</UL>
|
||||
</BLOCKQUOTE>
|
||||
</BLOCKQUOTE>
|
||||
|
||||
<P class="providedbyplugin">Provided by: <I>Data Graph Plugin</I></P>
|
||||
<BR>
|
||||
<BR>
|
||||
</BODY>
|
||||
</HTML>
|
||||
BIN
Binary file not shown.
|
After Width: | Height: | Size: 20 KiB |
BIN
Binary file not shown.
|
After Width: | Height: | Size: 48 KiB |
@@ -0,0 +1,84 @@
|
||||
/* ###
|
||||
* 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 datagraph;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import docking.action.builder.ActionBuilder;
|
||||
import ghidra.app.context.ListingActionContext;
|
||||
import ghidra.app.plugin.ProgramPlugin;
|
||||
import ghidra.framework.plugintool.PluginTool;
|
||||
import ghidra.program.model.listing.Data;
|
||||
import ghidra.program.util.ProgramLocation;
|
||||
import ghidra.util.HelpLocation;
|
||||
|
||||
/**
|
||||
* Base class for plugins that show a graph of data from program.
|
||||
*/
|
||||
|
||||
public abstract class AbstractDataGraphPlugin extends ProgramPlugin {
|
||||
private Set<DataGraphProvider> activeProviders = new HashSet<>();
|
||||
|
||||
public AbstractDataGraphPlugin(PluginTool plugintool) {
|
||||
super(plugintool);
|
||||
createActions();
|
||||
}
|
||||
|
||||
public void goTo(ProgramLocation location) {
|
||||
activeProviders.forEach(p -> p.goTo(location));
|
||||
}
|
||||
|
||||
private void createActions() {
|
||||
|
||||
new ActionBuilder("Display Data Graph", getName())
|
||||
.popupMenuPath("Data", "Display Data Graph")
|
||||
.keyBinding("ctrl G")
|
||||
.helpLocation(new HelpLocation("DataGraphPlugin", "Data_Graph"))
|
||||
.withContext(ListingActionContext.class)
|
||||
.enabledWhen(this::isGraphActionEnabled)
|
||||
.onAction(this::showDataGraph)
|
||||
.buildAndInstall(tool);
|
||||
}
|
||||
|
||||
protected boolean isGraphActionEnabled(ListingActionContext context) {
|
||||
return context.getCodeUnit() instanceof Data;
|
||||
}
|
||||
|
||||
private void showDataGraph(ListingActionContext context) {
|
||||
Data data = (Data) context.getCodeUnit();
|
||||
// the data from the context may be an internal sub-data, we want the outermost data.
|
||||
data = getTopLevelData(data);
|
||||
DataGraphProvider provider = new DataGraphProvider(this, data);
|
||||
activeProviders.add(provider);
|
||||
tool.showComponentProvider(provider, true);
|
||||
}
|
||||
|
||||
private Data getTopLevelData(Data data) {
|
||||
Data parent = data.getParent();
|
||||
while (parent != null) {
|
||||
data = parent;
|
||||
parent = data.getParent();
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
void removeProvider(DataGraphProvider provider) {
|
||||
activeProviders.remove(provider);
|
||||
}
|
||||
|
||||
public abstract void fireLocationEvent(ProgramLocation location);
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package datagraph;
|
||||
|
||||
import ghidra.app.CorePluginPackage;
|
||||
import ghidra.app.context.ListingActionContext;
|
||||
import ghidra.app.events.ProgramLocationPluginEvent;
|
||||
import ghidra.app.plugin.PluginCategoryNames;
|
||||
import ghidra.framework.plugintool.*;
|
||||
import ghidra.framework.plugintool.util.PluginStatus;
|
||||
import ghidra.program.util.ProgramLocation;
|
||||
|
||||
/**
|
||||
* Plugin for showing a graph of data from the listing.
|
||||
*/
|
||||
//@formatter:off
|
||||
@PluginInfo(
|
||||
status = PluginStatus.RELEASED,
|
||||
packageName = CorePluginPackage.NAME,
|
||||
category = PluginCategoryNames.GRAPH,
|
||||
shortDescription = "Data Graph",
|
||||
description = """
|
||||
Plugin for displaying graphs of data objects in memory. From any data object in the
|
||||
listing, the user can display a graph of that data object. Initially, a graph will be shown
|
||||
with one vertex that has a scrollable view of the values in memory associated with that data.
|
||||
Also, any pointers or references from or to that data can be explored by following the
|
||||
references and creating additional vertices for the referenced code or data.
|
||||
""",
|
||||
eventsConsumed = {
|
||||
ProgramLocationPluginEvent.class,
|
||||
},
|
||||
eventsProduced = {
|
||||
ProgramLocationPluginEvent.class,
|
||||
}
|
||||
)
|
||||
//@formatter:on
|
||||
public class DataGraphPlugin extends AbstractDataGraphPlugin {
|
||||
public DataGraphPlugin(PluginTool plugintool) {
|
||||
super(plugintool);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void processEvent(PluginEvent event) {
|
||||
if (event instanceof ProgramLocationPluginEvent ev) {
|
||||
ProgramLocation location = ev.getLocation();
|
||||
goTo(location);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fireLocationEvent(ProgramLocation location) {
|
||||
firePluginEvent(new ProgramLocationPluginEvent(getName(), location, location.getProgram()));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isGraphActionEnabled(ListingActionContext context) {
|
||||
if (context.getNavigatable().isDynamic()) {
|
||||
return false;
|
||||
}
|
||||
return super.isGraphActionEnabled(context);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,296 @@
|
||||
/* ###
|
||||
* 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 datagraph;
|
||||
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.swing.JComponent;
|
||||
import javax.swing.JPanel;
|
||||
|
||||
import datagraph.data.graph.*;
|
||||
import docking.ActionContext;
|
||||
import docking.ComponentProvider;
|
||||
import docking.action.ToggleDockingAction;
|
||||
import docking.action.builder.ActionBuilder;
|
||||
import docking.action.builder.ToggleActionBuilder;
|
||||
import generic.theme.GIcon;
|
||||
import ghidra.graph.VisualGraphComponentProvider;
|
||||
import ghidra.graph.viewer.*;
|
||||
import ghidra.graph.viewer.GraphComponent.SatellitePosition;
|
||||
import ghidra.graph.viewer.event.mouse.VertexMouseInfo;
|
||||
import ghidra.program.model.listing.Data;
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.program.util.ProgramLocation;
|
||||
import ghidra.util.HelpLocation;
|
||||
import ghidra.util.exception.AssertException;
|
||||
import resources.Icons;
|
||||
|
||||
/**
|
||||
* A {@link ComponentProvider} that is the UI component of the {@link DataGraphPlugin}. This
|
||||
* shows a graph of a Data object in memory and its referenced objects.
|
||||
*/
|
||||
public class DataGraphProvider
|
||||
extends VisualGraphComponentProvider<DegVertex, DegEdge, DataExplorationGraph> {
|
||||
|
||||
private static final GIcon DETAILS_ICON =
|
||||
new GIcon("icon.plugin.datagraph.action.viewer.vertex.format");
|
||||
private static final GIcon RESET_ICON = new GIcon("icon.plugin.datagraph.action.viewer.reset");
|
||||
private static final String NAME = "Data Graph";
|
||||
|
||||
private AbstractDataGraphPlugin plugin;
|
||||
private JPanel mainPanel;
|
||||
|
||||
private DegController controller;
|
||||
private ToggleDockingAction navagateInAction;
|
||||
private ToggleDockingAction navagateOutAction;
|
||||
private ToggleDockingAction expandedFormatAction;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param plugin the DataGraphPlugin
|
||||
* @param data the initial data object to display in the graph.
|
||||
*/
|
||||
public DataGraphProvider(AbstractDataGraphPlugin plugin, Data data) {
|
||||
super(plugin.getTool(), NAME, plugin.getName());
|
||||
this.plugin = plugin;
|
||||
controller = new DegController(this, data);
|
||||
createActions();
|
||||
setTransient();
|
||||
|
||||
buildComponent();
|
||||
addToTool();
|
||||
addSatelliteFeature(false, SatellitePosition.LOWER_LEFT);
|
||||
|
||||
setHelpLocation(new HelpLocation("DataGraphPlugin", "DataGraphPlugin"));
|
||||
}
|
||||
|
||||
private void buildComponent() {
|
||||
mainPanel = new JPanel(new BorderLayout());
|
||||
mainPanel.add(controller.getComponent());
|
||||
}
|
||||
|
||||
@Override
|
||||
public VisualGraphView<DegVertex, DegEdge, DataExplorationGraph> getView() {
|
||||
return controller.getView();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ActionContext getActionContext(MouseEvent event) {
|
||||
Set<DegVertex> selectedVertices = getSelectedVertices();
|
||||
if (event == null) {
|
||||
return new DegContext(this, controller.getFocusedVertex(),
|
||||
selectedVertices);
|
||||
}
|
||||
Object source = event.getSource();
|
||||
if (source instanceof SatelliteGraphViewer) {
|
||||
return new DegSatelliteContext(this);
|
||||
}
|
||||
|
||||
if (source instanceof GraphViewer) {
|
||||
@SuppressWarnings("unchecked")
|
||||
GraphViewer<DegVertex, DegEdge> viewer = (GraphViewer<DegVertex, DegEdge>) source;
|
||||
|
||||
VertexMouseInfo<DegVertex, DegEdge> vertexMouseInfo =
|
||||
GraphViewerUtils.convertMouseEventToVertexMouseEvent(viewer, event);
|
||||
DegVertex target = vertexMouseInfo != null ? vertexMouseInfo.getVertex() : null;
|
||||
return new DegContext(this, target, selectedVertices, vertexMouseInfo);
|
||||
}
|
||||
throw new AssertException(
|
||||
"Received mouse event from unexpected source in getActionContext(): " + source);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
plugin.removeProvider(this);
|
||||
controller.dispose();
|
||||
super.dispose();
|
||||
removeFromTool();
|
||||
}
|
||||
|
||||
@Override
|
||||
public JComponent getComponent() {
|
||||
return mainPanel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void closeComponent() {
|
||||
super.closeComponent();
|
||||
dispose();
|
||||
}
|
||||
|
||||
public Program getProgram() {
|
||||
return controller.getProgram();
|
||||
}
|
||||
|
||||
public DegController getController() {
|
||||
return controller;
|
||||
}
|
||||
|
||||
private void createActions() {
|
||||
new ActionBuilder("Select Home Vertex", plugin.getName())
|
||||
.toolBarIcon(Icons.HOME_ICON)
|
||||
.toolBarGroup("A")
|
||||
.description("Selects and Centers Original Source Vertx")
|
||||
.onAction(c -> controller.selectAndCenterHomeVertex())
|
||||
.buildAndInstallLocal(this);
|
||||
|
||||
new ActionBuilder("Relayout Graph", plugin.getName())
|
||||
.toolBarIcon(RESET_ICON)
|
||||
.toolBarGroup("A")
|
||||
.description("Erases all manual vertex positioning information")
|
||||
.onAction(c -> controller.resetAndRelayoutGraph())
|
||||
.buildAndInstallLocal(this);
|
||||
expandedFormatAction = new ToggleActionBuilder("Show Expanded Format", plugin.getName())
|
||||
.toolBarIcon(DETAILS_ICON)
|
||||
.toolBarGroup("A")
|
||||
.description("Show Expanded information in data vertices.")
|
||||
.helpLocation(new HelpLocation("DataGraphPlugin", "Expanded_Format"))
|
||||
.onAction(c -> controller.setCompactFormat(!expandedFormatAction.isSelected()))
|
||||
.buildAndInstallLocal(this);
|
||||
|
||||
navagateInAction =
|
||||
new ToggleActionBuilder("Navigate on Incoming Location Changes", plugin.getName())
|
||||
.sharedKeyBinding()
|
||||
.toolBarIcon(Icons.NAVIGATE_ON_INCOMING_EVENT_ICON)
|
||||
.toolBarGroup("B")
|
||||
.description("Attemps to select vertex corresponding to tool location changes.")
|
||||
.helpLocation(new HelpLocation("DataGraphPlugin", "Navigate_In"))
|
||||
.onAction(c -> controller.setNavigateIn(navagateInAction.isSelected()))
|
||||
.buildAndInstallLocal(this);
|
||||
|
||||
// this name is same as SelectionNavigationAction which allows sharing of keybinding
|
||||
navagateOutAction = new ToggleActionBuilder("Selection Navigation Action", plugin.getName())
|
||||
.toolBarIcon(Icons.NAVIGATE_ON_OUTGOING_EVENT_ICON)
|
||||
.toolBarGroup("B")
|
||||
.sharedKeyBinding()
|
||||
.description(
|
||||
"Selecting vetices or locations inside a vertex sends navigates the tool.")
|
||||
.helpLocation(new HelpLocation("DataGraphPlugin", "Navigate_Out"))
|
||||
.onAction(c -> controller.setNavigateOut(navagateOutAction.isSelected()))
|
||||
.selected(true)
|
||||
.buildAndInstallLocal(this);
|
||||
|
||||
new ActionBuilder("Incoming References", plugin.getName())
|
||||
.popupMenuPath("Add All Incoming References")
|
||||
.popupMenuGroup("A", "2")
|
||||
.description("Show Vertices for known references to this vertex.")
|
||||
.helpLocation(new HelpLocation("DataGraphPlugin", "Add Incoming"))
|
||||
.withContext(DegContext.class)
|
||||
.enabledWhen(c -> canShowReferences(c.getVertex()))
|
||||
.onAction(c -> controller.showAllIncommingReferences((DataDegVertex) c.getVertex()))
|
||||
.buildAndInstallLocal(this);
|
||||
|
||||
new ActionBuilder("Outgoing References", plugin.getName())
|
||||
.popupMenuPath("Add All Outgoing References")
|
||||
.popupMenuGroup("A", "1")
|
||||
.description("Show Vertices for known references to this vertex.")
|
||||
.helpLocation(new HelpLocation("DataGraphPlugin", "Add Outgoing"))
|
||||
.withContext(DegContext.class)
|
||||
.enabledWhen(c -> canShowReferences(c.getVertex()))
|
||||
.onAction(c -> controller.showAllOutgoingReferences((DataDegVertex) c.getVertex()))
|
||||
.buildAndInstallLocal(this);
|
||||
|
||||
new ActionBuilder("Delete Vertices", plugin.getName())
|
||||
.popupMenuPath("Delete Selected Vertices")
|
||||
.popupMenuGroup("B", "1")
|
||||
.description("Removes the selected vertices and their descendents from the graph")
|
||||
.helpLocation(new HelpLocation("DataGraphPlugin", "Delete_Selected"))
|
||||
.withContext(DegContext.class)
|
||||
.enabledWhen(c -> canClose(c.getSelectedVertices()))
|
||||
.onAction(c -> controller.deleteVertices(c.getSelectedVertices()))
|
||||
.buildAndInstallLocal(this);
|
||||
|
||||
new ActionBuilder("Set Original Vertex", plugin.getName())
|
||||
.popupMenuPath("Set Vertex as Original Source")
|
||||
.popupMenuGroup("B", "2")
|
||||
.description("Reorient graph as though this was the first vertex shown")
|
||||
.helpLocation(new HelpLocation("DataGraphPlugin", "Original_Source"))
|
||||
.withContext(DegContext.class)
|
||||
.enabledWhen(c -> canOrientGraphAround(c.getVertex()))
|
||||
.onAction(c -> controller.orientAround(c.getVertex()))
|
||||
.buildAndInstallLocal(this);
|
||||
|
||||
new ActionBuilder("Reset Vertex Location", plugin.getName())
|
||||
.popupMenuPath("Restore Location")
|
||||
.popupMenuGroup("B", "3")
|
||||
.popupMenuIcon(Icons.REFRESH_ICON)
|
||||
.description("Resets the vertex to the automated layout location.")
|
||||
.helpLocation(new HelpLocation("DataGraphPlugin", "Reset_Location"))
|
||||
.withContext(DegContext.class)
|
||||
.enabledWhen(c -> c.getVertex() != null && c.getVertex().hasUserChangedLocation())
|
||||
.onAction(c -> c.getVertex().clearUserChangedLocation())
|
||||
.buildAndInstallLocal(this);
|
||||
|
||||
new ActionBuilder("Expand Fully", plugin.getName())
|
||||
.popupMenuPath("Expand Fully")
|
||||
.popupMenuGroup("C", "1")
|
||||
.description("Expand all levels under selected row")
|
||||
.helpLocation(new HelpLocation("DataGraphPlugin", "Expand_Fully"))
|
||||
.withContext(DegContext.class)
|
||||
.enabledWhen(this::canExpandRecursively)
|
||||
.onAction(this::expandRecursively)
|
||||
.buildAndInstallLocal(this);
|
||||
}
|
||||
|
||||
private boolean canOrientGraphAround(DegVertex vertex) {
|
||||
if (vertex instanceof DataDegVertex) {
|
||||
return !vertex.isRoot();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean canShowReferences(DegVertex vertex) {
|
||||
return vertex instanceof DataDegVertex;
|
||||
}
|
||||
|
||||
private boolean canClose(Set<DegVertex> selectedVertices) {
|
||||
if (selectedVertices.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
if (selectedVertices.size() > 1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Special case for just one vertex selected. Can't delete the root vertex.
|
||||
DegVertex v = selectedVertices.iterator().next();
|
||||
return !v.isRoot();
|
||||
}
|
||||
|
||||
void goTo(ProgramLocation location) {
|
||||
controller.locationChanged(location);
|
||||
}
|
||||
|
||||
private boolean canExpandRecursively(DegContext context) {
|
||||
DegVertex vertex = context.getVertex();
|
||||
if (vertex instanceof DataDegVertex dataVertex) {
|
||||
return dataVertex.isOnExpandableRow();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void expandRecursively(DegContext context) {
|
||||
DataDegVertex vertex = (DataDegVertex) context.getVertex();
|
||||
vertex.expandSelectedRowRecursively();
|
||||
}
|
||||
|
||||
public void navigateOut(ProgramLocation location) {
|
||||
plugin.fireLocationEvent(location);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package datagraph;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import datagraph.data.graph.DegEdge;
|
||||
import datagraph.data.graph.DegVertex;
|
||||
import docking.ActionContext;
|
||||
import ghidra.graph.viewer.actions.VgVertexContext;
|
||||
import ghidra.graph.viewer.event.mouse.VertexMouseInfo;
|
||||
|
||||
/**
|
||||
* {@link ActionContext} for the data exploration graph.
|
||||
*/
|
||||
public class DegContext extends VgVertexContext<DegVertex> {
|
||||
|
||||
private Set<DegVertex> selectedVertices;
|
||||
|
||||
public DegContext(DataGraphProvider dataGraphProvider, DegVertex targetVertex,
|
||||
Set<DegVertex> selectedVertices) {
|
||||
this(dataGraphProvider, targetVertex, selectedVertices, null);
|
||||
}
|
||||
|
||||
public DegContext(DataGraphProvider dataGraphProvider,
|
||||
DegVertex targetVertex, Set<DegVertex> selectedVertices,
|
||||
VertexMouseInfo<DegVertex, DegEdge> vertexMouseInfo) {
|
||||
super(dataGraphProvider, targetVertex);
|
||||
this.selectedVertices = selectedVertices;
|
||||
}
|
||||
|
||||
public Set<DegVertex> getSelectedVertices() {
|
||||
return selectedVertices;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldShowSatelliteActions() {
|
||||
return getVertex() == null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/* ###
|
||||
* 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 datagraph;
|
||||
|
||||
import docking.ActionContext;
|
||||
import ghidra.app.context.ProgramActionContext;
|
||||
import ghidra.graph.viewer.actions.VisualGraphActionContext;
|
||||
|
||||
/**
|
||||
* {@link ActionContext} for the data exploration satellite graph.
|
||||
*/
|
||||
public class DegSatelliteContext extends ProgramActionContext
|
||||
implements VisualGraphActionContext {
|
||||
|
||||
public DegSatelliteContext(DataGraphProvider dataGraphProvider) {
|
||||
super(dataGraphProvider, dataGraphProvider.getProgram());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,181 @@
|
||||
/* ###
|
||||
* 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 datagraph.data.graph;
|
||||
|
||||
import java.awt.Component;
|
||||
import java.awt.Shape;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.awt.geom.Point2D;
|
||||
|
||||
import javax.swing.JComponent;
|
||||
|
||||
import datagraph.graph.explore.EgVertex;
|
||||
import docking.action.DockingAction;
|
||||
import ghidra.base.graph.CircleWithLabelVertexShapeProvider;
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.model.listing.*;
|
||||
|
||||
/**
|
||||
* A vertex that represents code in a data exploration graph. Currently, code vertices are
|
||||
* "dead end" vertices in the graph and cannot be explored further.
|
||||
*/
|
||||
public class CodeDegVertex extends DegVertex {
|
||||
|
||||
private Instruction instruction;
|
||||
private CircleWithLabelVertexShapeProvider shapeProvider;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param controller the graph controller
|
||||
* @param instruction the instruction that is reference from/to a data object in the graph.
|
||||
* @param parent the source vertex (from what vertex did you explore to get here)
|
||||
*/
|
||||
public CodeDegVertex(DegController controller, Instruction instruction, DegVertex parent) {
|
||||
super(controller, parent);
|
||||
this.instruction = instruction;
|
||||
String label = getVertexLabel();
|
||||
|
||||
this.shapeProvider = new CircleWithLabelVertexShapeProvider(label);
|
||||
shapeProvider.setTogglesVisible(false);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTitle() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return instruction.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public DegVertexStatus refreshGraph(boolean checkDataTypes) {
|
||||
return DegVertexStatus.VALID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
CodeDegVertex other = (CodeDegVertex) obj;
|
||||
return instruction.equals(other.instruction);
|
||||
}
|
||||
|
||||
private String getVertexLabel() {
|
||||
Address address = instruction.getAddress();
|
||||
Program program = instruction.getProgram();
|
||||
FunctionManager functionManager = program.getFunctionManager();
|
||||
Function f = functionManager.getFunctionContaining(address);
|
||||
if (f != null) {
|
||||
String name = f.getName();
|
||||
if (!f.getEntryPoint().equals(address)) {
|
||||
name += " + " + address.subtract(f.getEntryPoint());
|
||||
}
|
||||
return name;
|
||||
}
|
||||
return address.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearUserChangedLocation() {
|
||||
super.clearUserChangedLocation();
|
||||
controller.relayoutGraph();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Instruction @ " + instruction.getAddress().toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Shape getCompactShape() {
|
||||
return shapeProvider.getCompactShape();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Shape getFullShape() {
|
||||
return shapeProvider.getFullShape();
|
||||
}
|
||||
|
||||
@Override
|
||||
public JComponent getComponent() {
|
||||
return shapeProvider.getComponent();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void addAction(DockingAction action) {
|
||||
//codeVertexPanel.addAction(action);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Address getAddress() {
|
||||
return instruction.getMinAddress();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSelected(boolean selected) {
|
||||
super.setSelected(selected);
|
||||
controller.navigateOut(instruction.getAddress(), null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CodeUnit getCodeUnit() {
|
||||
return instruction;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isGrabbable(Component component) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Point2D getStartingEdgePoint(EgVertex end) {
|
||||
Point2D location = getLocation();
|
||||
return new Point2D.Double(location.getX(),
|
||||
location.getY() + shapeProvider.getCircleCenterYOffset());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Point2D getEndingEdgePoint(EgVertex start) {
|
||||
Point2D location = getLocation();
|
||||
return new Point2D.Double(location.getX(),
|
||||
location.getY() + shapeProvider.getCircleCenterYOffset());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean containsAddress(Address address) {
|
||||
return instruction.contains(address);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTooltip(MouseEvent e) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compare(DegVertex o1, DegVertex o2) {
|
||||
return o1.getAddress().compareTo(o2.getAddress());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,390 @@
|
||||
/* ###
|
||||
* 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 datagraph.data.graph;
|
||||
|
||||
import static datagraph.data.graph.DegVertex.DegVertexStatus.*;
|
||||
|
||||
import java.awt.Dimension;
|
||||
import java.awt.Shape;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.awt.geom.Point2D;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.swing.JComponent;
|
||||
|
||||
import datagraph.DataGraphPlugin;
|
||||
import datagraph.data.graph.panel.DataVertexPanel;
|
||||
import datagraph.data.graph.panel.model.row.DataRowObject;
|
||||
import datagraph.graph.explore.EgVertex;
|
||||
import docking.action.DockingAction;
|
||||
import docking.action.DockingActionIf;
|
||||
import docking.action.builder.ActionBuilder;
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.model.data.*;
|
||||
import ghidra.program.model.listing.CodeUnit;
|
||||
import ghidra.program.model.listing.Data;
|
||||
import ghidra.util.HelpLocation;
|
||||
import resources.Icons;
|
||||
|
||||
/**
|
||||
* A vertex in the data exploration graph for displaying the contents of a single data object.
|
||||
*/
|
||||
public class DataDegVertex extends DegVertex {
|
||||
|
||||
private Data data;
|
||||
private DataVertexPanel dataVertexPanel;
|
||||
private DockingAction deleteAction;
|
||||
private long dataTypeHash;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param controller the controller
|
||||
* @param data the Data object to be displayed by this vertex
|
||||
* @param source the source vertex (from what vertex did you explore to get here)
|
||||
* @param compactFormat determines if the row displays are in a compact format or an expanded
|
||||
* format
|
||||
*/
|
||||
public DataDegVertex(DegController controller, Data data, DegVertex source,
|
||||
boolean compactFormat) {
|
||||
super(controller, source);
|
||||
this.data = data;
|
||||
dataVertexPanel = new DataVertexPanel(controller, this, compactFormat);
|
||||
createActions();
|
||||
dataVertexPanel.updateHeader();
|
||||
dataVertexPanel.updateShape();
|
||||
|
||||
if (source == null) {
|
||||
dataVertexPanel.setIsRoot(true);
|
||||
}
|
||||
dataTypeHash = hash(data.getDataType());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTitle() {
|
||||
return dataVertexPanel.getTitle();
|
||||
}
|
||||
|
||||
@Override
|
||||
public DegVertexStatus refreshGraph(boolean checkDataType) {
|
||||
Address address = data.getAddress();
|
||||
Data newData = data.getProgram().getListing().getDataAt(address);
|
||||
if (newData == null) {
|
||||
return MISSING;
|
||||
}
|
||||
if (data != newData) {
|
||||
this.data = newData;
|
||||
dataVertexPanel.setData(newData);
|
||||
return CHANGED;
|
||||
}
|
||||
|
||||
if (checkDataType) {
|
||||
long newHash = hash(data.getDataType());
|
||||
if (newHash != dataTypeHash) {
|
||||
dataTypeHash = newHash;
|
||||
dataVertexPanel.setData(data); // force the data model to reset
|
||||
return CHANGED;
|
||||
}
|
||||
}
|
||||
return VALID;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@return a list of vertex row objects (currently only used for testing).}
|
||||
*/
|
||||
public List<DataRowObject> getRowObjects() {
|
||||
return dataVertexPanel.getRowObjects();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearUserChangedLocation() {
|
||||
super.clearUserChangedLocation();
|
||||
controller.relayoutGraph();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return data.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSource(EgVertex source) {
|
||||
super.setSource(source);
|
||||
dataVertexPanel.setIsRoot(source == null);
|
||||
deleteAction.setEnabled(source != null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
DataDegVertex other = (DataDegVertex) obj;
|
||||
return data.equals(other.data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public JComponent getComponent() {
|
||||
return dataVertexPanel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Data @ " + data.getAddress().toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Address getAddress() {
|
||||
return data.getAddress();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CodeUnit getCodeUnit() {
|
||||
return data;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void addAction(DockingAction action) {
|
||||
dataVertexPanel.addAction(action);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DockingActionIf getAction(String name) {
|
||||
return dataVertexPanel.getAction(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOutgoingEdgeOffsetFromCenter(EgVertex v) {
|
||||
return dataVertexPanel.getOutgoingEdgeOffsetFromCenter(v);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIncomingEdgeOffsetFromCenter(EgVertex vertex) {
|
||||
return dataVertexPanel.getIncommingEdgeOffsetFromCenter(vertex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSelected(boolean selected) {
|
||||
super.setSelected(selected);
|
||||
dataVertexPanel.setSelected(selected);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFocused(boolean focused) {
|
||||
super.setFocused(focused);
|
||||
dataVertexPanel.setFocused(focused);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
dataVertexPanel.dispose();
|
||||
dataVertexPanel = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Shape getCompactShape() {
|
||||
Shape shape = dataVertexPanel.getShape();
|
||||
return shape;
|
||||
}
|
||||
|
||||
public Data getData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
public Dimension getSize() {
|
||||
return dataVertexPanel.getSize();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the size of this vertex by the user.
|
||||
* @param dimension the new size for this vertex;
|
||||
*/
|
||||
public void setSizeByUser(Dimension dimension) {
|
||||
dataVertexPanel.setSizeByUser(dimension);
|
||||
}
|
||||
|
||||
/**
|
||||
* Records the componentPath of the sub-data in this vertex that the edge going to
|
||||
* the end vertex is associated with. Used to compute the y coordinate of the edge so that
|
||||
* aligns with that data as the data is scrolled within the component.
|
||||
* @param end the vertex that our outgoing edge to attached
|
||||
* @param componentPath the component path of the sub data in this vertex associated with
|
||||
* the edge going to the end vertex
|
||||
*/
|
||||
public void addOutgoingEdgeAnchor(DegVertex end, int[] componentPath) {
|
||||
dataVertexPanel.addOutgoingEdge(end, componentPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Records the Address of the sub-data in this vertex that the edge coming in
|
||||
* from the start vertex is associated with. Used to compute the y coordinate of the edge so
|
||||
* that it aligns with the sub-data as the data is scrolled within the component. For incoming
|
||||
* edges, we only record edges that are offset from the data start. If the incoming edge points
|
||||
* to the overall data object, the edge will always be attached to the top of the vertex
|
||||
* regardless of the scroll position.
|
||||
*
|
||||
* @param start the vertex that associated with an incoming edge
|
||||
* @param address the address of the sub data in this vertex associated with
|
||||
* the edge coming from the start vertex.
|
||||
*/
|
||||
public void addIncomingEdgeAnchor(DegVertex start, Address address) {
|
||||
dataVertexPanel.addIncommingEdge(start, address);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compare(DegVertex v1, DegVertex v2) {
|
||||
// outgoing child vertices are ordered based on the paths of the data that references
|
||||
// them so that they are in the same order they appear in the referring structure datatype.
|
||||
return dataVertexPanel.comparePaths(v1, v2);
|
||||
}
|
||||
|
||||
private void createActions() {
|
||||
String owner = DataGraphPlugin.class.getSimpleName();
|
||||
if (dataVertexPanel.isExpandable()) {
|
||||
DockingAction openAllAction = new ActionBuilder("Expand All", owner)
|
||||
.toolBarIcon(Icons.EXPAND_ALL_ICON)
|
||||
.description("Recursively open all data in this vertex.")
|
||||
.helpLocation(new HelpLocation("DataGraphPlugin", "Expand_All"))
|
||||
.onAction(c -> dataVertexPanel.expandAll())
|
||||
.build();
|
||||
addAction(openAllAction);
|
||||
DockingAction closeAllAction = new ActionBuilder("Collapse All", owner)
|
||||
.toolBarIcon(Icons.COLLAPSE_ALL_ICON)
|
||||
.description("Close all data in this vertex.")
|
||||
.helpLocation(new HelpLocation("DataGraphPlugin", "Collapse_All"))
|
||||
.onAction(c -> dataVertexPanel.collapseAll())
|
||||
.build();
|
||||
addAction(closeAllAction);
|
||||
}
|
||||
|
||||
deleteAction = new ActionBuilder("Close Vertex", owner)
|
||||
.toolBarIcon(Icons.CLOSE_ICON)
|
||||
.description("Removes this vertex and any of its descendents from the graph.")
|
||||
.helpLocation(new HelpLocation("DataGraphPlugin", "Delete_Vertex"))
|
||||
.enabled(source != null)
|
||||
.onAction(c -> controller.deleteVertices(Set.of(DataDegVertex.this)))
|
||||
.build();
|
||||
addAction(deleteAction);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTooltip(MouseEvent e) {
|
||||
return dataVertexPanel.getToolTipText(e);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Point2D getStartingEdgePoint(EgVertex end) {
|
||||
Point2D startLocation = getLocation();
|
||||
|
||||
// For the edge leaving this vertex going to the given end vertex, we need the
|
||||
// starting point of the edge.
|
||||
//
|
||||
// We do this by starting with this vertex's location which is at the center point in
|
||||
// the vertex. We want the x coordinate to be on the right edge of the vertex, so we add in
|
||||
// half the width. We want the y coordinate to be wherever the corresponding data element
|
||||
// is being displayed, so we need to know how much above or below the center point
|
||||
// to draw the edge point to make it line up in the scrolled display.
|
||||
|
||||
int yOffset = getOutgoingEdgeOffsetFromCenter(end);
|
||||
double x = startLocation.getX() + getSize().width / 2;
|
||||
double y = startLocation.getY() + yOffset;
|
||||
return new Point2D.Double(x, y);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Point2D getEndingEdgePoint(EgVertex start) {
|
||||
Point2D endLocation = getLocation();
|
||||
|
||||
// For the edge entering this vertex from the given start vertex, we need the
|
||||
// ending point of the edge.
|
||||
//
|
||||
// We do this by starting with this vertex's location which is at the center point in
|
||||
// the vertex. We want the x coordinate to be on the left edge of the vertex, so we subtract
|
||||
// half the width. We want the y coordinate to be wherever the corresponding address of
|
||||
// the reference is being displayed, so we need to know how much above or below the center
|
||||
// point to draw the edge point to make it line up in the scrolled display.
|
||||
|
||||
int yOffset = getIncomingEdgeOffsetFromCenter(start);
|
||||
|
||||
double x = endLocation.getX() - getSize().width / 2;
|
||||
double y = endLocation.getY() + yOffset;
|
||||
return new Point2D.Double(x, y);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether the column model should be a compact format or an expanded format. The basic
|
||||
* difference is that expanded format includes a datatype for each row and the compact only
|
||||
* shows a datatype if there is no value.
|
||||
* @param b true to show a compact row, false to show more information.
|
||||
*/
|
||||
public void setCompactFormat(boolean b) {
|
||||
dataVertexPanel.setCompactFormat(b);
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the given data row to show its sub-data components.
|
||||
* @param row the row to expand
|
||||
*/
|
||||
public void expand(int row) {
|
||||
dataVertexPanel.expand(row);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new vertex following the reference(s) coming out of the data component on the given
|
||||
* row.
|
||||
* @param row the row to open new vertices from
|
||||
*/
|
||||
public void openPointerReference(int row) {
|
||||
dataVertexPanel.openPointerReference(row);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@return true if the selected row in the vertex is expandable.}
|
||||
*/
|
||||
public boolean isOnExpandableRow() {
|
||||
return dataVertexPanel.isSelectedRowExpandable();
|
||||
}
|
||||
|
||||
/**
|
||||
* Expands the selected row in the vertex recursively.
|
||||
*/
|
||||
public void expandSelectedRowRecursively() {
|
||||
dataVertexPanel.expandSelectedRowRecursively();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean containsAddress(Address address) {
|
||||
return data.contains(address);
|
||||
}
|
||||
|
||||
private long hash(DataType dataType) {
|
||||
long hash = dataType.getLength() * 31 + dataType.getName().hashCode();
|
||||
if (dataType instanceof Composite composite) {
|
||||
for (DataTypeComponent dataTypeComponent : composite.getDefinedComponents()) {
|
||||
hash = 31 * hash + hash(dataTypeComponent.getDataType());
|
||||
}
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
}
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
/* ###
|
||||
* 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 datagraph.data.graph;
|
||||
|
||||
import datagraph.graph.explore.AbstractExplorationGraph;
|
||||
import ghidra.graph.viewer.layout.VisualGraphLayout;
|
||||
|
||||
/**
|
||||
* A graph for exploring data and its incoming and outgoing references.
|
||||
*/
|
||||
public class DataExplorationGraph extends AbstractExplorationGraph<DegVertex, DegEdge> {
|
||||
|
||||
private VisualGraphLayout<DegVertex, DegEdge> layout;
|
||||
|
||||
/**
|
||||
* The initial vertex for the graph. All other vertices in this graph can trace back its source
|
||||
* to this vertex.
|
||||
* @param root the initial source vertex for this explore graph
|
||||
*/
|
||||
public DataExplorationGraph(DegVertex root) {
|
||||
super(root);
|
||||
}
|
||||
|
||||
@Override
|
||||
public VisualGraphLayout<DegVertex, DegEdge> getLayout() {
|
||||
return layout;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataExplorationGraph copy() {
|
||||
DataExplorationGraph newGraph = new DataExplorationGraph(getRoot());
|
||||
|
||||
for (DegVertex v : vertices.keySet()) {
|
||||
newGraph.addVertex(v);
|
||||
}
|
||||
|
||||
for (DegEdge e : edges.keySet()) {
|
||||
newGraph.addEdge(e);
|
||||
}
|
||||
|
||||
return newGraph;
|
||||
}
|
||||
|
||||
void setLayout(VisualGraphLayout<DegVertex, DegEdge> layout) {
|
||||
this.layout = layout;
|
||||
}
|
||||
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,35 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package datagraph.data.graph;
|
||||
|
||||
import datagraph.graph.explore.EgEdge;
|
||||
|
||||
/**
|
||||
* An edge for the {@link DataExplorationGraph}
|
||||
*/
|
||||
public class DegEdge extends EgEdge<DegVertex> {
|
||||
|
||||
public DegEdge(DegVertex start, DegVertex end) {
|
||||
super(start, end);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
// Suppressing warning on the return type; we know our class is the right type
|
||||
@Override
|
||||
public DegEdge cloneEdge(DegVertex start, DegVertex end) {
|
||||
return new DegEdge(start, end);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
/* ###
|
||||
* 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 datagraph.data.graph;
|
||||
|
||||
import java.awt.event.MouseEvent;
|
||||
|
||||
import javax.swing.JComponent;
|
||||
|
||||
import ghidra.graph.viewer.GraphViewer;
|
||||
import ghidra.graph.viewer.VisualGraphView;
|
||||
import ghidra.graph.viewer.event.mouse.VertexTooltipProvider;
|
||||
|
||||
/**
|
||||
* Extends the VisualGraphView mainly to provide appropriate tool tips.
|
||||
*/
|
||||
public class DegGraphView extends VisualGraphView<DegVertex, DegEdge, DataExplorationGraph> {
|
||||
DegGraphView() {
|
||||
super();
|
||||
setSatelliteVisible(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void installGraphViewer() {
|
||||
super.installGraphViewer();
|
||||
GraphViewer<DegVertex, DegEdge> viewer = graphComponent.getPrimaryViewer();
|
||||
viewer.setVertexTooltipProvider(new DataGraphVertexTipProvider());
|
||||
}
|
||||
|
||||
private class DataGraphVertexTipProvider implements VertexTooltipProvider<DegVertex, DegEdge> {
|
||||
|
||||
@Override
|
||||
public JComponent getTooltip(DegVertex v) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JComponent getTooltip(DegVertex v, DegEdge e) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTooltipText(DegVertex v, MouseEvent e) {
|
||||
return v.getTooltip(e);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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 datagraph.data.graph;
|
||||
|
||||
import java.util.Comparator;
|
||||
|
||||
import datagraph.graph.explore.EgEdgeTransformer;
|
||||
import datagraph.graph.explore.EgGraphLayout;
|
||||
import ghidra.graph.VisualGraph;
|
||||
import ghidra.graph.viewer.layout.AbstractVisualGraphLayout;
|
||||
|
||||
/**
|
||||
* The layout for the DataExplorationGraph. It extends the {@link EgGraphLayout} the implements
|
||||
* the basic incoming and outgoing tree structures. This basically just adds the ordering logic
|
||||
* for vertices.
|
||||
*/
|
||||
public class DegLayout extends EgGraphLayout<DegVertex, DegEdge> {
|
||||
|
||||
protected DegLayout(DataExplorationGraph g, int verticalGap, int horizontalGap) {
|
||||
super(g, "Data Graph Layout", verticalGap, horizontalGap);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataExplorationGraph getVisualGraph() {
|
||||
return (DataExplorationGraph) getGraph();
|
||||
}
|
||||
|
||||
@Override
|
||||
public AbstractVisualGraphLayout<DegVertex, DegEdge> createClonedLayout(
|
||||
VisualGraph<DegVertex, DegEdge> newGraph) {
|
||||
if (!(newGraph instanceof DataExplorationGraph dataGraph)) {
|
||||
throw new IllegalArgumentException(
|
||||
"Must pass a " + DataExplorationGraph.class.getSimpleName() +
|
||||
"to clone the " + getClass().getSimpleName());
|
||||
}
|
||||
|
||||
DegLayout newLayout = new DegLayout(dataGraph, verticalGap, horizontalGap);
|
||||
return newLayout;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Comparator<DegVertex> getIncommingVertexComparator() {
|
||||
return (v1, v2) -> v1.getAddress().compareTo(v2.getAddress());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Comparator<DegVertex> getOutgoingVertexComparator() {
|
||||
return (v1, v2) -> {
|
||||
DegVertex parent = (DegVertex) v1.getSourceVertex();
|
||||
return parent.compare(v1, v2);
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
protected EgEdgeTransformer<DegVertex, DegEdge> createEdgeTransformer() {
|
||||
return new EgEdgeTransformer<>();
|
||||
}
|
||||
}
|
||||
@@ -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 datagraph.data.graph;
|
||||
|
||||
import ghidra.graph.viewer.layout.AbstractLayoutProvider;
|
||||
import ghidra.graph.viewer.layout.VisualGraphLayout;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
/**
|
||||
* Provider for the DegLayout
|
||||
*/
|
||||
public class DegLayoutProvider
|
||||
extends AbstractLayoutProvider<DegVertex, DegEdge, DataExplorationGraph> {
|
||||
private static final String NAME = "Data Graph Layout";
|
||||
|
||||
private static final int VERTICAL_GAP = 50;
|
||||
private static final int HORIZONTAL_GAP = 100;
|
||||
|
||||
@Override
|
||||
public VisualGraphLayout<DegVertex, DegEdge> getLayout(DataExplorationGraph graph,
|
||||
TaskMonitor monitor)
|
||||
throws CancelledException {
|
||||
DegLayout layout = new DegLayout(graph, VERTICAL_GAP, HORIZONTAL_GAP);
|
||||
initVertexLocations(graph, layout);
|
||||
return layout;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getLayoutName() {
|
||||
return NAME;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package datagraph.data.graph;
|
||||
|
||||
import java.awt.Component;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.util.Comparator;
|
||||
|
||||
import datagraph.graph.explore.EgVertex;
|
||||
import docking.GenericHeader;
|
||||
import docking.action.DockingAction;
|
||||
import docking.action.DockingActionIf;
|
||||
import ghidra.graph.viewer.vertex.VertexShapeProvider;
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.model.listing.CodeUnit;
|
||||
|
||||
/**
|
||||
* A vertex for the {@DataExorationGraph}
|
||||
*/
|
||||
public abstract class DegVertex extends EgVertex implements VertexShapeProvider,
|
||||
Comparator<DegVertex> {
|
||||
// enum for returning that status of a vertex when refreshing after a program change
|
||||
public enum DegVertexStatus {
|
||||
VALID,
|
||||
MISSING,
|
||||
CHANGED
|
||||
}
|
||||
|
||||
protected DegController controller;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param controller the controller for the data exploration graph
|
||||
* @param source the vertex that spawned this vertex. The original source vertex has no
|
||||
* parent source, but all the others must have a source which can be used to trace back
|
||||
* to the original source vertex.
|
||||
*/
|
||||
public DegVertex(DegController controller, DegVertex source) {
|
||||
super(source);
|
||||
this.controller = controller;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isGrabbable(Component component) {
|
||||
Component c = component;
|
||||
while (c != null) {
|
||||
if (c instanceof GenericHeader) {
|
||||
return true;
|
||||
}
|
||||
c = c.getParent();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
*{@return the program's address associated with this node.}
|
||||
*/
|
||||
public abstract Address getAddress();
|
||||
|
||||
/**
|
||||
*{@return the CodeUnit associated with this node.}
|
||||
*/
|
||||
public abstract CodeUnit getCodeUnit();
|
||||
|
||||
/**
|
||||
* {@return the tooltip for this vertex}
|
||||
* @param e the the mouse even triggering this call
|
||||
*/
|
||||
public abstract String getTooltip(MouseEvent e);
|
||||
|
||||
/**
|
||||
* Checks if the given vertex is still valid after a program change.
|
||||
* @param checkDataTypes if true, the underlying datatype should also be checked
|
||||
* @return the status of the vertex. The vertex can be valid, changed, or missing.
|
||||
*/
|
||||
public abstract DegVertexStatus refreshGraph(boolean checkDataTypes);
|
||||
|
||||
/**
|
||||
* {@return the title of this vertex}
|
||||
*/
|
||||
public abstract String getTitle();
|
||||
|
||||
/**
|
||||
* {@return true if code unit associated with this vertex contains the given address.}
|
||||
* @param address the address to check if it is at or in this vertex
|
||||
*/
|
||||
protected abstract boolean containsAddress(Address address);
|
||||
|
||||
/**
|
||||
* {@return the docking action with the given name from this vertex.}
|
||||
* @param name the name of the action to retrieve
|
||||
*/
|
||||
public DockingActionIf getAction(String name) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the given action to this vertex.
|
||||
* @param action the action to add to this vertex
|
||||
*/
|
||||
protected abstract void addAction(DockingAction action);
|
||||
|
||||
}
|
||||
+656
File diff suppressed because it is too large
Load Diff
+43
@@ -0,0 +1,43 @@
|
||||
/* ###
|
||||
* 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 datagraph.data.graph.panel;
|
||||
|
||||
import java.util.Comparator;
|
||||
|
||||
/**
|
||||
* Comparator for comparing two data component paths
|
||||
*/
|
||||
public class DtComponentPathComparator implements Comparator<int[]> {
|
||||
@Override
|
||||
public int compare(int[] o1, int[] o2) {
|
||||
int level = 0;
|
||||
int length1 = o1.length;
|
||||
int length2 = o2.length;
|
||||
while (level < length1 && level < length2) {
|
||||
int index1 = o1[level];
|
||||
int index2 = o2[level];
|
||||
if (index1 != index2) {
|
||||
return index1 - index2;
|
||||
}
|
||||
level++;
|
||||
}
|
||||
if (length1 == length2) {
|
||||
return 0;
|
||||
}
|
||||
return length1 - length2;
|
||||
|
||||
}
|
||||
}
|
||||
+132
@@ -0,0 +1,132 @@
|
||||
/* ###
|
||||
* 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 datagraph.data.graph.panel.model.column;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import datagraph.data.graph.panel.model.row.DataRowObject;
|
||||
import docking.widgets.trable.*;
|
||||
|
||||
/**
|
||||
* A GTrable column model for showing information about data and sub-data items. This model
|
||||
* shows the information in a compact format generally showing the field name and the current
|
||||
* value for that field. For the top element that has no field name, the datatype name is shown
|
||||
* instead. Also for the value field, if it doesn't have a value because it has sub pieces that
|
||||
* have values (structure), then the datatype name is shown in the value value field.
|
||||
*/
|
||||
public class CompactDataColumnModel extends GTrableColumnModel<DataRowObject> {
|
||||
|
||||
@Override
|
||||
protected void populateColumns(List<GTrableColumn<DataRowObject, ?>> columnList) {
|
||||
columnList.add(new NameColumn());
|
||||
columnList.add(new ValueColumn());
|
||||
columnList.add(new PointerButtonColumn());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@return true if the given column represents the pointer icon column where the user can
|
||||
* click to add new vertices to the graph.}
|
||||
* @param column the column to check
|
||||
*/
|
||||
public boolean isPointerButtonColumn(int column) {
|
||||
return column == 2;
|
||||
}
|
||||
|
||||
private class NameColumn extends GTrableColumn<DataRowObject, String> {
|
||||
@Override
|
||||
public String getValue(DataRowObject row) {
|
||||
// if in compact format, show type in name field if it doesn't have a name
|
||||
if (row.getIndentLevel() == 0) {
|
||||
return row.getDataType();
|
||||
}
|
||||
return row.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getPreferredWidth() {
|
||||
return 150;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class TypeColumn extends GTrableColumn<DataRowObject, String> {
|
||||
@Override
|
||||
public String getValue(DataRowObject row) {
|
||||
return row.getDataType();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getPreferredWidth() {
|
||||
return 120;
|
||||
}
|
||||
}
|
||||
|
||||
private class ValueColumn extends GTrableColumn<DataRowObject, String> {
|
||||
private GTrableCellRenderer<String> renderer = new ValueColumnRenderer();
|
||||
|
||||
@Override
|
||||
public String getValue(DataRowObject row) {
|
||||
String value = row.getValue();
|
||||
if (!StringUtils.isBlank(value)) {
|
||||
value = " = " + value;
|
||||
}
|
||||
else if (row.getIndentLevel() > 0) {
|
||||
// special case if in compact for to show data types in the value column
|
||||
// on entries that can be opened to show inner values.
|
||||
value = " " + row.getDataType();
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getPreferredWidth() {
|
||||
return 100;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GTrableCellRenderer<String> getRenderer() {
|
||||
return renderer;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class PointerButtonColumn extends GTrableColumn<DataRowObject, Boolean> {
|
||||
private GTrableCellRenderer<Boolean> renderer = new PointerColumnRenderer();
|
||||
|
||||
@Override
|
||||
public Boolean getValue(DataRowObject row) {
|
||||
return row.hasOutgoingReferences();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getPreferredWidth() {
|
||||
return 24;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isResizable() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GTrableCellRenderer<Boolean> getRenderer() {
|
||||
return renderer;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+122
@@ -0,0 +1,122 @@
|
||||
/* ###
|
||||
* 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 datagraph.data.graph.panel.model.column;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import datagraph.data.graph.panel.model.row.DataRowObject;
|
||||
import docking.widgets.trable.*;
|
||||
|
||||
/**
|
||||
* A GTrable column model for showing information about data and sub-data items. This model
|
||||
* shows the information in a expanded format that displays the datatype, the field name and
|
||||
* the current value for that field.
|
||||
*/
|
||||
public class ExpandedDataColumnModel extends GTrableColumnModel<DataRowObject> {
|
||||
|
||||
@Override
|
||||
protected void populateColumns(List<GTrableColumn<DataRowObject, ?>> columnList) {
|
||||
columnList.add(new TypeColumn());
|
||||
columnList.add(new NameColumn());
|
||||
columnList.add(new ValueColumn());
|
||||
columnList.add(new PointerButtonColumn());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@return true if the given column represents the pointer icon column where the user can
|
||||
* click to add new vertices to the graph.}
|
||||
* @param column the column to check
|
||||
*/
|
||||
public boolean isPointerButtonColumn(int column) {
|
||||
return column == 3;
|
||||
}
|
||||
|
||||
private static class NameColumn extends GTrableColumn<DataRowObject, String> {
|
||||
@Override
|
||||
public String getValue(DataRowObject row) {
|
||||
return row.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getPreferredWidth() {
|
||||
return 150;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class TypeColumn extends GTrableColumn<DataRowObject, String> {
|
||||
@Override
|
||||
public String getValue(DataRowObject row) {
|
||||
return row.getDataType();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getPreferredWidth() {
|
||||
return 120;
|
||||
}
|
||||
}
|
||||
|
||||
private static class ValueColumn extends GTrableColumn<DataRowObject, String> {
|
||||
private GTrableCellRenderer<String> renderer = new ValueColumnRenderer();
|
||||
|
||||
@Override
|
||||
public String getValue(DataRowObject row) {
|
||||
String value = row.getValue();
|
||||
if (!StringUtils.isBlank(value)) {
|
||||
value = " = " + value;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getPreferredWidth() {
|
||||
return 100;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GTrableCellRenderer<String> getRenderer() {
|
||||
return renderer;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class PointerButtonColumn extends GTrableColumn<DataRowObject, Boolean> {
|
||||
private GTrableCellRenderer<Boolean> renderer = new PointerColumnRenderer();
|
||||
|
||||
@Override
|
||||
public Boolean getValue(DataRowObject row) {
|
||||
return row.hasOutgoingReferences();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getPreferredWidth() {
|
||||
return 24;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isResizable() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GTrableCellRenderer<Boolean> getRenderer() {
|
||||
return renderer;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+44
@@ -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 datagraph.data.graph.panel.model.column;
|
||||
|
||||
import java.awt.Component;
|
||||
|
||||
import javax.swing.Icon;
|
||||
|
||||
import docking.widgets.trable.DefaultGTrableCellRenderer;
|
||||
import docking.widgets.trable.GTrable;
|
||||
import resources.Icons;
|
||||
|
||||
/**
|
||||
* Renderer for the pointer icon column where the use can click to add vertices to the graph.
|
||||
*/
|
||||
public class PointerColumnRenderer extends DefaultGTrableCellRenderer<Boolean> {
|
||||
|
||||
private static final Icon ICON = Icons.RIGHT_ICON;
|
||||
|
||||
@Override
|
||||
public Component getCellRenderer(GTrable<?> trable, Boolean value, boolean isSelected,
|
||||
boolean hasFocus, int row, int column) {
|
||||
|
||||
super.getCellRenderer(trable, null, isSelected, hasFocus, row, column);
|
||||
|
||||
boolean isPointer = value;
|
||||
Icon icon = isPointer ? ICON : null;
|
||||
setIcon(icon);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package datagraph.data.graph.panel.model.column;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.Component;
|
||||
|
||||
import docking.widgets.trable.DefaultGTrableCellRenderer;
|
||||
import docking.widgets.trable.GTrable;
|
||||
import generic.theme.GColor;
|
||||
|
||||
/**
|
||||
* Column renderer for the values column. Used to change the foreground color for values.
|
||||
*/
|
||||
public class ValueColumnRenderer extends DefaultGTrableCellRenderer<String> {
|
||||
private Color valueColor = new GColor("color.fg.datagraph.value");
|
||||
|
||||
@Override
|
||||
public Component getCellRenderer(GTrable<?> trable, String value, boolean isSelected,
|
||||
boolean hasFocus, int row, int column) {
|
||||
|
||||
super.getCellRenderer(trable, value, isSelected, hasFocus, row, column);
|
||||
if (value.startsWith(" =") && !isSelected) {
|
||||
setForeground(valueColor);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
}
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
/* ###
|
||||
* 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 datagraph.data.graph.panel.model.row;
|
||||
|
||||
import ghidra.program.model.listing.Data;
|
||||
|
||||
/**
|
||||
* DataRowObject for groups of array elements. Because arrays can be large they are recursively
|
||||
* grouped.
|
||||
*/
|
||||
public class ArrayGroupDataRowObject extends DataRowObject {
|
||||
|
||||
private String name;
|
||||
private Data data;
|
||||
|
||||
ArrayGroupDataRowObject(Data data, int startIndex, int length, int indentLevel,
|
||||
boolean isOpen) {
|
||||
super(indentLevel, isOpen);
|
||||
this.data = data;
|
||||
this.name = "[" + startIndex + " - " + (startIndex + length - 1) + "]";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getValue() {
|
||||
return "";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDataType() {
|
||||
return "";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isExpandable() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Data getData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
}
|
||||
+65
@@ -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 datagraph.data.graph.panel.model.row;
|
||||
|
||||
import ghidra.program.model.listing.Data;
|
||||
|
||||
/**
|
||||
* DataRowObject for actual DataComponents. This directly corresponds to a
|
||||
* Data or sub Data object in a program.
|
||||
*/
|
||||
public class ComponentDataRowObject extends DataRowObject {
|
||||
static final int ARRAY_GROUP_SIZE = 100;
|
||||
protected Data data;
|
||||
|
||||
public ComponentDataRowObject(int indentLevel, Data data, boolean isOpen) {
|
||||
super(indentLevel, isOpen);
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isExpandable() {
|
||||
return data.getNumComponents() > 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Data getData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return data.getFieldName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getValue() {
|
||||
return data.getDefaultValueRepresentation();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDataType() {
|
||||
return data.getDataType().getDisplayName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasOutgoingReferences() {
|
||||
if (data.isPointer()) {
|
||||
return true;
|
||||
}
|
||||
return data.getProgram().getReferenceManager().hasReferencesFrom(data.getAddress());
|
||||
}
|
||||
}
|
||||
+85
@@ -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 datagraph.data.graph.panel.model.row;
|
||||
|
||||
import docking.widgets.table.GTable;
|
||||
import docking.widgets.table.RowObject;
|
||||
import ghidra.program.model.listing.Data;
|
||||
|
||||
/**
|
||||
* Abstract class for displaying rows in a Data GTrable model. Similar to a {@link RowObject} in
|
||||
* a {@link GTable}. The big difference is that each row maintains its indent level and whether
|
||||
* or not is is expanded. GTrables are like tables, but with a tree like structure. Each row that is
|
||||
* a child of another row has its indent level set to one more than its parent. The expanded flag is
|
||||
* used to indicate if a given row has visible child rows showing or not.
|
||||
*/
|
||||
|
||||
public abstract class DataRowObject {
|
||||
private int indentLevel;
|
||||
private boolean isExpanded;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param indentLevel the indent level for this row object
|
||||
* @param isExpanded true if this object has child rows that are being displayed
|
||||
*/
|
||||
protected DataRowObject(int indentLevel, boolean isExpanded) {
|
||||
this.indentLevel = indentLevel;
|
||||
this.isExpanded = isExpanded;
|
||||
}
|
||||
|
||||
public int getIndentLevel() {
|
||||
return indentLevel;
|
||||
}
|
||||
|
||||
public boolean isExpanded() {
|
||||
return isExpanded;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@return the name for this row. Typically this will be the field name, but it could be
|
||||
* a descriptive title for a group such as array range.}
|
||||
*/
|
||||
public abstract String getName();
|
||||
|
||||
/**
|
||||
* {@return the interpreted value for the data at this location.}
|
||||
*/
|
||||
public abstract String getValue();
|
||||
|
||||
/**
|
||||
* {@return the name of the datatype at this location.}
|
||||
*/
|
||||
public abstract String getDataType();
|
||||
|
||||
/**
|
||||
* {@return true if the row can produce child rows.}
|
||||
*/
|
||||
public abstract boolean isExpandable();
|
||||
|
||||
/**
|
||||
* {@return true if this location represents a pointer or has outgoing references}
|
||||
*/
|
||||
public boolean hasOutgoingReferences() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @{return the Data object associated with this row.}
|
||||
*/
|
||||
public abstract Data getData();
|
||||
|
||||
}
|
||||
+59
@@ -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 datagraph.data.graph.panel.model.row;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Cache for {@link DataRowObject}s. DataRowObjects are created as needed to conserve space. The
|
||||
* visible rows are kept in this cache to avoid having to recreate them on each paint call. It uses
|
||||
* a simple premise that paint calls will paint rows in order. So anytime a put occurs that is not
|
||||
* one more than the previous call, the assumption is that the view was scrolled, so the cache
|
||||
* is cleared and a new cache sequence is started.
|
||||
*/
|
||||
public class DataRowObjectCache {
|
||||
private static final int MAX_CACHE_SIZE = 300;
|
||||
List<DataRowObject> cachedRows = new ArrayList<>();
|
||||
int startIndex = 0;
|
||||
|
||||
public boolean contains(int rowIndex) {
|
||||
int cacheIndex = rowIndex - startIndex;
|
||||
return cacheIndex >= 0 && cacheIndex < cachedRows.size();
|
||||
}
|
||||
|
||||
public DataRowObject getDataRow(int rowIndex) {
|
||||
return cachedRows.get(rowIndex - startIndex);
|
||||
}
|
||||
|
||||
public void putData(int rowIndex, DataRowObject row) {
|
||||
// This cache expects data to be put in sequentially from some start row. The idea is
|
||||
// to cache the rows that are currently in the scrolled view. So anytime we are putting
|
||||
// in a row that is not the next expected row in sequence, throw away the cache and
|
||||
// start over.
|
||||
if (rowIndex != startIndex + cachedRows.size() || cachedRows.size() > MAX_CACHE_SIZE) {
|
||||
clear();
|
||||
startIndex = rowIndex;
|
||||
}
|
||||
cachedRows.add(row);
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
startIndex = 0;
|
||||
cachedRows.clear();
|
||||
}
|
||||
|
||||
}
|
||||
+141
@@ -0,0 +1,141 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package datagraph.data.graph.panel.model.row;
|
||||
|
||||
import docking.widgets.trable.AbstractGTrableRowModel;
|
||||
import docking.widgets.trable.GTrable;
|
||||
import ghidra.program.model.listing.Data;
|
||||
|
||||
/**
|
||||
* Row model for Data objects in {@link GTrable}. Most of the complexity is handled by
|
||||
* the {@link OpenDataChildren} object. If only the top most row is displaying (i.e. it is not
|
||||
* expanded), then the openChildren object is null. When row 0 is expanded, an OpenChildren is
|
||||
* created to manage the rows for the sub data child rows. This pattern is repeated inside the
|
||||
* OpenChilren node (open rows have corresponding OpenChildren objects to manage its child
|
||||
* rows.)
|
||||
*/
|
||||
public class DataTrableRowModel extends AbstractGTrableRowModel<DataRowObject> {
|
||||
private Data data;
|
||||
private OpenDataChildren openChildren;
|
||||
private DataRowObjectCache cache = new DataRowObjectCache();
|
||||
|
||||
public DataTrableRowModel(Data data) {
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public Data getData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
public void setData(Data data) {
|
||||
this.data = data;
|
||||
cache.clear();
|
||||
openChildren = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRowCount() {
|
||||
if (openChildren == null) {
|
||||
return 1;
|
||||
}
|
||||
return openChildren.getRowCount() + 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataRowObject getRow(int rowIndex) {
|
||||
if (cache.contains(rowIndex)) {
|
||||
return cache.getDataRow(rowIndex);
|
||||
}
|
||||
DataRowObject row = generateRow(rowIndex);
|
||||
cache.putData(rowIndex, row);
|
||||
return row;
|
||||
}
|
||||
|
||||
private DataRowObject generateRow(int rowIndex) {
|
||||
if (rowIndex == 0) {
|
||||
return new ComponentDataRowObject(0, data, openChildren != null);
|
||||
}
|
||||
return openChildren.getRow(rowIndex - 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isExpandable(int rowIndex) {
|
||||
DataRowObject dataRow = getRow(rowIndex);
|
||||
return dataRow != null && dataRow.isExpandable();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isExpanded(int rowIndex) {
|
||||
DataRowObject row = getRow(rowIndex);
|
||||
return row != null && row.isExpanded();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int collapseRow(int rowIndex) {
|
||||
cache.clear();
|
||||
if (rowIndex < 0 || rowIndex >= getRowCount()) {
|
||||
throw new IndexOutOfBoundsException();
|
||||
}
|
||||
if (rowIndex == 0) {
|
||||
if (openChildren == null) {
|
||||
return 0;
|
||||
}
|
||||
int diff = openChildren.getRowCount();
|
||||
openChildren = null;
|
||||
fireModelChanged();
|
||||
return diff;
|
||||
}
|
||||
int rowCountDiff = openChildren.collapseChild(rowIndex - 1);
|
||||
fireModelChanged();
|
||||
return rowCountDiff;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int expandRow(int rowIndex) {
|
||||
cache.clear();
|
||||
if (rowIndex < 0 || rowIndex >= getRowCount()) {
|
||||
throw new IndexOutOfBoundsException();
|
||||
}
|
||||
if (rowIndex == 0) {
|
||||
// are we already open?
|
||||
if (openChildren != null) {
|
||||
return 0;
|
||||
}
|
||||
openChildren = OpenDataChildren.createOpenDataNode(data, 0, 0, 1);
|
||||
fireModelChanged();
|
||||
return openChildren.getRowCount();
|
||||
}
|
||||
int diff = openChildren.expandChild(rowIndex - 1);
|
||||
fireModelChanged();
|
||||
return diff;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIndentLevel(int rowIndex) {
|
||||
DataRowObject row = getRow(rowIndex);
|
||||
return row.getIndentLevel();
|
||||
}
|
||||
|
||||
public void refresh() {
|
||||
cache.clear();
|
||||
if (openChildren != null) {
|
||||
if (!openChildren.refresh(data)) {
|
||||
openChildren = null;
|
||||
}
|
||||
}
|
||||
fireModelChanged();
|
||||
}
|
||||
}
|
||||
+469
@@ -0,0 +1,469 @@
|
||||
/* ###
|
||||
* 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 datagraph.data.graph.panel.model.row;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import ghidra.program.model.data.Array;
|
||||
import ghidra.program.model.listing.Data;
|
||||
|
||||
/**
|
||||
* Manages open rows in a DataTrableModel. Since this manages the open rows for its parent data
|
||||
* object, it has one row for each sub data component in its parent data. Of course, each of these
|
||||
* rows can also potentially be expandable. If any of the top level rows managed by this object are
|
||||
* expanded, it creates a OpenDataChildren object to manage its sub rows. The OpenChildrenObjects
|
||||
* are store in a list that is ordered by its row index.
|
||||
* <P>
|
||||
* To find out if a row is expanded or not, a binary search is used to find out if there is an
|
||||
* OpenDataChildren object for that row. If it is expanded, that object is used to recursively go
|
||||
* down to get the leaf row object.
|
||||
*/
|
||||
public abstract class OpenDataChildren implements Comparable<OpenDataChildren> {
|
||||
// when arrays are bigger than 100, we group them into chunks of 100 each. The size
|
||||
// should be a power of 10 and 100 seems like a good choice.
|
||||
private static final int ARRAY_GROUP_SIZE = 100;
|
||||
private int rowIndex; // row index relative to parent
|
||||
private int rowCount; // number of rows represented by this node, including any open children
|
||||
private int componentIndex;
|
||||
private int componentCount;
|
||||
protected int indentLevel;
|
||||
protected List<OpenDataChildren> openChildren;
|
||||
protected Data data;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param data the data object that this is managing rows for its child data components
|
||||
* @param rowIndex the row index is the overall row index of the first child element row
|
||||
* @param componentIndex the index of the the managed data component within its parent
|
||||
* @param componentCount the number of direct rows this managed component has. (It may have
|
||||
* indirect rows if its rows have child rows)
|
||||
* @param indentLevel the indent level for all direct rows managed by this object
|
||||
*/
|
||||
protected OpenDataChildren(Data data, int rowIndex, int componentIndex,
|
||||
int componentCount, int indentLevel) {
|
||||
this.data = data;
|
||||
this.rowIndex = rowIndex;
|
||||
this.componentCount = componentCount;
|
||||
this.rowCount = componentCount;
|
||||
this.componentIndex = componentIndex;
|
||||
this.indentLevel = indentLevel;
|
||||
openChildren = new ArrayList<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience static factory method for create the correct subtype of an OpenDataChildren
|
||||
* object. (Arrays require special handling.)
|
||||
* @param data the data object that is being expanded and this object will manage its child
|
||||
* data components.
|
||||
* @param rowIndex the overall row index of the first child row that is expanded
|
||||
* @param componentIndex the component index of the data within its parent that this object
|
||||
* is managing
|
||||
* @param indentLevel the indent level for all rows directly managed by this object
|
||||
* @return a new OpenDataChildren object
|
||||
*/
|
||||
public static OpenDataChildren createOpenDataNode(Data data, int rowIndex, int componentIndex,
|
||||
int indentLevel) {
|
||||
int numComponents = data.getNumComponents();
|
||||
if (data.getDataType() instanceof Array) {
|
||||
if (numComponents <= ARRAY_GROUP_SIZE) {
|
||||
return new ArrayElementsComponentNode(data, rowIndex, componentIndex, numComponents,
|
||||
0, indentLevel);
|
||||
}
|
||||
return new ArrayGroupComponentNode(data, rowIndex, componentIndex, 0, numComponents,
|
||||
indentLevel);
|
||||
}
|
||||
return new DataComponentNode(data, rowIndex, componentIndex, numComponents, indentLevel);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Private constructor used to create a object that can be used in a binary search
|
||||
* @param rowIndex the row to search for
|
||||
*/
|
||||
private OpenDataChildren(int rowIndex) {
|
||||
this.rowIndex = rowIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@return the total number of rows currently managed by this object (includes rows
|
||||
* recursively managed by its expanded children)}
|
||||
*/
|
||||
public int getRowCount() {
|
||||
return rowCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@return the row object for the given index.}
|
||||
* @param childRowIndex the index to get a row for. This index is relative to this
|
||||
* OpenDataChildren object and not the overall row index for the model.
|
||||
*/
|
||||
public DataRowObject getRow(int childRowIndex) {
|
||||
|
||||
OpenDataChildren node = findChildNodeAtOrBefore(childRowIndex);
|
||||
if (node == null) {
|
||||
return generateRow(childRowIndex, false);
|
||||
}
|
||||
|
||||
if (node.getRowIndex() == childRowIndex) {
|
||||
return generateRow(node.getComponentIndex(), true);
|
||||
}
|
||||
|
||||
int childIndex = childRowIndex - node.getRowIndex() - 1;
|
||||
if (childIndex < node.getRowCount()) {
|
||||
return node.getRow(childIndex);
|
||||
}
|
||||
|
||||
int childComponentIndex = node.getComponentIndex() + childRowIndex - node.getRowIndex() -
|
||||
node.getRowCount();
|
||||
return generateRow(childComponentIndex, false);
|
||||
}
|
||||
|
||||
protected int getComponentIndex() {
|
||||
return componentIndex;
|
||||
}
|
||||
|
||||
protected abstract DataRowObject generateRow(int childComponentIndex, boolean isOpen);
|
||||
|
||||
/**
|
||||
* Expands the sub child at the given relative row index
|
||||
* @param childRowIndex relative index to this OpenDataChildren object and not the overall
|
||||
* model row index.
|
||||
* @return the number of additional rows this caused to be added to the overall model
|
||||
*/
|
||||
public int expandChild(int childRowIndex) {
|
||||
OpenDataChildren node = findChildNodeAtOrBefore(childRowIndex);
|
||||
if (node == null) {
|
||||
return insertNode(childRowIndex, childRowIndex);
|
||||
}
|
||||
int indexPastNode = childRowIndex - node.getRowIndex();
|
||||
if (indexPastNode == 0) {
|
||||
return 0; // we are already open
|
||||
}
|
||||
if (indexPastNode <= node.getRowCount()) {
|
||||
int diff = node.expandChild(indexPastNode - 1);
|
||||
rebuildNodeIndex();
|
||||
return diff;
|
||||
}
|
||||
int childComponentIndex = node.getComponentIndex() + indexPastNode - node.rowCount;
|
||||
return insertNode(childRowIndex, childComponentIndex);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Collapse the child at the relative index.
|
||||
* @param childRowIndex the relative child index to collapse
|
||||
* @return the number of rows removed from the overall model
|
||||
*/
|
||||
public int collapseChild(int childRowIndex) {
|
||||
OpenDataChildren node = findChildNodeAtOrBefore(childRowIndex);
|
||||
|
||||
// the given index is not open, so do nothing
|
||||
if (node == null) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// compute the number of indexes past the open node we found
|
||||
int offsetIndex = childRowIndex - node.rowIndex;
|
||||
|
||||
// if we found a node at that index, just delete it since we only retain open nodes
|
||||
if (offsetIndex == 0) {
|
||||
openChildren.remove(node);
|
||||
rebuildNodeIndex();
|
||||
return node.getRowCount();
|
||||
}
|
||||
|
||||
// if the index is contained in the node, recurse down to close
|
||||
if (offsetIndex <= node.getRowCount()) {
|
||||
int diff = node.collapseChild(offsetIndex - 1);
|
||||
rebuildNodeIndex();
|
||||
return diff;
|
||||
}
|
||||
|
||||
// again, the given index is not open, so do nothing
|
||||
return 0;
|
||||
}
|
||||
|
||||
protected void rebuildNodeIndex() {
|
||||
rowCount = componentCount;
|
||||
|
||||
OpenDataChildren lastNode = null;
|
||||
for (OpenDataChildren node : openChildren) {
|
||||
if (lastNode == null) {
|
||||
node.rowIndex = node.componentIndex;
|
||||
}
|
||||
else {
|
||||
node.rowIndex = lastNode.rowIndex + node.componentIndex - lastNode.componentIndex +
|
||||
lastNode.rowCount;
|
||||
}
|
||||
rowCount += node.rowCount;
|
||||
lastNode = node;
|
||||
}
|
||||
}
|
||||
|
||||
protected int insertNode(int childRowIndex, int childComponentIndex) {
|
||||
OpenDataChildren newNode = generatedNode(childRowIndex, childComponentIndex);
|
||||
if (newNode == null) {
|
||||
return 0; // tried to open a node that can't be opened
|
||||
}
|
||||
int index = Collections.binarySearch(openChildren, newNode);
|
||||
|
||||
// It should never be positive since we searched and didn't find one at this index
|
||||
if (index >= 0) {
|
||||
return 0;
|
||||
}
|
||||
int insertionIndex = -index - 1;
|
||||
openChildren.add(insertionIndex, newNode);
|
||||
rebuildNodeIndex();
|
||||
return newNode.getRowCount();
|
||||
}
|
||||
|
||||
protected abstract OpenDataChildren generatedNode(int childRowIndex, int childComponentIndex);
|
||||
|
||||
/**
|
||||
* Returns the rowIndex of of this node relative to its parent.
|
||||
* @return the rowIndex of of this node relative to its parent
|
||||
*/
|
||||
int getRowIndex() {
|
||||
return rowIndex;
|
||||
}
|
||||
|
||||
void setRowIndex(int rowIndex) {
|
||||
this.rowIndex = rowIndex;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(OpenDataChildren o) {
|
||||
return getRowIndex() - o.getRowIndex();
|
||||
}
|
||||
|
||||
public boolean refresh(Data newData) {
|
||||
int newComponentCount = data.getNumComponents();
|
||||
|
||||
// if the data is different or it went to something not expandable (count == 0)
|
||||
// return false to indicate to parent this node needs to be removed
|
||||
if (data != newData || newComponentCount == 0) {
|
||||
openChildren.clear();
|
||||
return false;
|
||||
}
|
||||
|
||||
// if the number of components changes, this node can remain, but need to close any
|
||||
// children it has because it gets too complicated to correct
|
||||
if (newComponentCount != componentCount) {
|
||||
|
||||
openChildren.clear();
|
||||
componentCount = newComponentCount;
|
||||
rowCount = componentCount;
|
||||
return true;
|
||||
}
|
||||
|
||||
// recursively refresh open children, removing any that can't be refreshed
|
||||
Iterator<OpenDataChildren> it = openChildren.iterator();
|
||||
while (it.hasNext()) {
|
||||
OpenDataChildren child = it.next();
|
||||
if (!child.refresh(data.getComponent(child.componentIndex))) {
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
rebuildNodeIndex();
|
||||
return true;
|
||||
}
|
||||
|
||||
protected OpenDataChildren findChildNodeAtOrBefore(int childRowIndex) {
|
||||
if (openChildren.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
int index = Collections.binarySearch(openChildren, new SearchKeyNode(childRowIndex));
|
||||
if (index < 0) {
|
||||
index = -index - 2;
|
||||
}
|
||||
return index < 0 ? null : openChildren.get(index);
|
||||
}
|
||||
|
||||
private static class SearchKeyNode extends OpenDataChildren {
|
||||
SearchKeyNode(int rowIndex) {
|
||||
super(rowIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected DataRowObject generateRow(int childComponentIndex, boolean isOpen) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected OpenDataChildren generatedNode(int childRowIndex, int childComponentIndex) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static class DataComponentNode extends OpenDataChildren {
|
||||
|
||||
public DataComponentNode(Data data, int rowIndex, int componentIndex,
|
||||
int numComponents, int indentLevel) {
|
||||
super(data, rowIndex, componentIndex, numComponents, indentLevel);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected DataRowObject generateRow(int childComponentIndex, boolean isOpen) {
|
||||
Data component = data.getComponent(childComponentIndex);
|
||||
return new ComponentDataRowObject(indentLevel, component, isOpen);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected OpenDataChildren generatedNode(int childRowIndex, int childComponentIndex) {
|
||||
Data component = data.getComponent(childComponentIndex);
|
||||
return createOpenDataNode(component, childRowIndex, childComponentIndex,
|
||||
indentLevel + 1);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class ArrayElementsComponentNode extends OpenDataChildren {
|
||||
|
||||
private int arrayStartIndex;
|
||||
private int totalArraySize;
|
||||
|
||||
protected ArrayElementsComponentNode(Data data, int rowIndex, int componentIndex,
|
||||
int componentCount, int arrayStartIndex, int indentLevel) {
|
||||
super(data, rowIndex, componentIndex, componentCount, indentLevel);
|
||||
this.arrayStartIndex = arrayStartIndex;
|
||||
this.totalArraySize = data.getNumComponents();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected DataRowObject generateRow(int childComponentIndex, boolean isOpen) {
|
||||
Data component = data.getComponent(arrayStartIndex + childComponentIndex);
|
||||
return new ComponentDataRowObject(indentLevel + 1, component, isOpen);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected OpenDataChildren generatedNode(int childRowIndex, int childComponentIndex) {
|
||||
Data component = data.getComponent(arrayStartIndex + childComponentIndex);
|
||||
return createOpenDataNode(component, childRowIndex, childComponentIndex,
|
||||
indentLevel + 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean refresh(Data newData) {
|
||||
// NOTE: if this is a child of a array group node, this these check that can return
|
||||
// false can't happen as they have already been checked by the parent node. These
|
||||
// exist in case this is directly parented from a normal data node
|
||||
if (!(newData.getDataType() instanceof Array)) {
|
||||
return false;
|
||||
}
|
||||
int newTotalArraySize = data.getNumComponents();
|
||||
|
||||
// if the data is different or the array changed size, it needs to be removed
|
||||
if (data != newData || newTotalArraySize != totalArraySize) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// recursively refresh open children. Since the elements are all the same type, if
|
||||
// one can't refresh, then none can refresh
|
||||
for (OpenDataChildren child : openChildren) {
|
||||
Data component = data.getComponent(arrayStartIndex + child.getComponentIndex());
|
||||
if (!child.refresh(component)) {
|
||||
openChildren.clear();
|
||||
break;
|
||||
}
|
||||
}
|
||||
rebuildNodeIndex();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private static class ArrayGroupComponentNode extends OpenDataChildren {
|
||||
|
||||
private int arrayStartIndex;
|
||||
private int groupSize;
|
||||
private int arrayCount;
|
||||
private int totalArraySize;
|
||||
|
||||
public ArrayGroupComponentNode(Data data, int rowIndex, int componentIndex,
|
||||
int arrayStartIndex, int arrayCount, int indentLevel) {
|
||||
super(data, rowIndex, componentIndex, getGroupCount(arrayCount), indentLevel);
|
||||
this.arrayStartIndex = arrayStartIndex;
|
||||
this.arrayCount = arrayCount;
|
||||
this.groupSize = getGroupSize(arrayCount);
|
||||
this.totalArraySize = data.getNumComponents();
|
||||
}
|
||||
|
||||
private static int getGroupCount(int length) {
|
||||
int groupSize = ARRAY_GROUP_SIZE;
|
||||
int numGroups = (length + groupSize - 1) / groupSize;
|
||||
while (numGroups > ARRAY_GROUP_SIZE) {
|
||||
groupSize = groupSize * ARRAY_GROUP_SIZE;
|
||||
numGroups = (length + groupSize - 1) / groupSize;
|
||||
}
|
||||
return numGroups;
|
||||
}
|
||||
|
||||
private static int getGroupSize(int length) {
|
||||
int groupSize = ARRAY_GROUP_SIZE;
|
||||
int numGroups = (length + groupSize - 1) / groupSize;
|
||||
while (numGroups > ARRAY_GROUP_SIZE) {
|
||||
groupSize = groupSize * ARRAY_GROUP_SIZE;
|
||||
numGroups = (length + groupSize - 1) / groupSize;
|
||||
}
|
||||
return groupSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected DataRowObject generateRow(int childComponentIndex, boolean isOpen) {
|
||||
int subArrayStartIndex = arrayStartIndex + childComponentIndex * groupSize;
|
||||
int length = Math.min(groupSize, arrayCount - (childComponentIndex * groupSize));
|
||||
return new ArrayGroupDataRowObject(data, subArrayStartIndex, length, indentLevel + 1,
|
||||
isOpen);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected OpenDataChildren generatedNode(int childRowIndex, int childComponentIndex) {
|
||||
int arrayOffsetFromStart = childComponentIndex * groupSize;
|
||||
int subArrayStartIndex = arrayStartIndex + arrayOffsetFromStart;
|
||||
int length = Math.min(groupSize, arrayCount - arrayOffsetFromStart);
|
||||
if (groupSize == ARRAY_GROUP_SIZE) {
|
||||
return new ArrayElementsComponentNode(data, childRowIndex, childComponentIndex,
|
||||
length, subArrayStartIndex, indentLevel + 1);
|
||||
}
|
||||
return new ArrayGroupComponentNode(data, childRowIndex, childComponentIndex,
|
||||
subArrayStartIndex, length, indentLevel + 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean refresh(Data newData) {
|
||||
if (!(newData.getDataType() instanceof Array)) {
|
||||
return false;
|
||||
}
|
||||
int newTotalArraySize = data.getNumComponents();
|
||||
|
||||
// if the data is different or the array changed size,
|
||||
// it needs to be removed
|
||||
if (data != newData || newTotalArraySize != totalArraySize) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// recursively refresh open children. Since the elements are all the same type, if
|
||||
// one can't refresh, then none can refresh so clear all open children
|
||||
for (OpenDataChildren child : openChildren) {
|
||||
if (!child.refresh(data)) {
|
||||
openChildren.clear();
|
||||
break;
|
||||
}
|
||||
}
|
||||
rebuildNodeIndex();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
+143
@@ -0,0 +1,143 @@
|
||||
/* ###
|
||||
* 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 datagraph.graph.explore;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import ghidra.graph.graphs.DefaultVisualGraph;
|
||||
import ghidra.graph.viewer.layout.VisualGraphLayout;
|
||||
|
||||
/**
|
||||
* Base graph for exploration graphs. An exploration graph is a graph that typically starts with
|
||||
* just one vertex, but then is interactively expanded with additional vertices by exploring
|
||||
* incoming and outgoing links.
|
||||
*
|
||||
* @param <V> the vertex type
|
||||
* @param <E> the edge type
|
||||
*/
|
||||
public abstract class AbstractExplorationGraph<V extends EgVertex, E extends EgEdge<V>>
|
||||
extends DefaultVisualGraph<V, E> {
|
||||
|
||||
private V root;
|
||||
private EgGraphLayout<V, E> layout;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param root the initial vertex in the graph. All nodes in the graph must be connected directly
|
||||
* or indirectly to this vertex.
|
||||
*/
|
||||
public AbstractExplorationGraph(V root) {
|
||||
this.root = root;
|
||||
addVertex(root);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@return the original source vertex for this graph.}
|
||||
*/
|
||||
public V getRoot() {
|
||||
return root;
|
||||
}
|
||||
|
||||
public void setLayout(EgGraphLayout<V, E> layout) {
|
||||
this.layout = layout;
|
||||
}
|
||||
|
||||
@Override
|
||||
public VisualGraphLayout<V, E> getLayout() {
|
||||
return layout;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@return a set of all vertices that can trace a source path back to the given vertex. In
|
||||
* other words either getSource() or getSource().getSource(), and so on, on the given vertex.}
|
||||
* @param source the vertex to see if a vertex is a descendant from.
|
||||
*/
|
||||
public Set<V> getDescendants(V source) {
|
||||
Set<V> descendents = new HashSet<>();
|
||||
getDescendants(source, descendents);
|
||||
return descendents;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the root of this graph to the new root.
|
||||
* @param newRoot the new source root for the graph
|
||||
*/
|
||||
public void setRoot(V newRoot) {
|
||||
|
||||
// First clear out the source from each vertex to so they can be reassigned as we
|
||||
// explore from the new root. We will use a null source to indicate the vertex hasn't been
|
||||
// processed.
|
||||
getVertices().forEach(v -> v.setSource(null));
|
||||
|
||||
// temporarily set the root source to mark it as processed
|
||||
newRoot.setSource(newRoot);
|
||||
|
||||
// create a queue of vertices to process and prime with the new root
|
||||
Queue<V> vertexQueue = new LinkedList<>();
|
||||
vertexQueue.add(newRoot);
|
||||
|
||||
// follow edges assigning new source vertices
|
||||
assignSource(vertexQueue);
|
||||
|
||||
// set the root source to null to indicate it is the root source vertex.
|
||||
newRoot.setSource(null);
|
||||
|
||||
this.root = newRoot;
|
||||
}
|
||||
|
||||
private void getDescendants(V source, Set<V> descendents) {
|
||||
for (E e : getOutEdges(source)) {
|
||||
V end = e.getEnd();
|
||||
if (source.equals(end.source)) {
|
||||
descendents.add(end);
|
||||
getDescendants(end, descendents);
|
||||
}
|
||||
}
|
||||
for (E e : getInEdges(source)) {
|
||||
V start = e.getStart();
|
||||
if (source.equals(start.source)) {
|
||||
descendents.add(start);
|
||||
getDescendants(start, descendents);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void assignSource(Queue<V> vertexQueue) {
|
||||
while (!vertexQueue.isEmpty()) {
|
||||
V remove = vertexQueue.remove();
|
||||
processEdges(remove, vertexQueue);
|
||||
}
|
||||
}
|
||||
|
||||
private void processEdges(V v, Queue<V> vertexQueue) {
|
||||
Collection<E> outEdges = getOutEdges(v);
|
||||
for (EgEdge<V> edge : outEdges) {
|
||||
V next = edge.getEnd();
|
||||
if (next.getSourceVertex() == null) {
|
||||
next.setSource(v);
|
||||
vertexQueue.add(next);
|
||||
}
|
||||
}
|
||||
Collection<E> inEdges = getInEdges(v);
|
||||
for (E e : inEdges) {
|
||||
V previous = e.getStart();
|
||||
if (previous.getSourceVertex() == null) {
|
||||
previous.setSource(v);
|
||||
vertexQueue.add(previous);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
/* ###
|
||||
* 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 datagraph.graph.explore;
|
||||
|
||||
import ghidra.graph.viewer.edge.AbstractVisualEdge;
|
||||
|
||||
/**
|
||||
* An edge for the {@link AbstractExplorationGraph}
|
||||
* @param <V> The vertex type
|
||||
*/
|
||||
public abstract class EgEdge<V extends EgVertex> extends AbstractVisualEdge<V> {
|
||||
|
||||
public EgEdge(V start, V end) {
|
||||
super(start, end);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package datagraph.graph.explore;
|
||||
|
||||
import java.awt.Shape;
|
||||
import java.awt.geom.AffineTransform;
|
||||
|
||||
import com.google.common.base.Function;
|
||||
|
||||
import edu.uci.ics.jung.graph.Graph;
|
||||
import edu.uci.ics.jung.visualization.RenderContext;
|
||||
import ghidra.graph.viewer.VisualEdge;
|
||||
import ghidra.graph.viewer.edge.VisualEdgeRenderer;
|
||||
|
||||
/**
|
||||
* Edge renderer for {@link AbstractExplorationGraph}s. Using information from the vertices to
|
||||
* vertically align incoming and outgoing edges with the corresponding inner pieces in the
|
||||
* vertex's display component.
|
||||
*
|
||||
* @param <V> the vertex type
|
||||
* @param <E> the edge type
|
||||
*/
|
||||
public class EgEdgeRenderer<V extends EgVertex, E extends VisualEdge<V>>
|
||||
extends VisualEdgeRenderer<V, E> {
|
||||
|
||||
@Override
|
||||
public Shape getEdgeShape(RenderContext<V, E> rc, Graph<V, E> graph, E e, float x1, float y1,
|
||||
float x2, float y2, boolean isLoop, Shape vertexShape) {
|
||||
Function<? super E, Shape> edgeXform = rc.getEdgeShapeTransformer();
|
||||
Shape shape = edgeXform.apply(e);
|
||||
AffineTransform xform = AffineTransform.getTranslateInstance(x1, y1);
|
||||
|
||||
// apply the transformations; converting the given shape from model space into graph space
|
||||
return xform.createTransformedShape(shape);
|
||||
}
|
||||
}
|
||||
+88
@@ -0,0 +1,88 @@
|
||||
/* ###
|
||||
* 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 datagraph.graph.explore;
|
||||
|
||||
import java.awt.Dimension;
|
||||
import java.awt.Shape;
|
||||
import java.awt.geom.*;
|
||||
|
||||
import com.google.common.base.Function;
|
||||
|
||||
import ghidra.graph.viewer.GraphViewerUtils;
|
||||
import ghidra.graph.viewer.VisualEdge;
|
||||
|
||||
/**
|
||||
* An edge shape that draws edges from left side of source vertex to the right side of the
|
||||
* destination vertex. The vertical position on edges of the source vertex and destination is
|
||||
* determined by the calls to the vertex so that edges can be aligned with a vertex's internals.
|
||||
*
|
||||
* @param <V> the vertex type
|
||||
* @param <E> the edge type
|
||||
*/
|
||||
public class EgEdgeTransformer<V extends EgVertex, E extends VisualEdge<V>>
|
||||
implements Function<E, Shape> {
|
||||
private static final double OVERLAP_GAP = 20;
|
||||
private static int LOOP_SIZE = 12;
|
||||
|
||||
/**
|
||||
* Get the shape for this edge
|
||||
*
|
||||
* @param e the edge
|
||||
* @return the edge shape
|
||||
*/
|
||||
@Override
|
||||
public Shape apply(E e) {
|
||||
V start = e.getStart();
|
||||
V end = e.getEnd();
|
||||
Dimension startSize = start.getComponent().getSize();
|
||||
|
||||
Point2D location = start.getLocation();
|
||||
double originX = location.getX();
|
||||
double originY = location.getY();
|
||||
|
||||
Point2D startPoint = start.getStartingEdgePoint(end);
|
||||
Point2D endPoint = end.getEndingEdgePoint(start);
|
||||
|
||||
boolean isLoop = start.equals(end);
|
||||
|
||||
if (isLoop) {
|
||||
Shape hollowEgdeLoop = GraphViewerUtils.createHollowEgdeLoop();
|
||||
AffineTransform xform =
|
||||
AffineTransform.getTranslateInstance(startSize.width / 2 + LOOP_SIZE / 2,
|
||||
start.getOutgoingEdgeOffsetFromCenter(end));
|
||||
|
||||
xform.scale(LOOP_SIZE, LOOP_SIZE);
|
||||
return xform.createTransformedShape(hollowEgdeLoop);
|
||||
}
|
||||
GeneralPath path = new GeneralPath();
|
||||
|
||||
path.moveTo(startPoint.getX() - originX, startPoint.getY() - originY);
|
||||
if (startPoint.getX() > endPoint.getX() - OVERLAP_GAP) {
|
||||
if (start.getLocation().getX() != startPoint.getX()) {
|
||||
path.lineTo(startPoint.getX() - originX + OVERLAP_GAP, startPoint.getY() - originY);
|
||||
}
|
||||
if (end.getLocation().getX() != endPoint.getX()) {
|
||||
path.lineTo(endPoint.getX() - originX - OVERLAP_GAP, endPoint.getY() - originY);
|
||||
}
|
||||
}
|
||||
|
||||
path.lineTo(endPoint.getX() - originX, endPoint.getY() - originY);
|
||||
|
||||
return path;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,268 @@
|
||||
/* ###
|
||||
* 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 datagraph.graph.explore;
|
||||
|
||||
import java.awt.Rectangle;
|
||||
import java.awt.Shape;
|
||||
import java.awt.geom.Point2D;
|
||||
import java.util.*;
|
||||
import java.util.function.Function;
|
||||
|
||||
import javax.help.UnsupportedOperationException;
|
||||
|
||||
import edu.uci.ics.jung.visualization.RenderContext;
|
||||
import edu.uci.ics.jung.visualization.renderers.BasicEdgeRenderer;
|
||||
import ghidra.graph.VisualGraph;
|
||||
import ghidra.graph.viewer.layout.*;
|
||||
import ghidra.graph.viewer.layout.LayoutListener.ChangeType;
|
||||
import ghidra.graph.viewer.vertex.VisualGraphVertexShapeTransformer;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
/**
|
||||
* A custom layout to arrange the vertices of an {@link AbstractExplorationGraph} using a tree
|
||||
* structure that reflects the exploration order of the vertices. The basic algorithm is that
|
||||
* an original vertex is the root vertex and all other vertices are displayed in a double tree
|
||||
* structure that show a tree of outgoing edges to the right and a tree of incoming edges
|
||||
* to the left. The immediate vertices at each level are simply shown in a vertical column.
|
||||
* <P>
|
||||
* However, the tricky concept is that each vertex can then have edges that go back. For example,
|
||||
* if the original vertex has three outgoing vertices, but then those
|
||||
* vertices can spawn other incoming vertices. In this case, the vertex is pushed further out to
|
||||
* make room for its spawned incoming vertices. This is done recursively, where each child
|
||||
* vertex's sub tree is computed and then all the child subtrees are stacked in a column.
|
||||
*
|
||||
* @param <V> the vertex type
|
||||
* @param <E> the edge type
|
||||
*/
|
||||
public abstract class EgGraphLayout<V extends EgVertex, E extends EgEdge<V>>
|
||||
extends AbstractVisualGraphLayout<V, E> {
|
||||
|
||||
private EgEdgeRenderer<V, E> edgeRenderer = new EgEdgeRenderer<>();
|
||||
private EgEdgeTransformer<V, E> edgeTransformer;
|
||||
private Function<V, Shape> vertexShapeTransformer = new VisualGraphVertexShapeTransformer<V>();
|
||||
protected int verticalGap;
|
||||
protected int horizontalGap;
|
||||
|
||||
protected EgGraphLayout(AbstractExplorationGraph<V, E> graph, String name, int verticalGap,
|
||||
int horizontalGap) {
|
||||
super(graph, name);
|
||||
this.verticalGap = verticalGap;
|
||||
this.horizontalGap = horizontalGap;
|
||||
this.edgeTransformer = createEdgeTransformer();
|
||||
}
|
||||
|
||||
protected abstract EgEdgeTransformer<V, E> createEdgeTransformer();
|
||||
|
||||
protected abstract Comparator<V> getIncommingVertexComparator();
|
||||
|
||||
protected abstract Comparator<V> getOutgoingVertexComparator();
|
||||
|
||||
@Override
|
||||
public Point2D apply(V v) {
|
||||
if (v.hasUserChangedLocation()) {
|
||||
return v.getLocation();
|
||||
}
|
||||
return super.apply(v);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public VisualGraph<V, E> getVisualGraph() {
|
||||
return (VisualGraph<V, E>) getGraph();
|
||||
}
|
||||
|
||||
@Override
|
||||
public BasicEdgeRenderer<V, E> getEdgeRenderer() {
|
||||
return edgeRenderer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public com.google.common.base.Function<E, Shape> getEdgeShapeTransformer(
|
||||
RenderContext<V, E> context) {
|
||||
|
||||
return edgeTransformer;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected LayoutPositions<V, E> doCalculateLocations(VisualGraph<V, E> g,
|
||||
TaskMonitor taskMonitor) {
|
||||
if (!(g instanceof AbstractExplorationGraph<V, E> layeredGraph)) {
|
||||
throw new IllegalArgumentException("This layout only supports Layered graphs!");
|
||||
}
|
||||
|
||||
try {
|
||||
monitor = taskMonitor;
|
||||
return computePositions(layeredGraph);
|
||||
}
|
||||
finally {
|
||||
monitor = TaskMonitor.DUMMY;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private LayoutPositions<V, E> computePositions(AbstractExplorationGraph<V, E> g) {
|
||||
GraphLocationMap<V> locationMap = getLocationMap(g, g.getRoot());
|
||||
Map<V, Point2D> vertexLocations = locationMap.getVertexLocations();
|
||||
return LayoutPositions.createNewPositions(vertexLocations,
|
||||
Collections.emptyMap());
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected GridLocationMap<V, E> performInitialGridLayout(VisualGraph<V, E> g) {
|
||||
// we override the method that calls this abstract method, so it isn't used.
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
private GraphLocationMap<V> getLocationMap(AbstractExplorationGraph<V, E> g, V v) {
|
||||
List<GraphLocationMap<V>> leftMaps = getMapsForIncommingEdges(g, v);
|
||||
List<GraphLocationMap<V>> rightMaps = getMapsForOutgoingEdges(g, v);
|
||||
|
||||
Shape shape = vertexShapeTransformer.apply(v);
|
||||
Rectangle bounds = shape.getBounds();
|
||||
GraphLocationMap<V> baseMap = new GraphLocationMap<>(v, bounds.width, bounds.height);
|
||||
|
||||
if (leftMaps != null) {
|
||||
mergeLeftMaps(baseMap, leftMaps);
|
||||
}
|
||||
if (rightMaps != null) {
|
||||
mergeRightMaps(baseMap, rightMaps);
|
||||
}
|
||||
return baseMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Merges all the incoming vertex sub-tree maps in a column to the left of the base map. Since
|
||||
* these maps will all be to the left of their parent vertex base map, we align them in the
|
||||
* column such that their right map edge boundaries align.
|
||||
* @param baseMap the map for the parent vertex
|
||||
* @param leftMaps the list of maps to be organized in a column to the left of the base map.
|
||||
*/
|
||||
private void mergeLeftMaps(GraphLocationMap<V> baseMap, List<GraphLocationMap<V>> leftMaps) {
|
||||
|
||||
int shiftY = getTopGroupShift(leftMaps, verticalGap);
|
||||
int baseShiftX = baseMap.getMinX() - horizontalGap;
|
||||
for (GraphLocationMap<V> map : leftMaps) {
|
||||
shiftY += map.getHeight() / 2;
|
||||
int shiftX = baseShiftX - map.getMaxX();
|
||||
baseMap.merge(map, shiftX, shiftY);
|
||||
shiftY += map.getHeight() / 2 + verticalGap;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Merges all the outgoing vertex sub-tree maps in a column to the right of the base map. Since
|
||||
* these maps will all be to the right of their parent vertex base map, we align them in the
|
||||
* column such that their left map edge boundaries align.
|
||||
* @param baseMap the map for the parent vertex
|
||||
* @param rightMaps the list of maps to be organized in a column to the right of the base map.
|
||||
*/
|
||||
private void mergeRightMaps(GraphLocationMap<V> baseMap, List<GraphLocationMap<V>> rightMaps) {
|
||||
int shiftY = getTopGroupShift(rightMaps, verticalGap);
|
||||
int baseShiftX = baseMap.getMaxX() + horizontalGap;
|
||||
for (GraphLocationMap<V> map : rightMaps) {
|
||||
shiftY += map.getHeight() / 2;
|
||||
int shiftX = baseShiftX - map.getMinX();
|
||||
baseMap.merge(map, shiftX, shiftY);
|
||||
shiftY += map.getHeight() / 2 + verticalGap;
|
||||
}
|
||||
}
|
||||
|
||||
private int getTopGroupShift(List<GraphLocationMap<V>> maps, int gap) {
|
||||
int totalHeight = 0;
|
||||
for (GraphLocationMap<V> map : maps) {
|
||||
totalHeight += map.getHeight();
|
||||
}
|
||||
totalHeight += (maps.size() - 1) * gap;
|
||||
return -totalHeight / 2;
|
||||
}
|
||||
|
||||
private List<GraphLocationMap<V>> getMapsForOutgoingEdges(AbstractExplorationGraph<V, E> g,
|
||||
V v) {
|
||||
List<E> edges = getOutgoingNextLayerEdges(g, v);
|
||||
if (edges == null || edges.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return getOutgoingGraphMaps(g, edges);
|
||||
}
|
||||
|
||||
private List<GraphLocationMap<V>> getMapsForIncommingEdges(AbstractExplorationGraph<V, E> g,
|
||||
V v) {
|
||||
List<E> edges = getIncommingNextLayerEdges(g, v);
|
||||
if (edges == null || edges.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return getIncomingGraphMaps(g, edges);
|
||||
}
|
||||
|
||||
private List<GraphLocationMap<V>> getOutgoingGraphMaps(AbstractExplorationGraph<V, E> g,
|
||||
List<E> edges) {
|
||||
List<GraphLocationMap<V>> maps = new ArrayList<>(edges.size());
|
||||
for (E e : edges) {
|
||||
maps.add(getLocationMap(g, e.getEnd()));
|
||||
}
|
||||
return maps;
|
||||
}
|
||||
|
||||
private List<GraphLocationMap<V>> getIncomingGraphMaps(AbstractExplorationGraph<V, E> g,
|
||||
List<E> edges) {
|
||||
List<GraphLocationMap<V>> maps = new ArrayList<>(edges.size());
|
||||
for (E e : edges) {
|
||||
maps.add(getLocationMap(g, e.getStart()));
|
||||
}
|
||||
return maps;
|
||||
}
|
||||
|
||||
private List<E> getOutgoingNextLayerEdges(AbstractExplorationGraph<V, E> g, V v) {
|
||||
Collection<E> outEdges = g.getOutEdges(v);
|
||||
if (outEdges == null || outEdges.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
List<E> nextLayerEdges = new ArrayList<>();
|
||||
for (E e : outEdges) {
|
||||
if (v.equals(e.getEnd().getSourceVertex())) {
|
||||
nextLayerEdges.add(e);
|
||||
}
|
||||
}
|
||||
Comparator<V> c = getOutgoingVertexComparator();
|
||||
nextLayerEdges.sort((e1, e2) -> c.compare(e1.getEnd(), e2.getEnd()));
|
||||
return nextLayerEdges;
|
||||
}
|
||||
|
||||
private List<E> getIncommingNextLayerEdges(AbstractExplorationGraph<V, E> g, V v) {
|
||||
Collection<E> inEdges = g.getInEdges(v);
|
||||
if (inEdges == null || inEdges.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
List<E> nextLayerEdges = new ArrayList<>();
|
||||
for (E e : inEdges) {
|
||||
if (v.equals(e.getStart().getSourceVertex())) {
|
||||
nextLayerEdges.add(e);
|
||||
}
|
||||
}
|
||||
Comparator<V> c = getIncommingVertexComparator();
|
||||
nextLayerEdges.sort((e1, e2) -> c.compare(e1.getStart(), e2.getStart()));
|
||||
return nextLayerEdges;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void fireVertexLocationChanged(V v, Point2D p, ChangeType type) {
|
||||
if (type == ChangeType.USER) {
|
||||
v.setUserChangedLocation(new Point2D.Double(p.getX(), p.getY()));
|
||||
}
|
||||
super.fireVertexLocationChanged(v, p, type);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
/* ###
|
||||
* 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 datagraph.graph.explore;
|
||||
|
||||
import java.awt.geom.Point2D;
|
||||
|
||||
import ghidra.graph.viewer.vertex.AbstractVisualVertex;
|
||||
|
||||
/**
|
||||
* A type of VisualVertex that is part of a exploration graph where each vertex (except the root
|
||||
* vertex) has a concept of a source vertex that indicates what vertex's edge was first used to add
|
||||
* this vertex to the graph. Any vertex in the graph can follow the source vertex and it's source
|
||||
* vertex all the way back to the root source vertex. This means that any graph consisting of
|
||||
* {@link EgVertex}s, regardless of its full set of edges, has a natural representation as a tree
|
||||
* which is useful for creating a layout that reflects how the tree was generated from a single
|
||||
* source seed vertex.
|
||||
*/
|
||||
public abstract class EgVertex extends AbstractVisualVertex {
|
||||
protected EgVertex source;
|
||||
private boolean userChangedLocation = false;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param source the vertex from which this vertex was added by following an edge
|
||||
*/
|
||||
public EgVertex(EgVertex source) {
|
||||
this.source = source;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@return the vertex that was used to discover this vertex.}
|
||||
*/
|
||||
public EgVertex getSourceVertex() {
|
||||
return source;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
// subclasses can override this if they need to perform cleanup
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLocation(Point2D location) {
|
||||
if (userChangedLocation) {
|
||||
return;
|
||||
}
|
||||
super.setLocation(location);
|
||||
}
|
||||
|
||||
/**
|
||||
* Manually sets the location for this vertex.
|
||||
* @param location the location for the vertex
|
||||
*/
|
||||
public void setUserChangedLocation(Point2D location) {
|
||||
userChangedLocation = true;
|
||||
super.setLocation(location);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the user changed location lock, allowing the vertex to get a new location when the
|
||||
* graph is laid out.
|
||||
*/
|
||||
public void clearUserChangedLocation() {
|
||||
userChangedLocation = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@return true if the user manually set the location. A manually set location ignores
|
||||
* locations generated by the layout.}
|
||||
*/
|
||||
public boolean hasUserChangedLocation() {
|
||||
return userChangedLocation;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@return true if this is the root node for the exploration graph.}
|
||||
*/
|
||||
public boolean isRoot() {
|
||||
return source == null;
|
||||
}
|
||||
|
||||
protected void setSource(EgVertex source) {
|
||||
this.source = source;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@return the point to use for the first point in an edge going to the given end vertex.}
|
||||
* @param end the destination vertex for the edge
|
||||
*/
|
||||
protected abstract Point2D getStartingEdgePoint(EgVertex end);
|
||||
|
||||
/**
|
||||
* {@return the point to use for the last point in an edge coming from the given start vertex.}
|
||||
* @param start the starting vertex for the edge
|
||||
*/
|
||||
protected abstract Point2D getEndingEdgePoint(EgVertex start);
|
||||
|
||||
/**
|
||||
* {@return the outgoing offset of the edge from the center of the vertex for the edge ending
|
||||
* at the given end vertex.}
|
||||
* @param end the destination vertex of the edge to get an outgoing edge offset for
|
||||
*/
|
||||
protected int getOutgoingEdgeOffsetFromCenter(EgVertex end) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@return the incoming offset of the edge from the center of the vertex from the edge starting
|
||||
* at the given start vertex.}
|
||||
* @param start the starting vertex of the edge to get an incoming edge offset for
|
||||
*/
|
||||
protected int getIncomingEdgeOffsetFromCenter(EgVertex start) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
||||
+130
@@ -0,0 +1,130 @@
|
||||
/* ###
|
||||
* 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 datagraph.graph.explore;
|
||||
|
||||
import java.awt.Point;
|
||||
import java.awt.geom.Point2D;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Map of vertex locations in layout space and the boundaries for the space this sub graph
|
||||
* occupies.
|
||||
*
|
||||
* @param <V> the vertex type
|
||||
*/
|
||||
public class GraphLocationMap<V> {
|
||||
private Map<V, Point> vertexPoints = new HashMap<>();
|
||||
private int minX;
|
||||
private int maxX;
|
||||
private int minY;
|
||||
private int maxY;
|
||||
|
||||
/**
|
||||
* Constructs a map with exactly one vertex which is located at P(0,0). The size of the vertex
|
||||
* determines the map's bounds.
|
||||
* @param vertex the initial vertex
|
||||
* @param width the width of the the verte.
|
||||
* @param height the height of the vertex
|
||||
*/
|
||||
public GraphLocationMap(V vertex, int width, int height) {
|
||||
vertexPoints.put(vertex, new Point(0, 0));
|
||||
this.maxX = (width + 1) / 2; // add 1 so that odd sizes have the extra size to the right
|
||||
this.minX = -width / 2;
|
||||
this.maxY = (height + 1) / 2; // add 1 so that odd sizes have the extra size to the bottom
|
||||
this.minY = -height / 2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Merges another {@link GraphLocationMap} into this one shifting its bounds and bounds by the
|
||||
* given shift values. When this completes, the other map is disposed.
|
||||
* @param other the other GridLocationMap to merge into this one
|
||||
* @param xShift the amount to shift the other map horizontally before merging.
|
||||
* @param yShift the amount to shift the other map vertically before merging.
|
||||
*/
|
||||
public void merge(GraphLocationMap<V> other, int xShift, int yShift) {
|
||||
other.shift(xShift, yShift);
|
||||
vertexPoints.putAll(other.vertexPoints);
|
||||
|
||||
this.minX = Math.min(minX, other.minX);
|
||||
this.maxX = Math.max(maxX, other.maxX);
|
||||
this.minY = Math.min(minY, other.minY);
|
||||
this.maxY = Math.max(maxY, other.maxY);
|
||||
other.dispose();
|
||||
}
|
||||
|
||||
private void dispose() {
|
||||
vertexPoints.clear();
|
||||
}
|
||||
|
||||
private void shift(int xShift, int yShift) {
|
||||
Collection<Point> values = vertexPoints.values();
|
||||
for (Point point : values) {
|
||||
point.x += xShift;
|
||||
point.y += yShift;
|
||||
}
|
||||
maxX += xShift;
|
||||
minX += xShift;
|
||||
maxY += yShift;
|
||||
minY += yShift;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@return the width of this map. This includes space for the size of vertices and not just
|
||||
* their center points.}
|
||||
*/
|
||||
public int getWidth() {
|
||||
return maxX - minX;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@return the height of this map. This includes space for the size of vertices and not just
|
||||
* their center points.}
|
||||
*/
|
||||
public int getHeight() {
|
||||
return maxY - minY;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@return the location for the given vertex.}
|
||||
* @param v the vertex to get a location for
|
||||
*/
|
||||
public Point get(V v) {
|
||||
return vertexPoints.get(v);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@return a map of the vertices and their locations.}
|
||||
*/
|
||||
public Map<V, Point2D> getVertexLocations() {
|
||||
Map<V, Point2D> points = new HashMap<>();
|
||||
points.putAll(vertexPoints);
|
||||
return points;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@return the minimum x coordinate of the graph.}
|
||||
*/
|
||||
public int getMinX() {
|
||||
return minX;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@return the maximum x coordinate of the graph.}
|
||||
*/
|
||||
public int getMaxX() {
|
||||
return maxX;
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 961 B |
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,304 @@
|
||||
/* ###
|
||||
* 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 datagraph.graph;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.awt.Dimension;
|
||||
import java.awt.Point;
|
||||
import java.awt.geom.Point2D;
|
||||
import java.util.Collection;
|
||||
import java.util.Comparator;
|
||||
|
||||
import javax.swing.*;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import datagraph.graph.explore.*;
|
||||
import docking.test.AbstractDockingTest;
|
||||
import docking.widgets.label.GDLabel;
|
||||
import generic.theme.GThemeDefaults.Colors.Palette;
|
||||
import ghidra.graph.VisualGraph;
|
||||
import ghidra.graph.graphs.DefaultVisualGraph;
|
||||
import ghidra.graph.viewer.GraphComponent;
|
||||
import ghidra.graph.viewer.layout.AbstractVisualGraphLayout;
|
||||
import ghidra.util.Swing;
|
||||
|
||||
public class EgGraphLayoutTest extends AbstractDockingTest {
|
||||
private static int VERTEX_SIZE = 50;
|
||||
private static int VERTEX_GAP = 100;
|
||||
private TestExplorationGraph g;
|
||||
|
||||
private TestVertex root = new TestVertex(null, "R", VERTEX_SIZE, VERTEX_SIZE);
|
||||
private GraphComponent<TestVertex, TestEdge, TestExplorationGraph> graphComponent;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
g = new TestExplorationGraph(root);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGraphOneOutgoingChild() {
|
||||
TestVertex A = v(root, "A");
|
||||
edge(root, A);
|
||||
|
||||
showGraph();
|
||||
|
||||
assertEquals(p(0, 0), root.getLocation());
|
||||
int expectedX = VERTEX_GAP + root.width / 2 + A.width / 2;
|
||||
assertEquals(p(expectedX, 0), A.getLocation());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGraphTwoOutgoingChildren() {
|
||||
TestVertex A = v(root, "A");
|
||||
TestVertex B = v(root, "B");
|
||||
edge(root, A);
|
||||
edge(root, B);
|
||||
|
||||
showGraph();
|
||||
|
||||
assertEquals(p(0, 0), root.getLocation());
|
||||
int expectedX = VERTEX_GAP + root.width / 2 + A.width / 2;
|
||||
int totalHeight = VERTEX_GAP + A.height + B.height;
|
||||
int expectedYa = -totalHeight / 2 + A.height / 2;
|
||||
int expectedYb = totalHeight / 2 - B.height / 2;
|
||||
assertEquals(p(expectedX, expectedYa), A.getLocation());
|
||||
assertEquals(p(expectedX, expectedYb), B.getLocation());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGraphOneIncomingChild() {
|
||||
TestVertex A = v(root, "A", VERTEX_SIZE, VERTEX_SIZE);
|
||||
edge(A, root);
|
||||
|
||||
showGraph();
|
||||
|
||||
assertEquals(p(0, 0), root.getLocation());
|
||||
int expectedX = -VERTEX_GAP - root.width / 2 - A.width / 2;
|
||||
assertEquals(p(expectedX, 0), A.getLocation());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGraphTwoIncomingChildren() {
|
||||
TestVertex A = v(root, "A");
|
||||
TestVertex B = v(root, "B");
|
||||
edge(A, root);
|
||||
edge(B, root);
|
||||
|
||||
showGraph();
|
||||
|
||||
assertEquals(p(0, 0), root.getLocation());
|
||||
int expectedX = -VERTEX_GAP - root.width / 2 - A.width / 2;
|
||||
int expectedYa = -VERTEX_GAP / 2 - A.height / 2;
|
||||
int expectedYb = VERTEX_GAP / 2 + B.height / 2;
|
||||
assertEquals(p(expectedX, expectedYa), A.getLocation());
|
||||
assertEquals(p(expectedX, expectedYb), B.getLocation());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGraphTwoOutgoingChildrenDifferentSize() {
|
||||
TestVertex A = v(root, "A");
|
||||
TestVertex B = bigV(root, "B");
|
||||
edge(root, A);
|
||||
edge(root, B);
|
||||
|
||||
showGraph();
|
||||
|
||||
assertEquals(p(0, 0), root.getLocation());
|
||||
int expectedXa = VERTEX_GAP + root.width / 2 + A.width / 2;
|
||||
int expectedXb = VERTEX_GAP + root.width / 2 + B.width / 2;
|
||||
int totalHeight = VERTEX_GAP + A.height + B.height;
|
||||
int expectedYa = -totalHeight / 2 + A.height / 2;
|
||||
int expectedYb = totalHeight / 2 - B.height / 2;
|
||||
assertEquals(p(expectedXa, expectedYa), A.getLocation());
|
||||
assertEquals(p(expectedXb, expectedYb), B.getLocation());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGraphTwoIncomingChildrenDifferentSize() {
|
||||
TestVertex A = v(root, "A");
|
||||
TestVertex B = bigV(root, "B");
|
||||
edge(A, root);
|
||||
edge(B, root);
|
||||
|
||||
showGraph();
|
||||
|
||||
assertEquals(p(0, 0), root.getLocation());
|
||||
int expectedXa = -VERTEX_GAP - root.width / 2 - A.width / 2;
|
||||
int expectedXb = -VERTEX_GAP - root.width / 2 - B.width / 2;
|
||||
int totalHeight = VERTEX_GAP + A.height + B.height;
|
||||
int expectedYa = -totalHeight / 2 + A.height / 2;
|
||||
int expectedYb = totalHeight / 2 - B.height / 2;
|
||||
assertEquals(p(expectedXa, expectedYa), A.getLocation());
|
||||
assertEquals(p(expectedXb, expectedYb), B.getLocation());
|
||||
}
|
||||
|
||||
private Point p(int x, int y) {
|
||||
return new Point(x, y);
|
||||
}
|
||||
|
||||
protected void showGraph() {
|
||||
|
||||
Swing.runNow(() -> {
|
||||
JFrame frame = new JFrame("Graph Viewer Test");
|
||||
|
||||
TestEgGraphLayout layout = new TestEgGraphLayout(g, root);
|
||||
g.setLayout(layout);
|
||||
graphComponent = new GraphComponent<>(g);
|
||||
graphComponent.setSatelliteVisible(false);
|
||||
|
||||
frame.setSize(new Dimension(800, 800));
|
||||
frame.setLocation(2400, 100);
|
||||
frame.getContentPane().add(graphComponent.getComponent());
|
||||
frame.setVisible(true);
|
||||
frame.validate();
|
||||
});
|
||||
}
|
||||
|
||||
protected TestVertex bigV(TestVertex source, String name) {
|
||||
TestVertex v = new TestVertex(source, name, VERTEX_SIZE * 2, VERTEX_SIZE * 2);
|
||||
g.addVertex(v);
|
||||
return v;
|
||||
}
|
||||
|
||||
protected TestVertex v(TestVertex source, String name) {
|
||||
TestVertex v = new TestVertex(source, name, VERTEX_SIZE, VERTEX_SIZE);
|
||||
g.addVertex(v);
|
||||
return v;
|
||||
}
|
||||
|
||||
private TestVertex v(TestVertex source, String name, int width, int height) {
|
||||
TestVertex v = new TestVertex(source, name, width, height);
|
||||
g.addVertex(v);
|
||||
return v;
|
||||
}
|
||||
|
||||
protected TestEdge edge(TestVertex v1, TestVertex v2) {
|
||||
TestEdge testEdge = new TestEdge(v1, v2);
|
||||
g.addEdge(testEdge);
|
||||
return testEdge;
|
||||
}
|
||||
|
||||
private class TestVertex extends EgVertex {
|
||||
|
||||
private JLabel label;
|
||||
private String name;
|
||||
private int width;
|
||||
private int height;
|
||||
|
||||
TestVertex(TestVertex source, String name, int width, int height) {
|
||||
super(source);
|
||||
this.name = name;
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JComponent getComponent() {
|
||||
if (label == null) {
|
||||
label = new GDLabel();
|
||||
label.setText(name);
|
||||
label.setPreferredSize(new Dimension(width, height));
|
||||
label.setBackground(Palette.GOLD);
|
||||
label.setOpaque(true);
|
||||
label.setBorder(BorderFactory.createRaisedBevelBorder());
|
||||
label.setHorizontalAlignment(SwingConstants.CENTER);
|
||||
}
|
||||
return label;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Point2D getStartingEdgePoint(EgVertex end) {
|
||||
return new Point(0, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Point2D getEndingEdgePoint(EgVertex start) {
|
||||
return new Point(0, 0);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private class TestEdge extends EgEdge<TestVertex> {
|
||||
|
||||
public TestEdge(TestVertex start, TestVertex end) {
|
||||
super(start, end);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
// Suppressing warning on the return type; we know our class is the right type
|
||||
@Override
|
||||
public TestEdge cloneEdge(TestVertex start, TestVertex end) {
|
||||
return new TestEdge(start, end);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private class TestExplorationGraph extends AbstractExplorationGraph<TestVertex, TestEdge> {
|
||||
|
||||
TestExplorationGraph(TestVertex root) {
|
||||
super(root);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DefaultVisualGraph<TestVertex, TestEdge> copy() {
|
||||
Collection<TestVertex> v = getVertices();
|
||||
Collection<TestEdge> e = getEdges();
|
||||
TestExplorationGraph newGraph = new TestExplorationGraph(getRoot());
|
||||
v.forEach(newGraph::addVertex);
|
||||
e.forEach(newGraph::addEdge);
|
||||
return newGraph;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class TestEgGraphLayout
|
||||
extends EgGraphLayout<TestVertex, TestEdge> {
|
||||
|
||||
protected TestEgGraphLayout(TestExplorationGraph graph, TestVertex root) {
|
||||
super(graph, "Test", VERTEX_GAP, VERTEX_GAP);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AbstractVisualGraphLayout<TestVertex, TestEdge> createClonedLayout(
|
||||
VisualGraph<TestVertex, TestEdge> newGraph) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected EgEdgeTransformer<TestVertex, TestEdge> createEdgeTransformer() {
|
||||
return new EgEdgeTransformer<EgGraphLayoutTest.TestVertex, EgGraphLayoutTest.TestEdge>();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Comparator<TestVertex> getIncommingVertexComparator() {
|
||||
return (v1, v2) -> v1.name.compareTo(v2.name);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Comparator<TestVertex> getOutgoingVertexComparator() {
|
||||
return (v1, v2) -> v1.name.compareTo(v2.name);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
/* ###
|
||||
* 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 datagraph.graph;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.awt.Point;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import datagraph.graph.explore.GraphLocationMap;
|
||||
|
||||
public class GraphLocationMapTest {
|
||||
|
||||
private GraphLocationMap<TestVertex> map1;
|
||||
private GraphLocationMap<TestVertex> map2;
|
||||
|
||||
@Test
|
||||
public void testInitialState() {
|
||||
TestVertex v = new TestVertex("A");
|
||||
assertEquals("A", v.getName());
|
||||
map1 = new GraphLocationMap<>(v, 10, 20);
|
||||
|
||||
assertEquals(10, map1.getWidth());
|
||||
assertEquals(20, map1.getHeight());
|
||||
assertEquals(p(0, 0), map1.get(v));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMergeRight() {
|
||||
TestVertex a = new TestVertex("A");
|
||||
TestVertex b = new TestVertex("B");
|
||||
|
||||
map1 = new GraphLocationMap<>(a, 10, 20);
|
||||
map2 = new GraphLocationMap<>(b, 100, 50);
|
||||
|
||||
// merge maps left to right, shifting map2 by 1000
|
||||
map1.merge(map2, 1000, 0);
|
||||
|
||||
assertEquals(1055, map1.getWidth()); // width of both maps + the gap
|
||||
assertEquals(50, map1.getHeight()); // height of the tallest map
|
||||
assertEquals(p(0, 0), map1.get(a)); // point in first map doesn't move
|
||||
assertEquals(p(1000, 0), map1.get(b));// point in second map moved by shift
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMergeBottom() {
|
||||
TestVertex a = new TestVertex("A");
|
||||
TestVertex b = new TestVertex("B");
|
||||
|
||||
map1 = new GraphLocationMap<>(a, 10, 20);
|
||||
map2 = new GraphLocationMap<>(b, 100, 50);
|
||||
|
||||
// merge maps left to right, shifting map2 by 1000
|
||||
map1.merge(map2, 0, 1000);
|
||||
|
||||
assertEquals(100, map1.getWidth()); // width of both maps + the gap
|
||||
assertEquals(1035, map1.getHeight()); // height of the tallest map
|
||||
assertEquals(p(0, 0), map1.get(a)); // point in first map doesn't move
|
||||
assertEquals(p(0, 1000), map1.get(b));// point in second map moved by shift
|
||||
}
|
||||
|
||||
private Point p(int x, int y) {
|
||||
return new Point(x, y);
|
||||
}
|
||||
|
||||
private class TestVertex {
|
||||
private String name;
|
||||
|
||||
TestVertex(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -54,7 +54,7 @@
|
||||
|
||||
<tocdef id="Function Graph" text="Function Graph" target="help/topics/FunctionGraphPlugin/Function_Graph.html" >
|
||||
<tocdef id="Primary View" sortgroup="a" text="Primary View" target="help/topics/FunctionGraphPlugin/Function_Graph.html#Primary_View" />
|
||||
<tocdef id="Satellite View" sortgroup="b" text="Satellite View" target="help/topics/FunctionGraphPlugin/Function_Graph.html#Satellite_View" />
|
||||
<tocdef id="Satellite View" sortgroup="b" text="Satellite View" target="help/topics/VisualGraph/Visual_Graph.html#Satellite_View" />
|
||||
|
||||
<tocdef id="Vertices" sortgroup="c" text="Vertices" target="help/topics/FunctionGraphPlugin/Function_Graph.html#Vertices">
|
||||
<tocdef id="Vertex Grouping" sortgroup="a" text="Vertex Grouping" target="help/topics/FunctionGraphPlugin/Function_Graph.html#Vertex_Grouping" />
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user