mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2026-05-31 19:28:34 +08:00
Created the concept of graph types and display options for those graph types.
This commit is contained in:
+1
-1
@@ -63,7 +63,7 @@ public class DisplayAsGraphAction extends DisplayAsAction {
|
|||||||
|
|
||||||
public void addGraph(ObjectContainer container) {
|
public void addGraph(ObjectContainer container) {
|
||||||
GraphDisplayProvider graphProvider = graphBroker.getDefaultGraphDisplayProvider();
|
GraphDisplayProvider graphProvider = graphBroker.getDefaultGraphDisplayProvider();
|
||||||
AttributedGraph graph = new AttributedGraph();
|
AttributedGraph graph = new AttributedGraph(container.getName(), new EmptyGraphType());
|
||||||
AttributedVertex start = graph.addVertex(container.toString(), container.getName());
|
AttributedVertex start = graph.addVertex(container.toString(), container.getName());
|
||||||
graphContainer(container, graph, start);
|
graphContainer(container, graph, start);
|
||||||
try {
|
try {
|
||||||
|
|||||||
+1
-1
@@ -81,7 +81,7 @@ public class DisplayFilteredGraphAction extends DisplayFilteredAction {
|
|||||||
@Override
|
@Override
|
||||||
protected void finishGetOffspring(ObjectContainer container, final List<String> path) {
|
protected void finishGetOffspring(ObjectContainer container, final List<String> path) {
|
||||||
GraphDisplayProvider graphProvider = graphBroker.getDefaultGraphDisplayProvider();
|
GraphDisplayProvider graphProvider = graphBroker.getDefaultGraphDisplayProvider();
|
||||||
AttributedGraph graph = new AttributedGraph();
|
AttributedGraph graph = new AttributedGraph(container.getName(), new EmptyGraphType());
|
||||||
AttributedVertex start = graph.addVertex(container.getName(), container.toString());
|
AttributedVertex start = graph.addVertex(container.getName(), container.toString());
|
||||||
graphContainer(container, graph, start);
|
graphContainer(container, graph, start);
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ import ghidra.service.graph.*;
|
|||||||
* Script to generate graph to test BrandesKopf algorithm
|
* Script to generate graph to test BrandesKopf algorithm
|
||||||
*/
|
*/
|
||||||
public class GenerateBrandesKopfGraphScript extends GhidraScript {
|
public class GenerateBrandesKopfGraphScript extends GhidraScript {
|
||||||
private AttributedGraph graph = new AttributedGraph();
|
private AttributedGraph graph = new AttributedGraph("test", new EmptyGraphType());
|
||||||
private int nextEdgeID = 1;
|
private int nextEdgeID = 1;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ import ghidra.service.graph.*;
|
|||||||
* Sample script to test graph service
|
* Sample script to test graph service
|
||||||
*/
|
*/
|
||||||
public class GenerateTestGraphScript extends GhidraScript {
|
public class GenerateTestGraphScript extends GhidraScript {
|
||||||
private AttributedGraph graph = new AttributedGraph();
|
private AttributedGraph graph = new AttributedGraph("Test", new EmptyGraphType());
|
||||||
private int nextEdgeID = 1;
|
private int nextEdgeID = 1;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ import ghidra.service.graph.*;
|
|||||||
* Example script for creating and displaying a graph in ghidra
|
* Example script for creating and displaying a graph in ghidra
|
||||||
*/
|
*/
|
||||||
public class ExampleGraphServiceScript extends GhidraScript {
|
public class ExampleGraphServiceScript extends GhidraScript {
|
||||||
private AttributedGraph graph = new AttributedGraph();
|
private AttributedGraph graph = new AttributedGraph("Test", new EmptyGraphType());
|
||||||
private int nextEdgeID = 1;
|
private int nextEdgeID = 1;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -107,7 +107,7 @@ public class GraphClassesScript extends GhidraScript {
|
|||||||
*/
|
*/
|
||||||
private AttributedGraph createGraph() throws Exception {
|
private AttributedGraph createGraph() throws Exception {
|
||||||
|
|
||||||
AttributedGraph g = new AttributedGraph();
|
AttributedGraph g = new AttributedGraph("Test Graph", new EmptyGraphType());
|
||||||
|
|
||||||
for (Structure classStructure : classStructures) {
|
for (Structure classStructure : classStructures) {
|
||||||
|
|
||||||
|
|||||||
+20
-12
@@ -15,6 +15,8 @@
|
|||||||
*/
|
*/
|
||||||
package ghidra.app.plugin.core.datamgr.actions;
|
package ghidra.app.plugin.core.datamgr.actions;
|
||||||
|
|
||||||
|
import java.awt.Color;
|
||||||
|
|
||||||
import ghidra.app.util.ToolTipUtils;
|
import ghidra.app.util.ToolTipUtils;
|
||||||
import ghidra.program.model.data.*;
|
import ghidra.program.model.data.*;
|
||||||
import ghidra.service.graph.*;
|
import ghidra.service.graph.*;
|
||||||
@@ -35,9 +37,8 @@ public class TypeGraphTask extends Task {
|
|||||||
private String graphTitle;
|
private String graphTitle;
|
||||||
private GraphDisplayProvider graphService;
|
private GraphDisplayProvider graphService;
|
||||||
|
|
||||||
public static final String TYPE_ATTRIBUTE = "Type";
|
public static final String COMPOSITE = "Composite";
|
||||||
public static final String EMBEDDED = "Composite";
|
public static final String REFERENCE = "Reference";
|
||||||
public static final String POINTER = "Reference";
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Constructor
|
* Constructor
|
||||||
@@ -57,8 +58,18 @@ public class TypeGraphTask extends Task {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void run(TaskMonitor monitor) throws CancelledException {
|
public void run(TaskMonitor monitor) throws CancelledException {
|
||||||
|
GraphType graphType = new GraphTypeBuilder("Data Graph")
|
||||||
|
.edgeType(REFERENCE)
|
||||||
|
.edgeType(COMPOSITE)
|
||||||
|
.build();
|
||||||
|
|
||||||
AttributedGraph graph = new AttributedGraph();
|
GraphDisplayOptions options = new GraphDisplayOptionsBuilder(graphType)
|
||||||
|
.defaultVertexColor(Color.BLUE)
|
||||||
|
.edge(COMPOSITE, Color.MAGENTA)
|
||||||
|
.edge(REFERENCE, Color.BLUE)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
AttributedGraph graph = new AttributedGraph(graphTitle, graphType);
|
||||||
try {
|
try {
|
||||||
if (type instanceof Pointer) {
|
if (type instanceof Pointer) {
|
||||||
recursePointer((Pointer) type, graph, null, monitor);
|
recursePointer((Pointer) type, graph, null, monitor);
|
||||||
@@ -75,7 +86,7 @@ public class TypeGraphTask extends Task {
|
|||||||
GraphDisplay display;
|
GraphDisplay display;
|
||||||
try {
|
try {
|
||||||
display = graphService.getGraphDisplay(false, monitor);
|
display = graphService.getGraphDisplay(false, monitor);
|
||||||
display.setGraph(graph, graphTitle, false, monitor);
|
display.setGraph(graph, options, graphTitle, false, monitor);
|
||||||
}
|
}
|
||||||
catch (GraphException e) {
|
catch (GraphException e) {
|
||||||
Msg.showError(this, null, "Data Type Graph Error",
|
Msg.showError(this, null, "Data Type Graph Error",
|
||||||
@@ -94,11 +105,8 @@ public class TypeGraphTask extends Task {
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
AttributedEdge edge = graph.addEdge(lastVertex, newVertex);
|
AttributedEdge edge = graph.addEdge(lastVertex, newVertex);
|
||||||
if (edgeType == POINTER) {
|
edge.setEdgeType(edgeType);
|
||||||
edge.setAttribute("Color", "Blue");
|
if (edge.hasAttribute(AttributedGraph.WEIGHT)) {
|
||||||
}
|
|
||||||
edge.setAttribute(TYPE_ATTRIBUTE, edgeType);
|
|
||||||
if (edge.hasAttribute("Weight")) {
|
|
||||||
//did this already, don't cycle
|
//did this already, don't cycle
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -115,7 +123,7 @@ public class TypeGraphTask extends Task {
|
|||||||
recursePointer((Pointer) dt, graph, newVertex, monitor);
|
recursePointer((Pointer) dt, graph, newVertex, monitor);
|
||||||
}
|
}
|
||||||
else if (dt instanceof Composite) {
|
else if (dt instanceof Composite) {
|
||||||
recurseComposite((Composite) dt, graph, newVertex, EMBEDDED, monitor);
|
recurseComposite((Composite) dt, graph, newVertex, COMPOSITE, monitor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -135,7 +143,7 @@ public class TypeGraphTask extends Task {
|
|||||||
recursePointer((Pointer) ptrType, graph, lastVertex, monitor);
|
recursePointer((Pointer) ptrType, graph, lastVertex, monitor);
|
||||||
}
|
}
|
||||||
else if (ptrType instanceof Composite) {
|
else if (ptrType instanceof Composite) {
|
||||||
recurseComposite((Composite) ptrType, graph, lastVertex, POINTER, monitor);
|
recurseComposite((Composite) ptrType, graph, lastVertex, REFERENCE, monitor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+4
-3
@@ -148,10 +148,11 @@ public class GraphDisplayBrokerPlugin extends Plugin
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public GraphDisplay getDefaultGraphDisplay(boolean reuseGraph, Map<String, String> properties,
|
public GraphDisplay getDefaultGraphDisplay(boolean reuseGraph, TaskMonitor monitor)
|
||||||
TaskMonitor monitor) throws GraphException {
|
throws GraphException {
|
||||||
|
|
||||||
if (defaultGraphDisplayProvider != null) {
|
if (defaultGraphDisplayProvider != null) {
|
||||||
return defaultGraphDisplayProvider.getGraphDisplay(reuseGraph, properties, monitor);
|
return defaultGraphDisplayProvider.getGraphDisplay(reuseGraph, monitor);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
package ghidra.app.services;
|
package ghidra.app.services;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.List;
|
||||||
|
|
||||||
import ghidra.app.plugin.core.graph.GraphDisplayBrokerListener;
|
import ghidra.app.plugin.core.graph.GraphDisplayBrokerListener;
|
||||||
import ghidra.app.plugin.core.graph.GraphDisplayBrokerPlugin;
|
import ghidra.app.plugin.core.graph.GraphDisplayBrokerPlugin;
|
||||||
@@ -60,25 +60,7 @@ public interface GraphDisplayBroker {
|
|||||||
* @return a {@link GraphDisplay} object to sends graphs to be displayed or exported.
|
* @return a {@link GraphDisplay} object to sends graphs to be displayed or exported.
|
||||||
* @throws GraphException thrown if an error occurs trying to get a graph display
|
* @throws GraphException thrown if an error occurs trying to get a graph display
|
||||||
*/
|
*/
|
||||||
public default GraphDisplay getDefaultGraphDisplay(boolean reuseGraph, TaskMonitor monitor)
|
public GraphDisplay getDefaultGraphDisplay(boolean reuseGraph, TaskMonitor monitor)
|
||||||
throws GraphException {
|
|
||||||
return getDefaultGraphDisplay(reuseGraph, Collections.emptyMap(), monitor);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A convenience method for getting a {@link GraphDisplay} from the currently active provider
|
|
||||||
*
|
|
||||||
* <p>This method allows users to override default graph properties for the graph provider
|
|
||||||
* being created. See the graph provider implementation for a list of supported properties
|
|
||||||
*
|
|
||||||
* @param reuseGraph if true, the provider will attempt to re-use a current graph display
|
|
||||||
* @param properties a {@code Map} of property key/values that can be used to customize the display
|
|
||||||
* @param monitor the {@link TaskMonitor} that can be used to cancel the operation
|
|
||||||
* @return a {@link GraphDisplay} object to sends graphs to be displayed or exported.
|
|
||||||
* @throws GraphException thrown if an error occurs trying to get a graph display
|
|
||||||
*/
|
|
||||||
public GraphDisplay getDefaultGraphDisplay(boolean reuseGraph, Map<String, String> properties,
|
|
||||||
TaskMonitor monitor)
|
|
||||||
throws GraphException;
|
throws GraphException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
/* ###
|
||||||
|
* IP: GHIDRA
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package ghidra.graph;
|
||||||
|
|
||||||
|
public class BlockFlowGraphType extends ProgramGraphType {
|
||||||
|
|
||||||
|
public BlockFlowGraphType() {
|
||||||
|
super("Block Flow Graph", "Shows program basic block flow");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
/* ###
|
||||||
|
* IP: GHIDRA
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package ghidra.graph;
|
||||||
|
|
||||||
|
public class CallGraphType extends ProgramGraphType {
|
||||||
|
|
||||||
|
public CallGraphType() {
|
||||||
|
super("Call Graph", "Shows relationships between functions");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
/* ###
|
||||||
|
* IP: GHIDRA
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package ghidra.graph;
|
||||||
|
|
||||||
|
public class CodeFlowGraphType extends ProgramGraphType {
|
||||||
|
|
||||||
|
public CodeFlowGraphType() {
|
||||||
|
super("Code Flow Graph",
|
||||||
|
"Shows code block flow (similar to Block Flow graph type, but shows the code in each veretx)");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
/* ###
|
||||||
|
* IP: GHIDRA
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package ghidra.graph;
|
||||||
|
|
||||||
|
public class DataFlowGraphType extends ProgramGraphType {
|
||||||
|
|
||||||
|
public DataFlowGraphType() {
|
||||||
|
super("Data Flow Graph", "Shows program data relationships");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
/* ###
|
||||||
|
* IP: GHIDRA
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package ghidra.graph;
|
||||||
|
|
||||||
|
import static ghidra.graph.ProgramGraphType.*;
|
||||||
|
import static ghidra.service.graph.VertexShape.*;
|
||||||
|
|
||||||
|
import ghidra.framework.plugintool.PluginTool;
|
||||||
|
import ghidra.service.graph.GraphDisplayOptions;
|
||||||
|
import ghidra.service.graph.VertexShape;
|
||||||
|
import ghidra.util.WebColors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link GraphDisplayOptions} for {@link ProgramGraphType}
|
||||||
|
*/
|
||||||
|
public class ProgramGraphDisplayOptions extends GraphDisplayOptions {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* constructor
|
||||||
|
* @param graphType the specific ProgramGraphType subclass for these options
|
||||||
|
* @param tool if non-null, will load values from tool options
|
||||||
|
*/
|
||||||
|
public ProgramGraphDisplayOptions(ProgramGraphType graphType, PluginTool tool) {
|
||||||
|
super(graphType, tool);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void initializeDefaults() {
|
||||||
|
setDefaultVertexShape(ELLIPSE);
|
||||||
|
setDefaultVertexColor(WebColors.RED);
|
||||||
|
setDefaultEdgeColor(WebColors.RED);
|
||||||
|
setFavoredEdgeType(FALL_THROUGH);
|
||||||
|
|
||||||
|
configureVertexType(BODY, RECTANGLE, WebColors.BLUE);
|
||||||
|
configureVertexType(ENTRY, TRIANGLE_DOWN, WebColors.DARK_ORANGE);
|
||||||
|
configureVertexType(EXIT, TRIANGLE_UP, WebColors.DARK_MAGENTA);
|
||||||
|
configureVertexType(SWITCH, DIAMOND, WebColors.DARK_CYAN);
|
||||||
|
configureVertexType(EXTERNAL, RECTANGLE, WebColors.DARK_GREEN);
|
||||||
|
configureVertexType(BAD, ELLIPSE, WebColors.RED);
|
||||||
|
configureVertexType(DATA, ELLIPSE, WebColors.PINK);
|
||||||
|
configureVertexType(ENTRY_NEXUS, ELLIPSE, WebColors.WHEAT);
|
||||||
|
configureVertexType(INSTRUCTION, VertexShape.HEXAGON, WebColors.BLUE);
|
||||||
|
configureVertexType(STACK, RECTANGLE, WebColors.GREEN);
|
||||||
|
|
||||||
|
configureEdgeType(ENTRY_EDGE, WebColors.GRAY);
|
||||||
|
configureEdgeType(FALL_THROUGH, WebColors.BLUE);
|
||||||
|
configureEdgeType(UNCONDITIONAL_JUMP, WebColors.DARK_GREEN);
|
||||||
|
configureEdgeType(UNCONDITIONAL_CALL, WebColors.DARK_ORANGE);
|
||||||
|
configureEdgeType(TERMINATOR, WebColors.PURPLE);
|
||||||
|
configureEdgeType(JUMP_TERMINATOR, WebColors.PURPLE);
|
||||||
|
configureEdgeType(INDIRECTION, WebColors.PINK);
|
||||||
|
|
||||||
|
configureEdgeType(CONDITIONAL_JUMP, WebColors.DARK_GOLDENROD);
|
||||||
|
configureEdgeType(CONDITIONAL_CALL, WebColors.DARK_ORANGE);
|
||||||
|
configureEdgeType(CONDITIONAL_TERMINATOR, WebColors.PURPLE);
|
||||||
|
configureEdgeType(CONDITIONAL_CALL_TERMINATOR, WebColors.PURPLE);
|
||||||
|
|
||||||
|
configureEdgeType(COMPUTED_JUMP, WebColors.CYAN);
|
||||||
|
configureEdgeType(COMPUTED_CALL, WebColors.CYAN);
|
||||||
|
configureEdgeType(COMPUTED_CALL_TERMINATOR, WebColors.PURPLE);
|
||||||
|
|
||||||
|
configureEdgeType(CONDITIONAL_COMPUTED_CALL, WebColors.CYAN);
|
||||||
|
configureEdgeType(CONDITIONAL_COMPUTED_JUMP, WebColors.CYAN);
|
||||||
|
|
||||||
|
configureEdgeType(CALL_OVERRIDE_UNCONDITIONAL, WebColors.RED);
|
||||||
|
configureEdgeType(JUMP_OVERRIDE_UNCONDITIONAL, WebColors.RED);
|
||||||
|
configureEdgeType(CALLOTHER_OVERRIDE_CALL, WebColors.RED);
|
||||||
|
configureEdgeType(CALLOTHER_OVERRIDE_JUMP, WebColors.RED);
|
||||||
|
|
||||||
|
configureEdgeType(READ, WebColors.GREEN);
|
||||||
|
configureEdgeType(WRITE, WebColors.RED);
|
||||||
|
configureEdgeType(READ_WRITE, WebColors.DARK_GOLDENROD);
|
||||||
|
configureEdgeType(UNKNOWN_DATA, WebColors.BLACK);
|
||||||
|
configureEdgeType(EXTERNAL_REF, WebColors.PURPLE);
|
||||||
|
|
||||||
|
configureEdgeType(READ_INDIRECT, WebColors.DARK_GREEN);
|
||||||
|
configureEdgeType(WRITE_INDIRECT, WebColors.DARK_RED);
|
||||||
|
configureEdgeType(READ_WRITE_INDIRECT, WebColors.BROWN);
|
||||||
|
configureEdgeType(DATA_INDIRECT, WebColors.DARK_ORANGE);
|
||||||
|
|
||||||
|
configureEdgeType(PARAM, WebColors.CYAN);
|
||||||
|
configureEdgeType(THUNK, WebColors.BLUE);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,126 @@
|
|||||||
|
/* ###
|
||||||
|
* IP: GHIDRA
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package ghidra.graph;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
import org.apache.commons.text.WordUtils;
|
||||||
|
|
||||||
|
import ghidra.program.model.symbol.RefType;
|
||||||
|
import ghidra.service.graph.GraphType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Defines a common set of vertex and edge types {@link GraphType} for program code and data flow
|
||||||
|
* graphs. Each specific type of program graph will use a subclass to specifically identify the
|
||||||
|
* graph type.
|
||||||
|
*/
|
||||||
|
|
||||||
|
public abstract class ProgramGraphType extends GraphType {
|
||||||
|
private static Map<RefType, String> refTypeToEdgeTypeMap = new HashMap<>();
|
||||||
|
private static List<String> vertexTypes = new ArrayList<>();
|
||||||
|
private static List<String> edgeTypes = new ArrayList<>();
|
||||||
|
//@formatter:off
|
||||||
|
|
||||||
|
// Vertex Types
|
||||||
|
public static final String BODY = vertex("Body");
|
||||||
|
public static final String ENTRY = vertex("Entry");
|
||||||
|
public static final String EXIT = vertex("Exit");
|
||||||
|
public static final String SWITCH = vertex("Switch");
|
||||||
|
public static final String EXTERNAL = vertex("External");
|
||||||
|
public static final String BAD = vertex("Bad");
|
||||||
|
public static final String INSTRUCTION = vertex("Instruction");
|
||||||
|
public static final String DATA = vertex("Data");
|
||||||
|
public static final String ENTRY_NEXUS = vertex("Entry-Nexus");
|
||||||
|
public static final String STACK = vertex("Stack");
|
||||||
|
|
||||||
|
// Edge Types - Flow
|
||||||
|
public static final String ENTRY_EDGE = edge("Entry"); // This edge if for adding an "Entry Nexus" Vertex
|
||||||
|
public static final String FALL_THROUGH = edge(map(RefType.FALL_THROUGH));
|
||||||
|
public static final String UNCONDITIONAL_JUMP = edge(map(RefType.UNCONDITIONAL_JUMP));
|
||||||
|
public static final String UNCONDITIONAL_CALL = edge(map(RefType.UNCONDITIONAL_CALL));
|
||||||
|
public static final String TERMINATOR = edge(map(RefType.TERMINATOR));
|
||||||
|
public static final String JUMP_TERMINATOR = edge(map(RefType.JUMP_TERMINATOR));
|
||||||
|
public static final String INDIRECTION = edge(map(RefType.INDIRECTION));
|
||||||
|
|
||||||
|
public static final String CONDITIONAL_JUMP = edge(map(RefType.CONDITIONAL_JUMP));
|
||||||
|
public static final String CONDITIONAL_CALL = edge(map(RefType.CONDITIONAL_CALL));
|
||||||
|
public static final String CONDITIONAL_TERMINATOR = edge(map(RefType.CONDITIONAL_TERMINATOR));
|
||||||
|
public static final String CONDITIONAL_CALL_TERMINATOR =edge(map(RefType.CONDITIONAL_CALL_TERMINATOR));
|
||||||
|
|
||||||
|
public static final String COMPUTED_JUMP = edge(map(RefType.COMPUTED_JUMP));
|
||||||
|
public static final String COMPUTED_CALL = edge(map(RefType.COMPUTED_CALL));
|
||||||
|
public static final String COMPUTED_CALL_TERMINATOR = edge(map(RefType.COMPUTED_CALL_TERMINATOR));
|
||||||
|
|
||||||
|
public static final String CONDITIONAL_COMPUTED_CALL = edge(map(RefType.CONDITIONAL_COMPUTED_CALL));
|
||||||
|
public static final String CONDITIONAL_COMPUTED_JUMP =edge(map(RefType.CONDITIONAL_COMPUTED_JUMP));
|
||||||
|
|
||||||
|
public static final String CALL_OVERRIDE_UNCONDITIONAL = edge(map(RefType.CALL_OVERRIDE_UNCONDITIONAL));
|
||||||
|
public static final String JUMP_OVERRIDE_UNCONDITIONAL = edge(map(RefType.CALL_OVERRIDE_UNCONDITIONAL));
|
||||||
|
public static final String CALLOTHER_OVERRIDE_CALL = edge(map(RefType.CALL_OVERRIDE_UNCONDITIONAL));
|
||||||
|
public static final String CALLOTHER_OVERRIDE_JUMP = edge(map(RefType.CALL_OVERRIDE_UNCONDITIONAL));
|
||||||
|
|
||||||
|
// Edge Types Data Refs
|
||||||
|
public static final String READ = edge(map(RefType.READ));
|
||||||
|
public static final String WRITE = edge(map(RefType.WRITE));
|
||||||
|
public static final String READ_WRITE = edge(map(RefType.READ_WRITE));
|
||||||
|
public static final String UNKNOWN_DATA = edge(map(RefType.DATA));
|
||||||
|
public static final String EXTERNAL_REF = edge(map(RefType.EXTERNAL_REF));
|
||||||
|
|
||||||
|
public static final String READ_INDIRECT = edge(map(RefType.READ_IND));
|
||||||
|
public static final String WRITE_INDIRECT = edge(map(RefType.WRITE_IND));
|
||||||
|
public static final String READ_WRITE_INDIRECT = edge(map(RefType.READ_WRITE_IND));
|
||||||
|
public static final String DATA_INDIRECT = edge(map(RefType.DATA_IND));
|
||||||
|
|
||||||
|
public static final String PARAM = edge(map(RefType.PARAM));
|
||||||
|
public static final String THUNK = edge(map(RefType.THUNK));
|
||||||
|
|
||||||
|
//@formatter:on
|
||||||
|
|
||||||
|
protected ProgramGraphType(String name, String description) {
|
||||||
|
super(name, description, vertexTypes, edgeTypes);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String vertex(String vertexType) {
|
||||||
|
vertexTypes.add(vertexType);
|
||||||
|
return vertexType;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String edge(String edgeType) {
|
||||||
|
edgeTypes.add(edgeType);
|
||||||
|
return edgeType;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String map(RefType refType) {
|
||||||
|
String edgeTypeName = fixup(refType.getName());
|
||||||
|
refTypeToEdgeTypeMap.put(refType, edgeTypeName);
|
||||||
|
return edgeTypeName;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String fixup(String name) {
|
||||||
|
name = name.replace('_', ' ');
|
||||||
|
return WordUtils.capitalizeFully(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getEdgeType(RefType refType) {
|
||||||
|
return refTypeToEdgeTypeMap.get(refType);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getOptionsName() {
|
||||||
|
return "Program Graph Display Options";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+46
-31
@@ -160,11 +160,12 @@ public class OptionsDialogTest extends AbstractGhidraHeadedIntegrationTest {
|
|||||||
// get the options panel
|
// get the options panel
|
||||||
ScrollableOptionsEditor simpleOptionsPanel =
|
ScrollableOptionsEditor simpleOptionsPanel =
|
||||||
(ScrollableOptionsEditor) getEditorPanel(consoleNode);
|
(ScrollableOptionsEditor) getEditorPanel(consoleNode);
|
||||||
assertNotNull(simpleOptionsPanel);
|
JComponent comp = simpleOptionsPanel.getComponent();
|
||||||
assertTrue(simpleOptionsPanel.isShowing());
|
assertNotNull(comp);
|
||||||
|
assertTrue(comp.isShowing());
|
||||||
|
|
||||||
String optionName = (String) getInstanceField("MAXIMUM_CHARACTERS_OPTION_NAME", textPane);
|
String optionName = (String) getInstanceField("MAXIMUM_CHARACTERS_OPTION_NAME", textPane);
|
||||||
final Component component = findPairedComponent(simpleOptionsPanel, optionName);
|
final Component component = findPairedComponent(comp, optionName);
|
||||||
assertNotNull(component);
|
assertNotNull(component);
|
||||||
|
|
||||||
// click the option to toggle its state
|
// click the option to toggle its state
|
||||||
@@ -244,7 +245,8 @@ public class OptionsDialogTest extends AbstractGhidraHeadedIntegrationTest {
|
|||||||
ScrollableOptionsEditor editor = (ScrollableOptionsEditor) getEditorPanel(parentNode);
|
ScrollableOptionsEditor editor = (ScrollableOptionsEditor) getEditorPanel(parentNode);
|
||||||
|
|
||||||
assertNotNull("Did not find options editor for name: " + simpleName, editor);
|
assertNotNull("Did not find options editor for name: " + simpleName, editor);
|
||||||
assertNotNull("simpleName = " + simpleName, findPairedComponent(editor, simpleName));
|
assertNotNull("simpleName = " + simpleName,
|
||||||
|
findPairedComponent(editor.getComponent(), simpleName));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -273,7 +275,7 @@ public class OptionsDialogTest extends AbstractGhidraHeadedIntegrationTest {
|
|||||||
}
|
}
|
||||||
ScrollableOptionsEditor p = (ScrollableOptionsEditor) getEditorPanel(parent);
|
ScrollableOptionsEditor p = (ScrollableOptionsEditor) getEditorPanel(parent);
|
||||||
assertNotNull(p);
|
assertNotNull(p);
|
||||||
assertNotNull(findPairedComponent(p, simpleName));
|
assertNotNull(findPairedComponent(p.getComponent(), simpleName));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -331,9 +333,10 @@ public class OptionsDialogTest extends AbstractGhidraHeadedIntegrationTest {
|
|||||||
ScrollableOptionsEditor simpleOptionsPanel =
|
ScrollableOptionsEditor simpleOptionsPanel =
|
||||||
(ScrollableOptionsEditor) getEditorPanel(toolNode);
|
(ScrollableOptionsEditor) getEditorPanel(toolNode);
|
||||||
assertNotNull(simpleOptionsPanel);
|
assertNotNull(simpleOptionsPanel);
|
||||||
assertTrue(simpleOptionsPanel.isShowing());
|
JComponent comp = simpleOptionsPanel.getComponent();
|
||||||
|
assertTrue(comp.isShowing());
|
||||||
|
|
||||||
Component component = findPairedComponent(simpleOptionsPanel, "Favorite Color");
|
Component component = findPairedComponent(comp, "Favorite Color");
|
||||||
assertNotNull(component);
|
assertNotNull(component);
|
||||||
Rectangle rect = component.getBounds();
|
Rectangle rect = component.getBounds();
|
||||||
clickMouse(component, 1, rect.x, rect.y, 2, 0);
|
clickMouse(component, 1, rect.x, rect.y, 2, 0);
|
||||||
@@ -366,10 +369,11 @@ public class OptionsDialogTest extends AbstractGhidraHeadedIntegrationTest {
|
|||||||
ScrollableOptionsEditor simpleOptionsPanel =
|
ScrollableOptionsEditor simpleOptionsPanel =
|
||||||
(ScrollableOptionsEditor) getEditorPanel(buttonNode);
|
(ScrollableOptionsEditor) getEditorPanel(buttonNode);
|
||||||
assertNotNull(simpleOptionsPanel);
|
assertNotNull(simpleOptionsPanel);
|
||||||
assertTrue(simpleOptionsPanel.isShowing());
|
JComponent comp = simpleOptionsPanel.getComponent();
|
||||||
|
assertTrue(comp.isShowing());
|
||||||
|
|
||||||
PropertySelector ps =
|
PropertySelector ps =
|
||||||
(PropertySelector) findPairedComponent(simpleOptionsPanel, "Mouse Button To Activate");
|
(PropertySelector) findPairedComponent(comp, "Mouse Button To Activate");
|
||||||
assertNotNull(ps);
|
assertNotNull(ps);
|
||||||
runSwing(() -> ps.setSelectedIndex(0));
|
runSwing(() -> ps.setSelectedIndex(0));
|
||||||
assertEquals("LEFT", ps.getSelectedItem());
|
assertEquals("LEFT", ps.getSelectedItem());
|
||||||
@@ -513,10 +517,12 @@ public class OptionsDialogTest extends AbstractGhidraHeadedIntegrationTest {
|
|||||||
ScrollableOptionsEditor simpleOptionsPanel =
|
ScrollableOptionsEditor simpleOptionsPanel =
|
||||||
(ScrollableOptionsEditor) getEditorPanel(buttonNode);
|
(ScrollableOptionsEditor) getEditorPanel(buttonNode);
|
||||||
assertNotNull(simpleOptionsPanel);
|
assertNotNull(simpleOptionsPanel);
|
||||||
assertTrue(simpleOptionsPanel.isShowing());
|
JComponent comp = simpleOptionsPanel.getComponent();
|
||||||
|
|
||||||
|
assertTrue(comp.isShowing());
|
||||||
|
|
||||||
PropertySelector ps =
|
PropertySelector ps =
|
||||||
(PropertySelector) findPairedComponent(simpleOptionsPanel, "Mouse Button To Activate");
|
(PropertySelector) findPairedComponent(comp, "Mouse Button To Activate");
|
||||||
|
|
||||||
// change to "LEFT"
|
// change to "LEFT"
|
||||||
runSwing(() -> ps.setSelectedIndex(0));
|
runSwing(() -> ps.setSelectedIndex(0));
|
||||||
@@ -545,11 +551,13 @@ public class OptionsDialogTest extends AbstractGhidraHeadedIntegrationTest {
|
|||||||
|
|
||||||
ScrollableOptionsEditor simpleOptionsPanel =
|
ScrollableOptionsEditor simpleOptionsPanel =
|
||||||
(ScrollableOptionsEditor) getEditorPanel(buttonNode);
|
(ScrollableOptionsEditor) getEditorPanel(buttonNode);
|
||||||
|
|
||||||
assertNotNull(simpleOptionsPanel);
|
assertNotNull(simpleOptionsPanel);
|
||||||
assertTrue(simpleOptionsPanel.isShowing());
|
JComponent comp = simpleOptionsPanel.getComponent();
|
||||||
|
assertTrue(comp.isShowing());
|
||||||
|
|
||||||
PropertySelector ps =
|
PropertySelector ps =
|
||||||
(PropertySelector) findPairedComponent(simpleOptionsPanel, "Mouse Button To Activate");
|
(PropertySelector) findPairedComponent(comp, "Mouse Button To Activate");
|
||||||
|
|
||||||
// change to "LEFT"
|
// change to "LEFT"
|
||||||
runSwing(() -> ps.setSelectedIndex(0));
|
runSwing(() -> ps.setSelectedIndex(0));
|
||||||
@@ -592,15 +600,17 @@ public class OptionsDialogTest extends AbstractGhidraHeadedIntegrationTest {
|
|||||||
|
|
||||||
ScrollableOptionsEditor p = (ScrollableOptionsEditor) getEditorPanel(testNode);
|
ScrollableOptionsEditor p = (ScrollableOptionsEditor) getEditorPanel(testNode);
|
||||||
assertNotNull(p);
|
assertNotNull(p);
|
||||||
assertTrue(p.isShowing());
|
JComponent comp = p.getComponent();
|
||||||
|
|
||||||
JTextField field = (JTextField) findPairedComponent(p, "String Value 1");
|
assertTrue(comp.isShowing());
|
||||||
|
|
||||||
|
JTextField field = (JTextField) findPairedComponent(comp, "String Value 1");
|
||||||
assertNotNull(field);
|
assertNotNull(field);
|
||||||
field = (JTextField) findPairedComponent(p, "String Value 2");
|
field = (JTextField) findPairedComponent(comp, "String Value 2");
|
||||||
assertNotNull(field);
|
assertNotNull(field);
|
||||||
field = (JTextField) findPairedComponent(p, "String Value 3");
|
field = (JTextField) findPairedComponent(comp, "String Value 3");
|
||||||
assertNotNull(field);
|
assertNotNull(field);
|
||||||
field = (JTextField) findPairedComponent(p, "Int Value");
|
field = (JTextField) findPairedComponent(comp, "Int Value");
|
||||||
assertNotNull(field);
|
assertNotNull(field);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -614,9 +624,10 @@ public class OptionsDialogTest extends AbstractGhidraHeadedIntegrationTest {
|
|||||||
ScrollableOptionsEditor simpleOptionsPanel =
|
ScrollableOptionsEditor simpleOptionsPanel =
|
||||||
(ScrollableOptionsEditor) getEditorPanel(toolNode);
|
(ScrollableOptionsEditor) getEditorPanel(toolNode);
|
||||||
assertNotNull(simpleOptionsPanel);
|
assertNotNull(simpleOptionsPanel);
|
||||||
assertTrue(simpleOptionsPanel.isShowing());
|
JComponent comp = simpleOptionsPanel.getComponent();
|
||||||
|
assertTrue(comp.isShowing());
|
||||||
|
|
||||||
Component component = findPairedComponent(simpleOptionsPanel, "Favorite Color");
|
Component component = findPairedComponent(comp, "Favorite Color");
|
||||||
assertNotNull(component);
|
assertNotNull(component);
|
||||||
Rectangle rect = component.getBounds();
|
Rectangle rect = component.getBounds();
|
||||||
clickMouse(component, 1, rect.x, rect.y, 2, 0);
|
clickMouse(component, 1, rect.x, rect.y, 2, 0);
|
||||||
@@ -659,9 +670,10 @@ public class OptionsDialogTest extends AbstractGhidraHeadedIntegrationTest {
|
|||||||
ScrollableOptionsEditor simpleOptionsPanel =
|
ScrollableOptionsEditor simpleOptionsPanel =
|
||||||
(ScrollableOptionsEditor) getEditorPanel(toolNode);
|
(ScrollableOptionsEditor) getEditorPanel(toolNode);
|
||||||
assertNotNull(simpleOptionsPanel);
|
assertNotNull(simpleOptionsPanel);
|
||||||
assertTrue(simpleOptionsPanel.isShowing());
|
JComponent comp = simpleOptionsPanel.getComponent();
|
||||||
|
assertTrue(comp.isShowing());
|
||||||
|
|
||||||
Component canvas = findPairedComponent(simpleOptionsPanel, "Favorite Color");
|
Component canvas = findPairedComponent(comp, "Favorite Color");
|
||||||
assertNotNull(canvas);
|
assertNotNull(canvas);
|
||||||
Rectangle rect = canvas.getBounds();
|
Rectangle rect = canvas.getBounds();
|
||||||
clickMouse(canvas, 1, rect.x, rect.y, 2, 0);
|
clickMouse(canvas, 1, rect.x, rect.y, 2, 0);
|
||||||
@@ -846,7 +858,7 @@ public class OptionsDialogTest extends AbstractGhidraHeadedIntegrationTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void pressBrowseButton(ScrollableOptionsEditor editor, String optionName) {
|
private void pressBrowseButton(ScrollableOptionsEditor editor, String optionName) {
|
||||||
Component comp = findPairedComponent(editor, optionName);
|
Component comp = findPairedComponent(editor.getComponent(), optionName);
|
||||||
assertNotNull(comp);
|
assertNotNull(comp);
|
||||||
AbstractButton button = findAbstractButtonByName((Container) comp, "BrowseButton");
|
AbstractButton button = findAbstractButtonByName((Container) comp, "BrowseButton");
|
||||||
assertNotNull(button);
|
assertNotNull(button);
|
||||||
@@ -856,7 +868,7 @@ public class OptionsDialogTest extends AbstractGhidraHeadedIntegrationTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private JTextField getEditorTextField(ScrollableOptionsEditor editor, String optionName) {
|
private JTextField getEditorTextField(ScrollableOptionsEditor editor, String optionName) {
|
||||||
Component comp = findPairedComponent(editor, optionName);
|
Component comp = findPairedComponent(editor.getComponent(), optionName);
|
||||||
assertNotNull(comp);
|
assertNotNull(comp);
|
||||||
|
|
||||||
JTextField tf = findComponent((Container) comp, JTextField.class);
|
JTextField tf = findComponent((Container) comp, JTextField.class);
|
||||||
@@ -877,7 +889,7 @@ public class OptionsDialogTest extends AbstractGhidraHeadedIntegrationTest {
|
|||||||
|
|
||||||
ScrollableOptionsEditor editor = (ScrollableOptionsEditor) getEditorPanel(toolNode);
|
ScrollableOptionsEditor editor = (ScrollableOptionsEditor) getEditorPanel(toolNode);
|
||||||
assertNotNull(editor);
|
assertNotNull(editor);
|
||||||
assertTrue(editor.isShowing());
|
assertTrue(editor.getComponent().isShowing());
|
||||||
return editor;
|
return editor;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -905,7 +917,7 @@ public class OptionsDialogTest extends AbstractGhidraHeadedIntegrationTest {
|
|||||||
|
|
||||||
ScrollableOptionsEditor editor =
|
ScrollableOptionsEditor editor =
|
||||||
selectSubNodeWithDefaultEditor(parentNodeName, childNodeName);
|
selectSubNodeWithDefaultEditor(parentNodeName, childNodeName);
|
||||||
JCheckBox checkBox = (JCheckBox) findPairedComponent(editor, optionName);
|
JCheckBox checkBox = (JCheckBox) findPairedComponent(editor.getComponent(), optionName);
|
||||||
return checkBox.isSelected();
|
return checkBox.isSelected();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -914,7 +926,8 @@ public class OptionsDialogTest extends AbstractGhidraHeadedIntegrationTest {
|
|||||||
|
|
||||||
ScrollableOptionsEditor editor =
|
ScrollableOptionsEditor editor =
|
||||||
selectSubNodeWithDefaultEditor(parentNodeName, childNodeName);
|
selectSubNodeWithDefaultEditor(parentNodeName, childNodeName);
|
||||||
final JCheckBox checkBox = (JCheckBox) findPairedComponent(editor, optionName);
|
final JCheckBox checkBox =
|
||||||
|
(JCheckBox) findPairedComponent(editor.getComponent(), optionName);
|
||||||
runSwing(() -> checkBox.setSelected(newValue));
|
runSwing(() -> checkBox.setSelected(newValue));
|
||||||
assertEquals(newValue, checkBox.isSelected());
|
assertEquals(newValue, checkBox.isSelected());
|
||||||
}
|
}
|
||||||
@@ -923,7 +936,8 @@ public class OptionsDialogTest extends AbstractGhidraHeadedIntegrationTest {
|
|||||||
throws Exception {
|
throws Exception {
|
||||||
|
|
||||||
ScrollableOptionsEditor editor = selectNodeWithDefaultEditor(parentNodeName);
|
ScrollableOptionsEditor editor = selectNodeWithDefaultEditor(parentNodeName);
|
||||||
JTextField textField = (JTextField) findPairedComponent(editor, childNodeName);
|
JTextField textField =
|
||||||
|
(JTextField) findPairedComponent(editor.getComponent(), childNodeName);
|
||||||
return getText(textField);
|
return getText(textField);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -931,7 +945,8 @@ public class OptionsDialogTest extends AbstractGhidraHeadedIntegrationTest {
|
|||||||
String newValue) throws Exception {
|
String newValue) throws Exception {
|
||||||
|
|
||||||
ScrollableOptionsEditor editor = selectNodeWithDefaultEditor(parentNodeName);
|
ScrollableOptionsEditor editor = selectNodeWithDefaultEditor(parentNodeName);
|
||||||
JTextField textField = (JTextField) findPairedComponent(editor, childNodeName);
|
JTextField textField =
|
||||||
|
(JTextField) findPairedComponent(editor.getComponent(), childNodeName);
|
||||||
setText(textField, newValue);
|
setText(textField, newValue);
|
||||||
String updatedText = getText(textField);
|
String updatedText = getText(textField);
|
||||||
|
|
||||||
@@ -952,7 +967,7 @@ public class OptionsDialogTest extends AbstractGhidraHeadedIntegrationTest {
|
|||||||
|
|
||||||
ScrollableOptionsEditor editor = (ScrollableOptionsEditor) getEditorPanel(node);
|
ScrollableOptionsEditor editor = (ScrollableOptionsEditor) getEditorPanel(node);
|
||||||
assertNotNull(editor);
|
assertNotNull(editor);
|
||||||
assertTrue(editor.isShowing());
|
assertTrue(editor.getComponent().isShowing());
|
||||||
return editor;
|
return editor;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -975,7 +990,7 @@ public class OptionsDialogTest extends AbstractGhidraHeadedIntegrationTest {
|
|||||||
|
|
||||||
ScrollableOptionsEditor editor = (ScrollableOptionsEditor) getEditorPanel(childNode);
|
ScrollableOptionsEditor editor = (ScrollableOptionsEditor) getEditorPanel(childNode);
|
||||||
assertNotNull(editor);
|
assertNotNull(editor);
|
||||||
assertTrue(editor.isShowing());
|
assertTrue(editor.getComponent().isShowing());
|
||||||
return editor;
|
return editor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -30,13 +30,21 @@ import ghidra.program.model.listing.Program;
|
|||||||
import ghidra.program.model.pcode.*;
|
import ghidra.program.model.pcode.*;
|
||||||
import ghidra.service.graph.*;
|
import ghidra.service.graph.*;
|
||||||
import ghidra.util.Msg;
|
import ghidra.util.Msg;
|
||||||
import java.util.*;
|
import ghidra.util.WebColors;
|
||||||
import static ghidra.service.graph.GraphDisplay.*;
|
|
||||||
|
|
||||||
public class GraphAST extends GhidraScript {
|
public class GraphAST extends GhidraScript {
|
||||||
protected static final String COLOR_ATTRIBUTE = "Color";
|
private static final String SHAPE_ATTRIBUTE = "Shape";
|
||||||
protected static final String ICON_ATTRIBUTE = "Icon";
|
|
||||||
|
|
||||||
|
protected static final String DEFAULT = "Default";
|
||||||
|
protected static final String CONSTANT = "Constant";
|
||||||
|
protected static final String REGISTER = "Register";
|
||||||
|
protected static final String UNIQUE = "Unique";
|
||||||
|
protected static final String PERSISTENT = "Persistent";
|
||||||
|
protected static final String ADDRESS_TIED = "Address Tied";
|
||||||
|
protected static final String OP = "Op";
|
||||||
|
|
||||||
|
protected static final String TYPE_OUTPUT = "Output";
|
||||||
|
protected static final String TYPE_INPUT = "Input";
|
||||||
private Function func;
|
private Function func;
|
||||||
private AttributedGraph graph;
|
private AttributedGraph graph;
|
||||||
protected HighFunction high;
|
protected HighFunction high;
|
||||||
@@ -63,24 +71,48 @@ public class GraphAST extends GhidraScript {
|
|||||||
|
|
||||||
buildAST();
|
buildAST();
|
||||||
|
|
||||||
graph = new AttributedGraph();
|
GraphType graphType = new GraphTypeBuilder("AST")
|
||||||
|
.vertexType(DEFAULT)
|
||||||
|
.vertexType(CONSTANT)
|
||||||
|
.vertexType(REGISTER)
|
||||||
|
.vertexType(UNIQUE)
|
||||||
|
.vertexType(PERSISTENT)
|
||||||
|
.vertexType(ADDRESS_TIED)
|
||||||
|
.vertexType(OP)
|
||||||
|
.edgeType(DEFAULT)
|
||||||
|
.edgeType(TYPE_OUTPUT)
|
||||||
|
.edgeType(TYPE_INPUT)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
GraphDisplayOptions displayOptions = new GraphDisplayOptionsBuilder(graphType)
|
||||||
|
.vertexSelectionColor(WebColors.DEEP_PINK)
|
||||||
|
.edgeSelectionColor(WebColors.DEEP_PINK)
|
||||||
|
.defaultVertexColor(WebColors.RED)
|
||||||
|
.defaultEdgeColor(WebColors.NAVY)
|
||||||
|
.defaultVertexShape(VertexShape.ELLIPSE)
|
||||||
|
.defaultLayoutAlgorithm("Hierarchical MinCross Coffman Graham")
|
||||||
|
.useIcons(false)
|
||||||
|
.labelPosition(GraphLabelPosition.SOUTH)
|
||||||
|
.shapeOverrideAttribute(SHAPE_ATTRIBUTE)
|
||||||
|
.vertex(DEFAULT, VertexShape.ELLIPSE, WebColors.RED)
|
||||||
|
.vertex(CONSTANT, VertexShape.ELLIPSE, WebColors.DARK_GREEN)
|
||||||
|
.vertex(REGISTER, VertexShape.ELLIPSE, WebColors.NAVY)
|
||||||
|
.vertex(UNIQUE, VertexShape.ELLIPSE, WebColors.BLACK)
|
||||||
|
.vertex(PERSISTENT, VertexShape.ELLIPSE, WebColors.DARK_ORANGE)
|
||||||
|
.vertex(ADDRESS_TIED, VertexShape.ELLIPSE, WebColors.ORANGE)
|
||||||
|
.vertex(OP, VertexShape.RECTANGLE, WebColors.RED)
|
||||||
|
.edge(DEFAULT, WebColors.BLUE)
|
||||||
|
.edge(TYPE_OUTPUT, WebColors.BLACK)
|
||||||
|
.edge(TYPE_INPUT, WebColors.RED)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
graph = new AttributedGraph("AST Graph", graphType);
|
||||||
buildGraph();
|
buildGraph();
|
||||||
|
|
||||||
Map<String, String> properties = new HashMap<>();
|
GraphDisplay graphDisplay = graphDisplayBroker.getDefaultGraphDisplay(false, monitor);
|
||||||
properties.put(SELECTED_VERTEX_COLOR, "0xFF1493");
|
|
||||||
properties.put(SELECTED_EDGE_COLOR, "0xFF1493");
|
|
||||||
properties.put(INITIAL_LAYOUT_ALGORITHM, "Hierarchical MinCross Coffman Graham");
|
|
||||||
properties.put(DISPLAY_VERTICES_AS_ICONS, "false");
|
|
||||||
properties.put(VERTEX_LABEL_POSITION, "S");
|
|
||||||
properties.put(ENABLE_EDGE_SELECTION, "true");
|
|
||||||
GraphDisplay graphDisplay =
|
|
||||||
graphDisplayBroker.getDefaultGraphDisplay(false, properties, monitor);
|
|
||||||
// graphDisplay.defineVertexAttribute(CODE_ATTRIBUTE); //
|
|
||||||
// graphDisplay.defineVertexAttribute(SYMBOLS_ATTRIBUTE);
|
|
||||||
// graphDisplay.defineEdgeAttribute(EDGE_TYPE_ATTRIBUTE);
|
|
||||||
String description = "AST Data Flow Graph For " + func.getName();
|
|
||||||
|
|
||||||
graphDisplay.setGraph(graph, description, false, monitor);
|
String description = "AST Data Flow Graph For " + func.getName();
|
||||||
|
graphDisplay.setGraph(graph, displayOptions, description, false, monitor);
|
||||||
|
|
||||||
// Install a handler so the selection/location will map
|
// Install a handler so the selection/location will map
|
||||||
graphDisplay.setGraphDisplayListener(
|
graphDisplay.setGraphDisplayListener(
|
||||||
@@ -125,34 +157,33 @@ public class GraphAST extends GhidraScript {
|
|||||||
protected AttributedVertex createVarnodeVertex(VarnodeAST vn) {
|
protected AttributedVertex createVarnodeVertex(VarnodeAST vn) {
|
||||||
String name = vn.getAddress().toString(true);
|
String name = vn.getAddress().toString(true);
|
||||||
String id = getVarnodeKey(vn);
|
String id = getVarnodeKey(vn);
|
||||||
String colorattrib = "Red";
|
String vertexType = DEFAULT;
|
||||||
if (vn.isConstant()) {
|
if (vn.isConstant()) {
|
||||||
colorattrib = "DarkGreen";
|
vertexType = CONSTANT;
|
||||||
}
|
}
|
||||||
else if (vn.isRegister()) {
|
else if (vn.isRegister()) {
|
||||||
colorattrib = "Blue";
|
vertexType = REGISTER;
|
||||||
Register reg = func.getProgram().getRegister(vn.getAddress(), vn.getSize());
|
Register reg = func.getProgram().getRegister(vn.getAddress(), vn.getSize());
|
||||||
if (reg != null) {
|
if (reg != null) {
|
||||||
name = reg.getName();
|
name = reg.getName();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (vn.isUnique()) {
|
else if (vn.isUnique()) {
|
||||||
colorattrib = "Black";
|
vertexType = UNIQUE;
|
||||||
}
|
}
|
||||||
else if (vn.isPersistent()) {
|
else if (vn.isPersistent()) {
|
||||||
colorattrib = "DarkOrange";
|
vertexType = PERSISTENT;
|
||||||
}
|
}
|
||||||
else if (vn.isAddrTied()) {
|
else if (vn.isAddrTied()) {
|
||||||
colorattrib = "Orange";
|
vertexType = ADDRESS_TIED;
|
||||||
}
|
}
|
||||||
AttributedVertex vert = graph.addVertex(id, name);
|
AttributedVertex vert = graph.addVertex(id, name);
|
||||||
|
vert.setVertexType(vertexType);
|
||||||
|
|
||||||
|
// if it is an input override the shape to be a triangle
|
||||||
if (vn.isInput()) {
|
if (vn.isInput()) {
|
||||||
vert.setAttribute(ICON_ATTRIBUTE, "TriangleDown");
|
vert.setAttribute(SHAPE_ATTRIBUTE, VertexShape.TRIANGLE_DOWN.getName());
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
vert.setAttribute(ICON_ATTRIBUTE, "Circle");
|
|
||||||
}
|
|
||||||
vert.setAttribute(COLOR_ATTRIBUTE, colorattrib);
|
|
||||||
return vert;
|
return vert;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -176,7 +207,7 @@ public class GraphAST extends GhidraScript {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
AttributedVertex vert = graph.addVertex(id, name);
|
AttributedVertex vert = graph.addVertex(id, name);
|
||||||
vert.setAttribute(ICON_ATTRIBUTE, "Square");
|
vert.setVertexType(OP);
|
||||||
return vert;
|
return vert;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -192,7 +223,9 @@ public class GraphAST extends GhidraScript {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected AttributedEdge createEdge(AttributedVertex in, AttributedVertex out) {
|
protected AttributedEdge createEdge(AttributedVertex in, AttributedVertex out) {
|
||||||
return graph.addEdge(in, out);
|
AttributedEdge newEdge = graph.addEdge(in, out);
|
||||||
|
newEdge.setEdgeType(DEFAULT);
|
||||||
|
return newEdge;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void buildGraph() {
|
protected void buildGraph() {
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ public class GraphASTAndFlow extends GraphAST {
|
|||||||
}
|
}
|
||||||
if (prev != null && map.containsKey(prev) && map.containsKey(next)) {
|
if (prev != null && map.containsKey(prev) && map.containsKey(next)) {
|
||||||
AttributedEdge edge = createEdge(map.get(prev), map.get(next));
|
AttributedEdge edge = createEdge(map.get(prev), map.get(next));
|
||||||
edge.setAttribute(COLOR_ATTRIBUTE, "Black");
|
edge.setEdgeType(TYPE_OUTPUT);
|
||||||
}
|
}
|
||||||
prev = next;
|
prev = next;
|
||||||
}
|
}
|
||||||
@@ -92,7 +92,7 @@ public class GraphASTAndFlow extends GraphAST {
|
|||||||
PcodeBlock in = block.getIn(i);
|
PcodeBlock in = block.getIn(i);
|
||||||
if (last.containsKey(in)) {
|
if (last.containsKey(in)) {
|
||||||
AttributedEdge edge = createEdge(last.get(in), first.get(block));
|
AttributedEdge edge = createEdge(last.get(in), first.get(block));
|
||||||
edge.setAttribute(COLOR_ATTRIBUTE, "Red");
|
edge.setEdgeType(TYPE_INPUT);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -334,7 +334,7 @@ public class RecoverClassesFromRTTIScript extends GhidraScript {
|
|||||||
private AttributedGraph createGraph(List<RecoveredClass> recoveredClasses)
|
private AttributedGraph createGraph(List<RecoveredClass> recoveredClasses)
|
||||||
throws CancelledException {
|
throws CancelledException {
|
||||||
|
|
||||||
AttributedGraph g = new AttributedGraph();
|
AttributedGraph g = new AttributedGraph("Test Graph", new EmptyGraphType());
|
||||||
|
|
||||||
Iterator<RecoveredClass> recoveredClassIterator = recoveredClasses.iterator();
|
Iterator<RecoveredClass> recoveredClassIterator = recoveredClasses.iterator();
|
||||||
while (recoveredClassIterator.hasNext()) {
|
while (recoveredClassIterator.hasNext()) {
|
||||||
|
|||||||
+4
-4
@@ -15,11 +15,11 @@
|
|||||||
*/
|
*/
|
||||||
package ghidra.app.plugin.core.decompile.actions;
|
package ghidra.app.plugin.core.decompile.actions;
|
||||||
|
|
||||||
import static ghidra.app.plugin.core.decompile.actions.ASTGraphTask.GraphType.*;
|
import static ghidra.app.plugin.core.decompile.actions.ASTGraphTask.AstGraphSubType.*;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
import ghidra.app.plugin.core.decompile.actions.ASTGraphTask.GraphType;
|
import ghidra.app.plugin.core.decompile.actions.ASTGraphTask.AstGraphSubType;
|
||||||
import ghidra.app.plugin.core.graph.AddressBasedGraphDisplayListener;
|
import ghidra.app.plugin.core.graph.AddressBasedGraphDisplayListener;
|
||||||
import ghidra.framework.plugintool.PluginTool;
|
import ghidra.framework.plugintool.PluginTool;
|
||||||
import ghidra.program.model.address.*;
|
import ghidra.program.model.address.*;
|
||||||
@@ -33,10 +33,10 @@ import ghidra.util.exception.AssertException;
|
|||||||
*/
|
*/
|
||||||
public class ASTGraphDisplayListener extends AddressBasedGraphDisplayListener {
|
public class ASTGraphDisplayListener extends AddressBasedGraphDisplayListener {
|
||||||
private HighFunction hfunction;
|
private HighFunction hfunction;
|
||||||
private GraphType graphType;
|
private AstGraphSubType graphType;
|
||||||
|
|
||||||
ASTGraphDisplayListener(PluginTool tool, GraphDisplay display, HighFunction hfunction,
|
ASTGraphDisplayListener(PluginTool tool, GraphDisplay display, HighFunction hfunction,
|
||||||
GraphType graphType) {
|
AstGraphSubType graphType) {
|
||||||
super(tool, hfunction.getFunction().getProgram(), display);
|
super(tool, hfunction.getFunction().getProgram(), display);
|
||||||
this.hfunction = hfunction;
|
this.hfunction = hfunction;
|
||||||
this.graphType = graphType;
|
this.graphType = graphType;
|
||||||
|
|||||||
+36
-55
@@ -15,13 +15,15 @@
|
|||||||
*/
|
*/
|
||||||
package ghidra.app.plugin.core.decompile.actions;
|
package ghidra.app.plugin.core.decompile.actions;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import static ghidra.graph.ProgramGraphType.*;
|
||||||
|
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import docking.widgets.EventTrigger;
|
import docking.widgets.EventTrigger;
|
||||||
import ghidra.app.services.GraphDisplayBroker;
|
import ghidra.app.services.GraphDisplayBroker;
|
||||||
import ghidra.framework.plugintool.PluginTool;
|
import ghidra.framework.plugintool.PluginTool;
|
||||||
|
import ghidra.graph.ProgramGraphDisplayOptions;
|
||||||
|
import ghidra.graph.ProgramGraphType;
|
||||||
import ghidra.program.model.address.Address;
|
import ghidra.program.model.address.Address;
|
||||||
import ghidra.program.model.lang.Register;
|
import ghidra.program.model.lang.Register;
|
||||||
import ghidra.program.model.listing.Program;
|
import ghidra.program.model.listing.Program;
|
||||||
@@ -34,14 +36,12 @@ import ghidra.util.exception.GraphException;
|
|||||||
import ghidra.util.task.Task;
|
import ghidra.util.task.Task;
|
||||||
import ghidra.util.task.TaskMonitor;
|
import ghidra.util.task.TaskMonitor;
|
||||||
|
|
||||||
import static ghidra.service.graph.GraphDisplay.*;
|
|
||||||
|
|
||||||
public class ASTGraphTask extends Task {
|
public class ASTGraphTask extends Task {
|
||||||
enum GraphType {
|
enum AstGraphSubType {
|
||||||
CONTROL_FLOW_GRAPH("AST Control Flow"), DATA_FLOW_GRAPH("AST Data Flow");
|
CONTROL_FLOW_GRAPH("AST Control Flow"), DATA_FLOW_GRAPH("AST Data Flow");
|
||||||
private String name;
|
private String name;
|
||||||
|
|
||||||
GraphType(String name) {
|
AstGraphSubType(String name) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,35 +51,19 @@ public class ASTGraphTask extends Task {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static final String CODE_ATTRIBUTE = "Code";
|
private static final String CODE_ATTRIBUTE = "Code";
|
||||||
private static final String SYMBOLS_ATTRIBUTE = "Symbols";
|
|
||||||
private static final String VERTEX_TYPE_ATTRIBUTE = "VertexType";
|
|
||||||
|
|
||||||
// Vertex Types
|
|
||||||
private final static String ENTRY_NODE = "Entry";
|
|
||||||
// "1"; // beginning of a block, someone calls it
|
|
||||||
private final static String BODY_NODE = "Body";
|
|
||||||
// "2"; // Body block, no flow
|
|
||||||
private final static String EXIT_NODE = "Exit";
|
|
||||||
// "3"; // Terminator
|
|
||||||
private final static String SWITCH_NODE = "Switch";
|
|
||||||
// "4"; // Switch/computed jump
|
|
||||||
private final static String BAD_NODE = "Bad";
|
|
||||||
// "5"; // Bad destination
|
|
||||||
private final static String DATA_NODE = "Data";
|
|
||||||
// "6"; // Data Node, used for indirection
|
|
||||||
|
|
||||||
private GraphDisplayBroker graphService;
|
private GraphDisplayBroker graphService;
|
||||||
private boolean newGraph;
|
private boolean newGraph;
|
||||||
private int codeLimitPerBlock;
|
private int codeLimitPerBlock;
|
||||||
private Address location;
|
private Address location;
|
||||||
private HighFunction hfunction;
|
private HighFunction hfunction;
|
||||||
private GraphType graphType;
|
private AstGraphSubType astGraphType;
|
||||||
|
|
||||||
private int uniqueNum = 0;
|
private int uniqueNum = 0;
|
||||||
private PluginTool tool;
|
private PluginTool tool;
|
||||||
|
|
||||||
public ASTGraphTask(GraphDisplayBroker graphService, boolean newGraph, int codeLimitPerBlock,
|
public ASTGraphTask(GraphDisplayBroker graphService, boolean newGraph, int codeLimitPerBlock,
|
||||||
Address location, HighFunction hfunction, GraphType graphType, PluginTool tool) {
|
Address location, HighFunction hfunction, AstGraphSubType graphType, PluginTool tool) {
|
||||||
super("Graph " + graphType.getName(), true, false, true);
|
super("Graph " + graphType.getName(), true, false, true);
|
||||||
|
|
||||||
this.graphService = graphService;
|
this.graphService = graphService;
|
||||||
@@ -87,32 +71,30 @@ public class ASTGraphTask extends Task {
|
|||||||
this.codeLimitPerBlock = codeLimitPerBlock;
|
this.codeLimitPerBlock = codeLimitPerBlock;
|
||||||
this.location = location;
|
this.location = location;
|
||||||
this.hfunction = hfunction;
|
this.hfunction = hfunction;
|
||||||
this.graphType = graphType;
|
this.astGraphType = graphType;
|
||||||
this.tool = tool;
|
this.tool = tool;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void run(TaskMonitor monitor) {
|
public void run(TaskMonitor monitor) {
|
||||||
|
GraphType graphType = new AstGraphType();
|
||||||
|
|
||||||
// get a new graph
|
// get a new graph
|
||||||
AttributedGraph graph = new AttributedGraph();
|
AttributedGraph graph = new AttributedGraph(astGraphType.getName(), graphType);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
monitor.setMessage("Computing Graph...");
|
monitor.setMessage("Computing Graph...");
|
||||||
if (graphType == GraphType.DATA_FLOW_GRAPH) {
|
if (astGraphType == AstGraphSubType.DATA_FLOW_GRAPH) {
|
||||||
createDataFlowGraph(graph, monitor);
|
createDataFlowGraph(graph, monitor);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
createControlFlowGraph(graph, monitor);
|
createControlFlowGraph(graph, monitor);
|
||||||
}
|
}
|
||||||
Map<String, String> properties = new HashMap<>();
|
|
||||||
properties.put(SELECTED_VERTEX_COLOR, "0xFF1493");
|
GraphDisplay display =
|
||||||
properties.put(SELECTED_EDGE_COLOR, "0xFF1493");
|
graphService.getDefaultGraphDisplay(!newGraph, monitor);
|
||||||
properties.put(INITIAL_LAYOUT_ALGORITHM, "Hierarchical MinCross Coffman Graham");
|
|
||||||
properties.put(ENABLE_EDGE_SELECTION, "true");
|
|
||||||
GraphDisplay display = graphService.getDefaultGraphDisplay(!newGraph, properties, monitor);
|
|
||||||
ASTGraphDisplayListener displayListener =
|
ASTGraphDisplayListener displayListener =
|
||||||
new ASTGraphDisplayListener(tool, display, hfunction, graphType);
|
new ASTGraphDisplayListener(tool, display, hfunction, astGraphType);
|
||||||
display.setGraphDisplayListener(displayListener);
|
display.setGraphDisplayListener(displayListener);
|
||||||
|
|
||||||
monitor.setMessage("Obtaining handle to graph provider...");
|
monitor.setMessage("Obtaining handle to graph provider...");
|
||||||
@@ -122,20 +104,19 @@ public class ASTGraphTask extends Task {
|
|||||||
monitor.setCancelEnabled(false);
|
monitor.setCancelEnabled(false);
|
||||||
|
|
||||||
monitor.setMessage("Rendering Graph...");
|
monitor.setMessage("Rendering Graph...");
|
||||||
display.defineVertexAttribute(CODE_ATTRIBUTE);
|
|
||||||
display.defineVertexAttribute(SYMBOLS_ATTRIBUTE);
|
|
||||||
|
|
||||||
display.setVertexLabelAttribute(CODE_ATTRIBUTE, GraphDisplay.ALIGN_LEFT, 12, true,
|
|
||||||
graphType == GraphType.CONTROL_FLOW_GRAPH ? (codeLimitPerBlock + 1) : 1);
|
|
||||||
|
|
||||||
String description =
|
String description =
|
||||||
graphType == GraphType.DATA_FLOW_GRAPH ? "AST Data Flow" : "AST Control Flow";
|
astGraphType == AstGraphSubType.DATA_FLOW_GRAPH ? "AST Data Flow" : "AST Control Flow";
|
||||||
description = description + " for " + hfunction.getFunction().getName();
|
description = description + " for " + hfunction.getFunction().getName();
|
||||||
display.setGraph(graph, description, false, monitor);
|
GraphDisplayOptions graphDisplayOptions =
|
||||||
|
new ProgramGraphDisplayOptions(new AstGraphType(), tool);
|
||||||
|
graphDisplayOptions.setVertexLabelOverrideAttributeKey(CODE_ATTRIBUTE);
|
||||||
|
display.setGraph(graph, graphDisplayOptions, description, false, monitor);
|
||||||
setGraphLocation(display, displayListener);
|
setGraphLocation(display, displayListener);
|
||||||
}
|
}
|
||||||
catch (GraphException e) {
|
catch (GraphException e) {
|
||||||
Msg.showError(this, null, "Graph Error", e.getMessage());
|
Msg.showError(this, null, "Graph Error",
|
||||||
|
"Can't create graph display: " + e.getMessage());
|
||||||
}
|
}
|
||||||
catch (CancelledException e1) {
|
catch (CancelledException e1) {
|
||||||
return;
|
return;
|
||||||
@@ -230,20 +211,20 @@ public class ASTGraphTask extends Task {
|
|||||||
|
|
||||||
vertex.setAttribute(CODE_ATTRIBUTE, formatOpMnemonic(op));
|
vertex.setAttribute(CODE_ATTRIBUTE, formatOpMnemonic(op));
|
||||||
|
|
||||||
String vertexType = BODY_NODE;
|
String vertexType = BODY;
|
||||||
switch (op.getOpcode()) {
|
switch (op.getOpcode()) {
|
||||||
case PcodeOp.BRANCH:
|
case PcodeOp.BRANCH:
|
||||||
case PcodeOp.BRANCHIND:
|
case PcodeOp.BRANCHIND:
|
||||||
case PcodeOp.CBRANCH:
|
case PcodeOp.CBRANCH:
|
||||||
case PcodeOp.CALL:
|
case PcodeOp.CALL:
|
||||||
case PcodeOp.CALLIND:
|
case PcodeOp.CALLIND:
|
||||||
vertexType = SWITCH_NODE;
|
vertexType = SWITCH;
|
||||||
break;
|
break;
|
||||||
case PcodeOp.RETURN:
|
case PcodeOp.RETURN:
|
||||||
vertexType = EXIT_NODE;
|
vertexType = EXIT;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
vertex.setAttribute(VERTEX_TYPE_ATTRIBUTE, vertexType);
|
vertex.setVertexType(vertexType);
|
||||||
}
|
}
|
||||||
|
|
||||||
private AttributedVertex getDataVertex(AttributedGraph graph, Varnode node,
|
private AttributedVertex getDataVertex(AttributedGraph graph, Varnode node,
|
||||||
@@ -278,7 +259,7 @@ public class ASTGraphTask extends Task {
|
|||||||
}
|
}
|
||||||
label += translateVarnode(node, false);
|
label += translateVarnode(node, false);
|
||||||
vertex.setAttribute(CODE_ATTRIBUTE, label);
|
vertex.setAttribute(CODE_ATTRIBUTE, label);
|
||||||
vertex.setAttribute(VERTEX_TYPE_ATTRIBUTE, DATA_NODE);
|
vertex.setVertexType(ProgramGraphType.DATA);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void createControlFlowGraph(AttributedGraph graph, TaskMonitor monitor)
|
protected void createControlFlowGraph(AttributedGraph graph, TaskMonitor monitor)
|
||||||
@@ -322,7 +303,7 @@ public class ASTGraphTask extends Task {
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
vertex.setAttribute(CODE_ATTRIBUTE, "<???>");
|
vertex.setAttribute(CODE_ATTRIBUTE, "<???>");
|
||||||
vertex.setAttribute(VERTEX_TYPE_ATTRIBUTE, BAD_NODE);
|
vertex.setVertexType(BAD);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return vertex;
|
return vertex;
|
||||||
@@ -348,23 +329,23 @@ public class ASTGraphTask extends Task {
|
|||||||
vertex.setAttribute(CODE_ATTRIBUTE, buf.toString());
|
vertex.setAttribute(CODE_ATTRIBUTE, buf.toString());
|
||||||
|
|
||||||
// Establish vertex type
|
// Establish vertex type
|
||||||
String vertexType = BODY_NODE;
|
String vertexType = BODY;
|
||||||
if (basicBlk.getInSize() == 0) {
|
if (basicBlk.getInSize() == 0) {
|
||||||
vertexType = ENTRY_NODE;
|
vertexType = ENTRY;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
switch (basicBlk.getOutSize()) {
|
switch (basicBlk.getOutSize()) {
|
||||||
case 0:
|
case 0:
|
||||||
vertexType = EXIT_NODE;
|
vertexType = EXIT;
|
||||||
break;
|
break;
|
||||||
case 1:
|
case 1:
|
||||||
vertexType = BODY_NODE;
|
vertexType = BODY;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
vertexType = SWITCH_NODE;
|
vertexType = SWITCH;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
vertex.setAttribute(VERTEX_TYPE_ATTRIBUTE, vertexType);
|
vertex.setVertexType(vertexType);
|
||||||
}
|
}
|
||||||
|
|
||||||
private String formatOpMnemonic(PcodeOp op) {
|
private String formatOpMnemonic(PcodeOp op) {
|
||||||
|
|||||||
+26
@@ -0,0 +1,26 @@
|
|||||||
|
/* ###
|
||||||
|
* 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.decompile.actions;
|
||||||
|
|
||||||
|
import ghidra.graph.ProgramGraphType;
|
||||||
|
|
||||||
|
public class AstGraphType extends ProgramGraphType {
|
||||||
|
|
||||||
|
protected AstGraphType() {
|
||||||
|
super("AST", "Graph to show pcode for function");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+1
-1
@@ -15,7 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
package ghidra.app.plugin.core.decompile.actions;
|
package ghidra.app.plugin.core.decompile.actions;
|
||||||
|
|
||||||
import static ghidra.app.plugin.core.decompile.actions.ASTGraphTask.GraphType.*;
|
import static ghidra.app.plugin.core.decompile.actions.ASTGraphTask.AstGraphSubType.*;
|
||||||
|
|
||||||
import docking.action.MenuData;
|
import docking.action.MenuData;
|
||||||
import ghidra.app.plugin.core.decompile.DecompilerActionContext;
|
import ghidra.app.plugin.core.decompile.DecompilerActionContext;
|
||||||
|
|||||||
+35
@@ -248,6 +248,41 @@
|
|||||||
</UL>
|
</UL>
|
||||||
</BLOCKQUOTE>
|
</BLOCKQUOTE>
|
||||||
|
|
||||||
|
<H2><A name="Graph_Type_Display_Options">Graph Type Display Options</H2>
|
||||||
|
|
||||||
|
<BLOCKQUOTE>
|
||||||
|
<P>Graphs have a graph type which defines vertex types and edge types. Users can
|
||||||
|
configure the display properties for each vertex and edge type. These options have the
|
||||||
|
following subsections:</P>
|
||||||
|
|
||||||
|
<H3>Edge Colors</H3>
|
||||||
|
<BLOCKQUOTE>
|
||||||
|
<P>Allows setting the color for each edge type. Each Edge type will be listed with its
|
||||||
|
current color.</P>
|
||||||
|
</BLOCKQUOTE>
|
||||||
|
<H3>Miscellaneous</H3>
|
||||||
|
<UL>
|
||||||
|
<LI>Default Vertex Color - color for vertices with no defined vertex type</LI>
|
||||||
|
<LI>Default Vertex Shape - shape for vertices with no defined vertex type</LI>
|
||||||
|
<LI>Default Edge Color - color for edges with no defined edge type</LI>
|
||||||
|
<LI>Favored Edge - edge type to be favored by graph layout algorithms</LI>
|
||||||
|
</UL>
|
||||||
|
|
||||||
|
<H3>Vertex Colors</H3>
|
||||||
|
<BLOCKQUOTE>
|
||||||
|
<P>Allows setting the color for each vertex type. Each vertex type will be listed with
|
||||||
|
its current color.</P>
|
||||||
|
</BLOCKQUOTE>
|
||||||
|
<H3>Vertex Shapes</H3>
|
||||||
|
<BLOCKQUOTE>
|
||||||
|
<P>Allows setting the shape for each vertex type. Each vertex type will be listed with a
|
||||||
|
combo box for picking a supported shape. Supported shapes include Ellipse, Rectangle
|
||||||
|
Diamond, TriangleUp, TriangleDown, Star, Pentagon, Hexagon, and Octagon.</P>
|
||||||
|
</BLOCKQUOTE>
|
||||||
|
</BLOCKQUOTE>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<P class="providedbyplugin">Provided By: <I>GraphDisplayBrokerPlugin</I></P>
|
<P class="providedbyplugin">Provided By: <I>GraphDisplayBrokerPlugin</I></P>
|
||||||
|
|
||||||
<P class="relatedtopic">Related Topics:</P>
|
<P class="relatedtopic">Related Topics:</P>
|
||||||
|
|||||||
@@ -155,7 +155,7 @@ public class AttributeFilters implements ItemSelectable {
|
|||||||
|
|
||||||
// count up the unique attribute values (skipping the 'precluded names' we know we don't want)
|
// count up the unique attribute values (skipping the 'precluded names' we know we don't want)
|
||||||
for (Attributed element : elements) {
|
for (Attributed element : elements) {
|
||||||
Map<String, String> attributeMap = new HashMap<>(element.getAttributeMap());
|
Map<String, String> attributeMap = new HashMap<>(element.getAttributes());
|
||||||
for (Map.Entry<String, String> entry : attributeMap.entrySet()) {
|
for (Map.Entry<String, String> entry : attributeMap.entrySet()) {
|
||||||
if (!precludedNames.contains(entry.getKey())) {
|
if (!precludedNames.contains(entry.getKey())) {
|
||||||
multiset.add(entry.getValue());
|
multiset.add(entry.getValue());
|
||||||
|
|||||||
+7
-18
@@ -24,6 +24,7 @@ import docking.widgets.EventTrigger;
|
|||||||
import ghidra.app.services.GraphDisplayBroker;
|
import ghidra.app.services.GraphDisplayBroker;
|
||||||
import ghidra.framework.plugintool.PluginTool;
|
import ghidra.framework.plugintool.PluginTool;
|
||||||
import ghidra.service.graph.*;
|
import ghidra.service.graph.*;
|
||||||
|
import ghidra.util.exception.CancelledException;
|
||||||
import ghidra.util.task.TaskMonitor;
|
import ghidra.util.task.TaskMonitor;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -77,23 +78,6 @@ class ExportAttributedGraphDisplay implements GraphDisplay {
|
|||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void defineVertexAttribute(String attributeName) {
|
|
||||||
// no effect
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void defineEdgeAttribute(String attributeName) {
|
|
||||||
// no effect
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setVertexLabelAttribute(String attributeName, int alignment, int size,
|
|
||||||
boolean monospace,
|
|
||||||
int maxLines) {
|
|
||||||
// no effect
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setGraph(AttributedGraph graph, String title, boolean append,
|
public void setGraph(AttributedGraph graph, String title, boolean append,
|
||||||
TaskMonitor monitor) {
|
TaskMonitor monitor) {
|
||||||
@@ -102,6 +86,12 @@ class ExportAttributedGraphDisplay implements GraphDisplay {
|
|||||||
doSetGraphData(graph);
|
doSetGraphData(graph);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setGraph(AttributedGraph graph, GraphDisplayOptions options, String title,
|
||||||
|
boolean append, TaskMonitor monitor) throws CancelledException {
|
||||||
|
this.setGraph(graph, title, append, monitor);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* remove all vertices and edges from the {@link Graph}
|
* remove all vertices and edges from the {@link Graph}
|
||||||
*/
|
*/
|
||||||
@@ -149,5 +139,4 @@ class ExportAttributedGraphDisplay implements GraphDisplay {
|
|||||||
public void selectVertices(Set<AttributedVertex> vertexList, EventTrigger eventTrigger) {
|
public void selectVertices(Set<AttributedVertex> vertexList, EventTrigger eventTrigger) {
|
||||||
// not interactive, so N/A
|
// not interactive, so N/A
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -50,13 +50,13 @@ public class GraphMlGraphExporter extends AbstractAttributedGraphExporter {
|
|||||||
entry -> new DefaultAttribute<>(entry.getValue(), AttributeType.STRING))));
|
entry -> new DefaultAttribute<>(entry.getValue(), AttributeType.STRING))));
|
||||||
|
|
||||||
graph.vertexSet().stream()
|
graph.vertexSet().stream()
|
||||||
.map(Attributed::getAttributeMap)
|
.map(Attributed::getAttributes)
|
||||||
.flatMap(m -> m.entrySet().stream())
|
.flatMap(m -> m.entrySet().stream())
|
||||||
.map(Map.Entry::getKey)
|
.map(Map.Entry::getKey)
|
||||||
.forEach(key -> exporter.registerAttribute(key, GraphMLExporter.AttributeCategory.NODE, AttributeType.STRING));
|
.forEach(key -> exporter.registerAttribute(key, GraphMLExporter.AttributeCategory.NODE, AttributeType.STRING));
|
||||||
|
|
||||||
graph.edgeSet().stream()
|
graph.edgeSet().stream()
|
||||||
.map(Attributed::getAttributeMap)
|
.map(Attributed::getAttributes)
|
||||||
.flatMap(m -> m.entrySet().stream())
|
.flatMap(m -> m.entrySet().stream())
|
||||||
.map(Map.Entry::getKey)
|
.map(Map.Entry::getKey)
|
||||||
.forEach(key -> exporter.registerAttribute(key, GraphMLExporter.AttributeCategory.EDGE, AttributeType.STRING));
|
.forEach(key -> exporter.registerAttribute(key, GraphMLExporter.AttributeCategory.EDGE, AttributeType.STRING));
|
||||||
|
|||||||
+137
@@ -0,0 +1,137 @@
|
|||||||
|
/* ###
|
||||||
|
* IP: GHIDRA
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package ghidra.graph.visualization;
|
||||||
|
|
||||||
|
import java.awt.event.MouseEvent;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Map.Entry;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import javax.swing.JComponent;
|
||||||
|
import javax.swing.JToolTip;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.ArrayUtils;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.apache.commons.text.StringEscapeUtils;
|
||||||
|
|
||||||
|
import com.google.common.base.Splitter;
|
||||||
|
|
||||||
|
import ghidra.graph.viewer.popup.ToolTipInfo;
|
||||||
|
import ghidra.service.graph.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates tool tips for an {@link AttributedVertex} or {@link AttributedEdge} in
|
||||||
|
* an {@link AttributedGraph}
|
||||||
|
*/
|
||||||
|
public class AttributedToolTipInfo extends ToolTipInfo<Attributed> {
|
||||||
|
|
||||||
|
AttributedToolTipInfo(Attributed graphObject, MouseEvent event) {
|
||||||
|
super(event, graphObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public JComponent createToolTipComponent() {
|
||||||
|
if (graphObject == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
String toolTip = getToolTipText();
|
||||||
|
if (StringUtils.isBlank(toolTip)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
JToolTip jToolTip = new JToolTip();
|
||||||
|
jToolTip.setTipText(toolTip);
|
||||||
|
return jToolTip;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void emphasize() {
|
||||||
|
// this graph display does not have a notion of emphasizing
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void deEmphasize() {
|
||||||
|
// this graph display does not have a notion of emphasizing
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the tool tip for the graphObject this object manages
|
||||||
|
* @return the tool tip for the graphObject this object manages
|
||||||
|
*/
|
||||||
|
public String getToolTipText() {
|
||||||
|
String tooltipText = graphObject.getDescription();
|
||||||
|
if (tooltipText != null) {
|
||||||
|
return tooltipText;
|
||||||
|
}
|
||||||
|
|
||||||
|
StringBuilder buf = new StringBuilder();
|
||||||
|
buf.append("<HTML>");
|
||||||
|
|
||||||
|
if (graphObject instanceof AttributedVertex) {
|
||||||
|
addToolTipTextForVertex(buf, (AttributedVertex) graphObject);
|
||||||
|
}
|
||||||
|
else if (graphObject instanceof AttributedEdge) {
|
||||||
|
addToolTipTextForEdge(buf, (AttributedEdge) graphObject);
|
||||||
|
}
|
||||||
|
return buf.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addToolTipTextForVertex(StringBuilder buf, AttributedVertex vertex) {
|
||||||
|
String vertexType = vertex.getVertexType();
|
||||||
|
|
||||||
|
buf.append("<H4>");
|
||||||
|
buf.append(vertex.getName());
|
||||||
|
if (vertexType != null) {
|
||||||
|
buf.append("<br>");
|
||||||
|
buf.append("Type: " + vertexType);
|
||||||
|
}
|
||||||
|
buf.append("</H4>");
|
||||||
|
|
||||||
|
addAttributes(buf, AttributedVertex.NAME_KEY, AttributedVertex.VERTEX_TYPE_KEY);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addToolTipTextForEdge(StringBuilder buf, AttributedEdge edge) {
|
||||||
|
String edgeType = edge.getEdgeType();
|
||||||
|
if (edgeType != null) {
|
||||||
|
buf.append("<H4>");
|
||||||
|
buf.append("Type: " + edgeType);
|
||||||
|
buf.append("</H4>");
|
||||||
|
}
|
||||||
|
addAttributes(buf, AttributedEdge.EDGE_TYPE_KEY);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addAttributes(StringBuilder buf, String...excludedKeys) {
|
||||||
|
|
||||||
|
Set<Entry<String, String>> entries = graphObject.entrySet();
|
||||||
|
|
||||||
|
for (Map.Entry<String, String> entry : entries) {
|
||||||
|
String key = entry.getKey();
|
||||||
|
if (ArrayUtils.contains(excludedKeys, key)) {
|
||||||
|
continue; // skip keys handled in header
|
||||||
|
}
|
||||||
|
buf.append(key);
|
||||||
|
buf.append(": ");
|
||||||
|
String value = entry.getValue();
|
||||||
|
value = StringEscapeUtils.escapeHtml4(value);
|
||||||
|
String split = String.join("<br>", Splitter.on('\n').split(value));
|
||||||
|
split = split.replaceAll("\\s", " ");
|
||||||
|
buf.append(split);
|
||||||
|
buf.append("<br>");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,288 +0,0 @@
|
|||||||
/* ###
|
|
||||||
* IP: GHIDRA
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
package ghidra.graph.visualization;
|
|
||||||
|
|
||||||
import static java.util.Map.*;
|
|
||||||
|
|
||||||
import java.awt.Color;
|
|
||||||
import java.awt.Paint;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.regex.Matcher;
|
|
||||||
import java.util.regex.Pattern;
|
|
||||||
|
|
||||||
import ghidra.service.graph.Attributed;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* support for coercing colors from attributes or color names
|
|
||||||
*/
|
|
||||||
public abstract class Colors {
|
|
||||||
|
|
||||||
private static final Pattern HEX_PATTERN = Pattern.compile("(0x|#)[0-9A-Fa-f]{6}");
|
|
||||||
|
|
||||||
// cannot instantiate nor extend
|
|
||||||
private Colors() {
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* a map of well-known 'web' color names to colors
|
|
||||||
*/
|
|
||||||
static Map<String, Color> WEB_COLOR_MAP = Map.ofEntries(
|
|
||||||
entry("Black", Color.decode("0x000000")),
|
|
||||||
entry("Navy", Color.decode("0x000080")),
|
|
||||||
entry("DarkBlue", Color.decode("0x00008B")),
|
|
||||||
entry("MediumBlue", Color.decode("0x0000CD")),
|
|
||||||
entry("Blue", Color.decode("0x0000FF")),
|
|
||||||
entry("DarkGreen", Color.decode("0x006400")),
|
|
||||||
entry("Green", Color.decode("0x008000")),
|
|
||||||
entry("Teal", Color.decode("0x008080")),
|
|
||||||
entry("DarkCyan", Color.decode("0x008B8B")),
|
|
||||||
entry("DeepSkyBlue", Color.decode("0x00BFFF")),
|
|
||||||
entry("DarkTurquoise", Color.decode("0x00CED1")),
|
|
||||||
entry("MediumSpringGreen", Color.decode("0x00FA9A")),
|
|
||||||
entry("Lime", Color.decode("0x00FF00")),
|
|
||||||
entry("SpringGreen", Color.decode("0x00FF7F")),
|
|
||||||
entry("Aqua", Color.decode("0x00FFFF")),
|
|
||||||
entry("Cyan", Color.decode("0x00FFFF")),
|
|
||||||
entry("MidnightBlue", Color.decode("0x191970")),
|
|
||||||
entry("DodgerBlue", Color.decode("0x1E90FF")),
|
|
||||||
entry("LightSeaGreen", Color.decode("0x20B2AA")),
|
|
||||||
entry("ForestGreen", Color.decode("0x228B22")),
|
|
||||||
entry("SeaGreen", Color.decode("0x2E8B57")),
|
|
||||||
entry("DarkSlateGray", Color.decode("0x2F4F4F")),
|
|
||||||
entry("DarkSlateGrey", Color.decode("0x2F4F4F")),
|
|
||||||
entry("LimeGreen", Color.decode("0x32CD32")),
|
|
||||||
entry("MediumSeaGreen", Color.decode("0x3CB371")),
|
|
||||||
entry("Turquoise", Color.decode("0x40E0D0")),
|
|
||||||
entry("RoyalBlue", Color.decode("0x4169E1")),
|
|
||||||
entry("SteelBlue", Color.decode("0x4682B4")),
|
|
||||||
entry("DarkSlateBlue", Color.decode("0x483D8B")),
|
|
||||||
entry("MediumTurquoise", Color.decode("0x48D1CC")),
|
|
||||||
entry("Indigo", Color.decode("0x4B0082")),
|
|
||||||
entry("DarkOliveGreen", Color.decode("0x556B2F")),
|
|
||||||
entry("CadetBlue", Color.decode("0x5F9EA0")),
|
|
||||||
entry("CornflowerBlue", Color.decode("0x6495ED")),
|
|
||||||
entry("RebeccaPurple", Color.decode("0x663399")),
|
|
||||||
entry("MediumAquaMarine", Color.decode("0x66CDAA")),
|
|
||||||
entry("DimGray", Color.decode("0x696969")),
|
|
||||||
entry("DimGrey", Color.decode("0x696969")),
|
|
||||||
entry("SlateBlue", Color.decode("0x6A5ACD")),
|
|
||||||
entry("OliveDrab", Color.decode("0x6B8E23")),
|
|
||||||
entry("SlateGray", Color.decode("0x708090")),
|
|
||||||
entry("SlateGrey", Color.decode("0x708090")),
|
|
||||||
entry("LightSlateGray", Color.decode("0x778899")),
|
|
||||||
entry("LightSlateGrey", Color.decode("0x778899")),
|
|
||||||
entry("MediumSlateBlue", Color.decode("0x7B68EE")),
|
|
||||||
entry("LawnGreen", Color.decode("0x7CFC00")),
|
|
||||||
entry("Chartreuse", Color.decode("0x7FFF00")),
|
|
||||||
entry("Aquamarine", Color.decode("0x7FFFD4")),
|
|
||||||
entry("Maroon", Color.decode("0x800000")),
|
|
||||||
entry("Purple", Color.decode("0x800080")),
|
|
||||||
entry("Olive", Color.decode("0x808000")),
|
|
||||||
entry("Gray", Color.decode("0x808080")),
|
|
||||||
entry("Grey", Color.decode("0x808080")),
|
|
||||||
entry("SkyBlue", Color.decode("0x87CEEB")),
|
|
||||||
entry("LightSkyBlue", Color.decode("0x87CEFA")),
|
|
||||||
entry("BlueViolet", Color.decode("0x8A2BE2")),
|
|
||||||
entry("DarkRed", Color.decode("0x8B0000")),
|
|
||||||
entry("DarkMagenta", Color.decode("0x8B008B")),
|
|
||||||
entry("SaddleBrown", Color.decode("0x8B4513")),
|
|
||||||
entry("DarkSeaGreen", Color.decode("0x8FBC8F")),
|
|
||||||
entry("LightGreen", Color.decode("0x90EE90")),
|
|
||||||
entry("MediumPurple", Color.decode("0x9370DB")),
|
|
||||||
entry("DarkViolet", Color.decode("0x9400D3")),
|
|
||||||
entry("PaleGreen", Color.decode("0x98FB98")),
|
|
||||||
entry("DarkOrchid", Color.decode("0x9932CC")),
|
|
||||||
entry("YellowGreen", Color.decode("0x9ACD32")),
|
|
||||||
entry("Sienna", Color.decode("0xA0522D")),
|
|
||||||
entry("Brown", Color.decode("0xA52A2A")),
|
|
||||||
entry("DarkGray", Color.decode("0xA9A9A9")),
|
|
||||||
entry("DarkGrey", Color.decode("0xA9A9A9")),
|
|
||||||
entry("LightBlue", Color.decode("0xADD8E6")),
|
|
||||||
entry("GreenYellow", Color.decode("0xADFF2F")),
|
|
||||||
entry("PaleTurquoise", Color.decode("0xAFEEEE")),
|
|
||||||
entry("LightSteelBlue", Color.decode("0xB0C4DE")),
|
|
||||||
entry("PowderBlue", Color.decode("0xB0E0E6")),
|
|
||||||
entry("FireBrick", Color.decode("0xB22222")),
|
|
||||||
entry("DarkGoldenRod", Color.decode("0xB8860B")),
|
|
||||||
entry("MediumOrchid", Color.decode("0xBA55D3")),
|
|
||||||
entry("RosyBrown", Color.decode("0xBC8F8F")),
|
|
||||||
entry("DarkKhaki", Color.decode("0xBDB76B")),
|
|
||||||
entry("Silver", Color.decode("0xC0C0C0")),
|
|
||||||
entry("MediumVioletRed", Color.decode("0xC71585")),
|
|
||||||
entry("IndianRed", Color.decode("0xCD5C5C")),
|
|
||||||
entry("Peru", Color.decode("0xCD853F")),
|
|
||||||
entry("Chocolate", Color.decode("0xD2691E")),
|
|
||||||
entry("Tan", Color.decode("0xD2B48C")),
|
|
||||||
entry("LightGray", Color.decode("0xD3D3D3")),
|
|
||||||
entry("LightGrey", Color.decode("0xD3D3D3")),
|
|
||||||
entry("Thistle", Color.decode("0xD8BFD8")),
|
|
||||||
entry("Orchid", Color.decode("0xDA70D6")),
|
|
||||||
entry("GoldenRod", Color.decode("0xDAA520")),
|
|
||||||
entry("PaleVioletRed", Color.decode("0xDB7093")),
|
|
||||||
entry("Crimson", Color.decode("0xDC143C")),
|
|
||||||
entry("Gainsboro", Color.decode("0xDCDCDC")),
|
|
||||||
entry("Plum", Color.decode("0xDDA0DD")),
|
|
||||||
entry("BurlyWood", Color.decode("0xDEB887")),
|
|
||||||
entry("LightCyan", Color.decode("0xE0FFFF")),
|
|
||||||
entry("Lavender", Color.decode("0xE6E6FA")),
|
|
||||||
entry("DarkSalmon", Color.decode("0xE9967A")),
|
|
||||||
entry("Violet", Color.decode("0xEE82EE")),
|
|
||||||
entry("PaleGoldenRod", Color.decode("0xEEE8AA")),
|
|
||||||
entry("LightCoral", Color.decode("0xF08080")),
|
|
||||||
entry("Khaki", Color.decode("0xF0E68C")),
|
|
||||||
entry("AliceBlue", Color.decode("0xF0F8FF")),
|
|
||||||
entry("HoneyDew", Color.decode("0xF0FFF0")),
|
|
||||||
entry("Azure", Color.decode("0xF0FFFF")),
|
|
||||||
entry("SandyBrown", Color.decode("0xF4A460")),
|
|
||||||
entry("Wheat", Color.decode("0xF5DEB3")),
|
|
||||||
entry("Beige", Color.decode("0xF5F5DC")),
|
|
||||||
entry("WhiteSmoke", Color.decode("0xF5F5F5")),
|
|
||||||
entry("MintCream", Color.decode("0xF5FFFA")),
|
|
||||||
entry("GhostWhite", Color.decode("0xF8F8FF")),
|
|
||||||
entry("Salmon", Color.decode("0xFA8072")),
|
|
||||||
entry("AntiqueWhite", Color.decode("0xFAEBD7")),
|
|
||||||
entry("Linen", Color.decode("0xFAF0E6")),
|
|
||||||
entry("LightGoldenRodYellow", Color.decode("0xFAFAD2")),
|
|
||||||
entry("OldLace", Color.decode("0xFDF5E6")),
|
|
||||||
entry("Red", Color.decode("0xFF0000")),
|
|
||||||
entry("Fuchsia", Color.decode("0xFF00FF")),
|
|
||||||
entry("Magenta", Color.decode("0xFF00FF")),
|
|
||||||
entry("DeepPink", Color.decode("0xFF1493")),
|
|
||||||
entry("OrangeRed", Color.decode("0xFF4500")),
|
|
||||||
entry("Tomato", Color.decode("0xFF6347")),
|
|
||||||
entry("HotPink", Color.decode("0xFF69B4")),
|
|
||||||
entry("Coral", Color.decode("0xFF7F50")),
|
|
||||||
entry("DarkOrange", Color.decode("0xFF8C00")),
|
|
||||||
entry("LightSalmon", Color.decode("0xFFA07A")),
|
|
||||||
entry("Orange", Color.decode("0xFFA500")),
|
|
||||||
entry("LightPink", Color.decode("0xFFB6C1")),
|
|
||||||
entry("Pink", Color.decode("0xFFC0CB")),
|
|
||||||
entry("Gold", Color.decode("0xFFD700")),
|
|
||||||
entry("PeachPuff", Color.decode("0xFFDAB9")),
|
|
||||||
entry("NavajoWhite", Color.decode("0xFFDEAD")),
|
|
||||||
entry("Moccasin", Color.decode("0xFFE4B5")),
|
|
||||||
entry("Bisque", Color.decode("0xFFE4C4")),
|
|
||||||
entry("MistyRose", Color.decode("0xFFE4E1")),
|
|
||||||
entry("BlanchedAlmond", Color.decode("0xFFEBCD")),
|
|
||||||
entry("PapayaWhip", Color.decode("0xFFEFD5")),
|
|
||||||
entry("LavenderBlush", Color.decode("0xFFF0F5")),
|
|
||||||
entry("SeaShell", Color.decode("0xFFF5EE")),
|
|
||||||
entry("Cornsilk", Color.decode("0xFFF8DC")),
|
|
||||||
entry("LemonChiffon", Color.decode("0xFFFACD")),
|
|
||||||
entry("FloralWhite", Color.decode("0xFFFAF0")),
|
|
||||||
entry("Snow", Color.decode("0xFFFAFA")),
|
|
||||||
entry("Yellow", Color.decode("0xFFFF00")),
|
|
||||||
entry("LightYellow", Color.decode("0xFFFFE0")),
|
|
||||||
entry("Ivory", Color.decode("0xFFFFF0")),
|
|
||||||
entry("White", Color.decode("0xFFFFFF"))
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* a blue that is not as dark as {@code Color.blue}
|
|
||||||
*/
|
|
||||||
private static Color blue = new Color(100, 100, 255);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* a yellow that is darker than {@code Color.yellow}
|
|
||||||
*/
|
|
||||||
private static Color darkerYellow = new Color(225, 225, 0);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* these are vertex or edge types that have defined colors
|
|
||||||
* (the keys are the property values for the vertex/edge keys:
|
|
||||||
* VertexType and EdgeType)
|
|
||||||
*/
|
|
||||||
public static Map<String,Paint> VERTEX_TYPE_TO_COLOR_MAP =
|
|
||||||
Map.ofEntries(
|
|
||||||
entry("Body", blue),
|
|
||||||
entry("Entry", WEB_COLOR_MAP.get("DarkOrange")),
|
|
||||||
entry("Exit", Color.magenta),
|
|
||||||
entry("Switch", Color.cyan),
|
|
||||||
entry("Bad",Color.red),
|
|
||||||
entry("Entry-Nexus",Color.white),
|
|
||||||
entry("External",Color.green),
|
|
||||||
entry("Folder",WEB_COLOR_MAP.get("DarkOrange")),
|
|
||||||
entry("Fragment",WEB_COLOR_MAP.get("Purple")),
|
|
||||||
entry("Data",Color.pink)
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* these are vertex or edge types that have defined colors
|
|
||||||
* (the keys are the property values for the vertex/edge keys:
|
|
||||||
* VertexType and EdgeType)
|
|
||||||
*/
|
|
||||||
public static Map<String,Paint> EDGE_TYPE_TO_COLOR_MAP =
|
|
||||||
Map.ofEntries(
|
|
||||||
|
|
||||||
entry("Entry", Color.gray), // white??
|
|
||||||
entry("Fall-Through", Color.blue),
|
|
||||||
entry("Conditional-Call", WEB_COLOR_MAP.get("DarkOrange")),
|
|
||||||
entry("Unconditional-Call", WEB_COLOR_MAP.get("DarkOrange")),
|
|
||||||
entry("Computed",Color.cyan),
|
|
||||||
entry("Indirection",Color.pink),
|
|
||||||
entry("Unconditional-Jump", Color.green),
|
|
||||||
entry("Conditional-Jump", darkerYellow),
|
|
||||||
entry("Terminator", WEB_COLOR_MAP.get("Purple")),
|
|
||||||
entry("Conditional-Return", WEB_COLOR_MAP.get("Purple"))
|
|
||||||
);
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Determine a color for the given {@link Attributed} object.
|
|
||||||
* <P>
|
|
||||||
* The attributed object can be an vertex or an edge. This method examines the attributes
|
|
||||||
* and tries to find an attribute that has a color mapping. Otherwise it returns a default
|
|
||||||
* color
|
|
||||||
* @param attributed the vertex or edge for which to determine a color
|
|
||||||
* @return the color to paint the given Attributed
|
|
||||||
*/
|
|
||||||
public static Paint getColor(Attributed attributed) {
|
|
||||||
Map<String, String> map = attributed.getAttributeMap();
|
|
||||||
// if there is a 'VertexType' attribute key, use its value to choose a predefined color
|
|
||||||
if (map.containsKey("VertexType")) {
|
|
||||||
String typeValue = map.get("VertexType");
|
|
||||||
return VERTEX_TYPE_TO_COLOR_MAP.getOrDefault(typeValue, Color.blue);
|
|
||||||
}
|
|
||||||
// if there is an 'EdgeType' attribute key, use its value to choose a predefined color
|
|
||||||
if (map.containsKey("EdgeType")) {
|
|
||||||
String typeValue = map.get("EdgeType");
|
|
||||||
return EDGE_TYPE_TO_COLOR_MAP.getOrDefault(typeValue, Color.green);
|
|
||||||
}
|
|
||||||
// if there is a 'Color' attribute key, use its value (either a color name or an RGB hex string)
|
|
||||||
// to choose a color
|
|
||||||
if (map.containsKey("Color")) {
|
|
||||||
String colorName = map.get("Color");
|
|
||||||
if (WEB_COLOR_MAP.containsKey(colorName)) {
|
|
||||||
return WEB_COLOR_MAP.get(colorName);
|
|
||||||
}
|
|
||||||
// if the value matches an RGB hex string, turn that into a color
|
|
||||||
Color c = getHexColor(colorName);
|
|
||||||
if (c != null) {
|
|
||||||
return c;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// default value when nothing else matches
|
|
||||||
return Color.green;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Color getHexColor(String hexString) {
|
|
||||||
Matcher matcher = HEX_PATTERN.matcher(hexString);
|
|
||||||
if (matcher.matches()) {
|
|
||||||
return Color.decode(hexString);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+137
-237
File diff suppressed because it is too large
Load Diff
+2
-10
@@ -15,9 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
package ghidra.graph.visualization;
|
package ghidra.graph.visualization;
|
||||||
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import ghidra.framework.options.Options;
|
import ghidra.framework.options.Options;
|
||||||
@@ -54,13 +52,7 @@ public class DefaultGraphDisplayProvider implements GraphDisplayProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public GraphDisplay getGraphDisplay(boolean reuseGraph, TaskMonitor monitor) {
|
public GraphDisplay getGraphDisplay(boolean reuseGraph, TaskMonitor monitor) {
|
||||||
return getGraphDisplay(reuseGraph, Collections.emptyMap(), monitor);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public GraphDisplay getGraphDisplay(boolean reuseGraph, Map<String, String> properties,
|
|
||||||
TaskMonitor monitor) {
|
|
||||||
|
|
||||||
if (reuseGraph && !displays.isEmpty()) {
|
if (reuseGraph && !displays.isEmpty()) {
|
||||||
DefaultGraphDisplay visibleGraph = getVisibleGraph();
|
DefaultGraphDisplay visibleGraph = getVisibleGraph();
|
||||||
@@ -69,7 +61,7 @@ public class DefaultGraphDisplayProvider implements GraphDisplayProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
DefaultGraphDisplay display =
|
DefaultGraphDisplay display =
|
||||||
Swing.runNow(() -> new DefaultGraphDisplay(this, properties, displayCounter++));
|
Swing.runNow(() -> new DefaultGraphDisplay(this, displayCounter++));
|
||||||
displays.add(display);
|
displays.add(display);
|
||||||
return display;
|
return display;
|
||||||
}
|
}
|
||||||
|
|||||||
+326
@@ -0,0 +1,326 @@
|
|||||||
|
/* ###
|
||||||
|
* IP: GHIDRA
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package ghidra.graph.visualization;
|
||||||
|
|
||||||
|
import static org.jungrapht.visualization.renderers.BiModalRenderer.*;
|
||||||
|
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.geom.AffineTransform;
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
import javax.swing.border.Border;
|
||||||
|
|
||||||
|
import org.jungrapht.visualization.RenderContext;
|
||||||
|
import org.jungrapht.visualization.VisualizationViewer;
|
||||||
|
import org.jungrapht.visualization.decorators.*;
|
||||||
|
import org.jungrapht.visualization.layout.algorithms.util.InitialDimensionFunction;
|
||||||
|
import org.jungrapht.visualization.renderers.*;
|
||||||
|
import org.jungrapht.visualization.renderers.Renderer;
|
||||||
|
import org.jungrapht.visualization.renderers.Renderer.VertexLabel.Position;
|
||||||
|
import org.jungrapht.visualization.util.RectangleUtils;
|
||||||
|
|
||||||
|
import generic.util.image.ImageUtils;
|
||||||
|
import ghidra.service.graph.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles the rendering of graphs for the {@link DefaultGraphDisplay}
|
||||||
|
*/
|
||||||
|
public class DefaultGraphRenderer implements GraphRenderer {
|
||||||
|
private static final int DEFAULT_MARGIN_BORDER_SIZE = 4;
|
||||||
|
private static final int DEFAULT_STROKE_THICKNESS = 6;
|
||||||
|
private static final int ICON_ZOOM = 5;
|
||||||
|
|
||||||
|
private int labelBorderSize = DEFAULT_MARGIN_BORDER_SIZE;
|
||||||
|
private int strokeThickness = DEFAULT_STROKE_THICKNESS;
|
||||||
|
private JLabel label;
|
||||||
|
|
||||||
|
private GraphDisplayOptions options;
|
||||||
|
private final Map<AttributedVertex, Icon> iconCache = new ConcurrentHashMap<>();
|
||||||
|
private final Map<RenderingHints.Key, Object> renderingHints = new HashMap<>();
|
||||||
|
private Stroke edgeStroke = new BasicStroke(4.0f);
|
||||||
|
public DefaultGraphRenderer() {
|
||||||
|
this(new DefaultGraphDisplayOptions());
|
||||||
|
}
|
||||||
|
|
||||||
|
public DefaultGraphRenderer(GraphDisplayOptions options) {
|
||||||
|
this.options = options;
|
||||||
|
renderingHints.put(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
||||||
|
label = new JLabel();
|
||||||
|
label.setForeground(Color.black);
|
||||||
|
label.setBackground(Color.white);
|
||||||
|
label.setOpaque(false);
|
||||||
|
Border marginBorder = BorderFactory.createEmptyBorder(labelBorderSize, 2 * labelBorderSize,
|
||||||
|
labelBorderSize, 2 * labelBorderSize);
|
||||||
|
label.setBorder(marginBorder);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setGraphTypeDisplayOptions(GraphDisplayOptions options) {
|
||||||
|
this.options = options;
|
||||||
|
clearCache();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public GraphDisplayOptions getGraphDisplayOptions() {
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void clearCache() {
|
||||||
|
iconCache.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void initializeViewer(VisualizationViewer<AttributedVertex, AttributedEdge> viewer) {
|
||||||
|
|
||||||
|
RenderContext<AttributedVertex, AttributedEdge> renderContext = viewer.getRenderContext();
|
||||||
|
Function<Shape, org.jungrapht.visualization.layout.model.Rectangle> toRectangle =
|
||||||
|
s -> RectangleUtils.convert(s.getBounds2D());
|
||||||
|
|
||||||
|
if (options.usesIcons()) {
|
||||||
|
// set up the shape and color functions
|
||||||
|
IconShapeFunction<AttributedVertex> nodeShaper =
|
||||||
|
new IconShapeFunction<>(new EllipseShapeFunction<>());
|
||||||
|
|
||||||
|
nodeShaper.setIconFunction(this::getIcon);
|
||||||
|
renderContext.setVertexShapeFunction(nodeShaper);
|
||||||
|
renderContext.setVertexIconFunction(this::getIcon);
|
||||||
|
int arrowLength = options.getArrowLength() * ICON_ZOOM;
|
||||||
|
int arrowWidth = (int) (arrowLength * 1.3);
|
||||||
|
renderContext.setEdgeArrowWidth(arrowWidth);
|
||||||
|
renderContext.setEdgeArrowLength(arrowLength);
|
||||||
|
renderContext.setVertexLabelFunction(v -> "");
|
||||||
|
viewer.setInitialDimensionFunction(
|
||||||
|
InitialDimensionFunction.builder(nodeShaper.andThen(toRectangle)).build());
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
renderContext.setVertexIconFunction(null);
|
||||||
|
renderContext.setVertexShapeFunction(this::getVertexShape);
|
||||||
|
viewer.setInitialDimensionFunction(InitialDimensionFunction
|
||||||
|
.builder(renderContext.getVertexShapeFunction().andThen(toRectangle))
|
||||||
|
.build());
|
||||||
|
renderContext.setVertexLabelFunction(Object::toString);
|
||||||
|
GraphLabelPosition labelPosition = options.getLabelPosition();
|
||||||
|
renderContext.setVertexLabelPosition(getJungraphTPosition(labelPosition));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// assign the shapes to the modal renderer
|
||||||
|
// the modal renderer optimizes rendering for large graphs by removing detail
|
||||||
|
ModalRenderer<AttributedVertex, AttributedEdge> modalRenderer = viewer.getRenderer();
|
||||||
|
Renderer.Vertex<AttributedVertex, AttributedEdge> lightWeightRenderer =
|
||||||
|
modalRenderer.getVertexRenderer(LIGHTWEIGHT);
|
||||||
|
|
||||||
|
|
||||||
|
// set the lightweight (optimized) renderer to use the vertex shapes instead
|
||||||
|
// of using default shapes.
|
||||||
|
if (lightWeightRenderer instanceof LightweightVertexRenderer) {
|
||||||
|
LightweightVertexRenderer<AttributedVertex, AttributedEdge> lightweightVertexRenderer =
|
||||||
|
(LightweightVertexRenderer<AttributedVertex, AttributedEdge>) lightWeightRenderer;
|
||||||
|
|
||||||
|
Function<AttributedVertex, Shape> vertexShapeFunction =
|
||||||
|
renderContext.getVertexShapeFunction();
|
||||||
|
lightweightVertexRenderer.setVertexShapeFunction(vertexShapeFunction);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderContext.setVertexFontFunction(this::getFont);
|
||||||
|
renderContext.setVertexLabelRenderer(new JLabelVertexLabelRenderer(Color.black));
|
||||||
|
renderContext.setVertexDrawPaintFunction(this::getVertexColor);
|
||||||
|
renderContext.setVertexFillPaintFunction(this::getVertexColor);
|
||||||
|
renderContext.setVertexStrokeFunction(n -> new BasicStroke(3.0f));
|
||||||
|
|
||||||
|
renderContext.setEdgeStrokeFunction(this::getEdgeStroke);
|
||||||
|
renderContext.setEdgeDrawPaintFunction(this::getEdgeColor);
|
||||||
|
renderContext.setArrowDrawPaintFunction(this::getEdgeColor);
|
||||||
|
renderContext.setArrowFillPaintFunction(this::getEdgeColor);
|
||||||
|
renderContext.setEdgeShapeFunction(EdgeShape.line());
|
||||||
|
}
|
||||||
|
|
||||||
|
private Shape getVertexShape(AttributedVertex vertex) {
|
||||||
|
if (vertex instanceof GroupVertex) {
|
||||||
|
return VertexShape.STAR.getShape();
|
||||||
|
}
|
||||||
|
VertexShape vertexShape = options.getVertexShape(vertex);
|
||||||
|
return vertexShape != null ? vertexShape.getShape() : VertexShape.RECTANGLE.getShape();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Position getJungraphTPosition(GraphLabelPosition labelPosition) {
|
||||||
|
switch (labelPosition) {
|
||||||
|
case CENTER:
|
||||||
|
return Position.CNTR;
|
||||||
|
case EAST:
|
||||||
|
return Position.E;
|
||||||
|
case NORTH:
|
||||||
|
return Position.N;
|
||||||
|
case NORTHEAST:
|
||||||
|
return Position.NE;
|
||||||
|
case NORTHWEST:
|
||||||
|
return Position.NW;
|
||||||
|
case SOUTH:
|
||||||
|
return Position.S;
|
||||||
|
case SOUTHEAST:
|
||||||
|
return Position.SE;
|
||||||
|
case SOUTHWEST:
|
||||||
|
return Position.SW;
|
||||||
|
case WEST:
|
||||||
|
return Position.W;
|
||||||
|
default:
|
||||||
|
return Position.AUTO;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Color getVertexColor(AttributedVertex vertex) {
|
||||||
|
return options.getVertexColor(vertex);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Color getEdgeColor(AttributedEdge edge) {
|
||||||
|
return options.getEdgeColor(edge);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Icon getIcon(AttributedVertex vertex) {
|
||||||
|
|
||||||
|
// WARNING: very important to not use map's computeIfAbsent() method
|
||||||
|
// because the map is synchronized and the createIcon() method will
|
||||||
|
// attempt to acquire the AWT lock. That combination will cause a deadlock
|
||||||
|
// if computeIfAbsent() is used and this method is called from non-swing thread.
|
||||||
|
Icon icon = iconCache.get(vertex);
|
||||||
|
if (icon == null) {
|
||||||
|
icon = createIcon(vertex);
|
||||||
|
iconCache.put(vertex, icon);
|
||||||
|
}
|
||||||
|
return icon;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Icon createIcon(AttributedVertex vertex) {
|
||||||
|
VertexShape vertexShape = options.getVertexShape(vertex);
|
||||||
|
Color vertexColor = options.getVertexColor(vertex);
|
||||||
|
String labelText = options.getVertexLabel(vertex);
|
||||||
|
|
||||||
|
return createImage(vertexShape, labelText, vertexColor);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void vertexChanged(AttributedVertex vertex) {
|
||||||
|
iconCache.remove(vertex);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ImageIcon createImage(VertexShape vertexShape, String vertexName, Color vertexColor) {
|
||||||
|
prepareLabel(vertexName, vertexColor);
|
||||||
|
|
||||||
|
Shape unitShape = vertexShape.getShape();
|
||||||
|
Rectangle bounds = unitShape.getBounds();
|
||||||
|
|
||||||
|
int maxWidthToHeightRatio = vertexShape.getMaxWidthToHeightRatio();
|
||||||
|
double sizeFactor = vertexShape.getShapeToLabelRatio();
|
||||||
|
|
||||||
|
int labelWidth = label.getWidth();
|
||||||
|
int labelHeight = label.getHeight();
|
||||||
|
|
||||||
|
int iconWidth =
|
||||||
|
(int) (Math.max(labelWidth, labelHeight * 2.0) * sizeFactor) + strokeThickness;
|
||||||
|
int iconHeight =
|
||||||
|
(int) (Math.max(label.getHeight(), labelWidth / maxWidthToHeightRatio) * sizeFactor) +
|
||||||
|
strokeThickness;
|
||||||
|
|
||||||
|
|
||||||
|
double scalex = iconWidth / bounds.getWidth();
|
||||||
|
double scaley = iconHeight / bounds.getHeight();
|
||||||
|
|
||||||
|
|
||||||
|
Shape scaledShape =
|
||||||
|
AffineTransform.getScaleInstance(scalex, scaley).createTransformedShape(unitShape);
|
||||||
|
|
||||||
|
double labelOffsetRatio = vertexShape.getLabelPosition();
|
||||||
|
|
||||||
|
bounds = scaledShape.getBounds();
|
||||||
|
|
||||||
|
int width = bounds.width + 2 * strokeThickness;
|
||||||
|
int height = bounds.height + strokeThickness;
|
||||||
|
BufferedImage bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
|
||||||
|
|
||||||
|
|
||||||
|
Graphics2D graphics = bufferedImage.createGraphics();
|
||||||
|
graphics.setRenderingHints(renderingHints);
|
||||||
|
AffineTransform graphicsTransform = graphics.getTransform();
|
||||||
|
|
||||||
|
graphics.translate(-bounds.x + strokeThickness, -bounds.y + strokeThickness / 2);
|
||||||
|
graphics.setPaint(Color.WHITE);
|
||||||
|
graphics.fill(scaledShape);
|
||||||
|
graphics.setPaint(vertexColor);
|
||||||
|
graphics.setStroke(new BasicStroke(strokeThickness));
|
||||||
|
graphics.draw(scaledShape);
|
||||||
|
|
||||||
|
|
||||||
|
graphics.setTransform(graphicsTransform);
|
||||||
|
int xOffset = (width - label.getWidth()) / 2;
|
||||||
|
int yOffset = (int) ((height - label.getHeight()) * labelOffsetRatio);
|
||||||
|
graphics.translate(xOffset, yOffset);
|
||||||
|
graphics.setPaint(Color.black);
|
||||||
|
label.paint(graphics);
|
||||||
|
|
||||||
|
graphics.setTransform(graphicsTransform); // restore the original transform
|
||||||
|
graphics.dispose();
|
||||||
|
Image scaledImage =
|
||||||
|
ImageUtils.createScaledImage(bufferedImage, width * ICON_ZOOM, height * ICON_ZOOM,
|
||||||
|
Image.SCALE_FAST);
|
||||||
|
|
||||||
|
ImageIcon imageIcon = new ImageIcon(scaledImage);
|
||||||
|
return imageIcon;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void prepareLabel(String vertexName, Color vertexColor) {
|
||||||
|
label.setFont(options.getFont());
|
||||||
|
label.setText(vertexName);
|
||||||
|
Dimension labelSize = label.getPreferredSize();
|
||||||
|
label.setSize(labelSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getFavoredEdgeType() {
|
||||||
|
return options.getFavoredEdgeType();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Integer getEdgePriority(String edgeType) {
|
||||||
|
return options.getEdgePriority(edgeType);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Stroke getEdgeStroke(AttributedEdge edge) {
|
||||||
|
return edgeStroke;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Color getVertexSelectionColor() {
|
||||||
|
return options.getVertexSelectionColor();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Color getEdgeSelectionColor() {
|
||||||
|
return options.getEdgeSelectionColor();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Font getFont(AttributedVertex attributedvertex1) {
|
||||||
|
return options.getFont();
|
||||||
|
}
|
||||||
|
}
|
||||||
+26
-28
@@ -15,44 +15,42 @@
|
|||||||
*/
|
*/
|
||||||
package ghidra.graph.visualization;
|
package ghidra.graph.visualization;
|
||||||
|
|
||||||
import ghidra.service.graph.AttributedEdge;
|
|
||||||
|
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
import ghidra.service.graph.AttributedEdge;
|
||||||
import java.util.Map;
|
import ghidra.service.graph.GraphType;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@code Comparator} to order {@code AttributedEdge}s based on their position in a
|
* Edge comparator that compares edges based on their edge type. The default renderer will use
|
||||||
* supplied {@code List}.
|
* the order in which the edge types were defined in the {@link GraphType}.
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
public class EdgeComparator implements Comparator<AttributedEdge> {
|
public class EdgeComparator implements Comparator<AttributedEdge> {
|
||||||
|
|
||||||
/**
|
private GraphRenderer renderer;
|
||||||
* {@code Map} of EdgeType attribute value to integer priority
|
|
||||||
*/
|
|
||||||
private Map<String, Integer> edgePriorityMap = new HashMap();
|
|
||||||
|
|
||||||
/**
|
public EdgeComparator(GraphRenderer renderer) {
|
||||||
* Create an instance and place the list values into the {@code edgePriorityMap}
|
this.renderer = renderer;
|
||||||
* with a one-up counter expressing their relative priority
|
|
||||||
* @param edgePriorityList
|
|
||||||
*/
|
|
||||||
public EdgeComparator(List<String> edgePriorityList) {
|
|
||||||
edgePriorityList.forEach(s -> edgePriorityMap.put(s, edgePriorityList.indexOf(s)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
* Compares the {@code AttributedEdge}s using their priority in the supplied {@code edgePriorityMap}
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public int compare(AttributedEdge edgeOne, AttributedEdge edgeTwo) {
|
public int compare(AttributedEdge edge1, AttributedEdge edge2) {
|
||||||
return priority(edgeOne).compareTo(priority(edgeTwo));
|
String edgeType1 = edge1.getEdgeType();
|
||||||
|
String edgeType2 = edge2.getEdgeType();
|
||||||
|
|
||||||
|
if (edgeType1 == null && edgeType2 == null) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (edgeType1 == null) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
if (edgeType2 == null) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
Integer priority1 = renderer.getEdgePriority(edgeType1);
|
||||||
|
Integer priority2 = renderer.getEdgePriority(edgeType2);
|
||||||
|
|
||||||
|
return priority1.compareTo(priority2);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Integer priority(AttributedEdge e) {
|
|
||||||
return edgePriorityMap.getOrDefault(e.getAttribute("EdgeType"), 0);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
-210
@@ -1,210 +0,0 @@
|
|||||||
/* ###
|
|
||||||
* IP: GHIDRA
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
package ghidra.graph.visualization;
|
|
||||||
|
|
||||||
import java.awt.*;
|
|
||||||
import java.awt.geom.AffineTransform;
|
|
||||||
import java.awt.image.BufferedImage;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
|
||||||
|
|
||||||
import javax.swing.*;
|
|
||||||
import javax.swing.border.Border;
|
|
||||||
import javax.swing.border.CompoundBorder;
|
|
||||||
|
|
||||||
import ghidra.service.graph.AttributedVertex;
|
|
||||||
|
|
||||||
public class GhidraIconCache {
|
|
||||||
|
|
||||||
private static final int DEFAULT_STROKE_THICKNESS = 12;
|
|
||||||
private static final int DEFAULT_FONT_SIZE = 12;
|
|
||||||
private static final String DEFAULT_FONT_NAME = "Dialog";
|
|
||||||
private static final int DEFAULT_MARGIN_BORDER_SIZE = 8;
|
|
||||||
private static final float LABEL_TO_ICON_PROPORTION = 1.1f;
|
|
||||||
private final JLabel rendererLabel = new JLabel();
|
|
||||||
private final Map<RenderingHints.Key, Object> renderingHints = new HashMap<>();
|
|
||||||
private int strokeThickness = DEFAULT_STROKE_THICKNESS;
|
|
||||||
|
|
||||||
private final Map<AttributedVertex, Icon> map = new ConcurrentHashMap<>();
|
|
||||||
|
|
||||||
private final IconShape.Function iconShapeFunction = new IconShape.Function();
|
|
||||||
private String preferredVeretxLabelAttribute = null;
|
|
||||||
|
|
||||||
Icon get(AttributedVertex vertex) {
|
|
||||||
|
|
||||||
// WARNING: very important to not use map's computeIfAbsent() method
|
|
||||||
// because the map is synchronized and the createIcon() method will
|
|
||||||
// attempt to acquire the AWT lock. That combination will cause a deadlock
|
|
||||||
// if computeIfAbsent() is used and this method is called from non-swing thread.
|
|
||||||
Icon icon = map.get(vertex);
|
|
||||||
if (icon == null) {
|
|
||||||
icon = createIcon(vertex);
|
|
||||||
map.put(vertex, icon);
|
|
||||||
}
|
|
||||||
return icon;
|
|
||||||
}
|
|
||||||
|
|
||||||
GhidraIconCache() {
|
|
||||||
renderingHints.put(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Icon createIcon(AttributedVertex vertex) {
|
|
||||||
rendererLabel
|
|
||||||
.setText(ProgramGraphFunctions.getLabel(vertex, preferredVeretxLabelAttribute));
|
|
||||||
rendererLabel.setFont(new Font(DEFAULT_FONT_NAME, Font.BOLD, DEFAULT_FONT_SIZE));
|
|
||||||
rendererLabel.setForeground(Color.black);
|
|
||||||
rendererLabel.setBackground(Color.white);
|
|
||||||
rendererLabel.setOpaque(true);
|
|
||||||
Border lineBorder = BorderFactory.createLineBorder((Color) Colors.getColor(vertex), 2);
|
|
||||||
Border marginBorder = BorderFactory.createEmptyBorder(DEFAULT_MARGIN_BORDER_SIZE,
|
|
||||||
DEFAULT_MARGIN_BORDER_SIZE, DEFAULT_MARGIN_BORDER_SIZE, DEFAULT_MARGIN_BORDER_SIZE);
|
|
||||||
rendererLabel.setBorder(new CompoundBorder(lineBorder, marginBorder));
|
|
||||||
|
|
||||||
Dimension labelSize = rendererLabel.getPreferredSize();
|
|
||||||
rendererLabel.setSize(labelSize);
|
|
||||||
Shape shape = ProgramGraphFunctions.getVertexShape(vertex);
|
|
||||||
|
|
||||||
IconShape.Type shapeType = iconShapeFunction.apply(shape);
|
|
||||||
|
|
||||||
return createImageIcon(vertex, shapeType, rendererLabel, labelSize, shape);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Based on the shape and characteristics of the vertex label (color, text) create and cache an ImageIcon
|
|
||||||
* that will be used to draw the vertex
|
|
||||||
*
|
|
||||||
* @param vertex the vertex to draw (and the key for the cache)
|
|
||||||
* @param vertexShapeCategory the type of Ghidra vertex shape
|
|
||||||
* @param label the {@link JLabel} used to draw the label. Note that it will parse html for formatting.
|
|
||||||
* @param labelSize the dimensions of the JLabel after it has been parsed
|
|
||||||
* @param vertexShape the primitive {@link Shape} used to represent the vertex
|
|
||||||
*/
|
|
||||||
private Icon createImageIcon(AttributedVertex vertex, IconShape.Type vertexShapeCategory,
|
|
||||||
JLabel label, Dimension labelSize, Shape vertexShape) {
|
|
||||||
int offset = 0;
|
|
||||||
double scalex;
|
|
||||||
double scaley;
|
|
||||||
switch (vertexShapeCategory) {
|
|
||||||
// triangles have a non-zero +/- yoffset instead of centering the label
|
|
||||||
case TRIANGLE:
|
|
||||||
// scale the vertex shape
|
|
||||||
scalex = labelSize.getWidth() / vertexShape.getBounds().getWidth() *
|
|
||||||
LABEL_TO_ICON_PROPORTION;
|
|
||||||
scaley = labelSize.getHeight() / vertexShape.getBounds().getHeight() *
|
|
||||||
LABEL_TO_ICON_PROPORTION;
|
|
||||||
vertexShape = AffineTransform.getScaleInstance(scalex, scaley)
|
|
||||||
.createTransformedShape(vertexShape);
|
|
||||||
offset = -(int) ((vertexShape.getBounds().getHeight() - labelSize.getHeight()) / 2);
|
|
||||||
break;
|
|
||||||
case INVERTED_TRIANGLE:
|
|
||||||
scalex = labelSize.getWidth() / vertexShape.getBounds().getWidth() *
|
|
||||||
LABEL_TO_ICON_PROPORTION;
|
|
||||||
scaley = labelSize.getHeight() / vertexShape.getBounds().getHeight() *
|
|
||||||
LABEL_TO_ICON_PROPORTION;
|
|
||||||
vertexShape = AffineTransform.getScaleInstance(scalex, scaley)
|
|
||||||
.createTransformedShape(vertexShape);
|
|
||||||
offset = (int) ((vertexShape.getBounds().getHeight() - labelSize.getHeight()) / 2);
|
|
||||||
break;
|
|
||||||
|
|
||||||
// rectangles can fit a full-sized label
|
|
||||||
case RECTANGLE:
|
|
||||||
scalex = labelSize.getWidth() / vertexShape.getBounds().getWidth();
|
|
||||||
scaley = labelSize.getHeight() / vertexShape.getBounds().getHeight();
|
|
||||||
vertexShape = AffineTransform.getScaleInstance(scalex, scaley)
|
|
||||||
.createTransformedShape(vertexShape);
|
|
||||||
break;
|
|
||||||
|
|
||||||
// diamonds and ellipses reduce the label size to fit
|
|
||||||
case DIAMOND:
|
|
||||||
default: // ELLIPSE
|
|
||||||
scalex =
|
|
||||||
labelSize.getWidth() / vertexShape.getBounds().getWidth() * 1.1;
|
|
||||||
scaley = labelSize.getHeight() / vertexShape.getBounds().getHeight() * 1.1;
|
|
||||||
vertexShape = AffineTransform.getScaleInstance(scalex, scaley)
|
|
||||||
.createTransformedShape(vertexShape);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
Rectangle vertexBounds = vertexShape.getBounds();
|
|
||||||
|
|
||||||
BufferedImage bufferedImage = new BufferedImage(vertexBounds.width + (2 * strokeThickness),
|
|
||||||
vertexBounds.height + (2 * strokeThickness), BufferedImage.TYPE_INT_ARGB);
|
|
||||||
|
|
||||||
Graphics2D graphics = bufferedImage.createGraphics();
|
|
||||||
graphics.setRenderingHints(renderingHints);
|
|
||||||
AffineTransform graphicsTransform = graphics.getTransform();
|
|
||||||
|
|
||||||
// draw the shape, offset by 1/2 its width and the strokeThickness
|
|
||||||
AffineTransform offsetTransform =
|
|
||||||
AffineTransform.getTranslateInstance(strokeThickness + vertexBounds.width / 2.0,
|
|
||||||
strokeThickness + vertexBounds.height / 2.0);
|
|
||||||
offsetTransform.preConcatenate(graphicsTransform);
|
|
||||||
graphics.setTransform(offsetTransform);
|
|
||||||
graphics.setPaint(Color.white);
|
|
||||||
graphics.fill(vertexShape);
|
|
||||||
graphics.setPaint(Colors.getColor(vertex));
|
|
||||||
graphics.setStroke(new BasicStroke(strokeThickness));
|
|
||||||
graphics.draw(vertexShape);
|
|
||||||
|
|
||||||
// draw the JLabel, offset by 1/2 its width and the strokeThickness
|
|
||||||
int xoffset = strokeThickness + (vertexBounds.width - labelSize.width) / 2;
|
|
||||||
int yoffset = strokeThickness + (vertexBounds.height - labelSize.height) / 2;
|
|
||||||
offsetTransform = AffineTransform.getTranslateInstance(xoffset, yoffset + offset);
|
|
||||||
offsetTransform.preConcatenate(graphicsTransform);
|
|
||||||
graphics.setPaint(Color.black);
|
|
||||||
graphics.setTransform(offsetTransform);
|
|
||||||
label.paint(graphics);
|
|
||||||
// draw the shape again, but lighter (on top of the label)
|
|
||||||
offsetTransform =
|
|
||||||
AffineTransform.getTranslateInstance(strokeThickness + vertexBounds.width / 2.0,
|
|
||||||
strokeThickness + vertexBounds.height / 2.0);
|
|
||||||
offsetTransform.preConcatenate(graphicsTransform);
|
|
||||||
graphics.setTransform(offsetTransform);
|
|
||||||
Paint paint = Colors.getColor(vertex);
|
|
||||||
if (paint instanceof Color) {
|
|
||||||
Color color = (Color) paint;
|
|
||||||
Color transparent = new Color(color.getRed(), color.getGreen(), color.getBlue(), 50);
|
|
||||||
graphics.setPaint(transparent);
|
|
||||||
graphics.setStroke(new BasicStroke(strokeThickness));
|
|
||||||
graphics.draw(vertexShape);
|
|
||||||
}
|
|
||||||
|
|
||||||
graphics.setTransform(graphicsTransform); // restore the original transform
|
|
||||||
graphics.dispose();
|
|
||||||
return new ImageIcon(bufferedImage);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void clear() {
|
|
||||||
map.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* evict the passed vertex from the cache so that it will be recomputed
|
|
||||||
* with presumably changed values
|
|
||||||
* @param vertex to remove from the cache
|
|
||||||
*/
|
|
||||||
public void evict(AttributedVertex vertex) {
|
|
||||||
map.remove(vertex);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the vertex label to the value of the passed attribute name
|
|
||||||
* @param attributeName the attribute key for the vertex label value to be displayed
|
|
||||||
*/
|
|
||||||
public void setPreferredVertexLabelAttribute(String attributeName) {
|
|
||||||
this.preferredVeretxLabelAttribute = attributeName;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+90
@@ -0,0 +1,90 @@
|
|||||||
|
/* ###
|
||||||
|
* IP: GHIDRA
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package ghidra.graph.visualization;
|
||||||
|
|
||||||
|
import java.awt.Color;
|
||||||
|
|
||||||
|
import org.jungrapht.visualization.VisualizationViewer;
|
||||||
|
|
||||||
|
import ghidra.service.graph.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface for GraphRenderer used by the {@link DefaultGraphDisplay}. Developers can add new
|
||||||
|
* implementations to change the graph rendering
|
||||||
|
*/
|
||||||
|
public interface GraphRenderer {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the {@link VisualizationViewer}. When a new {@link DefaultGraphDisplay} is
|
||||||
|
* created, it uses a JungraphT {@link VisualizationViewer} to display a graph. That viewer
|
||||||
|
* has many configuration settings. The GraphRender needs to initialize the viewer so that
|
||||||
|
* it calls back to this renderer to get the vertex and edge data/functions that it needs
|
||||||
|
* to render a graph. This is how the GraphRender can inject is display style into the graph
|
||||||
|
* display.
|
||||||
|
* <P>
|
||||||
|
* @param viewer the {@link VisualizationViewer}
|
||||||
|
*/
|
||||||
|
public void initializeViewer(VisualizationViewer<AttributedVertex, AttributedEdge> viewer);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the graph display options that are specific to a particular graph type
|
||||||
|
* @param options the {@link GraphDisplayOptions} which are options for a specific graph type
|
||||||
|
*/
|
||||||
|
public void setGraphTypeDisplayOptions(GraphDisplayOptions options);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the current {@link GraphDisplayOptions} being used
|
||||||
|
* @return the current {@link GraphDisplayOptions} being used
|
||||||
|
*/
|
||||||
|
public GraphDisplayOptions getGraphDisplayOptions();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tells this renderer that the given vertex changed and needs to be redrawn
|
||||||
|
* @param vertex the vertex that changed
|
||||||
|
*/
|
||||||
|
public void vertexChanged(AttributedVertex vertex);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the favored edge type
|
||||||
|
* @return the favored edge type
|
||||||
|
*/
|
||||||
|
public String getFavoredEdgeType();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the edge priority for the edge type
|
||||||
|
* @param edgeType the edge type to get priority for
|
||||||
|
* @return the edge priority for the edge type
|
||||||
|
*/
|
||||||
|
public Integer getEdgePriority(String edgeType);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears any cached renderings
|
||||||
|
*/
|
||||||
|
public void clearCache();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the vertex selection color
|
||||||
|
* @return the vertex selection color
|
||||||
|
*/
|
||||||
|
public Color getVertexSelectionColor();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the edge selection color
|
||||||
|
* @return the edge selection color
|
||||||
|
*/
|
||||||
|
public Color getEdgeSelectionColor();
|
||||||
|
|
||||||
|
}
|
||||||
+1
-2
@@ -46,8 +46,7 @@ public class GroupVertex extends AttributedVertex {
|
|||||||
super(id);
|
super(id);
|
||||||
this.first = first;
|
this.first = first;
|
||||||
this.children = children;
|
this.children = children;
|
||||||
setAttribute("VertexType", "Collapsed");
|
setVertexType("Collapsed Group");
|
||||||
setAttribute("Icon", "Star");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
-104
@@ -1,104 +0,0 @@
|
|||||||
/* ###
|
|
||||||
* IP: GHIDRA
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
package ghidra.graph.visualization;
|
|
||||||
|
|
||||||
import java.awt.Shape;
|
|
||||||
import java.awt.geom.PathIterator;
|
|
||||||
import java.awt.geom.Point2D;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Holds the enum for shape type and the Function to categorize the archetype Shapes into
|
|
||||||
* IconShape.Types. Note that the archetype shapes are centered at the origin
|
|
||||||
*/
|
|
||||||
public class IconShape {
|
|
||||||
|
|
||||||
public enum Type {
|
|
||||||
TRIANGLE, INVERTED_TRIANGLE, RECTANGLE, DIAMOND, ELLIPSE
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Categorize the supplied Shape into one of several simple types.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
static class Function implements java.util.function.Function<Shape, Type> {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Type apply(Shape shape) {
|
|
||||||
List<Point2D> points = getShapePoints(shape);
|
|
||||||
if (points.size() == 3) {
|
|
||||||
if (isInvertedTriangle(points)) {
|
|
||||||
return Type.INVERTED_TRIANGLE;
|
|
||||||
} else {
|
|
||||||
return Type.TRIANGLE;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// there are 5 points because the final point is the same as the first
|
|
||||||
// and closes the shape.
|
|
||||||
if (points.size() == 5) {
|
|
||||||
if (isDiamond(points)) {
|
|
||||||
return Type.DIAMOND;
|
|
||||||
} else {
|
|
||||||
return Type.RECTANGLE;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// default to ellipse for anything with more that 4 sides
|
|
||||||
return Type.ELLIPSE;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* Note that for awt drawing, the origin is at the upper left so positive y extends downwards.
|
|
||||||
* @param threePoints odd number of points bounding a {@link Shape} centered at the origin
|
|
||||||
* @return true it there are fewer points with y below 0
|
|
||||||
*/
|
|
||||||
boolean isInvertedTriangle(List<Point2D> threePoints) {
|
|
||||||
if (threePoints.size() != 3) {
|
|
||||||
throw new IllegalArgumentException("Shape from " + threePoints + " is not a triangle");
|
|
||||||
}
|
|
||||||
return threePoints.stream().filter(p -> p.getY() < 0).count() <= threePoints.size() / 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param fivePoints odd number of points bounding a {@link Shape} centered at the origin
|
|
||||||
* @return true it there are 2 points with y value 0
|
|
||||||
*/
|
|
||||||
boolean isDiamond(List<Point2D> fivePoints) {
|
|
||||||
if (fivePoints.size() != 5) {
|
|
||||||
throw new IllegalArgumentException(
|
|
||||||
"Shape from " + fivePoints + " is not a quadrilateral");
|
|
||||||
}
|
|
||||||
return fivePoints.stream().filter(p -> (int) p.getY() == 0).count() == 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
List<Point2D> getShapePoints(Shape shape) {
|
|
||||||
float[] seg = new float[6];
|
|
||||||
List<Point2D> points = new ArrayList<>();
|
|
||||||
for (PathIterator i = shape.getPathIterator(null, 1); !i.isDone(); i.next()) {
|
|
||||||
int ret = i.currentSegment(seg);
|
|
||||||
if (ret == PathIterator.SEG_MOVETO) {
|
|
||||||
points.add(new Point2D.Float(seg[0], seg[1]));
|
|
||||||
}
|
|
||||||
else if (ret == PathIterator.SEG_LINETO) {
|
|
||||||
points.add(new Point2D.Float(seg[0], seg[1]));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return points;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+14
-39
@@ -15,6 +15,8 @@
|
|||||||
*/
|
*/
|
||||||
package ghidra.graph.visualization;
|
package ghidra.graph.visualization;
|
||||||
|
|
||||||
|
import static ghidra.service.graph.LayoutAlgorithmNames.*;
|
||||||
|
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import java.util.function.Predicate;
|
import java.util.function.Predicate;
|
||||||
@@ -23,6 +25,8 @@ import org.jungrapht.visualization.layout.algorithms.*;
|
|||||||
import org.jungrapht.visualization.layout.algorithms.repulsion.BarnesHutFRRepulsion;
|
import org.jungrapht.visualization.layout.algorithms.repulsion.BarnesHutFRRepulsion;
|
||||||
import org.jungrapht.visualization.layout.algorithms.sugiyama.Layering;
|
import org.jungrapht.visualization.layout.algorithms.sugiyama.Layering;
|
||||||
|
|
||||||
|
import com.google.common.base.Objects;
|
||||||
|
|
||||||
import ghidra.service.graph.AttributedEdge;
|
import ghidra.service.graph.AttributedEdge;
|
||||||
import ghidra.service.graph.AttributedVertex;
|
import ghidra.service.graph.AttributedVertex;
|
||||||
|
|
||||||
@@ -36,61 +40,32 @@ import ghidra.service.graph.AttributedVertex;
|
|||||||
class LayoutFunction
|
class LayoutFunction
|
||||||
implements Function<String, LayoutAlgorithm.Builder<AttributedVertex, ?, ?>> {
|
implements Function<String, LayoutAlgorithm.Builder<AttributedVertex, ?, ?>> {
|
||||||
|
|
||||||
static final String KAMADA_KAWAI = "Force Balanced";
|
|
||||||
static final String FRUCTERMAN_REINGOLD = "Force Directed";
|
|
||||||
static final String CIRCLE = "Circle";
|
|
||||||
static final String TIDIER_TREE = "Compact Hierarchical";
|
|
||||||
static final String TIDIER_RADIAL_TREE = "Compact Radial";
|
|
||||||
static final String MIN_CROSS = "Hierarchical MinCross"; //not an alg, just a parent category
|
|
||||||
static final String MIN_CROSS_TOP_DOWN = "Hierarchical MinCross Top Down";
|
|
||||||
static final String MIN_CROSS_LONGEST_PATH = "Hierarchical MinCross Longest Path";
|
|
||||||
static final String MIN_CROSS_NETWORK_SIMPLEX = "Hierarchical MinCross Network Simplex";
|
|
||||||
static final String MIN_CROSS_COFFMAN_GRAHAM = "Hierarchical MinCross Coffman Graham";
|
|
||||||
static final String VERT_MIN_CROSS = "Vertical Hierarchical MinCross"; //not an alg, just a parent category
|
|
||||||
static final String VERT_MIN_CROSS_TOP_DOWN = "Vertical Hierarchical MinCross Top Down";
|
|
||||||
static final String VERT_MIN_CROSS_LONGEST_PATH = "Vertical Hierarchical MinCross Longest Path";
|
|
||||||
static final String VERT_MIN_CROSS_NETWORK_SIMPLEX = "Vertical Hierarchical MinCross Network Simplex";
|
|
||||||
static final String VERT_MIN_CROSS_COFFMAN_GRAHAM = "Vertical Hierarchical MinCross Coffman Graham";
|
|
||||||
static final String TREE = "Hierarchical";
|
|
||||||
static final String RADIAL = "Radial";
|
|
||||||
static final String BALLOON = "Balloon";
|
|
||||||
static final String GEM = "GEM";
|
|
||||||
|
|
||||||
Predicate<AttributedEdge> favoredEdgePredicate;
|
Predicate<AttributedEdge> favoredEdgePredicate;
|
||||||
Comparator<AttributedEdge> edgeTypeComparator;
|
Comparator<AttributedEdge> edgeTypeComparator;
|
||||||
|
|
||||||
LayoutFunction(Comparator<AttributedEdge> edgeTypeComparator, Predicate<AttributedEdge> favoredEdgePredicate) {
|
LayoutFunction(GraphRenderer renderer) {
|
||||||
this.edgeTypeComparator = edgeTypeComparator;
|
this.edgeTypeComparator = new EdgeComparator(renderer);
|
||||||
this.favoredEdgePredicate = favoredEdgePredicate;
|
this.favoredEdgePredicate =
|
||||||
|
edge -> Objects.equal(edge.getEdgeType(), renderer.getFavoredEdgeType());
|
||||||
}
|
}
|
||||||
|
|
||||||
public String[] getNames() {
|
|
||||||
return new String[] { TIDIER_TREE, TREE,
|
|
||||||
TIDIER_RADIAL_TREE, MIN_CROSS_TOP_DOWN, MIN_CROSS_LONGEST_PATH,
|
|
||||||
MIN_CROSS_NETWORK_SIMPLEX, MIN_CROSS_COFFMAN_GRAHAM, CIRCLE,
|
|
||||||
VERT_MIN_CROSS_TOP_DOWN,
|
|
||||||
VERT_MIN_CROSS_LONGEST_PATH,
|
|
||||||
VERT_MIN_CROSS_NETWORK_SIMPLEX,
|
|
||||||
VERT_MIN_CROSS_COFFMAN_GRAHAM,
|
|
||||||
KAMADA_KAWAI, FRUCTERMAN_REINGOLD, RADIAL, BALLOON, GEM
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public LayoutAlgorithm.Builder<AttributedVertex, ?, ?> apply(String name) {
|
public LayoutAlgorithm.Builder<AttributedVertex, ?, ?> apply(String name) {
|
||||||
switch(name) {
|
switch(name) {
|
||||||
case GEM:
|
case GEM:
|
||||||
return GEMLayoutAlgorithm.edgeAwareBuilder();
|
return GEMLayoutAlgorithm.edgeAwareBuilder();
|
||||||
case KAMADA_KAWAI:
|
case FORCED_BALANCED:
|
||||||
return KKLayoutAlgorithm.<AttributedVertex> builder()
|
return KKLayoutAlgorithm.<AttributedVertex> builder()
|
||||||
.preRelaxDuration(1000);
|
.preRelaxDuration(1000);
|
||||||
case FRUCTERMAN_REINGOLD:
|
case FORCE_DIRECTED:
|
||||||
return FRLayoutAlgorithm.<AttributedVertex> builder()
|
return FRLayoutAlgorithm.<AttributedVertex> builder()
|
||||||
.repulsionContractBuilder(BarnesHutFRRepulsion.builder());
|
.repulsionContractBuilder(BarnesHutFRRepulsion.builder());
|
||||||
case CIRCLE:
|
case CIRCLE:
|
||||||
return CircleLayoutAlgorithm.<AttributedVertex> builder()
|
return CircleLayoutAlgorithm.<AttributedVertex> builder()
|
||||||
.reduceEdgeCrossing(false);
|
.reduceEdgeCrossing(false);
|
||||||
case TIDIER_RADIAL_TREE:
|
case COMPACT_RADIAL:
|
||||||
return TidierRadialTreeLayoutAlgorithm
|
return TidierRadialTreeLayoutAlgorithm
|
||||||
.<AttributedVertex, AttributedEdge> edgeAwareBuilder()
|
.<AttributedVertex, AttributedEdge> edgeAwareBuilder()
|
||||||
.edgeComparator(edgeTypeComparator);
|
.edgeComparator(edgeTypeComparator);
|
||||||
@@ -146,10 +121,10 @@ class LayoutFunction
|
|||||||
return BalloonLayoutAlgorithm
|
return BalloonLayoutAlgorithm
|
||||||
.<AttributedVertex> builder()
|
.<AttributedVertex> builder()
|
||||||
.verticalVertexSpacing(300);
|
.verticalVertexSpacing(300);
|
||||||
case TREE:
|
case HIERACHICAL:
|
||||||
return EdgeAwareTreeLayoutAlgorithm
|
return EdgeAwareTreeLayoutAlgorithm
|
||||||
.<AttributedVertex, AttributedEdge>edgeAwareBuilder();
|
.<AttributedVertex, AttributedEdge>edgeAwareBuilder();
|
||||||
case TIDIER_TREE:
|
case COMPACT_HIERACHICAL:
|
||||||
default:
|
default:
|
||||||
return TidierTreeLayoutAlgorithm
|
return TidierTreeLayoutAlgorithm
|
||||||
.<AttributedVertex, AttributedEdge> edgeAwareBuilder()
|
.<AttributedVertex, AttributedEdge> edgeAwareBuilder()
|
||||||
|
|||||||
+5
-18
@@ -15,10 +15,6 @@
|
|||||||
*/
|
*/
|
||||||
package ghidra.graph.visualization;
|
package ghidra.graph.visualization;
|
||||||
|
|
||||||
import static ghidra.graph.visualization.LayoutFunction.*;
|
|
||||||
|
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import java.util.function.Predicate;
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
@@ -30,8 +26,7 @@ import org.jungrapht.visualization.layout.model.Rectangle;
|
|||||||
import org.jungrapht.visualization.util.LayoutAlgorithmTransition;
|
import org.jungrapht.visualization.util.LayoutAlgorithmTransition;
|
||||||
import org.jungrapht.visualization.util.LayoutPaintable;
|
import org.jungrapht.visualization.util.LayoutPaintable;
|
||||||
|
|
||||||
import ghidra.service.graph.AttributedEdge;
|
import ghidra.service.graph.*;
|
||||||
import ghidra.service.graph.AttributedVertex;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Manages the selection and transition from one {@link LayoutAlgorithm} to another
|
* Manages the selection and transition from one {@link LayoutAlgorithm} to another
|
||||||
@@ -73,14 +68,13 @@ class LayoutTransitionManager {
|
|||||||
public LayoutTransitionManager(
|
public LayoutTransitionManager(
|
||||||
VisualizationServer<AttributedVertex, AttributedEdge> visualizationServer,
|
VisualizationServer<AttributedVertex, AttributedEdge> visualizationServer,
|
||||||
Predicate<AttributedVertex> rootPredicate,
|
Predicate<AttributedVertex> rootPredicate,
|
||||||
List<String> edgeTypePriorityList,
|
GraphRenderer renderer) {
|
||||||
Predicate<AttributedEdge> favoredEdgePredicate) {
|
|
||||||
this.visualizationServer = visualizationServer;
|
this.visualizationServer = visualizationServer;
|
||||||
this.rootPredicate = rootPredicate;
|
this.rootPredicate = rootPredicate;
|
||||||
this.renderContext = visualizationServer.getRenderContext();
|
this.renderContext = visualizationServer.getRenderContext();
|
||||||
this.vertexBoundsFunction = visualizationServer.getRenderContext().getVertexBoundsFunction();
|
this.vertexBoundsFunction = visualizationServer.getRenderContext().getVertexBoundsFunction();
|
||||||
this.layoutFunction = new LayoutFunction(new EdgeComparator(edgeTypePriorityList),
|
this.layoutFunction = new LayoutFunction(renderer);
|
||||||
favoredEdgePredicate);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -144,7 +138,7 @@ class LayoutTransitionManager {
|
|||||||
*/
|
*/
|
||||||
public LayoutAlgorithm<AttributedVertex> getInitialLayoutAlgorithm() {
|
public LayoutAlgorithm<AttributedVertex> getInitialLayoutAlgorithm() {
|
||||||
LayoutAlgorithm<AttributedVertex> initialLayoutAlgorithm =
|
LayoutAlgorithm<AttributedVertex> initialLayoutAlgorithm =
|
||||||
layoutFunction.apply(TIDIER_TREE).build();
|
layoutFunction.apply(LayoutAlgorithmNames.COMPACT_HIERACHICAL).build();
|
||||||
|
|
||||||
if (initialLayoutAlgorithm instanceof TreeLayout) {
|
if (initialLayoutAlgorithm instanceof TreeLayout) {
|
||||||
((TreeLayout<AttributedVertex>) initialLayoutAlgorithm)
|
((TreeLayout<AttributedVertex>) initialLayoutAlgorithm)
|
||||||
@@ -157,11 +151,4 @@ class LayoutTransitionManager {
|
|||||||
return initialLayoutAlgorithm;
|
return initialLayoutAlgorithm;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Supplies a {@code String[]} array of the supported layout names
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public String[] getLayoutNames() {
|
|
||||||
return layoutFunction.getNames();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
-140
@@ -1,140 +0,0 @@
|
|||||||
/* ###
|
|
||||||
* IP: GHIDRA
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
package ghidra.graph.visualization;
|
|
||||||
|
|
||||||
import static org.jungrapht.visualization.layout.util.PropertyLoader.*;
|
|
||||||
|
|
||||||
import java.awt.*;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import org.apache.commons.text.StringEscapeUtils;
|
|
||||||
import org.jungrapht.visualization.util.ShapeFactory;
|
|
||||||
|
|
||||||
import com.google.common.base.Splitter;
|
|
||||||
|
|
||||||
import ghidra.service.graph.Attributed;
|
|
||||||
import ghidra.service.graph.AttributedEdge;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* a container for various functions used by ProgramGraph
|
|
||||||
*/
|
|
||||||
abstract class ProgramGraphFunctions {
|
|
||||||
static float edgeWidth = Float.parseFloat(System.getProperty(PREFIX + "edgeWidth", "4.0f"));
|
|
||||||
|
|
||||||
// cannot instantiate nor extend
|
|
||||||
private ProgramGraphFunctions() {
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* a default implementation of a {@link ShapeFactory} to supply shapes for attributed vertices and edges
|
|
||||||
*/
|
|
||||||
private static ShapeFactory<Attributed> shapeFactory = new ShapeFactory<>(n -> 50, n -> 1.0f);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* return various 'Shapes' based on an attribute name
|
|
||||||
*
|
|
||||||
* @param n the attributed key (a vertex or edge)
|
|
||||||
* @param name the attribute name
|
|
||||||
* @return a Shape for the passed 'n' with attribute 'name'
|
|
||||||
*/
|
|
||||||
private static Shape byShapeName(Attributed n, String name) {
|
|
||||||
if (name == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
switch (name) {
|
|
||||||
case "Square":
|
|
||||||
return shapeFactory.getRectangle(n);
|
|
||||||
case "Circle":
|
|
||||||
return shapeFactory.getEllipse(n);
|
|
||||||
case "Triangle":
|
|
||||||
return shapeFactory.getRegularPolygon(n, 3);
|
|
||||||
case "TriangleDown":
|
|
||||||
return shapeFactory.getRegularPolygon(n, 3, Math.PI);
|
|
||||||
case "Diamond":
|
|
||||||
return shapeFactory.getRectangle(n, Math.PI / 4);
|
|
||||||
case "Star":
|
|
||||||
return shapeFactory.getRegularStar(n, 5);
|
|
||||||
case "Pentagon":
|
|
||||||
return shapeFactory.getRegularPolygon(n, 5);
|
|
||||||
case "Hexagon":
|
|
||||||
return shapeFactory.getRegularPolygon(n, 6);
|
|
||||||
case "Octagon":
|
|
||||||
return shapeFactory.getRegularPolygon(n, 8);
|
|
||||||
default:
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Gets the Shape object to use when drawing this vertex. If "Icon" attribute
|
|
||||||
* is set it will use that, otherwise "VertexType" to will translate a code flow
|
|
||||||
* name to a shape
|
|
||||||
*
|
|
||||||
* @param vertex the Attributed object to get a shape for
|
|
||||||
* @return a Shape object to use when displaying the object
|
|
||||||
*/
|
|
||||||
public static Shape getVertexShape(Attributed vertex) {
|
|
||||||
Shape shape = byShapeName(vertex, vertex.getAttribute("Icon"));
|
|
||||||
if (shape != null) {
|
|
||||||
return shape;
|
|
||||||
}
|
|
||||||
String vertexType = vertex.getAttribute("VertexType");
|
|
||||||
if (vertexType == null) {
|
|
||||||
return shapeFactory.getRectangle(vertex);
|
|
||||||
}
|
|
||||||
switch (vertexType) {
|
|
||||||
case "Entry":
|
|
||||||
return shapeFactory.getRegularPolygon(vertex, 3, Math.PI);
|
|
||||||
case "Exit":
|
|
||||||
return shapeFactory.getRegularPolygon(vertex, 3);
|
|
||||||
case "Switch":
|
|
||||||
return shapeFactory.getRectangle(vertex, Math.PI / 4);
|
|
||||||
case "Body":
|
|
||||||
case "External":
|
|
||||||
return shapeFactory.getRectangle(vertex);
|
|
||||||
default:
|
|
||||||
return shapeFactory.getEllipse(vertex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Provides a {@link Stroke} (line width and style) for an attributed edge
|
|
||||||
* @param edge the edge to get a stroke value
|
|
||||||
* @return the stroke for the edge
|
|
||||||
*/
|
|
||||||
public static Stroke getEdgeStroke(AttributedEdge edge) {
|
|
||||||
String edgeType = edge.getAttribute("EdgeType");
|
|
||||||
if (edgeType != null && edgeType.equals("Fall-Through")) {
|
|
||||||
return new BasicStroke(edgeWidth * 2);
|
|
||||||
}
|
|
||||||
return new BasicStroke(edgeWidth);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* gets a display label from an {@link Attributed} object (vertex)
|
|
||||||
* @param attributed the attributed object to get a label for
|
|
||||||
* @param preferredLabelAttribute the attribute to use for the label, if available
|
|
||||||
* @return the label for the given {@link Attributed}
|
|
||||||
*/
|
|
||||||
public static String getLabel(Attributed attributed, String preferredLabelAttribute) {
|
|
||||||
Map<String, String> map = attributed.getAttributeMap();
|
|
||||||
String name = StringEscapeUtils.escapeHtml4(map.get("Name"));
|
|
||||||
if (map.containsKey(preferredLabelAttribute)) {
|
|
||||||
name = StringEscapeUtils.escapeHtml4(map.get(preferredLabelAttribute));
|
|
||||||
}
|
|
||||||
return "<html>" + String.join("<p>", Splitter.on('\n').split(name));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+4
@@ -73,4 +73,8 @@ public class JgtGraphMouse extends DefaultGraphMouse<AttributedVertex, Attribute
|
|||||||
setPluginsLoaded();
|
setPluginsLoaded();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean allowsEdgeSelection() {
|
||||||
|
return allowEdgeSelection;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ jungrapht.edgeWidth=4.0f
|
|||||||
# stroke size for the magnifier lens
|
# stroke size for the magnifier lens
|
||||||
jungrapht.lensStrokeWidth=10.0
|
jungrapht.lensStrokeWidth=10.0
|
||||||
# when scale is < .1, switch to lightweight rendering
|
# when scale is < .1, switch to lightweight rendering
|
||||||
jungrapht.lightweightScaleThreshold=.1
|
jungrapht.lightweightScaleThreshold=.01
|
||||||
# under 50 vertices will use heavyweight rendering all the time
|
# under 50 vertices will use heavyweight rendering all the time
|
||||||
jungrapht.lightweightCountThreshold=80
|
jungrapht.lightweightCountThreshold=80
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -277,7 +277,7 @@ public class AttributedGraphExportersTest extends AbstractGenericTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private AttributedGraph createGraph() {
|
private AttributedGraph createGraph() {
|
||||||
AttributedGraph g = new AttributedGraph();
|
AttributedGraph g = new AttributedGraph("Test", new EmptyGraphType());
|
||||||
AttributedVertex vA = g.addVertex("A");
|
AttributedVertex vA = g.addVertex("A");
|
||||||
AttributedVertex vB = g.addVertex("B");
|
AttributedVertex vB = g.addVertex("B");
|
||||||
AttributedVertex vC = g.addVertex("C");
|
AttributedVertex vC = g.addVertex("C");
|
||||||
|
|||||||
+1
-1
@@ -311,7 +311,7 @@ public class GraphExportTest extends AbstractGhidraHeadedIntegrationTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private AttributedGraph createGraph() {
|
private AttributedGraph createGraph() {
|
||||||
AttributedGraph graph = new AttributedGraph();
|
AttributedGraph graph = new AttributedGraph("Test", new EmptyGraphType());
|
||||||
AttributedVertex vA = graph.addVertex("A");
|
AttributedVertex vA = graph.addVertex("A");
|
||||||
AttributedVertex vB = graph.addVertex("B");
|
AttributedVertex vB = graph.addVertex("B");
|
||||||
AttributedVertex vC = graph.addVertex("C");
|
AttributedVertex vC = graph.addVertex("C");
|
||||||
|
|||||||
-75
@@ -1,75 +0,0 @@
|
|||||||
/* ###
|
|
||||||
* IP: GHIDRA
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
package ghidra.graph.visualization;
|
|
||||||
|
|
||||||
import static org.junit.Assert.*;
|
|
||||||
|
|
||||||
import java.awt.Color;
|
|
||||||
|
|
||||||
import org.junit.Test;
|
|
||||||
|
|
||||||
import ghidra.service.graph.AttributedEdge;
|
|
||||||
import ghidra.service.graph.AttributedVertex;
|
|
||||||
|
|
||||||
public class ColorsTest {
|
|
||||||
@Test
|
|
||||||
public void testParseHashHexColor() {
|
|
||||||
Color hexColor = Colors.getHexColor("#ff0000");
|
|
||||||
assertEquals(Color.RED, hexColor);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testParseHexColor() {
|
|
||||||
Color hexColor = Colors.getHexColor("0xff0000");
|
|
||||||
assertEquals(Color.RED, hexColor);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testGetColorFromVertexType() {
|
|
||||||
AttributedVertex vertex = new AttributedVertex("A");
|
|
||||||
vertex.setAttribute("VertexType", "Exit");
|
|
||||||
vertex.setAttribute("Color", "0xffffff");
|
|
||||||
assertEquals(Color.MAGENTA, Colors.getColor(vertex));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testGetColorFromVertexNoVertexType() {
|
|
||||||
AttributedVertex vertex = new AttributedVertex("A");
|
|
||||||
vertex.setAttribute("Color", "0xffffff");
|
|
||||||
assertEquals(Color.WHITE, Colors.getColor(vertex));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testGetColorFromVertexNoAttributes() {
|
|
||||||
AttributedVertex vertex = new AttributedVertex("A");
|
|
||||||
assertEquals(Color.GREEN, Colors.getColor(vertex));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testGetColorFromEdgeType() {
|
|
||||||
AttributedEdge edge = new AttributedEdge("A");
|
|
||||||
edge.setAttribute("EdgeType", "Computed");
|
|
||||||
edge.setAttribute("Color", "0xffffff");
|
|
||||||
assertEquals(Color.CYAN, Colors.getColor(edge));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testGetColorFromEdgeNoEdgeType() {
|
|
||||||
AttributedEdge edge = new AttributedEdge("A");
|
|
||||||
edge.setAttribute("Color", "0xffffff");
|
|
||||||
assertEquals(Color.WHITE, Colors.getColor(edge));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-70
@@ -1,70 +0,0 @@
|
|||||||
/* ###
|
|
||||||
* IP: GHIDRA
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
package ghidra.graph.visualization;
|
|
||||||
|
|
||||||
import ghidra.service.graph.AttributedVertex;
|
|
||||||
import org.junit.Assert;
|
|
||||||
import org.junit.Test;
|
|
||||||
|
|
||||||
import java.awt.geom.Rectangle2D;
|
|
||||||
|
|
||||||
public class IconShapeTest {
|
|
||||||
|
|
||||||
private IconShape.Function iconShapeFunction = new IconShape.Function();
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testShapes() {
|
|
||||||
|
|
||||||
Rectangle2D rectangle = new Rectangle2D.Double(-10, -10, 20, 20);
|
|
||||||
|
|
||||||
Assert.assertEquals(IconShape.Type.RECTANGLE, iconShapeFunction.apply(rectangle));
|
|
||||||
|
|
||||||
AttributedVertex v = new AttributedVertex("id", "name");
|
|
||||||
// by vertex type
|
|
||||||
v.setAttribute("VertexType", "Entry");
|
|
||||||
Assert.assertEquals(IconShape.Type.TRIANGLE,
|
|
||||||
iconShapeFunction.apply(ProgramGraphFunctions.getVertexShape(v)));
|
|
||||||
|
|
||||||
v.setAttribute("VertexType", "Exit");
|
|
||||||
Assert.assertEquals(IconShape.Type.INVERTED_TRIANGLE,
|
|
||||||
iconShapeFunction.apply(ProgramGraphFunctions.getVertexShape(v)));
|
|
||||||
|
|
||||||
v.setAttribute("VertexType", "Switch");
|
|
||||||
Assert.assertEquals(IconShape.Type.DIAMOND, iconShapeFunction.apply(ProgramGraphFunctions.getVertexShape(v)));
|
|
||||||
v.setAttribute("VertexType", "Body");
|
|
||||||
Assert.assertEquals(IconShape.Type.RECTANGLE, iconShapeFunction.apply(ProgramGraphFunctions.getVertexShape(v)));
|
|
||||||
v.setAttribute("VertexType", "External");
|
|
||||||
Assert.assertEquals(IconShape.Type.RECTANGLE, iconShapeFunction.apply(ProgramGraphFunctions.getVertexShape(v)));
|
|
||||||
v.setAttribute("VertexType", "Foo");
|
|
||||||
Assert.assertEquals(IconShape.Type.ELLIPSE, iconShapeFunction.apply(ProgramGraphFunctions.getVertexShape(v)));
|
|
||||||
|
|
||||||
|
|
||||||
// by vertex icon shape name
|
|
||||||
v.removeAttribute("VertexType");
|
|
||||||
v.setAttribute("Icon", "Square");
|
|
||||||
Assert.assertEquals(IconShape.Type.RECTANGLE, iconShapeFunction.apply(ProgramGraphFunctions.getVertexShape(v)));
|
|
||||||
v.setAttribute("Icon", "TriangleDown");
|
|
||||||
Assert.assertEquals(IconShape.Type.TRIANGLE, iconShapeFunction.apply(ProgramGraphFunctions.getVertexShape(v)));
|
|
||||||
v.setAttribute("Icon", "Triangle");
|
|
||||||
Assert.assertEquals(IconShape.Type.INVERTED_TRIANGLE, iconShapeFunction.apply(ProgramGraphFunctions.getVertexShape(v)));
|
|
||||||
v.setAttribute("Icon", "Diamond");
|
|
||||||
Assert.assertEquals(IconShape.Type.DIAMOND, iconShapeFunction.apply(ProgramGraphFunctions.getVertexShape(v)));
|
|
||||||
v.setAttribute("Icon", "Circle");
|
|
||||||
Assert.assertEquals(IconShape.Type.ELLIPSE, iconShapeFunction.apply(ProgramGraphFunctions.getVertexShape(v)));
|
|
||||||
v.setAttribute("Icon", "Foo");
|
|
||||||
Assert.assertEquals(IconShape.Type.RECTANGLE, iconShapeFunction.apply(ProgramGraphFunctions.getVertexShape(v)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+11
-9
@@ -489,16 +489,18 @@
|
|||||||
</BLOCKQUOTE>
|
</BLOCKQUOTE>
|
||||||
|
|
||||||
</BLOCKQUOTE>
|
</BLOCKQUOTE>
|
||||||
|
|
||||||
<BR>
|
|
||||||
<BR>
|
|
||||||
<BR>
|
|
||||||
<BR>
|
|
||||||
<BR>
|
|
||||||
</ul>
|
|
||||||
<P class="providedbyplugin">Provided by: <I>Program Graph Plugin</I></P>
|
|
||||||
|
|
||||||
<P class="relatedtopic">Related Topics</P>
|
<H2><A NAME="Program_Graphs_Display_Options">Program Graphs Display Options</H2>
|
||||||
|
<BLOCKQUOTE>
|
||||||
|
<P>These are the display options for graphs that are types of "Program Graphs" such as
|
||||||
|
Call graphs, Block graphs, etc. These types of graphs
|
||||||
|
use program elements as vertices and reference types as edges. See
|
||||||
|
<A href="help/topics/GraphServices/GraphDisplay.htm#Graph_Type_Display_Options">Graph Type Display Options</A> for
|
||||||
|
general help on graph type display options.</P>
|
||||||
|
</BLOCKQUOTE>
|
||||||
|
<P class="providedbyplugin">Provided by: <I>Program Graph Plugin</I></P>
|
||||||
|
|
||||||
|
<P class="relatedtopic">Related Topics</P>
|
||||||
<UL>
|
<UL>
|
||||||
<LI><A href="help/topics/GraphServices/GraphDisplay.htm">Default Graph Display</A></LI>
|
<LI><A href="help/topics/GraphServices/GraphDisplay.htm">Default Graph Display</A></LI>
|
||||||
<LI><A href="help/topics/GraphServices/GraphExport.htm">Graph Export</A></LI>
|
<LI><A href="help/topics/GraphServices/GraphExport.htm">Graph Export</A></LI>
|
||||||
|
|||||||
+35
-140
@@ -15,7 +15,8 @@
|
|||||||
*/
|
*/
|
||||||
package ghidra.graph.program;
|
package ghidra.graph.program;
|
||||||
|
|
||||||
import java.awt.Color;
|
import static ghidra.graph.ProgramGraphType.*;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
import docking.action.builder.ActionBuilder;
|
import docking.action.builder.ActionBuilder;
|
||||||
@@ -23,6 +24,7 @@ import docking.widgets.EventTrigger;
|
|||||||
import ghidra.app.plugin.core.colorizer.ColorizingService;
|
import ghidra.app.plugin.core.colorizer.ColorizingService;
|
||||||
import ghidra.app.util.AddEditDialog;
|
import ghidra.app.util.AddEditDialog;
|
||||||
import ghidra.framework.plugintool.PluginTool;
|
import ghidra.framework.plugintool.PluginTool;
|
||||||
|
import ghidra.graph.*;
|
||||||
import ghidra.program.model.address.*;
|
import ghidra.program.model.address.*;
|
||||||
import ghidra.program.model.block.*;
|
import ghidra.program.model.block.*;
|
||||||
import ghidra.program.model.listing.*;
|
import ghidra.program.model.listing.*;
|
||||||
@@ -30,7 +32,8 @@ import ghidra.program.model.symbol.*;
|
|||||||
import ghidra.program.util.ProgramLocation;
|
import ghidra.program.util.ProgramLocation;
|
||||||
import ghidra.program.util.ProgramSelection;
|
import ghidra.program.util.ProgramSelection;
|
||||||
import ghidra.service.graph.*;
|
import ghidra.service.graph.*;
|
||||||
import ghidra.util.*;
|
import ghidra.util.HelpLocation;
|
||||||
|
import ghidra.util.Msg;
|
||||||
import ghidra.util.exception.CancelledException;
|
import ghidra.util.exception.CancelledException;
|
||||||
import ghidra.util.exception.GraphException;
|
import ghidra.util.exception.GraphException;
|
||||||
import ghidra.util.task.Task;
|
import ghidra.util.task.Task;
|
||||||
@@ -53,55 +56,6 @@ public class BlockGraphTask extends Task {
|
|||||||
|
|
||||||
private ColorizingService colorizingService;
|
private ColorizingService colorizingService;
|
||||||
|
|
||||||
/**
|
|
||||||
* Edge flow tags
|
|
||||||
*/
|
|
||||||
protected final static int FALLTHROUGH = 0;
|
|
||||||
protected final static int CONDITIONAL_RETURN = 1;
|
|
||||||
protected final static int UNCONDITIONAL_JUMP = 2;
|
|
||||||
protected final static int CONDITIONAL_JUMP = 3;
|
|
||||||
protected final static int UNCONDITIONAL_CALL = 4;
|
|
||||||
protected final static int CONDITIONAL_CALL = 5;
|
|
||||||
protected final static int TERMINATOR = 6;
|
|
||||||
protected final static int COMPUTED = 7;
|
|
||||||
protected final static int INDIRECTION = 8;
|
|
||||||
protected final static int ENTRY = 9; // from Entry Nexus
|
|
||||||
|
|
||||||
protected final static String[] edgeNames =
|
|
||||||
{ "1", "2", "3", "4", "5", "6", "7", "13", "14", "15" };
|
|
||||||
|
|
||||||
// @formatter:off
|
|
||||||
protected final static String[] edgeTypes = {
|
|
||||||
"Fall-Through",
|
|
||||||
"Conditional-Return",
|
|
||||||
"Unconditional-Jump",
|
|
||||||
"Conditional-Jump",
|
|
||||||
"Unconditional-Call",
|
|
||||||
"Conditional-Call",
|
|
||||||
"Terminator",
|
|
||||||
"Computed",
|
|
||||||
"Indirection",
|
|
||||||
"Entry"
|
|
||||||
};
|
|
||||||
// @formatter:on
|
|
||||||
|
|
||||||
private final static String ENTRY_NODE = "Entry";
|
|
||||||
// "1"; // beginning of a block, someone calls it
|
|
||||||
private final static String BODY_NODE = "Body";
|
|
||||||
// "2"; // Body block, no flow
|
|
||||||
private final static String EXIT_NODE = "Exit";
|
|
||||||
// "3"; // Terminator
|
|
||||||
private final static String SWITCH_NODE = "Switch";
|
|
||||||
// "4"; // Switch/computed jump
|
|
||||||
private final static String BAD_NODE = "Bad";
|
|
||||||
// "5"; // Bad destination
|
|
||||||
private final static String DATA_NODE = "Data";
|
|
||||||
// "6"; // Data Node, used for indirection
|
|
||||||
private final static String ENTRY_NEXUS = "Entry-Nexus";
|
|
||||||
// "7"; //
|
|
||||||
private final static String EXTERNAL_NODE = "External";
|
|
||||||
// "8"; // node is external to program
|
|
||||||
|
|
||||||
private final static String ENTRY_NEXUS_NAME = "Entry Points";
|
private final static String ENTRY_NEXUS_NAME = "Entry Points";
|
||||||
private CodeBlockModel blockModel;
|
private CodeBlockModel blockModel;
|
||||||
private AddressSetView selection;
|
private AddressSetView selection;
|
||||||
@@ -113,15 +67,17 @@ public class BlockGraphTask extends Task {
|
|||||||
private Program program;
|
private Program program;
|
||||||
private AddressSetView graphScope;
|
private AddressSetView graphScope;
|
||||||
private String graphTitle;
|
private String graphTitle;
|
||||||
|
private ProgramGraphType graphType;
|
||||||
|
|
||||||
public BlockGraphTask(String actionName, boolean graphEntryPointNexus, boolean showCode,
|
public BlockGraphTask(ProgramGraphType graphType,
|
||||||
boolean reuseGraph, boolean appendGraph, PluginTool tool, ProgramSelection selection,
|
boolean graphEntryPointNexus, boolean reuseGraph, boolean appendGraph,
|
||||||
ProgramLocation location, CodeBlockModel blockModel,
|
PluginTool tool, ProgramSelection selection, ProgramLocation location,
|
||||||
GraphDisplayProvider graphProvider) {
|
CodeBlockModel blockModel, GraphDisplayProvider graphProvider) {
|
||||||
|
|
||||||
super("Graph Program", true, false, true);
|
super("Graph Program", true, false, true);
|
||||||
|
this.graphType = graphType;
|
||||||
this.graphEntryPointNexus = graphEntryPointNexus;
|
this.graphEntryPointNexus = graphEntryPointNexus;
|
||||||
this.showCode = showCode;
|
this.showCode = graphType instanceof CodeFlowGraphType;
|
||||||
this.reuseGraph = reuseGraph;
|
this.reuseGraph = reuseGraph;
|
||||||
this.appendGraph = appendGraph;
|
this.appendGraph = appendGraph;
|
||||||
this.tool = tool;
|
this.tool = tool;
|
||||||
@@ -131,7 +87,7 @@ public class BlockGraphTask extends Task {
|
|||||||
this.selection = selection;
|
this.selection = selection;
|
||||||
this.location = location;
|
this.location = location;
|
||||||
this.program = blockModel.getProgram();
|
this.program = blockModel.getProgram();
|
||||||
this.graphTitle = actionName + ": ";
|
this.graphTitle = graphType.getName() + ": ";
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -140,22 +96,25 @@ public class BlockGraphTask extends Task {
|
|||||||
@Override
|
@Override
|
||||||
public void run(TaskMonitor monitor) throws CancelledException {
|
public void run(TaskMonitor monitor) throws CancelledException {
|
||||||
this.graphScope = getGraphScopeAndGenerateGraphTitle();
|
this.graphScope = getGraphScopeAndGenerateGraphTitle();
|
||||||
AttributedGraph graph = createGraph();
|
AttributedGraph graph = createGraph(graphTitle);
|
||||||
monitor.setMessage("Generating Graph...");
|
monitor.setMessage("Generating Graph...");
|
||||||
try {
|
try {
|
||||||
GraphDisplay display = graphProvider.getGraphDisplay(reuseGraph, monitor);
|
GraphDisplay display =
|
||||||
|
graphProvider.getGraphDisplay(reuseGraph, monitor);
|
||||||
|
GraphDisplayOptions graphOptions = new ProgramGraphDisplayOptions(graphType, tool);
|
||||||
|
if (showCode) { // arrows need to be bigger as this generates larger vertices
|
||||||
|
graphOptions.setArrowLength(30);
|
||||||
|
}
|
||||||
|
|
||||||
BlockModelGraphDisplayListener listener =
|
BlockModelGraphDisplayListener listener =
|
||||||
new BlockModelGraphDisplayListener(tool, blockModel, display);
|
new BlockModelGraphDisplayListener(tool, blockModel, display);
|
||||||
addActions(display, v -> listener.getAddress(v));
|
addActions(display, v -> listener.getAddress(v));
|
||||||
display.setGraphDisplayListener(listener);
|
display.setGraphDisplayListener(listener);
|
||||||
|
|
||||||
if (showCode) {
|
if (showCode) {
|
||||||
display.defineVertexAttribute(CODE_ATTRIBUTE);
|
graphOptions.setVertexLabelOverrideAttributeKey(CODE_ATTRIBUTE);
|
||||||
display.defineVertexAttribute(SYMBOLS_ATTRIBUTE);
|
|
||||||
display.setVertexLabelAttribute(CODE_ATTRIBUTE, GraphDisplay.ALIGN_LEFT, 12, true,
|
|
||||||
codeLimitPerBlock + 1);
|
|
||||||
}
|
}
|
||||||
display.setGraph(graph, graphTitle, appendGraph, monitor);
|
display.setGraph(graph, graphOptions, graphTitle, appendGraph, monitor);
|
||||||
|
|
||||||
if (location != null) {
|
if (location != null) {
|
||||||
// initialize the graph location, but don't have the graph send an event
|
// initialize the graph location, but don't have the graph send an event
|
||||||
@@ -217,9 +176,9 @@ public class BlockGraphTask extends Task {
|
|||||||
codeLimitPerBlock = maxLines;
|
codeLimitPerBlock = maxLines;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected AttributedGraph createGraph() throws CancelledException {
|
protected AttributedGraph createGraph(String name) throws CancelledException {
|
||||||
int blockCount = 0;
|
int blockCount = 0;
|
||||||
AttributedGraph graph = new AttributedGraph();
|
AttributedGraph graph = new AttributedGraph(name, graphType);
|
||||||
|
|
||||||
CodeBlockIterator it = getBlockIterator();
|
CodeBlockIterator it = getBlockIterator();
|
||||||
List<AttributedVertex> entryPoints = new ArrayList<>();
|
List<AttributedVertex> entryPoints = new ArrayList<>();
|
||||||
@@ -334,8 +293,7 @@ public class BlockGraphTask extends Task {
|
|||||||
AttributedVertex entryNexusVertex = getEntryNexusVertex(graph);
|
AttributedVertex entryNexusVertex = getEntryNexusVertex(graph);
|
||||||
for (AttributedVertex vertex : entries) {
|
for (AttributedVertex vertex : entries) {
|
||||||
AttributedEdge edge = graph.addEdge(entryNexusVertex, vertex);
|
AttributedEdge edge = graph.addEdge(entryNexusVertex, vertex);
|
||||||
edge.setAttribute("Name", edgeNames[ENTRY]);
|
edge.setAttribute("EdgeType", ENTRY_NEXUS);
|
||||||
edge.setAttribute("EdgeType", edgeTypes[ENTRY]);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -517,99 +475,36 @@ public class BlockGraphTask extends Task {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected void setEdgeAttributes(AttributedEdge edge, CodeBlockReference ref) {
|
protected void setEdgeAttributes(AttributedEdge edge, CodeBlockReference ref) {
|
||||||
|
edge.setEdgeType(ProgramGraphType.getEdgeType(ref.getFlowType()));
|
||||||
int edgeType;
|
|
||||||
FlowType flowType = ref.getFlowType();
|
|
||||||
if (flowType == RefType.FALL_THROUGH) {
|
|
||||||
edgeType = FALLTHROUGH;
|
|
||||||
}
|
|
||||||
else if (flowType == RefType.UNCONDITIONAL_JUMP) {
|
|
||||||
edgeType = UNCONDITIONAL_JUMP;
|
|
||||||
}
|
|
||||||
else if (flowType == RefType.CONDITIONAL_JUMP) {
|
|
||||||
edgeType = CONDITIONAL_JUMP;
|
|
||||||
}
|
|
||||||
else if (flowType == RefType.UNCONDITIONAL_CALL) {
|
|
||||||
edgeType = UNCONDITIONAL_CALL;
|
|
||||||
}
|
|
||||||
else if (flowType == RefType.CONDITIONAL_CALL) {
|
|
||||||
edgeType = CONDITIONAL_CALL;
|
|
||||||
}
|
|
||||||
else if (flowType.isComputed()) {
|
|
||||||
edgeType = COMPUTED;
|
|
||||||
}
|
|
||||||
else if (flowType.isIndirect()) {
|
|
||||||
edgeType = INDIRECTION;
|
|
||||||
}
|
|
||||||
else if (flowType == RefType.TERMINATOR) {
|
|
||||||
edgeType = TERMINATOR;
|
|
||||||
}
|
|
||||||
else { // only FlowType.CONDITIONAL_TERMINATOR remains unchecked
|
|
||||||
edgeType = CONDITIONAL_RETURN;
|
|
||||||
}
|
|
||||||
// set attributes on this edge
|
|
||||||
edge.setAttribute("Name", edgeNames[edgeType]);
|
|
||||||
edge.setAttribute("EdgeType", edgeTypes[edgeType]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void setVertexAttributes(AttributedVertex vertex, CodeBlock bb, boolean isEntry) {
|
protected void setVertexAttributes(AttributedVertex vertex, CodeBlock bb, boolean isEntry) {
|
||||||
|
|
||||||
String vertexType = BODY_NODE;
|
String vertexType = BODY;
|
||||||
|
|
||||||
Address firstStartAddress = bb.getFirstStartAddress();
|
Address firstStartAddress = bb.getFirstStartAddress();
|
||||||
if (firstStartAddress.isExternalAddress()) {
|
if (firstStartAddress.isExternalAddress()) {
|
||||||
vertexType = EXTERNAL_NODE;
|
vertexType = EXTERNAL;
|
||||||
}
|
}
|
||||||
else if (isEntry) {
|
else if (isEntry) {
|
||||||
vertexType = ENTRY_NODE;
|
vertexType = ENTRY;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
FlowType flowType = bb.getFlowType();
|
FlowType flowType = bb.getFlowType();
|
||||||
if (flowType.isTerminal()) {
|
if (flowType.isTerminal()) {
|
||||||
vertexType = EXIT_NODE;
|
vertexType = EXIT;
|
||||||
}
|
}
|
||||||
else if (flowType.isComputed()) {
|
else if (flowType.isComputed()) {
|
||||||
vertexType = SWITCH_NODE;
|
vertexType = SWITCH;
|
||||||
}
|
}
|
||||||
else if (flowType == RefType.INDIRECTION) {
|
else if (flowType == RefType.INDIRECTION) {
|
||||||
vertexType = DATA_NODE;
|
vertexType = DATA;
|
||||||
}
|
}
|
||||||
else if (flowType == RefType.INVALID) {
|
else if (flowType == RefType.INVALID) {
|
||||||
vertexType = BAD_NODE;
|
vertexType = BAD;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
vertex.setVertexType(vertexType);
|
||||||
vertex.setAttribute("VertexType", vertexType);
|
|
||||||
|
|
||||||
setVertexColor(vertex, vertexType, firstStartAddress);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setVertexColor(AttributedVertex vertex, String vertexType, Address address) {
|
|
||||||
|
|
||||||
if (colorizingService == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Color color = colorizingService.getBackgroundColor(address);
|
|
||||||
if (color == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// color format: RGBrrrgggbbb
|
|
||||||
// -where rrr/ggg/bbb is a three digit int value for each respective color range
|
|
||||||
String rgb = "RGB" + HTMLUtilities.toRGBString(color);
|
|
||||||
vertex.setAttribute("Color", rgb); // sets the vertex color
|
|
||||||
|
|
||||||
// This value triggers the vertex to be painted with its color and not a
|
|
||||||
// while background.
|
|
||||||
if (showCode) {
|
|
||||||
// our own custom override of Labels/Icons
|
|
||||||
vertex.setAttribute("VertexType", "ColorFilled");
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// the default preferences for VertexType
|
|
||||||
vertex.setAttribute("VertexType", vertexType + ".Filled");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private AttributedVertex getEntryNexusVertex(AttributedGraph graph) {
|
private AttributedVertex getEntryNexusVertex(AttributedGraph graph) {
|
||||||
|
|||||||
+10
-2
@@ -15,6 +15,8 @@
|
|||||||
*/
|
*/
|
||||||
package ghidra.graph.program;
|
package ghidra.graph.program;
|
||||||
|
|
||||||
|
import ghidra.graph.DataFlowGraphType;
|
||||||
|
import ghidra.graph.ProgramGraphType;
|
||||||
import ghidra.program.model.address.Address;
|
import ghidra.program.model.address.Address;
|
||||||
import ghidra.program.model.listing.*;
|
import ghidra.program.model.listing.*;
|
||||||
import ghidra.program.model.symbol.Reference;
|
import ghidra.program.model.symbol.Reference;
|
||||||
@@ -53,6 +55,7 @@ public class DataReferenceGraph extends AttributedGraph {
|
|||||||
* @param depth the number of hops to graph per call (0 for recursion until no more hops)
|
* @param depth the number of hops to graph per call (0 for recursion until no more hops)
|
||||||
*/
|
*/
|
||||||
public DataReferenceGraph(Program program, int depth) {
|
public DataReferenceGraph(Program program, int depth) {
|
||||||
|
super("Data Reference", new DataFlowGraphType());
|
||||||
this.program = program;
|
this.program = program;
|
||||||
this.depthPerStep = depth;
|
this.depthPerStep = depth;
|
||||||
}
|
}
|
||||||
@@ -106,13 +109,14 @@ public class DataReferenceGraph extends AttributedGraph {
|
|||||||
|
|
||||||
private void setupEdge(AttributedEdge edge, Reference ref) {
|
private void setupEdge(AttributedEdge edge, Reference ref) {
|
||||||
edge.setAttribute(REF_SOURCE_ATTRIBUTE, ref.getSource().getDisplayString());
|
edge.setAttribute(REF_SOURCE_ATTRIBUTE, ref.getSource().getDisplayString());
|
||||||
edge.setAttribute(REF_TYPE_ATTRIBUTE, ref.getReferenceType().toString());
|
edge.setEdgeType(ProgramGraphType.getEdgeType(ref.getReferenceType()));
|
||||||
if (ref.getSymbolID() != -1) {
|
if (ref.getSymbolID() != -1) {
|
||||||
edge.setAttribute(REF_SYMBOL_ATTRIBUTE,
|
edge.setAttribute(REF_SYMBOL_ATTRIBUTE,
|
||||||
program.getSymbolTable().getSymbol(ref.getSymbolID()).getName());
|
program.getSymbolTable().getSymbol(ref.getSymbolID()).getName());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private void setupVertex(AttributedVertex vertex) {
|
private void setupVertex(AttributedVertex vertex) {
|
||||||
Address address =
|
Address address =
|
||||||
program.getAddressFactory().getAddress(vertex.getAttribute(ADDRESS_ATTRIBUTE));
|
program.getAddressFactory().getAddress(vertex.getAttribute(ADDRESS_ATTRIBUTE));
|
||||||
@@ -122,9 +126,13 @@ public class DataReferenceGraph extends AttributedGraph {
|
|||||||
CodeUnit unit = program.getListing().getCodeUnitContaining(address);
|
CodeUnit unit = program.getListing().getCodeUnitContaining(address);
|
||||||
if (unit instanceof Data) {
|
if (unit instanceof Data) {
|
||||||
vertex.setAttribute(DATA_ATTRIBUTE, ((Data) unit).getBaseDataType().getName());
|
vertex.setAttribute(DATA_ATTRIBUTE, ((Data) unit).getBaseDataType().getName());
|
||||||
|
vertex.setVertexType(ProgramGraphType.DATA);
|
||||||
}
|
}
|
||||||
else if (unit instanceof Instruction) {
|
else if (unit instanceof Instruction) {
|
||||||
vertex.setAttribute("Icon", "TriangleDown");
|
vertex.setVertexType(ProgramGraphType.INSTRUCTION);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
vertex.setVertexType(ProgramGraphType.STACK);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+9
-9
@@ -17,6 +17,8 @@ package ghidra.graph.program;
|
|||||||
|
|
||||||
import docking.widgets.EventTrigger;
|
import docking.widgets.EventTrigger;
|
||||||
import ghidra.framework.plugintool.PluginTool;
|
import ghidra.framework.plugintool.PluginTool;
|
||||||
|
import ghidra.graph.DataFlowGraphType;
|
||||||
|
import ghidra.graph.ProgramGraphDisplayOptions;
|
||||||
import ghidra.program.model.address.Address;
|
import ghidra.program.model.address.Address;
|
||||||
import ghidra.program.model.address.AddressSet;
|
import ghidra.program.model.address.AddressSet;
|
||||||
import ghidra.program.model.listing.CodeUnit;
|
import ghidra.program.model.listing.CodeUnit;
|
||||||
@@ -35,6 +37,7 @@ import ghidra.util.task.TaskMonitor;
|
|||||||
*/
|
*/
|
||||||
public class DataReferenceGraphTask extends Task {
|
public class DataReferenceGraphTask extends Task {
|
||||||
|
|
||||||
|
private static final String VERTEX_COLOR_OVERRIDE = "Color";
|
||||||
private String graphTitle;
|
private String graphTitle;
|
||||||
private GraphDisplayProvider graphProvider;
|
private GraphDisplayProvider graphProvider;
|
||||||
private boolean reuseGraph;
|
private boolean reuseGraph;
|
||||||
@@ -117,7 +120,7 @@ public class DataReferenceGraphTask extends Task {
|
|||||||
/* TODO
|
/* TODO
|
||||||
* Want to make initial vertex easy to find, is this the best way?
|
* Want to make initial vertex easy to find, is this the best way?
|
||||||
*/
|
*/
|
||||||
centerVertex.setAttribute("Color", "Orange");
|
centerVertex.setAttribute(VERTEX_COLOR_OVERRIDE, "Orange");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (CancelledException e) {
|
catch (CancelledException e) {
|
||||||
@@ -128,19 +131,16 @@ public class DataReferenceGraphTask extends Task {
|
|||||||
try {
|
try {
|
||||||
if (display == null) {
|
if (display == null) {
|
||||||
display = graphProvider.getGraphDisplay(reuseGraph, monitor);
|
display = graphProvider.getGraphDisplay(reuseGraph, monitor);
|
||||||
display.defineEdgeAttribute(DataReferenceGraph.REF_SOURCE_ATTRIBUTE);
|
|
||||||
display.defineEdgeAttribute(DataReferenceGraph.REF_TYPE_ATTRIBUTE);
|
|
||||||
display.defineEdgeAttribute(DataReferenceGraph.REF_SYMBOL_ATTRIBUTE);
|
|
||||||
display.defineVertexAttribute(DataReferenceGraph.DATA_ATTRIBUTE);
|
|
||||||
display.setVertexLabelAttribute(DataReferenceGraph.LABEL_ATTRIBUTE,
|
|
||||||
GraphDisplay.ALIGN_LEFT, 12, true, maxLabelLength);
|
|
||||||
|
|
||||||
DataReferenceGraphDisplayListener listener =
|
DataReferenceGraphDisplayListener listener =
|
||||||
new DataReferenceGraphDisplayListener(tool, display, program, totalMaxDepth);
|
new DataReferenceGraphDisplayListener(tool, display, program, totalMaxDepth);
|
||||||
display.setGraphDisplayListener(listener);
|
display.setGraphDisplayListener(listener);
|
||||||
}
|
}
|
||||||
|
GraphDisplayOptions graphDisplayOptions =
|
||||||
display.setGraph(graph, graphTitle, appendGraph, monitor);
|
new ProgramGraphDisplayOptions(new DataFlowGraphType(), tool);
|
||||||
|
// set the vertex color override so that we can color "initial" vertices differently
|
||||||
|
graphDisplayOptions.setVertexColorOverrideAttributeKey(VERTEX_COLOR_OVERRIDE);
|
||||||
|
display.setGraph(graph, graphDisplayOptions, graphTitle, appendGraph, monitor);
|
||||||
|
|
||||||
if (location != null) {
|
if (location != null) {
|
||||||
// initialize the graph location, but don't have the graph send an event
|
// initialize the graph location, but don't have the graph send an event
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user