diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/graph/data/DebuggerDataGraphPlugin.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/graph/data/DebuggerDataGraphPlugin.java deleted file mode 100644 index e1846e08be..0000000000 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/graph/data/DebuggerDataGraphPlugin.java +++ /dev/null @@ -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); - } -} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/nav/Navigatable.java b/Ghidra/Features/Base/src/main/java/ghidra/app/nav/Navigatable.java index c354a0ca25..cbf9b133a6 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/nav/Navigatable.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/nav/Navigatable.java @@ -44,6 +44,16 @@ public interface Navigatable { */ 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 * diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/codebrowser/hover/DataTypeListingHover.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/codebrowser/hover/DataTypeListingHover.java index 2263c30d47..52f5f852a0 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/codebrowser/hover/DataTypeListingHover.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/codebrowser/hover/DataTypeListingHover.java @@ -141,7 +141,7 @@ public class DataTypeListingHover extends AbstractConfigurableHover implements L sb.append(""); if (parent != null) { 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()); sb.append(row("Offset: ", NumericUtilities.toHexString(offset))); sb.append(row("Field Name: ", nameLoc.getFieldName())); @@ -149,7 +149,7 @@ public class DataTypeListingHover extends AbstractConfigurableHover implements L DataTypeComponent dtc = pst.getComponentAt(offset); String comment = dtc == null ? null : dtc.getComment(); 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; } - private String row(String... cols) { + private String row(Object... cols) { StringBuilder sb = new StringBuilder(""); - for (String col : cols) { - sb.append(""); + for (Object col : cols) { + String escaped = escapeHtml(col); + sb.append(""); } sb.append(""); return sb.toString(); } - private String html(Object obj) { + private String escapeHtml(Object obj) { return obj == null ? "" : HTMLUtilities.friendlyEncodeHTML(obj.toString()); } @@ -182,10 +183,12 @@ public class DataTypeListingHover extends AbstractConfigurableHover implements L if (StringDataInstance.isString(dataInstance)) { StringDataInstance sdi = StringDataInstance.getStringDataInstance(dataInstance); if (sdi.isShowTranslation()) { - result += String.format("
Original value: %s", html(sdi.getStringValue())); + String escaped = escapeHtml(sdi.getStringValue()); + result += String.format("
Original value: %s", escaped); } if (!sdi.isShowTranslation() && sdi.getTranslatedValue() != null) { - result += String.format("
Translated value: %s", html(sdi.getTranslatedValue())); + String escaped = escapeHtml(sdi.getTranslatedValue()); + result += String.format("
Translated value: %s", escaped); } if (sdi.isMissingNullTerminator()) { result += "
Missing NULL terminator."; diff --git a/Ghidra/Features/DataGraph/src/main/help/help/topics/DataGraphPlugin/Data_Graph.html b/Ghidra/Features/DataGraph/src/main/help/help/topics/DataGraphPlugin/Data_Graph.html index 979f025f00..62a7399460 100644 --- a/Ghidra/Features/DataGraph/src/main/help/help/topics/DataGraphPlugin/Data_Graph.html +++ b/Ghidra/Features/DataGraph/src/main/help/help/topics/DataGraphPlugin/Data_Graph.html @@ -141,6 +141,10 @@
  •  Delete Vertex - Removes this vertex and all vertices that descend from this vertex.
  • + +
  •  Restore Location - Moves the vertex + to its preferred location (only shows up if the vertex was manually moved).
  • +

    The following popup actions are are available depending on where the @@ -167,6 +171,9 @@

  • Delete Selected Vertices - Deletes the selected vertices and any descendants vertices (vertices that were discovered via exploring from that vertex.)
  • + +
  • Show Popups - If selected, hovering over data rows in a data + vertex will show additional information for the data in that row.
  • Selecting Vertices

    @@ -227,6 +234,7 @@ 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 generally show the datatype, field name, and its value. + Standard Graph Features and Actions diff --git a/Ghidra/Features/DataGraph/src/main/java/datagraph/AbstractDataGraphPlugin.java b/Ghidra/Features/DataGraph/src/main/java/datagraph/AbstractDataGraphPlugin.java deleted file mode 100644 index 20a54fca50..0000000000 --- a/Ghidra/Features/DataGraph/src/main/java/datagraph/AbstractDataGraphPlugin.java +++ /dev/null @@ -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 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); -} diff --git a/Ghidra/Features/DataGraph/src/main/java/datagraph/DataGraphPlugin.java b/Ghidra/Features/DataGraph/src/main/java/datagraph/DataGraphPlugin.java index 229daf7e6d..ba07f040d1 100644 --- a/Ghidra/Features/DataGraph/src/main/java/datagraph/DataGraphPlugin.java +++ b/Ghidra/Features/DataGraph/src/main/java/datagraph/DataGraphPlugin.java @@ -15,13 +15,22 @@ */ package datagraph; +import java.util.HashSet; +import java.util.Set; + +import docking.action.builder.ActionBuilder; import ghidra.app.CorePluginPackage; import ghidra.app.context.ListingActionContext; +import ghidra.app.events.AbstractLocationPluginEvent; import ghidra.app.events.ProgramLocationPluginEvent; import ghidra.app.plugin.PluginCategoryNames; +import ghidra.app.plugin.ProgramPlugin; +import ghidra.framework.options.SaveState; import ghidra.framework.plugintool.*; import ghidra.framework.plugintool.util.PluginStatus; +import ghidra.program.model.listing.Data; import ghidra.program.util.ProgramLocation; +import ghidra.util.HelpLocation; /** * 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. """, eventsConsumed = { - ProgramLocationPluginEvent.class, + ProgramLocationPluginEvent.class, }, eventsProduced = { ProgramLocationPluginEvent.class, } ) //@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 activeProviders = new HashSet<>(); + private DegSharedConfig sharedConfig = new DegSharedConfig(); + public DataGraphPlugin(PluginTool plugintool) { super(plugintool); + createActions(); } @Override public void processEvent(PluginEvent event) { - if (event instanceof ProgramLocationPluginEvent ev) { + if (event instanceof AbstractLocationPluginEvent ev) { ProgramLocation location = ev.getLocation(); - goTo(location); + setLocation(location); } } - @Override - public void fireLocationEvent(ProgramLocation location) { - firePluginEvent(new ProgramLocationPluginEvent(getName(), location, location.getProgram())); + /** + * Pass incoming tool location events to each active provider. + * @param location the new tool location + */ + public void setLocation(ProgramLocation location) { + activeProviders.forEach(p -> p.setLocation(location)); } @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) { - if (context.getNavigatable().isDynamic()) { - return false; - } - return super.isGraphActionEnabled(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, 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); + } + } diff --git a/Ghidra/Features/DataGraph/src/main/java/datagraph/DataGraphProvider.java b/Ghidra/Features/DataGraph/src/main/java/datagraph/DataGraphProvider.java index 7f7491103e..7cb30ac2c8 100644 --- a/Ghidra/Features/DataGraph/src/main/java/datagraph/DataGraphProvider.java +++ b/Ghidra/Features/DataGraph/src/main/java/datagraph/DataGraphProvider.java @@ -29,10 +29,12 @@ import docking.action.ToggleDockingAction; import docking.action.builder.ActionBuilder; import docking.action.builder.ToggleActionBuilder; import generic.theme.GIcon; +import ghidra.app.nav.Navigatable; import ghidra.graph.VisualGraphComponentProvider; import ghidra.graph.viewer.*; import ghidra.graph.viewer.GraphComponent.SatellitePosition; import ghidra.graph.viewer.event.mouse.VertexMouseInfo; +import ghidra.program.model.address.Address; import ghidra.program.model.listing.Data; import ghidra.program.model.listing.Program; 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 String NAME = "Data Graph"; - private AbstractDataGraphPlugin plugin; + private DataGraphPlugin plugin; private JPanel mainPanel; private DegController controller; private ToggleDockingAction navagateInAction; private ToggleDockingAction navagateOutAction; private ToggleDockingAction expandedFormatAction; + private Navigatable navigatable; + private ToggleDockingAction togglePopups; /** * Constructor * @param plugin the DataGraphPlugin * @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()); this.plugin = plugin; - controller = new DegController(this, data); - createActions(); + this.navigatable = navigatable; + controller = new DegController(this, data, sharedConfig); + createActions(sharedConfig); setTransient(); buildComponent(); @@ -77,6 +83,14 @@ public class DataGraphProvider addSatelliteFeature(false, SatellitePosition.LOWER_LEFT); 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() { @@ -141,7 +155,7 @@ public class DataGraphProvider return controller; } - private void createActions() { + private void createActions(DegSharedConfig sharedConfig) { new ActionBuilder("Select Home Vertex", plugin.getName()) .toolBarIcon(Icons.HOME_ICON) .toolBarGroup("A") @@ -159,6 +173,7 @@ public class DataGraphProvider .toolBarIcon(DETAILS_ICON) .toolBarGroup("A") .description("Show Expanded information in data vertices.") + .selected(!sharedConfig.useCompactFormat()) .helpLocation(new HelpLocation("DataGraphPlugin", "Expanded_Format")) .onAction(c -> controller.setCompactFormat(!expandedFormatAction.isSelected())) .buildAndInstallLocal(this); @@ -169,6 +184,7 @@ public class DataGraphProvider .toolBarIcon(Icons.NAVIGATE_ON_INCOMING_EVENT_ICON) .toolBarGroup("B") .description("Attemps to select vertex corresponding to tool location changes.") + .selected(sharedConfig.isNavigateIn()) .helpLocation(new HelpLocation("DataGraphPlugin", "Navigate_In")) .onAction(c -> controller.setNavigateIn(navagateInAction.isSelected())) .buildAndInstallLocal(this); @@ -180,11 +196,22 @@ public class DataGraphProvider .sharedKeyBinding() .description( "Selecting vetices or locations inside a vertex sends navigates the tool.") + .selected(sharedConfig.isNavigateOut()) .helpLocation(new HelpLocation("DataGraphPlugin", "Navigate_Out")) .onAction(c -> controller.setNavigateOut(navagateOutAction.isSelected())) .selected(true) .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()) .popupMenuPath("Add All Incoming References") .popupMenuGroup("A", "2") @@ -271,8 +298,8 @@ public class DataGraphProvider return !v.isRoot(); } - void goTo(ProgramLocation location) { - controller.locationChanged(location); + void setLocation(ProgramLocation location) { + controller.setLocation(location); } private boolean canExpandRecursively(DegContext context) { @@ -289,8 +316,7 @@ public class DataGraphProvider } public void navigateOut(ProgramLocation location) { - plugin.fireLocationEvent(location); - + navigatable.goTo(location); } } diff --git a/Ghidra/Features/DataGraph/src/main/java/datagraph/DegSharedConfig.java b/Ghidra/Features/DataGraph/src/main/java/datagraph/DegSharedConfig.java new file mode 100644 index 0000000000..38234e8b63 --- /dev/null +++ b/Ghidra/Features/DataGraph/src/main/java/datagraph/DegSharedConfig.java @@ -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; + } + +} diff --git a/Ghidra/Features/DataGraph/src/main/java/datagraph/data/graph/DegController.java b/Ghidra/Features/DataGraph/src/main/java/datagraph/data/graph/DegController.java index 7278855a23..978335df1a 100644 --- a/Ghidra/Features/DataGraph/src/main/java/datagraph/data/graph/DegController.java +++ b/Ghidra/Features/DataGraph/src/main/java/datagraph/data/graph/DegController.java @@ -28,6 +28,7 @@ import java.util.stream.Collectors; import javax.swing.JComponent; import datagraph.DataGraphProvider; +import datagraph.DegSharedConfig; import datagraph.data.graph.DegVertex.DegVertexStatus; import edu.uci.ics.jung.visualization.control.AbstractGraphMousePlugin; import ghidra.app.util.XReferenceUtils; @@ -43,6 +44,7 @@ import ghidra.program.model.address.Address; import ghidra.program.model.listing.*; import ghidra.program.model.symbol.Reference; import ghidra.program.model.symbol.ReferenceManager; +import ghidra.program.util.AddressFieldLocation; import ghidra.program.util.ProgramLocation; import ghidra.util.Msg; import ghidra.util.exception.CancelledException; @@ -62,14 +64,17 @@ public class DegController implements DomainObjectListener { private boolean navigateIn = false; private boolean compactFormat = true; private DataGraphProvider provider; + private DegSharedConfig sharedConfig; /** * Constructs a new data exploration graph controller. * @param provider The data graph provider that created this controller * @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.sharedConfig = sharedConfig; this.program = data.getProgram(); view = new DegGraphView(); DegVertex root = new DataDegVertex(this, data, null, true); @@ -88,8 +93,9 @@ public class DegController implements DomainObjectListener { */ public void navigateOut(Address address, int[] componentPath) { if (navigateOut) { + // Using an address field location skips past pre or plate comments ProgramLocation location = - new ProgramLocation(program, address, address, componentPath, null, 0, 0, 0); + new AddressFieldLocation(program, address, componentPath, address.toString(), 0); provider.navigateOut(location); } } @@ -226,6 +232,7 @@ public class DegController implements DomainObjectListener { public void orientAround(DegVertex newRoot) { graph.setRoot(newRoot); relayoutGraphAndCenter(newRoot); + provider.updateSubTitle(); } public VisualGraphView getView() { @@ -247,6 +254,7 @@ public class DegController implements DomainObjectListener { */ public void setNavigateOut(boolean b) { navigateOut = b; + sharedConfig.setNavigateOut(b); } /** @@ -256,6 +264,7 @@ public class DegController implements DomainObjectListener { */ public void setNavigateIn(boolean b) { navigateIn = b; + sharedConfig.setNavigateIn(b); } /** @@ -270,6 +279,12 @@ public class DegController implements DomainObjectListener { } }); relayoutGraph(); + sharedConfig.setCompactFormat(b); + } + + public void setPopupsVisible(boolean b) { + view.setPopupsVisible(b); + sharedConfig.setShowPopups(b); } public boolean isCompactFormat() { @@ -330,7 +345,7 @@ public class DegController implements DomainObjectListener { * graph will select that vertex. * @param location the new location for the tool */ - public void locationChanged(ProgramLocation location) { + public void setLocation(ProgramLocation location) { if (!navigateIn) { return; } diff --git a/Ghidra/Features/DataGraph/src/main/java/datagraph/data/graph/panel/DataVertexPanel.java b/Ghidra/Features/DataGraph/src/main/java/datagraph/data/graph/panel/DataVertexPanel.java index fdecb8b583..85d4216a81 100644 --- a/Ghidra/Features/DataGraph/src/main/java/datagraph/data/graph/panel/DataVertexPanel.java +++ b/Ghidra/Features/DataGraph/src/main/java/datagraph/data/graph/panel/DataVertexPanel.java @@ -34,7 +34,10 @@ import docking.action.DockingActionIf; import docking.widgets.trable.GTrable; import docking.widgets.trable.GTrableColumnModel; import ghidra.program.model.address.Address; +import ghidra.program.model.data.DataType; import ghidra.program.model.listing.Data; +import ghidra.util.HTMLUtilities; +import ghidra.util.NumericUtilities; import ghidra.util.datastruct.Range; import resources.Icons; @@ -119,7 +122,34 @@ public class DataVertexPanel extends JPanel { JComponent jComponent = (JComponent) source; 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("
    ").append(col).append("").append(escaped).append("
    "); + 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("
    "); + return sb.toString(); + } + + private String row(Object... cols) { + StringBuilder sb = new StringBuilder(""); + for (Object col : cols) { + String escaped = escapeHtml(col); + sb.append("").append(escaped).append(""); + } + sb.append(""); + return sb.toString(); + } + + private String escapeHtml(Object obj) { + return obj == null ? "" : HTMLUtilities.friendlyEncodeHTML(obj.toString()); } /** diff --git a/Ghidra/Features/ProgramGraph/src/main/java/ghidra/graph/program/ProgramGraphPlugin.java b/Ghidra/Features/ProgramGraph/src/main/java/ghidra/graph/program/ProgramGraphPlugin.java index f1b99a81cf..bc8728c72b 100644 --- a/Ghidra/Features/ProgramGraph/src/main/java/ghidra/graph/program/ProgramGraphPlugin.java +++ b/Ghidra/Features/ProgramGraph/src/main/java/ghidra/graph/program/ProgramGraphPlugin.java @@ -185,54 +185,57 @@ public class ProgramGraphPlugin extends ProgramPlugin private void createActions() { - new ActionBuilder("Graph Block Flow", getName()).menuPath(MENU_GRAPH, "&Block Flow") - .menuGroup(MENU_GRAPH, "A") + new ActionBuilder("Graph Block Flow", getName()) + .menuPath(MENU_GRAPH, "&Block Flow") + .menuGroup("Code Graph", "A") .onAction(c -> graphBlockFlow()) .enabledWhen(this::canGraph) .buildAndInstall(tool); - new ActionBuilder("Graph Code Flow", getName()).menuPath(MENU_GRAPH, "C&ode Flow") - .menuGroup(MENU_GRAPH, "B") + new ActionBuilder("Graph Code Flow", getName()) + .menuPath(MENU_GRAPH, "C&ode Flow") + .menuGroup("Code Graph", "B") .onAction(c -> graphCodeFlow()) .enabledWhen(this::canGraph) .buildAndInstall(tool); new ActionBuilder("Graph Calls Using Default Model", getName()) .menuPath(MENU_GRAPH, "&Calls") - .menuGroup(MENU_GRAPH, "C") + .menuGroup("Code Graph", "C") .onAction(c -> createDefaultCallGraph()) .enabledWhen(this::canGraph) .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"); new ActionBuilder("Graph To/From Data References", getName()) - .menuPath(MENU_GRAPH, "Data", "To/From &References") - .menuGroup(MENU_GRAPH, "Data") + .menuPath(MENU_GRAPH, "Data References", "To/From &References") + .menuGroup("Graph", "Data") .helpLocation(helpLoc) .onAction(c -> graphDataReferences()) .enabledWhen(this::canGraph) .buildAndInstall(tool); new ActionBuilder("Graph To Data References", getName()) - .menuPath(MENU_GRAPH, "Data", "&To References") - .menuGroup(MENU_GRAPH, "Data") + .menuPath(MENU_GRAPH, "Data References", "&To References") + .menuGroup("Graph", "Data") .helpLocation(helpLoc) .onAction(c -> graphToDataReferences()) .enabledWhen(this::canGraph) .buildAndInstall(tool); new ActionBuilder("Graph From Data References", getName()) - .menuPath(MENU_GRAPH, "Data", "&From References") - .menuGroup(MENU_GRAPH, "Data") + .menuPath(MENU_GRAPH, "Data References", "&From References") + .menuGroup("Graph", "Data") .helpLocation(helpLoc) .onAction(c -> graphFromDataReferences()) .enabledWhen(this::canGraph) .buildAndInstall(tool); reuseGraphAction = - new ToggleActionBuilder("Reuse Graph", getName()).menuPath(MENU_GRAPH, "Reuse Graph") + new ToggleActionBuilder("Reuse Graph", getName()) + .menuPath(MENU_GRAPH, "Reuse Graph") .menuGroup("Graph Options") .selected(reuseGraph) .onAction(c -> reuseGraph = reuseGraphAction.isSelected()) @@ -240,7 +243,8 @@ public class ProgramGraphPlugin extends ProgramPlugin .buildAndInstall(tool); appendGraphAction = - new ToggleActionBuilder("Append Graph", getName()).menuPath(MENU_GRAPH, "Append Graph") + new ToggleActionBuilder("Append Graph", getName()) + .menuPath(MENU_GRAPH, "Append Graph") .menuGroup("Graph Options") .selected(false) .onAction(c -> updateAppendAndReuseGraph()) @@ -297,13 +301,12 @@ public class ProgramGraphPlugin extends ProgramPlugin 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) { return new ActionBuilder("Graph Calls using " + blockModelName, getName()) .menuPath(MENU_GRAPH, "Calls Using Model", blockModelName) - .menuGroup(MENU_GRAPH, "C") .helpLocation(helpLoc) .onAction(c -> createCallGraphUsing(blockModelName)) .enabledWhen(this::canGraph) diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/trable/GTrable.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/trable/GTrable.java index e6a71501fc..0279dbb88f 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/trable/GTrable.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/trable/GTrable.java @@ -370,6 +370,10 @@ public class GTrable extends JComponent columnModel.setWidth(width); } + public int getRow(Point p) { + return p.y / rowHeight; + } + private void notifySelectedRowConsumers() { for (Consumer consumer : selectedRowConsumers) { consumer.accept(selectedRow);