Merge remote-tracking branch 'origin/GP-5823_ghidragon_data_graph_improvements--SQUASHED'

This commit is contained in:
Ryan Kurtz 2025-09-02 05:56:50 -04:00
commit 84fb7f6d08
12 changed files with 275 additions and 208 deletions

View file

@ -1,76 +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.debug.gui.graph.data;
import datagraph.AbstractDataGraphPlugin;
import ghidra.app.context.ListingActionContext;
import ghidra.app.plugin.PluginCategoryNames;
import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
import ghidra.app.plugin.core.debug.event.TraceLocationPluginEvent;
import ghidra.framework.plugintool.*;
import ghidra.framework.plugintool.util.PluginStatus;
import ghidra.program.util.ProgramLocation;
/**
* Plugin for showing a graph of data from the listing.
*/
//@formatter:off
@PluginInfo(
status = PluginStatus.RELEASED,
packageName = DebuggerPluginPackage.NAME,
category = PluginCategoryNames.DEBUGGER,
shortDescription = "Debugger Data Graph",
description = """
Plugin for displaying graphs of data objects in memory. From any data object in the
listing, the user can display a graph of that data object. Initially, a graph will be shown
with one vertex that has a scrollable view of the values in memory associated with that data.
Also, any pointers or references from or to that data can be explored by following the
references and creating additional vertices for the referenced code or data.
""",
eventsConsumed = {
TraceLocationPluginEvent.class,
},
eventsProduced = {
TraceLocationPluginEvent.class,
}
)
//@formatter:on
public class DebuggerDataGraphPlugin extends AbstractDataGraphPlugin {
public DebuggerDataGraphPlugin(PluginTool plugintool) {
super(plugintool);
}
@Override
public void processEvent(PluginEvent event) {
if (event instanceof TraceLocationPluginEvent ev) {
ProgramLocation location = ev.getLocation();
goTo(location);
}
}
@Override
public void fireLocationEvent(ProgramLocation location) {
firePluginEvent(new TraceLocationPluginEvent(getName(), location));
}
@Override
protected boolean isGraphActionEnabled(ListingActionContext context) {
if (!context.getNavigatable().isDynamic()) {
return false;
}
return super.isGraphActionEnabled(context);
}
}

View file

