moved generic graph interfaces to features graph module
created graph service broker

first commit of program graph module adapted to new graph api

GT-3317 connected listeners, documented and prettied up code
changed GhidraGraph to preserve order of created graph. Removed edge
filtering from initial program graph display

GT-3317 added exporters for supported formats

GT-3317 fixed GhidraGraph bug where it lost edges

updates

changed to new action builder
removed icons, improved AttributeFilters

removed DialogComponentProviderBuilder
fixed generic alphabet soup

added vertex name updating.

GT-3317 added threading to sugiyama
adapted to take advantage of multi-threaded edge crossing reduction in
circle layout
eliminated parallel edges, improved sizing, updated jungrapht version

GT-3317 fixing AST graph and moving modules and packages
started help
GT-3317 updated min-cross and color selections
uses min-cross that optimizes for graph size

GT-3317 help, javadocs

changes from review comments and cleaning up warnings and simplifying
exporter code
fixing warnings, simplifying unnecessarily complicated code
more changes from review
more changes from review, simplifications. removed unnecessary
threading, renamed vertex, edge, etc
GT-3317 squashed many commits to make rebase easier. Mostly changes from
first code review.
This commit is contained in:
ghidravore 2019-12-06 11:32:03 -05:00
parent 0001ee2651
commit 410af5a272
112 changed files with 8736 additions and 1094 deletions

View File

@ -19,3 +19,4 @@ InstructionSkipper
DataTypeReferenceFinder
ChecksumAlgorithm
OverviewColorService

View File

@ -0,0 +1,131 @@
/* ###
* 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.
*/
import ghidra.app.script.GhidraScript;
import ghidra.app.services.GraphDisplayBroker;
import ghidra.framework.plugintool.PluginTool;
import ghidra.service.graph.*;
/**
* Script to generate graph to test BrandesKopf algorithm
*/
public class GenerateBrandesKopfGraphScript extends GhidraScript {
private AttributedGraph graph = new AttributedGraph();
private int nextEdgeID = 1;
@Override
protected void run() throws Exception {
PluginTool tool = getState().getTool();
GraphDisplayBroker service = tool.getService(GraphDisplayBroker.class);
GraphDisplay display = service.getDefaultGraphDisplay(false, monitor);
generateGraph();
display.setGraph(graph, "Test2", false, monitor);
}
private void generateGraph() {
AttributedVertex[] list = new AttributedVertex[24];
int i=1;
list[i++] = vertex("1");
list[i++] = vertex("2");
list[i++] = vertex("3");
list[i++] = vertex("4");
list[i++] = vertex("5");
list[i++] = vertex("6");
list[i++] = vertex("7");
list[i++] = vertex("8");
list[i++] = vertex("9");
list[i++] = vertex("10");
list[i++] = vertex("11");
list[i++] = vertex("12");
list[i++] = vertex("13");
list[i++] = vertex("14");
list[i++] = vertex("15");
list[i++] = vertex("16");
list[i++] = vertex("17");
list[i++] = vertex("18");
list[i++] = vertex("19");
list[i++] = vertex("20");
list[i++] = vertex("21");
list[i++] = vertex("22");
list[i++] = vertex("23");
edge(list[1], list[3]);
edge(list[1], list[4]);
edge(list[1], list[13]);
edge(list[1], list[21]);
edge(list[2], list[3]);
edge(list[2], list[20]);
edge(list[3], list[4]);
edge(list[3], list[5]);
edge(list[3], list[23]);
edge(list[4], list[6]);
edge(list[5], list[7]);
edge(list[6], list[8]);
edge(list[6], list[16]);
edge(list[6], list[23]);
edge(list[7], list[9]);
edge(list[8], list[10]);
edge(list[8], list[11]);
edge(list[9], list[12]);
edge(list[10], list[13]);
edge(list[10], list[14]);
edge(list[10], list[15]);
edge(list[11], list[15]);
edge(list[11], list[16]);
edge(list[12], list[20]);
edge(list[13], list[17]);
edge(list[14], list[17]);
edge(list[14], list[18]);
// no 15 targets
edge(list[16], list[18]);
edge(list[16], list[19]);
edge(list[16], list[20]);
edge(list[18], list[21]);
edge(list[19], list[22]);
edge(list[21], list[23]);
edge(list[22], list[23]);
}
private AttributedVertex vertex(String name) {
return graph.addVertex(name, name);
}
private AttributedEdge edge(AttributedVertex v1, AttributedVertex v2) {
return graph.addEdge(v1, v2);
}
}

View File

@ -0,0 +1,60 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import ghidra.app.script.GhidraScript;
import ghidra.app.services.GraphDisplayBroker;
import ghidra.framework.plugintool.PluginTool;
import ghidra.service.graph.*;
/**
* Sample script to test graph service
*/
public class GenerateTestGraphScript extends GhidraScript {
private AttributedGraph graph = new AttributedGraph();
private int nextEdgeID = 1;
@Override
protected void run() throws Exception {
PluginTool tool = getState().getTool();
GraphDisplayBroker service = tool.getService(GraphDisplayBroker.class);
GraphDisplay display = service.getDefaultGraphDisplay(false, monitor);
generateGraph();
display.setGraph(graph, "Test", false, monitor);
}
private void generateGraph() {
AttributedVertex A = vertex("A");
AttributedVertex B = vertex("B");
AttributedVertex C = vertex("C");
AttributedVertex D = vertex("D");
edge(A, B);
edge(A, C);
edge(B, D);
edge(C, D);
edge(D, A);
}
private AttributedVertex vertex(String name) {
return graph.addVertex(name, name);
}
private AttributedEdge edge(AttributedVertex v1, AttributedVertex v2) {
return graph.addEdge(v1, v2);
}
}

View File

@ -0,0 +1,59 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import ghidra.app.script.GhidraScript;
import ghidra.app.services.GraphDisplayBroker;
import ghidra.framework.plugintool.PluginTool;
import ghidra.service.graph.*;
/**
* Example script for creating and displaying a graph in ghidra
*/
public class ExampleGraphServiceScript extends GhidraScript {
private AttributedGraph graph = new AttributedGraph();
private int nextEdgeID = 1;
@Override
protected void run() throws Exception {
PluginTool tool = getState().getTool();
GraphDisplayBroker service = tool.getService(GraphDisplayBroker.class);
GraphDisplay display = service.getDefaultGraphDisplay(false, monitor);
generateGraph();
display.setGraph(graph, "Test", false, monitor);
}
private void generateGraph() {
AttributedVertex A = vertex("A");
AttributedVertex B = vertex("B");
AttributedVertex C = vertex("C");
AttributedVertex D = vertex("D");
edge(A, B);
edge(A, C);
edge(B, D);
edge(C, D);
edge(D, A);
}
private AttributedVertex vertex(String name) {
return graph.addVertex(name, name);
}
private AttributedEdge edge(AttributedVertex v1, AttributedVertex v2) {
return graph.addEdge(v1, v2);
}
}

View File

@ -0,0 +1,190 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.graph;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicInteger;
import ghidra.app.events.*;
import ghidra.framework.model.*;
import ghidra.framework.plugintool.PluginEvent;
import ghidra.framework.plugintool.PluginTool;
import ghidra.framework.plugintool.util.PluginEventListener;
import ghidra.program.model.address.*;
import ghidra.program.model.listing.Program;
import ghidra.program.model.symbol.*;
import ghidra.program.util.*;
import ghidra.service.graph.GraphDisplay;
import ghidra.service.graph.GraphDisplayListener;
import ghidra.util.Swing;
/**
* Base class for GraphDisplay listeners whose nodes represent addresses.
*/
public abstract class AddressBasedGraphDisplayListener
implements GraphDisplayListener, PluginEventListener, DomainObjectListener {
private PluginTool tool;
private GraphDisplay graphDisplay;
protected Program program;
private SymbolTable symbolTable;
private String name;
private static AtomicInteger instanceCount = new AtomicInteger(1);
public AddressBasedGraphDisplayListener(PluginTool tool, Program program,
GraphDisplay display) {
this.tool = tool;
this.program = program;
this.symbolTable = program.getSymbolTable();
this.graphDisplay = display;
name = getClass().getSimpleName() + instanceCount.getAndAdd(1);
tool.addListenerForAllPluginEvents(this);
program.addListener(this);
}
@Override
public void graphClosed() {
dispose();
}
@Override
public void locationChanged(String vertexId) {
Address address = getAddressForVertexId(vertexId);
if (address != null) {
ProgramLocation location = new ProgramLocation(program, address);
tool.firePluginEvent(new ProgramLocationPluginEvent(name, location, program));
}
}
@Override
public void selectionChanged(List<String> vertexIds) {
AddressSet addressSet = getAddressSetForVertices(vertexIds);
if (addressSet != null) {
ProgramSelection selection = new ProgramSelection(addressSet);
ProgramSelectionPluginEvent event =
new ProgramSelectionPluginEvent(name, selection, program);
tool.firePluginEvent(event);
}
}
@Override
public void eventSent(PluginEvent event) {
if (Objects.equals(event.getSourceName(), name)) {
return;
}
if (event instanceof ProgramClosedPluginEvent) {
ProgramClosedPluginEvent ev = (ProgramClosedPluginEvent) event;
if (isMyProgram(ev.getProgram())) {
graphDisplay.close();
dispose();
}
}
else if (event instanceof ProgramLocationPluginEvent) {
ProgramLocationPluginEvent ev = (ProgramLocationPluginEvent) event;
if (isMyProgram(ev.getProgram())) {
ProgramLocation location = ev.getLocation();
graphDisplay.setLocation(getVertexIdForAddress(location.getAddress()));
}
}
else if (event instanceof ProgramSelectionPluginEvent) {
ProgramSelectionPluginEvent ev = (ProgramSelectionPluginEvent) event;
if (isMyProgram(ev.getProgram())) {
ProgramSelection selection = ev.getSelection();
List<String> selectedVertices = getVertices(selection);
if (selectedVertices != null) {
graphDisplay.selectVertices(selectedVertices);
}
}
}
}
protected String getVertexIdForAddress(Address address) {
// vertex ids for external locations use symbol names since they don't have meaningful addresses.
if (address.isExternalAddress()) {
Symbol s = symbolTable.getPrimarySymbol(address);
return s.getName(true);
}
return address.toString();
}
protected Address getAddress(String vertexIdString) {
Address address = program.getAddressFactory().getAddress(vertexIdString);
if (address != null) {
return address;
}
// the vertex id was not an address, see if it is an external symbol name
int index = vertexIdString.indexOf(Namespace.DELIMITER);
if (index <= 0) {
return null;
}
String namespaceName = vertexIdString.substring(0, index);
String symbolName = vertexIdString.substring(index + 2);
Namespace namespace = symbolTable.getNamespace(namespaceName, null);
if (namespace == null) {
return null;
}
List<Symbol> symbols = symbolTable.getSymbols(symbolName, namespace);
if (symbols.isEmpty()) {
return null;
}
// there should only be one external symbol with the same name, so just assume the first one is good
return symbols.get(0).getAddress();
}
protected Address getAddressForVertexId(String vertexId) {
return getAddress(vertexId);
}
protected abstract List<String> getVertices(AddressSetView selection);
protected abstract AddressSet getAddressSetForVertices(List<String> vertexIds);
private boolean isMyProgram(Program p) {
return p == program;
}
@Override
public void domainObjectChanged(DomainObjectChangedEvent ev) {
if (!ev.containsEvent(ChangeManager.DOCR_SYMBOL_RENAMED)) {
return;
}
for (DomainObjectChangeRecord record : ev) {
if (record.getEventType() == ChangeManager.DOCR_SYMBOL_RENAMED) {
ProgramChangeRecord programRecord = (ProgramChangeRecord) record;
handleSymbolRenamed(programRecord);
}
}
}
private void handleSymbolRenamed(ProgramChangeRecord programRecord) {
Symbol symbol = (Symbol) programRecord.getObject();
String newName = symbol.getName();
Address address = symbol.getAddress();
String id = getVertexIdForAddress(address);
graphDisplay.updateVertexName(id, newName);
}
private void dispose() {
Swing.runLater(() -> tool.removeListenerForAllPluginEvents(this));
program.removeListener(this);
}
}

View File

@ -0,0 +1,22 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.graph;
public interface GraphDisplayBrokerListener {
void providersChanged();
}

View File

@ -0,0 +1,209 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.graph;
import java.util.*;
import docking.ActionContext;
import docking.action.MenuData;
import docking.action.ToggleDockingAction;
import ghidra.app.CorePluginPackage;
import ghidra.app.plugin.PluginCategoryNames;
import ghidra.app.services.GraphDisplayBroker;
import ghidra.framework.options.*;
import ghidra.framework.plugintool.*;
import ghidra.framework.plugintool.util.PluginStatus;
import ghidra.service.graph.GraphDisplay;
import ghidra.service.graph.GraphDisplayProvider;
import ghidra.util.classfinder.ClassSearcher;
import ghidra.util.exception.GraphException;
import ghidra.util.task.TaskMonitor;
//@formatter:off
@PluginInfo(
status = PluginStatus.RELEASED,
packageName = CorePluginPackage.NAME,
category = PluginCategoryNames.GRAPH,
shortDescription = "Manages the active Graph Display Service",
description = "This plugin searches for available graph display providers and if it finds more" +
"than one, it provides menu options for the user to choose the active provider.",
servicesProvided = { GraphDisplayBroker.class }
)
//@formatter:on
public class GraphDisplayBrokerPlugin extends Plugin
implements GraphDisplayBroker, OptionsChangeListener {
private static final String ACTIVE_GRAPH_PROVIDER = "ACTIVE_GRAPH_PROVIDER";
private List<GraphDisplayProvider> graphDisplayProviders = new ArrayList<>();
private GraphDisplayProvider defaultGraphDisplayProvider;
private List<GraphDisplayBrokerListener> listeners = new ArrayList<>();
private List<GraphSelectionAction> actions = new ArrayList<>();
public GraphDisplayBrokerPlugin(PluginTool tool) {
super(tool);
loadServices();
buildActions();
}
@Override
public void writeConfigState(SaveState saveState) {
if (defaultGraphDisplayProvider != null) {
saveState.putString(ACTIVE_GRAPH_PROVIDER, defaultGraphDisplayProvider.getName());
}
}
@Override
public void readConfigState(SaveState saveState) {
String active = saveState.getString(ACTIVE_GRAPH_PROVIDER, null);
if (active != null) {
for (GraphDisplayProvider provider : graphDisplayProviders) {
if (provider.getName().equals(active)) {
setDefaultGraphDisplayProvider(provider);
return;
}
}
}
}
private void loadServices() {
Set<GraphDisplayProvider> instances =
new HashSet<>(ClassSearcher.getInstances(GraphDisplayProvider.class));
graphDisplayProviders = new ArrayList<>(instances);
Collections.sort(graphDisplayProviders, (s1, s2) -> s1.getName().compareTo(s2.getName()));
initializeServices();
if (!graphDisplayProviders.isEmpty()) {
defaultGraphDisplayProvider = graphDisplayProviders.get(0);
}
}
private void initializeServices() {
for (GraphDisplayProvider service : graphDisplayProviders) {
ToolOptions options = tool.getOptions("Graph");
options.addOptionsChangeListener(this);
service.initialize(tool, options);
}
}
private void buildActions() {
if (graphDisplayProviders.size() <= 1) {
return;
}
for (GraphDisplayProvider graphDisplayProvider : graphDisplayProviders) {
createAction(graphDisplayProvider);
}
updateActions();
}
private void createAction(GraphDisplayProvider provider) {
GraphSelectionAction action = new GraphSelectionAction(getName(), provider);
actions.add(action);
tool.addAction(action);
}
private void updateActions() {
for (GraphSelectionAction action : actions) {
action.setSelected(defaultGraphDisplayProvider == action.provider);
}
}
protected void notifyListeners() {
for (GraphDisplayBrokerListener listener : listeners) {
listener.providersChanged();
}
}
@Override
public GraphDisplayProvider getDefaultGraphDisplayProvider() {
return defaultGraphDisplayProvider;
}
@Override
public void addGraphDisplayBrokerListener(GraphDisplayBrokerListener listener) {
listeners.add(listener);
}
@Override
public void removeGraphDisplayBrokerLisetener(GraphDisplayBrokerListener listener) {
listeners.remove(listener);
}
@Override
public void dispose() {
for (GraphDisplayProvider graphService : graphDisplayProviders) {
graphService.dispose();
}
}
@Override
public GraphDisplay getDefaultGraphDisplay(boolean reuseGraph,
TaskMonitor monitor) throws GraphException {
if (defaultGraphDisplayProvider != null) {
return defaultGraphDisplayProvider.getGraphDisplay(reuseGraph, monitor);
}
return null;
}
public void setDefaultGraphDisplayProvider(GraphDisplayProvider provider) {
defaultGraphDisplayProvider = provider;
notifyListeners();
updateActions();
}
@Override
public boolean hasDefaultGraphDisplayProvider() {
return !graphDisplayProviders.isEmpty();
}
/**
* Action for selecting a {@link GraphDisplayProvider} to be the currently active provider
*/
private class GraphSelectionAction extends ToggleDockingAction {
private GraphDisplayProvider provider;
public GraphSelectionAction(String owner, GraphDisplayProvider provider) {
super(provider.getName(), owner);
this.provider = provider;
setMenuBarData(
new MenuData(new String[] { "Graph", "Graph Output", provider.getName() }, "z"));
setHelpLocation(provider.getHelpLocation());
}
@Override
public void actionPerformed(ActionContext context) {
setDefaultGraphDisplayProvider(provider);
}
}
@Override
public void optionsChanged(ToolOptions options, String optionName, Object oldValue,
Object newValue) {
for (GraphDisplayProvider graphService : graphDisplayProviders) {
graphService.optionsChanged(options);
}
}
@Override
public GraphDisplayProvider getGraphDisplayProvider(String providerName) {
for (GraphDisplayProvider provider : graphDisplayProviders) {
if (provider.getName().equals(providerName)) {
return provider;
}
}
return null;
}
}

View File

@ -0,0 +1,77 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.services;
import ghidra.app.plugin.core.graph.GraphDisplayBrokerListener;
import ghidra.app.plugin.core.graph.GraphDisplayBrokerPlugin;
import ghidra.framework.plugintool.ServiceInfo;
import ghidra.service.graph.GraphDisplay;
import ghidra.service.graph.GraphDisplayProvider;
import ghidra.util.exception.GraphException;
import ghidra.util.task.TaskMonitor;
/**
* Ghidra service interface for managing and directing graph output. It purpose is to discover
* available graphing display providers and (if more than one) allow the user to select the currently
* active graph consumer. Clients that generate graphs don't have to worry about how to display them
* or export graphs. They simply send their graphs to the broker and register for graph events if
* they want interactive support.
*/
@ServiceInfo(defaultProvider = GraphDisplayBrokerPlugin.class, description = "Get a Graph Display")
public interface GraphDisplayBroker {
/**
* Gets the currently active GraphDisplayProvider that will be used to display/export graphs
* @return the currently active GraphDisplayProvider
*/
public GraphDisplayProvider getDefaultGraphDisplayProvider();
/**
* Adds a listener for notification when the set of graph display providers change or the currently
* active graph display provider changes
* @param listener the listener to be notified
*/
public void addGraphDisplayBrokerListener(GraphDisplayBrokerListener listener);
/**
* Removes the given listener
* @param listener the listener to no longer be notified of changes
*/
public void removeGraphDisplayBrokerLisetener(GraphDisplayBrokerListener listener);
/**
* A convenience method for getting a {@link GraphDisplay} from the currently active provider
* @param reuseGraph if true, the provider will attempt to re-use a current graph display
* @param monitor the {@link TaskMonitor} that can be used to cancel the operation
* @return a {@link GraphDisplay} object to sends graphs to be displayed or exported.
* @throws GraphException thrown if an error occurs trying to get a graph display
*/
public GraphDisplay getDefaultGraphDisplay(boolean reuseGraph, TaskMonitor monitor)
throws GraphException;
/**
* Checks if there is at least one {@link GraphDisplayProvider} in the system.
* @return true if there is at least one {@link GraphDisplayProvider}
*/
public boolean hasDefaultGraphDisplayProvider();
/**
* Gets the {@link GraphDisplayProvider} with the given name
* @param name the name of the GraphDisplayProvider to get
* @return the GraphDisplayProvider with the given name or null if none with that name exists.
*/
public GraphDisplayProvider getGraphDisplayProvider(String name);
}

View File

@ -1,82 +0,0 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.services;
import ghidra.framework.plugintool.ServiceInfo;
import ghidra.program.model.graph.*;
import ghidra.util.exception.GraphException;
/**
* Service for getting a Graph display.
*
*/
@ServiceInfo(/* defaultProvider = NONE, */ description = "Get a Graph Display")
public interface GraphService {
/**
* Create Graph Data compatible with this graph service
*/
GraphData createGraphContent();
/**
* Get a graph display.
* @param newDisplay a new graph window will be used if true.
* @throws GraphException if unable to obtain a graph window.
*/
GraphDisplay getGraphDisplay(boolean newDisplay) throws GraphException;
/**
* Get a graph display.
* @throws GraphException if unable to obtain a graph window.
*/
GraphDisplay getGraphDisplay() throws GraphException;
/**
* Send specified selection object to all connected graphs
* that understand the concept of "selection."
* @param selection selection object to interpret
*/
void setSelection(Object selection);
/**
* Send specified location object to all connected graphs that understand
* the concept of "location."
* @param location location object to interpret
*/
void setLocation(Object location);
/**
* Set the selection for all connected graphs and fire a selection event
* for Ghidra.
* @param selection selection object to interpret
*/
void fireSelectionEvent(Object selection);
/**
* Set the location for all connected graphs and fire a location event
* for Ghidra.
* @param location location object to interpret
*/
void fireLocationEvent(Object location);
/**
* Handle notification from graph.
* @param notificationType command generated from graph
* @param handler associated graph handler
* @return true if notification was handled and there is no need for any other
* graph service provider to notified.
*/
boolean fireNotificationEvent(String notificationType, GraphSelectionHandler handler);
}

View File

