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
@@ -19,3 +19,4 @@ InstructionSkipper
DataTypeReferenceFinder
ChecksumAlgorithm
OverviewColorService
@@ -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);
}
}
@@ -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);
}
}
@@ -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);
}
}
@@ -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);
}
}
@@ -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();
}
@@ -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;
}
}
@@ -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);
}
@@ -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);
}
@@ -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);
-1
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"
File diff suppressed because it is too large Load Diff
@@ -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");
// }
// }
}
}
@@ -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);
}
@@ -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;
}
}
@@ -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;
}
}
@@ -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());
// }
// }
}
@@ -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());
}
@@ -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);
@@ -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
@@ -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')
}
@@ -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|
@@ -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>
@@ -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

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

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

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