diff --git a/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/DefaultGraphDisplay.java b/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/DefaultGraphDisplay.java
index 3cb3d8c91a..931e0de67b 100644
--- a/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/DefaultGraphDisplay.java
+++ b/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/DefaultGraphDisplay.java
@@ -187,7 +187,7 @@ public class DefaultGraphDisplay implements GraphDisplay {
* Multi-selection is done in a free-form traced shape instead of a rectangle
*/
private boolean freeFormSelection;
-
+
/**
* Handles all mouse interaction
*/
diff --git a/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/ProgramGraphFunctions.java b/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/ProgramGraphFunctions.java
index 95834b2269..e3249d1ac5 100644
--- a/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/ProgramGraphFunctions.java
+++ b/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/ProgramGraphFunctions.java
@@ -41,8 +41,7 @@ abstract class ProgramGraphFunctions {
/**
* a default implementation of a {@link ShapeFactory} to supply shapes for attributed vertices and edges
*/
- private static ShapeFactory shapeFactory =
- new ShapeFactory<>(n -> 50, n -> 1.0f);
+ private static ShapeFactory shapeFactory = new ShapeFactory<>(n -> 50, n -> 1.0f);
/**
* return various 'Shapes' based on an attribute name
@@ -79,34 +78,36 @@ abstract class ProgramGraphFunctions {
}
}
+ /*
+ * 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) {
- try {
- String vertexType = vertex.getAttribute("VertexType");
- Shape shape = byShapeName(vertex, vertex.getAttribute("Icon"));
- if (shape != null) {
- return shape;
- }
- 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);
- }
+ Shape shape = byShapeName(vertex, vertex.getAttribute("Icon"));
+ if (shape != null) {
+ return shape;
}
- catch (Exception ex) {
- // just return a rectangle
+ 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);
+ }
}
/**
diff --git a/Ghidra/Features/ProgramGraph/src/main/help/help/topics/ProgramGraphPlugin/ProgramGraph.htm b/Ghidra/Features/ProgramGraph/src/main/help/help/topics/ProgramGraphPlugin/ProgramGraph.htm
index 1a71d1c47e..014afa7086 100644
--- a/Ghidra/Features/ProgramGraph/src/main/help/help/topics/ProgramGraphPlugin/ProgramGraph.htm
+++ b/Ghidra/Features/ProgramGraph/src/main/help/help/topics/ProgramGraphPlugin/ProgramGraph.htm
@@ -27,10 +27,16 @@
another. The nodes of the graph represent blocks of code and the edges represent flow between
the blocks of code.
- There are two basic graph types, Block Flow and There are two basic flow graph types, Block Flow and Call Graph. Different colors and shapes are used to
- depict each node and the flow between them. Multiple graphs of either type can exist at any
- time.
+ depict each node and the flow between them.
+
+ Data graphs show reference relationships between memory
+ locations. They can be constrained to just "To References", just "From References",
+ or both. By default a single hop is graphed with the option to extend from within the
+ graph.
+
+ Multiple graphs of any or all types can exist at any time.
A Call Graph depicts subroutines as nodes. Calls between subroutines are shown as
@@ -433,7 +439,56 @@
set of block models configured into the tool.
+
+ Graph Data References
+ A graph of data references can be created with Graph
+ Data then selecting To References, From References or To/From References.
+ Only the selected references will be included. By default only a single layer of references will
+ be graphed, this can be adjusted using the Edit
+ Tool Options
Graph
+ Max Reference Depth
+
+
+
+
Unlike flow graphs, where only the selection is
+ included in the graph, the selection and its references are included.
+
+ To graph Data References,
+
+
+ - Select the data to start from in the listing
+
- Select Graph
Data
+ -
+ Select one of
+
+
+ - To References - selected data and all references to it
+
+ - From References - selected data all references from it
+
+ - To/From References - selected data and all references to and from it
+
+
+
+ - A new graph window is created with the selected data and the specified references.
+ By default this includes one layer of references but this may be adjusted in the options
+ as specified above, or dynamically using the Add References
+ action on the desired node
+
+
+
+
+
+ Add References
+
+ Allows the user to add references starting from
+ the selected nodes, with To References, From References or Bidirectional as selected.
+
+
+
+
diff --git a/Ghidra/Features/ProgramGraph/src/main/java/ghidra/graph/program/DataReferenceGraph.java b/Ghidra/Features/ProgramGraph/src/main/java/ghidra/graph/program/DataReferenceGraph.java
new file mode 100644
index 0000000000..b3c9bac058
--- /dev/null
+++ b/Ghidra/Features/ProgramGraph/src/main/java/ghidra/graph/program/DataReferenceGraph.java
@@ -0,0 +1,214 @@
+/* ###
+ * IP: GHIDRA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package ghidra.graph.program;
+
+import ghidra.program.model.address.Address;
+import ghidra.program.model.listing.*;
+import ghidra.program.model.symbol.Reference;
+import ghidra.service.graph.*;
+import ghidra.util.exception.CancelledException;
+import ghidra.util.task.TaskMonitor;
+
+/*
+ * A graph meant to hold data reference information
+ *
+ * Recursively adds references from a specified address to a specified number
+ * of hops. Displays the reference type and source as attributes. Supports graph
+ * extension in place.
+ */
+public class DataReferenceGraph extends AttributedGraph {
+ public static final String REF_SOURCE_ATTRIBUTE = "Source";
+ public static final String REF_TYPE_ATTRIBUTE = "Type";
+ public static final String REF_SYMBOL_ATTRIBUTE = "Symbol";
+ public static final String DATA_ATTRIBUTE = "Data Type";
+ public static final String ADDRESS_ATTRIBUTE = "Address";
+ public static final String LABEL_ATTRIBUTE = "Label";
+
+ public enum Directions {
+ TO_ONLY, FROM_ONLY, BOTH_WAYS
+ }
+
+ private Program program;
+ private int depthPerStep;
+
+ /*
+ * Constructor
+ *
+ * @param program the Program to pull references from
+ * @param depth the number of hops to graph per call (0 for recursion until no more hops)
+ */
+ public DataReferenceGraph(Program program, int depth) {
+ this.program = program;
+ this.depthPerStep = depth;
+ }
+
+ /*
+ * Constructs the name of the Vertex for the specified address. All addresses in the same CodeUnit
+ * will use the same name/Vertex. If a symbol is available it will use that, otherwise the
+ * string of the address
+ *
+ * @param address address to create the name for
+ * @return String to be used as a VertexId
+ */
+ public String makeName(Address address) {
+ CodeUnit unit = program.getListing().getCodeUnitContaining(address);
+ if (unit == null) {
+ return address.toString();
+ }
+ Address unitAddress = unit.getAddress();
+
+ String name;
+ if (program.getSymbolTable().getPrimarySymbol(unitAddress) != null) {
+ name = program.getSymbolTable().getPrimarySymbol(unitAddress).getName(true);
+ }
+ else {
+ name = unitAddress.toString();
+ }
+
+ return name;
+ }
+
+ /*
+ * Graphs the references starting at a specified address. If the specified address
+ * doesn't lead to any references the graph will contain only that address.
+ *
+ * @param baseAddress Address to start graphing from
+ * @param direction controls whether to, from, or both references are followed
+ * @param monitor monitor for cancellation
+ */
+ public AttributedVertex graphFrom(Address baseAddress, Directions direction,
+ TaskMonitor monitor) throws CancelledException {
+ if (baseAddress == null) {
+ return null;
+ }
+ AttributedVertex baseVertex = new AttributedVertex(makeName(baseAddress));
+ baseVertex.setAttribute(ADDRESS_ATTRIBUTE, baseAddress.toString());
+ setupVertex(baseVertex);
+ addVertex(baseVertex);
+ recurseGraph(baseAddress, depthPerStep, direction, monitor);
+ return baseVertex;
+ }
+
+ private void setupEdge(AttributedEdge edge, Reference ref) {
+ edge.setAttribute(REF_SOURCE_ATTRIBUTE, ref.getSource().getDisplayString());
+ edge.setAttribute(REF_TYPE_ATTRIBUTE, ref.getReferenceType().toString());
+ if (ref.getSymbolID() != -1) {
+ edge.setAttribute(REF_SYMBOL_ATTRIBUTE,
+ program.getSymbolTable().getSymbol(ref.getSymbolID()).getName());
+ }
+ }
+
+ private void setupVertex(AttributedVertex vertex) {
+ Address address =
+ program.getAddressFactory().getAddress(vertex.getAttribute(ADDRESS_ATTRIBUTE));
+ if (address == null) {
+ return;
+ }
+ CodeUnit unit = program.getListing().getCodeUnitContaining(address);
+ if (unit instanceof Data) {
+ vertex.setAttribute(DATA_ATTRIBUTE, ((Data) unit).getBaseDataType().getName());
+ }
+ else if (unit instanceof Instruction) {
+ vertex.setAttribute("Icon", "TriangleDown");
+ }
+ }
+
+ /*
+ * recursion function, maxDepth of zero indicates go to end
+ */
+ private void recurseGraph(Address startAddress, int maxDepth, Directions direction,
+ TaskMonitor monitor) throws CancelledException {
+ AttributedVertex startVertex = getVertex(makeName(startAddress));
+
+ if (direction != Directions.FROM_ONLY) {
+ for (Reference ref : program.getListing()
+ .getCodeUnitContaining(startAddress)
+ .getReferenceIteratorTo()) {
+ if (!ref.getReferenceType().isFlow()) {
+ Address nextAddress = processReference(Directions.TO_ONLY, startVertex, ref);
+ monitor.checkCanceled();
+ if (nextAddress != null) {
+ /*
+ * maxDepth > 1 -> subtract 1 to count this level, and keep going
+ * maxDepth = 0 -> no limit, always keep going
+ * maxDepth = 1 -> This is the last one, stop recursion
+ */
+ if (maxDepth > 1) {
+ recurseGraph(nextAddress, maxDepth - 1, direction, monitor);
+ }
+ else if (maxDepth == 0) {
+ recurseGraph(nextAddress, 0, direction, monitor);
+ }
+ }
+ }
+ }
+ }
+
+ if (direction != Directions.TO_ONLY) {
+ for (Reference ref : program.getListing()
+ .getCodeUnitContaining(startAddress)
+ .getReferencesFrom()) {
+ if (!ref.getReferenceType().isFlow()) {
+ Address nextAddress = processReference(Directions.FROM_ONLY, startVertex, ref);
+ monitor.checkCanceled();
+ if (nextAddress != null) {
+ /*
+ * maxDepth > 1 -> subtract 1 to count this level, and keep going
+ * maxDepth = 0 -> no limit, always keep going
+ * maxDepth = 1 -> This is the last one, stop recursion
+ */
+ if (maxDepth > 1) {
+ recurseGraph(nextAddress, maxDepth - 1, direction, monitor);
+ }
+ else if (maxDepth == 0) {
+ recurseGraph(nextAddress, 0, direction, monitor);
+ }
+ }
+ }
+
+ }
+ }
+ }
+
+ private Address processReference(Directions direction, AttributedVertex startVertex,
+ Reference ref) {
+ Address targetAddress;
+ if (direction == Directions.TO_ONLY) {
+ targetAddress = ref.getFromAddress();
+ }
+ else { //should be FROM_ONLY
+ targetAddress = ref.getToAddress();
+ }
+ AttributedVertex newVertex = new AttributedVertex(makeName(targetAddress));
+ newVertex.setAttribute(ADDRESS_ATTRIBUTE, targetAddress.toString());
+ setupVertex(newVertex);
+ AttributedEdge edge;
+ if (direction == Directions.TO_ONLY) {
+ edge = addEdge(newVertex, startVertex);
+ }
+ else { //should be FROM_ONLY
+ edge = addEdge(startVertex, newVertex);
+ }
+ /*
+ * if we've seen this before don't do it again
+ */
+ if (edge.hasAttribute("Weight")) {
+ return null;
+ }
+ setupEdge(edge, ref);
+ return targetAddress;
+ }
+}
diff --git a/Ghidra/Features/ProgramGraph/src/main/java/ghidra/graph/program/DataReferenceGraphDisplayListener.java b/Ghidra/Features/ProgramGraph/src/main/java/ghidra/graph/program/DataReferenceGraphDisplayListener.java
new file mode 100644
index 0000000000..c3de982abd
--- /dev/null
+++ b/Ghidra/Features/ProgramGraph/src/main/java/ghidra/graph/program/DataReferenceGraphDisplayListener.java
@@ -0,0 +1,140 @@
+/* ###
+ * IP: GHIDRA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package ghidra.graph.program;
+
+import java.util.*;
+
+import docking.action.builder.ActionBuilder;
+import docking.widgets.EventTrigger;
+import ghidra.app.plugin.core.graph.AddressBasedGraphDisplayListener;
+import ghidra.framework.plugintool.PluginTool;
+import ghidra.program.model.address.*;
+import ghidra.program.model.listing.Program;
+import ghidra.service.graph.*;
+import ghidra.util.HelpLocation;
+import ghidra.util.task.TaskLauncher;
+
+/*
+ * Listener for a GraphDisplay holding a Reference graph. Allows for the extension of the
+ * graph from a specified node
+ */
+public class DataReferenceGraphDisplayListener extends AddressBasedGraphDisplayListener {
+
+ private int stepDepth;
+
+ public DataReferenceGraphDisplayListener(PluginTool tool, GraphDisplay display, Program program,
+ int depth) {
+ super(tool, program, display);
+
+ stepDepth = depth;
+
+ HelpLocation helpLoc = new HelpLocation("ProgramGraphPlugin", "Add_References");
+ display.addAction(new ActionBuilder("Add To/From References", "Data Graph")
+ .popupMenuPath("Add Bidirectional References For Selection")
+ .withContext(VertexGraphActionContext.class)
+ .helpLocation(helpLoc)
+ .onAction(this::addToGraph)
+ .build());
+ display.addAction(new ActionBuilder("Add To References", "Data Graph")
+ .popupMenuPath("Add References To Selection")
+ .withContext(VertexGraphActionContext.class)
+ .helpLocation(helpLoc)
+ .onAction(this::addTosToGraph)
+ .build());
+ display.addAction(new ActionBuilder("Add From References", "Data Graph")
+ .popupMenuPath("Add References From Selection")
+ .withContext(VertexGraphActionContext.class)
+ .helpLocation(helpLoc)
+ .onAction(this::addFromsToGraph)
+ .build());
+ }
+
+ private void addToGraph(VertexGraphActionContext context) {
+ doAdd(context, DataReferenceGraph.Directions.BOTH_WAYS);
+ }
+
+ private void addTosToGraph(VertexGraphActionContext context) {
+ doAdd(context, DataReferenceGraph.Directions.TO_ONLY);
+ }
+
+ private void addFromsToGraph(VertexGraphActionContext context) {
+ doAdd(context, DataReferenceGraph.Directions.FROM_ONLY);
+ }
+
+ private void doAdd(VertexGraphActionContext context, DataReferenceGraph.Directions direction) {
+ AddressSet addresses = new AddressSet();
+ for (AttributedVertex vertex : context.getSelectedVertices()) {
+ addresses.add(getAddress(vertex));
+ }
+ DataReferenceGraphTask task = new DataReferenceGraphTask(tool, program, addresses,
+ graphDisplay, stepDepth, direction);
+ new TaskLauncher(task, tool.getToolFrame());
+
+ /* I don't know why the selection was going all wonky, but reset it */
+ graphDisplay.selectVertices(context.getSelectedVertices(), EventTrigger.INTERNAL_ONLY);
+ }
+
+ @Override
+ public GraphDisplayListener cloneWith(GraphDisplay newDisplay) {
+ return new DataReferenceGraphDisplayListener(tool, newDisplay, program, stepDepth);
+ }
+
+ @Override
+ protected Set getVertices(AddressSetView selection) {
+ if (selection.isEmpty()) {
+ return Collections.emptySet();
+ }
+
+ Set vertices = new HashSet<>();
+ DataReferenceGraph graph = (DataReferenceGraph) graphDisplay.getGraph();
+ for (AddressRange range : selection) {
+ for (Address address : range) {
+ AttributedVertex vertex = graph.getVertex(graph.makeName(address));
+ if (vertex != null) {
+ vertices.add(vertex);
+ }
+ }
+ }
+
+ return vertices;
+ }
+
+ @Override
+ protected AddressSet getAddresses(Set vertexIds) {
+ AddressSet addrSet = new AddressSet();
+
+ for (AttributedVertex vertex : vertexIds) {
+ Address addr = getAddress(vertex.getName());
+ if (addr != null) {
+ addrSet.add(addr);
+ }
+ }
+ return addrSet;
+ }
+
+ @Override
+ protected Address getAddress(String vertexIdString) {
+ AttributedVertex vertex = graphDisplay.getGraph().getVertex(vertexIdString);
+ return program.getAddressFactory()
+ .getAddress(vertex.getAttribute(DataReferenceGraph.ADDRESS_ATTRIBUTE));
+ }
+
+ @Override
+ protected String getVertexId(Address address) {
+ DataReferenceGraph graph = (DataReferenceGraph) graphDisplay.getGraph();
+ return graph.makeName(address);
+ }
+}
diff --git a/Ghidra/Features/ProgramGraph/src/main/java/ghidra/graph/program/DataReferenceGraphTask.java b/Ghidra/Features/ProgramGraph/src/main/java/ghidra/graph/program/DataReferenceGraphTask.java
new file mode 100644
index 0000000000..765e262d64
--- /dev/null
+++ b/Ghidra/Features/ProgramGraph/src/main/java/ghidra/graph/program/DataReferenceGraphTask.java
@@ -0,0 +1,158 @@
+/* ###
+ * IP: GHIDRA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package ghidra.graph.program;
+
+import docking.widgets.EventTrigger;
+import ghidra.framework.plugintool.PluginTool;
+import ghidra.program.model.address.Address;
+import ghidra.program.model.address.AddressSet;
+import ghidra.program.model.listing.CodeUnit;
+import ghidra.program.model.listing.Program;
+import ghidra.program.util.ProgramLocation;
+import ghidra.program.util.ProgramSelection;
+import ghidra.service.graph.*;
+import ghidra.util.Msg;
+import ghidra.util.exception.CancelledException;
+import ghidra.util.exception.GraphException;
+import ghidra.util.task.Task;
+import ghidra.util.task.TaskMonitor;
+
+/*
+ * Task for creating and displaying a data reference graph
+ */
+public class DataReferenceGraphTask extends Task {
+
+ private String graphTitle;
+ private GraphDisplayProvider graphProvider;
+ private boolean reuseGraph;
+ private boolean appendGraph;
+ private PluginTool tool;
+ private Program program;
+ private int totalMaxDepth;
+ private int maxLabelLength;
+ private ProgramLocation location;
+ private DataReferenceGraph.Directions direction;
+ private AddressSet addresses;
+ private GraphDisplay display;
+
+ /*
+ * Constructor intended for creating a new graph
+ */
+ public DataReferenceGraphTask(boolean reuseGraph, boolean appendToGraph, PluginTool tool,
+ ProgramSelection selection, ProgramLocation location,
+ GraphDisplayProvider graphProvider, int maxDepth, int maxLines,
+ DataReferenceGraph.Directions direction) {
+ super("Graph Data References", true, false, true);
+
+ this.reuseGraph = reuseGraph;
+ this.appendGraph = appendToGraph;
+ this.tool = tool;
+ this.graphProvider = graphProvider;
+ this.program = location.getProgram();
+ this.graphTitle = "Data references for: ";
+ this.totalMaxDepth = maxDepth;
+ this.maxLabelLength = maxLines;
+ this.location = location;
+ this.direction = direction;
+ this.display = null;
+ graphTitle = graphTitle + location.getAddress().toString();
+
+ Address locationAddress = location.getAddress();
+ addresses = new AddressSet(locationAddress);
+ if ((selection != null) && (selection.contains(locationAddress))) {
+ addresses.add(selection);
+ }
+ else {
+ /* grab current address and the code unit it is part of so we don't miss stuff assigned to say the structure */
+ Address unitAddress =
+ program.getListing().getCodeUnitContaining(locationAddress).getAddress();
+ addresses.add(unitAddress);
+ }
+ }
+
+ /*
+ * constructor intended for extending a graph in the same display
+ */
+ public DataReferenceGraphTask(PluginTool tool, Program program, AddressSet addresses,
+ GraphDisplay display, int maxDepth, DataReferenceGraph.Directions direction) {
+ super("Graph Data References", true, false, true);
+
+ this.reuseGraph = true;
+ this.appendGraph = true;
+ this.tool = tool;
+ this.display = display;
+ this.program = program;
+ this.graphTitle = display.getGraphTitle();
+ this.totalMaxDepth = maxDepth;
+ this.maxLabelLength = 10;
+ this.direction = direction;
+ this.addresses = addresses;
+ }
+
+ @Override
+ public void run(TaskMonitor monitor) throws CancelledException {
+ DataReferenceGraph graph = new DataReferenceGraph(program, totalMaxDepth);
+
+ monitor.setMessage("Generating Graph...");
+ monitor.setIndeterminate(true);
+
+ try {
+ for (CodeUnit unit : program.getListing().getCodeUnits(addresses, true)) {
+ monitor.checkCanceled();
+ AttributedVertex centerVertex =
+ graph.graphFrom(unit.getAddress(), direction, monitor);
+ /* TODO
+ * Want to make initial vertex easy to find, is this the best way?
+ */
+ centerVertex.setAttribute("Color", "Orange");
+ }
+ }
+ catch (CancelledException e) {
+ monitor.setMessage("Cancelling");
+ graphTitle = graphTitle + " (partial)";
+ }
+
+ try {
+ if (display == null) {
+ 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 =
+ new DataReferenceGraphDisplayListener(tool, display, program, totalMaxDepth);
+ display.setGraphDisplayListener(listener);
+ }
+
+ display.setGraph(graph, graphTitle, appendGraph, monitor);
+
+ if (location != null) {
+ // initialize the graph location, but don't have the graph send an event
+ AttributedVertex vertex = graph.getVertex(graph.makeName(location.getAddress()));
+ display.setFocusedVertex(vertex, EventTrigger.INTERNAL_ONLY);
+ }
+ }
+ catch (GraphException e) {
+ if (!monitor.isCancelled()) {
+ Msg.showError(this, null, "Reference Graph Error",
+ "Unexpected error while graphing: " + e.getMessage(), e);
+ }
+ }
+ }
+}
diff --git a/Ghidra/Features/ProgramGraph/src/main/java/ghidra/graph/program/ProgramGraphPlugin.java b/Ghidra/Features/ProgramGraph/src/main/java/ghidra/graph/program/ProgramGraphPlugin.java
index 8a687cda9d..d9fac4b83b 100644
--- a/Ghidra/Features/ProgramGraph/src/main/java/ghidra/graph/program/ProgramGraphPlugin.java
+++ b/Ghidra/Features/ProgramGraph/src/main/java/ghidra/graph/program/ProgramGraphPlugin.java
@@ -4,9 +4,9 @@
* 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.
@@ -42,7 +42,7 @@ import ghidra.util.exception.NotFoundException;
import ghidra.util.task.TaskLauncher;
/**
- * Plugin for generating program graphs. It uses the GraphServiceBroker to consume/display
+ * Plugin for generating program graphs. It uses the GraphServiceBroker to consume/display
* the graphs that it generates. This plugin generates several different types of program graphs.
* Both the "Block flow" and "code flow" actions generate graph of basic block flows. The only
* difference is that the "code flow" action generates a graph that
@@ -73,6 +73,7 @@ public class ProgramGraphPlugin extends ProgramPlugin
private static final String REUSE_GRAPH = "Reuse Graph";
private static final String GRAPH_ENTRY_POINT_NEXUS = "Graph Entry Point Nexus";
private static final String FORCE_LOCATION_DISPLAY_OPTION = "Force Location Visible on Graph";
+ private static final String MAX_DEPTH_OPTION = "Max Reference Depth";
public static final String MENU_GRAPH = "&Graph";
private BlockModelService blockModelService;
@@ -86,6 +87,7 @@ public class ProgramGraphPlugin extends ProgramPlugin
private boolean graphEntryPointNexus = false;
private int codeLimitPerBlock = 10;
+ private int dataMaxDepth = 1;
private ToggleDockingAction forceLocationVisibleAction;
@@ -117,6 +119,8 @@ public class ProgramGraphPlugin extends ProgramPlugin
"Specifies whether or not " +
"graph displays should force the visible graph to pan and/or scale to ensure that focused " +
"locations are visible.");
+ options.registerOption(MAX_DEPTH_OPTION, 1, help,
+ "Specifies max depth of data references to graph (0 for no limit)");
setOptions(options);
options.addOptionsChangeListener(this);
@@ -147,7 +151,7 @@ public class ProgramGraphPlugin extends ProgramPlugin
/**
* Notification that an option changed.
- *
+ *
* @param options
* options object containing the property that changed
* @param optionName
@@ -170,21 +174,20 @@ public class ProgramGraphPlugin extends ProgramPlugin
if (reuseGraphAction != null) {
reuseGraphAction.setSelected(reuseGraph);
}
+ dataMaxDepth = options.getInt(MAX_DEPTH_OPTION, dataMaxDepth);
// Note: we don't care about the FORCE_LOCATION_DISPLAY_OPTION. We register it, but its
// the actually the various GraphDisplays the make use of it.
}
private void createActions() {
- new ActionBuilder("Graph Block Flow", getName())
- .menuPath(MENU_GRAPH, "&Block Flow")
+ new ActionBuilder("Graph Block Flow", getName()).menuPath(MENU_GRAPH, "&Block Flow")
.menuGroup("Graph", "A")
.onAction(c -> graphBlockFlow())
.enabledWhen(this::canGraph)
.buildAndInstall(tool);
- new ActionBuilder("Graph Code Flow", getName())
- .menuPath(MENU_GRAPH, "C&ode Flow")
+ new ActionBuilder("Graph Code Flow", getName()).menuPath(MENU_GRAPH, "C&ode Flow")
.menuGroup("Graph", "B")
.onAction(c -> graphCodeFlow())
.enabledWhen(this::canGraph)
@@ -197,22 +200,49 @@ public class ProgramGraphPlugin extends ProgramPlugin
.enabledWhen(this::canGraph)
.buildAndInstall(tool);
- reuseGraphAction = new ToggleActionBuilder("Reuse Graph", getName())
- .menuPath(MENU_GRAPH, "Reuse Graph")
- .menuGroup("Graph Options")
- .selected(reuseGraph)
- .onAction(c -> reuseGraph = reuseGraphAction.isSelected())
+ tool.setMenuGroup(new String[] { MENU_GRAPH, "Data" }, "Graph", "Data");
+ HelpLocation helpLoc = new HelpLocation(getName(), "Data_Reference_Graph");
+
+ new ActionBuilder("Graph To/From Data References", getName())
+ .menuPath(MENU_GRAPH, "Data", "To/From &References")
+ .menuGroup(MENU_GRAPH, "Data")
+ .helpLocation(helpLoc)
+ .onAction(c -> graphDataReferences())
.enabledWhen(this::canGraph)
.buildAndInstall(tool);
- appendGraphAction = new ToggleActionBuilder("Append Graph", getName())
- .menuPath(MENU_GRAPH, "Append Graph")
- .menuGroup("Graph Options")
- .selected(false)
- .onAction(c -> updateAppendAndReuseGraph())
+ new ActionBuilder("Graph To Data References", getName())
+ .menuPath(MENU_GRAPH, "Data", "&To References")
+ .menuGroup(MENU_GRAPH, "Data")
+ .helpLocation(helpLoc)
+ .onAction(c -> graphToDataReferences())
.enabledWhen(this::canGraph)
.buildAndInstall(tool);
+ new ActionBuilder("Graph From Data References", getName())
+ .menuPath(MENU_GRAPH, "Data", "&From References")
+ .menuGroup(MENU_GRAPH, "Data")
+ .helpLocation(helpLoc)
+ .onAction(c -> graphFromDataReferences())
+ .enabledWhen(this::canGraph)
+ .buildAndInstall(tool);
+
+ reuseGraphAction =
+ new ToggleActionBuilder("Reuse Graph", getName()).menuPath(MENU_GRAPH, "Reuse Graph")
+ .menuGroup("Graph Options")
+ .selected(reuseGraph)
+ .onAction(c -> reuseGraph = reuseGraphAction.isSelected())
+ .enabledWhen(this::canGraph)
+ .buildAndInstall(tool);
+
+ appendGraphAction =
+ new ToggleActionBuilder("Append Graph", getName()).menuPath(MENU_GRAPH, "Append Graph")
+ .menuGroup("Graph Options")
+ .selected(false)
+ .onAction(c -> updateAppendAndReuseGraph())
+ .enabledWhen(this::canGraph)
+ .buildAndInstall(tool);
+
forceLocationVisibleAction = new ToggleActionBuilder("Show Location in Graph", getName())
.menuPath(MENU_GRAPH, "Show Location")
.description("Tell the graph to pan/scale as need to keep location changes visible")
@@ -248,7 +278,7 @@ public class ProgramGraphPlugin extends ProgramPlugin
tool.removeAction(action);
}
- // Create subroutine graph actions for each subroutine provided by BlockModelService
+ // Create subroutine graph actions for each subroutine provided by BlockModelService
String[] subModels =
blockModelService.getAvailableModelNames(BlockModelService.SUBROUTINE_MODEL);
@@ -263,13 +293,13 @@ public class ProgramGraphPlugin extends ProgramPlugin
subUsingGraphActions.add(action);
}
- tool.setMenuGroup(new String[] { "Graph", "Calls Using Model" }, "Graph");
+ tool.setMenuGroup(new String[] { MENU_GRAPH, "Calls Using Model" }, "Graph", "C");
}
private DockingAction buildGraphActionWithModel(String blockModelName, HelpLocation helpLoc) {
return new ActionBuilder("Graph Calls using " + blockModelName, getName())
- .menuPath("Graph", "Calls Using Model", blockModelName)
- .menuGroup("Graph")
+ .menuPath(MENU_GRAPH, "Calls Using Model", blockModelName)
+ .menuGroup(MENU_GRAPH, "C")
.helpLocation(helpLoc)
.onAction(c -> graphSubroutinesUsing(blockModelName))
.enabledWhen(this::canGraph)
@@ -292,14 +322,25 @@ public class ProgramGraphPlugin extends ProgramPlugin
graph("Call Graph (" + modelName + ")", modelName, false);
}
+ private void graphDataReferences() {
+ graphData(DataReferenceGraph.Directions.BOTH_WAYS);
+ }
+
+ private void graphToDataReferences() {
+ graphData(DataReferenceGraph.Directions.TO_ONLY);
+ }
+
+ private void graphFromDataReferences() {
+ graphData(DataReferenceGraph.Directions.FROM_ONLY);
+ }
+
private void graph(String actionName, String modelName, boolean showCode) {
try {
CodeBlockModel model =
blockModelService.getNewModelByName(modelName, currentProgram, true);
- BlockGraphTask task =
- new BlockGraphTask(actionName, graphEntryPointNexus, showCode, reuseGraph,
- appendToGraph, tool, currentSelection, currentLocation, model,
- defaultGraphService);
+ BlockGraphTask task = new BlockGraphTask(actionName, graphEntryPointNexus, showCode,
+ reuseGraph, appendToGraph, tool, currentSelection, currentLocation, model,
+ defaultGraphService);
task.setCodeLimitPerBlock(codeLimitPerBlock);
new TaskLauncher(task, tool.getToolFrame());
}
@@ -309,6 +350,13 @@ public class ProgramGraphPlugin extends ProgramPlugin
}
}
+ void graphData(DataReferenceGraph.Directions direction) {
+ DataReferenceGraphTask task =
+ new DataReferenceGraphTask(reuseGraph, appendToGraph, tool, currentSelection,
+ currentLocation, defaultGraphService, dataMaxDepth, codeLimitPerBlock, direction);
+ new TaskLauncher(task, tool.getToolFrame());
+ }
+
String getProgramName() {
return currentProgram != null ? currentProgram.getName() : null;
}
diff --git a/Ghidra/Features/ProgramGraph/src/test/java/ghidra/graph/program/AbstractDataReferenceGraphTest.java b/Ghidra/Features/ProgramGraph/src/test/java/ghidra/graph/program/AbstractDataReferenceGraphTest.java
new file mode 100644
index 0000000000..2316affec8
--- /dev/null
+++ b/Ghidra/Features/ProgramGraph/src/test/java/ghidra/graph/program/AbstractDataReferenceGraphTest.java
@@ -0,0 +1,128 @@
+/* ###
+ * IP: GHIDRA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package ghidra.graph.program;
+
+import java.nio.charset.StandardCharsets;
+
+import org.junit.After;
+import org.junit.Before;
+
+import ghidra.app.plugin.core.codebrowser.CodeBrowserPlugin;
+import ghidra.app.services.ProgramManager;
+import ghidra.framework.plugintool.PluginTool;
+import ghidra.framework.plugintool.util.PluginException;
+import ghidra.program.database.ProgramDB;
+import ghidra.program.model.address.Address;
+import ghidra.program.model.address.AddressSet;
+import ghidra.program.model.data.*;
+import ghidra.program.model.mem.MemoryAccessException;
+import ghidra.program.model.symbol.RefType;
+import ghidra.program.model.symbol.SourceType;
+import ghidra.test.*;
+
+public class AbstractDataReferenceGraphTest extends AbstractGhidraHeadedIntegrationTest {
+
+ protected PluginTool tool;
+ protected ProgramDB program;
+ protected TestEnv env;
+ private ToyProgramBuilder builder;
+ protected CodeBrowserPlugin codeBrowser;
+
+ @Before
+ public void setUp() throws Exception {
+
+ setErrorGUIEnabled(false);
+
+ env = new TestEnv();
+ tool = env.getTool();
+
+ initializeTool();
+ }
+
+ @After
+ public void tearDown() {
+ env.dispose();
+ }
+
+ protected void initializeTool() throws Exception {
+ installPlugins();
+
+ openProgram();
+ ProgramManager pm = tool.getService(ProgramManager.class);
+ pm.openProgram(program.getDomainFile());
+
+ showTool(tool);
+ }
+
+ protected void installPlugins() throws PluginException {
+ tool.addPlugin(CodeBrowserPlugin.class.getName());
+ codeBrowser = env.getPlugin(CodeBrowserPlugin.class);
+ }
+
+ protected void openProgram() throws Exception {
+
+ builder = new ToyProgramBuilder("sample", true);
+ builder.createMemory("data", "0x01000000", 64);
+ builder.createMemory("caller", "0x01002200", 8);
+
+ buildFunction();
+ buildData();
+
+ program = builder.getProgram();
+ }
+
+ private void buildData() throws Exception {
+ builder.createString("0x01000000", "thing here", StandardCharsets.US_ASCII, true,
+ StringDataType.dataType);
+ builder.createMemoryReference("0x0100000c", "0x0100000f", RefType.DATA,
+ SourceType.ANALYSIS);
+ builder.createString("0x0100000f", "another thing", StandardCharsets.US_ASCII, true,
+ StringDataType.dataType);
+ builder.addDataType(IntegerDataType.dataType);
+ builder.createMemoryReference("0x01000021", "0x0100000c", RefType.DATA,
+ SourceType.ANALYSIS);
+
+ Structure pointerStructure = new StructureDataType("pointer_thing", 0);
+ pointerStructure.setInternallyAligned(true);
+ pointerStructure.add(IntegerDataType.dataType, "num", null);
+ pointerStructure.add(PointerDataType.dataType, "ptr", null);
+ builder.addDataType(pointerStructure);
+ builder.applyDataType("0x0100001d", pointerStructure);
+ }
+
+ private void buildFunction() throws MemoryAccessException {
+ // just a function that calls another
+ builder.createMemoryReference("0x1002200", "0x01000000", RefType.DATA, SourceType.ANALYSIS);
+ builder.addBytesCall("0x01002201", "0x01002239");// jump to C
+ builder.addBytesReturn("0x01002203");
+
+ builder.disassemble("0x01002200", 4, true);
+ builder.createFunction("0x01002200");
+ builder.createLabel("0x01002200", "entry");// function label
+ }
+
+ protected Address addr(long addr) {
+ return builder.getAddress(addr);
+ }
+
+ protected Address addr(String addressString) {
+ return builder.addr(addressString);
+ }
+
+ protected AddressSet addrSet(long start, long end) {
+ return new AddressSet(addr(start), addr(end));
+ }
+}
diff --git a/Ghidra/Features/ProgramGraph/src/test/java/ghidra/graph/program/DataReferenceGraphEventTest.java b/Ghidra/Features/ProgramGraph/src/test/java/ghidra/graph/program/DataReferenceGraphEventTest.java
new file mode 100644
index 0000000000..99b1f36f20
--- /dev/null
+++ b/Ghidra/Features/ProgramGraph/src/test/java/ghidra/graph/program/DataReferenceGraphEventTest.java
@@ -0,0 +1,111 @@
+/* ###
+ * IP: GHIDRA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package ghidra.graph.program;
+
+import static org.junit.Assert.*;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import org.junit.Test;
+
+import ghidra.program.model.address.AddressSet;
+import ghidra.program.util.ProgramLocation;
+import ghidra.program.util.ProgramSelection;
+import ghidra.service.graph.AttributedVertex;
+import ghidra.util.task.TaskMonitor;
+
+public class DataReferenceGraphEventTest extends AbstractDataReferenceGraphTest {
+
+ private TestGraphDisplay display;
+ private DataReferenceGraph graph;
+
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ TestGraphService graphService = new TestGraphService();
+ ProgramLocation location = new ProgramLocation(program, addr(0x01000000));
+ DataReferenceGraphTask task = new DataReferenceGraphTask(false, false, tool, null, location,
+ graphService, 0, 10, DataReferenceGraph.Directions.BOTH_WAYS);
+
+ task.monitoredRun(TaskMonitor.DUMMY);
+
+ display = (TestGraphDisplay) graphService.getGraphDisplay(true, TaskMonitor.DUMMY);
+ DataReferenceGraph otherGraph = new DataReferenceGraph(program, 0);
+ otherGraph.graphFrom(addr(0x0100001d), DataReferenceGraph.Directions.BOTH_WAYS,
+ TaskMonitor.DUMMY);
+ display.setGraph(otherGraph, "testing", true, TaskMonitor.DUMMY);
+ graph = (DataReferenceGraph) display.getGraph();
+ }
+
+ @Test
+ public void testGhidraLocationChanged() {
+ codeBrowser.goTo(new ProgramLocation(program, addr(0x01002200)));
+ assertEquals("01002200",
+ display.getFocusedVertex().getAttribute(DataReferenceGraph.ADDRESS_ATTRIBUTE));
+
+ codeBrowser.goTo(new ProgramLocation(program, addr(0x1000000)));
+ assertEquals("01000000",
+ display.getFocusedVertex().getAttribute(DataReferenceGraph.ADDRESS_ATTRIBUTE));
+
+ // also try a location that is not the start of a block
+ codeBrowser.goTo(new ProgramLocation(program, addr(0x1000021)));
+ assertEquals("0100001d",
+ display.getFocusedVertex().getAttribute(DataReferenceGraph.ADDRESS_ATTRIBUTE));
+ }
+
+ @Test
+ public void testGhidraSelectionChanged() {
+ makeSelection(tool, program, addrSet(0x1000000, 0x100001c));
+ Set selected = new HashSet<>(display.getSelectedVertices());
+ assertEquals(3, selected.size());
+ assertTrue(selected.contains(graph.getVertex(graph.makeName(addr("01000000")))));
+ assertTrue(selected.contains(graph.getVertex(graph.makeName(addr("0100000c")))));
+ assertTrue(selected.contains(graph.getVertex(graph.makeName(addr("0100000f")))));
+
+ makeSelection(tool, program, new AddressSet(addr(0x100000f), addr(0x1000021)));
+ selected = new HashSet<>(display.getSelectedVertices());
+ assertEquals(2, selected.size());
+ assertTrue(selected.contains(graph.getVertex(graph.makeName(addr("0100000f")))));
+ assertTrue(selected.contains(graph.getVertex(graph.makeName(addr("01000021")))));
+
+ }
+
+ @Test
+ public void testGraphNodeFocused() {
+ display.focusChanged(graph.getVertex(graph.makeName(addr(0x01002200))));
+ assertEquals(addr(0x01002200), codeBrowser.getCurrentLocation().getAddress());
+
+ display.focusChanged(graph.getVertex(graph.makeName(addr("01000000"))));
+ assertEquals(addr(0x01000000), codeBrowser.getCurrentLocation().getAddress());
+
+ }
+
+ @Test
+ public void testGraphNodesSelected() {
+ display.selectionChanged(Set.of(graph.getVertex(graph.makeName(addr("01000000"))),
+ graph.getVertex(graph.makeName(addr("0100000c")))));
+ ProgramSelection selection = codeBrowser.getCurrentSelection();
+ assertEquals(addr(0x01000000), selection.getMinAddress());
+ assertEquals(addr(0x0100000c), selection.getMaxAddress());
+
+ display.selectionChanged(Set.of(graph.getVertex(graph.makeName(addr("0100000f"))),
+ graph.getVertex(graph.makeName(addr("01000021")))));
+ selection = codeBrowser.getCurrentSelection();
+ assertEquals(addr(0x0100000f), selection.getMinAddress());
+ assertEquals(addr(0x01000024), selection.getMaxAddress());
+ }
+}
diff --git a/Ghidra/Features/ProgramGraph/src/test/java/ghidra/graph/program/DataReferenceGraphTaskTest.java b/Ghidra/Features/ProgramGraph/src/test/java/ghidra/graph/program/DataReferenceGraphTaskTest.java
new file mode 100644
index 0000000000..d65b60abc9
--- /dev/null
+++ b/Ghidra/Features/ProgramGraph/src/test/java/ghidra/graph/program/DataReferenceGraphTaskTest.java
@@ -0,0 +1,264 @@
+/* ###
+ * IP: GHIDRA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package ghidra.graph.program;
+
+import static org.junit.Assert.*;
+
+import org.junit.Test;
+
+import ghidra.program.util.ProgramLocation;
+import ghidra.program.util.ProgramSelection;
+import ghidra.service.graph.AttributedEdge;
+import ghidra.service.graph.AttributedVertex;
+import ghidra.util.exception.GraphException;
+import ghidra.util.task.TaskMonitor;
+
+public class DataReferenceGraphTaskTest extends AbstractDataReferenceGraphTest {
+
+ @Test
+ public void testGraphWithLimit() throws GraphException {
+
+ TestGraphService graphService = new TestGraphService();
+ DataReferenceGraphTask task = new DataReferenceGraphTask(false, false, tool, null,
+ new ProgramLocation(program, addr(0x0100001d)), graphService, 1, 10,
+ DataReferenceGraph.Directions.BOTH_WAYS);
+ task.monitoredRun(TaskMonitor.DUMMY);
+
+ TestGraphDisplay display =
+ (TestGraphDisplay) graphService.getGraphDisplay(true, TaskMonitor.DUMMY);
+ DataReferenceGraph graph = (DataReferenceGraph) display.getGraph();
+
+ assertEquals(2, graph.getVertexCount());
+ AttributedVertex v1 = graph.getVertex(graph.makeName(addr(0x0100001d)));
+ AttributedVertex v2 = graph.getVertex(graph.makeName(addr(0x0100000c)));
+ AttributedEdge e1 = graph.getEdge(v1, v2);
+ assertNotNull(v1);
+ assertNotNull(v2);
+ assertNotNull(e1);
+ }
+
+ @Test
+ public void testGraphWithoutLimit() throws GraphException {
+
+ TestGraphService graphService = new TestGraphService();
+ DataReferenceGraphTask task = new DataReferenceGraphTask(false, false, tool, null,
+ new ProgramLocation(program, addr(0x0100001d)), graphService, 0, 10,
+ DataReferenceGraph.Directions.BOTH_WAYS);
+ task.monitoredRun(TaskMonitor.DUMMY);
+
+ TestGraphDisplay display =
+ (TestGraphDisplay) graphService.getGraphDisplay(true, TaskMonitor.DUMMY);
+ DataReferenceGraph graph = (DataReferenceGraph) display.getGraph();
+
+ assertEquals(3, graph.getVertexCount());
+ AttributedVertex v1 = graph.getVertex(graph.makeName(addr(0x0100001d)));
+ AttributedVertex v2 = graph.getVertex(graph.makeName(addr(0x0100000c)));
+ AttributedVertex v3 = graph.getVertex(graph.makeName(addr(0x0100000f)));
+ AttributedEdge e1 = graph.getEdge(v1, v2);
+ AttributedEdge e2 = graph.getEdge(v2, v3);
+ assertNotNull(v1);
+ assertNotNull(v2);
+ assertNotNull(v3);
+ assertNotNull(e1);
+ assertNotNull(e2);
+ }
+
+ @Test
+ public void testGraphAdd() throws GraphException {
+ TestGraphService graphService = new TestGraphService();
+ DataReferenceGraphTask task = new DataReferenceGraphTask(false, false, tool, null,
+ new ProgramLocation(program, addr(0x0100001d)), graphService, 1, 10,
+ DataReferenceGraph.Directions.BOTH_WAYS);
+ task.monitoredRun(TaskMonitor.DUMMY);
+
+ TestGraphDisplay display =
+ (TestGraphDisplay) graphService.getGraphDisplay(true, TaskMonitor.DUMMY);
+ DataReferenceGraph graph = (DataReferenceGraph) display.getGraph();
+
+ assertEquals(2, graph.getVertexCount());
+ AttributedVertex v1 = graph.getVertex(graph.makeName(addr(0x0100001d)));
+ AttributedVertex v2 = graph.getVertex(graph.makeName(addr(0x0100000c)));
+ AttributedEdge e1 = graph.getEdge(v1, v2);
+ assertNotNull(v1);
+ assertNotNull(v2);
+ assertNotNull(e1);
+
+ DataReferenceGraphTask taskAdd = new DataReferenceGraphTask(tool, program,
+ addrSet(0x0100000c, 0x0100000c), display, 1, DataReferenceGraph.Directions.BOTH_WAYS);
+ taskAdd.monitoredRun(TaskMonitor.DUMMY);
+ graph = (DataReferenceGraph) display.getGraph();
+ assertEquals(3, graph.getVertexCount());
+ AttributedVertex v1Add = graph.getVertex(graph.makeName(addr(0x0100001d)));
+ AttributedVertex v2Add = graph.getVertex(graph.makeName(addr(0x0100000c)));
+ AttributedVertex v3Add = graph.getVertex(graph.makeName(addr(0x0100000f)));
+ AttributedEdge e1Add = graph.getEdge(v1Add, v2Add);
+ AttributedEdge e2Add = graph.getEdge(v2Add, v3Add);
+ assertNotNull(v1Add);
+ assertNotNull(v2Add);
+ assertNotNull(v3Add);
+ assertNotNull(e1Add);
+ assertNotNull(e2Add);
+ }
+
+ @Test
+ public void testDirectionsBoth() throws GraphException {
+ TestGraphService graphService = new TestGraphService();
+ DataReferenceGraphTask task = new DataReferenceGraphTask(false, false, tool, null,
+ new ProgramLocation(program, addr(0x0100000c)), graphService, 0, 10,
+ DataReferenceGraph.Directions.BOTH_WAYS);
+ task.monitoredRun(TaskMonitor.DUMMY);
+
+ TestGraphDisplay display =
+ (TestGraphDisplay) graphService.getGraphDisplay(true, TaskMonitor.DUMMY);
+ DataReferenceGraph graph = (DataReferenceGraph) display.getGraph();
+
+ assertEquals(3, graph.getVertexCount());
+ AttributedVertex v1 = graph.getVertex(graph.makeName(addr(0x0100001d)));
+ AttributedVertex v2 = graph.getVertex(graph.makeName(addr(0x0100000c)));
+ AttributedVertex v3 = graph.getVertex(graph.makeName(addr(0x0100000f)));
+ AttributedEdge e1 = graph.getEdge(v1, v2);
+ AttributedEdge e2 = graph.getEdge(v2, v3);
+ assertNotNull(v1);
+ assertNotNull(v2);
+ assertNotNull(v3);
+ assertNotNull(e1);
+ assertNotNull(e2);
+ }
+
+ @Test
+ public void testDirectionsTo() throws GraphException {
+ TestGraphService graphService = new TestGraphService();
+ DataReferenceGraphTask task = new DataReferenceGraphTask(false, false, tool, null,
+ new ProgramLocation(program, addr(0x0100000c)), graphService, 0, 10,
+ DataReferenceGraph.Directions.TO_ONLY);
+ task.monitoredRun(TaskMonitor.DUMMY);
+
+ TestGraphDisplay display =
+ (TestGraphDisplay) graphService.getGraphDisplay(true, TaskMonitor.DUMMY);
+ DataReferenceGraph graph = (DataReferenceGraph) display.getGraph();
+
+ assertEquals(2, graph.getVertexCount());
+ AttributedVertex v1 = graph.getVertex(graph.makeName(addr(0x0100001d)));
+ AttributedVertex v2 = graph.getVertex(graph.makeName(addr(0x0100000c)));
+ AttributedVertex v3 = graph.getVertex(graph.makeName(addr(0x0100000f)));
+ AttributedEdge e1 = graph.getEdge(v1, v2);
+ AttributedEdge e2 = graph.getEdge(v2, v3);
+ assertNotNull(v1);
+ assertNotNull(v2);
+ assertNull(v3);
+ assertNotNull(e1);
+ assertNull(e2);
+ }
+
+ @Test
+ public void testDirectionsFrom() throws GraphException {
+ TestGraphService graphService = new TestGraphService();
+ DataReferenceGraphTask task = new DataReferenceGraphTask(false, false, tool, null,
+ new ProgramLocation(program, addr(0x0100000c)), graphService, 0, 10,
+ DataReferenceGraph.Directions.FROM_ONLY);
+ task.monitoredRun(TaskMonitor.DUMMY);
+
+ TestGraphDisplay display =
+ (TestGraphDisplay) graphService.getGraphDisplay(true, TaskMonitor.DUMMY);
+ DataReferenceGraph graph = (DataReferenceGraph) display.getGraph();
+
+ assertEquals(2, graph.getVertexCount());
+ AttributedVertex v1 = graph.getVertex(graph.makeName(addr(0x0100001d)));
+ AttributedVertex v2 = graph.getVertex(graph.makeName(addr(0x0100000c)));
+ AttributedVertex v3 = graph.getVertex(graph.makeName(addr(0x0100000f)));
+ AttributedEdge e1 = graph.getEdge(v1, v2);
+ AttributedEdge e2 = graph.getEdge(v2, v3);
+ assertNull(v1);
+ assertNotNull(v2);
+ assertNotNull(v3);
+ assertNull(e1);
+ assertNotNull(e2);
+ }
+
+ @Test
+ public void testNodeWithType() throws GraphException {
+ TestGraphService graphService = new TestGraphService();
+ DataReferenceGraphTask task = new DataReferenceGraphTask(false, false, tool, null,
+ new ProgramLocation(program, addr(0x0100001d)), graphService, 1, 10,
+ DataReferenceGraph.Directions.BOTH_WAYS);
+ task.monitoredRun(TaskMonitor.DUMMY);
+
+ TestGraphDisplay display =
+ (TestGraphDisplay) graphService.getGraphDisplay(true, TaskMonitor.DUMMY);
+ DataReferenceGraph graph = (DataReferenceGraph) display.getGraph();
+
+ AttributedVertex vertex = graph.getVertex(graph.makeName(addr(0x0100001d)));
+ assertEquals("pointer_thing", vertex.getAttribute(DataReferenceGraph.DATA_ATTRIBUTE));
+ assertEquals("0100001d\npointer_thing",
+ vertex.getAttribute(DataReferenceGraph.LABEL_ATTRIBUTE));
+ }
+
+ @Test
+ public void testCodeReference() throws GraphException {
+ TestGraphService graphService = new TestGraphService();
+ DataReferenceGraphTask task = new DataReferenceGraphTask(false, false, tool, null,
+ new ProgramLocation(program, addr(0x01002200)), graphService, 0, 10,
+ DataReferenceGraph.Directions.BOTH_WAYS);
+ task.monitoredRun(TaskMonitor.DUMMY);
+
+ TestGraphDisplay display =
+ (TestGraphDisplay) graphService.getGraphDisplay(true, TaskMonitor.DUMMY);
+ DataReferenceGraph graph = (DataReferenceGraph) display.getGraph();
+
+ assertEquals(2, graph.getVertexCount());
+ AttributedVertex v1 = graph.getVertex(graph.makeName(addr(0x01002200)));
+ AttributedVertex v2 = graph.getVertex(graph.makeName(addr(0x01000000)));
+ AttributedEdge e1 = graph.getEdge(v1, v2);
+ assertNotNull(v1);
+ assertNotNull(v2);
+ assertNotNull(e1);
+ assertEquals("TriangleDown", v1.getAttribute("Icon"));
+ }
+
+ @Test
+ public void testGraphSelection() throws GraphException {
+ ProgramSelection selection = new ProgramSelection(addr(0x01000000), addr(0x0100000c));
+ TestGraphService graphService = new TestGraphService();
+ DataReferenceGraphTask task = new DataReferenceGraphTask(false, false, tool, selection,
+ new ProgramLocation(program, addr(0x0100000c)), graphService, 0, 10,
+ DataReferenceGraph.Directions.BOTH_WAYS);
+ task.monitoredRun(TaskMonitor.DUMMY);
+
+ TestGraphDisplay display =
+ (TestGraphDisplay) graphService.getGraphDisplay(true, TaskMonitor.DUMMY);
+ DataReferenceGraph graph = (DataReferenceGraph) display.getGraph();
+
+ //there's an extra vertex at 0x0100000b as an artifact of selection construction
+ assertEquals(6, graph.getVertexCount());
+ AttributedVertex v1 = graph.getVertex(graph.makeName(addr(0x0100001d)));
+ AttributedVertex v2 = graph.getVertex(graph.makeName(addr(0x0100000c)));
+ AttributedVertex v3 = graph.getVertex(graph.makeName(addr(0x0100000f)));
+ AttributedVertex v4 = graph.getVertex(graph.makeName(addr(0x01002200)));
+ AttributedVertex v5 = graph.getVertex(graph.makeName(addr(0x01000000)));
+ AttributedEdge e3 = graph.getEdge(v4, v5);
+ AttributedEdge e1 = graph.getEdge(v1, v2);
+ AttributedEdge e2 = graph.getEdge(v2, v3);
+ assertNotNull(v1);
+ assertNotNull(v2);
+ assertNotNull(v3);
+ assertNotNull(v4);
+ assertNotNull(v5);
+ assertNotNull(e1);
+ assertNotNull(e2);
+ assertNotNull(e3);
+ }
+
+}
diff --git a/Ghidra/Features/ProgramGraph/src/test/java/ghidra/graph/program/TestGraphDisplay.java b/Ghidra/Features/ProgramGraph/src/test/java/ghidra/graph/program/TestGraphDisplay.java
index 61c2d279c0..28379d07c5 100644
--- a/Ghidra/Features/ProgramGraph/src/test/java/ghidra/graph/program/TestGraphDisplay.java
+++ b/Ghidra/Features/ProgramGraph/src/test/java/ghidra/graph/program/TestGraphDisplay.java
@@ -15,8 +15,7 @@
*/
package ghidra.graph.program;
-import java.util.HashSet;
-import java.util.Set;
+import java.util.*;
import docking.action.DockingActionIf;
import docking.widgets.EventTrigger;
@@ -84,7 +83,12 @@ public class TestGraphDisplay implements GraphDisplay {
public void setGraph(AttributedGraph graph, String title, boolean append,
TaskMonitor monitor)
throws CancelledException {
- this.graph = graph;
+ if (append) {
+ this.graph = mergeGraphs(graph, this.graph);
+ }
+ else {
+ this.graph = graph;
+ }
this.title = title;
}
@@ -120,4 +124,20 @@ public class TestGraphDisplay implements GraphDisplay {
public void addAction(DockingActionIf action) {
// do nothing, actions are not supported by this display
}
+
+ private AttributedGraph mergeGraphs(AttributedGraph newGraph, AttributedGraph oldGraph) {
+ for (AttributedVertex vertex : oldGraph.vertexSet()) {
+ newGraph.addVertex(vertex);
+ }
+ for (AttributedEdge edge : oldGraph.edgeSet()) {
+ AttributedVertex from = oldGraph.getEdgeSource(edge);
+ AttributedVertex to = oldGraph.getEdgeTarget(edge);
+ AttributedEdge newEdge = newGraph.addEdge(from, to);
+ Map attributeMap = edge.getAttributeMap();
+ for (String key : attributeMap.keySet()) {
+ newEdge.setAttribute(key, edge.getAttribute(key));
+ }
+ }
+ return newGraph;
+ }
}