mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2026-05-10 06:28:15 +08:00
GT-3317
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:
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
+190
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
+22
@@ -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();
|
||||
|
||||
}
|
||||
+209
@@ -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);
|
||||
}
|
||||
+1
-1
@@ -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);
|
||||
|
||||
@@ -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");
|
||||
// }
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+8
-4
@@ -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);
|
||||
}
|
||||
|
||||
+96
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
-132
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
+65
-100
@@ -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());
|
||||
// }
|
||||
// }
|
||||
|
||||
}
|
||||
|
||||
+8
-7
@@ -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());
|
||||
}
|
||||
|
||||
|
||||
+4
-4
@@ -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 |
+67
@@ -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>
|
||||
BIN
Binary file not shown.
|
After Width: | Height: | Size: 9.8 KiB |
BIN
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+152
@@ -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);
|
||||
}
|
||||
}
|
||||
+60
@@ -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));
|
||||
}
|
||||
}
|
||||
+118
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
+79
@@ -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");
|
||||
}
|
||||
}
|
||||
+38
@@ -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;
|
||||
}
|
||||
}
|
||||
+333
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
+83
@@ -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;
|
||||
}
|
||||
}
|
||||
+35
@@ -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");
|
||||
}
|
||||
+750
File diff suppressed because it is too large
Load Diff
+51
@@ -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();
|
||||
}
|
||||
}
|
||||
+96
@@ -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);
|
||||
}
|
||||
}
|
||||
+47
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
+96
@@ -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;
|
||||
}
|
||||
}
|
||||
+173
@@ -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();
|
||||
}
|
||||
}
|
||||
+104
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+77
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
+210
@@ -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
Reference in New Issue
Block a user