@ -34,7 +34,7 @@ import ghidra.program.util.XRefHeaderFieldLocation;
* Field for display XRef headers.
*/
public class XRefHeaderFieldFactory extends XRefFieldFactory {
private static final String XREF_FIELD_NAME = "XRef Header";
public static final String XREF_FIELD_NAME = "XRef Header";
public XRefHeaderFieldFactory() {
super(XREF_FIELD_NAME);

View File

@ -13,7 +13,6 @@ eclipse.project.name = 'Features Decompiler'
dependencies {
compile project(':Base')
compile project(':SoftwareModeling')
// include Base src/test/resources when running decompiler integration tests (uses defaultTools)
integrationTestRuntime project(path: ':Base', configuration: 'testArtifacts')
testCompile "org.jmockit:jmockit:1.44"

View File

@ -19,309 +19,245 @@
import java.util.*;
import ghidra.app.decompiler.*;
import ghidra.app.plugin.core.graph.AddressBasedGraphDisplayListener;
import ghidra.app.script.GhidraScript;
import ghidra.app.services.GraphService;
import ghidra.app.services.GraphDisplayBroker;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.address.*;
import ghidra.program.model.graph.*;
import ghidra.program.model.lang.Register;
import ghidra.program.model.listing.Function;
import ghidra.program.model.listing.Program;
import ghidra.program.model.pcode.*;
import ghidra.service.graph.*;
import ghidra.util.Msg;
public class GraphAST extends GhidraScript {
protected static final String COLOR_ATTRIBUTE = "Color";
protected static final String ICON_ATTRIBUTE = "Icon";
Function func;
HighFunction high;
GraphData graph;
int edgecount;
@Override
public void run() throws Exception {
private Function func;
private AttributedGraph graph;
protected HighFunction high;
@Override
public void run() throws Exception {
PluginTool tool = state.getTool();
if (tool == null) {
println("Script is not running in GUI");
}
GraphService graphSvc = tool.getService(GraphService.class);
if (graphSvc == null) {
Msg.showError(this,
tool.getToolFrame(),
"GraphAST Error",
"GraphService not found: Please add a graph service provider to your tool");
GraphDisplayBroker graphDisplayBroker = tool.getService(GraphDisplayBroker.class);
if (graphDisplayBroker == null) {
Msg.showError(this, tool.getToolFrame(), "GraphAST Error",
"No graph display providers found: Please add a graph display provider to your tool");
return;
}
func = this.getFunctionContaining(this.currentAddress);
func = this.getFunctionContaining(this.currentAddress);
if (func == null) {
Msg.showWarn(this,
state.getTool().getToolFrame(),
"GraphAST Error",
"No Function at current location");
Msg.showWarn(this, state.getTool().getToolFrame(), "GraphAST Error",
"No Function at current location");
return;
}
buildAST();
graph = graphSvc.createGraphContent();
graph = new AttributedGraph();
buildGraph();
GraphDisplay graphDisplay = graphSvc.getGraphDisplay(true);
GraphDisplay graphDisplay =
graphDisplayBroker.getDefaultGraphDisplay(false, monitor);
// graphDisplay.defineVertexAttribute(CODE_ATTRIBUTE); //
// graphDisplay.defineVertexAttribute(SYMBOLS_ATTRIBUTE);
// graphDisplay.defineEdgeAttribute(EDGE_TYPE_ATTRIBUTE);
graphDisplay.setGraphData(graph);
// Install a handler so the selection/location will map
graphDisplay.setSelectionHandler(new GraphASTSelectionHandler(graphSvc, high,func.getProgram().getAddressFactory()));
}
graphDisplay.setGraph(graph, "Data-flow AST", false, monitor);
private void buildAST() throws DecompileException {
DecompileOptions options = new DecompileOptions();
// Install a handler so the selection/location will map
graphDisplay.setGraphDisplayListener(
new ASTGraphDisplayListener(tool, graphDisplay, high, func.getProgram()));
}
private void buildAST() throws DecompileException {
DecompileOptions options = new DecompileOptions();
DecompInterface ifc = new DecompInterface();
ifc.setOptions(options);
if ( !ifc.openProgram(this.currentProgram) ) {
throw new DecompileException("Decompiler", "Unable to initialize: "+ifc.getLastMessage());
if (!ifc.openProgram(this.currentProgram)) {
throw new DecompileException("Decompiler",
"Unable to initialize: " + ifc.getLastMessage());
}
ifc.setSimplificationStyle("normalize");
DecompileResults res = ifc.decompileFunction(func, 30, null);
high = res.getHighFunction();
}
private String getVarnodeKey(VarnodeAST vn) {
PcodeOp op = vn.getDef();
String id;
if (op != null)
id = op.getSeqnum().getTarget().toString(true) + " v " + Integer.toString(vn.getUniqueId());
else
id = "i v " + Integer.toString(vn.getUniqueId());
return id;
}
private String getOpKey(PcodeOpAST op) {
SequenceNumber sq = op.getSeqnum();
String id = sq.getTarget().toString(true) + " o " +Integer.toString(op.getSeqnum().getTime());
return id;
}
protected GraphVertex createVarnodeVertex(VarnodeAST vn) {
String name = vn.getAddress().toString(true);
String id = getVarnodeKey(vn);
String colorattrib = "Red";
if (vn.isConstant())
colorattrib = "DarkGreen";
else if (vn.isRegister()) {
colorattrib = "Blue";
Register reg = func.getProgram().getRegister(vn.getAddress(),vn.getSize());
if (reg != null)
name = reg.getName();
}
else if (vn.isUnique())
colorattrib = "Black";
else if (vn.isPersistant())
colorattrib = "DarkOrange";
else if (vn.isAddrTied())
colorattrib = "Orange";
GraphVertex vert = graph.createVertex(name, id);
if (vn.isInput())
vert.setAttribute(ICON_ATTRIBUTE, "TriangleDown");
else
vert.setAttribute(ICON_ATTRIBUTE, "Circle");
vert.setAttribute(COLOR_ATTRIBUTE,colorattrib);
return vert;
}
protected GraphVertex createOpVertex(PcodeOpAST op) {
String name = op.getMnemonic();
String id = getOpKey(op);
int opcode = op.getOpcode();
if ((opcode==PcodeOp.LOAD)||(opcode==PcodeOp.STORE)) {
Varnode vn = op.getInput(0);
AddressSpace addrspace = func.getProgram().getAddressFactory().getAddressSpace((int)vn.getOffset());
name += ' ' + addrspace.getName();
}
else if (opcode == PcodeOp.INDIRECT) {
Varnode vn = op.getInput(1);
if (vn != null) {
PcodeOp indOp = high.getOpRef((int)vn.getOffset());
if (indOp != null) {
name += " (" + indOp.getMnemonic() +')';
}
}
}
GraphVertex vert = graph.createVertex(name, id);
vert.setAttribute(ICON_ATTRIBUTE, "Square");
return vert;
}
protected GraphVertex getVarnodeVertex(HashMap<Integer,GraphVertex> vertices,VarnodeAST vn) {
GraphVertex res;
res = vertices.get(vn.getUniqueId());
if (res == null) {
res = createVarnodeVertex(vn);
vertices.put(vn.getUniqueId(), res);
}
return res;
}
protected GraphEdge createEdge(GraphVertex in,GraphVertex out) {
String id = Integer.toString(edgecount);
edgecount += 1;
return graph.createEdge(id, in, out);
}
protected void buildGraph() {
high = res.getHighFunction();
HashMap<Integer, GraphVertex> vertices = new HashMap<Integer, GraphVertex>();
edgecount = 0;
Iterator<PcodeOpAST> opiter = getPcodeOpIterator();
while(opiter.hasNext()) {
PcodeOpAST op = opiter.next();
GraphVertex o = createOpVertex(op);
for(int i=0;i<op.getNumInputs();++i) {
int opcode = op.getOpcode();
if ((i==0)&&((opcode==PcodeOp.LOAD)||(opcode==PcodeOp.STORE)))
continue;
if ((i==1)&&(opcode==PcodeOp.INDIRECT))
continue;
VarnodeAST vn = (VarnodeAST)op.getInput(i);
if (vn != null) {
GraphVertex v = getVarnodeVertex(vertices,vn);
createEdge(v,o);
}
}
VarnodeAST outvn = (VarnodeAST)op.getOutput();
if (outvn != null) {
GraphVertex outv = getVarnodeVertex(vertices,outvn);
if (outv != null)
createEdge(o,outv);
}
private String getVarnodeKey(VarnodeAST vn) {
PcodeOp op = vn.getDef();
String id;
if (op != null) {
id = op.getSeqnum().getTarget().toString(true) + " v " +
Integer.toString(vn.getUniqueId());
}
else {
id = "i v " + Integer.toString(vn.getUniqueId());
}
return id;
}
private String getOpKey(PcodeOpAST op) {
SequenceNumber sq = op.getSeqnum();
String id =
sq.getTarget().toString(true) + " o " + Integer.toString(op.getSeqnum().getTime());
return id;
}
protected AttributedVertex createVarnodeVertex(VarnodeAST vn) {
String name = vn.getAddress().toString(true);
String id = getVarnodeKey(vn);
String colorattrib = "Red";
if (vn.isConstant()) {
colorattrib = "DarkGreen";
}
else if (vn.isRegister()) {
colorattrib = "Blue";
Register reg = func.getProgram().getRegister(vn.getAddress(), vn.getSize());
if (reg != null) {
name = reg.getName();
}
}
}
else if (vn.isUnique()) {
colorattrib = "Black";
}
else if (vn.isPersistant()) {
colorattrib = "DarkOrange";
}
else if (vn.isAddrTied()) {
colorattrib = "Orange";
}
AttributedVertex vert = graph.addVertex(id, name);
if (vn.isInput()) {
vert.setAttribute(ICON_ATTRIBUTE, "TriangleDown");
}
else {
vert.setAttribute(ICON_ATTRIBUTE, "Circle");
}
vert.setAttribute(COLOR_ATTRIBUTE, colorattrib);
return vert;
}
protected AttributedVertex createOpVertex(PcodeOpAST op) {
String name = op.getMnemonic();
String id = getOpKey(op);
int opcode = op.getOpcode();
if ((opcode == PcodeOp.LOAD) || (opcode == PcodeOp.STORE)) {
Varnode vn = op.getInput(0);
AddressSpace addrspace =
func.getProgram().getAddressFactory().getAddressSpace((int) vn.getOffset());
name += ' ' + addrspace.getName();
}
else if (opcode == PcodeOp.INDIRECT) {
Varnode vn = op.getInput(1);
if (vn != null) {
PcodeOp indOp = high.getOpRef((int) vn.getOffset());
if (indOp != null) {
name += " (" + indOp.getMnemonic() + ')';
}
}
}
AttributedVertex vert = graph.addVertex(id, name);
vert.setAttribute(ICON_ATTRIBUTE, "Square");
return vert;
}
protected AttributedVertex getVarnodeVertex(Map<Integer, AttributedVertex> vertices, VarnodeAST vn) {
AttributedVertex res;
res = vertices.get(vn.getUniqueId());
if (res == null) {
res = createVarnodeVertex(vn);
vertices.put(vn.getUniqueId(), res);
}
return res;
}
protected AttributedEdge createEdge(AttributedVertex in, AttributedVertex out) {
return graph.addEdge(in, out);
}
protected void buildGraph() {
HashMap<Integer, AttributedVertex> vertices = new HashMap<>();
Iterator<PcodeOpAST> opiter = getPcodeOpIterator();
while (opiter.hasNext()) {
PcodeOpAST op = opiter.next();
AttributedVertex o = createOpVertex(op);
for (int i = 0; i < op.getNumInputs(); ++i) {
int opcode = op.getOpcode();
if ((i == 0) && ((opcode == PcodeOp.LOAD) || (opcode == PcodeOp.STORE))) {
continue;
}
if ((i == 1) && (opcode == PcodeOp.INDIRECT)) {
continue;
}
VarnodeAST vn = (VarnodeAST) op.getInput(i);
if (vn != null) {
AttributedVertex v = getVarnodeVertex(vertices, vn);
createEdge(v, o);
}
}
VarnodeAST outvn = (VarnodeAST) op.getOutput();
if (outvn != null) {
AttributedVertex outv = getVarnodeVertex(vertices, outvn);
if (outv != null) {
createEdge(o, outv);
}
}
}
}
protected Iterator<PcodeOpAST> getPcodeOpIterator() {
Iterator<PcodeOpAST> opiter = high.getPcodeOps();
return opiter;
}
class GraphASTSelectionHandler implements GraphSelectionHandler {
private boolean active; // true if the window is active
private boolean enabled;
HighFunction highfunc;
private GraphService graphService;
private AddressFactory addrFactory;
public GraphASTSelectionHandler(GraphService graphService,HighFunction highfunc,AddressFactory addrFactory) {
active = false;
enabled = true;
this.graphService = graphService;
this.highfunc = highfunc;
this.addrFactory = addrFactory;
}
class ASTGraphDisplayListener extends AddressBasedGraphDisplayListener {
private Address keyToAddress(String key) {
int firstcolon = key.indexOf(':');
if (firstcolon == -1) return null;
int firstspace = key.indexOf(' ');
String addrspacestring = key.substring(0,firstcolon);
String addrstring = key.substring(firstcolon+1,firstspace);
AddressSpace spc = addrFactory.getAddressSpace(addrspacestring);
if (spc == null) return null;
try {
return spc.getAddress(addrstring);
} catch (AddressFormatException e) {
HighFunction highfunc;
public ASTGraphDisplayListener(PluginTool tool, GraphDisplay display, HighFunction high,
Program program) {
super(tool, program, display);
highfunc = high;
}
@Override
protected List<String> getVertices(AddressSetView selection) {
List<String> ids = new ArrayList<String>();
return ids;
}
@Override
protected AddressSet getAddressSetForVertices(List<String> vertexIds) {
AddressSet set = new AddressSet();
for (String id : vertexIds) {
Address address = getAddressForVertexId(id);
if (address != null) {
set.add(address);
}
}
return set;
}
@Override
protected Address getAddressForVertexId(String vertexId) {
int firstcolon = vertexId.indexOf(':');
if (firstcolon == -1) {
return null;
}
}
public String getGraphType() {
return "Data-flow AST";
}
public boolean isActive() {
return active;
int firstSpace = vertexId.indexOf(' ');
String addrString = vertexId.substring(0, firstSpace);
return getAddress(addrString);
}
public boolean isEnabled() {
return enabled;
}
public void locate(String renoirLocation) {
Address addr = keyToAddress(renoirLocation);
if (addr==null) return;
graphService.fireLocationEvent(addr);
}
public String locate(Object ghidraLocation) {
if (!(ghidraLocation instanceof Address))
return null;
Address addr = (Address)ghidraLocation;
Iterator<PcodeOpAST> iter = highfunc.getPcodeOps(addr);
if (iter.hasNext()) {
PcodeOpAST op = iter.next();
return getOpKey(op);
}
return null;
}
public boolean notify(String notificationType) {
return false;
}
public void select(String[] renoirSelections) {
if (!enabled)
return;
AddressSet set = new AddressSet();
for (int i = 0; i < renoirSelections.length; i++) {
Address addr = keyToAddress(renoirSelections[i]);
if (addr == null) {
continue;
}
set.addRange(addr,addr);
}
graphService.fireSelectionEvent(set);
}
public String[] select(Object ghidraSelection) {
String [] keys;
if (ghidraSelection == null) {
keys = new String[0];
return keys;
}
if (!(ghidraSelection instanceof AddressSetView)) {
return null; // selection not understood
}
AddressSetView set = (AddressSetView) ghidraSelection;
ArrayList<String> ops = new ArrayList<String>();
Iterator<PcodeOpAST> iter = highfunc.getPcodeOps();
while(iter.hasNext()) {
PcodeOpAST op = iter.next();
Address addr = op.getSeqnum().getTarget();
if (set.contains(addr)) {
ops.add(getOpKey(op));
VarnodeAST vn = (VarnodeAST)op.getOutput();
if (vn != null)
ops.add(getVarnodeKey(vn));
}
}
keys = new String[ ops.size() ];
return ops.toArray(keys);
}
public void setActive(boolean active) {
this.active = active;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
}
}
}

View File

@ -18,40 +18,40 @@
import java.util.*;
import ghidra.program.model.graph.GraphEdge;
import ghidra.program.model.graph.GraphVertex;
import ghidra.program.model.pcode.*;
import ghidra.service.graph.AttributedEdge;
import ghidra.service.graph.AttributedVertex;
public class GraphASTAndFlow extends GraphAST {
@Override
protected void buildGraph() {
HashMap<Integer, GraphVertex> vertices = new HashMap<Integer, GraphVertex>();
HashMap<Integer, AttributedVertex> vertices = new HashMap<>();
edgecount = 0;
Iterator<PcodeOpAST> opiter = getPcodeOpIterator();
HashMap<PcodeOp, GraphVertex> map = new HashMap<PcodeOp, GraphVertex>();
HashMap<PcodeOp, AttributedVertex> map = new HashMap<PcodeOp, AttributedVertex>();
while (opiter.hasNext()) {
PcodeOpAST op = opiter.next();
GraphVertex o = createOpVertex(op);
AttributedVertex o = createOpVertex(op);
map.put(op, o);
for (int i = 0; i < op.getNumInputs(); ++i) {
if ((i == 0) &&
((op.getOpcode() == PcodeOp.LOAD) || (op.getOpcode() == PcodeOp.STORE))) {
continue;
}
if ((i == 1)&&(op.getOpcode() == PcodeOp.INDIRECT))
if ((i == 1) && (op.getOpcode() == PcodeOp.INDIRECT)) {
continue;
}
VarnodeAST vn = (VarnodeAST) op.getInput(i);
if (vn != null) {
GraphVertex v = getVarnodeVertex(vertices, vn);
AttributedVertex v = getVarnodeVertex(vertices, vn);
createEdge(v, o);
}
}
VarnodeAST outvn = (VarnodeAST) op.getOutput();
if (outvn != null) {
GraphVertex outv = getVarnodeVertex(vertices, outvn);
AttributedVertex outv = getVarnodeVertex(vertices, outvn);
if (outv != null) {
createEdge(o, outv);
}
@ -59,8 +59,8 @@ public class GraphASTAndFlow extends GraphAST {
}
opiter = getPcodeOpIterator();
HashSet<PcodeBlockBasic> seenParents = new HashSet<PcodeBlockBasic>();
HashMap<PcodeBlock, GraphVertex> first = new HashMap<PcodeBlock, GraphVertex>();
HashMap<PcodeBlock, GraphVertex> last = new HashMap<PcodeBlock, GraphVertex>();
HashMap<PcodeBlock, AttributedVertex> first = new HashMap<>();
HashMap<PcodeBlock, AttributedVertex> last = new HashMap<>();
while (opiter.hasNext()) {
PcodeOpAST op = opiter.next();
PcodeBlockBasic parent = op.getParent();
@ -76,7 +76,7 @@ public class GraphASTAndFlow extends GraphAST {
first.put(parent, map.get(next));
}
if (prev != null && map.containsKey(prev) && map.containsKey(next)) {
GraphEdge edge = createEdge(map.get(prev), map.get(next));
AttributedEdge edge = createEdge(map.get(prev), map.get(next));
edge.setAttribute(COLOR_ATTRIBUTE, "Black");
}
prev = next;
@ -91,18 +91,10 @@ public class GraphASTAndFlow extends GraphAST {
for (int i = 0; i < block.getInSize(); i++) {
PcodeBlock in = block.getIn(i);
if (last.containsKey(in)) {
GraphEdge edge = createEdge(last.get(in), first.get(block));
AttributedEdge edge = createEdge(last.get(in), first.get(block));
edge.setAttribute(COLOR_ATTRIBUTE, "Red");
}
}
// All outs were already handled by the ins! Don't make two links!
// for (int i = 0; i < block.getOutSize(); i++) {
// PcodeBlock out = block.getOut(i);
// if (first.containsKey(out)) {
// GraphEdge edge = createEdge(last.get(block), first.get(out));
// edge.setAttribute(COLOR_ATTRIBUTE, "Red");
// }
// }
}
}

View File

@ -125,14 +125,14 @@ public class DecompilerProvider extends NavigatableComponentProviderAdapter
@Override
public void serviceRemoved(Class<?> interfaceClass, Object service) {
if (interfaceClass.equals(GraphService.class)) {
if (interfaceClass.equals(GraphDisplayBroker.class)) {
graphServiceRemoved();
}
}
@Override
public void serviceAdded(Class<?> interfaceClass, Object service) {
if (interfaceClass.equals(GraphService.class)) {
if (interfaceClass.equals(GraphDisplayBroker.class)) {
graphServiceAdded();
}
}
@ -967,7 +967,10 @@ public class DecompilerProvider extends NavigatableComponentProviderAdapter
}
private void graphServiceRemoved() {
if (graphASTControlFlowAction != null && tool.getService(GraphService.class) == null) {
if (graphASTControlFlowAction == null) {
return;
}
if (tool.getService(GraphDisplayBroker.class) == null) {
tool.removeAction(graphASTControlFlowAction);
graphASTControlFlowAction.dispose();
graphASTControlFlowAction = null;
@ -975,7 +978,8 @@ public class DecompilerProvider extends NavigatableComponentProviderAdapter
}
private void graphServiceAdded() {
if (graphASTControlFlowAction == null && tool.getService(GraphService.class) != null) {
GraphDisplayBroker service = tool.getService(GraphDisplayBroker.class);
if (service != null && service.getDefaultGraphDisplayProvider() != null) {
graphASTControlFlowAction = new GraphASTControlFlowAction();
addLocalAction(graphASTControlFlowAction);
}

View File

@ -0,0 +1,96 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.decompile.actions;
import static ghidra.app.plugin.core.decompile.actions.ASTGraphTask.GraphType.*;
import java.util.List;
import ghidra.app.plugin.core.decompile.actions.ASTGraphTask.GraphType;
import ghidra.app.plugin.core.graph.AddressBasedGraphDisplayListener;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.address.*;
import ghidra.program.model.pcode.HighFunction;
import ghidra.program.model.pcode.PcodeBlockBasic;
import ghidra.service.graph.GraphDisplay;
/**
* Listener for when an AST graph's nodes are selected.
*/
public class ASTGraphDisplayListener extends AddressBasedGraphDisplayListener {
private HighFunction hfunction;
private GraphType graphType;
ASTGraphDisplayListener(PluginTool tool, GraphDisplay display, HighFunction hfunction,
GraphType graphType) {
super(tool, hfunction.getFunction().getProgram(), display);
this.hfunction = hfunction;
this.graphType = graphType;
}
@Override
protected List<String> getVertices(AddressSetView selection) {
return null;
}
@Override
protected AddressSet getAddressSetForVertices(List<String> vertexIds) {
if (graphType != CONTROL_FLOW_GRAPH) {
return null;
}
AddressSet set = new AddressSet();
Address location = null;
List<PcodeBlockBasic> blocks = hfunction.getBasicBlocks();
for (String vertixId : vertexIds) {
try {
int index = Integer.parseInt(vertixId);
PcodeBlockBasic block = blocks.get(index);
Address start = block.getStart();
set.addRange(start, block.getStop());
if (location == null || start.compareTo(location) < 0) {
location = start;
}
}
catch (NumberFormatException e) {
// continue
}
}
return set;
}
@Override
protected String getVertexIdForAddress(Address address) {
if (graphType != CONTROL_FLOW_GRAPH) {
return null;
}
List<PcodeBlockBasic> blocks = hfunction.getBasicBlocks();
for (PcodeBlockBasic block : blocks) {
Address start = block.getStart();
Address stop = block.getStop();
if (address.compareTo(start) >= 0 && address.compareTo(stop) <= 0) {
return Integer.toString(block.getIndex());
}
}
return super.getVertexIdForAddress(address);
}
@Override
protected Address getAddressForVertexId(String vertexId) {
return null;
}
}

View File

@ -1,132 +0,0 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.decompile.actions;
import java.util.List;
import ghidra.app.services.GraphService;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSet;
import ghidra.program.model.graph.GraphSelectionHandler;
import ghidra.program.model.pcode.HighFunction;
import ghidra.program.model.pcode.PcodeBlockBasic;
import ghidra.program.util.ProgramSelection;
class ASTGraphSelectionHandler implements GraphSelectionHandler {
private GraphService graphService;
private HighFunction hfunction;
private int graphType;
private boolean active = false; // true if the window is active
private boolean enabled = true;
ASTGraphSelectionHandler(GraphService graphService, HighFunction hfunction, int graphType) {
this.graphService = graphService;
this.hfunction = hfunction;
this.graphType = graphType;
}
public String getGraphType() {
return graphType == ASTGraphTask.DATA_FLOW_GRAPH ?
"AST Data Flow" : "AST Control Flow";
}
public boolean isActive() {
return active;
}
public boolean isEnabled() {
return enabled;
}
public void setActive(boolean active) {
this.active = active;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public void locate(String location) {
//Msg.debug(this, "locate1: " + location);
}
public String locate(Object locationObject) {
if (graphType != ASTGraphTask.CONTROL_FLOW_GRAPH) {
return null;
}
if (!(locationObject instanceof Address))
return null;
Address addr = (Address) locationObject;
List<PcodeBlockBasic> blocks = hfunction.getBasicBlocks();
for (PcodeBlockBasic block : blocks) {
Address start = block.getStart();
Address stop = block.getStop();
if (addr.compareTo(start) >= 0 && addr.compareTo(stop) <= 0) {
//Msg.debug(this, "index=" + block.getIndex());
return Integer.toString(block.getIndex());
}
}
return addr.toString();
}
public boolean notify(String notificationType) {
//Msg.debug(this, "notify: " + notificationType);
return false;
}
public void select(String[] selectedIndexes) {
if (graphType != ASTGraphTask.CONTROL_FLOW_GRAPH) {
return;
}
AddressSet set = new AddressSet();
Address location = null;
List<PcodeBlockBasic> blocks = hfunction.getBasicBlocks();
for (String indexStr : selectedIndexes) {
try {
int index = Integer.parseInt(indexStr);
PcodeBlockBasic block = blocks.get(index);
Address start = block.getStart();
set.addRange(start, block.getStop());
if (location == null || start.compareTo(location) < 0) {
location = start;
}
}
catch (NumberFormatException e) {
// continue
}
}
if (location != null) {
graphService.fireLocationEvent(location);
}
graphService.fireSelectionEvent(new ProgramSelection(set));
}
public String[] select(Object ghidraSelection) {
// TODO Auto-generated method stub
return null;
}
}

View File

@ -15,27 +15,34 @@
*/
package ghidra.app.plugin.core.decompile.actions;
import ghidra.app.services.GraphService;
import java.util.Iterator;
import ghidra.app.services.GraphDisplayBroker;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.address.Address;
import ghidra.program.model.graph.*;
import ghidra.program.model.lang.Register;
import ghidra.program.model.listing.Program;
import ghidra.program.model.pcode.*;
import ghidra.service.graph.*;
import ghidra.util.Msg;
import ghidra.util.NumericUtilities;
import ghidra.util.exception.CancelledException;
import ghidra.util.exception.GraphException;
import ghidra.util.task.*;
import java.util.Iterator;
import ghidra.util.task.Task;
import ghidra.util.task.TaskMonitor;
public class ASTGraphTask extends Task {
enum GraphType {
CONTROL_FLOW_GRAPH("AST Control Flow"), DATA_FLOW_GRAPH("AST Data Flow");
private String name;
GraphType(String name) {
this.name = name;
}
static final int CONTROL_FLOW_GRAPH = 0;
static final int DATA_FLOW_GRAPH = 1;
private static final String[] GRAPH_TYPES =
new String[] { "AST Control Flow", "AST Data Flow" };
public String getName() {
return name;
}
}
private static final String CODE_ATTRIBUTE = "Code";
private static final String SYMBOLS_ATTRIBUTE = "Symbols";
@ -55,19 +62,19 @@ public class ASTGraphTask extends Task {
private final static String DATA_NODE = "Data";
// "6"; // Data Node, used for indirection
private GraphService graphService;
private GraphDisplayBroker graphService;
private boolean newGraph;
private int codeLimitPerBlock;
private Address location;
private HighFunction hfunction;
private int graphType;
private GraphType graphType;
private int uniqueNum = 0;
private TaskListener listener;
private PluginTool tool;
public ASTGraphTask(GraphService graphService, boolean newGraph, int codeLimitPerBlock,
Address location, HighFunction hfunction, int graphType) {
super("Graph " + GRAPH_TYPES[graphType], true, false, true);
public ASTGraphTask(GraphDisplayBroker graphService, boolean newGraph, int codeLimitPerBlock,
Address location, HighFunction hfunction, GraphType graphType, PluginTool tool) {
super("Graph " + graphType.getName(), true, false, true);
this.graphService = graphService;
this.newGraph = newGraph;
@ -75,87 +82,60 @@ public class ASTGraphTask extends Task {
this.location = location;
this.hfunction = hfunction;
this.graphType = graphType;
this.listener = new TaskListener() {
@Override
public void taskCancelled(Task task) {
// don't care
}
@Override
public void taskCompleted(Task task) {
try {
GraphDisplay graphDisplay =
ASTGraphTask.this.graphService.getGraphDisplay(false);
if (graphDisplay != null) {
graphDisplay.popup();
}
}
catch (GraphException e) {
// the programmer was too lazy to handle this
}
}
};
addTaskListener(listener);
this.tool = tool;
}
@Override
public void run(TaskMonitor monitor) {
// get a new graph
GraphData graph = graphService.createGraphContent();
if (graph == null)
return;
AttributedGraph graph = new AttributedGraph();
ASTGraphSelectionHandler handler = null;
try {
monitor.setMessage("Computing Graph...");
if (graphType == DATA_FLOW_GRAPH) {
if (graphType == GraphType.DATA_FLOW_GRAPH) {
createDataFlowGraph(graph, monitor);
}
else {
createControlFlowGraph(graph, monitor);
}
handler = new ASTGraphSelectionHandler(graphService, hfunction, graphType);
}
catch (CancelledException e1) {
return;
}
GraphDisplay display = graphService.getDefaultGraphDisplay(!newGraph, monitor);
ASTGraphDisplayListener displayListener =
new ASTGraphDisplayListener(tool, display, hfunction, graphType);
display.setGraphDisplayListener(displayListener);
GraphDisplay display;
try {
monitor.setMessage("Obtaining handle to graph provider...");
display = graphService.getGraphDisplay(newGraph);
if (monitor.isCancelled())
if (monitor.isCancelled()) {
return;
monitor.setCancelEnabled(false);
if (!newGraph) {
display.clear();
}
display.setSelectionHandler(handler);
monitor.setCancelEnabled(false);
monitor.setMessage("Rendering Graph...");
display.defineVertexAttribute(CODE_ATTRIBUTE);
display.defineVertexAttribute(SYMBOLS_ATTRIBUTE);
display.setGraphData(graph);
display.setVertexLabel(CODE_ATTRIBUTE, GraphDisplay.ALIGN_LEFT, 12, true,
graphType == CONTROL_FLOW_GRAPH ? (codeLimitPerBlock + 1) : 1);
graphType == GraphType.CONTROL_FLOW_GRAPH ? (codeLimitPerBlock + 1) : 1);
String description =
graphType == GraphType.DATA_FLOW_GRAPH ? "AST Data Flow" : "AST Control Flow";
display.setGraph(graph, description, false, monitor);
// set the graph location
if (location != null) {
display.locate(location, false);
display.setLocation(displayListener.getVertexIdForAddress(location));
}
}
catch (GraphException e) {
Msg.showError(this, null, "Graph Error", e.getMessage());
}
catch (CancelledException e1) {
return;
}
}
protected void createDataFlowGraph(GraphData graph, TaskMonitor monitor)
protected void createDataFlowGraph(AttributedGraph graph, TaskMonitor monitor)
throws CancelledException {
Iterator<PcodeOpAST> opIter = hfunction.getPcodeOps();
while (opIter.hasNext()) {
@ -164,7 +144,7 @@ public class ASTGraphTask extends Task {
}
}
private void graphOpData(GraphData graph, PcodeOpAST op, TaskMonitor monitor)
private void graphOpData(AttributedGraph graph, PcodeOpAST op, TaskMonitor monitor)
throws CancelledException {
// TODO: Dropped INDIRECT pcode ops ??
@ -173,13 +153,13 @@ public class ASTGraphTask extends Task {
return;
}
GraphVertex opVertex = getOpVertex(graph, op, monitor);
AttributedVertex opVertex = getOpVertex(graph, op, monitor);
Varnode output = op.getOutput();
if (output != null) {
opVertex = getOpVertex(graph, op, monitor);
GraphVertex outVertex = getDataVertex(graph, output, monitor);
graph.createEdge(Integer.toString(++uniqueNum), opVertex, outVertex);
AttributedVertex outVertex = getDataVertex(graph, output, monitor);
graph.addEdge(opVertex, outVertex);
// TODO: set edge attributes ??
}
@ -204,26 +184,26 @@ public class ASTGraphTask extends Task {
if (opVertex == null) {
opVertex = getOpVertex(graph, op, monitor);
}
GraphVertex inVertex = getDataVertex(graph, input, monitor);
graph.createEdge(Integer.toString(++uniqueNum), inVertex, opVertex);
AttributedVertex inVertex = getDataVertex(graph, input, monitor);
graph.addEdge(inVertex, opVertex);
// TODO: set edge attributes ??
}
}
}
private GraphVertex getOpVertex(GraphData graph, PcodeOpAST op, TaskMonitor monitor) {
private AttributedVertex getOpVertex(AttributedGraph graph, PcodeOpAST op, TaskMonitor monitor) {
String key = "O_" + Integer.toString(op.getSeqnum().getTime());
GraphVertex vertex = graph.getVertex(key);
AttributedVertex vertex = graph.getVertex(key);
if (vertex == null) {
vertex = graph.createVertex(key, key);
vertex = graph.addVertex(key, key);
setOpVertexAttributes(vertex, op);
}
return vertex;
}
private void setOpVertexAttributes(GraphVertex vertex, PcodeOpAST op) {
private void setOpVertexAttributes(AttributedVertex vertex, PcodeOpAST op) {
vertex.setAttribute(CODE_ATTRIBUTE, formatOpMnemonic(op));
@ -243,11 +223,11 @@ public class ASTGraphTask extends Task {
vertex.setAttribute(VERTEX_TYPE_ATTRIBUTE, vertexType);
}
private GraphVertex getDataVertex(GraphData graph, Varnode node, TaskMonitor monitor) {
private AttributedVertex getDataVertex(AttributedGraph graph, Varnode node, TaskMonitor monitor) {
// TODO: Missing Varnode unique ID ??
GraphVertex vertex = null;
AttributedVertex vertex = null;
HighVariable var = node.getHigh();
String key;
if (var != null) {
@ -259,13 +239,13 @@ public class ASTGraphTask extends Task {
}
if (vertex == null) {
vertex = graph.createVertex(key, key);
vertex = graph.addVertex(key, key);
setVarnodeVertexAttributes(vertex, node);
}
return vertex;
}
private void setVarnodeVertexAttributes(GraphVertex vertex, Varnode node) {
private void setVarnodeVertexAttributes(AttributedVertex vertex, Varnode node) {
String label = "";
HighVariable var = node.getHigh();
@ -277,7 +257,7 @@ public class ASTGraphTask extends Task {
vertex.setAttribute(VERTEX_TYPE_ATTRIBUTE, DATA_NODE);
}
protected void createControlFlowGraph(GraphData graph, TaskMonitor monitor)
protected void createControlFlowGraph(AttributedGraph graph, TaskMonitor monitor)
throws CancelledException {
Iterator<PcodeBlockBasic> pblockIter = hfunction.getBasicBlocks().iterator();
while (pblockIter.hasNext()) {
@ -286,32 +266,32 @@ public class ASTGraphTask extends Task {
}
}
private void graphPcodeBlock(GraphData graph, PcodeBlock pblock, TaskMonitor monitor)
private void graphPcodeBlock(AttributedGraph graph, PcodeBlock pblock, TaskMonitor monitor)
throws CancelledException {
if (pblock == null) {
return;
}
GraphVertex fromVertex = getBlockVertex(graph, pblock, monitor);
AttributedVertex fromVertex = getBlockVertex(graph, pblock, monitor);
int outCnt = pblock.getOutSize();
for (int i = 0; i < outCnt; i++) {
monitor.checkCanceled();
PcodeBlock outPBlock = pblock.getOut(i);
GraphVertex toVertex = getBlockVertex(graph, outPBlock, monitor);
graph.createEdge(Integer.toString(++uniqueNum), fromVertex, toVertex);
AttributedVertex toVertex = getBlockVertex(graph, outPBlock, monitor);
graph.addEdge(fromVertex, toVertex);
// TODO: set edge attributes ??
}
}
private GraphVertex getBlockVertex(GraphData graph, PcodeBlock pblock, TaskMonitor monitor) {
private AttributedVertex getBlockVertex(AttributedGraph graph, PcodeBlock pblock, TaskMonitor monitor) {
String key = Integer.toString(pblock.getIndex());
GraphVertex vertex = graph.getVertex(key);
AttributedVertex vertex = graph.getVertex(key);
if (vertex == null) {
vertex = graph.createVertex(key, key);
vertex = graph.addVertex(key, key);
if (pblock instanceof PcodeBlockBasic) {
setBlockVertexAttributes(vertex, (PcodeBlockBasic) pblock);
}
@ -323,7 +303,7 @@ public class ASTGraphTask extends Task {
return vertex;
}
private void setBlockVertexAttributes(GraphVertex vertex, PcodeBlockBasic basicBlk) {
private void setBlockVertexAttributes(AttributedVertex vertex, PcodeBlockBasic basicBlk) {
// Build Pcode representation
StringBuffer buf = new StringBuffer();
@ -440,19 +420,4 @@ public class ASTGraphTask extends Task {
}
return node.toString();
}
// private void assignVertexSymbols(GraphVertex vertex, Address addr) {
// Symbol[] symbols = function.getProgram().getSymbolTable().getSymbols(addr);
// if (symbols.length != 0) {
// StringBuffer buf = new StringBuffer();
// for (int i = 0; i < symbols.length; i++) {
// if (i != 0) {
// buf.append('\n');
// }
// buf.append(symbols[i].getName());
// }
// vertex.setAttribute(SYMBOLS_ATTRIBUTE, buf.toString());
// }
// }
}

View File

@ -15,16 +15,17 @@
*/
package ghidra.app.plugin.core.decompile.actions;
import static ghidra.app.plugin.core.decompile.actions.ASTGraphTask.GraphType.*;
import docking.action.MenuData;
import ghidra.app.plugin.core.decompile.DecompilerActionContext;
import ghidra.app.services.GraphService;
import ghidra.app.services.GraphDisplayBroker;
import ghidra.framework.options.Options;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.address.Address;
import ghidra.program.model.pcode.HighFunction;
import ghidra.util.Msg;
import ghidra.util.task.TaskLauncher;
public class GraphASTControlFlowAction extends AbstractDecompilerAction {
public GraphASTControlFlowAction() {
@ -40,10 +41,10 @@ public class GraphASTControlFlowAction extends AbstractDecompilerAction {
@Override
protected void decompilerActionPerformed(DecompilerActionContext context) {
PluginTool tool = context.getTool();
GraphService graphService = tool.getService(GraphService.class);
if (graphService == null) {
GraphDisplayBroker service = tool.getService(GraphDisplayBroker.class);
if (service == null) {
Msg.showError(this, tool.getToolFrame(), "AST Graph Failed",
"GraphService not found: Please add a graph service provider to your tool");
"Graph consumer not found: Please add a graph consumer provider to your tool");
return;
}
@ -53,8 +54,8 @@ public class GraphASTControlFlowAction extends AbstractDecompilerAction {
int codeLimitPerBlock = options.getInt("Max Code Lines Displayed", 10);
HighFunction highFunction = context.getHighFunction();
Address locationAddr = context.getLocation().getAddress();
ASTGraphTask task = new ASTGraphTask(graphService, !reuseGraph, codeLimitPerBlock,
locationAddr, highFunction, ASTGraphTask.CONTROL_FLOW_GRAPH);
ASTGraphTask task = new ASTGraphTask(service, !reuseGraph, codeLimitPerBlock, locationAddr,
highFunction, CONTROL_FLOW_GRAPH, tool);
new TaskLauncher(task, tool.getToolFrame());
}

View File

@ -115,10 +115,10 @@ public class Krb5ActiveDirectoryAuthenticationModule implements AuthenticationMo
throw new IOException("Missing username or password values");
}
NameCallback destNcb = AuthenticationModule.getFirstCallbackOfType(
NameCallback.class, loginmodule_callbacks);
PasswordCallback destPcb = AuthenticationModule.getFirstCallbackOfType(
PasswordCallback.class, loginmodule_callbacks);
NameCallback destNcb = AuthenticationModule
.getFirstCallbackOfType(NameCallback.class, loginmodule_callbacks);
PasswordCallback destPcb = AuthenticationModule
.getFirstCallbackOfType(PasswordCallback.class, loginmodule_callbacks);
if (destNcb != null) {
destNcb.setName(tmpName);

View File

@ -0,0 +1,7 @@
EXCLUDE FROM GHIDRA JAR: true
MODULE FILE LICENSE: lib/jungrapht-visualization-2.11.20 BSD
MODULE FILE LICENSE: lib/jgrapht-core-1.3.1.jar LGPL 2.1
MODULE FILE LICENSE: lib/jgrapht-io-1.3.1.jar LGPL 2.1
MODULE FILE LICENSE: lib/jheaps-0.10.jar Apache License 2.0
MODULE FILE LICENSE: lib/slf4j-api-1.7.25.jar MIT

View File

@ -0,0 +1,25 @@
apply from: "$rootProject.projectDir/gradle/distributableGhidraModule.gradle"
apply from: "$rootProject.projectDir/gradle/javaProject.gradle"
apply from: "$rootProject.projectDir/gradle/helpProject.gradle"
apply from: "$rootProject.projectDir/gradle/jacocoProject.gradle"
apply from: "$rootProject.projectDir/gradle/javaTestProject.gradle"
apply plugin: 'eclipse'
eclipse.project.name = 'Features Graph Services'
dependencies {
compile project(":Base")
compile "com.github.tomnelson:jungrapht-visualization:1.0-RC7"
compile "org.jgrapht:jgrapht-core:1.4.0"
// not using jgrapht-io code that depends on antlr, so exclude antlr
compile ("org.jgrapht:jgrapht-io:1.4.0") { exclude group: "org.antlr", module: "antlr4-runtime" }
runtime "org.slf4j:slf4j-api:1.7.25"
runtime "org.jheaps:jheaps:0.11"
helpPath project(path: ":Base", configuration: 'helpPath')
}

View File

@ -0,0 +1,17 @@
##VERSION: 2.0
##MODULE IP: BSD
Module.manifest||GHIDRA||||END|
build.gradle||GHIDRA||||END|
src/main/help/help/TOC_Source.xml||GHIDRA||||END|
src/main/help/help/shared/arrow.gif||GHIDRA||||END|
src/main/help/help/shared/note.png||Oxygen Icons - LGPL 3.0|||Oxygen icon theme (dual license; LGPL or CC-SA-3.0)|END|
src/main/help/help/shared/tip.png||Oxygen Icons - LGPL 3.0|||Oxygen icon theme (dual license; LGPL or CC-SA-3.0)|END|
src/main/help/help/topics/GraphServices/GraphDisplay.htm||GHIDRA||||END|
src/main/help/help/topics/GraphServices/GraphExport.htm||GHIDRA||||END|
src/main/help/help/topics/GraphServices/images/DefaultGraphDisplay.png||GHIDRA||||END|
src/main/help/help/topics/GraphServices/images/ExportDialog.png||GHIDRA||||END|
src/main/resources/images/magnifier.png||FAMFAMFAM Icons - CC 2.5|||famfamfam silk icon set|END|
src/main/resources/images/redspheregraph.png||GHIDRA||||END|
src/main/resources/images/sat2.png||GHIDRA||||END|
src/main/resources/images/tree.png||GHIDRA||||END|
src/main/resources/jungrapht.properties||GHIDRA||||END|

View File

@ -0,0 +1,60 @@
<?xml version='1.0' encoding='ISO-8859-1' ?>
<!--
This is an XML file intended to be parsed by the Ghidra help system. It is loosely based
upon the JavaHelp table of contents document format. The Ghidra help system uses a
TOC_Source.xml file to allow a module with help to define how its contents appear in the
Ghidra help viewer's table of contents. The main document (in the Base module)
defines a basic structure for the
Ghidra table of contents system. Other TOC_Source.xml files may use this structure to insert
their files directly into this structure (and optionally define a substructure).
In this document, a tag can be either a <tocdef> or a <tocref>. The former is a definition
of an XML item that may have a link and may contain other <tocdef> and <tocref> children.
<tocdef> items may be referred to in other documents by using a <tocref> tag with the
appropriate id attribute value. Using these two tags allows any module to define a place
in the table of contents system (<tocdef>), which also provides a place for
other TOC_Source.xml files to insert content (<tocref>).
During the help build time, all TOC_Source.xml files will be parsed and validated to ensure
that all <tocref> tags point to valid <tocdef> tags. From these files will be generated
<module name>_TOC.xml files, which are table of contents files written in the format
desired by the JavaHelp system. Additionally, the genated files will be merged together
as they are loaded by the JavaHelp system. In the end, when displaying help in the Ghidra
help GUI, there will be on table of contents that has been created from the definitions in
all of the modules' TOC_Source.xml files.
Tags and Attributes
<tocdef>
-id - the name of the definition (this must be unique across all TOC_Source.xml files)
-text - the display text of the node, as seen in the help GUI
-target** - the file to display when the node is clicked in the GUI
-sortgroup - this is a string that defines where a given node should appear under a given
parent. The string values will be sorted by the JavaHelp system using
a javax.text.RulesBasedCollator. If this attribute is not specified, then
the text of attribute will be used.
<tocref>
-id - The id of the <tocdef> that this reference points to
**The URL for the target is relative and should start with 'help/topics'. This text is
used by the Ghidra help system to provide a universal starting point for all links so that
they can be resolved at runtime, across modules.
-->
<tocroot>
<tocref id="Graphing">
<tocdef id="Graph Services" text="Graph Services">
<tocdef id="Default Graph Display" text="Default Graph Display" target="help/topics/GraphServices/GraphDisplay.htm" />
<tocdef id="Exporting a Graph" text="Exporting a Graph" target="help/topics/GraphServices/GraphExport.htm" />
</tocdef>
</tocref>
</tocroot>

View File

@ -0,0 +1,58 @@
/* ###
* 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.
*/
/*
WARNING!
This file is copied to all help directories. If you change this file, you must copy it
to each src/main/help/help/shared directory.
Java Help Note: JavaHelp does not accept sizes (like in 'margin-top') in anything but
px (pixel) or with no type marking.
*/
body { margin-bottom: 50px; margin-left: 10px; margin-right: 10px; margin-top: 10px; } /* some padding to improve readability */
li { font-family:times new roman; font-size:14pt; }
h1 { color:#000080; font-family:times new roman; font-size:36pt; font-style:italic; font-weight:bold; text-align:center; }
h2 { margin: 10px; margin-top: 20px; color:#984c4c; font-family:times new roman; font-size:18pt; font-weight:bold; }
h3 { margin-left: 10px; margin-top: 20px; color:#0000ff; font-family:times new roman; font-size:14pt; font-weight:bold; }
h4 { margin-left: 10px; margin-top: 20px; font-family:times new roman; font-size:14pt; font-style:italic; }
/*
P tag code. Most of the help files nest P tags inside of blockquote tags (the was the
way it had been done in the beginning). The net effect is that the text is indented. In
modern HTML we would use CSS to do this. We need to support the Ghidra P tags, nested in
blockquote tags, as well as naked P tags. The following two lines accomplish this. Note
that the 'blockquote p' definition will inherit from the first 'p' definition.
*/
p { margin-left: 40px; font-family:times new roman; font-size:14pt; }
blockquote p { margin-left: 10px; }
p.providedbyplugin { color:#7f7f7f; margin-left: 10px; font-size:14pt; margin-top:100px }
p.ProvidedByPlugin { color:#7f7f7f; margin-left: 10px; font-size:14pt; margin-top:100px }
p.relatedtopic { color:#800080; margin-left: 10px; font-size:14pt; }
p.RelatedTopic { color:#800080; margin-left: 10px; font-size:14pt; }
/*
We wish for a tables to have space between it and the preceding element, so that text
is not too close to the top of the table. Also, nest the table a bit so that it is clear
the table relates to the preceding text.
*/
table { margin-left: 20px; margin-top: 10px; width: 80%;}
td { font-family:times new roman; font-size:14pt; vertical-align: top; }
th { font-family:times new roman; font-size:14pt; font-weight:bold; background-color: #EDF3FE; }
code { color: black; font-family: courier new; font-size: 14pt; }

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -0,0 +1,67 @@
<!DOCTYPE doctype PUBLIC "-//W3C//DTD HTML 4.0 Frameset//EN">
<HTML>
<HEAD>
<META name="generator" content=
"HTML Tidy for Java (vers. 2009-12-01), see jtidy.sourceforge.net">
<TITLE>Graphing</TITLE>
<META http-equiv="Content-Type" content="text/html; charset=windows-1252">
<LINK rel="stylesheet" type="text/css" href="../../shared/Frontpage.css">
</HEAD>
<BODY lang="EN-US">
<A name="Default_Graph_Display"/>
<H1>Default Graph Display</H1>
<H2>Visualization of a Graph</H2>
<BLOCKQUOTE>
<P>The visualization display will show the graph in a new window or in a new tab of a previously created graph window.</P>
<BLOCKQUOTE>
<BLOCKQUOTE>
<P align="left"><IMG src="images/DefaultGraphDisplay.png" border="1"></P>
</BLOCKQUOTE>
</BLOCKQUOTE>
</BLOCKQUOTE>
<H2>Manipulating the Graph:</H2>
<ul>
<li>MouseButton1+drag will translate the display in the x and y axis</li>
<li>Mouse Wheel will zoom in and out</li>
<li>Ctrl+MouseButton1 will select a vertex or edge</li>
<ul>
<li>Shift+Ctrl+MouseButton1 over an unselected vertex will add that vertex to the selection</li>
<li>Shift+Ctrl+MouseButton1 over a previously selected vertex will remove that vertex from the selection</li>
</ul>
<li>Ctrl+MouseButton1+drag on an empty area will create a rectangular area and select enclosed vertices</li>
<li>Ctrl+MouseButton1+drag over a vertex will reposition all selected vertices</li>
</ul>
<H2>Upper-right Icon Buttons:</H2>
<ul>
<li>The <IMG src="images/fingerPointer.png"> toggle button, when 'set' will cause a located vertex (red arrow) to be scrolled to the center of the view</li>
<li>The <IMG src="images/sat2.png" width="16" height="16"> button will open a satellite mini view of the graph in the lower right corner. The mini-view can be manipulated with the mouse to affect the main view</li>
<li>The <IMG src="images/reload3.png"> button will reset any visual transformations on the graph and center it at a best-effort size</li>
<li>The <IMG src="images/magnifier.png"> button will open a rectangular magnification lens in the graph view</li>
<ul>
<li>MouseButton1 click-drag on the lens center circle to move the magnifier lens</li>
<li>MouseButton1 click-draw on a lens edge diamond to resize the magnifier lens </li>
<li>MouseButton1 click on the upper-right circle-cross to dispose of the magnifier lens</li>
<li>Ctrl-MouseWheel to change the magnification of the lens</li>
</ul>
<li>The <IMG src="images/view-filter.png"> button will open a Filter dialog. Select buttons in the dialog to hide specific vertices or edges in the display</li>
<ul>
<li>The Filter dialog buttons are created by examining the graph vertex/edge properties to discover candidates for filtering</li>
</ul>
<li>Pull-Down the <IMG src="images/katomic.png" width="16" height="16"> Arrangement menu to select one of several graph layout algorithms.</li>
<ul>
<li>Force Balanced is a <b>Force Directed Layout Algorithm</b> using the the <b>Kamada Kawai</b> approach. It attempts to balance the graph by considering vertices and edge connections.</li>
<li>Force Directed is a <b>Force Directed Layout Algorithm</b> using the <b>Fructermann Reingold</b> approach. It pushes unconnected vertices apart and draws connected vertices together.</li>
<li>Circle will arrange vertices in a Circle. If there are not too many edges (less than specified in the jungrapht.circle.reduceEdgeCrossingMaxEdges property with a default of 200), it will attempt to reduce edge crossing by rearranging the vertices.</li>
<li>Compact Hierarchical is the <b>TidierTree Layout Algorithm</b>. It builds a tree structure and attempts to reduce horizontal space.</li>
<li>Hierarchical MinCross is the <b>Sugiyama Layout Algorithm</b>. It attempts to route edges around vertices in order to reduce crossing.</li>
<li>Hierarchical is a basic Tree algorithm. It prioritizes 'important' edges while constructing the tree.</li>
<li>Hierarchical Multi Row is the Tree algorithm above, but it will create new rows (typewriter fashion) to reduce horizontal spread.</li>
<li>Radial is a Tree structure with the root(s) at the center and child vertices radiating outwards.</li>
</ul>
</BODY>
</HTML>

View File

@ -0,0 +1,53 @@
<!DOCTYPE doctype PUBLIC "-//W3C//DTD HTML 4.0 Frameset//EN">
<HTML>
<HEAD>
<META name="generator" content=
"HTML Tidy for Java (vers. 2009-12-01), see jtidy.sourceforge.net">
<TITLE>Graph Export</TITLE>
<META http-equiv="Content-Type" content="text/html; charset=windows-1252">
<LINK rel="stylesheet" type="text/css" href="../../shared/Frontpage.css">
</HEAD>
<BODY lang="EN-US">
<A name="Default Graph Exporter"/>
<H1>Graph Export Service</H1>
<H2> Export Dialog </H2>
<P> Whenever a graph is generated and the graph output is set to <B>Graph Export</B>, then the
following graph export dialog is displayed: </P>
<BR>
<BLOCKQUOTE>
<BLOCKQUOTE>
<P align="left"><IMG src="images/ExportDialog.png"></P>
</BLOCKQUOTE>
</BLOCKQUOTE>
<BR>
<BLOCKQUOTE>
<P>The Export Graph dialog offers a choice of the following graph formats:</P>
<BLOCKQUOTE>
<ul>
<li>CSV</li>
<li>DIMACS</li>
<li>DOT</li>
<li>GML</li>
<li>Graph6_Sparse6</li>
<li>GraphML</li>
<li>JSON</li>
<li>Lemon</li>
<li>Matrix</li>
<li>Visio</li>
</ul>
</BLOCKQUOTE>
</BLOCKQUOTE>
<p>The output file may be selected from the dialog text area combined with the ellipis button that will open a file system browser.
<p>The <b>Ok</b> button will marshal the graph to the selected file in the selected format and close the dialog.</p>
<p>The <b>Cancel</b> button will close the dialog and perform no other action.</p>
</BODY>
</HTML>

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

@ -0,0 +1,242 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.graph;
import java.awt.*;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.util.*;
import java.util.List;
import java.util.function.Function;
import java.util.function.Supplier;
import javax.swing.AbstractButton;
import javax.swing.JRadioButton;
import javax.swing.event.EventListenerList;
import com.google.common.collect.HashMultiset;
import com.google.common.collect.Multiset;
import ghidra.service.graph.Attributed;
/**
* A filtering mechanism where filters (and filter control buttons) are discovered and created
* based on the contents of the Attribute map
*/
public class AttributeFilters implements ItemSelectable {
public static class Builder {
/**
* key names that are precluded from being considered for filter creation
*/
private Collection<String> excludedAttributes = Collections.emptyList();
/**
* all of the Attributed elements that will be considered for the creation of filters
*/
private Set<? extends Attributed> elements;
/**
* a factor used to control whether a filter is created or not.
* {@code double threshold = Math.max(2, elements.size() * maxFactor);}
* determines the threshold for the creation of a filter on an attribute value
*/
private double maxFactor;
/**
* provides a toolkit button control for the filters ({@link JRadioButton} by default)
*/
private Supplier<AbstractButton> buttonSupplier = JRadioButton::new;
/**
* a {@link Function} to allow custom coloring of the individual toolkit button foreground
*/
private Function<String, Paint> paintFunction = v -> Color.black;
/**
* @param excluded ignored keys
* @return the Builder
*/
public Builder exclude(Collection<String> excluded) {
this.excludedAttributes = excluded;
return this;
}
/**
* @param newElements the elements to consider
* @return this Builder
*/
public Builder elements(Set<? extends Attributed> newElements) {
this.elements = newElements;
return this;
}
/**
* @param newMaxFactor the factor to use in creating a threshold for filter creation
* @return this Builder
*/
public Builder maxFactor(double newMaxFactor) {
this.maxFactor = newMaxFactor;
return this;
}
/**
* @param newButtonSupplier the toolkit button to provide
* @return this Builder
*/
public Builder buttonSupplier(Supplier<AbstractButton> newButtonSupplier) {
this.buttonSupplier = newButtonSupplier;
return this;
}
/**
* @param newPaintFunction the {@code Function} to color the toolkit buttons
* @return this Builder
*/
public Builder paintFunction(Function<String, Paint> newPaintFunction) {
this.paintFunction = newPaintFunction;
return this;
}
/**
* create the configured instance
* @return the configured instance
*/
public AttributeFilters build() {
return new AttributeFilters(this);
}
}
/**
*
* @return a builder to configure an instance of AttributeFilters
*/
public static Builder builder() {
return new Builder();
}
/**
*
* @param builder configurations for the instance
*/
private AttributeFilters(Builder builder) {
this(builder.excludedAttributes, builder.elements, builder.maxFactor,
builder.buttonSupplier, builder.paintFunction);
}
List<AbstractButton> buttons = new ArrayList<>();
Multiset<String> multiset = HashMultiset.create();
Set<String> selectedTexts = new HashSet<>();
protected EventListenerList listenerList = new EventListenerList();
/**
*
* @param precludedNames keys that will not be considered for filters
* @param elements elements that will be considered for filters
* @param maxFactor controls the threshold for filter creation
* @param buttonSupplier provides toolkit controls for the filters
* @param paintFunction provides a way to individually color the control buttons
*/
private AttributeFilters(Collection<String> precludedNames, Set<? extends Attributed> elements,
double maxFactor,
Supplier<AbstractButton> buttonSupplier, Function<String, Paint> paintFunction) {
// count up the unique attribute values (skipping the 'precluded names' we know we don't want)
for (Attributed element : elements) {
Map<String, String> attributeMap = new HashMap<>(element.getAttributeMap());
for (Map.Entry<String, String> entry : attributeMap.entrySet()) {
if (!precludedNames.contains(entry.getKey())) {
multiset.add(entry.getValue());
}
}
}
if (maxFactor == 0) {
maxFactor = .01;
}
double threshold = Math.max(2, elements.size() * maxFactor);
// accept the values with cardinality above the max of 2 and maxFactor times the of the number elements.
multiset.removeIf(s -> multiset.count(s) < threshold);
// create a button for every element that was retained
multiset.elementSet();
for (String key : multiset.elementSet()) {
AbstractButton button = buttonSupplier.get();
button.setForeground((Color) paintFunction.apply(key));
button.setText(key);
button.addItemListener(item -> {
if (item.getStateChange() == ItemEvent.SELECTED) {
selectedTexts.add(button.getText());
fireItemStateChanged(new ItemEvent(this, ItemEvent.ITEM_STATE_CHANGED,
this.selectedTexts, ItemEvent.SELECTED));
}
else if (item.getStateChange() == ItemEvent.DESELECTED) {
selectedTexts.remove(button.getText());
fireItemStateChanged(new ItemEvent(this, ItemEvent.ITEM_STATE_CHANGED,
this.selectedTexts, ItemEvent.DESELECTED));
}
});
buttons.add(button);
}
}
/**
*
* @return the filter control toolkit buttons
*/
public List<AbstractButton> getButtons() {
return buttons;
}
// event support:
@Override
public Object[] getSelectedObjects() {
return selectedTexts.toArray();
}
/**
* add a listener to react to changes in the filter selection
* @param l the listener
*/
@Override
public void addItemListener(ItemListener l) {
listenerList.add(ItemListener.class, l);
}
/**
* remove a listener for filter changes
* @param l the listener
*/
@Override
public void removeItemListener(ItemListener l) {
listenerList.remove(ItemListener.class, l);
}
/**
* alert listeners that there is a change in the selected filters
* @param e the event
*/
protected void fireItemStateChanged(ItemEvent e) {
// Guaranteed to return a non-null array
Object[] listeners = listenerList.getListenerList();
// Process the listeners last to first, notifying
// those that are interested in this event
for (int i = listeners.length - 2; i >= 0; i -= 2) {
if (listeners[i] == ItemListener.class) {
((ItemListener) listeners[i + 1]).itemStateChanged(e);
}
}
}
}

View File

@ -0,0 +1,152 @@
/* ###
* 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.export;
import java.util.Collections;
import java.util.Map;
import java.util.function.Function;
import java.util.function.Supplier;
import org.jgrapht.nio.*;
import org.jgrapht.nio.csv.*;
import org.jgrapht.nio.dimacs.DIMACSExporter;
import org.jgrapht.nio.dimacs.DIMACSFormat;
import org.jgrapht.nio.dot.DOTExporter;
import org.jgrapht.nio.gml.GmlExporter;
import org.jgrapht.nio.graphml.GraphMLExporter;
import org.jgrapht.nio.json.JSONExporter;
import org.jgrapht.nio.lemon.LemonExporter;
import org.jgrapht.nio.matrix.MatrixExporter;
/**
* Base factory class for using the JGrapht export library. Clients should subclass this
* for specific graph types and provide better providers than the defaults defined here.
*
* @param <V> the graph vertex type
* @param <E> the graph edge type
*/
public abstract class AbstractGraphExporterFactory<V, E> {
protected char csvDelimiter = ',';
protected CSVFormat csvFormat = CSVFormat.EDGE_LIST;
protected DIMACSFormat dimacsFormat = DIMACSExporter.DEFAULT_DIMACS_FORMAT;
protected MatrixExporter.Format matrixFormat = MatrixExporter.Format.SPARSE_ADJACENCY_MATRIX;
protected Function<V, String> defaultVertexIdProvider = new IntegerIdProvider<>();
protected Function<E, String> defaultEdgeIdProvider = new IntegerIdProvider<>();
protected Supplier<String> graphIdProvider = () -> "Ghidra";
protected Function<V, String> vertexLabelProvider = Object::toString;
protected Function<E, String> edgeLabelProvider = Object::toString;
protected Function<E, String> edgeIdProvider = defaultEdgeIdProvider;
protected Function<V, String> vertexIdProvider = defaultVertexIdProvider;
protected Function<E, Map<String, Attribute>> edgeAttributeProvider =
e -> Collections.emptyMap();
protected Function<V, Map<String, Attribute>> vertexAttributeProvider =
v -> Collections.emptyMap();
/**
* Creates an exporter of the specified type
*
* @param format the file output type
* @return a {@link GraphExporter} configured to output in the specified format
*/
public GraphExporter<V, E> createExporter(GraphExportFormat format) {
switch (format) {
case CSV:
return createCsvExporter();
case DIMACS:
return createDimacsExporter();
case DOT:
return createDotExporter();
case GML:
return createGmlExporter();
case JSON:
return createJsonExporter();
case LEMON:
return createLemonExporter();
case MATRIX:
return createMatrixExporter();
case VISIO:
return createVisioExporter();
case GRAPHML:
default:
return createGraphMlExporter();
}
}
private GraphExporter<V, E> createGraphMlExporter() {
GraphMLExporter<V, E> exporter = new GraphMLExporter<>(vertexIdProvider);
setupExporter(exporter);
return exporter;
}
private GraphExporter<V, E> createVisioExporter() {
VisioExporter<V, E> exporter = new VisioExporter<>(vertexIdProvider);
setupExporter(exporter);
return exporter;
}
private GraphExporter<V, E> createMatrixExporter() {
MatrixExporter<V, E> exporter = new MatrixExporter<>(matrixFormat, vertexIdProvider);
setupExporter(exporter);
return exporter;
}
private GraphExporter<V, E> createLemonExporter() {
LemonExporter<V, E> exporter = new LemonExporter<>(vertexIdProvider);
setupExporter(exporter);
return exporter;
}
private GraphExporter<V, E> createJsonExporter() {
JSONExporter<V, E> exporter = new JSONExporter<>(vertexIdProvider);
setupExporter(exporter);
return exporter;
}
private GraphExporter<V, E> createGmlExporter() {
GmlExporter<V, E> exporter = new GmlExporter<>(vertexIdProvider);
setupExporter(exporter);
return exporter;
}
private GraphExporter<V, E> createDotExporter() {
DOTExporter<V, E> exporter = new DOTExporter<>(vertexIdProvider);
setupExporter(exporter);
return exporter;
}
private GraphExporter<V, E> createDimacsExporter() {
DIMACSExporter<V, E> exporter = new DIMACSExporter<>(vertexIdProvider, dimacsFormat);
setupExporter(exporter);
return exporter;
}
private GraphExporter<V, E> createCsvExporter() {
CSVExporter<V, E> exporter = new CSVExporter<>(vertexIdProvider, csvFormat, csvDelimiter);
setupExporter(exporter);
return exporter;
}
private void setupExporter(BaseExporter<V, E> exporter) {
exporter.setEdgeIdProvider(defaultEdgeIdProvider);
exporter.setVertexAttributeProvider(vertexAttributeProvider);
exporter.setEdgeAttributeProvider(edgeAttributeProvider);
exporter.setGraphIdProvider(graphIdProvider);
}
}

View File

@ -0,0 +1,60 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.graph.export;
import java.util.AbstractMap;
import java.util.Map;
import java.util.stream.Collectors;
import org.jgrapht.nio.*;
import ghidra.service.graph.*;
/**
* Specific implementation of {@link AbstractGraphExporterFactory} for exporting graphs with
* {@link AttributedVertex} vertices and {@link AttributedEdge} edges.
*/
public class AttributedGraphExporterFactory
extends AbstractGraphExporterFactory<AttributedVertex, AttributedEdge> {
AttributedGraphExporterFactory() {
vertexLabelProvider = AttributedVertex::getName;
edgeLabelProvider = Object::toString;
edgeIdProvider = e -> e.getId();
edgeAttributeProvider = AttributedGraphExporterFactory::getComponentAttributes;
vertexAttributeProvider = AttributedGraphExporterFactory::getComponentAttributes;
vertexIdProvider = AttributedVertex::getId;
}
/**
* Gets {@link GraphExporter} configured to output a graph in the specified format.
* @param format the output file format.
* @return {@link GraphExporter} configured to output a graph in the specified format.
*/
public static GraphExporter<AttributedVertex, AttributedEdge> getExporter(
GraphExportFormat format) {
return new AttributedGraphExporterFactory().createExporter(format);
}
private static Map<String, Attribute> getComponentAttributes(Attributed v) {
return v.getAttributeMap()
.entrySet()
.stream()
.map(entry -> new AbstractMap.SimpleEntry<String, Attribute>(entry.getKey(),
new DefaultAttribute<String>(entry.getValue(), AttributeType.STRING)))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
}
}

View File

@ -0,0 +1,118 @@
/* ###
* 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.export;
import java.util.List;
import org.jgrapht.Graph;
import ghidra.framework.plugintool.PluginTool;
import ghidra.service.graph.*;
import ghidra.util.Swing;
import ghidra.util.task.TaskMonitor;
/**
* {@link GraphDisplay} implementation for exporting graphs. In this case, there is no
* associated visual display, instead the graph output gets sent to a file. The
* {@link GraphDisplay} is mostly just a placeholder for executing the export function. By
* hijacking the {@link GraphDisplayProvider} and {@link GraphDisplay} interfaces for exporting,
* all graph generating operations can be exported instead of being displayed without changing
* the graph generation code.
*/
class ExportAttributedGraphDisplay implements GraphDisplay {
private PluginTool pluginTool;
private String description;
/**
* Create the initial display, the graph-less visualization viewer, and its controls
* @param programGraphDisplayProvider provides a {@link PluginTool} for Docking features
*/
ExportAttributedGraphDisplay(ExportAttributedGraphDisplayProvider programGraphDisplayProvider) {
this.pluginTool = programGraphDisplayProvider.getPluginTool();
}
@Override
public void close() {
// This display is not interactive, so N/A
}
@Override
public void setGraphDisplayListener(GraphDisplayListener listener) {
// This display is not interactive, so N/A
}
@Override
public void selectVertices(List<String> vertexList) {
// This display is not interactive, so N/A
}
@Override
public void setLocation(String vertexID) {
// This display is not interactive, so N/A
}
/**
* set the {@link AttributedGraph} for visualization
* @param attributedGraph the {@link AttributedGraph} to visualize
*/
private void doSetGraphData(AttributedGraph attributedGraph) {
GraphExporterDialog dialog = new GraphExporterDialog(attributedGraph);
Swing.runLater(() -> pluginTool.showDialog(dialog));
}
@Override
public void defineVertexAttribute(String attributeName) {
// no effect
}
@Override
public void defineEdgeAttribute(String attributeName) {
// no effect
}
@Override
public void setVertexLabel(String attributeName, int alignment, int size, boolean monospace,
int maxLines) {
// no effect
}
@Override
public void setGraph(AttributedGraph graphData, String description, boolean append,
TaskMonitor monitor) {
this.description = description;
doSetGraphData(graphData);
}
/**
* remove all vertices and edges from the {@link Graph}
*/
@Override
public void clear() {
// not interactive, so N/A
}
@Override
public void updateVertexName(String id, String newName) {
// do nothing
}
@Override
public String getGraphDescription() {
return description;
}
}

View File

@ -0,0 +1,79 @@
/* ###
* 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.export;
import ghidra.framework.options.Options;
import ghidra.framework.plugintool.PluginTool;
import ghidra.service.graph.GraphDisplay;
import ghidra.service.graph.GraphDisplayProvider;
import ghidra.util.HelpLocation;
import ghidra.util.task.TaskMonitor;
/**
* {@link GraphDisplayProvider} implementation for exporting graphs. In this case, there is no
* associated visual display, instead the graph output gets sent to a file. The corresponding
* {@link GraphDisplay} is mostly just a placeholder for executing the export function. By
* hijacking the {@link GraphDisplayProvider} and {@link GraphDisplay} interfaces for exporting,
* all graph generating operations can be exported instead of being displayed without changing
* the graph generation code.
*/
public class ExportAttributedGraphDisplayProvider implements GraphDisplayProvider {
private PluginTool pluginTool;
private Options options;
@Override
public String getName() {
return "Graph Export";
}
public PluginTool getPluginTool() {
return pluginTool;
}
public Options getOptions() {
return options;
}
@Override
public GraphDisplay getGraphDisplay(boolean reuseGraph,
TaskMonitor monitor) {
ExportAttributedGraphDisplay display = new ExportAttributedGraphDisplay(this);
return display;
}
@Override
public void initialize(PluginTool tool, Options graphOptions) {
this.pluginTool = tool;
this.options = graphOptions;
}
@Override
public void optionsChanged(Options graphOptions) {
// no options so far graph exporting
}
@Override
public void dispose() {
// nothing to clean up
}
@Override
public HelpLocation getHelpLocation() {
return new HelpLocation("GraphServices", "Default_Graph_Exporter");
}
}

View File

@ -0,0 +1,38 @@
/* ###
* 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.export;
enum GraphExportFormat {
CSV("csv"),
DIMACS("col"),
DOT("gv"),
GML("gml"),
GRAPHML("graphhml"),
JSON("json"),
LEMON("lgf"),
MATRIX("g"),
VISIO("vsd");
private final String fileExtension;
GraphExportFormat(String fileExtension) {
this.fileExtension = fileExtension;
}
public String getDefaultFileExtension() {
return fileExtension;
}
}

View File

@ -0,0 +1,333 @@
/* ###
* 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.export;
import java.awt.BorderLayout;
import java.awt.Component;
import java.io.*;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.swing.*;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import org.jgrapht.Graph;
import org.jgrapht.nio.GraphExporter;
import docking.DialogComponentProvider;
import docking.options.editor.ButtonPanelFactory;
import docking.widgets.OptionDialog;
import docking.widgets.combobox.GhidraComboBox;
import docking.widgets.filechooser.GhidraFileChooser;
import docking.widgets.filechooser.GhidraFileChooserMode;
import docking.widgets.label.GLabel;
import ghidra.framework.preferences.Preferences;
import ghidra.service.graph.AttributedEdge;
import ghidra.service.graph.AttributedVertex;
import ghidra.util.*;
import ghidra.util.filechooser.ExtensionFileFilter;
import ghidra.util.filechooser.GhidraFileFilter;
import ghidra.util.layout.PairLayout;
import ghidra.util.layout.VerticalLayout;
import ghidra.util.task.TaskLauncher;
import ghidra.util.task.TaskMonitor;
/**
* Dialog for exporting a program from a Ghidra project to an external file in one of the
* supported export formats.
*/
public class GraphExporterDialog extends DialogComponentProvider {
private static GraphExportFormat lastUsedExporterFormat = GraphExportFormat.GRAPHML; // default to GZF first time
private JTextField filePathTextField;
private JButton fileChooserButton;
private GhidraComboBox<GraphExportFormat> comboBox;
private Graph<AttributedVertex, AttributedEdge> graph;
/**
* Construct a new ExporterDialog for exporting a program, optionally only exported a
* selected region.
*
* @param graph the graph to save
*/
public GraphExporterDialog(Graph<AttributedVertex, AttributedEdge> graph) {
super("Export Graph");
this.graph = graph;
addWorkPanel(buildWorkPanel());
addOKButton();
addCancelButton();
setHelpLocation(new HelpLocation("ExporterPlugin", "Exporter_Dialog"));
validate();
}
private JComponent buildWorkPanel() {
JPanel panel = new JPanel(new VerticalLayout(5));
panel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
panel.add(buildMainPanel());
panel.add(buildButtonPanel());
return panel;
}
private Component buildButtonPanel() {
JPanel panel = new JPanel(new BorderLayout());
panel.setBorder(BorderFactory.createEmptyBorder(0, 10, 0, 10));
return panel;
}
private Component buildMainPanel() {
JPanel panel = new JPanel(new PairLayout(5, 5));
panel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
panel.add(new GLabel("Format: ", SwingConstants.RIGHT));
panel.add(buildFormatChooser());
panel.add(new GLabel("Output File: ", SwingConstants.RIGHT));
panel.add(buildFilePanel());
return panel;
}
public void setFilePath(String filePath) {
filePathTextField.setText(filePath);
}
private Component buildFilePanel() {
filePathTextField = new JTextField();
filePathTextField.setName("OUTPUT_FILE_TEXTFIELD");
filePathTextField.setText(getFileName());
filePathTextField.getDocument().addDocumentListener(new DocumentListener() {
@Override
public void changedUpdate(DocumentEvent e) {
validate();
}
@Override
public void insertUpdate(DocumentEvent e) {
validate();
}
@Override
public void removeUpdate(DocumentEvent e) {
validate();
}
});
fileChooserButton = ButtonPanelFactory.createButton(ButtonPanelFactory.BROWSE_TYPE);
fileChooserButton.addActionListener(e -> chooseDestinationFile());
JPanel panel = new JPanel(new BorderLayout());
panel.add(filePathTextField, BorderLayout.CENTER);
panel.add(fileChooserButton, BorderLayout.EAST);
return panel;
}
private String getFileName() {
String name = "graph";
File lastDir = getLastExportDirectory();
return lastDir.getAbsolutePath() + File.separator + name;
}
private void chooseDestinationFile() {
GhidraFileChooser chooser = new GhidraFileChooser(getComponent());
chooser.setSelectedFile(getLastExportDirectory());
chooser.setTitle("Select Output File");
chooser.setApproveButtonText("Select Output File");
chooser.setApproveButtonToolTipText("Select File");
chooser.setFileSelectionMode(GhidraFileChooserMode.FILES_ONLY);
chooser.setSelectedFileFilter(GhidraFileFilter.ALL);
GraphExportFormat exporter = getSelectedExporter();
if (exporter != null) {
chooser.setFileFilter(
new ExtensionFileFilter(exporter.getDefaultFileExtension(), exporter.toString()));
}
String filePath = filePathTextField.getText().trim();
File currentFile = filePath.isEmpty() ? null : new File(filePath);
if (currentFile != null) {
chooser.setSelectedFile(currentFile);
}
File file = chooser.getSelectedFile();
if (file != null) {
setLastExportDirectory(file);
filePathTextField.setText(file.getAbsolutePath());
}
}
private void setLastExportDirectory(File file) {
Preferences.setProperty(Preferences.LAST_EXPORT_DIRECTORY, file.getParent());
Preferences.store();
}
private File getLastExportDirectory() {
String lastDirStr = Preferences.getProperty(Preferences.LAST_EXPORT_DIRECTORY,
System.getProperty("user.home"), true);
return new File(lastDirStr);
}
private Component buildFormatChooser() {
List<GraphExportFormat> exporters = getApplicableExporters();
comboBox = new GhidraComboBox<>(exporters.toArray(new GraphExportFormat[0]));
GraphExportFormat defaultExporter = getDefaultExporter(exporters);
if (defaultExporter != null) {
comboBox.setSelectedItem(defaultExporter);
}
return comboBox;
}
private List<GraphExportFormat> getApplicableExporters() {
return Arrays.asList(GraphExportFormat.values());
}
private GraphExportFormat getDefaultExporter(List<GraphExportFormat> list) {
// first try the last one used
for (GraphExportFormat exporter : list) {
if (lastUsedExporterFormat.equals(exporter)) {
return exporter;
}
}
return list.isEmpty() ? null : list.get(0);
}
private void validate() {
setOkEnabled(false);
setStatusText("");
if (getSelectedExporter() == null) {
setStatusText("Please select an exporter format.");
return;
}
String fileToExportInto = filePathTextField.getText();
if (fileToExportInto.length() == 0) {
setStatusText("Please enter a destination file.");
return;
}
File file = new File(fileToExportInto);
if (file.isDirectory()) {
setStatusText("The specified output file is a directory.");
return;
}
if (file.exists() && !file.canWrite()) {
setStatusText("The specified output file is read-only.");
return;
}
setOkEnabled(true);
}
private GraphExportFormat getSelectedExporter() {
return (GraphExportFormat) comboBox.getSelectedItem();
}
private File getSelectedOutputFile() {
String filename = appendExporterFileExtension(filePathTextField.getText().trim());
File outputFileName = new File(filename);
if (outputFileName.getParent() == null) {
File defaultParent = new File(System.getProperty("user.home"));
outputFileName = new File(defaultParent, filename);
}
return outputFileName;
}
private String appendExporterFileExtension(String filename) {
GraphExportFormat exporterFormat = getSelectedExporter();
String extension = "." + exporterFormat.getDefaultFileExtension();
if (!filename.toLowerCase().endsWith(extension.toLowerCase())) {
return filename + extension;
}
return filename;
}
@Override
protected void okCallback() {
lastUsedExporterFormat = getSelectedExporter();
setLastExportDirectory(getSelectedOutputFile());
if (doExport()) {
close();
}
}
private boolean doExport() {
AtomicBoolean success = new AtomicBoolean();
TaskLauncher.launchModal("Exporting Graph",
monitor -> success.set(tryExport(monitor)));
return success.get();
}
private boolean tryExport(TaskMonitor monitor) {
GraphExportFormat exporterFormat = getSelectedExporter();
File outputFile = getSelectedOutputFile();
try {
if (outputFile.exists() &&
OptionDialog.showOptionDialog(getComponent(), "Overwrite Existing File?",
"The file " + outputFile + " already exists.\nDo you want to overwrite it?",
"Overwrite", OptionDialog.QUESTION_MESSAGE) != OptionDialog.OPTION_ONE) {
return false;
}
Writer writer = new FileWriter(outputFile);
GraphExporter<AttributedVertex, AttributedEdge> exporter =
AttributedGraphExporterFactory.getExporter(exporterFormat);
exporter.exportGraph(graph, writer);
displaySummaryResults(exporterFormat);
return true;
}
catch (Exception e) {
Msg.error(this, "Exception exporting", e);
SystemUtilities.runSwingLater(() -> setStatusText(
"Exception exporting: " + e.getMessage() + ". If null, see log for details."));
}
return false;
}
/**
* TODO: this does nothing useful
* @param exporter the export format
*/
private void displaySummaryResults(GraphExportFormat exporter) {
File outputFile = getSelectedOutputFile();
String results =
"Destination file: " +
"Destination file Size: " +
outputFile.length() + "\n" +
"Format: " +
exporter.toString() + "\n\n";
String log = exporter.toString();
if (log != null) {
results += log;
}
}
// for testing
public void setOutputFile(String outputFilePath) {
filePathTextField.setText(outputFilePath);
}
// for testing
public void setExportFormat(GraphExportFormat format) {
comboBox.setSelectedItem(format);
}
}

View File

@ -0,0 +1,83 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.graph.visualization;
import java.awt.geom.Point2D;
import org.jdesktop.animation.timing.Animator;
import org.jdesktop.animation.timing.interpolation.PropertySetter;
import org.jungrapht.visualization.MultiLayerTransformer;
import org.jungrapht.visualization.VisualizationViewer;
import ghidra.graph.job.AbstractAnimatorJob;
import ghidra.service.graph.AttributedEdge;
import ghidra.service.graph.AttributedVertex;
public class CenterAnimation extends AbstractAnimatorJob {
protected int duration = 1000;
private Point2D oldPoint;
private Point2D newPoint;
private Point2D lastPoint = new Point2D.Double();
private VisualizationViewer<AttributedVertex, AttributedEdge> viewer;
public CenterAnimation(VisualizationViewer<AttributedVertex, AttributedEdge> viewer,
Point2D oldPoint, Point2D newPoint) {
this.viewer = viewer;
this.oldPoint = oldPoint;
this.newPoint = newPoint;
lastPoint.setLocation(oldPoint.getX(), oldPoint.getY());
}
@Override
public Animator createAnimator() {
Animator newAnimator =
PropertySetter.createAnimator(duration, this, "percentComplete", 0.0, 1.0);
newAnimator.setAcceleration(0f);
newAnimator.setDeceleration(0.8f);
return newAnimator;
}
public void setPercentComplete(double percentComplete) {
double journeyX = (newPoint.getX() - oldPoint.getX()) * percentComplete;
double journeyY = (newPoint.getY() - oldPoint.getY()) * percentComplete;
double newX = oldPoint.getX() + journeyX;
double newY = oldPoint.getY() + journeyY;
double deltaX = lastPoint.getX() - newX;
double deltaY = lastPoint.getY() - newY;
lastPoint.setLocation(newX, newY);
if (deltaX == 0 && deltaY == 0) {
return;
}
viewer.getRenderContext()
.getMultiLayerTransformer()
.getTransformer(MultiLayerTransformer.Layer.LAYOUT)
.translate(deltaX, deltaY);
viewer.repaint();
}
@Override
public void finished() {
setPercentComplete(1);
}
}

View File

@ -0,0 +1,283 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.graph.visualization;
import static java.util.Map.*;
import java.awt.Color;
import java.awt.Paint;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import ghidra.service.graph.Attributed;
/**
* support for coercing colors from attributes or color names
*/
public abstract class Colors {
private static final Pattern HEX_PATTERN = Pattern.compile("(0x|#)[0-9A-Fa-f]{6}");
// cannot instantiate nor extend
private Colors() {
}
/**
* a map of well-known 'web' color names to colors
*/
static Map<String, Color> WEB_COLOR_MAP = Map.ofEntries(
entry("Black", Color.decode("0x000000")),
entry("Navy", Color.decode("0x000080")),
entry("DarkBlue", Color.decode("0x00008B")),
entry("MediumBlue", Color.decode("0x0000CD")),
entry("Blue", Color.decode("0x0000FF")),
entry("DarkGreen", Color.decode("0x006400")),
entry("Green", Color.decode("0x008000")),
entry("Teal", Color.decode("0x008080")),
entry("DarkCyan", Color.decode("0x008B8B")),
entry("DeepSkyBlue", Color.decode("0x00BFFF")),
entry("DarkTurquoise", Color.decode("0x00CED1")),
entry("MediumSpringGreen", Color.decode("0x00FA9A")),
entry("Lime", Color.decode("0x00FF00")),
entry("SpringGreen", Color.decode("0x00FF7F")),
entry("Aqua", Color.decode("0x00FFFF")),
entry("Cyan", Color.decode("0x00FFFF")),
entry("MidnightBlue", Color.decode("0x191970")),
entry("DodgerBlue", Color.decode("0x1E90FF")),
entry("LightSeaGreen", Color.decode("0x20B2AA")),
entry("ForestGreen", Color.decode("0x228B22")),
entry("SeaGreen", Color.decode("0x2E8B57")),
entry("DarkSlateGray", Color.decode("0x2F4F4F")),
entry("DarkSlateGrey", Color.decode("0x2F4F4F")),
entry("LimeGreen", Color.decode("0x32CD32")),
entry("MediumSeaGreen", Color.decode("0x3CB371")),
entry("Turquoise", Color.decode("0x40E0D0")),
entry("RoyalBlue", Color.decode("0x4169E1")),
entry("SteelBlue", Color.decode("0x4682B4")),
entry("DarkSlateBlue", Color.decode("0x483D8B")),
entry("MediumTurquoise", Color.decode("0x48D1CC")),
entry("Indigo", Color.decode("0x4B0082")),
entry("DarkOliveGreen", Color.decode("0x556B2F")),
entry("CadetBlue", Color.decode("0x5F9EA0")),
entry("CornflowerBlue", Color.decode("0x6495ED")),
entry("RebeccaPurple", Color.decode("0x663399")),
entry("MediumAquaMarine", Color.decode("0x66CDAA")),
entry("DimGray", Color.decode("0x696969")),
entry("DimGrey", Color.decode("0x696969")),
entry("SlateBlue", Color.decode("0x6A5ACD")),
entry("OliveDrab", Color.decode("0x6B8E23")),
entry("SlateGray", Color.decode("0x708090")),
entry("SlateGrey", Color.decode("0x708090")),
entry("LightSlateGray", Color.decode("0x778899")),
entry("LightSlateGrey", Color.decode("0x778899")),
entry("MediumSlateBlue", Color.decode("0x7B68EE")),
entry("LawnGreen", Color.decode("0x7CFC00")),
entry("Chartreuse", Color.decode("0x7FFF00")),
entry("Aquamarine", Color.decode("0x7FFFD4")),
entry("Maroon", Color.decode("0x800000")),
entry("Purple", Color.decode("0x800080")),
entry("Olive", Color.decode("0x808000")),
entry("Gray", Color.decode("0x808080")),
entry("Grey", Color.decode("0x808080")),
entry("SkyBlue", Color.decode("0x87CEEB")),
entry("LightSkyBlue", Color.decode("0x87CEFA")),
entry("BlueViolet", Color.decode("0x8A2BE2")),
entry("DarkRed", Color.decode("0x8B0000")),
entry("DarkMagenta", Color.decode("0x8B008B")),
entry("SaddleBrown", Color.decode("0x8B4513")),
entry("DarkSeaGreen", Color.decode("0x8FBC8F")),
entry("LightGreen", Color.decode("0x90EE90")),
entry("MediumPurple", Color.decode("0x9370DB")),
entry("DarkViolet", Color.decode("0x9400D3")),
entry("PaleGreen", Color.decode("0x98FB98")),
entry("DarkOrchid", Color.decode("0x9932CC")),
entry("YellowGreen", Color.decode("0x9ACD32")),
entry("Sienna", Color.decode("0xA0522D")),
entry("Brown", Color.decode("0xA52A2A")),
entry("DarkGray", Color.decode("0xA9A9A9")),
entry("DarkGrey", Color.decode("0xA9A9A9")),
entry("LightBlue", Color.decode("0xADD8E6")),
entry("GreenYellow", Color.decode("0xADFF2F")),
entry("PaleTurquoise", Color.decode("0xAFEEEE")),
entry("LightSteelBlue", Color.decode("0xB0C4DE")),
entry("PowderBlue", Color.decode("0xB0E0E6")),
entry("FireBrick", Color.decode("0xB22222")),
entry("DarkGoldenRod", Color.decode("0xB8860B")),
entry("MediumOrchid", Color.decode("0xBA55D3")),
entry("RosyBrown", Color.decode("0xBC8F8F")),
entry("DarkKhaki", Color.decode("0xBDB76B")),
entry("Silver", Color.decode("0xC0C0C0")),
entry("MediumVioletRed", Color.decode("0xC71585")),
entry("IndianRed", Color.decode("0xCD5C5C")),
entry("Peru", Color.decode("0xCD853F")),
entry("Chocolate", Color.decode("0xD2691E")),
entry("Tan", Color.decode("0xD2B48C")),
entry("LightGray", Color.decode("0xD3D3D3")),
entry("LightGrey", Color.decode("0xD3D3D3")),
entry("Thistle", Color.decode("0xD8BFD8")),
entry("Orchid", Color.decode("0xDA70D6")),
entry("GoldenRod", Color.decode("0xDAA520")),
entry("PaleVioletRed", Color.decode("0xDB7093")),
entry("Crimson", Color.decode("0xDC143C")),
entry("Gainsboro", Color.decode("0xDCDCDC")),
entry("Plum", Color.decode("0xDDA0DD")),
entry("BurlyWood", Color.decode("0xDEB887")),
entry("LightCyan", Color.decode("0xE0FFFF")),
entry("Lavender", Color.decode("0xE6E6FA")),
entry("DarkSalmon", Color.decode("0xE9967A")),
entry("Violet", Color.decode("0xEE82EE")),
entry("PaleGoldenRod", Color.decode("0xEEE8AA")),
entry("LightCoral", Color.decode("0xF08080")),
entry("Khaki", Color.decode("0xF0E68C")),
entry("AliceBlue", Color.decode("0xF0F8FF")),
entry("HoneyDew", Color.decode("0xF0FFF0")),
entry("Azure", Color.decode("0xF0FFFF")),
entry("SandyBrown", Color.decode("0xF4A460")),
entry("Wheat", Color.decode("0xF5DEB3")),
entry("Beige", Color.decode("0xF5F5DC")),
entry("WhiteSmoke", Color.decode("0xF5F5F5")),
entry("MintCream", Color.decode("0xF5FFFA")),
entry("GhostWhite", Color.decode("0xF8F8FF")),
entry("Salmon", Color.decode("0xFA8072")),
entry("AntiqueWhite", Color.decode("0xFAEBD7")),
entry("Linen", Color.decode("0xFAF0E6")),
entry("LightGoldenRodYellow", Color.decode("0xFAFAD2")),
entry("OldLace", Color.decode("0xFDF5E6")),
entry("Red", Color.decode("0xFF0000")),
entry("Fuchsia", Color.decode("0xFF00FF")),
entry("Magenta", Color.decode("0xFF00FF")),
entry("DeepPink", Color.decode("0xFF1493")),
entry("OrangeRed", Color.decode("0xFF4500")),
entry("Tomato", Color.decode("0xFF6347")),
entry("HotPink", Color.decode("0xFF69B4")),
entry("Coral", Color.decode("0xFF7F50")),
entry("DarkOrange", Color.decode("0xFF8C00")),
entry("LightSalmon", Color.decode("0xFFA07A")),
entry("Orange", Color.decode("0xFFA500")),
entry("LightPink", Color.decode("0xFFB6C1")),
entry("Pink", Color.decode("0xFFC0CB")),
entry("Gold", Color.decode("0xFFD700")),
entry("PeachPuff", Color.decode("0xFFDAB9")),
entry("NavajoWhite", Color.decode("0xFFDEAD")),
entry("Moccasin", Color.decode("0xFFE4B5")),
entry("Bisque", Color.decode("0xFFE4C4")),
entry("MistyRose", Color.decode("0xFFE4E1")),
entry("BlanchedAlmond", Color.decode("0xFFEBCD")),
entry("PapayaWhip", Color.decode("0xFFEFD5")),
entry("LavenderBlush", Color.decode("0xFFF0F5")),
entry("SeaShell", Color.decode("0xFFF5EE")),
entry("Cornsilk", Color.decode("0xFFF8DC")),
entry("LemonChiffon", Color.decode("0xFFFACD")),
entry("FloralWhite", Color.decode("0xFFFAF0")),
entry("Snow", Color.decode("0xFFFAFA")),
entry("Yellow", Color.decode("0xFFFF00")),
entry("LightYellow", Color.decode("0xFFFFE0")),
entry("Ivory", Color.decode("0xFFFFF0")),
entry("White", Color.decode("0xFFFFFF"))
);
/**
* a blue that is not as dark as {@code Color.blue}
*/
private static Color blue = new Color(100, 100, 255);
/**
* these are vertex or edge types that have defined colors
* (the keys are the property values for the vertex/edge keys:
* VertexType and EdgeType)
*/
public static Map<String,Paint> VERTEX_TYPE_TO_COLOR_MAP =
Map.ofEntries(
entry("Body", blue),
entry("Entry", WEB_COLOR_MAP.get("DarkOrange")),
entry("Exit", Color.magenta),
entry("Switch", Color.cyan),
entry("Bad",Color.red),
entry("Entry-Nexus",Color.white),
entry("External",Color.green),
entry("Folder",WEB_COLOR_MAP.get("DarkOrange")),
entry("Fragment",WEB_COLOR_MAP.get("Purple")),
entry("Data",Color.pink)
);
/**
* these are vertex or edge types that have defined colors
* (the keys are the property values for the vertex/edge keys:
* VertexType and EdgeType)
*/
public static Map<String,Paint> EDGE_TYPE_TO_COLOR_MAP =
Map.ofEntries(
entry("Entry", Color.gray), // white??
entry("Fall-Through", Color.blue),
entry("Conditional-Call", WEB_COLOR_MAP.get("DarkOrange")),
entry("Unconditional-Call", WEB_COLOR_MAP.get("DarkOrange")),
entry("Computed",Color.cyan),
entry("Indirection",Color.pink),
entry("Unconditional-Jump", Color.green),
entry("Conditional-Jump", Color.yellow),
entry("Terminator", WEB_COLOR_MAP.get("Purple")),
entry("Conditional-Return", WEB_COLOR_MAP.get("Purple"))
);
/**
* Determine a color for the given {@link Attributed} object.
* <P>
* The attributed object can be an vertex or an edge. This method examines the attributes
* and tries to find an attribute that has a color mapping. Otherwise it returns a default
* color
* @param attributed the vertex or edge for which to determine a color
* @return the color to paint the given Attributed
*/
public static Paint getColor(Attributed attributed) {
Map<String, String> map = attributed.getAttributeMap();
// if there is a 'VertexType' attribute key, use its value to choose a predefined color
if (map.containsKey("VertexType")) {
String typeValue = map.get("VertexType");
return VERTEX_TYPE_TO_COLOR_MAP.getOrDefault(typeValue, Color.blue);
}
// if there is an 'EdgeType' attribute key, use its value to choose a predefined color
if (map.containsKey("EdgeType")) {
String typeValue = map.get("EdgeType");
return EDGE_TYPE_TO_COLOR_MAP.getOrDefault(typeValue, Color.green);
}
// if there is a 'Color' attribute key, use its value (either a color name or an RGB hex string)
// to choose a color
if (map.containsKey("Color")) {
String colorName = map.get("Color");
if (WEB_COLOR_MAP.containsKey(colorName)) {
return WEB_COLOR_MAP.get(colorName);
}
// if the value matches an RGB hex string, turn that into a color
Color c = getHexColor(colorName);
if (c != null) {
return c;
}
}
// default value when nothing else matches
return Color.green;
}
public static Color getHexColor(String hexString) {
Matcher matcher = HEX_PATTERN.matcher(hexString);
if (matcher.matches()) {
return Color.decode(hexString);
}
return null;
}
}

View File

@ -0,0 +1,35 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.graph.visualization;
import javax.swing.Icon;
import resources.Icons;
/**
* Holds the icons used by the DefaultProgramGraph toolbars and menus.
*/
final class DefaultDisplayGraphIcons {
private DefaultDisplayGraphIcons() {
}
public static final Icon SATELLITE_VIEW_ICON = Icons.get("images/sat2.png");
public static final Icon VIEW_MAGNIFIER_ICON = Icons.get("images/magnifier.png");
public static final Icon GRAPH_FILTERS_ICON = Icons.get("images/view-filter.png");
public static final Icon PROGRAM_GRAPH_ICON = Icons.get("images/redspheregraph.png");
public static final Icon LAYOUT_ALGORITHM_ICON = Icons.get("images/katomic.png");
}

View File

@ -0,0 +1,750 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.graph.visualization;
import static org.jungrapht.visualization.renderers.BiModalRenderer.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.Point2D;
import java.util.*;
import java.util.List;
import java.util.logging.Logger;
import java.util.stream.Collectors;
import javax.swing.*;
import org.jgrapht.Graph;
import org.jungrapht.visualization.*;
import org.jungrapht.visualization.annotations.MultiSelectedVertexPaintable;
import org.jungrapht.visualization.annotations.SingleSelectedVertexPaintable;
import org.jungrapht.visualization.control.*;
import org.jungrapht.visualization.decorators.*;
import org.jungrapht.visualization.layout.algorithms.LayoutAlgorithm;
import org.jungrapht.visualization.layout.model.LayoutModel;
import org.jungrapht.visualization.layout.model.Point;
import org.jungrapht.visualization.renderers.*;
import org.jungrapht.visualization.renderers.Renderer;
import org.jungrapht.visualization.selection.MutableSelectedState;
import org.jungrapht.visualization.transform.*;
import org.jungrapht.visualization.transform.shape.MagnifyImageLensSupport;
import org.jungrapht.visualization.transform.shape.MagnifyShapeTransformer;
import docking.ActionContext;
import docking.action.builder.*;
import docking.menu.ActionState;
import ghidra.framework.plugintool.Plugin;
import ghidra.framework.plugintool.PluginTool;
import ghidra.graph.AttributeFilters;
import ghidra.graph.job.GraphJobRunner;
import ghidra.service.graph.*;
import ghidra.util.Msg;
import ghidra.util.Swing;
import ghidra.util.task.TaskMonitor;
import resources.Icons;
import util.CollectionUtils;
/**
* Delegates to a {@link VisualizationViewer} to draw a graph visualization
*/
public class DefaultGraphDisplay implements GraphDisplay {
public static final String FAVORED_EDGE = "Fall-Through";
private static final int MAX_NODES = 10000;
public static final Dimension PREFERRED_VIEW_SIZE = new Dimension(1000, 1000);
public static final Dimension PREFERRED_LAYOUT_SIZE = new Dimension(3000, 3000);
Logger log = Logger.getLogger(DefaultGraphDisplay.class.getName());
private GraphDisplayListener listener = new DummyGraphDisplayListener();
private String description;
/**
* the {@link Graph} to visualize
*/
private AttributedGraph graph;
/**
* a unique id for this {@link GraphDisplay}
*/
private final int displayId;
/**
* the delegate viewer to display the ProgramGraph
*/
private VisualizationViewer<AttributedVertex, AttributedEdge> viewer;
/**
* the {@link PluginTool}
*/
private PluginTool pluginTool;
/**
* the {@link Plugin} that manages this {@link GraphDisplay}
*/
private String pluginName = "ProgramGraphPlugin";
/**
* provides the component for the {@link GraphDisplay}
*/
private DefaultGraphDisplayComponentProvider componentProvider;
/**
* whether to scroll the visualization in order to center the selected vertex
* (or the centroid of the selected vertices)
*/
private boolean enableScrollToSelection = false;
/**
* allows selection of various {@link LayoutAlgorithm} ('arrangements')
*/
private LayoutTransitionManager layoutTransitionManager;
/**
* manages highlight painting of a single selected vertex
*/
private SingleSelectedVertexPaintable<AttributedVertex, AttributedEdge> singleSelectedVertexPaintable;
private MultiSelectedVertexPaintable<AttributedVertex, AttributedEdge> multiSelectedVertexPaintable;
private DefaultGraphDisplayProvider graphDisplayProvider;
/**
* Create the initial display, the graph-less visualization viewer, and its controls
* @param displayProvider provides a {@link PluginTool} for Docking features
* @param id the unique display id
*/
DefaultGraphDisplay(DefaultGraphDisplayProvider displayProvider, int id) {
this.graphDisplayProvider = displayProvider;
this.displayId = id;
this.pluginTool = graphDisplayProvider.getPluginTool();
this.viewer = createViewer();
buildHighlighers();
componentProvider = new DefaultGraphDisplayComponentProvider(this, pluginTool);
componentProvider.addToTool();
satelliteViewer = createSatelliteViewer(viewer);
layoutTransitionManager =
new LayoutTransitionManager(viewer, this::isRoot, this::isFavoredEdge);
viewer.getComponent().addComponentListener(new ComponentAdapter() {
@Override
public void componentResized(ComponentEvent e) {
super.componentResized(e);
Component vv = e.getComponent();
Dimension vvd = vv.getSize();
Dimension sd = satelliteViewer.getSize();
java.awt.Point p = new java.awt.Point(vvd.width - sd.width, vvd.height - sd.height);
satelliteViewer.getComponent().setBounds(p.x, p.y, sd.width, sd.height);
}
});
createActions();
connectSelectionStateListeners();
}
JComponent getComponent() {
JComponent component = viewer.getComponent();
component.setFocusable(true);
return component;
}
int getId() {
return displayId;
}
private LensSupport<LensGraphMouse> createManifiers() {
Lens lens = Lens.builder().lensShape(Lens.Shape.RECTANGLE).magnification(3.f).build();
lens.setMagnification(2.f);
LensMagnificationGraphMousePlugin magnificationPlugin =
new LensMagnificationGraphMousePlugin(1.f, 60.f, .2f);
MutableTransformer transformer = viewer.getRenderContext()
.getMultiLayerTransformer()
.getTransformer(MultiLayerTransformer.Layer.VIEW);
MagnifyShapeTransformer shapeTransformer = MagnifyShapeTransformer.builder(lens)
// this lens' delegate is the viewer's VIEW layer
.delegate(transformer)
.build();
return MagnifyImageLensSupport.builder(viewer)
.lensTransformer(shapeTransformer)
.lensGraphMouse(new DefaultLensGraphMouse<>(magnificationPlugin))
.build();
}
private void buildHighlighers() {
// for highlighting of multiple selected vertices
this.multiSelectedVertexPaintable = MultiSelectedVertexPaintable.builder(viewer)
.selectionStrokeMin(4.f)
.selectionPaint(Color.red)
.useBounds(true)
.build();
// for highlighting of single 'located' vertices
this.singleSelectedVertexPaintable = SingleSelectedVertexPaintable.builder(viewer)
.selectionStrokeMin(4.f)
.selectionPaint(Color.red)
.build();
// this draws the selection highlights
viewer.addPostRenderPaintable(multiSelectedVertexPaintable);
// this draws the location arrow
viewer.addPostRenderPaintable(singleSelectedVertexPaintable);
}
private void createActions() {
// create a toggle for 'scroll to selected vertex'
new ToggleActionBuilder("Scroll To Selection", pluginName)
.toolBarIcon(Icons.NAVIGATE_ON_INCOMING_EVENT_ICON)
.description("Scroll to Selection")
.selected(false)
.onAction(context -> enableScrollToSelection =
((AbstractButton) context.getSourceObject()).isSelected())
.buildAndInstallLocal(componentProvider);
// create an icon button to display the satellite view
new ToggleActionBuilder("SatelliteView", pluginName).description("Show Satellite View")
.toolBarIcon(DefaultDisplayGraphIcons.SATELLITE_VIEW_ICON)
.onAction(this::toggleSatellite)
.buildAndInstallLocal(componentProvider);
// create an icon button to reset the view transformations to identity (scaled to layout)
new ActionBuilder("Reset View", pluginName).description("Reset all view transforms")
.toolBarIcon(Icons.REFRESH_ICON)
.onAction(context -> {
viewer.reset();
viewer.scaleToLayout();
})
.buildAndInstallLocal(componentProvider);
// create a button to show the view magnify lens
LensSupport<LensGraphMouse> magnifyViewSupport = createManifiers();
@SuppressWarnings("unchecked")
LensSupport<LensGraphMouse>[] lenses = new LensSupport[] { magnifyViewSupport };
new ActionBuilder("View Magnifier", pluginName).description("Show View Magnifier")
.toolBarIcon(DefaultDisplayGraphIcons.VIEW_MAGNIFIER_ICON)
.onAction(context -> {
Arrays.stream(lenses).forEach(LensSupport::deactivate);
magnifyViewSupport.activate();
})
.buildAndInstallLocal(componentProvider);
new ActionBuilder("Show Filters", pluginName).description("Show Graph Filters")
.toolBarIcon(Icons.CONFIGURE_FILTER_ICON)
.onAction(context -> showFilterDialog())
.buildAndInstallLocal(componentProvider);
new MultiStateActionBuilder<String>("Arrangement", pluginName)
.description("Select Layout Arrangement")
.toolBarIcon(DefaultDisplayGraphIcons.LAYOUT_ALGORITHM_ICON)
.onActionStateChanged((s, t) -> layoutChanged(s.getName()))
.addStates(getLayoutActionStates())
.buildAndInstallLocal(componentProvider);
}
private List<ActionState<String>> getLayoutActionStates() {
String[] names = layoutTransitionManager.getLayoutNames();
List<ActionState<String>> actionStates = new ArrayList<>();
for (String layoutName : names) {
actionStates.add(new ActionState<String>(layoutName,
DefaultDisplayGraphIcons.LAYOUT_ALGORITHM_ICON, layoutName));
}
return actionStates;
}
private void layoutChanged(String layoutName) {
if (layoutTransitionManager != null) {
layoutTransitionManager.setLayout(layoutName);
}
}
private void showFilterDialog() {
if (filterDialog == null) {
if (vertexFilters == null) {
Msg.showWarn(this, null, "No Graph", "Can't set filters with no graph present!");
return;
}
filterDialog = new FilterDialog(vertexFilters.getButtons(), edgeFilters.getButtons());
}
componentProvider.getTool().showDialog(filterDialog);
}
private void toggleSatellite(ActionContext context) {
if (((AbstractButton) context.getSourceObject()).isSelected()) {
viewer.getComponent().add(satelliteViewer.getComponent());
}
else {
viewer.getComponent().remove(satelliteViewer.getComponent());
}
viewer.repaint();
}
/**
* create a SatelliteViewer for the Visualization
* @param parentViewer the main visualization 'parent' of the satellite view
* @return a new SatelliteVisualizationViewer
*/
private SatelliteVisualizationViewer<AttributedVertex, AttributedEdge> createSatelliteViewer(
VisualizationViewer<AttributedVertex, AttributedEdge> parentViewer) {
final SatelliteVisualizationViewer<AttributedVertex, AttributedEdge> satelliteViewer =
SatelliteVisualizationViewer.builder(parentViewer)
.viewSize(new Dimension(250, 250))
.build();
satelliteViewer.setGraphMouse(new DefaultSatelliteGraphMouse<>());
satelliteViewer.getRenderContext().setEdgeDrawPaintFunction(Colors::getColor);
satelliteViewer.getRenderContext()
.setEdgeStrokeFunction(ProgramGraphFunctions::getEdgeStroke);
satelliteViewer.getRenderContext().setVertexFillPaintFunction(Colors::getColor);
satelliteViewer.scaleToLayout();
satelliteViewer.getRenderContext().setVertexLabelFunction(n -> null);
satelliteViewer.getComponent().setBorder(BorderFactory.createEtchedBorder());
return satelliteViewer;
}
@Override
public void close() {
graphDisplayProvider.remove(this);
if (listener != null) {
listener.graphClosed();
}
listener = null;
}
@Override
public void setGraphDisplayListener(GraphDisplayListener listener) {
if (this.listener != null) {
this.listener.graphClosed();
}
this.listener = listener;
}
/**
* connect the selection state to to the visualization
*/
private void connectSelectionStateListeners() {
viewer.getSelectedVertexState().addItemListener(e -> Swing.runLater(() -> {
if (e.getStateChange() == ItemEvent.SELECTED) {
Collection<AttributedVertex> selectedVertices = getVertices(e.getItem());
List<String> selectedVertexIds = toVertexIds(selectedVertices);
notifySelectionChanged(selectedVertexIds);
multiSelectedVertexPaintable.setSelectedVertices(selectedVertices);
AttributedVertex vertex = CollectionUtils.any(selectedVertices);
if (vertex != null) {
singleSelectedVertexPaintable.setSelectedVertex(vertex);
notifyLocationChanged(vertex.getId());
}
}
else if (e.getStateChange() == ItemEvent.DESELECTED) {
singleSelectedVertexPaintable.setSelectedVertex(null);
multiSelectedVertexPaintable.setSelectedVertices(Collections.emptySet());
notifySelectionChanged(Collections.emptyList());
}
viewer.repaint();
}));
}
private List<String> toVertexIds(Collection<AttributedVertex> selectedVertices) {
return selectedVertices.stream().map(v -> v.getId()).collect(Collectors.toList());
}
@SuppressWarnings("unchecked")
private Collection<AttributedVertex> getVertices(Object item) {
if (item instanceof Collection) {
return (Collection<AttributedVertex>) item;
}
else if (item instanceof AttributedVertex) {
return List.of((AttributedVertex) item);
}
return Collections.emptyList();
}
private void notifySelectionChanged(List<String> vertexIds) {
Swing.runLater(() -> listener.selectionChanged(vertexIds));
}
private void notifyLocationChanged(String vertexId) {
Swing.runLater(() -> listener.locationChanged(vertexId));
}
/**
* Pass the supplied list of vertex id's to the underlying visualization to cause them to be 'selected' visually
* @param vertexIdList the vertex ids to select
*/
@Override
public void selectVertices(List<String> vertexIdList) {
MutableSelectedState<AttributedVertex> nodeSelectedState = viewer.getSelectedVertexState();
Set<AttributedVertex> selected = getVertices(vertexIdList);
if (vertexIdList.isEmpty()) {
nodeSelectedState.clear();
}
else if (!Arrays.asList(nodeSelectedState.getSelectedObjects()).containsAll(selected)) {
nodeSelectedState.clear();
nodeSelectedState.select(selected, false);
multiSelectedVertexPaintable.setSelectedVertices(selected);
scrollToSelected(selected);
}
viewer.repaint();
}
private Set<AttributedVertex> getVertices(Collection<String> vertexIds) {
Set<String> vertexSet = new HashSet<>(vertexIds);
return graph.vertexSet()
.stream()
.filter(v -> vertexSet.contains(v.getId()))
.collect(Collectors.toSet());
}
@Override
public void setLocation(String vertexID) {
Optional<AttributedVertex> selected =
graph.vertexSet().stream().filter(v -> vertexID.equals(v.getId())).findFirst();
log.fine("picking address:" + vertexID + " returned " + selected);
singleSelectedVertexPaintable.setSelectedVertex(selected.orElse(null));
viewer.repaint();
selected.ifPresent(this::scrollToSelected);
viewer.repaint();
}
/**
* set the {@link AttributedGraph} for visualization
* @param attributedGraph the {@link AttributedGraph} to visualize
*/
private void doSetGraphData(AttributedGraph attributedGraph) {
graph = attributedGraph;
layoutTransitionManager.setGraph(graph);
configureViewerPreferredSize();
Swing.runNow(() -> viewer.getVisualizationModel().setGraph(graph));
configureFilters();
LayoutAlgorithm<AttributedVertex> initialLayoutAlgorithm =
layoutTransitionManager.getInitialLayoutAlgorithm(graph);
viewer.getVisualizationModel().setLayoutAlgorithm(initialLayoutAlgorithm);
viewer.scaleToLayout();
componentProvider.setVisible(true);
}
/**
* Determines if a vertex is a root. For our purpose, a root either has no incomming edges
* or has at least one outgoing "favored" edge and no incomming "favored" edge
* @param vertex the vertex to test if it is a root
* @return true if the vertex is a root
*/
private boolean isRoot(AttributedVertex vertex) {
Set<AttributedEdge> incomingEdgesOf = graph.incomingEdgesOf(vertex);
if (incomingEdgesOf.isEmpty()) {
return true;
}
Set<AttributedEdge> outgoingEdgesOf = graph.outgoingEdgesOf(vertex);
return outgoingEdgesOf.stream().anyMatch(this::isFavoredEdge) &&
incomingEdgesOf.stream().noneMatch(this::isFavoredEdge);
}
private void configureFilters() {
// close and rebuild filter dialog if exists
if (filterDialog != null) {
filterDialog.close();
filterDialog = null;
}
Set<AttributedVertex> vertices = graph.vertexSet();
Set<AttributedEdge> edges = graph.edgeSet();
vertexFilters = AttributeFilters.builder()
.exclude(Set.of("Address", "Code", "Name"))
.elements(vertices)
.maxFactor(.05)
.buttonSupplier(JRadioButton::new)
.paintFunction(v -> Colors.VERTEX_TYPE_TO_COLOR_MAP.getOrDefault(v, Color.blue))
.build();
vertexFilters.addItemListener(item -> {
@SuppressWarnings("unchecked")
Set<String> selected = (Set<String>) item.getItem();
viewer.getRenderContext()
.setVertexIncludePredicate(
v -> v.getAttributeMap().values().stream().noneMatch(selected::contains));
viewer.repaint();
});
edgeFilters = AttributeFilters.builder()
.exclude(Set.of("*ToKey", "*FromKey", "Address", "Name"))
.elements(edges)
.maxFactor(.01)
.buttonSupplier(JRadioButton::new)
.paintFunction(e -> Colors.EDGE_TYPE_TO_COLOR_MAP.getOrDefault(e, Color.green))
.build();
edgeFilters.addItemListener(item -> {
@SuppressWarnings("unchecked")
Set<String> selected = (Set<String>) item.getItem();
viewer.getRenderContext()
.setEdgeIncludePredicate(
e -> e.getAttributeMap().values().stream().noneMatch(selected::contains));
viewer.repaint();
});
}
private void configureViewerPreferredSize() {
int vertexCount = graph.vertexSet().size();
// attempt to set a reasonable size for the layout based on the number of vertices
Dimension viewSize = viewer.getPreferredSize();
if (vertexCount < 100) {
viewer.getVisualizationModel()
.getLayoutModel()
.setPreferredSize(viewSize.width, viewSize.height);
}
else {
int newSize = viewSize.width + 5 * (vertexCount - 100);
viewer.getVisualizationModel().getLayoutModel().setPreferredSize(newSize, newSize);
}
}
@Override
public void defineVertexAttribute(String attributeName) {
log.fine("defineVertexAttribute " + attributeName + " is not implemented");
}
@Override
public void defineEdgeAttribute(String attributeName) {
log.fine("defineEdgeAttribute " + attributeName + " is not implemented");
}
/*
* @see ghidra.program.model.graph.GraphDisplay#setVertexLabel(java.lang.String, int, int, boolean, int)
*/
@Override
public void setVertexLabel(String attributeName, int alignment, int size, boolean monospace,
int maxLines) {
log.fine("setVertexLabel " + attributeName);
// this would have to set the label function, the label font function
}
@Override
public void setGraph(AttributedGraph graph, String description, boolean append,
TaskMonitor monitor) {
iconCache.clear();
if (append && Objects.equals(description, this.description) && this.graph != null) {
graph = mergeGraphs(graph, this.graph);
}
this.description = description;
int count = graph.getVertexCount();
if (count > MAX_NODES) {
Msg.showWarn(this, null, "Graph Not Rendered - Too many nodes!",
"Exceeded limit of " + MAX_NODES + " nodes.\n\n Graph contained " + count +
" nodes!");
graph = new AttributedGraph();
graph.addVertex("1", "Graph Aborted");
}
doSetGraphData(graph);
}
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<String, String> attributeMap = edge.getAttributeMap();
for (String key : attributeMap.keySet()) {
newEdge.setAttribute(key, edge.getAttribute(key));
}
}
return newGraph;
}
public void centerAndScale() {
viewer.reset();
viewer.scaleToLayout();
}
/**
* remove all vertices and edges from the {@link Graph}
*/
@Override
public void clear() {
this.graph.removeAllEdges(new HashSet<>(graph.edgeSet()));
this.graph.removeAllVertices(new HashSet<>(graph.vertexSet()));
}
/**
* scroll the visualization to center the passed vertices
* @param vertices the vertices to center
*/
void scrollToSelected(Collection<AttributedVertex> vertices) {
if (!enableScrollToSelection) {
return;
}
Point2D newCenter = getPointToCenter(vertices);
Point2D existingCenter = viewer.getRenderContext()
.getMultiLayerTransformer()
.inverseTransform(viewer.getCenter());
jobRunner.schedule(new CenterAnimation(viewer, existingCenter, newCenter));
}
/**w
* scroll the visualization to center the passed vertex
* @param vertex the vertex to center
*/
void scrollToSelected(AttributedVertex vertex) {
List<AttributedVertex> vertices =
vertex == null ? Collections.emptyList() : List.of(vertex);
scrollToSelected(vertices);
}
private Point2D getPointToCenter(Collection<AttributedVertex> vertices) {
LayoutModel<AttributedVertex> layoutModel = viewer.getVisualizationModel().getLayoutModel();
Collection<Point> points = vertices.stream().map(layoutModel).collect(Collectors.toList());
if (points.size() > 0) {
// center the selected vertices
Point p = Point.centroidOf(points);
return new Point2D.Double(p.x, p.y);
}
// they did not pick a vertex to center, so
// just center the graph
Point2D center = viewer.getCenter();
Point p = Point.of(center.getX(), center.getY());
return new Point2D.Double(p.x, p.y);
}
private GraphJobRunner jobRunner = new GraphJobRunner();
private SatelliteVisualizationViewer<AttributedVertex, AttributedEdge> satelliteViewer;
private AttributeFilters edgeFilters;
private AttributeFilters vertexFilters;
private FilterDialog filterDialog;
private GhidraIconCache iconCache;
@Override
public void updateVertexName(String id, String newName) {
// unsupported
}
@Override
public String getGraphDescription() {
return description;
}
private boolean isFavoredEdge(AttributedEdge edge) {
if (edge.getAttributeMap().containsKey("EdgeType")) {
return edge.getAttributeMap().getOrDefault("EdgeType", "NOTEQUAL").equals(FAVORED_EDGE);
}
return true;
}
public VisualizationViewer<AttributedVertex, AttributedEdge> createViewer() {
final VisualizationViewer<AttributedVertex, AttributedEdge> vv =
VisualizationViewer.<AttributedVertex, AttributedEdge> builder()
.viewSize(PREFERRED_VIEW_SIZE)
.layoutSize(PREFERRED_LAYOUT_SIZE)
.build();
// Add a component listener to scale and center the graph after the component
// has been initially sized. Remove the listener after the first time so that any
// subsequent resizing does not affect the graph.
vv.getComponent().addComponentListener(new ComponentAdapter() {
@Override
public void componentResized(ComponentEvent e) {
vv.getComponent().removeComponentListener(this);
Swing.runLater(() -> {
vv.reset();
vv.scaleToLayout();
});
}
});
vv.setVertexToolTipFunction(AttributedVertex::getHtmlString);
vv.setEdgeToolTipFunction(AttributedEdge::getHtmlString);
RenderContext<AttributedVertex, AttributedEdge> renderContext = vv.getRenderContext();
iconCache = new GhidraIconCache();
// set up the shape and color functions
IconShapeFunction<AttributedVertex> nodeImageShapeFunction =
new IconShapeFunction<>(new EllipseShapeFunction<>());
vv.getRenderContext().setVertexIconFunction(iconCache::get);
// cause the vertices to be drawn with custom icons/shapes
nodeImageShapeFunction.setIconFunction(iconCache::get);
renderContext.setVertexShapeFunction(nodeImageShapeFunction);
renderContext.setVertexIconFunction(iconCache::get);
// selected edges will be drawn with a wider stroke
renderContext.setEdgeStrokeFunction(
e -> renderContext.getSelectedEdgeState().isSelected(e) ? new BasicStroke(20.f)
: ProgramGraphFunctions.getEdgeStroke(e));
// selected edges will be drawn in red (instead of default)
renderContext.setEdgeDrawPaintFunction(
e -> renderContext.getSelectedEdgeState().isSelected(e) ? Color.red
: Colors.getColor(e));
vv.setToolTipText("");
// assign the shapes to the modal renderer
ModalRenderer<AttributedVertex, AttributedEdge> modalRenderer = vv.getRenderer();
// the modal renderer optimizes rendering for large graphs by removing detail
Renderer.Vertex<AttributedVertex, AttributedEdge> vertexRenderer =
modalRenderer.getVertexRenderer(LIGHTWEIGHT);
// cause the lightweight (optimized) renderer to use the vertex shapes instead
// of using default shapes.
if (vertexRenderer instanceof LightweightVertexRenderer) {
LightweightVertexRenderer<AttributedVertex, AttributedEdge> lightweightVertexRenderer =
(LightweightVertexRenderer<AttributedVertex, AttributedEdge>) vertexRenderer;
lightweightVertexRenderer.setVertexShapeFunction(ProgramGraphFunctions::getVertexShape);
}
renderContext.setVertexLabelRenderer(new JLabelVertexLabelRenderer(Color.black));
renderContext.setVertexDrawPaintFunction(Colors::getColor);
renderContext.setVertexFillPaintFunction(Colors::getColor);
renderContext.setVertexStrokeFunction(n -> new BasicStroke(3.0f));
renderContext.setEdgeShapeFunction(EdgeShape.line());
DefaultGraphMouse<AttributedVertex, AttributedEdge> graphMouse = new DefaultGraphMouse<>();
vv.setGraphMouse(graphMouse);
vv.getComponent().requestFocus();
vv.setBackground(Color.WHITE);
return vv;
}
/**
* a way to sort attributed vertices or edges based on attribute values
*/
}

View File

@ -0,0 +1,51 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.graph.visualization;
import javax.swing.JComponent;
import ghidra.framework.plugintool.ComponentProviderAdapter;
import ghidra.framework.plugintool.PluginTool;
import ghidra.util.HelpLocation;
/**
* provided a JComponent for the ProgramGraph visualization
*/
public class DefaultGraphDisplayComponentProvider extends ComponentProviderAdapter {
static final String WINDOW_GROUP = "ProgramGraph";
private DefaultGraphDisplay display;
DefaultGraphDisplayComponentProvider(DefaultGraphDisplay display, PluginTool pluginTool) {
super(pluginTool, "Graph: " + display.getId(), "DefaultGraphDisplay");
this.display = display;
setHelpLocation(new HelpLocation("GraphServices", "Default_Graph_Display"));
setIcon(DefaultDisplayGraphIcons.PROGRAM_GRAPH_ICON);
setTransient();
setWindowGroup(WINDOW_GROUP);
}
@Override
public JComponent getComponent() {
return display.getComponent();
}
@Override
public void closeComponent() {
super.closeComponent();
display.close();
}
}

View File

@ -0,0 +1,96 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.graph.visualization;
import java.util.HashSet;
import java.util.Set;
import ghidra.framework.options.Options;
import ghidra.framework.plugintool.PluginTool;
import ghidra.service.graph.GraphDisplay;
import ghidra.service.graph.GraphDisplayProvider;
import ghidra.util.HelpLocation;
import ghidra.util.Swing;
import ghidra.util.task.TaskMonitor;
public class DefaultGraphDisplayProvider implements GraphDisplayProvider {
private Set<DefaultGraphDisplay> displays = new HashSet<>();
private PluginTool pluginTool;
private Options options;
private int displayCounter;
@Override
public String getName() {
return "Default Graph Display";
}
public PluginTool getPluginTool() {
return pluginTool;
}
public Options getOptions() {
return options;
}
@Override
public GraphDisplay getGraphDisplay(boolean reuseGraph,
TaskMonitor monitor) {
if (reuseGraph && !displays.isEmpty()) {
return getExistingGraph();
}
DefaultGraphDisplay display =
Swing.runNow(() -> new DefaultGraphDisplay(this, displayCounter++));
displays.add(display);
return display;
}
@Override
public void initialize(PluginTool tool, Options graphOptions) {
this.pluginTool = tool;
this.options = graphOptions;
}
private GraphDisplay getExistingGraph() {
DefaultGraphDisplay display = displays.iterator().next();
return display;
}
@Override
public void optionsChanged(Options graphOptions) {
// no supported options
}
@Override
public void dispose() {
// first copy to new set to avoid concurrent modification exception
HashSet<DefaultGraphDisplay> set = new HashSet<>(displays);
for (DefaultGraphDisplay display : set) {
display.close();
}
}
@Override
public HelpLocation getHelpLocation() {
return new HelpLocation("GraphServices", "Default_Graph_Display");
}
public void remove(DefaultGraphDisplay defaultGraphDisplay) {
displays.remove(defaultGraphDisplay);
}
}

View File

@ -0,0 +1,47 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.graph.visualization;
import java.util.*;
import java.util.stream.Collectors;
import ghidra.service.graph.AttributedEdge;
import ghidra.service.graph.AttributedGraph;
public class EdgeComparator implements Comparator<AttributedEdge> {
private Set<AttributedEdge> prioritized;
public EdgeComparator(AttributedGraph graph, String attributeName, String value) {
prioritized = graph.edgeSet()
.stream()
.filter(e -> Objects.equals(e.getAttribute(attributeName), value))
.collect(Collectors.toSet());
}
@Override
public int compare(AttributedEdge edgeOne, AttributedEdge edgeTwo) {
boolean edgeOnePriority = prioritized.contains(edgeOne);
boolean edgeTwoPriority = prioritized.contains(edgeTwo);
if (edgeOnePriority && !edgeTwoPriority) {
return -1;
}
else if (!edgeOnePriority && edgeTwoPriority) {
return 1;
}
return 0;
}
}

View File

@ -0,0 +1,96 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.graph.visualization;
import java.util.List;
import javax.swing.*;
import docking.DialogComponentProvider;
import ghidra.util.layout.VerticalLayout;
/**
* Extends DialogComponentProvider to make a dialog with buttons to activate/deactivate
* filters on graph vertices and edges
*/
public class FilterDialog extends DialogComponentProvider {
/**
* A title for the vertex filter section of the dialog
*/
private static final String VERTEX_TITLE = "Vertex Filters";
/**
* A title for the edge filter section of the dialog
*/
private static final String EDGE_TITLE = "Edge Filters";
/**
* A {@code List} (possibly empty) of filter buttons for vertices
*/
private List<? extends AbstractButton> vertexButtons;
/**
* A {@code List} (possibly empty) of filter buttons for edges
*/
List<? extends AbstractButton> edgeButtons;
/**
* @param vertexButtons a {@code List} of {@code AbstractButton}s to filter vertices
* @param edgeButtons a {@code List} of {@code AbstractButton}s to filter edges
*/
public FilterDialog(List<? extends AbstractButton> vertexButtons,
List<? extends AbstractButton> edgeButtons) {
super("Filters", false);
this.vertexButtons = vertexButtons;
this.edgeButtons = edgeButtons;
super.addWorkPanel(createPanel());
setRememberSize(false);
addDismissButton();
setDefaultButton(dismissButton);
}
/**
* Create a layout-formatted JComponent holding 2 vertical lists
* of buttons, one list for vertex filter buttons and one list for
* edge filter buttons. Each list has a border and title.
* @return a formatted JComponent (container)
*/
JComponent createPanel() {
JPanel panel = new JPanel(new VerticalLayout(10));
if (!vertexButtons.isEmpty()) {
JPanel vertexPanel = new JPanel(new VerticalLayout(5));
vertexPanel.setBorder(BorderFactory.createTitledBorder(VERTEX_TITLE));
vertexButtons.forEach(vertexPanel::add);
panel.add(vertexPanel);
}
if (!edgeButtons.isEmpty()) {
JPanel edgePanel = new JPanel(new VerticalLayout(5));
edgePanel.setBorder(BorderFactory.createTitledBorder(EDGE_TITLE));
edgeButtons.forEach(edgePanel::add);
panel.add(edgePanel);
}
if (vertexButtons.isEmpty() && edgeButtons.isEmpty()) {
JLabel label = new JLabel("No Filters available for this graph!");
label.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
panel.add(label);
}
return panel;
}
}

View File

@ -0,0 +1,173 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.graph.visualization;
import java.awt.*;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import javax.swing.*;
import javax.swing.border.Border;
import javax.swing.border.CompoundBorder;
import ghidra.service.graph.AttributedVertex;
public class GhidraIconCache {
private static final int DEFAULT_STROKE_THICKNESS = 8;
private static final int DEFAULT_FONT_SIZE = 20;
private static final int DEFAULT_MARGIN_BORDER_SIZE = 4;
private static final float LABEL_TO_ICON_PROPORTION_WAG = 1.4f;
private static final double SQRT_2 = Math.sqrt(2.0);
private JLabel rendererLabel = new JLabel();
private Map<RenderingHints.Key, Object> renderingHints = new HashMap<>();
private int strokeThickness = DEFAULT_STROKE_THICKNESS;
private Map<AttributedVertex, Icon> map = new ConcurrentHashMap<>();
private IconShape.Function iconShapeFunction = new IconShape.Function();
Icon get(AttributedVertex vertex) {
// WARNING: very important to not use map's computeIfAbsent() method
// because the map is synchronized and the createIcon() method will
// attempt to acquire the AWT lock. That combination will cause a deadlock
// if computeIfAbsent() is used and this method is called from non-swing thread.
Icon icon = map.get(vertex);
if (icon == null) {
icon = createIcon(vertex);
map.put(vertex, icon);
}
return icon;
}
GhidraIconCache() {
renderingHints.put(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
}
private Icon createIcon(AttributedVertex vertex) {
rendererLabel.setText(ProgramGraphFunctions.getLabel(vertex));
rendererLabel.setFont(new Font("Serif", Font.BOLD, DEFAULT_FONT_SIZE));
rendererLabel.setForeground(Color.black);
rendererLabel.setBackground(Color.white);
rendererLabel.setOpaque(true);
Border lineBorder = BorderFactory.createLineBorder((Color) Colors.getColor(vertex), 2);
Border marginBorder = BorderFactory.createEmptyBorder(DEFAULT_MARGIN_BORDER_SIZE,
DEFAULT_MARGIN_BORDER_SIZE, DEFAULT_MARGIN_BORDER_SIZE, DEFAULT_MARGIN_BORDER_SIZE);
rendererLabel.setBorder(new CompoundBorder(lineBorder, marginBorder));
Dimension labelSize = rendererLabel.getPreferredSize();
rendererLabel.setSize(labelSize);
Shape shape = ProgramGraphFunctions.getVertexShape(vertex);
IconShape.Type shapeType = iconShapeFunction.apply(shape);
return createImageIcon(vertex, shapeType, rendererLabel, labelSize, shape);
}
/**
* Based on the shape and characteristics of the vertex label (color, text) create and cache an ImageIcon
* that will be used to draw the vertex
*
* @param vertex the vertex to draw (and the key for the cache)
* @param vertexShapeCategory the type of Ghidra vertex shape
* @param label the {@link JLabel} used to draw the label. Note that it will parse html for formatting.
* @param labelSize the dimensions of the JLabel after it has been parsed
* @param vertexShape the primitive {@link Shape} used to represent the vertex
*/
private Icon createImageIcon(AttributedVertex vertex, IconShape.Type vertexShapeCategory,
JLabel label, Dimension labelSize, Shape vertexShape) {
int offset = 0;
double scalex;
double scaley;
switch (vertexShapeCategory) {
// triangles have a non-zero +/- yoffset instead of centering the label
case TRIANGLE:
// scale the vertex shape
scalex = labelSize.getWidth() / vertexShape.getBounds().getWidth() * LABEL_TO_ICON_PROPORTION_WAG;
scaley = labelSize.getHeight() / vertexShape.getBounds().getHeight() * LABEL_TO_ICON_PROPORTION_WAG;
vertexShape = AffineTransform.getScaleInstance(scalex, scaley)
.createTransformedShape(vertexShape);
offset = -(int) ((vertexShape.getBounds().getHeight() - labelSize.getHeight()) / 2);
break;
case INVERTED_TRIANGLE:
scalex = labelSize.getWidth() / vertexShape.getBounds().getWidth() * LABEL_TO_ICON_PROPORTION_WAG;
scaley = labelSize.getHeight() / vertexShape.getBounds().getHeight() * LABEL_TO_ICON_PROPORTION_WAG;
vertexShape = AffineTransform.getScaleInstance(scalex, scaley)
.createTransformedShape(vertexShape);
offset = (int) ((vertexShape.getBounds().getHeight() - labelSize.getHeight()) / 2);
break;
// rectangles can fit a full-sized label
case RECTANGLE:
scalex = labelSize.getWidth() / vertexShape.getBounds().getWidth();
scaley = labelSize.getHeight() / vertexShape.getBounds().getHeight();
vertexShape = AffineTransform.getScaleInstance(scalex, scaley)
.createTransformedShape(vertexShape);
break;
// diamonds and ellipses reduce the label size to fit
case DIAMOND:
default: // ELLIPSE
scalex =
labelSize.getWidth() / vertexShape.getBounds().getWidth() * SQRT_2;
scaley = labelSize.getHeight() / vertexShape.getBounds().getHeight() * 2;
vertexShape = AffineTransform.getScaleInstance(scalex, scaley)
.createTransformedShape(vertexShape);
break;
}
Rectangle vertexBounds = vertexShape.getBounds();
BufferedImage bufferedImage = new BufferedImage(vertexBounds.width + (2 * strokeThickness),
vertexBounds.height + (2 * strokeThickness), BufferedImage.TYPE_INT_ARGB);
Graphics2D graphics = bufferedImage.createGraphics();
graphics.setRenderingHints(renderingHints);
AffineTransform graphicsTransform = graphics.getTransform();
// draw the shape, offset by 1/2 its width and the strokeThickness
AffineTransform offsetTransform =
AffineTransform.getTranslateInstance(strokeThickness + vertexBounds.width / 2.0,
strokeThickness + vertexBounds.height / 2.0);
offsetTransform.preConcatenate(graphicsTransform);
graphics.setTransform(offsetTransform);
graphics.setPaint(Color.white);
graphics.fill(vertexShape);
graphics.setPaint(Colors.getColor(vertex));
graphics.setStroke(new BasicStroke(strokeThickness));
graphics.draw(vertexShape);
// draw the JLabel, offset by 1/2 its width and the strokeThickness
int xoffset = strokeThickness + (vertexBounds.width - labelSize.width) / 2;
int yoffset = strokeThickness + (vertexBounds.height - labelSize.height) / 2;
offsetTransform = AffineTransform.getTranslateInstance(xoffset, yoffset + offset);
offsetTransform.preConcatenate(graphicsTransform);
graphics.setPaint(Color.black);
graphics.setTransform(offsetTransform);
label.paint(graphics);
graphics.setTransform(graphicsTransform); // restore the original transform
graphics.dispose();
return new ImageIcon(bufferedImage);
}
public void clear() {
map.clear();
}
}

View File

@ -0,0 +1,104 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.graph.visualization;
import java.awt.Shape;
import java.awt.geom.PathIterator;
import java.awt.geom.Point2D;
import java.util.ArrayList;
import java.util.List;
/**
* Holds the enum for shape type and the Function to categorize the archetype Shapes into
* IconShape.Types. Note that the archetype shapes are centered at the origin
*/
public class IconShape {
public enum Type {
TRIANGLE, INVERTED_TRIANGLE, RECTANGLE, DIAMOND, ELLIPSE
}
/**
* Categorize the supplied Shape into one of several simple types.
*
*/
static class Function implements java.util.function.Function<Shape, Type> {
@Override
public Type apply(Shape shape) {
List<Point2D> points = getShapePoints(shape);
if (points.size() == 3) {
if (isInvertedTriangle(points)) {
return Type.INVERTED_TRIANGLE;
} else {
return Type.TRIANGLE;
}
}
// there are 5 points because the final point is the same as the first
// and closes the shape.
if (points.size() == 5) {
if (isDiamond(points)) {
return Type.DIAMOND;
} else {
return Type.RECTANGLE;
}
}
// default to ellipse for anything with more that 4 sides
return Type.ELLIPSE;
}
/**
*
* Note that for awt drawing, the origin is at the upper left so positive y extends downwards.
* @param threePoints odd number of points bounding a {@link Shape} centered at the origin
* @return true it there are fewer points with y below 0
*/
boolean isInvertedTriangle(List<Point2D> threePoints) {
if (threePoints.size() != 3) {
throw new IllegalArgumentException("Shape from " + threePoints + " is not a triangle");
}
return threePoints.stream().filter(p -> p.getY() < 0).count() <= threePoints.size() / 2;
}
/**
*
* @param fivePoints odd number of points bounding a {@link Shape} centered at the origin
* @return true it there are 2 points with y value 0
*/
boolean isDiamond(List<Point2D> fivePoints) {
if (fivePoints.size() != 5) {
throw new IllegalArgumentException(
"Shape from " + fivePoints + " is not a quadrilateral");
}
return fivePoints.stream().filter(p -> (int) p.getY() == 0).count() == 2;
}
List<Point2D> getShapePoints(Shape shape) {
float[] seg = new float[6];
List<Point2D> points = new ArrayList<>();
for (PathIterator i = shape.getPathIterator(null, 1); !i.isDone(); i.next()) {
int ret = i.currentSegment(seg);
if (ret == PathIterator.SEG_MOVETO) {
points.add(new Point2D.Float(seg[0], seg[1]));
}
else if (ret == PathIterator.SEG_LINETO) {
points.add(new Point2D.Float(seg[0], seg[1]));
}
}
return points;
}
}
}

View File

@ -0,0 +1,77 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.graph.visualization;
import org.jungrapht.visualization.layout.algorithms.*;
import org.jungrapht.visualization.layout.algorithms.repulsion.BarnesHutFRRepulsion;
import ghidra.service.graph.AttributedEdge;
import ghidra.service.graph.AttributedVertex;
/**
* A central location to list and provide all layout algorithms, their names, and their builders
* Add or remove items here to change what layout algorithms are offered in the layout algorithm menu.
* Change the String name to affect the menu's label for a specific layout algorithm.
* This class provides LayoutAlgorithm builders instead of LayoutAlgorithms because some LayoutAlgorithms
* accumulate state information (so are used only one time).
*/
class LayoutFunction {
static final String KAMADA_KAWAI = "Force Balanced";
static final String FRUCTERMAN_REINGOLD = "Force Directed";
static final String CIRCLE_MINCROSS = "Circle";
static final String TIDIER_TREE = "Compact Hierarchical";
static final String MIN_CROSS = "Hierarchical Min Cross";
static final String MULTI_ROW_EDGE_AWARE_TREE = "Hierarchical MultiRow";
static final String EDGE_AWARE_TREE = "Hierarchical";
static final String EDGE_AWARE_RADIAL = "Radial";
public String[] getNames() {
return new String[] { KAMADA_KAWAI, FRUCTERMAN_REINGOLD, CIRCLE_MINCROSS, TIDIER_TREE, MIN_CROSS,
MULTI_ROW_EDGE_AWARE_TREE, EDGE_AWARE_TREE, EDGE_AWARE_RADIAL};
}
public LayoutAlgorithm.Builder<AttributedVertex, ?, ?> apply(
String name) {
switch(name) {
case KAMADA_KAWAI:
return KKLayoutAlgorithm.<AttributedVertex> builder().preRelaxDuration(1000);
case FRUCTERMAN_REINGOLD:
return FRLayoutAlgorithm.<AttributedVertex> builder()
.repulsionContractBuilder(BarnesHutFRRepulsion.barnesHutBuilder());
case CIRCLE_MINCROSS:
return CircleLayoutAlgorithm.<AttributedVertex> builder()
.threaded(true)
.reduceEdgeCrossing(true);
case TIDIER_TREE:
return TidierTreeLayoutAlgorithm.<AttributedVertex, AttributedEdge> edgeAwareBuilder();
case MIN_CROSS:
return HierarchicalMinCrossLayoutAlgorithm
.<AttributedVertex, AttributedEdge> edgeAwareBuilder()
.threaded(true);
case MULTI_ROW_EDGE_AWARE_TREE:
return MultiRowEdgeAwareTreeLayoutAlgorithm
.<AttributedVertex, AttributedEdge> edgeAwareBuilder();
case EDGE_AWARE_RADIAL:
return RadialEdgeAwareTreeLayoutAlgorithm
.<AttributedVertex, AttributedEdge> edgeAwareBuilder()
.verticalVertexSpacing(300);
case EDGE_AWARE_TREE:
default:
return EdgeAwareTreeLayoutAlgorithm.<AttributedVertex, AttributedEdge> edgeAwareBuilder();
}
}
}

View File

@ -0,0 +1,210 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.graph.visualization;
import static ghidra.graph.visualization.LayoutFunction.*;
import java.awt.Shape;
import java.util.*;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import org.jgrapht.Graph;
import org.jungrapht.visualization.RenderContext;
import org.jungrapht.visualization.VisualizationServer;
import org.jungrapht.visualization.layout.algorithms.*;
import org.jungrapht.visualization.layout.algorithms.util.*;
import org.jungrapht.visualization.layout.model.LayoutModel;
import org.jungrapht.visualization.util.Context;
import docking.menu.MultiActionDockingAction;
import ghidra.service.graph.*;
/**
* Manages the selection and transition from one {@link LayoutAlgorithm} to another
*/
class LayoutTransitionManager {
LayoutFunction layoutFunction = new LayoutFunction();
/**
* the {@link VisualizationServer} used to display graphs using the requested {@link LayoutAlgorithm}
*/
VisualizationServer<AttributedVertex, AttributedEdge> visualizationServer;
/**
* a {@link Predicate} to assist in determining which vertices are root vertices (for Tree layouts)
*/
Predicate<AttributedVertex> rootPredicate;
/**
* a {@link Predicate} to allow different handling of specific edge types
*/
Predicate<AttributedEdge> edgePredicate;
/**
* a {@link Comparator} to sort edges during layout graph traversal
*/
Comparator<AttributedEdge> edgeComparator;
/**
* a {@link MultiActionDockingAction} to allow the user to select a layout algorithm
*/
MultiActionDockingAction multiActionDockingAction;
/**
* the currently active {@code LayoutAlgorithm.Builder}
*/
LayoutAlgorithm.Builder<AttributedVertex, ?, ?> activeBuilder;
/**
* a {@link Function} to provide {@link Shape} (and thus bounds} for vertices
*/
Function<AttributedVertex, Shape> vertexShapeFunction;
/**
* the {@link RenderContext} used to draw the graph
*/
RenderContext<AttributedVertex, AttributedEdge> renderContext;
/**
* a LayoutAlgorithm may change the edge shape function (Sugiyama for articulated edges)
* This is a reference to the original edge shape function so that it can be returned to
* the original edge shape function for subsequent LayoutAlgorithm requests
*/
private Function<Context<Graph<AttributedVertex, AttributedEdge>, AttributedEdge>, Shape> originalEdgeShapeFunction;
/**
* Create an instance with passed parameters
* @param visualizationServer displays the graph
* @param rootPredicate selects root vertices
* @param edgePredicate differentiates edges
*/
public LayoutTransitionManager(
VisualizationServer<AttributedVertex, AttributedEdge> visualizationServer,
Predicate<AttributedVertex> rootPredicate, Predicate<AttributedEdge> edgePredicate) {
this.visualizationServer = visualizationServer;
this.rootPredicate = rootPredicate;
this.edgePredicate = edgePredicate;
this.renderContext = visualizationServer.getRenderContext();
this.vertexShapeFunction = visualizationServer.getRenderContext().getVertexShapeFunction();
this.originalEdgeShapeFunction =
visualizationServer.getRenderContext().getEdgeShapeFunction();
}
public void setGraph(AttributedGraph graph) {
edgeComparator = new EdgeComparator(graph, "EdgeType", DefaultGraphDisplay.FAVORED_EDGE);
}
/**
* set the layout in order to configure the requested {@link LayoutAlgorithm}
* @param layoutName the name of the layout algorithm to use
*/
@SuppressWarnings("unchecked")
public void setLayout(String layoutName) {
LayoutAlgorithm.Builder<AttributedVertex, ?, ?> builder = layoutFunction.apply(layoutName);
visualizationServer.getRenderContext().getMultiLayerTransformer().setToIdentity();
LayoutAlgorithm<AttributedVertex> layoutAlgorithm = builder.build();
if (layoutAlgorithm instanceof RenderContextAware) {
((RenderContextAware<AttributedVertex, AttributedEdge>) layoutAlgorithm)
.setRenderContext(visualizationServer.getRenderContext());
}
else {
visualizationServer.getRenderContext().setEdgeShapeFunction(originalEdgeShapeFunction);
}
if (layoutAlgorithm instanceof VertexShapeAware) {
((VertexShapeAware<AttributedVertex>) layoutAlgorithm)
.setVertexShapeFunction(vertexShapeFunction);
}
if (layoutAlgorithm instanceof TreeLayout) {
((TreeLayout<AttributedVertex>) layoutAlgorithm).setRootPredicate(rootPredicate);
}
if (layoutAlgorithm instanceof EdgeSorting) {
((EdgeSorting<AttributedEdge>) layoutAlgorithm).setEdgeComparator(edgeComparator);
}
if (layoutAlgorithm instanceof EdgePredicated) {
((EdgePredicated<AttributedEdge>) layoutAlgorithm).setEdgePredicate(edgePredicate);
}
if (!(layoutAlgorithm instanceof TreeLayout)) {
LayoutModel<AttributedVertex> layoutModel =
visualizationServer.getVisualizationModel().getLayoutModel();
int preferredWidth = layoutModel.getPreferredWidth();
int preferredHeight = layoutModel.getPreferredHeight();
layoutModel.setSize(preferredWidth, preferredHeight);
}
if (layoutAlgorithm instanceof RenderContextAware) {
((RenderContextAware<AttributedVertex, AttributedEdge>) layoutAlgorithm)
.setRenderContext(renderContext);
}
if (layoutAlgorithm instanceof AfterRunnable) {
((AfterRunnable) layoutAlgorithm).setAfter(visualizationServer::scaleToLayout);
}
LayoutAlgorithmTransition.apply(visualizationServer, layoutAlgorithm,
visualizationServer::scaleToLayout);
}
@SuppressWarnings("unchecked")
public LayoutAlgorithm<AttributedVertex> getInitialLayoutAlgorithm(
AttributedGraph graph) {
Set<AttributedVertex> roots = getRoots(graph);
// if there are no roots, don't attempt to create a Tree layout
if (roots.size() == 0) {
return layoutFunction.apply(FRUCTERMAN_REINGOLD).build();
}
LayoutAlgorithm<AttributedVertex> initialLayoutAlgorithm =
layoutFunction.apply(EDGE_AWARE_TREE).build();
if (initialLayoutAlgorithm instanceof TreeLayout) {
((TreeLayout<AttributedVertex>) initialLayoutAlgorithm)
.setRootPredicate(rootPredicate);
((TreeLayout<AttributedVertex>) initialLayoutAlgorithm)
.setVertexShapeFunction(vertexShapeFunction);
}
if (initialLayoutAlgorithm instanceof EdgeSorting) {
((EdgeSorting<AttributedEdge>) initialLayoutAlgorithm)
.setEdgeComparator(edgeComparator);
}
if (initialLayoutAlgorithm instanceof EdgePredicated) {
((EdgePredicated<AttributedEdge>) initialLayoutAlgorithm)
.setEdgePredicate(edgePredicate);
}
if (initialLayoutAlgorithm instanceof ShapeFunctionAware) {
((ShapeFunctionAware<AttributedVertex>) initialLayoutAlgorithm)
.setVertexShapeFunction(vertexShapeFunction);
}
return initialLayoutAlgorithm;
}
private Set<AttributedVertex> getRoots(AttributedGraph graph) {
return graph.edgeSet()
.stream()
.sorted(edgeComparator)
.map(graph::getEdgeSource)
.filter(rootPredicate)
.collect(Collectors.toCollection(LinkedHashSet::new));
}
public String[] getLayoutNames() {
return layoutFunction.getNames();
}
}

View File

@ -0,0 +1,137 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.graph.visualization;
import com.google.common.base.Splitter;
import ghidra.service.graph.Attributed;
import ghidra.service.graph.AttributedEdge;
import org.jungrapht.visualization.util.ShapeFactory;
import java.awt.BasicStroke;
import java.awt.Shape;
import java.awt.Stroke;
import java.util.Map;
import static org.jungrapht.visualization.VisualizationServer.PREFIX;
/**
* a container for various functions used by ProgramGraph
*/
abstract class ProgramGraphFunctions {
static float edgeWidth = Float.parseFloat(System.getProperty(PREFIX + "edgeWidth", "4.0f"));
// cannot instantiate nor extend
private ProgramGraphFunctions() {
}
/**
* a default implementation of a {@link ShapeFactory} to supply shapes for attributed vertices and edges
*/
private static ShapeFactory<Attributed> shapeFactory =
new ShapeFactory<>(n -> 50, n -> 1.0f);
/**
* return various 'Shapes' based on an attribute name
*
* @param n the attributed key (a vertex or edge)
* @param name the attribute name
* @return a Shape for the passed 'n' with attribute 'name'
*/
private static Shape byShapeName(Attributed n, String name) {
if (name == null) {
return null;
}
switch (name) {
case "Square":
return shapeFactory.getRectangle(n);
case "Circle":
return shapeFactory.getEllipse(n);
case "Triangle":
return shapeFactory.getRegularPolygon(n, 3);
case "TriangleDown":
return shapeFactory.getRegularPolygon(n, 3, Math.PI);
case "Diamond":
return shapeFactory.getRectangle(n, Math.PI / 4);
case "Star":
return shapeFactory.getRegularStar(n, 5);
case "Pentagon":
return shapeFactory.getRegularPolygon(n, 5);
case "Hexagon":
return shapeFactory.getRegularPolygon(n, 6);
case "Octagon":
return shapeFactory.getRegularPolygon(n, 8);
default:
return null;
}
}
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);
}
}
catch (Exception ex) {
// just return a rectangle
return shapeFactory.getRectangle(vertex);
}
}
/**
* Provides a {@link Stroke} (line width and style) for an attributed edge
* @param edge the edge to get a stroke value
* @return the stroke for the edge
*/
public static Stroke getEdgeStroke(AttributedEdge edge) {
String edgeType = edge.getAttribute("EdgeType");
if (edgeType != null && edgeType.equals("Fall-Through")) {
return new BasicStroke(edgeWidth * 2);
}
return new BasicStroke(edgeWidth);
}
/**
* gets a display label from an {@link Attributed} object (vertex)
* @param attributed the attributed object to get a label for
* @return the label for the given {@link Attributed}
*/
public static String getLabel(Attributed attributed) {
Map<String, String> map = attributed.getAttributeMap();
if (map.get("Code") != null) {
String code = map.get("Code");
return "<html>" + String.join("<p>", Splitter.on('\n').split(code));
}
return map.get("Name");
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 615 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 943 B

View File

@ -0,0 +1,53 @@
# property values for graph visualization
# see https://github.com/tomnelson/jungrapht-visualization/blob/master/jungrapht-visualization/src/main/resources/sample.jungrapht.properties
# for complete documentation of available properties and their default values
# when the graph is manipulated with the mouse, it will switch to lightweight rendering mode
# these properties control how long before it switches back to heavyweight
jungrapht.modalRendererTimerMax=10
jungrapht.modalRendererTimerIncrement=10
jungrapht.modalRendererTimerSleep=30
# whether the satellite view is drawn with a transparent background
jungrapht.satelliteBackgroundTransparent=false
# default spacing for tree layouts
jungrapht.treeLayoutHorizontalSpacing=400
jungrapht.treeLayoutVerticalSpacing=300
# default area of pick footprint (item is picked when footprint intersects item shape)
jungrapht.pickAreaSize=20
# default vertex size in pixels
jungrapht.vertexSize=100
# default stroke width for edges
jungrapht.edgeWidth=4.0f
# stroke size for the magnifier lens
jungrapht.lensStrokeWidth=10.0
# when scale is < .3, switch to lightweight rendering
jungrapht.lightweightScaleThreshold=.3
# under 50 vertices will use heavyweight rendering all the time
jungrapht.lightweightCountThreshold=50
# default pixels spacings for vertices
jungrapht.mincross.horizontalOffset=100
jungrapht.mincross.verticalOffset=50
# how many times to run the full all-level cross count
jungrapht.mincross.maxLevelCross=10
# how many times to iterate over the layers while swapping node positions (mincross)
jungrapht.mincrossTransposeLimit=5
# not using spatial data structures for edges
jungrapht.edgeSpatialSupport=NONE
# over 200 and the eiglsperger algorithm will run instead of the sugiyama
jungrapht.mincross.eiglspergerThreshold=200
# over 200 edges and the reduce edge crossing algorithm will not run
jungrapht.circle.reduceEdgeCrossingMaxEdges=200

View File

@ -0,0 +1,360 @@
/* ###
* 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.export;
import static org.junit.Assert.*;
import java.io.File;
import java.io.IOException;
import java.util.List;
import org.junit.*;
import ghidra.app.plugin.core.blockmodel.BlockModelServicePlugin;
import ghidra.app.plugin.core.codebrowser.CodeBrowserPlugin;
import ghidra.app.plugin.core.graph.GraphDisplayBrokerPlugin;
import ghidra.app.services.BlockModelService;
import ghidra.app.services.GraphDisplayBroker;
import ghidra.framework.plugintool.PluginTool;
import ghidra.framework.plugintool.util.PluginException;
import ghidra.program.database.ProgramDB;
import ghidra.service.graph.*;
import ghidra.test.AbstractGhidraHeadedIntegrationTest;
import ghidra.test.TestEnv;
import ghidra.util.Msg;
import ghidra.util.task.TaskMonitor;
import utilities.util.FileUtilities;
public class GraphExportTest extends AbstractGhidraHeadedIntegrationTest {
protected PluginTool tool;
protected ProgramDB program;
protected TestEnv env;
protected BlockModelService blockModelService;
protected CodeBrowserPlugin codeBrowser;
private GraphExporterDialog dialog;
@Before
public void setUp() throws Exception {
setErrorGUIEnabled(false);
env = new TestEnv();
tool = env.getTool();
initializeTool();
GraphDisplayBroker broker = tool.getService(GraphDisplayBroker.class);
GraphDisplayProvider exportProvider = broker.getGraphDisplayProvider("Graph Export");
AttributedGraph graph = createGraph();
GraphDisplay exporter = exportProvider.getGraphDisplay(false, TaskMonitor.DUMMY);
exporter.setGraph(graph, "Test", false, TaskMonitor.DUMMY);
waitForSwing();
}
@After
public void tearDown() {
env.dispose();
}
@Test
public void testCSV() throws Exception {
List<String> lines = processDialog(GraphExportFormat.CSV);
assertOutput(lines,
"A,B",
"B,C",
"B,D",
"C,E",
"D,E");
}
@Test
public void testDIMACS() throws Exception {
List<String> lines = processDialog(GraphExportFormat.DIMACS);
assertOutput(lines,
"c",
"c SOURCE: Generated using the JGraphT library",
"c",
"p edge 5 5",
"e A B",
"e B C",
"e B D",
"e C E",
"e D E");
}
@Test
public void testDOT() throws Exception {
List<String> lines = processDialog(GraphExportFormat.DOT);
assertOutput(lines,
"digraph Ghidra {",
" A [ Type=\"X\" Inverted=\"true\" Name=\"A\" ];",
" B [ Type=\"Y\" Name=\"B\" ];",
" C [ Type=\"Y\" Name=\"C\" ];",
" D [ Type=\"Y\" Name=\"D\" ];",
" E [ Type=\"Z\" Name=\"E\" ];",
" A -> B [ EType=\"Fall\" ];",
" B -> C [ EType=\"JMP\" ];",
" B -> D [ EType=\"Fall\" ];",
" C -> E [ EType=\"Fall\" ];",
" D -> E [ EType=\"Call\" ];",
"}");
}
@Test
public void testGML() throws Exception {
List<String> lines = processDialog(GraphExportFormat.GML);
assertOutput(lines,
"Creator \"JGraphT GML Exporter\"",
"Version 1",
"graph",
"[",
" label \"\"",
" directed 1",
" node",
" [",
" id A",
" ]",
" node",
" [",
" id B",
" ]",
" node",
" [",
" id C",
" ]",
" node",
" [",
" id D",
" ]",
" node",
" [",
" id E",
" ]",
" edge",
" [",
" id 1",
" source A",
" target B",
" ]",
" edge",
" [",
" id 2",
" source B",
" target C",
" ]",
" edge",
" [",
" id 3",
" source B",
" target D",
" ]",
" edge",
" [",
" id 4",
" source C",
" target E",
" ]",
" edge",
" [",
" id 5",
" source D",
" target E",
" ]",
"]");
}
@Test
public void testGRAPHML() throws Exception {
List<String> lines = processDialog(GraphExportFormat.GRAPHML);
assertOutput(lines,
"<?xml version=\"1.0\" encoding=\"UTF-8\"?><graphml xmlns=\"http://graphml.graphdrawing.org/xmlns\" xsi:schemaLocation=\"http://graphml.graphdrawing.org/xmlns http://graphml.graphdrawing.org/xmlns/1.0/graphml.xsd\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">",
" <graph edgedefault=\"directed\">",
" <node id=\"A\"/>",
" <node id=\"B\"/>",
" <node id=\"C\"/>",
" <node id=\"D\"/>",
" <node id=\"E\"/>",
" <edge id=\"1\" source=\"A\" target=\"B\"/>",
" <edge id=\"2\" source=\"B\" target=\"C\"/>",
" <edge id=\"3\" source=\"B\" target=\"D\"/>",
" <edge id=\"4\" source=\"C\" target=\"E\"/>",
" <edge id=\"5\" source=\"D\" target=\"E\"/>",
" </graph>",
"</graphml>");
}
@Test
public void testJSON() throws Exception {
List<String> lines = processDialog(GraphExportFormat.JSON);
assertOutput(lines,
"{\"creator\":\"JGraphT JSON Exporter\",\"version\":\"1\",\"nodes\":" +
"[{\"id\":\"A\",\"Type\":\"X\",\"Inverted\":\"true\",\"Name\":\"A\"}," +
"{\"id\":\"B\",\"Type\":\"Y\",\"Name\":\"B\"}," +
"{\"id\":\"C\",\"Type\":\"Y\",\"Name\":\"C\"}," +
"{\"id\":\"D\",\"Type\":\"Y\",\"Name\":\"D\"}," +
"{\"id\":\"E\",\"Type\":\"Z\",\"Name\":\"E\"}]," +
"\"edges\":[{\"id\":\"1\",\"source\":\"A\",\"target\":\"B\",\"EType\":\"Fall\"}," +
"{\"id\":\"2\",\"source\":\"B\",\"target\":\"C\",\"EType\":\"JMP\"}," +
"{\"id\":\"3\",\"source\":\"B\",\"target\":\"D\",\"EType\":\"Fall\"}," +
"{\"id\":\"4\",\"source\":\"C\",\"target\":\"E\",\"EType\":\"Fall\"}," +
"{\"id\":\"5\",\"source\":\"D\",\"target\":\"E\",\"EType\":\"Call\"}]}");
}
@Test
public void testLEMON() throws Exception {
List<String> lines = processDialog(GraphExportFormat.LEMON);
assertOutput(lines,
"#Creator: JGraphT Lemon (LGF) Exporter",
"#Version: 1",
"",
"@nodes",
"label",
"A",
"B",
"C",
"D",
"E",
"",
"@arcs",
" -",
"A B",
"B C",
"B D",
"C E",
"D E",
"");
}
@Test
public void testMATRIX() throws Exception {
List<String> lines = processDialog(GraphExportFormat.MATRIX);
assertOutput(lines,
"A B 1",
"B C 1",
"B D 1",
"C E 1",
"D E 1");
}
@Test
public void testVISIO() throws Exception {
List<String> lines = processDialog(GraphExportFormat.VISIO);
assertOutput(lines,
"Shape,A,,A",
"Shape,B,,B",
"Shape,C,,C",
"Shape,D,,D",
"Shape,E,,E",
"Link,A-->B,,,A,B",
"Link,B-->C,,,B,C",
"Link,B-->D,,,B,D",
"Link,C-->E,,,C,E",
"Link,D-->E,,,D,E");
}
protected void initializeTool() throws Exception {
installPlugins();
showTool(tool);
}
protected void installPlugins() throws PluginException {
tool.addPlugin(BlockModelServicePlugin.class.getName());
tool.addPlugin(GraphDisplayBrokerPlugin.class.getName());
}
private List<String> processDialog(GraphExportFormat format) throws IOException {
dialog = getDialogComponent(GraphExporterDialog.class);
String filePath =
createTempFilePath("GraphExportTest", "." + format.getDefaultFileExtension());
runSwing(() -> dialog.setOutputFile(filePath));
dialog.setExportFormat(format);
pressButtonByText(dialog, "OK");
List<String> lines = FileUtilities.getLines(new File(filePath));
return lines;
}
private AttributedGraph createGraph() {
AttributedGraph graph = new AttributedGraph();
AttributedVertex vA = graph.addVertex("A");
AttributedVertex vB = graph.addVertex("B");
AttributedVertex vC = graph.addVertex("C");
AttributedVertex vD = graph.addVertex("D");
AttributedVertex vE = graph.addVertex("E");
// A
// |
// B
// / \
// C D
// \ /
// E
AttributedEdge e1 = graph.addEdge(vA, vB);
AttributedEdge e2 = graph.addEdge(vB, vC);
AttributedEdge e3 = graph.addEdge(vB, vD);
AttributedEdge e4 = graph.addEdge(vC, vE);
AttributedEdge e5 = graph.addEdge(vD, vE);
vA.setAttribute("Type", "X");
vB.setAttribute("Type", "Y");
vC.setAttribute("Type", "Y");
vD.setAttribute("Type", "Y");
vE.setAttribute("Type", "Z");
e1.setAttribute("EType", "Fall");
e2.setAttribute("EType", "JMP");
e3.setAttribute("EType", "Fall");
e4.setAttribute("EType", "Fall");
e5.setAttribute("EType", "Call");
vA.setAttribute("Inverted", "true");
return graph;
}
private void printLines(List<String> lines) {
Msg.out("\n" + testName.getMethodName());
for (String line : lines) {
Msg.out("\"" + line + "\",");
}
}
private void assertOutput(List<String> actual, String... expected) {
try {
for (int i = 0; i < expected.length; i++) {
if (i >= actual.size()) {
fail(testName.getMethodName() + ": output line " + (i + 1) + ": expected :\"" +
expected[i] +
"\", got: EOF");
}
assertEquals(testName.getMethodName() + ": output line " + (i + 1) + ": ",
expected[i],
actual.get(i));
}
}
catch (Throwable e) {
printLines(actual);
throw e;
}
}
}

View File

@ -0,0 +1,75 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.graph.visualization;
import static org.junit.Assert.*;
import java.awt.Color;
import org.junit.Test;
import ghidra.service.graph.AttributedEdge;
import ghidra.service.graph.AttributedVertex;
public class ColorsTest {
@Test
public void testParseHashHexColor() {
Color hexColor = Colors.getHexColor("#ff0000");
assertEquals(Color.RED, hexColor);
}
@Test
public void testParseHexColor() {
Color hexColor = Colors.getHexColor("0xff0000");
assertEquals(Color.RED, hexColor);
}
@Test
public void testGetColorFromVertexType() {
AttributedVertex vertex = new AttributedVertex("A");
vertex.setAttribute("VertexType", "Exit");
vertex.setAttribute("Color", "0xffffff");
assertEquals(Color.MAGENTA, Colors.getColor(vertex));
}
@Test
public void testGetColorFromVertexNoVertexType() {
AttributedVertex vertex = new AttributedVertex("A");
vertex.setAttribute("Color", "0xffffff");
assertEquals(Color.WHITE, Colors.getColor(vertex));
}
@Test
public void testGetColorFromVertexNoAttributes() {
AttributedVertex vertex = new AttributedVertex("A");
assertEquals(Color.GREEN, Colors.getColor(vertex));
}
@Test
public void testGetColorFromEdgeType() {
AttributedEdge edge = new AttributedEdge("A");
edge.setAttribute("EdgeType", "Computed");
edge.setAttribute("Color", "0xffffff");
assertEquals(Color.CYAN, Colors.getColor(edge));
}
@Test
public void testGetColorFromEdgeNoEdgeType() {
AttributedEdge edge = new AttributedEdge("A");
edge.setAttribute("Color", "0xffffff");
assertEquals(Color.WHITE, Colors.getColor(edge));
}
}

View File

@ -0,0 +1,70 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.graph.visualization;
import ghidra.service.graph.AttributedVertex;
import org.junit.Assert;
import org.junit.Test;
import java.awt.geom.Rectangle2D;
public class IconShapeTest {
private IconShape.Function iconShapeFunction = new IconShape.Function();
@Test
public void testShapes() {
Rectangle2D rectangle = new Rectangle2D.Double(-10, -10, 20, 20);
Assert.assertEquals(IconShape.Type.RECTANGLE, iconShapeFunction.apply(rectangle));
AttributedVertex v = new AttributedVertex("id", "name");
// by vertex type
v.setAttribute("VertexType", "Entry");
Assert.assertEquals(IconShape.Type.TRIANGLE,
iconShapeFunction.apply(ProgramGraphFunctions.getVertexShape(v)));
v.setAttribute("VertexType", "Exit");
Assert.assertEquals(IconShape.Type.INVERTED_TRIANGLE,
iconShapeFunction.apply(ProgramGraphFunctions.getVertexShape(v)));
v.setAttribute("VertexType", "Switch");
Assert.assertEquals(IconShape.Type.DIAMOND, iconShapeFunction.apply(ProgramGraphFunctions.getVertexShape(v)));
v.setAttribute("VertexType", "Body");
Assert.assertEquals(IconShape.Type.RECTANGLE, iconShapeFunction.apply(ProgramGraphFunctions.getVertexShape(v)));
v.setAttribute("VertexType", "External");
Assert.assertEquals(IconShape.Type.RECTANGLE, iconShapeFunction.apply(ProgramGraphFunctions.getVertexShape(v)));
v.setAttribute("VertexType", "Foo");
Assert.assertEquals(IconShape.Type.ELLIPSE, iconShapeFunction.apply(ProgramGraphFunctions.getVertexShape(v)));
// by vertex icon shape name
v.removeAttribute("VertexType");
v.setAttribute("Icon", "Square");
Assert.assertEquals(IconShape.Type.RECTANGLE, iconShapeFunction.apply(ProgramGraphFunctions.getVertexShape(v)));
v.setAttribute("Icon", "TriangleDown");
Assert.assertEquals(IconShape.Type.TRIANGLE, iconShapeFunction.apply(ProgramGraphFunctions.getVertexShape(v)));
v.setAttribute("Icon", "Triangle");
Assert.assertEquals(IconShape.Type.INVERTED_TRIANGLE, iconShapeFunction.apply(ProgramGraphFunctions.getVertexShape(v)));
v.setAttribute("Icon", "Diamond");
Assert.assertEquals(IconShape.Type.DIAMOND, iconShapeFunction.apply(ProgramGraphFunctions.getVertexShape(v)));
v.setAttribute("Icon", "Circle");
Assert.assertEquals(IconShape.Type.ELLIPSE, iconShapeFunction.apply(ProgramGraphFunctions.getVertexShape(v)));
v.setAttribute("Icon", "Foo");
Assert.assertEquals(IconShape.Type.RECTANGLE, iconShapeFunction.apply(ProgramGraphFunctions.getVertexShape(v)));
}
}

View File

@ -0,0 +1,2 @@
EXCLUDE FROM GHIDRA JAR: true

View File

@ -0,0 +1,18 @@
apply from: "$rootProject.projectDir/gradle/distributableGhidraModule.gradle"
apply from: "$rootProject.projectDir/gradle/javaProject.gradle"
apply from: "$rootProject.projectDir/gradle/helpProject.gradle"
apply from: "$rootProject.projectDir/gradle/jacocoProject.gradle"
apply from: "$rootProject.projectDir/gradle/javaTestProject.gradle"
apply plugin: 'eclipse'
eclipse.project.name = 'Features Graph ProgramGraph'
dependencies {
compile project(":Base")
helpPath project(path: ":Base", configuration: 'helpPath')
helpPath project(path: ":GraphServices", configuration: 'helpPath')
}

View File

@ -0,0 +1,13 @@
##VERSION: 2.0
Module.manifest||GHIDRA||||END|
build.gradle||GHIDRA||||END|
src/main/help/help/TOC_Source.xml||GHIDRA||||END|
src/main/help/help/shared/arrow.gif||GHIDRA||||END|
src/main/help/help/shared/note.png||Oxygen Icons - LGPL 3.0|||Oxygen icon theme (dual license; LGPL or CC-SA-3.0)|END|
src/main/help/help/shared/tip.png||Oxygen Icons - LGPL 3.0|||Oxygen icon theme (dual license; LGPL or CC-SA-3.0)|END|
src/main/help/help/topics/ProgramGraphPlugin/ProgramGraph.htm||GHIDRA||||END|
src/main/help/help/topics/ProgramGraphPlugin/images/BasicBlockExampleCode.png||GHIDRA||||END|
src/main/help/help/topics/ProgramGraphPlugin/images/BasicBlockGraph.png||GHIDRA||||END|
src/main/help/help/topics/ProgramGraphPlugin/images/CodeBlockGraph.png||GHIDRA||||END|
src/main/help/help/topics/ProgramGraphPlugin/images/FocusGraphNode.png||GHIDRA||||END|
src/main/help/help/topics/ProgramGraphPlugin/images/SelectGraphNode.png||GHIDRA||||END|

View File

@ -0,0 +1,57 @@
<?xml version='1.0' encoding='ISO-8859-1' ?>
<!--
This is an XML file intended to be parsed by the Ghidra help system. It is loosely based
upon the JavaHelp table of contents document format. The Ghidra help system uses a
TOC_Source.xml file to allow a module with help to define how its contents appear in the
Ghidra help viewer's table of contents. The main document (in the Base module)
defines a basic structure for the
Ghidra table of contents system. Other TOC_Source.xml files may use this structure to insert
their files directly into this structure (and optionally define a substructure).
In this document, a tag can be either a <tocdef> or a <tocref>. The former is a definition
of an XML item that may have a link and may contain other <tocdef> and <tocref> children.
<tocdef> items may be referred to in other documents by using a <tocref> tag with the
appropriate id attribute value. Using these two tags allows any module to define a place
in the table of contents system (<tocdef>), which also provides a place for
other TOC_Source.xml files to insert content (<tocref>).
During the help build time, all TOC_Source.xml files will be parsed and validated to ensure
that all <tocref> tags point to valid <tocdef> tags. From these files will be generated
<module name>_TOC.xml files, which are table of contents files written in the format
desired by the JavaHelp system. Additionally, the genated files will be merged together
as they are loaded by the JavaHelp system. In the end, when displaying help in the Ghidra
help GUI, there will be on table of contents that has been created from the definitions in
all of the modules' TOC_Source.xml files.
Tags and Attributes
<tocdef>
-id - the name of the definition (this must be unique across all TOC_Source.xml files)
-text - the display text of the node, as seen in the help GUI
-target** - the file to display when the node is clicked in the GUI
-sortgroup - this is a string that defines where a given node should appear under a given
parent. The string values will be sorted by the JavaHelp system using
a javax.text.RulesBasedCollator. If this attribute is not specified, then
the text of attribute will be used.
<tocref>
-id - The id of the <tocdef> that this reference points to
**The URL for the target is relative and should start with 'help/topics'. This text is
used by the Ghidra help system to provide a universal starting point for all links so that
they can be resolved at runtime, across modules.
-->
<tocroot>
<tocref id="Graphing">
<tocdef id="Graphing the Program" text="Graphing the Program" target="help/topics/ProgramGraphPlugin/ProgramGraph.htm" />
</tocref>
</tocroot>

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -0,0 +1,397 @@
<!DOCTYPE doctype PUBLIC "-//W3C//DTD HTML 4.0 Frameset//EN">
<HTML>
<HEAD>
<META name="generator" content=
"HTML Tidy for Java (vers. 2009-12-01), see jtidy.sourceforge.net">
<TITLE>Graphing the Program</TITLE>
<META http-equiv="Content-Type" content="text/html; charset=windows-1252">
<LINK rel="stylesheet" type="text/css" href="../../shared/Frontpage.css">
</HEAD>
<BODY lang="EN-US">
<H1>Graphing the Program</H1>
<H3>Graph Output</H3>
<P>To display or export a graph, Ghidra supports multiple graph services. Ghidra has two
built-in graph services; one to display a graph and one to export a graph. Before invoking
the graph actions described below, make sure to choose the desired graph service. This choice
will direct the output of the graph function to the active graph service. To select a graph
service, use the <B>Graph<IMG src="../../shared/arrow.gif">Graph Output</B></LI> menu.
<H3>Graph types</H3>
<P>Program control flow Graphs can be created and then shown using an appropriate graph service.
A control flow graph is a representation of the flow from one portion of the code to
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=
"#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>
<BLOCKQUOTE>
<P>A <I>Call Graph</I> depicts subroutines as nodes. Calls between subroutines are shown as
edges. Under normal circumstances all nodes are subroutine entry points (orange triangle) and
all edges are calls (orange).</P>
<P>A Block Flow graph is a little more complicated. Each node is a contiguous set of
instructions or Code Block broken by an instruction that causes any type of flow. Move and
arithmetic instructions cause no change in flow so they will not break a block. Jump, Call,
and return instructions cause flow and will lie at the end of a Code Block. Block Flow graphs
are useful for looking at the control flow within a function in compiled code. For embedded
systems that have convoluted control flow structure, it may be beneficial to graph the entire
program.</P>
</BLOCKQUOTE>
<P>Selection and Location events are synchronized between each
graph and the other windows in the tool.
<H3>Selection</H3>
<P>The current selection within the graph display is represented by a red box around selected
nodes as shown below on the node labeled "00408133". A node is selected if any addresses it represents are contained within the
selection.</P>
<P align="center"><IMG src="images/SelectGraphNode.png"></P>
<P align="left"><BR>
When a selection is made in the graph display, all addresses represented by each selected node become
the new current selection within Ghidra.</P>
<BLOCKQUOTE>
<P><IMG src="../../shared/note.png"> See the documentation for the specific graph display for
descriptions of how to to make selections and navigate the graph.</P>
</BLOCKQUOTE>
<BLOCKQUOTE>
<P><IMG src="../../shared/tip.png"> A selection in one graph can be used to create a follow
on graph. When a graph is created, only blocks of code that fall within the current selection
will be part of the graph. For example, a single subroutine could be selected in a Call
Graph. From that selection, a <A href="#Graph_Block_Flow">Block Flow</A> graph can be created
from the basic blocks found within the selected subroutine.</P>
</BLOCKQUOTE>
<H3>Location</H3>
<P>The node containing the current address location is marked with a large red arrow as shown
below on the graph node labeled "00408133".</P>
<P align="center"><IMG src="images/FocusGraphNode.png"></P>
<P align="left">Whenever the cursor location changes in the Code Browser (or tool that created
the graph), the red arrow location on the graph is updated. The current location is also
changed when a selection of nodes is made within the graph display. The minimum address of all nodes
within the selection will become the current location. If the address of the current location
is not part of any node within the graph, the red arrow will not be visible.</P>
<P align="left">When the option <B><A href="#Show_Location_in_Graph">Graph<IMG border="0"
src="../../shared/arrow.gif"> Show Location</A></B> is turned on, the graph will re-orient
itself to insure that the red location arrow is always visible.</P>
<P align="left">Clicking on a node in the graph display causes the
current address location within Ghidra to change to the minimum address represented by the
graph node.</P>
<H3>Graph Representation</H3>
<P>By Default, the graphs use the following icons and colors to represent the nodes and edges.</P>
<CENTER>
<DIV align="center">
<CENTER>
<TABLE border="1" width="400" cellspacing="1">
<TBODY>
<TR>
<TD colspan="2" bgcolor="#c0c0c0">
<P align="center"><B>Graph Edge/Link Types</B></P>
</TD>
</TR>
<TR>
<TD width="200" align="center" bgcolor="#DDDDDD"><B>Edge Type</B></TD>
<TD width="200" align="center" bgcolor="#DDDDDD"><B>Color</B></TD>
</TR>
<TR>
<TD width="200" align="center" bgcolor="#0066FF">Fall-Through Flow</TD>
<TD width="200" align="center" bgcolor="#0066FF">Blue</TD>
</TR>
<TR>
<TD width="200" align="center" bgcolor="#00ff00">Unconditional Jump</TD>
<TD width="200" align="center" bgcolor="#00ff00">Green</TD>
</TR>
<TR>
<TD width="200" align="center" bgcolor="#ffff00">Conditional Jump</TD>
<TD width="200" align="center" bgcolor="#ffff00">Yellow</TD>
</TR>
<TR>
<TD width="200" align="center" bgcolor="#FF9900">Unconditional Call</TD>
<TD width="200" align="center" bgcolor="#FF9900">Orange</TD>
</TR>
<TR>
<TD width="200" align="center" bgcolor="#FF9900">Conditional Call</TD>
<TD width="200" align="center" bgcolor="#FF9900">Orange</TD>
</TR>
<TR>
<TD width="200" align="center" bgcolor="#00ffff">Computed Call or Jump</TD>
<TD width="200" align="center" bgcolor="#00ffff">Cyan</TD>
</TR>
<TR>
<TD width="200" align="center" bgcolor="#FF66FF">Indirect (i.e., pointer)</TD>
<TD width="200" align="center" bgcolor="#FF66FF">Pink</TD>
</TR>
<TR>
<TD width="200" align="center">From Entry Nexus</TD>
<TD width="200" align="center">White</TD>
</TR>
</TBODY>
</TABLE>
</CENTER>
</DIV>
</CENTER>
<BR>
<BR>
<CENTER>
<DIV align="center">
<CENTER>
<TABLE border="1" width="500" cellspacing="1">
<TBODY>
<TR>
<TD colspan="3" bgcolor="#c0c0c0" width="440">
<P align="center"><B>Graph Node/Vertex Types</B></P>
</TD>
</TR>
<TR>
<TD width="200" align="center" bgcolor="#DDDDDD"><B>Vertex Type</B></TD>
<TD width="200" align="center" bgcolor="#DDDDDD"><B>Color</B></TD>
<TD width="200" align="center" bgcolor="#DDDDDD"><B>Shape</B></TD>
</TR>
<TR>
<TD width="200" align="center" bgcolor="#FF9900">Entry</TD>
<TD width="200" align="center" bgcolor="#FF9900">Orange</TD>
<TD width="200" align="center" bgcolor="#FF9900">Triangle (down)</TD>
</TR>
<TR>
<TD width="200" align="center" bgcolor="#0000ff">Body</TD>
<TD width="200" align="center" bgcolor="#0000ff">Blue</TD>
<TD width="200" align="center" bgcolor="#0000ff">Oval</TD>
</TR>
<TR>
<TD width="200" align="center" bgcolor="#9900FF">Terminator</TD>
<TD width="200" align="center" bgcolor="#9900FF">Purple</TD>
<TD width="200" align="center" bgcolor="#9900FF">Triangle (up)</TD>
</TR>
<TR>
<TD width="200" align="center" bgcolor="#00ffff">Switch</TD>
<TD width="200" align="center" bgcolor="#00ffff">Cyan</TD>
<TD width="200" align="center" bgcolor="#00ffff">Diamond</TD>
</TR>
<TR>
<TD width="200" align="center" bgcolor="#ff0000">Bad</TD>
<TD width="200" align="center" bgcolor="#ff0000">Red</TD>
<TD width="200" align="center" bgcolor="#ff0000">Rectangle</TD>
</TR>
<TR>
<TD width="200" align="center" bgcolor="#FF66FF">Data</TD>
<TD width="200" align="center" bgcolor="#FF66FF">Pink</TD>
<TD width="200" align="center" bgcolor="#FF66FF">Cylinder</TD>
</TR>
<TR>
<TD width="200" align="center" bgcolor="#ffffff">Entry Nexus</TD>
<TD width="200" align="center" bgcolor="#ffffff">White</TD>
<TD width="200" align="center" bgcolor="#ffffff">Cone</TD>
</TR>
</TBODY>
</TABLE>
</CENTER>
</DIV>
</CENTER>
<H2><A name="Graph_Block_Flow"></A>Block Flow Graph</H2>
<P>A Block Flow Graph consists of nodes that represent Basic blocks of contiguous instructions.
Basic blocks are broken up by any instruction that causes a change in execution flow. All Jump,
Call, Branch, and Return instructions can cause the execution flow to change. Arithmetic and
store/load instructions do not break a Basic block because they do not change the execution
flow. A labeled instruction will always start a block regardless of the instruction type.</P>
<P>For example:</P>
<P align="center"><IMG src="images/BasicBlockExampleCode.png"></P>
<P>Would generate the following graph:</P>
<P align="center"><IMG src="images/BasicBlockGraph.png">
</P>
<BLOCKQUOTE>
<P><IMG src="../../shared/note.png"> If there is a current selection, the nodes and edges
will be restricted to blocks of code that fall within the selection.</P>
</BLOCKQUOTE>
<P>To Graph Block Flow Using the default model,</P>
<OL>
<LI>Select <B>Graph<IMG src="../../shared/arrow.gif"> Block Flow</B></LI>
<LI>A new graph window is created</LI>
</OL>
<H2><A name="Graph_Code_Flow"></A>Graph Code Flow</H2>
<P align="left">A Code Flow Graph is an extension of a <A href="#Graph_Block_Flow">Block Flow
Graph</A> in which each graph node (i.e., vertex) contains the list of instructions contained
within the associated block. The list of instructions are passed to the graph as the vertex
label.</P>
<P align="center"><BR>
<BR>
<IMG src="images/CodeBlockGraph.png"></P>
<H2><A name="Graph_Calls_using_Default_Model"></A>Graph Calls</H2>
<P>A graph of the call instruction flow from one subroutine to another can be created with
<B>Graph<IMG src="../../shared/arrow.gif"> Calls</B>. The graph is created using the default
Call Model. Several Subroutine Models are available. Each model provides a slightly
different perspective on what constitutes a subroutine.</P>
<BLOCKQUOTE>
<BLOCKQUOTE>
<P><IMG src="../../shared/note.png"> If there is a current selection, the nodes and edges
will be restricted to blocks of code that fall within the selection.</P>
</BLOCKQUOTE>
<P>To Graph Calls Using the default model,</P>
<OL>
<LI>Select <B>Graph<IMG src="../../shared/arrow.gif"> Calls</B></LI>
<LI>A new graph window is created</LI>
</OL>
<P><A name="Graph_Calls_Using_Model"></A>To Graph Calls Using a specific model*,</P>
<OL>
<LI>Select <B>Graph<IMG src="../../shared/arrow.gif"> Calls Using Model<IMG src=
"../../shared/arrow.gif"></B> &lt;<I><B>a Call Model</B></I>&gt;</LI>
<LI>
Select one of
<UL>
<LI>Isolated Entry Model</LI>
<LI>Multiple Entry Model</LI>
<LI>Overlapped Code Model</LI>
<LI>Partitioned Code Model</LI>
</UL>
</LI>
<LI>A new graph window is created</LI>
</OL>
<BLOCKQUOTE>
<P><IMG src="../../shared/note.png"> *For a more thorough description of each Call Block
Model (i.e., Subroutine Model), see <A href="help/topics/BlockModel/Block_Model.htm">Block
Models</A>. The specific list of models presented to the user may vary depending upon the
set of block models configured into the tool.</P>
</BLOCKQUOTE>
</BLOCKQUOTE>
<H2><A name="Reuse_Graph"></A>Reuse Graph</H2>
<P>When <I>Reuse Graph</I> is turned on, creating any new graphs will re-use the active graph
window. The active graph is the last graph window that was focused. This could be the
last graph window created, popped to the top, or interacted with to change the current
selection or location. Instead of a new window for each graph, all graphs will be rendered
using the same window. When a graph window is re-used the existing graph information is cleared
and replaced with the new graph information. If <A href="#Show_Location_in_Graph">Append
Graph</A> is also turned on, the information is also appended to the active graph window.</P>
<BLOCKQUOTE>
<P>To Reuse A Graph,</P>
<OL>
<LI>Turn on <B>Reuse Graph<BR>
</B> (a check mark will display next to the menu item)</LI>
<LI>Select an existing graph window to set it to the active window.<BR>
(this will pop the graph window to the front)</LI>
<LI>Create a graph using:<BR>
Select <B>Graph<IMG src="../../shared/arrow.gif"> Block Flow</B>, <B>Graph<IMG src=
"../../shared/arrow.gif"> Calls</B>, or any of the <B>Graph<IMG src=
"../../shared/arrow.gif"> Calls Using Model</B> items</LI>
</OL>
</BLOCKQUOTE>
<H2><A name="Append_Graph"></A>Append Graph</H2>
<P>When <I>Append Graph</I> is turned on, creating any new graphs will append the graph
information to the active graph. The active graph is the last graph window that was
focused. This could be the last graph window created, popped to the top, or interacted with to
change the current selection or location.</P>
<BLOCKQUOTE>
<P>To append to an existing graph,</P>
<OL>
<LI>Turn on <B>Append Graph</B><BR>
(a check mark will display next to the menu item)</LI>
<LI>Select the Graph window to append to<BR>
(this will pop the graph window to the front).</LI>
<LI>Create a graph using:<BR>
Select <B>Graph<IMG src="../../shared/arrow.gif"> Block Flow</B>, <B>Graph<IMG src=
"../../shared/arrow.gif"> Calls</B>, or any of the <B>Graph<IMG src=
"../../shared/arrow.gif"> Calls Using Model</B> menu items</LI>
</OL>
<P><IMG src="../../shared/note.png"> The <B>Reuse Graph</B> option must be enabled for the
<B>Append Graph</B> option to be considered. Toggling on the <B>Append Graph</B> option will
automatically turn on the <A href="#Reuse_Graph">Reuse Graph</A> option. Append Graph without
Reuse Graph will display new graphs in a new graph window, essentially having no effect.</P>
</BLOCKQUOTE>
<H2><A name="Show_Location_in_Graph"></A>Show Location</H2>
<P>When <I>Show Location</I> is turned <I><U>on</U></I>, the current address location will be
forced to visibly display within all graph windows. This may cause the graph to change
its view scale; resulting in disorientation when looking at a graph that has been carefully
arranged (clinically know as graphidisorientitis).</P>
<P>When Show Location is turned <U><I>off</I></U>, the graph view will not change as the
current address location changes.</P>
<BR>
<BR>
<BR>
<BR>
<BR>
</ul>
<P class="providedbyplugin">Provided by: <I>Program Graph Plugin</I></P>
<P class="relatedtopic">Related Topics</P>
<UL>
<LI><A href="help/topics/GraphServices/GraphDisplay.htm">Default Graph Display</A></LI>
<LI><A href="help/topics/GraphServices/GraphExport.htm">Graph Export</A></LI>
</UL><BR>
</BODY>
</HTML>

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

View File

@ -0,0 +1,522 @@
/* ###
* 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.awt.Color;
import java.util.*;
import ghidra.app.plugin.core.colorizer.ColorizingService;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSetView;
import ghidra.program.model.block.*;
import ghidra.program.model.listing.*;
import ghidra.program.model.symbol.*;
import ghidra.program.util.ProgramSelection;
import ghidra.service.graph.*;
import ghidra.util.HTMLUtilities;
import ghidra.util.Msg;
import ghidra.util.exception.CancelledException;
import ghidra.util.exception.GraphException;
import ghidra.util.task.Task;
import ghidra.util.task.TaskMonitor;
/**
* <CODE>GraphTask</CODE> is a threaded task creating either a block or call graph.
*/
public class BlockGraphTask extends Task {
private static final String CODE_ATTRIBUTE = "Code";
private static final String SYMBOLS_ATTRIBUTE = "Symbols";
protected static final String PROGRESS_DIALOG_TITLE = "Graphing Program";
protected static final String INIT_PROGRESS_MSG = "Graphing Program...";
private boolean graphEntryPointNexus = false;
private boolean showCode = false;
private int codeLimitPerBlock = 10;
private ColorizingService colorizingService;
/**
* Edge flow tags
*/
protected final static int FALLTHROUGH = 0;
protected final static int CONDITIONAL_RETURN = 1;
protected final static int UNCONDITIONAL_JUMP = 2;
protected final static int CONDITIONAL_JUMP = 3;
protected final static int UNCONDITIONAL_CALL = 4;
protected final static int CONDITIONAL_CALL = 5;
protected final static int TERMINATOR = 6;
protected final static int COMPUTED = 7;
protected final static int INDIRECTION = 8;
protected final static int ENTRY = 9; // from Entry Nexus
protected final static String[] edgeNames =
{ "1", "2", "3", "4", "5", "6", "7", "13", "14", "15" };
// @formatter:off
protected final static String[] edgeTypes = {
"Fall-Through",
"Conditional-Return",
"Unconditional-Jump",
"Conditional-Jump",
"Unconditional-Call",
"Conditional-Call",
"Terminator",
"Computed",
"Indirection",
"Entry"
};
// @formatter:on
private final static String ENTRY_NODE = "Entry";
// "1"; // beginning of a block, someone calls it
private final static String BODY_NODE = "Body";
// "2"; // Body block, no flow
private final static String EXIT_NODE = "Exit";
// "3"; // Terminator
private final static String SWITCH_NODE = "Switch";
// "4"; // Switch/computed jump
private final static String BAD_NODE = "Bad";
// "5"; // Bad destination
private final static String DATA_NODE = "Data";
// "6"; // Data Node, used for indirection
private final static String ENTRY_NEXUS = "Entry-Nexus";
// "7"; //
private final static String EXTERNAL_NODE = "External";
// "8"; // node is external to program
private final static String ENTRY_NEXUS_NAME = "Entry Points";
private CodeBlockModel blockModel;
private AddressSetView selection;
private GraphDisplayProvider graphService;
private boolean reuseGraph;
private boolean appendGraph;
private PluginTool tool;
private String actionName;
private Program program;
public BlockGraphTask(String actionName, boolean graphEntryPointNexus, boolean showCode,
boolean reuseGraph,
boolean appendGraph, PluginTool tool, ProgramSelection selection,
CodeBlockModel blockModel, GraphDisplayProvider graphService) {
super("Graph Program", true, false, true);
this.actionName = actionName;
this.graphEntryPointNexus = graphEntryPointNexus;
this.showCode = showCode;
this.reuseGraph = reuseGraph;
this.appendGraph = appendGraph;
this.tool = tool;
this.blockModel = blockModel;
this.graphService = graphService;
this.colorizingService = tool.getService(ColorizingService.class);
this.selection = selection;
this.program = blockModel.getProgram();
}
/**
* Runs the move memory operation.
*/
@Override
public void run(TaskMonitor monitor) throws CancelledException {
AttributedGraph graph = createGraph();
monitor.setMessage("Generating Graph...");
try {
GraphDisplay display = graphService.getGraphDisplay(reuseGraph, monitor);
display.setGraphDisplayListener(
new BlockModelGraphDisplayListener(tool, blockModel, display));
if (showCode) {
display.defineVertexAttribute(CODE_ATTRIBUTE);
display.defineVertexAttribute(SYMBOLS_ATTRIBUTE);
display.setVertexLabel(CODE_ATTRIBUTE, GraphDisplay.ALIGN_LEFT, 12, true,
codeLimitPerBlock + 1);
}
display.setGraph(graph, actionName, appendGraph, monitor);
}
catch (GraphException e) {
if (!monitor.isCancelled()) {
Msg.showError(this, null, "Graphing Error", e.getMessage());
}
}
}
/**
* Set the maximum number of code lines which will be used per block when
* showCode is enabled.
* @param maxLines maximum number of code lines
*/
public void setCodeLimitPerBlock(int maxLines) {
codeLimitPerBlock = maxLines;
}
protected AttributedGraph createGraph() throws CancelledException {
int blockCount = 0;
AttributedGraph graph = new AttributedGraph();
CodeBlockIterator it = getBlockIterator();
List<AttributedVertex> entryPoints = new ArrayList<>();
while (it.hasNext()) {
CodeBlock curBB = it.next();
Address start = graphBlock(graph, curBB, entryPoints);
if (start != null && (++blockCount % 50) == 0) {
taskMonitor.setMessage("Process Block: " + start.toString());
}
}
// if option is set and there is more than one entry point vertex, create fake entry node
// and connect to each entry point vertex
if (graphEntryPointNexus && entryPoints.size() > 1) {
addEntryEdges(graph, entryPoints);
}
return graph;
}
private CodeBlockIterator getBlockIterator() throws CancelledException {
if (selection == null || selection.isEmpty()) {
return blockModel.getCodeBlocks(taskMonitor);
}
return blockModel.getCodeBlocksContaining(selection, taskMonitor);
}
private Address graphBlock(AttributedGraph graph, CodeBlock curBB, List<AttributedVertex> entries)
throws CancelledException {
Address[] startAddrs = curBB.getStartAddresses();
if (startAddrs == null || startAddrs.length == 0) {
Msg.error(this, "Block not graphed, missing start address: " + curBB.getMinAddress());
return null;
}
AttributedVertex vertex = graphBasicBlock(graph, curBB);
if (graphEntryPointNexus && hasExternalEntryPoint(startAddrs)) {
entries.add(vertex);
}
return startAddrs[0];
}
private boolean hasExternalEntryPoint(Address[] startAddrs) {
SymbolTable symbolTable = program.getSymbolTable();
for (Address address : startAddrs) {
if (symbolTable.isExternalEntryPoint(address)) {
return true;
}
}
return false;
}
private void addEntryEdges(AttributedGraph graph, List<AttributedVertex> entries) {
AttributedVertex entryNexusVertex = getEntryNexusVertex(graph);
for (AttributedVertex vertex : entries) {
AttributedEdge edge = graph.addEdge(entryNexusVertex, vertex);
edge.setAttribute("Name", edgeNames[ENTRY]);
edge.setAttribute("EdgeType", edgeTypes[ENTRY]);
}
}
protected AttributedVertex graphBasicBlock(AttributedGraph graph, CodeBlock curBB)
throws CancelledException {
AttributedVertex fromVertex = getBasicBlockVertex(graph, curBB);
// for each destination block
// create a vertex if it doesn't exit and add an edge to the destination vertex
CodeBlockReferenceIterator refIter = curBB.getDestinations(taskMonitor);
while (refIter.hasNext()) {
CodeBlockReference cbRef = refIter.next();
CodeBlock db = cbRef.getDestinationBlock();
// must be a reference to a data block
if (db == null) {
continue;
}
// don't include destination if it does not overlap selection
// always include if selection is empty
if (selection != null && !selection.isEmpty() && !selection.intersects(db)) {
continue;
}
AttributedVertex toVertex = getBasicBlockVertex(graph, db);
if (toVertex == null) {
continue;
}
// put the edge in the graph
String edgeAddr = cbRef.getReferent().toString();
AttributedEdge newEdge = graph.addEdge(fromVertex, toVertex);
// set it's attributes (really its name)
setEdgeAttributes(newEdge, cbRef);
setEdgeColor(newEdge, fromVertex, toVertex);
}
return fromVertex;
}
private void setEdgeColor(AttributedEdge edge, AttributedVertex fromVertex, AttributedVertex toVertex) {
// color the edge: first on the 'from' vertex, then try to 'to' vertex
String fromColor = fromVertex.getAttribute("Color");
String toColor = toVertex.getAttribute("Color");
if (fromColor != null || toColor != null) {
if (fromColor != null) {
edge.setAttribute("Color", fromColor);
}
else if (toColor != null) {
edge.setAttribute("Color", toColor);
}
}
}
private String getVertexId(CodeBlock bb) {
// vertex has attributes of Name = Label
// Address = address of blocks start
// VertexType = flow type of vertex
Address addr = bb.getFirstStartAddress();
if (addr.isExternalAddress()) {
Symbol s = bb.getModel().getProgram().getSymbolTable().getPrimarySymbol(addr);
return s.getName(true);
}
return addr.toString();
}
protected AttributedVertex getBasicBlockVertex(AttributedGraph graph, CodeBlock bb)
throws CancelledException {
String vertexId = getVertexId(bb);
AttributedVertex vertex = graph.getVertex(vertexId);
if (vertex != null) {
return vertex;
}
String vertexName = bb.getName();
vertex = graph.addVertex(vertexId, vertexName);
// add attributes for this vertex -
setVertexAttributes(vertex, bb, vertexName.equals(vertexId) ? false : isEntryNode(bb));
if (showCode) {
addSymbolAttribute(vertex, bb);
addCodeAttribute(vertex, bb);
}
return vertex;
}
private void addCodeAttribute(AttributedVertex vertex, CodeBlock bb) {
if (!bb.getMinAddress().isMemoryAddress()) {
vertex.setAttribute(CODE_ATTRIBUTE, vertex.getAttribute(SYMBOLS_ATTRIBUTE));
}
Listing listing = program.getListing();
CodeUnitIterator cuIter = listing.getCodeUnits(bb, true);
int cnt = 0;
int maxMnemonicFieldLen = 0;
StringBuffer buf = new StringBuffer();
while (cuIter.hasNext()) {
CodeUnit cu = cuIter.next();
if (cnt != 0) {
buf.append('\n');
}
String line = cu.toString();
int ix = line.indexOf(' ');
if (ix > maxMnemonicFieldLen) {
maxMnemonicFieldLen = ix;
}
buf.append(line);
if (++cnt == codeLimitPerBlock) {
buf.append("\n...");
break;
}
}
vertex.setAttribute(CODE_ATTRIBUTE, adjustCode(buf, maxMnemonicFieldLen + 1));
}
private void addSymbolAttribute(AttributedVertex vertex, CodeBlock bb) {
Symbol[] symbols = program.getSymbolTable().getSymbols(bb.getMinAddress());
if (symbols.length != 0) {
StringBuffer buf = new StringBuffer();
for (int i = 0; i < symbols.length; i++) {
if (i != 0) {
buf.append('\n');
}
buf.append(symbols[i].getName());
}
vertex.setAttribute(SYMBOLS_ATTRIBUTE, buf.toString());
}
}
private String adjustCode(StringBuffer buf, int mnemonicFieldLen) {
if (mnemonicFieldLen <= 1) {
return buf.toString();
}
int ix = 0;
char[] pad = new char[mnemonicFieldLen];
Arrays.fill(pad, ' ');
while (ix < buf.length()) {
int eolIx = buf.indexOf("\n", ix);
if (eolIx < 0) {
eolIx = buf.length();
}
int padIx = buf.indexOf(" ", ix);
if (padIx > 0 && padIx < eolIx) {
int padSize = mnemonicFieldLen - padIx + ix;
if (padSize > 0) {
buf.insert(padIx, pad, 0, padSize);
eolIx += padSize;
}
}
ix = eolIx + 1;
}
return buf.toString();
}
/**
* Determine if the specified block is an entry node.
* @param block the basic block to test
* @return true if the specified block is an entry node.
* @throws CancelledException if the operation is cancelled
*/
protected boolean isEntryNode(CodeBlock block) throws CancelledException {
CodeBlockReferenceIterator iter = block.getSources(taskMonitor);
boolean isSource = true;
while (iter.hasNext()) {
isSource = false;
if (iter.next().getFlowType().isCall()) {
return true;
}
}
return isSource;
}
protected void setEdgeAttributes(AttributedEdge edge, CodeBlockReference ref) {
int edgeType;
FlowType flowType = ref.getFlowType();
if (flowType == RefType.FALL_THROUGH) {
edgeType = FALLTHROUGH;
}
else if (flowType == RefType.UNCONDITIONAL_JUMP) {
edgeType = UNCONDITIONAL_JUMP;
}
else if (flowType == RefType.CONDITIONAL_JUMP) {
edgeType = CONDITIONAL_JUMP;
}
else if (flowType == RefType.UNCONDITIONAL_CALL) {
edgeType = UNCONDITIONAL_CALL;
}
else if (flowType == RefType.CONDITIONAL_CALL) {
edgeType = CONDITIONAL_CALL;
}
else if (flowType.isComputed()) {
edgeType = COMPUTED;
}
else if (flowType.isIndirect()) {
edgeType = INDIRECTION;
}
else if (flowType == RefType.TERMINATOR) {
edgeType = TERMINATOR;
}
else { // only FlowType.CONDITIONAL_TERMINATOR remains unchecked
edgeType = CONDITIONAL_RETURN;
}
// set attributes on this edge
edge.setAttribute("Name", edgeNames[edgeType]);
edge.setAttribute("EdgeType", edgeTypes[edgeType]);
}
protected void setVertexAttributes(AttributedVertex vertex, CodeBlock bb, boolean isEntry) {
String vertexType = BODY_NODE;
Address firstStartAddress = bb.getFirstStartAddress();
if (firstStartAddress.isExternalAddress()) {
vertexType = EXTERNAL_NODE;
}
else if (isEntry) {
vertexType = ENTRY_NODE;
}
else {
FlowType flowType = bb.getFlowType();
if (flowType.isTerminal()) {
vertexType = EXIT_NODE;
}
else if (flowType.isComputed()) {
vertexType = SWITCH_NODE;
}
else if (flowType == RefType.INDIRECTION) {
vertexType = DATA_NODE;
}
else if (flowType == RefType.INVALID) {
vertexType = BAD_NODE;
}
}
vertex.setAttribute("VertexType", vertexType);
setVertexColor(vertex, vertexType, firstStartAddress);
}
private void setVertexColor(AttributedVertex vertex, String vertexType, Address address) {
if (colorizingService == null) {
return;
}
Color color = colorizingService.getBackgroundColor(address);
if (color == null) {
return;
}
// color format: RGBrrrgggbbb
// -where rrr/ggg/bbb is a three digit int value for each respective color range
String rgb = "RGB" + HTMLUtilities.toRGBString(color);
vertex.setAttribute("Color", rgb); // sets the vertex color
// This value triggers the vertex to be painted with its color and not a
// while background.
if (showCode) {
// our own custom override of Labels/Icons
vertex.setAttribute("VertexType", "ColorFilled");
}
else {
// the default preferences for VertexType
vertex.setAttribute("VertexType", vertexType + ".Filled");
}
}
private AttributedVertex getEntryNexusVertex(AttributedGraph graph) {
AttributedVertex vertex = graph.getVertex(ENTRY_NEXUS_NAME);
if (vertex == null) {
vertex = graph.addVertex(ENTRY_NEXUS_NAME, ENTRY_NEXUS_NAME);
vertex.setAttribute("VertexType", ENTRY_NEXUS);
}
return vertex;
}
}

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 ghidra.app.plugin.core.graph.AddressBasedGraphDisplayListener;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.address.*;
import ghidra.program.model.block.*;
import ghidra.program.model.symbol.Symbol;
import ghidra.program.model.symbol.SymbolTable;
import ghidra.service.graph.GraphDisplay;
import ghidra.service.graph.GraphDisplayListener;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;
/**
* {@link GraphDisplayListener} that handle events back and from from program graphs.
*/
public class BlockModelGraphDisplayListener extends AddressBasedGraphDisplayListener {
private CodeBlockModel blockModel;
public BlockModelGraphDisplayListener(PluginTool tool, CodeBlockModel blockModel,
GraphDisplay display) {
super(tool, blockModel.getProgram(), display);
this.blockModel = blockModel;
}
@Override
protected String getVertexIdForAddress(Address address) {
try {
CodeBlock[] blocks = blockModel.getCodeBlocksContaining(address, TaskMonitor.DUMMY);
if (blocks != null && blocks.length > 0) {
return super.getVertexIdForAddress(blocks[0].getFirstStartAddress());
}
}
catch (CancelledException e) {
// Will not happen with dummyMonitor
// Model has already done the work when the graph was created
}
return super.getVertexIdForAddress(address);
}
@Override
protected List<String> getVertices(AddressSetView addrSet) {
if (addrSet.isEmpty()) {
return Collections.emptyList();
}
// Identify all blocks which have an entry point within the selection address set
ArrayList<String> blockList = new ArrayList<String>();
try {
SymbolTable symTable = program.getSymbolTable();
CodeBlockIterator cbIter =
blockModel.getCodeBlocksContaining(addrSet, TaskMonitor.DUMMY);
while (cbIter.hasNext()) {
CodeBlock block = cbIter.next();
String addrString;
Address addr = block.getFirstStartAddress();
if (addr.isExternalAddress()) {
Symbol s = symTable.getPrimarySymbol(addr);
addrString = s.getName(true);
}
else {
addrString = addr.toString();
}
blockList.add(addrString);
}
}
catch (CancelledException e) {
// Will not happen with dummyMonitor
// Model has already done the work when the graph was created
}
return blockList;
}
@Override
protected AddressSet getAddressSetForVertices(List<String> vertexIds) {
AddressSet addrSet = new AddressSet();
try {
// for each address string, translate it into a block
// and add it to the address set.
for (String vertexId : vertexIds) {
Address blockAddr = getAddressForVertexId(vertexId);
if (!isValidAddress(blockAddr)) {
continue;
}
CodeBlock blocks[] = null;
if (blockModel != null) {
CodeBlock block = blockModel.getCodeBlockAt(blockAddr, TaskMonitor.DUMMY);
if (block != null) {
blocks = new CodeBlock[1];
blocks[0] = block;
}
else {
blocks = blockModel.getCodeBlocksContaining(blockAddr, TaskMonitor.DUMMY);
}
}
if (blocks != null && blocks.length > 0) {
for (CodeBlock block : blocks) {
addrSet.add(block);
}
}
else {
addrSet.addRange(blockAddr, blockAddr);
}
}
}
catch (CancelledException e) {
// Will not happen with dummyMonitor
// Model has already done the work when the graph was created
}
return addrSet;
}
protected boolean isValidAddress(Address addr) {
if (addr == null || program == null) {
return false;
}
return program.getMemory().contains(addr) || addr.isExternalAddress();
}
}

View File

@ -0,0 +1,334 @@
/* ###
* 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.ArrayList;
import java.util.List;
import docking.ActionContext;
import docking.action.DockingAction;
import docking.action.ToggleDockingAction;
import docking.action.builder.ActionBuilder;
import docking.action.builder.ToggleActionBuilder;
import ghidra.app.CorePluginPackage;
import ghidra.app.events.ProgramLocationPluginEvent;
import ghidra.app.events.ProgramSelectionPluginEvent;
import ghidra.app.plugin.PluginCategoryNames;
import ghidra.app.plugin.ProgramPlugin;
import ghidra.app.plugin.core.graph.GraphDisplayBrokerListener;
import ghidra.app.services.*;
import ghidra.framework.options.*;
import ghidra.framework.plugintool.PluginInfo;
import ghidra.framework.plugintool.PluginTool;
import ghidra.framework.plugintool.util.PluginStatus;
import ghidra.program.model.block.CodeBlockModel;
import ghidra.service.graph.GraphDisplayProvider;
import ghidra.util.HelpLocation;
import ghidra.util.Msg;
import ghidra.util.exception.NotFoundException;
import ghidra.util.task.TaskLauncher;
/**
* 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
* displays the assembly for for each basic block, whereas the "block flow" action generates a graph
* that displays the symbol or address at the start of the basic block. This plugin also
* generates call graphs, using either the default subroutine model or one that the user chooses.
*/
//@formatter:off
@PluginInfo(
status = PluginStatus.RELEASED,
packageName = CorePluginPackage.NAME,
category = PluginCategoryNames.GRAPH,
shortDescription = "Program graph generator",
description = "This plugin provides actions for creating and managing program graphs"
+ " (block graphs and call graphs)."
+ "Once a graph is created, it uses the currenly selected graph output to display "
+ "or export the graph. The plugin "
+ "also provides event handling to facilitate interaction between "
+ "the graph and the tool.",
servicesRequired = { GoToService.class, BlockModelService.class, GraphDisplayBroker.class },
eventsProduced = { ProgramLocationPluginEvent.class, ProgramSelectionPluginEvent.class }
)
//@formatter:on
public class ProgramGraphPlugin extends ProgramPlugin
implements OptionsChangeListener, BlockModelServiceListener, GraphDisplayBrokerListener {
private static final String MAX_CODE_LINES_DISPLAYED = "Max Code Lines Displayed";
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";
public static final String MENU_GRAPH = "&Graph";
private BlockModelService blockModelService;
private List<DockingAction> subUsingGraphActions = new ArrayList<>();
private ToggleDockingAction reuseGraphAction;
private ToggleDockingAction appendGraphAction;
private boolean reuseGraph = false;
private boolean appendToGraph = false;
private boolean graphEntryPointNexus = false;
private int codeLimitPerBlock = 10;
private ToggleDockingAction forceLocationVisibleAction;
private GraphDisplayBroker broker;
private GraphDisplayProvider defaultGraphService;
public ProgramGraphPlugin(PluginTool tool) {
super(tool, true, true);
intializeOptions();
}
private void intializeOptions() {
HelpLocation help = new HelpLocation(getName(), "Graph_Option");
ToolOptions options = tool.getOptions("Graph");
options.registerOption(MAX_CODE_LINES_DISPLAYED, codeLimitPerBlock, help,
"Specifies the maximum number of instructions to display in each graph " +
"node in a Code Flow Graph.");
options.registerOption(REUSE_GRAPH, false, help,
"Determines whether the graph will reuse the active graph window when displaying graphs.");
options.registerOption(GRAPH_ENTRY_POINT_NEXUS, false, help,
"Add a dummy node at the root of the graph and adds dummy edges to each node that has " +
"no incoming edges.");
options.registerOption(FORCE_LOCATION_DISPLAY_OPTION, false, help,
"Specifies whether or not " +
"graph displays should force the visible graph to pan and/or scale to ensure that focused " +
"locations are visible.");
setOptions(options);
options.addOptionsChangeListener(this);
options.setOptionsHelpLocation(new HelpLocation(getName(), "Graph_Option"));
}
@Override
protected void init() {
broker = tool.getService(GraphDisplayBroker.class);
broker.addGraphDisplayBrokerListener(this);
defaultGraphService = broker.getDefaultGraphDisplayProvider();
blockModelService = tool.getService(BlockModelService.class);
blockModelService.addListener(this);
createActions();
}
@Override
public void dispose() {
super.dispose();
if (blockModelService != null) {
blockModelService.removeListener(this);
blockModelService = null;
}
}
/**
* Notification that an option changed.
*
* @param options
* options object containing the property that changed
* @param optionName
* name of option that changed
* @param oldValue
* old value of the option
* @param newValue
* new value of the option
*/
@Override
public void optionsChanged(ToolOptions options, String optionName, Object oldValue,
Object newValue) {
setOptions(options);
}
private void setOptions(Options options) {
codeLimitPerBlock = options.getInt(MAX_CODE_LINES_DISPLAYED, codeLimitPerBlock);
graphEntryPointNexus = options.getBoolean(GRAPH_ENTRY_POINT_NEXUS, false);
reuseGraph = options.getBoolean(REUSE_GRAPH, false);
if (reuseGraphAction != null) {
reuseGraphAction.setSelected(reuseGraph);
}
// 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")
.menuGroup("Graph", "A")
.onAction(c -> graphBlockFlow())
.enabledWhen(this::canGraph)
.buildAndInstall(tool);
new ActionBuilder("Graph Code Flow", getName())
.menuPath(MENU_GRAPH, "C&ode Flow")
.menuGroup("Graph", "B")
.onAction(c -> graphCodeFlow())
.enabledWhen(this::canGraph)
.buildAndInstall(tool);
new ActionBuilder("Graph Calls Using Default Model", getName())
.menuPath(MENU_GRAPH, "&Calls")
.menuGroup("Graph", "C")
.onAction(c -> graphSubroutines())
.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")
.menuGroup("Graph Options")
.onAction(c -> toggleForceLocationVisible())
.enabledWhen(this::canGraph)
.buildAndInstall(tool);
updateSubroutineActions();
}
private boolean canGraph(ActionContext context) {
return currentProgram != null && defaultGraphService != null;
}
private void toggleForceLocationVisible() {
ToolOptions options = tool.getOptions("Graph");
options.setBoolean(FORCE_LOCATION_DISPLAY_OPTION, forceLocationVisibleAction.isSelected());
}
private void updateAppendAndReuseGraph() {
appendToGraph = appendGraphAction.isSelected();
if (appendToGraph && !reuseGraph) {
reuseGraph = true;
reuseGraphAction.setSelected(true);
}
}
private void updateSubroutineActions() {
// Remove old actions
for (DockingAction action : subUsingGraphActions) {
tool.removeAction(action);
}
// Create subroutine graph actions for each subroutine provided by BlockModelService
String[] subModels =
blockModelService.getAvailableModelNames(BlockModelService.SUBROUTINE_MODEL);
if (subModels.length <= 1) { // Not needed if only one subroutine model
return;
}
HelpLocation helpLoc = new HelpLocation(getName(), "Graph_Calls_Using_Model");
for (String blockModelName : subModels) {
DockingAction action = buildGraphActionWithModel(blockModelName, helpLoc);
subUsingGraphActions.add(action);
}
tool.setMenuGroup(new String[] { "Graph", "Calls Using Model" }, "Graph");
}
private DockingAction buildGraphActionWithModel(String blockModelName, HelpLocation helpLoc) {
return new ActionBuilder("Graph Calls using " + blockModelName, getName())
.menuPath("Graph", "Calls Using Model", blockModelName)
.menuGroup("Graph")
.helpLocation(helpLoc)
.onAction(c -> graphSubroutinesUsing(blockModelName))
.enabledWhen(this::canGraph)
.buildAndInstall(tool);
}
private void graphBlockFlow() {
graph("Flow Graph", blockModelService.getActiveBlockModelName(), false);
}
private void graphCodeFlow() {
graph("Code Graph", blockModelService.getActiveBlockModelName(), true);
}
private void graphSubroutines() {
graph("Call Graph", blockModelService.getActiveSubroutineModelName(), false);
}
private void graphSubroutinesUsing(String modelName) {
graph("Call Graph", modelName, false);
}
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, model, defaultGraphService);
task.setCodeLimitPerBlock(codeLimitPerBlock);
new TaskLauncher(task, tool.getToolFrame());
}
catch (NotFoundException e) {
Msg.showError(this, null, "Error That Can't Happen",
"Can't find a block model from a name that we got from the existing block models!");
}
}
String getProgramName() {
return currentProgram != null ? currentProgram.getName() : null;
}
@Override
public void modelAdded(String modeName, int modelType) {
if (modelType == BlockModelService.SUBROUTINE_MODEL) {
updateSubroutineActions();
}
}
@Override
public void modelRemoved(String modeName, int modelType) {
if (modelType == BlockModelService.SUBROUTINE_MODEL) {
updateSubroutineActions();
}
}
@Override
public void providersChanged() {
defaultGraphService = broker.getDefaultGraphDisplayProvider();
}
}

View File

@ -0,0 +1,131 @@
/* ###
* 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 org.junit.After;
import org.junit.Before;
import ghidra.app.plugin.core.blockmodel.BlockModelServicePlugin;
import ghidra.app.plugin.core.codebrowser.CodeBrowserPlugin;
import ghidra.app.services.BlockModelService;
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.mem.MemoryAccessException;
import ghidra.test.*;
public class AbstractBlockGraphTest extends AbstractGhidraHeadedIntegrationTest {
protected PluginTool tool;
protected ProgramDB program;
protected TestEnv env;
protected BlockModelService blockModelService;
private ToyProgramBuilder builder;
protected CodeBrowserPlugin codeBrowser;
protected Address addr(long addr) {
return builder.getAddress(addr);
}
@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);
blockModelService = tool.getService(BlockModelService.class);
}
protected void installPlugins() throws PluginException {
tool.addPlugin(CodeBrowserPlugin.class.getName());
tool.addPlugin(BlockModelServicePlugin.class.getName());
codeBrowser = env.getPlugin(CodeBrowserPlugin.class);
}
protected void openProgram() throws Exception {
builder = new ToyProgramBuilder("sample", true);
builder.createMemory("caller", "0x01002200", 8);
builder.createMemory("simple", "0x01002239", 8);
buildCallerFunction(builder);
buildSimpleFunction(builder);
program = builder.getProgram();
}
private void buildCallerFunction(ToyProgramBuilder builder) throws MemoryAccessException {
// just a function that calls another
builder.addBytesNOP("0x01002200", 1);
builder.addBytesCall("0x01002201", "0x01002239");// jump to C
builder.addBytesReturn("0x01002203");
builder.disassemble("0x01002200", 4, true);
builder.createFunction("0x01002200");
builder.createLabel("0x01002200", "entry");// function label
}
private void buildSimpleFunction(ToyProgramBuilder builder) throws MemoryAccessException {
// just a function to render in the graph so that we can clear out settings/cache
// 01002239
/*
A
|->B
C
*/
// A
builder.addBytesNOP("0x01002239", 1);
builder.addBytesBranchConditional("0x0100223a", "0x0100223e");// jump to C
// B
builder.addBytesNOP("0x0100223c", 1);
builder.addBytesNOP("0x0100223d", 1);// fallthrough to C
// C
builder.addBytesNOP("0x0100223e", 1);
builder.addBytesReturn("0x0100223f");
builder.disassemble("0x01002239", 8, true);
builder.createFunction("0x01002239");
builder.createLabel("0x01002239", "simple");// function label
}
}

View File

@ -0,0 +1,118 @@
/* ###
* 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.*;
import org.junit.Test;
import ghidra.app.events.ProgramSelectionPluginEvent;
import ghidra.program.model.address.AddressSet;
import ghidra.program.model.block.CodeBlockModel;
import ghidra.program.util.ProgramLocation;
import ghidra.program.util.ProgramSelection;
import ghidra.service.graph.AttributedGraph;
import ghidra.util.task.TaskMonitor;
public class BlockGraphEventTest extends AbstractBlockGraphTest {
private TestGraphDisplay display;
private AttributedGraph graph;
@Override
public void setUp() throws Exception {
super.setUp();
String modelName = blockModelService.getActiveBlockModelName();
CodeBlockModel model =
blockModelService.getNewModelByName(modelName, program, true);
TestGraphService graphService = new TestGraphService();
BlockGraphTask task =
new BlockGraphTask("test", false, false, false, false,
tool, null, model, graphService);
task.monitoredRun(TaskMonitor.DUMMY);
display = (TestGraphDisplay) graphService.getGraphDisplay(true, TaskMonitor.DUMMY);
graph = display.getGraph();
}
@Test
public void testGhidraLocationChanged() {
codeBrowser.goTo(new ProgramLocation(program, addr(0x1002239)));
assertEquals("01002239", display.getFocusedVertex());
codeBrowser.goTo(new ProgramLocation(program, addr(0x1002200)));
assertEquals("01002200", display.getFocusedVertex());
// also try a location that is not the start of a block
codeBrowser.goTo(new ProgramLocation(program, addr(0x100223a)));
assertEquals("01002239", display.getFocusedVertex());
}
private AddressSet addrSet(long start, long end) {
return new AddressSet(addr(start), addr(end));
}
@Test
public void testGhidraSelectionChanged() {
setSelection(addrSet(0x1002239, 0x1002241));
Set<String> selected = new HashSet<>(display.getSelectedVertices());
assertEquals(3, selected.size());
assertTrue(selected.contains("01002239"));
assertTrue(selected.contains("0100223c"));
assertTrue(selected.contains("0100223e"));
setSelection(new AddressSet(addr(0x1002200), addr(0x1002210)));
selected = new HashSet<>(display.getSelectedVertices());
assertEquals(2, selected.size());
assertTrue(selected.contains("01002200"));
assertTrue(selected.contains("01002203"));
}
@Test
public void testGraphNodeFocused() {
display.focusChanged("01002203");
assertEquals(addr(0x01002203), codeBrowser.getCurrentLocation().getAddress());
display.focusChanged("0100223c");
assertEquals(addr(0x0100223c), codeBrowser.getCurrentLocation().getAddress());
}
@Test
public void testGraphNodesSelected() {
display.selectionChanged(Arrays.asList("01002239", "0100223c"));
ProgramSelection selection = codeBrowser.getCurrentSelection();
assertEquals(addr(0x01002239), selection.getMinAddress());
assertEquals(addr(0x0100223d), selection.getMaxAddress());
display.selectionChanged(Arrays.asList("01002200", "01002203"));
selection = codeBrowser.getCurrentSelection();
assertEquals(addr(0x01002200), selection.getMinAddress());
assertEquals(addr(0x01002204), selection.getMaxAddress());
}
private void setSelection(final AddressSet addrSet) {
runSwing(
() -> tool.firePluginEvent(
new ProgramSelectionPluginEvent("test", new ProgramSelection(addrSet), program)),
true);
}
}

View File

@ -0,0 +1,234 @@
/* ###
* 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.Map;
import org.junit.Test;
import ghidra.program.model.block.CodeBlockModel;
import ghidra.program.util.ProgramSelection;
import ghidra.service.graph.*;
import ghidra.util.task.TaskMonitor;
public class BlockGraphTaskTest extends AbstractBlockGraphTest {
private static final boolean SHOW_CODE = true;
private static final boolean DONT_SHOW_CODE = false;
@Test
public void testBlockGraph() throws Exception {
String modelName = blockModelService.getActiveBlockModelName();
CodeBlockModel model =
blockModelService.getNewModelByName(modelName, program, true);
TestGraphService graphService = new TestGraphService();
BlockGraphTask task =
new BlockGraphTask("test", false, DONT_SHOW_CODE, false, false,
tool, null, model, graphService);
task.monitoredRun(TaskMonitor.DUMMY);
TestGraphDisplay display =
(TestGraphDisplay) graphService.getGraphDisplay(true, TaskMonitor.DUMMY);
AttributedGraph graph = display.getGraph();
assertEquals(5, graph.getVertexCount());
AttributedVertex v1 = graph.getVertex("01002200");
AttributedVertex v2 = graph.getVertex("01002203");
AttributedVertex v3 = graph.getVertex("01002239");
AttributedVertex v4 = graph.getVertex("0100223c");
AttributedVertex v5 = graph.getVertex("0100223e");
assertNotNull(v1);
assertNotNull(v2);
assertNotNull(v3);
assertNotNull(v4);
assertNotNull(v5);
assertEquals(5, graph.getEdgeCount());
AttributedEdge e1 = graph.getEdge(v1, v2);
AttributedEdge e2 = graph.getEdge(v1, v3);
AttributedEdge e3 = graph.getEdge(v3, v4);
AttributedEdge e4 = graph.getEdge(v4, v5);
AttributedEdge e5 = graph.getEdge(v3, v5);
assertNotNull(e1);
assertNotNull(e2);
assertNotNull(e3);
assertNotNull(e4);
assertNotNull(e5);
Map<String, String> map = v1.getAttributeMap();
assertEquals(2, map.size());
assertTrue(map.containsKey("Name"));
assertTrue(map.containsKey("VertexType"));
assertEquals("Entry", v3.getAttribute("VertexType"));
assertEquals("Body", v4.getAttribute("VertexType"));
assertEquals("Exit", v5.getAttribute("VertexType"));
map = e1.getAttributeMap();
assertEquals(2, map.size());
assertTrue(map.containsKey("Name"));
assertTrue(map.containsKey("EdgeType"));
assertEquals("Fall-Through", e3.getAttribute("EdgeType"));
assertEquals("Fall-Through", e4.getAttribute("EdgeType"));
assertEquals("Conditional-Jump", e5.getAttribute("EdgeType"));
}
@Test
public void testCodeBlockGraph() throws Exception {
String modelName = blockModelService.getActiveBlockModelName();
CodeBlockModel model =
blockModelService.getNewModelByName(modelName, program, true);
TestGraphService graphService = new TestGraphService();
BlockGraphTask task =
new BlockGraphTask("test", false, SHOW_CODE, false, false,
tool, null, model, graphService);
task.monitoredRun(TaskMonitor.DUMMY);
TestGraphDisplay display =
(TestGraphDisplay) graphService.getGraphDisplay(true, TaskMonitor.DUMMY);
AttributedGraph graph = display.getGraph();
assertEquals(5, graph.getVertexCount());
AttributedVertex v1 = graph.getVertex("01002200");
AttributedVertex v2 = graph.getVertex("01002203");
AttributedVertex v3 = graph.getVertex("01002239");
AttributedVertex v4 = graph.getVertex("0100223c");
AttributedVertex v5 = graph.getVertex("0100223e");
assertNotNull(v1);
assertNotNull(v2);
assertNotNull(v3);
assertNotNull(v4);
assertNotNull(v5);
assertEquals(5, graph.getEdgeCount());
AttributedEdge e1 = graph.getEdge(v1, v2);
AttributedEdge e2 = graph.getEdge(v1, v3);
AttributedEdge e3 = graph.getEdge(v3, v4);
AttributedEdge e4 = graph.getEdge(v4, v5);
AttributedEdge e5 = graph.getEdge(v3, v5);
assertNotNull(e1);
assertNotNull(e2);
assertNotNull(e3);
assertNotNull(e4);
assertNotNull(e5);
Map<String, String> map = v3.getAttributeMap();
assertEquals(4, map.size());
assertTrue(map.containsKey("Name"));
assertTrue(map.containsKey("VertexType"));
assertTrue(map.containsKey("Code"));
assertTrue(map.containsKey("Symbols"));
assertEquals("simple", v3.getAttribute("Symbols"));
assertEquals("nop #0x1\nbreq 0x0100223e", v3.getAttribute("Code"));
}
@Test
public void testCallGraph() throws Exception {
String modelName = blockModelService.getActiveSubroutineModelName();
CodeBlockModel model =
blockModelService.getNewModelByName(modelName, program, true);
TestGraphService graphService = new TestGraphService();
BlockGraphTask task =
new BlockGraphTask("test", false, false, false, false,
tool, null, model, graphService);
task.monitoredRun(TaskMonitor.DUMMY);
TestGraphDisplay display =
(TestGraphDisplay) graphService.getGraphDisplay(true, TaskMonitor.DUMMY);
AttributedGraph graph = display.getGraph();
assertEquals(2, graph.getVertexCount());
AttributedVertex v1 = graph.getVertex("01002200");
AttributedVertex v2 = graph.getVertex("01002239");
assertNotNull(v1);
assertNotNull(v2);
assertEquals(1, graph.getEdgeCount());
AttributedEdge e1 = graph.getEdge(v1, v2);
assertNotNull(e1);
Map<String, String> map = v1.getAttributeMap();
assertEquals(2, map.size());
assertTrue(map.containsKey("Name"));
assertTrue(map.containsKey("VertexType"));
assertEquals("Entry", v1.getAttribute("VertexType"));
assertEquals("Entry", v2.getAttribute("VertexType"));
map = e1.getAttributeMap();
assertEquals(2, map.size());
assertTrue(map.containsKey("Name"));
assertTrue(map.containsKey("EdgeType"));
assertEquals("Unconditional-Call", e1.getAttribute("EdgeType"));
}
@Test
public void testBlockGraphWithSelection() throws Exception {
String modelName = blockModelService.getActiveBlockModelName();
CodeBlockModel model =
blockModelService.getNewModelByName(modelName, program, true);
TestGraphService graphService = new TestGraphService();
ProgramSelection sel = new ProgramSelection(addr(0x1002239), addr(0x1002247));
BlockGraphTask task =
new BlockGraphTask("test", false, DONT_SHOW_CODE, false, false,
tool, sel, model, graphService);
task.monitoredRun(TaskMonitor.DUMMY);
TestGraphDisplay display =
(TestGraphDisplay) graphService.getGraphDisplay(true, TaskMonitor.DUMMY);
AttributedGraph graph = display.getGraph();
assertEquals(3, graph.getVertexCount());
AttributedVertex v1 = graph.getVertex("01002200");
AttributedVertex v2 = graph.getVertex("01002203");
AttributedVertex v3 = graph.getVertex("01002239");
AttributedVertex v4 = graph.getVertex("0100223c");
AttributedVertex v5 = graph.getVertex("0100223e");
assertNull(v1);
assertNull(v2);
assertNotNull(v3);
assertNotNull(v4);
assertNotNull(v5);
assertEquals(3, graph.getEdgeCount());
AttributedEdge e3 = graph.getEdge(v3, v4);
AttributedEdge e4 = graph.getEdge(v4, v5);
AttributedEdge e5 = graph.getEdge(v3, v5);
assertNotNull(e3);
assertNotNull(e4);
assertNotNull(e5);
}
}

View File

@ -0,0 +1,112 @@
/* ###
* 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 ghidra.service.graph.*;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;
public class TestGraphDisplay implements GraphDisplay {
private Set<String> definedVertexAttributes = new HashSet<>();
private Set<String> definedEdgeAttributes = new HashSet<>();
private String vertexAttributeName;
private AttributedGraph graph;
private String graphDescription;
private GraphDisplayListener listener;
private String currentFocusedVertex;
private List<String> currentSelection;
@Override
public void setGraphDisplayListener(GraphDisplayListener listener) {
this.listener = listener;
}
@Override
public void setLocation(String vertexID) {
currentFocusedVertex = vertexID;
}
public String getFocusedVertex() {
return currentFocusedVertex;
}
@Override
public void selectVertices(List<String> vertexList) {
currentSelection = vertexList;
}
public List<String> getSelectedVertices() {
return currentSelection;
}
@Override
public void close() {
// nothing
}
@Override
public void defineVertexAttribute(String name) {
definedVertexAttributes.add(name);
}
@Override
public void defineEdgeAttribute(String name) {
definedEdgeAttributes.add(name);
}
@Override
public void setVertexLabel(String attributeName, int alignment, int size, boolean monospace,
int maxLines) {
vertexAttributeName = attributeName;
}
@Override
public void setGraph(AttributedGraph graph, String description, boolean append,
TaskMonitor monitor)
throws CancelledException {
this.graph = graph;
this.graphDescription = description;
}
@Override
public void clear() {
// nothing
}
@Override
public void updateVertexName(String id, String newName) {
// nothing
}
@Override
public String getGraphDescription() {
return graphDescription;
}
public AttributedGraph getGraph() {
return graph;
}
public void focusChanged(String vertexId) {
listener.locationChanged(vertexId);
}
public void selectionChanged(List<String> vertexIds) {
listener.selectionChanged(vertexIds);
}
}

View File

@ -0,0 +1,62 @@
/* ###
* 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.framework.options.Options;
import ghidra.framework.plugintool.PluginTool;
import ghidra.service.graph.GraphDisplay;
import ghidra.service.graph.GraphDisplayProvider;
import ghidra.util.HelpLocation;
import ghidra.util.exception.GraphException;
import ghidra.util.task.TaskMonitor;
public class TestGraphService implements GraphDisplayProvider {
private TestGraphDisplay testDisplay = new TestGraphDisplay();
@Override
public String getName() {
return "Test Graph Service";
}
@Override
public GraphDisplay getGraphDisplay(boolean reuseGraph,
TaskMonitor monitor) throws GraphException {
return testDisplay;
}
@Override
public void initialize(PluginTool tool, Options options) {
// nothing
}
@Override
public void optionsChanged(Options options) {
// nothing
}
@Override
public void dispose() {
// nothing
}
@Override
public HelpLocation getHelpLocation() {
return null;
}
}

View File

@ -248,6 +248,7 @@ public abstract class ComponentProvider implements HelpDescriptor, ActionContext
* Removes this provider from the tool.
*/
public void removeFromTool() {
dockingTool.removeAction(showProviderAction);
dockingTool.removeComponentProvider(this);
}

View File

@ -594,7 +594,6 @@ public abstract class DockingAction implements DockingActionIf {
inceptionInformation = "";
return;
}
inceptionInformation = getInceptionFromTheFirstClassThatIsNotUsOrABuilder();
}

View File

@ -117,7 +117,6 @@ public abstract class AbstractActionBuilder<T extends DockingActionIf, C extends
* The mnemonic for the menu action (optional)
*/
private int menuMnemonic = MenuData.NO_MNEMONIC;
/**
* The icon for the menu item (optional)
*/

View File

@ -53,13 +53,12 @@ public class MultiActionBuilder
@Override
public MultiActionDockingAction build() {
validate();
MultiActionDockingAction action =
new MultiActionDockingAction(name, owner) {
@Override
public void actionPerformed(ActionContext context) {
actionCallback.accept(context);
}
};
MultiActionDockingAction action = new MultiActionDockingAction(name, owner) {
@Override
public void actionPerformed(ActionContext context) {
actionCallback.accept(context);
}
};
decorateAction(action);
action.setActions(actionList);
action.setPerformActionOnButtonClick(performActionOnButtonClick);
@ -96,7 +95,12 @@ public class MultiActionBuilder
@Override
protected void validate() {
super.validate();
// if the MultiAction performs an action when the main button is presseed, make sure that
// an action callback has been defined in before building (which is what super validate
// does). Otherwise, don't force the client to define an action callback if it won't be used.
if (performActionOnButtonClick) {
super.validate();
}
if (actionList == null) {
throw new IllegalStateException("No ActionList has been set");
}

View File

@ -15,8 +15,12 @@
*/
package docking.action.builder;
import java.util.ArrayList;
import java.util.List;
import java.util.function.BiConsumer;
import javax.swing.Icon;
import docking.ActionContext;
import docking.menu.*;
import docking.widgets.EventTrigger;
@ -30,7 +34,9 @@ public class MultiStateActionBuilder<T> extends
AbstractActionBuilder<MultiStateDockingAction<T>, ActionContext, MultiStateActionBuilder<T>> {
private BiConsumer<ActionState<T>, EventTrigger> actionStateChangedCallback;
private boolean performActionOnButtonClick;
private boolean performActionOnButtonClick = false;
private List<ActionState<T>> states = new ArrayList<>();
/**
* Builder constructor
@ -73,6 +79,41 @@ public class MultiStateActionBuilder<T> extends
return self();
}
/**
* Add an action state
*
* @param displayName the name to appear in the action menu
* @param icon the icon to appear in the action menu
* @param userData the data associated with this state
* @return this MultiActionDockingActionBuilder (for chaining)
*/
public MultiStateActionBuilder<T> addState(String displayName, Icon icon, T userData) {
states.add(new ActionState<T>(displayName, icon, userData));
return self();
}
/**
* Add an action state
*
* @param actionState the action state to add
* @return this MultiActionDockingActionBuilder (for chaining)
*/
public MultiStateActionBuilder<T> addState(ActionState<T> actionState) {
states.add(actionState);
return self();
}
/**
* Add a list of action states
*
* @param list a list of ActionStates;
* @return this MultiActionDockingActionBuilder (for chaining)
*/
public MultiStateActionBuilder<T> addStates(List<ActionState<T>> list) {
states.addAll(list);
return self();
}
@Override
public MultiStateDockingAction<T> build() {
validate();
@ -84,7 +125,7 @@ public class MultiStateActionBuilder<T> extends
EventTrigger trigger) {
actionStateChangedCallback.accept(newActionState, trigger);
}
@Override
protected void doActionPerformed(ActionContext context) {
if (actionCallback != null) {
@ -93,11 +134,14 @@ public class MultiStateActionBuilder<T> extends
}
};
for (ActionState<T> actionState : states) {
action.addActionState(actionState);
}
decorateAction(action);
action.setPerformActionOnPrimaryButtonClick(performActionOnButtonClick);
return action;
}
@Override
protected void validate() {

View File

@ -14,6 +14,7 @@
* limitations under the License.
*/
package docking.action.builder;
import docking.ActionContext;
import docking.action.ToggleDockingAction;
@ -69,4 +70,3 @@ public class ToggleActionBuilder extends
}
}

View File

@ -74,7 +74,7 @@ public class HelpManager implements HelpService {
private boolean isValidHelp;
private boolean hasBeenDisplayed;
private Set<Object> excludedFromHelp = new HashSet<>();
private Set<Object> excludedFromHelp = Collections.newSetFromMap(new WeakHashMap<>());
/**
* Constructor.

View File

@ -17,6 +17,8 @@ dependencies {
compile "org.apache.logging.log4j:log4j-core:2.12.1"
compile "org.apache.commons:commons-collections4:4.1"
compile "org.apache.commons:commons-lang3:3.9"
compile "org.apache.commons:commons-text:1.6"
compile "commons-io:commons-io:2.6"
compileOnly "junit:junit:4.12"

View File

@ -1,6 +1,5 @@
/* ###
* IP: GHIDRA
* REVIEWED: YES
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -22,17 +21,21 @@ package ghidra.util.exception;
public class GraphException extends UsrException {
/**
* Default constructor
*/
public GraphException() {
super("Graph Error.");
}
* Default constructor
*/
public GraphException() {
super("Graph Error.");
}
/**
* Constructor
* @param message detailed message
*/
public GraphException(String message) {
super(message);
}
/**
* Constructor
* @param message detailed message
*/
public GraphException(String message) {
super(message);
}
public GraphException(String title, Throwable cause) {
super(title, cause);
}
}

View File

@ -33,72 +33,58 @@ import resources.icons.TranslateIcon;
*/
public class Icons {
public static final ImageIcon EMPTY_ICON = ResourceManager.loadImage("images/EmptyIcon16.gif");
public static final ImageIcon EMPTY_ICON = get("images/EmptyIcon16.gif");
public static final ImageIcon HELP_ICON =
ResourceManager.loadImage("images/help-browser.png");
public static final ImageIcon HELP_ICON = get("images/help-browser.png");
public static final ImageIcon ADD_ICON = ResourceManager.loadImage("images/Plus2.png");
public static final ImageIcon ADD_ICON = get("images/Plus2.png");
public static final ImageIcon COLLAPSE_ALL_ICON =
ResourceManager.loadImage("images/collapse_all.png");
public static final ImageIcon EXPAND_ALL_ICON =
ResourceManager.loadImage("images/expand_all.png");
public static final ImageIcon COLLAPSE_ALL_ICON = get("images/collapse_all.png");
public static final ImageIcon EXPAND_ALL_ICON = get("images/expand_all.png");
public static final ImageIcon CONFIGURE_FILTER_ICON =
ResourceManager.loadImage("images/exec.png");
public static final ImageIcon DELETE_ICON = ResourceManager.loadImage("images/error.png");
public static final ImageIcon ERROR_ICON =
ResourceManager.loadImage("images/emblem-important.png");
public static final ImageIcon CONFIGURE_FILTER_ICON = get("images/exec.png");
public static final ImageIcon DELETE_ICON = get("images/error.png");
public static final ImageIcon ERROR_ICON = get("images/emblem-important.png");
public static final ImageIcon NAVIGATE_ON_INCOMING_EVENT_ICON =
ResourceManager.loadImage("images/locationIn.gif");
public static final ImageIcon NAVIGATE_ON_OUTGOING_EVENT_ICON =
ResourceManager.loadImage("images/locationOut.gif");
public static final ImageIcon NAVIGATE_ON_INCOMING_EVENT_ICON = get("images/locationIn.gif");
public static final ImageIcon NAVIGATE_ON_OUTGOING_EVENT_ICON = get("images/locationOut.gif");
public static final ImageIcon NOT_ALLOWED_ICON = ResourceManager.loadImage("images/no.png");
public static final ImageIcon OPEN_FOLDER_ICON =
ResourceManager.loadImage("images/openSmallFolder.png");
public static final ImageIcon REFRESH_ICON = ResourceManager.loadImage("images/reload3.png");
public static final ImageIcon NOT_ALLOWED_ICON = get("images/no.png");
public static final ImageIcon OPEN_FOLDER_ICON = get("images/openSmallFolder.png");
public static final ImageIcon REFRESH_ICON = get("images/reload3.png");
public static final ImageIcon SORT_ASCENDING_ICON =
ResourceManager.loadImage("images/sortascending.png");
public static final ImageIcon SORT_DESCENDING_ICON =
ResourceManager.loadImage("images/sortdescending.png");
public static final ImageIcon SORT_ASCENDING_ICON = get("images/sortascending.png");
public static final ImageIcon SORT_DESCENDING_ICON = get("images/sortdescending.png");
public static final ImageIcon STOP_ICON = ResourceManager.loadImage("images/process-stop.png");
public static final ImageIcon STRONG_WARNING_ICON =
ResourceManager.loadImage("images/software-update-urgent.png");
public static final ImageIcon STOP_ICON = get("images/process-stop.png");
public static final ImageIcon STRONG_WARNING_ICON = get("images/software-update-urgent.png");
public static final ImageIcon LEFT_ICON = ResourceManager.loadImage("images/left.png");
public static final ImageIcon RIGHT_ICON = ResourceManager.loadImage("images/right.png");
public static final ImageIcon LEFT_ICON = get("images/left.png");
public static final ImageIcon RIGHT_ICON = get("images/right.png");
/** An version of the LEFT_ICON with a different color */
public static final ImageIcon LEFT_ALTERNATE_ICON =
ResourceManager.loadImage("images/left.alternate.png");
public static final ImageIcon LEFT_ALTERNATE_ICON = get("images/left.alternate.png");
/** An version of the RIGHT_ICON with a different color */
public static final ImageIcon RIGHT_ALTERNATE_ICON =
ResourceManager.loadImage("images/right.alternate.png");
public static final ImageIcon RIGHT_ALTERNATE_ICON = get("images/right.alternate.png");
public static final ImageIcon SAVE_AS = ResourceManager.getImageIcon(
new DotDotDotIcon(ResourceManager.loadImage("images/Disk.png")));
public static final ImageIcon SAVE_AS =
ResourceManager.getImageIcon(new DotDotDotIcon(get("images/Disk.png")));
public static final ImageIcon MAKE_SELECTION_ICON =
ResourceManager.loadImage("images/text_align_justify.png");
public static final ImageIcon MAKE_SELECTION_ICON = get("images/text_align_justify.png");
// Not necessarily re-usable, but this is needed for the help system; these should
// probably be moved to the client that uses them, while updating the
// help system to use them there.
public static final ImageIcon ARROW_DOWN_RIGHT_ICON = ResourceManager.getImageIcon(
new RotateIcon(ResourceManager.loadImage("images/viewmagfit.png"), 90));
public static final ImageIcon ARROW_UP_LEFT_ICON = ResourceManager.getImageIcon(
new RotateIcon(ResourceManager.loadImage("images/viewmagfit.png"), 275));
public static final ImageIcon FILTER_NOT_ACCEPTED_ICON = ResourceManager.getImageIcon(
new MultiIcon(ResourceManager.loadImage("images/flag.png"), new TranslateIcon(
public static final ImageIcon ARROW_DOWN_RIGHT_ICON =
ResourceManager.getImageIcon(new RotateIcon(get("images/viewmagfit.png"), 90));
public static final ImageIcon ARROW_UP_LEFT_ICON =
ResourceManager.getImageIcon(new RotateIcon(get("images/viewmagfit.png"), 275));
public static final ImageIcon FILTER_NOT_ACCEPTED_ICON =
ResourceManager.getImageIcon(new MultiIcon(get("images/flag.png"), new TranslateIcon(
ResourceManager.loadImage("images/dialog-cancel.png", 10, 10), 6, 6)));
public static final ImageIcon APPLY_BLOCKED_MATCH_ICON = ResourceManager.getImageIcon(
new MultiIcon(ResourceManager.loadImage("images/kgpg.png"), new TranslateIcon(
public static final ImageIcon APPLY_BLOCKED_MATCH_ICON =
ResourceManager.getImageIcon(new MultiIcon(get("images/kgpg.png"), new TranslateIcon(
ResourceManager.loadImage("images/checkmark_green.gif", 12, 12), 4, 0)));
/**
@ -134,6 +120,39 @@ public class Icons {
return new IconProvider(icon, url);
}
/**
* Gets the icon for the given icon path. The given path should be relative to the classpath.
* If an icon by that name can't be found, the default "bomb" icon is returned instead.
* <P>
* For example, an icon named foo.png would typically be stored in the module at
* "{modulePath}/src/main/resources/image/foo.png". To reference that icon, use the path
* "images/foo.png", since "{modulePath}/src/main/resources" is in the classpath.
*
* @param iconPath the icon path (relative to the classpath)
* @return The icon referenced by that path.
*/
public static ImageIcon get(String iconPath) {
return ResourceManager.loadImage(iconPath);
}
/**
* Gets the icon for the given icon path and scale it to the specifed width and height.
* The given path should be relative to the classpath.
* If an icon by that name can't be found, the default "bomb" icon is returned instead.
* <P>
* For example, an icon named foo.png would typically be stored in the module at
* "{modulePath}/src/main/resources/image/foo.png". To reference that icon, use the path
* "images/foo.png", since "{modulePath}/src/main/resources" is in the classpath.
*
* @param iconPath the icon path (relative to the classpath)
* @param width the desired width after scaling
* @param height the desired height after scaling
* @return The icon referenced by that path.
*/
public static ImageIcon get(String iconPath, int width, int height) {
return ResourceManager.loadImage(iconPath, width, height);
}
private static String getIconName(String snippet) {
if (!isIconsReference(snippet)) {
return null;

View File

@ -17,6 +17,8 @@ dependencies {
compile "net.sf.jung:jung-graph-impl:2.1.1"
compile "net.sf.jung:jung-visualization:2.1.1"
compile "org.jgrapht:jgrapht-core:1.4.0"
// These have abstract test classes and stubs needed by this module
testCompile project(path: ':Docking', configuration: 'testArtifacts')
}

View File

@ -4,6 +4,7 @@
##MODULE IP: Oxygen Icons - LGPL 3.0
Module.manifest||GHIDRA||||END|
build.gradle||GHIDRA||||END|
data/ExtensionPoint.manifest||GHIDRA||||END|
src/main/docs/README.txt||GHIDRA||||END|
src/main/docs/VerticesAndEdges.png||GHIDRA||||END|
src/main/docs/VerticesAndEdges.xml||GHIDRA||||END|

View File

@ -0,0 +1,2 @@
GraphDisplayProvider

View File

@ -0,0 +1,138 @@
/* ###
* 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.service.graph;
import java.util.*;
public class Attributed {
/**
* the {@link HashMap} to contain attribute mappings
*/
private Map<String, String> attributes = new HashMap<>();
/**
* Returns an unmodifiable view of the attribute map
* @return an unmodifiable view of the attribute map
*/
public Map<String, String> getAttributeMap() {
return Collections.unmodifiableMap(attributes);
}
/**
* Sets the attribute with the given key and value
*
* @param key attribute key
* @param value attribute value
* @return the previous value of the attribute
*/
public String setAttribute(String key, String value) {
return attributes.put(key, value);
}
/**
* gets the value of the given attribute name
*
* @param key attribute name
* @return the mapped value for the supplied key
*/
public String getAttribute(String key) {
return attributes.get(key);
}
/**
* Removes the attribute with the given key
*
* @param key attribute key
* @return the value of the removed attribute
*/
public String removeAttribute(String key) {
return attributes.remove(key);
}
/**
* Returns true if there is an attribute with that name
*
* @param key attribute key
* @return true if there is an attribute with that name
*/
public boolean hasAttribute(String key) {
return attributes.containsKey(key);
}
/**
* Returns the number of attributes defined
*
* @return the number of attributes defined
*/
public int size() {
return attributes.size();
}
/**
* Return true if there are no attributes
*
* @return true if there are no mapped attributes
*/
public boolean isEmpty() {
return attributes.isEmpty();
}
/**
* Adds all the key/value pairs from the given map as attributes
*
* @param map a map of key/values to add as attributes
*/
public void putAttributes(Map<String, String> map) {
attributes.putAll(map);
}
/**
* removes all key/value mappings
*/
public void clear() {
attributes.clear();
}
/**
* Returns the keys for the attributes
*
* @return the keys for the attributes
*/
public Set<String> keys() {
return attributes.keySet();
}
/**
* Returns the attribute values
*
* @return the attribute values
*/
public Collection<String> values() {
return attributes.values();
}
/**
* Returns a {@link Set} containing the key/value entry associations
*
* @return a {@link Set} containing the key/value entry associations
*/
public Set<Map.Entry<String, String>> entrySet() {
return attributes.entrySet();
}
}

View File

@ -0,0 +1,88 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.service.graph;
import java.util.Map;
/**
* Generic directed graph edge implementation
*/
public class AttributedEdge extends Attributed {
private final String id;
/**
* cache of the edge label parsed as html
*/
private String htmlString;
/**
* Constructs a new GhidraEdge
* @param id the unique id for the edge
*/
public AttributedEdge(String id) {
this.id = id;
}
@Override
public String toString() {
return id;
}
/**
* create (once) the html representation of the key/values for this edge
* @return html formatted label for the edge
*/
public String getHtmlString() {
if (htmlString == null) {
StringBuilder buf = new StringBuilder("<html>");
for (Map.Entry<String, String> entry : entrySet()) {
buf.append(entry.getKey());
buf.append(":");
buf.append(entry.getValue());
buf.append("<br>");
}
htmlString = buf.toString();
}
return htmlString;
}
/**
* Returns the id for this edge
* @return the id for this edge
*/
public String getId() {
return id;
}
@Override
public int hashCode() {
return id.hashCode();
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
AttributedEdge other = (AttributedEdge) obj;
return id.equals(other.id);
}
}

View File

@ -0,0 +1,241 @@
/* ###
* 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.service.graph;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Supplier;
import org.jgrapht.graph.AbstractBaseGraph;
import org.jgrapht.graph.DefaultGraphType;
/**
* Basic graph implementation for a directed graph whose vertices and edges support attributes.
* <P>
* The graph can be configured as to how to handle multiple edges with the same source and destination
* vertices. One option is to simply allow multiple edges. The second option is to collapse
* duplicate edges such that there is only ever one edge with the same
* source and destination. In this case, each additional duplicate edge added will cause the
* edge to have a "Weight" attribute that will be the total number of edges that were added
* to the same source/destination vertex pair.
*/
public class AttributedGraph extends AbstractBaseGraph<AttributedVertex, AttributedEdge> {
private static final String WEIGHT = "Weight";
private Map<String, AttributedVertex> vertexMap = new HashMap<>();
private final boolean collapseDuplicateEdges;
/**
* Create a new empty AttributedGraph that automatically collapses duplicate edges
*/
public AttributedGraph() {
this(true);
}
/**
* Create a new empty AttributedGraph.
*
* @param collapseDuplicateEdges if true, duplicate edges will be collapsed into a single
* edge with a "Weight" attribute whose value is the number of edges between those vertices.
*/
public AttributedGraph(boolean collapseDuplicateEdges) {
super(new VertexSupplier(), new EdgeSupplier(), DefaultGraphType.directedPseudograph());
this.collapseDuplicateEdges = collapseDuplicateEdges;
}
/**
* Adds a new vertex with the given id. The vertex's name will be the same as the id.
* If a vertex already exists with that id,
* then that vertex will be returned.
*
* @param id the unique vertex id that the graph should have a vertex for.
* @return either an existing vertex with that id, or a newly added vertex with that id
*/
public AttributedVertex addVertex(String id) {
return addVertex(id, id);
}
/**
* Adds a new vertex with the given id and name. If a vertex already exists with that id,
* then that vertex will be returned, but with its name changed to the given name.
*
* @param id the unique vertex id that the graph should have a vertex for.
* @param name the name to associate with this vertex
* @return either an existing vertex with that id, or a newly added vertex with that id
*/
public AttributedVertex addVertex(String id, String name) {
if (vertexMap.containsKey(id)) {
AttributedVertex vertex = vertexMap.get(id);
vertex.setName(name);
}
AttributedVertex newVertex = new AttributedVertex(id, name);
addVertex(newVertex);
return newVertex;
}
@Override
public AttributedVertex addVertex() {
AttributedVertex vertex = super.addVertex();
vertexMap.put(vertex.getId(), vertex);
return vertex;
}
@Override
public boolean addVertex(AttributedVertex vertex) {
if (super.addVertex(vertex)) {
vertexMap.put(vertex.getId(), vertex);
return true;
}
return false;
}
/**
* Creates and adds a new directed edge with the given id between the given source and
* target vertices. If the graph is set to collapse duplicate edges and an edge for that
* source and target exists, then the existing edge will be return with its "Weight" attribute
* set to the total number of edges that have been added between the source and target vertices.
*
* @param source the source vertex of the directed edge to be created.
* @param target the target vertex of the directed edge to be created.
* @param edgeId the id to use for the new edge. Note: if this is a duplicate and edges
* are being collapsed, then this edgeId will not be used.
* @return a new edge between the source and target if it is the first one or the graph is
* not collapsing edges. Otherwise, an existing edge with its "Weight" attribute set accordingly.
*/
public AttributedEdge addEdge(AttributedVertex source, AttributedVertex target, String edgeId) {
AttributedEdge basicEdge = new AttributedEdge(edgeId);
addEdge(source, target, basicEdge);
return basicEdge;
}
/**
* Creates and adds a new directed edge with the given edge object. If the graph is set to
* collapse duplicate edges and an edge for that
* source and target exists, then the existing edge will be return with its "Weight" attribute
* set to the total number of edges that have been added between the source and target vertices.
*
* @param source the source vertex of the directed edge to be created.
* @param target the target vertex of the directed edge to be created.
* @param edge the BasicEdge object to use for the new edge. Note: if this is a duplicate and
* edges are being collapsed, then this edge object will not be used.
* @return true if the edge was added. Note that if this graph is collapsing duplicate edges, then
* it will always return true.
*/
@Override
public boolean addEdge(AttributedVertex source, AttributedVertex target, AttributedEdge edge) {
ensureInGraph(source);
ensureInGraph(target);
if (collapseDuplicateEdges) {
AttributedEdge existingEdge = getEdge(source, target);
if (existingEdge != null) {
incrementWeightProperty(existingEdge);
return true;
}
}
return super.addEdge(source, target, edge);
}
/**
* Creates and adds a new directed edge between the given source and
* target vertices. If the graph is set to collapse duplicate edges and an edge for that
* source and target exists, then the existing edge will be return with its "Weight" attribute
* set to the total number of edges that have been added between the source and target vertices.
*
* @param source the source vertex of the directed edge to be created.
* @param target the target vertex of the directed edge to be created.
* @return a new edge between the source and target if it is the first one or the graph is
* not collapsing edges. Otherwise, an existing edge with its "Weight" attribute set accordingly.
*/
@Override
public AttributedEdge addEdge(AttributedVertex source, AttributedVertex target) {
ensureInGraph(source);
ensureInGraph(target);
if (collapseDuplicateEdges) {
AttributedEdge edge = getEdge(source, target);
if (edge != null) {
incrementWeightProperty(edge);
return edge;
}
}
return super.addEdge(source, target);
}
/**
* Returns the total number of edges in the graph
* @return the total number of edges in the graph
*/
public int getEdgeCount() {
return edgeSet().size();
}
/**
* Returns the total number of vertices in the graph
* @return the total number of vertices in the graph
*/
public int getVertexCount() {
return vertexSet().size();
}
/**
* Returns the vertex with the given vertex id
* @param vertexId the id of the vertex to retrieve
* @return the vertex with the given vertex id or null if none found
*/
public AttributedVertex getVertex(String vertexId) {
return vertexMap.get(vertexId);
}
private void ensureInGraph(AttributedVertex vertex) {
if (!containsVertex(vertex)) {
addVertex(vertex);
}
}
private static void incrementWeightProperty(AttributedEdge edge) {
if (edge.hasAttribute(WEIGHT)) {
String weightString = edge.getAttribute(WEIGHT);
edge.setAttribute(WEIGHT, incrementWeightStringValue(weightString));
}
else {
edge.setAttribute(WEIGHT, "2");
}
}
private static String incrementWeightStringValue(String value) {
int weight = Integer.parseInt(value);
weight++;
return Integer.toString(weight);
}
private static class VertexSupplier implements Supplier<AttributedVertex> {
long id = 0;
@Override
public AttributedVertex get() {
return new AttributedVertex(Long.toString(id++));
}
}
private static class EdgeSupplier implements Supplier<AttributedEdge> {
long id = 0;
@Override
public AttributedEdge get() {
return new AttributedEdge(Long.toString(id++));
}
}
}

View File

@ -0,0 +1,115 @@
/* ###
* 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.service.graph;
import java.util.Map;
/**
* Graph vertex with attributes
*/
public class AttributedVertex extends Attributed {
private final String id;
/**
* cache of the html rendering of the vertex attributes
*/
private String htmlString;
/**
* Constructs a new GhidraVertex with the given id and name
*
* @param id the unique id for the vertex
* @param name the name for the vertex
*/
public AttributedVertex(String id, String name) {
this.id = id;
setName(name);
}
public AttributedVertex(String id) {
this(id, id);
}
/**
* Sets the name on the vertex
*
* @param name the new name for the vertex
*/
public void setName(String name) {
setAttribute("Name", name);
}
/**
* Returns the id for this vertex
* @return the id for this vertex
*/
public String getId() {
return id;
}
/**
* returns the name of the vertex
*
* @return the name of the vertex
*/
public String getName() {
return getAttribute("Name");
}
@Override
public String toString() {
return getName() + " (" + id + ")";
}
/**
* parse (one time) then cache the attributes to html
* @return the html string
*/
public String getHtmlString() {
if (htmlString == null) {
StringBuilder buf = new StringBuilder("<html>");
for (Map.Entry<String, String> entry : entrySet()) {
buf.append(entry.getKey());
buf.append(":");
buf.append(entry.getValue());
buf.append("<br>");
}
htmlString = buf.toString();
}
return htmlString;
}
@Override
public int hashCode() {
return id.hashCode();
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
AttributedVertex other = (AttributedVertex) obj;
return id.equals(other.id);
}
}

View File

@ -0,0 +1,218 @@
/* ###
* 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.service.graph;
import static org.junit.Assert.*;
import java.util.Set;
import org.junit.Before;
import org.junit.Test;
public class AttributedGraphTest {
private AttributedGraph graph;
@Before
public void setup() {
graph = new AttributedGraph();
}
@Test
public void testAddVertex() {
AttributedVertex v = graph.addVertex();
assertTrue(graph.containsVertex(v));
assertEquals(1, graph.getVertexCount());
}
@Test
public void testAddVertexTwice() {
AttributedVertex v = graph.addVertex();
assertFalse(graph.addVertex(v));
assertEquals(1, graph.getVertexCount());
}
@Test
public void testAddVertexWithId() {
AttributedVertex v = graph.addVertex("A");
assertTrue(graph.containsVertex(v));
assertEquals(1, graph.getVertexCount());
assertEquals("A", v.getId());
assertEquals("A", v.getName());
}
@Test
public void testAddVertexWithIdAndName() {
AttributedVertex v = graph.addVertex("A", "Bob");
assertTrue(graph.containsVertex(v));
assertEquals(1, graph.getVertexCount());
assertEquals("A", v.getId());
assertEquals("Bob", v.getName());
}
@Test
public void testAddVertexWithExistingVertex() {
AttributedVertex v = new AttributedVertex("A");
graph.addVertex(v);
assertTrue(graph.containsVertex(v));
assertEquals(1, graph.getVertexCount());
assertEquals("A", v.getId());
}
@Test
public void testAddDuplicateVertex() {
AttributedVertex v1 = graph.addVertex("A");
AttributedVertex v2 = graph.addVertex("A");
assertEquals(1, graph.getVertexCount());
assertTrue(v1 == v2);
}
@Test
public void testAddDuplicateVertexWithDifferentName() {
AttributedVertex v1 = graph.addVertex("A", "Bob");
AttributedVertex v2 = graph.addVertex("A", "Joe");
assertEquals(1, graph.getVertexCount());
assertTrue(v1 == v2);
assertEquals("Bob", v2.getName());
}
@Test
public void testAddEdge() {
AttributedVertex v1 = graph.addVertex("A", "Bob");
AttributedVertex v2 = graph.addVertex("B", "Joe");
AttributedEdge e = graph.addEdge(v1, v2);
assertEquals(1, graph.getEdgeCount());
assertEquals(v1, graph.getEdgeSource(e));
assertEquals(v2, graph.getEdgeTarget(e));
}
@Test
public void testAddExistingEdge() {
AttributedVertex v1 = graph.addVertex("A", "Bob");
AttributedVertex v2 = graph.addVertex("B", "Joe");
AttributedEdge e = new AttributedEdge("E1");
assertTrue(graph.addEdge(v1, v2, e));
assertEquals(1, graph.getEdgeCount());
assertEquals(v1, graph.getEdgeSource(e));
assertEquals(v2, graph.getEdgeTarget(e));
}
@Test
public void testAddEdgeWithId() {
AttributedVertex v1 = graph.addVertex("A", "Bob");
AttributedVertex v2 = graph.addVertex("B", "Joe");
AttributedEdge e = graph.addEdge(v1, v2, "X");
assertEquals(1, graph.getEdgeCount());
assertEquals(v1, graph.getEdgeSource(e));
assertEquals(v2, graph.getEdgeTarget(e));
assertEquals("X", e.getId());
}
@Test
public void testCanAddEdgeWithVerticesNotInGraph() {
AttributedVertex v1 = new AttributedVertex("A", "Bob");
AttributedVertex v2 = new AttributedVertex("B", "Joe");
AttributedEdge e = graph.addEdge(v1, v2);
assertEquals(2, graph.getVertexCount());
assertEquals(1, graph.getEdgeCount());
assertTrue(graph.containsVertex(v1));
assertTrue(graph.containsVertex(v2));
}
@Test
public void testGetVertexById() {
// create a vertex with all the possible ways
AttributedVertex v1 = graph.addVertex("A");
AttributedVertex v2 = graph.addVertex("B", "NAME");
AttributedVertex v3 = graph.addVertex();
AttributedVertex v4 = new AttributedVertex("C");
graph.addVertex(v4);
Set<AttributedVertex> vertexSet = graph.vertexSet();
assertEquals(4, vertexSet.size());
// make sure all vertices were added to the id to vertex map
assertEquals(v1, graph.getVertex("A"));
assertEquals(v2, graph.getVertex("B"));
assertEquals(v3, graph.getVertex(v3.getId()));
assertEquals(v4, graph.getVertex("C"));
}
@Test
public void testCollapseDuplicateEdges() {
AttributedVertex v1 = graph.addVertex("A");
AttributedVertex v2 = graph.addVertex("B");
graph.addEdge(v1, v2);
graph.addEdge(v1, v2);
graph.addEdge(v1, v2);
assertEquals(1, graph.getEdgeCount());
assertEquals("3", graph.getEdge(v1, v2).getAttribute("Weight"));
}
@Test
public void testCollapseDuplicateEdgesWithSuppliedEdges() {
AttributedVertex v1 = graph.addVertex("A");
AttributedVertex v2 = graph.addVertex("B");
graph.addEdge(v1, v2, new AttributedEdge("1"));
graph.addEdge(v1, v2, new AttributedEdge("2"));
graph.addEdge(v1, v2, new AttributedEdge("3"));
assertEquals(1, graph.getEdgeCount());
AttributedEdge edge = graph.getEdge(v1, v2);
assertEquals("3", edge.getAttribute("Weight"));
assertEquals("1", edge.getId());
}
@Test
public void testNonCollapsingEdges() {
graph = new AttributedGraph(false);
AttributedVertex v1 = graph.addVertex("A");
AttributedVertex v2 = graph.addVertex("B");
graph.addEdge(v1, v2);
graph.addEdge(v1, v2);
graph.addEdge(v1, v2, new AttributedEdge("x"));
assertEquals(3, graph.getEdgeCount());
}
@Test
public void testReverseEdgesDontCollapse() {
AttributedVertex v1 = graph.addVertex("A");
AttributedVertex v2 = graph.addVertex("B");
graph.addEdge(v1, v2);
graph.addEdge(v2, v1);
assertEquals(2, graph.getEdgeCount());
}
}

View File

@ -968,8 +968,8 @@ public abstract class PluginTool extends AbstractDockingTool {
saveAsAction.setMenuBarData(menuData);
saveAsAction.setEnabled(true);
saveAsAction.setHelpLocation(
new HelpLocation(ToolConstants.TOOL_HELP_TOPIC, "Tool_Changes"));
saveAsAction
.setHelpLocation(new HelpLocation(ToolConstants.TOOL_HELP_TOPIC, "Tool_Changes"));
addAction(saveAction);
addAction(saveAsAction);
@ -994,8 +994,8 @@ public abstract class PluginTool extends AbstractDockingTool {
new String[] { ToolConstants.MENU_FILE, exportPullright, "Export Tool..." });
menuData.setMenuSubGroup(Integer.toString(subGroup++));
exportToolAction.setMenuBarData(menuData);
exportToolAction.setHelpLocation(
new HelpLocation(ToolConstants.TOOL_HELP_TOPIC, "Export_Tool"));
exportToolAction
.setHelpLocation(new HelpLocation(ToolConstants.TOOL_HELP_TOPIC, "Export_Tool"));
addAction(exportToolAction);
DockingAction exportDefautToolAction =
@ -1340,7 +1340,8 @@ public abstract class PluginTool extends AbstractDockingTool {
eventMgr.addEventProducer(eventClass);
}
void addEventListener(Class<? extends PluginEvent> eventClass, PluginEventListener listener) {
public void addEventListener(Class<? extends PluginEvent> eventClass,
PluginEventListener listener) {
eventMgr.addEventListener(eventClass, listener);
}
@ -1356,7 +1357,7 @@ public abstract class PluginTool extends AbstractDockingTool {
eventMgr.removeAllEventListener(listener);
}
void removeEventListener(Class<? extends PluginEvent> eventClass,
public void removeEventListener(Class<? extends PluginEvent> eventClass,
PluginEventListener listener) {
eventMgr.removeEventListener(eventClass, listener);
}

View File

@ -1,81 +0,0 @@
/* ###
* IP: GHIDRA
* REVIEWED: YES
*
* 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.program.model.graph;
import java.util.Iterator;
/**
* Collection of edges and vertices that make up a graph.
* <code>GraphData</code> is intended to be displayed on a <code>GraphDisplay</code>.
*/
public interface GraphData {
/**
* Create a Vertex with a given name and vertex ID.
* The vertexID string is used to uniquely identify a vertex. It is
* used for selection and location mapping from/to Ghidra and the graph
* display. It should be mappable back to an location/selection that represents
* the vertex in ghidra terms.
*
* @param name name of the vertex, its label
* @param vertexID identifier to uniquely identify this vertex.
*
* @return a graph vertex
*/
public GraphVertex createVertex(String name, String vertexID);
/**
* Get a vertex with a given address string.
*
* @param vertexID identifier to uniquely identify this vertex. The key is
* useful for mapping location/selection from/to Ghidra and Renoir
*
* @return a vertex tagged with the given address.
*/
public GraphVertex getVertex(String vertexID);
/**
* Create an edge on the graph connecting two vertices.
* NOTE: These MUST be two vertices created from the above createVertex function.
*
* The address string is used to uniquely identify a vertex. It is
* used for selection and location mapping from/to Ghidra and the graph
* display. It should be mappable back to an actual address in ghidra
* terms.
*
* @param vertexID identifier to uniquely identify this vertex
* @param start start vertex
* @param end end vertex
*
* @return a graph edge
*/
public GraphEdge createEdge(String vertexID, GraphVertex start, GraphVertex end);
/**
* Get an iterator over all defined vertices. Every object in the iterator
* will be a GraphVertex.
*
* @return a vertex iterator
*/
public Iterator<? extends GraphVertex> getVertices();
/**
* Get an iterator over all defined edges. Every object in the iterator
* will be a GraphEdge.
*/
public Iterator<? extends GraphEdge> getEdges();
}

Some files were not shown because too many files have changed in this diff Show More