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,

+ +
    +
  1. Select the data to start from in the listing +
  2. Select Graph Data
  3. +
  4. + 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
    • +
    +
  5. + +
  6. 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
  7. +
+
+ +
+ +

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; + } }