@ -44,6 +44,16 @@ public interface Navigatable {
*/ */
public boolean goTo(Program program, ProgramLocation location); public boolean goTo(Program program, ProgramLocation location);
/**
* Commands this navigatable to goto (display) the given location, using the program
* in the location.
* @param location the location in that program to display
* @return true if the goto was successful
*/
public default boolean goTo(ProgramLocation location) {
return goTo(location.getProgram(), location);
}
/** /**
* Returns the current location of this Navigatable * Returns the current location of this Navigatable
* *

View file

@ -141,7 +141,7 @@ public class DataTypeListingHover extends AbstractConfigurableHover implements L
sb.append("<TABLE>"); sb.append("<TABLE>");
if (parent != null) { if (parent != null) {
DataType parentType = parent.getDataType(); DataType parentType = parent.getDataType();
sb.append(row("Parent: ", html(parentType.getDataTypePath()))); sb.append(row("Parent: ", parentType.getDataTypePath()));
int offset = (int) data.getAddress().subtract(parent.getAddress()); int offset = (int) data.getAddress().subtract(parent.getAddress());
sb.append(row("Offset: ", NumericUtilities.toHexString(offset))); sb.append(row("Offset: ", NumericUtilities.toHexString(offset)));
sb.append(row("Field Name: ", nameLoc.getFieldName())); sb.append(row("Field Name: ", nameLoc.getFieldName()));
@ -149,7 +149,7 @@ public class DataTypeListingHover extends AbstractConfigurableHover implements L
DataTypeComponent dtc = pst.getComponentAt(offset); DataTypeComponent dtc = pst.getComponentAt(offset);
String comment = dtc == null ? null : dtc.getComment(); String comment = dtc == null ? null : dtc.getComment();
if (comment != null) { if (comment != null) {
sb.append(row("Comment: ", html(comment))); sb.append(row("Comment: ", comment));
} }
} }
} }
@ -160,16 +160,17 @@ public class DataTypeListingHover extends AbstractConfigurableHover implements L
return null; return null;
} }
private String row(String... cols) { private String row(Object... cols) {
StringBuilder sb = new StringBuilder("<TR>"); StringBuilder sb = new StringBuilder("<TR>");
for (String col : cols) { for (Object col : cols) {
sb.append("<TD>").append(col).append("</TD>"); String escaped = escapeHtml(col);
sb.append("<TD>").append(escaped).append("</TD>");
} }
sb.append("</TR>"); sb.append("</TR>");
return sb.toString(); return sb.toString();
} }
private String html(Object obj) { private String escapeHtml(Object obj) {
return obj == null ? "" : HTMLUtilities.friendlyEncodeHTML(obj.toString()); return obj == null ? "" : HTMLUtilities.friendlyEncodeHTML(obj.toString());
} }
@ -182,10 +183,12 @@ public class DataTypeListingHover extends AbstractConfigurableHover implements L
if (StringDataInstance.isString(dataInstance)) { if (StringDataInstance.isString(dataInstance)) {
StringDataInstance sdi = StringDataInstance.getStringDataInstance(dataInstance); StringDataInstance sdi = StringDataInstance.getStringDataInstance(dataInstance);
if (sdi.isShowTranslation()) { if (sdi.isShowTranslation()) {
result += String.format("<br>Original value: %s", html(sdi.getStringValue())); String escaped = escapeHtml(sdi.getStringValue());
result += String.format("<br>Original value: %s", escaped);
} }
if (!sdi.isShowTranslation() && sdi.getTranslatedValue() != null) { if (!sdi.isShowTranslation() && sdi.getTranslatedValue() != null) {
result += String.format("<br>Translated value: %s", html(sdi.getTranslatedValue())); String escaped = escapeHtml(sdi.getTranslatedValue());
result += String.format("<br>Translated value: %s", escaped);
} }
if (sdi.isMissingNullTerminator()) { if (sdi.isMissingNullTerminator()) {
result += "<br>Missing NULL terminator."; result += "<br>Missing NULL terminator.";

View file

@ -141,6 +141,10 @@
<LI><A name="Delete_Vertex"><IMG alt="" src="Icons.CLOSE_ICON">&nbsp<B>Delete Vertex</B> - Removes this vertex and all vertices <LI><A name="Delete_Vertex"><IMG alt="" src="Icons.CLOSE_ICON">&nbsp<B>Delete Vertex</B> - Removes this vertex and all vertices
that descend from this vertex.</A></LI> that descend from this vertex.</A></LI>
<LI><A name="Reset_Location"><IMG alt="" src="Icons.REFRESH_ICON">&nbsp<B>Restore Location</B> - Moves the vertex
to its preferred location (only shows up if the vertex was manually moved).</A></LI>
</UL> </UL>
<P><A name="Popups">The following popup actions are are available depending on where the <P><A name="Popups">The following popup actions are are available depending on where the
@ -167,6 +171,9 @@
<LI><A name="Delete_Selected"><B>Delete Selected Vertices</B> - Deletes the selected <LI><A name="Delete_Selected"><B>Delete Selected Vertices</B> - Deletes the selected
vertices and any descendants vertices (vertices that were discovered via exploring from vertices and any descendants vertices (vertices that were discovered via exploring from
that vertex.)</A></LI> that vertex.)</A></LI>
<LI><A name="Show_Popups"><B>Show Popups</B> - If selected, hovering over data rows in a data
vertex will show additional information for the data in that row.</A></LI>
</UL> </UL>
</BLOCKQUOTE> </BLOCKQUOTE>
<H3>Selecting Vertices</H3> <H3>Selecting Vertices</H3>
@ -227,6 +234,7 @@
vertices will show more information for each row in the display. In compact mode, a data vertices will show more information for each row in the display. In compact mode, a data
row will generally show the field name and its value. In expanded mode, a data row will row will generally show the field name and its value. In expanded mode, a data row will
generally show the datatype, field name, and its value.</A></LI> generally show the datatype, field name, and its value.</A></LI>
</UL> </UL>
</BLOCKQUOTE </BLOCKQUOTE
<H2>Standard Graph Features and Actions</H2> <H2>Standard Graph Features and Actions</H2>

View file

@ -1,84 +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 datagraph;
import java.util.HashSet;
import java.util.Set;
import docking.action.builder.ActionBuilder;
import ghidra.app.context.ListingActionContext;
import ghidra.app.plugin.ProgramPlugin;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.listing.Data;
import ghidra.program.util.ProgramLocation;
import ghidra.util.HelpLocation;
/**
* Base class for plugins that show a graph of data from program.
*/
public abstract class AbstractDataGraphPlugin extends ProgramPlugin {
private Set<DataGraphProvider> activeProviders = new HashSet<>();
public AbstractDataGraphPlugin(PluginTool plugintool) {
super(plugintool);
createActions();
}
public void goTo(ProgramLocation location) {
activeProviders.forEach(p -> p.goTo(location));
}
private void createActions() {
new ActionBuilder("Display Data Graph", getName())
.popupMenuPath("Data", "Display Data Graph")
.keyBinding("ctrl G")
.helpLocation(new HelpLocation("DataGraphPlugin", "Data_Graph"))
.withContext(ListingActionContext.class)
.enabledWhen(this::isGraphActionEnabled)
.onAction(this::showDataGraph)
.buildAndInstall(tool);
}
protected boolean isGraphActionEnabled(ListingActionContext context) {
return context.getCodeUnit() instanceof Data;
}
private void showDataGraph(ListingActionContext context) {
Data data = (Data) context.getCodeUnit();
// the data from the context may be an internal sub-data, we want the outermost data.
data = getTopLevelData(data);
DataGraphProvider provider = new DataGraphProvider(this, data);
activeProviders.add(provider);
tool.showComponentProvider(provider, true);
}
private Data getTopLevelData(Data data) {
Data parent = data.getParent();
while (parent != null) {
data = parent;
parent = data.getParent();
}
return data;
}
void removeProvider(DataGraphProvider provider) {
activeProviders.remove(provider);
}
public abstract void fireLocationEvent(ProgramLocation location);
}

View file

@ -15,13 +15,22 @@
*/ */
package datagraph; package datagraph;
import java.util.HashSet;
import java.util.Set;
import docking.action.builder.ActionBuilder;
import ghidra.app.CorePluginPackage; import ghidra.app.CorePluginPackage;
import ghidra.app.context.ListingActionContext; import ghidra.app.context.ListingActionContext;
import ghidra.app.events.AbstractLocationPluginEvent;
import ghidra.app.events.ProgramLocationPluginEvent; import ghidra.app.events.ProgramLocationPluginEvent;
import ghidra.app.plugin.PluginCategoryNames; import ghidra.app.plugin.PluginCategoryNames;
import ghidra.app.plugin.ProgramPlugin;
import ghidra.framework.options.SaveState;
import ghidra.framework.plugintool.*; import ghidra.framework.plugintool.*;
import ghidra.framework.plugintool.util.PluginStatus; import ghidra.framework.plugintool.util.PluginStatus;
import ghidra.program.model.listing.Data;
import ghidra.program.util.ProgramLocation; import ghidra.program.util.ProgramLocation;
import ghidra.util.HelpLocation;
/** /**
* Plugin for showing a graph of data from the listing. * Plugin for showing a graph of data from the listing.
@ -40,36 +49,97 @@ import ghidra.program.util.ProgramLocation;
references and creating additional vertices for the referenced code or data. references and creating additional vertices for the referenced code or data.
""", """,
eventsConsumed = { eventsConsumed = {
ProgramLocationPluginEvent.class, ProgramLocationPluginEvent.class,
}, },
eventsProduced = { eventsProduced = {
ProgramLocationPluginEvent.class, ProgramLocationPluginEvent.class,
} }
) )
//@formatter:on //@formatter:on
public class DataGraphPlugin extends AbstractDataGraphPlugin { public class DataGraphPlugin extends ProgramPlugin {
private static final String NAVIGATE_IN = "Navigate In";
private static final String NAVIGATE_OUT = "Navigate Out";
private static final String COMPACT_FORMAT = "Compact Format";
private static final String SHOW_POPUPS = "Show Popups";
private Set<DataGraphProvider> activeProviders = new HashSet<>();
private DegSharedConfig sharedConfig = new DegSharedConfig();
public DataGraphPlugin(PluginTool plugintool) { public DataGraphPlugin(PluginTool plugintool) {
super(plugintool); super(plugintool);
createActions();
} }
@Override @Override
public void processEvent(PluginEvent event) { public void processEvent(PluginEvent event) {
if (event instanceof ProgramLocationPluginEvent ev) { if (event instanceof AbstractLocationPluginEvent ev) {
ProgramLocation location = ev.getLocation(); ProgramLocation location = ev.getLocation();
goTo(location); setLocation(location);
} }
} }
@Override /**
public void fireLocationEvent(ProgramLocation location) { * Pass incoming tool location events to each active provider.
firePluginEvent(new ProgramLocationPluginEvent(getName(), location, location.getProgram())); * @param location the new tool location
*/
public void setLocation(ProgramLocation location) {
activeProviders.forEach(p -> p.setLocation(location));
} }
@Override @Override
public void readConfigState(SaveState saveState) {
sharedConfig.setNavigateIn(saveState.getBoolean(NAVIGATE_IN, false));
sharedConfig.setNavigateOut(saveState.getBoolean(NAVIGATE_OUT, true));
sharedConfig.setCompactFormat(saveState.getBoolean(COMPACT_FORMAT, true));
sharedConfig.setShowPopups(saveState.getBoolean(SHOW_POPUPS, true));
}
@Override
public void writeConfigState(SaveState saveState) {
saveState.putBoolean(NAVIGATE_IN, sharedConfig.isNavigateIn());
saveState.putBoolean(NAVIGATE_OUT, sharedConfig.isNavigateOut());
saveState.putBoolean(COMPACT_FORMAT, sharedConfig.useCompactFormat());
saveState.putBoolean(SHOW_POPUPS, sharedConfig.isShowPopups());
}
private void createActions() {
new ActionBuilder("Display Data Graph", getName())
.menuPath("&Graph", "Data")
.menuGroup("Graph", "Data")
.popupMenuPath("Data", "Display Data Graph")
.keyBinding("ctrl G")
.helpLocation(new HelpLocation("DataGraphPlugin", "Data_Graph"))
.withContext(ListingActionContext.class)
.enabledWhen(this::isGraphActionEnabled)
.onAction(this::showDataGraph)
.buildAndInstall(tool);
}
protected boolean isGraphActionEnabled(ListingActionContext context) { protected boolean isGraphActionEnabled(ListingActionContext context) {
if (context.getNavigatable().isDynamic()) { return context.getCodeUnit() instanceof Data;
return false;
}
return super.isGraphActionEnabled(context);
} }
private void showDataGraph(ListingActionContext context) {
Data data = (Data) context.getCodeUnit();
// the data from the context may be an internal sub-data, we want the outermost data.
data = getTopLevelData(data);
DataGraphProvider provider =
new DataGraphProvider(this, context.getNavigatable(), data, sharedConfig);
activeProviders.add(provider);
tool.showComponentProvider(provider, true);
}
private Data getTopLevelData(Data data) {
Data parent = data.getParent();
while (parent != null) {
data = parent;
parent = data.getParent();
}
return data;
}
void removeProvider(DataGraphProvider provider) {
activeProviders.remove(provider);
}
} }

