getModelData() {
- return processors;
- }
-
- @Override
- public boolean isSortable(int columnIndex) {
- return false; // maybe later when we add more columns
- }
-
- @Override
- public int getColumnCount() {
- return 1;
- }
-
- @Override
- public int getRowCount() {
- return processors.size();
- }
-
- @Override
- public String getColumnName(int column) {
- switch (column) {
- case PROCESSOR_COL:
- return "Processor";
- }
- return null;
- }
-
- @Override
- public Class> getColumnClass(int columnIndex) {
- switch (columnIndex) {
- case PROCESSOR_COL:
- return String.class;
- }
- return Object.class;
- }
-
- }
-
-}
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/runtimeinfo/InstalledProcessorsProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/runtimeinfo/InstalledProcessorsProvider.java
new file mode 100644
index 0000000000..5ff316ca1f
--- /dev/null
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/runtimeinfo/InstalledProcessorsProvider.java
@@ -0,0 +1,85 @@
+/* ###
+ * 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.runtimeinfo;
+
+import java.awt.BorderLayout;
+import java.awt.Dimension;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.swing.*;
+
+import docking.ReusableDialogComponentProvider;
+import ghidra.program.model.lang.LanguageDescription;
+import ghidra.program.model.lang.Processor;
+import ghidra.program.util.DefaultLanguageService;
+
+/**
+ * A dialog that shows the supported platforms (processors, loaders, file systems, etc)
+ */
+class InstalledProcessorsProvider extends ReusableDialogComponentProvider {
+
+ private RuntimeInfoPlugin plugin;
+ private JTabbedPane tabbedPane;
+
+ /**
+ * Creates a new {@link InstalledProcessorsProvider}
+ *
+ * @param plugin The associated {@link RuntimeInfoPlugin}
+ */
+ InstalledProcessorsProvider(RuntimeInfoPlugin plugin) {
+ super("Installed Processors", false, false, true, false);
+ this.plugin = plugin;
+
+ setHelpLocation(plugin.getInstalledProcessorsHelpLocation());
+ addWorkPanel(createWorkPanel());
+ }
+
+ private JComponent createWorkPanel() {
+ tabbedPane = new JTabbedPane();
+
+ addProcessors();
+
+ JPanel mainPanel = new JPanel(new BorderLayout()) {
+ @Override
+ public Dimension getPreferredSize() {
+ return new Dimension(700, 400);
+ }
+ };
+ mainPanel.add(tabbedPane, BorderLayout.CENTER);
+ return mainPanel;
+ }
+
+ /**
+ * Adds a "processors" panel to the tabbed pane.
+ *
+ * The goal of this panel is to display every {@link Processor} that Ghidra discovered and
+ * loaded.
+ */
+ private void addProcessors() {
+ Map map = new HashMap<>();
+ for (LanguageDescription desc : DefaultLanguageService.getLanguageService()
+ .getLanguageDescriptions(true)) {
+ String processor = desc.getProcessor().toString();
+ int count = map.getOrDefault(processor, 0);
+ map.put(processor, count + 1);
+ }
+ String name = "Processors";
+ tabbedPane.add(
+ new MapTablePanel(name, map, "Name", "Variants", 300, false, plugin),
+ name);
+ }
+}
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/runtimeinfo/MapTablePanel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/runtimeinfo/MapTablePanel.java
new file mode 100644
index 0000000000..2375c24eef
--- /dev/null
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/runtimeinfo/MapTablePanel.java
@@ -0,0 +1,151 @@
+/* ###
+ * 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.runtimeinfo;
+
+import java.awt.BorderLayout;
+import java.util.*;
+
+import javax.swing.JPanel;
+
+import docking.widgets.table.*;
+import ghidra.docking.settings.Settings;
+import ghidra.framework.plugintool.Plugin;
+import ghidra.framework.plugintool.ServiceProvider;
+import ghidra.util.Disposable;
+
+/**
+ * A {@link JPanel} that displays a 2-column table created from a {@link Map}
+ *
+ * @param The {@link Map} key type
+ * @param The {@link Map} value type
+ */
+class MapTablePanel extends JPanel implements Disposable {
+
+ private String name;
+ private Map map;
+ private String keyColumnName;
+ private String valueColumnName;
+ private int keyColumnWidth;
+ private boolean showValueColumn;
+ private Plugin plugin;
+ private GFilterTable> table;
+
+ /**
+ * Creates a new {@link MapTablePanel}
+ *
+ * @param name The name of the panel
+ * @param map The {@link Map}
+ * @param keyColumnName The name of the key column
+ * @param valueName The name of the value column
+ * @param keyColumnWidth The width of the key column, in pixels
+ * @param showValueColumn True if the value column should be visible; false if it should be
+ * hidden
+ * @param plugin The {@link Plugin} associated with this {@link MapTablePanel}
+ */
+ MapTablePanel(String name, Map map, String keyColumnName, String valueName,
+ int keyColumnWidth, boolean showValueColumn, Plugin plugin) {
+ this.name = name;
+ this.map = map;
+ this.keyColumnName = keyColumnName;
+ this.valueColumnName = valueName;
+ this.keyColumnWidth = keyColumnWidth;
+ this.showValueColumn = showValueColumn;
+ this.plugin = plugin;
+ this.table = new GFilterTable<>(new MapModel());
+
+ setLayout(new BorderLayout());
+ add(table, BorderLayout.CENTER);
+ }
+
+ @Override
+ public void dispose() {
+ table.dispose();
+ }
+
+ private class MapModel
+ extends GDynamicColumnTableModel, List>> {
+
+ private List> entries;
+
+ public MapModel() {
+ super(plugin.getTool());
+ entries = new ArrayList<>(map.entrySet());
+ }
+
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public List> getModelData() {
+ return entries;
+ }
+
+ @Override
+ protected TableColumnDescriptor> createTableColumnDescriptor() {
+ TableColumnDescriptor> columnDescriptor = new TableColumnDescriptor<>();
+ columnDescriptor.addVisibleColumn(new KeyColumn());
+ if (showValueColumn) {
+ columnDescriptor.addVisibleColumn(new ValueColumn());
+ }
+ else {
+ columnDescriptor.addHiddenColumn(new ValueColumn());
+ }
+ return columnDescriptor;
+ }
+
+ @Override
+ public List> getDataSource() {
+ return entries;
+ }
+
+ private class KeyColumn
+ extends AbstractDynamicTableColumn, K, List>> {
+
+ @Override
+ public String getColumnName() {
+ return keyColumnName;
+ }
+
+ @Override
+ public K getValue(Map.Entry entry, Settings settings, List> data,
+ ServiceProvider services) throws IllegalArgumentException {
+ return entry.getKey();
+ }
+
+ @Override
+ public int getColumnPreferredWidth() {
+ return keyColumnWidth;
+ }
+ }
+
+ private class ValueColumn
+ extends AbstractDynamicTableColumn, V, List>> {
+
+ @Override
+ public String getColumnName() {
+ return valueColumnName;
+ }
+
+ @Override
+ public V getValue(Map.Entry entry, Settings settings, List> data,
+ ServiceProvider services) throws IllegalArgumentException {
+ return entry.getValue();
+ }
+ }
+ }
+}
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/runtimeinfo/MemoryUsagePanel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/runtimeinfo/MemoryUsagePanel.java
new file mode 100644
index 0000000000..b0d0800e32
--- /dev/null
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/runtimeinfo/MemoryUsagePanel.java
@@ -0,0 +1,106 @@
+/* ###
+ * 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.runtimeinfo;
+
+import java.awt.BorderLayout;
+import java.text.DecimalFormat;
+
+import javax.swing.*;
+
+import docking.widgets.label.GDLabel;
+import docking.widgets.label.GLabel;
+import ghidra.util.Disposable;
+import ghidra.util.layout.PairLayout;
+
+/**
+ * A {@link JPanel} that displays live memory usage and provides a button to initiate garbage
+ * collection on-demand
+ */
+class MemoryUsagePanel extends JPanel implements Disposable {
+
+ private static final DecimalFormat DECIMAL_FORMAT = new DecimalFormat();
+
+ private Timer timer;
+
+ /**
+ * Creates a new {@link MemoryUsagePanel}
+ */
+ MemoryUsagePanel() {
+ setLayout(new BorderLayout());
+
+ // Center panel
+ JPanel centerPanel = new JPanel(new PairLayout());
+ centerPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
+ JLabel maxMem = new GDLabel("00000000000", SwingConstants.RIGHT);
+ JLabel totalMem = new GDLabel("00000000000", SwingConstants.RIGHT);
+ JLabel freeMem = new GDLabel("00000000000", SwingConstants.RIGHT);
+ JLabel usedMem = new GDLabel("00000000000", SwingConstants.RIGHT);
+ centerPanel.add(new GLabel("Max Memory:"));
+ centerPanel.add(maxMem);
+ centerPanel.add(new GLabel("Total Memory:"));
+ centerPanel.add(totalMem);
+ centerPanel.add(new GLabel("Free Memory:"));
+ centerPanel.add(freeMem);
+ centerPanel.add(new GLabel("Used Memory:"));
+ centerPanel.add(usedMem);
+ add(centerPanel, BorderLayout.CENTER);
+
+ // Bottom panel
+ JPanel bottomPanel = new JPanel();
+ JButton gcButton = new JButton("Collect Garbage");
+ gcButton.addActionListener(e -> Runtime.getRuntime().gc());
+ bottomPanel.add(gcButton);
+ add(bottomPanel, BorderLayout.SOUTH);
+
+ // Garbage collection refresh timer
+ timer = new Timer(2000, e -> {
+ Runtime runtime = Runtime.getRuntime();
+ maxMem.setText(formatMemoryValue(runtime.maxMemory()));
+ totalMem.setText(formatMemoryValue(runtime.totalMemory()));
+ freeMem.setText(formatMemoryValue(runtime.freeMemory()));
+ usedMem.setText(formatMemoryValue(runtime.totalMemory() - runtime.freeMemory()));
+ });
+ }
+
+ @Override
+ public void dispose() {
+ timer.stop();
+ }
+
+ /**
+ * Should be called when this {@link MemoryUsagePanel} is shown
+ */
+ void shown() {
+ timer.start();
+ }
+
+ /**
+ * Should be called when this {@link MemoryUsagePanel} is hidden
+ */
+ void hidden() {
+ timer.stop();
+ }
+
+ /**
+ * Formats the given raw memory value (in bytes) to a more human-readable string
+ *
+ * @param value A memory value in bytes
+ * @return A more human-readable memory value representation
+ */
+ private String formatMemoryValue(long value) {
+ return DECIMAL_FORMAT.format(value >>> 20) + "MB";
+ }
+}
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/runtimeinfo/RuntimeInfoPlugin.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/runtimeinfo/RuntimeInfoPlugin.java
new file mode 100644
index 0000000000..a4c8e1e558
--- /dev/null
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/runtimeinfo/RuntimeInfoPlugin.java
@@ -0,0 +1,119 @@
+/* ###
+ * 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.runtimeinfo;
+
+import docking.action.builder.ActionBuilder;
+import ghidra.app.plugin.PluginCategoryNames;
+import ghidra.framework.main.ApplicationLevelOnlyPlugin;
+import ghidra.framework.main.UtilityPluginPackage;
+import ghidra.framework.plugintool.*;
+import ghidra.framework.plugintool.util.PluginStatus;
+import ghidra.util.HelpLocation;
+
+//@formatter:off
+@PluginInfo(
+ status = PluginStatus.RELEASED,
+ packageName = UtilityPluginPackage.NAME,
+ category = PluginCategoryNames.SUPPORT,
+ shortDescription = "Runtime Information",
+ description = "Plugin for displaying runtime information"
+)
+//@formatter:on
+public class RuntimeInfoPlugin extends Plugin implements ApplicationLevelOnlyPlugin {
+
+ private InstalledProcessorsProvider installedProcessorsProvider;
+ private RuntimeInfoProvider runtimeInfoProvider;
+
+ /**
+ * Creates a new {@link RuntimeInfoPlugin}
+ *
+ * @param tool The tool
+ */
+ public RuntimeInfoPlugin(PluginTool tool) {
+ super(tool);
+
+ String supportedActionName = "Installed Processors";
+ new ActionBuilder(supportedActionName, getName())
+ .onAction(context -> showInstalledProcessors())
+ .enabled(true)
+ .menuPath("Help", supportedActionName)
+ .menuGroup("YYY") // trying to put this just above the last menu entry
+ .helpLocation(getInstalledProcessorsHelpLocation())
+ .buildAndInstall(tool);
+
+ String runtimeInfoActionName = "Runtime Information";
+ new ActionBuilder(runtimeInfoActionName, getName())
+ .onAction(context -> showRuntimeInfo())
+ .enabled(true)
+ .menuPath("Help", runtimeInfoActionName)
+ .menuGroup("YYY")
+ .helpLocation(getRuntimeInfoHelpLocation())
+ .buildAndInstall(tool);
+ }
+
+ @Override
+ protected void dispose() {
+ super.dispose();
+ if (installedProcessorsProvider != null) {
+ installedProcessorsProvider.dispose();
+ installedProcessorsProvider = null;
+ }
+ if (runtimeInfoProvider != null) {
+ runtimeInfoProvider.dispose();
+ runtimeInfoProvider = null;
+ }
+ }
+
+ /**
+ * Gets this plugin's installed processors {@link HelpLocation}
+ *
+ * @return This plugin's installed processors {@link HelpLocation}
+ */
+ protected HelpLocation getInstalledProcessorsHelpLocation() {
+ return new HelpLocation("RuntimeInfoPlugin", "InstalledProcessors");
+ }
+
+ /**
+ * Gets this plugin's runtime info {@link HelpLocation}
+ *
+ * @return This plugin's runtime info {@link HelpLocation}
+ */
+ protected HelpLocation getRuntimeInfoHelpLocation() {
+ return new HelpLocation("RuntimeInfoPlugin", "RuntimeInfo");
+ }
+
+ /**
+ * Displays the {@link InstalledProcessorsProvider}
+ */
+ private void showInstalledProcessors() {
+ if (installedProcessorsProvider == null) {
+ installedProcessorsProvider = new InstalledProcessorsProvider(this);
+ }
+
+ tool.showDialog(installedProcessorsProvider);
+ }
+
+ /**
+ * Displays the {@link RuntimeInfoProvider}
+ */
+ private void showRuntimeInfo() {
+ if (runtimeInfoProvider == null) {
+ runtimeInfoProvider = new RuntimeInfoProvider(this);
+ }
+
+ tool.showDialog(runtimeInfoProvider);
+ }
+}
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/runtimeinfo/RuntimeInfoProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/runtimeinfo/RuntimeInfoProvider.java
new file mode 100644
index 0000000000..ac63281510
--- /dev/null
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/runtimeinfo/RuntimeInfoProvider.java
@@ -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.app.plugin.runtimeinfo;
+
+import java.awt.*;
+import java.io.File;
+import java.util.*;
+import java.util.stream.Collectors;
+
+import javax.swing.*;
+
+import docking.ReusableDialogComponentProvider;
+import generic.jar.ResourceFile;
+import ghidra.GhidraClassLoader;
+import ghidra.framework.Application;
+import ghidra.util.Disposable;
+import ghidra.util.classfinder.ClassSearcher;
+import ghidra.util.classfinder.ExtensionPoint;
+
+/**
+ * A dialog that shows useful runtime information
+ */
+class RuntimeInfoProvider extends ReusableDialogComponentProvider {
+
+ private RuntimeInfoPlugin plugin;
+ private JTabbedPane tabbedPane;
+ private MemoryUsagePanel memoryUsagePanel;
+
+ /**
+ * Creates a new {@link RuntimeInfoProvider}
+ *
+ * @param plugin The associated {@link RuntimeInfoPlugin}
+ */
+ RuntimeInfoProvider(RuntimeInfoPlugin plugin) {
+ super("Runtime Information", false, false, true, false);
+ this.plugin = plugin;
+
+
+ setHelpLocation(plugin.getRuntimeInfoHelpLocation());
+ addWorkPanel(createWorkPanel());
+ }
+
+ @Override
+ public void dispose() {
+ super.dispose();
+ for (Component c : tabbedPane.getComponents()) {
+ if (c instanceof Disposable d) {
+ d.dispose();
+ }
+ }
+ }
+
+ @Override
+ protected void dialogShown() {
+ memoryUsagePanel.shown();
+ }
+
+ @Override
+ protected void dialogClosed() {
+ memoryUsagePanel.hidden();
+ }
+
+
+ private JComponent createWorkPanel() {
+ tabbedPane = new JTabbedPane();
+
+ addVersionInfoPanel();
+ addMemory();
+ addApplicationLayout();
+ addProperties();
+ addEnvironment();
+ addModules();
+ addExtensionPoints();
+ addClasspath();
+ addExtensionsClasspath();
+
+ JPanel mainPanel = new JPanel(new BorderLayout()) {
+ @Override
+ public Dimension getPreferredSize() {
+ return new Dimension(700, 400);
+ }
+ };
+ mainPanel.add(tabbedPane, BorderLayout.CENTER);
+ return mainPanel;
+ }
+
+ /**
+ * Adds a "version" panel to the tabbed pane.
+ *
+ * The goal of this panel is to display version information that would be useful to include in
+ * a bug report, and provide a button that copies this information to the system clipboard.
+ */
+ private void addVersionInfoPanel() {
+ tabbedPane.add(new VersionInfoPanel(), "Version");
+ }
+
+ /**
+ * Adds a "memory" panel to the tabbed pane.
+ *
+ * The goal of this panel is to display live memory usage, and provide a button to initiate
+ * garbage collection on-demand.
+ */
+ private void addMemory() {
+ memoryUsagePanel = new MemoryUsagePanel();
+ tabbedPane.add(memoryUsagePanel, "Memory");
+ }
+
+ /**
+ * Adds an "application layout" panel to the tabbed pane.
+ *
+ * The goal of this panel is to display information information about the application such as
+ * what directories it is using on disk, what its PID is, etc.
+ */
+ private void addApplicationLayout() {
+ Map map = new HashMap<>();
+ map.put("PID", ProcessHandle.current().pid() + "");
+ map.put("Installation Directory", Application.getInstallationDirectory().getAbsolutePath());
+ map.put("Settings Directory", Application.getUserSettingsDirectory().getPath());
+ map.put("Cache Directory", Application.getUserCacheDirectory().getPath());
+ map.put("Temp Directory", Application.getUserTempDirectory().getPath());
+ String name = "Application Layout";
+ tabbedPane.add(
+ new MapTablePanel(name, map, "Name", "Path", 200, true, plugin), name);
+ }
+
+ /**
+ * Adds a "properties" panel to the tabbed pane.
+ *
+ * The goal of this panel is to display every defined system property in a table.
+ */
+ private void addProperties() {
+ Properties properties = System.getProperties();
+ Map map = new HashMap<>();
+ for (Object key : properties.keySet()) {
+ map.put(key.toString(), properties.getOrDefault(key, "").toString());
+ }
+ String name = "Properties";
+ tabbedPane.add(
+ new MapTablePanel(name, map, "Name", "Value", 400, true, plugin), name);
+ }
+
+ /**
+ * Adds an "environment" panel to the tabbed pane.
+ *
+ * The goal of this panel is to display every defined environment variable in a table.
+ */
+ private void addEnvironment() {
+ Map map = System.getenv();
+ String name = "Environment";
+ tabbedPane.add(
+ new MapTablePanel(name, map, "Name", "Value", 400, true, plugin), name);
+ }
+
+ /**
+ * Adds a "modules" panel to the tabbed pane.
+ *
+ * The goal of this panel is to display every module that Ghidra discovered and loaded.
+ */
+ private void addModules() {
+ Map map = Application.getApplicationLayout()
+ .getModules()
+ .entrySet()
+ .stream()
+ .collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().getModuleRoot()));
+ String name = "Modules";
+ tabbedPane.add(
+ new MapTablePanel(name, map, "Name", "Path", 400, true, plugin),
+ name);
+ }
+
+ /**
+ * Adds an "extension points" panel to the tabbed pane.
+ *
+ * The goal of this panel is to display every {@link ExtensionPoint} that Ghidra discovered and
+ * loaded.
+ */
+ private void addExtensionPoints() {
+ Map map = ClassSearcher.getClasses(ExtensionPoint.class)
+ .stream()
+ .collect(Collectors.toMap(e -> e.getName(),
+ e -> ClassSearcher.getExtensionPointName(e.getName())));
+ String name = "Extension Points";
+ tabbedPane.add(new MapTablePanel(name, map, "Name", "Extension Point", 400,
+ true, plugin), name);
+ }
+
+ /**
+ * Adds a "classpath" panel to the tabbed pane.
+ *
+ * The goal of this panel is to display Ghidra's current classpath.
+ */
+ private void addClasspath() {
+ Map map = getClasspathMap(GhidraClassLoader.CP);
+ String name = "Classpath";
+ tabbedPane.add(
+ new MapTablePanel(name, map, "Index", "Path", 40, true, plugin), name);
+ }
+
+ /**
+ * Adds an "extensions classpath" panel to the tabbed pane.
+ *
+ * The goal of this panel is to display Ghidra's current extension classpath.
+ */
+ private void addExtensionsClasspath() {
+ Map map = getClasspathMap(GhidraClassLoader.CP_EXT);
+ String name = "Extensions Classpath";
+ tabbedPane.add(
+ new MapTablePanel(name, map, "Index", "Path", 40, true, plugin), name);
+ }
+
+ /**
+ * Returns a {@link Map} of classpath entries, where the key is a 0-based integer index of each
+ * classpath entry
+ *
+ * @param propertyName The classpath system property name
+ * @return A {@link Map} of classpath entries, where the key is a 0-based integer index of each
+ * classpath entry
+ */
+ private Map getClasspathMap(String propertyName) {
+ Map map = new HashMap<>();
+ StringTokenizer st =
+ new StringTokenizer(System.getProperty(propertyName, ""), File.pathSeparator);
+ int i = 0;
+ while (st.hasMoreTokens()) {
+ map.put(i++, st.nextToken());
+ }
+ return map;
+ }
+}
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/runtimeinfo/VersionInfoPanel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/runtimeinfo/VersionInfoPanel.java
new file mode 100644
index 0000000000..252db04046
--- /dev/null
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/runtimeinfo/VersionInfoPanel.java
@@ -0,0 +1,129 @@
+/* ###
+ * 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.runtimeinfo;
+
+import java.awt.BorderLayout;
+import java.awt.datatransfer.Clipboard;
+import java.awt.datatransfer.StringSelection;
+import java.io.*;
+import java.util.*;
+
+import javax.swing.*;
+
+import docking.dnd.GClipboard;
+import ghidra.framework.*;
+import ghidra.util.SystemUtilities;
+
+/**
+ * A {@link JPanel} that displays version information that would be useful to include in a bug
+ * report, and provide a button that copies this information to the system clipboard
+ */
+class VersionInfoPanel extends JPanel {
+
+ /**
+ * Creates a new {@link VersionInfoPanel}
+ */
+ VersionInfoPanel() {
+ setLayout(new BorderLayout());
+
+ JTextArea textArea = new JTextArea(gatherVersionInfo());
+ add(textArea, BorderLayout.CENTER);
+
+ JPanel bottomPanel = new JPanel();
+ JButton copyButton = new JButton("Copy");
+ copyButton.addActionListener(e -> {
+ Clipboard clipboard = GClipboard.getSystemClipboard();
+ clipboard.setContents(new StringSelection(textArea.getText()), null);
+ });
+ bottomPanel.add(copyButton);
+ add(bottomPanel, BorderLayout.SOUTH);
+
+ }
+
+ /**
+ * Gathers version information
+ *
+ * @return The version information text
+ */
+ private String gatherVersionInfo() {
+ final String def = "???";
+ List lines = new ArrayList<>();
+
+ addApplicationInfo(lines, def);
+ addOperatingSystemInfo(lines, def);
+ addJavaInfo(lines, def);
+
+ return String.join("\n", lines);
+ }
+
+ /**
+ * Adds Ghidra application information to the version information
+ *
+ * @param lines A {@link List} of lines to add to
+ * @param def A default value to use if a piece of information cannot be found
+ */
+ private void addApplicationInfo(List lines, String def) {
+ ApplicationProperties props = Application.getApplicationLayout().getApplicationProperties();
+ lines.add("Ghidra Version: " + props.getApplicationVersion());
+ lines.add("Ghidra Release: " + props.getApplicationReleaseName());
+ lines.add("Ghidra Build Date: " + props.getApplicationBuildDate());
+ lines.add("Ghidra Revision: " +
+ props.getProperty(ApplicationProperties.REVISION_PROPERTY_PREFIX + "ghidra", def));
+ lines.add("Ghidra Development Mode: " + SystemUtilities.isInDevelopmentMode());
+ }
+
+ /**
+ * Adds operating system information to the version information
+ *
+ * @param lines A {@link List} of lines to add to
+ * @param def A default value to use if a piece of information cannot be found
+ */
+ private void addOperatingSystemInfo(List lines, String def) {
+ lines.add("OS Name: " + System.getProperty("os.name", def));
+ lines.add("OS Arch: " + System.getProperty("os.arch", def));
+ lines.add("OS Version: " + System.getProperty("os.version", def));
+ if (OperatingSystem.CURRENT_OPERATING_SYSTEM.equals(OperatingSystem.LINUX)) {
+ String prettyName = def;
+ File osReleaseFile = new File("/etc/os-release");
+ if (!osReleaseFile.isFile()) {
+ osReleaseFile = new File("/usr/lib/os-release");
+ }
+ try (BufferedReader reader = new BufferedReader(new FileReader(osReleaseFile))) {
+ Properties props = new Properties();
+ props.load(reader);
+ prettyName = props.getProperty("PRETTY_NAME", def);
+ if (prettyName.startsWith("\"") && prettyName.endsWith("\"")) {
+ prettyName = prettyName.substring(1, prettyName.length() - 1);
+ }
+ }
+ catch (IOException e) {
+ // That's ok, pretty name is optional
+ }
+ lines.add("OS Pretty Name: " + prettyName);
+ }
+ }
+
+ /**
+ * Adds Java/JVM information to the version information
+ *
+ * @param lines A {@link List} of lines to add to
+ * @param def A default value to use if a piece of information cannot be found
+ */
+ private void addJavaInfo(List lines, String def) {
+ lines.add("Java Vendor: " + System.getProperty("java.vendor", def));
+ lines.add("Java Version: " + System.getProperty("java.version", def));
+ }
+}
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/plugins/fsbrowser/FileSystemBrowserPlugin.java b/Ghidra/Features/Base/src/main/java/ghidra/plugins/fsbrowser/FileSystemBrowserPlugin.java
index 777ace29cd..e927ae8df7 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/plugins/fsbrowser/FileSystemBrowserPlugin.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/plugins/fsbrowser/FileSystemBrowserPlugin.java
@@ -27,14 +27,12 @@ import javax.swing.KeyStroke;
import docking.action.DockingAction;
import docking.action.builder.ActionBuilder;
import docking.tool.ToolConstants;
-import docking.widgets.dialogs.MultiLineMessageDialog;
import docking.widgets.filechooser.GhidraFileChooser;
import docking.widgets.filechooser.GhidraFileChooserMode;
import ghidra.app.CorePluginPackage;
import ghidra.app.events.ProgramActivatedPluginEvent;
import ghidra.app.plugin.PluginCategoryNames;
import ghidra.app.services.*;
-import ghidra.app.util.opinion.LoaderService;
import ghidra.formats.gfilesystem.*;
import ghidra.framework.main.ApplicationLevelPlugin;
import ghidra.framework.main.FrontEndService;
@@ -108,14 +106,6 @@ public class FileSystemBrowserPlugin extends Plugin
.keyBinding(KeyStroke.getKeyStroke(KeyEvent.VK_I, InputEvent.CTRL_DOWN_MASK))
.onAction(ac -> doOpenFileSystem())
.buildAndInstall(tool);
- showFileSystemImplsAction =
- new ActionBuilder("Display Supported File Systems and Loaders", this.getName())
- .description("Display Supported File Systems and Loaders")
- .enabledWhen(ac -> true)
- .menuPath(ToolConstants.MENU_HELP, "List File Systems")
- .menuGroup("AAAZ") // this "AAAZ" is from ProcessorListPlugin
- .onAction(ac -> showSupportedFileSystems())
- .buildAndInstall(tool);
}
@Override
@@ -326,29 +316,4 @@ public class FileSystemBrowserPlugin extends Plugin
}
return provider;
}
-
- /**
- * Shows a list of supported file system types and loaders.
- */
- private void showSupportedFileSystems() {
- StringBuilder sb = new StringBuilder();
-
- sb.append(
- "Supported File Systems | Supported Loaders |
\n");
- sb.append("");
- for (String fileSystemName : fsService().getAllFilesystemNames()) {
- sb.append("- " + fileSystemName + "\n");
- }
-
- sb.append("
| ");
- for (String loaderName : LoaderService.getAllLoaderNames()) {
- sb.append("- " + loaderName + "\n");
- }
- sb.append("
|
");
-
- MultiLineMessageDialog.showModalMessageDialog(getTool().getActiveWindow(),
- "Supported File Systems and Loaders", "", sb.toString(),
- MultiLineMessageDialog.INFORMATION_MESSAGE);
- }
-
}
diff --git a/Ghidra/Framework/Generic/src/main/java/ghidra/util/classfinder/ClassSearcher.java b/Ghidra/Framework/Generic/src/main/java/ghidra/util/classfinder/ClassSearcher.java
index 5f2c12a343..428b7fd272 100644
--- a/Ghidra/Framework/Generic/src/main/java/ghidra/util/classfinder/ClassSearcher.java
+++ b/Ghidra/Framework/Generic/src/main/java/ghidra/util/classfinder/ClassSearcher.java
@@ -21,6 +21,7 @@ import java.lang.reflect.Constructor;
import java.nio.file.*;
import java.util.*;
import java.util.function.Predicate;
+import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
@@ -278,6 +279,26 @@ public class ClassSearcher {
log.info(finishedMessage);
}
+ /**
+ * Gets the given class's extension point name
+ *
+ * @param className The name of the potential extension point class
+ * @return The given class's extension point name, or null if it is not an extension point
+ */
+ public static String getExtensionPointName(String className) {
+ if (className.indexOf("Test$") > 0 || className.endsWith("Test")) {
+ return null;
+ }
+ int packageIndex = className.lastIndexOf('.');
+ int innerClassIndex = className.lastIndexOf('$');
+ int maximumIndex = StrictMath.max(packageIndex, innerClassIndex);
+ if (maximumIndex > 0) {
+ className = className.substring(maximumIndex + 1);
+ }
+ Matcher m = extensionPointSuffixPattern.matcher(className);
+ return m.find() && m.groupCount() == 1 ? m.group(1) : null;
+ }
+
private static List gatherSearchPaths() {
//
@@ -394,16 +415,7 @@ public class ClassSearcher {
}
static boolean isExtensionPointName(String name) {
- if (name.indexOf("Test$") > 0 || name.endsWith("Test")) {
- return false;
- }
- int packageIndex = name.lastIndexOf('.');
- int innerClassIndex = name.lastIndexOf('$');
- int maximumIndex = StrictMath.max(packageIndex, innerClassIndex);
- if (maximumIndex > 0) {
- name = name.substring(maximumIndex + 1);
- }
- return extensionPointSuffixPattern.matcher(name).matches();
+ return getExtensionPointName(name) != null;
}
private static void fireClassListChanged() {
diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/app/plugin/debug/MemoryUsagePlugin.java b/Ghidra/Framework/Project/src/main/java/ghidra/app/plugin/debug/MemoryUsagePlugin.java
deleted file mode 100644
index b60b230965..0000000000
--- a/Ghidra/Framework/Project/src/main/java/ghidra/app/plugin/debug/MemoryUsagePlugin.java
+++ /dev/null
@@ -1,88 +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.debug;
-
-import docking.ActionContext;
-import docking.DialogComponentProvider;
-import docking.action.DockingAction;
-import docking.action.MenuData;
-import ghidra.app.plugin.PluginCategoryNames;
-import ghidra.framework.main.ApplicationLevelOnlyPlugin;
-import ghidra.framework.main.UtilityPluginPackage;
-import ghidra.framework.plugintool.*;
-import ghidra.framework.plugintool.util.PluginStatus;
-import ghidra.util.HelpLocation;
-
-//@formatter:off
-@PluginInfo(
- status = PluginStatus.RELEASED,
- packageName = UtilityPluginPackage.NAME,
- category = PluginCategoryNames.SUPPORT,
- shortDescription = "VM Memory Display",
- description = "Plugin for displaying the VM memory information."
-)
-//@formatter:on
-public class MemoryUsagePlugin extends Plugin implements ApplicationLevelOnlyPlugin {
- private DialogComponentProvider dialog;
-
- public MemoryUsagePlugin(PluginTool tool) {
-
- super(tool);
-
- setupActions();
- }
-
- @Override
- protected void dispose() {
- super.dispose();
-
- if (dialog != null) {
- dialog.dispose();
- }
- }
-
- private void setupActions() {
- DockingAction action;
-
- // add menu action for Hello->Program
- action = new DockingAction("Show VM memory", getName()) {
- @Override
- public void actionPerformed(ActionContext context) {
- showMemory();
- }
- };
-
- action.setEnabled(true);
- action.setHelpLocation(new HelpLocation("FrontEndPlugin", "ShowMemoryUsage"));
- String group = "YYY"; // trying to put this just above the last menu entry
- action.setMenuBarData(new MenuData(new String[] { "Help", "Show VM Memory" }, group));
- tool.addAction(action);
-
- }
-
- void clearDialog() {
- dialog = null;
- }
-
- public void showMemory() {
- if (dialog == null) {
- dialog = new ShowMemoryDialog(this);
- }
- else {
- dialog.toFront();
- }
- }
-}
diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/app/plugin/debug/ShowMemoryDialog.java b/Ghidra/Framework/Project/src/main/java/ghidra/app/plugin/debug/ShowMemoryDialog.java
deleted file mode 100644
index 8d48f7c2bd..0000000000
--- a/Ghidra/Framework/Project/src/main/java/ghidra/app/plugin/debug/ShowMemoryDialog.java
+++ /dev/null
@@ -1,96 +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.debug;
-
-import java.text.DecimalFormat;
-
-import javax.swing.*;
-
-import docking.ReusableDialogComponentProvider;
-import docking.widgets.label.GDLabel;
-import docking.widgets.label.GLabel;
-import ghidra.util.layout.PairLayout;
-
-class ShowMemoryDialog extends ReusableDialogComponentProvider {
- private MemoryUsagePlugin plugin;
- private JLabel maxMem;
- private JLabel totalMem;
- private JLabel freeMem;
- private JLabel usedMem;
- private Timer timer;
-
- ShowMemoryDialog(MemoryUsagePlugin plugin) {
- super("VM Memory Usage", false, false, true, false);
- this.plugin = plugin;
- addOKButton();
- setOkButtonText("GC");
- addWorkPanel(createWorkPanel());
- plugin.getTool().showDialog(this);
- final DecimalFormat df = new DecimalFormat();
- timer = new Timer(2000, e -> {
- Runtime runtime = Runtime.getRuntime();
- maxMem.setText(df.format(runtime.maxMemory() / 1000) + "K");
- totalMem.setText(df.format(runtime.totalMemory() / 1000) + "K");
- freeMem.setText(df.format(runtime.freeMemory() / 1000) + "K");
- usedMem.setText(
- df.format((runtime.totalMemory() - runtime.freeMemory()) / 1000) + "K");
- });
- timer.start();
- }
-
- boolean isInitialized() {
- String text = maxMem.getText();
- for (int i = 0; i < text.length(); i++) {
- char c = text.charAt(i);
- if ('0' != c) {
- return true;
- }
- }
- return false;
- }
-
- @Override
- protected void cancelCallback() {
- timer.stop();
- plugin.clearDialog();
- super.cancelCallback();
- }
-
- @Override
- protected void okCallback() {
- Runtime.getRuntime().gc();
- }
-
- private JComponent createWorkPanel() {
- JPanel panel = new JPanel(new PairLayout());
- panel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
- maxMem = new GDLabel("00000000000", SwingConstants.RIGHT);
- totalMem = new GDLabel("00000000000", SwingConstants.RIGHT);
- freeMem = new GDLabel("00000000000", SwingConstants.RIGHT);
- usedMem = new GDLabel("00000000000", SwingConstants.RIGHT);
-
- panel.add(new GLabel("Max Memory:"));
- panel.add(maxMem);
- panel.add(new GLabel("Total Memory:"));
- panel.add(totalMem);
- panel.add(new GLabel("Free Memory:"));
- panel.add(freeMem);
- panel.add(new GLabel("Used Memory:"));
- panel.add(usedMem);
-
- return panel;
- }
-}