Merge branch 'GP-194_ghidra42_data_reference_graph'

Conflicts:
	Ghidra/Features/ProgramGraph/src/test/java/ghidra/graph/program/TestGraphDisplay.java
This commit is contained in:
ghidra1 2021-05-19 22:08:13 -04:00
commit d3e7902a8b
10 changed files with 1175 additions and 56 deletions

View file

@ -27,10 +27,16 @@
another. The nodes of the graph represent blocks of code and the edges represent flow between
the blocks of code.</P>
<P>There are two basic graph types, <A href="#Graph_Block_Flow">Block Flow</A> and <A href=
<P>There are two basic flow graph types, <A href="#Graph_Block_Flow">Block Flow</A> and <A href=
"#Graph_Calls_Using_Default_Model">Call Graph</A>. Different colors and shapes are used to
depict each node and the flow between them. Multiple graphs of either type can exist at any
time.</P>
depict each node and the flow between them.</P>
<P><A href="#Data_Reference_Graph">Data graphs</A> show reference relationships between memory
locations. They can be constrained to just <B>"To References"</B>, just <B>"From References"</B>,
or both. By default a single hop is graphed with the option to extend from within the
graph.</P>
<P>Multiple graphs of any or all types can exist at any time.</P>
<BLOCKQUOTE>
<P>A <I>Call Graph</I> depicts subroutines as nodes. Calls between subroutines are shown as
@ -433,7 +439,56 @@
set of block models configured into the tool.</P>
</BLOCKQUOTE>
</BLOCKQUOTE>
<H2><A name="Data_Reference_Graph"></A>Graph Data References</H2>
<P>A graph of data references can be created with <B>Graph<IMG src="../../shared/arrow.gif">
Data</B> then selecting <B>To References</B>, <B>From References</B> or <B>To/From References</B>.
Only the selected references will be included. By default only a single layer of references will
be graphed, this can be adjusted using the <B>Edit <IMG src="../../shared/arrow.gif">
Tool Options <IMG src="../../shared/arrow.gif"> Graph <IMG src="../../shared/arrow.gif">
Max Reference Depth</B>
<BLOCKQUOTE>
<BLOCKQUOTE>
<P><IMG src="../../shared/note.png"> Unlike flow graphs, where only the selection is
included in the graph, the selection and its references are included.</P>
</BLOCKQUOTE>
<P>To graph Data References,</P>
<OL>
<LI>Select the data to start from in the listing
<LI>Select <B>Graph<IMG src="../../shared/arrow.gif"> Data<IMG src=
"../../shared/arrow.gif"></B></LI>
<LI>
Select one of
<UL>
<LI>To References - selected data and all references to it</LI>
<LI>From References - selected data all references from it</LI>
<LI>To/From References - selected data and all references to and from it</LI>
</UL>
</LI>
<LI>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 <A href="#Add_References">Add References
action</A> on the desired node</LI>
</OL>
</BLOCKQUOTE>
<BLOCKQUOTE>
<H3><A NAME="Add_References"></A>Add References</H3>
<BLOCKQUOTE>
<P>Allows the user to add references starting from
the selected nodes, with To References, From References or Bidirectional as selected.
</P>
</BLOCKQUOTE>
</BLOCKQUOTE>
<BR>
<BR>

View file

@ -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
* <p>
* 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;
}
}

View file

@ -0,0 +1,140 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.graph.program;
import java.util.*;
import 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<AttributedVertex> getVertices(AddressSetView selection) {
if (selection.isEmpty()) {
return Collections.emptySet();
}
Set<AttributedVertex> 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<AttributedVertex> 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);
}
}

View file

@ -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);
}
}
}
}

View file

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

View file

@ -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.setPackingEnabled(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));
}
}

View file

@ -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<AttributedVertex> 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());
}
}

View file

@ -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);
}
}