View file

@ -29,10 +29,12 @@ import docking.action.ToggleDockingAction;
import docking.action.builder.ActionBuilder; import docking.action.builder.ActionBuilder;
import docking.action.builder.ToggleActionBuilder; import docking.action.builder.ToggleActionBuilder;
import generic.theme.GIcon; import generic.theme.GIcon;
import ghidra.app.nav.Navigatable;
import ghidra.graph.VisualGraphComponentProvider; import ghidra.graph.VisualGraphComponentProvider;
import ghidra.graph.viewer.*; import ghidra.graph.viewer.*;
import ghidra.graph.viewer.GraphComponent.SatellitePosition; import ghidra.graph.viewer.GraphComponent.SatellitePosition;
import ghidra.graph.viewer.event.mouse.VertexMouseInfo; import ghidra.graph.viewer.event.mouse.VertexMouseInfo;
import ghidra.program.model.address.Address;
import ghidra.program.model.listing.Data; import ghidra.program.model.listing.Data;
import ghidra.program.model.listing.Program; import ghidra.program.model.listing.Program;
import ghidra.program.util.ProgramLocation; import ghidra.program.util.ProgramLocation;
@ -52,24 +54,28 @@ public class DataGraphProvider
private static final GIcon RESET_ICON = new GIcon("icon.plugin.datagraph.action.viewer.reset"); private static final GIcon RESET_ICON = new GIcon("icon.plugin.datagraph.action.viewer.reset");
private static final String NAME = "Data Graph"; private static final String NAME = "Data Graph";
private AbstractDataGraphPlugin plugin; private DataGraphPlugin plugin;
private JPanel mainPanel; private JPanel mainPanel;
private DegController controller; private DegController controller;
private ToggleDockingAction navagateInAction; private ToggleDockingAction navagateInAction;
private ToggleDockingAction navagateOutAction; private ToggleDockingAction navagateOutAction;
private ToggleDockingAction expandedFormatAction; private ToggleDockingAction expandedFormatAction;
private Navigatable navigatable;
private ToggleDockingAction togglePopups;
/** /**
* Constructor * Constructor
* @param plugin the DataGraphPlugin * @param plugin the DataGraphPlugin
* @param data the initial data object to display in the graph. * @param data the initial data object to display in the graph.
*/ */
public DataGraphProvider(AbstractDataGraphPlugin plugin, Data data) { public DataGraphProvider(DataGraphPlugin plugin, Navigatable navigatable, Data data,
DegSharedConfig sharedConfig) {
super(plugin.getTool(), NAME, plugin.getName()); super(plugin.getTool(), NAME, plugin.getName());
this.plugin = plugin; this.plugin = plugin;
controller = new DegController(this, data); this.navigatable = navigatable;
createActions(); controller = new DegController(this, data, sharedConfig);
createActions(sharedConfig);
setTransient(); setTransient();
buildComponent(); buildComponent();
@ -77,6 +83,14 @@ public class DataGraphProvider
addSatelliteFeature(false, SatellitePosition.LOWER_LEFT); addSatelliteFeature(false, SatellitePosition.LOWER_LEFT);
setHelpLocation(new HelpLocation("DataGraphPlugin", "DataGraphPlugin")); setHelpLocation(new HelpLocation("DataGraphPlugin", "DataGraphPlugin"));
updateSubTitle();
}
public void updateSubTitle() {
Program program = controller.getProgram();
DataExplorationGraph graph = controller.getGraph();
Address address = graph.getRoot().getAddress();
setSubTitle(program.getName() + " @ " + address);
} }
private void buildComponent() { private void buildComponent() {
@ -141,7 +155,7 @@ public class DataGraphProvider
return controller; return controller;
} }
private void createActions() { private void createActions(DegSharedConfig sharedConfig) {
new ActionBuilder("Select Home Vertex", plugin.getName()) new ActionBuilder("Select Home Vertex", plugin.getName())
.toolBarIcon(Icons.HOME_ICON) .toolBarIcon(Icons.HOME_ICON)
.toolBarGroup("A") .toolBarGroup("A")
@ -159,6 +173,7 @@ public class DataGraphProvider
.toolBarIcon(DETAILS_ICON) .toolBarIcon(DETAILS_ICON)
.toolBarGroup("A") .toolBarGroup("A")
.description("Show Expanded information in data vertices.") .description("Show Expanded information in data vertices.")
.selected(!sharedConfig.useCompactFormat())
.helpLocation(new HelpLocation("DataGraphPlugin", "Expanded_Format")) .helpLocation(new HelpLocation("DataGraphPlugin", "Expanded_Format"))
.onAction(c -> controller.setCompactFormat(!expandedFormatAction.isSelected())) .onAction(c -> controller.setCompactFormat(!expandedFormatAction.isSelected()))
.buildAndInstallLocal(this); .buildAndInstallLocal(this);
@ -169,6 +184,7 @@ public class DataGraphProvider
.toolBarIcon(Icons.NAVIGATE_ON_INCOMING_EVENT_ICON) .toolBarIcon(Icons.NAVIGATE_ON_INCOMING_EVENT_ICON)
.toolBarGroup("B") .toolBarGroup("B")
.description("Attemps to select vertex corresponding to tool location changes.") .description("Attemps to select vertex corresponding to tool location changes.")
.selected(sharedConfig.isNavigateIn())
.helpLocation(new HelpLocation("DataGraphPlugin", "Navigate_In")) .helpLocation(new HelpLocation("DataGraphPlugin", "Navigate_In"))
.onAction(c -> controller.setNavigateIn(navagateInAction.isSelected())) .onAction(c -> controller.setNavigateIn(navagateInAction.isSelected()))
.buildAndInstallLocal(this); .buildAndInstallLocal(this);
@ -180,11 +196,22 @@ public class DataGraphProvider
.sharedKeyBinding() .sharedKeyBinding()
.description( .description(
"Selecting vetices or locations inside a vertex sends navigates the tool.") "Selecting vetices or locations inside a vertex sends navigates the tool.")
.selected(sharedConfig.isNavigateOut())
.helpLocation(new HelpLocation("DataGraphPlugin", "Navigate_Out")) .helpLocation(new HelpLocation("DataGraphPlugin", "Navigate_Out"))
.onAction(c -> controller.setNavigateOut(navagateOutAction.isSelected())) .onAction(c -> controller.setNavigateOut(navagateOutAction.isSelected()))
.selected(true) .selected(true)
.buildAndInstallLocal(this); .buildAndInstallLocal(this);
togglePopups = new ToggleActionBuilder("Display Popup Windows", plugin.getName())
.popupMenuPath("Display Popup Windows")
.description("Toggles whether or not to show tooltips")
.selected(sharedConfig.isShowPopups())
.helpLocation(new HelpLocation("DataGraphPlugin", "Show_Popups"))
.withContext(DegContext.class)
.popupWhen(c -> c.getVertex() == null)
.onAction(c -> controller.setPopupsVisible(togglePopups.isSelected()))
.buildAndInstallLocal(this);
new ActionBuilder("Incoming References", plugin.getName()) new ActionBuilder("Incoming References", plugin.getName())
.popupMenuPath("Add All Incoming References") .popupMenuPath("Add All Incoming References")
.popupMenuGroup("A", "2") .popupMenuGroup("A", "2")
@ -271,8 +298,8 @@ public class DataGraphProvider
return !v.isRoot(); return !v.isRoot();
} }
void goTo(ProgramLocation location) { void setLocation(ProgramLocation location) {
controller.locationChanged(location); controller.setLocation(location);
} }
private boolean canExpandRecursively(DegContext context) { private boolean canExpandRecursively(DegContext context) {
@ -289,8 +316,7 @@ public class DataGraphProvider
} }
public void navigateOut(ProgramLocation location) { public void navigateOut(ProgramLocation location) {
plugin.fireLocationEvent(location); navigatable.goTo(location);
} }
} }

View file

@ -0,0 +1,58 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package datagraph;
// Simple storage of shared data graph configuration states. If any provider changes any of these,
// then that will be the value going forward. In other words, the last one in wins.
public class DegSharedConfig {
private boolean navigateIn = false;
private boolean navigateOut = true;
private boolean showPopups = true;
private boolean useCompactFormat = true;
public boolean isNavigateIn() {
return navigateIn;
}
public void setNavigateIn(boolean navigateIn) {
this.navigateIn = navigateIn;
}
public boolean isNavigateOut() {
return navigateOut;
}
public void setNavigateOut(boolean navigateOut) {
this.navigateOut = navigateOut;
}
public boolean isShowPopups() {
return showPopups;
}
public void setShowPopups(boolean showPopups) {
this.showPopups = showPopups;
}
public boolean useCompactFormat() {
return useCompactFormat;
}
public void setCompactFormat(boolean useCompactFormat) {
this.useCompactFormat = useCompactFormat;
}
}

View file

@ -28,6 +28,7 @@ import java.util.stream.Collectors;
import javax.swing.JComponent; import javax.swing.JComponent;
import datagraph.DataGraphProvider; import datagraph.DataGraphProvider;
import datagraph.DegSharedConfig;
import datagraph.data.graph.DegVertex.DegVertexStatus; import datagraph.data.graph.DegVertex.DegVertexStatus;
import edu.uci.ics.jung.visualization.control.AbstractGraphMousePlugin; import edu.uci.ics.jung.visualization.control.AbstractGraphMousePlugin;
import ghidra.app.util.XReferenceUtils; import ghidra.app.util.XReferenceUtils;
@ -43,6 +44,7 @@ import ghidra.program.model.address.Address;
import ghidra.program.model.listing.*; import ghidra.program.model.listing.*;
import ghidra.program.model.symbol.Reference; import ghidra.program.model.symbol.Reference;
import ghidra.program.model.symbol.ReferenceManager; import ghidra.program.model.symbol.ReferenceManager;
import ghidra.program.util.AddressFieldLocation;
import ghidra.program.util.ProgramLocation; import ghidra.program.util.ProgramLocation;
import ghidra.util.Msg; import ghidra.util.Msg;
import ghidra.util.exception.CancelledException; import ghidra.util.exception.CancelledException;
@ -62,14 +64,17 @@ public class DegController implements DomainObjectListener {
private boolean navigateIn = false; private boolean navigateIn = false;
private boolean compactFormat = true; private boolean compactFormat = true;
private DataGraphProvider provider; private DataGraphProvider provider;
private DegSharedConfig sharedConfig;
/** /**
* Constructs a new data exploration graph controller. * Constructs a new data exploration graph controller.
* @param provider The data graph provider that created this controller * @param provider The data graph provider that created this controller
* @param data the initial data to display in the graph * @param data the initial data to display in the graph
* @param sharedConfig the shared data graph configuration state
*/ */
public DegController(DataGraphProvider provider, Data data) { public DegController(DataGraphProvider provider, Data data, DegSharedConfig sharedConfig) {
this.provider = provider; this.provider = provider;
this.sharedConfig = sharedConfig;
this.program = data.getProgram(); this.program = data.getProgram();
view = new DegGraphView(); view = new DegGraphView();
DegVertex root = new DataDegVertex(this, data, null, true); DegVertex root = new DataDegVertex(this, data, null, true);
@ -88,8 +93,9 @@ public class DegController implements DomainObjectListener {
*/ */
public void navigateOut(Address address, int[] componentPath) { public void navigateOut(Address address, int[] componentPath) {
if (navigateOut) { if (navigateOut) {
// Using an address field location skips past pre or plate comments
ProgramLocation location = ProgramLocation location =
new ProgramLocation(program, address, address, componentPath, null, 0, 0, 0); new AddressFieldLocation(program, address, componentPath, address.toString(), 0);
provider.navigateOut(location); provider.navigateOut(location);
} }
} }
@ -226,6 +232,7 @@ public class DegController implements DomainObjectListener {
public void orientAround(DegVertex newRoot) { public void orientAround(DegVertex newRoot) {
graph.setRoot(newRoot); graph.setRoot(newRoot);
relayoutGraphAndCenter(newRoot); relayoutGraphAndCenter(newRoot);
provider.updateSubTitle();
} }
public VisualGraphView<DegVertex, DegEdge, DataExplorationGraph> getView() { public VisualGraphView<DegVertex, DegEdge, DataExplorationGraph> getView() {
@ -247,6 +254,7 @@ public class DegController implements DomainObjectListener {
*/ */
public void setNavigateOut(boolean b) { public void setNavigateOut(boolean b) {
navigateOut = b; navigateOut = b;
sharedConfig.setNavigateOut(b);
} }
/** /**
@ -256,6 +264,7 @@ public class DegController implements DomainObjectListener {
*/ */
public void setNavigateIn(boolean b) { public void setNavigateIn(boolean b) {
navigateIn = b; navigateIn = b;
sharedConfig.setNavigateIn(b);
} }
/** /**
@ -270,6 +279,12 @@ public class DegController implements DomainObjectListener {
} }
}); });
relayoutGraph(); relayoutGraph();
sharedConfig.setCompactFormat(b);
}
public void setPopupsVisible(boolean b) {
view.setPopupsVisible(b);
sharedConfig.setShowPopups(b);
} }
public boolean isCompactFormat() { public boolean isCompactFormat() {
@ -330,7 +345,7 @@ public class DegController implements DomainObjectListener {
* graph will select that vertex. * graph will select that vertex.
* @param location the new location for the tool * @param location the new location for the tool
*/ */
public void locationChanged(ProgramLocation location) { public void setLocation(ProgramLocation location) {
if (!navigateIn) { if (!navigateIn) {
return; return;
} }

View file

@ -34,7 +34,10 @@ import docking.action.DockingActionIf;
import docking.widgets.trable.GTrable; import docking.widgets.trable.GTrable;
import docking.widgets.trable.GTrableColumnModel; import docking.widgets.trable.GTrableColumnModel;
import ghidra.program.model.address.Address; import ghidra.program.model.address.Address;
import ghidra.program.model.data.DataType;
import ghidra.program.model.listing.Data; import ghidra.program.model.listing.Data;
import ghidra.util.HTMLUtilities;
import ghidra.util.NumericUtilities;
import ghidra.util.datastruct.Range; import ghidra.util.datastruct.Range;
import resources.Icons; import resources.Icons;
@ -119,7 +122,34 @@ public class DataVertexPanel extends JPanel {
JComponent jComponent = (JComponent) source; JComponent jComponent = (JComponent) source;
return jComponent.getToolTipText(); return jComponent.getToolTipText();
} }
return null; int row = gTrable.getRow(event.getPoint());
DataRowObject rowObject = model.getRow(row);
Data data = rowObject.getData();
StringBuilder sb = new StringBuilder(HTMLUtilities.HTML);
sb.append("<TABLE>");
DataType dataType = data.getDataType();
Address address = data.getAddress();
int rootOffset = data.getRootOffset();
sb.append(row("Address: ", address.toString()));
sb.append(row("Offset: ", NumericUtilities.toHexString(rootOffset)));
sb.append(row("Data Type: ", dataType.getName()));
sb.append("</TABLE>");
return sb.toString();
}
private String row(Object... cols) {
StringBuilder sb = new StringBuilder("<TR>");
for (Object col : cols) {
String escaped = escapeHtml(col);
sb.append("<TD>").append(escaped).append("</TD>");
}
sb.append("</TR>");
return sb.toString();
}
private String escapeHtml(Object obj) {
return obj == null ? "" : HTMLUtilities.friendlyEncodeHTML(obj.toString());
} }
/** /**

View file

@ -185,54 +185,57 @@ public class ProgramGraphPlugin extends ProgramPlugin
private void createActions() { private void createActions() {
new ActionBuilder("Graph Block Flow", getName()).menuPath(MENU_GRAPH, "&Block Flow") new ActionBuilder("Graph Block Flow", getName())
.menuGroup(MENU_GRAPH, "A") .menuPath(MENU_GRAPH, "&Block Flow")
.menuGroup("Code Graph", "A")
.onAction(c -> graphBlockFlow()) .onAction(c -> graphBlockFlow())
.enabledWhen(this::canGraph) .enabledWhen(this::canGraph)
.buildAndInstall(tool); .buildAndInstall(tool);
new ActionBuilder("Graph Code Flow", getName()).menuPath(MENU_GRAPH, "C&ode Flow") new ActionBuilder("Graph Code Flow", getName())
.menuGroup(MENU_GRAPH, "B") .menuPath(MENU_GRAPH, "C&ode Flow")
.menuGroup("Code Graph", "B")
.onAction(c -> graphCodeFlow()) .onAction(c -> graphCodeFlow())
.enabledWhen(this::canGraph) .enabledWhen(this::canGraph)
.buildAndInstall(tool); .buildAndInstall(tool);
new ActionBuilder("Graph Calls Using Default Model", getName()) new ActionBuilder("Graph Calls Using Default Model", getName())
.menuPath(MENU_GRAPH, "&Calls") .menuPath(MENU_GRAPH, "&Calls")
.menuGroup(MENU_GRAPH, "C") .menuGroup("Code Graph", "C")
.onAction(c -> createDefaultCallGraph()) .onAction(c -> createDefaultCallGraph())
.enabledWhen(this::canGraph) .enabledWhen(this::canGraph)
.buildAndInstall(tool); .buildAndInstall(tool);
tool.setMenuGroup(new String[] { MENU_GRAPH, "Data" }, "Graph", "Data"); tool.setMenuGroup(new String[] { MENU_GRAPH, "Data References" }, "Graph", "Data");
HelpLocation helpLoc = new HelpLocation(getName(), "Data_Reference_Graph"); HelpLocation helpLoc = new HelpLocation(getName(), "Data_Reference_Graph");
new ActionBuilder("Graph To/From Data References", getName()) new ActionBuilder("Graph To/From Data References", getName())
.menuPath(MENU_GRAPH, "Data", "To/From &References") .menuPath(MENU_GRAPH, "Data References", "To/From &References")
.menuGroup(MENU_GRAPH, "Data") .menuGroup("Graph", "Data")
.helpLocation(helpLoc) .helpLocation(helpLoc)
.onAction(c -> graphDataReferences()) .onAction(c -> graphDataReferences())
.enabledWhen(this::canGraph) .enabledWhen(this::canGraph)
.buildAndInstall(tool); .buildAndInstall(tool);
new ActionBuilder("Graph To Data References", getName()) new ActionBuilder("Graph To Data References", getName())
.menuPath(MENU_GRAPH, "Data", "&To References") .menuPath(MENU_GRAPH, "Data References", "&To References")
.menuGroup(MENU_GRAPH, "Data") .menuGroup("Graph", "Data")
.helpLocation(helpLoc) .helpLocation(helpLoc)
.onAction(c -> graphToDataReferences()) .onAction(c -> graphToDataReferences())
.enabledWhen(this::canGraph) .enabledWhen(this::canGraph)
.buildAndInstall(tool); .buildAndInstall(tool);
new ActionBuilder("Graph From Data References", getName()) new ActionBuilder("Graph From Data References", getName())
.menuPath(MENU_GRAPH, "Data", "&From References") .menuPath(MENU_GRAPH, "Data References", "&From References")
.menuGroup(MENU_GRAPH, "Data") .menuGroup("Graph", "Data")
.helpLocation(helpLoc) .helpLocation(helpLoc)
.onAction(c -> graphFromDataReferences()) .onAction(c -> graphFromDataReferences())
.enabledWhen(this::canGraph) .enabledWhen(this::canGraph)
.buildAndInstall(tool); .buildAndInstall(tool);
reuseGraphAction = reuseGraphAction =
new ToggleActionBuilder("Reuse Graph", getName()).menuPath(MENU_GRAPH, "Reuse Graph") new ToggleActionBuilder("Reuse Graph", getName())
.menuPath(MENU_GRAPH, "Reuse Graph")
.menuGroup("Graph Options") .menuGroup("Graph Options")
.selected(reuseGraph) .selected(reuseGraph)
.onAction(c -> reuseGraph = reuseGraphAction.isSelected()) .onAction(c -> reuseGraph = reuseGraphAction.isSelected())
@ -240,7 +243,8 @@ public class ProgramGraphPlugin extends ProgramPlugin
.buildAndInstall(tool); .buildAndInstall(tool);
appendGraphAction = appendGraphAction =
new ToggleActionBuilder("Append Graph", getName()).menuPath(MENU_GRAPH, "Append Graph") new ToggleActionBuilder("Append Graph", getName())
.menuPath(MENU_GRAPH, "Append Graph")
.menuGroup("Graph Options") .menuGroup("Graph Options")
.selected(false) .selected(false)
.onAction(c -> updateAppendAndReuseGraph()) .onAction(c -> updateAppendAndReuseGraph())
@ -297,13 +301,12 @@ public class ProgramGraphPlugin extends ProgramPlugin
subUsingGraphActions.add(action); subUsingGraphActions.add(action);
} }
tool.setMenuGroup(new String[] { MENU_GRAPH, "Calls Using Model" }, "Graph", "C"); tool.setMenuGroup(new String[] { MENU_GRAPH, "Calls Using Model" }, "Code Graph", "D");
} }
private DockingAction buildGraphActionWithModel(String blockModelName, HelpLocation helpLoc) { private DockingAction buildGraphActionWithModel(String blockModelName, HelpLocation helpLoc) {
return new ActionBuilder("Graph Calls using " + blockModelName, getName()) return new ActionBuilder("Graph Calls using " + blockModelName, getName())
.menuPath(MENU_GRAPH, "Calls Using Model", blockModelName) .menuPath(MENU_GRAPH, "Calls Using Model", blockModelName)
.menuGroup(MENU_GRAPH, "C")
.helpLocation(helpLoc) .helpLocation(helpLoc)
.onAction(c -> createCallGraphUsing(blockModelName)) .onAction(c -> createCallGraphUsing(blockModelName))
.enabledWhen(this::canGraph) .enabledWhen(this::canGraph)

View file

@ -370,6 +370,10 @@ public class GTrable<T> extends JComponent
columnModel.setWidth(width); columnModel.setWidth(width);
} }
public int getRow(Point p) {
return p.y / rowHeight;
}
private void notifySelectedRowConsumers() { private void notifySelectedRowConsumers() {
for (Consumer<Integer> consumer : selectedRowConsumers) { for (Consumer<Integer> consumer : selectedRowConsumers) {
consumer.accept(selectedRow); consumer.accept(selectedRow);